cropper-rails 2.2.1

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