$.extend(prototype, { // Show the crop box manually crop: function () { if (!this.built || this.disabled) { return; } if (!this.cropped) { this.cropped = 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.built || this.disabled) { return; } this.image = $.extend({}, this.initialImage); this.canvas = $.extend({}, this.initialCanvas); // Required for strict mode this.cropBox = $.extend({}, this.initialCropBox); this.renderCanvas(); if (this.cropped) { this.renderCropBox(); } }, // Clear the crop box clear: function () { if (!this.cropped || this.disabled) { return; } $.extend(this.cropBox, { left: 0, top: 0, width: 0, height: 0 }); this.cropped = 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 */ replace: function (url) { if (!this.disabled && url) { if (this.isImg) { this.replaced = true; this.$element.attr('src', url); } // Clear previous data this.options.data = null; this.load(url); } }, // Enable (unfreeze) the cropper enable: function () { if (this.built) { this.disabled = false; this.$cropper.removeClass(CLASS_DISABLED); } }, // Disable (freeze) the cropper disable: function () { if (this.built) { this.disabled = true; this.$cropper.addClass(CLASS_DISABLED); } }, // Destroy the cropper and remove the instance from the image destroy: function () { var $this = this.$element; if (this.ready) { if (this.isImg && this.replaced) { $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 * * @param {Number} offsetX * @param {Number} offsetY (optional) */ move: function (offsetX, offsetY) { var canvas = this.canvas; // If "offsetY" is not present, its default value is "offsetX" if (isUndefined(offsetY)) { offsetY = offsetX; } offsetX = num(offsetX); offsetY = num(offsetY); if (this.built && !this.disabled && this.options.movable) { canvas.left += isNumber(offsetX) ? offsetX : 0; canvas.top += isNumber(offsetY) ? offsetY : 0; this.renderCanvas(true); } }, /** * Zoom the canvas * * @param {Number} ratio * @param {Event} _originalEvent (private) */ zoom: function (ratio, _originalEvent) { var canvas = this.canvas; var width; var height; ratio = num(ratio); if (ratio && this.built && !this.disabled && this.options.zoomable) { if (this.trigger(EVENT_ZOOM, { originalEvent: _originalEvent, ratio: ratio }).isDefaultPrevented()) { return; } if (ratio < 0) { ratio = 1 / (1 - ratio); } else { ratio = 1 + ratio; } width = canvas.width * ratio; height = canvas.height * ratio; canvas.left -= (width - canvas.width) / 2; canvas.top -= (height - canvas.height) / 2; canvas.width = width; canvas.height = height; this.renderCanvas(true); this.setDragMode(ACTION_MOVE); } }, /** * Rotate the canvas * https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function#rotate() * * @param {Number} degree */ rotate: function (degree) { var image = this.image; var rotate = image.rotate || 0; degree = num(degree) || 0; if (this.built && !this.disabled && this.options.rotatable) { image.rotate = (rotate + degree) % 360; this.rotated = 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; // If "scaleY" is not present, its default value is "scaleX" if (isUndefined(scaleY)) { scaleY = scaleX; } scaleX = num(scaleX); scaleY = num(scaleY); if (this.built && !this.disabled && this.options.scalable) { image.scaleX = isNumber(scaleX) ? scaleX : 1; image.scaleY = isNumber(scaleY) ? scaleY : 1; this.renderImage(true); } }, /** * Get the cropped area position and size data (base on the original image) * * @param {Boolean} rounded (optional) * @return {Object} data */ getData: function (rounded) { var options = this.options; var image = this.image; var canvas = this.canvas; var cropBox = this.cropBox; var ratio; var data; if (this.built && this.cropped) { 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] = rounded ? Math.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 rotated; var scaled; var ratio; if ($.isFunction(data)) { data = data.call(this.element); } if (this.built && !this.disabled && $.isPlainObject(data)) { if (options.rotatable) { if (isNumber(data.rotate) && data.rotate !== image.rotate) { image.rotate = data.rotate; this.rotated = rotated = true; } } if (options.scalable) { if (isNumber(data.scaleX) && data.scaleX !== image.scaleX) { image.scaleX = data.scaleX; scaled = true; } if (isNumber(data.scaleY) && data.scaleY !== image.scaleY) { image.scaleY = data.scaleY; scaled = true; } } if (rotated) { this.renderCanvas(); } else if (scaled) { 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.built ? this.container : {}; }, /** * Get the image position and size data * * @return {Object} data */ getImageData: function () { return this.ready ? this.image : {}; }, /** * Get the canvas position and size data * * @return {Object} data */ getCanvasData: function () { var canvas = this.canvas; var data; if (this.built) { data = { left: canvas.left, top: canvas.top, width: canvas.width, height: canvas.height }; } 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.built && !this.disabled && $.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.built && this.cropped) { 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 widthChanged; var heightChanged; if ($.isFunction(data)) { data = data.call(this.$element); } if (this.built && this.cropped && !this.disabled && $.isPlainObject(data)) { if (isNumber(data.left)) { cropBox.left = data.left; } if (isNumber(data.top)) { cropBox.top = data.top; } if (isNumber(data.width) && data.width !== cropBox.width) { widthChanged = true; cropBox.width = data.width; } if (isNumber(data.height) && data.height !== cropBox.height) { heightChanged = true; cropBox.height = data.height; } if (aspectRatio) { if (widthChanged) { cropBox.height = cropBox.width / aspectRatio; } else if (heightChanged) { 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.built || !this.cropped || !SUPPORT_CANVAS) { return; } 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; } } canvasWidth = scaledWidth || originalWidth; canvasHeight = 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 args = [source]; // Source canvas var srcX = data.x; var srcY = data.y; 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); } args.push(srcX, srcY, srcWidth, 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) { args.push(dstX, dstY, dstWidth, dstHeight); } return args; }).call(this)); return canvas; }, /** * Change the aspect ratio of the crop box * * @param {Number} aspectRatio */ setAspectRatio: function (aspectRatio) { var options = this.options; if (!this.disabled && !isUndefined(aspectRatio)) { // 0 -> NaN options.aspectRatio = num(aspectRatio) || NaN; if (this.built) { this.initCropBox(); if (this.cropped) { this.renderCropBox(); } } } }, /** * Change the drag mode * * @param {String} mode (optional) */ setDragMode: function (mode) { var options = this.options; var croppable; var movable; if (this.ready && !this.disabled) { croppable = options.dragCrop && 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); } } } });