Browse code

Created repository.

DoubleBastionAdmin authored on 26/01/2022 20:32:42
Showing 1 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,1637 @@
1
+/**
2
+ *  Copyright (C) 2021  Double Bastion LLC
3
+ *
4
+ *  This file is part of Roundpin, which is licensed under the
5
+ *  GNU Affero General Public License Version 3.0. The license terms
6
+ *  are detailed in the "LICENSE.txt" file located in the root directory.
7
+ *
8
+ *  The file content from below is identical with that of the
9
+ *  original file "croppie.js" released under the MIT License:
10
+ *  https://github.com/Foliotek/Croppie/blob/master/LICENSE .
11
+ *  The copyright notice for the original content follows:
12
+ */
13
+/*************************
14
+ * Croppie
15
+ * Copyright 2019
16
+ * Foliotek
17
+ * Version: 2.6.4
18
+ *************************/
19
+(function (root, factory) {
20
+    if (typeof define === 'function' && define.amd) {
21
+        // AMD. Register as an anonymous module.
22
+        define(factory);
23
+    } else if (typeof exports === 'object' && typeof exports.nodeName !== 'string') {
24
+        // CommonJS
25
+        module.exports = factory();
26
+    } else {
27
+        // Browser globals
28
+        root.Croppie = factory();
29
+    }
30
+}(typeof self !== 'undefined' ? self : this, function () {
31
+
32
+    /* Polyfills */
33
+    if (typeof Promise !== 'function') {
34
+        /*! promise-polyfill 3.1.0 */
35
+        !function(a){function b(a,b){return function(){a.apply(b,arguments)}}function c(a){if("object"!==typeof this)throw new TypeError("Promises must be constructed via new");if("function"!==typeof a)throw new TypeError("not a function");this._state=null,this._value=null,this._deferreds=[],i(a,b(e,this),b(f,this))}function d(a){var b=this;return null===this._state?void this._deferreds.push(a):void k(function(){var c=b._state?a.onFulfilled:a.onRejected;if(null===c)return void(b._state?a.resolve:a.reject)(b._value);var d;try{d=c(b._value)}catch(e){return void a.reject(e)}a.resolve(d)})}function e(a){try{if(a===this)throw new TypeError("A promise cannot be resolved with itself.");if(a&&("object"===typeof a||"function"===typeof a)){var c=a.then;if("function"===typeof c)return void i(b(c,a),b(e,this),b(f,this))}this._state=!0,this._value=a,g.call(this)}catch(d){f.call(this,d)}}function f(a){this._state=!1,this._value=a,g.call(this)}function g(){for(var a=0,b=this._deferreds.length;b>a;a++)d.call(this,this._deferreds[a]);this._deferreds=null}function h(a,b,c,d){this.onFulfilled="function"===typeof a?a:null,this.onRejected="function"===typeof b?b:null,this.resolve=c,this.reject=d}function i(a,b,c){var d=!1;try{a(function(a){d||(d=!0,b(a))},function(a){d||(d=!0,c(a))})}catch(e){if(d)return;d=!0,c(e)}}var j=setTimeout,k="function"===typeof setImmediate&&setImmediate||function(a){j(a,1)},l=Array.isArray||function(a){return"[object Array]"===Object.prototype.toString.call(a)};c.prototype["catch"]=function(a){return this.then(null,a)},c.prototype.then=function(a,b){var e=this;return new c(function(c,f){d.call(e,new h(a,b,c,f))})},c.all=function(){var a=Array.prototype.slice.call(1===arguments.length&&l(arguments[0])?arguments[0]:arguments);return new c(function(b,c){function d(f,g){try{if(g&&("object"===typeof g||"function"===typeof g)){var h=g.then;if("function"===typeof h)return void h.call(g,function(a){d(f,a)},c)}a[f]=g,0===--e&&b(a)}catch(i){c(i)}}if(0===a.length)return b([]);for(var e=a.length,f=0;f<a.length;f++)d(f,a[f])})},c.resolve=function(a){return a&&"object"===typeof a&&a.constructor===c?a:new c(function(b){b(a)})},c.reject=function(a){return new c(function(b,c){c(a)})},c.race=function(a){return new c(function(b,c){for(var d=0,e=a.length;e>d;d++)a[d].then(b,c)})},c._setImmediateFn=function(a){k=a},"undefined"!==typeof module&&module.exports?module.exports=c:a.Promise||(a.Promise=c)}(this);
36
+    }
37
+
38
+    if ( typeof window.CustomEvent !== "function" ) {
39
+        (function(){
40
+            function CustomEvent ( event, params ) {
41
+                params = params || { bubbles: false, cancelable: false, detail: undefined };
42
+                var evt = document.createEvent( 'CustomEvent' );
43
+                evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail );
44
+                return evt;
45
+            }
46
+            CustomEvent.prototype = window.Event.prototype;
47
+            window.CustomEvent = CustomEvent;
48
+        }());
49
+    }
50
+
51
+    if (!HTMLCanvasElement.prototype.toBlob) {
52
+        Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', {
53
+            value: function (callback, type, quality) {
54
+                var binStr = atob( this.toDataURL(type, quality).split(',')[1] ),
55
+                len = binStr.length,
56
+                arr = new Uint8Array(len);
57
+
58
+                for (var i=0; i<len; i++ ) {
59
+                    arr[i] = binStr.charCodeAt(i);
60
+                }
61
+
62
+                callback( new Blob( [arr], {type: type || 'image/png'} ) );
63
+            }
64
+        });
65
+    }
66
+    /* End Polyfills */
67
+
68
+    var cssPrefixes = ['Webkit', 'Moz', 'ms'],
69
+        emptyStyles = document.createElement('div').style,
70
+        EXIF_NORM = [1,8,3,6],
71
+        EXIF_FLIP = [2,7,4,5],
72
+        CSS_TRANS_ORG,
73
+        CSS_TRANSFORM,
74
+        CSS_USERSELECT;
75
+
76
+    function vendorPrefix(prop) {
77
+        if (prop in emptyStyles) {
78
+            return prop;
79
+        }
80
+
81
+        var capProp = prop[0].toUpperCase() + prop.slice(1),
82
+            i = cssPrefixes.length;
83
+
84
+        while (i--) {
85
+            prop = cssPrefixes[i] + capProp;
86
+            if (prop in emptyStyles) {
87
+                return prop;
88
+            }
89
+        }
90
+    }
91
+
92
+    CSS_TRANSFORM = vendorPrefix('transform');
93
+    CSS_TRANS_ORG = vendorPrefix('transformOrigin');
94
+    CSS_USERSELECT = vendorPrefix('userSelect');
95
+
96
+    function getExifOffset(ornt, rotate) {
97
+        var arr = EXIF_NORM.indexOf(ornt) > -1 ? EXIF_NORM : EXIF_FLIP,
98
+            index = arr.indexOf(ornt),
99
+            offset = (rotate / 90) % arr.length;// 180 = 2%4 = 2 shift exif by 2 indexes
100
+
101
+        return arr[(arr.length + index + (offset % arr.length)) % arr.length];
102
+    }
103
+
104
+    // Credits to : Andrew Dupont - http://andrewdupont.net/2009/08/28/deep-extending-objects-in-javascript/
105
+    function deepExtend(destination, source) {
106
+        destination = destination || {};
107
+        for (var property in source) {
108
+            if (source[property] && source[property].constructor && source[property].constructor === Object) {
109
+                destination[property] = destination[property] || {};
110
+                deepExtend(destination[property], source[property]);
111
+            } else {
112
+                destination[property] = source[property];
113
+            }
114
+        }
115
+        return destination;
116
+    }
117
+
118
+    function clone(object) {
119
+        return deepExtend({}, object);
120
+    }
121
+
122
+    function debounce(func, wait, immediate) {
123
+        var timeout;
124
+        return function () {
125
+            var context = this, args = arguments;
126
+            var later = function () {
127
+                timeout = null;
128
+                if (!immediate) func.apply(context, args);
129
+            };
130
+            var callNow = immediate && !timeout;
131
+            clearTimeout(timeout);
132
+            timeout = setTimeout(later, wait);
133
+            if (callNow) func.apply(context, args);
134
+        };
135
+    }
136
+
137
+    function dispatchChange(element) {
138
+        if ("createEvent" in document) {
139
+            var evt = document.createEvent("HTMLEvents");
140
+            evt.initEvent("change", false, true);
141
+            element.dispatchEvent(evt);
142
+        }
143
+        else {
144
+            element.fireEvent("onchange");
145
+        }
146
+    }
147
+
148
+    //http://jsperf.com/vanilla-css
149
+    function css(el, styles, val) {
150
+        if (typeof (styles) === 'string') {
151
+            var tmp = styles;
152
+            styles = {};
153
+            styles[tmp] = val;
154
+        }
155
+
156
+        for (var prop in styles) {
157
+            el.style[prop] = styles[prop];
158
+        }
159
+    }
160
+
161
+    function addClass(el, c) {
162
+        if (el.classList) {
163
+            el.classList.add(c);
164
+        }
165
+        else {
166
+            el.className += ' ' + c;
167
+        }
168
+    }
169
+
170
+    function removeClass(el, c) {
171
+        if (el.classList) {
172
+            el.classList.remove(c);
173
+        }
174
+        else {
175
+            el.className = el.className.replace(c, '');
176
+        }
177
+    }
178
+
179
+    function setAttributes(el, attrs) {
180
+        for (var key in attrs) {
181
+            el.setAttribute(key, attrs[key]);
182
+        }
183
+    }
184
+
185
+    function num(v) {
186
+        return parseInt(v, 10);
187
+    }
188
+
189
+    /* Utilities */
190
+    function loadImage(src, doExif) {
191
+        var img = new Image();
192
+        img.style.opacity = '0';
193
+        return new Promise(function (resolve, reject) {
194
+            function _resolve() {
195
+                img.style.opacity = '1';
196
+                setTimeout(function () {
197
+                    resolve(img);
198
+                }, 1);
199
+            }
200
+
201
+            img.removeAttribute('crossOrigin');
202
+            if (src.match(/^https?:\/\/|^\/\//)) {
203
+                img.setAttribute('crossOrigin', 'anonymous');
204
+            }
205
+
206
+            img.onload = function () {
207
+                if (doExif) {
208
+                    EXIF.getData(img, function () {
209
+                        _resolve();
210
+                    });
211
+                }
212
+                else {
213
+                    _resolve();
214
+                }
215
+            };
216
+            img.onerror = function (ev) {
217
+                img.style.opacity = 1;
218
+                setTimeout(function () {
219
+                    reject(ev);
220
+                }, 1);
221
+            };
222
+            img.src = src;
223
+        });
224
+    }
225
+
226
+    function naturalImageDimensions(img, ornt) {
227
+        var w = img.naturalWidth;
228
+        var h = img.naturalHeight;
229
+        var orient = ornt || getExifOrientation(img);
230
+        if (orient && orient >= 5) {
231
+            var x= w;
232
+            w = h;
233
+            h = x;
234
+        }
235
+        return { width: w, height: h };
236
+    }
237
+
238
+    /* CSS Transform Prototype */
239
+    var TRANSLATE_OPTS = {
240
+        'translate3d': {
241
+            suffix: ', 0px'
242
+        },
243
+        'translate': {
244
+            suffix: ''
245
+        }
246
+    };
247
+    var Transform = function (x, y, scale) {
248
+        this.x = parseFloat(x);
249
+        this.y = parseFloat(y);
250
+        this.scale = parseFloat(scale);
251
+    };
252
+
253
+    Transform.parse = function (v) {
254
+        if (v.style) {
255
+            return Transform.parse(v.style[CSS_TRANSFORM]);
256
+        }
257
+        else if (v.indexOf('matrix') > -1 || v.indexOf('none') > -1) {
258
+            return Transform.fromMatrix(v);
259
+        }
260
+        else {
261
+            return Transform.fromString(v);
262
+        }
263
+    };
264
+
265
+    Transform.fromMatrix = function (v) {
266
+        var vals = v.substring(7).split(',');
267
+        if (!vals.length || v === 'none') {
268
+            vals = [1, 0, 0, 1, 0, 0];
269
+        }
270
+
271
+        return new Transform(num(vals[4]), num(vals[5]), parseFloat(vals[0]));
272
+    };
273
+
274
+    Transform.fromString = function (v) {
275
+        var values = v.split(') '),
276
+            translate = values[0].substring(Croppie.globals.translate.length + 1).split(','),
277
+            scale = values.length > 1 ? values[1].substring(6) : 1,
278
+            x = translate.length > 1 ? translate[0] : 0,
279
+            y = translate.length > 1 ? translate[1] : 0;
280
+
281
+        return new Transform(x, y, scale);
282
+    };
283
+
284
+    Transform.prototype.toString = function () {
285
+        var suffix = TRANSLATE_OPTS[Croppie.globals.translate].suffix || '';
286
+        return Croppie.globals.translate + '(' + this.x + 'px, ' + this.y + 'px' + suffix + ') scale(' + this.scale + ')';
287
+    };
288
+
289
+    var TransformOrigin = function (el) {
290
+        if (!el || !el.style[CSS_TRANS_ORG]) {
291
+            this.x = 0;
292
+            this.y = 0;
293
+            return;
294
+        }
295
+        var css = el.style[CSS_TRANS_ORG].split(' ');
296
+        this.x = parseFloat(css[0]);
297
+        this.y = parseFloat(css[1]);
298
+    };
299
+
300
+    TransformOrigin.prototype.toString = function () {
301
+        return this.x + 'px ' + this.y + 'px';
302
+    };
303
+
304
+    function getExifOrientation (img) {
305
+        return img.exifdata && img.exifdata.Orientation ? num(img.exifdata.Orientation) : 1;
306
+    }
307
+
308
+    function drawCanvas(canvas, img, orientation) {
309
+        var width = img.width,
310
+            height = img.height,
311
+            ctx = canvas.getContext('2d');
312
+
313
+        canvas.width = img.width;
314
+        canvas.height = img.height;
315
+
316
+        ctx.save();
317
+        switch (orientation) {
318
+          case 2:
319
+             ctx.translate(width, 0);
320
+             ctx.scale(-1, 1);
321
+             break;
322
+
323
+          case 3:
324
+              ctx.translate(width, height);
325
+              ctx.rotate(180*Math.PI/180);
326
+              break;
327
+
328
+          case 4:
329
+              ctx.translate(0, height);
330
+              ctx.scale(1, -1);
331
+              break;
332
+
333
+          case 5:
334
+              canvas.width = height;
335
+              canvas.height = width;
336
+              ctx.rotate(90*Math.PI/180);
337
+              ctx.scale(1, -1);
338
+              break;
339
+
340
+          case 6:
341
+              canvas.width = height;
342
+              canvas.height = width;
343
+              ctx.rotate(90*Math.PI/180);
344
+              ctx.translate(0, -height);
345
+              break;
346
+
347
+          case 7:
348
+              canvas.width = height;
349
+              canvas.height = width;
350
+              ctx.rotate(-90*Math.PI/180);
351
+              ctx.translate(-width, height);
352
+              ctx.scale(1, -1);
353
+              break;
354
+
355
+          case 8:
356
+              canvas.width = height;
357
+              canvas.height = width;
358
+              ctx.translate(0, width);
359
+              ctx.rotate(-90*Math.PI/180);
360
+              break;
361
+        }
362
+        ctx.drawImage(img, 0,0, width, height);
363
+        ctx.restore();
364
+    }
365
+
366
+    /* Private Methods */
367
+    function _create() {
368
+        var self = this,
369
+            contClass = 'croppie-container',
370
+            customViewportClass = self.options.viewport.type ? 'cr-vp-' + self.options.viewport.type : null,
371
+            boundary, img, viewport, overlay, bw, bh;
372
+
373
+        self.options.useCanvas = self.options.enableOrientation || _hasExif.call(self);
374
+        // Properties on class
375
+        self.data = {};
376
+        self.elements = {};
377
+
378
+        boundary = self.elements.boundary = document.createElement('div');
379
+        viewport = self.elements.viewport = document.createElement('div');
380
+        img = self.elements.img = document.createElement('img');
381
+        overlay = self.elements.overlay = document.createElement('div');
382
+
383
+        if (self.options.useCanvas) {
384
+            self.elements.canvas = document.createElement('canvas');
385
+            self.elements.preview = self.elements.canvas;
386
+        }
387
+        else {
388
+            self.elements.preview = img;
389
+        }
390
+
391
+        addClass(boundary, 'cr-boundary');
392
+        boundary.setAttribute('aria-dropeffect', 'none');
393
+        bw = self.options.boundary.width;
394
+        bh = self.options.boundary.height;
395
+        css(boundary, {
396
+            width: (bw + (isNaN(bw) ? '' : 'px')),
397
+            height: (bh + (isNaN(bh) ? '' : 'px'))
398
+        });
399
+
400
+        addClass(viewport, 'cr-viewport');
401
+        if (customViewportClass) {
402
+            addClass(viewport, customViewportClass);
403
+        }
404
+        css(viewport, {
405
+            width: self.options.viewport.width + 'px',
406
+            height: self.options.viewport.height + 'px'
407
+        });
408
+        viewport.setAttribute('tabindex', 0);
409
+
410
+        addClass(self.elements.preview, 'cr-image');
411
+        setAttributes(self.elements.preview, { 'alt': 'preview', 'aria-grabbed': 'false' });
412
+        addClass(overlay, 'cr-overlay');
413
+
414
+        self.element.appendChild(boundary);
415
+        boundary.appendChild(self.elements.preview);
416
+        boundary.appendChild(viewport);
417
+        boundary.appendChild(overlay);
418
+
419
+        addClass(self.element, contClass);
420
+        if (self.options.customClass) {
421
+            addClass(self.element, self.options.customClass);
422
+        }
423
+
424
+        _initDraggable.call(this);
425
+
426
+        if (self.options.enableZoom) {
427
+            _initializeZoom.call(self);
428
+        }
429
+
430
+        // if (self.options.enableOrientation) {
431
+        //     _initRotationControls.call(self);
432
+        // }
433
+
434
+        if (self.options.enableResize) {
435
+            _initializeResize.call(self);
436
+        }
437
+    }
438
+
439
+    // function _initRotationControls () {
440
+    //     var self = this,
441
+    //         wrap, btnLeft, btnRight, iLeft, iRight;
442
+
443
+    //     wrap = document.createElement('div');
444
+    //     self.elements.orientationBtnLeft = btnLeft = document.createElement('button');
445
+    //     self.elements.orientationBtnRight = btnRight = document.createElement('button');
446
+
447
+    //     wrap.appendChild(btnLeft);
448
+    //     wrap.appendChild(btnRight);
449
+
450
+    //     iLeft = document.createElement('i');
451
+    //     iRight = document.createElement('i');
452
+    //     btnLeft.appendChild(iLeft);
453
+    //     btnRight.appendChild(iRight);
454
+
455
+    //     addClass(wrap, 'cr-rotate-controls');
456
+    //     addClass(btnLeft, 'cr-rotate-l');
457
+    //     addClass(btnRight, 'cr-rotate-r');
458
+
459
+    //     self.elements.boundary.appendChild(wrap);
460
+
461
+    //     btnLeft.addEventListener('click', function () {
462
+    //         self.rotate(-90);
463
+    //     });
464
+    //     btnRight.addEventListener('click', function () {
465
+    //         self.rotate(90);
466
+    //     });
467
+    // }
468
+
469
+    function _hasExif() {
470
+        return this.options.enableExif && window.EXIF;
471
+    }
472
+
473
+    function _initializeResize () {
474
+        var self = this;
475
+        var wrap = document.createElement('div');
476
+        var isDragging = false;
477
+        var direction;
478
+        var originalX;
479
+        var originalY;
480
+        var minSize = 50;
481
+        var maxWidth;
482
+        var maxHeight;
483
+        var vr;
484
+        var hr;
485
+
486
+        addClass(wrap, 'cr-resizer');
487
+        css(wrap, {
488
+            width: this.options.viewport.width + 'px',
489
+            height: this.options.viewport.height + 'px'
490
+        });
491
+
492
+        if (this.options.resizeControls.height) {
493
+            vr = document.createElement('div');
494
+            addClass(vr, 'cr-resizer-vertical');
495
+            wrap.appendChild(vr);
496
+        }
497
+
498
+        if (this.options.resizeControls.width) {
499
+            hr = document.createElement('div');
500
+            addClass(hr, 'cr-resizer-horisontal');
501
+            wrap.appendChild(hr);
502
+        }
503
+
504
+        function mouseDown(ev) {
505
+            if (ev.button !== undefined && ev.button !== 0) return;
506
+
507
+            ev.preventDefault();
508
+            if (isDragging) {
509
+                return;
510
+            }
511
+
512
+            var overlayRect = self.elements.overlay.getBoundingClientRect();
513
+
514
+            isDragging = true;
515
+            originalX = ev.pageX;
516
+            originalY = ev.pageY;
517
+            direction = ev.currentTarget.className.indexOf('vertical') !== -1 ? 'v' : 'h';
518
+            maxWidth = overlayRect.width;
519
+            maxHeight = overlayRect.height;
520
+
521
+            if (ev.touches) {
522
+                var touches = ev.touches[0];
523
+                originalX = touches.pageX;
524
+                originalY = touches.pageY;
525
+            }
526
+
527
+            window.addEventListener('mousemove', mouseMove);
528
+            window.addEventListener('touchmove', mouseMove);
529
+            window.addEventListener('mouseup', mouseUp);
530
+            window.addEventListener('touchend', mouseUp);
531
+            document.body.style[CSS_USERSELECT] = 'none';
532
+        }
533
+
534
+        function mouseMove(ev) {
535
+            var pageX = ev.pageX;
536
+            var pageY = ev.pageY;
537
+
538
+            ev.preventDefault();
539
+
540
+            if (ev.touches) {
541
+                var touches = ev.touches[0];
542
+                pageX = touches.pageX;
543
+                pageY = touches.pageY;
544
+            }
545
+
546
+            var deltaX = pageX - originalX;
547
+            var deltaY = pageY - originalY;
548
+            var newHeight = self.options.viewport.height + deltaY;
549
+            var newWidth = self.options.viewport.width + deltaX;
550
+
551
+            if (direction === 'v' && newHeight >= minSize && newHeight <= maxHeight) {
552
+                css(wrap, {
553
+                    height: newHeight + 'px'
554
+                });
555
+
556
+                self.options.boundary.height += deltaY;
557
+                css(self.elements.boundary, {
558
+                    height: self.options.boundary.height + 'px'
559
+                });
560
+
561
+                self.options.viewport.height += deltaY;
562
+                css(self.elements.viewport, {
563
+                    height: self.options.viewport.height + 'px'
564
+                });
565
+            }
566
+            else if (direction === 'h' && newWidth >= minSize && newWidth <= maxWidth) {
567
+                css(wrap, {
568
+                    width: newWidth + 'px'
569
+                });
570
+
571
+                self.options.boundary.width += deltaX;
572
+                css(self.elements.boundary, {
573
+                    width: self.options.boundary.width + 'px'
574
+                });
575
+
576
+                self.options.viewport.width += deltaX;
577
+                css(self.elements.viewport, {
578
+                    width: self.options.viewport.width + 'px'
579
+                });
580
+            }
581
+
582
+            _updateOverlay.call(self);
583
+            _updateZoomLimits.call(self);
584
+            _updateCenterPoint.call(self);
585
+            _triggerUpdate.call(self);
586
+            originalY = pageY;
587
+            originalX = pageX;
588
+        }
589
+
590
+        function mouseUp() {
591
+            isDragging = false;
592
+            window.removeEventListener('mousemove', mouseMove);
593
+            window.removeEventListener('touchmove', mouseMove);
594
+            window.removeEventListener('mouseup', mouseUp);
595
+            window.removeEventListener('touchend', mouseUp);
596
+            document.body.style[CSS_USERSELECT] = '';
597
+        }
598
+
599
+        if (vr) {
600
+            vr.addEventListener('mousedown', mouseDown);
601
+            vr.addEventListener('touchstart', mouseDown);
602
+        }
603
+
604
+        if (hr) {
605
+            hr.addEventListener('mousedown', mouseDown);
606
+            hr.addEventListener('touchstart', mouseDown);
607
+        }
608
+
609
+        this.elements.boundary.appendChild(wrap);
610
+    }
611
+
612
+    function _setZoomerVal(v) {
613
+        if (this.options.enableZoom) {
614
+            var z = this.elements.zoomer,
615
+                val = fix(v, 4);
616
+
617
+            z.value = Math.max(parseFloat(z.min), Math.min(parseFloat(z.max), val)).toString();
618
+        }
619
+    }
620
+
621
+    function _initializeZoom() {
622
+        var self = this,
623
+            wrap = self.elements.zoomerWrap = document.createElement('div'),
624
+            zoomer = self.elements.zoomer = document.createElement('input');
625
+
626
+        addClass(wrap, 'cr-slider-wrap');
627
+        addClass(zoomer, 'cr-slider');
628
+        zoomer.type = 'range';
629
+        zoomer.step = '0.0001';
630
+        zoomer.value = '1';
631
+        zoomer.style.display = self.options.showZoomer ? '' : 'none';
632
+        zoomer.setAttribute('aria-label', 'zoom');
633
+
634
+        self.element.appendChild(wrap);
635
+        wrap.appendChild(zoomer);
636
+
637
+        self._currentZoom = 1;
638
+
639
+        function change() {
640
+            _onZoom.call(self, {
641
+                value: parseFloat(zoomer.value),
642
+                origin: new TransformOrigin(self.elements.preview),
643
+                viewportRect: self.elements.viewport.getBoundingClientRect(),
644
+                transform: Transform.parse(self.elements.preview)
645
+            });
646
+        }
647
+
648
+        function scroll(ev) {
649
+            var delta, targetZoom;
650
+
651
+            if(self.options.mouseWheelZoom === 'ctrl' && ev.ctrlKey !== true){
652
+              return 0; 
653
+            } else if (ev.wheelDelta) {
654
+                delta = ev.wheelDelta / 1200; //wheelDelta min: -120 max: 120 // max x 10 x 2
655
+            } else if (ev.deltaY) {
656
+                delta = ev.deltaY / 1060; //deltaY min: -53 max: 53 // max x 10 x 2
657
+            } else if (ev.detail) {
658
+                delta = ev.detail / -60; //delta min: -3 max: 3 // max x 10 x 2
659
+            } else {
660
+                delta = 0;
661
+            }
662
+
663
+            targetZoom = self._currentZoom + (delta * self._currentZoom);
664
+
665
+            ev.preventDefault();
666
+            _setZoomerVal.call(self, targetZoom);
667
+            change.call(self);
668
+        }
669
+
670
+        self.elements.zoomer.addEventListener('input', change);// this is being fired twice on keypress
671
+        self.elements.zoomer.addEventListener('change', change);
672
+
673
+        if (self.options.mouseWheelZoom) {
674
+            self.elements.boundary.addEventListener('mousewheel', scroll);
675
+            self.elements.boundary.addEventListener('DOMMouseScroll', scroll);
676
+        }
677
+    }
678
+
679
+    function _onZoom(ui) {
680
+        var self = this,
681
+            transform = ui ? ui.transform : Transform.parse(self.elements.preview),
682
+            vpRect = ui ? ui.viewportRect : self.elements.viewport.getBoundingClientRect(),
683
+            origin = ui ? ui.origin : new TransformOrigin(self.elements.preview);
684
+
685
+        function applyCss() {
686
+            var transCss = {};
687
+            transCss[CSS_TRANSFORM] = transform.toString();
688
+            transCss[CSS_TRANS_ORG] = origin.toString();
689
+            css(self.elements.preview, transCss);
690
+        }
691
+
692
+        self._currentZoom = ui ? ui.value : self._currentZoom;
693
+        transform.scale = self._currentZoom;
694
+        self.elements.zoomer.setAttribute('aria-valuenow', self._currentZoom);
695
+        applyCss();
696
+
697
+        if (self.options.enforceBoundary) {
698
+            var boundaries = _getVirtualBoundaries.call(self, vpRect),
699
+                transBoundaries = boundaries.translate,
700
+                oBoundaries = boundaries.origin;
701
+
702
+            if (transform.x >= transBoundaries.maxX) {
703
+                origin.x = oBoundaries.minX;
704
+                transform.x = transBoundaries.maxX;
705
+            }
706
+
707
+            if (transform.x <= transBoundaries.minX) {
708
+                origin.x = oBoundaries.maxX;
709
+                transform.x = transBoundaries.minX;
710
+            }
711
+
712
+            if (transform.y >= transBoundaries.maxY) {
713
+                origin.y = oBoundaries.minY;
714
+                transform.y = transBoundaries.maxY;
715
+            }
716
+
717
+            if (transform.y <= transBoundaries.minY) {
718
+                origin.y = oBoundaries.maxY;
719
+                transform.y = transBoundaries.minY;
720
+            }
721
+        }
722
+        applyCss();
723
+        _debouncedOverlay.call(self);
724
+        _triggerUpdate.call(self);
725
+    }
726
+
727
+    function _getVirtualBoundaries(viewport) {
728
+        var self = this,
729
+            scale = self._currentZoom,
730
+            vpWidth = viewport.width,
731
+            vpHeight = viewport.height,
732
+            centerFromBoundaryX = self.elements.boundary.clientWidth / 2,
733
+            centerFromBoundaryY = self.elements.boundary.clientHeight / 2,
734
+            imgRect = self.elements.preview.getBoundingClientRect(),
735
+            curImgWidth = imgRect.width,
736
+            curImgHeight = imgRect.height,
737
+            halfWidth = vpWidth / 2,
738
+            halfHeight = vpHeight / 2;
739
+
740
+        var maxX = ((halfWidth / scale) - centerFromBoundaryX) * -1;
741
+        var minX = maxX - ((curImgWidth * (1 / scale)) - (vpWidth * (1 / scale)));
742
+
743
+        var maxY = ((halfHeight / scale) - centerFromBoundaryY) * -1;
744
+        var minY = maxY - ((curImgHeight * (1 / scale)) - (vpHeight * (1 / scale)));
745
+
746
+        var originMinX = (1 / scale) * halfWidth;
747
+        var originMaxX = (curImgWidth * (1 / scale)) - originMinX;
748
+
749
+        var originMinY = (1 / scale) * halfHeight;
750
+        var originMaxY = (curImgHeight * (1 / scale)) - originMinY;
751
+
752
+        return {
753
+            translate: {
754
+                maxX: maxX,
755
+                minX: minX,
756
+                maxY: maxY,
757
+                minY: minY
758
+            },
759
+            origin: {
760
+                maxX: originMaxX,
761
+                minX: originMinX,
762
+                maxY: originMaxY,
763
+                minY: originMinY
764
+            }
765
+        };
766
+    }
767
+
768
+    function _updateCenterPoint(rotate) {
769
+        var self = this,
770
+            scale = self._currentZoom,
771
+            data = self.elements.preview.getBoundingClientRect(),
772
+            vpData = self.elements.viewport.getBoundingClientRect(),
773
+            transform = Transform.parse(self.elements.preview.style[CSS_TRANSFORM]),
774
+            pc = new TransformOrigin(self.elements.preview),
775
+            top = (vpData.top - data.top) + (vpData.height / 2),
776
+            left = (vpData.left - data.left) + (vpData.width / 2),
777
+            center = {},
778
+            adj = {};
779
+
780
+        if (rotate) {
781
+            var cx = pc.x;
782
+            var cy = pc.y;
783
+            var tx = transform.x;
784
+            var ty = transform.y;
785
+
786
+            center.y = cx;
787
+            center.x = cy;
788
+            transform.y = tx;
789
+            transform.x = ty;
790
+        }
791
+        else {
792
+            center.y = top / scale;
793
+            center.x = left / scale;
794
+
795
+            adj.y = (center.y - pc.y) * (1 - scale);
796
+            adj.x = (center.x - pc.x) * (1 - scale);
797
+
798
+            transform.x -= adj.x;
799
+            transform.y -= adj.y;
800
+        }
801
+
802
+        var newCss = {};
803
+        newCss[CSS_TRANS_ORG] = center.x + 'px ' + center.y + 'px';
804
+        newCss[CSS_TRANSFORM] = transform.toString();
805
+        css(self.elements.preview, newCss);
806
+    }
807
+
808
+    function _initDraggable() {
809
+        var self = this,
810
+            isDragging = false,
811
+            originalX,
812
+            originalY,
813
+            originalDistance,
814
+            vpRect,
815
+            transform;
816
+
817
+        function assignTransformCoordinates(deltaX, deltaY) {
818
+            var imgRect = self.elements.preview.getBoundingClientRect(),
819
+                top = transform.y + deltaY,
820
+                left = transform.x + deltaX;
821
+
822
+            if (self.options.enforceBoundary) {
823
+                if (vpRect.top > imgRect.top + deltaY && vpRect.bottom < imgRect.bottom + deltaY) {
824
+                    transform.y = top;
825
+                }
826
+
827
+                if (vpRect.left > imgRect.left + deltaX && vpRect.right < imgRect.right + deltaX) {
828
+                    transform.x = left;
829
+                }
830
+            }
831
+            else {
832
+                transform.y = top;
833
+                transform.x = left;
834
+            }
835
+        }
836
+
837
+        function toggleGrabState(isDragging) {
838
+          self.elements.preview.setAttribute('aria-grabbed', isDragging);
839
+          self.elements.boundary.setAttribute('aria-dropeffect', isDragging? 'move': 'none');
840
+        }
841
+
842
+        function keyDown(ev) {
843
+            var LEFT_ARROW  = 37,
844
+                UP_ARROW    = 38,
845
+                RIGHT_ARROW = 39,
846
+                DOWN_ARROW  = 40;
847
+
848
+            if (ev.shiftKey && (ev.keyCode === UP_ARROW || ev.keyCode === DOWN_ARROW)) {
849
+                var zoom;
850
+                if (ev.keyCode === UP_ARROW) {
851
+                    zoom = parseFloat(self.elements.zoomer.value) + parseFloat(self.elements.zoomer.step)
852
+                }
853
+                else {
854
+                    zoom = parseFloat(self.elements.zoomer.value) - parseFloat(self.elements.zoomer.step)
855
+                }
856
+                self.setZoom(zoom);
857
+            }
858
+            else if (self.options.enableKeyMovement && (ev.keyCode >= 37 && ev.keyCode <= 40)) {
859
+                ev.preventDefault();
860
+                var movement = parseKeyDown(ev.keyCode);
861
+
862
+                transform = Transform.parse(self.elements.preview);
863
+                document.body.style[CSS_USERSELECT] = 'none';
864
+                vpRect = self.elements.viewport.getBoundingClientRect();
865
+                keyMove(movement);
866
+            }
867
+
868
+            function parseKeyDown(key) {
869
+                switch (key) {
870
+                    case LEFT_ARROW:
871
+                        return [1, 0];
872
+                    case UP_ARROW:
873
+                        return [0, 1];
874
+                    case RIGHT_ARROW:
875
+                        return [-1, 0];
876
+                    case DOWN_ARROW:
877
+                        return [0, -1];
878
+                }
879
+            }
880
+        }
881
+
882
+        function keyMove(movement) {
883
+            var deltaX = movement[0],
884
+                deltaY = movement[1],
885
+                newCss = {};
886
+
887
+            assignTransformCoordinates(deltaX, deltaY);
888
+
889
+            newCss[CSS_TRANSFORM] = transform.toString();
890
+            css(self.elements.preview, newCss);
891
+            _updateOverlay.call(self);
892
+            document.body.style[CSS_USERSELECT] = '';
893
+            _updateCenterPoint.call(self);
894
+            _triggerUpdate.call(self);
895
+            originalDistance = 0;
896
+        }
897
+
898
+        function mouseDown(ev) {
899
+            if (ev.button !== undefined && ev.button !== 0) return;
900
+
901
+            ev.preventDefault();
902
+            if (isDragging) return;
903
+            isDragging = true;
904
+            originalX = ev.pageX;
905
+            originalY = ev.pageY;
906
+
907
+            if (ev.touches) {
908
+                var touches = ev.touches[0];
909
+                originalX = touches.pageX;
910
+                originalY = touches.pageY;
911
+            }
912
+            toggleGrabState(isDragging);
913
+            transform = Transform.parse(self.elements.preview);
914
+            window.addEventListener('mousemove', mouseMove);
915
+            window.addEventListener('touchmove', mouseMove);
916
+            window.addEventListener('mouseup', mouseUp);
917
+            window.addEventListener('touchend', mouseUp);
918
+            document.body.style[CSS_USERSELECT] = 'none';
919
+            vpRect = self.elements.viewport.getBoundingClientRect();
920
+        }
921
+
922
+        function mouseMove(ev) {
923
+            ev.preventDefault();
924
+            var pageX = ev.pageX,
925
+                pageY = ev.pageY;
926
+
927
+            if (ev.touches) {
928
+                var touches = ev.touches[0];
929
+                pageX = touches.pageX;
930
+                pageY = touches.pageY;
931
+            }
932
+
933
+            var deltaX = pageX - originalX,
934
+                deltaY = pageY - originalY,
935
+                newCss = {};
936
+
937
+            if (ev.type === 'touchmove') {
938
+                if (ev.touches.length > 1) {
939
+                    var touch1 = ev.touches[0];
940
+                    var touch2 = ev.touches[1];
941
+                    var dist = Math.sqrt((touch1.pageX - touch2.pageX) * (touch1.pageX - touch2.pageX) + (touch1.pageY - touch2.pageY) * (touch1.pageY - touch2.pageY));
942
+
943
+                    if (!originalDistance) {
944
+                        originalDistance = dist / self._currentZoom;
945
+                    }
946
+
947
+                    var scale = dist / originalDistance;
948
+
949
+                    _setZoomerVal.call(self, scale);
950
+                    dispatchChange(self.elements.zoomer);
951
+                    return;
952
+                }
953
+            }
954
+
955
+            assignTransformCoordinates(deltaX, deltaY);
956
+
957
+            newCss[CSS_TRANSFORM] = transform.toString();
958
+            css(self.elements.preview, newCss);
959
+            _updateOverlay.call(self);
960
+            originalY = pageY;
961
+            originalX = pageX;
962
+        }
963
+
964
+        function mouseUp() {
965
+            isDragging = false;
966
+            toggleGrabState(isDragging);
967
+            window.removeEventListener('mousemove', mouseMove);
968
+            window.removeEventListener('touchmove', mouseMove);
969
+            window.removeEventListener('mouseup', mouseUp);
970
+            window.removeEventListener('touchend', mouseUp);
971
+            document.body.style[CSS_USERSELECT] = '';
972
+            _updateCenterPoint.call(self);
973
+            _triggerUpdate.call(self);
974
+            originalDistance = 0;
975
+        }
976
+
977
+        self.elements.overlay.addEventListener('mousedown', mouseDown);
978
+        self.elements.viewport.addEventListener('keydown', keyDown);
979
+        self.elements.overlay.addEventListener('touchstart', mouseDown);
980
+    }
981
+
982
+    function _updateOverlay() {
983
+        if (!this.elements) return; // since this is debounced, it can be fired after destroy
984
+        var self = this,
985
+            boundRect = self.elements.boundary.getBoundingClientRect(),
986
+            imgData = self.elements.preview.getBoundingClientRect();
987
+
988
+        css(self.elements.overlay, {
989
+            width: imgData.width + 'px',
990
+            height: imgData.height + 'px',
991
+            top: (imgData.top - boundRect.top) + 'px',
992
+            left: (imgData.left - boundRect.left) + 'px'
993
+        });
994
+    }
995
+    var _debouncedOverlay = debounce(_updateOverlay, 500);
996
+
997
+    function _triggerUpdate() {
998
+        var self = this,
999
+            data = self.get();
1000
+
1001
+        if (!_isVisible.call(self)) {
1002
+            return;
1003
+        }
1004
+
1005
+        self.options.update.call(self, data);
1006
+        if (self.$ && typeof Prototype === 'undefined') {
1007
+            self.$(self.element).trigger('update.croppie', data);
1008
+        }
1009
+        else {
1010
+            var ev;
1011
+            if (window.CustomEvent) {
1012
+                ev = new CustomEvent('update', { detail: data });
1013
+            } else {
1014
+                ev = document.createEvent('CustomEvent');
1015
+                ev.initCustomEvent('update', true, true, data);
1016
+            }
1017
+
1018
+            self.element.dispatchEvent(ev);
1019
+        }
1020
+    }
1021
+
1022
+    function _isVisible() {
1023
+        return this.elements.preview.offsetHeight > 0 && this.elements.preview.offsetWidth > 0;
1024
+    }
1025
+
1026
+    function _updatePropertiesFromImage() {
1027
+        var self = this,
1028
+            initialZoom = 1,
1029
+            cssReset = {},
1030
+            img = self.elements.preview,
1031
+            imgData,
1032
+            transformReset = new Transform(0, 0, initialZoom),
1033
+            originReset = new TransformOrigin(),
1034
+            isVisible = _isVisible.call(self);
1035
+
1036
+        if (!isVisible || self.data.bound) {// if the croppie isn't visible or it doesn't need binding
1037
+            return;
1038
+        }
1039
+
1040
+        self.data.bound = true;
1041
+        cssReset[CSS_TRANSFORM] = transformReset.toString();
1042
+        cssReset[CSS_TRANS_ORG] = originReset.toString();
1043
+        cssReset['opacity'] = 1;
1044
+        css(img, cssReset);
1045
+
1046
+        imgData = self.elements.preview.getBoundingClientRect();
1047
+
1048
+        self._originalImageWidth = imgData.width;
1049
+        self._originalImageHeight = imgData.height;
1050
+        self.data.orientation = getExifOrientation(self.elements.img);
1051
+
1052
+        if (self.options.enableZoom) {
1053
+            _updateZoomLimits.call(self, true);
1054
+        }
1055
+        else {
1056
+            self._currentZoom = initialZoom;
1057
+        }
1058
+
1059
+        transformReset.scale = self._currentZoom;
1060
+        cssReset[CSS_TRANSFORM] = transformReset.toString();
1061
+        css(img, cssReset);
1062
+
1063
+        if (self.data.points.length) {
1064
+            _bindPoints.call(self, self.data.points);
1065
+        }
1066
+        else {
1067
+            _centerImage.call(self);
1068
+        }
1069
+
1070
+        _updateCenterPoint.call(self);
1071
+        _updateOverlay.call(self);
1072
+    }
1073
+
1074
+    function _updateZoomLimits (initial) {
1075
+        var self = this,
1076
+            minZoom = Math.max(self.options.minZoom, 0) || 0,
1077
+            maxZoom = self.options.maxZoom || 1.5,
1078
+            initialZoom,
1079
+            defaultInitialZoom,
1080
+            zoomer = self.elements.zoomer,
1081
+            scale = parseFloat(zoomer.value),
1082
+            boundaryData = self.elements.boundary.getBoundingClientRect(),
1083
+            imgData = naturalImageDimensions(self.elements.img, self.data.orientation),
1084
+            vpData = self.elements.viewport.getBoundingClientRect(),
1085
+            minW,
1086
+            minH;
1087
+        if (self.options.enforceBoundary) {
1088
+            minW = vpData.width / imgData.width;
1089
+            minH = vpData.height / imgData.height;
1090
+            minZoom = Math.max(minW, minH);
1091
+        }
1092
+
1093
+        if (minZoom >= maxZoom) {
1094
+            maxZoom = minZoom + 1;
1095
+        }
1096
+
1097
+        zoomer.min = fix(minZoom, 4);
1098
+        zoomer.max = fix(maxZoom, 4);
1099
+        
1100
+        if (!initial && (scale < zoomer.min || scale > zoomer.max)) {
1101
+            _setZoomerVal.call(self, scale < zoomer.min ? zoomer.min : zoomer.max);
1102
+        }
1103
+        else if (initial) {
1104
+            defaultInitialZoom = Math.max((boundaryData.width / imgData.width), (boundaryData.height / imgData.height));
1105
+            initialZoom = self.data.boundZoom !== null ? self.data.boundZoom : defaultInitialZoom;
1106
+            _setZoomerVal.call(self, initialZoom);
1107
+        }
1108
+
1109
+        dispatchChange(zoomer);
1110
+    }
1111
+
1112
+    function _bindPoints(points) {
1113
+        if (points.length !== 4) {
1114
+            throw "Croppie - Invalid number of points supplied: " + points;
1115
+        }
1116
+        var self = this,
1117
+            pointsWidth = points[2] - points[0],
1118
+            // pointsHeight = points[3] - points[1],
1119
+            vpData = self.elements.viewport.getBoundingClientRect(),
1120
+            boundRect = self.elements.boundary.getBoundingClientRect(),
1121
+            vpOffset = {
1122
+                left: vpData.left - boundRect.left,
1123
+                top: vpData.top - boundRect.top
1124
+            },
1125
+            scale = vpData.width / pointsWidth,
1126
+            originTop = points[1],
1127
+            originLeft = points[0],
1128
+            transformTop = (-1 * points[1]) + vpOffset.top,
1129
+            transformLeft = (-1 * points[0]) + vpOffset.left,
1130
+            newCss = {};
1131
+
1132
+        newCss[CSS_TRANS_ORG] = originLeft + 'px ' + originTop + 'px';
1133
+        newCss[CSS_TRANSFORM] = new Transform(transformLeft, transformTop, scale).toString();
1134
+        css(self.elements.preview, newCss);
1135
+
1136
+        _setZoomerVal.call(self, scale);
1137
+        self._currentZoom = scale;
1138
+    }
1139
+
1140
+    function _centerImage() {
1141
+        var self = this,
1142
+            imgDim = self.elements.preview.getBoundingClientRect(),
1143
+            vpDim = self.elements.viewport.getBoundingClientRect(),
1144
+            boundDim = self.elements.boundary.getBoundingClientRect(),
1145
+            vpLeft = vpDim.left - boundDim.left,
1146
+            vpTop = vpDim.top - boundDim.top,
1147
+            w = vpLeft - ((imgDim.width - vpDim.width) / 2),
1148
+            h = vpTop - ((imgDim.height - vpDim.height) / 2),
1149
+            transform = new Transform(w, h, self._currentZoom);
1150
+
1151
+        css(self.elements.preview, CSS_TRANSFORM, transform.toString());
1152
+    }
1153
+
1154
+    function _transferImageToCanvas(customOrientation) {
1155
+        var self = this,
1156
+            canvas = self.elements.canvas,
1157
+            img = self.elements.img,
1158
+            ctx = canvas.getContext('2d');
1159
+
1160
+        ctx.clearRect(0, 0, canvas.width, canvas.height);
1161
+        canvas.width = img.width;
1162
+        canvas.height = img.height;
1163
+
1164
+        var orientation = self.options.enableOrientation && customOrientation || getExifOrientation(img);
1165
+        drawCanvas(canvas, img, orientation);
1166
+    }
1167
+
1168
+    function _getCanvas(data) {
1169
+        var self = this,
1170
+            points = data.points,
1171
+            left = num(points[0]),
1172
+            top = num(points[1]),
1173
+            right = num(points[2]),
1174
+            bottom = num(points[3]),
1175
+            width = right-left,
1176
+            height = bottom-top,
1177
+            circle = data.circle,
1178
+            canvas = document.createElement('canvas'),
1179
+            ctx = canvas.getContext('2d'),
1180
+            startX = 0,
1181
+            startY = 0,
1182
+            canvasWidth = data.outputWidth || width,
1183
+            canvasHeight = data.outputHeight || height;
1184
+
1185
+        canvas.width = canvasWidth;
1186
+        canvas.height = canvasHeight;
1187
+
1188
+        if (data.backgroundColor) {
1189
+            ctx.fillStyle = data.backgroundColor;
1190
+            ctx.fillRect(0, 0, canvasWidth, canvasHeight);
1191
+        }
1192
+
1193
+        // By default assume we're going to draw the entire
1194
+        // source image onto the destination canvas.
1195
+        var sx = left,
1196
+            sy = top,
1197
+            sWidth = width,
1198
+            sHeight = height,
1199
+            dx = 0,
1200
+            dy = 0,
1201
+            dWidth = canvasWidth,
1202
+            dHeight = canvasHeight;
1203
+
1204
+        //
1205
+        // Do not go outside of the original image's bounds along the x-axis.
1206
+        // Handle translations when projecting onto the destination canvas.
1207
+        //
1208
+
1209
+        // The smallest possible source x-position is 0.
1210
+        if (left < 0) {
1211
+            sx = 0;
1212
+            dx = (Math.abs(left) / width) * canvasWidth;
1213
+        }
1214
+
1215
+        // The largest possible source width is the original image's width.
1216
+        if (sWidth + sx > self._originalImageWidth) {
1217
+            sWidth = self._originalImageWidth - sx;
1218
+            dWidth =  (sWidth / width) * canvasWidth;
1219
+        }
1220
+
1221
+        //
1222
+        // Do not go outside of the original image's bounds along the y-axis.
1223
+        //
1224
+
1225
+        // The smallest possible source y-position is 0.
1226
+        if (top < 0) {
1227
+            sy = 0;
1228
+            dy = (Math.abs(top) / height) * canvasHeight;
1229
+        }
1230
+
1231
+        // The largest possible source height is the original image's height.
1232
+        if (sHeight + sy > self._originalImageHeight) {
1233
+            sHeight = self._originalImageHeight - sy;
1234
+            dHeight = (sHeight / height) * canvasHeight;
1235
+        }
1236
+
1237
+        // console.table({ left, right, top, bottom, canvasWidth, canvasHeight, width, height, startX, startY, circle, sx, sy, dx, dy, sWidth, sHeight, dWidth, dHeight });
1238
+
1239
+        ctx.drawImage(this.elements.preview, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
1240
+        if (circle) {
1241
+            ctx.fillStyle = '#fff';
1242
+            ctx.globalCompositeOperation = 'destination-in';
1243
+            ctx.beginPath();
1244
+            ctx.arc(canvas.width / 2, canvas.height / 2, canvas.width / 2, 0, Math.PI * 2, true);
1245
+            ctx.closePath();
1246
+            ctx.fill();
1247
+        }
1248
+        return canvas;
1249
+    }
1250
+
1251
+    function _getHtmlResult(data) {
1252
+        var points = data.points,
1253
+            div = document.createElement('div'),
1254
+            img = document.createElement('img'),
1255
+            width = points[2] - points[0],
1256
+            height = points[3] - points[1];
1257
+
1258
+        addClass(div, 'croppie-result');
1259
+        div.appendChild(img);
1260
+        css(img, {
1261
+            left: (-1 * points[0]) + 'px',
1262
+            top: (-1 * points[1]) + 'px'
1263
+        });
1264
+        img.src = data.url;
1265
+        css(div, {
1266
+            width: width + 'px',
1267
+            height: height + 'px'
1268
+        });
1269
+
1270
+        return div;
1271
+    }
1272
+
1273
+    function _getBase64Result(data) {
1274
+        return _getCanvas.call(this, data).toDataURL(data.format, data.quality);
1275
+    }
1276
+
1277
+    function _getBlobResult(data) {
1278
+        var self = this;
1279
+        return new Promise(function (resolve) {
1280
+            _getCanvas.call(self, data).toBlob(function (blob) {
1281
+                resolve(blob);
1282
+            }, data.format, data.quality);
1283
+        });
1284
+    }
1285
+
1286
+    function _replaceImage(img) {
1287
+        if (this.elements.img.parentNode) {
1288
+            Array.prototype.forEach.call(this.elements.img.classList, function(c) { img.classList.add(c); });
1289
+            this.elements.img.parentNode.replaceChild(img, this.elements.img);
1290
+            this.elements.preview = img; // if the img is attached to the DOM, they're not using the canvas
1291
+        }
1292
+        this.elements.img = img;
1293
+    }
1294
+
1295
+    function _bind(options, cb) {
1296
+        var self = this,
1297
+            url,
1298
+            points = [],
1299
+            zoom = null,
1300
+            hasExif = _hasExif.call(self);
1301
+
1302
+        if (typeof (options) === 'string') {
1303
+            url = options;
1304
+            options = {};
1305
+        }
1306
+        else if (Array.isArray(options)) {
1307
+            points = options.slice();
1308
+        }
1309
+        else if (typeof (options) === 'undefined' && self.data.url) { //refreshing
1310
+            _updatePropertiesFromImage.call(self);
1311
+            _triggerUpdate.call(self);
1312
+            return null;
1313
+        }
1314
+        else {
1315
+            url = options.url;
1316
+            points = options.points || [];
1317
+            zoom = typeof(options.zoom) === 'undefined' ? null : options.zoom;
1318
+        }
1319
+
1320
+        self.data.bound = false;
1321
+        self.data.url = url || self.data.url;
1322
+        self.data.boundZoom = zoom;
1323
+
1324
+        return loadImage(url, hasExif).then(function (img) {
1325
+            _replaceImage.call(self, img);
1326
+            if (!points.length) {
1327
+                var natDim = naturalImageDimensions(img);
1328
+                var rect = self.elements.viewport.getBoundingClientRect();
1329
+                var aspectRatio = rect.width / rect.height;
1330
+                var imgAspectRatio = natDim.width / natDim.height;
1331
+                var width, height;
1332
+
1333
+                if (imgAspectRatio > aspectRatio) {
1334
+                    height = natDim.height;
1335
+                    width = height * aspectRatio;
1336
+                }
1337
+                else {
1338
+                    width = natDim.width;
1339
+                    height = natDim.height / aspectRatio;
1340
+                }
1341
+
1342
+                var x0 = (natDim.width - width) / 2;
1343
+                var y0 = (natDim.height - height) / 2;
1344
+                var x1 = x0 + width;
1345
+                var y1 = y0 + height;
1346
+                self.data.points = [x0, y0, x1, y1];
1347
+            }
1348
+            else if (self.options.relative) {
1349
+                points = [
1350
+                    points[0] * img.naturalWidth / 100,
1351
+                    points[1] * img.naturalHeight / 100,
1352
+                    points[2] * img.naturalWidth / 100,
1353
+                    points[3] * img.naturalHeight / 100
1354
+                ];
1355
+            }
1356
+
1357
+            self.data.points = points.map(function (p) {
1358
+                return parseFloat(p);
1359
+            });
1360
+            if (self.options.useCanvas) {
1361
+                _transferImageToCanvas.call(self, options.orientation);
1362
+            }
1363
+            _updatePropertiesFromImage.call(self);
1364
+            _triggerUpdate.call(self);
1365
+            cb && cb();
1366
+        });
1367
+    }
1368
+
1369
+    function fix(v, decimalPoints) {
1370
+        return parseFloat(v).toFixed(decimalPoints || 0);
1371
+    }
1372
+
1373
+    function _get() {
1374
+        var self = this,
1375
+            imgData = self.elements.preview.getBoundingClientRect(),
1376
+            vpData = self.elements.viewport.getBoundingClientRect(),
1377
+            x1 = vpData.left - imgData.left,
1378
+            y1 = vpData.top - imgData.top,
1379
+            widthDiff = (vpData.width - self.elements.viewport.offsetWidth) / 2, //border
1380
+            heightDiff = (vpData.height - self.elements.viewport.offsetHeight) / 2,
1381
+            x2 = x1 + self.elements.viewport.offsetWidth + widthDiff,
1382
+            y2 = y1 + self.elements.viewport.offsetHeight + heightDiff,
1383
+            scale = self._currentZoom;
1384
+
1385
+        if (scale === Infinity || isNaN(scale)) {
1386
+            scale = 1;
1387
+        }
1388
+
1389
+        var max = self.options.enforceBoundary ? 0 : Number.NEGATIVE_INFINITY;
1390
+        x1 = Math.max(max, x1 / scale);
1391
+        y1 = Math.max(max, y1 / scale);
1392
+        x2 = Math.max(max, x2 / scale);
1393
+        y2 = Math.max(max, y2 / scale);
1394
+
1395
+        return {
1396
+            points: [fix(x1), fix(y1), fix(x2), fix(y2)],
1397
+            zoom: scale,
1398
+            orientation: self.data.orientation
1399
+        };
1400
+    }
1401
+
1402
+    var RESULT_DEFAULTS = {
1403
+            type: 'canvas',
1404
+            format: 'png',
1405
+            quality: 1
1406
+        },
1407
+        RESULT_FORMATS = ['jpeg', 'webp', 'png'];
1408
+
1409
+    function _result(options) {
1410
+        var self = this,
1411
+            data = _get.call(self),
1412
+            opts = deepExtend(clone(RESULT_DEFAULTS), clone(options)),
1413
+            resultType = (typeof (options) === 'string' ? options : (opts.type || 'base64')),
1414
+            size = opts.size || 'viewport',
1415
+            format = opts.format,
1416
+            quality = opts.quality,
1417
+            backgroundColor = opts.backgroundColor,
1418
+            circle = typeof opts.circle === 'boolean' ? opts.circle : (self.options.viewport.type === 'circle'),
1419
+            vpRect = self.elements.viewport.getBoundingClientRect(),
1420
+            ratio = vpRect.width / vpRect.height,
1421
+            prom;
1422
+
1423
+        if (size === 'viewport') {
1424
+            data.outputWidth = vpRect.width;
1425
+            data.outputHeight = vpRect.height;
1426
+        } else if (typeof size === 'object') {
1427
+            if (size.width && size.height) {
1428
+                data.outputWidth = size.width;
1429
+                data.outputHeight = size.height;
1430
+            } else if (size.width) {
1431
+                data.outputWidth = size.width;
1432
+                data.outputHeight = size.width / ratio;
1433
+            } else if (size.height) {
1434
+                data.outputWidth = size.height * ratio;
1435
+                data.outputHeight = size.height;
1436
+            }
1437
+        }
1438
+
1439
+        if (RESULT_FORMATS.indexOf(format) > -1) {
1440
+            data.format = 'image/' + format;
1441
+            data.quality = quality;
1442
+        }
1443
+
1444
+        data.circle = circle;
1445
+        data.url = self.data.url;
1446
+        data.backgroundColor = backgroundColor;
1447
+
1448
+        prom = new Promise(function (resolve) {
1449
+            switch(resultType.toLowerCase())
1450
+            {
1451
+                case 'rawcanvas':
1452
+                    resolve(_getCanvas.call(self, data));
1453
+                    break;
1454
+                case 'canvas':
1455
+                case 'base64':
1456
+                    resolve(_getBase64Result.call(self, data));
1457
+                    break;
1458
+                case 'blob':
1459
+                    _getBlobResult.call(self, data).then(resolve);
1460
+                    break;
1461
+                default:
1462
+                    resolve(_getHtmlResult.call(self, data));
1463
+                    break;
1464
+            }
1465
+        });
1466
+        return prom;
1467
+    }
1468
+
1469
+    function _refresh() {
1470
+        _updatePropertiesFromImage.call(this);
1471
+    }
1472
+
1473
+    function _rotate(deg) {
1474
+        if (!this.options.useCanvas || !this.options.enableOrientation) {
1475
+            throw 'Croppie: Cannot rotate without enableOrientation && EXIF.js included';
1476
+        }
1477
+
1478
+        var self = this,
1479
+            canvas = self.elements.canvas;
1480
+
1481
+        self.data.orientation = getExifOffset(self.data.orientation, deg);
1482
+        drawCanvas(canvas, self.elements.img, self.data.orientation);
1483
+        _updateCenterPoint.call(self, true);
1484
+        _updateZoomLimits.call(self);
1485
+    }
1486
+
1487
+    function _destroy() {
1488
+        var self = this;
1489
+        self.element.removeChild(self.elements.boundary);
1490
+        removeClass(self.element, 'croppie-container');
1491
+        if (self.options.enableZoom) {
1492
+            self.element.removeChild(self.elements.zoomerWrap);
1493
+        }
1494
+        delete self.elements;
1495
+    }
1496
+
1497
+    if (window.jQuery) {
1498
+        var $ = window.jQuery;
1499
+        $.fn.croppie = function (opts) {
1500
+            var ot = typeof opts;
1501
+
1502
+            if (ot === 'string') {
1503
+                var args = Array.prototype.slice.call(arguments, 1);
1504
+                var singleInst = $(this).data('croppie');
1505
+
1506
+                if (opts === 'get') {
1507
+                    return singleInst.get();
1508
+                }
1509
+                else if (opts === 'result') {
1510
+                    return singleInst.result.apply(singleInst, args);
1511
+                }
1512
+                else if (opts === 'bind') {
1513
+                    return singleInst.bind.apply(singleInst, args);
1514
+                }
1515
+
1516
+                return this.each(function () {
1517
+                    var i = $(this).data('croppie');
1518
+                    if (!i) return;
1519
+
1520
+                    var method = i[opts];
1521
+                    if ($.isFunction(method)) {
1522
+                        method.apply(i, args);
1523
+                        if (opts === 'destroy') {
1524
+                            $(this).removeData('croppie');
1525
+                        }
1526
+                    }
1527
+                    else {
1528
+                        throw 'Croppie ' + opts + ' method not found';
1529
+                    }
1530
+                });
1531
+            }
1532
+            else {
1533
+                return this.each(function () {
1534
+                    var i = new Croppie(this, opts);
1535
+                    i.$ = $;
1536
+                    $(this).data('croppie', i);
1537
+                });
1538
+            }
1539
+        };
1540
+    }
1541
+
1542
+    function Croppie(element, opts) {
1543
+        if (element.className.indexOf('croppie-container') > -1) {
1544
+            throw new Error("Croppie: Can't initialize croppie more than once");
1545
+        }
1546
+        this.element = element;
1547
+        this.options = deepExtend(clone(Croppie.defaults), opts);
1548
+
1549
+        if (this.element.tagName.toLowerCase() === 'img') {
1550
+            var origImage = this.element;
1551
+            addClass(origImage, 'cr-original-image');
1552
+            setAttributes(origImage, {'aria-hidden' : 'true', 'alt' : '' });
1553
+            var replacementDiv = document.createElement('div');
1554
+            this.element.parentNode.appendChild(replacementDiv);
1555
+            replacementDiv.appendChild(origImage);
1556
+            this.element = replacementDiv;
1557
+            this.options.url = this.options.url || origImage.src;
1558
+        }
1559
+
1560
+        _create.call(this);
1561
+        if (this.options.url) {
1562
+            var bindOpts = {
1563
+                url: this.options.url,
1564
+                points: this.options.points
1565
+            };
1566
+            delete this.options['url'];
1567
+            delete this.options['points'];
1568
+            _bind.call(this, bindOpts);
1569
+        }
1570
+    }
1571
+
1572
+    Croppie.defaults = {
1573
+        viewport: {
1574
+            width: 100,
1575
+            height: 100,
1576
+            type: 'square'
1577
+        },
1578
+        boundary: { },
1579
+        orientationControls: {
1580
+            enabled: true,
1581
+            leftClass: '',
1582
+            rightClass: ''
1583
+        },
1584
+        resizeControls: {
1585
+            width: true,
1586
+            height: true
1587
+        },
1588
+        customClass: '',
1589
+        showZoomer: true,
1590
+        enableZoom: true,
1591
+        enableResize: false,
1592
+        mouseWheelZoom: true,
1593
+        enableExif: false,
1594
+        enforceBoundary: true,
1595
+        enableOrientation: false,
1596
+        enableKeyMovement: true,
1597
+        update: function () { }
1598
+    };
1599
+
1600
+    Croppie.globals = {
1601
+        translate: 'translate3d'
1602
+    };
1603
+
1604
+    deepExtend(Croppie.prototype, {
1605
+        bind: function (options, cb) {
1606
+            return _bind.call(this, options, cb);
1607
+        },
1608
+        get: function () {
1609
+            var data = _get.call(this);
1610
+            var points = data.points;
1611
+            if (this.options.relative) {
1612
+                points[0] /= this.elements.img.naturalWidth / 100;
1613
+                points[1] /= this.elements.img.naturalHeight / 100;
1614
+                points[2] /= this.elements.img.naturalWidth / 100;
1615
+                points[3] /= this.elements.img.naturalHeight / 100;
1616
+            }
1617
+            return data;
1618
+        },
1619
+        result: function (type) {
1620
+            return _result.call(this, type);
1621
+        },
1622
+        refresh: function () {
1623
+            return _refresh.call(this);
1624
+        },
1625
+        setZoom: function (v) {
1626
+            _setZoomerVal.call(this, v);
1627
+            dispatchChange(this.elements.zoomer);
1628
+        },
1629
+        rotate: function (deg) {
1630
+            _rotate.call(this, deg);
1631
+        },
1632
+        destroy: function () {
1633
+            return _destroy.call(this);
1634
+        }
1635
+    });
1636
+    return Croppie;
1637
+}));