Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 
 
 

7556 строки
190 KiB

  1. /*
  2. * Slim v3.1.1 - Image Cropping Made Easy
  3. * Copyright (c) 2016 Rik Schennink - http://slim.pqina.nl
  4. */
  5. (function(angular){
  6. // Slim Lib
  7. var Slim = (function() {
  8. // custom event polyfill for IE10
  9. (function() {
  10. function CustomEvent(event, params) {
  11. params = params || { bubbles: false, cancelable: false, detail: undefined };
  12. var evt = document.createEvent('CustomEvent');
  13. evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
  14. return evt;
  15. }
  16. CustomEvent.prototype = window.CustomEvent.prototype;
  17. window.CustomEvent = CustomEvent;
  18. })();
  19. /*
  20. * JavaScript Load Image
  21. * https://github.com/blueimp/JavaScript-Load-Image
  22. *
  23. * Copyright 2011, Sebastian Tschan
  24. * https://blueimp.net
  25. *
  26. * Licensed under the MIT license:
  27. * http://www.opensource.org/licenses/MIT
  28. */
  29. /*global define, module, window, document, URL, webkitURL, FileReader */
  30. // Loads an image for a given File object.
  31. // Invokes the callback with an img or optional canvas
  32. // element (if supported by the browser) as parameter:
  33. var loadImage = function (file, callback, options) {
  34. var img = document.createElement('img')
  35. var url
  36. var oUrl
  37. img.onerror = callback
  38. img.onload = function () {
  39. if (oUrl && !(options && options.noRevoke)) {
  40. loadImage.revokeObjectURL(oUrl)
  41. }
  42. if (callback) {
  43. callback(loadImage.scale(img, options))
  44. }
  45. }
  46. if (loadImage.isInstanceOf('Blob', file) ||
  47. // Files are also Blob instances, but some browsers
  48. // (Firefox 3.6) support the File API but not Blobs:
  49. loadImage.isInstanceOf('File', file)) {
  50. url = oUrl = loadImage.createObjectURL(file)
  51. // Store the file type for resize processing:
  52. img._type = file.type
  53. } else if (typeof file === 'string') {
  54. url = file
  55. if (options && options.crossOrigin) {
  56. img.crossOrigin = options.crossOrigin
  57. }
  58. } else {
  59. return false
  60. }
  61. if (url) {
  62. img.src = url
  63. return img
  64. }
  65. return loadImage.readFile(file, function (e) {
  66. var target = e.target
  67. if (target && target.result) {
  68. img.src = target.result
  69. } else {
  70. if (callback) {
  71. callback(e)
  72. }
  73. }
  74. })
  75. }
  76. // The check for URL.revokeObjectURL fixes an issue with Opera 12,
  77. // which provides URL.createObjectURL but doesn't properly implement it:
  78. var urlAPI = (window.createObjectURL && window) ||
  79. (window.URL && URL.revokeObjectURL && URL) ||
  80. (window.webkitURL && webkitURL)
  81. loadImage.isInstanceOf = function (type, obj) {
  82. // Cross-frame instanceof check
  83. return Object.prototype.toString.call(obj) === '[object ' + type + ']'
  84. }
  85. // Transform image coordinates, allows to override e.g.
  86. // the canvas orientation based on the orientation option,
  87. // gets canvas, options passed as arguments:
  88. loadImage.transformCoordinates = function () {
  89. return
  90. }
  91. // Returns transformed options, allows to override e.g.
  92. // maxWidth, maxHeight and crop options based on the aspectRatio.
  93. // gets img, options passed as arguments:
  94. loadImage.getTransformedOptions = function (img, options) {
  95. var aspectRatio = options.aspectRatio
  96. var newOptions
  97. var i
  98. var width
  99. var height
  100. if (!aspectRatio) {
  101. return options
  102. }
  103. newOptions = {}
  104. for (i in options) {
  105. if (options.hasOwnProperty(i)) {
  106. newOptions[i] = options[i]
  107. }
  108. }
  109. newOptions.crop = true
  110. width = img.naturalWidth || img.width
  111. height = img.naturalHeight || img.height
  112. if (width / height > aspectRatio) {
  113. newOptions.maxWidth = height * aspectRatio
  114. newOptions.maxHeight = height
  115. } else {
  116. newOptions.maxWidth = width
  117. newOptions.maxHeight = width / aspectRatio
  118. }
  119. return newOptions
  120. }
  121. // Canvas render method, allows to implement a different rendering algorithm:
  122. loadImage.renderImageToCanvas = function (
  123. canvas,
  124. img,
  125. sourceX,
  126. sourceY,
  127. sourceWidth,
  128. sourceHeight,
  129. destX,
  130. destY,
  131. destWidth,
  132. destHeight
  133. ) {
  134. canvas.getContext('2d').drawImage(
  135. img,
  136. sourceX,
  137. sourceY,
  138. sourceWidth,
  139. sourceHeight,
  140. destX,
  141. destY,
  142. destWidth,
  143. destHeight
  144. )
  145. return canvas
  146. }
  147. // This method is used to determine if the target image
  148. // should be a canvas element:
  149. loadImage.hasCanvasOption = function (options) {
  150. return options.canvas || options.crop || !!options.aspectRatio
  151. }
  152. // Scales and/or crops the given image (img or canvas HTML element)
  153. // using the given options.
  154. // Returns a canvas object if the browser supports canvas
  155. // and the hasCanvasOption method returns true or a canvas
  156. // object is passed as image, else the scaled image:
  157. loadImage.scale = function (img, options) {
  158. options = options || {}
  159. var canvas = document.createElement('canvas')
  160. var useCanvas = img.getContext ||
  161. (loadImage.hasCanvasOption(options) && canvas.getContext)
  162. var width = img.naturalWidth || img.width
  163. var height = img.naturalHeight || img.height
  164. var destWidth = width
  165. var destHeight = height
  166. var maxWidth
  167. var maxHeight
  168. var minWidth
  169. var minHeight
  170. var sourceWidth
  171. var sourceHeight
  172. var sourceX
  173. var sourceY
  174. var pixelRatio
  175. var downsamplingRatio
  176. var tmp
  177. function scaleUp () {
  178. var scale = Math.max(
  179. (minWidth || destWidth) / destWidth,
  180. (minHeight || destHeight) / destHeight
  181. )
  182. if (scale > 1) {
  183. destWidth *= scale
  184. destHeight *= scale
  185. }
  186. }
  187. function scaleDown () {
  188. var scale = Math.min(
  189. (maxWidth || destWidth) / destWidth,
  190. (maxHeight || destHeight) / destHeight
  191. )
  192. if (scale < 1) {
  193. destWidth *= scale
  194. destHeight *= scale
  195. }
  196. }
  197. if (useCanvas) {
  198. options = loadImage.getTransformedOptions(img, options)
  199. sourceX = options.left || 0
  200. sourceY = options.top || 0
  201. if (options.sourceWidth) {
  202. sourceWidth = options.sourceWidth
  203. if (options.right !== undefined && options.left === undefined) {
  204. sourceX = width - sourceWidth - options.right
  205. }
  206. } else {
  207. sourceWidth = width - sourceX - (options.right || 0)
  208. }
  209. if (options.sourceHeight) {
  210. sourceHeight = options.sourceHeight
  211. if (options.bottom !== undefined && options.top === undefined) {
  212. sourceY = height - sourceHeight - options.bottom
  213. }
  214. } else {
  215. sourceHeight = height - sourceY - (options.bottom || 0)
  216. }
  217. destWidth = sourceWidth
  218. destHeight = sourceHeight
  219. }
  220. maxWidth = options.maxWidth
  221. maxHeight = options.maxHeight
  222. minWidth = options.minWidth
  223. minHeight = options.minHeight
  224. if (useCanvas && maxWidth && maxHeight && options.crop) {
  225. destWidth = maxWidth
  226. destHeight = maxHeight
  227. tmp = sourceWidth / sourceHeight - maxWidth / maxHeight
  228. if (tmp < 0) {
  229. sourceHeight = maxHeight * sourceWidth / maxWidth
  230. if (options.top === undefined && options.bottom === undefined) {
  231. sourceY = (height - sourceHeight) / 2
  232. }
  233. } else if (tmp > 0) {
  234. sourceWidth = maxWidth * sourceHeight / maxHeight
  235. if (options.left === undefined && options.right === undefined) {
  236. sourceX = (width - sourceWidth) / 2
  237. }
  238. }
  239. } else {
  240. if (options.contain || options.cover) {
  241. minWidth = maxWidth = maxWidth || minWidth
  242. minHeight = maxHeight = maxHeight || minHeight
  243. }
  244. if (options.cover) {
  245. scaleDown()
  246. scaleUp()
  247. } else {
  248. scaleUp()
  249. scaleDown()
  250. }
  251. }
  252. if (useCanvas) {
  253. pixelRatio = options.pixelRatio
  254. if (pixelRatio > 1) {
  255. canvas.style.width = destWidth + 'px'
  256. canvas.style.height = destHeight + 'px'
  257. destWidth *= pixelRatio
  258. destHeight *= pixelRatio
  259. canvas.getContext('2d').scale(pixelRatio, pixelRatio)
  260. }
  261. downsamplingRatio = options.downsamplingRatio
  262. if (downsamplingRatio > 0 && downsamplingRatio < 1 &&
  263. destWidth < sourceWidth && destHeight < sourceHeight) {
  264. while (sourceWidth * downsamplingRatio > destWidth) {
  265. canvas.width = sourceWidth * downsamplingRatio
  266. canvas.height = sourceHeight * downsamplingRatio
  267. loadImage.renderImageToCanvas(
  268. canvas,
  269. img,
  270. sourceX,
  271. sourceY,
  272. sourceWidth,
  273. sourceHeight,
  274. 0,
  275. 0,
  276. canvas.width,
  277. canvas.height
  278. )
  279. sourceWidth = canvas.width
  280. sourceHeight = canvas.height
  281. img = document.createElement('canvas')
  282. img.width = sourceWidth
  283. img.height = sourceHeight
  284. loadImage.renderImageToCanvas(
  285. img,
  286. canvas,
  287. 0,
  288. 0,
  289. sourceWidth,
  290. sourceHeight,
  291. 0,
  292. 0,
  293. sourceWidth,
  294. sourceHeight
  295. )
  296. }
  297. }
  298. canvas.width = destWidth
  299. canvas.height = destHeight
  300. loadImage.transformCoordinates(
  301. canvas,
  302. options
  303. )
  304. return loadImage.renderImageToCanvas(
  305. canvas,
  306. img,
  307. sourceX,
  308. sourceY,
  309. sourceWidth,
  310. sourceHeight,
  311. 0,
  312. 0,
  313. destWidth,
  314. destHeight
  315. )
  316. }
  317. img.width = destWidth
  318. img.height = destHeight
  319. return img
  320. }
  321. loadImage.createObjectURL = function (file) {
  322. return urlAPI ? urlAPI.createObjectURL(file) : false
  323. }
  324. loadImage.revokeObjectURL = function (url) {
  325. return urlAPI ? urlAPI.revokeObjectURL(url) : false
  326. }
  327. // Loads a given File object via FileReader interface,
  328. // invokes the callback with the event object (load or error).
  329. // The result can be read via event.target.result:
  330. loadImage.readFile = function (file, callback, method) {
  331. if (window.FileReader) {
  332. var fileReader = new FileReader()
  333. fileReader.onload = fileReader.onerror = callback
  334. method = method || 'readAsDataURL'
  335. if (fileReader[method]) {
  336. fileReader[method](file)
  337. return fileReader
  338. }
  339. }
  340. return false
  341. }
  342. var originalHasCanvasOption = loadImage.hasCanvasOption
  343. var originalTransformCoordinates = loadImage.transformCoordinates
  344. var originalGetTransformedOptions = loadImage.getTransformedOptions
  345. // This method is used to determine if the target image
  346. // should be a canvas element:
  347. loadImage.hasCanvasOption = function (options) {
  348. return !!options.orientation ||
  349. originalHasCanvasOption.call(loadImage, options)
  350. }
  351. // Transform image orientation based on
  352. // the given EXIF orientation option:
  353. loadImage.transformCoordinates = function (canvas, options) {
  354. originalTransformCoordinates.call(loadImage, canvas, options)
  355. var ctx = canvas.getContext('2d')
  356. var width = canvas.width
  357. var height = canvas.height
  358. var styleWidth = canvas.style.width
  359. var styleHeight = canvas.style.height
  360. var orientation = options.orientation
  361. if (!orientation || orientation > 8) {
  362. return
  363. }
  364. if (orientation > 4) {
  365. canvas.width = height
  366. canvas.height = width
  367. canvas.style.width = styleHeight
  368. canvas.style.height = styleWidth
  369. }
  370. switch (orientation) {
  371. case 2:
  372. // horizontal flip
  373. ctx.translate(width, 0)
  374. ctx.scale(-1, 1)
  375. break
  376. case 3:
  377. // 180° rotate left
  378. ctx.translate(width, height)
  379. ctx.rotate(Math.PI)
  380. break
  381. case 4:
  382. // vertical flip
  383. ctx.translate(0, height)
  384. ctx.scale(1, -1)
  385. break
  386. case 5:
  387. // vertical flip + 90 rotate right
  388. ctx.rotate(0.5 * Math.PI)
  389. ctx.scale(1, -1)
  390. break
  391. case 6:
  392. // 90° rotate right
  393. ctx.rotate(0.5 * Math.PI)
  394. ctx.translate(0, -height)
  395. break
  396. case 7:
  397. // horizontal flip + 90 rotate right
  398. ctx.rotate(0.5 * Math.PI)
  399. ctx.translate(width, -height)
  400. ctx.scale(-1, 1)
  401. break
  402. case 8:
  403. // 90° rotate left
  404. ctx.rotate(-0.5 * Math.PI)
  405. ctx.translate(-width, 0)
  406. break
  407. }
  408. }
  409. // Transforms coordinate and dimension options
  410. // based on the given orientation option:
  411. loadImage.getTransformedOptions = function (img, opts) {
  412. var options = originalGetTransformedOptions.call(loadImage, img, opts)
  413. var orientation = options.orientation
  414. var newOptions
  415. var i
  416. if (!orientation || orientation > 8 || orientation === 1) {
  417. return options
  418. }
  419. newOptions = {}
  420. for (i in options) {
  421. if (options.hasOwnProperty(i)) {
  422. newOptions[i] = options[i]
  423. }
  424. }
  425. switch (options.orientation) {
  426. case 2:
  427. // horizontal flip
  428. newOptions.left = options.right
  429. newOptions.right = options.left
  430. break
  431. case 3:
  432. // 180° rotate left
  433. newOptions.left = options.right
  434. newOptions.top = options.bottom
  435. newOptions.right = options.left
  436. newOptions.bottom = options.top
  437. break
  438. case 4:
  439. // vertical flip
  440. newOptions.top = options.bottom
  441. newOptions.bottom = options.top
  442. break
  443. case 5:
  444. // vertical flip + 90 rotate right
  445. newOptions.left = options.top
  446. newOptions.top = options.left
  447. newOptions.right = options.bottom
  448. newOptions.bottom = options.right
  449. break
  450. case 6:
  451. // 90° rotate right
  452. newOptions.left = options.top
  453. newOptions.top = options.right
  454. newOptions.right = options.bottom
  455. newOptions.bottom = options.left
  456. break
  457. case 7:
  458. // horizontal flip + 90 rotate right
  459. newOptions.left = options.bottom
  460. newOptions.top = options.right
  461. newOptions.right = options.top
  462. newOptions.bottom = options.left
  463. break
  464. case 8:
  465. // 90° rotate left
  466. newOptions.left = options.bottom
  467. newOptions.top = options.left
  468. newOptions.right = options.top
  469. newOptions.bottom = options.right
  470. break
  471. }
  472. if (options.orientation > 4) {
  473. newOptions.maxWidth = options.maxHeight
  474. newOptions.maxHeight = options.maxWidth
  475. newOptions.minWidth = options.minHeight
  476. newOptions.minHeight = options.minWidth
  477. newOptions.sourceWidth = options.sourceHeight
  478. newOptions.sourceHeight = options.sourceWidth
  479. }
  480. return newOptions
  481. }
  482. var hasblobSlice = window.Blob && (Blob.prototype.slice ||
  483. Blob.prototype.webkitSlice || Blob.prototype.mozSlice)
  484. loadImage.blobSlice = hasblobSlice && function () {
  485. var slice = this.slice || this.webkitSlice || this.mozSlice
  486. return slice.apply(this, arguments)
  487. }
  488. loadImage.metaDataParsers = {
  489. jpeg: {
  490. 0xffe1: [] // APP1 marker
  491. }
  492. }
  493. // Parses image meta data and calls the callback with an object argument
  494. // with the following properties:
  495. // * imageHead: The complete image head as ArrayBuffer (Uint8Array for IE10)
  496. // The options arguments accepts an object and supports the following properties:
  497. // * maxMetaDataSize: Defines the maximum number of bytes to parse.
  498. // * disableImageHead: Disables creating the imageHead property.
  499. loadImage.parseMetaData = function (file, callback, options) {
  500. options = options || {}
  501. var that = this
  502. // 256 KiB should contain all EXIF/ICC/IPTC segments:
  503. var maxMetaDataSize = options.maxMetaDataSize || 262144
  504. var data = {}
  505. var noMetaData = !(window.DataView && file && file.size >= 12 &&
  506. file.type === 'image/jpeg' && loadImage.blobSlice)
  507. if (noMetaData || !loadImage.readFile(
  508. loadImage.blobSlice.call(file, 0, maxMetaDataSize),
  509. function (e) {
  510. if (e.target.error) {
  511. // FileReader error
  512. //console.log(e.target.error)
  513. callback(data)
  514. return
  515. }
  516. // Note on endianness:
  517. // Since the marker and length bytes in JPEG files are always
  518. // stored in big endian order, we can leave the endian parameter
  519. // of the DataView methods undefined, defaulting to big endian.
  520. var buffer = e.target.result
  521. var dataView = new DataView(buffer)
  522. var offset = 2
  523. var maxOffset = dataView.byteLength - 4
  524. var headLength = offset
  525. var markerBytes
  526. var markerLength
  527. var parsers
  528. var i
  529. // Check for the JPEG marker (0xffd8):
  530. if (dataView.getUint16(0) === 0xffd8) {
  531. while (offset < maxOffset) {
  532. markerBytes = dataView.getUint16(offset)
  533. // Search for APPn (0xffeN) and COM (0xfffe) markers,
  534. // which contain application-specific meta-data like
  535. // Exif, ICC and IPTC data and text comments:
  536. if ((markerBytes >= 0xffe0 && markerBytes <= 0xffef) ||
  537. markerBytes === 0xfffe) {
  538. // The marker bytes (2) are always followed by
  539. // the length bytes (2), indicating the length of the
  540. // marker segment, which includes the length bytes,
  541. // but not the marker bytes, so we add 2:
  542. markerLength = dataView.getUint16(offset + 2) + 2
  543. if (offset + markerLength > dataView.byteLength) {
  544. //console.log('Invalid meta data: Invalid segment size.')
  545. break
  546. }
  547. parsers = loadImage.metaDataParsers.jpeg[markerBytes]
  548. if (parsers) {
  549. for (i = 0; i < parsers.length; i += 1) {
  550. parsers[i].call(
  551. that,
  552. dataView,
  553. offset,
  554. markerLength,
  555. data,
  556. options
  557. )
  558. }
  559. }
  560. offset += markerLength
  561. headLength = offset
  562. } else {
  563. // Not an APPn or COM marker, probably safe to
  564. // assume that this is the end of the meta data
  565. break
  566. }
  567. }
  568. // Meta length must be longer than JPEG marker (2)
  569. // plus APPn marker (2), followed by length bytes (2):
  570. if (!options.disableImageHead && headLength > 6) {
  571. if (buffer.slice) {
  572. data.imageHead = buffer.slice(0, headLength)
  573. } else {
  574. // Workaround for IE10, which does not yet
  575. // support ArrayBuffer.slice:
  576. data.imageHead = new Uint8Array(buffer)
  577. .subarray(0, headLength)
  578. }
  579. }
  580. } else {
  581. //console.log('Invalid JPEG file: Missing JPEG marker.')
  582. }
  583. callback(data)
  584. },
  585. 'readAsArrayBuffer'
  586. )) {
  587. callback(data)
  588. }
  589. }
  590. loadImage.ExifMap = function () {
  591. return this
  592. }
  593. loadImage.ExifMap.prototype.map = {
  594. 'Orientation': 0x0112
  595. }
  596. loadImage.ExifMap.prototype.get = function (id) {
  597. return this[id] || this[this.map[id]]
  598. }
  599. loadImage.getExifThumbnail = function (dataView, offset, length) {
  600. var hexData,
  601. i,
  602. b
  603. if (!length || offset + length > dataView.byteLength) {
  604. //console.log('Invalid Exif data: Invalid thumbnail data.')
  605. return
  606. }
  607. hexData = []
  608. for (i = 0; i < length; i += 1) {
  609. b = dataView.getUint8(offset + i)
  610. hexData.push((b < 16 ? '0' : '') + b.toString(16))
  611. }
  612. return 'data:image/jpeg,%' + hexData.join('%')
  613. }
  614. loadImage.exifTagTypes = {
  615. // byte, 8-bit unsigned int:
  616. 1: {
  617. getValue: function (dataView, dataOffset) {
  618. return dataView.getUint8(dataOffset)
  619. },
  620. size: 1
  621. },
  622. // ascii, 8-bit byte:
  623. 2: {
  624. getValue: function (dataView, dataOffset) {
  625. return String.fromCharCode(dataView.getUint8(dataOffset))
  626. },
  627. size: 1,
  628. ascii: true
  629. },
  630. // short, 16 bit int:
  631. 3: {
  632. getValue: function (dataView, dataOffset, littleEndian) {
  633. return dataView.getUint16(dataOffset, littleEndian)
  634. },
  635. size: 2
  636. },
  637. // long, 32 bit int:
  638. 4: {
  639. getValue: function (dataView, dataOffset, littleEndian) {
  640. return dataView.getUint32(dataOffset, littleEndian)
  641. },
  642. size: 4
  643. },
  644. // rational = two long values, first is numerator, second is denominator:
  645. 5: {
  646. getValue: function (dataView, dataOffset, littleEndian) {
  647. return dataView.getUint32(dataOffset, littleEndian) /
  648. dataView.getUint32(dataOffset + 4, littleEndian)
  649. },
  650. size: 8
  651. },
  652. // slong, 32 bit signed int:
  653. 9: {
  654. getValue: function (dataView, dataOffset, littleEndian) {
  655. return dataView.getInt32(dataOffset, littleEndian)
  656. },
  657. size: 4
  658. },
  659. // srational, two slongs, first is numerator, second is denominator:
  660. 10: {
  661. getValue: function (dataView, dataOffset, littleEndian) {
  662. return dataView.getInt32(dataOffset, littleEndian) /
  663. dataView.getInt32(dataOffset + 4, littleEndian)
  664. },
  665. size: 8
  666. }
  667. }
  668. // undefined, 8-bit byte, value depending on field:
  669. loadImage.exifTagTypes[7] = loadImage.exifTagTypes[1]
  670. loadImage.getExifValue = function (dataView, tiffOffset, offset, type, length, littleEndian) {
  671. var tagType = loadImage.exifTagTypes[type]
  672. var tagSize
  673. var dataOffset
  674. var values
  675. var i
  676. var str
  677. var c
  678. if (!tagType) {
  679. //console.log('Invalid Exif data: Invalid tag type.')
  680. return
  681. }
  682. tagSize = tagType.size * length
  683. // Determine if the value is contained in the dataOffset bytes,
  684. // or if the value at the dataOffset is a pointer to the actual data:
  685. dataOffset = tagSize > 4
  686. ? tiffOffset + dataView.getUint32(offset + 8, littleEndian)
  687. : (offset + 8)
  688. if (dataOffset + tagSize > dataView.byteLength) {
  689. //console.log('Invalid Exif data: Invalid data offset.')
  690. return
  691. }
  692. if (length === 1) {
  693. return tagType.getValue(dataView, dataOffset, littleEndian)
  694. }
  695. values = []
  696. for (i = 0; i < length; i += 1) {
  697. values[i] = tagType.getValue(dataView, dataOffset + i * tagType.size, littleEndian)
  698. }
  699. if (tagType.ascii) {
  700. str = ''
  701. // Concatenate the chars:
  702. for (i = 0; i < values.length; i += 1) {
  703. c = values[i]
  704. // Ignore the terminating NULL byte(s):
  705. if (c === '\u0000') {
  706. break
  707. }
  708. str += c
  709. }
  710. return str
  711. }
  712. return values
  713. }
  714. loadImage.parseExifTag = function (dataView, tiffOffset, offset, littleEndian, data) {
  715. var tag = dataView.getUint16(offset, littleEndian)
  716. data.exif[tag] = loadImage.getExifValue(
  717. dataView,
  718. tiffOffset,
  719. offset,
  720. dataView.getUint16(offset + 2, littleEndian), // tag type
  721. dataView.getUint32(offset + 4, littleEndian), // tag length
  722. littleEndian
  723. )
  724. }
  725. loadImage.parseExifTags = function (dataView, tiffOffset, dirOffset, littleEndian, data) {
  726. var tagsNumber,
  727. dirEndOffset,
  728. i
  729. if (dirOffset + 6 > dataView.byteLength) {
  730. //console.log('Invalid Exif data: Invalid directory offset.')
  731. return
  732. }
  733. tagsNumber = dataView.getUint16(dirOffset, littleEndian)
  734. dirEndOffset = dirOffset + 2 + 12 * tagsNumber
  735. if (dirEndOffset + 4 > dataView.byteLength) {
  736. //console.log('Invalid Exif data: Invalid directory size.')
  737. return
  738. }
  739. for (i = 0; i < tagsNumber; i += 1) {
  740. this.parseExifTag(
  741. dataView,
  742. tiffOffset,
  743. dirOffset + 2 + 12 * i, // tag offset
  744. littleEndian,
  745. data
  746. )
  747. }
  748. // Return the offset to the next directory:
  749. return dataView.getUint32(dirEndOffset, littleEndian)
  750. }
  751. loadImage.parseExifData = function (dataView, offset, length, data, options) {
  752. if (options.disableExif) {
  753. return
  754. }
  755. var tiffOffset = offset + 10
  756. var littleEndian
  757. var dirOffset
  758. var thumbnailData
  759. // Check for the ASCII code for "Exif" (0x45786966):
  760. if (dataView.getUint32(offset + 4) !== 0x45786966) {
  761. // No Exif data, might be XMP data instead
  762. return
  763. }
  764. if (tiffOffset + 8 > dataView.byteLength) {
  765. //console.log('Invalid Exif data: Invalid segment size.')
  766. return
  767. }
  768. // Check for the two null bytes:
  769. if (dataView.getUint16(offset + 8) !== 0x0000) {
  770. //console.log('Invalid Exif data: Missing byte alignment offset.')
  771. return
  772. }
  773. // Check the byte alignment:
  774. switch (dataView.getUint16(tiffOffset)) {
  775. case 0x4949:
  776. littleEndian = true
  777. break
  778. case 0x4D4D:
  779. littleEndian = false
  780. break
  781. default:
  782. //console.log('Invalid Exif data: Invalid byte alignment marker.')
  783. return
  784. }
  785. // Check for the TIFF tag marker (0x002A):
  786. if (dataView.getUint16(tiffOffset + 2, littleEndian) !== 0x002A) {
  787. //console.log('Invalid Exif data: Missing TIFF marker.')
  788. return
  789. }
  790. // Retrieve the directory offset bytes, usually 0x00000008 or 8 decimal:
  791. dirOffset = dataView.getUint32(tiffOffset + 4, littleEndian)
  792. // Create the exif object to store the tags:
  793. data.exif = new loadImage.ExifMap()
  794. // Parse the tags of the main image directory and retrieve the
  795. // offset to the next directory, usually the thumbnail directory:
  796. dirOffset = loadImage.parseExifTags(
  797. dataView,
  798. tiffOffset,
  799. tiffOffset + dirOffset,
  800. littleEndian,
  801. data
  802. )
  803. if (dirOffset && !options.disableExifThumbnail) {
  804. thumbnailData = {exif: {}}
  805. dirOffset = loadImage.parseExifTags(
  806. dataView,
  807. tiffOffset,
  808. tiffOffset + dirOffset,
  809. littleEndian,
  810. thumbnailData
  811. )
  812. // Check for JPEG Thumbnail offset:
  813. if (thumbnailData.exif[0x0201]) {
  814. data.exif.Thumbnail = loadImage.getExifThumbnail(
  815. dataView,
  816. tiffOffset + thumbnailData.exif[0x0201],
  817. thumbnailData.exif[0x0202] // Thumbnail data length
  818. )
  819. }
  820. }
  821. // Check for Exif Sub IFD Pointer:
  822. if (data.exif[0x8769] && !options.disableExifSub) {
  823. loadImage.parseExifTags(
  824. dataView,
  825. tiffOffset,
  826. tiffOffset + data.exif[0x8769], // directory offset
  827. littleEndian,
  828. data
  829. )
  830. }
  831. // Check for GPS Info IFD Pointer:
  832. if (data.exif[0x8825] && !options.disableExifGps) {
  833. loadImage.parseExifTags(
  834. dataView,
  835. tiffOffset,
  836. tiffOffset + data.exif[0x8825], // directory offset
  837. littleEndian,
  838. data
  839. )
  840. }
  841. }
  842. // Registers the Exif parser for the APP1 JPEG meta data segment:
  843. loadImage.metaDataParsers.jpeg[0xffe1].push(loadImage.parseExifData)
  844. var snabbt = (function() {
  845. var tickRequests = [];
  846. var runningAnimations = [];
  847. var completedAnimations = [];
  848. var transformProperty = 'transform';
  849. // Find which vendor prefix to use
  850. var styles = window.getComputedStyle(document.documentElement, '');
  851. var vendorPrefix = (Array.prototype.slice
  852. .call(styles)
  853. .join('')
  854. .match(/-(moz|webkit|ms)-/) || (styles.OLink === '' && ['', 'o'])
  855. )[1];
  856. if(vendorPrefix === 'webkit')
  857. transformProperty = 'webkitTransform';
  858. /* Entry point, only function to be called by user */
  859. var snabbt = function(arg1, arg2, arg3) {
  860. var elements = arg1;
  861. // If argument is an Array or a NodeList or other list type that can be iterable.
  862. // Loop through and start one animation for each element.
  863. if(elements.length !== undefined) {
  864. var aggregateChainer = {
  865. chainers: [],
  866. then: function(opts) {
  867. return this.snabbt(opts);
  868. },
  869. snabbt: function(opts) {
  870. var len = this.chainers.length;
  871. this.chainers.forEach(function(chainer, index) {
  872. chainer.snabbt(preprocessOptions(opts, index, len));
  873. });
  874. return aggregateChainer;
  875. },
  876. setValue: function(value) {
  877. this.chainers.forEach(function(chainer) {
  878. chainer.setValue(value);
  879. });
  880. return aggregateChainer;
  881. },
  882. finish: function() {
  883. this.chainers.forEach(function(chainer) {
  884. chainer.finish();
  885. });
  886. return aggregateChainer;
  887. },
  888. rollback: function() {
  889. this.chainers.forEach(function(chainer) {
  890. chainer.rollback();
  891. });
  892. return aggregateChainer;
  893. }
  894. };
  895. for(var i=0, len=elements.length;i<len;++i) {
  896. if(typeof arg2 == 'string')
  897. aggregateChainer.chainers.push(snabbtSingleElement(elements[i], arg2, preprocessOptions(arg3, i, len)));
  898. else
  899. aggregateChainer.chainers.push(snabbtSingleElement(elements[i], preprocessOptions(arg2, i, len), arg3));
  900. }
  901. return aggregateChainer;
  902. } else {
  903. if(typeof arg2 == 'string')
  904. return snabbtSingleElement(elements, arg2, preprocessOptions(arg3, 0, 1));
  905. else
  906. return snabbtSingleElement(elements, preprocessOptions(arg2, 0, 1), arg3);
  907. }
  908. };
  909. var preprocessOptions = function(options, index, len) {
  910. if(!options)
  911. return options;
  912. var clone = cloneObject(options);
  913. if(isFunction(options.delay)) {
  914. clone.delay = options.delay(index, len);
  915. }
  916. if(isFunction(options.callback)) {
  917. clone.complete = function() {
  918. options.callback.call(this, index, len);
  919. };
  920. }
  921. var hasAllDoneCallback = isFunction(options.allDone);
  922. var hasCompleteCallback = isFunction(options.complete);
  923. if(hasCompleteCallback || hasAllDoneCallback) {
  924. clone.complete = function() {
  925. if(hasCompleteCallback) {
  926. options.complete.call(this, index, len);
  927. }
  928. if(hasAllDoneCallback && (index == len - 1)) {
  929. options.allDone();
  930. }
  931. };
  932. }
  933. if(isFunction(options.valueFeeder)) {
  934. clone.valueFeeder = function(i, matrix) {
  935. return options.valueFeeder(i, matrix, index, len);
  936. };
  937. }
  938. if(isFunction(options.easing)) {
  939. clone.easing = function(i) {
  940. return options.easing(i, index, len);
  941. };
  942. }
  943. var properties = [
  944. 'position',
  945. 'rotation',
  946. 'skew',
  947. 'rotationPost',
  948. 'scale',
  949. 'width',
  950. 'height',
  951. 'opacity',
  952. 'fromPosition',
  953. 'fromRotation',
  954. 'fromSkew',
  955. 'fromRotationPost',
  956. 'fromScale',
  957. 'fromWidth',
  958. 'fromHeight',
  959. 'fromOpacity',
  960. 'transformOrigin',
  961. 'duration',
  962. 'delay'
  963. ];
  964. properties.forEach(function(property) {
  965. if(isFunction(options[property])) {
  966. clone[property] = options[property](index, len);
  967. }
  968. });
  969. return clone;
  970. };
  971. var snabbtSingleElement = function(element, arg2, arg3) {
  972. if(arg2 === 'attention')
  973. return setupAttentionAnimation(element, arg3);
  974. if(arg2 === 'stop')
  975. return stopAnimation(element);
  976. var options = arg2;
  977. // Remove orphaned end states
  978. clearOphanedEndStates();
  979. // If there is a running or past completed animation with element, use that end state as start state
  980. var currentState = currentAnimationState(element);
  981. var start = currentState;
  982. // from has precendance over current animation state
  983. start = stateFromOptions(options, start, true);
  984. var end = cloneObject(currentState);
  985. end = stateFromOptions(options, end);
  986. var animOptions = setupAnimationOptions(start, end, options);
  987. var animation = createAnimation(animOptions);
  988. runningAnimations.push([element, animation]);
  989. animation.updateElement(element, true);
  990. var queue = [];
  991. var chainer = {
  992. snabbt: function(opts) {
  993. queue.unshift(preprocessOptions(opts, 0, 1));
  994. return chainer;
  995. },
  996. then: function(opts) {
  997. return this.snabbt(opts);
  998. }
  999. };
  1000. function tick(time) {
  1001. animation.tick(time);
  1002. animation.updateElement(element);
  1003. if(animation.isStopped())
  1004. return;
  1005. if(!animation.completed())
  1006. return queueTick(tick);
  1007. if(options.loop > 1 && !animation.isStopped()) {
  1008. // Loop current animation
  1009. options.loop -= 1;
  1010. animation.restart();
  1011. queueTick(tick);
  1012. } else {
  1013. if(options.complete) {
  1014. options.complete.call(element);
  1015. }
  1016. // Start next animation in queue
  1017. if(queue.length) {
  1018. options = queue.pop();
  1019. start = stateFromOptions(options, end, true);
  1020. end = stateFromOptions(options, cloneObject(end));
  1021. options = setupAnimationOptions(start, end, options);
  1022. animation = createAnimation(options);
  1023. runningAnimations.push([element, animation]);
  1024. animation.tick(time);
  1025. queueTick(tick);
  1026. }
  1027. }
  1028. }
  1029. queueTick(tick);
  1030. // Manual animations are not chainable, instead an animation controller object is returned
  1031. // with setValue, finish and rollback methods
  1032. if(options.manual)
  1033. return animation;
  1034. return chainer;
  1035. };
  1036. var setupAttentionAnimation = function(element, options) {
  1037. var movement = stateFromOptions(options, createState({}));
  1038. options.movement = movement;
  1039. var animation = createAttentionAnimation(options);
  1040. runningAnimations.push([element, animation]);
  1041. function tick(time) {
  1042. animation.tick(time);
  1043. animation.updateElement(element);
  1044. if(!animation.completed()) {
  1045. queueTick(tick);
  1046. } else {
  1047. if(options.callback) {
  1048. options.callback(element);
  1049. }
  1050. if(options.loop && options.loop > 1) {
  1051. options.loop--;
  1052. animation.restart();
  1053. queueTick(tick);
  1054. }
  1055. }
  1056. }
  1057. queueTick(tick);
  1058. };
  1059. var stopAnimation = function(element) {
  1060. for(var i= 0,len=runningAnimations.length;i<len;++i) {
  1061. var currentAnimation = runningAnimations[i];
  1062. var animatedElement = currentAnimation[0];
  1063. var animation = currentAnimation[1];
  1064. if(animatedElement === element) {
  1065. animation.stop();
  1066. }
  1067. }
  1068. };
  1069. var findAnimationState = function(animationList, element) {
  1070. for(var i=0,len=animationList.length;i<len;++i) {
  1071. var currentAnimation = animationList[i];
  1072. var animatedElement = currentAnimation[0];
  1073. var animation = currentAnimation[1];
  1074. if(animatedElement === element) {
  1075. var state = animation.getCurrentState();
  1076. animation.stop();
  1077. return state;
  1078. }
  1079. }
  1080. };
  1081. var clearOphanedEndStates = function() {
  1082. completedAnimations = completedAnimations.filter(function(animation) {
  1083. return (findUltimateAncestor(animation[0]).body);
  1084. });
  1085. };
  1086. var findUltimateAncestor = function(node) {
  1087. var ancestor = node;
  1088. while(ancestor.parentNode) {
  1089. ancestor = ancestor.parentNode;
  1090. }
  1091. return ancestor;
  1092. };
  1093. /**
  1094. * Returns the current state of element if there is an ongoing or previously finished
  1095. * animation releated to it. Will also call stop on the animation.
  1096. * TODO: The stopping of the animation is better put somewhere else
  1097. */
  1098. var currentAnimationState = function(element) {
  1099. // Check if a completed animation is stored for this element
  1100. var state = findAnimationState(runningAnimations, element);
  1101. if(state)
  1102. return state;
  1103. return findAnimationState(completedAnimations, element);
  1104. };
  1105. /**
  1106. * Parses an animation configuration object and returns a State instance
  1107. */
  1108. var stateFromOptions = function(options, state, useFromPrefix) {
  1109. if (!state) {
  1110. state = createState({
  1111. position: [0, 0, 0],
  1112. rotation: [0, 0, 0],
  1113. rotationPost: [0, 0, 0],
  1114. scale: [1, 1],
  1115. skew: [0, 0]
  1116. });
  1117. }
  1118. var position = 'position';
  1119. var rotation = 'rotation';
  1120. var skew = 'skew';
  1121. var rotationPost = 'rotationPost';
  1122. var scale = 'scale';
  1123. var scalePost = 'scalePost';
  1124. var width = 'width';
  1125. var height = 'height';
  1126. var opacity = 'opacity';
  1127. if(useFromPrefix) {
  1128. position = 'fromPosition';
  1129. rotation = 'fromRotation';
  1130. skew = 'fromSkew';
  1131. rotationPost = 'fromRotationPost';
  1132. scale = 'fromScale';
  1133. scalePost = 'fromScalePost';
  1134. width = 'fromWidth';
  1135. height = 'fromHeight';
  1136. opacity = 'fromOpacity';
  1137. }
  1138. state.position = optionOrDefault(options[position], state.position);
  1139. state.rotation = optionOrDefault(options[rotation], state.rotation);
  1140. state.rotationPost = optionOrDefault(options[rotationPost], state.rotationPost);
  1141. state.skew = optionOrDefault(options[skew], state.skew);
  1142. state.scale = optionOrDefault(options[scale], state.scale);
  1143. state.scalePost = optionOrDefault(options[scalePost], state.scalePost);
  1144. state.opacity = options[opacity];
  1145. state.width = options[width];
  1146. state.height = options[height];
  1147. return state;
  1148. };
  1149. var setupAnimationOptions = function(start, end, options) {
  1150. options.startState = start;
  1151. options.endState = end;
  1152. return options;
  1153. };
  1154. var polyFillrAF = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.msRequestAnimationFrame || function(callback) { return setTimeout(callback, 1000 / 60); };
  1155. var queueTick = function(func) {
  1156. if(tickRequests.length === 0)
  1157. polyFillrAF(tickAnimations);
  1158. tickRequests.push(func);
  1159. };
  1160. var tickAnimations = function(time) {
  1161. var len = tickRequests.length;
  1162. for(var i=0;i<len;++i) {
  1163. tickRequests[i](time);
  1164. }
  1165. tickRequests.splice(0, len);
  1166. var finishedAnimations = runningAnimations.filter(function(animation) {
  1167. return animation[1].completed();
  1168. });
  1169. // See if there are any previously completed animations on the same element, if so, remove it before merging
  1170. completedAnimations = completedAnimations.filter(function(animation) {
  1171. for(var i=0,len=finishedAnimations.length;i<len;++i) {
  1172. if(animation[0] === finishedAnimations[i][0]) {
  1173. return false;
  1174. }
  1175. }
  1176. return true;
  1177. });
  1178. completedAnimations = completedAnimations.concat(finishedAnimations);
  1179. runningAnimations = runningAnimations.filter(function(animation) {
  1180. return !animation[1].completed();
  1181. });
  1182. if(tickRequests.length !== 0)
  1183. polyFillrAF(tickAnimations);
  1184. };
  1185. // Class for handling animation between two states
  1186. var createAnimation = function(options) {
  1187. var startState = options.startState;
  1188. var endState = options.endState;
  1189. var duration = optionOrDefault(options.duration, 500);
  1190. var delay = optionOrDefault(options.delay, 0);
  1191. var perspective = options.perspective;
  1192. var easing = createEaser(optionOrDefault(options.easing, 'linear'), options);
  1193. var currentState = duration === 0 ? endState.clone() : startState.clone();
  1194. var transformOrigin = options.transformOrigin;
  1195. currentState.transformOrigin = options.transformOrigin;
  1196. var startTime = 0;
  1197. var currentTime = 0;
  1198. var stopped = false;
  1199. var started = false;
  1200. // Manual related
  1201. var manual = options.manual;
  1202. var manualValue = 0;
  1203. var manualDelayFactor = delay / duration;
  1204. var manualCallback;
  1205. var tweener;
  1206. // Setup tweener
  1207. if(options.valueFeeder) {
  1208. tweener = createValueFeederTweener(options.valueFeeder,
  1209. startState,
  1210. endState,
  1211. currentState);
  1212. } else {
  1213. tweener = createStateTweener(startState, endState, currentState);
  1214. }
  1215. // Public api
  1216. return {
  1217. stop: function() {
  1218. stopped = true;
  1219. },
  1220. isStopped: function() {
  1221. return stopped;
  1222. },
  1223. finish: function(callback) {
  1224. manual = false;
  1225. var manualDuration = duration * manualValue;
  1226. startTime = currentTime - manualDuration;
  1227. manualCallback = callback;
  1228. easing.resetFrom = manualValue;
  1229. },
  1230. rollback: function(callback) {
  1231. manual = false;
  1232. tweener.setReverse();
  1233. var manualDuration = duration * (1 - manualValue);
  1234. startTime = currentTime - manualDuration;
  1235. manualCallback = callback;
  1236. easing.resetFrom = manualValue;
  1237. },
  1238. restart: function() {
  1239. // Restart timer
  1240. startTime = undefined;
  1241. easing.resetFrom(0);
  1242. },
  1243. tick: function(time) {
  1244. if(stopped)
  1245. return;
  1246. if(manual) {
  1247. currentTime = time;
  1248. this.updateCurrentTransform();
  1249. return;
  1250. }
  1251. // If first tick, set startTime
  1252. if(!startTime) {
  1253. startTime = time;
  1254. }
  1255. if(time - startTime > delay) {
  1256. started = true;
  1257. currentTime = time - delay;
  1258. var curr = Math.min(Math.max(0.0, currentTime - startTime), duration);
  1259. easing.tick(curr / duration);
  1260. this.updateCurrentTransform();
  1261. if(this.completed() && manualCallback) {
  1262. manualCallback();
  1263. }
  1264. }
  1265. },
  1266. getCurrentState: function() {
  1267. return currentState;
  1268. },
  1269. setValue: function(_manualValue) {
  1270. started = true;
  1271. manualValue = Math.min(Math.max(_manualValue, 0.0001), 1 + manualDelayFactor);
  1272. },
  1273. updateCurrentTransform: function() {
  1274. var tweenValue = easing.getValue();
  1275. if(manual) {
  1276. var val = Math.max(0.00001, manualValue - manualDelayFactor);
  1277. easing.tick(val);
  1278. tweenValue = easing.getValue();
  1279. }
  1280. tweener.tween(tweenValue);
  1281. },
  1282. completed: function() {
  1283. if(stopped)
  1284. return true;
  1285. if(startTime === 0) {
  1286. return false;
  1287. }
  1288. return easing.completed();
  1289. },
  1290. updateElement: function(element, forceUpdate) {
  1291. if(!started && !forceUpdate)
  1292. return;
  1293. var matrix = tweener.asMatrix();
  1294. var properties = tweener.getProperties();
  1295. updateElementTransform(element, matrix, perspective);
  1296. updateElementProperties(element, properties);
  1297. }
  1298. };
  1299. };
  1300. // ------------------------------
  1301. // End Time animation
  1302. // ------------------------------
  1303. // ------------------------
  1304. // -- AttentionAnimation --
  1305. // ------------------------
  1306. var createAttentionAnimation = function(options) {
  1307. var movement = options.movement;
  1308. options.initialVelocity = 0.1;
  1309. options.equilibriumPosition = 0;
  1310. var spring = createSpringEasing(options);
  1311. var stopped = false;
  1312. var tweenPosition = movement.position;
  1313. var tweenRotation = movement.rotation;
  1314. var tweenRotationPost = movement.rotationPost;
  1315. var tweenScale = movement.scale;
  1316. var tweenSkew = movement.skew;
  1317. var currentMovement = createState({
  1318. position: tweenPosition ? [0, 0, 0] : undefined,
  1319. rotation: tweenRotation ? [0, 0, 0] : undefined,
  1320. rotationPost: tweenRotationPost ? [0, 0, 0] : undefined,
  1321. scale: tweenScale ? [0, 0] : undefined,
  1322. skew: tweenSkew ? [0, 0] : undefined,
  1323. });
  1324. // Public API
  1325. return {
  1326. stop: function() {
  1327. stopped = true;
  1328. },
  1329. isStopped: function(time) {
  1330. return stopped;
  1331. },
  1332. tick: function(time) {
  1333. if(stopped)
  1334. return;
  1335. if(spring.equilibrium)
  1336. return;
  1337. spring.tick();
  1338. this.updateMovement();
  1339. },
  1340. updateMovement:function() {
  1341. var value = spring.getValue();
  1342. if(tweenPosition) {
  1343. currentMovement.position[0] = movement.position[0] * value;
  1344. currentMovement.position[1] = movement.position[1] * value;
  1345. currentMovement.position[2] = movement.position[2] * value;
  1346. }
  1347. if(tweenRotation) {
  1348. currentMovement.rotation[0] = movement.rotation[0] * value;
  1349. currentMovement.rotation[1] = movement.rotation[1] * value;
  1350. currentMovement.rotation[2] = movement.rotation[2] * value;
  1351. }
  1352. if(tweenRotationPost) {
  1353. currentMovement.rotationPost[0] = movement.rotationPost[0] * value;
  1354. currentMovement.rotationPost[1] = movement.rotationPost[1] * value;
  1355. currentMovement.rotationPost[2] = movement.rotationPost[2] * value;
  1356. }
  1357. if(tweenScale) {
  1358. currentMovement.scale[0] = 1 + movement.scale[0] * value;
  1359. currentMovement.scale[1] = 1 + movement.scale[1] * value;
  1360. }
  1361. if(tweenSkew) {
  1362. currentMovement.skew[0] = movement.skew[0] * value;
  1363. currentMovement.skew[1] = movement.skew[1] * value;
  1364. }
  1365. },
  1366. updateElement: function(element) {
  1367. updateElementTransform(element, currentMovement.asMatrix());
  1368. updateElementProperties(element, currentMovement.getProperties());
  1369. },
  1370. getCurrentState: function() {
  1371. return currentMovement;
  1372. },
  1373. completed: function() {
  1374. return spring.equilibrium || stopped;
  1375. },
  1376. restart: function() {
  1377. // Restart spring
  1378. spring = createSpringEasing(options);
  1379. }
  1380. };
  1381. };
  1382. /**********
  1383. * Easings *
  1384. ***********/
  1385. var linearEasing = function(value) {
  1386. return value;
  1387. };
  1388. var ease = function(value) {
  1389. return (Math.cos(value*Math.PI + Math.PI) + 1)/2;
  1390. };
  1391. var easeIn = function(value) {
  1392. return value*value;
  1393. };
  1394. var easeOut = function(value) {
  1395. return -Math.pow(value - 1, 2) + 1;
  1396. };
  1397. var createSpringEasing = function(options) {
  1398. var position = optionOrDefault(options.startPosition, 0);
  1399. var equilibriumPosition = optionOrDefault(options.equilibriumPosition, 1);
  1400. var velocity = optionOrDefault(options.initialVelocity, 0);
  1401. var springConstant = optionOrDefault(options.springConstant, 0.8);
  1402. var deceleration = optionOrDefault(options.springDeceleration, 0.9);
  1403. var mass = optionOrDefault(options.springMass, 10);
  1404. var equilibrium = false;
  1405. // Public API
  1406. return {
  1407. tick: function(value) {
  1408. if(value === 0.0)
  1409. return;
  1410. if(equilibrium)
  1411. return;
  1412. var springForce = -(position - equilibriumPosition) * springConstant;
  1413. // f = m * a
  1414. // a = f / m
  1415. var a = springForce / mass;
  1416. // s = v * t
  1417. // t = 1 ( for now )
  1418. velocity += a;
  1419. position += velocity;
  1420. // Deceleration
  1421. velocity *= deceleration;
  1422. if(Math.abs(position - equilibriumPosition) < 0.001 && Math.abs(velocity) < 0.001) {
  1423. equilibrium = true;
  1424. }
  1425. },
  1426. resetFrom: function(value) {
  1427. position = value;
  1428. velocity = 0;
  1429. },
  1430. getValue: function() {
  1431. if(equilibrium)
  1432. return equilibriumPosition;
  1433. return position;
  1434. },
  1435. completed: function() {
  1436. return equilibrium;
  1437. }
  1438. };
  1439. };
  1440. var EASING_FUNCS = {
  1441. 'linear': linearEasing,
  1442. 'ease': ease,
  1443. 'easeIn': easeIn,
  1444. 'easeOut': easeOut,
  1445. };
  1446. var createEaser = function(easerName, options) {
  1447. if(easerName == 'spring') {
  1448. return createSpringEasing(options);
  1449. }
  1450. var easeFunction = easerName;
  1451. if(!isFunction(easerName)) {
  1452. easeFunction = EASING_FUNCS[easerName];
  1453. }
  1454. var easer = easeFunction;
  1455. var value = 0;
  1456. var lastValue;
  1457. // Public API
  1458. return {
  1459. tick: function(v) {
  1460. value = easer(v);
  1461. lastValue = v;
  1462. },
  1463. resetFrom: function(value) {
  1464. lastValue = 0;
  1465. },
  1466. getValue: function() {
  1467. return value;
  1468. },
  1469. completed: function() {
  1470. if(lastValue >= 1) {
  1471. return lastValue;
  1472. }
  1473. return false;
  1474. }
  1475. };
  1476. };
  1477. /***
  1478. * Matrix related
  1479. */
  1480. var assignTranslate = function(matrix, x, y, z) {
  1481. matrix[0] = 1;
  1482. matrix[1] = 0;
  1483. matrix[2] = 0;
  1484. matrix[3] = 0;
  1485. matrix[4] = 0;
  1486. matrix[5] = 1;
  1487. matrix[6] = 0;
  1488. matrix[7] = 0;
  1489. matrix[8] = 0;
  1490. matrix[9] = 0;
  1491. matrix[10] = 1;
  1492. matrix[11] = 0;
  1493. matrix[12] = x;
  1494. matrix[13] = y;
  1495. matrix[14] = z;
  1496. matrix[15] = 1;
  1497. };
  1498. var assignRotateX = function(matrix, rad) {
  1499. matrix[0] = 1;
  1500. matrix[1] = 0;
  1501. matrix[2] = 0;
  1502. matrix[3] = 0;
  1503. matrix[4] = 0;
  1504. matrix[5] = Math.cos(rad);
  1505. matrix[6] = -Math.sin(rad);
  1506. matrix[7] = 0;
  1507. matrix[8] = 0;
  1508. matrix[9] = Math.sin(rad);
  1509. matrix[10] = Math.cos(rad);
  1510. matrix[11] = 0;
  1511. matrix[12] = 0;
  1512. matrix[13] = 0;
  1513. matrix[14] = 0;
  1514. matrix[15] = 1;
  1515. };
  1516. var assignRotateY = function(matrix, rad) {
  1517. matrix[0] = Math.cos(rad);
  1518. matrix[1] = 0;
  1519. matrix[2] = Math.sin(rad);
  1520. matrix[3] = 0;
  1521. matrix[4] = 0;
  1522. matrix[5] = 1;
  1523. matrix[6] = 0;
  1524. matrix[7] = 0;
  1525. matrix[8] = -Math.sin(rad);
  1526. matrix[9] = 0;
  1527. matrix[10] = Math.cos(rad);
  1528. matrix[11] = 0;
  1529. matrix[12] = 0;
  1530. matrix[13] = 0;
  1531. matrix[14] = 0;
  1532. matrix[15] = 1;
  1533. };
  1534. var assignRotateZ = function(matrix, rad) {
  1535. matrix[0] = Math.cos(rad);
  1536. matrix[1] = -Math.sin(rad);
  1537. matrix[2] = 0;
  1538. matrix[3] = 0;
  1539. matrix[4] = Math.sin(rad);
  1540. matrix[5] = Math.cos(rad);
  1541. matrix[6] = 0;
  1542. matrix[7] = 0;
  1543. matrix[8] = 0;
  1544. matrix[9] = 0;
  1545. matrix[10] = 1;
  1546. matrix[11] = 0;
  1547. matrix[12] = 0;
  1548. matrix[13] = 0;
  1549. matrix[14] = 0;
  1550. matrix[15] = 1;
  1551. };
  1552. var assignSkew = function(matrix, ax, ay) {
  1553. matrix[0] = 1;
  1554. matrix[1] = Math.tan(ax);
  1555. matrix[2] = 0;
  1556. matrix[3] = 0;
  1557. matrix[4] = Math.tan(ay);
  1558. matrix[5] = 1;
  1559. matrix[6] = 0;
  1560. matrix[7] = 0;
  1561. matrix[8] = 0;
  1562. matrix[9] = 0;
  1563. matrix[10] = 1;
  1564. matrix[11] = 0;
  1565. matrix[12] = 0;
  1566. matrix[13] = 0;
  1567. matrix[14] = 0;
  1568. matrix[15] = 1;
  1569. };
  1570. var assignScale = function(matrix, x, y) {
  1571. matrix[0] = x;
  1572. matrix[1] = 0;
  1573. matrix[2] = 0;
  1574. matrix[3] = 0;
  1575. matrix[4] = 0;
  1576. matrix[5] = y;
  1577. matrix[6] = 0;
  1578. matrix[7] = 0;
  1579. matrix[8] = 0;
  1580. matrix[9] = 0;
  1581. matrix[10] = 1;
  1582. matrix[11] = 0;
  1583. matrix[12] = 0;
  1584. matrix[13] = 0;
  1585. matrix[14] = 0;
  1586. matrix[15] = 1;
  1587. };
  1588. var assignIdentity = function(matrix) {
  1589. matrix[0] = 1;
  1590. matrix[1] = 0;
  1591. matrix[2] = 0;
  1592. matrix[3] = 0;
  1593. matrix[4] = 0;
  1594. matrix[5] = 1;
  1595. matrix[6] = 0;
  1596. matrix[7] = 0;
  1597. matrix[8] = 0;
  1598. matrix[9] = 0;
  1599. matrix[10] = 1;
  1600. matrix[11] = 0;
  1601. matrix[12] = 0;
  1602. matrix[13] = 0;
  1603. matrix[14] = 0;
  1604. matrix[15] = 1;
  1605. };
  1606. var copyArray = function(a, b) {
  1607. b[0] = a[0];
  1608. b[1] = a[1];
  1609. b[2] = a[2];
  1610. b[3] = a[3];
  1611. b[4] = a[4];
  1612. b[5] = a[5];
  1613. b[6] = a[6];
  1614. b[7] = a[7];
  1615. b[8] = a[8];
  1616. b[9] = a[9];
  1617. b[10] = a[10];
  1618. b[11] = a[11];
  1619. b[12] = a[12];
  1620. b[13] = a[13];
  1621. b[14] = a[14];
  1622. b[15] = a[15];
  1623. };
  1624. var createMatrix = function() {
  1625. var data = new Float32Array(16);
  1626. var a = new Float32Array(16);
  1627. var b = new Float32Array(16);
  1628. assignIdentity(data);
  1629. return {
  1630. data: data,
  1631. asCSS: function() {
  1632. var css = 'matrix3d(';
  1633. for(var i=0;i<15;++i) {
  1634. if(Math.abs(data[i]) < 0.0001)
  1635. css += '0,';
  1636. else
  1637. css += data[i].toFixed(10) + ',';
  1638. }
  1639. if(Math.abs(data[15]) < 0.0001)
  1640. css += '0)';
  1641. else
  1642. css += data[15].toFixed(10) + ')';
  1643. return css;
  1644. },
  1645. clear: function() {
  1646. assignIdentity(data);
  1647. },
  1648. translate: function(x, y, z) {
  1649. copyArray(data, a);
  1650. assignTranslate(b, x, y, z);
  1651. assignedMatrixMultiplication(a, b, data);
  1652. return this;
  1653. },
  1654. rotateX: function(radians) {
  1655. copyArray(data, a);
  1656. assignRotateX(b, radians);
  1657. assignedMatrixMultiplication(a, b, data);
  1658. return this;
  1659. },
  1660. rotateY: function(radians) {
  1661. copyArray(data, a);
  1662. assignRotateY(b, radians);
  1663. assignedMatrixMultiplication(a, b, data);
  1664. return this;
  1665. },
  1666. rotateZ: function(radians) {
  1667. copyArray(data, a);
  1668. assignRotateZ(b, radians);
  1669. assignedMatrixMultiplication(a, b, data);
  1670. return this;
  1671. },
  1672. scale: function(x, y) {
  1673. copyArray(data, a);
  1674. assignScale(b, x, y);
  1675. assignedMatrixMultiplication(a, b, data);
  1676. return this;
  1677. },
  1678. skew: function(ax, ay) {
  1679. copyArray(data, a);
  1680. assignSkew(b, ax, ay);
  1681. assignedMatrixMultiplication(a, b, data);
  1682. return this;
  1683. }
  1684. };
  1685. };
  1686. var assignedMatrixMultiplication = function(a, b, res) {
  1687. // Unrolled loop
  1688. res[0] = a[0] * b[0] + a[1] * b[4] + a[2] * b[8] + a[3] * b[12];
  1689. res[1] = a[0] * b[1] + a[1] * b[5] + a[2] * b[9] + a[3] * b[13];
  1690. res[2] = a[0] * b[2] + a[1] * b[6] + a[2] * b[10] + a[3] * b[14];
  1691. res[3] = a[0] * b[3] + a[1] * b[7] + a[2] * b[11] + a[3] * b[15];
  1692. res[4] = a[4] * b[0] + a[5] * b[4] + a[6] * b[8] + a[7] * b[12];
  1693. res[5] = a[4] * b[1] + a[5] * b[5] + a[6] * b[9] + a[7] * b[13];
  1694. res[6] = a[4] * b[2] + a[5] * b[6] + a[6] * b[10] + a[7] * b[14];
  1695. res[7] = a[4] * b[3] + a[5] * b[7] + a[6] * b[11] + a[7] * b[15];
  1696. res[8] = a[8] * b[0] + a[9] * b[4] + a[10] * b[8] + a[11] * b[12];
  1697. res[9] = a[8] * b[1] + a[9] * b[5] + a[10] * b[9] + a[11] * b[13];
  1698. res[10] = a[8] * b[2] + a[9] * b[6] + a[10] * b[10] + a[11] * b[14];
  1699. res[11] = a[8] * b[3] + a[9] * b[7] + a[10] * b[11] + a[11] * b[15];
  1700. res[12] = a[12] * b[0] + a[13] * b[4] + a[14] * b[8] + a[15] * b[12];
  1701. res[13] = a[12] * b[1] + a[13] * b[5] + a[14] * b[9] + a[15] * b[13];
  1702. res[14] = a[12] * b[2] + a[13] * b[6] + a[14] * b[10] + a[15] * b[14];
  1703. res[15] = a[12] * b[3] + a[13] * b[7] + a[14] * b[11] + a[15] * b[15];
  1704. return res;
  1705. };
  1706. var createState = function(config) {
  1707. // Caching of matrix and properties so we don't have to create new ones everytime they are needed
  1708. var matrix = createMatrix();
  1709. var properties = {
  1710. opacity: undefined,
  1711. width: undefined,
  1712. height: undefined
  1713. };
  1714. // Public API
  1715. return {
  1716. position: config.position,
  1717. rotation: config.rotation,
  1718. rotationPost: config.rotationPost,
  1719. skew: config.skew,
  1720. scale: config.scale,
  1721. scalePost: config.scalePost,
  1722. opacity: config.opacity,
  1723. width: config.width,
  1724. height: config.height,
  1725. clone: function() {
  1726. return createState({
  1727. position: this.position ? this.position.slice(0) : undefined,
  1728. rotation: this.rotation ? this.rotation.slice(0) : undefined,
  1729. rotationPost: this.rotationPost ? this.rotationPost.slice(0) : undefined,
  1730. skew: this.skew ? this.skew.slice(0) : undefined,
  1731. scale: this.scale ? this.scale.slice(0) : undefined,
  1732. scalePost: this.scalePost ? this.scalePost.slice(0) : undefined,
  1733. height: this.height,
  1734. width: this.width,
  1735. opacity: this.opacity
  1736. });
  1737. },
  1738. asMatrix: function() {
  1739. var m = matrix;
  1740. m.clear();
  1741. if(this.transformOrigin)
  1742. m.translate(-this.transformOrigin[0], -this.transformOrigin[1], -this.transformOrigin[2]);
  1743. if(this.scale) {
  1744. m.scale(this.scale[0], this.scale[1]);
  1745. }
  1746. if(this.skew) {
  1747. m.skew(this.skew[0], this.skew[1]);
  1748. }
  1749. if(this.rotation) {
  1750. m.rotateX(this.rotation[0]);
  1751. m.rotateY(this.rotation[1]);
  1752. m.rotateZ(this.rotation[2]);
  1753. }
  1754. if(this.position) {
  1755. m.translate(this.position[0], this.position[1], this.position[2]);
  1756. }
  1757. if(this.rotationPost) {
  1758. m.rotateX(this.rotationPost[0]);
  1759. m.rotateY(this.rotationPost[1]);
  1760. m.rotateZ(this.rotationPost[2]);
  1761. }
  1762. if(this.scalePost) {
  1763. m.scale(this.scalePost[0], this.scalePost[1]);
  1764. }
  1765. if(this.transformOrigin)
  1766. m.translate(this.transformOrigin[0], this.transformOrigin[1], this.transformOrigin[2]);
  1767. return m;
  1768. },
  1769. getProperties: function() {
  1770. properties.opacity = this.opacity;
  1771. properties.width = this.width + 'px';
  1772. properties.height = this.height + 'px';
  1773. return properties;
  1774. }
  1775. };
  1776. };
  1777. // ------------------
  1778. // -- StateTweener --
  1779. // -------------------
  1780. var createStateTweener = function(startState, endState, resultState) {
  1781. var start = startState;
  1782. var end = endState;
  1783. var result = resultState;
  1784. var tweenPosition = end.position !== undefined;
  1785. var tweenRotation = end.rotation !== undefined;
  1786. var tweenRotationPost = end.rotationPost !== undefined;
  1787. var tweenScale = end.scale !== undefined;
  1788. var tweenSkew = end.skew !== undefined;
  1789. var tweenWidth = end.width !== undefined;
  1790. var tweenHeight = end.height !== undefined;
  1791. var tweenOpacity = end.opacity !== undefined;
  1792. // Public API
  1793. return {
  1794. tween: function(tweenValue) {
  1795. if(tweenPosition) {
  1796. var dX = (end.position[0] - start.position[0]);
  1797. var dY = (end.position[1] - start.position[1]);
  1798. var dZ = (end.position[2] - start.position[2]);
  1799. result.position[0] = start.position[0] + tweenValue*dX;
  1800. result.position[1] = start.position[1] + tweenValue*dY;
  1801. result.position[2] = start.position[2] + tweenValue*dZ;
  1802. }
  1803. if(tweenRotation) {
  1804. var dAX = (end.rotation[0] - start.rotation[0]);
  1805. var dAY = (end.rotation[1] - start.rotation[1]);
  1806. var dAZ = (end.rotation[2] - start.rotation[2]);
  1807. result.rotation[0] = start.rotation[0] + tweenValue*dAX;
  1808. result.rotation[1] = start.rotation[1] + tweenValue*dAY;
  1809. result.rotation[2] = start.rotation[2] + tweenValue*dAZ;
  1810. }
  1811. if(tweenRotationPost) {
  1812. var dBX = (end.rotationPost[0] - start.rotationPost[0]);
  1813. var dBY = (end.rotationPost[1] - start.rotationPost[1]);
  1814. var dBZ = (end.rotationPost[2] - start.rotationPost[2]);
  1815. result.rotationPost[0] = start.rotationPost[0] + tweenValue*dBX;
  1816. result.rotationPost[1] = start.rotationPost[1] + tweenValue*dBY;
  1817. result.rotationPost[2] = start.rotationPost[2] + tweenValue*dBZ;
  1818. }
  1819. if(tweenSkew) {
  1820. var dSX = (end.scale[0] - start.scale[0]);
  1821. var dSY = (end.scale[1] - start.scale[1]);
  1822. result.scale[0] = start.scale[0] + tweenValue*dSX;
  1823. result.scale[1] = start.scale[1] + tweenValue*dSY;
  1824. }
  1825. if(tweenScale) {
  1826. var dSkewX = (end.skew[0] - start.skew[0]);
  1827. var dSkewY = (end.skew[1] - start.skew[1]);
  1828. result.skew[0] = start.skew[0] + tweenValue*dSkewX;
  1829. result.skew[1] = start.skew[1] + tweenValue*dSkewY;
  1830. }
  1831. if(tweenWidth) {
  1832. var dWidth = (end.width - start.width);
  1833. result.width = start.width + tweenValue*dWidth;
  1834. }
  1835. if(tweenHeight) {
  1836. var dHeight = (end.height - start.height);
  1837. result.height = start.height + tweenValue*dHeight;
  1838. }
  1839. if(tweenOpacity) {
  1840. var dOpacity = (end.opacity - start.opacity);
  1841. result.opacity = start.opacity + tweenValue*dOpacity;
  1842. }
  1843. },
  1844. asMatrix: function() {
  1845. return result.asMatrix();
  1846. },
  1847. getProperties: function() {
  1848. return result.getProperties();
  1849. },
  1850. setReverse: function() {
  1851. var oldStart = start;
  1852. start = end;
  1853. end = oldStart;
  1854. }
  1855. };
  1856. };
  1857. // ------------------------
  1858. // -- ValueFeederTweener --
  1859. // ------------------------
  1860. var createValueFeederTweener = function(valueFeeder, startState, endState, resultState) {
  1861. var currentMatrix = valueFeeder(0, createMatrix());
  1862. var start = startState;
  1863. var end = endState;
  1864. var result = resultState;
  1865. var reverse = false;
  1866. // Public API
  1867. return {
  1868. tween: function(tweenValue) {
  1869. if(reverse)
  1870. tweenValue = 1 - tweenValue;
  1871. currentMatrix.clear();
  1872. currentMatrix = valueFeeder(tweenValue, currentMatrix);
  1873. var dWidth = (end.width - start.width);
  1874. var dHeight = (end.height - start.height);
  1875. var dOpacity = (end.opacity - start.opacity);
  1876. if(end.width !== undefined)
  1877. result.width = start.width + tweenValue*dWidth;
  1878. if(end.height !== undefined)
  1879. result.height = start.height + tweenValue*dHeight;
  1880. if(end.opacity !== undefined)
  1881. result.opacity = start.opacity + tweenValue*dOpacity;
  1882. },
  1883. asMatrix: function() {
  1884. return currentMatrix;
  1885. },
  1886. getProperties: function() {
  1887. return result.getProperties();
  1888. },
  1889. setReverse: function() {
  1890. reverse = true;
  1891. }
  1892. };
  1893. };
  1894. var optionOrDefault = function(option, def) {
  1895. if(typeof option == 'undefined') {
  1896. return def;
  1897. }
  1898. return option;
  1899. };
  1900. var updateElementTransform = function(element, matrix, perspective) {
  1901. var cssPerspective = '';
  1902. if(perspective) {
  1903. cssPerspective = 'perspective(' + perspective + 'px) ';
  1904. }
  1905. var cssMatrix = matrix.asCSS();
  1906. element.style[transformProperty] = cssPerspective + cssMatrix;
  1907. };
  1908. var updateElementProperties = function(element, properties) {
  1909. for(var key in properties) {
  1910. element.style[key] = properties[key];
  1911. }
  1912. };
  1913. var isFunction = function(object) {
  1914. return (typeof object === "function");
  1915. };
  1916. var cloneObject = function(object) {
  1917. if(!object)
  1918. return object;
  1919. var clone = {};
  1920. for(var key in object) {
  1921. clone[key] = object[key];
  1922. }
  1923. return clone;
  1924. };
  1925. snabbt.createMatrix = createMatrix;
  1926. snabbt.setElementTransform = updateElementTransform;
  1927. return snabbt;
  1928. }());
  1929. var stackBlur = (function(){
  1930. var mul_table = [
  1931. 512,512,456,512,328,456,335,512,405,328,271,456,388,335,292,512,
  1932. 454,405,364,328,298,271,496,456,420,388,360,335,312,292,273,512,
  1933. 482,454,428,405,383,364,345,328,312,298,284,271,259,496,475,456,
  1934. 437,420,404,388,374,360,347,335,323,312,302,292,282,273,265,512,
  1935. 497,482,468,454,441,428,417,405,394,383,373,364,354,345,337,328,
  1936. 320,312,305,298,291,284,278,271,265,259,507,496,485,475,465,456,
  1937. 446,437,428,420,412,404,396,388,381,374,367,360,354,347,341,335,
  1938. 329,323,318,312,307,302,297,292,287,282,278,273,269,265,261,512,
  1939. 505,497,489,482,475,468,461,454,447,441,435,428,422,417,411,405,
  1940. 399,394,389,383,378,373,368,364,359,354,350,345,341,337,332,328,
  1941. 324,320,316,312,309,305,301,298,294,291,287,284,281,278,274,271,
  1942. 268,265,262,259,257,507,501,496,491,485,480,475,470,465,460,456,
  1943. 451,446,442,437,433,428,424,420,416,412,408,404,400,396,392,388,
  1944. 385,381,377,374,370,367,363,360,357,354,350,347,344,341,338,335,
  1945. 332,329,326,323,320,318,315,312,310,307,304,302,299,297,294,292,
  1946. 289,287,285,282,280,278,275,273,271,269,267,265,263,261,259];
  1947. var shg_table = [
  1948. 9, 11, 12, 13, 13, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 17,
  1949. 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 19,
  1950. 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 20, 20, 20,
  1951. 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 21,
  1952. 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
  1953. 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22,
  1954. 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
  1955. 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23,
  1956. 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
  1957. 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
  1958. 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
  1959. 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
  1960. 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
  1961. 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
  1962. 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
  1963. 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24 ];
  1964. function getImageDataFromCanvas(canvas, top_x, top_y, width, height)
  1965. {
  1966. if (typeof(canvas) == 'string')
  1967. canvas = document.getElementById(canvas);
  1968. else if (!canvas instanceof HTMLCanvasElement)
  1969. return;
  1970. var context = canvas.getContext('2d');
  1971. var imageData;
  1972. try {
  1973. try {
  1974. imageData = context.getImageData(top_x, top_y, width, height);
  1975. } catch(e) {
  1976. throw new Error("unable to access local image data: " + e);
  1977. return;
  1978. }
  1979. } catch(e) {
  1980. throw new Error("unable to access image data: " + e);
  1981. }
  1982. return imageData;
  1983. }
  1984. function processCanvasRGBA(canvas, top_x, top_y, width, height, radius)
  1985. {
  1986. if (isNaN(radius) || radius < 1) return;
  1987. radius |= 0;
  1988. var imageData = getImageDataFromCanvas(canvas, top_x, top_y, width, height);
  1989. imageData = processImageDataRGBA(imageData, top_x, top_y, width, height, radius);
  1990. canvas.getContext('2d').putImageData(imageData, top_x, top_y);
  1991. }
  1992. function processImageDataRGBA(imageData, top_x, top_y, width, height, radius)
  1993. {
  1994. var pixels = imageData.data;
  1995. var x, y, i, p, yp, yi, yw, r_sum, g_sum, b_sum, a_sum,
  1996. r_out_sum, g_out_sum, b_out_sum, a_out_sum,
  1997. r_in_sum, g_in_sum, b_in_sum, a_in_sum,
  1998. pr, pg, pb, pa, rbs;
  1999. var div = radius + radius + 1;
  2000. var w4 = width << 2;
  2001. var widthMinus1 = width - 1;
  2002. var heightMinus1 = height - 1;
  2003. var radiusPlus1 = radius + 1;
  2004. var sumFactor = radiusPlus1 * (radiusPlus1 + 1) / 2;
  2005. var stackStart = new BlurStack();
  2006. var stack = stackStart;
  2007. for (i = 1; i < div; i++)
  2008. {
  2009. stack = stack.next = new BlurStack();
  2010. if (i == radiusPlus1) var stackEnd = stack;
  2011. }
  2012. stack.next = stackStart;
  2013. var stackIn = null;
  2014. var stackOut = null;
  2015. yw = yi = 0;
  2016. var mul_sum = mul_table[radius];
  2017. var shg_sum = shg_table[radius];
  2018. for (y = 0; y < height; y++)
  2019. {
  2020. r_in_sum = g_in_sum = b_in_sum = a_in_sum = r_sum = g_sum = b_sum = a_sum = 0;
  2021. r_out_sum = radiusPlus1 * (pr = pixels[yi]);
  2022. g_out_sum = radiusPlus1 * (pg = pixels[yi+1]);
  2023. b_out_sum = radiusPlus1 * (pb = pixels[yi+2]);
  2024. a_out_sum = radiusPlus1 * (pa = pixels[yi+3]);
  2025. r_sum += sumFactor * pr;
  2026. g_sum += sumFactor * pg;
  2027. b_sum += sumFactor * pb;
  2028. a_sum += sumFactor * pa;
  2029. stack = stackStart;
  2030. for (i = 0; i < radiusPlus1; i++)
  2031. {
  2032. stack.r = pr;
  2033. stack.g = pg;
  2034. stack.b = pb;
  2035. stack.a = pa;
  2036. stack = stack.next;
  2037. }
  2038. for (i = 1; i < radiusPlus1; i++)
  2039. {
  2040. p = yi + ((widthMinus1 < i ? widthMinus1 : i) << 2);
  2041. r_sum += (stack.r = (pr = pixels[p])) * (rbs = radiusPlus1 - i);
  2042. g_sum += (stack.g = (pg = pixels[p+1])) * rbs;
  2043. b_sum += (stack.b = (pb = pixels[p+2])) * rbs;
  2044. a_sum += (stack.a = (pa = pixels[p+3])) * rbs;
  2045. r_in_sum += pr;
  2046. g_in_sum += pg;
  2047. b_in_sum += pb;
  2048. a_in_sum += pa;
  2049. stack = stack.next;
  2050. }
  2051. stackIn = stackStart;
  2052. stackOut = stackEnd;
  2053. for (x = 0; x < width; x++)
  2054. {
  2055. pixels[yi+3] = pa = (a_sum * mul_sum) >> shg_sum;
  2056. if (pa != 0)
  2057. {
  2058. pa = 255 / pa;
  2059. pixels[yi] = ((r_sum * mul_sum) >> shg_sum) * pa;
  2060. pixels[yi+1] = ((g_sum * mul_sum) >> shg_sum) * pa;
  2061. pixels[yi+2] = ((b_sum * mul_sum) >> shg_sum) * pa;
  2062. } else {
  2063. pixels[yi] = pixels[yi+1] = pixels[yi+2] = 0;
  2064. }
  2065. r_sum -= r_out_sum;
  2066. g_sum -= g_out_sum;
  2067. b_sum -= b_out_sum;
  2068. a_sum -= a_out_sum;
  2069. r_out_sum -= stackIn.r;
  2070. g_out_sum -= stackIn.g;
  2071. b_out_sum -= stackIn.b;
  2072. a_out_sum -= stackIn.a;
  2073. p = (yw + ((p = x + radius + 1) < widthMinus1 ? p : widthMinus1)) << 2;
  2074. r_in_sum += (stackIn.r = pixels[p]);
  2075. g_in_sum += (stackIn.g = pixels[p+1]);
  2076. b_in_sum += (stackIn.b = pixels[p+2]);
  2077. a_in_sum += (stackIn.a = pixels[p+3]);
  2078. r_sum += r_in_sum;
  2079. g_sum += g_in_sum;
  2080. b_sum += b_in_sum;
  2081. a_sum += a_in_sum;
  2082. stackIn = stackIn.next;
  2083. r_out_sum += (pr = stackOut.r);
  2084. g_out_sum += (pg = stackOut.g);
  2085. b_out_sum += (pb = stackOut.b);
  2086. a_out_sum += (pa = stackOut.a);
  2087. r_in_sum -= pr;
  2088. g_in_sum -= pg;
  2089. b_in_sum -= pb;
  2090. a_in_sum -= pa;
  2091. stackOut = stackOut.next;
  2092. yi += 4;
  2093. }
  2094. yw += width;
  2095. }
  2096. for (x = 0; x < width; x++)
  2097. {
  2098. g_in_sum = b_in_sum = a_in_sum = r_in_sum = g_sum = b_sum = a_sum = r_sum = 0;
  2099. yi = x << 2;
  2100. r_out_sum = radiusPlus1 * (pr = pixels[yi]);
  2101. g_out_sum = radiusPlus1 * (pg = pixels[yi+1]);
  2102. b_out_sum = radiusPlus1 * (pb = pixels[yi+2]);
  2103. a_out_sum = radiusPlus1 * (pa = pixels[yi+3]);
  2104. r_sum += sumFactor * pr;
  2105. g_sum += sumFactor * pg;
  2106. b_sum += sumFactor * pb;
  2107. a_sum += sumFactor * pa;
  2108. stack = stackStart;
  2109. for (i = 0; i < radiusPlus1; i++)
  2110. {
  2111. stack.r = pr;
  2112. stack.g = pg;
  2113. stack.b = pb;
  2114. stack.a = pa;
  2115. stack = stack.next;
  2116. }
  2117. yp = width;
  2118. for (i = 1; i <= radius; i++)
  2119. {
  2120. yi = (yp + x) << 2;
  2121. r_sum += (stack.r = (pr = pixels[yi])) * (rbs = radiusPlus1 - i);
  2122. g_sum += (stack.g = (pg = pixels[yi+1])) * rbs;
  2123. b_sum += (stack.b = (pb = pixels[yi+2])) * rbs;
  2124. a_sum += (stack.a = (pa = pixels[yi+3])) * rbs;
  2125. r_in_sum += pr;
  2126. g_in_sum += pg;
  2127. b_in_sum += pb;
  2128. a_in_sum += pa;
  2129. stack = stack.next;
  2130. if(i < heightMinus1)
  2131. {
  2132. yp += width;
  2133. }
  2134. }
  2135. yi = x;
  2136. stackIn = stackStart;
  2137. stackOut = stackEnd;
  2138. for (y = 0; y < height; y++)
  2139. {
  2140. p = yi << 2;
  2141. pixels[p+3] = pa = (a_sum * mul_sum) >> shg_sum;
  2142. if (pa > 0)
  2143. {
  2144. pa = 255 / pa;
  2145. pixels[p] = ((r_sum * mul_sum) >> shg_sum) * pa;
  2146. pixels[p+1] = ((g_sum * mul_sum) >> shg_sum) * pa;
  2147. pixels[p+2] = ((b_sum * mul_sum) >> shg_sum) * pa;
  2148. } else {
  2149. pixels[p] = pixels[p+1] = pixels[p+2] = 0;
  2150. }
  2151. r_sum -= r_out_sum;
  2152. g_sum -= g_out_sum;
  2153. b_sum -= b_out_sum;
  2154. a_sum -= a_out_sum;
  2155. r_out_sum -= stackIn.r;
  2156. g_out_sum -= stackIn.g;
  2157. b_out_sum -= stackIn.b;
  2158. a_out_sum -= stackIn.a;
  2159. p = (x + (((p = y + radiusPlus1) < heightMinus1 ? p : heightMinus1) * width)) << 2;
  2160. r_sum += (r_in_sum += (stackIn.r = pixels[p]));
  2161. g_sum += (g_in_sum += (stackIn.g = pixels[p+1]));
  2162. b_sum += (b_in_sum += (stackIn.b = pixels[p+2]));
  2163. a_sum += (a_in_sum += (stackIn.a = pixels[p+3]));
  2164. stackIn = stackIn.next;
  2165. r_out_sum += (pr = stackOut.r);
  2166. g_out_sum += (pg = stackOut.g);
  2167. b_out_sum += (pb = stackOut.b);
  2168. a_out_sum += (pa = stackOut.a);
  2169. r_in_sum -= pr;
  2170. g_in_sum -= pg;
  2171. b_in_sum -= pb;
  2172. a_in_sum -= pa;
  2173. stackOut = stackOut.next;
  2174. yi += width;
  2175. }
  2176. }
  2177. return imageData;
  2178. }
  2179. function BlurStack()
  2180. {
  2181. this.r = 0;
  2182. this.g = 0;
  2183. this.b = 0;
  2184. this.a = 0;
  2185. this.next = null;
  2186. }
  2187. return processCanvasRGBA;
  2188. }());
  2189. // canvas to blob polyfill
  2190. // https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob#Polyfill
  2191. if (!HTMLCanvasElement.prototype.toBlob) {
  2192. Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', {
  2193. value: function (callback, type, quality) {
  2194. var binStr = atob( this.toDataURL(type, quality).split(',')[1] ),
  2195. len = binStr.length,
  2196. arr = new Uint8Array(len);
  2197. for (var i=0; i<len; i++ ) {
  2198. arr[i] = binStr.charCodeAt(i);
  2199. }
  2200. callback( new Blob( [arr], {type: type || 'image/png'} ) );
  2201. }
  2202. });
  2203. }
  2204. 'use strict';
  2205. var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
  2206. var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; };
  2207. function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
  2208. var getElementAttributes = function getElementAttributes(el) {
  2209. return Array.prototype.slice.call(el.attributes).map(function (attr) {
  2210. return {
  2211. 'name': attr.name,
  2212. 'value': attr.value
  2213. };
  2214. });
  2215. };
  2216. // helper method
  2217. var getOffsetByEvent = function getOffsetByEvent(e) {
  2218. return {
  2219. x: typeof e.offsetX === 'undefined' ? e.layerX : e.offsetX,
  2220. y: typeof e.offsetY === 'undefined' ? e.layerY : e.offsetY
  2221. };
  2222. };
  2223. // merge two objects together
  2224. var mergeOptions = function mergeOptions(base, additives) {
  2225. var key;
  2226. var options = {};
  2227. var optionsToMerge = additives || {};
  2228. for (key in base) {
  2229. if (!base.hasOwnProperty(key)) {
  2230. continue;
  2231. }
  2232. options[key] = typeof optionsToMerge[key] === 'undefined' ? base[key] : optionsToMerge[key];
  2233. }
  2234. return options;
  2235. };
  2236. // keys
  2237. var Key = {
  2238. ESC: 27,
  2239. RETURN: 13
  2240. };
  2241. // pointer events
  2242. var Events = {
  2243. DOWN: ['touchstart', 'pointerdown', 'mousedown'],
  2244. MOVE: ['touchmove', 'pointermove', 'mousemove'],
  2245. UP: ['touchend', 'touchcancel', 'pointerup', 'mouseup']
  2246. };
  2247. var MimeTypes = {
  2248. 'jpeg': 'image/jpeg',
  2249. 'jpg': 'image/jpeg',
  2250. 'jpe': 'image/jpeg',
  2251. 'png': 'image/png',
  2252. 'gif': 'image/gif',
  2253. 'bmp': 'image/bmp'
  2254. };
  2255. var ImageExtensionsRegex = /(\.png|\.bmp|\.gif|\.jpg|\.jpe|\.jpg|\.jpeg)$/;
  2256. var CanvasExportExtensions = /(jpe|jpg|jpeg|png)/;
  2257. // shortcuts
  2258. var create = function create(name, className) {
  2259. var node = document.createElement(name);
  2260. if (className) {
  2261. node.className = className;
  2262. }
  2263. return node;
  2264. };
  2265. // events
  2266. var addEvents = function addEvents(obj, events, scope) {
  2267. events.forEach(function (event) {
  2268. obj.addEventListener(event, scope, false);
  2269. });
  2270. };
  2271. var removeEvents = function removeEvents(obj, events, scope) {
  2272. events.forEach(function (event) {
  2273. obj.removeEventListener(event, scope, false);
  2274. });
  2275. };
  2276. var getEventOffset = function getEventOffset(e) {
  2277. var event = e.changedTouches ? e.changedTouches[0] : e;
  2278. // no event found, quit!
  2279. if (!event) {
  2280. return;
  2281. }
  2282. // get offset from events
  2283. return {
  2284. x: event.pageX,
  2285. y: event.pageY
  2286. };
  2287. };
  2288. var getEventOffsetLocal = function getEventOffsetLocal(e, local) {
  2289. var offset = getEventOffset(e);
  2290. var rect = local.getBoundingClientRect();
  2291. var top = window.pageYOffset || document.documentElement.scrollTop;
  2292. var left = window.pageXOffset || document.documentElement.scrollLeft;
  2293. return {
  2294. x: offset.x - rect.left - left,
  2295. y: offset.y - rect.top - top
  2296. };
  2297. };
  2298. var capitalizeFirstLetter = function capitalizeFirstLetter(string) {
  2299. return string.charAt(0).toUpperCase() + string.slice(1);
  2300. };
  2301. var last = function last(array) {
  2302. return array[array.length - 1];
  2303. };
  2304. var limit = function limit(value, min, max) {
  2305. return Math.max(min, Math.min(max, value));
  2306. };
  2307. var inArray = function inArray(needle, arr) {
  2308. return arr.indexOf(needle) !== -1;
  2309. };
  2310. var send = function send(url, data, requestDecorator, progress, success, err) {
  2311. var xhr = new XMLHttpRequest();
  2312. // if progress callback defined handle progress events
  2313. if (progress) {
  2314. xhr.upload.addEventListener('progress', function (e) {
  2315. progress(e.loaded, e.total);
  2316. });
  2317. }
  2318. // open the request
  2319. xhr.open('POST', url, true);
  2320. // if request decorator defined pass XMLHttpRequest instance to decorator
  2321. if (requestDecorator) {
  2322. requestDecorator(xhr);
  2323. }
  2324. // handle state changes
  2325. xhr.onreadystatechange = function () {
  2326. if (xhr.readyState === 4 && xhr.status === 200) {
  2327. var text = xhr.responseText;
  2328. // if no data returned from server assume success
  2329. if (!text.length) {
  2330. success();
  2331. return;
  2332. }
  2333. // catch possible PHP content length problem
  2334. if (text.indexOf('Content-Length') !== -1) {
  2335. err('file-too-big');
  2336. return;
  2337. }
  2338. // if data returned it should be in suggested JSON format
  2339. var obj = void 0;
  2340. try {
  2341. obj = JSON.parse(xhr.responseText);
  2342. } catch (e) {}
  2343. // if is failure response
  2344. if ((typeof obj === 'undefined' ? 'undefined' : _typeof(obj)) === 'object' && obj.status === 'failure') {
  2345. err(obj.message);
  2346. return;
  2347. }
  2348. success(obj || text);
  2349. } else if (xhr.readyState === 4) {
  2350. err('fail');
  2351. }
  2352. };
  2353. // do request
  2354. xhr.send(data);
  2355. };
  2356. var resetTransforms = function resetTransforms(element) {
  2357. element.style.webkitTransform = '';
  2358. element.style.transform = '';
  2359. };
  2360. var bytesToMegaBytes = function bytesToMegaBytes(b) {
  2361. return b / 1000000;
  2362. };
  2363. var megaBytesToBytes = function megaBytesToBytes(mb) {
  2364. return mb * 1000000;
  2365. };
  2366. var getCommonMimeTypes = function getCommonMimeTypes() {
  2367. var types = [];
  2368. var type = void 0;
  2369. var mimeType = void 0;
  2370. for (type in MimeTypes) {
  2371. if (!MimeTypes.hasOwnProperty(type)) {
  2372. continue;
  2373. }
  2374. mimeType = MimeTypes[type];
  2375. if (types.indexOf(mimeType) == -1) {
  2376. types.push(mimeType);
  2377. }
  2378. }
  2379. return types;
  2380. };
  2381. var isJPEGMimeType = function isJPEGMimeType(type) {
  2382. return type === 'image/jpeg';
  2383. };
  2384. var getExtensionByMimeType = function getExtensionByMimeType(mimetype) {
  2385. var type = void 0;
  2386. for (type in MimeTypes) {
  2387. if (!MimeTypes.hasOwnProperty(type)) {
  2388. continue;
  2389. }
  2390. if (MimeTypes[type] === mimetype) {
  2391. return type;
  2392. }
  2393. }
  2394. return mimetype;
  2395. };
  2396. /**
  2397. * Returns the mimetype matching the given extension
  2398. * @param {string} extension
  2399. * @returns {string}
  2400. */
  2401. function getFileMimeType(extension) {
  2402. return MimeTypes[extension ? extension.toLowerCase() : extension] || 'unknown';
  2403. };
  2404. var getFileName = function getFileName(path) {
  2405. return path.split('/').pop().split('?').shift();
  2406. };
  2407. var getFileNameWithoutExtension = function getFileNameWithoutExtension(path) {
  2408. if (typeof path !== 'string') {
  2409. return 'unknown';
  2410. }
  2411. var name = getFileName(path);
  2412. return name.split('.').shift();
  2413. };
  2414. var getFileMetaDataFromBase64 = function getFileMetaDataFromBase64(base64) {
  2415. var mimeType = getMimeTypeFromDataURI(base64);
  2416. var extension = getExtensionByMimeType(mimeType);
  2417. var name = 'unknown.' + extension;
  2418. return {
  2419. name: name,
  2420. type: mimeType,
  2421. size: Math.floor(base64.length * .73) // base64 is approx 37% bigger than final image, so let's reverse it to get the approx image size
  2422. };
  2423. };
  2424. var getFileMetaDataFromUrl = function getFileMetaDataFromUrl(url) {
  2425. var name = getFileName(url);
  2426. var mimeType = getFileMimeType(name.split('.').pop());
  2427. return {
  2428. name: name,
  2429. type: mimeType,
  2430. size: null
  2431. };
  2432. };
  2433. var getFileMetaDataFromFile = function getFileMetaDataFromFile(file) {
  2434. return {
  2435. name: file.name,
  2436. type: file.type,
  2437. size: file.size
  2438. };
  2439. };
  2440. var getFileMetaData = function getFileMetaData(file) {
  2441. if (typeof file === 'string') {
  2442. // test if is base 64 data uri
  2443. if (/^data:image/.test(file)) {
  2444. return getFileMetaDataFromBase64(file);
  2445. }
  2446. // is url
  2447. return getFileMetaDataFromUrl(file);
  2448. }
  2449. return getFileMetaDataFromFile(file);
  2450. };
  2451. var setFileMetaData = function setFileMetaData(fileData) {
  2452. var extension = arguments.length <= 1 || arguments[1] === undefined ? '' : arguments[1];
  2453. var data = clone(fileData);
  2454. // correct extension if includes dot
  2455. extension = extension.replace(/\./, '');
  2456. // test if extension if of correct type, if not, force to png
  2457. if (!CanvasExportExtensions.test(extension)) {
  2458. extension = 'png';
  2459. }
  2460. // if no extension set new extension
  2461. if (!ImageExtensionsRegex.test(data.name)) {
  2462. data.name += '.' + extension;
  2463. }
  2464. // replace existing extension
  2465. else {
  2466. data.name = data.name.replace(/\.[a-z]+$/, '.' + extension);
  2467. }
  2468. // force mime type
  2469. data.type = MimeTypes[extension];
  2470. return data;
  2471. };
  2472. var getImageAsCanvas = function getImageAsCanvas(src, callback) {
  2473. // only cross origin when it's not base64 data, to prevent errors in Safari
  2474. // http://stackoverflow.com/questions/31643096/why-does-safari-throw-cors-error-when-setting-base64-data-on-a-crossorigin-an
  2475. var crossOrigin = typeof src === 'string' ? src.indexOf('data:image') !== 0 : true;
  2476. loadImage.parseMetaData(src, function (meta) {
  2477. var options = {
  2478. canvas: true,
  2479. crossOrigin: crossOrigin
  2480. };
  2481. if (meta.exif) {
  2482. options.orientation = meta.exif.get('Orientation');
  2483. }
  2484. loadImage(src, function (res) {
  2485. if (res.type === 'error') {
  2486. callback();
  2487. return;
  2488. }
  2489. callback(res, meta);
  2490. }, options);
  2491. });
  2492. };
  2493. var getAutoCropRect = function getAutoCropRect(width, height, ratioOut) {
  2494. var x,
  2495. y,
  2496. w,
  2497. h,
  2498. ratioIn = height / width;
  2499. // if input is portrait and required is landscape
  2500. // width is portrait width, height is width times outputRatio
  2501. if (ratioIn < ratioOut) {
  2502. h = height;
  2503. w = h / ratioOut;
  2504. x = (width - w) * .5;
  2505. y = 0;
  2506. }
  2507. // if input is landscape and required is portrait
  2508. // height is landscape height, width is height divided by outputRatio
  2509. else {
  2510. w = width;
  2511. h = w * ratioOut;
  2512. x = 0;
  2513. y = (height - h) * .5;
  2514. }
  2515. return {
  2516. x: x,
  2517. y: y,
  2518. height: h,
  2519. width: w
  2520. };
  2521. };
  2522. var transformCanvas = function transformCanvas(canvas) {
  2523. var transforms = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
  2524. var cb = arguments[2];
  2525. var result = create('canvas');
  2526. var rotation = transforms.rotation;
  2527. var crop = transforms.crop;
  2528. var size = transforms.size;
  2529. // do crop transforms
  2530. if (crop) {
  2531. //console.log('rotation:', rotation);
  2532. //console.log('crop:', crop.x, crop.y, crop.width, crop.height);
  2533. //console.log('canvas:', canvas.width, canvas.height);
  2534. // do crop
  2535. var isTilted = rotation % 180 !== 0;
  2536. var space = {
  2537. width: isTilted ? canvas.height : canvas.width,
  2538. height: isTilted ? canvas.width : canvas.height
  2539. };
  2540. // crop offsets in percentages
  2541. var px = crop.x / space.width;
  2542. var py = crop.y / space.height;
  2543. var pw = crop.width / space.width;
  2544. var ph = crop.height / space.height;
  2545. // limit crop to size of canvas else safari might return transparent image
  2546. crop = roundRect(crop);
  2547. if (crop.y + crop.height > space.height) {
  2548. crop.y = Math.max(0, space.height - crop.height);
  2549. }
  2550. if (crop.x + crop.width > space.width) {
  2551. crop.x = Math.max(0, space.width - crop.width);
  2552. }
  2553. //console.log('limit:', crop.x, crop.y, crop.width, crop.height);
  2554. //console.log('percentages: ',px, py, pw, ph);
  2555. // resize canvas to the final crop result size
  2556. result.width = crop.width;
  2557. result.height = crop.height;
  2558. // draw the crop
  2559. var ctx = result.getContext('2d');
  2560. if (rotation === 90) {
  2561. ctx.translate(result.width * .5, result.height * .5);
  2562. ctx.rotate(-90 * Math.PI / 180);
  2563. ctx.drawImage(canvas,
  2564. // source rectangle (crop area)
  2565. (1 - py) * canvas.width - canvas.width * ph, crop.x, crop.height, crop.width,
  2566. // target area (cover)
  2567. -result.height * .5, -result.width * .5, result.height, result.width);
  2568. } else if (rotation === 180) {
  2569. ctx.translate(result.width * .5, result.height * .5);
  2570. ctx.rotate(-180 * Math.PI / 180);
  2571. ctx.drawImage(canvas,
  2572. // source rectangle (crop area)
  2573. (1 - (px + pw)) * space.width, (1 - (py + ph)) * space.height, pw * space.width, ph * space.height,
  2574. // target area (cover)
  2575. -result.width * .5, -result.height * .5, result.width, result.height);
  2576. } else if (rotation === 270) {
  2577. ctx.translate(result.width * .5, result.height * .5);
  2578. ctx.rotate(-270 * Math.PI / 180);
  2579. ctx.drawImage(canvas,
  2580. // source rectangle (crop area)
  2581. crop.y, (1 - px) * canvas.height - canvas.height * pw, crop.height, crop.width,
  2582. // target area (cover)
  2583. -result.height * .5, -result.width * .5, result.height, result.width);
  2584. } else {
  2585. ctx.drawImage(canvas,
  2586. // source rectangle (crop area)
  2587. crop.x, crop.y, crop.width, crop.height,
  2588. // target area (cover)
  2589. 0, 0, result.width, result.height);
  2590. }
  2591. }
  2592. // do size transforms
  2593. if (size) {
  2594. // pick smallest scalar
  2595. scaleCanvas(result, Math.min(size.width / result.width, size.height / result.height));
  2596. }
  2597. cb(result);
  2598. };
  2599. function scaleCanvas(canvas, scalar) {
  2600. // if not scaling down, bail out
  2601. if (scalar >= 1) {
  2602. return;
  2603. }
  2604. var c = cloneCanvas(canvas);
  2605. var ctx;
  2606. var width = canvas.width;
  2607. var height = canvas.height;
  2608. var targetWidth = Math.ceil(canvas.width * scalar);
  2609. var targetHeight = Math.ceil(canvas.height * scalar);
  2610. // scale down in steps
  2611. while (width > targetWidth) {
  2612. width *= .5;
  2613. height *= .5;
  2614. if (width < targetWidth) {
  2615. // skip to final draw
  2616. break;
  2617. }
  2618. c = create('canvas');
  2619. c.width = width;
  2620. c.height = height;
  2621. ctx = c.getContext('2d');
  2622. ctx.drawImage(canvas, 0, 0, width, height);
  2623. }
  2624. // draw final result back to original canvas
  2625. canvas.width = targetWidth;
  2626. canvas.height = targetHeight;
  2627. ctx = canvas.getContext('2d');
  2628. ctx.drawImage(c, 0, 0, targetWidth, targetHeight);
  2629. }
  2630. var cloneCanvas = function cloneCanvas(original) {
  2631. return cloneCanvasScaled(original, 1);
  2632. };
  2633. var cloneCanvasScaled = function cloneCanvasScaled(original, scalar) {
  2634. if (!original) {
  2635. return null;
  2636. }
  2637. var duplicate = document.createElement('canvas');
  2638. duplicate.setAttribute('data-file', original.getAttribute('data-file'));
  2639. var ctx = duplicate.getContext('2d');
  2640. duplicate.width = original.width;
  2641. duplicate.height = original.height;
  2642. ctx.drawImage(original, 0, 0);
  2643. if (scalar > 0 && scalar != 1) {
  2644. scaleCanvas(duplicate, scalar);
  2645. }
  2646. return duplicate;
  2647. };
  2648. var canvasHasDimensions = function canvasHasDimensions(canvas) {
  2649. return canvas.width && canvas.height;
  2650. };
  2651. var copyCanvas = function copyCanvas(original, destination) {
  2652. var ctx = destination.getContext('2d');
  2653. if (canvasHasDimensions(destination)) {
  2654. ctx.drawImage(original, 0, 0, destination.width, destination.height);
  2655. } else {
  2656. destination.width = original.width;
  2657. destination.height = original.height;
  2658. ctx.drawImage(original, 0, 0);
  2659. }
  2660. };
  2661. var clearCanvas = function clearCanvas(canvas) {
  2662. var ctx = canvas.getContext('2d');
  2663. ctx.clearRect(0, 0, canvas.width, canvas.height);
  2664. };
  2665. var blurCanvas = function blurCanvas(canvas) {
  2666. stackBlur(canvas, 0, 0, canvas.width, canvas.height, 3);
  2667. };
  2668. var downloadCanvas = function downloadCanvas(canvas, filename, mimeType) {
  2669. canvas.toBlob(function (blob) {
  2670. if ('msSaveBlob' in window.navigator) {
  2671. window.navigator.msSaveBlob(blob, filename);
  2672. return;
  2673. }
  2674. var url = URL.createObjectURL(blob);
  2675. // setup hidden link
  2676. var link = create('a');
  2677. link.style.display = 'none';
  2678. link.download = filename;
  2679. link.href = url;
  2680. // attach to DOM otherwise this does not work in Firefox
  2681. document.body.appendChild(link);
  2682. // fire click
  2683. link.click();
  2684. // delay on remove otherwise does not work in Firefox
  2685. setTimeout(function () {
  2686. document.body.removeChild(link);
  2687. URL.revokeObjectURL(url);
  2688. }, 0);
  2689. }, mimeType);
  2690. };
  2691. var covers = function covers(image, rect) {
  2692. return parseInt(image.width, 10) >= rect.width && parseInt(image.height, 10) >= rect.height;
  2693. };
  2694. var scaleRect = function scaleRect(rect, w, h) {
  2695. return {
  2696. x: rect.x * w,
  2697. y: rect.y * h,
  2698. width: rect.width * w,
  2699. height: rect.height * h
  2700. };
  2701. };
  2702. var divideRect = function divideRect(rect, w, h) {
  2703. return {
  2704. x: rect.x / w,
  2705. y: rect.y / h,
  2706. width: rect.width / w,
  2707. height: rect.height / h
  2708. };
  2709. };
  2710. var roundRect = function roundRect(rect) {
  2711. return {
  2712. x: Math.floor(rect.x),
  2713. y: Math.floor(rect.y),
  2714. width: Math.floor(rect.width),
  2715. height: Math.floor(rect.height)
  2716. };
  2717. };
  2718. var resetFileInput = function resetFileInput(input) {
  2719. // no value, no need to reset
  2720. if (!input || input.value === '') {
  2721. return;
  2722. }
  2723. try {
  2724. // for modern browsers
  2725. input.value = '';
  2726. } catch (err) {}
  2727. // for IE10
  2728. if (input.value) {
  2729. // quickly append input to temp form and reset form
  2730. var form = document.createElement('form');
  2731. var parentNode = input.parentNode;
  2732. var ref = input.nextSibling;
  2733. form.appendChild(input);
  2734. form.reset();
  2735. // re-inject input where it originally was
  2736. if (ref) {
  2737. parentNode.insertBefore(input, ref);
  2738. } else {
  2739. parentNode.appendChild(input);
  2740. }
  2741. }
  2742. };
  2743. var clone = function clone(obj) {
  2744. if ((typeof value === 'undefined' ? 'undefined' : _typeof(value)) === 'object' && value !== null) {
  2745. return JSON.parse(JSON.stringify(obj));
  2746. }
  2747. return obj;
  2748. };
  2749. var cloneData = function cloneData(obj) {
  2750. var input = cloneCanvas(obj.input.image);
  2751. var output = cloneCanvas(obj.output.image);
  2752. var dupe = clone(obj);
  2753. dupe.input.image = input;
  2754. dupe.output.image = output;
  2755. return dupe;
  2756. };
  2757. /**
  2758. * @param image
  2759. * @param type
  2760. * @param jpegCompression - value between 0 and 100 or undefined/null to use default compression
  2761. * @returns {*}
  2762. */
  2763. var toDataURL = function toDataURL(image, type, jpegCompression) {
  2764. if (!image || !type) {
  2765. return null;
  2766. }
  2767. return image.toDataURL(type, isJPEGMimeType(type) && typeof jpegCompression === 'number' ? jpegCompression / 100 : undefined);
  2768. };
  2769. var getMimeTypeFromDataURI = function getMimeTypeFromDataURI(dataUri) {
  2770. if (!dataUri) {
  2771. return null;
  2772. }
  2773. var matches = dataUri.substr(0, 16).match(/^.+;/);
  2774. if (matches.length) {
  2775. return matches[0].substring(5, matches[0].length - 1);
  2776. }
  2777. return null;
  2778. };
  2779. var flattenData = function flattenData(obj) {
  2780. var props = arguments.length <= 1 || arguments[1] === undefined ? [] : arguments[1];
  2781. var jpegCompression = arguments[2];
  2782. var data = {
  2783. server: clone(obj.server),
  2784. meta: clone(obj.meta),
  2785. input: {
  2786. name: obj.input.name,
  2787. type: obj.input.type,
  2788. size: obj.input.size,
  2789. width: obj.input.width,
  2790. height: obj.input.height
  2791. },
  2792. output: {
  2793. width: obj.output.width,
  2794. height: obj.output.height
  2795. }
  2796. };
  2797. if (inArray('input', props)) {
  2798. data.input.image = toDataURL(obj.input.image, obj.input.type);
  2799. }
  2800. if (inArray('output', props)) {
  2801. data.output.image = toDataURL(obj.output.image, obj.input.type, jpegCompression);
  2802. }
  2803. if (inArray('actions', props)) {
  2804. data.actions = clone(obj.actions);
  2805. }
  2806. // if output is of type png and input was of type jpeg we need to fix extension of filename
  2807. // so instead of testing the above situation we just always fix extension when handling png's
  2808. var type = getMimeTypeFromDataURI(data.output.image || data.input.image);
  2809. if (type === 'image/png') {
  2810. data.input.name = getFileNameWithoutExtension(data.input.name) + '.png';
  2811. data.input.type = type;
  2812. }
  2813. return data;
  2814. };
  2815. var toggleDisplayBySelector = function toggleDisplayBySelector(selector, enabled, root) {
  2816. var node = root.querySelector(selector);
  2817. if (!node) {
  2818. return;
  2819. }
  2820. node.style.display = enabled ? '' : 'none';
  2821. };
  2822. var nodeListToArray = function nodeListToArray(nl) {
  2823. return Array.prototype.slice.call(nl);
  2824. };
  2825. var removeElement = function removeElement(el) {
  2826. el.parentNode.removeChild(el);
  2827. };
  2828. var wrap = function wrap(element) {
  2829. var wrapper = create('div');
  2830. if (element.parentNode) {
  2831. if (element.nextSibling) {
  2832. element.parentNode.insertBefore(wrapper, element.nextSibling);
  2833. } else {
  2834. element.parentNode.appendChild(wrapper);
  2835. }
  2836. }
  2837. wrapper.appendChild(element);
  2838. return wrapper;
  2839. };
  2840. var polarToCartesian = function polarToCartesian(centerX, centerY, radius, angleInDegrees) {
  2841. var angleInRadians = (angleInDegrees - 90) * Math.PI / 180.0;
  2842. return {
  2843. x: centerX + radius * Math.cos(angleInRadians),
  2844. y: centerY + radius * Math.sin(angleInRadians)
  2845. };
  2846. };
  2847. var describeArc = function describeArc(x, y, radius, startAngle, endAngle) {
  2848. var start = polarToCartesian(x, y, radius, endAngle);
  2849. var end = polarToCartesian(x, y, radius, startAngle);
  2850. var arcSweep = endAngle - startAngle <= 180 ? '0' : '1';
  2851. var d = ['M', start.x, start.y, 'A', radius, radius, 0, arcSweep, 0, end.x, end.y].join(' ');
  2852. return d;
  2853. };
  2854. var percentageArc = function percentageArc(x, y, radius, p) {
  2855. return describeArc(x, y, radius, 0, p * 360);
  2856. };
  2857. var CropArea = function () {
  2858. var resizers = {
  2859. 'n': function n(rect, offset, space, ratio) {
  2860. var t, r, b, l, w, h, p, d;
  2861. // bottom is fixed
  2862. b = rect.y + rect.height;
  2863. // intended top
  2864. t = limit(offset.y, 0, b);
  2865. // if is too small vertically
  2866. if (b - t < space.min.height) {
  2867. t = b - space.min.height;
  2868. }
  2869. // if should scale by ratio, pick width by ratio of new height
  2870. w = ratio ? (b - t) / ratio : rect.width;
  2871. // check if has fallen below min width or height
  2872. if (w < space.min.width) {
  2873. w = space.min.width;
  2874. t = b - w * ratio;
  2875. }
  2876. // add half to left and half to right edge
  2877. p = (w - rect.width) * .5;
  2878. l = rect.x - p;
  2879. r = rect.x + rect.width + p;
  2880. // check if any of the edges has moved out of the available space, if so,
  2881. // set max size of rectangle from original position
  2882. if (l < 0 || r > space.width) {
  2883. // smallest distance to edge of space
  2884. d = Math.min(rect.x, space.width - (rect.x + rect.width));
  2885. // new left and right offsets
  2886. l = rect.x - d;
  2887. r = rect.x + rect.width + d;
  2888. // resulting width
  2889. w = r - l;
  2890. // resulting height based on ratio
  2891. h = w * ratio;
  2892. // new top position
  2893. t = b - h;
  2894. }
  2895. return {
  2896. x: l,
  2897. y: t,
  2898. width: r - l,
  2899. height: b - t
  2900. };
  2901. },
  2902. 's': function s(rect, offset, space, ratio) {
  2903. var t, r, b, l, w, h, p, d;
  2904. // top is fixed
  2905. t = rect.y;
  2906. // intended bottom
  2907. b = limit(offset.y, t, space.height);
  2908. // if is too small vertically
  2909. if (b - t < space.min.height) {
  2910. b = t + space.min.height;
  2911. }
  2912. // if should scale by ratio, pick width by ratio of new height
  2913. w = ratio ? (b - t) / ratio : rect.width;
  2914. // check if has fallen below min width or height
  2915. if (w < space.min.width) {
  2916. w = space.min.width;
  2917. b = t + w * ratio;
  2918. }
  2919. // add half to left and half to right edge
  2920. p = (w - rect.width) * .5;
  2921. l = rect.x - p;
  2922. r = rect.x + rect.width + p;
  2923. // check if any of the edges has moved out of the available space, if so,
  2924. // set max size of rectangle from original position
  2925. if (l < 0 || r > space.width) {
  2926. // smallest distance to edge of space
  2927. d = Math.min(rect.x, space.width - (rect.x + rect.width));
  2928. // new left and right offsets
  2929. l = rect.x - d;
  2930. r = rect.x + rect.width + d;
  2931. // resulting width
  2932. w = r - l;
  2933. // resulting height based on ratio
  2934. h = w * ratio;
  2935. // new bottom position
  2936. b = t + h;
  2937. }
  2938. return {
  2939. x: l,
  2940. y: t,
  2941. width: r - l,
  2942. height: b - t
  2943. };
  2944. },
  2945. 'e': function e(rect, offset, space, ratio) {
  2946. var t, r, b, l, w, h, p, d;
  2947. // left is fixed
  2948. l = rect.x;
  2949. // intended right edge
  2950. r = limit(offset.x, l, space.width);
  2951. // if is too small vertically
  2952. if (r - l < space.min.width) {
  2953. r = l + space.min.width;
  2954. }
  2955. // if should scale by ratio, pick height by ratio of new width
  2956. h = ratio ? (r - l) * ratio : rect.height;
  2957. // check if has fallen below min width or height
  2958. if (h < space.min.height) {
  2959. h = space.min.height;
  2960. r = l + h / ratio;
  2961. }
  2962. // add half to top and bottom
  2963. p = (h - rect.height) * .5;
  2964. t = rect.y - p;
  2965. b = rect.y + rect.height + p;
  2966. // check if any of the edges has moved out of the available space, if so,
  2967. // set max size of rectangle from original position
  2968. if (t < 0 || b > space.height) {
  2969. // smallest distance to edge of space
  2970. d = Math.min(rect.y, space.height - (rect.y + rect.height));
  2971. // new top and bottom offsets
  2972. t = rect.y - d;
  2973. b = rect.y + rect.height + d;
  2974. // resulting height
  2975. h = b - t;
  2976. // resulting width based on ratio
  2977. w = h / ratio;
  2978. // new right position
  2979. r = l + w;
  2980. }
  2981. return {
  2982. x: l,
  2983. y: t,
  2984. width: r - l,
  2985. height: b - t
  2986. };
  2987. },
  2988. 'w': function w(rect, offset, space, ratio) {
  2989. var t, r, b, l, w, h, p, d;
  2990. // right is fixed
  2991. r = rect.x + rect.width;
  2992. // intended left edge
  2993. l = limit(offset.x, 0, r);
  2994. // if is too small vertically
  2995. if (r - l < space.min.width) {
  2996. l = r - space.min.width;
  2997. }
  2998. // if should scale by ratio, pick height by ratio of new width
  2999. h = ratio ? (r - l) * ratio : rect.height;
  3000. // check if has fallen below min width or height
  3001. if (h < space.min.height) {
  3002. h = space.min.height;
  3003. l = r - h / ratio;
  3004. }
  3005. // add half to top and bottom
  3006. p = (h - rect.height) * .5;
  3007. t = rect.y - p;
  3008. b = rect.y + rect.height + p;
  3009. // check if any of the edges has moved out of the available space, if so,
  3010. // set max size of rectangle from original position
  3011. if (t < 0 || b > space.height) {
  3012. // smallest distance to edge of space
  3013. d = Math.min(rect.y, space.height - (rect.y + rect.height));
  3014. // new top and bottom offsets
  3015. t = rect.y - d;
  3016. b = rect.y + rect.height + d;
  3017. // resulting height
  3018. h = b - t;
  3019. // resulting width based on ratio
  3020. w = h / ratio;
  3021. // new right position
  3022. l = r - w;
  3023. }
  3024. return {
  3025. x: l,
  3026. y: t,
  3027. width: r - l,
  3028. height: b - t
  3029. };
  3030. },
  3031. 'ne': function ne(rect, offset, space, ratio) {
  3032. var t, r, b, l, w, h, d;
  3033. // left and bottom are fixed
  3034. l = rect.x;
  3035. b = rect.y + rect.height;
  3036. // intended right edge
  3037. r = limit(offset.x, l, space.width);
  3038. // if is too small vertically
  3039. if (r - l < space.min.width) {
  3040. r = l + space.min.width;
  3041. }
  3042. // if should scale by ratio, pick height by ratio of new width
  3043. h = ratio ? (r - l) * ratio : limit(b - offset.y, space.min.height, b);
  3044. // check if has fallen below min width or height
  3045. if (h < space.min.height) {
  3046. h = space.min.height;
  3047. r = l + h / ratio;
  3048. }
  3049. // add height difference with original height to top
  3050. t = rect.y - (h - rect.height);
  3051. // check if any of the edges has moved out of the available space, if so,
  3052. // set max size of rectangle from original position
  3053. if (t < 0 || b > space.height) {
  3054. // smallest distance to edge of space
  3055. d = Math.min(rect.y, space.height - (rect.y + rect.height));
  3056. // new top and bottom offsets
  3057. t = rect.y - d;
  3058. // resulting height
  3059. h = b - t;
  3060. // resulting width based on ratio
  3061. w = h / ratio;
  3062. // new right position
  3063. r = l + w;
  3064. }
  3065. return {
  3066. x: l,
  3067. y: t,
  3068. width: r - l,
  3069. height: b - t
  3070. };
  3071. },
  3072. 'se': function se(rect, offset, space, ratio) {
  3073. var t, r, b, l, w, h, d;
  3074. // left and top are fixed
  3075. l = rect.x;
  3076. t = rect.y;
  3077. // intended right edge
  3078. r = limit(offset.x, l, space.width);
  3079. // if is too small vertically
  3080. if (r - l < space.min.width) {
  3081. r = l + space.min.width;
  3082. }
  3083. // if should scale by ratio, pick height by ratio of new width
  3084. h = ratio ? (r - l) * ratio : limit(offset.y - rect.y, space.min.height, space.height - t);
  3085. // check if has fallen below min width or height
  3086. if (h < space.min.height) {
  3087. h = space.min.height;
  3088. r = l + h / ratio;
  3089. }
  3090. // add height difference with original height to bottom
  3091. b = rect.y + rect.height + (h - rect.height);
  3092. // check if any of the edges has moved out of the available space, if so,
  3093. // set max size of rectangle from original position
  3094. if (t < 0 || b > space.height) {
  3095. // smallest distance to edge of space
  3096. d = Math.min(rect.y, space.height - (rect.y + rect.height));
  3097. // new bottom offset
  3098. b = rect.y + rect.height + d;
  3099. // resulting height
  3100. h = b - t;
  3101. // resulting width based on ratio
  3102. w = h / ratio;
  3103. // new right position
  3104. r = l + w;
  3105. }
  3106. return {
  3107. x: l,
  3108. y: t,
  3109. width: r - l,
  3110. height: b - t
  3111. };
  3112. },
  3113. 'sw': function sw(rect, offset, space, ratio) {
  3114. var t, r, b, l, w, h, d;
  3115. // right and top are fixed
  3116. r = rect.x + rect.width;
  3117. t = rect.y;
  3118. // intended left edge
  3119. l = limit(offset.x, 0, r);
  3120. // if is too small vertically
  3121. if (r - l < space.min.width) {
  3122. l = r - space.min.width;
  3123. }
  3124. // if should scale by ratio, pick height by ratio of new width
  3125. h = ratio ? (r - l) * ratio : limit(offset.y - rect.y, space.min.height, space.height - t);
  3126. // check if has fallen below min width or height
  3127. if (h < space.min.height) {
  3128. h = space.min.height;
  3129. l = r - h / ratio;
  3130. }
  3131. // add height difference with original height to bottom
  3132. b = rect.y + rect.height + (h - rect.height);
  3133. // check if any of the edges has moved out of the available space, if so,
  3134. // set max size of rectangle from original position
  3135. if (t < 0 || b > space.height) {
  3136. // smallest distance to edge of space
  3137. d = Math.min(rect.y, space.height - (rect.y + rect.height));
  3138. // new bottom offset
  3139. b = rect.y + rect.height + d;
  3140. // resulting height
  3141. h = b - t;
  3142. // resulting width based on ratio
  3143. w = h / ratio;
  3144. // new left position
  3145. l = r - w;
  3146. }
  3147. return {
  3148. x: l,
  3149. y: t,
  3150. width: r - l,
  3151. height: b - t
  3152. };
  3153. },
  3154. 'nw': function nw(rect, offset, space, ratio) {
  3155. var t, r, b, l, w, h, d;
  3156. // right and bottom are fixed
  3157. r = rect.x + rect.width;
  3158. b = rect.y + rect.height;
  3159. // intended left edge
  3160. l = limit(offset.x, 0, r);
  3161. // if is too small vertically
  3162. if (r - l < space.min.width) {
  3163. l = r - space.min.width;
  3164. }
  3165. // if should scale by ratio, pick height by ratio of new width
  3166. h = ratio ? (r - l) * ratio : limit(b - offset.y, space.min.height, b);
  3167. // check if has fallen below min width or height
  3168. if (h < space.min.height) {
  3169. h = space.min.height;
  3170. l = r - h / ratio;
  3171. }
  3172. // add height difference with original height to bottom
  3173. t = rect.y - (h - rect.height);
  3174. // check if any of the edges has moved out of the available space, if so,
  3175. // set max size of rectangle from original position
  3176. if (t < 0 || b > space.height) {
  3177. // smallest distance to edge of space
  3178. d = Math.min(rect.y, space.height - (rect.y + rect.height));
  3179. // new bottom offset
  3180. t = rect.y - d;
  3181. // resulting height
  3182. h = b - t;
  3183. // resulting width based on ratio
  3184. w = h / ratio;
  3185. // new left position
  3186. l = r - w;
  3187. }
  3188. return {
  3189. x: l,
  3190. y: t,
  3191. width: r - l,
  3192. height: b - t
  3193. };
  3194. }
  3195. };
  3196. /**
  3197. * CropArea
  3198. */
  3199. return function () {
  3200. function CropArea() {
  3201. var element = arguments.length <= 0 || arguments[0] === undefined ? document.createElement('div') : arguments[0];
  3202. _classCallCheck(this, CropArea);
  3203. this._element = element;
  3204. this._interaction = null;
  3205. this._minWidth = 0;
  3206. this._minHeight = 0;
  3207. this._ratio = null;
  3208. this._rect = {
  3209. x: 0,
  3210. y: 0,
  3211. width: 0,
  3212. height: 0
  3213. };
  3214. this._space = {
  3215. width: 0,
  3216. height: 0
  3217. };
  3218. this._rectChanged = false;
  3219. this._init();
  3220. }
  3221. _createClass(CropArea, [{
  3222. key: '_init',
  3223. value: function _init() {
  3224. this._element.className = 'slim-crop-area';
  3225. // lines
  3226. var lines = create('div', 'grid');
  3227. this._element.appendChild(lines);
  3228. // corner & edge resize buttons
  3229. for (var handler in resizers) {
  3230. if (!resizers.hasOwnProperty(handler)) {
  3231. continue;
  3232. }
  3233. var _btn = create('button', handler);
  3234. this._element.appendChild(_btn);
  3235. }
  3236. var btn = create('button', 'c');
  3237. this._element.appendChild(btn);
  3238. addEvents(document, Events.DOWN, this);
  3239. }
  3240. }, {
  3241. key: 'reset',
  3242. value: function reset() {
  3243. this._interaction = null;
  3244. this._rect = {
  3245. x: 0,
  3246. y: 0,
  3247. width: 0,
  3248. height: 0
  3249. };
  3250. this._rectChanged = true;
  3251. this._redraw();
  3252. this._element.dispatchEvent(new CustomEvent('change'));
  3253. }
  3254. }, {
  3255. key: 'rescale',
  3256. value: function rescale(scale) {
  3257. // no rescale
  3258. if (scale === 1) {
  3259. return;
  3260. }
  3261. this._interaction = null;
  3262. this._rectChanged = true;
  3263. this._rect.x *= scale;
  3264. this._rect.y *= scale;
  3265. this._rect.width *= scale;
  3266. this._rect.height *= scale;
  3267. this._redraw();
  3268. this._element.dispatchEvent(new CustomEvent('change'));
  3269. }
  3270. }, {
  3271. key: 'limit',
  3272. value: function limit(width, height) {
  3273. this._space = {
  3274. width: width,
  3275. height: height
  3276. };
  3277. }
  3278. }, {
  3279. key: 'resize',
  3280. value: function resize(x, y, width, height) {
  3281. this._interaction = null;
  3282. this._rect = {
  3283. x: x,
  3284. y: y,
  3285. width: limit(width, 0, this._space.width),
  3286. height: limit(height, 0, this._space.height)
  3287. };
  3288. this._rectChanged = true;
  3289. this._redraw();
  3290. this._element.dispatchEvent(new CustomEvent('change'));
  3291. }
  3292. }, {
  3293. key: 'handleEvent',
  3294. value: function handleEvent(e) {
  3295. switch (e.type) {
  3296. case 'touchstart':
  3297. case 'pointerdown':
  3298. case 'mousedown':
  3299. {
  3300. this._onStartDrag(e);
  3301. }
  3302. break;
  3303. case 'touchmove':
  3304. case 'pointermove':
  3305. case 'mousemove':
  3306. {
  3307. this._onDrag(e);
  3308. }
  3309. break;
  3310. case 'touchend':
  3311. case 'touchcancel':
  3312. case 'pointerup':
  3313. case 'mouseup':
  3314. {
  3315. this._onStopDrag(e);
  3316. }
  3317. }
  3318. }
  3319. }, {
  3320. key: '_onStartDrag',
  3321. value: function _onStartDrag(e) {
  3322. // is not my event?
  3323. if (!this._element.contains(e.target)) {
  3324. return;
  3325. }
  3326. e.preventDefault();
  3327. // listen to drag related events
  3328. addEvents(document, Events.MOVE, this);
  3329. addEvents(document, Events.UP, this);
  3330. // now interacting with control
  3331. this._interaction = {
  3332. type: e.target.className,
  3333. offset: getEventOffsetLocal(e, this._element)
  3334. };
  3335. // now dragging
  3336. this._element.setAttribute('data-dragging', 'true');
  3337. // start the redraw update loop
  3338. this._redraw();
  3339. }
  3340. }, {
  3341. key: '_onDrag',
  3342. value: function _onDrag(e) {
  3343. e.preventDefault();
  3344. // get local offset for this event
  3345. var offset = getEventOffsetLocal(e, this._element.parentNode);
  3346. var type = this._interaction.type;
  3347. // drag
  3348. if (type === 'c') {
  3349. this._rect.x = limit(offset.x - this._interaction.offset.x, 0, this._space.width - this._rect.width);
  3350. this._rect.y = limit(offset.y - this._interaction.offset.y, 0, this._space.height - this._rect.height);
  3351. }
  3352. // resize
  3353. else if (resizers[type]) {
  3354. this._rect = resizers[type](this._rect, offset, {
  3355. x: 0,
  3356. y: 0,
  3357. width: this._space.width,
  3358. height: this._space.height,
  3359. min: {
  3360. width: this._minWidth,
  3361. height: this._minHeight
  3362. }
  3363. }, this._ratio);
  3364. }
  3365. this._rectChanged = true;
  3366. // dispatch
  3367. this._element.dispatchEvent(new CustomEvent('input'));
  3368. }
  3369. }, {
  3370. key: '_onStopDrag',
  3371. value: function _onStopDrag(e) {
  3372. e.preventDefault();
  3373. // stop listening to drag related events
  3374. removeEvents(document, Events.MOVE, this);
  3375. removeEvents(document, Events.UP, this);
  3376. // no longer interacting, so no need to redraw
  3377. this._interaction = null;
  3378. // now dragging
  3379. this._element.setAttribute('data-dragging', 'false');
  3380. // fire change event
  3381. this._element.dispatchEvent(new CustomEvent('change'));
  3382. }
  3383. }, {
  3384. key: '_redraw',
  3385. value: function _redraw() {
  3386. var _this = this;
  3387. if (this._rectChanged) {
  3388. this._element.style.cssText = '\n\t\t\t\t\tleft:' + this._rect.x + 'px;\n\t\t\t\t\ttop:' + this._rect.y + 'px;\n\t\t\t\t\twidth:' + this._rect.width + 'px;\n\t\t\t\t\theight:' + this._rect.height + 'px;\n\t\t\t\t';
  3389. this._rectChanged = false;
  3390. }
  3391. // if no longer interacting with crop area stop here
  3392. if (!this._interaction) {
  3393. return;
  3394. }
  3395. // redraw
  3396. requestAnimationFrame(function () {
  3397. return _this._redraw();
  3398. });
  3399. }
  3400. }, {
  3401. key: 'destroy',
  3402. value: function destroy() {
  3403. this._interaction = false;
  3404. this._rectChanged = false;
  3405. removeEvents(document, Events.DOWN, this);
  3406. removeEvents(document, Events.MOVE, this);
  3407. removeEvents(document, Events.UP, this);
  3408. removeElement(this._element);
  3409. }
  3410. }, {
  3411. key: 'element',
  3412. get: function get() {
  3413. return this._element;
  3414. }
  3415. }, {
  3416. key: 'space',
  3417. get: function get() {
  3418. return this._space;
  3419. }
  3420. }, {
  3421. key: 'area',
  3422. get: function get() {
  3423. return {
  3424. x: this._rect.x / this._space.width,
  3425. y: this._rect.y / this._space.height,
  3426. width: this._rect.width / this._space.width,
  3427. height: this._rect.height / this._space.height
  3428. };
  3429. }
  3430. }, {
  3431. key: 'dirty',
  3432. get: function get() {
  3433. return this._rect.x !== 0 || this._rect.y !== 0 || this._rect.width !== 0 || this._rect.height !== 0;
  3434. }
  3435. }, {
  3436. key: 'minWidth',
  3437. set: function set(value) {
  3438. this._minWidth = value;
  3439. }
  3440. }, {
  3441. key: 'minHeight',
  3442. set: function set(value) {
  3443. this._minHeight = value;
  3444. }
  3445. }, {
  3446. key: 'ratio',
  3447. set: function set(value) {
  3448. this._ratio = value;
  3449. }
  3450. }]);
  3451. return CropArea;
  3452. }();
  3453. }();
  3454. var ImageEditor = function () {
  3455. /**
  3456. * ImageEditor
  3457. * @param element
  3458. * @param options
  3459. * @constructor
  3460. */
  3461. var CropAreaEvents = ['input', 'change'];
  3462. var ImageEditor = function () {
  3463. function ImageEditor() {
  3464. var element = arguments.length <= 0 || arguments[0] === undefined ? document.createElement('div') : arguments[0];
  3465. var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
  3466. _classCallCheck(this, ImageEditor);
  3467. this._element = element;
  3468. this._options = mergeOptions(ImageEditor.options(), options);
  3469. this._ratio = null;
  3470. this._output = null;
  3471. this._input = null;
  3472. this._preview = null;
  3473. this._previewBlurred = null;
  3474. this._blurredPreview = false;
  3475. this._cropper = null;
  3476. this._previewWrapper = null;
  3477. this._currentWindowSize = {};
  3478. this._btnGroup = null;
  3479. this._dirty = false;
  3480. this._wrapperRotation = 0;
  3481. this._wrapperScale = 1.0;
  3482. this._init();
  3483. }
  3484. _createClass(ImageEditor, [{
  3485. key: '_init',
  3486. value: function _init() {
  3487. var _this2 = this;
  3488. this._element.className = 'slim-image-editor';
  3489. // container
  3490. this._container = create('div', 'slim-container');
  3491. // wrapper
  3492. this._wrapper = create('div', 'slim-wrapper');
  3493. // photo crop mark container
  3494. this._stage = create('div', 'slim-stage');
  3495. this._container.appendChild(this._stage);
  3496. // create crop marks
  3497. this._cropper = new CropArea();
  3498. CropAreaEvents.forEach(function (e) {
  3499. _this2._cropper.element.addEventListener(e, _this2);
  3500. });
  3501. this._stage.appendChild(this._cropper.element);
  3502. // canvas ghost
  3503. this._previewWrapper = create('div', 'slim-image-editor-preview slim-crop-preview');
  3504. this._previewBlurred = create('canvas', 'slim-crop-blur');
  3505. this._previewWrapper.appendChild(this._previewBlurred);
  3506. this._wrapper.appendChild(this._previewWrapper);
  3507. this._preview = create('img', 'slim-crop');
  3508. this._previewWrapper.appendChild(this._preview);
  3509. // buttons
  3510. this._btnGroup = create('div', 'slim-editor-btn-group');
  3511. ImageEditor.Buttons.forEach(function (c) {
  3512. var prop = capitalizeFirstLetter(c);
  3513. var label = _this2._options['button' + prop + 'Label'];
  3514. var title = _this2._options['button' + prop + 'Title'];
  3515. var className = _this2._options['button' + prop + 'ClassName'];
  3516. var btn = create('button', 'slim-editor-btn slim-btn-' + c + (className ? ' ' + className : ''));
  3517. btn.innerHTML = label;
  3518. btn.title = title || label;
  3519. btn.type = 'button';
  3520. btn.setAttribute('data-action', c);
  3521. btn.addEventListener('click', _this2);
  3522. _this2._btnGroup.appendChild(btn);
  3523. });
  3524. // utils
  3525. this._utilsGroup = create('div', 'slim-editor-utils-group');
  3526. // create rotation button
  3527. var btn = create('button', 'slim-editor-utils-btn slim-btn-rotate');
  3528. btn.setAttribute('data-action', 'rotate');
  3529. btn.addEventListener('click', this);
  3530. btn.title = this._options.buttonRotateTitle;
  3531. this._utilsGroup.appendChild(btn);
  3532. this._container.appendChild(this._wrapper);
  3533. this._element.appendChild(this._container);
  3534. this._element.appendChild(this._utilsGroup);
  3535. this._element.appendChild(this._btnGroup);
  3536. }
  3537. }, {
  3538. key: 'dirty',
  3539. value: function dirty() {
  3540. this._dirty = true;
  3541. }
  3542. }, {
  3543. key: 'handleEvent',
  3544. value: function handleEvent(e) {
  3545. switch (e.type) {
  3546. case 'click':
  3547. this._onClick(e);
  3548. break;
  3549. case 'change':
  3550. this._onGridChange(e);
  3551. break;
  3552. case 'input':
  3553. this._onGridInput(e);
  3554. break;
  3555. case 'keydown':
  3556. this._onKeyDown(e);
  3557. break;
  3558. case 'resize':
  3559. this._onResize(e);
  3560. break;
  3561. }
  3562. }
  3563. }, {
  3564. key: '_onKeyDown',
  3565. value: function _onKeyDown(e) {
  3566. switch (e.keyCode) {
  3567. case Key.RETURN:
  3568. {
  3569. this._confirm();
  3570. break;
  3571. }
  3572. case Key.ESC:
  3573. {
  3574. this._cancel();
  3575. break;
  3576. }
  3577. }
  3578. }
  3579. }, {
  3580. key: '_onClick',
  3581. value: function _onClick(e) {
  3582. if (e.target.classList.contains('slim-btn-cancel')) {
  3583. this._cancel();
  3584. }
  3585. if (e.target.classList.contains('slim-btn-confirm')) {
  3586. this._confirm();
  3587. }
  3588. if (e.target.classList.contains('slim-btn-rotate')) {
  3589. this._rotate();
  3590. }
  3591. }
  3592. }, {
  3593. key: '_onResize',
  3594. value: function _onResize() {
  3595. // remember window size
  3596. this._currentWindowSize = {
  3597. width: window.innerWidth,
  3598. height: window.innerHeight
  3599. };
  3600. // redraw the image editor based on new dimensions
  3601. this._redraw();
  3602. this._redrawCropper(this._cropper.area);
  3603. this._updateWrapperScale();
  3604. // apply rotation and scale
  3605. this._redrawWrapper();
  3606. }
  3607. }, {
  3608. key: '_redrawWrapper',
  3609. value: function _redrawWrapper() {
  3610. var matrix = snabbt.createMatrix();
  3611. matrix.scale(this._wrapperScale, this._wrapperScale);
  3612. matrix.rotateZ(this._wrapperRotation * (Math.PI / 180));
  3613. snabbt.setElementTransform(this._previewWrapper, matrix);
  3614. }
  3615. }, {
  3616. key: '_onGridInput',
  3617. value: function _onGridInput() {
  3618. this._redrawCropMask();
  3619. }
  3620. }, {
  3621. key: '_onGridChange',
  3622. value: function _onGridChange() {
  3623. this._redrawCropMask();
  3624. }
  3625. }, {
  3626. key: '_updateWrapperRotation',
  3627. value: function _updateWrapperRotation() {
  3628. if (this._options.minSize.width > this._input.height || this._options.minSize.height > this._input.width) {
  3629. this._wrapperRotation += 180;
  3630. } else {
  3631. this._wrapperRotation += 90;
  3632. }
  3633. }
  3634. }, {
  3635. key: '_updateWrapperScale',
  3636. value: function _updateWrapperScale() {
  3637. // test if is tilted
  3638. var isTilted = this._wrapperRotation % 180 !== 0;
  3639. // if tilted determine correct scale factor
  3640. if (isTilted) {
  3641. // maximum size
  3642. var maxWidth = this._container.offsetWidth;
  3643. var maxHeight = this._container.offsetHeight;
  3644. // get wrapper size
  3645. var wrapperWidth = this._wrapper.offsetHeight;
  3646. var wrapperHeight = this._wrapper.offsetWidth;
  3647. var scalar = maxWidth / wrapperWidth;
  3648. if (scalar * wrapperHeight > maxHeight) {
  3649. scalar = maxHeight / wrapperHeight;
  3650. }
  3651. this._wrapperScale = scalar;
  3652. } else {
  3653. this._wrapperScale = 1.0;
  3654. }
  3655. }
  3656. /**
  3657. * Action handlers
  3658. */
  3659. }, {
  3660. key: '_cancel',
  3661. value: function _cancel() {
  3662. this._element.dispatchEvent(new CustomEvent('cancel'));
  3663. }
  3664. }, {
  3665. key: '_confirm',
  3666. value: function _confirm() {
  3667. var isTilted = this._wrapperRotation % 180 !== 0;
  3668. this._element.dispatchEvent(new CustomEvent('confirm', {
  3669. detail: {
  3670. rotation: this._wrapperRotation % 360,
  3671. crop: scaleRect(this._cropper.area, isTilted ? this._input.height : this._input.width, isTilted ? this._input.width : this._input.height)
  3672. }
  3673. }));
  3674. }
  3675. }, {
  3676. key: '_rotate',
  3677. value: function _rotate() {
  3678. var _this3 = this;
  3679. // rotate!
  3680. this._updateWrapperRotation();
  3681. // wrapper might also need to be scaled
  3682. this._updateWrapperScale();
  3683. // hide the mask
  3684. this._clearCropMask();
  3685. // hide the cropper
  3686. this._hideCropper();
  3687. // rotation effect
  3688. snabbt(this._previewWrapper, {
  3689. rotation: [0, 0, this._wrapperRotation * (Math.PI / 180)],
  3690. scale: [this._wrapperScale, this._wrapperScale],
  3691. easing: 'spring',
  3692. springConstant: .8,
  3693. springDeceleration: .65,
  3694. complete: function complete() {
  3695. // redraws auto cropper
  3696. _this3._redrawCropper();
  3697. // shows the cropper
  3698. _this3._showCropper();
  3699. }
  3700. });
  3701. }
  3702. }, {
  3703. key: '_showCropper',
  3704. value: function _showCropper() {
  3705. snabbt(this._stage, {
  3706. easing: 'ease',
  3707. duration: 250,
  3708. fromOpacity: 0,
  3709. opacity: 1.0
  3710. });
  3711. }
  3712. }, {
  3713. key: '_hideCropper',
  3714. value: function _hideCropper() {
  3715. snabbt(this._stage, {
  3716. duration: 0,
  3717. fromOpacity: 0,
  3718. opacity: 0
  3719. });
  3720. }
  3721. }, {
  3722. key: '_clearCropMask',
  3723. value: function _clearCropMask() {
  3724. this._preview.style.clip = '';
  3725. }
  3726. }, {
  3727. key: '_redrawCropMask',
  3728. value: function _redrawCropMask() {
  3729. var _this4 = this;
  3730. // get rotation
  3731. var rotation = this._wrapperRotation % 360;
  3732. // get image size
  3733. var canvas = {
  3734. width: this._wrapper.offsetWidth,
  3735. height: this._wrapper.offsetHeight
  3736. };
  3737. // get default mask cropper area
  3738. var mask = this._cropper.area;
  3739. var px = mask.x;
  3740. var py = mask.y;
  3741. var pw = mask.width;
  3742. var ph = mask.height;
  3743. // rotate cropper area
  3744. if (rotation === 90) {
  3745. mask.x = 1 - py - ph;
  3746. mask.y = px;
  3747. mask.width = ph;
  3748. mask.height = pw;
  3749. } else if (rotation === 180) {
  3750. mask.x = 1 - (px + pw);
  3751. mask.y = 1 - (py + ph);
  3752. mask.width = pw;
  3753. mask.height = ph;
  3754. } else if (rotation === 270) {
  3755. mask.x = py;
  3756. mask.y = 1 - px - pw;
  3757. mask.width = ph;
  3758. mask.height = pw;
  3759. }
  3760. // scale rect to fit canvas
  3761. mask = scaleRect(mask, canvas.width, canvas.height);
  3762. // update on next frame (so it's in sync with crop area redraw)
  3763. requestAnimationFrame(function () {
  3764. // show only crop area of original image
  3765. _this4._preview.style.clip = 'rect(\n\t\t\t\t\t' + mask.y + 'px,\n\t\t\t\t\t' + (mask.x + mask.width) + 'px,\n\t\t\t\t\t' + (mask.y + mask.height) + 'px,\n\t\t\t\t\t' + mask.x + 'px)\n\t\t\t\t';
  3766. });
  3767. }
  3768. }, {
  3769. key: 'open',
  3770. value: function open(image, ratio, crop, rotation, complete) {
  3771. var _this5 = this;
  3772. // test if is same image
  3773. if (this._input && !this._dirty) {
  3774. complete();
  3775. return;
  3776. }
  3777. // reset dirty value
  3778. this._dirty = false;
  3779. // reset rotation
  3780. this._wrapperRotation = rotation || 0;
  3781. // we'll always have to blur the preview of a newly opened image
  3782. this._blurredPreview = false;
  3783. // set ratio
  3784. this._ratio = ratio;
  3785. // hide editor
  3786. this._element.style.opacity = '0';
  3787. // set as new input image
  3788. this._input = image;
  3789. // calculate crop
  3790. var tilted = rotation % 180 !== 0;
  3791. var relativeCrop = divideRect(crop, tilted ? image.height : image.width, tilted ? image.width : image.height);
  3792. // preview has now loaded
  3793. this._preview.onload = function () {
  3794. // reset onload listener
  3795. _this5._preview.onload = null;
  3796. // setup cropper
  3797. _this5._cropper.ratio = _this5.ratio;
  3798. // redraw view (for first time)
  3799. _this5._redraw();
  3800. // redraw cropper
  3801. _this5._redrawCropper(relativeCrop);
  3802. // done
  3803. complete();
  3804. // fade in
  3805. _this5._element.style.opacity = '';
  3806. };
  3807. // load lower resolution preview image
  3808. this._preview.src = cloneCanvasScaled(this._input, Math.min(this._container.offsetWidth / this._input.width, this._container.offsetHeight / this._input.height)).toDataURL();
  3809. }
  3810. }, {
  3811. key: '_redrawCropper',
  3812. value: function _redrawCropper(rect) {
  3813. var isTilted = this._wrapperRotation % 180 !== 0;
  3814. // get dimensions of image wrapper
  3815. var width = this._wrapper.offsetWidth;
  3816. var height = this._wrapper.offsetHeight;
  3817. // get space
  3818. var maxWidth = this._container.offsetWidth;
  3819. var maxHeight = this._container.offsetHeight;
  3820. // rescale wrapper
  3821. this._updateWrapperScale();
  3822. // position cropper container to fit wrapper
  3823. var sw = this._wrapperScale * (isTilted ? height : width);
  3824. var sh = this._wrapperScale * (isTilted ? width : height);
  3825. var sx = isTilted ? (maxWidth - sw) * .5 : this._wrapper.offsetLeft;
  3826. var sy = isTilted ? (maxHeight - sh) * .5 : this._wrapper.offsetTop;
  3827. this._stage.style.cssText = '\n\t\t\t\tleft:' + sx + 'px;\n\t\t\t\ttop:' + sy + 'px;\n\t\t\t\twidth:' + sw + 'px;\n\t\t\t\theight:' + sh + 'px;\n\t\t\t';
  3828. // set new size limit for crops
  3829. this._cropper.limit(sw, sh);
  3830. // set min and max height of cropper
  3831. this._cropper.minWidth = this._wrapperScale * this._options.minSize.width * this.scalar;
  3832. this._cropper.minHeight = this._wrapperScale * this._options.minSize.height * this.scalar;
  3833. // set crop rect
  3834. var crop = null;
  3835. if (rect) {
  3836. crop = {
  3837. x: rect.x * sw,
  3838. y: rect.y * sh,
  3839. width: rect.width * sw,
  3840. height: rect.height * sh
  3841. };
  3842. } else {
  3843. crop = getAutoCropRect(sw, sh, this._ratio || sh / sw);
  3844. }
  3845. this._cropper.resize(crop.x, crop.y, crop.width, crop.height);
  3846. // resize of cropper will automatically trigger a change event which will update the blur mask
  3847. }
  3848. }, {
  3849. key: '_redraw',
  3850. value: function _redraw() {
  3851. // image ratio
  3852. var ratio = this._input.height / this._input.width;
  3853. // determine rounded size
  3854. var maxWidth = this._container.clientWidth;
  3855. var maxHeight = this._container.clientHeight;
  3856. var width = maxWidth;
  3857. var height = width * ratio;
  3858. if (height > maxHeight) {
  3859. height = maxHeight;
  3860. width = height / ratio;
  3861. }
  3862. width = Math.round(width);
  3863. height = Math.round(height);
  3864. // rescale and recenter wrapper
  3865. var x = (maxWidth - width) / 2;
  3866. var y = (maxHeight - height) / 2;
  3867. this._wrapper.style.cssText = '\n\t\t\t\tleft:' + x + 'px;\n\t\t\t\ttop:' + y + 'px;\n\t\t\t\twidth:' + width + 'px;\n\t\t\t\theight:' + height + 'px;\n\t\t\t';
  3868. // set image size based on container dimensions
  3869. this._previewBlurred.style.cssText = '\n\t\t\t\twidth:' + width + 'px;\n\t\t\t\theight:' + height + 'px;\n\t\t\t';
  3870. this._preview.width = width;
  3871. this._preview.height = height;
  3872. // scale and blur the blurry preview
  3873. if (!this._blurredPreview) {
  3874. this._previewBlurred.width = 300;
  3875. this._previewBlurred.height = this._previewBlurred.width * ratio;
  3876. copyCanvas(this._input, this._previewBlurred);
  3877. blurCanvas(this._previewBlurred, 3);
  3878. this._blurredPreview = true;
  3879. }
  3880. }
  3881. }, {
  3882. key: 'show',
  3883. value: function show() {
  3884. var callback = arguments.length <= 0 || arguments[0] === undefined ? function () {} : arguments[0];
  3885. // test if window size has changed since previous showing
  3886. if (this._currentWindowSize.width !== window.innerWidth || this._currentWindowSize.height !== window.innerHeight) {
  3887. // if so, reposition elements
  3888. this._redraw();
  3889. // redraw cropper position
  3890. this._redrawCropper(this._cropper.area);
  3891. }
  3892. // listen to keydown so we can close or confirm with RETURN / ESC
  3893. document.addEventListener('keydown', this);
  3894. // when window is scaled we want to resize the editor
  3895. window.addEventListener('resize', this);
  3896. // fade in preview
  3897. var rotation = this._wrapperRotation * (Math.PI / 180);
  3898. snabbt(this._previewWrapper, {
  3899. fromRotation: [0, 0, rotation],
  3900. rotation: [0, 0, rotation],
  3901. fromPosition: [0, 0, 0],
  3902. position: [0, 0, 0],
  3903. fromOpacity: 0,
  3904. opacity: .9999, // fixes IE render bug
  3905. fromScale: [this._wrapperScale - .02, this._wrapperScale - .02],
  3906. scale: [this._wrapperScale, this._wrapperScale],
  3907. easing: 'spring',
  3908. springConstant: .3,
  3909. springDeceleration: .85,
  3910. delay: 450,
  3911. complete: function complete() {
  3912. //resetTransforms(this);
  3913. }
  3914. });
  3915. if (this._cropper.dirty) {
  3916. // now show cropper
  3917. snabbt(this._stage, {
  3918. fromPosition: [0, 0, 0],
  3919. position: [0, 0, 0],
  3920. fromOpacity: 0,
  3921. opacity: 1,
  3922. duration: 250,
  3923. delay: 550,
  3924. complete: function complete() {
  3925. resetTransforms(this);
  3926. callback();
  3927. }
  3928. });
  3929. } else {
  3930. // now show cropper
  3931. snabbt(this._stage, {
  3932. fromPosition: [0, 0, 0],
  3933. position: [0, 0, 0],
  3934. fromOpacity: 0,
  3935. opacity: 1,
  3936. duration: 250,
  3937. delay: 1000,
  3938. complete: function complete() {
  3939. resetTransforms(this);
  3940. }
  3941. });
  3942. }
  3943. // now show buttons
  3944. snabbt(this._btnGroup.childNodes, {
  3945. fromScale: [.9, .9],
  3946. scale: [1, 1],
  3947. fromOpacity: 0,
  3948. opacity: 1,
  3949. delay: function delay(i) {
  3950. return 1000 + i * 100;
  3951. },
  3952. easing: 'spring',
  3953. springConstant: .3,
  3954. springDeceleration: .85,
  3955. complete: function complete() {
  3956. resetTransforms(this);
  3957. }
  3958. });
  3959. snabbt(this._utilsGroup.childNodes, {
  3960. fromScale: [.9, .9],
  3961. scale: [1, 1],
  3962. fromOpacity: 0,
  3963. opacity: 1,
  3964. easing: 'spring',
  3965. springConstant: .3,
  3966. springDeceleration: .85,
  3967. delay: 1250,
  3968. complete: function complete() {
  3969. resetTransforms(this);
  3970. }
  3971. });
  3972. }
  3973. }, {
  3974. key: 'hide',
  3975. value: function hide() {
  3976. var callback = arguments.length <= 0 || arguments[0] === undefined ? function () {} : arguments[0];
  3977. // no more need to listen to keydown
  3978. document.removeEventListener('keydown', this);
  3979. // no more need to resize editor when window is scaled
  3980. window.removeEventListener('resize', this);
  3981. // fade out buttons
  3982. snabbt(this._utilsGroup.childNodes, {
  3983. fromOpacity: 1,
  3984. opacity: 0,
  3985. duration: 250
  3986. });
  3987. snabbt(this._btnGroup.childNodes, {
  3988. fromOpacity: 1,
  3989. opacity: 0,
  3990. delay: 200,
  3991. duration: 350
  3992. });
  3993. snabbt([this._stage, this._previewWrapper], {
  3994. fromPosition: [0, 0, 0],
  3995. position: [0, -250, 0],
  3996. fromOpacity: 1,
  3997. opacity: 0,
  3998. easing: 'spring',
  3999. springConstant: .3,
  4000. springDeceleration: .75,
  4001. delay: 250,
  4002. allDone: function allDone() {
  4003. callback();
  4004. }
  4005. });
  4006. }
  4007. }, {
  4008. key: 'destroy',
  4009. value: function destroy() {
  4010. var _this6 = this;
  4011. nodeListToArray(this._btnGroup.children).forEach(function (btn) {
  4012. btn.removeEventListener('click', _this6);
  4013. });
  4014. CropAreaEvents.forEach(function (e) {
  4015. _this6._cropper.element.removeEventListener(e, _this6);
  4016. });
  4017. this._cropper.destroy();
  4018. removeElement(this._element);
  4019. }
  4020. }, {
  4021. key: 'element',
  4022. get: function get() {
  4023. return this._element;
  4024. }
  4025. }, {
  4026. key: 'ratio',
  4027. get: function get() {
  4028. if (this._ratio === 'input') {
  4029. return this._input.height / this._input.width;
  4030. }
  4031. return this._ratio;
  4032. }
  4033. }, {
  4034. key: 'offset',
  4035. get: function get() {
  4036. return this._element.getBoundingClientRect();
  4037. }
  4038. }, {
  4039. key: 'original',
  4040. get: function get() {
  4041. return this._input;
  4042. }
  4043. }, {
  4044. key: 'scalar',
  4045. get: function get() {
  4046. return this._preview.width / this._input.width;
  4047. }
  4048. }], [{
  4049. key: 'options',
  4050. value: function options() {
  4051. return {
  4052. buttonCancelClassName: null,
  4053. buttonConfirmClassName: null,
  4054. buttonCancelLabel: 'Cancel',
  4055. buttonConfirmLabel: 'Confirm',
  4056. buttonCancelTitle: null,
  4057. buttonConfirmTitle: null,
  4058. buttonRotateTitle: 'Rotate',
  4059. minSize: {
  4060. width: 0,
  4061. height: 0
  4062. }
  4063. };
  4064. }
  4065. }]);
  4066. return ImageEditor;
  4067. }();
  4068. ImageEditor.Buttons = ['cancel', 'confirm'];
  4069. return ImageEditor;
  4070. }(CropArea);
  4071. var FileHopper = function () {
  4072. /**
  4073. * FileHopper
  4074. * @param element
  4075. * @param options
  4076. * @constructor
  4077. */
  4078. var DragDropEvents = ['dragover', 'dragleave', 'drop'];
  4079. return function () {
  4080. function FileHopper() {
  4081. var element = arguments.length <= 0 || arguments[0] === undefined ? document.createElement('div') : arguments[0];
  4082. _classCallCheck(this, FileHopper);
  4083. this._element = element;
  4084. this._accept = [];
  4085. this._dragPath = null;
  4086. this._init();
  4087. }
  4088. // public properties
  4089. _createClass(FileHopper, [{
  4090. key: 'areValidFiles',
  4091. value: function areValidFiles(files) {
  4092. if (this._accept.length && files) {
  4093. return this._accept.indexOf(files[0].type) != -1;
  4094. }
  4095. return true;
  4096. }
  4097. // public methods
  4098. }, {
  4099. key: 'reset',
  4100. value: function reset() {
  4101. this._element.files = null;
  4102. }
  4103. // private
  4104. }, {
  4105. key: '_init',
  4106. value: function _init() {
  4107. var _this7 = this;
  4108. this._element.className = 'slim-file-hopper';
  4109. DragDropEvents.forEach(function (e) {
  4110. _this7._element.addEventListener(e, _this7);
  4111. });
  4112. }
  4113. // the input has changed
  4114. }, {
  4115. key: 'handleEvent',
  4116. value: function handleEvent(e) {
  4117. switch (e.type) {
  4118. case 'dragover':
  4119. this._onDragOver(e);
  4120. break;
  4121. case 'dragleave':
  4122. this._onDragLeave(e);
  4123. break;
  4124. case 'drop':
  4125. this._onDrop(e);
  4126. break;
  4127. }
  4128. }
  4129. }, {
  4130. key: '_onDrop',
  4131. value: function _onDrop(e) {
  4132. // prevents browser from opening image
  4133. e.preventDefault();
  4134. // test type in older browsers
  4135. if (!this.areValidFiles(e.dataTransfer.files)) {
  4136. // invalid files, stop here
  4137. this._element.dispatchEvent(new CustomEvent('file-invalid-drop'));
  4138. // no longer hovering
  4139. this._dragPath = null;
  4140. return;
  4141. }
  4142. // store dropped files on element files property, so can be accessed by same function as file input handler
  4143. this._element.files = e.dataTransfer.files;
  4144. // file has been dropped
  4145. this._element.dispatchEvent(new CustomEvent('file-drop', {
  4146. detail: getOffsetByEvent(e)
  4147. }));
  4148. // file list has changed, let's notify others
  4149. this._element.dispatchEvent(new CustomEvent('change'));
  4150. // no longer hovering
  4151. this._dragPath = null;
  4152. }
  4153. }, {
  4154. key: '_onDragOver',
  4155. value: function _onDragOver(e) {
  4156. // prevents browser from opening image
  4157. e.preventDefault();
  4158. e.dataTransfer.dropEffect = 'copy';
  4159. if (!this.areValidFiles(e.dataTransfer.items)) {
  4160. // indicate drop mode to user
  4161. e.dataTransfer.dropEffect = 'none';
  4162. // invalid files, stop here
  4163. this._element.dispatchEvent(new CustomEvent('file-invalid'));
  4164. return;
  4165. }
  4166. // now hovering file over the area
  4167. if (!this._dragPath) {
  4168. this._dragPath = [];
  4169. }
  4170. // push location
  4171. this._dragPath.push(getOffsetByEvent(e));
  4172. // file is hovering over element
  4173. this._element.dispatchEvent(new CustomEvent('file-over', {
  4174. detail: {
  4175. x: last(this._dragPath).x,
  4176. y: last(this._dragPath).y
  4177. }
  4178. }));
  4179. }
  4180. }, {
  4181. key: '_onDragLeave',
  4182. value: function _onDragLeave(e) {
  4183. // user has dragged file out of element area
  4184. this._element.dispatchEvent(new CustomEvent('file-out', {
  4185. detail: getOffsetByEvent(e)
  4186. }));
  4187. // now longer hovering file
  4188. this._dragPath = null;
  4189. }
  4190. }, {
  4191. key: 'destroy',
  4192. value: function destroy() {
  4193. var _this8 = this;
  4194. DragDropEvents.forEach(function (e) {
  4195. _this8._element.removeEventListener(e, _this8);
  4196. });
  4197. removeElement(this._element);
  4198. }
  4199. }, {
  4200. key: 'element',
  4201. get: function get() {
  4202. return this._element;
  4203. }
  4204. }, {
  4205. key: 'dragPath',
  4206. get: function get() {
  4207. return this._dragPath;
  4208. }
  4209. }, {
  4210. key: 'enabled',
  4211. get: function get() {
  4212. return this._element.style.display === '';
  4213. },
  4214. set: function set(value) {
  4215. this._element.style.display = value ? '' : 'none';
  4216. }
  4217. }, {
  4218. key: 'accept',
  4219. set: function set(mimetypes) {
  4220. this._accept = mimetypes;
  4221. },
  4222. get: function get() {
  4223. return this._accept;
  4224. }
  4225. }]);
  4226. return FileHopper;
  4227. }();
  4228. }();
  4229. var Popover = function () {
  4230. /**
  4231. * Popover
  4232. */
  4233. return function () {
  4234. function Popover() {
  4235. _classCallCheck(this, Popover);
  4236. this._element = null;
  4237. this._inner = null;
  4238. this._init();
  4239. }
  4240. _createClass(Popover, [{
  4241. key: '_init',
  4242. value: function _init() {
  4243. this._element = create('div', 'slim-popover');
  4244. this._element.setAttribute('data-state', 'off');
  4245. document.body.appendChild(this._element);
  4246. }
  4247. }, {
  4248. key: 'show',
  4249. value: function show() {
  4250. var _this9 = this;
  4251. var callback = arguments.length <= 0 || arguments[0] === undefined ? function () {} : arguments[0];
  4252. // turn on
  4253. this._element.setAttribute('data-state', 'on');
  4254. // show and animate in
  4255. snabbt(this._element, {
  4256. fromOpacity: 0,
  4257. opacity: 1,
  4258. duration: 350,
  4259. complete: function complete() {
  4260. // clean up transforms
  4261. resetTransforms(_this9._element);
  4262. // ready!
  4263. callback();
  4264. }
  4265. });
  4266. }
  4267. }, {
  4268. key: 'hide',
  4269. value: function hide() {
  4270. var _this10 = this;
  4271. var callback = arguments.length <= 0 || arguments[0] === undefined ? function () {} : arguments[0];
  4272. // animate out and hide
  4273. snabbt(this._element, {
  4274. fromOpacity: 1,
  4275. opacity: 0,
  4276. duration: 500,
  4277. complete: function complete() {
  4278. // clean up transforms
  4279. resetTransforms(_this10._element);
  4280. // hide the editor
  4281. _this10._element.setAttribute('data-state', 'off');
  4282. // ready!
  4283. callback();
  4284. }
  4285. });
  4286. }
  4287. }, {
  4288. key: 'destroy',
  4289. value: function destroy() {
  4290. if (!this._element.parentNode) {
  4291. return;
  4292. }
  4293. this._element.parentNode.removeChild(this._element);
  4294. }
  4295. }, {
  4296. key: 'inner',
  4297. set: function set(element) {
  4298. this._inner = element;
  4299. if (this._element.firstChild) {
  4300. this._element.removeChild(this._element.firstChild);
  4301. }
  4302. this._element.appendChild(this._inner);
  4303. }
  4304. }]);
  4305. return Popover;
  4306. }();
  4307. }();
  4308. var intSplit = function intSplit(v, c) {
  4309. return v.split(c).map(function (v) {
  4310. return parseInt(v, 10);
  4311. });
  4312. };
  4313. var isWrapper = function isWrapper(element) {
  4314. return element.nodeName === 'DIV';
  4315. };
  4316. var CropType = {
  4317. AUTO: 'auto',
  4318. INITIAL: 'initial',
  4319. MANUAL: 'manual'
  4320. };
  4321. var Rect = ['x', 'y', 'width', 'height'];
  4322. var HopperEvents = ['file-invalid-drop', 'file-invalid', 'file-drop', 'file-over', 'file-out', 'click'];
  4323. var ImageEditorEvents = ['cancel', 'confirm'];
  4324. var SlimButtons = ['remove', 'edit', 'download', 'upload'];
  4325. var SlimPopover = null;
  4326. var SlimCount = 0;
  4327. var SlimLoaderHTML = '\n<div class="slim-loader">\n\t<svg>\n\t\t<path class="slim-loader-background" fill="none" stroke-width="3" />\n\t\t<path class="slim-loader-foreground" fill="none" stroke-width="3" />\n\t</svg>\n</div>\n';
  4328. var SlimUploadStatusHTML = '\n<div class="slim-upload-status"></div>\n';
  4329. var Slim = function () {
  4330. function Slim(element) {
  4331. var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
  4332. _classCallCheck(this, Slim);
  4333. // create popover element if not already created
  4334. if (!SlimPopover) {
  4335. SlimPopover = new Popover();
  4336. }
  4337. // we create a new counter, we use this counter to determine if we need to clean up the popover
  4338. SlimCount++;
  4339. // the options to use
  4340. this._options = mergeOptions(Slim.options(), options);
  4341. // reference to original element so we can restore original situation
  4342. this._originalElement = element;
  4343. this._originalElementInner = element.innerHTML;
  4344. this._originalElementAttributes = getElementAttributes(element);
  4345. // should be file input, image or slim wrapper, if not wrapper, wrap
  4346. if (!isWrapper(element)) {
  4347. this._element = wrap(element);
  4348. this._element.className = element.className;
  4349. element.className = '';
  4350. // ratio is used for CSS so let's set default attribute
  4351. this._element.setAttribute('data-ratio', this._options.ratio);
  4352. } else {
  4353. this._element = element;
  4354. }
  4355. this._element.classList.add('slim');
  4356. this._element.setAttribute('data-state', 'init');
  4357. // the source element (can either be an input or an img)
  4358. this._input = null;
  4359. // the output element (hidden input that contains the resulting json object)
  4360. this._output = null;
  4361. // current image ratio
  4362. this._ratio = null;
  4363. // was required field
  4364. this._isRequired = false;
  4365. // components
  4366. this._imageHopper = null;
  4367. this._imageEditor = null;
  4368. // progress path
  4369. this._progressEnabled = true;
  4370. // resulting data
  4371. this._data = {};
  4372. this._resetData();
  4373. // editor state
  4374. this._state = [];
  4375. // the circle below the mouse hover point
  4376. this._drip = null;
  4377. // had initial image
  4378. this._hasInitialImage = false;
  4379. // initial crop
  4380. this._initialCrop = this._options.crop;
  4381. // set to true when destroy method is called, used to halt any timeouts or async processes
  4382. this._isBeingDestroyed = false;
  4383. // let's go!
  4384. if (Slim.supported) {
  4385. this._init();
  4386. } else {
  4387. this._fallback();
  4388. }
  4389. }
  4390. _createClass(Slim, [{
  4391. key: 'isAttachedTo',
  4392. // methods
  4393. // Test if this Slim object has been bound to the given element
  4394. value: function isAttachedTo(element) {
  4395. return this._element === element || this._originalElement === element;
  4396. }
  4397. }, {
  4398. key: 'isDetached',
  4399. value: function isDetached() {
  4400. return this._element.parentNode === null;
  4401. }
  4402. }, {
  4403. key: 'load',
  4404. value: function load(src) {
  4405. var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
  4406. var callback = arguments[2];
  4407. if (typeof options == 'function') {
  4408. callback = options;
  4409. } else {
  4410. // store in options
  4411. this._options.crop = options.crop;
  4412. // set initial crop
  4413. this._initialCrop = this._options.crop;
  4414. }
  4415. this._load(src, callback);
  4416. }
  4417. }, {
  4418. key: 'upload',
  4419. value: function upload(callback) {
  4420. this._doUpload(callback);
  4421. }
  4422. }, {
  4423. key: 'download',
  4424. value: function download() {
  4425. this._doDownload();
  4426. }
  4427. }, {
  4428. key: 'remove',
  4429. value: function remove() {
  4430. return this._doRemove();
  4431. }
  4432. }, {
  4433. key: 'destroy',
  4434. value: function destroy() {
  4435. this._doDestroy();
  4436. }
  4437. }, {
  4438. key: 'edit',
  4439. value: function edit() {
  4440. this._doEdit();
  4441. }
  4442. }, {
  4443. key: 'crop',
  4444. value: function crop(rect, callback) {
  4445. this._crop(rect.x, rect.y, rect.width, rect.height, callback);
  4446. }
  4447. /**
  4448. * State Related
  4449. */
  4450. }, {
  4451. key: '_canInstantEdit',
  4452. value: function _canInstantEdit() {
  4453. return this._options.instantEdit && !this._isInitialising;
  4454. }
  4455. }, {
  4456. key: '_getFileInput',
  4457. value: function _getFileInput() {
  4458. return this._element.querySelector('input[type=file]');
  4459. }
  4460. }, {
  4461. key: '_getInitialImage',
  4462. value: function _getInitialImage() {
  4463. return this._element.querySelector('img');
  4464. }
  4465. }, {
  4466. key: '_getInputElement',
  4467. value: function _getInputElement() {
  4468. return this._getFileInput() || this._getInitialImage();
  4469. }
  4470. }, {
  4471. key: '_getRatioSpacerElement',
  4472. value: function _getRatioSpacerElement() {
  4473. return this._element.children[0];
  4474. }
  4475. }, {
  4476. key: '_isImageOnly',
  4477. value: function _isImageOnly() {
  4478. return this._input.nodeName !== 'INPUT';
  4479. }
  4480. }, {
  4481. key: '_isFixedRatio',
  4482. value: function _isFixedRatio() {
  4483. return this._options.ratio.indexOf(':') !== -1;
  4484. }
  4485. }, {
  4486. key: '_toggleButton',
  4487. value: function _toggleButton(action, state) {
  4488. toggleDisplayBySelector('.slim-btn[data-action="' + action + '"]', state, this._element);
  4489. }
  4490. }, {
  4491. key: '_clearState',
  4492. value: function _clearState() {
  4493. this._state = [];
  4494. this._updateState();
  4495. }
  4496. }, {
  4497. key: '_removeState',
  4498. value: function _removeState(state) {
  4499. this._state = this._state.filter(function (item) {
  4500. return item !== state;
  4501. });
  4502. this._updateState();
  4503. }
  4504. }, {
  4505. key: '_addState',
  4506. value: function _addState(state) {
  4507. if (inArray(state, this._state)) {
  4508. return;
  4509. }
  4510. this._state.push(state);
  4511. this._updateState();
  4512. }
  4513. }, {
  4514. key: '_updateState',
  4515. value: function _updateState() {
  4516. this._element.setAttribute('data-state', this._state.join(','));
  4517. }
  4518. }, {
  4519. key: '_resetData',
  4520. value: function _resetData() {
  4521. // resets data object
  4522. this._data = {
  4523. server: null,
  4524. meta: null,
  4525. input: {
  4526. image: null,
  4527. name: null,
  4528. type: null,
  4529. width: 0,
  4530. height: 0
  4531. },
  4532. output: {
  4533. image: null,
  4534. width: 0,
  4535. height: 0
  4536. },
  4537. actions: {
  4538. rotation: null,
  4539. crop: null,
  4540. size: null
  4541. }
  4542. };
  4543. // resets hidden input clone (has not yet been created when reset call in constructor, hence the check)
  4544. if (this._output) {
  4545. this._output.value = '';
  4546. }
  4547. // should reset file input
  4548. resetFileInput(this._getFileInput());
  4549. }
  4550. /**
  4551. * Initialisation
  4552. */
  4553. }, {
  4554. key: '_init',
  4555. value: function _init() {
  4556. var _this11 = this;
  4557. // busy initialising
  4558. this._isInitialising = true;
  4559. // cropper root is not file input
  4560. this._addState('empty');
  4561. // get input element
  4562. this._input = this._getInputElement();
  4563. // if no input supplied we'll automatically create one
  4564. if (!this._input) {
  4565. this._input = create('input');
  4566. this._input.type = 'file';
  4567. this._element.appendChild(this._input);
  4568. }
  4569. // get required state of input element
  4570. this._isRequired = this._input.required === true;
  4571. // set or create output field
  4572. this._output = this._element.querySelector('input[type=hidden]');
  4573. // if no output element defined we'll create one automagically
  4574. if (!this._output) {
  4575. this._output = create('input');
  4576. this._output.type = 'hidden';
  4577. this._output.name = this._input.name || this._options.defaultInputName;
  4578. this._element.appendChild(this._output);
  4579. }
  4580. // prevent the original file input field from posting (value will be duplicated to output field)
  4581. this._input.removeAttribute('name');
  4582. // create drop area
  4583. var area = create('div', 'slim-area');
  4584. // test if contains initial image
  4585. var initialImage = this._getInitialImage();
  4586. var initialImageSrc = (initialImage || {}).src;
  4587. if (initialImageSrc) {
  4588. this._hasInitialImage = true;
  4589. } else {
  4590. this._initialCrop = null;
  4591. }
  4592. var resultHTML = '\n\t\t<div class="slim-result">\n\t\t\t<img class="in" style="opacity:0" ' + (initialImageSrc ? 'src="' + initialImageSrc + '"' : '') + '><img><img style="opacity:0">\n\t\t</div>';
  4593. // add drop overlay
  4594. if (this._isImageOnly()) {
  4595. area.innerHTML = '\n\t\t\t\t' + SlimLoaderHTML + '\n\t\t\t\t' + SlimUploadStatusHTML + '\n\t\t\t\t' + resultHTML + '\n\t\t\t';
  4596. } else {
  4597. // set common image mime type to the accept attribute
  4598. var mimetypes = void 0;
  4599. if (!this._input.hasAttribute('accept') || this._input.getAttribute('accept') === 'image/*') {
  4600. mimetypes = getCommonMimeTypes();
  4601. this._input.setAttribute('accept', mimetypes.join(','));
  4602. } else {
  4603. mimetypes = this._input.accept.split(',').map(function (mimetype) {
  4604. return mimetype.trim();
  4605. }).filter(function (mimetype) {
  4606. return mimetype.length > 0;
  4607. });
  4608. }
  4609. // setup hopper
  4610. this._imageHopper = new FileHopper();
  4611. this._imageHopper.accept = mimetypes;
  4612. this._element.appendChild(this._imageHopper.element);
  4613. HopperEvents.forEach(function (e) {
  4614. _this11._imageHopper.element.addEventListener(e, _this11);
  4615. });
  4616. // render area
  4617. area.innerHTML = '\n\t\t\t\t' + SlimLoaderHTML + '\n\t\t\t\t' + SlimUploadStatusHTML + '\n\t\t\t\t<div class="slim-drip"><span><span></span></span></div>\n\t\t\t\t<div class="slim-status"><div class="slim-label">' + (this._options.label || '') + '</div></div>\n\t\t\t\t' + resultHTML + '\n\t\t\t';
  4618. // start listening for events so we can load image on file input change
  4619. this._input.addEventListener('change', this);
  4620. }
  4621. // add area node
  4622. this._element.appendChild(area);
  4623. // add button group
  4624. this._btnGroup = create('div', 'slim-btn-group');
  4625. this._btnGroup.style.display = 'none';
  4626. this._element.appendChild(this._btnGroup);
  4627. SlimButtons.filter(function (c) {
  4628. return _this11._isButtonAllowed(c);
  4629. }).forEach(function (c) {
  4630. var prop = capitalizeFirstLetter(c);
  4631. var label = _this11._options['button' + prop + 'Label'];
  4632. var title = _this11._options['button' + prop + 'Title'] || label;
  4633. var className = _this11._options['button' + prop + 'ClassName'];
  4634. var btn = create('button', 'slim-btn slim-btn-' + c + (className ? ' ' + className : ''));
  4635. btn.innerHTML = label;
  4636. btn.title = title;
  4637. btn.type = 'button';
  4638. btn.addEventListener('click', _this11);
  4639. btn.setAttribute('data-action', c);
  4640. _this11._btnGroup.appendChild(btn);
  4641. });
  4642. // add ratio padding
  4643. if (this._isFixedRatio()) {
  4644. var parts = intSplit(this._options.ratio, ':');
  4645. this._ratio = parts[1] / parts[0];
  4646. this._scaleDropArea(this._ratio);
  4647. }
  4648. // setup loader
  4649. this._updateProgress(.5);
  4650. // preload source
  4651. if (initialImageSrc) {
  4652. this._load(initialImageSrc, function () {
  4653. _this11._onInit();
  4654. });
  4655. } else {
  4656. this._onInit();
  4657. }
  4658. }
  4659. }, {
  4660. key: '_onInit',
  4661. value: function _onInit() {
  4662. var _this12 = this;
  4663. // we're done initialising
  4664. this._isInitialising = false;
  4665. // done initialising now, else is only called after image load
  4666. var done = function done() {
  4667. // we call this async so the constructor of Slim has returned before the onInit is called, allowing clean immidiate destroy
  4668. setTimeout(function () {
  4669. _this12._options.didInit.apply(_this12, [_this12.data]);
  4670. }, 0);
  4671. };
  4672. // save initial image
  4673. if (this._options.saveInitialImage) {
  4674. this._save(function () {
  4675. done();
  4676. }, false);
  4677. } else {
  4678. done();
  4679. }
  4680. }
  4681. }, {
  4682. key: '_updateProgress',
  4683. value: function _updateProgress(progress) {
  4684. if (!this._progressEnabled) {
  4685. return;
  4686. }
  4687. var loader = this._element.querySelector('.slim-loader');
  4688. if (!loader) {
  4689. return;
  4690. }
  4691. var rect = loader.getBoundingClientRect();
  4692. var paths = loader.querySelectorAll('path');
  4693. var ringWidth = parseInt(paths[0].getAttribute('stroke-width'), 10);
  4694. paths[0].setAttribute('d', percentageArc(rect.width * .5, rect.height * .5, rect.width * .5 - ringWidth, .9999));
  4695. paths[1].setAttribute('d', percentageArc(rect.width * .5, rect.height * .5, rect.width * .5 - ringWidth, progress));
  4696. }
  4697. }, {
  4698. key: '_startProgress',
  4699. value: function _startProgress() {
  4700. var _this13 = this;
  4701. this._progressEnabled = false;
  4702. var loader = this._element.querySelector('.slim-loader');
  4703. if (!loader) {
  4704. return;
  4705. }
  4706. var ring = loader.children[0];
  4707. this._stopProgressLoop(function () {
  4708. loader.removeAttribute('style');
  4709. ring.removeAttribute('style');
  4710. _this13._progressEnabled = true;
  4711. _this13._updateProgress(0);
  4712. _this13._progressEnabled = false;
  4713. snabbt(ring, {
  4714. fromOpacity: 0,
  4715. opacity: 1,
  4716. duration: 250,
  4717. complete: function complete() {
  4718. _this13._progressEnabled = true;
  4719. }
  4720. });
  4721. });
  4722. }
  4723. }, {
  4724. key: '_stopProgress',
  4725. value: function _stopProgress() {
  4726. var _this14 = this;
  4727. var loader = this._element.querySelector('.slim-loader');
  4728. if (!loader) {
  4729. return;
  4730. }
  4731. var ring = loader.children[0];
  4732. this._updateProgress(1);
  4733. snabbt(ring, {
  4734. fromOpacity: 1,
  4735. opacity: 0,
  4736. duration: 250,
  4737. complete: function complete() {
  4738. loader.removeAttribute('style');
  4739. ring.removeAttribute('style');
  4740. _this14._updateProgress(.5);
  4741. _this14._progressEnabled = false;
  4742. }
  4743. });
  4744. }
  4745. }, {
  4746. key: '_startProgressLoop',
  4747. value: function _startProgressLoop() {
  4748. // start loading animation
  4749. var loader = this._element.querySelector('.slim-loader');
  4750. if (!loader) {
  4751. return;
  4752. }
  4753. var ring = loader.children[0];
  4754. loader.removeAttribute('style');
  4755. ring.removeAttribute('style');
  4756. // set infinite load state
  4757. this._updateProgress(.5);
  4758. // repeat!
  4759. var repeat = 1000;
  4760. snabbt(loader, {
  4761. rotation: [0, 0, -(Math.PI * 2) * repeat],
  4762. easing: 'linear',
  4763. duration: repeat * 1000
  4764. });
  4765. snabbt(ring, {
  4766. fromOpacity: 0,
  4767. opacity: 1,
  4768. duration: 250
  4769. });
  4770. }
  4771. }, {
  4772. key: '_stopProgressLoop',
  4773. value: function _stopProgressLoop(callback) {
  4774. var loader = this._element.querySelector('.slim-loader');
  4775. if (!loader) {
  4776. return;
  4777. }
  4778. var ring = loader.children[0];
  4779. snabbt(ring, {
  4780. fromOpacity: parseFloat(ring.style.opacity),
  4781. opacity: 0,
  4782. duration: 250,
  4783. complete: function complete() {
  4784. snabbt(loader, 'stop');
  4785. if (callback) {
  4786. callback();
  4787. }
  4788. }
  4789. });
  4790. }
  4791. }, {
  4792. key: '_isButtonAllowed',
  4793. value: function _isButtonAllowed(button) {
  4794. if (button === 'edit') {
  4795. return this._options.edit;
  4796. }
  4797. if (button === 'download') {
  4798. return this._options.download;
  4799. }
  4800. if (button === 'upload') {
  4801. // if no service defined upload button makes no sense
  4802. if (!this._options.service) {
  4803. return false;
  4804. }
  4805. // if push mode is set, no need for upload button
  4806. if (this._options.push) {
  4807. return false;
  4808. }
  4809. // set upload button
  4810. return true;
  4811. }
  4812. if (button === 'remove') {
  4813. return !this._isImageOnly();
  4814. }
  4815. return true;
  4816. }
  4817. }, {
  4818. key: '_fallback',
  4819. value: function _fallback() {
  4820. this._removeState('init');
  4821. // create status area
  4822. var area = create('div', 'slim-area');
  4823. area.innerHTML = '\n\t\t\t<div class="slim-status"><div class="slim-label">' + (this._options.label || '') + '</div></div>\n\t\t';
  4824. this._element.appendChild(area);
  4825. this._throwError(this._options.statusNoSupport);
  4826. }
  4827. /**
  4828. * Event routing
  4829. */
  4830. }, {
  4831. key: 'handleEvent',
  4832. value: function handleEvent(e) {
  4833. switch (e.type) {
  4834. case 'click':
  4835. this._onClick(e);
  4836. break;
  4837. case 'change':
  4838. this._onChange(e);
  4839. break;
  4840. case 'cancel':
  4841. this._onCancel(e);
  4842. break;
  4843. case 'confirm':
  4844. this._onConfirm(e);
  4845. break;
  4846. case 'file-over':
  4847. this._onFileOver(e);
  4848. break;
  4849. case 'file-out':
  4850. this._onFileOut(e);
  4851. break;
  4852. case 'file-drop':
  4853. this._onDropFile(e);
  4854. break;
  4855. case 'file-invalid':
  4856. this._onInvalidFile(e);
  4857. break;
  4858. case 'file-invalid-drop':
  4859. this._onInvalidFileDrop(e);
  4860. break;
  4861. }
  4862. }
  4863. }, {
  4864. key: '_getIntro',
  4865. value: function _getIntro() {
  4866. return this._element.querySelector('.slim-result .in');
  4867. }
  4868. }, {
  4869. key: '_getOutro',
  4870. value: function _getOutro() {
  4871. return this._element.querySelector('.slim-result .out');
  4872. }
  4873. }, {
  4874. key: '_getInOut',
  4875. value: function _getInOut() {
  4876. return this._element.querySelectorAll('.slim-result img');
  4877. }
  4878. }, {
  4879. key: '_getDrip',
  4880. value: function _getDrip() {
  4881. if (!this._drip) {
  4882. this._drip = this._element.querySelector('.slim-drip > span');
  4883. }
  4884. return this._drip;
  4885. }
  4886. // errors
  4887. }, {
  4888. key: '_throwError',
  4889. value: function _throwError(error) {
  4890. this._addState('error');
  4891. this._element.querySelector('.slim-label').style.display = 'none';
  4892. var node = this._element.querySelector('.slim-error');
  4893. if (!node) {
  4894. node = create('div', 'slim-error');
  4895. this._element.querySelector('.slim-status').appendChild(node);
  4896. }
  4897. node.innerHTML = error;
  4898. }
  4899. }, {
  4900. key: '_removeError',
  4901. value: function _removeError() {
  4902. this._removeState('error');
  4903. this._element.querySelector('.slim-label').style.display = '';
  4904. var error = this._element.querySelector('.slim-error');
  4905. if (error) {
  4906. error.parentNode.removeChild(error);
  4907. }
  4908. }
  4909. }, {
  4910. key: '_openFileDialog',
  4911. value: function _openFileDialog() {
  4912. this._removeError();
  4913. this._input.click();
  4914. }
  4915. // drop area clicked
  4916. }, {
  4917. key: '_onClick',
  4918. value: function _onClick(e) {
  4919. var _this15 = this;
  4920. var list = e.target.classList;
  4921. var target = e.target;
  4922. // no preview, so possible to drop file
  4923. if (list.contains('slim-file-hopper')) {
  4924. this._openFileDialog();
  4925. return;
  4926. }
  4927. // decide what button was clicked
  4928. switch (target.getAttribute('data-action')) {
  4929. case 'remove':
  4930. this._options.willRemove.apply(this, [this.data, function () {
  4931. _this15._doRemove();
  4932. }]);
  4933. break;
  4934. case 'edit':
  4935. this._doEdit();
  4936. break;
  4937. case 'download':
  4938. this._doDownload();
  4939. break;
  4940. case 'upload':
  4941. this._doUpload();
  4942. break;
  4943. }
  4944. }
  4945. }, {
  4946. key: '_onInvalidFileDrop',
  4947. value: function _onInvalidFileDrop() {
  4948. this._onInvalidFile();
  4949. this._removeState('file-over');
  4950. // animate out drip
  4951. var drip = this._getDrip();
  4952. snabbt(drip.firstChild, {
  4953. fromScale: [.5, .5],
  4954. scale: [0, 0],
  4955. fromOpacity: .5,
  4956. opacity: 0,
  4957. duration: 150,
  4958. complete: function complete() {
  4959. // clean up transforms
  4960. resetTransforms(drip.firstChild);
  4961. }
  4962. });
  4963. }
  4964. }, {
  4965. key: '_onInvalidFile',
  4966. value: function _onInvalidFile() {
  4967. var types = this._imageHopper.accept.map(getExtensionByMimeType);
  4968. var error = this._options.statusFileType.replace('$0', types.join(', '));
  4969. this._throwError(error);
  4970. }
  4971. }, {
  4972. key: '_onImageTooSmall',
  4973. value: function _onImageTooSmall() {
  4974. var error = this._options.statusImageTooSmall.replace('$0', this._options.minSize.width + ' \xD7 ' + this._options.minSize.height);
  4975. this._throwError(error);
  4976. }
  4977. }, {
  4978. key: '_onOverWeightFile',
  4979. value: function _onOverWeightFile() {
  4980. var error = this._options.statusFileSize.replace('$0', this._options.maxFileSize);
  4981. this._throwError(error);
  4982. }
  4983. }, {
  4984. key: '_onFileOver',
  4985. value: function _onFileOver(e) {
  4986. this._addState('file-over');
  4987. this._removeError();
  4988. // animations
  4989. var drip = this._getDrip();
  4990. // move around drip
  4991. var matrix = snabbt.createMatrix();
  4992. matrix.translate(e.detail.x, e.detail.y, 0);
  4993. snabbt.setElementTransform(drip, matrix);
  4994. // on first entry fade in blob
  4995. if (this._imageHopper.dragPath.length == 1) {
  4996. // show
  4997. drip.style.opacity = 1;
  4998. // animate in
  4999. snabbt(drip.firstChild, {
  5000. fromOpacity: 0,
  5001. opacity: .5,
  5002. fromScale: [0, 0],
  5003. scale: [.5, .5],
  5004. duration: 150
  5005. });
  5006. }
  5007. }
  5008. }, {
  5009. key: '_onFileOut',
  5010. value: function _onFileOut(e) {
  5011. this._removeState('file-over');
  5012. this._removeState('file-invalid');
  5013. this._removeError();
  5014. // move to last position
  5015. var drip = this._getDrip();
  5016. var matrix = snabbt.createMatrix();
  5017. matrix.translate(e.detail.x, e.detail.y, 0);
  5018. snabbt.setElementTransform(drip, matrix);
  5019. // animate out
  5020. snabbt(drip.firstChild, {
  5021. fromScale: [.5, .5],
  5022. scale: [0, 0],
  5023. fromOpacity: .5,
  5024. opacity: 0,
  5025. duration: 150,
  5026. complete: function complete() {
  5027. // clean up transforms
  5028. resetTransforms(drip.firstChild);
  5029. }
  5030. });
  5031. }
  5032. /**
  5033. * When a file was literally dropped on the drop area
  5034. * @param e
  5035. * @private
  5036. */
  5037. }, {
  5038. key: '_onDropFile',
  5039. value: function _onDropFile(e) {
  5040. var _this16 = this;
  5041. this._removeState('file-over');
  5042. // get drip node reference and set to last position
  5043. var drip = this._getDrip();
  5044. var matrix = snabbt.createMatrix();
  5045. matrix.translate(e.detail.x, e.detail.y, 0);
  5046. snabbt.setElementTransform(drip, matrix);
  5047. // get element minimum 10 entries distant from the last entry so we can calculate velocity of movement
  5048. var l = this._imageHopper.dragPath.length;
  5049. var jump = this._imageHopper.dragPath[l - Math.min(10, l)];
  5050. // velocity
  5051. var dx = e.detail.x - jump.x;
  5052. var dy = e.detail.y - jump.y;
  5053. snabbt(drip, {
  5054. fromPosition: [e.detail.x, e.detail.y, 0],
  5055. position: [e.detail.x + dx, e.detail.y + dy, 0],
  5056. duration: 200
  5057. });
  5058. // animate out drip
  5059. snabbt(drip.firstChild, {
  5060. fromScale: [.5, .5],
  5061. scale: [2, 2],
  5062. fromOpacity: 1,
  5063. opacity: 0,
  5064. duration: 200,
  5065. complete: function complete() {
  5066. // clean up transforms
  5067. resetTransforms(drip.firstChild);
  5068. // load dropped resource
  5069. _this16._load(e.target.files[0]);
  5070. }
  5071. });
  5072. }
  5073. /**
  5074. * When a file has been selected after a click on the drop area
  5075. * @param e
  5076. * @private
  5077. */
  5078. }, {
  5079. key: '_onChange',
  5080. value: function _onChange(e) {
  5081. this._load(e.target.files[0]);
  5082. }
  5083. /**
  5084. * Loads a resource (blocking operation)
  5085. * @param resource
  5086. * @param callback(err)
  5087. * @private
  5088. */
  5089. }, {
  5090. key: '_load',
  5091. value: function _load(resource, callback) {
  5092. var _this17 = this;
  5093. // stop here
  5094. if (this._isBeingDestroyed) {
  5095. return;
  5096. }
  5097. // meta data
  5098. var file = getFileMetaData(resource);
  5099. // test if is unknown file or if we should override the mime type
  5100. if (file.type === 'unknown' || this._options.forceType) {
  5101. file = setFileMetaData(file, this._options.forceType || 'png');
  5102. }
  5103. // re-test if is valid file type
  5104. if (this._imageHopper && this._imageHopper.accept.indexOf(file.type) === -1) {
  5105. this._onInvalidFile();
  5106. if (callback) {
  5107. callback.apply(this, ['file-invalid']);
  5108. }
  5109. return;
  5110. }
  5111. // test if too big
  5112. if (file.size && this._options.maxFileSize && bytesToMegaBytes(file.size) > this._options.maxFileSize) {
  5113. this._onOverWeightFile();
  5114. if (callback) {
  5115. callback.apply(this, ['file-too-big']);
  5116. }
  5117. return;
  5118. }
  5119. // no longer empty
  5120. this._removeState('empty');
  5121. // can't drop any other image
  5122. if (this._imageHopper) {
  5123. this._imageHopper.enabled = false;
  5124. }
  5125. // if has loaded image editor set to dirty
  5126. if (this._imageEditor) {
  5127. this._imageEditor.dirty();
  5128. }
  5129. // continue
  5130. this._data.input.name = file.name;
  5131. this._data.input.type = file.type;
  5132. this._data.input.size = file.size;
  5133. // start loading indicator
  5134. this._startProgressLoop();
  5135. this._addState('busy');
  5136. // fetch resource
  5137. getImageAsCanvas(resource, function (image, meta) {
  5138. var rewind = function rewind() {
  5139. // rewind state
  5140. if (_this17._imageHopper) {
  5141. _this17._imageHopper.enabled = true;
  5142. }
  5143. _this17._removeState('busy');
  5144. _this17._stopProgressLoop();
  5145. _this17._resetData();
  5146. };
  5147. // if no image, something went wrong
  5148. if (!image) {
  5149. rewind();
  5150. if (callback) {
  5151. callback.apply(_this17, ['file-not-found']);
  5152. }
  5153. return;
  5154. }
  5155. // test if image is too small
  5156. if (!covers(image, _this17._options.minSize)) {
  5157. rewind();
  5158. _this17._onImageTooSmall();
  5159. if (callback) {
  5160. callback.apply(_this17, ['image-too-small']);
  5161. }
  5162. return;
  5163. }
  5164. var status = _this17._options.didLoad.apply(_this17, [file, image, meta]);
  5165. if (status !== true) {
  5166. rewind();
  5167. if (status !== false) {
  5168. _this17._throwError(status);
  5169. }
  5170. if (callback) {
  5171. callback.apply(_this17, [status]);
  5172. }
  5173. return;
  5174. }
  5175. // load the image
  5176. _this17._loadCanvas(image, function () {
  5177. // do intro stuff
  5178. var intro = _this17._getIntro();
  5179. // setup base animation
  5180. var animation = {
  5181. fromScale: [1.25, 1.25],
  5182. scale: [1, 1],
  5183. fromOpacity: 0,
  5184. opacity: 1,
  5185. complete: function complete() {
  5186. resetTransforms(intro);
  5187. intro.style.opacity = 1;
  5188. // don't show buttons when instant editing
  5189. // the buttons will be triggered by the closing of the popup
  5190. if (!_this17._canInstantEdit()) {
  5191. _this17._showButtons();
  5192. }
  5193. _this17._stopProgressLoop();
  5194. _this17._removeState('busy');
  5195. _this17._addState('preview');
  5196. if (callback) {
  5197. callback.apply(_this17, [null, _this17.data]);
  5198. }
  5199. }
  5200. };
  5201. // if not attached to DOM, don't animate
  5202. if (_this17.isDetached()) {
  5203. animation.duration = 1;
  5204. } else {
  5205. animation.easing = 'spring';
  5206. animation.springConstant = .3;
  5207. animation.springDeceleration = .7;
  5208. }
  5209. // if is instant edit mode don't zoom out but zoom in
  5210. if (_this17._canInstantEdit()) {
  5211. animation.delay = 500;
  5212. animation.duration = 1;
  5213. // instant edit mode just fire up the editor immidiately
  5214. _this17._doEdit();
  5215. }
  5216. // reveal loaded image
  5217. snabbt(intro, animation);
  5218. });
  5219. });
  5220. }
  5221. }, {
  5222. key: '_loadCanvas',
  5223. value: function _loadCanvas(image, ready) {
  5224. var _this18 = this;
  5225. // halt here if cropper is currently being destroyed
  5226. if (this._isBeingDestroyed) {
  5227. return;
  5228. }
  5229. // scales the drop area
  5230. // if is 'input' or 'free' parameter
  5231. if (!this._isFixedRatio()) {
  5232. this._ratio = image.height / image.width;
  5233. this._scaleDropArea(this._ratio);
  5234. }
  5235. // store raw data
  5236. this._data.input.image = image;
  5237. this._data.input.width = image.width;
  5238. this._data.input.height = image.height;
  5239. if (this._initialCrop) {
  5240. // use initial supplied crop rectangle
  5241. this._data.actions.crop = clone(this._initialCrop);
  5242. this._data.actions.crop.type = CropType.INITIAL;
  5243. // clear initial crop, it's no longer useful
  5244. this._initialCrop = null;
  5245. } else {
  5246. // get automagical crop rectangle
  5247. this._data.actions.crop = getAutoCropRect(image.width, image.height, this._ratio);
  5248. this._data.actions.crop.type = CropType.AUTO;
  5249. }
  5250. // if max size set
  5251. if (this._options.size) {
  5252. this._data.actions.size = {
  5253. width: this._options.size.width,
  5254. height: this._options.size.height
  5255. };
  5256. }
  5257. // do initial auto transform
  5258. this._applyTransforms(image, function (transformedImage) {
  5259. var intro = _this18._getIntro();
  5260. var scalar = intro.offsetWidth / transformedImage.width;
  5261. // store data, if has preview image this prevents initial load from pushing
  5262. var willUpload = false;
  5263. // can only do auto upload when service is defined and push is enabled...
  5264. if (_this18._options.service && _this18._options.push) {
  5265. // ...and is not transformation of initial image
  5266. if (!_this18._hasInitialImage) {
  5267. willUpload = true;
  5268. }
  5269. }
  5270. // store data (possibly)
  5271. _this18._save(function () {}, willUpload);
  5272. // show intro animation
  5273. intro.src = '';
  5274. intro.src = cloneCanvasScaled(transformedImage, scalar).toDataURL();
  5275. intro.onload = function () {
  5276. intro.onload = null;
  5277. // bail out if we've been cleaned up
  5278. if (_this18._isBeingDestroyed) {
  5279. return;
  5280. }
  5281. if (ready) {
  5282. ready();
  5283. }
  5284. };
  5285. });
  5286. }
  5287. }, {
  5288. key: '_applyTransforms',
  5289. value: function _applyTransforms(image, ready) {
  5290. var _this19 = this;
  5291. transformCanvas(image, this._data.actions, function (transformedImage) {
  5292. _this19._data.output.width = transformedImage.width;
  5293. _this19._data.output.height = transformedImage.height;
  5294. _this19._data.output.image = transformedImage;
  5295. _this19._onTransformCanvas(function (transformedData) {
  5296. _this19._data = transformedData;
  5297. _this19._options.didTransform.apply(_this19, [_this19.data]);
  5298. ready(_this19._data.output.image);
  5299. });
  5300. });
  5301. }
  5302. }, {
  5303. key: '_onTransformCanvas',
  5304. value: function _onTransformCanvas(ready) {
  5305. this._options.willTransform.apply(this, [this.data, ready]);
  5306. }
  5307. /**
  5308. * Creates the editor nodes
  5309. * @private
  5310. */
  5311. }, {
  5312. key: '_appendEditor',
  5313. value: function _appendEditor() {
  5314. var _this20 = this;
  5315. // we already have an editor
  5316. if (this._imageEditor) {
  5317. return;
  5318. }
  5319. // add editor
  5320. this._imageEditor = new ImageEditor(create('div'), {
  5321. minSize: this._options.minSize,
  5322. buttonConfirmClassName: 'slim-btn-confirm',
  5323. buttonCancelClassName: 'slim-btn-cancel',
  5324. buttonConfirmLabel: this._options.buttonConfirmLabel,
  5325. buttonCancelLabel: this._options.buttonCancelLabel,
  5326. buttonConfirmTitle: this._options.buttonConfirmTitle,
  5327. buttonCancelTitle: this._options.buttonCancelTitle
  5328. });
  5329. // listen to events
  5330. ImageEditorEvents.forEach(function (e) {
  5331. _this20._imageEditor.element.addEventListener(e, _this20);
  5332. });
  5333. }
  5334. }, {
  5335. key: '_scaleDropArea',
  5336. value: function _scaleDropArea(ratio) {
  5337. var node = this._getRatioSpacerElement();
  5338. if (!node || !this._element) {
  5339. return;
  5340. }
  5341. node.style.marginBottom = ratio * 100 + '%';
  5342. this._element.setAttribute('data-ratio', '1:' + ratio);
  5343. }
  5344. /**
  5345. * Data Layer
  5346. * @private
  5347. */
  5348. // image editor closed
  5349. }, {
  5350. key: '_onCancel',
  5351. value: function _onCancel(e) {
  5352. this._removeState('editor');
  5353. this._showButtons();
  5354. this._hideEditor();
  5355. }
  5356. // user confirmed changes
  5357. }, {
  5358. key: '_onConfirm',
  5359. value: function _onConfirm(e) {
  5360. var _this21 = this;
  5361. this._removeState('editor');
  5362. this._startProgressLoop();
  5363. this._addState('busy');
  5364. // clear data
  5365. this._output.value = '';
  5366. // apply new action object to this._data
  5367. this._data.actions.rotation = e.detail.rotation;
  5368. this._data.actions.crop = e.detail.crop;
  5369. this._data.actions.crop.type = CropType.MANUAL;
  5370. // do transforms
  5371. this._applyTransforms(this._data.input.image, function (transformedImage) {
  5372. // set new image result
  5373. var images = _this21._getInOut();
  5374. var intro = images[0].className === 'out' ? images[0] : images[1];
  5375. var outro = intro === images[0] ? images[1] : images[0];
  5376. intro.className = 'in';
  5377. intro.style.opacity = '0';
  5378. intro.style.zIndex = '2';
  5379. outro.className = 'out';
  5380. outro.style.zIndex = '1';
  5381. // new image get's
  5382. intro.src = '';
  5383. intro.src = cloneCanvasScaled(transformedImage, intro.offsetWidth / transformedImage.width).toDataURL();
  5384. intro.onload = function () {
  5385. intro.onload = null;
  5386. // scale the dropzone
  5387. if (_this21._options.ratio === 'free') {
  5388. _this21._ratio = intro.naturalHeight / intro.naturalWidth;
  5389. _this21._scaleDropArea(_this21._ratio);
  5390. }
  5391. // close the editor
  5392. _this21._hideEditor();
  5393. // wait a tiny bit so animations sync up nicely
  5394. setTimeout(function () {
  5395. // show the preview
  5396. _this21._showPreview(intro, function () {
  5397. // if
  5398. // - service set
  5399. // - and we are pushing
  5400. // we will upload
  5401. var willUpload = _this21._options.service && _this21._options.push;
  5402. // save the data
  5403. _this21._save(function (err, data, res) {
  5404. // done!
  5405. _this21._toggleButton('upload', true);
  5406. _this21._stopProgressLoop();
  5407. _this21._removeState('busy');
  5408. _this21._showButtons();
  5409. }, willUpload);
  5410. });
  5411. }, 250);
  5412. };
  5413. });
  5414. }
  5415. }, {
  5416. key: '_crop',
  5417. value: function _crop(x, y, width, height) {
  5418. var _this22 = this;
  5419. var callback = arguments.length <= 4 || arguments[4] === undefined ? function () {} : arguments[4];
  5420. this._startProgressLoop();
  5421. this._addState('busy');
  5422. // clear data
  5423. this._output.value = '';
  5424. // apply new action object to this._data
  5425. this._data.actions.crop = {
  5426. x: x,
  5427. y: y,
  5428. width: width,
  5429. height: height
  5430. };
  5431. this._data.actions.crop.type = CropType.MANUAL;
  5432. // do transforms
  5433. this._applyTransforms(this._data.input.image, function (transformedImage) {
  5434. // set new image result
  5435. var images = _this22._getInOut();
  5436. var intro = images[0].className === 'out' ? images[0] : images[1];
  5437. var outro = intro === images[0] ? images[1] : images[0];
  5438. intro.className = 'in';
  5439. intro.style.opacity = '1';
  5440. intro.style.zIndex = '2';
  5441. outro.className = 'out';
  5442. outro.style.zIndex = '0';
  5443. // new image
  5444. intro.src = '';
  5445. intro.src = cloneCanvasScaled(transformedImage, intro.offsetWidth / transformedImage.width).toDataURL();
  5446. intro.onload = function () {
  5447. intro.onload = null;
  5448. // scale the dropzone
  5449. if (_this22._options.ratio === 'free') {
  5450. _this22._ratio = intro.naturalHeight / intro.naturalWidth;
  5451. _this22._scaleDropArea(_this22._ratio);
  5452. }
  5453. // determine if will also upload
  5454. var willUpload = _this22._options.service && _this22._options.push;
  5455. // save the data
  5456. _this22._save(function (err, data, res) {
  5457. _this22._stopProgressLoop();
  5458. _this22._removeState('busy');
  5459. callback.apply(_this22, [_this22.data]);
  5460. }, willUpload);
  5461. };
  5462. });
  5463. }
  5464. }, {
  5465. key: '_save',
  5466. value: function _save() {
  5467. var _this23 = this;
  5468. var callback = arguments.length <= 0 || arguments[0] === undefined ? function () {} : arguments[0];
  5469. var allowUpload = arguments.length <= 1 || arguments[1] === undefined ? true : arguments[1];
  5470. // flatten data also turns canvas into data uri's
  5471. var data = this.dataBase64;
  5472. // decide if we need to
  5473. // - A. Store the data in an output field
  5474. // - B. Upload the data and store the response in output field
  5475. // - we are not doing async uploading (in which case output is used for response)
  5476. // - we are not initialising a replaceable image
  5477. if (!this._options.service && !(this._isInitialising && !this._isImageOnly())) {
  5478. this._options.willSave.apply(this, [data, function (data) {
  5479. _this23._store(data);
  5480. _this23._options.didSave.apply(_this23, [data]);
  5481. }]);
  5482. }
  5483. // is remote service defined upload async
  5484. if (this._options.service && allowUpload) {
  5485. // allow user to modify the data
  5486. this._options.willSave.apply(this, [data, function (data) {
  5487. // do the actual uploading
  5488. _this23._upload(data, function (err, res) {
  5489. // store response
  5490. if (!err) {
  5491. _this23._storeServerResponse(res);
  5492. }
  5493. // we did upload data
  5494. _this23._options.didUpload.apply(_this23, [err, data, res]);
  5495. // done!
  5496. callback(err, data, res);
  5497. });
  5498. }]);
  5499. }
  5500. // if no service, we're done here
  5501. if (!this._options.service || !allowUpload) {
  5502. callback();
  5503. }
  5504. }
  5505. // stores active file information in hidden output field
  5506. }, {
  5507. key: '_storeServerResponse',
  5508. value: function _storeServerResponse(data) {
  5509. // remove required flag
  5510. if (this._isRequired) {
  5511. this._input.required = false;
  5512. }
  5513. // store data returned from server
  5514. this._data.server = data;
  5515. // sync with output value
  5516. this._output.value = (typeof data === 'undefined' ? 'undefined' : _typeof(data)) === 'object' ? JSON.stringify(this._data.server) : data;
  5517. }
  5518. // stores data in output field
  5519. }, {
  5520. key: '_store',
  5521. value: function _store(data) {
  5522. if (this._isRequired) {
  5523. this._input.required = false;
  5524. }
  5525. this._output.value = JSON.stringify(data);
  5526. }
  5527. // uploads given data to server
  5528. }, {
  5529. key: '_upload',
  5530. value: function _upload(data, callback) {
  5531. var _this24 = this;
  5532. var formData = new FormData();
  5533. formData.append(this._output.name, JSON.stringify(data));
  5534. var statusNode = this._element.querySelector('.slim-upload-status');
  5535. var requestDecorator = this._options.willRequest;
  5536. send(
  5537. // url to service
  5538. this._options.service,
  5539. // data
  5540. formData,
  5541. // decorator (useful to add headers to request
  5542. requestDecorator,
  5543. // progress
  5544. function (loaded, total) {
  5545. _this24._updateProgress(loaded / total);
  5546. },
  5547. // success
  5548. function (obj) {
  5549. setTimeout(function () {
  5550. statusNode.innerHTML = _this24._options.statusUploadSuccess;
  5551. statusNode.setAttribute('data-state', 'success');
  5552. statusNode.style.opacity = 1;
  5553. // hide status update after 2 seconds
  5554. setTimeout(function () {
  5555. statusNode.style.opacity = 0;
  5556. }, 2000);
  5557. }, 250);
  5558. callback(null, obj);
  5559. },
  5560. // error
  5561. function (status) {
  5562. var html = '';
  5563. if (status === 'file-too-big') {
  5564. html = _this24._options.statusContentLength;
  5565. } else {
  5566. html = _this24._options.statusUnknownResponse;
  5567. }
  5568. // when an error occurs the status update is not automatically hidden
  5569. setTimeout(function () {
  5570. statusNode.innerHTML = html;
  5571. statusNode.setAttribute('data-state', 'error');
  5572. statusNode.style.opacity = 1;
  5573. }, 250);
  5574. callback(status);
  5575. });
  5576. }
  5577. }, {
  5578. key: '_showEditor',
  5579. value: function _showEditor() {
  5580. SlimPopover.show();
  5581. this._imageEditor.show();
  5582. }
  5583. }, {
  5584. key: '_hideEditor',
  5585. value: function _hideEditor() {
  5586. this._imageEditor.hide();
  5587. setTimeout(function () {
  5588. SlimPopover.hide();
  5589. }, 250);
  5590. }
  5591. /**
  5592. * Animations
  5593. */
  5594. }, {
  5595. key: '_showPreview',
  5596. value: function _showPreview(intro, callback) {
  5597. snabbt(intro, {
  5598. fromPosition: [0, 50, 0],
  5599. position: [0, 0, 0],
  5600. fromScale: [1.5, 1.5],
  5601. scale: [1, 1],
  5602. fromOpacity: 0,
  5603. opacity: 1,
  5604. easing: 'spring',
  5605. springConstant: .3,
  5606. springDeceleration: .7,
  5607. complete: function complete() {
  5608. resetTransforms(intro);
  5609. if (callback) {
  5610. callback();
  5611. }
  5612. }
  5613. });
  5614. }
  5615. }, {
  5616. key: '_hideResult',
  5617. value: function _hideResult(callback) {
  5618. var intro = this._getIntro();
  5619. if (!intro) {
  5620. return;
  5621. }
  5622. snabbt(intro, {
  5623. fromScale: [1, 1],
  5624. scale: [.5, .5],
  5625. fromOpacity: 1,
  5626. opacity: 0,
  5627. easing: 'spring',
  5628. springConstant: .3,
  5629. springDeceleration: .75,
  5630. complete: function complete() {
  5631. resetTransforms(intro);
  5632. if (callback) {
  5633. callback();
  5634. }
  5635. }
  5636. });
  5637. }
  5638. }, {
  5639. key: '_showButtons',
  5640. value: function _showButtons(callback) {
  5641. this._btnGroup.style.display = '';
  5642. // setup animation
  5643. var animation = {
  5644. fromScale: [.5, .5],
  5645. scale: [1, 1],
  5646. fromPosition: [0, 10, 0],
  5647. position: [0, 0, 0],
  5648. fromOpacity: 0,
  5649. opacity: 1,
  5650. complete: function complete() {
  5651. resetTransforms(this);
  5652. },
  5653. allDone: function allDone() {
  5654. if (callback) {
  5655. callback();
  5656. }
  5657. }
  5658. };
  5659. // don't animate when detached
  5660. if (this.isDetached()) {
  5661. animation.duration = 1;
  5662. } else {
  5663. animation.delay = function (i) {
  5664. return 250 + i * 50;
  5665. };
  5666. animation.easing = 'spring';
  5667. animation.springConstant = .3;
  5668. animation.springDeceleration = .85;
  5669. }
  5670. snabbt(this._btnGroup.childNodes, animation);
  5671. }
  5672. }, {
  5673. key: '_hideButtons',
  5674. value: function _hideButtons(callback) {
  5675. var _this25 = this;
  5676. var animation = {
  5677. fromScale: [1, 1],
  5678. scale: [.85, .85],
  5679. fromOpacity: 1,
  5680. opacity: 0,
  5681. allDone: function allDone() {
  5682. _this25._btnGroup.style.display = 'none';
  5683. if (callback) {
  5684. callback();
  5685. }
  5686. }
  5687. };
  5688. // don't animate when detached
  5689. if (this.isDetached()) {
  5690. animation.duration = 1;
  5691. } else {
  5692. animation.easing = 'spring';
  5693. animation.springConstant = .3;
  5694. animation.springDeceleration = .75;
  5695. }
  5696. // go hide the buttons
  5697. snabbt(this._btnGroup.childNodes, animation);
  5698. }
  5699. }, {
  5700. key: '_hideStatus',
  5701. value: function _hideStatus() {
  5702. var statusNode = this._element.querySelector('.slim-upload-status');
  5703. statusNode.style.opacity = 0;
  5704. }
  5705. }, {
  5706. key: '_doEdit',
  5707. value: function _doEdit() {
  5708. var _this26 = this;
  5709. // if no input data available, can't edit anything
  5710. if (!this._data.input.image) {
  5711. return;
  5712. }
  5713. // now in editor mode
  5714. this._addState('editor');
  5715. // create editor (if not already created)
  5716. if (!this._imageEditor) {
  5717. this._appendEditor();
  5718. }
  5719. // append to popover
  5720. SlimPopover.inner = this._imageEditor.element;
  5721. // read the data
  5722. this._imageEditor.open(
  5723. // send copy of canvas to the editor
  5724. cloneCanvas(this._data.input.image),
  5725. // determine ratio
  5726. this._options.ratio === 'free' ? null : this._ratio,
  5727. // the initial crop to show
  5728. this._data.actions.crop,
  5729. // the initial rotation of the image
  5730. this._data.actions.rotation,
  5731. // handle editor load
  5732. function () {
  5733. _this26._showEditor();
  5734. _this26._hideButtons();
  5735. _this26._hideStatus();
  5736. });
  5737. }
  5738. }, {
  5739. key: '_doRemove',
  5740. value: function _doRemove() {
  5741. var _this27 = this;
  5742. // cannot remove when is only one image
  5743. if (this._isImageOnly()) {
  5744. return;
  5745. }
  5746. this._clearState();
  5747. this._addState('empty');
  5748. this._hasInitialImage = false;
  5749. this._imageHopper.enabled = true;
  5750. if (this._isRequired) {
  5751. this._input.required = true;
  5752. }
  5753. var out = this._getOutro();
  5754. if (out) {
  5755. out.style.opacity = '0';
  5756. }
  5757. // get public available clone of data
  5758. var data = this.data;
  5759. // now reset all data
  5760. this._resetData();
  5761. setTimeout(function () {
  5762. if (_this27._isBeingDestroyed) {
  5763. return;
  5764. }
  5765. _this27._hideButtons(function () {
  5766. _this27._toggleButton('upload', true);
  5767. });
  5768. _this27._hideStatus();
  5769. _this27._hideResult();
  5770. _this27._options.didRemove.apply(_this27, [data]);
  5771. }, this.isDetached() ? 0 : 250);
  5772. return data;
  5773. }
  5774. }, {
  5775. key: '_doUpload',
  5776. value: function _doUpload(callback) {
  5777. var _this28 = this;
  5778. // if no input data available, can't upload anything
  5779. if (!this._data.input.image) {
  5780. return;
  5781. }
  5782. this._addState('upload');
  5783. this._startProgress();
  5784. this._hideButtons(function () {
  5785. // block upload button
  5786. _this28._toggleButton('upload', false);
  5787. _this28._save(function (err, data, res) {
  5788. _this28._removeState('upload');
  5789. _this28._stopProgress();
  5790. if (callback) {
  5791. callback.apply(_this28, [err, data, res]);
  5792. }
  5793. if (err) {
  5794. _this28._toggleButton('upload', true);
  5795. }
  5796. _this28._showButtons();
  5797. });
  5798. });
  5799. }
  5800. }, {
  5801. key: '_doDownload',
  5802. value: function _doDownload() {
  5803. var image = this._data.output.image;
  5804. if (!image) {
  5805. return;
  5806. }
  5807. downloadCanvas(image, this._data.input.name, this._data.input.type);
  5808. }
  5809. }, {
  5810. key: '_doDestroy',
  5811. value: function _doDestroy() {
  5812. var _this29 = this;
  5813. // set destroy flag to halt any running functionality
  5814. this._isBeingDestroyed = true;
  5815. // this removes the image hopper if it's attached
  5816. if (this._imageHopper) {
  5817. HopperEvents.forEach(function (e) {
  5818. _this29._imageHopper.element.removeEventListener(e, _this29);
  5819. });
  5820. this._imageHopper.destroy();
  5821. }
  5822. // this block removes the image editor
  5823. if (this._imageEditor) {
  5824. ImageEditorEvents.forEach(function (e) {
  5825. _this29._imageEditor.element.removeEventListener(e, _this29);
  5826. });
  5827. this._imageEditor.destroy();
  5828. }
  5829. // remove button event listeners
  5830. nodeListToArray(this._btnGroup.children).forEach(function (btn) {
  5831. btn.removeEventListener('click', _this29);
  5832. });
  5833. // stop listening to input
  5834. this._input.removeEventListener('change', this);
  5835. // detect if was wrapped, if so, remove wrapping (needs to have parent node)
  5836. if (this._element !== this._originalElement && this._element.parentNode) {
  5837. this._element.parentNode.replaceChild(this._originalElement, this._element);
  5838. }
  5839. // restore HTML of original element
  5840. this._originalElement.innerHTML = this._originalElementInner;
  5841. // get current attributes and remove all, then add original attributes
  5842. function matchesAttributeInList(a, attributes) {
  5843. return attributes.filter(function (attr) {
  5844. return a.name === attr.name && a.value === attr.value;
  5845. }).length !== 0;
  5846. }
  5847. var attributes = getElementAttributes(this._originalElement);
  5848. attributes.forEach(function (attribute) {
  5849. // if attribute is contained in original element attribute list and is the same, don't remove
  5850. if (matchesAttributeInList(attribute, _this29._originalElementAttributes)) {
  5851. return;
  5852. }
  5853. // else remove
  5854. _this29._originalElement.removeAttribute(attribute.name);
  5855. });
  5856. this._originalElementAttributes.forEach(function (attribute) {
  5857. // attribute was never removed
  5858. if (matchesAttributeInList(attribute, attributes)) {
  5859. return;
  5860. }
  5861. // add attribute
  5862. _this29._originalElement.setAttribute(attribute.name, attribute.value);
  5863. });
  5864. // now destroyed this counter so the total Slim count can be lowered
  5865. SlimCount = Math.max(0, SlimCount - 1);
  5866. // if slim count has reached 0 it's time to clean up the popover
  5867. if (SlimPopover && SlimCount === 0) {
  5868. SlimPopover.destroy();
  5869. SlimPopover = null;
  5870. }
  5871. }
  5872. }, {
  5873. key: 'dataBase64',
  5874. /**
  5875. * Public API
  5876. */
  5877. // properties
  5878. get: function get() {
  5879. return flattenData(this._data, this._options.post, this._options.jpegCompression);
  5880. }
  5881. }, {
  5882. key: 'data',
  5883. get: function get() {
  5884. return cloneData(this._data);
  5885. }
  5886. }, {
  5887. key: 'element',
  5888. get: function get() {
  5889. return this._element;
  5890. }
  5891. }, {
  5892. key: 'size',
  5893. set: function set(dimensions) {
  5894. if (!dimensions || !dimensions.width || !dimensions.height) {
  5895. return;
  5896. }
  5897. this._options.size = clone(dimensions);
  5898. this._data.actions.size = clone(dimensions);
  5899. }
  5900. }, {
  5901. key: 'ratio',
  5902. set: function set(ratio) {
  5903. if (!ratio || typeof ratio !== 'string') {
  5904. return;
  5905. }
  5906. this._options.ratio = ratio;
  5907. if (this._isFixedRatio()) {
  5908. var parts = intSplit(this._options.ratio, ':');
  5909. this._ratio = parts[1] / parts[0];
  5910. this._scaleDropArea(this._ratio);
  5911. }
  5912. }
  5913. }], [{
  5914. key: 'options',
  5915. value: function options() {
  5916. var defaults = {
  5917. // edit button is enabled by default
  5918. edit: true,
  5919. // immidiately summons editor on load
  5920. instantEdit: false,
  5921. // ratio of crop by default is the same as input image ratio
  5922. ratio: 'free',
  5923. // dimensions to resize the resulting image to
  5924. size: null,
  5925. // initial crop settings for example: {x:0, y:0, width:100, height:100}
  5926. crop: null,
  5927. // post these values
  5928. post: ['output', 'actions'],
  5929. // call this service to submit cropped data
  5930. service: null,
  5931. // when service is set, and this is set to true, Soon will auto upload all crops (also auto crops)
  5932. push: false,
  5933. // default fallback name for field
  5934. defaultInputName: 'slim[]',
  5935. // minimum size of cropped area object with width and height property
  5936. minSize: {
  5937. width: 100,
  5938. height: 100
  5939. },
  5940. // maximum file size in MB to upload
  5941. maxFileSize: null,
  5942. // compression of JPEG (between 0 and 100)
  5943. jpegCompression: null,
  5944. // render download link
  5945. download: false,
  5946. // save initially loaded image
  5947. saveInitialImage: false,
  5948. // the type to force (jpe|jpg|jpeg or png)
  5949. forceType: false,
  5950. // label HTML to show inside drop area
  5951. label: '<p>Drop your image here</p>',
  5952. // error messages
  5953. statusFileType: '<p>Invalid file type, expects: $0.</p>',
  5954. statusFileSize: '<p>File is too big, maximum file size: $0 MB.</p>',
  5955. statusNoSupport: '<p>Your browser does not support image cropping.</p>',
  5956. statusImageTooSmall: '<p>Image is too small, minimum size is: $0 pixels.</p>',
  5957. statusContentLength: '<span class="slim-upload-status-icon"></span> The file is probably too big',
  5958. statusUnknownResponse: '<span class="slim-upload-status-icon"></span> An unknown error occurred',
  5959. statusUploadSuccess: '<span class="slim-upload-status-icon"></span> Saved',
  5960. // callback methods
  5961. didInit: function didInit(data) {},
  5962. didLoad: function didLoad(file, image, meta) {
  5963. return true;
  5964. },
  5965. didSave: function didSave(data) {},
  5966. didUpload: function didUpload(err, data, res) {},
  5967. didRemove: function didRemove(data) {},
  5968. didTransform: function didTransform(data) {},
  5969. willTransform: function willTransform(data, cb) {
  5970. cb(data);
  5971. },
  5972. willSave: function willSave(data, cb) {
  5973. cb(data);
  5974. },
  5975. willRemove: function willRemove(data, cb) {
  5976. cb();
  5977. },
  5978. willRequest: function willRequest(xhr) {}
  5979. };
  5980. // add default button labels
  5981. SlimButtons.concat(ImageEditor.Buttons).forEach(function (btn) {
  5982. var capitalized = capitalizeFirstLetter(btn);
  5983. defaults['button' + capitalized + 'ClassName'] = null;
  5984. defaults['button' + capitalized + 'Label'] = capitalized;
  5985. defaults['button' + capitalized + 'Title'] = null;
  5986. });
  5987. return defaults;
  5988. }
  5989. }]);
  5990. return Slim;
  5991. }();
  5992. /**
  5993. * Slim Static Methods
  5994. */
  5995. (function () {
  5996. var instances = [];
  5997. var indexOfElement = function indexOfElement(element) {
  5998. var i = 0;
  5999. var l = instances.length;
  6000. for (; i < l; i++) {
  6001. if (instances[i].isAttachedTo(element)) {
  6002. return i;
  6003. }
  6004. }
  6005. return -1;
  6006. };
  6007. function getData(el, attribute) {
  6008. return el.getAttribute('data-' + attribute);
  6009. }
  6010. function toAttribute(property) {
  6011. return property.replace(/([a-z](?=[A-Z]))/g, '$1-').toLowerCase();
  6012. }
  6013. function toLabel(v) {
  6014. // if value set, use as label
  6015. if (v) {
  6016. return '<p>' + v + '</p>';
  6017. }
  6018. // else use default text
  6019. return null;
  6020. }
  6021. function toFunctionReference(name) {
  6022. var ref = window;
  6023. var levels = name.split('.');
  6024. levels.forEach(function (level, index) {
  6025. ref = ref[levels[index]];
  6026. });
  6027. return ref;
  6028. }
  6029. var passThrough = function passThrough(e, v) {
  6030. return v;
  6031. };
  6032. var defaultFalse = function defaultFalse(e, v) {
  6033. return v === 'true';
  6034. };
  6035. var defaultTrue = function defaultTrue(e, v) {
  6036. return v ? v === 'true' : true;
  6037. };
  6038. var defaultLabel = function defaultLabel(e, v) {
  6039. return toLabel(v);
  6040. };
  6041. var defaultFunction = function defaultFunction(e, v) {
  6042. return v ? toFunctionReference(v) : null;
  6043. };
  6044. var defaultSize = function defaultSize(e, v) {
  6045. if (!v) {
  6046. return null;
  6047. }
  6048. var parts = intSplit(v, ',');
  6049. return {
  6050. width: parts[0],
  6051. height: parts[1]
  6052. };
  6053. };
  6054. var toFloat = function toFloat(e, v) {
  6055. if (!v) {
  6056. return null;
  6057. }
  6058. return parseFloat(v);
  6059. };
  6060. var toInt = function toInt(e, v) {
  6061. if (!v) {
  6062. return null;
  6063. }
  6064. return parseInt(v, 10);
  6065. };
  6066. var toRect = function toRect(e, v) {
  6067. if (!v) {
  6068. return null;
  6069. }
  6070. var obj = {};
  6071. v.split(',').map(function (p) {
  6072. return parseInt(p, 10);
  6073. }).forEach(function (v, i) {
  6074. obj[Rect[i]] = v;
  6075. });
  6076. return obj;
  6077. };
  6078. var defaults = {
  6079. // is user allowed to download the cropped image?
  6080. 'download': defaultFalse,
  6081. // is user allowed to edit the cropped image?
  6082. 'edit': defaultTrue,
  6083. // open editor immidiately on file drop
  6084. 'instantEdit': defaultFalse,
  6085. // minimum crop size in pixels of original image
  6086. 'minSize': defaultSize,
  6087. // the final size of the output image
  6088. 'size': defaultSize,
  6089. // url to post to
  6090. 'service': function service(e, v) {
  6091. return typeof v === 'undefined' ? null : v;
  6092. },
  6093. // set auto push mode
  6094. 'push': defaultFalse,
  6095. // set crop rect
  6096. 'crop': toRect,
  6097. // what to post
  6098. 'post': function post(e, v) {
  6099. if (!v) {
  6100. return null;
  6101. }
  6102. return v.split(',').map(function (item) {
  6103. return item.trim();
  6104. });
  6105. },
  6106. // default input name
  6107. 'defaultInputName': passThrough,
  6108. // the ratio of the crop
  6109. 'ratio': function ratio(e, v) {
  6110. if (!v) {
  6111. return null;
  6112. }
  6113. return v;
  6114. },
  6115. // maximum file size
  6116. 'maxFileSize': toFloat,
  6117. // jpeg compression
  6118. 'jpegCompression': toInt,
  6119. // sets file type to force output to
  6120. 'forceType': defaultFalse,
  6121. // bool determining if initial image should be saved
  6122. 'saveInitialImage': defaultFalse,
  6123. // default labels
  6124. 'label': defaultLabel
  6125. };
  6126. // labels
  6127. ['FileSize', 'FileType', 'NoSupport', 'ImageTooSmall'].forEach(function (status) {
  6128. defaults['status' + status] = defaultLabel;
  6129. });
  6130. // status
  6131. ['ContentLength', 'UnknownResponse', 'UploadSuccess'].forEach(function (status) {
  6132. defaults['status' + status] = passThrough;
  6133. });
  6134. // the did callbacks
  6135. ['Init', 'Load', 'Save', 'Upload', 'Remove', 'Transform'].forEach(function (cb) {
  6136. defaults['did' + cb] = defaultFunction;
  6137. });
  6138. // the will callbacks
  6139. ['Transform', 'Save', 'Remove', 'Request'].forEach(function (cb) {
  6140. defaults['will' + cb] = defaultFunction;
  6141. });
  6142. // button defaults
  6143. var buttonOptions = ['ClassName', 'Label', 'Title'];
  6144. SlimButtons.concat(ImageEditor.Buttons).forEach(function (btn) {
  6145. var capitalized = capitalizeFirstLetter(btn);
  6146. buttonOptions.forEach(function (opt) {
  6147. defaults['button' + capitalized + opt] = passThrough;
  6148. });
  6149. });
  6150. Slim.supported = function () {
  6151. return typeof window.FileReader !== 'undefined';
  6152. }();
  6153. Slim.parse = function (context) {
  6154. var elements;
  6155. var element;
  6156. var i;
  6157. var croppers = [];
  6158. // find all crop elements and bind Crop behavior
  6159. elements = context.querySelectorAll('.slim:not([data-state])');
  6160. i = elements.length;
  6161. while (i--) {
  6162. element = elements[i];
  6163. croppers.push(Slim.create(element, Slim.getOptionsFromAttributes(element)));
  6164. }
  6165. return croppers;
  6166. };
  6167. Slim.getOptionsFromAttributes = function (element) {
  6168. var options = {};
  6169. for (var prop in defaults) {
  6170. if (!defaults.hasOwnProperty(prop)) {
  6171. continue;
  6172. }
  6173. var _value = defaults[prop](element, getData(element, toAttribute(prop)));
  6174. options[prop] = _value === null ? clone(Slim.options()[prop]) : _value;
  6175. }
  6176. return options;
  6177. };
  6178. Slim.find = function (element) {
  6179. var result = instances.filter(function (instance) {
  6180. return instance.isAttachedTo(element);
  6181. });
  6182. return result ? result[0] : null;
  6183. };
  6184. Slim.create = function (element, options) {
  6185. // if already in array, can't create another slim
  6186. if (Slim.find(element)) {
  6187. return;
  6188. }
  6189. // if no options supplied, try to get the options from the element
  6190. if (!options) {
  6191. options = Slim.getOptionsFromAttributes(element);
  6192. }
  6193. // instance
  6194. var slim = new Slim(element, options);
  6195. // add new slim
  6196. instances.push(slim);
  6197. // return the slim instance
  6198. return slim;
  6199. };
  6200. Slim.destroy = function (element) {
  6201. var index = indexOfElement(element);
  6202. if (index < 0) {
  6203. return false;
  6204. }
  6205. instances[index].destroy();
  6206. instances[index] = null;
  6207. instances.splice(index, 1);
  6208. return true;
  6209. };
  6210. })();
  6211. return Slim;
  6212. }());
  6213. // Create callback scope
  6214. var callbacks = {};
  6215. for (var key in Slim.options()) {
  6216. if (/^(did|will)/.test(key)) {
  6217. callbacks[key] = '&';
  6218. }
  6219. }
  6220. // Angular Directive
  6221. angular
  6222. .module('slim', [])
  6223. .directive('slim', function($timeout) {
  6224. return {
  6225. restrict: 'C',
  6226. scope: callbacks,
  6227. link: function(scope, element, attributes) {
  6228. $timeout(function () {
  6229. // get options
  6230. var options = Slim.getOptionsFromAttributes(element[0]);
  6231. // add callbacks
  6232. for (var key in attributes) {
  6233. if (/^(did|will)/.test(key)) {
  6234. options[key] = scope[key]();
  6235. }
  6236. }
  6237. // create slim
  6238. Slim.create(element[0], options);
  6239. });
  6240. }
  6241. };
  6242. });
  6243. }(angular));