You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

7599 rivejä
192 KiB

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