Non puoi selezionare più di 25 argomenti Gli argomenti devono iniziare con una lettera o un numero, possono includere trattini ('-') e possono essere lunghi fino a 35 caratteri.
 
 
 
 
 

7513 righe
190 KiB

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