Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 
 
 
 

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