source: prototipo_portal_2018/prototipo/static/js/cropper.js @ 747e744

Last change on this file since 747e744 was 747e744, checked in by Antonio Araujo <aaraujo@…>, 7 years ago

Creado directorio prototipo con fuentes de la modificación del portal para establecer la ubicación de la firma visible

  • Property mode set to 100644
File size: 43.5 KB
Line 
1(function (factory) {
2  if (typeof define === "function" && define.amd) {
3    // AMD. Register as anonymous module.
4    define(["jquery"], factory);
5  } else if (typeof exports === "object") {
6    // Node / CommonJS
7    factory(require("jquery"));
8  } else {
9    // Browser globals.
10    factory(jQuery);
11  }
12})(function ($) {
13
14  "use strict";
15
16  var $window = $(window),
17      $document = $(document),
18      location = window.location,
19
20      // Constants
21      TRUE = true,
22      FALSE = false,
23      NULL = null,
24      NAN = NaN,
25      INFINITY = Infinity,
26      STRING_UNDEFINED = "undefined",
27      STRING_DIRECTIVE = "directive",
28      CROPPER_NAMESPACE = ".cropper",
29
30      // RegExps
31      REGEXP_DIRECTIVES = /^(e|n|w|s|ne|nw|sw|se|all|crop|move|zoom)$/,
32      REGEXP_OPTIONS = /^(x|y|width|height)$/,
33      REGEXP_PROPERTIES = /^(naturalWidth|naturalHeight|width|height|aspectRatio|ratio|rotate)$/,
34
35      // Classes
36      CLASS_MODAL = "cropper-modal",
37      CLASS_HIDDEN = "cropper-hidden",
38      CLASS_INVISIBLE = "cropper-invisible",
39      CLASS_MOVE = "cropper-move",
40      CLASS_CROP = "cropper-crop",
41      CLASS_DISABLED = "cropper-disabled",
42
43      // Events
44      EVENT_MOUSE_DOWN = "mousedown touchstart",
45      EVENT_MOUSE_MOVE = "mousemove touchmove",
46      EVENT_MOUSE_UP = "mouseup mouseleave touchend touchleave touchcancel",
47      EVENT_WHEEL = "wheel mousewheel DOMMouseScroll",
48      EVENT_RESIZE = "resize" + CROPPER_NAMESPACE, // Bind to window with namespace
49      EVENT_DBLCLICK = "dblclick",
50      EVENT_BUILD = "build" + CROPPER_NAMESPACE,
51      EVENT_BUILT = "built" + CROPPER_NAMESPACE,
52      EVENT_DRAG_START = "dragstart" + CROPPER_NAMESPACE,
53      EVENT_DRAG_MOVE = "dragmove" + CROPPER_NAMESPACE,
54      EVENT_DRAG_END = "dragend" + CROPPER_NAMESPACE,
55
56      // Functions
57      isNumber = function (n) {
58        return typeof n === "number";
59      },
60
61      toArray = function (obj, offset) {
62        var args = [];
63
64        if (isNumber(offset)) { // It's necessary for IE8
65          args.push(offset);
66        }
67
68        return args.slice.apply(obj, args);
69      },
70
71      // Custom proxy to avoid jQuery's guid
72      proxy = function (fn, context) {
73        var args = toArray(arguments, 2);
74
75        return function () {
76          return fn.apply(context, args.concat(toArray(arguments)));
77        };
78      },
79
80      addTimestamp = function (url) {
81        var timestamp = "timestamp=" + (new Date()).getTime();
82
83        return (url + (url.indexOf("?") === -1 ? "?" : "&") + timestamp);
84      },
85
86      // Constructor
87      Cropper = function (element, options) {
88        this.element = element;
89        this.$element = $(element);
90        this.defaults = $.extend({}, Cropper.DEFAULTS, $.isPlainObject(options) ? options : {});
91        this.$original = NULL;
92        this.ready = FALSE;
93        this.built = FALSE;
94        this.cropped = FALSE;
95        this.rotated = FALSE;
96        this.disabled = FALSE;
97        this.replaced = FALSE;
98        this.init();
99      },
100
101      // Others
102      sqrt = Math.sqrt,
103      min = Math.min,
104      max = Math.max,
105      abs = Math.abs,
106      sin = Math.sin,
107      cos = Math.cos,
108      num = parseFloat;
109
110  Cropper.prototype = {
111    constructor: Cropper,
112
113    support: {
114      canvas: $.isFunction($("<canvas>")[0].getContext)
115    },
116
117    init: function () {
118      var defaults = this.defaults;
119
120      $.each(defaults, function (i, n) {
121        switch (i) {
122          case "aspectRatio":
123            defaults[i] = abs(num(n)) || NAN; // 0 -> NaN
124            break;
125
126          case "autoCropArea":
127            defaults[i] = abs(num(n)) || 0.8; // 0 | NaN -> 0.8
128            break;
129
130          case "minWidth":
131          case "minHeight":
132            defaults[i] = abs(num(n)) || 0; // NaN -> 0
133            break;
134
135          case "maxWidth":
136          case "maxHeight":
137            defaults[i] = abs(num(n)) || INFINITY; // 0 | NaN -> Infinity
138            break;
139
140          // No default
141        }
142      });
143
144      // Set default image data
145      this.image = {
146        rotate: 0
147      };
148
149      this.load();
150    },
151
152    load: function () {
153      var _this = this,
154          $this = this.$element,
155          element = this.element,
156          image = this.image,
157          crossOrigin = "",
158          $clone,
159          url;
160
161      if ($this.is("img")) {
162        url = $this.prop("src");
163      } else if ($this.is("canvas") && this.support.canvas) {
164        url = element.toDataURL();
165      }
166
167      if (!url) {
168        return;
169      }
170
171      // Reset image rotate degree
172      if (this.replaced) {
173        image.rotate = 0;
174      }
175
176      if (this.defaults.checkImageOrigin && this.isCrossOriginURL(url)) {
177        crossOrigin = " crossOrigin";
178        url = addTimestamp(url); // Bust cache (#119, #148)
179      }
180
181      this.$clone = ($clone = $("<img" + crossOrigin + ' src="' + url + '">'));
182
183      $clone.one("load", function () {
184        image.naturalWidth = this.naturalWidth || $clone.width();
185        image.naturalHeight = this.naturalHeight || $clone.height();
186        image.aspectRatio = image.naturalWidth / image.naturalHeight;
187
188        _this.url = url;
189        _this.ready = TRUE;
190        _this.build();
191      });
192
193      // Hide and prepend the clone iamge to the document body (Don't append to).
194      $clone.addClass(CLASS_INVISIBLE).prependTo("body");
195    },
196
197    isCrossOriginURL: function (url) {
198      var parts = url.match(/^(https?:)\/\/([^\:\/\?#]+):?(\d*)/i);
199
200      if (parts && (parts[1] !== location.protocol || parts[2] !== location.hostname || parts[3] !== location.port)) {
201        return TRUE;
202      }
203
204      return FALSE;
205    },
206
207    build: function () {
208      var $this = this.$element,
209          defaults = this.defaults,
210          buildEvent,
211          $cropper;
212
213      if (!this.ready) {
214        return;
215      }
216
217      if (this.built) {
218        this.unbuild();
219      }
220
221      $this.one(EVENT_BUILD, defaults.build); // Only trigger once
222      buildEvent = $.Event(EVENT_BUILD);
223      $this.trigger(buildEvent);
224
225      if (buildEvent.isDefaultPrevented()) {
226        return;
227      }
228
229      // Create cropper elements
230      this.$cropper = ($cropper = $(Cropper.TEMPLATE));
231
232      // Hide the original image
233      $this.addClass(CLASS_HIDDEN);
234
235      // Show and prepend the clone iamge to the cropper
236      this.$clone.removeClass(CLASS_INVISIBLE).prependTo($cropper);
237
238      // Save original image for rotation
239      if (!this.rotated) {
240        this.$original = this.$clone.clone();
241
242        // Append the image to document to avoid "NS_ERROR_NOT_AVAILABLE" error on Firefox when call the "drawImage" method.
243        this.$original.addClass(CLASS_HIDDEN).prependTo(this.$cropper);
244
245        this.originalImage = $.extend({}, this.image);
246      }
247
248      this.$container = $this.parent();
249      this.$container.append($cropper);
250
251      this.$canvas = $cropper.find(".cropper-canvas");
252      this.$dragger = $cropper.find(".cropper-dragger");
253      this.$viewer = $cropper.find(".cropper-viewer");
254
255      defaults.autoCrop ? (this.cropped = TRUE) : this.$dragger.addClass(CLASS_HIDDEN);
256      defaults.modal && this.$canvas.addClass(CLASS_MODAL);
257      !defaults.dashed && this.$dragger.find(".cropper-dashed").addClass(CLASS_HIDDEN);
258      !defaults.movable && this.$dragger.find(".cropper-face").data(STRING_DIRECTIVE, "move");
259      !defaults.resizable && this.$dragger.find(".cropper-line, .cropper-point").addClass(CLASS_HIDDEN);
260
261      this.addListeners();
262      this.initPreview();
263
264      this.built = TRUE; // Set `true` before update
265      defaults.dragCrop && this.setDragMode("crop"); // Set after built
266      this.update();
267      this.replaced = FALSE; // Reset to `false` after update
268
269      $this.one(EVENT_BUILT, defaults.built); // Only trigger once
270      $this.trigger(EVENT_BUILT);
271    },
272
273    unbuild: function () {
274      if (!this.built) {
275        return;
276      }
277
278      this.built = FALSE;
279      this.removeListeners();
280
281      this.$preview.empty();
282      this.$preview = NULL;
283
284      this.$dragger = NULL;
285      this.$canvas = NULL;
286      this.$container = NULL;
287
288      this.$cropper.remove();
289      this.$cropper = NULL;
290    },
291
292    update: function (data) {
293      this.initContainer();
294      this.initCropper();
295      this.initImage();
296      this.initDragger();
297
298      if (data) {
299        this.setData(data, TRUE);
300        this.setDragMode("crop");
301      } else {
302        this.setData(this.defaults.data);
303      }
304    },
305
306    resize: function () {
307      clearTimeout(this.resizing);
308      this.resizing = setTimeout($.proxy(this.update, this, this.getData()), 200);
309    },
310
311    preview: function () {
312      var image = this.image,
313          dragger = this.dragger,
314          width = image.width,
315          height = image.height,
316          left = dragger.left - image.left,
317          top = dragger.top - image.top;
318
319      this.$viewer.find("img").css({
320        width: width,
321        height: height,
322        marginLeft: -left,
323        marginTop: -top
324      });
325
326      this.$preview.each(function () {
327        var $this = $(this),
328            data = $this.data(),
329            ratio = data.width / dragger.width,
330            newWidth = data.width,
331            newHeight = dragger.height * ratio;
332
333        if (newHeight > data.height) {
334          ratio = data.height / dragger.height,
335          newWidth = dragger.width * ratio;
336          newHeight = data.height;
337        }
338
339        $this.width(newWidth).height(newHeight).find("img").css({
340          width: width * ratio,
341          height: height * ratio,
342          marginLeft: -left * ratio,
343          marginTop: -top * ratio
344        });
345      });
346    },
347
348    addListeners: function () {
349      var defaults = this.defaults;
350
351      this.$element.on(EVENT_DRAG_START, defaults.dragstart).on(EVENT_DRAG_MOVE, defaults.dragmove).on(EVENT_DRAG_END, defaults.dragend);
352      this.$cropper.on(EVENT_MOUSE_DOWN, $.proxy(this.dragstart, this)).on(EVENT_DBLCLICK, $.proxy(this.dblclick, this));
353
354      if (defaults.zoomable) {
355        this.$cropper.on(EVENT_WHEEL, $.proxy(this.wheel, this));
356      }
357
358      if (defaults.multiple) {
359        this.$cropper.on(EVENT_MOUSE_MOVE, $.proxy(this.dragmove, this)).on(EVENT_MOUSE_UP, $.proxy(this.dragend, this));
360      } else {
361        $document.on(EVENT_MOUSE_MOVE, (this._dragmove = proxy(this.dragmove, this))).on(EVENT_MOUSE_UP, (this._dragend = proxy(this.dragend, this)));
362      }
363
364      $window.on(EVENT_RESIZE, (this._resize = proxy(this.resize, this)));
365    },
366
367    removeListeners: function () {
368      var defaults = this.defaults;
369
370      this.$element.off(EVENT_DRAG_START, defaults.dragstart).off(EVENT_DRAG_MOVE, defaults.dragmove).off(EVENT_DRAG_END, defaults.dragend);
371      this.$cropper.off(EVENT_MOUSE_DOWN, this.dragstart).off(EVENT_DBLCLICK, this.dblclick);
372
373      if (defaults.zoomable) {
374        this.$cropper.off(EVENT_WHEEL, this.wheel);
375      }
376
377      if (defaults.multiple) {
378        this.$cropper.off(EVENT_MOUSE_MOVE, this.dragmove).off(EVENT_MOUSE_UP, this.dragend);
379      } else {
380        $document.off(EVENT_MOUSE_MOVE, this._dragmove).off(EVENT_MOUSE_UP, this._dragend);
381      }
382
383      $window.off(EVENT_RESIZE, this._resize);
384    },
385
386    initPreview: function () {
387      var url = this.url;
388
389      this.$preview = $(this.defaults.preview);
390      this.$viewer.html('<img src="' + url + '">');
391
392      this.$preview.each(function () {
393        var $this = $(this);
394
395        $this.data({
396          width: $this.width(),
397          height: $this.height()
398        }).html('<img src="' + url + '" style="display:block;width:100%;min-width:0!important;min-height:0!important;max-width:none!important;max-height:none!important;">');
399      });
400    },
401
402    initContainer: function () {
403      var $this = this.$element,
404          $container = this.$container,
405          $cropper = this.$cropper,
406          defaults = this.defaults;
407
408      $cropper.addClass(CLASS_HIDDEN);
409      $this.removeClass(CLASS_HIDDEN);
410
411      this.container = {
412        width: max($container.width(), defaults.minContainerWidth),
413        height: max($container.height(), defaults.minContainerHeight)
414      };
415
416      $this.addClass(CLASS_HIDDEN);
417      $cropper.removeClass(CLASS_HIDDEN);
418    },
419
420    initCropper: function () {
421      var container = this.container,
422          image = this.image,
423          cropper;
424
425      if (((image.naturalWidth * container.height / image.naturalHeight) - container.width) >= 0) {
426        cropper = {
427          width: container.width,
428          height: container.width / image.aspectRatio,
429          left: 0
430        };
431
432        cropper.top = (container.height - cropper.height) / 2;
433      } else {
434        cropper = {
435          width: container.height * image.aspectRatio,
436          height: container.height,
437          top: 0
438        };
439
440        cropper.left = (container.width - cropper.width) / 2;
441      }
442
443      this.$cropper.css({
444        width: cropper.width,
445        height: cropper.height,
446        left: cropper.left,
447        top: cropper.top
448      });
449
450      this.cropper = cropper;
451    },
452
453    initImage: function () {
454      var image = this.image,
455          cropper = this.cropper,
456          defaultImage = {
457            _width: cropper.width,
458            _height: cropper.height,
459            width: cropper.width,
460            height: cropper.height,
461            left: 0,
462            top: 0,
463            ratio: cropper.width / image.naturalWidth
464          };
465
466      this.defaultImage = $.extend({}, image, defaultImage);
467
468      if (image._width !== cropper.width || image._height !== cropper.height) {
469        $.extend(image, defaultImage);
470      } else {
471        image = $.extend({}, defaultImage, image);
472
473        // Reset image ratio
474        if (this.replaced) {
475          image.ratio = defaultImage.ratio;
476        }
477      }
478
479      this.image = image;
480      this.renderImage();
481    },
482
483    renderImage: function (mode) {
484      var image = this.image;
485
486      if (mode === "zoom") {
487        image.left -= (image.width - image.oldWidth) / 2;
488        image.top -= (image.height - image.oldHeight) / 2;
489      }
490
491      image.left = min(max(image.left, image._width - image.width), 0);
492      image.top = min(max(image.top, image._height - image.height), 0);
493
494      this.$clone.css({
495        width: image.width,
496        height: image.height,
497        marginLeft: image.left,
498        marginTop: image.top
499      });
500
501      if (mode) {
502        this.defaults.done(this.getData());
503        this.preview();
504      }
505    },
506
507    initDragger: function () {
508      var defaults = this.defaults,
509          cropper = this.cropper,
510          // If not set, use the original aspect ratio of the image.
511          aspectRatio = defaults.aspectRatio || this.image.aspectRatio,
512          ratio = this.image.ratio,
513          autoCropDragger,
514          dragger;
515
516      if (((cropper.height * aspectRatio) - cropper.width) >= 0) {
517        dragger = {
518          height: cropper.width / aspectRatio,
519          width: cropper.width,
520          left: 0,
521          top: (cropper.height - (cropper.width / aspectRatio)) / 2,
522          maxWidth: cropper.width,
523          maxHeight: cropper.width / aspectRatio
524        };
525      } else {
526        dragger = {
527          height: cropper.height,
528          width: cropper.height * aspectRatio,
529          left: (cropper.width - (cropper.height * aspectRatio)) / 2,
530          top: 0,
531          maxWidth: cropper.height * aspectRatio,
532          maxHeight: cropper.height
533        };
534      }
535
536      dragger.minWidth = 0;
537      dragger.minHeight = 0;
538
539      if (defaults.aspectRatio) {
540        if (isFinite(defaults.maxWidth)) {
541          dragger.maxWidth = min(dragger.maxWidth, defaults.maxWidth * ratio);
542          dragger.maxHeight = dragger.maxWidth / aspectRatio;
543        } else if (isFinite(defaults.maxHeight)) {
544          dragger.maxHeight = min(dragger.maxHeight, defaults.maxHeight * ratio);
545          dragger.maxWidth = dragger.maxHeight * aspectRatio;
546        }
547
548        if (defaults.minWidth > 0) {
549          dragger.minWidth = max(0, defaults.minWidth * ratio);
550          dragger.minHeight = dragger.minWidth / aspectRatio;
551        } else if (defaults.minHeight > 0) {
552          dragger.minHeight = max(0, defaults.minHeight * ratio);
553          dragger.minWidth = dragger.minHeight * aspectRatio;
554        }
555      } else {
556        dragger.maxWidth = min(dragger.maxWidth, defaults.maxWidth * ratio);
557        dragger.maxHeight = min(dragger.maxHeight, defaults.maxHeight * ratio);
558        dragger.minWidth = max(0, defaults.minWidth * ratio);
559        dragger.minHeight = max(0, defaults.minHeight * ratio);
560      }
561
562      // minWidth can't be greater than maxWidth, and minHeight too.
563      dragger.minWidth = min(dragger.maxWidth, dragger.minWidth);
564      dragger.minHeight = min(dragger.maxHeight, dragger.minHeight);
565
566      // Center the dragger by default
567      autoCropDragger = $.extend({}, dragger);
568
569      // The width of auto crop area must large than minWidth, and the height too. (#164)
570      autoCropDragger.width = max(dragger.minWidth, dragger.width * defaults.autoCropArea);
571      autoCropDragger.height = max(dragger.minHeight, dragger.height * defaults.autoCropArea);
572      autoCropDragger.left = (cropper.width - autoCropDragger.width) / 2;
573      autoCropDragger.top = (cropper.height - autoCropDragger.height) / 2;
574
575      autoCropDragger.oldLeft = dragger.oldLeft = dragger.left;
576      autoCropDragger.oldTop = dragger.oldTop = dragger.top;
577
578      this.autoCropDragger = autoCropDragger;
579      this.defaultDragger = $.extend({}, dragger);
580      this.dragger = dragger;
581    },
582
583    renderDragger: function () {
584      var dragger = this.dragger,
585          cropper = this.cropper;
586
587      if (dragger.width > dragger.maxWidth) {
588        dragger.width = dragger.maxWidth;
589        dragger.left = dragger.oldLeft;
590      } else if (dragger.width < dragger.minWidth) {
591        dragger.width = dragger.minWidth;
592        dragger.left = dragger.oldLeft;
593      }
594
595      if (dragger.height > dragger.maxHeight) {
596        dragger.height = dragger.maxHeight;
597        dragger.top = dragger.oldTop;
598      } else if (dragger.height < dragger.minHeight) {
599        dragger.height = dragger.minHeight;
600        dragger.top = dragger.oldTop;
601      }
602
603      dragger.left = min(max(dragger.left, 0), cropper.width - dragger.width);
604      dragger.top = min(max(dragger.top, 0), cropper.height - dragger.height);
605      dragger.oldLeft = dragger.left;
606      dragger.oldTop = dragger.top;
607
608      // Re-render the dragger
609      this.dragger = dragger;
610
611      // #186
612      if (this.defaults.movable) {
613        this.$dragger.find(".cropper-face").data(STRING_DIRECTIVE, (dragger.width === cropper.width && dragger.height === cropper.height) ? "move" : "all");
614      }
615
616      if (!this.disabled) {
617        this.defaults.done(this.getData());
618      }
619
620      this.$dragger.css({
621        width: dragger.width,
622        height: dragger.height,
623        left: dragger.left,
624        top: dragger.top
625      });
626
627      this.preview();
628    },
629
630    reset: function (deep) {
631      if (!this.cropped || this.disabled) {
632        return;
633      }
634
635      if (deep) {
636        this.defaults.data = {};
637      }
638
639      this.image = $.extend({}, this.defaultImage);
640      this.renderImage();
641      this.dragger = $.extend({}, this.defaultDragger);
642      this.setData(this.defaults.data);
643    },
644
645    clear: function () {
646      if (!this.cropped || this.disabled) {
647        return;
648      }
649
650      this.cropped = FALSE;
651
652      this.setData({
653        x: 0,
654        y: 0,
655        width: 0,
656        height: 0
657      });
658
659      this.$canvas.removeClass(CLASS_MODAL);
660      this.$dragger.addClass(CLASS_HIDDEN);
661    },
662
663    destroy: function () {
664      var $this = this.$element;
665
666      if (!this.ready) {
667        this.$clone.off("load").remove();
668      }
669
670      this.unbuild();
671      $this.removeClass(CLASS_HIDDEN).removeData("cropper");
672
673      if (this.rotated) {
674        $this.attr("src", this.$original.attr("src"));
675      }
676    },
677
678    replace: function (url, /*INTERNAL*/ rotated) {
679      var _this = this,
680          $this = this.$element,
681          element = this.element,
682          context;
683
684      if (!this.disabled && url && url !== this.url && url !== $this.attr("src")) {
685        if (!rotated) {
686          this.rotated = FALSE;
687          this.replaced = TRUE;
688        }
689
690        if ($this.is("img")) {
691          $this.attr("src", url);
692          this.load();
693        } else if ($this.is("canvas") && this.support.canvas) {
694          context = element.getContext("2d");
695
696          $('<img src="' + url + '">').one("load", function () {
697            element.width = this.width;
698            element.height = this.height;
699            context.clearRect(0, 0, element.width, element.height);
700            context.drawImage(this, 0, 0);
701            _this.load();
702          });
703        }
704      }
705    },
706
707    setData: function (data, /*INTERNAL*/ once) {
708      var cropper = this.cropper,
709          dragger = this.dragger,
710          image = this.image,
711          aspectRatio = this.defaults.aspectRatio;
712
713      if (!this.built || this.disabled || typeof data === STRING_UNDEFINED) {
714        return;
715      }
716
717      if (data === NULL || $.isEmptyObject(data)) {
718        dragger = $.extend({}, this.autoCropDragger);
719      }
720
721      if ($.isPlainObject(data) && !$.isEmptyObject(data)) {
722
723        if (!once) {
724          this.defaults.data = data;
725        }
726
727        data = this.transformData(data);
728
729        if (isNumber(data.x) && data.x <= cropper.width - image.left) {
730          dragger.left = data.x + image.left;
731        }
732
733        if (isNumber(data.y) && data.y <= cropper.height - image.top) {
734          dragger.top = data.y + image.top;
735        }
736
737        if (aspectRatio) {
738          if (isNumber(data.width) && data.width <= dragger.maxWidth && data.width >= dragger.minWidth) {
739            dragger.width = data.width;
740            dragger.height = dragger.width / aspectRatio;
741          } else if (isNumber(data.height) && data.height <= dragger.maxHeight && data.height >= dragger.minHeight) {
742            dragger.height = data.height;
743            dragger.width = dragger.height * aspectRatio;
744          }
745        } else {
746          if (isNumber(data.width) && data.width <= dragger.maxWidth && data.width >= dragger.minWidth) {
747            dragger.width = data.width;
748          }
749
750          if (isNumber(data.height) && data.height <= dragger.maxHeight && data.height >= dragger.minHeight) {
751            dragger.height = data.height;
752          }
753        }
754      }
755
756      this.dragger = dragger;
757      this.renderDragger();
758    },
759
760    getData: function (rounded) {
761      var dragger = this.dragger,
762          image = this.image,
763          data = {};
764
765      if (this.built) {
766        data = {
767          x: dragger.left - image.left,
768          y: dragger.top - image.top,
769          width: dragger.width,
770          height: dragger.height
771        };
772
773        data = this.transformData(data, TRUE, rounded);
774      }
775
776      return data;
777    },
778
779    transformData: function (data, reversed, rounded) {
780      var ratio = this.image.ratio,
781          result = {};
782
783      $.each(data, function (i, n) {
784        n = num(n);
785
786        if (REGEXP_OPTIONS.test(i) && !isNaN(n)) {
787          result[i] = reversed ? (rounded ? Math.round(n / ratio) : n / ratio) : n * ratio;
788        }
789      });
790
791      return result;
792    },
793
794    setAspectRatio: function (aspectRatio) {
795      var freeRatio = aspectRatio === "auto";
796
797      if (this.disabled) {
798        return;
799      }
800
801      aspectRatio = num(aspectRatio);
802
803      if (freeRatio || (!isNaN(aspectRatio) && aspectRatio > 0)) {
804        this.defaults.aspectRatio = freeRatio ? NAN : aspectRatio;
805
806        if (this.built) {
807          this.initDragger();
808          this.renderDragger();
809          this.setData(this.defaults.data); // Reset to initial state
810        }
811      }
812    },
813
814    getImageData: function () {
815      var data = {};
816
817      if (this.ready) {
818        $.each(this.image, function (name, value) {
819          if (REGEXP_PROPERTIES.test(name)) {
820            data[name] = value;
821          }
822        });
823      }
824
825      return data;
826    },
827
828    getDataURL: function (options, type, quality) {
829      var canvas = $("<canvas>")[0],
830          data = this.getData(),
831          dataURL = "",
832          context;
833
834      if (!$.isPlainObject(options)) {
835        quality = type;
836        type = options;
837        options = {};
838      }
839
840      options = $.extend({
841        width: data.width,
842        height: data.height
843      }, options);
844
845      if (this.cropped && this.support.canvas) {
846        canvas.width = options.width;
847        canvas.height = options.height;
848        context = canvas.getContext("2d");
849
850        if (type === "image/jpeg") {
851          context.fillStyle = "#fff";
852          context.fillRect(0, 0, options.width, options.height);
853        }
854
855        context.drawImage(this.$clone[0], data.x, data.y, data.width, data.height, 0, 0, options.width, options.height);
856        dataURL = canvas.toDataURL(type, quality);
857      }
858
859      return dataURL;
860    },
861
862    setDragMode: function (mode) {
863      var $canvas = this.$canvas,
864          defaults = this.defaults,
865          cropable = FALSE,
866          movable = FALSE;
867
868      if (!this.built || this.disabled) {
869        return;
870      }
871
872      switch (mode) {
873        case "crop":
874          if (defaults.dragCrop) {
875            cropable = TRUE;
876            $canvas.data(STRING_DIRECTIVE, mode);
877          }
878
879          break;
880
881        case "move":
882          movable = TRUE;
883          $canvas.data(STRING_DIRECTIVE, mode);
884
885          break;
886
887        default:
888          $canvas.removeData(STRING_DIRECTIVE);
889      }
890
891      $canvas.toggleClass(CLASS_CROP, cropable).toggleClass(CLASS_MOVE, movable);
892    },
893
894    enable: function () {
895      if (this.built) {
896        this.disabled = FALSE;
897        this.$cropper.removeClass(CLASS_DISABLED);
898      }
899    },
900
901    disable: function () {
902      if (this.built) {
903        this.disabled = TRUE;
904        this.$cropper.addClass(CLASS_DISABLED);
905      }
906    },
907
908    rotate: function (degree) {
909      var image = this.image;
910
911      degree = num(degree) || 0;
912
913      if (!this.built || degree === 0 || this.disabled || !this.defaults.rotatable || !this.support.canvas) {
914        return;
915      }
916
917      this.rotated = TRUE;
918      degree = (image.rotate = (image.rotate + degree) % 360);
919
920       // replace with "true" to prevent to override the original image
921      this.replace(this.getRotatedDataURL(degree), true);
922    },
923
924    getRotatedDataURL: function (degree) {
925      var canvas = $("<canvas>")[0],
926          context = canvas.getContext("2d"),
927          originalImage = this.originalImage,
928          naturalWidth = originalImage.naturalWidth,
929          naturalHeight = originalImage.naturalHeight,
930          deg = abs(degree) % 180,
931          arc = (deg > 90 ? (180 - deg) : deg) * Math.PI / 180,
932          width = naturalWidth * cos(arc) + naturalHeight * sin(arc),
933          height = naturalWidth * sin(arc) + naturalHeight * cos(arc);
934
935      canvas.width = width;
936      canvas.height = height;
937      context.save();
938      context.translate(width / 2, height / 2);
939      context.rotate(degree * Math.PI / 180);
940      context.drawImage(this.$original[0], -naturalWidth / 2, -naturalHeight / 2, naturalWidth, naturalHeight);
941      context.restore();
942
943      return canvas.toDataURL();
944    },
945
946    zoom: function (delta) {
947      var image = this.image,
948          width,
949          height,
950          range;
951
952      delta = num(delta);
953
954      if (!this.built || !delta || this.disabled || !this.defaults.zoomable) {
955        return;
956      }
957
958      width = image.width * (1 + delta);
959      height = image.height * (1 + delta);
960      range = width / image._width;
961
962      if (range > 10) {
963        return;
964      }
965
966      if (range < 1) {
967        width = image._width;
968        height = image._height;
969      }
970
971      if (range <= 1) {
972        this.setDragMode("crop");
973      } else {
974        this.setDragMode("move");
975      }
976
977      image.oldWidth = image.width;
978      image.oldHeight = image.height;
979
980      image.width = width;
981      image.height = height;
982      image.ratio = image.width / image.naturalWidth;
983
984      this.renderImage("zoom");
985    },
986
987    dblclick: function () {
988      if (this.disabled) {
989        return;
990      }
991
992      if (this.$canvas.hasClass(CLASS_CROP)) {
993        this.setDragMode("move");
994      } else {
995        this.setDragMode("crop");
996      }
997    },
998
999    wheel: function (event) {
1000      var e = event.originalEvent,
1001          delta = 1;
1002
1003      if (this.disabled) {
1004        return;
1005      }
1006
1007      event.preventDefault();
1008
1009      if (e.deltaY) {
1010        delta = e.deltaY > 0 ? 1 : -1;
1011      } else if (e.wheelDelta) {
1012        delta = -e.wheelDelta / 120;
1013      } else if (e.detail) {
1014        delta = e.detail > 0 ? 1 : -1;
1015      }
1016
1017      this.zoom(delta * 0.1);
1018    },
1019
1020    dragstart: function (event) {
1021      var touches = event.originalEvent.touches,
1022          e = event,
1023          directive,
1024          dragStartEvent,
1025          touchesLength;
1026
1027      if (this.disabled) {
1028        return;
1029      }
1030
1031      if (touches) {
1032        touchesLength = touches.length;
1033
1034        if (touchesLength > 1) {
1035          if (this.defaults.zoomable && touchesLength === 2) {
1036            e = touches[1];
1037            this.startX2 = e.pageX;
1038            this.startY2 = e.pageY;
1039            directive = "zoom";
1040          } else {
1041            return;
1042          }
1043        }
1044
1045        e = touches[0];
1046      }
1047
1048      directive = directive || $(e.target).data(STRING_DIRECTIVE);
1049
1050      if (REGEXP_DIRECTIVES.test(directive)) {
1051        event.preventDefault();
1052
1053        dragStartEvent = $.Event(EVENT_DRAG_START);
1054        this.$element.trigger(dragStartEvent);
1055
1056        if (dragStartEvent.isDefaultPrevented()) {
1057          return;
1058        }
1059
1060        this.directive = directive;
1061        this.cropping = FALSE;
1062        this.startX = e.pageX;
1063        this.startY = e.pageY;
1064
1065        if (directive === "crop") {
1066          this.cropping = TRUE;
1067          this.$canvas.addClass(CLASS_MODAL);
1068        }
1069      }
1070    },
1071
1072    dragmove: function (event) {
1073      var touches = event.originalEvent.touches,
1074          e = event,
1075          dragMoveEvent,
1076          touchesLength;
1077
1078      if (this.disabled) {
1079        return;
1080      }
1081
1082      if (touches) {
1083        touchesLength = touches.length;
1084
1085        if (touchesLength > 1) {
1086          if (this.defaults.zoomable && touchesLength === 2) {
1087            e = touches[1];
1088            this.endX2 = e.pageX;
1089            this.endY2 = e.pageY;
1090          } else {
1091            return;
1092          }
1093        }
1094
1095        e = touches[0];
1096      }
1097
1098      if (this.directive) {
1099        event.preventDefault();
1100
1101        dragMoveEvent = $.Event(EVENT_DRAG_MOVE);
1102        this.$element.trigger(dragMoveEvent);
1103
1104        if (dragMoveEvent.isDefaultPrevented()) {
1105          return;
1106        }
1107
1108        this.endX = e.pageX;
1109        this.endY = e.pageY;
1110
1111        this.dragging();
1112      }
1113    },
1114
1115    dragend: function (event) {
1116      var dragEndEvent;
1117
1118      if (this.disabled) {
1119        return;
1120      }
1121
1122      if (this.directive) {
1123        event.preventDefault();
1124
1125        dragEndEvent = $.Event(EVENT_DRAG_END);
1126        this.$element.trigger(dragEndEvent);
1127
1128        if (dragEndEvent.isDefaultPrevented()) {
1129          return;
1130        }
1131
1132        if (this.cropping) {
1133          this.cropping = FALSE;
1134          this.$canvas.toggleClass(CLASS_MODAL, this.cropped && this.defaults.modal);
1135        }
1136
1137        this.directive = "";
1138      }
1139    },
1140
1141    dragging: function () {
1142      var directive = this.directive,
1143          image = this.image,
1144          cropper = this.cropper,
1145          maxWidth = cropper.width,
1146          maxHeight = cropper.height,
1147          dragger = this.dragger,
1148          width = dragger.width,
1149          height = dragger.height,
1150          left = dragger.left,
1151          top = dragger.top,
1152          right = left + width,
1153          bottom = top + height,
1154          renderable = TRUE,
1155          aspectRatio = this.defaults.aspectRatio,
1156          range = {
1157            x: this.endX - this.startX,
1158            y: this.endY - this.startY
1159          },
1160          offset;
1161
1162      if (aspectRatio) {
1163        range.X = range.y * aspectRatio;
1164        range.Y = range.x / aspectRatio;
1165      }
1166
1167      switch (directive) {
1168        // Move dragger
1169        case "all":
1170          left += range.x;
1171          top += range.y;
1172
1173          break;
1174
1175        // Resize dragger
1176        case "e":
1177          if (range.x >= 0 && (right >= maxWidth || aspectRatio && (top <= 0 || bottom >= maxHeight))) {
1178            renderable = FALSE;
1179            break;
1180          }
1181
1182          width += range.x;
1183
1184          if (aspectRatio) {
1185            height = width / aspectRatio;
1186            top -= range.Y / 2;
1187          }
1188
1189          if (width < 0) {
1190            directive = "w";
1191            width = 0;
1192          }
1193
1194          break;
1195
1196        case "n":
1197          if (range.y <= 0 && (top <= 0 || aspectRatio && (left <= 0 || right >= maxWidth))) {
1198            renderable = FALSE;
1199            break;
1200          }
1201
1202          height -= range.y;
1203          top += range.y;
1204
1205          if (aspectRatio) {
1206            width = height * aspectRatio;
1207            left += range.X / 2;
1208          }
1209
1210          if (height < 0) {
1211            directive = "s";
1212            height = 0;
1213          }
1214
1215          break;
1216
1217        case "w":
1218          if (range.x <= 0 && (left <= 0 || aspectRatio && (top <= 0 || bottom >= maxHeight))) {
1219            renderable = FALSE;
1220            break;
1221          }
1222
1223          width -= range.x;
1224          left += range.x;
1225
1226          if (aspectRatio) {
1227            height = width / aspectRatio;
1228            top += range.Y / 2;
1229          }
1230
1231          if (width < 0) {
1232            directive = "e";
1233            width = 0;
1234          }
1235
1236          break;
1237
1238        case "s":
1239          if (range.y >= 0 && (bottom >= maxHeight || aspectRatio && (left <= 0 || right >= maxWidth))) {
1240            renderable = FALSE;
1241            break;
1242          }
1243
1244          height += range.y;
1245
1246          if (aspectRatio) {
1247            width = height * aspectRatio;
1248            left -= range.X / 2;
1249          }
1250
1251          if (height < 0) {
1252            directive = "n";
1253            height = 0;
1254          }
1255
1256          break;
1257
1258        case "ne":
1259          if (aspectRatio) {
1260            if (range.y <= 0 && (top <= 0 || right >= maxWidth)) {
1261              renderable = FALSE;
1262              break;
1263            }
1264
1265            height -= range.y;
1266            top += range.y;
1267            width = height * aspectRatio;
1268          } else {
1269            if (range.x >= 0) {
1270              if (right < maxWidth) {
1271                width += range.x;
1272              } else if (range.y <= 0 && top <= 0) {
1273                renderable = FALSE;
1274              }
1275            } else {
1276              width += range.x;
1277            }
1278
1279            if (range.y <= 0) {
1280              if (top > 0) {
1281                height -= range.y;
1282                top += range.y;
1283              }
1284            } else {
1285              height -= range.y;
1286              top += range.y;
1287            }
1288          }
1289
1290          if (width < 0 && height < 0) {
1291            directive = "sw";
1292            height = 0;
1293            width = 0;
1294          } else if (width < 0) {
1295            directive = "nw";
1296            width = 0;
1297          } else if (height < 0) {
1298            directive = "se";
1299            height = 0;
1300          }
1301
1302          break;
1303
1304        case "nw":
1305          if (aspectRatio) {
1306            if (range.y <= 0 && (top <= 0 || left <= 0)) {
1307              renderable = FALSE;
1308              break;
1309            }
1310
1311            height -= range.y;
1312            top += range.y;
1313            width = height * aspectRatio;
1314            left += range.X;
1315          } else {
1316            if (range.x <= 0) {
1317              if (left > 0) {
1318                width -= range.x;
1319                left += range.x;
1320              } else if (range.y <= 0 && top <= 0) {
1321                renderable = FALSE;
1322              }
1323            } else {
1324              width -= range.x;
1325              left += range.x;
1326            }
1327
1328            if (range.y <= 0) {
1329              if (top > 0) {
1330                height -= range.y;
1331                top += range.y;
1332              }
1333            } else {
1334              height -= range.y;
1335              top += range.y;
1336            }
1337          }
1338
1339          if (width < 0 && height < 0) {
1340            directive = "se";
1341            height = 0;
1342            width = 0;
1343          } else if (width < 0) {
1344            directive = "ne";
1345            width = 0;
1346          } else if (height < 0) {
1347            directive = "sw";
1348            height = 0;
1349          }
1350
1351          break;
1352
1353        case "sw":
1354          if (aspectRatio) {
1355            if (range.x <= 0 && (left <= 0 || bottom >= maxHeight)) {
1356              renderable = FALSE;
1357              break;
1358            }
1359
1360            width -= range.x;
1361            left += range.x;
1362            height = width / aspectRatio;
1363          } else {
1364            if (range.x <= 0) {
1365              if (left > 0) {
1366                width -= range.x;
1367                left += range.x;
1368              } else if (range.y >= 0 && bottom >= maxHeight) {
1369                renderable = FALSE;
1370              }
1371            } else {
1372              width -= range.x;
1373              left += range.x;
1374            }
1375
1376            if (range.y >= 0) {
1377              if (bottom < maxHeight) {
1378                height += range.y;
1379              }
1380            } else {
1381              height += range.y;
1382            }
1383          }
1384
1385          if (width < 0 && height < 0) {
1386            directive = "ne";
1387            height = 0;
1388            width = 0;
1389          } else if (width < 0) {
1390            directive = "se";
1391            width = 0;
1392          } else if (height < 0) {
1393            directive = "nw";
1394            height = 0;
1395          }
1396
1397          break;
1398
1399        case "se":
1400          if (aspectRatio) {
1401            if (range.x >= 0 && (right >= maxWidth || bottom >= maxHeight)) {
1402              renderable = FALSE;
1403              break;
1404            }
1405
1406            width += range.x;
1407            height = width / aspectRatio;
1408          } else {
1409            if (range.x >= 0) {
1410              if (right < maxWidth) {
1411                width += range.x;
1412              } else if (range.y >= 0 && bottom >= maxHeight) {
1413                renderable = FALSE;
1414              }
1415            } else {
1416              width += range.x;
1417            }
1418
1419            if (range.y >= 0) {
1420              if (bottom < maxHeight) {
1421                height += range.y;
1422              }
1423            } else {
1424              height += range.y;
1425            }
1426          }
1427
1428          if (width < 0 && height < 0) {
1429            directive = "nw";
1430            height = 0;
1431            width = 0;
1432          } else if (width < 0) {
1433            directive = "sw";
1434            width = 0;
1435          } else if (height < 0) {
1436            directive = "ne";
1437            height = 0;
1438          }
1439
1440          break;
1441
1442        // Move image
1443        case "move":
1444          image.left += range.x;
1445          image.top += range.y;
1446          this.renderImage("move");
1447          renderable = FALSE;
1448          break;
1449
1450        // Scale image
1451        case "zoom":
1452          this.zoom(function (x, y, x1, y1, x2, y2) {
1453            return (sqrt(x2 * x2 + y2 * y2) - sqrt(x1 * x1 + y1 * y1)) / sqrt(x * x + y * y);
1454          }(
1455            image.width,
1456            image.height,
1457            abs(this.startX - this.startX2),
1458            abs(this.startY - this.startY2),
1459            abs(this.endX - this.endX2),
1460            abs(this.endY - this.endY2)
1461          ));
1462
1463          this.endX2 = this.startX2;
1464          this.endY2 = this.startY2;
1465          renderable = FALSE;
1466          break;
1467
1468        // Crop image
1469        case "crop":
1470          if (range.x && range.y) {
1471            offset = this.$cropper.offset();
1472            left = this.startX - offset.left;
1473            top = this.startY - offset.top;
1474            width = dragger.minWidth;
1475            height = dragger.minHeight;
1476
1477            if (range.x > 0) {
1478              if (range.y > 0) {
1479                directive = "se";
1480              } else {
1481                directive = "ne";
1482                top -= height;
1483              }
1484            } else {
1485              if (range.y > 0) {
1486                directive = "sw";
1487                left -= width;
1488              } else {
1489                directive = "nw";
1490                left -= width;
1491                top -= height;
1492              }
1493            }
1494
1495            // Show the dragger if is hidden
1496            if (!this.cropped) {
1497              this.cropped = TRUE;
1498              this.$dragger.removeClass(CLASS_HIDDEN);
1499            }
1500          }
1501
1502          break;
1503
1504        // No default
1505      }
1506
1507      if (renderable) {
1508        dragger.width = width;
1509        dragger.height = height;
1510        dragger.left = left;
1511        dragger.top = top;
1512        this.directive = directive;
1513
1514        this.renderDragger();
1515      }
1516
1517      // Override
1518      this.startX = this.endX;
1519      this.startY = this.endY;
1520    }
1521  };
1522
1523  // Use the string compressor: Strmin (https://github.com/fengyuanchen/strmin)
1524  Cropper.TEMPLATE = (function (source, words) {
1525    words = words.split(",");
1526    return source.replace(/\d+/g, function (i) {
1527      return words[i];
1528    });
1529  })('<0 6="5-container"><0 6="5-canvas"></0><0 6="5-dragger"><1 6="5-viewer"></1><1 6="5-8 8-h"></1><1 6="5-8 8-v"></1><1 6="5-face" 3-2="all"></1><1 6="5-7 7-e" 3-2="e"></1><1 6="5-7 7-n" 3-2="n"></1><1 6="5-7 7-w" 3-2="w"></1><1 6="5-7 7-s" 3-2="s"></1><1 6="5-4 4-e" 3-2="e"></1><1 6="5-4 4-n" 3-2="n"></1><1 6="5-4 4-w" 3-2="w"></1><1 6="5-4 4-s" 3-2="s"></1><1 6="5-4 4-ne" 3-2="ne"></1><1 6="5-4 4-nw" 3-2="nw"></1><1 6="5-4 4-sw" 3-2="sw"></1><1 6="5-4 4-se" 3-2="se"></1></0></0>', "div,span,directive,data,point,cropper,class,line,dashed");
1530
1531  /* Template source:
1532  <div class="cropper-container">
1533    <div class="cropper-canvas"></div>
1534    <div class="cropper-dragger">
1535      <span class="cropper-viewer"></span>
1536      <span class="cropper-dashed dashed-h"></span>
1537      <span class="cropper-dashed dashed-v"></span>
1538      <span class="cropper-face" data-directive="all"></span>
1539      <span class="cropper-line line-e" data-directive="e"></span>
1540      <span class="cropper-line line-n" data-directive="n"></span>
1541      <span class="cropper-line line-w" data-directive="w"></span>
1542      <span class="cropper-line line-s" data-directive="s"></span>
1543      <span class="cropper-point point-e" data-directive="e"></span>
1544      <span class="cropper-point point-n" data-directive="n"></span>
1545      <span class="cropper-point point-w" data-directive="w"></span>
1546      <span class="cropper-point point-s" data-directive="s"></span>
1547      <span class="cropper-point point-ne" data-directive="ne"></span>
1548      <span class="cropper-point point-nw" data-directive="nw"></span>
1549      <span class="cropper-point point-sw" data-directive="sw"></span>
1550      <span class="cropper-point point-se" data-directive="se"></span>
1551    </div>
1552  </div>
1553  */
1554
1555  Cropper.DEFAULTS = {
1556    // Basic
1557    aspectRatio: "auto",
1558    autoCropArea: 0.8, // 80%
1559    data: {
1560       x: 18,
1561       y: 30,
1562       width: 150,
1563       height: 85
1564    },
1565    done: $.noop,
1566    preview: "",
1567
1568    // Toggles
1569    multiple: FALSE,
1570    autoCrop: TRUE,
1571    dragCrop: TRUE,
1572    dashed: TRUE,
1573    modal: FALSE,
1574    movable: TRUE,
1575    resizable: TRUE,
1576    zoomable: FALSE,
1577    rotatable: TRUE,
1578    checkImageOrigin: TRUE,
1579
1580    // Dimensions
1581    minWidth: 0,
1582    minHeight: 0,
1583    maxWidth: INFINITY,
1584    maxHeight: INFINITY,
1585    minContainerWidth: 300,
1586    minContainerHeight: 150,
1587
1588    // Events
1589    build: NULL,
1590    built: NULL,
1591    dragstart: NULL,
1592    dragmove: NULL,
1593    dragend: NULL
1594  };
1595
1596  Cropper.setDefaults = function (options) {
1597    $.extend(Cropper.DEFAULTS, options);
1598  };
1599
1600  // Save the other cropper
1601  Cropper.other = $.fn.cropper;
1602
1603  // Register as jQuery plugin
1604  $.fn.cropper = function (options) {
1605    var args = toArray(arguments, 1),
1606        result;
1607
1608    this.each(function () {
1609      var $this = $(this),
1610          data = $this.data("cropper"),
1611          fn;
1612
1613      if (!data) {
1614        $this.data("cropper", (data = new Cropper(this, options)));
1615      }
1616
1617      if (typeof options === "string" && $.isFunction((fn = data[options]))) {
1618        result = fn.apply(data, args);
1619      }
1620    });
1621
1622    return (typeof result !== STRING_UNDEFINED ? result : this);
1623  };
1624
1625  $.fn.cropper.Constructor = Cropper;
1626  $.fn.cropper.setDefaults = Cropper.setDefaults;
1627
1628  // No conflict
1629  $.fn.cropper.noConflict = function () {
1630    $.fn.cropper = Cropper.other;
1631    return this;
1632  };
1633});
Note: See TracBrowser for help on using the repository browser.