/*! * Cropper v2.3.4 * https://github.com/fengyuanchen/cropper * * Copyright (c) 2014-2016 Fengyuan Chen and contributors * Released under the MIT license * * Date: 2016-09-03T05:50:45.412Z */ (function (factory) { if (typeof define === 'function' && define.amd) { // AMD. Register as anonymous module. define(['jquery'], factory); } else if (typeof exports === 'object') { // Node / CommonJS factory(require('jquery')); } else { // Browser globals. factory(jQuery); } })(function ($) { 'use strict'; // Globals var $window = $(window); var $document = $(document); var location = window.location; var navigator = window.navigator; var ArrayBuffer = window.ArrayBuffer; var Uint8Array = window.Uint8Array; var DataView = window.DataView; var btoa = window.btoa; // Constants var NAMESPACE = 'cropper'; // Classes var CLASS_MODAL = 'cropper-modal'; var CLASS_HIDE = 'cropper-hide'; var CLASS_HIDDEN = 'cropper-hidden'; var CLASS_INVISIBLE = 'cropper-invisible'; var CLASS_MOVE = 'cropper-move'; var CLASS_CROP = 'cropper-crop'; var CLASS_DISABLED = 'cropper-disabled'; var CLASS_BG = 'cropper-bg'; // Events var EVENT_MOUSE_DOWN = 'mousedown touchstart pointerdown MSPointerDown'; var EVENT_MOUSE_MOVE = 'mousemove touchmove pointermove MSPointerMove'; var EVENT_MOUSE_UP = 'mouseup touchend touchcancel pointerup pointercancel MSPointerUp MSPointerCancel'; var EVENT_WHEEL = 'wheel mousewheel DOMMouseScroll'; var EVENT_DBLCLICK = 'dblclick'; var EVENT_LOAD = 'load.' + NAMESPACE; var EVENT_ERROR = 'error.' + NAMESPACE; var EVENT_RESIZE = 'resize.' + NAMESPACE; // Bind to window with namespace var EVENT_BUILD = 'build.' + NAMESPACE; var EVENT_BUILT = 'built.' + NAMESPACE; var EVENT_CROP_START = 'cropstart.' + NAMESPACE; var EVENT_CROP_MOVE = 'cropmove.' + NAMESPACE; var EVENT_CROP_END = 'cropend.' + NAMESPACE; var EVENT_CROP = 'crop.' + NAMESPACE; var EVENT_ZOOM = 'zoom.' + NAMESPACE; // RegExps var REGEXP_ACTIONS = /^(e|w|s|n|se|sw|ne|nw|all|crop|move|zoom)$/; var REGEXP_DATA_URL = /^data:/; var REGEXP_DATA_URL_HEAD = /^data:([^;]+);base64,/; var REGEXP_DATA_URL_JPEG = /^data:image\/jpeg.*;base64,/; // Data keys var DATA_PREVIEW = 'preview'; var DATA_ACTION = 'action'; // Actions var ACTION_EAST = 'e'; var ACTION_WEST = 'w'; var ACTION_SOUTH = 's'; var ACTION_NORTH = 'n'; var ACTION_SOUTH_EAST = 'se'; var ACTION_SOUTH_WEST = 'sw'; var ACTION_NORTH_EAST = 'ne'; var ACTION_NORTH_WEST = 'nw'; var ACTION_ALL = 'all'; var ACTION_CROP = 'crop'; var ACTION_MOVE = 'move'; var ACTION_ZOOM = 'zoom'; var ACTION_NONE = 'none'; // Supports var SUPPORT_CANVAS = $.isFunction($('')[0].getContext); var IS_SAFARI_OR_UIWEBVIEW = navigator && /(Macintosh|iPhone|iPod|iPad).*AppleWebKit/i.test(navigator.userAgent); // Maths var num = Number; var min = Math.min; var max = Math.max; var abs = Math.abs; var sin = Math.sin; var cos = Math.cos; var sqrt = Math.sqrt; var round = Math.round; var floor = Math.floor; // Utilities var fromCharCode = String.fromCharCode; function isNumber(n) { return typeof n === 'number' && !isNaN(n); } function isUndefined(n) { return typeof n === 'undefined'; } function toArray(obj, offset) { var args = []; // This is necessary for IE8 if (isNumber(offset)) { args.push(offset); } return args.slice.apply(obj, args); } // Custom proxy to avoid jQuery's guid function proxy(fn, context) { var args = toArray(arguments, 2); return function () { return fn.apply(context, args.concat(toArray(arguments))); }; } function isCrossOriginURL(url) { var parts = url.match(/^(https?:)\/\/([^\:\/\?#]+):?(\d*)/i); return parts && ( parts[1] !== location.protocol || parts[2] !== location.hostname || parts[3] !== location.port ); } function addTimestamp(url) { var timestamp = 'timestamp=' + (new Date()).getTime(); return (url + (url.indexOf('?') === -1 ? '?' : '&') + timestamp); } function getCrossOrigin(crossOrigin) { return crossOrigin ? ' crossOrigin="' + crossOrigin + '"' : ''; } function getImageSize(image, callback) { var newImage; // Modern browsers (ignore Safari, #120 & #509) if (image.naturalWidth && !IS_SAFARI_OR_UIWEBVIEW) { return callback(image.naturalWidth, image.naturalHeight); } // IE8: Don't use `new Image()` here (#319) newImage = document.createElement('img'); newImage.onload = function () { callback(this.width, this.height); }; newImage.src = image.src; } function getTransform(options) { var transforms = []; var rotate = options.rotate; var scaleX = options.scaleX; var scaleY = options.scaleY; // Rotate should come first before scale to match orientation transform if (isNumber(rotate) && rotate !== 0) { transforms.push('rotate(' + rotate + 'deg)'); } if (isNumber(scaleX) && scaleX !== 1) { transforms.push('scaleX(' + scaleX + ')'); } if (isNumber(scaleY) && scaleY !== 1) { transforms.push('scaleY(' + scaleY + ')'); } return transforms.length ? transforms.join(' ') : 'none'; } function getRotatedSizes(data, isReversed) { var deg = abs(data.degree) % 180; var arc = (deg > 90 ? (180 - deg) : deg) * Math.PI / 180; var sinArc = sin(arc); var cosArc = cos(arc); var width = data.width; var height = data.height; var aspectRatio = data.aspectRatio; var newWidth; var newHeight; if (!isReversed) { newWidth = width * cosArc + height * sinArc; newHeight = width * sinArc + height * cosArc; } else { newWidth = width / (cosArc + sinArc / aspectRatio); newHeight = newWidth / aspectRatio; } return { width: newWidth, height: newHeight }; } function getSourceCanvas(image, data) { var canvas = $('')[0]; var context = canvas.getContext('2d'); var dstX = 0; var dstY = 0; var dstWidth = data.naturalWidth; var dstHeight = data.naturalHeight; var rotate = data.rotate; var scaleX = data.scaleX; var scaleY = data.scaleY; var scalable = isNumber(scaleX) && isNumber(scaleY) && (scaleX !== 1 || scaleY !== 1); var rotatable = isNumber(rotate) && rotate !== 0; var advanced = rotatable || scalable; var canvasWidth = dstWidth * abs(scaleX || 1); var canvasHeight = dstHeight * abs(scaleY || 1); var translateX; var translateY; var rotated; if (scalable) { translateX = canvasWidth / 2; translateY = canvasHeight / 2; } if (rotatable) { rotated = getRotatedSizes({ width: canvasWidth, height: canvasHeight, degree: rotate }); canvasWidth = rotated.width; canvasHeight = rotated.height; translateX = canvasWidth / 2; translateY = canvasHeight / 2; } canvas.width = canvasWidth; canvas.height = canvasHeight; if (advanced) { dstX = -dstWidth / 2; dstY = -dstHeight / 2; context.save(); context.translate(translateX, translateY); } // Rotate should come first before scale as in the "getTransform" function if (rotatable) { context.rotate(rotate * Math.PI / 180); } if (scalable) { context.scale(scaleX, scaleY); } context.drawImage(image, floor(dstX), floor(dstY), floor(dstWidth), floor(dstHeight)); if (advanced) { context.restore(); } return canvas; } function getTouchesCenter(touches) { var length = touches.length; var pageX = 0; var pageY = 0; if (length) { $.each(touches, function (i, touch) { pageX += touch.pageX; pageY += touch.pageY; }); pageX /= length; pageY /= length; } return { pageX: pageX, pageY: pageY }; } function getStringFromCharCode(dataView, start, length) { var str = ''; var i; for (i = start, length += start; i < length; i++) { str += fromCharCode(dataView.getUint8(i)); } return str; } function getOrientation(arrayBuffer) { var dataView = new DataView(arrayBuffer); var length = dataView.byteLength; var orientation; var exifIDCode; var tiffOffset; var firstIFDOffset; var littleEndian; var endianness; var app1Start; var ifdStart; var offset; var i; // Only handle JPEG image (start by 0xFFD8) if (dataView.getUint8(0) === 0xFF && dataView.getUint8(1) === 0xD8) { offset = 2; while (offset < length) { if (dataView.getUint8(offset) === 0xFF && dataView.getUint8(offset + 1) === 0xE1) { app1Start = offset; break; } offset++; } } if (app1Start) { exifIDCode = app1Start + 4; tiffOffset = app1Start + 10; if (getStringFromCharCode(dataView, exifIDCode, 4) === 'Exif') { endianness = dataView.getUint16(tiffOffset); littleEndian = endianness === 0x4949; if (littleEndian || endianness === 0x4D4D /* bigEndian */) { if (dataView.getUint16(tiffOffset + 2, littleEndian) === 0x002A) { firstIFDOffset = dataView.getUint32(tiffOffset + 4, littleEndian); if (firstIFDOffset >= 0x00000008) { ifdStart = tiffOffset + firstIFDOffset; } } } } } if (ifdStart) { length = dataView.getUint16(ifdStart, littleEndian); for (i = 0; i < length; i++) { offset = ifdStart + i * 12 + 2; if (dataView.getUint16(offset, littleEndian) === 0x0112 /* Orientation */) { // 8 is the offset of the current tag's value offset += 8; // Get the original orientation value orientation = dataView.getUint16(offset, littleEndian); // Override the orientation with its default value for Safari (#120) if (IS_SAFARI_OR_UIWEBVIEW) { dataView.setUint16(offset, 1, littleEndian); } break; } } } return orientation; } function dataURLToArrayBuffer(dataURL) { var base64 = dataURL.replace(REGEXP_DATA_URL_HEAD, ''); var binary = atob(base64); var length = binary.length; var arrayBuffer = new ArrayBuffer(length); var dataView = new Uint8Array(arrayBuffer); var i; for (i = 0; i < length; i++) { dataView[i] = binary.charCodeAt(i); } return arrayBuffer; } // Only available for JPEG image function arrayBufferToDataURL(arrayBuffer) { var dataView = new Uint8Array(arrayBuffer); var length = dataView.length; var base64 = ''; var i; for (i = 0; i < length; i++) { base64 += fromCharCode(dataView[i]); } return 'data:image/jpeg;base64,' + btoa(base64); } function Cropper(element, options) { this.$element = $(element); this.options = $.extend({}, Cropper.DEFAULTS, $.isPlainObject(options) && options); this.isLoaded = false; this.isBuilt = false; this.isCompleted = false; this.isRotated = false; this.isCropped = false; this.isDisabled = false; this.isReplaced = false; this.isLimited = false; this.wheeling = false; this.isImg = false; this.originalUrl = ''; this.canvas = null; this.cropBox = null; this.init(); } Cropper.prototype = { constructor: Cropper, init: function () { var $this = this.$element; var url; if ($this.is('img')) { this.isImg = true; // Should use `$.fn.attr` here. e.g.: "img/picture.jpg" this.originalUrl = url = $this.attr('src'); // Stop when it's a blank image if (!url) { return; } // Should use `$.fn.prop` here. e.g.: "http://example.com/img/picture.jpg" url = $this.prop('src'); } else if ($this.is('canvas') && SUPPORT_CANVAS) { url = $this[0].toDataURL(); } this.load(url); }, // A shortcut for triggering custom events trigger: function (type, data) { var e = $.Event(type, data); this.$element.trigger(e); return e; }, load: function (url) { var options = this.options; var $this = this.$element; var read; var xhr; if (!url) { return; } // Trigger build event first $this.one(EVENT_BUILD, options.build); if (this.trigger(EVENT_BUILD).isDefaultPrevented()) { return; } this.url = url; this.image = {}; if (!options.checkOrientation || !ArrayBuffer) { return this.clone(); } read = $.proxy(this.read, this); // XMLHttpRequest disallows to open a Data URL in some browsers like IE11 and Safari if (REGEXP_DATA_URL.test(url)) { return REGEXP_DATA_URL_JPEG.test(url) ? read(dataURLToArrayBuffer(url)) : this.clone(); } xhr = new XMLHttpRequest(); xhr.onerror = xhr.onabort = $.proxy(function () { this.clone(); }, this); xhr.onload = function () { read(this.response); }; if (options.checkCrossOrigin && isCrossOriginURL(url) && $this.prop('crossOrigin')) { url = addTimestamp(url); } xhr.open('get', url); xhr.responseType = 'arraybuffer'; xhr.send(); }, read: function (arrayBuffer) { var options = this.options; var orientation = getOrientation(arrayBuffer); var image = this.image; var rotate = 0; var scaleX = 1; var scaleY = 1; if (orientation > 1) { this.url = arrayBufferToDataURL(arrayBuffer); switch (orientation) { // flip horizontal case 2: scaleX = -1; break; // rotate left 180° case 3: rotate = -180; break; // flip vertical case 4: scaleY = -1; break; // flip vertical + rotate right 90° case 5: rotate = 90; scaleY = -1; break; // rotate right 90° case 6: rotate = 90; break; // flip horizontal + rotate right 90° case 7: rotate = 90; scaleX = -1; break; // rotate left 90° case 8: rotate = -90; break; } } if (options.rotatable) { image.rotate = rotate; } if (options.scalable) { image.scaleX = scaleX; image.scaleY = scaleY; } this.clone(); }, clone: function () { var options = this.options; var $this = this.$element; var url = this.url; var crossOrigin = ''; var crossOriginUrl; var $clone; if (options.checkCrossOrigin && isCrossOriginURL(url)) { crossOrigin = $this.prop('crossOrigin'); if (crossOrigin) { crossOriginUrl = url; } else { crossOrigin = 'anonymous'; // Bust cache (#148) when there is not a "crossOrigin" property crossOriginUrl = addTimestamp(url); } } this.crossOrigin = crossOrigin; this.crossOriginUrl = crossOriginUrl; this.$clone = $clone = $(''); if (this.isImg) { if ($this[0].complete) { this.start(); } else { $this.one(EVENT_LOAD, $.proxy(this.start, this)); } } else { $clone. one(EVENT_LOAD, $.proxy(this.start, this)). one(EVENT_ERROR, $.proxy(this.stop, this)). addClass(CLASS_HIDE). insertAfter($this); } }, start: function () { var $image = this.$element; var $clone = this.$clone; if (!this.isImg) { $clone.off(EVENT_ERROR, this.stop); $image = $clone; } getImageSize($image[0], $.proxy(function (naturalWidth, naturalHeight) { $.extend(this.image, { naturalWidth: naturalWidth, naturalHeight: naturalHeight, aspectRatio: naturalWidth / naturalHeight }); this.isLoaded = true; this.build(); }, this)); }, stop: function () { this.$clone.remove(); this.$clone = null; }, build: function () { var options = this.options; var $this = this.$element; var $clone = this.$clone; var $cropper; var $cropBox; var $face; if (!this.isLoaded) { return; } // Unbuild first when replace if (this.isBuilt) { this.unbuild(); } // Create cropper elements this.$container = $this.parent(); this.$cropper = $cropper = $(Cropper.TEMPLATE); this.$canvas = $cropper.find('.cropper-canvas').append($clone); this.$dragBox = $cropper.find('.cropper-drag-box'); this.$cropBox = $cropBox = $cropper.find('.cropper-crop-box'); this.$viewBox = $cropper.find('.cropper-view-box'); this.$face = $face = $cropBox.find('.cropper-face'); // Hide the original image $this.addClass(CLASS_HIDDEN).after($cropper); // Show the clone image if is hidden if (!this.isImg) { $clone.removeClass(CLASS_HIDE); } this.initPreview(); this.bind(); options.aspectRatio = max(0, options.aspectRatio) || NaN; options.viewMode = max(0, min(3, round(options.viewMode))) || 0; if (options.autoCrop) { this.isCropped = true; if (options.modal) { this.$dragBox.addClass(CLASS_MODAL); } } else { $cropBox.addClass(CLASS_HIDDEN); } if (!options.guides) { $cropBox.find('.cropper-dashed').addClass(CLASS_HIDDEN); } if (!options.center) { $cropBox.find('.cropper-center').addClass(CLASS_HIDDEN); } if (options.cropBoxMovable) { $face.addClass(CLASS_MOVE).data(DATA_ACTION, ACTION_ALL); } if (!options.highlight) { $face.addClass(CLASS_INVISIBLE); } if (options.background) { $cropper.addClass(CLASS_BG); } if (!options.cropBoxResizable) { $cropBox.find('.cropper-line, .cropper-point').addClass(CLASS_HIDDEN); } this.setDragMode(options.dragMode); this.render(); this.isBuilt = true; this.setData(options.data); $this.one(EVENT_BUILT, options.built); // Trigger the built event asynchronously to keep `data('cropper')` is defined this.completing = setTimeout($.proxy(function () { this.trigger(EVENT_BUILT); this.trigger(EVENT_CROP, this.getData()); this.isCompleted = true; }, this), 0); }, unbuild: function () { if (!this.isBuilt) { return; } if (!this.isCompleted) { clearTimeout(this.completing); } this.isBuilt = false; this.isCompleted = false; this.initialImage = null; // Clear `initialCanvas` is necessary when replace this.initialCanvas = null; this.initialCropBox = null; this.container = null; this.canvas = null; // Clear `cropBox` is necessary when replace this.cropBox = null; this.unbind(); this.resetPreview(); this.$preview = null; this.$viewBox = null; this.$cropBox = null; this.$dragBox = null; this.$canvas = null; this.$container = null; this.$cropper.remove(); this.$cropper = null; }, render: function () { this.initContainer(); this.initCanvas(); this.initCropBox(); this.renderCanvas(); if (this.isCropped) { this.renderCropBox(); } }, initContainer: function () { var options = this.options; var $this = this.$element; var $container = this.$container; var $cropper = this.$cropper; $cropper.addClass(CLASS_HIDDEN); $this.removeClass(CLASS_HIDDEN); $cropper.css((this.container = { width: max($container.width(), num(options.minContainerWidth) || 200), height: max($container.height(), num(options.minContainerHeight) || 100) })); $this.addClass(CLASS_HIDDEN); $cropper.removeClass(CLASS_HIDDEN); }, // Canvas (image wrapper) initCanvas: function () { var viewMode = this.options.viewMode; var container = this.container; var containerWidth = container.width; var containerHeight = container.height; var image = this.image; var imageNaturalWidth = image.naturalWidth; var imageNaturalHeight = image.naturalHeight; var is90Degree = abs(image.rotate) === 90; var naturalWidth = is90Degree ? imageNaturalHeight : imageNaturalWidth; var naturalHeight = is90Degree ? imageNaturalWidth : imageNaturalHeight; var aspectRatio = naturalWidth / naturalHeight; var canvasWidth = containerWidth; var canvasHeight = containerHeight; var canvas; if (containerHeight * aspectRatio > containerWidth) { if (viewMode === 3) { canvasWidth = containerHeight * aspectRatio; } else { canvasHeight = containerWidth / aspectRatio; } } else { if (viewMode === 3) { canvasHeight = containerWidth / aspectRatio; } else { canvasWidth = containerHeight * aspectRatio; } } canvas = { naturalWidth: naturalWidth, naturalHeight: naturalHeight, aspectRatio: aspectRatio, width: canvasWidth, height: canvasHeight }; canvas.oldLeft = canvas.left = (containerWidth - canvasWidth) / 2; canvas.oldTop = canvas.top = (containerHeight - canvasHeight) / 2; this.canvas = canvas; this.isLimited = (viewMode === 1 || viewMode === 2); this.limitCanvas(true, true); this.initialImage = $.extend({}, image); this.initialCanvas = $.extend({}, canvas); }, limitCanvas: function (isSizeLimited, isPositionLimited) { var options = this.options; var viewMode = options.viewMode; var container = this.container; var containerWidth = container.width; var containerHeight = container.height; var canvas = this.canvas; var aspectRatio = canvas.aspectRatio; var cropBox = this.cropBox; var isCropped = this.isCropped && cropBox; var minCanvasWidth; var minCanvasHeight; var newCanvasLeft; var newCanvasTop; if (isSizeLimited) { minCanvasWidth = num(options.minCanvasWidth) || 0; minCanvasHeight = num(options.minCanvasHeight) || 0; if (viewMode) { if (viewMode > 1) { minCanvasWidth = max(minCanvasWidth, containerWidth); minCanvasHeight = max(minCanvasHeight, containerHeight); if (viewMode === 3) { if (minCanvasHeight * aspectRatio > minCanvasWidth) { minCanvasWidth = minCanvasHeight * aspectRatio; } else { minCanvasHeight = minCanvasWidth / aspectRatio; } } } else { if (minCanvasWidth) { minCanvasWidth = max(minCanvasWidth, isCropped ? cropBox.width : 0); } else if (minCanvasHeight) { minCanvasHeight = max(minCanvasHeight, isCropped ? cropBox.height : 0); } else if (isCropped) { minCanvasWidth = cropBox.width; minCanvasHeight = cropBox.height; if (minCanvasHeight * aspectRatio > minCanvasWidth) { minCanvasWidth = minCanvasHeight * aspectRatio; } else { minCanvasHeight = minCanvasWidth / aspectRatio; } } } } if (minCanvasWidth && minCanvasHeight) { if (minCanvasHeight * aspectRatio > minCanvasWidth) { minCanvasHeight = minCanvasWidth / aspectRatio; } else { minCanvasWidth = minCanvasHeight * aspectRatio; } } else if (minCanvasWidth) { minCanvasHeight = minCanvasWidth / aspectRatio; } else if (minCanvasHeight) { minCanvasWidth = minCanvasHeight * aspectRatio; } canvas.minWidth = minCanvasWidth; canvas.minHeight = minCanvasHeight; canvas.maxWidth = Infinity; canvas.maxHeight = Infinity; } if (isPositionLimited) { if (viewMode) { newCanvasLeft = containerWidth - canvas.width; newCanvasTop = containerHeight - canvas.height; canvas.minLeft = min(0, newCanvasLeft); canvas.minTop = min(0, newCanvasTop); canvas.maxLeft = max(0, newCanvasLeft); canvas.maxTop = max(0, newCanvasTop); if (isCropped && this.isLimited) { canvas.minLeft = min( cropBox.left, cropBox.left + cropBox.width - canvas.width ); canvas.minTop = min( cropBox.top, cropBox.top + cropBox.height - canvas.height ); canvas.maxLeft = cropBox.left; canvas.maxTop = cropBox.top; if (viewMode === 2) { if (canvas.width >= containerWidth) { canvas.minLeft = min(0, newCanvasLeft); canvas.maxLeft = max(0, newCanvasLeft); } if (canvas.height >= containerHeight) { canvas.minTop = min(0, newCanvasTop); canvas.maxTop = max(0, newCanvasTop); } } } } else { canvas.minLeft = -canvas.width; canvas.minTop = -canvas.height; canvas.maxLeft = containerWidth; canvas.maxTop = containerHeight; } } }, renderCanvas: function (isChanged) { var canvas = this.canvas; var image = this.image; var rotate = image.rotate; var naturalWidth = image.naturalWidth; var naturalHeight = image.naturalHeight; var aspectRatio; var rotated; if (this.isRotated) { this.isRotated = false; // Computes rotated sizes with image sizes rotated = getRotatedSizes({ width: image.width, height: image.height, degree: rotate }); aspectRatio = rotated.width / rotated.height; if (aspectRatio !== canvas.aspectRatio) { canvas.left -= (rotated.width - canvas.width) / 2; canvas.top -= (rotated.height - canvas.height) / 2; canvas.width = rotated.width; canvas.height = rotated.height; canvas.aspectRatio = aspectRatio; canvas.naturalWidth = naturalWidth; canvas.naturalHeight = naturalHeight; // Computes rotated sizes with natural image sizes if (rotate % 180) { rotated = getRotatedSizes({ width: naturalWidth, height: naturalHeight, degree: rotate }); canvas.naturalWidth = rotated.width; canvas.naturalHeight = rotated.height; } this.limitCanvas(true, false); } } if (canvas.width > canvas.maxWidth || canvas.width < canvas.minWidth) { canvas.left = canvas.oldLeft; } if (canvas.height > canvas.maxHeight || canvas.height < canvas.minHeight) { canvas.top = canvas.oldTop; } canvas.width = min(max(canvas.width, canvas.minWidth), canvas.maxWidth); canvas.height = min(max(canvas.height, canvas.minHeight), canvas.maxHeight); this.limitCanvas(false, true); canvas.oldLeft = canvas.left = min(max(canvas.left, canvas.minLeft), canvas.maxLeft); canvas.oldTop = canvas.top = min(max(canvas.top, canvas.minTop), canvas.maxTop); this.$canvas.css({ width: canvas.width, height: canvas.height, left: canvas.left, top: canvas.top }); this.renderImage(); if (this.isCropped && this.isLimited) { this.limitCropBox(true, true); } if (isChanged) { this.output(); } }, renderImage: function (isChanged) { var canvas = this.canvas; var image = this.image; var reversed; if (image.rotate) { reversed = getRotatedSizes({ width: canvas.width, height: canvas.height, degree: image.rotate, aspectRatio: image.aspectRatio }, true); } $.extend(image, reversed ? { width: reversed.width, height: reversed.height, left: (canvas.width - reversed.width) / 2, top: (canvas.height - reversed.height) / 2 } : { width: canvas.width, height: canvas.height, left: 0, top: 0 }); this.$clone.css({ width: image.width, height: image.height, marginLeft: image.left, marginTop: image.top, transform: getTransform(image) }); if (isChanged) { this.output(); } }, initCropBox: function () { var options = this.options; var canvas = this.canvas; var aspectRatio = options.aspectRatio; var autoCropArea = num(options.autoCropArea) || 0.8; var cropBox = { width: canvas.width, height: canvas.height }; if (aspectRatio) { if (canvas.height * aspectRatio > canvas.width) { cropBox.height = cropBox.width / aspectRatio; } else { cropBox.width = cropBox.height * aspectRatio; } } this.cropBox = cropBox; this.limitCropBox(true, true); // Initialize auto crop area cropBox.width = min(max(cropBox.width, cropBox.minWidth), cropBox.maxWidth); cropBox.height = min(max(cropBox.height, cropBox.minHeight), cropBox.maxHeight); // The width of auto crop area must large than "minWidth", and the height too. (#164) cropBox.width = max(cropBox.minWidth, cropBox.width * autoCropArea); cropBox.height = max(cropBox.minHeight, cropBox.height * autoCropArea); cropBox.oldLeft = cropBox.left = canvas.left + (canvas.width - cropBox.width) / 2; cropBox.oldTop = cropBox.top = canvas.top + (canvas.height - cropBox.height) / 2; this.initialCropBox = $.extend({}, cropBox); }, limitCropBox: function (isSizeLimited, isPositionLimited) { var options = this.options; var aspectRatio = options.aspectRatio; var container = this.container; var containerWidth = container.width; var containerHeight = container.height; var canvas = this.canvas; var cropBox = this.cropBox; var isLimited = this.isLimited; var minCropBoxWidth; var minCropBoxHeight; var maxCropBoxWidth; var maxCropBoxHeight; if (isSizeLimited) { minCropBoxWidth = num(options.minCropBoxWidth) || 0; minCropBoxHeight = num(options.minCropBoxHeight) || 0; // The min/maxCropBoxWidth/Height must be less than containerWidth/Height minCropBoxWidth = min(minCropBoxWidth, containerWidth); minCropBoxHeight = min(minCropBoxHeight, containerHeight); maxCropBoxWidth = min(containerWidth, isLimited ? canvas.width : containerWidth); maxCropBoxHeight = min(containerHeight, isLimited ? canvas.height : containerHeight); if (aspectRatio) { if (minCropBoxWidth && minCropBoxHeight) { if (minCropBoxHeight * aspectRatio > minCropBoxWidth) { minCropBoxHeight = minCropBoxWidth / aspectRatio; } else { minCropBoxWidth = minCropBoxHeight * aspectRatio; } } else if (minCropBoxWidth) { minCropBoxHeight = minCropBoxWidth / aspectRatio; } else if (minCropBoxHeight) { minCropBoxWidth = minCropBoxHeight * aspectRatio; } if (maxCropBoxHeight * aspectRatio > maxCropBoxWidth) { maxCropBoxHeight = maxCropBoxWidth / aspectRatio; } else { maxCropBoxWidth = maxCropBoxHeight * aspectRatio; } } // The minWidth/Height must be less than maxWidth/Height cropBox.minWidth = min(minCropBoxWidth, maxCropBoxWidth); cropBox.minHeight = min(minCropBoxHeight, maxCropBoxHeight); cropBox.maxWidth = maxCropBoxWidth; cropBox.maxHeight = maxCropBoxHeight; } if (isPositionLimited) { if (isLimited) { cropBox.minLeft = max(0, canvas.left); cropBox.minTop = max(0, canvas.top); cropBox.maxLeft = min(containerWidth, canvas.left + canvas.width) - cropBox.width; cropBox.maxTop = min(containerHeight, canvas.top + canvas.height) - cropBox.height; } else { cropBox.minLeft = 0; cropBox.minTop = 0; cropBox.maxLeft = containerWidth - cropBox.width; cropBox.maxTop = containerHeight - cropBox.height; } } }, renderCropBox: function () { var options = this.options; var container = this.container; var containerWidth = container.width; var containerHeight = container.height; var cropBox = this.cropBox; if (cropBox.width > cropBox.maxWidth || cropBox.width < cropBox.minWidth) { cropBox.left = cropBox.oldLeft; } if (cropBox.height > cropBox.maxHeight || cropBox.height < cropBox.minHeight) { cropBox.top = cropBox.oldTop; } cropBox.width = min(max(cropBox.width, cropBox.minWidth), cropBox.maxWidth); cropBox.height = min(max(cropBox.height, cropBox.minHeight), cropBox.maxHeight); this.limitCropBox(false, true); cropBox.oldLeft = cropBox.left = min(max(cropBox.left, cropBox.minLeft), cropBox.maxLeft); cropBox.oldTop = cropBox.top = min(max(cropBox.top, cropBox.minTop), cropBox.maxTop); if (options.movable && options.cropBoxMovable) { // Turn to move the canvas when the crop box is equal to the container this.$face.data(DATA_ACTION, (cropBox.width === containerWidth && cropBox.height === containerHeight) ? ACTION_MOVE : ACTION_ALL); } this.$cropBox.css({ width: cropBox.width, height: cropBox.height, left: cropBox.left, top: cropBox.top }); if (this.isCropped && this.isLimited) { this.limitCanvas(true, true); } if (!this.isDisabled) { this.output(); } }, output: function () { this.preview(); if (this.isCompleted) { this.trigger(EVENT_CROP, this.getData()); } }, initPreview: function () { var crossOrigin = getCrossOrigin(this.crossOrigin); var url = crossOrigin ? this.crossOriginUrl : this.url; var $clone2; this.$preview = $(this.options.preview); this.$clone2 = $clone2 = $(''); this.$viewBox.html($clone2); this.$preview.each(function () { var $this = $(this); // Save the original size for recover $this.data(DATA_PREVIEW, { width: $this.width(), height: $this.height(), html: $this.html() }); /** * Override img element styles * Add `display:block` to avoid margin top issue * (Occur only when margin-top <= -height) */ $this.html( '' ); }); }, resetPreview: function () { this.$preview.each(function () { var $this = $(this); var data = $this.data(DATA_PREVIEW); $this.css({ width: data.width, height: data.height }).html(data.html).removeData(DATA_PREVIEW); }); }, preview: function () { var image = this.image; var canvas = this.canvas; var cropBox = this.cropBox; var cropBoxWidth = cropBox.width; var cropBoxHeight = cropBox.height; var width = image.width; var height = image.height; var left = cropBox.left - canvas.left - image.left; var top = cropBox.top - canvas.top - image.top; if (!this.isCropped || this.isDisabled) { return; } this.$clone2.css({ width: width, height: height, marginLeft: -left, marginTop: -top, transform: getTransform(image) }); this.$preview.each(function () { var $this = $(this); var data = $this.data(DATA_PREVIEW); var originalWidth = data.width; var originalHeight = data.height; var newWidth = originalWidth; var newHeight = originalHeight; var ratio = 1; if (cropBoxWidth) { ratio = originalWidth / cropBoxWidth; newHeight = cropBoxHeight * ratio; } if (cropBoxHeight && newHeight > originalHeight) { ratio = originalHeight / cropBoxHeight; newWidth = cropBoxWidth * ratio; newHeight = originalHeight; } $this.css({ width: newWidth, height: newHeight }).find('img').css({ width: width * ratio, height: height * ratio, marginLeft: -left * ratio, marginTop: -top * ratio, transform: getTransform(image) }); }); }, bind: function () { var options = this.options; var $this = this.$element; var $cropper = this.$cropper; if ($.isFunction(options.cropstart)) { $this.on(EVENT_CROP_START, options.cropstart); } if ($.isFunction(options.cropmove)) { $this.on(EVENT_CROP_MOVE, options.cropmove); } if ($.isFunction(options.cropend)) { $this.on(EVENT_CROP_END, options.cropend); } if ($.isFunction(options.crop)) { $this.on(EVENT_CROP, options.crop); } if ($.isFunction(options.zoom)) { $this.on(EVENT_ZOOM, options.zoom); } $cropper.on(EVENT_MOUSE_DOWN, $.proxy(this.cropStart, this)); if (options.zoomable && options.zoomOnWheel) { $cropper.on(EVENT_WHEEL, $.proxy(this.wheel, this)); } if (options.toggleDragModeOnDblclick) { $cropper.on(EVENT_DBLCLICK, $.proxy(this.dblclick, this)); } $document. on(EVENT_MOUSE_MOVE, (this._cropMove = proxy(this.cropMove, this))). on(EVENT_MOUSE_UP, (this._cropEnd = proxy(this.cropEnd, this))); if (options.responsive) { $window.on(EVENT_RESIZE, (this._resize = proxy(this.resize, this))); } }, unbind: function () { var options = this.options; var $this = this.$element; var $cropper = this.$cropper; if ($.isFunction(options.cropstart)) { $this.off(EVENT_CROP_START, options.cropstart); } if ($.isFunction(options.cropmove)) { $this.off(EVENT_CROP_MOVE, options.cropmove); } if ($.isFunction(options.cropend)) { $this.off(EVENT_CROP_END, options.cropend); } if ($.isFunction(options.crop)) { $this.off(EVENT_CROP, options.crop); } if ($.isFunction(options.zoom)) { $this.off(EVENT_ZOOM, options.zoom); } $cropper.off(EVENT_MOUSE_DOWN, this.cropStart); if (options.zoomable && options.zoomOnWheel) { $cropper.off(EVENT_WHEEL, this.wheel); } if (options.toggleDragModeOnDblclick) { $cropper.off(EVENT_DBLCLICK, this.dblclick); } $document. off(EVENT_MOUSE_MOVE, this._cropMove). off(EVENT_MOUSE_UP, this._cropEnd); if (options.responsive) { $window.off(EVENT_RESIZE, this._resize); } }, resize: function () { var restore = this.options.restore; var $container = this.$container; var container = this.container; var canvasData; var cropBoxData; var ratio; // Check `container` is necessary for IE8 if (this.isDisabled || !container) { return; } ratio = $container.width() / container.width; // Resize when width changed or height changed if (ratio !== 1 || $container.height() !== container.height) { if (restore) { canvasData = this.getCanvasData(); cropBoxData = this.getCropBoxData(); } this.render(); if (restore) { this.setCanvasData($.each(canvasData, function (i, n) { canvasData[i] = n * ratio; })); this.setCropBoxData($.each(cropBoxData, function (i, n) { cropBoxData[i] = n * ratio; })); } } }, dblclick: function () { if (this.isDisabled) { return; } if (this.$dragBox.hasClass(CLASS_CROP)) { this.setDragMode(ACTION_MOVE); } else { this.setDragMode(ACTION_CROP); } }, wheel: function (event) { var e = event.originalEvent || event; var ratio = num(this.options.wheelZoomRatio) || 0.1; var delta = 1; if (this.isDisabled) { return; } event.preventDefault(); // Limit wheel speed to prevent zoom too fast if (this.wheeling) { return; } this.wheeling = true; setTimeout($.proxy(function () { this.wheeling = false; }, this), 50); if (e.deltaY) { delta = e.deltaY > 0 ? 1 : -1; } else if (e.wheelDelta) { delta = -e.wheelDelta / 120; } else if (e.detail) { delta = e.detail > 0 ? 1 : -1; } this.zoom(-delta * ratio, event); }, cropStart: function (event) { var options = this.options; var originalEvent = event.originalEvent; var touches = originalEvent && originalEvent.touches; var e = event; var touchesLength; var action; if (this.isDisabled) { return; } if (touches) { touchesLength = touches.length; if (touchesLength > 1) { if (options.zoomable && options.zoomOnTouch && touchesLength === 2) { e = touches[1]; this.startX2 = e.pageX; this.startY2 = e.pageY; action = ACTION_ZOOM; } else { return; } } e = touches[0]; } action = action || $(e.target).data(DATA_ACTION); if (REGEXP_ACTIONS.test(action)) { if (this.trigger(EVENT_CROP_START, { originalEvent: originalEvent, action: action }).isDefaultPrevented()) { return; } event.preventDefault(); this.action = action; this.cropping = false; // IE8 has `event.pageX/Y`, but not `event.originalEvent.pageX/Y` // IE10 has `event.originalEvent.pageX/Y`, but not `event.pageX/Y` this.startX = e.pageX || originalEvent && originalEvent.pageX; this.startY = e.pageY || originalEvent && originalEvent.pageY; if (action === ACTION_CROP) { this.cropping = true; this.$dragBox.addClass(CLASS_MODAL); } } }, cropMove: function (event) { var options = this.options; var originalEvent = event.originalEvent; var touches = originalEvent && originalEvent.touches; var e = event; var action = this.action; var touchesLength; if (this.isDisabled) { return; } if (touches) { touchesLength = touches.length; if (touchesLength > 1) { if (options.zoomable && options.zoomOnTouch && touchesLength === 2) { e = touches[1]; this.endX2 = e.pageX; this.endY2 = e.pageY; } else { return; } } e = touches[0]; } if (action) { if (this.trigger(EVENT_CROP_MOVE, { originalEvent: originalEvent, action: action }).isDefaultPrevented()) { return; } event.preventDefault(); this.endX = e.pageX || originalEvent && originalEvent.pageX; this.endY = e.pageY || originalEvent && originalEvent.pageY; this.change(e.shiftKey, action === ACTION_ZOOM ? event : null); } }, cropEnd: function (event) { var originalEvent = event.originalEvent; var action = this.action; if (this.isDisabled) { return; } if (action) { event.preventDefault(); if (this.cropping) { this.cropping = false; this.$dragBox.toggleClass(CLASS_MODAL, this.isCropped && this.options.modal); } this.action = ''; this.trigger(EVENT_CROP_END, { originalEvent: originalEvent, action: action }); } }, change: function (shiftKey, event) { var options = this.options; var aspectRatio = options.aspectRatio; var action = this.action; var container = this.container; var canvas = this.canvas; var cropBox = this.cropBox; var width = cropBox.width; var height = cropBox.height; var left = cropBox.left; var top = cropBox.top; var right = left + width; var bottom = top + height; var minLeft = 0; var minTop = 0; var maxWidth = container.width; var maxHeight = container.height; var renderable = true; var offset; var range; // Locking aspect ratio in "free mode" by holding shift key (#259) if (!aspectRatio && shiftKey) { aspectRatio = width && height ? width / height : 1; } if (this.isLimited) { minLeft = cropBox.minLeft; minTop = cropBox.minTop; maxWidth = minLeft + min(container.width, canvas.width, canvas.left + canvas.width); maxHeight = minTop + min(container.height, canvas.height, canvas.top + canvas.height); } range = { x: this.endX - this.startX, y: this.endY - this.startY }; if (aspectRatio) { range.X = range.y * aspectRatio; range.Y = range.x / aspectRatio; } switch (action) { // Move crop box case ACTION_ALL: left += range.x; top += range.y; break; // Resize crop box case ACTION_EAST: if (range.x >= 0 && (right >= maxWidth || aspectRatio && (top <= minTop || bottom >= maxHeight))) { renderable = false; break; } width += range.x; if (aspectRatio) { height = width / aspectRatio; top -= range.Y / 2; } if (width < 0) { action = ACTION_WEST; width = 0; } break; case ACTION_NORTH: if (range.y <= 0 && (top <= minTop || aspectRatio && (left <= minLeft || right >= maxWidth))) { renderable = false; break; } height -= range.y; top += range.y; if (aspectRatio) { width = height * aspectRatio; left += range.X / 2; } if (height < 0) { action = ACTION_SOUTH; height = 0; } break; case ACTION_WEST: if (range.x <= 0 && (left <= minLeft || aspectRatio && (top <= minTop || bottom >= maxHeight))) { renderable = false; break; } width -= range.x; left += range.x; if (aspectRatio) { height = width / aspectRatio; top += range.Y / 2; } if (width < 0) { action = ACTION_EAST; width = 0; } break; case ACTION_SOUTH: if (range.y >= 0 && (bottom >= maxHeight || aspectRatio && (left <= minLeft || right >= maxWidth))) { renderable = false; break; } height += range.y; if (aspectRatio) { width = height * aspectRatio; left -= range.X / 2; } if (height < 0) { action = ACTION_NORTH; height = 0; } break; case ACTION_NORTH_EAST: if (aspectRatio) { if (range.y <= 0 && (top <= minTop || right >= maxWidth)) { renderable = false; break; } height -= range.y; top += range.y; width = height * aspectRatio; } else { if (range.x >= 0) { if (right < maxWidth) { width += range.x; } else if (range.y <= 0 && top <= minTop) { renderable = false; } } else { width += range.x; } if (range.y <= 0) { if (top > minTop) { height -= range.y; top += range.y; } } else { height -= range.y; top += range.y; } } if (width < 0 && height < 0) { action = ACTION_SOUTH_WEST; height = 0; width = 0; } else if (width < 0) { action = ACTION_NORTH_WEST; width = 0; } else if (height < 0) { action = ACTION_SOUTH_EAST; height = 0; } break; case ACTION_NORTH_WEST: if (aspectRatio) { if (range.y <= 0 && (top <= minTop || left <= minLeft)) { renderable = false; break; } height -= range.y; top += range.y; width = height * aspectRatio; left += range.X; } else { if (range.x <= 0) { if (left > minLeft) { width -= range.x; left += range.x; } else if (range.y <= 0 && top <= minTop) { renderable = false; } } else { width -= range.x; left += range.x; } if (range.y <= 0) { if (top > minTop) { height -= range.y; top += range.y; } } else { height -= range.y; top += range.y; } } if (width < 0 && height < 0) { action = ACTION_SOUTH_EAST; height = 0; width = 0; } else if (width < 0) { action = ACTION_NORTH_EAST; width = 0; } else if (height < 0) { action = ACTION_SOUTH_WEST; height = 0; } break; case ACTION_SOUTH_WEST: if (aspectRatio) { if (range.x <= 0 && (left <= minLeft || bottom >= maxHeight)) { renderable = false; break; } width -= range.x; left += range.x; height = width / aspectRatio; } else { if (range.x <= 0) { if (left > minLeft) { width -= range.x; left += range.x; } else if (range.y >= 0 && bottom >= maxHeight) { renderable = false; } } else { width -= range.x; left += range.x; } if (range.y >= 0) { if (bottom < maxHeight) { height += range.y; } } else { height += range.y; } } if (width < 0 && height < 0) { action = ACTION_NORTH_EAST; height = 0; width = 0; } else if (width < 0) { action = ACTION_SOUTH_EAST; width = 0; } else if (height < 0) { action = ACTION_NORTH_WEST; height = 0; } break; case ACTION_SOUTH_EAST: if (aspectRatio) { if (range.x >= 0 && (right >= maxWidth || bottom >= maxHeight)) { renderable = false; break; } width += range.x; height = width / aspectRatio; } else { if (range.x >= 0) { if (right < maxWidth) { width += range.x; } else if (range.y >= 0 && bottom >= maxHeight) { renderable = false; } } else { width += range.x; } if (range.y >= 0) { if (bottom < maxHeight) { height += range.y; } } else { height += range.y; } } if (width < 0 && height < 0) { action = ACTION_NORTH_WEST; height = 0; width = 0; } else if (width < 0) { action = ACTION_SOUTH_WEST; width = 0; } else if (height < 0) { action = ACTION_NORTH_EAST; height = 0; } break; // Move canvas case ACTION_MOVE: this.move(range.x, range.y); renderable = false; break; // Zoom canvas case ACTION_ZOOM: this.zoom((function (x1, y1, x2, y2) { var z1 = sqrt(x1 * x1 + y1 * y1); var z2 = sqrt(x2 * x2 + y2 * y2); return (z2 - z1) / z1; })( abs(this.startX - this.startX2), abs(this.startY - this.startY2), abs(this.endX - this.endX2), abs(this.endY - this.endY2) ), event); this.startX2 = this.endX2; this.startY2 = this.endY2; renderable = false; break; // Create crop box case ACTION_CROP: if (!range.x || !range.y) { renderable = false; break; } offset = this.$cropper.offset(); left = this.startX - offset.left; top = this.startY - offset.top; width = cropBox.minWidth; height = cropBox.minHeight; if (range.x > 0) { action = range.y > 0 ? ACTION_SOUTH_EAST : ACTION_NORTH_EAST; } else if (range.x < 0) { left -= width; action = range.y > 0 ? ACTION_SOUTH_WEST : ACTION_NORTH_WEST; } if (range.y < 0) { top -= height; } // Show the crop box if is hidden if (!this.isCropped) { this.$cropBox.removeClass(CLASS_HIDDEN); this.isCropped = true; if (this.isLimited) { this.limitCropBox(true, true); } } break; // No default } if (renderable) { cropBox.width = width; cropBox.height = height; cropBox.left = left; cropBox.top = top; this.action = action; this.renderCropBox(); } // Override this.startX = this.endX; this.startY = this.endY; }, // Show the crop box manually crop: function () { if (!this.isBuilt || this.isDisabled) { return; } if (!this.isCropped) { this.isCropped = true; this.limitCropBox(true, true); if (this.options.modal) { this.$dragBox.addClass(CLASS_MODAL); } this.$cropBox.removeClass(CLASS_HIDDEN); } this.setCropBoxData(this.initialCropBox); }, // Reset the image and crop box to their initial states reset: function () { if (!this.isBuilt || this.isDisabled) { return; } this.image = $.extend({}, this.initialImage); this.canvas = $.extend({}, this.initialCanvas); this.cropBox = $.extend({}, this.initialCropBox); this.renderCanvas(); if (this.isCropped) { this.renderCropBox(); } }, // Clear the crop box clear: function () { if (!this.isCropped || this.isDisabled) { return; } $.extend(this.cropBox, { left: 0, top: 0, width: 0, height: 0 }); this.isCropped = false; this.renderCropBox(); this.limitCanvas(true, true); // Render canvas after crop box rendered this.renderCanvas(); this.$dragBox.removeClass(CLASS_MODAL); this.$cropBox.addClass(CLASS_HIDDEN); }, /** * Replace the image's src and rebuild the cropper * * @param {String} url * @param {Boolean} onlyColorChanged (optional) */ replace: function (url, onlyColorChanged) { if (!this.isDisabled && url) { if (this.isImg) { this.$element.attr('src', url); } if (onlyColorChanged) { this.url = url; this.$clone.attr('src', url); if (this.isBuilt) { this.$preview.find('img').add(this.$clone2).attr('src', url); } } else { if (this.isImg) { this.isReplaced = true; } // Clear previous data this.options.data = null; this.load(url); } } }, // Enable (unfreeze) the cropper enable: function () { if (this.isBuilt) { this.isDisabled = false; this.$cropper.removeClass(CLASS_DISABLED); } }, // Disable (freeze) the cropper disable: function () { if (this.isBuilt) { this.isDisabled = true; this.$cropper.addClass(CLASS_DISABLED); } }, // Destroy the cropper and remove the instance from the image destroy: function () { var $this = this.$element; if (this.isLoaded) { if (this.isImg && this.isReplaced) { $this.attr('src', this.originalUrl); } this.unbuild(); $this.removeClass(CLASS_HIDDEN); } else { if (this.isImg) { $this.off(EVENT_LOAD, this.start); } else if (this.$clone) { this.$clone.remove(); } } $this.removeData(NAMESPACE); }, /** * Move the canvas with relative offsets * * @param {Number} offsetX * @param {Number} offsetY (optional) */ move: function (offsetX, offsetY) { var canvas = this.canvas; this.moveTo( isUndefined(offsetX) ? offsetX : canvas.left + num(offsetX), isUndefined(offsetY) ? offsetY : canvas.top + num(offsetY) ); }, /** * Move the canvas to an absolute point * * @param {Number} x * @param {Number} y (optional) */ moveTo: function (x, y) { var canvas = this.canvas; var isChanged = false; // If "y" is not present, its default value is "x" if (isUndefined(y)) { y = x; } x = num(x); y = num(y); if (this.isBuilt && !this.isDisabled && this.options.movable) { if (isNumber(x)) { canvas.left = x; isChanged = true; } if (isNumber(y)) { canvas.top = y; isChanged = true; } if (isChanged) { this.renderCanvas(true); } } }, /** * Zoom the canvas with a relative ratio * * @param {Number} ratio * @param {jQuery Event} _event (private) */ zoom: function (ratio, _event) { var canvas = this.canvas; ratio = num(ratio); if (ratio < 0) { ratio = 1 / (1 - ratio); } else { ratio = 1 + ratio; } this.zoomTo(canvas.width * ratio / canvas.naturalWidth, _event); }, /** * Zoom the canvas to an absolute ratio * * @param {Number} ratio * @param {jQuery Event} _event (private) */ zoomTo: function (ratio, _event) { var options = this.options; var canvas = this.canvas; var width = canvas.width; var height = canvas.height; var naturalWidth = canvas.naturalWidth; var naturalHeight = canvas.naturalHeight; var originalEvent; var newWidth; var newHeight; var offset; var center; ratio = num(ratio); if (ratio >= 0 && this.isBuilt && !this.isDisabled && options.zoomable) { newWidth = naturalWidth * ratio; newHeight = naturalHeight * ratio; if (_event) { originalEvent = _event.originalEvent; } if (this.trigger(EVENT_ZOOM, { originalEvent: originalEvent, oldRatio: width / naturalWidth, ratio: newWidth / naturalWidth }).isDefaultPrevented()) { return; } if (originalEvent) { offset = this.$cropper.offset(); center = originalEvent.touches ? getTouchesCenter(originalEvent.touches) : { pageX: _event.pageX || originalEvent.pageX || 0, pageY: _event.pageY || originalEvent.pageY || 0 }; // Zoom from the triggering point of the event canvas.left -= (newWidth - width) * ( ((center.pageX - offset.left) - canvas.left) / width ); canvas.top -= (newHeight - height) * ( ((center.pageY - offset.top) - canvas.top) / height ); } else { // Zoom from the center of the canvas canvas.left -= (newWidth - width) / 2; canvas.top -= (newHeight - height) / 2; } canvas.width = newWidth; canvas.height = newHeight; this.renderCanvas(true); } }, /** * Rotate the canvas with a relative degree * * @param {Number} degree */ rotate: function (degree) { this.rotateTo((this.image.rotate || 0) + num(degree)); }, /** * Rotate the canvas to an absolute degree * https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function#rotate() * * @param {Number} degree */ rotateTo: function (degree) { degree = num(degree); if (isNumber(degree) && this.isBuilt && !this.isDisabled && this.options.rotatable) { this.image.rotate = degree % 360; this.isRotated = true; this.renderCanvas(true); } }, /** * Scale the image * https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function#scale() * * @param {Number} scaleX * @param {Number} scaleY (optional) */ scale: function (scaleX, scaleY) { var image = this.image; var isChanged = false; // If "scaleY" is not present, its default value is "scaleX" if (isUndefined(scaleY)) { scaleY = scaleX; } scaleX = num(scaleX); scaleY = num(scaleY); if (this.isBuilt && !this.isDisabled && this.options.scalable) { if (isNumber(scaleX)) { image.scaleX = scaleX; isChanged = true; } if (isNumber(scaleY)) { image.scaleY = scaleY; isChanged = true; } if (isChanged) { this.renderImage(true); } } }, /** * Scale the abscissa of the image * * @param {Number} scaleX */ scaleX: function (scaleX) { var scaleY = this.image.scaleY; this.scale(scaleX, isNumber(scaleY) ? scaleY : 1); }, /** * Scale the ordinate of the image * * @param {Number} scaleY */ scaleY: function (scaleY) { var scaleX = this.image.scaleX; this.scale(isNumber(scaleX) ? scaleX : 1, scaleY); }, /** * Get the cropped area position and size data (base on the original image) * * @param {Boolean} isRounded (optional) * @return {Object} data */ getData: function (isRounded) { var options = this.options; var image = this.image; var canvas = this.canvas; var cropBox = this.cropBox; var ratio; var data; if (this.isBuilt && this.isCropped) { data = { x: cropBox.left - canvas.left, y: cropBox.top - canvas.top, width: cropBox.width, height: cropBox.height }; ratio = image.width / image.naturalWidth; $.each(data, function (i, n) { n = n / ratio; data[i] = isRounded ? round(n) : n; }); } else { data = { x: 0, y: 0, width: 0, height: 0 }; } if (options.rotatable) { data.rotate = image.rotate || 0; } if (options.scalable) { data.scaleX = image.scaleX || 1; data.scaleY = image.scaleY || 1; } return data; }, /** * Set the cropped area position and size with new data * * @param {Object} data */ setData: function (data) { var options = this.options; var image = this.image; var canvas = this.canvas; var cropBoxData = {}; var isRotated; var isScaled; var ratio; if ($.isFunction(data)) { data = data.call(this.element); } if (this.isBuilt && !this.isDisabled && $.isPlainObject(data)) { if (options.rotatable) { if (isNumber(data.rotate) && data.rotate !== image.rotate) { image.rotate = data.rotate; this.isRotated = isRotated = true; } } if (options.scalable) { if (isNumber(data.scaleX) && data.scaleX !== image.scaleX) { image.scaleX = data.scaleX; isScaled = true; } if (isNumber(data.scaleY) && data.scaleY !== image.scaleY) { image.scaleY = data.scaleY; isScaled = true; } } if (isRotated) { this.renderCanvas(); } else if (isScaled) { this.renderImage(); } ratio = image.width / image.naturalWidth; if (isNumber(data.x)) { cropBoxData.left = data.x * ratio + canvas.left; } if (isNumber(data.y)) { cropBoxData.top = data.y * ratio + canvas.top; } if (isNumber(data.width)) { cropBoxData.width = data.width * ratio; } if (isNumber(data.height)) { cropBoxData.height = data.height * ratio; } this.setCropBoxData(cropBoxData); } }, /** * Get the container size data * * @return {Object} data */ getContainerData: function () { return this.isBuilt ? this.container : {}; }, /** * Get the image position and size data * * @return {Object} data */ getImageData: function () { return this.isLoaded ? this.image : {}; }, /** * Get the canvas position and size data * * @return {Object} data */ getCanvasData: function () { var canvas = this.canvas; var data = {}; if (this.isBuilt) { $.each([ 'left', 'top', 'width', 'height', 'naturalWidth', 'naturalHeight' ], function (i, n) { data[n] = canvas[n]; }); } return data; }, /** * Set the canvas position and size with new data * * @param {Object} data */ setCanvasData: function (data) { var canvas = this.canvas; var aspectRatio = canvas.aspectRatio; if ($.isFunction(data)) { data = data.call(this.$element); } if (this.isBuilt && !this.isDisabled && $.isPlainObject(data)) { if (isNumber(data.left)) { canvas.left = data.left; } if (isNumber(data.top)) { canvas.top = data.top; } if (isNumber(data.width)) { canvas.width = data.width; canvas.height = data.width / aspectRatio; } else if (isNumber(data.height)) { canvas.height = data.height; canvas.width = data.height * aspectRatio; } this.renderCanvas(true); } }, /** * Get the crop box position and size data * * @return {Object} data */ getCropBoxData: function () { var cropBox = this.cropBox; var data; if (this.isBuilt && this.isCropped) { data = { left: cropBox.left, top: cropBox.top, width: cropBox.width, height: cropBox.height }; } return data || {}; }, /** * Set the crop box position and size with new data * * @param {Object} data */ setCropBoxData: function (data) { var cropBox = this.cropBox; var aspectRatio = this.options.aspectRatio; var isWidthChanged; var isHeightChanged; if ($.isFunction(data)) { data = data.call(this.$element); } if (this.isBuilt && this.isCropped && !this.isDisabled && $.isPlainObject(data)) { if (isNumber(data.left)) { cropBox.left = data.left; } if (isNumber(data.top)) { cropBox.top = data.top; } if (isNumber(data.width)) { isWidthChanged = true; cropBox.width = data.width; } if (isNumber(data.height)) { isHeightChanged = true; cropBox.height = data.height; } if (aspectRatio) { if (isWidthChanged) { cropBox.height = cropBox.width / aspectRatio; } else if (isHeightChanged) { cropBox.width = cropBox.height * aspectRatio; } } this.renderCropBox(); } }, /** * Get a canvas drawn the cropped image * * @param {Object} options (optional) * @return {HTMLCanvasElement} canvas */ getCroppedCanvas: function (options) { var originalWidth; var originalHeight; var canvasWidth; var canvasHeight; var scaledWidth; var scaledHeight; var scaledRatio; var aspectRatio; var canvas; var context; var data; if (!this.isBuilt || !SUPPORT_CANVAS) { return; } if (!this.isCropped) { return getSourceCanvas(this.$clone[0], this.image); } if (!$.isPlainObject(options)) { options = {}; } data = this.getData(); originalWidth = data.width; originalHeight = data.height; aspectRatio = originalWidth / originalHeight; if ($.isPlainObject(options)) { scaledWidth = options.width; scaledHeight = options.height; if (scaledWidth) { scaledHeight = scaledWidth / aspectRatio; scaledRatio = scaledWidth / originalWidth; } else if (scaledHeight) { scaledWidth = scaledHeight * aspectRatio; scaledRatio = scaledHeight / originalHeight; } } // The canvas element will use `Math.floor` on a float number, so floor first canvasWidth = floor(scaledWidth || originalWidth); canvasHeight = floor(scaledHeight || originalHeight); canvas = $('')[0]; canvas.width = canvasWidth; canvas.height = canvasHeight; context = canvas.getContext('2d'); if (options.fillColor) { context.fillStyle = options.fillColor; context.fillRect(0, 0, canvasWidth, canvasHeight); } // https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D.drawImage context.drawImage.apply(context, (function () { var source = getSourceCanvas(this.$clone[0], this.image); var sourceWidth = source.width; var sourceHeight = source.height; var canvas = this.canvas; var params = [source]; // Source canvas var srcX = data.x + canvas.naturalWidth * (abs(data.scaleX || 1) - 1) / 2; var srcY = data.y + canvas.naturalHeight * (abs(data.scaleY || 1) - 1) / 2; var srcWidth; var srcHeight; // Destination canvas var dstX; var dstY; var dstWidth; var dstHeight; if (srcX <= -originalWidth || srcX > sourceWidth) { srcX = srcWidth = dstX = dstWidth = 0; } else if (srcX <= 0) { dstX = -srcX; srcX = 0; srcWidth = dstWidth = min(sourceWidth, originalWidth + srcX); } else if (srcX <= sourceWidth) { dstX = 0; srcWidth = dstWidth = min(originalWidth, sourceWidth - srcX); } if (srcWidth <= 0 || srcY <= -originalHeight || srcY > sourceHeight) { srcY = srcHeight = dstY = dstHeight = 0; } else if (srcY <= 0) { dstY = -srcY; srcY = 0; srcHeight = dstHeight = min(sourceHeight, originalHeight + srcY); } else if (srcY <= sourceHeight) { dstY = 0; srcHeight = dstHeight = min(originalHeight, sourceHeight - srcY); } // All the numerical parameters should be integer for `drawImage` (#476) params.push(floor(srcX), floor(srcY), floor(srcWidth), floor(srcHeight)); // Scale destination sizes if (scaledRatio) { dstX *= scaledRatio; dstY *= scaledRatio; dstWidth *= scaledRatio; dstHeight *= scaledRatio; } // Avoid "IndexSizeError" in IE and Firefox if (dstWidth > 0 && dstHeight > 0) { params.push(floor(dstX), floor(dstY), floor(dstWidth), floor(dstHeight)); } return params; }).call(this)); return canvas; }, /** * Change the aspect ratio of the crop box * * @param {Number} aspectRatio */ setAspectRatio: function (aspectRatio) { var options = this.options; if (!this.isDisabled && !isUndefined(aspectRatio)) { // 0 -> NaN options.aspectRatio = max(0, aspectRatio) || NaN; if (this.isBuilt) { this.initCropBox(); if (this.isCropped) { this.renderCropBox(); } } } }, /** * Change the drag mode * * @param {String} mode (optional) */ setDragMode: function (mode) { var options = this.options; var croppable; var movable; if (this.isLoaded && !this.isDisabled) { croppable = mode === ACTION_CROP; movable = options.movable && mode === ACTION_MOVE; mode = (croppable || movable) ? mode : ACTION_NONE; this.$dragBox. data(DATA_ACTION, mode). toggleClass(CLASS_CROP, croppable). toggleClass(CLASS_MOVE, movable); if (!options.cropBoxMovable) { // Sync drag mode to crop box when it is not movable(#300) this.$face. data(DATA_ACTION, mode). toggleClass(CLASS_CROP, croppable). toggleClass(CLASS_MOVE, movable); } } } }; Cropper.DEFAULTS = { // Define the view mode of the cropper viewMode: 0, // 0, 1, 2, 3 // Define the dragging mode of the cropper dragMode: 'crop', // 'crop', 'move' or 'none' // Define the aspect ratio of the crop box aspectRatio: NaN, // An object with the previous cropping result data data: null, // A jQuery selector for adding extra containers to preview preview: '', // Re-render the cropper when resize the window responsive: true, // Restore the cropped area after resize the window restore: true, // Check if the current image is a cross-origin image checkCrossOrigin: true, // Check the current image's Exif Orientation information checkOrientation: true, // Show the black modal modal: true, // Show the dashed lines for guiding guides: true, // Show the center indicator for guiding center: true, // Show the white modal to highlight the crop box highlight: true, // Show the grid background background: true, // Enable to crop the image automatically when initialize autoCrop: true, // Define the percentage of automatic cropping area when initializes autoCropArea: 0.8, // Enable to move the image movable: true, // Enable to rotate the image rotatable: true, // Enable to scale the image scalable: true, // Enable to zoom the image zoomable: true, // Enable to zoom the image by dragging touch zoomOnTouch: true, // Enable to zoom the image by wheeling mouse zoomOnWheel: true, // Define zoom ratio when zoom the image by wheeling mouse wheelZoomRatio: 0.1, // Enable to move the crop box cropBoxMovable: true, // Enable to resize the crop box cropBoxResizable: true, // Toggle drag mode between "crop" and "move" when click twice on the cropper toggleDragModeOnDblclick: true, // Size limitation minCanvasWidth: 0, minCanvasHeight: 0, minCropBoxWidth: 0, minCropBoxHeight: 0, minContainerWidth: 200, minContainerHeight: 100, // Shortcuts of events build: null, built: null, cropstart: null, cropmove: null, cropend: null, crop: null, zoom: null }; Cropper.setDefaults = function (options) { $.extend(Cropper.DEFAULTS, options); }; Cropper.TEMPLATE = ( '
' + '
' + '
' + '
' + '
' + '
' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '
' + '
' ); // Save the other cropper Cropper.other = $.fn.cropper; // Register as jQuery plugin $.fn.cropper = function (option) { var args = toArray(arguments, 1); var result; this.each(function () { var $this = $(this); var data = $this.data(NAMESPACE); var options; var fn; if (!data) { if (/destroy/.test(option)) { return; } options = $.extend({}, $this.data(), $.isPlainObject(option) && option); $this.data(NAMESPACE, (data = new Cropper(this, options))); } if (typeof option === 'string' && $.isFunction(fn = data[option])) { result = fn.apply(data, args); } }); return isUndefined(result) ? this : result; }; $.fn.cropper.Constructor = Cropper; $.fn.cropper.setDefaults = Cropper.setDefaults; // No conflict $.fn.cropper.noConflict = function () { $.fn.cropper = Cropper.other; return this; }; });