|
- /*
- * Slim v3.1.1 - Image Cropping Made Easy
- * Copyright (c) 2016 Rik Schennink - http://slim.pqina.nl
- */
- (function($,undefined){
-
- 'use strict';
-
- // if no jquery, stop here
- if (!$) {return;}
-
- // library reference
- var Slim = (function() {
-
- // custom event polyfill for IE10
- (function() {
- function CustomEvent(event, params) {
- params = params || { bubbles: false, cancelable: false, detail: undefined };
- var evt = document.createEvent('CustomEvent');
- evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
- return evt;
- }
- CustomEvent.prototype = window.CustomEvent.prototype;
- window.CustomEvent = CustomEvent;
- })();
- /*
- * JavaScript Load Image
- * https://github.com/blueimp/JavaScript-Load-Image
- *
- * Copyright 2011, Sebastian Tschan
- * https://blueimp.net
- *
- * Licensed under the MIT license:
- * http://www.opensource.org/licenses/MIT
- */
-
- /*global define, module, window, document, URL, webkitURL, FileReader */
-
- // Loads an image for a given File object.
- // Invokes the callback with an img or optional canvas
- // element (if supported by the browser) as parameter:
- var loadImage = function (file, callback, options) {
- var img = document.createElement('img')
- var url
- var oUrl
- img.onerror = callback
- img.onload = function () {
- if (oUrl && !(options && options.noRevoke)) {
- loadImage.revokeObjectURL(oUrl)
- }
- if (callback) {
- callback(loadImage.scale(img, options))
- }
- }
- if (loadImage.isInstanceOf('Blob', file) ||
- // Files are also Blob instances, but some browsers
- // (Firefox 3.6) support the File API but not Blobs:
- loadImage.isInstanceOf('File', file)) {
- url = oUrl = loadImage.createObjectURL(file)
- // Store the file type for resize processing:
- img._type = file.type
- } else if (typeof file === 'string') {
- url = file
- if (options && options.crossOrigin) {
- img.crossOrigin = options.crossOrigin
- }
- } else {
- return false
- }
- if (url) {
- img.src = url
- return img
- }
- return loadImage.readFile(file, function (e) {
- var target = e.target
- if (target && target.result) {
- img.src = target.result
- } else {
- if (callback) {
- callback(e)
- }
- }
- })
- }
- // The check for URL.revokeObjectURL fixes an issue with Opera 12,
- // which provides URL.createObjectURL but doesn't properly implement it:
- var urlAPI = (window.createObjectURL && window) ||
- (window.URL && URL.revokeObjectURL && URL) ||
- (window.webkitURL && webkitURL)
-
- loadImage.isInstanceOf = function (type, obj) {
- // Cross-frame instanceof check
- return Object.prototype.toString.call(obj) === '[object ' + type + ']'
- }
-
- // Transform image coordinates, allows to override e.g.
- // the canvas orientation based on the orientation option,
- // gets canvas, options passed as arguments:
- loadImage.transformCoordinates = function () {
- return
- }
-
- // Returns transformed options, allows to override e.g.
- // maxWidth, maxHeight and crop options based on the aspectRatio.
- // gets img, options passed as arguments:
- loadImage.getTransformedOptions = function (img, options) {
- var aspectRatio = options.aspectRatio
- var newOptions
- var i
- var width
- var height
- if (!aspectRatio) {
- return options
- }
- newOptions = {}
- for (i in options) {
- if (options.hasOwnProperty(i)) {
- newOptions[i] = options[i]
- }
- }
- newOptions.crop = true
- width = img.naturalWidth || img.width
- height = img.naturalHeight || img.height
- if (width / height > aspectRatio) {
- newOptions.maxWidth = height * aspectRatio
- newOptions.maxHeight = height
- } else {
- newOptions.maxWidth = width
- newOptions.maxHeight = width / aspectRatio
- }
- return newOptions
- }
-
- // Canvas render method, allows to implement a different rendering algorithm:
- loadImage.renderImageToCanvas = function (
- canvas,
- img,
- sourceX,
- sourceY,
- sourceWidth,
- sourceHeight,
- destX,
- destY,
- destWidth,
- destHeight
- ) {
- canvas.getContext('2d').drawImage(
- img,
- sourceX,
- sourceY,
- sourceWidth,
- sourceHeight,
- destX,
- destY,
- destWidth,
- destHeight
- )
- return canvas
- }
-
- // This method is used to determine if the target image
- // should be a canvas element:
- loadImage.hasCanvasOption = function (options) {
- return options.canvas || options.crop || !!options.aspectRatio
- }
-
- // Scales and/or crops the given image (img or canvas HTML element)
- // using the given options.
- // Returns a canvas object if the browser supports canvas
- // and the hasCanvasOption method returns true or a canvas
- // object is passed as image, else the scaled image:
- loadImage.scale = function (img, options) {
- options = options || {}
- var canvas = document.createElement('canvas')
- var useCanvas = img.getContext ||
- (loadImage.hasCanvasOption(options) && canvas.getContext)
- var width = img.naturalWidth || img.width
- var height = img.naturalHeight || img.height
- var destWidth = width
- var destHeight = height
- var maxWidth
- var maxHeight
- var minWidth
- var minHeight
- var sourceWidth
- var sourceHeight
- var sourceX
- var sourceY
- var pixelRatio
- var downsamplingRatio
- var tmp
- function scaleUp () {
- var scale = Math.max(
- (minWidth || destWidth) / destWidth,
- (minHeight || destHeight) / destHeight
- )
- if (scale > 1) {
- destWidth *= scale
- destHeight *= scale
- }
- }
- function scaleDown () {
- var scale = Math.min(
- (maxWidth || destWidth) / destWidth,
- (maxHeight || destHeight) / destHeight
- )
- if (scale < 1) {
- destWidth *= scale
- destHeight *= scale
- }
- }
- if (useCanvas) {
- options = loadImage.getTransformedOptions(img, options)
- sourceX = options.left || 0
- sourceY = options.top || 0
- if (options.sourceWidth) {
- sourceWidth = options.sourceWidth
- if (options.right !== undefined && options.left === undefined) {
- sourceX = width - sourceWidth - options.right
- }
- } else {
- sourceWidth = width - sourceX - (options.right || 0)
- }
- if (options.sourceHeight) {
- sourceHeight = options.sourceHeight
- if (options.bottom !== undefined && options.top === undefined) {
- sourceY = height - sourceHeight - options.bottom
- }
- } else {
- sourceHeight = height - sourceY - (options.bottom || 0)
- }
- destWidth = sourceWidth
- destHeight = sourceHeight
- }
- maxWidth = options.maxWidth
- maxHeight = options.maxHeight
- minWidth = options.minWidth
- minHeight = options.minHeight
- if (useCanvas && maxWidth && maxHeight && options.crop) {
- destWidth = maxWidth
- destHeight = maxHeight
- tmp = sourceWidth / sourceHeight - maxWidth / maxHeight
- if (tmp < 0) {
- sourceHeight = maxHeight * sourceWidth / maxWidth
- if (options.top === undefined && options.bottom === undefined) {
- sourceY = (height - sourceHeight) / 2
- }
- } else if (tmp > 0) {
- sourceWidth = maxWidth * sourceHeight / maxHeight
- if (options.left === undefined && options.right === undefined) {
- sourceX = (width - sourceWidth) / 2
- }
- }
- } else {
- if (options.contain || options.cover) {
- minWidth = maxWidth = maxWidth || minWidth
- minHeight = maxHeight = maxHeight || minHeight
- }
- if (options.cover) {
- scaleDown()
- scaleUp()
- } else {
- scaleUp()
- scaleDown()
- }
- }
- if (useCanvas) {
- pixelRatio = options.pixelRatio
- if (pixelRatio > 1) {
- canvas.style.width = destWidth + 'px'
- canvas.style.height = destHeight + 'px'
- destWidth *= pixelRatio
- destHeight *= pixelRatio
- canvas.getContext('2d').scale(pixelRatio, pixelRatio)
- }
- downsamplingRatio = options.downsamplingRatio
- if (downsamplingRatio > 0 && downsamplingRatio < 1 &&
- destWidth < sourceWidth && destHeight < sourceHeight) {
- while (sourceWidth * downsamplingRatio > destWidth) {
- canvas.width = sourceWidth * downsamplingRatio
- canvas.height = sourceHeight * downsamplingRatio
- loadImage.renderImageToCanvas(
- canvas,
- img,
- sourceX,
- sourceY,
- sourceWidth,
- sourceHeight,
- 0,
- 0,
- canvas.width,
- canvas.height
- )
- sourceWidth = canvas.width
- sourceHeight = canvas.height
- img = document.createElement('canvas')
- img.width = sourceWidth
- img.height = sourceHeight
- loadImage.renderImageToCanvas(
- img,
- canvas,
- 0,
- 0,
- sourceWidth,
- sourceHeight,
- 0,
- 0,
- sourceWidth,
- sourceHeight
- )
- }
- }
- canvas.width = destWidth
- canvas.height = destHeight
- loadImage.transformCoordinates(
- canvas,
- options
- )
- return loadImage.renderImageToCanvas(
- canvas,
- img,
- sourceX,
- sourceY,
- sourceWidth,
- sourceHeight,
- 0,
- 0,
- destWidth,
- destHeight
- )
- }
- img.width = destWidth
- img.height = destHeight
- return img
- }
-
- loadImage.createObjectURL = function (file) {
- return urlAPI ? urlAPI.createObjectURL(file) : false
- }
-
- loadImage.revokeObjectURL = function (url) {
- return urlAPI ? urlAPI.revokeObjectURL(url) : false
- }
-
- // Loads a given File object via FileReader interface,
- // invokes the callback with the event object (load or error).
- // The result can be read via event.target.result:
- loadImage.readFile = function (file, callback, method) {
- if (window.FileReader) {
- var fileReader = new FileReader()
- fileReader.onload = fileReader.onerror = callback
- method = method || 'readAsDataURL'
- if (fileReader[method]) {
- fileReader[method](file)
- return fileReader
- }
- }
- return false
- }
-
- var originalHasCanvasOption = loadImage.hasCanvasOption
- var originalTransformCoordinates = loadImage.transformCoordinates
- var originalGetTransformedOptions = loadImage.getTransformedOptions
-
- // This method is used to determine if the target image
- // should be a canvas element:
- loadImage.hasCanvasOption = function (options) {
- return !!options.orientation ||
- originalHasCanvasOption.call(loadImage, options)
- }
-
- // Transform image orientation based on
- // the given EXIF orientation option:
- loadImage.transformCoordinates = function (canvas, options) {
- originalTransformCoordinates.call(loadImage, canvas, options)
- var ctx = canvas.getContext('2d')
- var width = canvas.width
- var height = canvas.height
- var styleWidth = canvas.style.width
- var styleHeight = canvas.style.height
- var orientation = options.orientation
- if (!orientation || orientation > 8) {
- return
- }
- if (orientation > 4) {
- canvas.width = height
- canvas.height = width
- canvas.style.width = styleHeight
- canvas.style.height = styleWidth
- }
- switch (orientation) {
- case 2:
- // horizontal flip
- ctx.translate(width, 0)
- ctx.scale(-1, 1)
- break
- case 3:
- // 180° rotate left
- ctx.translate(width, height)
- ctx.rotate(Math.PI)
- break
- case 4:
- // vertical flip
- ctx.translate(0, height)
- ctx.scale(1, -1)
- break
- case 5:
- // vertical flip + 90 rotate right
- ctx.rotate(0.5 * Math.PI)
- ctx.scale(1, -1)
- break
- case 6:
- // 90° rotate right
- ctx.rotate(0.5 * Math.PI)
- ctx.translate(0, -height)
- break
- case 7:
- // horizontal flip + 90 rotate right
- ctx.rotate(0.5 * Math.PI)
- ctx.translate(width, -height)
- ctx.scale(-1, 1)
- break
- case 8:
- // 90° rotate left
- ctx.rotate(-0.5 * Math.PI)
- ctx.translate(-width, 0)
- break
- }
- }
-
- // Transforms coordinate and dimension options
- // based on the given orientation option:
- loadImage.getTransformedOptions = function (img, opts) {
- var options = originalGetTransformedOptions.call(loadImage, img, opts)
- var orientation = options.orientation
- var newOptions
- var i
- if (!orientation || orientation > 8 || orientation === 1) {
- return options
- }
- newOptions = {}
- for (i in options) {
- if (options.hasOwnProperty(i)) {
- newOptions[i] = options[i]
- }
- }
- switch (options.orientation) {
- case 2:
- // horizontal flip
- newOptions.left = options.right
- newOptions.right = options.left
- break
- case 3:
- // 180° rotate left
- newOptions.left = options.right
- newOptions.top = options.bottom
- newOptions.right = options.left
- newOptions.bottom = options.top
- break
- case 4:
- // vertical flip
- newOptions.top = options.bottom
- newOptions.bottom = options.top
- break
- case 5:
- // vertical flip + 90 rotate right
- newOptions.left = options.top
- newOptions.top = options.left
- newOptions.right = options.bottom
- newOptions.bottom = options.right
- break
- case 6:
- // 90° rotate right
- newOptions.left = options.top
- newOptions.top = options.right
- newOptions.right = options.bottom
- newOptions.bottom = options.left
- break
- case 7:
- // horizontal flip + 90 rotate right
- newOptions.left = options.bottom
- newOptions.top = options.right
- newOptions.right = options.top
- newOptions.bottom = options.left
- break
- case 8:
- // 90° rotate left
- newOptions.left = options.bottom
- newOptions.top = options.left
- newOptions.right = options.top
- newOptions.bottom = options.right
- break
- }
- if (options.orientation > 4) {
- newOptions.maxWidth = options.maxHeight
- newOptions.maxHeight = options.maxWidth
- newOptions.minWidth = options.minHeight
- newOptions.minHeight = options.minWidth
- newOptions.sourceWidth = options.sourceHeight
- newOptions.sourceHeight = options.sourceWidth
- }
- return newOptions
- }
-
- var hasblobSlice = window.Blob && (Blob.prototype.slice ||
- Blob.prototype.webkitSlice || Blob.prototype.mozSlice)
-
- loadImage.blobSlice = hasblobSlice && function () {
- var slice = this.slice || this.webkitSlice || this.mozSlice
- return slice.apply(this, arguments)
- }
-
- loadImage.metaDataParsers = {
- jpeg: {
- 0xffe1: [] // APP1 marker
- }
- }
-
- // Parses image meta data and calls the callback with an object argument
- // with the following properties:
- // * imageHead: The complete image head as ArrayBuffer (Uint8Array for IE10)
- // The options arguments accepts an object and supports the following properties:
- // * maxMetaDataSize: Defines the maximum number of bytes to parse.
- // * disableImageHead: Disables creating the imageHead property.
- loadImage.parseMetaData = function (file, callback, options) {
- options = options || {}
- var that = this
- // 256 KiB should contain all EXIF/ICC/IPTC segments:
- var maxMetaDataSize = options.maxMetaDataSize || 262144
- var data = {}
- var noMetaData = !(window.DataView && file && file.size >= 12 &&
- file.type === 'image/jpeg' && loadImage.blobSlice)
- if (noMetaData || !loadImage.readFile(
- loadImage.blobSlice.call(file, 0, maxMetaDataSize),
- function (e) {
- if (e.target.error) {
- // FileReader error
- //console.log(e.target.error)
- callback(data)
- return
- }
- // Note on endianness:
- // Since the marker and length bytes in JPEG files are always
- // stored in big endian order, we can leave the endian parameter
- // of the DataView methods undefined, defaulting to big endian.
- var buffer = e.target.result
- var dataView = new DataView(buffer)
- var offset = 2
- var maxOffset = dataView.byteLength - 4
- var headLength = offset
- var markerBytes
- var markerLength
- var parsers
- var i
- // Check for the JPEG marker (0xffd8):
- if (dataView.getUint16(0) === 0xffd8) {
- while (offset < maxOffset) {
- markerBytes = dataView.getUint16(offset)
- // Search for APPn (0xffeN) and COM (0xfffe) markers,
- // which contain application-specific meta-data like
- // Exif, ICC and IPTC data and text comments:
- if ((markerBytes >= 0xffe0 && markerBytes <= 0xffef) ||
- markerBytes === 0xfffe) {
- // The marker bytes (2) are always followed by
- // the length bytes (2), indicating the length of the
- // marker segment, which includes the length bytes,
- // but not the marker bytes, so we add 2:
- markerLength = dataView.getUint16(offset + 2) + 2
- if (offset + markerLength > dataView.byteLength) {
- //console.log('Invalid meta data: Invalid segment size.')
- break
- }
- parsers = loadImage.metaDataParsers.jpeg[markerBytes]
- if (parsers) {
- for (i = 0; i < parsers.length; i += 1) {
- parsers[i].call(
- that,
- dataView,
- offset,
- markerLength,
- data,
- options
- )
- }
- }
- offset += markerLength
- headLength = offset
- } else {
- // Not an APPn or COM marker, probably safe to
- // assume that this is the end of the meta data
- break
- }
- }
- // Meta length must be longer than JPEG marker (2)
- // plus APPn marker (2), followed by length bytes (2):
- if (!options.disableImageHead && headLength > 6) {
- if (buffer.slice) {
- data.imageHead = buffer.slice(0, headLength)
- } else {
- // Workaround for IE10, which does not yet
- // support ArrayBuffer.slice:
- data.imageHead = new Uint8Array(buffer)
- .subarray(0, headLength)
- }
- }
- } else {
- //console.log('Invalid JPEG file: Missing JPEG marker.')
- }
- callback(data)
- },
- 'readAsArrayBuffer'
- )) {
- callback(data)
- }
- }
-
- loadImage.ExifMap = function () {
- return this
- }
-
- loadImage.ExifMap.prototype.map = {
- 'Orientation': 0x0112
- }
-
- loadImage.ExifMap.prototype.get = function (id) {
- return this[id] || this[this.map[id]]
- }
-
- loadImage.getExifThumbnail = function (dataView, offset, length) {
- var hexData,
- i,
- b
- if (!length || offset + length > dataView.byteLength) {
- //console.log('Invalid Exif data: Invalid thumbnail data.')
- return
- }
- hexData = []
- for (i = 0; i < length; i += 1) {
- b = dataView.getUint8(offset + i)
- hexData.push((b < 16 ? '0' : '') + b.toString(16))
- }
- return 'data:image/jpeg,%' + hexData.join('%')
- }
-
- loadImage.exifTagTypes = {
- // byte, 8-bit unsigned int:
- 1: {
- getValue: function (dataView, dataOffset) {
- return dataView.getUint8(dataOffset)
- },
- size: 1
- },
- // ascii, 8-bit byte:
- 2: {
- getValue: function (dataView, dataOffset) {
- return String.fromCharCode(dataView.getUint8(dataOffset))
- },
- size: 1,
- ascii: true
- },
- // short, 16 bit int:
- 3: {
- getValue: function (dataView, dataOffset, littleEndian) {
- return dataView.getUint16(dataOffset, littleEndian)
- },
- size: 2
- },
- // long, 32 bit int:
- 4: {
- getValue: function (dataView, dataOffset, littleEndian) {
- return dataView.getUint32(dataOffset, littleEndian)
- },
- size: 4
- },
- // rational = two long values, first is numerator, second is denominator:
- 5: {
- getValue: function (dataView, dataOffset, littleEndian) {
- return dataView.getUint32(dataOffset, littleEndian) /
- dataView.getUint32(dataOffset + 4, littleEndian)
- },
- size: 8
- },
- // slong, 32 bit signed int:
- 9: {
- getValue: function (dataView, dataOffset, littleEndian) {
- return dataView.getInt32(dataOffset, littleEndian)
- },
- size: 4
- },
- // srational, two slongs, first is numerator, second is denominator:
- 10: {
- getValue: function (dataView, dataOffset, littleEndian) {
- return dataView.getInt32(dataOffset, littleEndian) /
- dataView.getInt32(dataOffset + 4, littleEndian)
- },
- size: 8
- }
- }
- // undefined, 8-bit byte, value depending on field:
- loadImage.exifTagTypes[7] = loadImage.exifTagTypes[1]
-
- loadImage.getExifValue = function (dataView, tiffOffset, offset, type, length, littleEndian) {
- var tagType = loadImage.exifTagTypes[type]
- var tagSize
- var dataOffset
- var values
- var i
- var str
- var c
- if (!tagType) {
- //console.log('Invalid Exif data: Invalid tag type.')
- return
- }
- tagSize = tagType.size * length
- // Determine if the value is contained in the dataOffset bytes,
- // or if the value at the dataOffset is a pointer to the actual data:
- dataOffset = tagSize > 4
- ? tiffOffset + dataView.getUint32(offset + 8, littleEndian)
- : (offset + 8)
- if (dataOffset + tagSize > dataView.byteLength) {
- //console.log('Invalid Exif data: Invalid data offset.')
- return
- }
- if (length === 1) {
- return tagType.getValue(dataView, dataOffset, littleEndian)
- }
- values = []
- for (i = 0; i < length; i += 1) {
- values[i] = tagType.getValue(dataView, dataOffset + i * tagType.size, littleEndian)
- }
- if (tagType.ascii) {
- str = ''
- // Concatenate the chars:
- for (i = 0; i < values.length; i += 1) {
- c = values[i]
- // Ignore the terminating NULL byte(s):
- if (c === '\u0000') {
- break
- }
- str += c
- }
- return str
- }
- return values
- }
-
- loadImage.parseExifTag = function (dataView, tiffOffset, offset, littleEndian, data) {
- var tag = dataView.getUint16(offset, littleEndian)
- data.exif[tag] = loadImage.getExifValue(
- dataView,
- tiffOffset,
- offset,
- dataView.getUint16(offset + 2, littleEndian), // tag type
- dataView.getUint32(offset + 4, littleEndian), // tag length
- littleEndian
- )
- }
-
- loadImage.parseExifTags = function (dataView, tiffOffset, dirOffset, littleEndian, data) {
- var tagsNumber,
- dirEndOffset,
- i
- if (dirOffset + 6 > dataView.byteLength) {
- //console.log('Invalid Exif data: Invalid directory offset.')
- return
- }
- tagsNumber = dataView.getUint16(dirOffset, littleEndian)
- dirEndOffset = dirOffset + 2 + 12 * tagsNumber
- if (dirEndOffset + 4 > dataView.byteLength) {
- //console.log('Invalid Exif data: Invalid directory size.')
- return
- }
- for (i = 0; i < tagsNumber; i += 1) {
- this.parseExifTag(
- dataView,
- tiffOffset,
- dirOffset + 2 + 12 * i, // tag offset
- littleEndian,
- data
- )
- }
- // Return the offset to the next directory:
- return dataView.getUint32(dirEndOffset, littleEndian)
- }
-
- loadImage.parseExifData = function (dataView, offset, length, data, options) {
- if (options.disableExif) {
- return
- }
- var tiffOffset = offset + 10
- var littleEndian
- var dirOffset
- var thumbnailData
- // Check for the ASCII code for "Exif" (0x45786966):
- if (dataView.getUint32(offset + 4) !== 0x45786966) {
- // No Exif data, might be XMP data instead
- return
- }
- if (tiffOffset + 8 > dataView.byteLength) {
- //console.log('Invalid Exif data: Invalid segment size.')
- return
- }
- // Check for the two null bytes:
- if (dataView.getUint16(offset + 8) !== 0x0000) {
- //console.log('Invalid Exif data: Missing byte alignment offset.')
- return
- }
- // Check the byte alignment:
- switch (dataView.getUint16(tiffOffset)) {
- case 0x4949:
- littleEndian = true
- break
- case 0x4D4D:
- littleEndian = false
- break
- default:
- //console.log('Invalid Exif data: Invalid byte alignment marker.')
- return
- }
- // Check for the TIFF tag marker (0x002A):
- if (dataView.getUint16(tiffOffset + 2, littleEndian) !== 0x002A) {
- //console.log('Invalid Exif data: Missing TIFF marker.')
- return
- }
- // Retrieve the directory offset bytes, usually 0x00000008 or 8 decimal:
- dirOffset = dataView.getUint32(tiffOffset + 4, littleEndian)
- // Create the exif object to store the tags:
- data.exif = new loadImage.ExifMap()
- // Parse the tags of the main image directory and retrieve the
- // offset to the next directory, usually the thumbnail directory:
- dirOffset = loadImage.parseExifTags(
- dataView,
- tiffOffset,
- tiffOffset + dirOffset,
- littleEndian,
- data
- )
- if (dirOffset && !options.disableExifThumbnail) {
- thumbnailData = {exif: {}}
- dirOffset = loadImage.parseExifTags(
- dataView,
- tiffOffset,
- tiffOffset + dirOffset,
- littleEndian,
- thumbnailData
- )
- // Check for JPEG Thumbnail offset:
- if (thumbnailData.exif[0x0201]) {
- data.exif.Thumbnail = loadImage.getExifThumbnail(
- dataView,
- tiffOffset + thumbnailData.exif[0x0201],
- thumbnailData.exif[0x0202] // Thumbnail data length
- )
- }
- }
- // Check for Exif Sub IFD Pointer:
- if (data.exif[0x8769] && !options.disableExifSub) {
- loadImage.parseExifTags(
- dataView,
- tiffOffset,
- tiffOffset + data.exif[0x8769], // directory offset
- littleEndian,
- data
- )
- }
- // Check for GPS Info IFD Pointer:
- if (data.exif[0x8825] && !options.disableExifGps) {
- loadImage.parseExifTags(
- dataView,
- tiffOffset,
- tiffOffset + data.exif[0x8825], // directory offset
- littleEndian,
- data
- )
- }
- }
-
- // Registers the Exif parser for the APP1 JPEG meta data segment:
- loadImage.metaDataParsers.jpeg[0xffe1].push(loadImage.parseExifData)
- var snabbt = (function() {
-
- var tickRequests = [];
- var runningAnimations = [];
- var completedAnimations = [];
- var transformProperty = 'transform';
-
- // Find which vendor prefix to use
- var styles = window.getComputedStyle(document.documentElement, '');
- var vendorPrefix = (Array.prototype.slice
- .call(styles)
- .join('')
- .match(/-(moz|webkit|ms)-/) || (styles.OLink === '' && ['', 'o'])
- )[1];
- if(vendorPrefix === 'webkit')
- transformProperty = 'webkitTransform';
-
- /* Entry point, only function to be called by user */
- var snabbt = function(arg1, arg2, arg3) {
-
- var elements = arg1;
-
- // If argument is an Array or a NodeList or other list type that can be iterable.
- // Loop through and start one animation for each element.
- if(elements.length !== undefined) {
- var aggregateChainer = {
- chainers: [],
- then: function(opts) {
- return this.snabbt(opts);
- },
- snabbt: function(opts) {
- var len = this.chainers.length;
- this.chainers.forEach(function(chainer, index) {
- chainer.snabbt(preprocessOptions(opts, index, len));
- });
- return aggregateChainer;
- },
- setValue: function(value) {
- this.chainers.forEach(function(chainer) {
- chainer.setValue(value);
- });
- return aggregateChainer;
- },
- finish: function() {
- this.chainers.forEach(function(chainer) {
- chainer.finish();
- });
- return aggregateChainer;
- },
- rollback: function() {
- this.chainers.forEach(function(chainer) {
- chainer.rollback();
- });
- return aggregateChainer;
- }
- };
-
- for(var i=0, len=elements.length;i<len;++i) {
- if(typeof arg2 == 'string')
- aggregateChainer.chainers.push(snabbtSingleElement(elements[i], arg2, preprocessOptions(arg3, i, len)));
- else
- aggregateChainer.chainers.push(snabbtSingleElement(elements[i], preprocessOptions(arg2, i, len), arg3));
- }
- return aggregateChainer;
- } else {
- if(typeof arg2 == 'string')
- return snabbtSingleElement(elements, arg2, preprocessOptions(arg3, 0, 1));
- else
- return snabbtSingleElement(elements, preprocessOptions(arg2, 0, 1), arg3);
- }
- };
-
- var preprocessOptions = function(options, index, len) {
- if(!options)
- return options;
- var clone = cloneObject(options);
-
- if(isFunction(options.delay)) {
- clone.delay = options.delay(index, len);
- }
-
- if(isFunction(options.callback)) {
- clone.complete = function() {
- options.callback.call(this, index, len);
- };
- }
-
- var hasAllDoneCallback = isFunction(options.allDone);
- var hasCompleteCallback = isFunction(options.complete);
-
- if(hasCompleteCallback || hasAllDoneCallback) {
- clone.complete = function() {
- if(hasCompleteCallback) {
- options.complete.call(this, index, len);
- }
- if(hasAllDoneCallback && (index == len - 1)) {
- options.allDone();
- }
- };
- }
-
- if(isFunction(options.valueFeeder)) {
- clone.valueFeeder = function(i, matrix) {
- return options.valueFeeder(i, matrix, index, len);
- };
- }
- if(isFunction(options.easing)) {
- clone.easing = function(i) {
- return options.easing(i, index, len);
- };
- }
-
- var properties = [
- 'position',
- 'rotation',
- 'skew',
- 'rotationPost',
- 'scale',
- 'width',
- 'height',
- 'opacity',
- 'fromPosition',
- 'fromRotation',
- 'fromSkew',
- 'fromRotationPost',
- 'fromScale',
- 'fromWidth',
- 'fromHeight',
- 'fromOpacity',
- 'transformOrigin',
- 'duration',
- 'delay'
- ];
-
- properties.forEach(function(property) {
- if(isFunction(options[property])) {
- clone[property] = options[property](index, len);
- }
- });
-
- return clone;
- };
-
- var snabbtSingleElement = function(element, arg2, arg3) {
- if(arg2 === 'attention')
- return setupAttentionAnimation(element, arg3);
- if(arg2 === 'stop')
- return stopAnimation(element);
- var options = arg2;
-
- // Remove orphaned end states
- clearOphanedEndStates();
-
- // If there is a running or past completed animation with element, use that end state as start state
- var currentState = currentAnimationState(element);
- var start = currentState;
- // from has precendance over current animation state
- start = stateFromOptions(options, start, true);
- var end = cloneObject(currentState);
- end = stateFromOptions(options, end);
-
- var animOptions = setupAnimationOptions(start, end, options);
- var animation = createAnimation(animOptions);
-
- runningAnimations.push([element, animation]);
-
- animation.updateElement(element, true);
- var queue = [];
- var chainer = {
- snabbt: function(opts) {
- queue.unshift(preprocessOptions(opts, 0, 1));
- return chainer;
- },
- then: function(opts) {
- return this.snabbt(opts);
- }
- };
-
- function tick(time) {
- animation.tick(time);
- animation.updateElement(element);
- if(animation.isStopped())
- return;
-
- if(!animation.completed())
- return queueTick(tick);
-
- if(options.loop > 1 && !animation.isStopped()) {
- // Loop current animation
- options.loop -= 1;
- animation.restart();
- queueTick(tick);
- } else {
- if(options.complete) {
- options.complete.call(element);
- }
-
- // Start next animation in queue
- if(queue.length) {
- options = queue.pop();
-
- start = stateFromOptions(options, end, true);
- end = stateFromOptions(options, cloneObject(end));
- options = setupAnimationOptions(start, end, options);
-
- animation = createAnimation(options);
- runningAnimations.push([element, animation]);
-
- animation.tick(time);
- queueTick(tick);
- }
- }
- }
-
- queueTick(tick);
- // Manual animations are not chainable, instead an animation controller object is returned
- // with setValue, finish and rollback methods
- if(options.manual)
- return animation;
- return chainer;
- };
-
- var setupAttentionAnimation = function(element, options) {
- var movement = stateFromOptions(options, createState({}));
- options.movement = movement;
- var animation = createAttentionAnimation(options);
-
- runningAnimations.push([element, animation]);
- function tick(time) {
- animation.tick(time);
- animation.updateElement(element);
- if(!animation.completed()) {
- queueTick(tick);
- } else {
- if(options.callback) {
- options.callback(element);
- }
- if(options.loop && options.loop > 1) {
- options.loop--;
- animation.restart();
- queueTick(tick);
- }
- }
- }
- queueTick(tick);
- };
-
- var stopAnimation = function(element) {
- for(var i= 0,len=runningAnimations.length;i<len;++i) {
- var currentAnimation = runningAnimations[i];
- var animatedElement = currentAnimation[0];
- var animation = currentAnimation[1];
-
- if(animatedElement === element) {
- animation.stop();
- }
- }
- };
-
- var findAnimationState = function(animationList, element) {
- for(var i=0,len=animationList.length;i<len;++i) {
- var currentAnimation = animationList[i];
- var animatedElement = currentAnimation[0];
- var animation = currentAnimation[1];
-
- if(animatedElement === element) {
- var state = animation.getCurrentState();
- animation.stop();
- return state;
- }
- }
- };
-
- var clearOphanedEndStates = function() {
- completedAnimations = completedAnimations.filter(function(animation) {
- return (findUltimateAncestor(animation[0]).body);
- });
- };
-
- var findUltimateAncestor = function(node) {
- var ancestor = node;
- while(ancestor.parentNode) {
- ancestor = ancestor.parentNode;
- }
- return ancestor;
- };
-
- /**
- * Returns the current state of element if there is an ongoing or previously finished
- * animation releated to it. Will also call stop on the animation.
- * TODO: The stopping of the animation is better put somewhere else
- */
- var currentAnimationState = function(element) {
- // Check if a completed animation is stored for this element
- var state = findAnimationState(runningAnimations, element);
- if(state)
- return state;
-
- return findAnimationState(completedAnimations, element);
- };
-
- /**
- * Parses an animation configuration object and returns a State instance
- */
- var stateFromOptions = function(options, state, useFromPrefix) {
- if (!state) {
- state = createState({
- position: [0, 0, 0],
- rotation: [0, 0, 0],
- rotationPost: [0, 0, 0],
- scale: [1, 1],
- skew: [0, 0]
- });
- }
- var position = 'position';
- var rotation = 'rotation';
- var skew = 'skew';
- var rotationPost = 'rotationPost';
- var scale = 'scale';
- var scalePost = 'scalePost';
- var width = 'width';
- var height = 'height';
- var opacity = 'opacity';
-
- if(useFromPrefix) {
- position = 'fromPosition';
- rotation = 'fromRotation';
- skew = 'fromSkew';
- rotationPost = 'fromRotationPost';
- scale = 'fromScale';
- scalePost = 'fromScalePost';
- width = 'fromWidth';
- height = 'fromHeight';
- opacity = 'fromOpacity';
- }
-
- state.position = optionOrDefault(options[position], state.position);
- state.rotation = optionOrDefault(options[rotation], state.rotation);
- state.rotationPost = optionOrDefault(options[rotationPost], state.rotationPost);
- state.skew = optionOrDefault(options[skew], state.skew);
- state.scale = optionOrDefault(options[scale], state.scale);
- state.scalePost = optionOrDefault(options[scalePost], state.scalePost);
- state.opacity = options[opacity];
- state.width = options[width];
- state.height = options[height];
-
- return state;
- };
-
- var setupAnimationOptions = function(start, end, options) {
- options.startState = start;
- options.endState = end;
- return options;
- };
-
- var polyFillrAF = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.msRequestAnimationFrame || function(callback) { return setTimeout(callback, 1000 / 60); };
-
- var queueTick = function(func) {
- if(tickRequests.length === 0)
- polyFillrAF(tickAnimations);
- tickRequests.push(func);
- };
-
- var tickAnimations = function(time) {
- var len = tickRequests.length;
- for(var i=0;i<len;++i) {
- tickRequests[i](time);
- }
- tickRequests.splice(0, len);
-
- var finishedAnimations = runningAnimations.filter(function(animation) {
- return animation[1].completed();
- });
-
- // See if there are any previously completed animations on the same element, if so, remove it before merging
- completedAnimations = completedAnimations.filter(function(animation) {
- for(var i=0,len=finishedAnimations.length;i<len;++i) {
- if(animation[0] === finishedAnimations[i][0]) {
- return false;
- }
- }
- return true;
- });
-
- completedAnimations = completedAnimations.concat(finishedAnimations);
-
- runningAnimations = runningAnimations.filter(function(animation) {
- return !animation[1].completed();
- });
-
- if(tickRequests.length !== 0)
- polyFillrAF(tickAnimations);
- };
-
-
- // Class for handling animation between two states
- var createAnimation = function(options) {
- var startState = options.startState;
- var endState = options.endState;
- var duration = optionOrDefault(options.duration, 500);
- var delay = optionOrDefault(options.delay, 0);
- var perspective = options.perspective;
- var easing = createEaser(optionOrDefault(options.easing, 'linear'), options);
- var currentState = duration === 0 ? endState.clone() : startState.clone();
- var transformOrigin = options.transformOrigin;
- currentState.transformOrigin = options.transformOrigin;
-
- var startTime = 0;
- var currentTime = 0;
- var stopped = false;
- var started = false;
-
- // Manual related
- var manual = options.manual;
- var manualValue = 0;
- var manualDelayFactor = delay / duration;
- var manualCallback;
-
- var tweener;
- // Setup tweener
- if(options.valueFeeder) {
- tweener = createValueFeederTweener(options.valueFeeder,
- startState,
- endState,
- currentState);
- } else {
- tweener = createStateTweener(startState, endState, currentState);
- }
-
- // Public api
- return {
- stop: function() {
- stopped = true;
- },
- isStopped: function() {
- return stopped;
- },
-
- finish: function(callback) {
- manual = false;
- var manualDuration = duration * manualValue;
- startTime = currentTime - manualDuration;
- manualCallback = callback;
- easing.resetFrom = manualValue;
- },
-
- rollback: function(callback) {
- manual = false;
- tweener.setReverse();
- var manualDuration = duration * (1 - manualValue);
- startTime = currentTime - manualDuration;
- manualCallback = callback;
- easing.resetFrom = manualValue;
- },
-
- restart: function() {
- // Restart timer
- startTime = undefined;
- easing.resetFrom(0);
- },
-
- tick: function(time) {
- if(stopped)
- return;
-
- if(manual) {
- currentTime = time;
- this.updateCurrentTransform();
- return;
- }
-
- // If first tick, set startTime
- if(!startTime) {
- startTime = time;
- }
- if(time - startTime > delay) {
- started = true;
- currentTime = time - delay;
-
- var curr = Math.min(Math.max(0.0, currentTime - startTime), duration);
- easing.tick(curr / duration);
- this.updateCurrentTransform();
- if(this.completed() && manualCallback) {
- manualCallback();
- }
- }
- },
-
- getCurrentState: function() {
- return currentState;
- },
-
- setValue: function(_manualValue) {
- started = true;
- manualValue = Math.min(Math.max(_manualValue, 0.0001), 1 + manualDelayFactor);
- },
-
- updateCurrentTransform: function() {
- var tweenValue = easing.getValue();
- if(manual) {
- var val = Math.max(0.00001, manualValue - manualDelayFactor);
- easing.tick(val);
- tweenValue = easing.getValue();
- }
- tweener.tween(tweenValue);
- },
-
- completed: function() {
- if(stopped)
- return true;
- if(startTime === 0) {
- return false;
- }
- return easing.completed();
- },
-
- updateElement: function(element, forceUpdate) {
- if(!started && !forceUpdate)
- return;
- var matrix = tweener.asMatrix();
- var properties = tweener.getProperties();
- updateElementTransform(element, matrix, perspective);
- updateElementProperties(element, properties);
- }
- };
- };
-
- // ------------------------------
- // End Time animation
- // ------------------------------
-
- // ------------------------
- // -- AttentionAnimation --
- // ------------------------
-
- var createAttentionAnimation = function(options) {
- var movement = options.movement;
- options.initialVelocity = 0.1;
- options.equilibriumPosition = 0;
- var spring = createSpringEasing(options);
- var stopped = false;
- var tweenPosition = movement.position;
- var tweenRotation = movement.rotation;
- var tweenRotationPost = movement.rotationPost;
- var tweenScale = movement.scale;
- var tweenSkew = movement.skew;
-
- var currentMovement = createState({
- position: tweenPosition ? [0, 0, 0] : undefined,
- rotation: tweenRotation ? [0, 0, 0] : undefined,
- rotationPost: tweenRotationPost ? [0, 0, 0] : undefined,
- scale: tweenScale ? [0, 0] : undefined,
- skew: tweenSkew ? [0, 0] : undefined,
- });
-
- // Public API
- return {
- stop: function() {
- stopped = true;
- },
-
- isStopped: function(time) {
- return stopped;
- },
-
- tick: function(time) {
- if(stopped)
- return;
- if(spring.equilibrium)
- return;
- spring.tick();
-
- this.updateMovement();
- },
-
- updateMovement:function() {
- var value = spring.getValue();
- if(tweenPosition) {
- currentMovement.position[0] = movement.position[0] * value;
- currentMovement.position[1] = movement.position[1] * value;
- currentMovement.position[2] = movement.position[2] * value;
- }
- if(tweenRotation) {
- currentMovement.rotation[0] = movement.rotation[0] * value;
- currentMovement.rotation[1] = movement.rotation[1] * value;
- currentMovement.rotation[2] = movement.rotation[2] * value;
- }
- if(tweenRotationPost) {
- currentMovement.rotationPost[0] = movement.rotationPost[0] * value;
- currentMovement.rotationPost[1] = movement.rotationPost[1] * value;
- currentMovement.rotationPost[2] = movement.rotationPost[2] * value;
- }
- if(tweenScale) {
- currentMovement.scale[0] = 1 + movement.scale[0] * value;
- currentMovement.scale[1] = 1 + movement.scale[1] * value;
- }
-
- if(tweenSkew) {
- currentMovement.skew[0] = movement.skew[0] * value;
- currentMovement.skew[1] = movement.skew[1] * value;
- }
- },
-
- updateElement: function(element) {
- updateElementTransform(element, currentMovement.asMatrix());
- updateElementProperties(element, currentMovement.getProperties());
- },
-
- getCurrentState: function() {
- return currentMovement;
- },
-
- completed: function() {
- return spring.equilibrium || stopped;
- },
-
- restart: function() {
- // Restart spring
- spring = createSpringEasing(options);
- }
- };
- };
-
-
- /**********
- * Easings *
- ***********/
-
- var linearEasing = function(value) {
- return value;
- };
-
- var ease = function(value) {
- return (Math.cos(value*Math.PI + Math.PI) + 1)/2;
- };
-
- var easeIn = function(value) {
- return value*value;
- };
-
- var easeOut = function(value) {
- return -Math.pow(value - 1, 2) + 1;
- };
-
- var createSpringEasing = function(options) {
- var position = optionOrDefault(options.startPosition, 0);
- var equilibriumPosition = optionOrDefault(options.equilibriumPosition, 1);
- var velocity = optionOrDefault(options.initialVelocity, 0);
- var springConstant = optionOrDefault(options.springConstant, 0.8);
- var deceleration = optionOrDefault(options.springDeceleration, 0.9);
- var mass = optionOrDefault(options.springMass, 10);
-
- var equilibrium = false;
-
- // Public API
- return {
-
- tick: function(value) {
- if(value === 0.0)
- return;
- if(equilibrium)
- return;
- var springForce = -(position - equilibriumPosition) * springConstant;
- // f = m * a
- // a = f / m
- var a = springForce / mass;
- // s = v * t
- // t = 1 ( for now )
- velocity += a;
- position += velocity;
-
- // Deceleration
- velocity *= deceleration;
-
- if(Math.abs(position - equilibriumPosition) < 0.001 && Math.abs(velocity) < 0.001) {
- equilibrium = true;
- }
- },
-
- resetFrom: function(value) {
- position = value;
- velocity = 0;
- },
-
-
- getValue: function() {
- if(equilibrium)
- return equilibriumPosition;
- return position;
- },
-
- completed: function() {
- return equilibrium;
- }
- };
- };
-
- var EASING_FUNCS = {
- 'linear': linearEasing,
- 'ease': ease,
- 'easeIn': easeIn,
- 'easeOut': easeOut,
- };
-
-
- var createEaser = function(easerName, options) {
- if(easerName == 'spring') {
- return createSpringEasing(options);
- }
- var easeFunction = easerName;
- if(!isFunction(easerName)) {
- easeFunction = EASING_FUNCS[easerName];
- }
-
- var easer = easeFunction;
- var value = 0;
- var lastValue;
-
- // Public API
- return {
- tick: function(v) {
- value = easer(v);
- lastValue = v;
- },
-
- resetFrom: function(value) {
- lastValue = 0;
- },
-
- getValue: function() {
- return value;
- },
-
- completed: function() {
- if(lastValue >= 1) {
- return lastValue;
- }
- return false;
- }
- };
- };
-
- /***
- * Matrix related
- */
-
- var assignTranslate = function(matrix, x, y, z) {
- matrix[0] = 1;
- matrix[1] = 0;
- matrix[2] = 0;
- matrix[3] = 0;
- matrix[4] = 0;
- matrix[5] = 1;
- matrix[6] = 0;
- matrix[7] = 0;
- matrix[8] = 0;
- matrix[9] = 0;
- matrix[10] = 1;
- matrix[11] = 0;
- matrix[12] = x;
- matrix[13] = y;
- matrix[14] = z;
- matrix[15] = 1;
- };
-
- var assignRotateX = function(matrix, rad) {
- matrix[0] = 1;
- matrix[1] = 0;
- matrix[2] = 0;
- matrix[3] = 0;
- matrix[4] = 0;
- matrix[5] = Math.cos(rad);
- matrix[6] = -Math.sin(rad);
- matrix[7] = 0;
- matrix[8] = 0;
- matrix[9] = Math.sin(rad);
- matrix[10] = Math.cos(rad);
- matrix[11] = 0;
- matrix[12] = 0;
- matrix[13] = 0;
- matrix[14] = 0;
- matrix[15] = 1;
- };
-
-
- var assignRotateY = function(matrix, rad) {
- matrix[0] = Math.cos(rad);
- matrix[1] = 0;
- matrix[2] = Math.sin(rad);
- matrix[3] = 0;
- matrix[4] = 0;
- matrix[5] = 1;
- matrix[6] = 0;
- matrix[7] = 0;
- matrix[8] = -Math.sin(rad);
- matrix[9] = 0;
- matrix[10] = Math.cos(rad);
- matrix[11] = 0;
- matrix[12] = 0;
- matrix[13] = 0;
- matrix[14] = 0;
- matrix[15] = 1;
- };
-
- var assignRotateZ = function(matrix, rad) {
- matrix[0] = Math.cos(rad);
- matrix[1] = -Math.sin(rad);
- matrix[2] = 0;
- matrix[3] = 0;
- matrix[4] = Math.sin(rad);
- matrix[5] = Math.cos(rad);
- matrix[6] = 0;
- matrix[7] = 0;
- matrix[8] = 0;
- matrix[9] = 0;
- matrix[10] = 1;
- matrix[11] = 0;
- matrix[12] = 0;
- matrix[13] = 0;
- matrix[14] = 0;
- matrix[15] = 1;
- };
-
- var assignSkew = function(matrix, ax, ay) {
- matrix[0] = 1;
- matrix[1] = Math.tan(ax);
- matrix[2] = 0;
- matrix[3] = 0;
- matrix[4] = Math.tan(ay);
- matrix[5] = 1;
- matrix[6] = 0;
- matrix[7] = 0;
- matrix[8] = 0;
- matrix[9] = 0;
- matrix[10] = 1;
- matrix[11] = 0;
- matrix[12] = 0;
- matrix[13] = 0;
- matrix[14] = 0;
- matrix[15] = 1;
- };
-
-
- var assignScale = function(matrix, x, y) {
- matrix[0] = x;
- matrix[1] = 0;
- matrix[2] = 0;
- matrix[3] = 0;
- matrix[4] = 0;
- matrix[5] = y;
- matrix[6] = 0;
- matrix[7] = 0;
- matrix[8] = 0;
- matrix[9] = 0;
- matrix[10] = 1;
- matrix[11] = 0;
- matrix[12] = 0;
- matrix[13] = 0;
- matrix[14] = 0;
- matrix[15] = 1;
- };
-
- var assignIdentity = function(matrix) {
- matrix[0] = 1;
- matrix[1] = 0;
- matrix[2] = 0;
- matrix[3] = 0;
- matrix[4] = 0;
- matrix[5] = 1;
- matrix[6] = 0;
- matrix[7] = 0;
- matrix[8] = 0;
- matrix[9] = 0;
- matrix[10] = 1;
- matrix[11] = 0;
- matrix[12] = 0;
- matrix[13] = 0;
- matrix[14] = 0;
- matrix[15] = 1;
- };
-
- var copyArray = function(a, b) {
- b[0] = a[0];
- b[1] = a[1];
- b[2] = a[2];
- b[3] = a[3];
- b[4] = a[4];
- b[5] = a[5];
- b[6] = a[6];
- b[7] = a[7];
- b[8] = a[8];
- b[9] = a[9];
- b[10] = a[10];
- b[11] = a[11];
- b[12] = a[12];
- b[13] = a[13];
- b[14] = a[14];
- b[15] = a[15];
- };
-
- var createMatrix = function() {
- var data = new Float32Array(16);
- var a = new Float32Array(16);
- var b = new Float32Array(16);
- assignIdentity(data);
-
- return {
- data: data,
-
- asCSS: function() {
- var css = 'matrix3d(';
- for(var i=0;i<15;++i) {
- if(Math.abs(data[i]) < 0.0001)
- css += '0,';
- else
- css += data[i].toFixed(10) + ',';
- }
- if(Math.abs(data[15]) < 0.0001)
- css += '0)';
- else
- css += data[15].toFixed(10) + ')';
- return css;
- },
-
- clear: function() {
- assignIdentity(data);
- },
-
- translate: function(x, y, z) {
- copyArray(data, a);
- assignTranslate(b, x, y, z);
- assignedMatrixMultiplication(a, b, data);
- return this;
- },
-
- rotateX: function(radians) {
- copyArray(data, a);
- assignRotateX(b, radians);
- assignedMatrixMultiplication(a, b, data);
- return this;
- },
-
- rotateY: function(radians) {
- copyArray(data, a);
- assignRotateY(b, radians);
- assignedMatrixMultiplication(a, b, data);
- return this;
- },
-
- rotateZ: function(radians) {
- copyArray(data, a);
- assignRotateZ(b, radians);
- assignedMatrixMultiplication(a, b, data);
- return this;
- },
-
- scale: function(x, y) {
- copyArray(data, a);
- assignScale(b, x, y);
- assignedMatrixMultiplication(a, b, data);
- return this;
- },
-
- skew: function(ax, ay) {
- copyArray(data, a);
- assignSkew(b, ax, ay);
- assignedMatrixMultiplication(a, b, data);
- return this;
- }
- };
- };
-
- var assignedMatrixMultiplication = function(a, b, res) {
- // Unrolled loop
- res[0] = a[0] * b[0] + a[1] * b[4] + a[2] * b[8] + a[3] * b[12];
- res[1] = a[0] * b[1] + a[1] * b[5] + a[2] * b[9] + a[3] * b[13];
- res[2] = a[0] * b[2] + a[1] * b[6] + a[2] * b[10] + a[3] * b[14];
- res[3] = a[0] * b[3] + a[1] * b[7] + a[2] * b[11] + a[3] * b[15];
-
- res[4] = a[4] * b[0] + a[5] * b[4] + a[6] * b[8] + a[7] * b[12];
- res[5] = a[4] * b[1] + a[5] * b[5] + a[6] * b[9] + a[7] * b[13];
- res[6] = a[4] * b[2] + a[5] * b[6] + a[6] * b[10] + a[7] * b[14];
- res[7] = a[4] * b[3] + a[5] * b[7] + a[6] * b[11] + a[7] * b[15];
-
- res[8] = a[8] * b[0] + a[9] * b[4] + a[10] * b[8] + a[11] * b[12];
- res[9] = a[8] * b[1] + a[9] * b[5] + a[10] * b[9] + a[11] * b[13];
- res[10] = a[8] * b[2] + a[9] * b[6] + a[10] * b[10] + a[11] * b[14];
- res[11] = a[8] * b[3] + a[9] * b[7] + a[10] * b[11] + a[11] * b[15];
-
- res[12] = a[12] * b[0] + a[13] * b[4] + a[14] * b[8] + a[15] * b[12];
- res[13] = a[12] * b[1] + a[13] * b[5] + a[14] * b[9] + a[15] * b[13];
- res[14] = a[12] * b[2] + a[13] * b[6] + a[14] * b[10] + a[15] * b[14];
- res[15] = a[12] * b[3] + a[13] * b[7] + a[14] * b[11] + a[15] * b[15];
-
- return res;
- };
-
- var createState = function(config) {
- // Caching of matrix and properties so we don't have to create new ones everytime they are needed
- var matrix = createMatrix();
- var properties = {
- opacity: undefined,
- width: undefined,
- height: undefined
- };
-
- // Public API
- return {
- position: config.position,
- rotation: config.rotation,
- rotationPost: config.rotationPost,
- skew: config.skew,
- scale: config.scale,
- scalePost: config.scalePost,
- opacity: config.opacity,
- width: config.width,
- height: config.height,
-
-
- clone: function() {
- return createState({
- position: this.position ? this.position.slice(0) : undefined,
- rotation: this.rotation ? this.rotation.slice(0) : undefined,
- rotationPost: this.rotationPost ? this.rotationPost.slice(0) : undefined,
- skew: this.skew ? this.skew.slice(0) : undefined,
- scale: this.scale ? this.scale.slice(0) : undefined,
- scalePost: this.scalePost ? this.scalePost.slice(0) : undefined,
- height: this.height,
- width: this.width,
- opacity: this.opacity
- });
- },
-
- asMatrix: function() {
- var m = matrix;
- m.clear();
-
- if(this.transformOrigin)
- m.translate(-this.transformOrigin[0], -this.transformOrigin[1], -this.transformOrigin[2]);
-
- if(this.scale) {
- m.scale(this.scale[0], this.scale[1]);
- }
-
- if(this.skew) {
- m.skew(this.skew[0], this.skew[1]);
- }
-
- if(this.rotation) {
- m.rotateX(this.rotation[0]);
- m.rotateY(this.rotation[1]);
- m.rotateZ(this.rotation[2]);
- }
-
- if(this.position) {
- m.translate(this.position[0], this.position[1], this.position[2]);
- }
-
- if(this.rotationPost) {
- m.rotateX(this.rotationPost[0]);
- m.rotateY(this.rotationPost[1]);
- m.rotateZ(this.rotationPost[2]);
- }
-
- if(this.scalePost) {
- m.scale(this.scalePost[0], this.scalePost[1]);
- }
-
- if(this.transformOrigin)
- m.translate(this.transformOrigin[0], this.transformOrigin[1], this.transformOrigin[2]);
- return m;
- },
-
- getProperties: function() {
- properties.opacity = this.opacity;
- properties.width = this.width + 'px';
- properties.height = this.height + 'px';
- return properties;
- }
- };
- };
- // ------------------
- // -- StateTweener --
- // -------------------
-
- var createStateTweener = function(startState, endState, resultState) {
- var start = startState;
- var end = endState;
- var result = resultState;
-
- var tweenPosition = end.position !== undefined;
- var tweenRotation = end.rotation !== undefined;
- var tweenRotationPost = end.rotationPost !== undefined;
- var tweenScale = end.scale !== undefined;
- var tweenSkew = end.skew !== undefined;
- var tweenWidth = end.width !== undefined;
- var tweenHeight = end.height !== undefined;
- var tweenOpacity = end.opacity !== undefined;
-
- // Public API
- return {
-
- tween: function(tweenValue) {
-
- if(tweenPosition) {
- var dX = (end.position[0] - start.position[0]);
- var dY = (end.position[1] - start.position[1]);
- var dZ = (end.position[2] - start.position[2]);
- result.position[0] = start.position[0] + tweenValue*dX;
- result.position[1] = start.position[1] + tweenValue*dY;
- result.position[2] = start.position[2] + tweenValue*dZ;
- }
-
- if(tweenRotation) {
- var dAX = (end.rotation[0] - start.rotation[0]);
- var dAY = (end.rotation[1] - start.rotation[1]);
- var dAZ = (end.rotation[2] - start.rotation[2]);
- result.rotation[0] = start.rotation[0] + tweenValue*dAX;
- result.rotation[1] = start.rotation[1] + tweenValue*dAY;
- result.rotation[2] = start.rotation[2] + tweenValue*dAZ;
- }
-
- if(tweenRotationPost) {
- var dBX = (end.rotationPost[0] - start.rotationPost[0]);
- var dBY = (end.rotationPost[1] - start.rotationPost[1]);
- var dBZ = (end.rotationPost[2] - start.rotationPost[2]);
- result.rotationPost[0] = start.rotationPost[0] + tweenValue*dBX;
- result.rotationPost[1] = start.rotationPost[1] + tweenValue*dBY;
- result.rotationPost[2] = start.rotationPost[2] + tweenValue*dBZ;
- }
-
- if(tweenSkew) {
- var dSX = (end.scale[0] - start.scale[0]);
- var dSY = (end.scale[1] - start.scale[1]);
-
- result.scale[0] = start.scale[0] + tweenValue*dSX;
- result.scale[1] = start.scale[1] + tweenValue*dSY;
- }
-
- if(tweenScale) {
- var dSkewX = (end.skew[0] - start.skew[0]);
- var dSkewY = (end.skew[1] - start.skew[1]);
-
- result.skew[0] = start.skew[0] + tweenValue*dSkewX;
- result.skew[1] = start.skew[1] + tweenValue*dSkewY;
- }
-
- if(tweenWidth) {
- var dWidth = (end.width - start.width);
- result.width = start.width + tweenValue*dWidth;
- }
-
-
- if(tweenHeight) {
- var dHeight = (end.height - start.height);
- result.height = start.height + tweenValue*dHeight;
- }
-
- if(tweenOpacity) {
- var dOpacity = (end.opacity - start.opacity);
- result.opacity = start.opacity + tweenValue*dOpacity;
- }
-
- },
-
- asMatrix: function() {
- return result.asMatrix();
- },
-
- getProperties: function() {
- return result.getProperties();
- },
-
- setReverse: function() {
- var oldStart = start;
- start = end;
- end = oldStart;
- }
- };
- };
-
- // ------------------------
- // -- ValueFeederTweener --
- // ------------------------
-
- var createValueFeederTweener = function(valueFeeder, startState, endState, resultState) {
- var currentMatrix = valueFeeder(0, createMatrix());
- var start = startState;
- var end = endState;
- var result = resultState;
- var reverse = false;
-
-
- // Public API
- return {
-
- tween: function(tweenValue) {
- if(reverse)
- tweenValue = 1 - tweenValue;
- currentMatrix.clear();
- currentMatrix = valueFeeder(tweenValue, currentMatrix);
-
- var dWidth = (end.width - start.width);
- var dHeight = (end.height - start.height);
- var dOpacity = (end.opacity - start.opacity);
-
- if(end.width !== undefined)
- result.width = start.width + tweenValue*dWidth;
- if(end.height !== undefined)
- result.height = start.height + tweenValue*dHeight;
- if(end.opacity !== undefined)
- result.opacity = start.opacity + tweenValue*dOpacity;
- },
-
- asMatrix: function() {
- return currentMatrix;
- },
-
- getProperties: function() {
- return result.getProperties();
- },
-
- setReverse: function() {
- reverse = true;
- }
-
- };
- };
-
- var optionOrDefault = function(option, def) {
- if(typeof option == 'undefined') {
- return def;
- }
- return option;
- };
-
- var updateElementTransform = function(element, matrix, perspective) {
- var cssPerspective = '';
- if(perspective) {
- cssPerspective = 'perspective(' + perspective + 'px) ';
- }
- var cssMatrix = matrix.asCSS();
- element.style[transformProperty] = cssPerspective + cssMatrix;
- };
-
- var updateElementProperties = function(element, properties) {
- for(var key in properties) {
- element.style[key] = properties[key];
- }
- };
-
- var isFunction = function(object) {
- return (typeof object === "function");
- };
-
- var cloneObject = function(object) {
- if(!object)
- return object;
- var clone = {};
- for(var key in object) {
- clone[key] = object[key];
- }
- return clone;
- };
-
- snabbt.createMatrix = createMatrix;
- snabbt.setElementTransform = updateElementTransform;
- return snabbt;
- }());
- var stackBlur = (function(){
-
- var mul_table = [
- 512,512,456,512,328,456,335,512,405,328,271,456,388,335,292,512,
- 454,405,364,328,298,271,496,456,420,388,360,335,312,292,273,512,
- 482,454,428,405,383,364,345,328,312,298,284,271,259,496,475,456,
- 437,420,404,388,374,360,347,335,323,312,302,292,282,273,265,512,
- 497,482,468,454,441,428,417,405,394,383,373,364,354,345,337,328,
- 320,312,305,298,291,284,278,271,265,259,507,496,485,475,465,456,
- 446,437,428,420,412,404,396,388,381,374,367,360,354,347,341,335,
- 329,323,318,312,307,302,297,292,287,282,278,273,269,265,261,512,
- 505,497,489,482,475,468,461,454,447,441,435,428,422,417,411,405,
- 399,394,389,383,378,373,368,364,359,354,350,345,341,337,332,328,
- 324,320,316,312,309,305,301,298,294,291,287,284,281,278,274,271,
- 268,265,262,259,257,507,501,496,491,485,480,475,470,465,460,456,
- 451,446,442,437,433,428,424,420,416,412,408,404,400,396,392,388,
- 385,381,377,374,370,367,363,360,357,354,350,347,344,341,338,335,
- 332,329,326,323,320,318,315,312,310,307,304,302,299,297,294,292,
- 289,287,285,282,280,278,275,273,271,269,267,265,263,261,259];
-
-
- var shg_table = [
- 9, 11, 12, 13, 13, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 17,
- 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 19,
- 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 20, 20, 20,
- 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 21,
- 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
- 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22,
- 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
- 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23,
- 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
- 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
- 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
- 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
- 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
- 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
- 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
- 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24 ];
-
- function getImageDataFromCanvas(canvas, top_x, top_y, width, height)
- {
- if (typeof(canvas) == 'string')
- canvas = document.getElementById(canvas);
- else if (!canvas instanceof HTMLCanvasElement)
- return;
-
- var context = canvas.getContext('2d');
- var imageData;
-
- try {
- try {
- imageData = context.getImageData(top_x, top_y, width, height);
- } catch(e) {
- throw new Error("unable to access local image data: " + e);
- return;
- }
- } catch(e) {
- throw new Error("unable to access image data: " + e);
- }
-
- return imageData;
- }
-
- function processCanvasRGBA(canvas, top_x, top_y, width, height, radius)
- {
- if (isNaN(radius) || radius < 1) return;
- radius |= 0;
-
- var imageData = getImageDataFromCanvas(canvas, top_x, top_y, width, height);
-
- imageData = processImageDataRGBA(imageData, top_x, top_y, width, height, radius);
-
- canvas.getContext('2d').putImageData(imageData, top_x, top_y);
- }
-
- function processImageDataRGBA(imageData, top_x, top_y, width, height, radius)
- {
- var pixels = imageData.data;
-
- var x, y, i, p, yp, yi, yw, r_sum, g_sum, b_sum, a_sum,
- r_out_sum, g_out_sum, b_out_sum, a_out_sum,
- r_in_sum, g_in_sum, b_in_sum, a_in_sum,
- pr, pg, pb, pa, rbs;
-
- var div = radius + radius + 1;
- var w4 = width << 2;
- var widthMinus1 = width - 1;
- var heightMinus1 = height - 1;
- var radiusPlus1 = radius + 1;
- var sumFactor = radiusPlus1 * (radiusPlus1 + 1) / 2;
-
- var stackStart = new BlurStack();
- var stack = stackStart;
- for (i = 1; i < div; i++)
- {
- stack = stack.next = new BlurStack();
- if (i == radiusPlus1) var stackEnd = stack;
- }
- stack.next = stackStart;
- var stackIn = null;
- var stackOut = null;
-
- yw = yi = 0;
-
- var mul_sum = mul_table[radius];
- var shg_sum = shg_table[radius];
-
- for (y = 0; y < height; y++)
- {
- r_in_sum = g_in_sum = b_in_sum = a_in_sum = r_sum = g_sum = b_sum = a_sum = 0;
-
- r_out_sum = radiusPlus1 * (pr = pixels[yi]);
- g_out_sum = radiusPlus1 * (pg = pixels[yi+1]);
- b_out_sum = radiusPlus1 * (pb = pixels[yi+2]);
- a_out_sum = radiusPlus1 * (pa = pixels[yi+3]);
-
- r_sum += sumFactor * pr;
- g_sum += sumFactor * pg;
- b_sum += sumFactor * pb;
- a_sum += sumFactor * pa;
-
- stack = stackStart;
-
- for (i = 0; i < radiusPlus1; i++)
- {
- stack.r = pr;
- stack.g = pg;
- stack.b = pb;
- stack.a = pa;
- stack = stack.next;
- }
-
- for (i = 1; i < radiusPlus1; i++)
- {
- p = yi + ((widthMinus1 < i ? widthMinus1 : i) << 2);
- r_sum += (stack.r = (pr = pixels[p])) * (rbs = radiusPlus1 - i);
- g_sum += (stack.g = (pg = pixels[p+1])) * rbs;
- b_sum += (stack.b = (pb = pixels[p+2])) * rbs;
- a_sum += (stack.a = (pa = pixels[p+3])) * rbs;
-
- r_in_sum += pr;
- g_in_sum += pg;
- b_in_sum += pb;
- a_in_sum += pa;
-
- stack = stack.next;
- }
-
-
- stackIn = stackStart;
- stackOut = stackEnd;
- for (x = 0; x < width; x++)
- {
- pixels[yi+3] = pa = (a_sum * mul_sum) >> shg_sum;
- if (pa != 0)
- {
- pa = 255 / pa;
- pixels[yi] = ((r_sum * mul_sum) >> shg_sum) * pa;
- pixels[yi+1] = ((g_sum * mul_sum) >> shg_sum) * pa;
- pixels[yi+2] = ((b_sum * mul_sum) >> shg_sum) * pa;
- } else {
- pixels[yi] = pixels[yi+1] = pixels[yi+2] = 0;
- }
-
- r_sum -= r_out_sum;
- g_sum -= g_out_sum;
- b_sum -= b_out_sum;
- a_sum -= a_out_sum;
-
- r_out_sum -= stackIn.r;
- g_out_sum -= stackIn.g;
- b_out_sum -= stackIn.b;
- a_out_sum -= stackIn.a;
-
- p = (yw + ((p = x + radius + 1) < widthMinus1 ? p : widthMinus1)) << 2;
-
- r_in_sum += (stackIn.r = pixels[p]);
- g_in_sum += (stackIn.g = pixels[p+1]);
- b_in_sum += (stackIn.b = pixels[p+2]);
- a_in_sum += (stackIn.a = pixels[p+3]);
-
- r_sum += r_in_sum;
- g_sum += g_in_sum;
- b_sum += b_in_sum;
- a_sum += a_in_sum;
-
- stackIn = stackIn.next;
-
- r_out_sum += (pr = stackOut.r);
- g_out_sum += (pg = stackOut.g);
- b_out_sum += (pb = stackOut.b);
- a_out_sum += (pa = stackOut.a);
-
- r_in_sum -= pr;
- g_in_sum -= pg;
- b_in_sum -= pb;
- a_in_sum -= pa;
-
- stackOut = stackOut.next;
-
- yi += 4;
- }
- yw += width;
- }
-
-
- for (x = 0; x < width; x++)
- {
- g_in_sum = b_in_sum = a_in_sum = r_in_sum = g_sum = b_sum = a_sum = r_sum = 0;
-
- yi = x << 2;
- r_out_sum = radiusPlus1 * (pr = pixels[yi]);
- g_out_sum = radiusPlus1 * (pg = pixels[yi+1]);
- b_out_sum = radiusPlus1 * (pb = pixels[yi+2]);
- a_out_sum = radiusPlus1 * (pa = pixels[yi+3]);
-
- r_sum += sumFactor * pr;
- g_sum += sumFactor * pg;
- b_sum += sumFactor * pb;
- a_sum += sumFactor * pa;
-
- stack = stackStart;
-
- for (i = 0; i < radiusPlus1; i++)
- {
- stack.r = pr;
- stack.g = pg;
- stack.b = pb;
- stack.a = pa;
- stack = stack.next;
- }
-
- yp = width;
-
- for (i = 1; i <= radius; i++)
- {
- yi = (yp + x) << 2;
-
- r_sum += (stack.r = (pr = pixels[yi])) * (rbs = radiusPlus1 - i);
- g_sum += (stack.g = (pg = pixels[yi+1])) * rbs;
- b_sum += (stack.b = (pb = pixels[yi+2])) * rbs;
- a_sum += (stack.a = (pa = pixels[yi+3])) * rbs;
-
- r_in_sum += pr;
- g_in_sum += pg;
- b_in_sum += pb;
- a_in_sum += pa;
-
- stack = stack.next;
-
- if(i < heightMinus1)
- {
- yp += width;
- }
- }
-
- yi = x;
- stackIn = stackStart;
- stackOut = stackEnd;
- for (y = 0; y < height; y++)
- {
- p = yi << 2;
- pixels[p+3] = pa = (a_sum * mul_sum) >> shg_sum;
- if (pa > 0)
- {
- pa = 255 / pa;
- pixels[p] = ((r_sum * mul_sum) >> shg_sum) * pa;
- pixels[p+1] = ((g_sum * mul_sum) >> shg_sum) * pa;
- pixels[p+2] = ((b_sum * mul_sum) >> shg_sum) * pa;
- } else {
- pixels[p] = pixels[p+1] = pixels[p+2] = 0;
- }
-
- r_sum -= r_out_sum;
- g_sum -= g_out_sum;
- b_sum -= b_out_sum;
- a_sum -= a_out_sum;
-
- r_out_sum -= stackIn.r;
- g_out_sum -= stackIn.g;
- b_out_sum -= stackIn.b;
- a_out_sum -= stackIn.a;
-
- p = (x + (((p = y + radiusPlus1) < heightMinus1 ? p : heightMinus1) * width)) << 2;
-
- r_sum += (r_in_sum += (stackIn.r = pixels[p]));
- g_sum += (g_in_sum += (stackIn.g = pixels[p+1]));
- b_sum += (b_in_sum += (stackIn.b = pixels[p+2]));
- a_sum += (a_in_sum += (stackIn.a = pixels[p+3]));
-
- stackIn = stackIn.next;
-
- r_out_sum += (pr = stackOut.r);
- g_out_sum += (pg = stackOut.g);
- b_out_sum += (pb = stackOut.b);
- a_out_sum += (pa = stackOut.a);
-
- r_in_sum -= pr;
- g_in_sum -= pg;
- b_in_sum -= pb;
- a_in_sum -= pa;
-
- stackOut = stackOut.next;
-
- yi += width;
- }
- }
- return imageData;
- }
-
- function BlurStack()
- {
- this.r = 0;
- this.g = 0;
- this.b = 0;
- this.a = 0;
- this.next = null;
- }
-
- return processCanvasRGBA;
-
- }());
- // canvas to blob polyfill
- // https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob#Polyfill
- if (!HTMLCanvasElement.prototype.toBlob) {
- Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', {
- value: function (callback, type, quality) {
-
- var binStr = atob( this.toDataURL(type, quality).split(',')[1] ),
- len = binStr.length,
- arr = new Uint8Array(len);
-
- for (var i=0; i<len; i++ ) {
- arr[i] = binStr.charCodeAt(i);
- }
-
- callback( new Blob( [arr], {type: type || 'image/png'} ) );
- }
- });
- }
- 'use strict';
-
- 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; }; }();
-
- 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; };
-
- function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
-
- var getElementAttributes = function getElementAttributes(el) {
- return Array.prototype.slice.call(el.attributes).map(function (attr) {
- return {
- 'name': attr.name,
- 'value': attr.value
- };
- });
- };
-
- // helper method
- var getOffsetByEvent = function getOffsetByEvent(e) {
- return {
- x: typeof e.offsetX === 'undefined' ? e.layerX : e.offsetX,
- y: typeof e.offsetY === 'undefined' ? e.layerY : e.offsetY
- };
- };
-
- // merge two objects together
- var mergeOptions = function mergeOptions(base, additives) {
-
- var key;
- var options = {};
- var optionsToMerge = additives || {};
-
- for (key in base) {
- if (!base.hasOwnProperty(key)) {
- continue;
- }
- options[key] = typeof optionsToMerge[key] === 'undefined' ? base[key] : optionsToMerge[key];
- }
-
- return options;
- };
-
- // keys
- var Key = {
- ESC: 27,
- RETURN: 13
- };
-
- // pointer events
- var Events = {
- DOWN: ['touchstart', 'pointerdown', 'mousedown'],
- MOVE: ['touchmove', 'pointermove', 'mousemove'],
- UP: ['touchend', 'touchcancel', 'pointerup', 'mouseup']
- };
-
- var MimeTypes = {
- 'jpeg': 'image/jpeg',
- 'jpg': 'image/jpeg',
- 'jpe': 'image/jpeg',
- 'png': 'image/png',
- 'gif': 'image/gif',
- 'bmp': 'image/bmp'
- };
-
- var ImageExtensionsRegex = /(\.png|\.bmp|\.gif|\.jpg|\.jpe|\.jpg|\.jpeg)$/;
-
- var CanvasExportExtensions = /(jpe|jpg|jpeg|png)/;
-
- // shortcuts
- var create = function create(name, className) {
- var node = document.createElement(name);
- if (className) {
- node.className = className;
- }
- return node;
- };
-
- // events
- var addEvents = function addEvents(obj, events, scope) {
- events.forEach(function (event) {
- obj.addEventListener(event, scope, false);
- });
- };
-
- var removeEvents = function removeEvents(obj, events, scope) {
- events.forEach(function (event) {
- obj.removeEventListener(event, scope, false);
- });
- };
-
- var getEventOffset = function getEventOffset(e) {
-
- var event = e.changedTouches ? e.changedTouches[0] : e;
-
- // no event found, quit!
- if (!event) {
- return;
- }
-
- // get offset from events
- return {
- x: event.pageX,
- y: event.pageY
- };
- };
-
- var getEventOffsetLocal = function getEventOffsetLocal(e, local) {
-
- var offset = getEventOffset(e);
- var rect = local.getBoundingClientRect();
- var top = window.pageYOffset || document.documentElement.scrollTop;
- var left = window.pageXOffset || document.documentElement.scrollLeft;
-
- return {
- x: offset.x - rect.left - left,
- y: offset.y - rect.top - top
- };
- };
-
- var capitalizeFirstLetter = function capitalizeFirstLetter(string) {
- return string.charAt(0).toUpperCase() + string.slice(1);
- };
-
- var last = function last(array) {
- return array[array.length - 1];
- };
-
- var limit = function limit(value, min, max) {
- return Math.max(min, Math.min(max, value));
- };
-
- var inArray = function inArray(needle, arr) {
- return arr.indexOf(needle) !== -1;
- };
-
- var send = function send(url, data, requestDecorator, progress, success, err) {
-
- var xhr = new XMLHttpRequest();
-
- // if progress callback defined handle progress events
- if (progress) {
- xhr.upload.addEventListener('progress', function (e) {
- progress(e.loaded, e.total);
- });
- }
-
- // open the request
- xhr.open('POST', url, true);
-
- // if request decorator defined pass XMLHttpRequest instance to decorator
- if (requestDecorator) {
- requestDecorator(xhr);
- }
-
- // handle state changes
- xhr.onreadystatechange = function () {
-
- if (xhr.readyState === 4 && xhr.status === 200) {
-
- var text = xhr.responseText;
-
- // if no data returned from server assume success
- if (!text.length) {
- success();
- return;
- }
-
- // catch possible PHP content length problem
- if (text.indexOf('Content-Length') !== -1) {
- err('file-too-big');
- return;
- }
-
- // if data returned it should be in suggested JSON format
- var obj = void 0;
- try {
- obj = JSON.parse(xhr.responseText);
- } catch (e) {}
-
- // if is failure response
- if ((typeof obj === 'undefined' ? 'undefined' : _typeof(obj)) === 'object' && obj.status === 'failure') {
- err(obj.message);
- return;
- }
-
- success(obj || text);
- } else if (xhr.readyState === 4) {
- err('fail');
- }
- };
-
- // do request
- xhr.send(data);
- };
-
- var resetTransforms = function resetTransforms(element) {
- element.style.webkitTransform = '';
- element.style.transform = '';
- };
-
- var bytesToMegaBytes = function bytesToMegaBytes(b) {
- return b / 1000000;
- };
-
- var megaBytesToBytes = function megaBytesToBytes(mb) {
- return mb * 1000000;
- };
-
- var getCommonMimeTypes = function getCommonMimeTypes() {
- var types = [];
- var type = void 0;
- var mimeType = void 0;
- for (type in MimeTypes) {
- if (!MimeTypes.hasOwnProperty(type)) {
- continue;
- }
- mimeType = MimeTypes[type];
- if (types.indexOf(mimeType) == -1) {
- types.push(mimeType);
- }
- }
- return types;
- };
-
- var isJPEGMimeType = function isJPEGMimeType(type) {
- return type === 'image/jpeg';
- };
-
- var getExtensionByMimeType = function getExtensionByMimeType(mimetype) {
- var type = void 0;
- for (type in MimeTypes) {
- if (!MimeTypes.hasOwnProperty(type)) {
- continue;
- }
- if (MimeTypes[type] === mimetype) {
- return type;
- }
- }
- return mimetype;
- };
-
- /**
- * Returns the mimetype matching the given extension
- * @param {string} extension
- * @returns {string}
- */
- function getFileMimeType(extension) {
- return MimeTypes[extension ? extension.toLowerCase() : extension] || 'unknown';
- };
-
- var getFileName = function getFileName(path) {
- return path.split('/').pop().split('?').shift();
- };
-
- var getFileNameWithoutExtension = function getFileNameWithoutExtension(path) {
- if (typeof path !== 'string') {
- return 'unknown';
- }
- var name = getFileName(path);
- return name.split('.').shift();
- };
-
- var getFileMetaDataFromBase64 = function getFileMetaDataFromBase64(base64) {
- var mimeType = getMimeTypeFromDataURI(base64);
- var extension = getExtensionByMimeType(mimeType);
- var name = 'unknown.' + extension;
- return {
- name: name,
- type: mimeType,
- 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
- };
- };
-
- var getFileMetaDataFromUrl = function getFileMetaDataFromUrl(url) {
- var name = getFileName(url);
- var mimeType = getFileMimeType(name.split('.').pop());
- return {
- name: name,
- type: mimeType,
- size: null
- };
- };
-
- var getFileMetaDataFromFile = function getFileMetaDataFromFile(file) {
- return {
- name: file.name,
- type: file.type,
- size: file.size
- };
- };
-
- var getFileMetaData = function getFileMetaData(file) {
-
- if (typeof file === 'string') {
-
- // test if is base 64 data uri
- if (/^data:image/.test(file)) {
- return getFileMetaDataFromBase64(file);
- }
-
- // is url
- return getFileMetaDataFromUrl(file);
- }
-
- return getFileMetaDataFromFile(file);
- };
-
- var setFileMetaData = function setFileMetaData(fileData) {
- var extension = arguments.length <= 1 || arguments[1] === undefined ? '' : arguments[1];
-
-
- var data = clone(fileData);
-
- // correct extension if includes dot
- extension = extension.replace(/\./, '');
-
- // test if extension if of correct type, if not, force to png
- if (!CanvasExportExtensions.test(extension)) {
- extension = 'png';
- }
-
- // if no extension set new extension
- if (!ImageExtensionsRegex.test(data.name)) {
- data.name += '.' + extension;
- }
- // replace existing extension
- else {
- data.name = data.name.replace(/\.[a-z]+$/, '.' + extension);
- }
-
- // force mime type
- data.type = MimeTypes[extension];
-
- return data;
- };
-
- var getImageAsCanvas = function getImageAsCanvas(src, callback) {
-
- // only cross origin when it's not base64 data, to prevent errors in Safari
- // http://stackoverflow.com/questions/31643096/why-does-safari-throw-cors-error-when-setting-base64-data-on-a-crossorigin-an
- var crossOrigin = typeof src === 'string' ? src.indexOf('data:image') !== 0 : true;
-
- loadImage.parseMetaData(src, function (meta) {
-
- var options = {
- canvas: true,
- crossOrigin: crossOrigin
- };
-
- if (meta.exif) {
- options.orientation = meta.exif.get('Orientation');
- }
-
- loadImage(src, function (res) {
-
- if (res.type === 'error') {
- callback();
- return;
- }
-
- callback(res, meta);
- }, options);
- });
- };
-
- var getAutoCropRect = function getAutoCropRect(width, height, ratioOut) {
-
- var x,
- y,
- w,
- h,
- ratioIn = height / width;
-
- // if input is portrait and required is landscape
- // width is portrait width, height is width times outputRatio
- if (ratioIn < ratioOut) {
- h = height;
- w = h / ratioOut;
- x = (width - w) * .5;
- y = 0;
- }
-
- // if input is landscape and required is portrait
- // height is landscape height, width is height divided by outputRatio
- else {
- w = width;
- h = w * ratioOut;
- x = 0;
- y = (height - h) * .5;
- }
-
- return {
- x: x,
- y: y,
- height: h,
- width: w
- };
- };
-
- var transformCanvas = function transformCanvas(canvas) {
- var transforms = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
- var cb = arguments[2];
-
-
- var result = create('canvas');
-
- var rotation = transforms.rotation;
- var crop = transforms.crop;
- var size = transforms.size;
-
- // do crop transforms
-
- if (crop) {
-
- //console.log('rotation:', rotation);
- //console.log('crop:', crop.x, crop.y, crop.width, crop.height);
- //console.log('canvas:', canvas.width, canvas.height);
-
- // do crop
- var isTilted = rotation % 180 !== 0;
- var space = {
- width: isTilted ? canvas.height : canvas.width,
- height: isTilted ? canvas.width : canvas.height
- };
-
- // crop offsets in percentages
- var px = crop.x / space.width;
- var py = crop.y / space.height;
- var pw = crop.width / space.width;
- var ph = crop.height / space.height;
-
- // limit crop to size of canvas else safari might return transparent image
- crop = roundRect(crop);
- if (crop.y + crop.height > space.height) {
- crop.y = Math.max(0, space.height - crop.height);
- }
-
- if (crop.x + crop.width > space.width) {
- crop.x = Math.max(0, space.width - crop.width);
- }
-
- //console.log('limit:', crop.x, crop.y, crop.width, crop.height);
-
- //console.log('percentages: ',px, py, pw, ph);
-
- // resize canvas to the final crop result size
- result.width = crop.width;
- result.height = crop.height;
-
- // draw the crop
- var ctx = result.getContext('2d');
-
- if (rotation === 90) {
-
- ctx.translate(result.width * .5, result.height * .5);
- ctx.rotate(-90 * Math.PI / 180);
- ctx.drawImage(canvas,
-
- // source rectangle (crop area)
- (1 - py) * canvas.width - canvas.width * ph, crop.x, crop.height, crop.width,
-
- // target area (cover)
- -result.height * .5, -result.width * .5, result.height, result.width);
- } else if (rotation === 180) {
-
- ctx.translate(result.width * .5, result.height * .5);
- ctx.rotate(-180 * Math.PI / 180);
- ctx.drawImage(canvas,
-
- // source rectangle (crop area)
- (1 - (px + pw)) * space.width, (1 - (py + ph)) * space.height, pw * space.width, ph * space.height,
-
- // target area (cover)
- -result.width * .5, -result.height * .5, result.width, result.height);
- } else if (rotation === 270) {
-
- ctx.translate(result.width * .5, result.height * .5);
- ctx.rotate(-270 * Math.PI / 180);
- ctx.drawImage(canvas,
-
- // source rectangle (crop area)
- crop.y, (1 - px) * canvas.height - canvas.height * pw, crop.height, crop.width,
-
- // target area (cover)
- -result.height * .5, -result.width * .5, result.height, result.width);
- } else {
-
- ctx.drawImage(canvas,
-
- // source rectangle (crop area)
- crop.x, crop.y, crop.width, crop.height,
-
- // target area (cover)
- 0, 0, result.width, result.height);
- }
- }
-
- // do size transforms
- if (size) {
-
- // pick smallest scalar
- scaleCanvas(result, Math.min(size.width / result.width, size.height / result.height));
- }
-
- cb(result);
- };
-
- function scaleCanvas(canvas, scalar) {
-
- // if not scaling down, bail out
- if (scalar >= 1) {
- return;
- }
-
- var c = cloneCanvas(canvas);
- var ctx;
- var width = canvas.width;
- var height = canvas.height;
- var targetWidth = Math.ceil(canvas.width * scalar);
- var targetHeight = Math.ceil(canvas.height * scalar);
-
- // scale down in steps
- while (width > targetWidth) {
-
- width *= .5;
- height *= .5;
-
- if (width < targetWidth) {
- // skip to final draw
- break;
- }
-
- c = create('canvas');
- c.width = width;
- c.height = height;
- ctx = c.getContext('2d');
- ctx.drawImage(canvas, 0, 0, width, height);
- }
-
- // draw final result back to original canvas
- canvas.width = targetWidth;
- canvas.height = targetHeight;
- ctx = canvas.getContext('2d');
- ctx.drawImage(c, 0, 0, targetWidth, targetHeight);
- }
-
- var cloneCanvas = function cloneCanvas(original) {
- return cloneCanvasScaled(original, 1);
- };
-
- var cloneCanvasScaled = function cloneCanvasScaled(original, scalar) {
-
- if (!original) {
- return null;
- }
-
- var duplicate = document.createElement('canvas');
- duplicate.setAttribute('data-file', original.getAttribute('data-file'));
- var ctx = duplicate.getContext('2d');
- duplicate.width = original.width;
- duplicate.height = original.height;
- ctx.drawImage(original, 0, 0);
- if (scalar > 0 && scalar != 1) {
- scaleCanvas(duplicate, scalar);
- }
- return duplicate;
- };
-
- var canvasHasDimensions = function canvasHasDimensions(canvas) {
- return canvas.width && canvas.height;
- };
-
- var copyCanvas = function copyCanvas(original, destination) {
-
- var ctx = destination.getContext('2d');
- if (canvasHasDimensions(destination)) {
- ctx.drawImage(original, 0, 0, destination.width, destination.height);
- } else {
- destination.width = original.width;
- destination.height = original.height;
- ctx.drawImage(original, 0, 0);
- }
- };
-
- var clearCanvas = function clearCanvas(canvas) {
- var ctx = canvas.getContext('2d');
- ctx.clearRect(0, 0, canvas.width, canvas.height);
- };
-
- var blurCanvas = function blurCanvas(canvas) {
- stackBlur(canvas, 0, 0, canvas.width, canvas.height, 3);
- };
-
- var downloadCanvas = function downloadCanvas(canvas, filename, mimeType) {
-
- canvas.toBlob(function (blob) {
-
- if ('msSaveBlob' in window.navigator) {
- window.navigator.msSaveBlob(blob, filename);
- return;
- }
-
- var url = URL.createObjectURL(blob);
-
- // setup hidden link
- var link = create('a');
- link.style.display = 'none';
- link.download = filename;
- link.href = url;
-
- // attach to DOM otherwise this does not work in Firefox
- document.body.appendChild(link);
-
- // fire click
- link.click();
-
- // delay on remove otherwise does not work in Firefox
- setTimeout(function () {
- document.body.removeChild(link);
- URL.revokeObjectURL(url);
- }, 0);
- }, mimeType);
- };
-
- var covers = function covers(image, rect) {
- return parseInt(image.width, 10) >= rect.width && parseInt(image.height, 10) >= rect.height;
- };
-
- var scaleRect = function scaleRect(rect, w, h) {
- return {
- x: rect.x * w,
- y: rect.y * h,
- width: rect.width * w,
- height: rect.height * h
- };
- };
-
- var divideRect = function divideRect(rect, w, h) {
- return {
- x: rect.x / w,
- y: rect.y / h,
- width: rect.width / w,
- height: rect.height / h
- };
- };
-
- var roundRect = function roundRect(rect) {
- return {
- x: Math.floor(rect.x),
- y: Math.floor(rect.y),
- width: Math.floor(rect.width),
- height: Math.floor(rect.height)
- };
- };
-
- var resetFileInput = function resetFileInput(input) {
-
- // no value, no need to reset
- if (!input || input.value === '') {
- return;
- }
-
- try {
- // for modern browsers
- input.value = '';
- } catch (err) {}
-
- // for IE10
- if (input.value) {
-
- // quickly append input to temp form and reset form
- var form = document.createElement('form');
- var parentNode = input.parentNode;
- var ref = input.nextSibling;
- form.appendChild(input);
- form.reset();
-
- // re-inject input where it originally was
- if (ref) {
- parentNode.insertBefore(input, ref);
- } else {
- parentNode.appendChild(input);
- }
- }
- };
-
- var clone = function clone(obj) {
- if ((typeof value === 'undefined' ? 'undefined' : _typeof(value)) === 'object' && value !== null) {
- return JSON.parse(JSON.stringify(obj));
- }
- return obj;
- };
-
- var cloneData = function cloneData(obj) {
- var input = cloneCanvas(obj.input.image);
- var output = cloneCanvas(obj.output.image);
- var dupe = clone(obj);
- dupe.input.image = input;
- dupe.output.image = output;
- return dupe;
- };
-
- /**
- * @param image
- * @param type
- * @param jpegCompression - value between 0 and 100 or undefined/null to use default compression
- * @returns {*}
- */
- var toDataURL = function toDataURL(image, type, jpegCompression) {
- if (!image || !type) {
- return null;
- }
- return image.toDataURL(type, isJPEGMimeType(type) && typeof jpegCompression === 'number' ? jpegCompression / 100 : undefined);
- };
-
- var getMimeTypeFromDataURI = function getMimeTypeFromDataURI(dataUri) {
- if (!dataUri) {
- return null;
- }
- var matches = dataUri.substr(0, 16).match(/^.+;/);
- if (matches.length) {
- return matches[0].substring(5, matches[0].length - 1);
- }
- return null;
- };
-
- var flattenData = function flattenData(obj) {
- var props = arguments.length <= 1 || arguments[1] === undefined ? [] : arguments[1];
- var jpegCompression = arguments[2];
-
-
- var data = {
- server: clone(obj.server),
- meta: clone(obj.meta),
- input: {
- name: obj.input.name,
- type: obj.input.type,
- size: obj.input.size,
- width: obj.input.width,
- height: obj.input.height
- },
- output: {
- width: obj.output.width,
- height: obj.output.height
- }
- };
-
- if (inArray('input', props)) {
- data.input.image = toDataURL(obj.input.image, obj.input.type);
- }
-
- if (inArray('output', props)) {
- data.output.image = toDataURL(obj.output.image, obj.input.type, jpegCompression);
- }
-
- if (inArray('actions', props)) {
- data.actions = clone(obj.actions);
- }
-
- // if output is of type png and input was of type jpeg we need to fix extension of filename
- // so instead of testing the above situation we just always fix extension when handling png's
- var type = getMimeTypeFromDataURI(data.output.image || data.input.image);
- if (type === 'image/png') {
- data.input.name = getFileNameWithoutExtension(data.input.name) + '.png';
- data.input.type = type;
- }
-
- return data;
- };
-
- var toggleDisplayBySelector = function toggleDisplayBySelector(selector, enabled, root) {
- var node = root.querySelector(selector);
- if (!node) {
- return;
- }
- node.style.display = enabled ? '' : 'none';
- };
-
- var nodeListToArray = function nodeListToArray(nl) {
- return Array.prototype.slice.call(nl);
- };
-
- var removeElement = function removeElement(el) {
- el.parentNode.removeChild(el);
- };
-
- var wrap = function wrap(element) {
- var wrapper = create('div');
- if (element.parentNode) {
- if (element.nextSibling) {
- element.parentNode.insertBefore(wrapper, element.nextSibling);
- } else {
- element.parentNode.appendChild(wrapper);
- }
- }
- wrapper.appendChild(element);
- return wrapper;
- };
-
- var polarToCartesian = function polarToCartesian(centerX, centerY, radius, angleInDegrees) {
- var angleInRadians = (angleInDegrees - 90) * Math.PI / 180.0;
-
- return {
- x: centerX + radius * Math.cos(angleInRadians),
- y: centerY + radius * Math.sin(angleInRadians)
- };
- };
-
- var describeArc = function describeArc(x, y, radius, startAngle, endAngle) {
-
- var start = polarToCartesian(x, y, radius, endAngle);
- var end = polarToCartesian(x, y, radius, startAngle);
-
- var arcSweep = endAngle - startAngle <= 180 ? '0' : '1';
-
- var d = ['M', start.x, start.y, 'A', radius, radius, 0, arcSweep, 0, end.x, end.y].join(' ');
-
- return d;
- };
-
- var percentageArc = function percentageArc(x, y, radius, p) {
- return describeArc(x, y, radius, 0, p * 360);
- };
- var CropArea = function () {
-
- var resizers = {
- 'n': function n(rect, offset, space, ratio) {
-
- var t, r, b, l, w, h, p, d;
-
- // bottom is fixed
- b = rect.y + rect.height;
-
- // intended top
- t = limit(offset.y, 0, b);
-
- // if is too small vertically
- if (b - t < space.min.height) {
- t = b - space.min.height;
- }
-
- // if should scale by ratio, pick width by ratio of new height
- w = ratio ? (b - t) / ratio : rect.width;
-
- // check if has fallen below min width or height
- if (w < space.min.width) {
- w = space.min.width;
- t = b - w * ratio;
- }
-
- // add half to left and half to right edge
- p = (w - rect.width) * .5;
- l = rect.x - p;
- r = rect.x + rect.width + p;
-
- // check if any of the edges has moved out of the available space, if so,
- // set max size of rectangle from original position
- if (l < 0 || r > space.width) {
-
- // smallest distance to edge of space
- d = Math.min(rect.x, space.width - (rect.x + rect.width));
-
- // new left and right offsets
- l = rect.x - d;
- r = rect.x + rect.width + d;
-
- // resulting width
- w = r - l;
-
- // resulting height based on ratio
- h = w * ratio;
-
- // new top position
- t = b - h;
- }
-
- return {
- x: l,
- y: t,
- width: r - l,
- height: b - t
- };
- },
- 's': function s(rect, offset, space, ratio) {
-
- var t, r, b, l, w, h, p, d;
-
- // top is fixed
- t = rect.y;
-
- // intended bottom
- b = limit(offset.y, t, space.height);
-
- // if is too small vertically
- if (b - t < space.min.height) {
- b = t + space.min.height;
- }
-
- // if should scale by ratio, pick width by ratio of new height
- w = ratio ? (b - t) / ratio : rect.width;
-
- // check if has fallen below min width or height
- if (w < space.min.width) {
- w = space.min.width;
- b = t + w * ratio;
- }
-
- // add half to left and half to right edge
- p = (w - rect.width) * .5;
- l = rect.x - p;
- r = rect.x + rect.width + p;
-
- // check if any of the edges has moved out of the available space, if so,
- // set max size of rectangle from original position
- if (l < 0 || r > space.width) {
-
- // smallest distance to edge of space
- d = Math.min(rect.x, space.width - (rect.x + rect.width));
-
- // new left and right offsets
- l = rect.x - d;
- r = rect.x + rect.width + d;
-
- // resulting width
- w = r - l;
-
- // resulting height based on ratio
- h = w * ratio;
-
- // new bottom position
- b = t + h;
- }
-
- return {
- x: l,
- y: t,
- width: r - l,
- height: b - t
- };
- },
- 'e': function e(rect, offset, space, ratio) {
-
- var t, r, b, l, w, h, p, d;
-
- // left is fixed
- l = rect.x;
-
- // intended right edge
- r = limit(offset.x, l, space.width);
-
- // if is too small vertically
- if (r - l < space.min.width) {
- r = l + space.min.width;
- }
-
- // if should scale by ratio, pick height by ratio of new width
- h = ratio ? (r - l) * ratio : rect.height;
-
- // check if has fallen below min width or height
- if (h < space.min.height) {
- h = space.min.height;
- r = l + h / ratio;
- }
-
- // add half to top and bottom
- p = (h - rect.height) * .5;
- t = rect.y - p;
- b = rect.y + rect.height + p;
-
- // check if any of the edges has moved out of the available space, if so,
- // set max size of rectangle from original position
- if (t < 0 || b > space.height) {
-
- // smallest distance to edge of space
- d = Math.min(rect.y, space.height - (rect.y + rect.height));
-
- // new top and bottom offsets
- t = rect.y - d;
- b = rect.y + rect.height + d;
-
- // resulting height
- h = b - t;
-
- // resulting width based on ratio
- w = h / ratio;
-
- // new right position
- r = l + w;
- }
-
- return {
- x: l,
- y: t,
- width: r - l,
- height: b - t
- };
- },
- 'w': function w(rect, offset, space, ratio) {
-
- var t, r, b, l, w, h, p, d;
-
- // right is fixed
- r = rect.x + rect.width;
-
- // intended left edge
- l = limit(offset.x, 0, r);
-
- // if is too small vertically
- if (r - l < space.min.width) {
- l = r - space.min.width;
- }
-
- // if should scale by ratio, pick height by ratio of new width
- h = ratio ? (r - l) * ratio : rect.height;
-
- // check if has fallen below min width or height
- if (h < space.min.height) {
- h = space.min.height;
- l = r - h / ratio;
- }
-
- // add half to top and bottom
- p = (h - rect.height) * .5;
- t = rect.y - p;
- b = rect.y + rect.height + p;
-
- // check if any of the edges has moved out of the available space, if so,
- // set max size of rectangle from original position
- if (t < 0 || b > space.height) {
-
- // smallest distance to edge of space
- d = Math.min(rect.y, space.height - (rect.y + rect.height));
-
- // new top and bottom offsets
- t = rect.y - d;
- b = rect.y + rect.height + d;
-
- // resulting height
- h = b - t;
-
- // resulting width based on ratio
- w = h / ratio;
-
- // new right position
- l = r - w;
- }
-
- return {
- x: l,
- y: t,
- width: r - l,
- height: b - t
- };
- },
- 'ne': function ne(rect, offset, space, ratio) {
-
- var t, r, b, l, w, h, d;
-
- // left and bottom are fixed
- l = rect.x;
- b = rect.y + rect.height;
-
- // intended right edge
- r = limit(offset.x, l, space.width);
-
- // if is too small vertically
- if (r - l < space.min.width) {
- r = l + space.min.width;
- }
-
- // if should scale by ratio, pick height by ratio of new width
- h = ratio ? (r - l) * ratio : limit(b - offset.y, space.min.height, b);
-
- // check if has fallen below min width or height
- if (h < space.min.height) {
- h = space.min.height;
- r = l + h / ratio;
- }
-
- // add height difference with original height to top
- t = rect.y - (h - rect.height);
-
- // check if any of the edges has moved out of the available space, if so,
- // set max size of rectangle from original position
- if (t < 0 || b > space.height) {
-
- // smallest distance to edge of space
- d = Math.min(rect.y, space.height - (rect.y + rect.height));
-
- // new top and bottom offsets
- t = rect.y - d;
-
- // resulting height
- h = b - t;
-
- // resulting width based on ratio
- w = h / ratio;
-
- // new right position
- r = l + w;
- }
-
- return {
- x: l,
- y: t,
- width: r - l,
- height: b - t
- };
- },
- 'se': function se(rect, offset, space, ratio) {
-
- var t, r, b, l, w, h, d;
-
- // left and top are fixed
- l = rect.x;
- t = rect.y;
-
- // intended right edge
- r = limit(offset.x, l, space.width);
-
- // if is too small vertically
- if (r - l < space.min.width) {
- r = l + space.min.width;
- }
-
- // if should scale by ratio, pick height by ratio of new width
- h = ratio ? (r - l) * ratio : limit(offset.y - rect.y, space.min.height, space.height - t);
-
- // check if has fallen below min width or height
- if (h < space.min.height) {
- h = space.min.height;
- r = l + h / ratio;
- }
-
- // add height difference with original height to bottom
- b = rect.y + rect.height + (h - rect.height);
-
- // check if any of the edges has moved out of the available space, if so,
- // set max size of rectangle from original position
- if (t < 0 || b > space.height) {
-
- // smallest distance to edge of space
- d = Math.min(rect.y, space.height - (rect.y + rect.height));
-
- // new bottom offset
- b = rect.y + rect.height + d;
-
- // resulting height
- h = b - t;
-
- // resulting width based on ratio
- w = h / ratio;
-
- // new right position
- r = l + w;
- }
-
- return {
- x: l,
- y: t,
- width: r - l,
- height: b - t
- };
- },
- 'sw': function sw(rect, offset, space, ratio) {
-
- var t, r, b, l, w, h, d;
-
- // right and top are fixed
- r = rect.x + rect.width;
- t = rect.y;
-
- // intended left edge
- l = limit(offset.x, 0, r);
-
- // if is too small vertically
- if (r - l < space.min.width) {
- l = r - space.min.width;
- }
-
- // if should scale by ratio, pick height by ratio of new width
- h = ratio ? (r - l) * ratio : limit(offset.y - rect.y, space.min.height, space.height - t);
-
- // check if has fallen below min width or height
- if (h < space.min.height) {
- h = space.min.height;
- l = r - h / ratio;
- }
-
- // add height difference with original height to bottom
- b = rect.y + rect.height + (h - rect.height);
-
- // check if any of the edges has moved out of the available space, if so,
- // set max size of rectangle from original position
- if (t < 0 || b > space.height) {
-
- // smallest distance to edge of space
- d = Math.min(rect.y, space.height - (rect.y + rect.height));
-
- // new bottom offset
- b = rect.y + rect.height + d;
-
- // resulting height
- h = b - t;
-
- // resulting width based on ratio
- w = h / ratio;
-
- // new left position
- l = r - w;
- }
-
- return {
- x: l,
- y: t,
- width: r - l,
- height: b - t
- };
- },
- 'nw': function nw(rect, offset, space, ratio) {
-
- var t, r, b, l, w, h, d;
-
- // right and bottom are fixed
- r = rect.x + rect.width;
- b = rect.y + rect.height;
-
- // intended left edge
- l = limit(offset.x, 0, r);
-
- // if is too small vertically
- if (r - l < space.min.width) {
- l = r - space.min.width;
- }
-
- // if should scale by ratio, pick height by ratio of new width
- h = ratio ? (r - l) * ratio : limit(b - offset.y, space.min.height, b);
-
- // check if has fallen below min width or height
- if (h < space.min.height) {
- h = space.min.height;
- l = r - h / ratio;
- }
-
- // add height difference with original height to bottom
- t = rect.y - (h - rect.height);
-
- // check if any of the edges has moved out of the available space, if so,
- // set max size of rectangle from original position
- if (t < 0 || b > space.height) {
-
- // smallest distance to edge of space
- d = Math.min(rect.y, space.height - (rect.y + rect.height));
-
- // new bottom offset
- t = rect.y - d;
-
- // resulting height
- h = b - t;
-
- // resulting width based on ratio
- w = h / ratio;
-
- // new left position
- l = r - w;
- }
-
- return {
- x: l,
- y: t,
- width: r - l,
- height: b - t
- };
- }
-
- };
-
- /**
- * CropArea
- */
- return function () {
- function CropArea() {
- var element = arguments.length <= 0 || arguments[0] === undefined ? document.createElement('div') : arguments[0];
-
- _classCallCheck(this, CropArea);
-
- this._element = element;
-
- this._interaction = null;
-
- this._minWidth = 0;
- this._minHeight = 0;
-
- this._ratio = null;
-
- this._rect = {
- x: 0,
- y: 0,
- width: 0,
- height: 0
- };
-
- this._space = {
- width: 0,
- height: 0
- };
-
- this._rectChanged = false;
-
- this._init();
- }
-
- _createClass(CropArea, [{
- key: '_init',
- value: function _init() {
-
- this._element.className = 'slim-crop-area';
-
- // lines
- var lines = create('div', 'grid');
- this._element.appendChild(lines);
-
- // corner & edge resize buttons
- for (var handler in resizers) {
- if (!resizers.hasOwnProperty(handler)) {
- continue;
- }
- var _btn = create('button', handler);
- this._element.appendChild(_btn);
- }
- var btn = create('button', 'c');
- this._element.appendChild(btn);
-
- addEvents(document, Events.DOWN, this);
- }
- }, {
- key: 'reset',
- value: function reset() {
-
- this._interaction = null;
-
- this._rect = {
- x: 0,
- y: 0,
- width: 0,
- height: 0
- };
-
- this._rectChanged = true;
-
- this._redraw();
-
- this._element.dispatchEvent(new CustomEvent('change'));
- }
- }, {
- key: 'rescale',
- value: function rescale(scale) {
-
- // no rescale
- if (scale === 1) {
- return;
- }
-
- this._interaction = null;
-
- this._rectChanged = true;
-
- this._rect.x *= scale;
- this._rect.y *= scale;
- this._rect.width *= scale;
- this._rect.height *= scale;
-
- this._redraw();
-
- this._element.dispatchEvent(new CustomEvent('change'));
- }
- }, {
- key: 'limit',
- value: function limit(width, height) {
- this._space = {
- width: width,
- height: height
- };
- }
- }, {
- key: 'resize',
- value: function resize(x, y, width, height) {
-
- this._interaction = null;
-
- this._rect = {
- x: x,
- y: y,
- width: limit(width, 0, this._space.width),
- height: limit(height, 0, this._space.height)
- };
-
- this._rectChanged = true;
-
- this._redraw();
-
- this._element.dispatchEvent(new CustomEvent('change'));
- }
- }, {
- key: 'handleEvent',
- value: function handleEvent(e) {
-
- switch (e.type) {
-
- case 'touchstart':
- case 'pointerdown':
- case 'mousedown':
- {
- this._onStartDrag(e);
- }
- break;
- case 'touchmove':
- case 'pointermove':
- case 'mousemove':
- {
- this._onDrag(e);
- }
- break;
- case 'touchend':
- case 'touchcancel':
- case 'pointerup':
- case 'mouseup':
- {
- this._onStopDrag(e);
- }
-
- }
- }
- }, {
- key: '_onStartDrag',
- value: function _onStartDrag(e) {
-
- // is not my event?
- if (!this._element.contains(e.target)) {
- return;
- }
-
- e.preventDefault();
-
- // listen to drag related events
- addEvents(document, Events.MOVE, this);
- addEvents(document, Events.UP, this);
-
- // now interacting with control
- this._interaction = {
- type: e.target.className,
- offset: getEventOffsetLocal(e, this._element)
- };
-
- // now dragging
- this._element.setAttribute('data-dragging', 'true');
-
- // start the redraw update loop
- this._redraw();
- }
- }, {
- key: '_onDrag',
- value: function _onDrag(e) {
-
- e.preventDefault();
-
- // get local offset for this event
- var offset = getEventOffsetLocal(e, this._element.parentNode);
- var type = this._interaction.type;
-
- // drag
- if (type === 'c') {
-
- this._rect.x = limit(offset.x - this._interaction.offset.x, 0, this._space.width - this._rect.width);
- this._rect.y = limit(offset.y - this._interaction.offset.y, 0, this._space.height - this._rect.height);
- }
- // resize
- else if (resizers[type]) {
-
- this._rect = resizers[type](this._rect, offset, {
- x: 0,
- y: 0,
- width: this._space.width,
- height: this._space.height,
- min: {
- width: this._minWidth,
- height: this._minHeight
- }
- }, this._ratio);
- }
-
- this._rectChanged = true;
-
- // dispatch
- this._element.dispatchEvent(new CustomEvent('input'));
- }
- }, {
- key: '_onStopDrag',
- value: function _onStopDrag(e) {
-
- e.preventDefault();
-
- // stop listening to drag related events
- removeEvents(document, Events.MOVE, this);
- removeEvents(document, Events.UP, this);
-
- // no longer interacting, so no need to redraw
- this._interaction = null;
-
- // now dragging
- this._element.setAttribute('data-dragging', 'false');
-
- // fire change event
- this._element.dispatchEvent(new CustomEvent('change'));
- }
- }, {
- key: '_redraw',
- value: function _redraw() {
- var _this = this;
-
- if (this._rectChanged) {
-
- 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';
-
- this._rectChanged = false;
- }
-
- // if no longer interacting with crop area stop here
- if (!this._interaction) {
- return;
- }
-
- // redraw
- requestAnimationFrame(function () {
- return _this._redraw();
- });
- }
- }, {
- key: 'destroy',
- value: function destroy() {
-
- this._interaction = false;
- this._rectChanged = false;
-
- removeEvents(document, Events.DOWN, this);
- removeEvents(document, Events.MOVE, this);
- removeEvents(document, Events.UP, this);
-
- removeElement(this._element);
- }
- }, {
- key: 'element',
- get: function get() {
- return this._element;
- }
- }, {
- key: 'space',
- get: function get() {
- return this._space;
- }
- }, {
- key: 'area',
- get: function get() {
- return {
- x: this._rect.x / this._space.width,
- y: this._rect.y / this._space.height,
- width: this._rect.width / this._space.width,
- height: this._rect.height / this._space.height
- };
- }
- }, {
- key: 'dirty',
- get: function get() {
- return this._rect.x !== 0 || this._rect.y !== 0 || this._rect.width !== 0 || this._rect.height !== 0;
- }
- }, {
- key: 'minWidth',
- set: function set(value) {
- this._minWidth = value;
- }
- }, {
- key: 'minHeight',
- set: function set(value) {
- this._minHeight = value;
- }
- }, {
- key: 'ratio',
- set: function set(value) {
- this._ratio = value;
- }
- }]);
-
- return CropArea;
- }();
- }();
- var ImageEditor = function () {
-
- /**
- * ImageEditor
- * @param element
- * @param options
- * @constructor
- */
-
- var CropAreaEvents = ['input', 'change'];
-
- var ImageEditor = function () {
- function ImageEditor() {
- var element = arguments.length <= 0 || arguments[0] === undefined ? document.createElement('div') : arguments[0];
- var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
-
- _classCallCheck(this, ImageEditor);
-
- this._element = element;
- this._options = mergeOptions(ImageEditor.options(), options);
-
- this._ratio = null;
- this._output = null;
-
- this._input = null;
-
- this._preview = null;
- this._previewBlurred = null;
-
- this._blurredPreview = false;
-
- this._cropper = null;
- this._previewWrapper = null;
- this._currentWindowSize = {};
-
- this._btnGroup = null;
-
- this._dirty = false;
-
- this._wrapperRotation = 0;
- this._wrapperScale = 1.0;
-
- this._init();
- }
-
- _createClass(ImageEditor, [{
- key: '_init',
- value: function _init() {
- var _this2 = this;
-
- this._element.className = 'slim-image-editor';
-
- // container
- this._container = create('div', 'slim-container');
-
- // wrapper
- this._wrapper = create('div', 'slim-wrapper');
-
- // photo crop mark container
- this._stage = create('div', 'slim-stage');
- this._container.appendChild(this._stage);
-
- // create crop marks
- this._cropper = new CropArea();
- CropAreaEvents.forEach(function (e) {
- _this2._cropper.element.addEventListener(e, _this2);
- });
- this._stage.appendChild(this._cropper.element);
-
- // canvas ghost
- this._previewWrapper = create('div', 'slim-image-editor-preview slim-crop-preview');
- this._previewBlurred = create('canvas', 'slim-crop-blur');
- this._previewWrapper.appendChild(this._previewBlurred);
- this._wrapper.appendChild(this._previewWrapper);
-
- this._preview = create('img', 'slim-crop');
- this._previewWrapper.appendChild(this._preview);
-
- // buttons
- this._btnGroup = create('div', 'slim-editor-btn-group');
- ImageEditor.Buttons.forEach(function (c) {
- var prop = capitalizeFirstLetter(c);
- var label = _this2._options['button' + prop + 'Label'];
- var title = _this2._options['button' + prop + 'Title'];
- var className = _this2._options['button' + prop + 'ClassName'];
- var btn = create('button', 'slim-editor-btn slim-btn-' + c + (className ? ' ' + className : ''));
- btn.innerHTML = label;
- btn.title = title || label;
- btn.type = 'button';
- btn.setAttribute('data-action', c);
- btn.addEventListener('click', _this2);
- _this2._btnGroup.appendChild(btn);
- });
-
- // utils
- this._utilsGroup = create('div', 'slim-editor-utils-group');
-
- // create rotation button
- var btn = create('button', 'slim-editor-utils-btn slim-btn-rotate');
- btn.setAttribute('data-action', 'rotate');
- btn.addEventListener('click', this);
- btn.title = this._options.buttonRotateTitle;
- this._utilsGroup.appendChild(btn);
-
- this._container.appendChild(this._wrapper);
-
- this._element.appendChild(this._container);
- this._element.appendChild(this._utilsGroup);
- this._element.appendChild(this._btnGroup);
- }
- }, {
- key: 'dirty',
- value: function dirty() {
- this._dirty = true;
- }
- }, {
- key: 'handleEvent',
- value: function handleEvent(e) {
-
- switch (e.type) {
- case 'click':
- this._onClick(e);
- break;
- case 'change':
- this._onGridChange(e);
- break;
- case 'input':
- this._onGridInput(e);
- break;
- case 'keydown':
- this._onKeyDown(e);
- break;
- case 'resize':
- this._onResize(e);
- break;
- }
- }
- }, {
- key: '_onKeyDown',
- value: function _onKeyDown(e) {
-
- switch (e.keyCode) {
- case Key.RETURN:
- {
- this._confirm();
- break;
- }
- case Key.ESC:
- {
- this._cancel();
- break;
- }
- }
- }
- }, {
- key: '_onClick',
- value: function _onClick(e) {
-
- if (e.target.classList.contains('slim-btn-cancel')) {
- this._cancel();
- }
-
- if (e.target.classList.contains('slim-btn-confirm')) {
- this._confirm();
- }
-
- if (e.target.classList.contains('slim-btn-rotate')) {
- this._rotate();
- }
- }
- }, {
- key: '_onResize',
- value: function _onResize() {
-
- // remember window size
- this._currentWindowSize = {
- width: window.innerWidth,
- height: window.innerHeight
- };
-
- // redraw the image editor based on new dimensions
- this._redraw();
-
- this._redrawCropper(this._cropper.area);
-
- this._updateWrapperScale();
-
- // apply rotation and scale
- this._redrawWrapper();
- }
- }, {
- key: '_redrawWrapper',
- value: function _redrawWrapper() {
- var matrix = snabbt.createMatrix();
- matrix.scale(this._wrapperScale, this._wrapperScale);
- matrix.rotateZ(this._wrapperRotation * (Math.PI / 180));
- snabbt.setElementTransform(this._previewWrapper, matrix);
- }
- }, {
- key: '_onGridInput',
- value: function _onGridInput() {
-
- this._redrawCropMask();
- }
- }, {
- key: '_onGridChange',
- value: function _onGridChange() {
-
- this._redrawCropMask();
- }
- }, {
- key: '_updateWrapperRotation',
- value: function _updateWrapperRotation() {
-
- if (this._options.minSize.width > this._input.height || this._options.minSize.height > this._input.width) {
- this._wrapperRotation += 180;
- } else {
- this._wrapperRotation += 90;
- }
- }
- }, {
- key: '_updateWrapperScale',
- value: function _updateWrapperScale() {
-
- // test if is tilted
- var isTilted = this._wrapperRotation % 180 !== 0;
-
- // if tilted determine correct scale factor
- if (isTilted) {
-
- // maximum size
- var maxWidth = this._container.offsetWidth;
- var maxHeight = this._container.offsetHeight;
-
- // get wrapper size
- var wrapperWidth = this._wrapper.offsetHeight;
- var wrapperHeight = this._wrapper.offsetWidth;
-
- var scalar = maxWidth / wrapperWidth;
- if (scalar * wrapperHeight > maxHeight) {
- scalar = maxHeight / wrapperHeight;
- }
-
- this._wrapperScale = scalar;
- } else {
- this._wrapperScale = 1.0;
- }
- }
-
- /**
- * Action handlers
- */
-
- }, {
- key: '_cancel',
- value: function _cancel() {
-
- this._element.dispatchEvent(new CustomEvent('cancel'));
- }
- }, {
- key: '_confirm',
- value: function _confirm() {
-
- var isTilted = this._wrapperRotation % 180 !== 0;
- this._element.dispatchEvent(new CustomEvent('confirm', {
- detail: {
- rotation: this._wrapperRotation % 360,
- crop: scaleRect(this._cropper.area, isTilted ? this._input.height : this._input.width, isTilted ? this._input.width : this._input.height)
- }
- }));
- }
- }, {
- key: '_rotate',
- value: function _rotate() {
- var _this3 = this;
-
- // rotate!
- this._updateWrapperRotation();
-
- // wrapper might also need to be scaled
- this._updateWrapperScale();
-
- // hide the mask
- this._clearCropMask();
-
- // hide the cropper
- this._hideCropper();
-
- // rotation effect
- snabbt(this._previewWrapper, {
- rotation: [0, 0, this._wrapperRotation * (Math.PI / 180)],
- scale: [this._wrapperScale, this._wrapperScale],
- easing: 'spring',
- springConstant: .8,
- springDeceleration: .65,
- complete: function complete() {
-
- // redraws auto cropper
- _this3._redrawCropper();
-
- // shows the cropper
- _this3._showCropper();
- }
- });
- }
- }, {
- key: '_showCropper',
- value: function _showCropper() {
-
- snabbt(this._stage, {
- easing: 'ease',
- duration: 250,
- fromOpacity: 0,
- opacity: 1.0
- });
- }
- }, {
- key: '_hideCropper',
- value: function _hideCropper() {
-
- snabbt(this._stage, {
- duration: 0,
- fromOpacity: 0,
- opacity: 0
- });
- }
- }, {
- key: '_clearCropMask',
- value: function _clearCropMask() {
- this._preview.style.clip = '';
- }
- }, {
- key: '_redrawCropMask',
- value: function _redrawCropMask() {
- var _this4 = this;
-
- // get rotation
- var rotation = this._wrapperRotation % 360;
-
- // get image size
- var canvas = {
- width: this._wrapper.offsetWidth,
- height: this._wrapper.offsetHeight
- };
-
- // get default mask cropper area
- var mask = this._cropper.area;
- var px = mask.x;
- var py = mask.y;
- var pw = mask.width;
- var ph = mask.height;
-
- // rotate cropper area
- if (rotation === 90) {
- mask.x = 1 - py - ph;
- mask.y = px;
- mask.width = ph;
- mask.height = pw;
- } else if (rotation === 180) {
- mask.x = 1 - (px + pw);
- mask.y = 1 - (py + ph);
- mask.width = pw;
- mask.height = ph;
- } else if (rotation === 270) {
- mask.x = py;
- mask.y = 1 - px - pw;
- mask.width = ph;
- mask.height = pw;
- }
-
- // scale rect to fit canvas
- mask = scaleRect(mask, canvas.width, canvas.height);
-
- // update on next frame (so it's in sync with crop area redraw)
- requestAnimationFrame(function () {
-
- // show only crop area of original image
- _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';
- });
- }
- }, {
- key: 'open',
- value: function open(image, ratio, crop, rotation, complete) {
- var _this5 = this;
-
- // test if is same image
- if (this._input && !this._dirty) {
- complete();
- return;
- }
-
- // reset dirty value
- this._dirty = false;
-
- // reset rotation
- this._wrapperRotation = rotation || 0;
-
- // we'll always have to blur the preview of a newly opened image
- this._blurredPreview = false;
-
- // set ratio
- this._ratio = ratio;
-
- // hide editor
- this._element.style.opacity = '0';
-
- // set as new input image
- this._input = image;
-
- // calculate crop
- var tilted = rotation % 180 !== 0;
- var relativeCrop = divideRect(crop, tilted ? image.height : image.width, tilted ? image.width : image.height);
-
- // preview has now loaded
- this._preview.onload = function () {
-
- // reset onload listener
- _this5._preview.onload = null;
-
- // setup cropper
- _this5._cropper.ratio = _this5.ratio;
-
- // redraw view (for first time)
- _this5._redraw();
-
- // redraw cropper
- _this5._redrawCropper(relativeCrop);
-
- // done
- complete();
-
- // fade in
- _this5._element.style.opacity = '';
- };
-
- // load lower resolution preview image
- this._preview.src = cloneCanvasScaled(this._input, Math.min(this._container.offsetWidth / this._input.width, this._container.offsetHeight / this._input.height)).toDataURL();
- }
- }, {
- key: '_redrawCropper',
- value: function _redrawCropper(rect) {
-
- var isTilted = this._wrapperRotation % 180 !== 0;
-
- // get dimensions of image wrapper
- var width = this._wrapper.offsetWidth;
- var height = this._wrapper.offsetHeight;
-
- // get space
- var maxWidth = this._container.offsetWidth;
- var maxHeight = this._container.offsetHeight;
-
- // rescale wrapper
- this._updateWrapperScale();
-
- // position cropper container to fit wrapper
- var sw = this._wrapperScale * (isTilted ? height : width);
- var sh = this._wrapperScale * (isTilted ? width : height);
- var sx = isTilted ? (maxWidth - sw) * .5 : this._wrapper.offsetLeft;
- var sy = isTilted ? (maxHeight - sh) * .5 : this._wrapper.offsetTop;
-
- 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';
-
- // set new size limit for crops
- this._cropper.limit(sw, sh);
-
- // set min and max height of cropper
- this._cropper.minWidth = this._wrapperScale * this._options.minSize.width * this.scalar;
- this._cropper.minHeight = this._wrapperScale * this._options.minSize.height * this.scalar;
-
- // set crop rect
- var crop = null;
- if (rect) {
- crop = {
- x: rect.x * sw,
- y: rect.y * sh,
- width: rect.width * sw,
- height: rect.height * sh
- };
- } else {
- crop = getAutoCropRect(sw, sh, this._ratio || sh / sw);
- }
-
- this._cropper.resize(crop.x, crop.y, crop.width, crop.height);
-
- // resize of cropper will automatically trigger a change event which will update the blur mask
- }
- }, {
- key: '_redraw',
- value: function _redraw() {
-
- // image ratio
- var ratio = this._input.height / this._input.width;
-
- // determine rounded size
- var maxWidth = this._container.clientWidth;
- var maxHeight = this._container.clientHeight;
-
- var width = maxWidth;
- var height = width * ratio;
-
- if (height > maxHeight) {
- height = maxHeight;
- width = height / ratio;
- }
-
- width = Math.round(width);
- height = Math.round(height);
-
- // rescale and recenter wrapper
- var x = (maxWidth - width) / 2;
- var y = (maxHeight - height) / 2;
-
- 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';
-
- // set image size based on container dimensions
- this._previewBlurred.style.cssText = '\n\t\t\t\twidth:' + width + 'px;\n\t\t\t\theight:' + height + 'px;\n\t\t\t';
-
- this._preview.width = width;
- this._preview.height = height;
-
- // scale and blur the blurry preview
- if (!this._blurredPreview) {
-
- this._previewBlurred.width = 300;
- this._previewBlurred.height = this._previewBlurred.width * ratio;
-
- copyCanvas(this._input, this._previewBlurred);
-
- blurCanvas(this._previewBlurred, 3);
-
- this._blurredPreview = true;
- }
- }
- }, {
- key: 'show',
- value: function show() {
- var callback = arguments.length <= 0 || arguments[0] === undefined ? function () {} : arguments[0];
-
-
- // test if window size has changed since previous showing
- if (this._currentWindowSize.width !== window.innerWidth || this._currentWindowSize.height !== window.innerHeight) {
-
- // if so, reposition elements
- this._redraw();
-
- // redraw cropper position
- this._redrawCropper(this._cropper.area);
- }
-
- // listen to keydown so we can close or confirm with RETURN / ESC
- document.addEventListener('keydown', this);
-
- // when window is scaled we want to resize the editor
- window.addEventListener('resize', this);
-
- // fade in preview
- var rotation = this._wrapperRotation * (Math.PI / 180);
- snabbt(this._previewWrapper, {
- fromRotation: [0, 0, rotation],
- rotation: [0, 0, rotation],
- fromPosition: [0, 0, 0],
- position: [0, 0, 0],
- fromOpacity: 0,
- opacity: .9999, // fixes IE render bug
- fromScale: [this._wrapperScale - .02, this._wrapperScale - .02],
- scale: [this._wrapperScale, this._wrapperScale],
- easing: 'spring',
- springConstant: .3,
- springDeceleration: .85,
- delay: 450,
- complete: function complete() {
- //resetTransforms(this);
- }
- });
-
- if (this._cropper.dirty) {
-
- // now show cropper
- snabbt(this._stage, {
- fromPosition: [0, 0, 0],
- position: [0, 0, 0],
- fromOpacity: 0,
- opacity: 1,
- duration: 250,
- delay: 550,
- complete: function complete() {
- resetTransforms(this);
- callback();
- }
- });
- } else {
-
- // now show cropper
- snabbt(this._stage, {
- fromPosition: [0, 0, 0],
- position: [0, 0, 0],
- fromOpacity: 0,
- opacity: 1,
- duration: 250,
- delay: 1000,
- complete: function complete() {
- resetTransforms(this);
- }
- });
- }
-
- // now show buttons
- snabbt(this._btnGroup.childNodes, {
- fromScale: [.9, .9],
- scale: [1, 1],
- fromOpacity: 0,
- opacity: 1,
- delay: function delay(i) {
- return 1000 + i * 100;
- },
- easing: 'spring',
- springConstant: .3,
- springDeceleration: .85,
- complete: function complete() {
- resetTransforms(this);
- }
- });
-
- snabbt(this._utilsGroup.childNodes, {
- fromScale: [.9, .9],
- scale: [1, 1],
- fromOpacity: 0,
- opacity: 1,
- easing: 'spring',
- springConstant: .3,
- springDeceleration: .85,
- delay: 1250,
- complete: function complete() {
- resetTransforms(this);
- }
- });
- }
- }, {
- key: 'hide',
- value: function hide() {
- var callback = arguments.length <= 0 || arguments[0] === undefined ? function () {} : arguments[0];
-
-
- // no more need to listen to keydown
- document.removeEventListener('keydown', this);
-
- // no more need to resize editor when window is scaled
- window.removeEventListener('resize', this);
-
- // fade out buttons
- snabbt(this._utilsGroup.childNodes, {
- fromOpacity: 1,
- opacity: 0,
- duration: 250
- });
-
- snabbt(this._btnGroup.childNodes, {
- fromOpacity: 1,
- opacity: 0,
- delay: 200,
- duration: 350
- });
-
- snabbt([this._stage, this._previewWrapper], {
- fromPosition: [0, 0, 0],
- position: [0, -250, 0],
- fromOpacity: 1,
- opacity: 0,
- easing: 'spring',
- springConstant: .3,
- springDeceleration: .75,
- delay: 250,
- allDone: function allDone() {
-
- callback();
- }
- });
- }
- }, {
- key: 'destroy',
- value: function destroy() {
- var _this6 = this;
-
- nodeListToArray(this._btnGroup.children).forEach(function (btn) {
- btn.removeEventListener('click', _this6);
- });
-
- CropAreaEvents.forEach(function (e) {
- _this6._cropper.element.removeEventListener(e, _this6);
- });
-
- this._cropper.destroy();
-
- removeElement(this._element);
- }
- }, {
- key: 'element',
- get: function get() {
- return this._element;
- }
- }, {
- key: 'ratio',
- get: function get() {
- if (this._ratio === 'input') {
- return this._input.height / this._input.width;
- }
- return this._ratio;
- }
- }, {
- key: 'offset',
- get: function get() {
- return this._element.getBoundingClientRect();
- }
- }, {
- key: 'original',
- get: function get() {
- return this._input;
- }
- }, {
- key: 'scalar',
- get: function get() {
- return this._preview.width / this._input.width;
- }
- }], [{
- key: 'options',
- value: function options() {
-
- return {
- buttonCancelClassName: null,
- buttonConfirmClassName: null,
- buttonCancelLabel: 'Cancel',
- buttonConfirmLabel: 'Confirm',
- buttonCancelTitle: null,
- buttonConfirmTitle: null,
-
- buttonRotateTitle: 'Rotate',
-
- minSize: {
- width: 0,
- height: 0
- }
- };
- }
- }]);
-
- return ImageEditor;
- }();
-
- ImageEditor.Buttons = ['cancel', 'confirm'];
-
- return ImageEditor;
- }(CropArea);
- var FileHopper = function () {
-
- /**
- * FileHopper
- * @param element
- * @param options
- * @constructor
- */
- var DragDropEvents = ['dragover', 'dragleave', 'drop'];
-
- return function () {
- function FileHopper() {
- var element = arguments.length <= 0 || arguments[0] === undefined ? document.createElement('div') : arguments[0];
-
- _classCallCheck(this, FileHopper);
-
- this._element = element;
- this._accept = [];
-
- this._dragPath = null;
-
- this._init();
- }
-
- // public properties
-
-
- _createClass(FileHopper, [{
- key: 'areValidFiles',
- value: function areValidFiles(files) {
-
- if (this._accept.length && files) {
- return this._accept.indexOf(files[0].type) != -1;
- }
- return true;
- }
-
- // public methods
-
- }, {
- key: 'reset',
- value: function reset() {
- this._element.files = null;
- }
-
- // private
-
- }, {
- key: '_init',
- value: function _init() {
- var _this7 = this;
-
- this._element.className = 'slim-file-hopper';
-
- DragDropEvents.forEach(function (e) {
- _this7._element.addEventListener(e, _this7);
- });
- }
-
- // the input has changed
-
- }, {
- key: 'handleEvent',
- value: function handleEvent(e) {
-
- switch (e.type) {
- case 'dragover':
- this._onDragOver(e);
- break;
- case 'dragleave':
- this._onDragLeave(e);
- break;
- case 'drop':
- this._onDrop(e);
- break;
- }
- }
- }, {
- key: '_onDrop',
- value: function _onDrop(e) {
-
- // prevents browser from opening image
- e.preventDefault();
-
- // test type in older browsers
- if (!this.areValidFiles(e.dataTransfer.files)) {
-
- // invalid files, stop here
- this._element.dispatchEvent(new CustomEvent('file-invalid-drop'));
-
- // no longer hovering
- this._dragPath = null;
- return;
- }
-
- // store dropped files on element files property, so can be accessed by same function as file input handler
- this._element.files = e.dataTransfer.files;
-
- // file has been dropped
- this._element.dispatchEvent(new CustomEvent('file-drop', {
- detail: getOffsetByEvent(e)
- }));
-
- // file list has changed, let's notify others
- this._element.dispatchEvent(new CustomEvent('change'));
-
- // no longer hovering
- this._dragPath = null;
- }
- }, {
- key: '_onDragOver',
- value: function _onDragOver(e) {
-
- // prevents browser from opening image
- e.preventDefault();
-
- e.dataTransfer.dropEffect = 'copy';
-
- if (!this.areValidFiles(e.dataTransfer.items)) {
-
- // indicate drop mode to user
- e.dataTransfer.dropEffect = 'none';
-
- // invalid files, stop here
- this._element.dispatchEvent(new CustomEvent('file-invalid'));
- return;
- }
-
- // now hovering file over the area
- if (!this._dragPath) {
- this._dragPath = [];
- }
-
- // push location
- this._dragPath.push(getOffsetByEvent(e));
-
- // file is hovering over element
- this._element.dispatchEvent(new CustomEvent('file-over', {
- detail: {
- x: last(this._dragPath).x,
- y: last(this._dragPath).y
- }
- }));
- }
- }, {
- key: '_onDragLeave',
- value: function _onDragLeave(e) {
-
- // user has dragged file out of element area
- this._element.dispatchEvent(new CustomEvent('file-out', {
- detail: getOffsetByEvent(e)
- }));
-
- // now longer hovering file
- this._dragPath = null;
- }
- }, {
- key: 'destroy',
- value: function destroy() {
- var _this8 = this;
-
- DragDropEvents.forEach(function (e) {
- _this8._element.removeEventListener(e, _this8);
- });
-
- removeElement(this._element);
- }
- }, {
- key: 'element',
- get: function get() {
- return this._element;
- }
- }, {
- key: 'dragPath',
- get: function get() {
- return this._dragPath;
- }
- }, {
- key: 'enabled',
- get: function get() {
- return this._element.style.display === '';
- },
- set: function set(value) {
- this._element.style.display = value ? '' : 'none';
- }
- }, {
- key: 'accept',
- set: function set(mimetypes) {
- this._accept = mimetypes;
- },
- get: function get() {
- return this._accept;
- }
- }]);
-
- return FileHopper;
- }();
- }();
- var Popover = function () {
-
- /**
- * Popover
- */
- return function () {
- function Popover() {
- _classCallCheck(this, Popover);
-
- this._element = null;
- this._inner = null;
-
- this._init();
- }
-
- _createClass(Popover, [{
- key: '_init',
- value: function _init() {
-
- this._element = create('div', 'slim-popover');
- this._element.setAttribute('data-state', 'off');
- document.body.appendChild(this._element);
- }
- }, {
- key: 'show',
- value: function show() {
- var _this9 = this;
-
- var callback = arguments.length <= 0 || arguments[0] === undefined ? function () {} : arguments[0];
-
-
- // turn on
- this._element.setAttribute('data-state', 'on');
-
- // show and animate in
- snabbt(this._element, {
- fromOpacity: 0,
- opacity: 1,
- duration: 350,
- complete: function complete() {
-
- // clean up transforms
- resetTransforms(_this9._element);
-
- // ready!
- callback();
- }
- });
- }
- }, {
- key: 'hide',
- value: function hide() {
- var _this10 = this;
-
- var callback = arguments.length <= 0 || arguments[0] === undefined ? function () {} : arguments[0];
-
-
- // animate out and hide
- snabbt(this._element, {
- fromOpacity: 1,
- opacity: 0,
- duration: 500,
- complete: function complete() {
-
- // clean up transforms
- resetTransforms(_this10._element);
-
- // hide the editor
- _this10._element.setAttribute('data-state', 'off');
-
- // ready!
- callback();
- }
- });
- }
- }, {
- key: 'destroy',
- value: function destroy() {
- if (!this._element.parentNode) {
- return;
- }
- this._element.parentNode.removeChild(this._element);
- }
- }, {
- key: 'inner',
- set: function set(element) {
-
- this._inner = element;
- if (this._element.firstChild) {
- this._element.removeChild(this._element.firstChild);
- }
- this._element.appendChild(this._inner);
- }
- }]);
-
- return Popover;
- }();
- }();
- var intSplit = function intSplit(v, c) {
- return v.split(c).map(function (v) {
- return parseInt(v, 10);
- });
- };
-
- var isWrapper = function isWrapper(element) {
- return element.nodeName === 'DIV';
- };
-
- var CropType = {
- AUTO: 'auto',
- INITIAL: 'initial',
- MANUAL: 'manual'
- };
-
- var Rect = ['x', 'y', 'width', 'height'];
-
- var HopperEvents = ['file-invalid-drop', 'file-invalid', 'file-drop', 'file-over', 'file-out', 'click'];
-
- var ImageEditorEvents = ['cancel', 'confirm'];
-
- var SlimButtons = ['remove', 'edit', 'download', 'upload'];
-
- var SlimPopover = null;
- var SlimCount = 0;
-
- 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';
-
- var SlimUploadStatusHTML = '\n<div class="slim-upload-status"></div>\n';
-
- var Slim = function () {
- function Slim(element) {
- var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
-
- _classCallCheck(this, Slim);
-
- // create popover element if not already created
- if (!SlimPopover) {
- SlimPopover = new Popover();
- }
-
- // we create a new counter, we use this counter to determine if we need to clean up the popover
- SlimCount++;
-
- // the options to use
- this._options = mergeOptions(Slim.options(), options);
-
- // reference to original element so we can restore original situation
- this._originalElement = element;
- this._originalElementInner = element.innerHTML;
- this._originalElementAttributes = getElementAttributes(element);
-
- // should be file input, image or slim wrapper, if not wrapper, wrap
- if (!isWrapper(element)) {
-
- this._element = wrap(element);
- this._element.className = element.className;
- element.className = '';
-
- // ratio is used for CSS so let's set default attribute
- this._element.setAttribute('data-ratio', this._options.ratio);
- } else {
- this._element = element;
- }
- this._element.classList.add('slim');
- this._element.setAttribute('data-state', 'init');
-
- // the source element (can either be an input or an img)
- this._input = null;
-
- // the output element (hidden input that contains the resulting json object)
- this._output = null;
-
- // current image ratio
- this._ratio = null;
-
- // was required field
- this._isRequired = false;
-
- // components
- this._imageHopper = null;
- this._imageEditor = null;
-
- // progress path
- this._progressEnabled = true;
-
- // resulting data
- this._data = {};
- this._resetData();
-
- // editor state
- this._state = [];
-
- // the circle below the mouse hover point
- this._drip = null;
-
- // had initial image
- this._hasInitialImage = false;
-
- // initial crop
- this._initialCrop = this._options.crop;
-
- // set to true when destroy method is called, used to halt any timeouts or async processes
- this._isBeingDestroyed = false;
-
- // let's go!
- if (Slim.supported) {
- this._init();
- } else {
- this._fallback();
- }
- }
-
- _createClass(Slim, [{
- key: 'isAttachedTo',
-
-
- // methods
- // Test if this Slim object has been bound to the given element
- value: function isAttachedTo(element) {
- return this._element === element || this._originalElement === element;
- }
- }, {
- key: 'isDetached',
- value: function isDetached() {
- return this._element.parentNode === null;
- }
- }, {
- key: 'load',
- value: function load(src) {
- var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
- var callback = arguments[2];
-
-
- if (typeof options == 'function') {
- callback = options;
- } else {
-
- // store in options
- this._options.crop = options.crop;
-
- // set initial crop
- this._initialCrop = this._options.crop;
- }
-
- this._load(src, callback);
- }
- }, {
- key: 'upload',
- value: function upload(callback) {
- this._doUpload(callback);
- }
- }, {
- key: 'download',
- value: function download() {
- this._doDownload();
- }
- }, {
- key: 'remove',
- value: function remove() {
- return this._doRemove();
- }
- }, {
- key: 'destroy',
- value: function destroy() {
- this._doDestroy();
- }
- }, {
- key: 'edit',
- value: function edit() {
- this._doEdit();
- }
- }, {
- key: 'crop',
- value: function crop(rect, callback) {
- this._crop(rect.x, rect.y, rect.width, rect.height, callback);
- }
-
- /**
- * State Related
- */
-
- }, {
- key: '_canInstantEdit',
- value: function _canInstantEdit() {
- return this._options.instantEdit && !this._isInitialising;
- }
- }, {
- key: '_getFileInput',
- value: function _getFileInput() {
- return this._element.querySelector('input[type=file]');
- }
- }, {
- key: '_getInitialImage',
- value: function _getInitialImage() {
- return this._element.querySelector('img');
- }
- }, {
- key: '_getInputElement',
- value: function _getInputElement() {
- return this._getFileInput() || this._getInitialImage();
- }
- }, {
- key: '_getRatioSpacerElement',
- value: function _getRatioSpacerElement() {
- return this._element.children[0];
- }
- }, {
- key: '_isImageOnly',
- value: function _isImageOnly() {
- return this._input.nodeName !== 'INPUT';
- }
- }, {
- key: '_isFixedRatio',
- value: function _isFixedRatio() {
- return this._options.ratio.indexOf(':') !== -1;
- }
- }, {
- key: '_toggleButton',
- value: function _toggleButton(action, state) {
- toggleDisplayBySelector('.slim-btn[data-action="' + action + '"]', state, this._element);
- }
- }, {
- key: '_clearState',
- value: function _clearState() {
- this._state = [];
- this._updateState();
- }
- }, {
- key: '_removeState',
- value: function _removeState(state) {
- this._state = this._state.filter(function (item) {
- return item !== state;
- });
- this._updateState();
- }
- }, {
- key: '_addState',
- value: function _addState(state) {
- if (inArray(state, this._state)) {
- return;
- }
- this._state.push(state);
- this._updateState();
- }
- }, {
- key: '_updateState',
- value: function _updateState() {
- this._element.setAttribute('data-state', this._state.join(','));
- }
- }, {
- key: '_resetData',
- value: function _resetData() {
-
- // resets data object
- this._data = {
- server: null,
- meta: null,
- input: {
- image: null,
- name: null,
- type: null,
- width: 0,
- height: 0
- },
- output: {
- image: null,
- width: 0,
- height: 0
- },
- actions: {
- rotation: null,
- crop: null,
- size: null
- }
- };
-
- // resets hidden input clone (has not yet been created when reset call in constructor, hence the check)
- if (this._output) {
- this._output.value = '';
- }
-
- // should reset file input
- resetFileInput(this._getFileInput());
- }
-
- /**
- * Initialisation
- */
-
- }, {
- key: '_init',
- value: function _init() {
- var _this11 = this;
-
- // busy initialising
- this._isInitialising = true;
-
- // cropper root is not file input
- this._addState('empty');
-
- // get input element
- this._input = this._getInputElement();
-
- // if no input supplied we'll automatically create one
- if (!this._input) {
- this._input = create('input');
- this._input.type = 'file';
- this._element.appendChild(this._input);
- }
-
- // get required state of input element
- this._isRequired = this._input.required === true;
-
- // set or create output field
- this._output = this._element.querySelector('input[type=hidden]');
-
- // if no output element defined we'll create one automagically
- if (!this._output) {
- this._output = create('input');
- this._output.type = 'hidden';
- this._output.name = this._input.name || this._options.defaultInputName;
- this._element.appendChild(this._output);
- }
-
- // prevent the original file input field from posting (value will be duplicated to output field)
- this._input.removeAttribute('name');
-
- // create drop area
- var area = create('div', 'slim-area');
-
- // test if contains initial image
- var initialImage = this._getInitialImage();
- var initialImageSrc = (initialImage || {}).src;
- if (initialImageSrc) {
- this._hasInitialImage = true;
- } else {
- this._initialCrop = null;
- }
-
- 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>';
-
- // add drop overlay
- if (this._isImageOnly()) {
- area.innerHTML = '\n\t\t\t\t' + SlimLoaderHTML + '\n\t\t\t\t' + SlimUploadStatusHTML + '\n\t\t\t\t' + resultHTML + '\n\t\t\t';
- } else {
-
- // set common image mime type to the accept attribute
- var mimetypes = void 0;
- if (!this._input.hasAttribute('accept') || this._input.getAttribute('accept') === 'image/*') {
- mimetypes = getCommonMimeTypes();
- this._input.setAttribute('accept', mimetypes.join(','));
- } else {
- mimetypes = this._input.accept.split(',').map(function (mimetype) {
- return mimetype.trim();
- }).filter(function (mimetype) {
- return mimetype.length > 0;
- });
- }
-
- // setup hopper
- this._imageHopper = new FileHopper();
- this._imageHopper.accept = mimetypes;
- this._element.appendChild(this._imageHopper.element);
- HopperEvents.forEach(function (e) {
- _this11._imageHopper.element.addEventListener(e, _this11);
- });
-
- // render area
- 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';
-
- // start listening for events so we can load image on file input change
- this._input.addEventListener('change', this);
- }
-
- // add area node
- this._element.appendChild(area);
-
- // add button group
- this._btnGroup = create('div', 'slim-btn-group');
- this._btnGroup.style.display = 'none';
- this._element.appendChild(this._btnGroup);
- SlimButtons.filter(function (c) {
- return _this11._isButtonAllowed(c);
- }).forEach(function (c) {
- var prop = capitalizeFirstLetter(c);
- var label = _this11._options['button' + prop + 'Label'];
- var title = _this11._options['button' + prop + 'Title'] || label;
- var className = _this11._options['button' + prop + 'ClassName'];
- var btn = create('button', 'slim-btn slim-btn-' + c + (className ? ' ' + className : ''));
- btn.innerHTML = label;
- btn.title = title;
- btn.type = 'button';
- btn.addEventListener('click', _this11);
- btn.setAttribute('data-action', c);
- _this11._btnGroup.appendChild(btn);
- });
-
- // add ratio padding
- if (this._isFixedRatio()) {
- var parts = intSplit(this._options.ratio, ':');
- this._ratio = parts[1] / parts[0];
- this._scaleDropArea(this._ratio);
- }
-
- // setup loader
- this._updateProgress(.5);
-
- // preload source
- if (initialImageSrc) {
-
- this._load(initialImageSrc, function () {
- _this11._onInit();
- });
- } else {
- this._onInit();
- }
- }
- }, {
- key: '_onInit',
- value: function _onInit() {
- var _this12 = this;
-
- // we're done initialising
- this._isInitialising = false;
-
- // done initialising now, else is only called after image load
- var done = function done() {
- // we call this async so the constructor of Slim has returned before the onInit is called, allowing clean immidiate destroy
- setTimeout(function () {
- _this12._options.didInit.apply(_this12, [_this12.data]);
- }, 0);
- };
-
- // save initial image
- if (this._options.saveInitialImage) {
- this._save(function () {
- done();
- }, false);
- } else {
- done();
- }
- }
- }, {
- key: '_updateProgress',
- value: function _updateProgress(progress) {
-
- if (!this._progressEnabled) {
- return;
- }
-
- var loader = this._element.querySelector('.slim-loader');
- if (!loader) {
- return;
- }
- var rect = loader.getBoundingClientRect();
- var paths = loader.querySelectorAll('path');
- var ringWidth = parseInt(paths[0].getAttribute('stroke-width'), 10);
-
- paths[0].setAttribute('d', percentageArc(rect.width * .5, rect.height * .5, rect.width * .5 - ringWidth, .9999));
-
- paths[1].setAttribute('d', percentageArc(rect.width * .5, rect.height * .5, rect.width * .5 - ringWidth, progress));
- }
- }, {
- key: '_startProgress',
- value: function _startProgress() {
- var _this13 = this;
-
- this._progressEnabled = false;
-
- var loader = this._element.querySelector('.slim-loader');
- if (!loader) {
- return;
- }
- var ring = loader.children[0];
-
- this._stopProgressLoop(function () {
-
- loader.removeAttribute('style');
- ring.removeAttribute('style');
-
- _this13._progressEnabled = true;
-
- _this13._updateProgress(0);
-
- _this13._progressEnabled = false;
-
- snabbt(ring, {
-
- fromOpacity: 0,
- opacity: 1,
- duration: 250,
- complete: function complete() {
- _this13._progressEnabled = true;
- }
- });
- });
- }
- }, {
- key: '_stopProgress',
- value: function _stopProgress() {
- var _this14 = this;
-
- var loader = this._element.querySelector('.slim-loader');
- if (!loader) {
- return;
- }
- var ring = loader.children[0];
-
- this._updateProgress(1);
-
- snabbt(ring, {
- fromOpacity: 1,
- opacity: 0,
- duration: 250,
- complete: function complete() {
-
- loader.removeAttribute('style');
- ring.removeAttribute('style');
-
- _this14._updateProgress(.5);
-
- _this14._progressEnabled = false;
- }
- });
- }
- }, {
- key: '_startProgressLoop',
- value: function _startProgressLoop() {
-
- // start loading animation
- var loader = this._element.querySelector('.slim-loader');
- if (!loader) {
- return;
- }
- var ring = loader.children[0];
- loader.removeAttribute('style');
- ring.removeAttribute('style');
-
- // set infinite load state
- this._updateProgress(.5);
-
- // repeat!
- var repeat = 1000;
-
- snabbt(loader, {
- rotation: [0, 0, -(Math.PI * 2) * repeat],
- easing: 'linear',
- duration: repeat * 1000
- });
-
- snabbt(ring, {
- fromOpacity: 0,
- opacity: 1,
- duration: 250
- });
- }
- }, {
- key: '_stopProgressLoop',
- value: function _stopProgressLoop(callback) {
-
- var loader = this._element.querySelector('.slim-loader');
- if (!loader) {
- return;
- }
- var ring = loader.children[0];
-
- snabbt(ring, {
- fromOpacity: parseFloat(ring.style.opacity),
- opacity: 0,
- duration: 250,
- complete: function complete() {
- snabbt(loader, 'stop');
- if (callback) {
- callback();
- }
- }
- });
- }
- }, {
- key: '_isButtonAllowed',
- value: function _isButtonAllowed(button) {
-
- if (button === 'edit') {
- return this._options.edit;
- }
-
- if (button === 'download') {
- return this._options.download;
- }
-
- if (button === 'upload') {
-
- // if no service defined upload button makes no sense
- if (!this._options.service) {
- return false;
- }
-
- // if push mode is set, no need for upload button
- if (this._options.push) {
- return false;
- }
-
- // set upload button
- return true;
- }
-
- if (button === 'remove') {
- return !this._isImageOnly();
- }
-
- return true;
- }
- }, {
- key: '_fallback',
- value: function _fallback() {
-
- this._removeState('init');
-
- // create status area
- var area = create('div', 'slim-area');
- area.innerHTML = '\n\t\t\t<div class="slim-status"><div class="slim-label">' + (this._options.label || '') + '</div></div>\n\t\t';
- this._element.appendChild(area);
-
- this._throwError(this._options.statusNoSupport);
- }
-
- /**
- * Event routing
- */
-
- }, {
- key: 'handleEvent',
- value: function handleEvent(e) {
-
- switch (e.type) {
- case 'click':
- this._onClick(e);
- break;
- case 'change':
- this._onChange(e);
- break;
- case 'cancel':
- this._onCancel(e);
- break;
- case 'confirm':
- this._onConfirm(e);
- break;
- case 'file-over':
- this._onFileOver(e);
- break;
- case 'file-out':
- this._onFileOut(e);
- break;
- case 'file-drop':
- this._onDropFile(e);
- break;
- case 'file-invalid':
- this._onInvalidFile(e);
- break;
- case 'file-invalid-drop':
- this._onInvalidFileDrop(e);
- break;
- }
- }
- }, {
- key: '_getIntro',
- value: function _getIntro() {
- return this._element.querySelector('.slim-result .in');
- }
- }, {
- key: '_getOutro',
- value: function _getOutro() {
- return this._element.querySelector('.slim-result .out');
- }
- }, {
- key: '_getInOut',
- value: function _getInOut() {
- return this._element.querySelectorAll('.slim-result img');
- }
- }, {
- key: '_getDrip',
- value: function _getDrip() {
- if (!this._drip) {
- this._drip = this._element.querySelector('.slim-drip > span');
- }
- return this._drip;
- }
-
- // errors
-
- }, {
- key: '_throwError',
- value: function _throwError(error) {
-
- this._addState('error');
-
- this._element.querySelector('.slim-label').style.display = 'none';
-
- var node = this._element.querySelector('.slim-error');
- if (!node) {
- node = create('div', 'slim-error');
- this._element.querySelector('.slim-status').appendChild(node);
- }
-
- node.innerHTML = error;
- }
- }, {
- key: '_removeError',
- value: function _removeError() {
-
- this._removeState('error');
- this._element.querySelector('.slim-label').style.display = '';
-
- var error = this._element.querySelector('.slim-error');
- if (error) {
- error.parentNode.removeChild(error);
- }
- }
- }, {
- key: '_openFileDialog',
- value: function _openFileDialog() {
- this._removeError();
- this._input.click();
- }
-
- // drop area clicked
-
- }, {
- key: '_onClick',
- value: function _onClick(e) {
- var _this15 = this;
-
- var list = e.target.classList;
- var target = e.target;
-
- // no preview, so possible to drop file
- if (list.contains('slim-file-hopper')) {
- this._openFileDialog();
- return;
- }
-
- // decide what button was clicked
- switch (target.getAttribute('data-action')) {
- case 'remove':
- this._options.willRemove.apply(this, [this.data, function () {
- _this15._doRemove();
- }]);
- break;
- case 'edit':
- this._doEdit();
- break;
- case 'download':
- this._doDownload();
- break;
- case 'upload':
- this._doUpload();
- break;
- }
- }
- }, {
- key: '_onInvalidFileDrop',
- value: function _onInvalidFileDrop() {
-
- this._onInvalidFile();
-
- this._removeState('file-over');
-
- // animate out drip
- var drip = this._getDrip();
- snabbt(drip.firstChild, {
- fromScale: [.5, .5],
- scale: [0, 0],
- fromOpacity: .5,
- opacity: 0,
- duration: 150,
- complete: function complete() {
-
- // clean up transforms
- resetTransforms(drip.firstChild);
- }
- });
- }
- }, {
- key: '_onInvalidFile',
- value: function _onInvalidFile() {
-
- var types = this._imageHopper.accept.map(getExtensionByMimeType);
- var error = this._options.statusFileType.replace('$0', types.join(', '));
- this._throwError(error);
- }
- }, {
- key: '_onImageTooSmall',
- value: function _onImageTooSmall() {
-
- var error = this._options.statusImageTooSmall.replace('$0', this._options.minSize.width + ' \xD7 ' + this._options.minSize.height);
- this._throwError(error);
- }
- }, {
- key: '_onOverWeightFile',
- value: function _onOverWeightFile() {
-
- var error = this._options.statusFileSize.replace('$0', this._options.maxFileSize);
- this._throwError(error);
- }
- }, {
- key: '_onFileOver',
- value: function _onFileOver(e) {
-
- this._addState('file-over');
- this._removeError();
-
- // animations
- var drip = this._getDrip();
-
- // move around drip
- var matrix = snabbt.createMatrix();
- matrix.translate(e.detail.x, e.detail.y, 0);
- snabbt.setElementTransform(drip, matrix);
-
- // on first entry fade in blob
- if (this._imageHopper.dragPath.length == 1) {
-
- // show
- drip.style.opacity = 1;
-
- // animate in
- snabbt(drip.firstChild, {
- fromOpacity: 0,
- opacity: .5,
- fromScale: [0, 0],
- scale: [.5, .5],
- duration: 150
- });
- }
- }
- }, {
- key: '_onFileOut',
- value: function _onFileOut(e) {
-
- this._removeState('file-over');
- this._removeState('file-invalid');
- this._removeError();
-
- // move to last position
- var drip = this._getDrip();
- var matrix = snabbt.createMatrix();
- matrix.translate(e.detail.x, e.detail.y, 0);
- snabbt.setElementTransform(drip, matrix);
-
- // animate out
- snabbt(drip.firstChild, {
- fromScale: [.5, .5],
- scale: [0, 0],
- fromOpacity: .5,
- opacity: 0,
- duration: 150,
- complete: function complete() {
-
- // clean up transforms
- resetTransforms(drip.firstChild);
- }
- });
- }
-
- /**
- * When a file was literally dropped on the drop area
- * @param e
- * @private
- */
-
- }, {
- key: '_onDropFile',
- value: function _onDropFile(e) {
- var _this16 = this;
-
- this._removeState('file-over');
-
- // get drip node reference and set to last position
- var drip = this._getDrip();
- var matrix = snabbt.createMatrix();
- matrix.translate(e.detail.x, e.detail.y, 0);
- snabbt.setElementTransform(drip, matrix);
-
- // get element minimum 10 entries distant from the last entry so we can calculate velocity of movement
- var l = this._imageHopper.dragPath.length;
- var jump = this._imageHopper.dragPath[l - Math.min(10, l)];
-
- // velocity
- var dx = e.detail.x - jump.x;
- var dy = e.detail.y - jump.y;
-
- snabbt(drip, {
- fromPosition: [e.detail.x, e.detail.y, 0],
- position: [e.detail.x + dx, e.detail.y + dy, 0],
- duration: 200
- });
-
- // animate out drip
- snabbt(drip.firstChild, {
-
- fromScale: [.5, .5],
- scale: [2, 2],
- fromOpacity: 1,
- opacity: 0,
- duration: 200,
-
- complete: function complete() {
-
- // clean up transforms
- resetTransforms(drip.firstChild);
-
- // load dropped resource
- _this16._load(e.target.files[0]);
- }
- });
- }
-
- /**
- * When a file has been selected after a click on the drop area
- * @param e
- * @private
- */
-
- }, {
- key: '_onChange',
- value: function _onChange(e) {
-
- this._load(e.target.files[0]);
- }
-
- /**
- * Loads a resource (blocking operation)
- * @param resource
- * @param callback(err)
- * @private
- */
-
- }, {
- key: '_load',
- value: function _load(resource, callback) {
- var _this17 = this;
-
- // stop here
- if (this._isBeingDestroyed) {
- return;
- }
-
- // meta data
- var file = getFileMetaData(resource);
-
- // test if is unknown file or if we should override the mime type
- if (file.type === 'unknown' || this._options.forceType) {
- file = setFileMetaData(file, this._options.forceType || 'png');
- }
-
- // re-test if is valid file type
- if (this._imageHopper && this._imageHopper.accept.indexOf(file.type) === -1) {
- this._onInvalidFile();
- if (callback) {
- callback.apply(this, ['file-invalid']);
- }
- return;
- }
-
- // test if too big
- if (file.size && this._options.maxFileSize && bytesToMegaBytes(file.size) > this._options.maxFileSize) {
- this._onOverWeightFile();
- if (callback) {
- callback.apply(this, ['file-too-big']);
- }
- return;
- }
-
- // no longer empty
- this._removeState('empty');
-
- // can't drop any other image
- if (this._imageHopper) {
- this._imageHopper.enabled = false;
- }
-
- // if has loaded image editor set to dirty
- if (this._imageEditor) {
- this._imageEditor.dirty();
- }
-
- // continue
- this._data.input.name = file.name;
- this._data.input.type = file.type;
- this._data.input.size = file.size;
-
- // start loading indicator
- this._startProgressLoop();
- this._addState('busy');
-
- // fetch resource
- getImageAsCanvas(resource, function (image, meta) {
-
- var rewind = function rewind() {
-
- // rewind state
- if (_this17._imageHopper) {
- _this17._imageHopper.enabled = true;
- }
-
- _this17._removeState('busy');
- _this17._stopProgressLoop();
- _this17._resetData();
- };
-
- // if no image, something went wrong
- if (!image) {
-
- rewind();
-
- if (callback) {
- callback.apply(_this17, ['file-not-found']);
- }
-
- return;
- }
-
- // test if image is too small
- if (!covers(image, _this17._options.minSize)) {
-
- rewind();
-
- _this17._onImageTooSmall();
-
- if (callback) {
- callback.apply(_this17, ['image-too-small']);
- }
-
- return;
- }
-
- var status = _this17._options.didLoad.apply(_this17, [file, image, meta]);
- if (status !== true) {
-
- rewind();
-
- if (status !== false) {
- _this17._throwError(status);
- }
-
- if (callback) {
- callback.apply(_this17, [status]);
- }
-
- return;
- }
-
- // load the image
- _this17._loadCanvas(image, function () {
-
- // do intro stuff
- var intro = _this17._getIntro();
-
- // setup base animation
- var animation = {
- fromScale: [1.25, 1.25],
- scale: [1, 1],
- fromOpacity: 0,
- opacity: 1,
- complete: function complete() {
-
- resetTransforms(intro);
-
- intro.style.opacity = 1;
-
- // don't show buttons when instant editing
- // the buttons will be triggered by the closing of the popup
- if (!_this17._canInstantEdit()) {
- _this17._showButtons();
- }
-
- _this17._stopProgressLoop();
- _this17._removeState('busy');
- _this17._addState('preview');
-
- if (callback) {
- callback.apply(_this17, [null, _this17.data]);
- }
- }
-
- };
-
- // if not attached to DOM, don't animate
- if (_this17.isDetached()) {
- animation.duration = 1;
- } else {
- animation.easing = 'spring';
- animation.springConstant = .3;
- animation.springDeceleration = .7;
- }
-
- // if is instant edit mode don't zoom out but zoom in
- if (_this17._canInstantEdit()) {
- animation.delay = 500;
- animation.duration = 1;
-
- // instant edit mode just fire up the editor immidiately
- _this17._doEdit();
- }
-
- // reveal loaded image
- snabbt(intro, animation);
- });
- });
- }
- }, {
- key: '_loadCanvas',
- value: function _loadCanvas(image, ready) {
- var _this18 = this;
-
- // halt here if cropper is currently being destroyed
- if (this._isBeingDestroyed) {
- return;
- }
-
- // scales the drop area
- // if is 'input' or 'free' parameter
- if (!this._isFixedRatio()) {
- this._ratio = image.height / image.width;
- this._scaleDropArea(this._ratio);
- }
-
- // store raw data
- this._data.input.image = image;
- this._data.input.width = image.width;
- this._data.input.height = image.height;
-
- if (this._initialCrop) {
-
- // use initial supplied crop rectangle
- this._data.actions.crop = clone(this._initialCrop);
- this._data.actions.crop.type = CropType.INITIAL;
-
- // clear initial crop, it's no longer useful
- this._initialCrop = null;
- } else {
- // get automagical crop rectangle
- this._data.actions.crop = getAutoCropRect(image.width, image.height, this._ratio);
- this._data.actions.crop.type = CropType.AUTO;
- }
-
- // if max size set
- if (this._options.size) {
- this._data.actions.size = {
- width: this._options.size.width,
- height: this._options.size.height
- };
- }
-
- // do initial auto transform
- this._applyTransforms(image, function (transformedImage) {
-
- var intro = _this18._getIntro();
- var scalar = intro.offsetWidth / transformedImage.width;
-
- // store data, if has preview image this prevents initial load from pushing
- var willUpload = false;
-
- // can only do auto upload when service is defined and push is enabled...
- if (_this18._options.service && _this18._options.push) {
-
- // ...and is not transformation of initial image
- if (!_this18._hasInitialImage) {
- willUpload = true;
- }
- }
-
- // store data (possibly)
- _this18._save(function () {}, willUpload);
-
- // show intro animation
- intro.src = '';
- intro.src = cloneCanvasScaled(transformedImage, scalar).toDataURL();
- intro.onload = function () {
-
- intro.onload = null;
-
- // bail out if we've been cleaned up
- if (_this18._isBeingDestroyed) {
- return;
- }
-
- if (ready) {
- ready();
- }
- };
- });
- }
- }, {
- key: '_applyTransforms',
- value: function _applyTransforms(image, ready) {
- var _this19 = this;
-
- transformCanvas(image, this._data.actions, function (transformedImage) {
-
- _this19._data.output.width = transformedImage.width;
- _this19._data.output.height = transformedImage.height;
- _this19._data.output.image = transformedImage;
-
- _this19._onTransformCanvas(function (transformedData) {
-
- _this19._data = transformedData;
-
- _this19._options.didTransform.apply(_this19, [_this19.data]);
-
- ready(_this19._data.output.image);
- });
- });
- }
- }, {
- key: '_onTransformCanvas',
- value: function _onTransformCanvas(ready) {
-
- this._options.willTransform.apply(this, [this.data, ready]);
- }
-
- /**
- * Creates the editor nodes
- * @private
- */
-
- }, {
- key: '_appendEditor',
- value: function _appendEditor() {
- var _this20 = this;
-
- // we already have an editor
- if (this._imageEditor) {
- return;
- }
-
- // add editor
- this._imageEditor = new ImageEditor(create('div'), {
- minSize: this._options.minSize,
-
- buttonConfirmClassName: 'slim-btn-confirm',
- buttonCancelClassName: 'slim-btn-cancel',
-
- buttonConfirmLabel: this._options.buttonConfirmLabel,
- buttonCancelLabel: this._options.buttonCancelLabel,
-
- buttonConfirmTitle: this._options.buttonConfirmTitle,
- buttonCancelTitle: this._options.buttonCancelTitle
-
- });
-
- // listen to events
- ImageEditorEvents.forEach(function (e) {
- _this20._imageEditor.element.addEventListener(e, _this20);
- });
- }
- }, {
- key: '_scaleDropArea',
- value: function _scaleDropArea(ratio) {
- var node = this._getRatioSpacerElement();
- if (!node || !this._element) {
- return;
- }
- node.style.marginBottom = ratio * 100 + '%';
- this._element.setAttribute('data-ratio', '1:' + ratio);
- }
-
- /**
- * Data Layer
- * @private
- */
- // image editor closed
-
- }, {
- key: '_onCancel',
- value: function _onCancel(e) {
-
- this._removeState('editor');
-
- this._showButtons();
-
- this._hideEditor();
- }
-
- // user confirmed changes
-
- }, {
- key: '_onConfirm',
- value: function _onConfirm(e) {
- var _this21 = this;
-
- this._removeState('editor');
-
- this._startProgressLoop();
- this._addState('busy');
-
- // clear data
- this._output.value = '';
-
- // apply new action object to this._data
- this._data.actions.rotation = e.detail.rotation;
- this._data.actions.crop = e.detail.crop;
- this._data.actions.crop.type = CropType.MANUAL;
-
- // do transforms
- this._applyTransforms(this._data.input.image, function (transformedImage) {
-
- // set new image result
- var images = _this21._getInOut();
- var intro = images[0].className === 'out' ? images[0] : images[1];
- var outro = intro === images[0] ? images[1] : images[0];
-
- intro.className = 'in';
- intro.style.opacity = '0';
- intro.style.zIndex = '2';
- outro.className = 'out';
- outro.style.zIndex = '1';
-
- // new image get's
- intro.src = '';
- intro.src = cloneCanvasScaled(transformedImage, intro.offsetWidth / transformedImage.width).toDataURL();
- intro.onload = function () {
-
- intro.onload = null;
-
- // scale the dropzone
- if (_this21._options.ratio === 'free') {
- _this21._ratio = intro.naturalHeight / intro.naturalWidth;
- _this21._scaleDropArea(_this21._ratio);
- }
-
- // close the editor
- _this21._hideEditor();
-
- // wait a tiny bit so animations sync up nicely
- setTimeout(function () {
-
- // show the preview
- _this21._showPreview(intro, function () {
-
- // if
- // - service set
- // - and we are pushing
- // we will upload
- var willUpload = _this21._options.service && _this21._options.push;
-
- // save the data
- _this21._save(function (err, data, res) {
-
- // done!
- _this21._toggleButton('upload', true);
-
- _this21._stopProgressLoop();
- _this21._removeState('busy');
-
- _this21._showButtons();
- }, willUpload);
- });
- }, 250);
- };
- });
- }
- }, {
- key: '_crop',
- value: function _crop(x, y, width, height) {
- var _this22 = this;
-
- var callback = arguments.length <= 4 || arguments[4] === undefined ? function () {} : arguments[4];
-
-
- this._startProgressLoop();
- this._addState('busy');
-
- // clear data
- this._output.value = '';
-
- // apply new action object to this._data
- this._data.actions.crop = {
- x: x,
- y: y,
- width: width,
- height: height
- };
- this._data.actions.crop.type = CropType.MANUAL;
-
- // do transforms
- this._applyTransforms(this._data.input.image, function (transformedImage) {
-
- // set new image result
- var images = _this22._getInOut();
- var intro = images[0].className === 'out' ? images[0] : images[1];
- var outro = intro === images[0] ? images[1] : images[0];
-
- intro.className = 'in';
- intro.style.opacity = '1';
- intro.style.zIndex = '2';
- outro.className = 'out';
- outro.style.zIndex = '0';
-
- // new image
- intro.src = '';
- intro.src = cloneCanvasScaled(transformedImage, intro.offsetWidth / transformedImage.width).toDataURL();
- intro.onload = function () {
- intro.onload = null;
-
- // scale the dropzone
- if (_this22._options.ratio === 'free') {
- _this22._ratio = intro.naturalHeight / intro.naturalWidth;
- _this22._scaleDropArea(_this22._ratio);
- }
-
- // determine if will also upload
- var willUpload = _this22._options.service && _this22._options.push;
-
- // save the data
- _this22._save(function (err, data, res) {
-
- _this22._stopProgressLoop();
- _this22._removeState('busy');
-
- callback.apply(_this22, [_this22.data]);
- }, willUpload);
- };
- });
- }
- }, {
- key: '_save',
- value: function _save() {
- var _this23 = this;
-
- var callback = arguments.length <= 0 || arguments[0] === undefined ? function () {} : arguments[0];
- var allowUpload = arguments.length <= 1 || arguments[1] === undefined ? true : arguments[1];
-
-
- // flatten data also turns canvas into data uri's
- var data = this.dataBase64;
-
- // decide if we need to
- // - A. Store the data in an output field
- // - B. Upload the data and store the response in output field
-
- // - we are not doing async uploading (in which case output is used for response)
- // - we are not initialising a replaceable image
- if (!this._options.service && !(this._isInitialising && !this._isImageOnly())) {
-
- this._options.willSave.apply(this, [data, function (data) {
-
- _this23._store(data);
-
- _this23._options.didSave.apply(_this23, [data]);
- }]);
- }
-
- // is remote service defined upload async
- if (this._options.service && allowUpload) {
-
- // allow user to modify the data
- this._options.willSave.apply(this, [data, function (data) {
-
- // do the actual uploading
- _this23._upload(data, function (err, res) {
-
- // store response
- if (!err) {
- _this23._storeServerResponse(res);
- }
-
- // we did upload data
- _this23._options.didUpload.apply(_this23, [err, data, res]);
-
- // done!
- callback(err, data, res);
- });
- }]);
- }
-
- // if no service, we're done here
- if (!this._options.service || !allowUpload) {
- callback();
- }
- }
-
- // stores active file information in hidden output field
-
- }, {
- key: '_storeServerResponse',
- value: function _storeServerResponse(data) {
-
- // remove required flag
- if (this._isRequired) {
- this._input.required = false;
- }
-
- // store data returned from server
- this._data.server = data;
-
- // sync with output value
- this._output.value = (typeof data === 'undefined' ? 'undefined' : _typeof(data)) === 'object' ? JSON.stringify(this._data.server) : data;
- }
-
- // stores data in output field
-
- }, {
- key: '_store',
- value: function _store(data) {
-
- if (this._isRequired) {
- this._input.required = false;
- }
-
- this._output.value = JSON.stringify(data);
- }
-
- // uploads given data to server
-
- }, {
- key: '_upload',
- value: function _upload(data, callback) {
- var _this24 = this;
-
- var formData = new FormData();
- formData.append(this._output.name, JSON.stringify(data));
-
- var statusNode = this._element.querySelector('.slim-upload-status');
-
- var requestDecorator = this._options.willRequest;
-
- send(
- // url to service
- this._options.service,
-
- // data
- formData,
-
- // decorator (useful to add headers to request
- requestDecorator,
-
- // progress
- function (loaded, total) {
-
- _this24._updateProgress(loaded / total);
- },
-
- // success
- function (obj) {
-
- setTimeout(function () {
-
- statusNode.innerHTML = _this24._options.statusUploadSuccess;
- statusNode.setAttribute('data-state', 'success');
- statusNode.style.opacity = 1;
-
- // hide status update after 2 seconds
- setTimeout(function () {
- statusNode.style.opacity = 0;
- }, 2000);
- }, 250);
-
- callback(null, obj);
- },
-
- // error
- function (status) {
-
- var html = '';
- if (status === 'file-too-big') {
- html = _this24._options.statusContentLength;
- } else {
- html = _this24._options.statusUnknownResponse;
- }
-
- // when an error occurs the status update is not automatically hidden
- setTimeout(function () {
-
- statusNode.innerHTML = html;
- statusNode.setAttribute('data-state', 'error');
- statusNode.style.opacity = 1;
- }, 250);
-
- callback(status);
- });
- }
- }, {
- key: '_showEditor',
- value: function _showEditor() {
-
- SlimPopover.show();
-
- this._imageEditor.show();
- }
- }, {
- key: '_hideEditor',
- value: function _hideEditor() {
-
- this._imageEditor.hide();
-
- setTimeout(function () {
-
- SlimPopover.hide();
- }, 250);
- }
-
- /**
- * Animations
- */
-
- }, {
- key: '_showPreview',
- value: function _showPreview(intro, callback) {
-
- snabbt(intro, {
-
- fromPosition: [0, 50, 0],
- position: [0, 0, 0],
-
- fromScale: [1.5, 1.5],
- scale: [1, 1],
-
- fromOpacity: 0,
- opacity: 1,
-
- easing: 'spring',
- springConstant: .3,
- springDeceleration: .7,
-
- complete: function complete() {
-
- resetTransforms(intro);
-
- if (callback) {
- callback();
- }
- }
-
- });
- }
- }, {
- key: '_hideResult',
- value: function _hideResult(callback) {
-
- var intro = this._getIntro();
- if (!intro) {
- return;
- }
-
- snabbt(intro, {
-
- fromScale: [1, 1],
- scale: [.5, .5],
-
- fromOpacity: 1,
- opacity: 0,
-
- easing: 'spring',
- springConstant: .3,
- springDeceleration: .75,
-
- complete: function complete() {
- resetTransforms(intro);
- if (callback) {
- callback();
- }
- }
-
- });
- }
- }, {
- key: '_showButtons',
- value: function _showButtons(callback) {
-
- this._btnGroup.style.display = '';
-
- // setup animation
- var animation = {
- fromScale: [.5, .5],
- scale: [1, 1],
- fromPosition: [0, 10, 0],
- position: [0, 0, 0],
- fromOpacity: 0,
- opacity: 1,
- complete: function complete() {
- resetTransforms(this);
- },
- allDone: function allDone() {
- if (callback) {
- callback();
- }
- }
- };
-
- // don't animate when detached
- if (this.isDetached()) {
- animation.duration = 1;
- } else {
- animation.delay = function (i) {
- return 250 + i * 50;
- };
- animation.easing = 'spring';
- animation.springConstant = .3;
- animation.springDeceleration = .85;
- }
-
- snabbt(this._btnGroup.childNodes, animation);
- }
- }, {
- key: '_hideButtons',
- value: function _hideButtons(callback) {
- var _this25 = this;
-
- var animation = {
- fromScale: [1, 1],
- scale: [.85, .85],
- fromOpacity: 1,
- opacity: 0,
- allDone: function allDone() {
- _this25._btnGroup.style.display = 'none';
- if (callback) {
- callback();
- }
- }
- };
-
- // don't animate when detached
- if (this.isDetached()) {
- animation.duration = 1;
- } else {
- animation.easing = 'spring';
- animation.springConstant = .3;
- animation.springDeceleration = .75;
- }
-
- // go hide the buttons
- snabbt(this._btnGroup.childNodes, animation);
- }
- }, {
- key: '_hideStatus',
- value: function _hideStatus() {
-
- var statusNode = this._element.querySelector('.slim-upload-status');
- statusNode.style.opacity = 0;
- }
- }, {
- key: '_doEdit',
- value: function _doEdit() {
- var _this26 = this;
-
- // if no input data available, can't edit anything
- if (!this._data.input.image) {
- return;
- }
-
- // now in editor mode
- this._addState('editor');
-
- // create editor (if not already created)
- if (!this._imageEditor) {
- this._appendEditor();
- }
-
- // append to popover
- SlimPopover.inner = this._imageEditor.element;
-
- // read the data
- this._imageEditor.open(
-
- // send copy of canvas to the editor
- cloneCanvas(this._data.input.image),
-
- // determine ratio
- this._options.ratio === 'free' ? null : this._ratio,
-
- // the initial crop to show
- this._data.actions.crop,
-
- // the initial rotation of the image
- this._data.actions.rotation,
-
- // handle editor load
- function () {
-
- _this26._showEditor();
-
- _this26._hideButtons();
-
- _this26._hideStatus();
- });
- }
- }, {
- key: '_doRemove',
- value: function _doRemove() {
- var _this27 = this;
-
- // cannot remove when is only one image
- if (this._isImageOnly()) {
- return;
- }
-
- this._clearState();
- this._addState('empty');
-
- this._hasInitialImage = false;
- this._imageHopper.enabled = true;
-
- if (this._isRequired) {
- this._input.required = true;
- }
-
- var out = this._getOutro();
- if (out) {
- out.style.opacity = '0';
- }
-
- // get public available clone of data
- var data = this.data;
-
- // now reset all data
- this._resetData();
-
- setTimeout(function () {
-
- if (_this27._isBeingDestroyed) {
- return;
- }
-
- _this27._hideButtons(function () {
-
- _this27._toggleButton('upload', true);
- });
-
- _this27._hideStatus();
-
- _this27._hideResult();
-
- _this27._options.didRemove.apply(_this27, [data]);
- }, this.isDetached() ? 0 : 250);
-
- return data;
- }
- }, {
- key: '_doUpload',
- value: function _doUpload(callback) {
- var _this28 = this;
-
- // if no input data available, can't upload anything
- if (!this._data.input.image) {
- return;
- }
-
- this._addState('upload');
- this._startProgress();
-
- this._hideButtons(function () {
-
- // block upload button
- _this28._toggleButton('upload', false);
-
- _this28._save(function (err, data, res) {
-
- _this28._removeState('upload');
- _this28._stopProgress();
-
- if (callback) {
- callback.apply(_this28, [err, data, res]);
- }
-
- if (err) {
- _this28._toggleButton('upload', true);
- }
-
- _this28._showButtons();
- });
- });
- }
- }, {
- key: '_doDownload',
- value: function _doDownload() {
-
- var image = this._data.output.image;
- if (!image) {
- return;
- }
-
- downloadCanvas(image, this._data.input.name, this._data.input.type);
- }
- }, {
- key: '_doDestroy',
- value: function _doDestroy() {
- var _this29 = this;
-
- // set destroy flag to halt any running functionality
- this._isBeingDestroyed = true;
-
- // this removes the image hopper if it's attached
- if (this._imageHopper) {
- HopperEvents.forEach(function (e) {
- _this29._imageHopper.element.removeEventListener(e, _this29);
- });
- this._imageHopper.destroy();
- }
-
- // this block removes the image editor
- if (this._imageEditor) {
- ImageEditorEvents.forEach(function (e) {
- _this29._imageEditor.element.removeEventListener(e, _this29);
- });
- this._imageEditor.destroy();
- }
-
- // remove button event listeners
- nodeListToArray(this._btnGroup.children).forEach(function (btn) {
- btn.removeEventListener('click', _this29);
- });
-
- // stop listening to input
- this._input.removeEventListener('change', this);
-
- // detect if was wrapped, if so, remove wrapping (needs to have parent node)
- if (this._element !== this._originalElement && this._element.parentNode) {
- this._element.parentNode.replaceChild(this._originalElement, this._element);
- }
-
- // restore HTML of original element
- this._originalElement.innerHTML = this._originalElementInner;
-
- // get current attributes and remove all, then add original attributes
- function matchesAttributeInList(a, attributes) {
- return attributes.filter(function (attr) {
- return a.name === attr.name && a.value === attr.value;
- }).length !== 0;
- }
-
- var attributes = getElementAttributes(this._originalElement);
- attributes.forEach(function (attribute) {
-
- // if attribute is contained in original element attribute list and is the same, don't remove
- if (matchesAttributeInList(attribute, _this29._originalElementAttributes)) {
- return;
- }
-
- // else remove
- _this29._originalElement.removeAttribute(attribute.name);
- });
-
- this._originalElementAttributes.forEach(function (attribute) {
-
- // attribute was never removed
- if (matchesAttributeInList(attribute, attributes)) {
- return;
- }
-
- // add attribute
- _this29._originalElement.setAttribute(attribute.name, attribute.value);
- });
-
- // now destroyed this counter so the total Slim count can be lowered
- SlimCount = Math.max(0, SlimCount - 1);
-
- // if slim count has reached 0 it's time to clean up the popover
- if (SlimPopover && SlimCount === 0) {
- SlimPopover.destroy();
- SlimPopover = null;
- }
- }
- }, {
- key: 'dataBase64',
-
-
- /**
- * Public API
- */
- // properties
- get: function get() {
- return flattenData(this._data, this._options.post, this._options.jpegCompression);
- }
- }, {
- key: 'data',
- get: function get() {
- return cloneData(this._data);
- }
- }, {
- key: 'element',
- get: function get() {
- return this._element;
- }
- }, {
- key: 'size',
- set: function set(dimensions) {
-
- if (!dimensions || !dimensions.width || !dimensions.height) {
- return;
- }
- this._options.size = clone(dimensions);
- this._data.actions.size = clone(dimensions);
- }
- }, {
- key: 'ratio',
- set: function set(ratio) {
-
- if (!ratio || typeof ratio !== 'string') {
- return;
- }
-
- this._options.ratio = ratio;
-
- if (this._isFixedRatio()) {
- var parts = intSplit(this._options.ratio, ':');
- this._ratio = parts[1] / parts[0];
- this._scaleDropArea(this._ratio);
- }
- }
- }], [{
- key: 'options',
- value: function options() {
-
- var defaults = {
-
- // edit button is enabled by default
- edit: true,
-
- // immidiately summons editor on load
- instantEdit: false,
-
- // ratio of crop by default is the same as input image ratio
- ratio: 'free',
-
- // dimensions to resize the resulting image to
- size: null,
-
- // initial crop settings for example: {x:0, y:0, width:100, height:100}
- crop: null,
-
- // post these values
- post: ['output', 'actions'],
-
- // call this service to submit cropped data
- service: null,
-
- // when service is set, and this is set to true, Soon will auto upload all crops (also auto crops)
- push: false,
-
- // default fallback name for field
- defaultInputName: 'slim[]',
-
- // minimum size of cropped area object with width and height property
- minSize: {
- width: 100,
- height: 100
- },
-
- // maximum file size in MB to upload
- maxFileSize: null,
-
- // compression of JPEG (between 0 and 100)
- jpegCompression: null,
-
- // render download link
- download: false,
-
- // save initially loaded image
- saveInitialImage: false,
-
- // the type to force (jpe|jpg|jpeg or png)
- forceType: false,
-
- // label HTML to show inside drop area
- label: '<p>Drop your image here</p>',
-
- // error messages
- statusFileType: '<p>Invalid file type, expects: $0.</p>',
- statusFileSize: '<p>File is too big, maximum file size: $0 MB.</p>',
- statusNoSupport: '<p>Your browser does not support image cropping.</p>',
- statusImageTooSmall: '<p>Image is too small, minimum size is: $0 pixels.</p>',
- statusContentLength: '<span class="slim-upload-status-icon"></span> The file is probably too big',
- statusUnknownResponse: '<span class="slim-upload-status-icon"></span> An unknown error occurred',
- statusUploadSuccess: '<span class="slim-upload-status-icon"></span> Saved',
-
- // callback methods
- didInit: function didInit(data) {},
- didLoad: function didLoad(file, image, meta) {
- return true;
- },
- didSave: function didSave(data) {},
- didUpload: function didUpload(err, data, res) {},
- didRemove: function didRemove(data) {},
- didTransform: function didTransform(data) {},
-
- willTransform: function willTransform(data, cb) {
- cb(data);
- },
- willSave: function willSave(data, cb) {
- cb(data);
- },
- willRemove: function willRemove(data, cb) {
- cb();
- },
- willRequest: function willRequest(xhr) {}
-
- };
-
- // add default button labels
- SlimButtons.concat(ImageEditor.Buttons).forEach(function (btn) {
- var capitalized = capitalizeFirstLetter(btn);
- defaults['button' + capitalized + 'ClassName'] = null;
- defaults['button' + capitalized + 'Label'] = capitalized;
- defaults['button' + capitalized + 'Title'] = null;
- });
-
- return defaults;
- }
- }]);
-
- return Slim;
- }();
-
- /**
- * Slim Static Methods
- */
-
-
- (function () {
-
- var instances = [];
-
- var indexOfElement = function indexOfElement(element) {
- var i = 0;
- var l = instances.length;
- for (; i < l; i++) {
- if (instances[i].isAttachedTo(element)) {
- return i;
- }
- }
- return -1;
- };
-
- function getData(el, attribute) {
- return el.getAttribute('data-' + attribute);
- }
-
- function toAttribute(property) {
- return property.replace(/([a-z](?=[A-Z]))/g, '$1-').toLowerCase();
- }
-
- function toLabel(v) {
- // if value set, use as label
- if (v) {
- return '<p>' + v + '</p>';
- }
-
- // else use default text
- return null;
- }
-
- function toFunctionReference(name) {
- var ref = window;
- var levels = name.split('.');
- levels.forEach(function (level, index) {
- ref = ref[levels[index]];
- });
- return ref;
- }
-
- var passThrough = function passThrough(e, v) {
- return v;
- };
- var defaultFalse = function defaultFalse(e, v) {
- return v === 'true';
- };
- var defaultTrue = function defaultTrue(e, v) {
- return v ? v === 'true' : true;
- };
- var defaultLabel = function defaultLabel(e, v) {
- return toLabel(v);
- };
- var defaultFunction = function defaultFunction(e, v) {
- return v ? toFunctionReference(v) : null;
- };
- var defaultSize = function defaultSize(e, v) {
- if (!v) {
- return null;
- }
- var parts = intSplit(v, ',');
- return {
- width: parts[0],
- height: parts[1]
- };
- };
-
- var toFloat = function toFloat(e, v) {
- if (!v) {
- return null;
- }
- return parseFloat(v);
- };
-
- var toInt = function toInt(e, v) {
- if (!v) {
- return null;
- }
- return parseInt(v, 10);
- };
-
- var toRect = function toRect(e, v) {
- if (!v) {
- return null;
- }
- var obj = {};
- v.split(',').map(function (p) {
- return parseInt(p, 10);
- }).forEach(function (v, i) {
- obj[Rect[i]] = v;
- });
- return obj;
- };
-
- var defaults = {
-
- // is user allowed to download the cropped image?
- 'download': defaultFalse,
-
- // is user allowed to edit the cropped image?
- 'edit': defaultTrue,
-
- // open editor immidiately on file drop
- 'instantEdit': defaultFalse,
-
- // minimum crop size in pixels of original image
- 'minSize': defaultSize,
-
- // the final size of the output image
- 'size': defaultSize,
-
- // url to post to
- 'service': function service(e, v) {
- return typeof v === 'undefined' ? null : v;
- },
-
- // set auto push mode
- 'push': defaultFalse,
-
- // set crop rect
- 'crop': toRect,
-
- // what to post
- 'post': function post(e, v) {
- if (!v) {
- return null;
- }
- return v.split(',').map(function (item) {
- return item.trim();
- });
- },
-
- // default input name
- 'defaultInputName': passThrough,
-
- // the ratio of the crop
- 'ratio': function ratio(e, v) {
- if (!v) {
- return null;
- }
- return v;
- },
-
- // maximum file size
- 'maxFileSize': toFloat,
-
- // jpeg compression
- 'jpegCompression': toInt,
-
- // sets file type to force output to
- 'forceType': defaultFalse,
-
- // bool determining if initial image should be saved
- 'saveInitialImage': defaultFalse,
-
- // default labels
- 'label': defaultLabel
-
- };
-
- // labels
- ['FileSize', 'FileType', 'NoSupport', 'ImageTooSmall'].forEach(function (status) {
- defaults['status' + status] = defaultLabel;
- });
-
- // status
- ['ContentLength', 'UnknownResponse', 'UploadSuccess'].forEach(function (status) {
- defaults['status' + status] = passThrough;
- });
-
- // the did callbacks
- ['Init', 'Load', 'Save', 'Upload', 'Remove', 'Transform'].forEach(function (cb) {
- defaults['did' + cb] = defaultFunction;
- });
-
- // the will callbacks
- ['Transform', 'Save', 'Remove', 'Request'].forEach(function (cb) {
- defaults['will' + cb] = defaultFunction;
- });
-
- // button defaults
- var buttonOptions = ['ClassName', 'Label', 'Title'];
- SlimButtons.concat(ImageEditor.Buttons).forEach(function (btn) {
- var capitalized = capitalizeFirstLetter(btn);
- buttonOptions.forEach(function (opt) {
- defaults['button' + capitalized + opt] = passThrough;
- });
- });
-
- Slim.supported = function () {
- return typeof window.FileReader !== 'undefined';
- }();
-
- Slim.parse = function (context) {
- var elements;
- var element;
- var i;
- var croppers = [];
-
- // find all crop elements and bind Crop behavior
- elements = context.querySelectorAll('.slim:not([data-state])');
- i = elements.length;
-
- while (i--) {
- element = elements[i];
- croppers.push(Slim.create(element, Slim.getOptionsFromAttributes(element)));
- }
-
- return croppers;
- };
-
- Slim.getOptionsFromAttributes = function (element) {
- var options = {};
- for (var prop in defaults) {
- if (!defaults.hasOwnProperty(prop)) {
- continue;
- }
- var _value = defaults[prop](element, getData(element, toAttribute(prop)));
- options[prop] = _value === null ? clone(Slim.options()[prop]) : _value;
- }
- return options;
- };
-
- Slim.find = function (element) {
- var result = instances.filter(function (instance) {
- return instance.isAttachedTo(element);
- });
- return result ? result[0] : null;
- };
-
- Slim.create = function (element, options) {
-
- // if already in array, can't create another slim
- if (Slim.find(element)) {
- return;
- }
-
- // if no options supplied, try to get the options from the element
- if (!options) {
- options = Slim.getOptionsFromAttributes(element);
- }
-
- // instance
- var slim = new Slim(element, options);
-
- // add new slim
- instances.push(slim);
-
- // return the slim instance
- return slim;
- };
-
- Slim.destroy = function (element) {
- var index = indexOfElement(element);
-
- if (index < 0) {
- return false;
- }
-
- instances[index].destroy();
- instances[index] = null;
- instances.splice(index, 1);
- return true;
- };
- })();
- return Slim;
- }());
-
- // helpers
- function argsToArray(args) {
- return Array.prototype.slice.call(args);
- }
-
- function isConstructor(parameters) {
- return typeof parameters[0] === 'object' || parameters.length === 0;
- }
-
- function isGetter(slim, method, parameters) {
- var descriptor = Object.getOwnPropertyDescriptor(Slim.prototype, method);
- return descriptor ? typeof descriptor.get !== 'undefined' : false;
- }
-
- function isSetter(slim, method, parameters) {
- var descriptor = Object.getOwnPropertyDescriptor(Slim.prototype, method);
- return descriptor ? typeof descriptor.set !== 'undefined' : false;
- }
-
- function isMethod(slim, method) {
- return typeof slim[method] === 'function';
- }
-
- // plugin
- $.fn['slim'] = function() {
-
- // get arguments as array
- var parameters = argsToArray(arguments);
-
- // is method
- if (isConstructor(parameters)) {
- return this.each(function(){
- Slim.create(this, parameters[0]);
- });
- }
- else {
- var method = parameters.shift();
- switch (method) {
- case 'supported':
- return Slim.supported;
- case 'destroy':
- return this.each(function(){
- Slim.destroy(this);
- });
- case 'parse':
- return this.each(function(){
- Slim.parse(this);
- });
- default:
- var results = [];
- this.each(function(){
-
- var slim = Slim.find(this);
- if (!slim) {
- return;
- }
-
- if (isMethod(slim, method)) {
- results.push(slim[method].apply(slim, parameters));
- }
-
- else if (isGetter(slim, method)) {
- results.push(slim[method]);
- }
-
- else if (isSetter(slim, method)) {
- results.push(slim[method] = parameters[0]);
- }
-
- });
- return results;
- }
- }
-
- };
-
- }(window.jQuery));
|