PageSourceSearch

https://www.hycu.ac.kr/content/pgm/lib/cropprt/cropper.js

js hycu.ac.kr collected 2026-10-02 17:42:04 UTC 80,147 bytes, 3,005 lines download raw bytes

1/*!
2 * Cropper v2.3.4
3 * https://github.com/fengyuanchen/cropper
4 *
5 * Copyright (c) 2014-2016 Fengyuan Chen and contributors
6 * Released under the MIT license
7 *
8 * Date: 2016-09-03T05:50:45.412Z
9 */
10
11(function (factory) {
12  if (typeof define === 'function' && define.amd) {
13    // AMD. Register as anonymous module.
14    define(['jquery'], factory);
15  } else if (typeof exports === 'object') {
16    // Node / CommonJS
17    factory(require('jquery'));
18  } else {
19    // Browser globals.
20    factory(jQuery);
21  }
22})(function ($) {
23
24  'use strict';
25
26  // Globals
27  var $window = $(window);
28  var $document = $(document);
29  var location = window.location;
30  var navigator = window.navigator;
31  var ArrayBuffer = window.ArrayBuffer;
32  var Uint8Array = window.Uint8Array;
33  var DataView = window.DataView;
34  var btoa = window.btoa;
35
36  // Constants
37  var NAMESPACE = 'cropper';
38
39  // Classes
40  var CLASS_MODAL = 'cropper-modal';
41  var CLASS_HIDE = 'cropper-hide';
42  var CLASS_HIDDEN = 'cropper-hidden';
43  var CLASS_INVISIBLE = 'cropper-invisible';
44  var CLASS_MOVE = 'cropper-move';
45  var CLASS_CROP = 'cropper-crop';
46  var CLASS_DISABLED = 'cropper-disabled';
47  var CLASS_BG = 'cropper-bg';
48
49  // Events
50  var EVENT_MOUSE_DOWN = 'mousedown touchstart pointerdown MSPointerDown';
51  var EVENT_MOUSE_MOVE = 'mousemove touchmove pointermove MSPointerMove';
52  var EVENT_MOUSE_UP = 'mouseup touchend touchcancel pointerup pointercancel MSPointerUp MSPointerCancel';
53  var EVENT_WHEEL = 'wheel mousewheel DOMMouseScroll';
54  var EVENT_DBLCLICK = 'dblclick';
55  var EVENT_LOAD = 'load.' + NAMESPACE;
56  var EVENT_ERROR = 'error.' + NAMESPACE;
57  var EVENT_RESIZE = 'resize.' + NAMESPACE; // Bind to window with namespace
58  var EVENT_BUILD = 'build.' + NAMESPACE;
59  var EVENT_BUILT = 'built.' + NAMESPACE;
60  var EVENT_CROP_START = 'cropstart.' + NAMESPACE;
61  var EVENT_CROP_MOVE = 'cropmove.' + NAMESPACE;
62  var EVENT_CROP_END = 'cropend.' + NAMESPACE;
63  var EVENT_CROP = 'crop.' + NAMESPACE;
64  var EVENT_ZOOM = 'zoom.' + NAMESPACE;
65
66  // RegExps
67  var REGEXP_ACTIONS = /^(e|w|s|n|se|sw|ne|nw|all|crop|move|zoom)$/;
68  var REGEXP_DATA_URL = /^data:/;
69  var REGEXP_DATA_URL_HEAD = /^data:([^;]+);base64,/;
70  var REGEXP_DATA_URL_JPEG = /^data:image\/jpeg.*;base64,/;
71
72  // Data keys
73  var DATA_PREVIEW = 'preview';
74  var DATA_ACTION = 'action';
75
76  // Actions
77  var ACTION_EAST = 'e';
78  var ACTION_WEST = 'w';
79  var ACTION_SOUTH = 's';
80  var ACTION_NORTH = 'n';
81  var ACTION_SOUTH_EAST = 'se';
82  var ACTION_SOUTH_WEST = 'sw';
83  var ACTION_NORTH_EAST = 'ne';
84  var ACTION_NORTH_WEST = 'nw';
85  var ACTION_ALL = 'all';
86  var ACTION_CROP = 'crop';
87  var ACTION_MOVE = 'move';
88  var ACTION_ZOOM = 'zoom';
89  var ACTION_NONE = 'none';
90
91  // Supports
92  var SUPPORT_CANVAS = $.isFunction($('<canvas>')[0].getContext);
93  var IS_SAFARI_OR_UIWEBVIEW = navigator && /(Macintosh|iPhone|iPod|iPad).*AppleWebKit/i.test(navigator.userAgent);
94
95  // Maths
96  var num = Number;
97  var min = Math.min;
98  var max = Math.max;
99  var abs = Math.abs;
100  var sin = Math.sin;
101  var cos = Math.cos;
102  var sqrt = Math.sqrt;
103  var round = Math.round;
104  var floor = Math.floor;
105
106  // Utilities
107  var fromCharCode = String.fromCharCode;
108
109  function isNumber(n) {
110    return typeof n === 'number' && !isNaN(n);
111  }
112
113  function isUndefined(n) {
114    return typeof n === 'undefined';
115  }
116
117  function toArray(obj, offset) {
118    var args = [];
119
120    // This is necessary for IE8
121    if (isNumber(offset)) {
122      args.push(offset);
123    }
124
125    return args.slice.apply(obj, args);
126  }
127
128  // Custom proxy to avoid jQuery's guid
129  function proxy(fn, context) {
130    var args = toArray(arguments, 2);
131
132    return function () {
133      return fn.apply(context, args.concat(toArray(arguments)));
134    };
135  }
136
137  function isCrossOriginURL(url) {
138    var parts = url.match(/^(https?:)\/\/([^\:\/\?#]+):?(\d*)/i);
139
140    return parts && (
141      parts[1] !== location.protocol ||
142      parts[2] !== location.hostname ||
143      parts[3] !== location.port
144    );
145  }
146
147  function addTimestamp(url) {
148    var timestamp = 'timestamp=' + (new Date()).getTime();
149
150    return (url + (url.indexOf('?') === -1 ? '?' : '&') + timestamp);
151  }
152
153  function getCrossOrigin(crossOrigin) {
154    return crossOrigin ? ' crossOrigin="' + crossOrigin + '"' : '';
155  }
156
157  function getImageSize(image, callback) {
158    var newImage;
159
160    // Modern browsers (ignore Safari, #120 & #509)
161    if (image.naturalWidth && !IS_SAFARI_OR_UIWEBVIEW) {
162      return callback(image.naturalWidth, image.naturalHeight);
163    }
164
165    // IE8: Don't use `new Image()` here (#319)
166    newImage = document.createElement('img');
167
168    newImage.onload = function () {
169      callback(this.width, this.height);
170    };
171
172    newImage.src = image.src;
173  }
174
175  function getTransform(options) {
176    var transforms = [];
177    var rotate = options.rotate;
178    var scaleX = options.scaleX;
179    var scaleY = options.scaleY;
180
181    // Rotate should come first before scale to match orientation transform
182    if (isNumber(rotate) && rotate !== 0) {
183      transforms.push('rotate(' + rotate + 'deg)');
184    }
185
186    if (isNumber(scaleX) && scaleX !== 1) {
187      transforms.push('scaleX(' + scaleX + ')');
188    }
189
190    if (isNumber(scaleY) && scaleY !== 1) {
191      transforms.push('scaleY(' + scaleY + ')');
192    }
193
194    return transforms.length ? transforms.join(' ') : 'none';
195  }
196
197  function getRotatedSizes(data, isReversed) {
198    var deg = abs(data.degree) % 180;
199    var arc = (deg > 90 ? (180 - deg) : deg) * Math.PI / 180;
200    var sinArc = sin(arc);
201    var cosArc = cos(arc);
202    var width = data.width;
203    var height = data.height;
204    var aspectRatio = data.aspectRatio;
205    var newWidth;
206    var newHeight;
207
208    if (!isReversed) {
209      newWidth = width * cosArc + height * sinArc;
210      newHeight = width * sinArc + height * cosArc;
211    } else {
212      newWidth = width / (cosArc + sinArc / aspectRatio);
213      newHeight = newWidth / aspectRatio;
214    }
215
216    return {
217      width: newWidth,
218      height: newHeight
219    };
220  }
221
222  function getSourceCanvas(image, data) {
223    var canvas = $('<canvas>')[0];
224    var context = canvas.getContext('2d');
225    var dstX = 0;
226    var dstY = 0;
227    var dstWidth = data.naturalWidth;
228    var dstHeight = data.naturalHeight;
229    var rotate = data.rotate;
230    var scaleX = data.scaleX;
231    var scaleY = data.scaleY;
232    var scalable = isNumber(scaleX) && isNumber(scaleY) && (scaleX !== 1 || scaleY !== 1);
233    var rotatable = isNumber(rotate) && rotate !== 0;
234    var advanced = rotatable || scalable;
235    var canvasWidth = dstWidth * abs(scaleX || 1);
236    var canvasHeight = dstHeight * abs(scaleY || 1);
237    var translateX;
238    var translateY;
239    var rotated;
240
241    if (scalable) {
242      translateX = canvasWidth / 2;
243      translateY = canvasHeight / 2;
244    }
245
246    if (rotatable) {
247      rotated = getRotatedSizes({
248        width: canvasWidth,
249        height: canvasHeight,
250        degree: rotate
251      });
252
253      canvasWidth = rotated.width;
254      canvasHeight = rotated.height;
255      translateX = canvasWidth / 2;
256      translateY = canvasHeight / 2;
257    }
258
259    canvas.width = canvasWidth;
260    canvas.height = canvasHeight;
261
262    if (advanced) {
263      dstX = -dstWidth / 2;
264      dstY = -dstHeight / 2;
265
266      context.save();
267      context.translate(translateX, translateY);
268    }
269
270    // Rotate should come first before scale as in the "getTransform" function
271    if (rotatable) {
272      context.rotate(rotate * Math.PI / 180);
273    }
274
275    if (scalable) {
276      context.scale(scaleX, scaleY);
277    }
278
279    context.drawImage(image, floor(dstX), floor(dstY), floor(dstWidth), floor(dstHeight));
280
281    if (advanced) {
282      context.restore();
283    }
284
285    return canvas;
286  }
287
288  function getTouchesCenter(touches) {
289    var length = touches.length;
290    var pageX = 0;
291    var pageY = 0;
292
293    if (length) {
294      $.each(touches, function (i, touch) {
295        pageX += touch.pageX;
296        pageY += touch.pageY;
297      });
298
299      pageX /= length;
300      pageY /= length;
301    }
302
303    return {
304      pageX: pageX,
305      pageY: pageY
306    };
307  }
308
309  function getStringFromCharCode(dataView, start, length) {
310    var str = '';
311    var i;
312
313    for (i = start, length += start; i < length; i++) {
314      str += fromCharCode(dataView.getUint8(i));
315    }
316
317    return str;
318  }
319
320  function getOrientation(arrayBuffer) {
321    var dataView = new DataView(arrayBuffer);
322    var length = dataView.byteLength;
323    var orientation;
324    var exifIDCode;
325    var tiffOffset;
326    var firstIFDOffset;
327    var littleEndian;
328    var endianness;
329    var app1Start;
330    var ifdStart;
331    var offset;
332    var i;
333
334    // Only handle JPEG image (start by 0xFFD8)
335    if (dataView.getUint8(0) === 0xFF && dataView.getUint8(1) === 0xD8) {
336      offset = 2;
337
338      while (offset < length) {
339        if (dataView.getUint8(offset) === 0xFF && dataView.getUint8(offset + 1) === 0xE1) {
340          app1Start = offset;
341          break;
342        }
343
344        offset++;
345      }
346    }
347
348    if (app1Start) {
349      exifIDCode = app1Start + 4;
350      tiffOffset = app1Start + 10;
351
352      if (getStringFromCharCode(dataView, exifIDCode, 4) === 'Exif') {
353        endianness = dataView.getUint16(tiffOffset);
354        littleEndian = endianness === 0x4949;
355
356        if (littleEndian || endianness === 0x4D4D /* bigEndian */) {
357          if (dataView.getUint16(tiffOffset + 2, littleEndian) === 0x002A) {
358            firstIFDOffset = dataView.getUint32(tiffOffset + 4, littleEndian);
359
360            if (firstIFDOffset >= 0x00000008) {
361              ifdStart = tiffOffset + firstIFDOffset;
362            }
363          }
364        }
365      }
366    }
367
368    if (ifdStart) {
369      length = dataView.getUint16(ifdStart, littleEndian);
370
371      for (i = 0; i < length; i++) {
372        offset = ifdStart + i * 12 + 2;
373
374        if (dataView.getUint16(offset, littleEndian) === 0x0112 /* Orientation */) {
375
376          // 8 is the offset of the current tag's value
377          offset += 8;
378
379          // Get the original orientation value
380          orientation = dataView.getUint16(offset, littleEndian);
381
382          // Override the orientation with its default value for Safari (#120)
383          if (IS_SAFARI_OR_UIWEBVIEW) {
384            dataView.setUint16(offset, 1, littleEndian);
385          }
386
387          break;
388        }
389      }
390    }
391
392    return orientation;
393  }
394
395  function dataURLToArrayBuffer(dataURL) {
396    var base64 = dataURL.replace(REGEXP_DATA_URL_HEAD, '');
397    var binary = atob(base64);
398    var length = binary.length;
399    var arrayBuffer = new ArrayBuffer(length);
400    var dataView = new Uint8Array(arrayBuffer);
401    var i;
402
403    for (i = 0; i < length; i++) {
404      dataView[i] = binary.charCodeAt(i);
405    }
406
407    return arrayBuffer;
408  }
409
410  // Only available for JPEG image
411  function arrayBufferToDataURL(arrayBuffer) {
412    var dataView = new Uint8Array(arrayBuffer);
413    var length = dataView.length;
414    var base64 = '';
415    var i;
416
417    for (i = 0; i < length; i++) {
418      base64 += fromCharCode(dataView[i]);
419    }
420
421    return 'data:image/jpeg;base64,' + btoa(base64);
422  }
423
424  function Cropper(element, options) {
425    this.$element = $(element);
426    this.options = $.extend({}, Cropper.DEFAULTS, $.isPlainObject(options) && options);
427    this.isLoaded = false;
428    this.isBuilt = false;
429    this.isCompleted = false;
430    this.isRotated = false;
431    this.isCropped = false;
432    this.isDisabled = false;
433    this.isReplaced = false;
434    this.isLimited = false;
435    this.wheeling = false;
436    this.isImg = false;
437    this.originalUrl = '';
438    this.canvas = null;
439    this.cropBox = null;
440    this.init();
441  }
442
443  Cropper.prototype = {
444    constructor: Cropper,
445
446    init: function () {
447      var $this = this.$element;
448      var url;
449
450      if ($this.is('img')) {
451        this.isImg = true;
452
453        // Should use `$.fn.attr` here. e.g.: "img/picture.jpg"
454        this.originalUrl = url = $this.attr('src');
455
456        // Stop when it's a blank image
457        if (!url) {
458          return;
459        }
460
461        // Should use `$.fn.prop` here. e.g.: "http://example.com/img/picture.jpg"
462        url = $this.prop('src');
463      } else if ($this.is('canvas') && SUPPORT_CANVAS) {
464        url = $this[0].toDataURL();
465      }
466
467      this.load(url);
468    },
469
470    // A shortcut for triggering custom events
471    trigger: function (type, data) {
472      var e = $.Event(type, data);
473
474      this.$element.trigger(e);
475
476      return e;
477    },
478
479    load: function (url) {
480      var options = this.options;
481      var $this = this.$element;
482      var read;
483      var xhr;
484
485      if (!url) {
486        return;
487      }
488
489      // Trigger build event first
490      $this.one(EVENT_BUILD, options.build);
491
492      if (this.trigger(EVENT_BUILD).isDefaultPrevented()) {
493        return;
494      }
495
496      this.url = url;
497      this.image = {};
498
499      if (!options.checkOrientation || !ArrayBuffer) {
500        return this.clone();
501      }
502
503      read = $.proxy(this.read, this);
504
505      // XMLHttpRequest disallows to open a Data URL in some browsers like IE11 and Safari
506      if (REGEXP_DATA_URL.test(url)) {
507        return REGEXP_DATA_URL_JPEG.test(url) ?
508          read(dataURLToArrayBuffer(url)) :
509          this.clone();
510      }
511
512      xhr = new XMLHttpRequest();
513
514      xhr.onerror = xhr.onabort = $.proxy(function () {
515        this.clone();
516      }, this);
517
518      xhr.onload = function () {
519        read(this.response);
520      };
521
522      if (options.checkCrossOrigin && isCrossOriginURL(url) && $this.prop('crossOrigin')) {
523        url = addTimestamp(url);
524      }
525
526      xhr.open('get', url);
527      xhr.responseType = 'arraybuffer';
528      xhr.send();
529    },
530
531    read: function (arrayBuffer) {
532      var options = this.options;
533      var orientation = getOrientation(arrayBuffer);
534      var image = this.image;
535      var rotate = 0;
536      var scaleX = 1;
537      var scaleY = 1;
538
539      if (orientation > 1) {
540        this.url = arrayBufferToDataURL(arrayBuffer);
541
542        switch (orientation) {
543
544          // flip horizontal
545          case 2:
546            scaleX = -1;
547            break;
548
549          // rotate left 180°
550          case 3:
551            rotate = -180;
552            break;
553
554          // flip vertical
555          case 4:
556            scaleY = -1;
557            break;
558
559          // flip vertical + rotate right 90°
560          case 5:
561            rotate = 90;
562            scaleY = -1;
563            break;
564
565          // rotate right 90°
566          case 6:
567            rotate = 90;
568            break;
569
570          // flip horizontal + rotate right 90°
571          case 7:
572            rotate = 90;
573            scaleX = -1;
574            break;
575
576          // rotate left 90°
577          case 8:
578            rotate = -90;
579            break;
580        }
581      }
582
583      if (options.rotatable) {
584        image.rotate = rotate;
585      }
586
587      if (options.scalable) {
588        image.scaleX = scaleX;
589        image.scaleY = scaleY;
590      }
591
592      this.clone();
593    },
594
595    clone: function () {
596      var options = this.options;
597      var $this = this.$element;
598      var url = this.url;
599      var crossOrigin = '';
600      var crossOriginUrl;
601      var $clone;
602
603      if (options.checkCrossOrigin && isCrossOriginURL(url)) {
604        crossOrigin = $this.prop('crossOrigin');
605
606        if (crossOrigin) {
607          crossOriginUrl = url;
608        } else {
609          crossOrigin = 'anonymous';
610
611          // Bust cache (#148) when there is not a "crossOrigin" property
612          crossOriginUrl = addTimestamp(url);
613        }
614      }
615
616      this.crossOrigin = crossOrigin;
617      this.crossOriginUrl = crossOriginUrl;
618      this.$clone = $clone = $('<img' + getCrossOrigin(crossOrigin) + ' src="' + (crossOriginUrl || url) + '">');
619
620      if (this.isImg) {
621        if ($this[0].complete) {
622          this.start();
623        } else {
624          $this.one(EVENT_LOAD, $.proxy(this.start, this));
625        }
626      } else {
627        $clone.
628          one(EVENT_LOAD, $.proxy(this.start, this)).
629          one(EVENT_ERROR, $.proxy(this.stop, this)).
630          addClass(CLASS_HIDE).
631          insertAfter($this);
632      }
633    },
634
635    start: function () {
636      var $image = this.$element;
637      var $clone = this.$clone;
638
639      if (!this.isImg) {
640        $clone.off(EVENT_ERROR, this.stop);
641        $image = $clone;
642      }
643
644      getImageSize($image[0], $.proxy(function (naturalWidth, naturalHeight) {
645        $.extend(this.image, {
646          naturalWidth: naturalWidth,
647          naturalHeight: naturalHeight,
648          aspectRatio: naturalWidth / naturalHeight
649        });
650
651        this.isLoaded = true;
652        this.build();
653      }, this));
654    },
655
656    stop: function () {
657      this.$clone.remove();
658      this.$clone = null;
659    },
660
661    build: function () {
662      var options = this.options;
663      var $this = this.$element;
664      var $clone = this.$clone;
665      var $cropper;
666      var $cropBox;
667      var $face;
668
669      if (!this.isLoaded) {
670        return;
671      }
672
673      // Unbuild first when replace
674      if (this.isBuilt) {
675        this.unbuild();
676      }
677
678      // Create cropper elements
679      this.$container = $this.parent();
680      this.$cropper = $cropper = $(Cropper.TEMPLATE);
681      this.$canvas = $cropper.find('.cropper-canvas').append($clone);
682      this.$dragBox = $cropper.find('.cropper-drag-box');
683      this.$cropBox = $cropBox = $cropper.find('.cropper-crop-box');
684      this.$viewBox = $cropper.find('.cropper-view-box');
685      this.$face = $face = $cropBox.find('.cropper-face');
686
687      // Hide the original image
688      $this.addClass(CLASS_HIDDEN).after($cropper);
689
690      // Show the clone image if is hidden
691      if (!this.isImg) {
692        $clone.removeClass(CLASS_HIDE);
693      }
694
695      this.initPreview();
696      this.bind();
697
698      options.aspectRatio = max(0, options.aspectRatio) || NaN;
699      options.viewMode = max(0, min(3, round(options.viewMode))) || 0;
700
701      if (options.autoCrop) {
702        this.isCropped = true;
703
704        if (options.modal) {
705          this.$dragBox.addClass(CLASS_MODAL);
706        }
707      } else {
708        $cropBox.addClass(CLASS_HIDDEN);
709      }
710
711      if (!options.guides) {
712        $cropBox.find('.cropper-dashed').addClass(CLASS_HIDDEN);
713      }
714
715      if (!options.center) {
716        $cropBox.find('.cropper-center').addClass(CLASS_HIDDEN);
717      }
718
719      if (options.cropBoxMovable) {
720        $face.addClass(CLASS_MOVE).data(DATA_ACTION, ACTION_ALL);
721      }
722
723      if (!options.highlight) {
724        $face.addClass(CLASS_INVISIBLE);
725      }
726
727      if (options.background) {
728        $cropper.addClass(CLASS_BG);
729      }
730
731      if (!options.cropBoxResizable) {
732        $cropBox.find('.cropper-line, .cropper-point').addClass(CLASS_HIDDEN);
733      }
734
735      this.setDragMode(options.dragMode);
736      this.render();
737      this.isBuilt = true;
738      this.setData(options.data);
739      $this.one(EVENT_BUILT, options.built);
740
741      // Trigger the built event asynchronously to keep `data('cropper')` is defined
742      this.completing = setTimeout($.proxy(function () {
743        this.trigger(EVENT_BUILT);
744        this.trigger(EVENT_CROP, this.getData());
745        this.isCompleted = true;
746      }, this), 0);
747    },
748
749    unbuild: function () {
750      if (!this.isBuilt) {
751        return;
752      }
753
754      if (!this.isCompleted) {
755        clearTimeout(this.completing);
756      }
757
758      this.isBuilt = false;
759      this.isCompleted = false;
760      this.initialImage = null;
761
762      // Clear `initialCanvas` is necessary when replace
763      this.initialCanvas = null;
764      this.initialCropBox = null;
765      this.container = null;
766      this.canvas = null;
767
768      // Clear `cropBox` is necessary when replace
769      this.cropBox = null;
770      this.unbind();
771
772      this.resetPreview();
773      this.$preview = null;
774
775      this.$viewBox = null;
776      this.$cropBox = null;
777      this.$dragBox = null;
778      this.$canvas = null;
779      this.$container = null;
780
781      this.$cropper.remove();
782      this.$cropper = null;
783    },
784
785    render: function () {
786      this.initContainer();
787      this.initCanvas();
788      this.initCropBox();
789
790      this.renderCanvas();
791
792      if (this.isCropped) {
793        this.renderCropBox();
794      }
795    },
796
797    initContainer: function () {
798      var options = this.options;
799      var $this = this.$element;
800      var $container = this.$container;
801      var $cropper = this.$cropper;
802
803      $cropper.addClass(CLASS_HIDDEN);
804      $this.removeClass(CLASS_HIDDEN);
805
806      $cropper.css((this.container = {
807        width: max($container.width(), num(options.minContainerWidth) || 200),
808        height: max($container.height(), num(options.minContainerHeight) || 100)
809      }));
810
811      $this.addClass(CLASS_HIDDEN);
812      $cropper.removeClass(CLASS_HIDDEN);
813    },
814
815    // Canvas (image wrapper)
816    initCanvas: function () {
817      var viewMode = this.options.viewMode;
818      var container = this.container;
819      var containerWidth = container.width;
820      var containerHeight = container.height;
821      var image = this.image;
822      var imageNaturalWidth = image.naturalWidth;
823      var imageNaturalHeight = image.naturalHeight;
824      var is90Degree = abs(image.rotate) === 90;
825      var naturalWidth = is90Degree ? imageNaturalHeight : imageNaturalWidth;
826      var naturalHeight = is90Degree ? imageNaturalWidth : imageNaturalHeight;
827      var aspectRatio = naturalWidth / naturalHeight;
828      var canvasWidth = containerWidth;
829      var canvasHeight = containerHeight;
830      var canvas;
831
832      if (containerHeight * aspectRatio > containerWidth) {
833        if (viewMode === 3) {
834          canvasWidth = containerHeight * aspectRatio;
835        } else {
836          canvasHeight = containerWidth / aspectRatio;
837        }
838      } else {
839        if (viewMode === 3) {
840          canvasHeight = containerWidth / aspectRatio;
841        } else {
842          canvasWidth = containerHeight * aspectRatio;
843        }
844      }
845
846      canvas = {
847        naturalWidth: naturalWidth,
848        naturalHeight: naturalHeight,
849        aspectRatio: aspectRatio,
850        width: canvasWidth,
851        height: canvasHeight
852      };
853
854      canvas.oldLeft = canvas.left = (containerWidth - canvasWidth) / 2;
855      canvas.oldTop = canvas.top = (containerHeight - canvasHeight) / 2;
856
857      this.canvas = canvas;
858      this.isLimited = (viewMode === 1 || viewMode === 2);
859      this.limitCanvas(true, true);
860      this.initialImage = $.extend({}, image);
861      this.initialCanvas = $.extend({}, canvas);
862    },
863
864    limitCanvas: function (isSizeLimited, isPositionLimited) {
865      var options = this.options;
866      var viewMode = options.viewMode;
867      var container = this.container;
868      var containerWidth = container.width;
869      var containerHeight = container.height;
870      var canvas = this.canvas;
871      var aspectRatio = canvas.aspectRatio;
872      var cropBox = this.cropBox;
873      var isCropped = this.isCropped && cropBox;
874      var minCanvasWidth;
875      var minCanvasHeight;
876      var newCanvasLeft;
877      var newCanvasTop;
878
879      if (isSizeLimited) {
880        minCanvasWidth = num(options.minCanvasWidth) || 0;
881        minCanvasHeight = num(options.minCanvasHeight) || 0;
882
883        if (viewMode) {
884          if (viewMode > 1) {
885            minCanvasWidth = max(minCanvasWidth, containerWidth);
886            minCanvasHeight = max(minCanvasHeight, containerHeight);
887
888            if (viewMode === 3) {
889              if (minCanvasHeight * aspectRatio > minCanvasWidth) {
890                minCanvasWidth = minCanvasHeight * aspectRatio;
891              } else {
892                minCanvasHeight = minCanvasWidth / aspectRatio;
893              }
894            }
895          } else {
896            if (minCanvasWidth) {
897              minCanvasWidth = max(minCanvasWidth, isCropped ? cropBox.width : 0);
898            } else if (minCanvasHeight) {
899              minCanvasHeight = max(minCanvasHeight, isCropped ? cropBox.height : 0);
900            } else if (isCropped) {
901              minCanvasWidth = cropBox.width;
902              minCanvasHeight = cropBox.height;
903
904              if (minCanvasHeight * aspectRatio > minCanvasWidth) {
905                minCanvasWidth = minCanvasHeight * aspectRatio;
906              } else {
907                minCanvasHeight = minCanvasWidth / aspectRatio;
908              }
909            }
910          }
911        }
912
913        if (minCanvasWidth && minCanvasHeight) {
914          if (minCanvasHeight * aspectRatio > minCanvasWidth) {
915            minCanvasHeight = minCanvasWidth / aspectRatio;
916          } else {
917            minCanvasWidth = minCanvasHeight * aspectRatio;
918          }
919        } else if (minCanvasWidth) {
920          minCanvasHeight = minCanvasWidth / aspectRatio;
921        } else if (minCanvasHeight) {
922          minCanvasWidth = minCanvasHeight * aspectRatio;
923        }
924
925        canvas.minWidth = minCanvasWidth;
926        canvas.minHeight = minCanvasHeight;
927        canvas.maxWidth = Infinity;
928        canvas.maxHeight = Infinity;
929      }
930
931      if (isPositionLimited) {
932        if (viewMode) {
933          newCanvasLeft = containerWidth - canvas.width;
934          newCanvasTop = containerHeight - canvas.height;
935
936          canvas.minLeft = min(0, newCanvasLeft);
937          canvas.minTop = min(0, newCanvasTop);
938          canvas.maxLeft = max(0, newCanvasLeft);
939          canvas.maxTop = max(0, newCanvasTop);
940
941          if (isCropped && this.isLimited) {
942            canvas.minLeft = min(
943              cropBox.left,
944              cropBox.left + cropBox.width - canvas.width
945            );
946            canvas.minTop = min(
947              cropBox.top,
948              cropBox.top + cropBox.height - canvas.height
949            );
950            canvas.maxLeft = cropBox.left;
951            canvas.maxTop = cropBox.top;
952
953            if (viewMode === 2) {
954              if (canvas.width >= containerWidth) {
955                canvas.minLeft = min(0, newCanvasLeft);
956                canvas.maxLeft = max(0, newCanvasLeft);
957              }
958
959              if (canvas.height >= containerHeight) {
960                canvas.minTop = min(0, newCanvasTop);
961                canvas.maxTop = max(0, newCanvasTop);
962              }
963            }
964          }
965        } else {
966          canvas.minLeft = -canvas.width;
967          canvas.minTop = -canvas.height;
968          canvas.maxLeft = containerWidth;
969          canvas.maxTop = containerHeight;
970        }
971      }
972    },
973
974    renderCanvas: function (isChanged) {
975      var canvas = this.canvas;
976      var image = this.image;
977      var rotate = image.rotate;
978      var naturalWidth = image.naturalWidth;
979      var naturalHeight = image.naturalHeight;
980      var aspectRatio;
981      var rotated;
982
983      if (this.isRotated) {
984        this.isRotated = false;
985
986        // Computes rotated sizes with image sizes
987        rotated = getRotatedSizes({
988          width: image.width,
989          height: image.height,
990          degree: rotate
991        });
992
993        aspectRatio = rotated.width / rotated.height;
994
995        if (aspectRatio !== canvas.aspectRatio) {
996          canvas.left -= (rotated.width - canvas.width) / 2;
997          canvas.top -= (rotated.height - canvas.height) / 2;
998          canvas.width = rotated.width;
999          canvas.height = rotated.height;
1000          canvas.aspectRatio = aspectRatio;
1001          canvas.naturalWidth = naturalWidth;
1002          canvas.naturalHeight = naturalHeight;
1003
1004          // Computes rotated sizes with natural image sizes
1005          if (rotate % 180) {
1006            rotated = getRotatedSizes({
1007              width: naturalWidth,
1008              height: naturalHeight,
1009              degree: rotate
1010            });
1011
1012            canvas.naturalWidth = rotated.width;
1013            canvas.naturalHeight = rotated.height;
1014          }
1015
1016          this.limitCanvas(true, false);
1017        }
1018      }
1019
1020      if (canvas.width > canvas.maxWidth || canvas.width < canvas.minWidth) {
1021        canvas.left = canvas.oldLeft;
1022      }
1023
1024      if (canvas.height > canvas.maxHeight || canvas.height < canvas.minHeight) {
1025        canvas.top = canvas.oldTop;
1026      }
1027
1028      canvas.width = min(max(canvas.width, canvas.minWidth), canvas.maxWidth);
1029      canvas.height = min(max(canvas.height, canvas.minHeight), canvas.maxHeight);
1030
1031      this.limitCanvas(false, true);
1032
1033      canvas.oldLeft = canvas.left = min(max(canvas.left, canvas.minLeft), canvas.maxLeft);
1034      canvas.oldTop = canvas.top = min(max(canvas.top, canvas.minTop), canvas.maxTop);
1035
1036      this.$canvas.css({
1037        width: canvas.width,
1038        height: canvas.height,
1039        left: canvas.left,
1040        top: canvas.top
1041      });
1042
1043      this.renderImage();
1044
1045      if (this.isCropped && this.isLimited) {
1046        this.limitCropBox(true, true);
1047      }
1048
1049      if (isChanged) {
1050        this.output();
1051      }
1052    },
1053
1054    renderImage: function (isChanged) {
1055      var canvas = this.canvas;
1056      var image = this.image;
1057      var reversed;
1058
1059      if (image.rotate) {
1060        reversed = getRotatedSizes({
1061          width: canvas.width,
1062          height: canvas.height,
1063          degree: image.rotate,
1064          aspectRatio: image.aspectRatio
1065        }, true);
1066      }
1067
1068      $.extend(image, reversed ? {
1069        width: reversed.width,
1070        height: reversed.height,
1071        left: (canvas.width - reversed.width) / 2,
1072        top: (canvas.height - reversed.height) / 2
1073      } : {
1074        width: canvas.width,
1075        height: canvas.height,
1076        left: 0,
1077        top: 0
1078      });
1079
1080      this.$clone.css({
1081        width: image.width,
1082        height: image.height,
1083        marginLeft: image.left,
1084        marginTop: image.top,
1085        transform: getTransform(image)
1086      });
1087
1088      if (isChanged) {
1089        this.output();
1090      }
1091    },
1092
1093    initCropBox: function () {
1094      var options = this.options;
1095      var canvas = this.canvas;
1096      var aspectRatio = options.aspectRatio;
1097      var autoCropArea = num(options.autoCropArea) || 0.8;
1098      var cropBox = {
1099            width: canvas.width,
1100            height: canvas.height
1101          };
1102
1103      if (aspectRatio) {
1104        if (canvas.height * aspectRatio > canvas.width) {
1105          cropBox.height = cropBox.width / aspectRatio;
1106        } else {
1107          cropBox.width = cropBox.height * aspectRatio;
1108        }
1109      }
1110
1111      this.cropBox = cropBox;
1112      this.limitCropBox(true, true);
1113
1114      // Initialize auto crop area
1115      cropBox.width = min(max(cropBox.width, cropBox.minWidth), cropBox.maxWidth);
1116      cropBox.height = min(max(cropBox.height, cropBox.minHeight), cropBox.maxHeight);
1117
1118      // The width of auto crop area must large than "minWidth", and the height too. (#164)
1119      cropBox.width = max(cropBox.minWidth, cropBox.width * autoCropArea);
1120      cropBox.height = max(cropBox.minHeight, cropBox.height * autoCropArea);
1121      cropBox.oldLeft = cropBox.left = canvas.left + (canvas.width - cropBox.width) / 2;
1122      cropBox.oldTop = cropBox.top = canvas.top + (canvas.height - cropBox.height) / 2;
1123
1124      this.initialCropBox = $.extend({}, cropBox);
1125    },
1126
1127    limitCropBox: function (isSizeLimited, isPositionLimited) {
1128      var options = this.options;
1129      var aspectRatio = options.aspectRatio;
1130      var container = this.container;
1131      var containerWidth = container.width;
1132      var containerHeight = container.height;
1133      var canvas = this.canvas;
1134      var cropBox = this.cropBox;
1135      var isLimited = this.isLimited;
1136      var minCropBoxWidth;
1137      var minCropBoxHeight;
1138      var maxCropBoxWidth;
1139      var maxCropBoxHeight;
1140
1141      if (isSizeLimited) {
1142        minCropBoxWidth = num(options.minCropBoxWidth) || 0;
1143        minCropBoxHeight = num(options.minCropBoxHeight) || 0;
1144
1145        // The min/maxCropBoxWidth/Height must be less than containerWidth/Height
1146        minCropBoxWidth = min(minCropBoxWidth, containerWidth);
1147        minCropBoxHeight = min(minCropBoxHeight, containerHeight);
1148        maxCropBoxWidth = min(containerWidth, isLimited ? canvas.width : containerWidth);
1149        maxCropBoxHeight = min(containerHeight, isLimited ? canvas.height : containerHeight);
1150
1151        if (aspectRatio) {
1152          if (minCropBoxWidth && minCropBoxHeight) {
1153            if (minCropBoxHeight * aspectRatio > minCropBoxWidth) {
1154              minCropBoxHeight = minCropBoxWidth / aspectRatio;
1155            } else {
1156              minCropBoxWidth = minCropBoxHeight * aspectRatio;
1157            }
1158          } else if (minCropBoxWidth) {
1159            minCropBoxHeight = minCropBoxWidth / aspectRatio;
1160          } else if (minCropBoxHeight) {
1161            minCropBoxWidth = minCropBoxHeight * aspectRatio;
1162          }
1163
1164          if (maxCropBoxHeight * aspectRatio > maxCropBoxWidth) {
1165            maxCropBoxHeight = maxCropBoxWidth / aspectRatio;
1166          } else {
1167            maxCropBoxWidth = maxCropBoxHeight * aspectRatio;
1168          }
1169        }
1170
1171        // The minWidth/Height must be less than maxWidth/Height
1172        cropBox.minWidth = min(minCropBoxWidth, maxCropBoxWidth);
1173        cropBox.minHeight = min(minCropBoxHeight, maxCropBoxHeight);
1174        cropBox.maxWidth = maxCropBoxWidth;
1175        cropBox.maxHeight = maxCropBoxHeight;
1176      }
1177
1178      if (isPositionLimited) {
1179        if (isLimited) {
1180          cropBox.minLeft = max(0, canvas.left);
1181          cropBox.minTop = max(0, canvas.top);
1182          cropBox.maxLeft = min(containerWidth, canvas.left + canvas.width) - cropBox.width;
1183          cropBox.maxTop = min(containerHeight, canvas.top + canvas.height) - cropBox.height;
1184        } else {
1185          cropBox.minLeft = 0;
1186          cropBox.minTop = 0;
1187          cropBox.maxLeft = containerWidth - cropBox.width;
1188          cropBox.maxTop = containerHeight - cropBox.height;
1189        }
1190      }
1191    },
1192
1193    renderCropBox: function () {
1194      var options = this.options;
1195      var container = this.container;
1196      var containerWidth = container.width;
1197      var containerHeight = container.height;
1198      var cropBox = this.cropBox;
1199
1200      if (cropBox.width > cropBox.maxWidth || cropBox.width < cropBox.minWidth) {
1201        cropBox.left = cropBox.oldLeft;
1202      }
1203
1204      if (cropBox.height > cropBox.maxHeight || cropBox.height < cropBox.minHeight) {
1205        cropBox.top = cropBox.oldTop;
1206      }
1207
1208      cropBox.width = min(max(cropBox.width, cropBox.minWidth), cropBox.maxWidth);
1209      cropBox.height = min(max(cropBox.height, cropBox.minHeight), cropBox.maxHeight);
1210
1211      this.limitCropBox(false, true);
1212
1213      cropBox.oldLeft = cropBox.left = min(max(cropBox.left, cropBox.minLeft), cropBox.maxLeft);
1214      cropBox.oldTop = cropBox.top = min(max(cropBox.top, cropBox.minTop), cropBox.maxTop);
1215
1216      if (options.movable && options.cropBoxMovable) {
1217
1218        // Turn to move the canvas when the crop box is equal to the container
1219        this.$face.data(DATA_ACTION, (cropBox.width === containerWidth && cropBox.height === containerHeight) ? ACTION_MOVE : ACTION_ALL);
1220      }
1221
1222      this.$cropBox.css({
1223        width: cropBox.width,
1224        height: cropBox.height,
1225        left: cropBox.left,
1226        top: cropBox.top
1227      });
1228
1229      if (this.isCropped && this.isLimited) {
1230        this.limitCanvas(true, true);
1231      }
1232
1233      if (!this.isDisabled) {
1234        this.output();
1235      }
1236    },
1237
1238    output: function () {
1239      this.preview();
1240
1241      if (this.isCompleted) {
1242        this.trigger(EVENT_CROP, this.getData());
1243      }
1244    },
1245
1246    initPreview: function () {
1247      var crossOrigin = getCrossOrigin(this.crossOrigin);
1248      var url = crossOrigin ? this.crossOriginUrl : this.url;
1249      var $clone2;
1250
1251      this.$preview = $(this.options.preview);
1252      this.$clone2 = $clone2 = $('<img' + crossOrigin + ' src="' + url + '">');
1253      this.$viewBox.html($clone2);
1254      this.$preview.each(function () {
1255        var $this = $(this);
1256
1257        // Save the original size for recover
1258        $this.data(DATA_PREVIEW, {
1259          width: $this.width(),
1260          height: $this.height(),
1261          html: $this.html()
1262        });
1263
1264        /**
1265         * Override img element styles
1266         * Add `display:block` to avoid margin top issue
1267         * (Occur only when margin-top <= -height)
1268         */
1269        $this.html(
1270          '<img' + crossOrigin + ' src="' + url + '" style="' +
1271          'display:block;width:100%;height:auto;' +
1272          'min-width:0!important;min-height:0!important;' +
1273          'max-width:none!important;max-height:none!important;' +
1274          'image-orientation:0deg!important;">'
1275        );
1276      });
1277    },
1278
1279    resetPreview: function () {
1280      this.$preview.each(function () {
1281        var $this = $(this);
1282        var data = $this.data(DATA_PREVIEW);
1283
1284        $this.css({
1285          width: data.width,
1286          height: data.height
1287        }).html(data.html).removeData(DATA_PREVIEW);
1288      });
1289    },
1290
1291    preview: function () {
1292      var image = this.image;
1293      var canvas = this.canvas;
1294      var cropBox = this.cropBox;
1295      var cropBoxWidth = cropBox.width;
1296      var cropBoxHeight = cropBox.height;
1297      var width = image.width;
1298      var height = image.height;
1299      var left = cropBox.left - canvas.left - image.left;
1300      var top = cropBox.top - canvas.top - image.top;
1301
1302      if (!this.isCropped || this.isDisabled) {
1303        return;
1304      }
1305
1306      this.$clone2.css({
1307        width: width,
1308        height: height,
1309        marginLeft: -left,
1310        marginTop: -top,
1311        transform: getTransform(image)
1312      });
1313
1314      this.$preview.each(function () {
1315        var $this = $(this);
1316        var data = $this.data(DATA_PREVIEW);
1317        var originalWidth = data.width;
1318        var originalHeight = data.height;
1319        var newWidth = originalWidth;
1320        var newHeight = originalHeight;
1321        var ratio = 1;
1322
1323        if (cropBoxWidth) {
1324          ratio = originalWidth / cropBoxWidth;
1325          newHeight = cropBoxHeight * ratio;
1326        }
1327
1328        if (cropBoxHeight && newHeight > originalHeight) {
1329          ratio = originalHeight / cropBoxHeight;
1330          newWidth = cropBoxWidth * ratio;
1331          newHeight = originalHeight;
1332        }
1333
1334        $this.css({
1335          width: newWidth,
1336          height: newHeight
1337        }).find('img').css({
1338          width: width * ratio,
1339          height: height * ratio,
1340          marginLeft: -left * ratio,
1341          marginTop: -top * ratio,
1342          transform: getTransform(image)
1343        });
1344      });
1345    },
1346
1347    bind: function () {
1348      var options = this.options;
1349      var $this = this.$element;
1350      var $cropper = this.$cropper;
1351
1352      if ($.isFunction(options.cropstart)) {
1353        $this.on(EVENT_CROP_START, options.cropstart);
1354      }
1355
1356      if ($.isFunction(options.cropmove)) {
1357        $this.on(EVENT_CROP_MOVE, options.cropmove);
1358      }
1359
1360      if ($.isFunction(options.cropend)) {
1361        $this.on(EVENT_CROP_END, options.cropend);
1362      }
1363
1364      if ($.isFunction(options.crop)) {
1365        $this.on(EVENT_CROP, options.crop);
1366      }
1367
1368      if ($.isFunction(options.zoom)) {
1369        $this.on(EVENT_ZOOM, options.zoom);
1370      }
1371
1372      $cropper.on(EVENT_MOUSE_DOWN, $.proxy(this.cropStart, this));
1373
1374      if (options.zoomable && options.zoomOnWheel) {
1375        $cropper.on(EVENT_WHEEL, $.proxy(this.wheel, this));
1376      }
1377
1378      if (options.toggleDragModeOnDblclick) {
1379        $cropper.on(EVENT_DBLCLICK, $.proxy(this.dblclick, this));
1380      }
1381
1382      $document.
1383        on(EVENT_MOUSE_MOVE, (this._cropMove = proxy(this.cropMove, this))).
1384        on(EVENT_MOUSE_UP, (this._cropEnd = proxy(this.cropEnd, this)));
1385
1386      if (options.responsive) {
1387        $window.on(EVENT_RESIZE, (this._resize = proxy(this.resize, this)));
1388      }
1389    },
1390
1391    unbind: function () {
1392      var options = this.options;
1393      var $this = this.$element;
1394      var $cropper = this.$cropper;
1395
1396      if ($.isFunction(options.cropstart)) {
1397        $this.off(EVENT_CROP_START, options.cropstart);
1398      }
1399
1400      if ($.isFunction(options.cropmove)) {
1401        $this.off(EVENT_CROP_MOVE, options.cropmove);
1402      }
1403
1404      if ($.isFunction(options.cropend)) {
1405        $this.off(EVENT_CROP_END, options.cropend);
1406      }
1407
1408      if ($.isFunction(options.crop)) {
1409        $this.off(EVENT_CROP, options.crop);
1410      }
1411
1412      if ($.isFunction(options.zoom)) {
1413        $this.off(EVENT_ZOOM, options.zoom);
1414      }
1415
1416      $cropper.off(EVENT_MOUSE_DOWN, this.cropStart);
1417
1418      if (options.zoomable && options.zoomOnWheel) {
1419        $cropper.off(EVENT_WHEEL, this.wheel);
1420      }
1421
1422      if (options.toggleDragModeOnDblclick) {
1423        $cropper.off(EVENT_DBLCLICK, this.dblclick);
1424      }
1425
1426      $document.
1427        off(EVENT_MOUSE_MOVE, this._cropMove).
1428        off(EVENT_MOUSE_UP, this._cropEnd);
1429
1430      if (options.responsive) {
1431        $window.off(EVENT_RESIZE, this._resize);
1432      }
1433    },
1434
1435    resize: function () {
1436      var restore = this.options.restore;
1437      var $container = this.$container;
1438      var container = this.container;
1439      var canvasData;
1440      var cropBoxData;
1441      var ratio;
1442
1443      // Check `container` is necessary for IE8
1444      if (this.isDisabled || !container) {
1445        return;
1446      }
1447
1448      ratio = $container.width() / container.width;
1449
1450      // Resize when width changed or height changed
1451      if (ratio !== 1 || $container.height() !== container.height) {
1452        if (restore) {
1453          canvasData = this.getCanvasData();
1454          cropBoxData = this.getCropBoxData();
1455        }
1456
1457        this.render();
1458
1459        if (restore) {
1460          this.setCanvasData($.each(canvasData, function (i, n) {
1461            canvasData[i] = n * ratio;
1462          }));
1463          this.setCropBoxData($.each(cropBoxData, function (i, n) {
1464            cropBoxData[i] = n * ratio;
1465          }));
1466        }
1467      }
1468    },
1469
1470    dblclick: function () {
1471      if (this.isDisabled) {
1472        return;
1473      }
1474
1475      if (this.$dragBox.hasClass(CLASS_CROP)) {
1476        this.setDragMode(ACTION_MOVE);
1477      } else {
1478        this.setDragMode(ACTION_CROP);
1479      }
1480    },
1481
1482    wheel: function (event) {
1483      var e = event.originalEvent || event;
1484      var ratio = num(this.options.wheelZoomRatio) || 0.1;
1485      var delta = 1;
1486
1487      if (this.isDisabled) {
1488        return;
1489      }
1490
1491      event.preventDefault();
1492
1493      // Limit wheel speed to prevent zoom too fast
1494      if (this.wheeling) {
1495        return;
1496      }
1497
1498      this.wheeling = true;
1499
1500      setTimeout($.proxy(function () {
1501        this.wheeling = false;
1502      }, this), 50);
1503
1504      if (e.deltaY) {
1505        delta = e.deltaY > 0 ? 1 : -1;
1506      } else if (e.wheelDelta) {
1507        delta = -e.wheelDelta / 120;
1508      } else if (e.detail) {
1509        delta = e.detail > 0 ? 1 : -1;
1510      }
1511
1512      this.zoom(-delta * ratio, event);
1513    },
1514
1515    cropStart: function (event) {
1516      var options = this.options;
1517      var originalEvent = event.originalEvent;
1518      var touches = originalEvent && originalEvent.touches;
1519      var e = event;
1520      var touchesLength;
1521      var action;
1522
1523      if (this.isDisabled) {
1524        return;
1525      }
1526
1527      if (touches) {
1528        touchesLength = touches.length;
1529
1530        if (touchesLength > 1) {
1531          if (options.zoomable && options.zoomOnTouch && touchesLength === 2) {
1532            e = touches[1];
1533            this.startX2 = e.pageX;
1534            this.startY2 = e.pageY;
1535            action = ACTION_ZOOM;
1536          } else {
1537            return;
1538          }
1539        }
1540
1541        e = touches[0];
1542      }
1543
1544      action = action || $(e.target).data(DATA_ACTION);
1545
1546      if (REGEXP_ACTIONS.test(action)) {
1547        if (this.trigger(EVENT_CROP_START, {
1548          originalEvent: originalEvent,
1549          action: action
1550        }).isDefaultPrevented()) {
1551          return;
1552        }
1553
1554        event.preventDefault();
1555
1556        this.action = action;
1557        this.cropping = false;
1558
1559        // IE8  has `event.pageX/Y`, but not `event.originalEvent.pageX/Y`
1560        // IE10 has `event.originalEvent.pageX/Y`, but not `event.pageX/Y`
1561        this.startX = e.pageX || originalEvent && originalEvent.pageX;
1562        this.startY = e.pageY || originalEvent && originalEvent.pageY;
1563
1564        if (action === ACTION_CROP) {
1565          this.cropping = true;
1566          this.$dragBox.addClass(CLASS_MODAL);
1567        }
1568      }
1569    },
1570
1571    cropMove: function (event) {
1572      var options = this.options;
1573      var originalEvent = event.originalEvent;
1574      var touches = originalEvent && originalEvent.touches;
1575      var e = event;
1576      var action = this.action;
1577      var touchesLength;
1578
1579      if (this.isDisabled) {
1580        return;
1581      }
1582
1583      if (touches) {
1584        touchesLength = touches.length;
1585
1586        if (touchesLength > 1) {
1587          if (options.zoomable && options.zoomOnTouch && touchesLength === 2) {
1588            e = touches[1];
1589            this.endX2 = e.pageX;
1590            this.endY2 = e.pageY;
1591          } else {
1592            return;
1593          }
1594        }
1595
1596        e = touches[0];
1597      }
1598
1599      if (action) {
1600        if (this.trigger(EVENT_CROP_MOVE, {
1601          originalEvent: originalEvent,
1602          action: action
1603        }).isDefaultPrevented()) {
1604          return;
1605        }
1606
1607        event.preventDefault();
1608
1609        this.endX = e.pageX || originalEvent && originalEvent.pageX;
1610        this.endY = e.pageY || originalEvent && originalEvent.pageY;
1611
1612        this.change(e.shiftKey, action === ACTION_ZOOM ? event : null);
1613      }
1614    },
1615
1616    cropEnd: function (event) {
1617      var originalEvent = event.originalEvent;
1618      var action = this.action;
1619
1620      if (this.isDisabled) {
1621        return;
1622      }
1623
1624      if (action) {
1625        event.preventDefault();
1626
1627        if (this.cropping) {
1628          this.cropping = false;
1629          this.$dragBox.toggleClass(CLASS_MODAL, this.isCropped && this.options.modal);
1630        }
1631
1632        this.action = '';
1633
1634        this.trigger(EVENT_CROP_END, {
1635          originalEvent: originalEvent,
1636          action: action
1637        });
1638      }
1639    },
1640
1641    change: function (shiftKey, event) {
1642      var options = this.options;
1643      var aspectRatio = options.aspectRatio;
1644      var action = this.action;
1645      var container = this.container;
1646      var canvas = this.canvas;
1647      var cropBox = this.cropBox;
1648      var width = cropBox.width;
1649      var height = cropBox.height;
1650      var left = cropBox.left;
1651      var top = cropBox.top;
1652      var right = left + width;
1653      var bottom = top + height;
1654      var minLeft = 0;
1655      var minTop = 0;
1656      var maxWidth = container.width;
1657      var maxHeight = container.height;
1658      var renderable = true;
1659      var offset;
1660      var range;
1661
1662      // Locking aspect ratio in "free mode" by holding shift key (#259)
1663      if (!aspectRatio && shiftKey) {
1664        aspectRatio = width && height ? width / height : 1;
1665      }
1666
1667      if (this.isLimited) {
1668        minLeft = cropBox.minLeft;
1669        minTop = cropBox.minTop;
1670        maxWidth = minLeft + min(container.width, canvas.width, canvas.left + canvas.width);
1671        maxHeight = minTop + min(container.height, canvas.height, canvas.top + canvas.height);
1672      }
1673
1674      range = {
1675        x: this.endX - this.startX,
1676        y: this.endY - this.startY
1677      };
1678
1679      if (aspectRatio) {
1680        range.X = range.y * aspectRatio;
1681        range.Y = range.x / aspectRatio;
1682      }
1683
1684      switch (action) {
1685        // Move crop box
1686        case ACTION_ALL:
1687          left += range.x;
1688          top += range.y;
1689          break;
1690
1691        // Resize crop box
1692        case ACTION_EAST:
1693          if (range.x >= 0 && (right >= maxWidth || aspectRatio &&
1694            (top <= minTop || bottom >= maxHeight))) {
1695
1696            renderable = false;
1697            break;
1698          }
1699
1700          width += range.x;
1701
1702          if (aspectRatio) {
1703            height = width / aspectRatio;
1704            top -= range.Y / 2;
1705          }
1706
1707          if (width < 0) {
1708            action = ACTION_WEST;
1709            width = 0;
1710          }
1711
1712          break;
1713
1714        case ACTION_NORTH:
1715          if (range.y <= 0 && (top <= minTop || aspectRatio &&
1716            (left <= minLeft || right >= maxWidth))) {
1717
1718            renderable = false;
1719            break;
1720          }
1721
1722          height -= range.y;
1723          top += range.y;
1724
1725          if (aspectRatio) {
1726            width = height * aspectRatio;
1727            left += range.X / 2;
1728          }
1729
1730          if (height < 0) {
1731            action = ACTION_SOUTH;
1732            height = 0;
1733          }
1734
1735          break;
1736
1737        case ACTION_WEST:
1738          if (range.x <= 0 && (left <= minLeft || aspectRatio &&
1739            (top <= minTop || bottom >= maxHeight))) {
1740
1741            renderable = false;
1742            break;
1743          }
1744
1745          width -= range.x;
1746          left += range.x;
1747
1748          if (aspectRatio) {
1749            height = width / aspectRatio;
1750            top += range.Y / 2;
1751          }
1752
1753          if (width < 0) {
1754            action = ACTION_EAST;
1755            width = 0;
1756          }
1757
1758          break;
1759
1760        case ACTION_SOUTH:
1761          if (range.y >= 0 && (bottom >= maxHeight || aspectRatio &&
1762            (left <= minLeft || right >= maxWidth))) {
1763
1764            renderable = false;
1765            break;
1766          }
1767
1768          height += range.y;
1769
1770          if (aspectRatio) {
1771            width = height * aspectRatio;
1772            left -= range.X / 2;
1773          }
1774
1775          if (height < 0) {
1776            action = ACTION_NORTH;
1777            height = 0;
1778          }
1779
1780          break;
1781
1782        case ACTION_NORTH_EAST:
1783          if (aspectRatio) {
1784            if (range.y <= 0 && (top <= minTop || right >= maxWidth)) {
1785              renderable = false;
1786              break;
1787            }
1788
1789            height -= range.y;
1790            top += range.y;
1791            width = height * aspectRatio;
1792          } else {
1793            if (range.x >= 0) {
1794              if (right < maxWidth) {
1795                width += range.x;
1796              } else if (range.y <= 0 && top <= minTop) {
1797                renderable = false;
1798              }
1799            } else {
1800              width += range.x;
1801            }
1802
1803            if (range.y <= 0) {
1804              if (top > minTop) {
1805                height -= range.y;
1806                top += range.y;
1807              }
1808            } else {
1809              height -= range.y;
1810              top += range.y;
1811            }
1812          }
1813
1814          if (width < 0 && height < 0) {
1815            action = ACTION_SOUTH_WEST;
1816            height = 0;
1817            width = 0;
1818          } else if (width < 0) {
1819            action = ACTION_NORTH_WEST;
1820            width = 0;
1821          } else if (height < 0) {
1822            action = ACTION_SOUTH_EAST;
1823            height = 0;
1824          }
1825
1826          break;
1827
1828        case ACTION_NORTH_WEST:
1829          if (aspectRatio) {
1830            if (range.y <= 0 && (top <= minTop || left <= minLeft)) {
1831              renderable = false;
1832              break;
1833            }
1834
1835            height -= range.y;
1836            top += range.y;
1837            width = height * aspectRatio;
1838            left += range.X;
1839          } else {
1840            if (range.x <= 0) {
1841              if (left > minLeft) {
1842                width -= range.x;
1843                left += range.x;
1844              } else if (range.y <= 0 && top <= minTop) {
1845                renderable = false;
1846              }
1847            } else {
1848              width -= range.x;
1849              left += range.x;
1850            }
1851
1852            if (range.y <= 0) {
1853              if (top > minTop) {
1854                height -= range.y;
1855                top += range.y;
1856              }
1857            } else {
1858              height -= range.y;
1859              top += range.y;
1860            }
1861          }
1862
1863          if (width < 0 && height < 0) {
1864            action = ACTION_SOUTH_EAST;
1865            height = 0;
1866            width = 0;
1867          } else if (width < 0) {
1868            action = ACTION_NORTH_EAST;
1869            width = 0;
1870          } else if (height < 0) {
1871            action = ACTION_SOUTH_WEST;
1872            height = 0;
1873          }
1874
1875          break;
1876
1877        case ACTION_SOUTH_WEST:
1878          if (aspectRatio) {
1879            if (range.x <= 0 && (left <= minLeft || bottom >= maxHeight)) {
1880              renderable = false;
1881              break;
1882            }
1883
1884            width -= range.x;
1885            left += range.x;
1886            height = width / aspectRatio;
1887          } else {
1888            if (range.x <= 0) {
1889              if (left > minLeft) {
1890                width -= range.x;
1891                left += range.x;
1892              } else if (range.y >= 0 && bottom >= maxHeight) {
1893                renderable = false;
1894              }
1895            } else {
1896              width -= range.x;
1897              left += range.x;
1898            }
1899
1900            if (range.y >= 0) {
1901              if (bottom < maxHeight) {
1902                height += range.y;
1903              }
1904            } else {
1905              height += range.y;
1906            }
1907          }
1908
1909          if (width < 0 && height < 0) {
1910            action = ACTION_NORTH_EAST;
1911            height = 0;
1912            width = 0;
1913          } else if (width < 0) {
1914            action = ACTION_SOUTH_EAST;
1915            width = 0;
1916          } else if (height < 0) {
1917            action = ACTION_NORTH_WEST;
1918            height = 0;
1919          }
1920
1921          break;
1922
1923        case ACTION_SOUTH_EAST:
1924          if (aspectRatio) {
1925            if (range.x >= 0 && (right >= maxWidth || bottom >= maxHeight)) {
1926              renderable = false;
1927              break;
1928            }
1929
1930            width += range.x;
1931            height = width / aspectRatio;
1932          } else {
1933            if (range.x >= 0) {
1934              if (right < maxWidth) {
1935                width += range.x;
1936              } else if (range.y >= 0 && bottom >= maxHeight) {
1937                renderable = false;
1938              }
1939            } else {
1940              width += range.x;
1941            }
1942
1943            if (range.y >= 0) {
1944              if (bottom < maxHeight) {
1945                height += range.y;
1946              }
1947            } else {
1948              height += range.y;
1949            }
1950          }
1951
1952          if (width < 0 && height < 0) {
1953            action = ACTION_NORTH_WEST;
1954            height = 0;
1955            width = 0;
1956          } else if (width < 0) {
1957            action = ACTION_SOUTH_WEST;
1958            width = 0;
1959          } else if (height < 0) {
1960            action = ACTION_NORTH_EAST;
1961            height = 0;
1962          }
1963
1964          break;
1965
1966        // Move canvas
1967        case ACTION_MOVE:
1968          this.move(range.x, range.y);
1969          renderable = false;
1970          break;
1971
1972        // Zoom canvas
1973        case ACTION_ZOOM:
1974          this.zoom((function (x1, y1, x2, y2) {
1975            var z1 = sqrt(x1 * x1 + y1 * y1);
1976            var z2 = sqrt(x2 * x2 + y2 * y2);
1977
1978            return (z2 - z1) / z1;
1979          })(
1980            abs(this.startX - this.startX2),
1981            abs(this.startY - this.startY2),
1982            abs(this.endX - this.endX2),
1983            abs(this.endY - this.endY2)
1984          ), event);
1985          this.startX2 = this.endX2;
1986          this.startY2 = this.endY2;
1987          renderable = false;
1988          break;
1989
1990        // Create crop box
1991        case ACTION_CROP:
1992          if (!range.x || !range.y) {
1993            renderable = false;
1994            break;
1995          }
1996
1997          offset = this.$cropper.offset();
1998          left = this.startX - offset.left;
1999          top = this.startY - offset.top;
2000          width = cropBox.minWidth;
2001          height = cropBox.minHeight;
2002
2003          if (range.x > 0) {
2004            action = range.y > 0 ? ACTION_SOUTH_EAST : ACTION_NORTH_EAST;
2005          } else if (range.x < 0) {
2006            left -= width;
2007            action = range.y > 0 ? ACTION_SOUTH_WEST : ACTION_NORTH_WEST;
2008          }
2009
2010          if (range.y < 0) {
2011            top -= height;
2012          }
2013
2014          // Show the crop box if is hidden
2015          if (!this.isCropped) {
2016            this.$cropBox.removeClass(CLASS_HIDDEN);
2017            this.isCropped = true;
2018
2019            if (this.isLimited) {
2020              this.limitCropBox(true, true);
2021            }
2022          }
2023
2024          break;
2025
2026        // No default
2027      }
2028
2029      if (renderable) {
2030        cropBox.width = width;
2031        cropBox.height = height;
2032        cropBox.left = left;
2033        cropBox.top = top;
2034        this.action = action;
2035
2036        this.renderCropBox();
2037      }
2038
2039      // Override
2040      this.startX = this.endX;
2041      this.startY = this.endY;
2042    },
2043
2044    // Show the crop box manually
2045    crop: function () {
2046      if (!this.isBuilt || this.isDisabled) {
2047        return;
2048      }
2049
2050      if (!this.isCropped) {
2051        this.isCropped = true;
2052        this.limitCropBox(true, true);
2053
2054        if (this.options.modal) {
2055          this.$dragBox.addClass(CLASS_MODAL);
2056        }
2057
2058        this.$cropBox.removeClass(CLASS_HIDDEN);
2059      }
2060
2061      this.setCropBoxData(this.initialCropBox);
2062    },
2063
2064    // Reset the image and crop box to their initial states
2065    reset: function () {
2066      if (!this.isBuilt || this.isDisabled) {
2067        return;
2068      }
2069
2070      this.image = $.extend({}, this.initialImage);
2071      this.canvas = $.extend({}, this.initialCanvas);
2072      this.cropBox = $.extend({}, this.initialCropBox);
2073
2074      this.renderCanvas();
2075
2076      if (this.isCropped) {
2077        this.renderCropBox();
2078      }
2079    },
2080
2081    // Clear the crop box
2082    clear: function () {
2083      if (!this.isCropped || this.isDisabled) {
2084        return;
2085      }
2086
2087      $.extend(this.cropBox, {
2088        left: 0,
2089        top: 0,
2090        width: 0,
2091        height: 0
2092      });
2093
2094      this.isCropped = false;
2095      this.renderCropBox();
2096
2097      this.limitCanvas(true, true);
2098
2099      // Render canvas after crop box rendered
2100      this.renderCanvas();
2101
2102      this.$dragBox.removeClass(CLASS_MODAL);
2103      this.$cropBox.addClass(CLASS_HIDDEN);
2104    },
2105
2106    /**
2107     * Replace the image's src and rebuild the cropper
2108     *
2109     * @param {String} url
2110     * @param {Boolean} onlyColorChanged (optional)
2111     */
2112    replace: function (url, onlyColorChanged) {
2113      if (!this.isDisabled && url) {
2114        if (this.isImg) {
2115          this.$element.attr('src', url);
2116        }
2117
2118        if (onlyColorChanged) {
2119          this.url = url;
2120          this.$clone.attr('src', url);
2121
2122          if (this.isBuilt) {
2123            this.$preview.find('img').add(this.$clone2).attr('src', url);
2124          }
2125        } else {
2126          if (this.isImg) {
2127            this.isReplaced = true;
2128          }
2129
2130          // Clear previous data
2131          this.options.data = null;
2132          this.load(url);
2133        }
2134      }
2135    },
2136
2137    // Enable (unfreeze) the cropper
2138    enable: function () {
2139      if (this.isBuilt) {
2140        this.isDisabled = false;
2141        this.$cropper.removeClass(CLASS_DISABLED);
2142      }
2143    },
2144
2145    // Disable (freeze) the cropper
2146    disable: function () {
2147      if (this.isBuilt) {
2148        this.isDisabled = true;
2149        this.$cropper.addClass(CLASS_DISABLED);
2150      }
2151    },
2152
2153    // Destroy the cropper and remove the instance from the image
2154    destroy: function () {
2155      var $this = this.$element;
2156
2157      if (this.isLoaded) {
2158        if (this.isImg && this.isReplaced) {
2159          $this.attr('src', this.originalUrl);
2160        }
2161
2162        this.unbuild();
2163        $this.removeClass(CLASS_HIDDEN);
2164      } else {
2165        if (this.isImg) {
2166          $this.off(EVENT_LOAD, this.start);
2167        } else if (this.$clone) {
2168          this.$clone.remove();
2169        }
2170      }
2171
2172      $this.removeData(NAMESPACE);
2173    },
2174
2175    /**
2176     * Move the canvas with relative offsets
2177     *
2178     * @param {Number} offsetX
2179     * @param {Number} offsetY (optional)
2180     */
2181    move: function (offsetX, offsetY) {
2182      var canvas = this.canvas;
2183
2184      this.moveTo(
2185        isUndefined(offsetX) ? offsetX : canvas.left + num(offsetX),
2186        isUndefined(offsetY) ? offsetY : canvas.top + num(offsetY)
2187      );
2188    },
2189
2190    /**
2191     * Move the canvas to an absolute point
2192     *
2193     * @param {Number} x
2194     * @param {Number} y (optional)
2195     */
2196    moveTo: function (x, y) {
2197      var canvas = this.canvas;
2198      var isChanged = false;
2199
2200      // If "y" is not present, its default value is "x"
2201      if (isUndefined(y)) {
2202        y = x;
2203      }
2204
2205      x = num(x);
2206      y = num(y);
2207
2208      if (this.isBuilt && !this.isDisabled && this.options.movable) {
2209        if (isNumber(x)) {
2210          canvas.left = x;
2211          isChanged = true;
2212        }
2213
2214        if (isNumber(y)) {
2215          canvas.top = y;
2216          isChanged = true;
2217        }
2218
2219        if (isChanged) {
2220          this.renderCanvas(true);
2221        }
2222      }
2223    },
2224
2225    /**
2226     * Zoom the canvas with a relative ratio
2227     *
2228     * @param {Number} ratio
2229     * @param {jQuery Event} _event (private)
2230     */
2231    zoom: function (ratio, _event) {
2232      var canvas = this.canvas;
2233
2234      ratio = num(ratio);
2235
2236      if (ratio < 0) {
2237        ratio =  1 / (1 - ratio);
2238      } else {
2239        ratio = 1 + ratio;
2240      }
2241
2242      this.zoomTo(canvas.width * ratio / canvas.naturalWidth, _event);
2243    },
2244
2245    /**
2246     * Zoom the canvas to an absolute ratio
2247     *
2248     * @param {Number} ratio
2249     * @param {jQuery Event} _event (private)
2250     */
2251    zoomTo: function (ratio, _event) {
2252      var options = this.options;
2253      var canvas = this.canvas;
2254      var width = canvas.width;
2255      var height = canvas.height;
2256      var naturalWidth = canvas.naturalWidth;
2257      var naturalHeight = canvas.naturalHeight;
2258      var originalEvent;
2259      var newWidth;
2260      var newHeight;
2261      var offset;
2262      var center;
2263
2264      ratio = num(ratio);
2265
2266      if (ratio >= 0 && this.isBuilt && !this.isDisabled && options.zoomable) {
2267        newWidth = naturalWidth * ratio;
2268        newHeight = naturalHeight * ratio;
2269
2270        if (_event) {
2271          originalEvent = _event.originalEvent;
2272        }
2273
2274        if (this.trigger(EVENT_ZOOM, {
2275          originalEvent: originalEvent,
2276          oldRatio: width / naturalWidth,
2277          ratio: newWidth / naturalWidth
2278        }).isDefaultPrevented()) {
2279          return;
2280        }
2281
2282        if (originalEvent) {
2283          offset = this.$cropper.offset();
2284          center = originalEvent.touches ? getTouchesCenter(originalEvent.touches) : {
2285            pageX: _event.pageX || originalEvent.pageX || 0,
2286            pageY: _event.pageY || originalEvent.pageY || 0
2287          };
2288
2289          // Zoom from the triggering point of the event
2290          canvas.left -= (newWidth - width) * (
2291            ((center.pageX - offset.left) - canvas.left) / width
2292          );
2293          canvas.top -= (newHeight - height) * (
2294            ((center.pageY - offset.top) - canvas.top) / height
2295          );
2296        } else {
2297
2298          // Zoom from the center of the canvas
2299          canvas.left -= (newWidth - width) / 2;
2300          canvas.top -= (newHeight - height) / 2;
2301        }
2302
2303        canvas.width = newWidth;
2304        canvas.height = newHeight;
2305        this.renderCanvas(true);
2306      }
2307    },
2308
2309    /**
2310     * Rotate the canvas with a relative degree
2311     *
2312     * @param {Number} degree
2313     */
2314    rotate: function (degree) {
2315      this.rotateTo((this.image.rotate || 0) + num(degree));
2316    },
2317
2318    /**
2319     * Rotate the canvas to an absolute degree
2320     * https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function#rotate()
2321     *
2322     * @param {Number} degree
2323     */
2324    rotateTo: function (degree) {
2325      degree = num(degree);
2326
2327      if (isNumber(degree) && this.isBuilt && !this.isDisabled && this.options.rotatable) {
2328        this.image.rotate = degree % 360;
2329        this.isRotated = true;
2330        this.renderCanvas(true);
2331      }
2332    },
2333
2334    /**
2335     * Scale the image
2336     * https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function#scale()
2337     *
2338     * @param {Number} scaleX
2339     * @param {Number} scaleY (optional)
2340     */
2341    scale: function (scaleX, scaleY) {
2342      var image = this.image;
2343      var isChanged = false;
2344
2345      // If "scaleY" is not present, its default value is "scaleX"
2346      if (isUndefined(scaleY)) {
2347        scaleY = scaleX;
2348      }
2349
2350      scaleX = num(scaleX);
2351      scaleY = num(scaleY);
2352
2353      if (this.isBuilt && !this.isDisabled && this.options.scalable) {
2354        if (isNumber(scaleX)) {
2355          image.scaleX = scaleX;
2356          isChanged = true;
2357        }
2358
2359        if (isNumber(scaleY)) {
2360          image.scaleY = scaleY;
2361          isChanged = true;
2362        }
2363
2364        if (isChanged) {
2365          this.renderImage(true);
2366        }
2367      }
2368    },
2369
2370    /**
2371     * Scale the abscissa of the image
2372     *
2373     * @param {Number} scaleX
2374     */
2375    scaleX: function (scaleX) {
2376      var scaleY = this.image.scaleY;
2377
2378      this.scale(scaleX, isNumber(scaleY) ? scaleY : 1);
2379    },
2380
2381    /**
2382     * Scale the ordinate of the image
2383     *
2384     * @param {Number} scaleY
2385     */
2386    scaleY: function (scaleY) {
2387      var scaleX = this.image.scaleX;
2388
2389      this.scale(isNumber(scaleX) ? scaleX : 1, scaleY);
2390    },
2391
2392    /**
2393     * Get the cropped area position and size data (base on the original image)
2394     *
2395     * @param {Boolean} isRounded (optional)
2396     * @return {Object} data
2397     */
2398    getData: function (isRounded) {
2399      var options = this.options;
2400      var image = this.image;
2401      var canvas = this.canvas;
2402      var cropBox = this.cropBox;
2403      var ratio;
2404      var data;
2405
2406      if (this.isBuilt && this.isCropped) {
2407        data = {
2408          x: cropBox.left - canvas.left,
2409          y: cropBox.top - canvas.top,
2410          width: cropBox.width,
2411          height: cropBox.height
2412        };
2413
2414        ratio = image.width / image.naturalWidth;
2415
2416        $.each(data, function (i, n) {
2417          n = n / ratio;
2418          data[i] = isRounded ? round(n) : n;
2419        });
2420
2421      } else {
2422        data = {
2423          x: 0,
2424          y: 0,
2425          width: 0,
2426          height: 0
2427        };
2428      }
2429
2430      if (options.rotatable) {
2431        data.rotate = image.rotate || 0;
2432      }
2433
2434      if (options.scalable) {
2435        data.scaleX = image.scaleX || 1;
2436        data.scaleY = image.scaleY || 1;
2437      }
2438
2439      return data;
2440    },
2441
2442    /**
2443     * Set the cropped area position and size with new data
2444     *
2445     * @param {Object} data
2446     */
2447    setData: function (data) {
2448      var options = this.options;
2449      var image = this.image;
2450      var canvas = this.canvas;
2451      var cropBoxData = {};
2452      var isRotated;
2453      var isScaled;
2454      var ratio;
2455
2456      if ($.isFunction(data)) {
2457        data = data.call(this.element);
2458      }
2459
2460      if (this.isBuilt && !this.isDisabled && $.isPlainObject(data)) {
2461        if (options.rotatable) {
2462          if (isNumber(data.rotate) && data.rotate !== image.rotate) {
2463            image.rotate = data.rotate;
2464            this.isRotated = isRotated = true;
2465          }
2466        }
2467
2468        if (options.scalable) {
2469          if (isNumber(data.scaleX) && data.scaleX !== image.scaleX) {
2470            image.scaleX = data.scaleX;
2471            isScaled = true;
2472          }
2473
2474          if (isNumber(data.scaleY) && data.scaleY !== image.scaleY) {
2475            image.scaleY = data.scaleY;
2476            isScaled = true;
2477          }
2478        }
2479
2480        if (isRotated) {
2481          this.renderCanvas();
2482        } else if (isScaled) {
2483          this.renderImage();
2484        }
2485
2486        ratio = image.width / image.naturalWidth;
2487
2488        if (isNumber(data.x)) {
2489          cropBoxData.left = data.x * ratio + canvas.left;
2490        }
2491
2492        if (isNumber(data.y)) {
2493          cropBoxData.top = data.y * ratio + canvas.top;
2494        }
2495
2496        if (isNumber(data.width)) {
2497          cropBoxData.width = data.width * ratio;
2498        }
2499
2500        if (isNumber(data.height)) {
2501          cropBoxData.height = data.height * ratio;
2502        }
2503
2504        this.setCropBoxData(cropBoxData);
2505      }
2506    },
2507
2508    /**
2509     * Get the container size data
2510     *
2511     * @return {Object} data
2512     */
2513    getContainerData: function () {
2514      return this.isBuilt ? this.container : {};
2515    },
2516
2517    /**
2518     * Get the image position and size data
2519     *
2520     * @return {Object} data
2521     */
2522    getImageData: function () {
2523      return this.isLoaded ? this.image : {};
2524    },
2525
2526    /**
2527     * Get the canvas position and size data
2528     *
2529     * @return {Object} data
2530     */
2531    getCanvasData: function () {
2532      var canvas = this.canvas;
2533      var data = {};
2534
2535      if (this.isBuilt) {
2536        $.each([
2537          'left',
2538          'top',
2539          'width',
2540          'height',
2541          'naturalWidth',
2542          'naturalHeight'
2543        ], function (i, n) {
2544          data[n] = canvas[n];
2545        });
2546      }
2547
2548      return data;
2549    },
2550
2551    /**
2552     * Set the canvas position and size with new data
2553     *
2554     * @param {Object} data
2555     */
2556    setCanvasData: function (data) {
2557      var canvas = this.canvas;
2558      var aspectRatio = canvas.aspectRatio;
2559
2560      if ($.isFunction(data)) {
2561        data = data.call(this.$element);
2562      }
2563
2564      if (this.isBuilt && !this.isDisabled && $.isPlainObject(data)) {
2565        if (isNumber(data.left)) {
2566          canvas.left = data.left;
2567        }
2568
2569        if (isNumber(data.top)) {
2570          canvas.top = data.top;
2571        }
2572
2573        if (isNumber(data.width)) {
2574          canvas.width = data.width;
2575          canvas.height = data.width / aspectRatio;
2576        } else if (isNumber(data.height)) {
2577          canvas.height = data.height;
2578          canvas.width = data.height * aspectRatio;
2579        }
2580
2581        this.renderCanvas(true);
2582      }
2583    },
2584
2585    /**
2586     * Get the crop box position and size data
2587     *
2588     * @return {Object} data
2589     */
2590    getCropBoxData: function () {
2591      var cropBox = this.cropBox;
2592      var data;
2593
2594      if (this.isBuilt && this.isCropped) {
2595        data = {
2596          left: cropBox.left,
2597          top: cropBox.top,
2598          width: cropBox.width,
2599          height: cropBox.height
2600        };
2601      }
2602
2603      return data || {};
2604    },
2605
2606    /**
2607     * Set the crop box position and size with new data
2608     *
2609     * @param {Object} data
2610     */
2611    setCropBoxData: function (data) {
2612      var cropBox = this.cropBox;
2613      var aspectRatio = this.options.aspectRatio;
2614      var isWidthChanged;
2615      var isHeightChanged;
2616
2617      if ($.isFunction(data)) {
2618        data = data.call(this.$element);
2619      }
2620
2621      if (this.isBuilt && this.isCropped && !this.isDisabled && $.isPlainObject(data)) {
2622
2623        if (isNumber(data.left)) {
2624          cropBox.left = data.left;
2625        }
2626
2627        if (isNumber(data.top)) {
2628          cropBox.top = data.top;
2629        }
2630
2631        if (isNumber(data.width)) {
2632          isWidthChanged = true;
2633          cropBox.width = data.width;
2634        }
2635
2636        if (isNumber(data.height)) {
2637          isHeightChanged = true;
2638          cropBox.height = data.height;
2639        }
2640
2641        if (aspectRatio) {
2642          if (isWidthChanged) {
2643            cropBox.height = cropBox.width / aspectRatio;
2644          } else if (isHeightChanged) {
2645            cropBox.width = cropBox.height * aspectRatio;
2646          }
2647        }
2648
2649        this.renderCropBox();
2650      }
2651    },
2652
2653    /**
2654     * Get a canvas drawn the cropped image
2655     *
2656     * @param {Object} options (optional)
2657     * @return {HTMLCanvasElement} canvas
2658     */
2659    getCroppedCanvas: function (options) {
2660      var originalWidth;
2661      var originalHeight;
2662      var canvasWidth;
2663      var canvasHeight;
2664      var scaledWidth;
2665      var scaledHeight;
2666      var scaledRatio;
2667      var aspectRatio;
2668      var canvas;
2669      var context;
2670      var data;
2671
2672      if (!this.isBuilt || !SUPPORT_CANVAS) {
2673        return;
2674      }
2675
2676      if (!this.isCropped) {
2677        return getSourceCanvas(this.$clone[0], this.image);
2678      }
2679
2680      if (!$.isPlainObject(options)) {
2681        options = {};
2682      }
2683
2684      data = this.getData();
2685      originalWidth = data.width;
2686      originalHeight = data.height;
2687      aspectRatio = originalWidth / originalHeight;
2688
2689      if ($.isPlainObject(options)) {
2690        scaledWidth = options.width;
2691        scaledHeight = options.height;
2692
2693        if (scaledWidth) {
2694          scaledHeight = scaledWidth / aspectRatio;
2695          scaledRatio = scaledWidth / originalWidth;
2696        } else if (scaledHeight) {
2697          scaledWidth = scaledHeight * aspectRatio;
2698          scaledRatio = scaledHeight / originalHeight;
2699        }
2700      }
2701
2702      // The canvas element will use `Math.floor` on a float number, so floor first
2703      canvasWidth = floor(scaledWidth || originalWidth);
2704      canvasHeight = floor(scaledHeight || originalHeight);
2705
2706      canvas = $('<canvas>')[0];
2707      canvas.width = canvasWidth;
2708      canvas.height = canvasHeight;
2709      context = canvas.getContext('2d');
2710
2711      if (options.fillColor) {
2712        context.fillStyle = options.fillColor;
2713        context.fillRect(0, 0, canvasWidth, canvasHeight);
2714      }
2715
2716      // https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D.drawImage
2717      context.drawImage.apply(context, (function () {
2718        var source = getSourceCanvas(this.$clone[0], this.image);
2719        var sourceWidth = source.width;
2720        var sourceHeight = source.height;
2721        var canvas = this.canvas;
2722        var params = [source];
2723
2724        // Source canvas
2725        var srcX = data.x + canvas.naturalWidth * (abs(data.scaleX || 1) - 1) / 2;
2726        var srcY = data.y + canvas.naturalHeight * (abs(data.scaleY || 1) - 1) / 2;
2727        var srcWidth;
2728        var srcHeight;
2729
2730        // Destination canvas
2731        var dstX;
2732        var dstY;
2733        var dstWidth;
2734        var dstHeight;
2735
2736        if (srcX <= -originalWidth || srcX > sourceWidth) {
2737          srcX = srcWidth = dstX = dstWidth = 0;
2738        } else if (srcX <= 0) {
2739          dstX = -srcX;
2740          srcX = 0;
2741          srcWidth = dstWidth = min(sourceWidth, originalWidth + srcX);
2742        } else if (srcX <= sourceWidth) {
2743          dstX = 0;
2744          srcWidth = dstWidth = min(originalWidth, sourceWidth - srcX);
2745        }
2746
2747        if (srcWidth <= 0 || srcY <= -originalHeight || srcY > sourceHeight) {
2748          srcY = srcHeight = dstY = dstHeight = 0;
2749        } else if (srcY <= 0) {
2750          dstY = -srcY;
2751          srcY = 0;
2752          srcHeight = dstHeight = min(sourceHeight, originalHeight + srcY);
2753        } else if (srcY <= sourceHeight) {
2754          dstY = 0;
2755          srcHeight = dstHeight = min(originalHeight, sourceHeight - srcY);
2756        }
2757
2758        // All the numerical parameters should be integer for `drawImage` (#476)
2759        params.push(floor(srcX), floor(srcY), floor(srcWidth), floor(srcHeight));
2760
2761        // Scale destination sizes
2762        if (scaledRatio) {
2763          dstX *= scaledRatio;
2764          dstY *= scaledRatio;
2765          dstWidth *= scaledRatio;
2766          dstHeight *= scaledRatio;
2767        }
2768
2769        // Avoid "IndexSizeError" in IE and Firefox
2770        if (dstWidth > 0 && dstHeight > 0) {
2771          params.push(floor(dstX), floor(dstY), floor(dstWidth), floor(dstHeight));
2772        }
2773
2774        return params;
2775      }).call(this));
2776
2777      return canvas;
2778    },
2779
2780    /**
2781     * Change the aspect ratio of the crop box
2782     *
2783     * @param {Number} aspectRatio
2784     */
2785    setAspectRatio: function (aspectRatio) {
2786      var options = this.options;
2787
2788      if (!this.isDisabled && !isUndefined(aspectRatio)) {
2789
2790        // 0 -> NaN
2791        options.aspectRatio = max(0, aspectRatio) || NaN;
2792
2793        if (this.isBuilt) {
2794          this.initCropBox();
2795
2796          if (this.isCropped) {
2797            this.renderCropBox();
2798          }
2799        }
2800      }
2801    },
2802
2803    /**
2804     * Change the drag mode
2805     *
2806     * @param {String} mode (optional)
2807     */
2808    setDragMode: function (mode) {
2809      var options = this.options;
2810      var croppable;
2811      var movable;
2812
2813      if (this.isLoaded && !this.isDisabled) {
2814        croppable = mode === ACTION_CROP;
2815        movable = options.movable && mode === ACTION_MOVE;
2816        mode = (croppable || movable) ? mode : ACTION_NONE;
2817
2818        this.$dragBox.
2819          data(DATA_ACTION, mode).
2820          toggleClass(CLASS_CROP, croppable).
2821          toggleClass(CLASS_MOVE, movable);
2822
2823        if (!options.cropBoxMovable) {
2824
2825          // Sync drag mode to crop box when it is not movable(#300)
2826          this.$face.
2827            data(DATA_ACTION, mode).
2828            toggleClass(CLASS_CROP, croppable).
2829            toggleClass(CLASS_MOVE, movable);
2830        }
2831      }
2832    }
2833  };
2834
2835  Cropper.DEFAULTS = {
2836
2837    // Define the view mode of the cropper
2838    viewMode: 0, // 0, 1, 2, 3
2839
2840    // Define the dragging mode of the cropper
2841    dragMode: 'crop', // 'crop', 'move' or 'none'
2842
2843    // Define the aspect ratio of the crop box
2844    aspectRatio: NaN,
2845
2846    // An object with the previous cropping result data
2847    data: null,
2848
2849    // A jQuery selector for adding extra containers to preview
2850    preview: '',
2851
2852    // Re-render the cropper when resize the window
2853    responsive: true,
2854
2855    // Restore the cropped area after resize the window
2856    restore: true,
2857
2858    // Check if the current image is a cross-origin image
2859    checkCrossOrigin: true,
2860
2861    // Check the current image's Exif Orientation information
2862    checkOrientation: true,
2863
2864    // Show the black modal
2865    modal: true,
2866
2867    // Show the dashed lines for guiding
2868    guides: true,
2869
2870    // Show the center indicator for guiding
2871    center: true,
2872
2873    // Show the white modal to highlight the crop box
2874    highlight: true,
2875
2876    // Show the grid background
2877    background: true,
2878
2879    // Enable to crop the image automatically when initialize
2880    autoCrop: true,
2881
2882    // Define the percentage of automatic cropping area when initializes
2883    autoCropArea: 0.8,
2884
2885    // Enable to move the image
2886    movable: true,
2887
2888    // Enable to rotate the image
2889    rotatable: true,
2890
2891    // Enable to scale the image
2892    scalable: true,
2893
2894    // Enable to zoom the image
2895    zoomable: true,
2896
2897    // Enable to zoom the image by dragging touch
2898    zoomOnTouch: true,
2899
2900    // Enable to zoom the image by wheeling mouse
2901    zoomOnWheel: true,
2902
2903    // Define zoom ratio when zoom the image by wheeling mouse
2904    wheelZoomRatio: 0.1,
2905
2906    // Enable to move the crop box
2907    cropBoxMovable: true,
2908
2909    // Enable to resize the crop box
2910    cropBoxResizable: true,
2911
2912    // Toggle drag mode between "crop" and "move" when click twice on the cropper
2913    toggleDragModeOnDblclick: true,
2914
2915    // Size limitation
2916    minCanvasWidth: 0,
2917    minCanvasHeight: 0,
2918    minCropBoxWidth: 0,
2919    minCropBoxHeight: 0,
2920    minContainerWidth: 200,
2921    minContainerHeight: 100,
2922
2923    // Shortcuts of events
2924    build: null,
2925    built: null,
2926    cropstart: null,
2927    cropmove: null,
2928    cropend: null,
2929    crop: null,
2930    zoom: null
2931  };
2932
2933  Cropper.setDefaults = function (options) {
2934    $.extend(Cropper.DEFAULTS, options);
2935  };
2936
2937  Cropper.TEMPLATE = (
2938    '<div class="cropper-container">' +
2939      '<div class="cropper-wrap-box">' +
2940        '<div class="cropper-canvas"></div>' +
2941      '</div>' +
2942      '<div class="cropper-drag-box"></div>' +
2943      '<div class="cropper-crop-box">' +
2944        '<span class="cropper-view-box"></span>' +
2945        '<span class="cropper-dashed dashed-h"></span>' +
2946        '<span class="cropper-dashed dashed-v"></span>' +
2947        '<span class="cropper-center"></span>' +
2948        '<span class="cropper-face"></span>' +
2949        '<span class="cropper-line line-e" data-action="e"></span>' +
2950        '<span class="cropper-line line-n" data-action="n"></span>' +
2951        '<span class="cropper-line line-w" data-action="w"></span>' +
2952        '<span class="cropper-line line-s" data-action="s"></span>' +
2953        '<span class="cropper-point point-e" data-action="e"></span>' +
2954        '<span class="cropper-point point-n" data-action="n"></span>' +
2955        '<span class="cropper-point point-w" data-action="w"></span>' +
2956        '<span class="cropper-point point-s" data-action="s"></span>' +
2957        '<span class="cropper-point point-ne" data-action="ne"></span>' +
2958        '<span class="cropper-point point-nw" data-action="nw"></span>' +
2959        '<span class="cropper-point point-sw" data-action="sw"></span>' +
2960        '<span class="cropper-point point-se" data-action="se"></span>' +
2961      '</div>' +
2962    '</div>'
2963  );
2964
2965  // Save the other cropper
2966  Cropper.other = $.fn.cropper;
2967
2968  // Register as jQuery plugin
2969  $.fn.cropper = function (option) {
2970    var args = toArray(arguments, 1);
2971    var result;
2972
2973    this.each(function () {
2974      var $this = $(this);
2975      var data = $this.data(NAMESPACE);
2976      var options;
2977      var fn;
2978
2979      if (!data) {
2980        if (/destroy/.test(option)) {
2981          return;
2982        }
2983
2984        options = $.extend({}, $this.data(), $.isPlainObject(option) && option);
2985        $this.data(NAMESPACE, (data = new Cropper(this, options)));
2986      }
2987
2988      if (typeof option === 'string' && $.isFunction(fn = data[option])) {
2989        result = fn.apply(data, args);
2990      }
2991    });
2992
2993    return isUndefined(result) ? this : result;
2994  };
2995
2996  $.fn.cropper.Constructor = Cropper;
2997  $.fn.cropper.setDefaults = Cropper.setDefaults;
2998
2999  // No conflict
3000  $.fn.cropper.noConflict = function () {
3001    $.fn.cropper = Cropper.other;
3002    return this;
3003  };
3004
3005});

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.