PageSourceSearch

https://sedna360.com/js/ui-cropper.js

js sedna360.com collected 2026-09-24 11:06:00 UTC 172,103 bytes, 4,744 lines download raw bytes

1/*!
2 * uiCropper v1.0.9
3 * https://crackerakiua.github.io/ui-cropper/
4 *
5 * Copyright (c) 2019 Alex Kaul
6 * License: MIT
7 *
8 * Generated at Monday, May 20th, 2019, 10:17:03 PM
9 */
10(function() {
11angular.module('uiCropper', []);
12
13angular.module('uiCropper').factory('cropAreaCircle', ['cropArea', function(CropArea) {
14    var CropAreaCircle = function() {
15        CropArea.apply(this, arguments);
16
17        this._boxResizeBaseSize = 25;
18        this._boxResizeNormalRatio = 1;
19        this._boxResizeHoverRatio = 1.2;
20        this._iconMoveNormalRatio = 0.9;
21        this._iconMoveHoverRatio = 1.2;
22
23        this._boxResizeNormalSize = this._boxResizeBaseSize * this._boxResizeNormalRatio;
24        this._boxResizeHoverSize = this._boxResizeBaseSize * this._boxResizeHoverRatio;
25
26        this._posDragStartX = 0;
27        this._posDragStartY = 0;
28        this._posResizeStartX = 0;
29        this._posResizeStartY = 0;
30        this._posResizeStartSize = 0;
31
32        this._boxResizeIsHover = false;
33        this._areaIsHover = false;
34        this._boxResizeIsDragging = false;
35        this._areaIsDragging = false;
36    };
37
38    CropAreaCircle.prototype = new CropArea();
39
40    CropAreaCircle.prototype.getType = function() {
41        return 'circle';
42    };
43
44    CropAreaCircle.prototype._calcCirclePerimeterCoords = function(angleDegrees) {
45        var hSize = this._size.w / 2;
46        var angleRadians = angleDegrees * (Math.PI / 180),
47            circlePerimeterX = this.getCenterPoint().x + hSize * Math.cos(angleRadians),
48            circlePerimeterY = this.getCenterPoint().y + hSize * Math.sin(angleRadians);
49        return [circlePerimeterX, circlePerimeterY];
50    };
51
52    CropAreaCircle.prototype._calcResizeIconCenterCoords = function() {
53        return this._calcCirclePerimeterCoords(-45);
54    };
55
56    CropAreaCircle.prototype._isCoordWithinArea = function(coord) {
57        return Math.sqrt((coord[0] - this.getCenterPoint().x) * (coord[0] - this.getCenterPoint().x) + (coord[1] - this.getCenterPoint().y) * (coord[1] - this.getCenterPoint().y)) < this._size.w / 2;
58    };
59    CropAreaCircle.prototype._isCoordWithinBoxResize = function(coord) {
60        var resizeIconCenterCoords = this._calcResizeIconCenterCoords();
61        var hSize = this._boxResizeHoverSize / 2;
62        return (coord[0] > resizeIconCenterCoords[0] - hSize && coord[0] < resizeIconCenterCoords[0] + hSize &&
63            coord[1] > resizeIconCenterCoords[1] - hSize && coord[1] < resizeIconCenterCoords[1] + hSize);
64    };
65
66    CropAreaCircle.prototype._drawArea = function(ctx, centerCoords, size) {
67        ctx.arc(centerCoords.x, centerCoords.y, size.w / 2, 0, 2 * Math.PI);
68    };
69
70    CropAreaCircle.prototype.draw = function() {
71        CropArea.prototype.draw.apply(this, arguments);
72
73        // draw move icon
74        var center = this.getCenterPoint();
75        this._cropCanvas.drawIconMove([center.x, center.y], this._areaIsHover ? this._iconMoveHoverRatio : this._iconMoveNormalRatio);
76
77        // draw resize cubes
78        this._cropCanvas.drawIconResizeBoxNESW(this._calcResizeIconCenterCoords(), this._boxResizeBaseSize, this._boxResizeIsHover ? this._boxResizeHoverRatio : this._boxResizeNormalRatio);
79    };
80
81    CropAreaCircle.prototype.setSizeByScale = function(scale) {
82        var center = this.getCenterPoint();
83        var size = this.getSize();
84        var newRadius = size.w * scale / 2;
85        var northWestPoint = {
86            x: center.x - newRadius,
87            y: center.y - newRadius
88        };
89        var southEastPoint = {
90            x: center.x + newRadius,
91            y: center.y + newRadius
92        };
93        this.circleOnMove(northWestPoint, southEastPoint);
94        this._events.trigger('area-resize');
95    };
96
97    CropAreaCircle.prototype.processMouseMove = function(mouseCurX, mouseCurY) {
98        var cursor = 'default';
99        var res = false;
100
101        this._boxResizeIsHover = false;
102        this._areaIsHover = false;
103
104        if (this._areaIsDragging) {
105            this.setCenterPointOnMove({
106                x: mouseCurX - this._posDragStartX,
107                y: mouseCurY - this._posDragStartY
108            });
109            this._areaIsHover = true;
110            cursor = 'move';
111            res = true;
112            this._events.trigger('area-move');
113        } else if (this._boxResizeIsDragging) {
114            cursor = 'nesw-resize';
115            var iFR, iFX, iFY;
116            iFX = mouseCurX - this._posResizeStartX;
117            iFY = this._posResizeStartY - mouseCurY;
118            if (iFX > iFY) {
119                iFR = this._posResizeStartSize.w + iFY * 2;
120            } else {
121                iFR = this._posResizeStartSize.w + iFX * 2;
122            }
123
124            var newNO = {},
125                newSE = {};
126
127            newNO.x = this.getCenterPoint().x - iFR * 0.5;
128            newSE.x = this.getCenterPoint().x + iFR * 0.5;
129
130            newNO.y = this.getCenterPoint().y - iFR * 0.5;
131            newSE.y = this.getCenterPoint().y + iFR * 0.5;
132
133            this.circleOnMove(newNO, newSE);
134            this._boxResizeIsHover = true;
135            res = true;
136            this._events.trigger('area-resize');
137        } else if (this._isCoordWithinBoxResize([mouseCurX, mouseCurY])) {
138            cursor = 'nesw-resize';
139            this._areaIsHover = false;
140            this._boxResizeIsHover = true;
141            res = true;
142        } else if (this._isCoordWithinArea([mouseCurX, mouseCurY])) {
143            cursor = 'move';
144            this._areaIsHover = true;
145            res = true;
146        }
147
148        //this._dontDragOutside();
149        angular.element(this._ctx.canvas).css({
150            'cursor': cursor
151        });
152
153        return res;
154    };
155
156    CropAreaCircle.prototype.processMouseDown = function(mouseDownX, mouseDownY) {
157        if (this._isCoordWithinBoxResize([mouseDownX, mouseDownY])) {
158            this._areaIsDragging = false;
159            this._areaIsHover = false;
160            this._boxResizeIsDragging = true;
161            this._boxResizeIsHover = true;
162            this._posResizeStartX = mouseDownX;
163            this._posResizeStartY = mouseDownY;
164            this._posResizeStartSize = this._size;
165            this._events.trigger('area-resize-start');
166        } else if (this._isCoordWithinArea([mouseDownX, mouseDownY])) {
167            this._areaIsDragging = true;
168            this._areaIsHover = true;
169            this._boxResizeIsDragging = false;
170            this._boxResizeIsHover = false;
171            var center = this.getCenterPoint();
172            this._posDragStartX = mouseDownX - center.x;
173            this._posDragStartY = mouseDownY - center.y;
174            this._events.trigger('area-move-start');
175        }
176    };
177
178    CropAreaCircle.prototype.processMouseUp = function( /*mouseUpX, mouseUpY*/ ) {
179        if (this._areaIsDragging) {
180            this._areaIsDragging = false;
181            this._events.trigger('area-move-end');
182        }
183        if (this._boxResizeIsDragging) {
184            this._boxResizeIsDragging = false;
185            this._events.trigger('area-resize-end');
186        }
187        this._areaIsHover = false;
188        this._boxResizeIsHover = false;
189
190        this._posDragStartX = 0;
191        this._posDragStartY = 0;
192    };
193
194    return CropAreaCircle;
195}]);
196
197angular.module('uiCropper').factory('cropAreaRectangle', ['cropArea', function (CropArea) {
198    var CropAreaRectangle = function () {
199        CropArea.apply(this, arguments);
200
201        this._resizeCtrlBaseRadius = 15;
202        this._resizeCtrlNormalRatio = 0.6;
203        this._resizeCtrlHoverRatio = 0.70;
204        this._iconMoveNormalRatio = 0.9;
205        this._iconMoveHoverRatio = 1.2;
206
207        this._resizeCtrlNormalRadius = this._resizeCtrlBaseRadius * this._resizeCtrlNormalRatio;
208        this._resizeCtrlHoverRadius = this._resizeCtrlBaseRadius * this._resizeCtrlHoverRatio;
209
210        this._posDragStartX = 0;
211        this._posDragStartY = 0;
212        this._posResizeStartX = 0;
213        this._posResizeStartY = 0;
214        this._posResizeStartSize = {
215            w: 0,
216            h: 0
217        };
218
219        this._resizeCtrlIsHover = -1;
220        this._areaIsHover = false;
221        this._resizeCtrlIsDragging = -1;
222        this._areaIsDragging = false;
223    };
224
225    CropAreaRectangle.prototype = new CropArea();
226
227    // return a type string
228    CropAreaRectangle.prototype.getType = function () {
229        return 'rectangle';
230    };
231
232    CropAreaRectangle.prototype._calcRectangleCorners = function () {
233        var size = this.getSize();
234        var se = this.getSouthEastBound();
235        return [
236            [size.x, size.y], //northwest
237            [se.x, size.y], //northeast
238            [size.x, se.y], //southwest
239            [se.x, se.y] //southeast
240        ];
241    };
242
243    CropAreaRectangle.prototype._calcRectangleDimensions = function () {
244        var size = this.getSize();
245        var se = this.getSouthEastBound();
246        return {
247            left: size.x,
248            top: size.y,
249            right: se.x,
250            bottom: se.y
251        };
252    };
253
254    CropAreaRectangle.prototype._isCoordWithinArea = function (coord) {
255        var rectangleDimensions = this._calcRectangleDimensions();
256        return (coord[0] >= rectangleDimensions.left && coord[0] <= rectangleDimensions.right && coord[1] >= rectangleDimensions.top && coord[1] <= rectangleDimensions.bottom);
257    };
258
259    CropAreaRectangle.prototype._isCoordWithinResizeCtrl = function (coord) {
260        var resizeIconsCenterCoords = this._calcRectangleCorners();
261        var res = -1;
262        for (var i = 0, len = resizeIconsCenterCoords.length; i < len; i++) {
263            var resizeIconCenterCoords = resizeIconsCenterCoords[i];
264            if (coord[0] > resizeIconCenterCoords[0] - this._resizeCtrlHoverRadius && coord[0] < resizeIconCenterCoords[0] + this._resizeCtrlHoverRadius &&
265                coord[1] > resizeIconCenterCoords[1] - this._resizeCtrlHoverRadius && coord[1] < resizeIconCenterCoords[1] + this._resizeCtrlHoverRadius) {
266                res = i;
267                break;
268            }
269        }
270        return res;
271    };
272
273    CropAreaRectangle.prototype._drawArea = function (ctx, center, size) {
274        ctx.rect(size.x, size.y, size.w, size.h);
275    };
276
277    CropAreaRectangle.prototype.draw = function () {
278        CropArea.prototype.draw.apply(this, arguments);
279
280        var center = this.getCenterPoint();
281        // draw move icon
282        this._cropCanvas.drawIconMove([center.x, center.y], this._areaIsHover ? this._iconMoveHoverRatio : this._iconMoveNormalRatio);
283
284        // draw resize thumbs
285        var resizeIconsCenterCoords = this._calcRectangleCorners();
286        for (var i = 0, len = resizeIconsCenterCoords.length; i < len; i++) {
287            var resizeIconCenterCoords = resizeIconsCenterCoords[i];
288            this._cropCanvas.drawIconResizeBoxBase(resizeIconCenterCoords, this._resizeCtrlBaseRadius, this._resizeCtrlIsHover === i ? this._resizeCtrlHoverRatio : this._resizeCtrlNormalRatio);
289        }
290    };
291
292    CropAreaRectangle.prototype.setSizeByScale = function(scale, direction) {
293        var center = this.getCenterPoint();
294        var size = this.getSize();
295        var northWestPoint;
296        var southEastPoint;
297
298        if (this._aspect) {
299            var newWidth = size.w * scale;
300            var newHeight = size.h * scale;
301            northWestPoint = {
302                x: center.x - (newWidth / 2),
303                y: center.y - (newHeight / 2)
304            };
305            southEastPoint = {
306                x: center.x + (newWidth / 2),
307                y: center.y + (newHeight / 2)
308            };
309        } else {
310            northWestPoint = {
311                x: size.x,
312                y: size.y
313            };
314            southEastPoint = {
315                x: size.x + size.w,
316                y: size.y + size.h
317            };
318            switch (direction) {
319                case 'up':
320                case 'down':
321                    southEastPoint.y = size.y + (size.h * scale);
322                    break;
323                case 'left':
324                case 'right':
325                    southEastPoint.x = size.x + (size.w * scale);
326                    break;
327            }
328        }
329
330        this.setSizeByCorners(northWestPoint, southEastPoint);
331        this._events.trigger('area-resize');
332    };
333
334    CropAreaRectangle.prototype.processMouseMove = function (mouseCurX, mouseCurY) {
335        var cursor = 'default';
336        var res = false;
337
338        this._resizeCtrlIsHover = -1;
339        this._areaIsHover = false;
340
341        if (this._areaIsDragging) {
342            this.setCenterPointOnMove({
343                x: mouseCurX - this._posDragStartX,
344                y: mouseCurY - this._posDragStartY
345            });
346            this._areaIsHover = true;
347            cursor = 'move';
348            res = true;
349            this._events.trigger('area-move');
350        } else if (this._resizeCtrlIsDragging > -1) {
351            var s = this.getSize();
352            var se = this.getSouthEastBound();
353            var posX = mouseCurX;
354            switch (this._resizeCtrlIsDragging) {
355                case 0: // Top Left
356                    if (this._aspect) {
357                        posX = se.x - ((se.y - mouseCurY) * this._aspect);
358                    }
359                    this.setSizeByCorners({
360                        x: posX,
361                        y: mouseCurY
362                    }, {
363                        x: se.x,
364                        y: se.y
365                    });
366                    cursor = 'nwse-resize';
367                    break;
368                case 1: // Top Right
369                    if (this._aspect) {
370                        posX = s.x + ((se.y - mouseCurY) * this._aspect);
371                    }
372                    this.setSizeByCorners({
373                        x: s.x,
374                        y: mouseCurY
375                    }, {
376                        x: posX,
377                        y: se.y
378                    });
379                    cursor = 'nesw-resize';
380                    break;
381                case 2: // Bottom Left
382                    if (this._aspect) {
383                        posX = se.x - ((mouseCurY - s.y) * this._aspect);
384                    }
385                    this.setSizeByCorners({
386                        x: posX,
387                        y: s.y
388                    }, {
389                        x: se.x,
390                        y: mouseCurY
391                    });
392                    cursor = 'nesw-resize';
393                    break;
394                case 3: // Bottom Right
395                    if (this._aspect) {
396                        posX = s.x + ((mouseCurY - s.y) * this._aspect);
397                    }
398                    this.setSizeByCorners({
399                        x: s.x,
400                        y: s.y
401                    }, {
402                        x: posX,
403                        y: mouseCurY
404                    });
405                    cursor = 'nwse-resize';
406                    break;
407            }
408
409            this._resizeCtrlIsHover = this._resizeCtrlIsDragging;
410            res = true;
411            this._events.trigger('area-resize');
412        } else {
413            var hoveredResizeBox = this._isCoordWithinResizeCtrl([mouseCurX, mouseCurY]);
414            if (hoveredResizeBox > -1) {
415                switch (hoveredResizeBox) {
416                    case 0:
417                        cursor = 'nwse-resize';
418                        break;
419                    case 1:
420                        cursor = 'nesw-resize';
421                        break;
422                    case 2:
423                        cursor = 'nesw-resize';
424                        break;
425                    case 3:
426                        cursor = 'nwse-resize';
427                        break;
428                }
429                this._areaIsHover = false;
430                this._resizeCtrlIsHover = hoveredResizeBox;
431                res = true;
432            } else if (this._isCoordWithinArea([mouseCurX, mouseCurY])) {
433                cursor = 'move';
434                this._areaIsHover = true;
435                res = true;
436            }
437        }
438
439        angular.element(this._ctx.canvas).css({
440            'cursor': cursor
441        });
442
443        return res;
444    };
445
446    CropAreaRectangle.prototype.processMouseDown = function (mouseDownX, mouseDownY) {
447        var isWithinResizeCtrl = this._isCoordWithinResizeCtrl([mouseDownX, mouseDownY]);
448        if (isWithinResizeCtrl > -1) {
449            this._areaIsDragging = false;
450            this._areaIsHover = false;
451            this._resizeCtrlIsDragging = isWithinResizeCtrl;
452            this._resizeCtrlIsHover = isWithinResizeCtrl;
453            this._posResizeStartX = mouseDownX;
454            this._posResizeStartY = mouseDownY;
455            this._posResizeStartSize = this._size;
456            this._events.trigger('area-resize-start');
457        } else if (this._isCoordWithinArea([mouseDownX, mouseDownY])) {
458            this._areaIsDragging = true;
459            this._areaIsHover = true;
460            this._resizeCtrlIsDragging = -1;
461            this._resizeCtrlIsHover = -1;
462            var center = this.getCenterPoint();
463            this._posDragStartX = mouseDownX - center.x;
464            this._posDragStartY = mouseDownY - center.y;
465            this._events.trigger('area-move-start');
466        }
467    };
468
469    CropAreaRectangle.prototype.processMouseUp = function (/*mouseUpX, mouseUpY*/) {
470        if (this._areaIsDragging) {
471            this._areaIsDragging = false;
472            this._events.trigger('area-move-end');
473        }
474        if (this._resizeCtrlIsDragging > -1) {
475            this._resizeCtrlIsDragging = -1;
476            this._events.trigger('area-resize-end');
477        }
478        this._areaIsHover = false;
479        this._resizeCtrlIsHover = -1;
480
481        this._posDragStartX = 0;
482        this._posDragStartY = 0;
483    };
484
485    return CropAreaRectangle;
486}]);
487
488angular.module('uiCropper').factory('cropAreaSquare', ['cropArea', function(CropArea) {
489    var CropAreaSquare = function() {
490        CropArea.apply(this, arguments);
491
492        this._resizeCtrlBaseRadius = 15;
493        this._resizeCtrlNormalRatio = 0.6;
494        this._resizeCtrlHoverRatio = 0.70;
495        this._iconMoveNormalRatio = 0.9;
496        this._iconMoveHoverRatio = 1.2;
497
498        this._resizeCtrlNormalRadius = this._resizeCtrlBaseRadius * this._resizeCtrlNormalRatio;
499        this._resizeCtrlHoverRadius = this._resizeCtrlBaseRadius * this._resizeCtrlHoverRatio;
500
501        this._posDragStartX = 0;
502        this._posDragStartY = 0;
503        this._posResizeStartX = 0;
504        this._posResizeStartY = 0;
505        this._posResizeStartSize = 0;
506
507        this._resizeCtrlIsHover = -1;
508        this._areaIsHover = false;
509        this._resizeCtrlIsDragging = -1;
510        this._areaIsDragging = false;
511    };
512
513    CropAreaSquare.prototype = new CropArea();
514
515    CropAreaSquare.prototype.getType = function() {
516        return 'square';
517    };
518
519    CropAreaSquare.prototype._calcSquareCorners = function() {
520        var size = this.getSize(),
521            se = this.getSouthEastBound();
522        return [
523            [size.x, size.y], //northwest
524            [se.x, size.y], //northeast
525            [size.x, se.y], //southwest
526            [se.x, se.y] //southeast
527        ];
528    };
529
530    CropAreaSquare.prototype._calcSquareDimensions = function() {
531        var size = this.getSize(),
532            se = this.getSouthEastBound();
533        return {
534            left: size.x,
535            top: size.y,
536            right: se.x,
537            bottom: se.y
538        };
539    };
540
541    CropAreaSquare.prototype._isCoordWithinArea = function(coord) {
542        var squareDimensions = this._calcSquareDimensions();
543        return (coord[0] >= squareDimensions.left && coord[0] <= squareDimensions.right && coord[1] >= squareDimensions.top && coord[1] <= squareDimensions.bottom);
544    };
545
546    CropAreaSquare.prototype._isCoordWithinResizeCtrl = function(coord) {
547        var resizeIconsCenterCoords = this._calcSquareCorners();
548        var res = -1;
549        for (var i = 0, len = resizeIconsCenterCoords.length; i < len; i++) {
550            var resizeIconCenterCoords = resizeIconsCenterCoords[i];
551            if (coord[0] > resizeIconCenterCoords[0] - this._resizeCtrlHoverRadius && coord[0] < resizeIconCenterCoords[0] + this._resizeCtrlHoverRadius &&
552                coord[1] > resizeIconCenterCoords[1] - this._resizeCtrlHoverRadius && coord[1] < resizeIconCenterCoords[1] + this._resizeCtrlHoverRadius) {
553                res = i;
554                break;
555            }
556        }
557        return res;
558    };
559
560    CropAreaSquare.prototype._drawArea = function(ctx, centerCoords, size) {
561        ctx.rect(size.x, size.y, size.w, size.h);
562    };
563
564    CropAreaSquare.prototype.draw = function() {
565        CropArea.prototype.draw.apply(this, arguments);
566
567        // draw move icon
568        var center = this.getCenterPoint();
569        this._cropCanvas.drawIconMove([center.x, center.y], this._areaIsHover ? this._iconMoveHoverRatio : this._iconMoveNormalRatio);
570
571        // draw resize cubes
572        var resizeIconsCenterCoords = this._calcSquareCorners();
573        for (var i = 0, len = resizeIconsCenterCoords.length; i < len; i++) {
574            var resizeIconCenterCoords = resizeIconsCenterCoords[i];
575            this._cropCanvas.drawIconResizeBoxBase(resizeIconCenterCoords, this._resizeCtrlBaseRadius, this._resizeCtrlIsHover === i ? this._resizeCtrlHoverRatio : this._resizeCtrlNormalRatio);
576        }
577    };
578
579    CropAreaSquare.prototype._clampPoint = function(x, y) {
580        var size = this._ctx.canvas.width;
581
582        if(x < 0) {
583            y -= Math.abs(x);
584            x = 0;
585        }
586
587        if(y < 0) {
588            x -= Math.abs(y);
589            y = 0;
590        }
591
592        if(x > size) {
593            y -= (size - x);
594            x = size;
595        }
596
597        if(y > size) {
598            x -= (size - y);
599            y = size;
600        }
601
602        return {
603            x: x,
604            y: y
605        };
606    };
607
608    CropAreaSquare.prototype.setSizeByScale = function(scale) {
609        var center = this.getCenterPoint();
610        var size = this.getSize();
611        var newSize = size.w * scale;
612        if (newSize < this._minSize.w) {
613            newSize = this._minSize.w;
614        }
615        var northWestPoint = {
616            x: center.x - (newSize / 2),
617            y: center.y - (newSize / 2)
618        };
619        var southEastPoint = {
620            x: center.x + (newSize / 2),
621            y: center.y + (newSize / 2)
622        };
623        this.setSizeByCorners(northWestPoint, southEastPoint);
624        this._events.trigger('area-resize');
625    };
626
627    CropAreaSquare.prototype.processMouseMove = function(mouseCurX, mouseCurY) {
628        var cursor = 'default';
629        var res = false;
630
631        this._resizeCtrlIsHover = -1;
632        this._areaIsHover = false;
633
634        if (this._areaIsDragging) {
635            this.setCenterPointOnMove({
636                x: mouseCurX - this._posDragStartX,
637                y: mouseCurY - this._posDragStartY
638            });
639            this._areaIsHover = true;
640            cursor = 'move';
641            res = true;
642            this._events.trigger('area-move');
643        } else if (this._resizeCtrlIsDragging > -1) {
644            var xMulti, yMulti;
645            switch (this._resizeCtrlIsDragging) {
646                case 0: // Top Left
647                    xMulti = -1;
648                    yMulti = -1;
649                    cursor = 'nwse-resize';
650                    break;
651                case 1: // Top Right
652                    xMulti = 1;
653                    yMulti = -1;
654                    cursor = 'nesw-resize';
655                    break;
656                case 2: // Bottom Left
657                    xMulti = -1;
658                    yMulti = 1;
659                    cursor = 'nesw-resize';
660                    break;
661                case 3: // Bottom Right
662                    xMulti = 1;
663                    yMulti = 1;
664                    cursor = 'nwse-resize';
665                    break;
666            }
667            var iFX = (mouseCurX - this._posResizeStartX) * xMulti,
668                iFY = (mouseCurY - this._posResizeStartY) * yMulti,
669                iFR;
670            if (iFX > iFY) {
671                iFR = this._posResizeStartSize.w + iFY;
672            } else {
673                iFR = this._posResizeStartSize.w + iFX;
674            }
675            var newSize = Math.max(this._minSize.w, iFR),
676                newNO = {},
677                newSE = {},
678                newSO = {},
679                newNE = {},
680                s = this.getSize(),
681                se = this.getSouthEastBound();
682
683            switch (this._resizeCtrlIsDragging) {
684                case 0: // Top Left
685                    newNO.x = se.x - newSize;
686                    newNO.y = se.y - newSize;
687
688                    newNO = this._clampPoint(newNO.x, newNO.y);
689
690                    this.setSizeByCorners(newNO, {
691                        x: se.x,
692                        y: se.y
693                    });
694
695                    cursor = 'nwse-resize';
696                    break;
697                case 1: // Top Right
698
699                    newNE.x = s.x + newSize;
700                    newNE.y = se.y - newSize;
701
702                    newNE = this._clampPoint(newNE.x, newNE.y);
703
704                    this.setSizeByCorners({
705                        x: s.x,
706                        y: newNE.y
707                    }, {
708                        x: newNE.x,
709                        y: se.y
710                    });
711
712                    cursor = 'nesw-resize';
713                    break;
714                case 2: // Bottom Left
715                    newSO.x = se.x - newSize;
716                    newSO.y = s.y + newSize;
717
718                    newSO = this._clampPoint(newSO.x, newSO.y);
719
720                    this.setSizeByCorners({
721                        x: newSO.x,
722                        y: s.y
723                    }, {
724                        x: se.x,
725                        y: newSO.y
726                    });
727
728                    cursor = 'nesw-resize';
729                    break;
730                case 3: // Bottom Right
731
732                    newSE.x = s.x + newSize;
733                    newSE.y = s.y + newSize;
734
735                    newSE = this._clampPoint(newSE.x, newSE.y);
736
737                    this.setSizeByCorners({
738                        x: s.x,
739                        y: s.y
740                    }, newSE);
741
742                    cursor = 'nwse-resize';
743                    break;
744            }
745            this._resizeCtrlIsHover = this._resizeCtrlIsDragging;
746            res = true;
747            this._events.trigger('area-resize');
748        } else {
749            var hoveredResizeBox = this._isCoordWithinResizeCtrl([mouseCurX, mouseCurY]);
750            if (hoveredResizeBox > -1) {
751                switch (hoveredResizeBox) {
752                    case 0:
753                        cursor = 'nwse-resize';
754                        break;
755                    case 1:
756                        cursor = 'nesw-resize';
757                        break;
758                    case 2:
759                        cursor = 'nesw-resize';
760                        break;
761                    case 3:
762                        cursor = 'nwse-resize';
763                        break;
764                }
765                this._areaIsHover = false;
766                this._resizeCtrlIsHover = hoveredResizeBox;
767                res = true;
768            } else if (this._isCoordWithinArea([mouseCurX, mouseCurY])) {
769                cursor = 'move';
770                this._areaIsHover = true;
771                res = true;
772            }
773        }
774
775        angular.element(this._ctx.canvas).css({
776            'cursor': cursor
777        });
778
779        return res;
780    };
781
782    CropAreaSquare.prototype.processMouseDown = function(mouseDownX, mouseDownY) {
783        var isWithinResizeCtrl = this._isCoordWithinResizeCtrl([mouseDownX, mouseDownY]);
784        if (isWithinResizeCtrl > -1) {
785            this._areaIsDragging = false;
786            this._areaIsHover = false;
787            this._resizeCtrlIsDragging = isWithinResizeCtrl;
788            this._resizeCtrlIsHover = isWithinResizeCtrl;
789            this._posResizeStartX = mouseDownX;
790            this._posResizeStartY = mouseDownY;
791            this._posResizeStartSize = this._size;
792            this._events.trigger('area-resize-start');
793        } else if (this._isCoordWithinArea([mouseDownX, mouseDownY])) {
794            this._areaIsDragging = true;
795            this._areaIsHover = true;
796            this._resizeCtrlIsDragging = -1;
797            this._resizeCtrlIsHover = -1;
798            var center = this.getCenterPoint();
799            this._posDragStartX = mouseDownX - center.x;
800            this._posDragStartY = mouseDownY - center.y;
801            this._events.trigger('area-move-start');
802        }
803    };
804
805    CropAreaSquare.prototype.processMouseUp = function( /*mouseUpX, mouseUpY*/ ) {
806        if (this._areaIsDragging) {
807            this._areaIsDragging = false;
808            this._events.trigger('area-move-end');
809        }
810        if (this._resizeCtrlIsDragging > -1) {
811            this._resizeCtrlIsDragging = -1;
812            this._events.trigger('area-resize-end');
813        }
814        this._areaIsHover = false;
815        this._resizeCtrlIsHover = -1;
816
817        this._posDragStartX = 0;
818        this._posDragStartY = 0;
819    };
820
821    return CropAreaSquare;
822}]);
823
824angular.module('uiCropper').factory('cropArea', ['cropCanvas', function (CropCanvas) {
825    var CropArea = function (ctx, events) {
826        this._ctx = ctx;
827        this._events = events;
828
829        this._minSize = {
830            x: 0,
831            y: 0,
832            w: 80,
833            h: 80
834        };
835
836        this._initSize = undefined;
837        this._initCoords = undefined;
838        this._allowCropResizeOnCorners = false;
839
840        this._forceAspectRatio = false;
841        this._aspect = null;
842        this._disableCrop = false;
843
844        this._cropCanvas = new CropCanvas(ctx, this._disableCrop);
845
846        this._image = new Image();
847        this._size = {
848            x: 0,
849            y: 0,
850            w: 150,
851            h: 150
852        };
853    };
854
855    /* GETTERS/SETTERS */
856
857    CropArea.prototype.setAllowCropResizeOnCorners = function (bool) {
858        this._allowCropResizeOnCorners = bool;
859    };
860    CropArea.prototype.getImage = function () {
861        return this._image;
862    };
863    CropArea.prototype.setImage = function (image) {
864        this._image = image;
865    };
866    CropArea.prototype.setForceAspectRatio = function (force) {
867        this._forceAspectRatio = force;
868    };
869    CropArea.prototype.setAspect = function (aspect) {
870        this._aspect = aspect;
871    };
872    CropArea.prototype.getAspect = function () {
873        return this._aspect;
874    };
875    CropArea.prototype.getCanvasSize = function () {
876        return {
877            w: this._ctx.canvas.width,
878            h: this._ctx.canvas.height
879        };
880    };
881    CropArea.prototype.getSize = function () {
882        return this._size;
883    };
884    CropArea.prototype.setSize = function (size) {
885        size = this._processSize(size);
886        this._size = this._preventBoundaryCollision(size);
887    };
888    CropArea.prototype.setSizeOnMove = function (size) {
889        size = this._processSize(size);
890        if (this._allowCropResizeOnCorners) {
891            this._size = this._preventBoundaryCollision(size);
892        } else {
893            this._size = this._allowMouseOutsideCanvas(size);
894        }
895    };
896    CropArea.prototype.circleOnMove = function (northWestCorner, southEastCorner) {
897        var size = {
898            x: northWestCorner.x,
899            y: northWestCorner.y,
900            w: southEastCorner.x - northWestCorner.x,
901            h: southEastCorner.y - northWestCorner.y
902        };
903        var canvasH = this._ctx.canvas.height,
904            canvasW = this._ctx.canvas.width;
905        if (size.w > canvasW || size.h > canvasH) {
906            if (canvasW < canvasH) {
907                size.w = canvasW;
908                size.h = canvasW;
909            } else {
910                size.w = canvasH;
911                size.h = canvasH;
912            }
913        }
914        if (size.x + size.w > canvasW) {
915            size.x = canvasW - size.w;
916        }
917        if (size.y + size.h > canvasH) {
918            size.y = canvasH - size.h;
919        }
920        if (size.x < 0) {
921            size.x = 0;
922        }
923        if (size.y < 0) {
924            size.y = 0;
925        }
926        if (this._minSize.w > size.w) {
927            size.w = this._minSize.w;
928            size.x = this._size.x;
929        }
930        if (this._minSize.h > size.h) {
931            size.h = this._minSize.h;
932            size.y = this._size.y;
933        }
934        this._size = size;
935    };
936
937    CropArea.prototype.setSizeByCorners = function (northWestCorner, southEastCorner) {
938
939        var size = {
940            x: northWestCorner.x,
941            y: northWestCorner.y,
942            w: southEastCorner.x - northWestCorner.x,
943            h: southEastCorner.y - northWestCorner.y
944        };
945        this.setSize(size);
946    };
947
948    CropArea.prototype.getSouthEastBound = function () {
949        return this._southEastBound(this.getSize());
950    };
951
952    CropArea.prototype.setMinSize = function (size) {
953        this._minSize = this._processSize(size);
954        this.setSize(this._minSize);
955    };
956
957    CropArea.prototype.getMinSize = function () {
958        return this._minSize;
959    };
960
961    CropArea.prototype.getCenterPoint = function () {
962        var s = this.getSize();
963        return {
964            x: s.x + (s.w / 2),
965            y: s.y + (s.h / 2)
966        };
967    };
968
969    CropArea.prototype.setCenterPoint = function (point) {
970        var s = this.getSize();
971        this.setSize({
972            x: point.x - s.w / 2,
973            y: point.y - s.h / 2,
974            w: s.w,
975            h: s.h
976        });
977    };
978
979    CropArea.prototype.setCenterPointOnMove = function (point) {
980        var s = this.getSize();
981        this.setSizeOnMove({
982            x: point.x - s.w / 2,
983            y: point.y - s.h / 2,
984            w: s.w,
985            h: s.h
986        });
987    };
988
989    CropArea.prototype.setInitSize = function (size) {
990        this._initSize = this._processSize(size);
991        this.setSize(this._initSize);
992    };
993
994    CropArea.prototype.getInitSize = function () {
995        return this._initSize;
996    };
997
998    CropArea.prototype.setInitCoords = function (coords) {
999        //add h/w-data to coords-object
1000        coords.h = this.getSize().h;
1001        coords.w = this.getSize().w;
1002        this._initCoords = this._processSize(coords);
1003        this.setSize(this._initCoords);
1004    };
1005
1006    CropArea.prototype.setDisableCrop = function(value){
1007        this._disableCrop = value;
1008        this._cropCanvas = new CropCanvas(this._ctx, this._disableCrop);
1009    };
1010
1011    CropArea.prototype.getInitCoords = function () {
1012        return this._initCoords;
1013    };
1014
1015    // return a type string
1016    CropArea.prototype.getType = function () {
1017        //default to circle
1018        return 'circle';
1019    };
1020
1021    /* FUNCTIONS */
1022    CropArea.prototype._allowMouseOutsideCanvas = function (size) {
1023        var canvasH = this._ctx.canvas.height,
1024            canvasW = this._ctx.canvas.width;
1025        var newSize = {
1026            w: size.w,
1027            h: size.h,
1028        };
1029        if (size.x < 0) {
1030            newSize.x = 0;
1031        }
1032        else if (size.x + size.w > canvasW) {
1033            newSize.x = canvasW - size.w;
1034        }
1035        else {
1036            newSize.x = size.x;
1037        }
1038        if (size.y < 0) {
1039            newSize.y = 0;
1040        }
1041        else if (size.y + size.h > canvasH) {
1042            newSize.y = canvasH - size.h;
1043        }
1044        else {
1045            newSize.y = size.y;
1046        }
1047        return newSize;
1048    };
1049
1050    CropArea.prototype._preventBoundaryCollision = function (size) {
1051        var canvasH = this._ctx.canvas.height,
1052            canvasW = this._ctx.canvas.width;
1053
1054        var nw = {
1055            x: size.x,
1056            y: size.y
1057        };
1058        var se = this._southEastBound(size);
1059
1060        // check northwest corner
1061        if (nw.x < 0) {
1062            nw.x = 0;
1063        }
1064        if (nw.y < 0) {
1065            nw.y = 0;
1066        }
1067
1068        // check southeast corner
1069        if (se.x > canvasW) {
1070            se.x = canvasW;
1071        }
1072        if (se.y > canvasH) {
1073            se.y = canvasH;
1074        }
1075
1076        var newSizeWidth = (this._forceAspectRatio) ? size.w : se.x - nw.x,
1077            newSizeHeight = (this._forceAspectRatio) ? size.h : se.y - nw.y;
1078
1079        if (newSizeHeight > canvasH) {
1080            newSizeHeight = canvasH;
1081        }
1082
1083        // save rectangle scale
1084        if (this._aspect) {
1085            newSizeWidth = newSizeHeight * this._aspect;
1086            if (nw.x + newSizeWidth > canvasW) {
1087                newSizeWidth = canvasW - nw.x;
1088                newSizeHeight = newSizeWidth / this._aspect;
1089                if (this._minSize.w > newSizeWidth) {
1090                    newSizeWidth = this._minSize.w;
1091                }
1092                if (this._minSize.h > newSizeHeight) {
1093                    newSizeHeight = this._minSize.h;
1094                }
1095                nw.x = canvasW - newSizeWidth;
1096            }
1097            if (nw.y + newSizeHeight > canvasH) {
1098                nw.y = canvasH - newSizeHeight;
1099            }
1100        }
1101
1102        // save square scale
1103        if (this._forceAspectRatio) {
1104            newSizeWidth = newSizeHeight;
1105            if (nw.x + newSizeWidth > canvasW) {
1106                newSizeWidth = canvasW - nw.x;
1107                if (newSizeWidth < this._minSize.w) {
1108                    newSizeWidth = this._minSize.w;
1109                }
1110                newSizeHeight = newSizeWidth;
1111            }
1112        }
1113
1114        var newSize = {
1115            x: nw.x,
1116            y: nw.y,
1117            w: newSizeWidth,
1118            h: newSizeHeight
1119        };
1120
1121        //check size (if < min, adjust nw corner)
1122        if ((newSize.w < this._minSize.w) && !this._forceAspectRatio) {
1123            newSize.w = this._minSize.w;
1124            se = this._southEastBound(newSize);
1125            //adjust se corner, if it's out of bounds
1126            if (se.x > canvasW) {
1127                se.x = canvasW;
1128                //adjust nw corner according to min width
1129                nw.x = Math.max(se.x - canvasW, se.x - this._minSize.w);
1130                newSize = {
1131                    x: nw.x,
1132                    y: nw.y,
1133                    w: se.x - nw.x,
1134                    h: se.y - nw.y
1135                };
1136            }
1137        }
1138
1139        if ((newSize.h < this._minSize.h) && !this._forceAspectRatio) {
1140            newSize.h = this._minSize.h;
1141            se = this._southEastBound(newSize);
1142
1143            if (se.y > canvasH) {
1144                se.y = canvasH;
1145                //adjust nw corner according to min height
1146                nw.y = Math.max(se.y - canvasH, se.y - this._minSize.h);
1147                newSize = {
1148                    x: nw.x,
1149                    y: nw.y,
1150                    w: se.x - nw.x,
1151                    h: se.y - nw.y
1152                };
1153            }
1154        }
1155
1156        if (this._forceAspectRatio) {
1157            //check if outside SE bound
1158            se = this._southEastBound(newSize);
1159            if (se.y > canvasH) {
1160                newSize.y = canvasH - newSize.h;
1161            }
1162            if (se.x > canvasW) {
1163                newSize.x = canvasW - newSize.w;
1164            }
1165        }
1166
1167        return newSize;
1168    };
1169
1170    CropArea.prototype._dontDragOutside = function () {
1171        var h = this._ctx.canvas.height,
1172            w = this._ctx.canvas.width;
1173
1174        if (this._width > w) {
1175            this._width = w;
1176        }
1177        if (this._height > h) {
1178            this._height = h;
1179        }
1180        if (this._x < this._width / 2) {
1181            this._x = this._width / 2;
1182        }
1183        if (this._x > w - this._width / 2) {
1184            this._x = w - this._width / 2;
1185        }
1186        if (this._y < this._height / 2) {
1187            this._y = this._height / 2;
1188        }
1189        if (this._y > h - this._height / 2) {
1190            this._y = h - this._height / 2;
1191        }
1192    };
1193
1194    CropArea.prototype._drawArea = function () {
1195    };
1196
1197    CropArea.prototype._processSize = function (size) {
1198        // make this polymorphic to accept a single floating point number
1199        // for square-like sizes (including circle)
1200        if (typeof size === 'number') {
1201            size = {
1202                w: size,
1203                h: size
1204            };
1205        }
1206        var width = size.w;
1207        var height = size.h;
1208        if (this._aspect) {
1209            // In order to apply the initMax from the crop-host we need to update the width only when the aspect ratio is above 1.
1210            if (this.aspect >= 1) {
1211                width = size.h * this._aspect;
1212            } else {
1213                height = size.w / this._aspect;
1214            }
1215        }
1216        return {
1217            x: (typeof size.x === 'undefined') ? this.getSize().x : size.x,
1218            y: (typeof size.y === 'undefined') ? this.getSize().y : size.y,
1219            w: width || this._minSize.w,
1220            h: height || this._minSize.h
1221        };
1222    };
1223
1224    CropArea.prototype._southEastBound = function (size) {
1225        return {
1226            x: size.x + size.w,
1227            y: size.y + size.h
1228        };
1229    };
1230
1231    CropArea.prototype.draw = function () {
1232        // draw crop area
1233        this._cropCanvas.drawCropArea(this._image, this.getCenterPoint(), this._size, this._drawArea);
1234    };
1235
1236    CropArea.prototype.processMouseMove = function () {
1237    };
1238
1239    CropArea.prototype.processMouseDown = function () {
1240    };
1241
1242    CropArea.prototype.processMouseUp = function () {
1243    };
1244
1245    return CropArea;
1246}]);
1247
1248angular.module('uiCropper').factory('cropCanvas', [function() {
1249    // Shape = Array of [x,y]; [0, 0] - center
1250    var shapeArrowNW = [
1251        [-0.5, -2],
1252        [-3, -4.5],
1253        [-0.5, -7],
1254        [-7, -7],
1255        [-7, -0.5],
1256        [-4.5, -3],
1257        [-2, -0.5]
1258    ];
1259    var shapeArrowNE = [
1260        [0.5, -2],
1261        [3, -4.5],
1262        [0.5, -7],
1263        [7, -7],
1264        [7, -0.5],
1265        [4.5, -3],
1266        [2, -0.5]
1267    ];
1268    var shapeArrowSW = [
1269        [-0.5, 2],
1270        [-3, 4.5],
1271        [-0.5, 7],
1272        [-7, 7],
1273        [-7, 0.5],
1274        [-4.5, 3],
1275        [-2, 0.5]
1276    ];
1277    var shapeArrowSE = [
1278        [0.5, 2],
1279        [3, 4.5],
1280        [0.5, 7],
1281        [7, 7],
1282        [7, 0.5],
1283        [4.5, 3],
1284        [2, 0.5]
1285    ];
1286    var shapeArrowN = [
1287        [-1.5, -2.5],
1288        [-1.5, -6],
1289        [-5, -6],
1290        [0, -11],
1291        [5, -6],
1292        [1.5, -6],
1293        [1.5, -2.5]
1294    ];
1295    var shapeArrowW = [
1296        [-2.5, -1.5],
1297        [-6, -1.5],
1298        [-6, -5],
1299        [-11, 0],
1300        [-6, 5],
1301        [-6, 1.5],
1302        [-2.5, 1.5]
1303    ];
1304    var shapeArrowS = [
1305        [-1.5, 2.5],
1306        [-1.5, 6],
1307        [-5, 6],
1308        [0, 11],
1309        [5, 6],
1310        [1.5, 6],
1311        [1.5, 2.5]
1312    ];
1313    var shapeArrowE = [
1314        [2.5, -1.5],
1315        [6, -1.5],
1316        [6, -5],
1317        [11, 0],
1318        [6, 5],
1319        [6, 1.5],
1320        [2.5, 1.5]
1321    ];
1322
1323    // Colors
1324    var colors = {
1325        areaOutline: '#fff',
1326        resizeBoxStroke: '#bababa',
1327        resizeBoxFill: '#444',
1328        resizeBoxArrowFill: '#fff',
1329        resizeCircleStroke: '#bababa',
1330        resizeCircleFill: '#444',
1331        moveIconFill: '#fff'
1332    };
1333
1334    var cropper = {
1335        strokeWidth: 1
1336    };
1337
1338    return function(ctx, disable) {
1339
1340        /* Base functions */
1341
1342        // Calculate Point
1343        var calcPoint = function(point, offset, scale) {
1344            return [scale * point[0] + offset[0], scale * point[1] + offset[1]];
1345        };
1346
1347        // Draw Filled Polygon
1348        var drawFilledPolygon = function(shape, fillStyle, centerCoords, scale) {
1349            if(disable) {
1350                return;
1351            }
1352
1353            ctx.save();
1354            ctx.fillStyle = fillStyle;
1355            ctx.beginPath();
1356            var pc, pc0 = calcPoint(shape[0], centerCoords, scale);
1357            ctx.moveTo(pc0[0], pc0[1]);
1358
1359            for (var p in shape) {
1360                if (p > 0) {
1361                    pc = calcPoint(shape[p], centerCoords, scale);
1362                    ctx.lineTo(pc[0], pc[1]);
1363                }
1364            }
1365
1366            ctx.lineTo(pc0[0], pc0[1]);
1367            ctx.fill();
1368            ctx.closePath();
1369            ctx.restore();
1370        };
1371
1372        /* Icons */
1373
1374        this.drawIconMove = function(centerCoords, scale) {
1375            if(disable) {
1376                return;
1377            }
1378
1379            drawFilledPolygon(shapeArrowN, colors.moveIconFill, centerCoords, scale);
1380            drawFilledPolygon(shapeArrowW, colors.moveIconFill, centerCoords, scale);
1381            drawFilledPolygon(shapeArrowS, colors.moveIconFill, centerCoords, scale);
1382            drawFilledPolygon(shapeArrowE, colors.moveIconFill, centerCoords, scale);
1383        };
1384
1385        this.drawIconResizeCircle = function(centerCoords, circleRadius, scale) {
1386            if(disable) {
1387                return;
1388            }
1389
1390            var scaledCircleRadius = circleRadius * scale;
1391            ctx.save();
1392            ctx.strokeStyle = colors.resizeCircleStroke;
1393            ctx.lineWidth = cropper.strokeWidth;
1394            ctx.fillStyle = colors.resizeCircleFill;
1395            ctx.beginPath();
1396            ctx.arc(centerCoords[0], centerCoords[1], scaledCircleRadius, 0, 2 * Math.PI);
1397            ctx.fill();
1398            ctx.stroke();
1399            ctx.closePath();
1400            ctx.restore();
1401        };
1402
1403        this.drawIconResizeBoxBase = function(centerCoords, boxSize, scale) {
1404            if(disable) {
1405                return;
1406            }
1407
1408            var scaledBoxSize = boxSize * scale;
1409            ctx.save();
1410            ctx.strokeStyle = colors.resizeBoxStroke;
1411            ctx.lineWidth = cropper.strokeWidth;
1412            ctx.fillStyle = colors.resizeBoxFill;
1413            ctx.fillRect(centerCoords[0] - scaledBoxSize / 2, centerCoords[1] - scaledBoxSize / 2, scaledBoxSize, scaledBoxSize);
1414            ctx.strokeRect(centerCoords[0] - scaledBoxSize / 2, centerCoords[1] - scaledBoxSize / 2, scaledBoxSize, scaledBoxSize);
1415            ctx.restore();
1416        };
1417        this.drawIconResizeBoxNESW = function(centerCoords, boxSize, scale) {
1418            if(disable) {
1419                return;
1420            }
1421
1422            this.drawIconResizeBoxBase(centerCoords, boxSize, scale);
1423            drawFilledPolygon(shapeArrowNE, colors.resizeBoxArrowFill, centerCoords, scale);
1424            drawFilledPolygon(shapeArrowSW, colors.resizeBoxArrowFill, centerCoords, scale);
1425        };
1426        this.drawIconResizeBoxNWSE = function(centerCoords, boxSize, scale) {
1427            if(disable) {
1428                return;
1429            }
1430
1431            this.drawIconResizeBoxBase(centerCoords, boxSize, scale);
1432            drawFilledPolygon(shapeArrowNW, colors.resizeBoxArrowFill, centerCoords, scale);
1433            drawFilledPolygon(shapeArrowSE, colors.resizeBoxArrowFill, centerCoords, scale);
1434        };
1435
1436        /* Crop Area */
1437
1438        this.drawCropArea = function(image, centerCoords, size, fnDrawClipPath) {
1439            if(disable) {
1440                return;
1441            }
1442
1443            var xRatio = Math.abs(image.width / ctx.canvas.width),
1444                yRatio = Math.abs(image.height / ctx.canvas.height),
1445                xLeft = Math.abs(centerCoords.x - size.w / 2),
1446                yTop = Math.abs(centerCoords.y - size.h / 2);
1447
1448            ctx.save();
1449            ctx.strokeStyle = colors.areaOutline;
1450            ctx.lineWidth = cropper.strokeWidth;
1451            ctx.setLineDash([5, 5]);
1452            ctx.beginPath();
1453            fnDrawClipPath(ctx, centerCoords, size);
1454            ctx.stroke();
1455            ctx.clip();
1456
1457            // draw part of original image
1458            if (size.w > 0) {
1459                ctx.drawImage(image, xLeft * xRatio, yTop * yRatio, Math.abs(size.w * xRatio), Math.abs(size.h * yRatio), xLeft, yTop, Math.abs(size.w), Math.abs(size.h));
1460            }
1461
1462            ctx.beginPath();
1463            fnDrawClipPath(ctx, centerCoords, size);
1464            ctx.stroke();
1465            ctx.clip();
1466
1467            ctx.restore();
1468        };
1469    };
1470}]);
1471
1472/**
1473 * EXIF service is based on the exif-js library (https://github.com/jseidelin/exif-js)
1474 */
1475/*eslint-disable */
1476//Disable eslint as this is a 3rd party lib
1477angular.module('uiCropper').service('cropEXIF', [function () {
1478    var debug = false;
1479
1480    var ExifTags = this.Tags = {
1481
1482        // version tags
1483        0x9000: 'ExifVersion', // EXIF version
1484        0xA000: 'FlashpixVersion', // Flashpix format version
1485
1486        // colorspace tags
1487        0xA001: 'ColorSpace', // Color space information tag
1488
1489        // image configuration
1490        0xA002: 'PixelXDimension', // Valid width of meaningful image
1491        0xA003: 'PixelYDimension', // Valid height of meaningful image
1492        0x9101: 'ComponentsConfiguration', // Information about channels
1493        0x9102: 'CompressedBitsPerPixel', // Compressed bits per pixel
1494
1495        // user information
1496        0x927C: 'MakerNote', // Any desired information written by the manufacturer
1497        0x9286: 'UserComment', // Comments by user
1498
1499        // related file
1500        0xA004: 'RelatedSoundFile', // Name of related sound file
1501
1502        // date and time
1503        0x9003: 'DateTimeOriginal', // Date and time when the original image was generated
1504        0x9004: 'DateTimeDigitized', // Date and time when the image was stored digitally
1505        0x9290: 'SubsecTime', // Fractions of seconds for DateTime
1506        0x9291: 'SubsecTimeOriginal', // Fractions of seconds for DateTimeOriginal
1507        0x9292: 'SubsecTimeDigitized', // Fractions of seconds for DateTimeDigitized
1508
1509        // picture-taking conditions
1510        0x829A: 'ExposureTime', // Exposure time (in seconds)
1511        0x829D: 'FNumber', // F number
1512        0x8822: 'ExposureProgram', // Exposure program
1513        0x8824: 'SpectralSensitivity', // Spectral sensitivity
1514        0x8827: 'ISOSpeedRatings', // ISO speed rating
1515        0x8828: 'OECF', // Optoelectric conversion factor
1516        0x9201: 'ShutterSpeedValue', // Shutter speed
1517        0x9202: 'ApertureValue', // Lens aperture
1518        0x9203: 'BrightnessValue', // Value of brightness
1519        0x9204: 'ExposureBias', // Exposure bias
1520        0x9205: 'MaxApertureValue', // Smallest F number of lens
1521        0x9206: 'SubjectDistance', // Distance to subject in meters
1522        0x9207: 'MeteringMode', // Metering mode
1523        0x9208: 'LightSource', // Kind of light source
1524        0x9209: 'Flash', // Flash status
1525        0x9214: 'SubjectArea', // Location and area of main subject
1526        0x920A: 'FocalLength', // Focal length of the lens in mm
1527        0xA20B: 'FlashEnergy', // Strobe energy in BCPS
1528        0xA20C: 'SpatialFrequencyResponse', //
1529        0xA20E: 'FocalPlaneXResolution', // Number of pixels in width direction per FocalPlaneResolutionUnit
1530        0xA20F: 'FocalPlaneYResolution', // Number of pixels in height direction per FocalPlaneResolutionUnit
1531        0xA210: 'FocalPlaneResolutionUnit', // Unit for measuring FocalPlaneXResolution and FocalPlaneYResolution
1532        0xA214: 'SubjectLocation', // Location of subject in image
1533        0xA215: 'ExposureIndex', // Exposure index selected on camera
1534        0xA217: 'SensingMethod', // Image sensor type
1535        0xA300: 'FileSource', // Image source (3 == DSC)
1536        0xA301: 'SceneType', // Scene type (1 == directly photographed)
1537        0xA302: 'CFAPattern', // Color filter array geometric pattern
1538        0xA401: 'CustomRendered', // Special processing
1539        0xA402: 'ExposureMode', // Exposure mode
1540        0xA403: 'WhiteBalance', // 1 = auto white balance, 2 = manual
1541        0xA404: 'DigitalZoomRation', // Digital zoom ratio
1542        0xA405: 'FocalLengthIn35mmFilm', // Equivalent foacl length assuming 35mm film camera (in mm)
1543        0xA406: 'SceneCaptureType', // Type of scene
1544        0xA407: 'GainControl', // Degree of overall image gain adjustment
1545        0xA408: 'Contrast', // Direction of contrast processing applied by camera
1546        0xA409: 'Saturation', // Direction of saturation processing applied by camera
1547        0xA40A: 'Sharpness', // Direction of sharpness processing applied by camera
1548        0xA40B: 'DeviceSettingDescription', //
1549        0xA40C: 'SubjectDistanceRange', // Distance to subject
1550
1551        // other tags
1552        0xA005: 'InteroperabilityIFDPointer',
1553        0xA420: 'ImageUniqueID' // Identifier assigned uniquely to each image
1554    };
1555
1556    var TiffTags = this.TiffTags = {
1557        0x0100: 'ImageWidth',
1558        0x0101: 'ImageHeight',
1559        0x8769: 'ExifIFDPointer',
1560        0x8825: 'GPSInfoIFDPointer',
1561        0xA005: 'InteroperabilityIFDPointer',
1562        0x0102: 'BitsPerSample',
1563        0x0103: 'Compression',
1564        0x0106: 'PhotometricInterpretation',
1565        0x0112: 'Orientation',
1566        0x0115: 'SamplesPerPixel',
1567        0x011C: 'PlanarConfiguration',
1568        0x0212: 'YCbCrSubSampling',
1569        0x0213: 'YCbCrPositioning',
1570        0x011A: 'XResolution',
1571        0x011B: 'YResolution',
1572        0x0128: 'ResolutionUnit',
1573        0x0111: 'StripOffsets',
1574        0x0116: 'RowsPerStrip',
1575        0x0117: 'StripByteCounts',
1576        0x0201: 'JPEGInterchangeFormat',
1577        0x0202: 'JPEGInterchangeFormatLength',
1578        0x012D: 'TransferFunction',
1579        0x013E: 'WhitePoint',
1580        0x013F: 'PrimaryChromaticities',
1581        0x0211: 'YCbCrCoefficients',
1582        0x0214: 'ReferenceBlackWhite',
1583        0x0132: 'DateTime',
1584        0x010E: 'ImageDescription',
1585        0x010F: 'Make',
1586        0x0110: 'Model',
1587        0x0131: 'Software',
vendor: 6,769 bytes, lines 1588-1783
1588        0x013B: 'Artist',
1589        0x8298: 'Copyright'
1590    };
1591
1592    var GPSTags = this.GPSTags = {
1593        0x0000: 'GPSVersionID',
1594        0x0001: 'GPSLatitudeRef',
1595        0x0002: 'GPSLatitude',
1596        0x0003: 'GPSLongitudeRef',
1597        0x0004: 'GPSLongitude',
1598        0x0005: 'GPSAltitudeRef',
1599        0x0006: 'GPSAltitude',
1600        0x0007: 'GPSTimeStamp',
1601        0x0008: 'GPSSatellites',
1602        0x0009: 'GPSStatus',
1603        0x000A: 'GPSMeasureMode',
1604        0x000B: 'GPSDOP',
1605        0x000C: 'GPSSpeedRef',
1606        0x000D: 'GPSSpeed',
1607        0x000E: 'GPSTrackRef',
1608        0x000F: 'GPSTrack',
1609        0x0010: 'GPSImgDirectionRef',
1610        0x0011: 'GPSImgDirection',
1611        0x0012: 'GPSMapDatum',
1612        0x0013: 'GPSDestLatitudeRef',
1613        0x0014: 'GPSDestLatitude',
1614        0x0015: 'GPSDestLongitudeRef',
1615        0x0016: 'GPSDestLongitude',
1616        0x0017: 'GPSDestBearingRef',
1617        0x0018: 'GPSDestBearing',
1618        0x0019: 'GPSDestDistanceRef',
1619        0x001A: 'GPSDestDistance',
1620        0x001B: 'GPSProcessingMethod',
1621        0x001C: 'GPSAreaInformation',
1622        0x001D: 'GPSDateStamp',
1623        0x001E: 'GPSDifferential'
1624    };
1625
1626    var StringValues = this.StringValues = {
1627        ExposureProgram: {
1628            0: 'Not defined',
1629            1: 'Manual',
1630            2: 'Normal program',
1631            3: 'Aperture priority',
1632            4: 'Shutter priority',
1633            5: 'Creative program',
1634            6: 'Action program',
1635            7: 'Portrait mode',
1636            8: 'Landscape mode'
1637        },
1638        MeteringMode: {
1639            0: 'Unknown',
1640            1: 'Average',
1641            2: 'CenterWeightedAverage',
1642            3: 'Spot',
1643            4: 'MultiSpot',
1644            5: 'Pattern',
1645            6: 'Partial',
1646            255: 'Other'
1647        },
1648        LightSource: {
1649            0: 'Unknown',
1650            1: 'Daylight',
1651            2: 'Fluorescent',
1652            3: 'Tungsten (incandescent light)',
1653            4: 'Flash',
1654            9: 'Fine weather',
1655            10: 'Cloudy weather',
1656            11: 'Shade',
1657            12: 'Daylight fluorescent (D 5700 - 7100K)',
1658            13: 'Day white fluorescent (N 4600 - 5400K)',
1659            14: 'Cool white fluorescent (W 3900 - 4500K)',
1660            15: 'White fluorescent (WW 3200 - 3700K)',
1661            17: 'Standard light A',
1662            18: 'Standard light B',
1663            19: 'Standard light C',
1664            20: 'D55',
1665            21: 'D65',
1666            22: 'D75',
1667            23: 'D50',
1668            24: 'ISO studio tungsten',
1669            255: 'Other'
1670        },
1671        Flash: {
1672            0x0000: 'Flash did not fire',
1673            0x0001: 'Flash fired',
1674            0x0005: 'Strobe return light not detected',
1675            0x0007: 'Strobe return light detected',
1676            0x0009: 'Flash fired, compulsory flash mode',
1677            0x000D: 'Flash fired, compulsory flash mode, return light not detected',
1678            0x000F: 'Flash fired, compulsory flash mode, return light detected',
1679            0x0010: 'Flash did not fire, compulsory flash mode',
1680            0x0018: 'Flash did not fire, auto mode',
1681            0x0019: 'Flash fired, auto mode',
1682            0x001D: 'Flash fired, auto mode, return light not detected',
1683            0x001F: 'Flash fired, auto mode, return light detected',
1684            0x0020: 'No flash function',
1685            0x0041: 'Flash fired, red-eye reduction mode',
1686            0x0045: 'Flash fired, red-eye reduction mode, return light not detected',
1687            0x0047: 'Flash fired, red-eye reduction mode, return light detected',
1688            0x0049: 'Flash fired, compulsory flash mode, red-eye reduction mode',
1689            0x004D: 'Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected',
1690            0x004F: 'Flash fired, compulsory flash mode, red-eye reduction mode, return light detected',
1691            0x0059: 'Flash fired, auto mode, red-eye reduction mode',
1692            0x005D: 'Flash fired, auto mode, return light not detected, red-eye reduction mode',
1693            0x005F: 'Flash fired, auto mode, return light detected, red-eye reduction mode'
1694        },
1695        SensingMethod: {
1696            1: 'Not defined',
1697            2: 'One-chip color area sensor',
1698            3: 'Two-chip color area sensor',
1699            4: 'Three-chip color area sensor',
1700            5: 'Color sequential area sensor',
1701            7: 'Trilinear sensor',
1702            8: 'Color sequential linear sensor'
1703        },
1704        SceneCaptureType: {
1705            0: 'Standard',
1706            1: 'Landscape',
1707            2: 'Portrait',
1708            3: 'Night scene'
1709        },
1710        SceneType: {
1711            1: 'Directly photographed'
1712        },
1713        CustomRendered: {
1714            0: 'Normal process',
1715            1: 'Custom process'
1716        },
1717        WhiteBalance: {
1718            0: 'Auto white balance',
1719            1: 'Manual white balance'
1720        },
1721        GainControl: {
1722            0: 'None',
1723            1: 'Low gain up',
1724            2: 'High gain up',
1725            3: 'Low gain down',
1726            4: 'High gain down'
1727        },
1728        Contrast: {
1729            0: 'Normal',
1730            1: 'Soft',
1731            2: 'Hard'
1732        },
1733        Saturation: {
1734            0: 'Normal',
1735            1: 'Low saturation',
1736            2: 'High saturation'
1737        },
1738        Sharpness: {
1739            0: 'Normal',
1740            1: 'Soft',
1741            2: 'Hard'
1742        },
1743        SubjectDistanceRange: {
1744            0: 'Unknown',
1745            1: 'Macro',
1746            2: 'Close view',
1747            3: 'Distant view'
1748        },
1749        FileSource: {
1750            3: 'DSC'
1751        },
1752
1753        Components: {
1754            0: '',
1755            1: 'Y',
1756            2: 'Cb',
1757            3: 'Cr',
1758            4: 'R',
1759            5: 'G',
1760            6: 'B'
1761        }
1762    };
1763
1764    function addEvent(element, event, handler) {
1765        if (element.addEventListener) {
1766            element.addEventListener(event, handler, false);
1767        } else if (element.attachEvent) {
1768            element.attachEvent('on' + event, handler);
1769        }
1770    }
1771
1772    function imageHasData(img) {
1773        return !!(img.exifdata);
1774    }
1775
1776    function base64ToArrayBuffer(base64, contentType) {
1777        contentType = contentType || base64.match(/^data\:([^\;]+)\;base64,/mi)[1] || ''; // e.g. 'data:image/jpeg;base64,...' => 'image/jpeg'
1778        base64 = base64.replace(/^data\:([^\;]+)\;base64,/gmi, '');
1779        var binary = atob(base64);
1780        var len = binary.length;
1781        var buffer = new ArrayBuffer(len);
1782        var view = new Uint8Array(buffer);
1783        for (var i = 0; i < len; i++) {
1784            view[i] = binary.charCodeAt(i);
1785        }
1786        return buffer;
1787    }
1788
1789    function objectURLToBlob(url, callback) {
1790        var http = new XMLHttpRequest();
1791        http.open('GET', url, true);
1792        http.responseType = 'blob';
1793        http.onload = function (e) {
1794            if (this.status === 200 || this.status === 0) {
1795                callback(this.response);
1796            }
1797        };
1798        http.send();
1799    }
1800
1801    function getImageData(img, callback) {
1802        function handleBinaryFile(binFile) {
1803            var data = findEXIFinJPEG(binFile);
1804            var iptcdata = findIPTCinJPEG(binFile);
1805            img.exifdata = data || {};
1806            img.iptcdata = iptcdata || {};
1807            if (callback) {
1808                callback.call(img);
1809            }
1810        }
1811
1812        var fileReader = new FileReader();
1813
1814        if (img.src) {
1815
1816            if (/^data\:/i.test(img.src)) { // Data URI
1817                var arrayBuffer = base64ToArrayBuffer(img.src);
1818                handleBinaryFile(arrayBuffer);
1819
1820            } else if (/^blob\:/i.test(img.src)) { // Object URL
1821                fileReader.onload = function (e) {
1822                    handleBinaryFile(e.target.result);
1823                };
1824                objectURLToBlob(img.src, function (blob) {
1825                    fileReader.readAsArrayBuffer(blob);
1826                });
1827            }
1828            var http = new XMLHttpRequest();
1829            http.onload = function () {
1830                if (this.status === 200 || this.status === 0) {
1831                    handleBinaryFile(http.response);
1832                } else {
1833                    throw 'Could not load image';
1834                }
1835                http = null;
1836            };
1837            http.open('GET', img.src, true);
1838            http.responseType = 'arraybuffer';
1839            try {
1840                http.send(null);
1841            } catch(e) {
1842            }
1843        } else if (window.FileReader && (img instanceof window.Blob || img instanceof window.File)) {
1844            fileReader.onload = function (e) {
1845                if (debug) {
1846                    console.log('Got file of length ' + e.target.result.byteLength);
1847                }
1848                handleBinaryFile(e.target.result);
1849            };
1850
1851            fileReader.readAsArrayBuffer(img);
1852        }
1853    }
1854
1855    function findEXIFinJPEG(file) {
1856        var dataView = new DataView(file);
1857
1858        if (debug) {
1859            console.log('Got file of length ' + file.byteLength);
1860        }
1861        if ((dataView.getUint8(0) !== 0xFF) || (dataView.getUint8(1) !== 0xD8)) {
1862            if (debug) {
1863                console.log('Not a valid JPEG');
1864            }
1865            return false; // not a valid jpeg
1866        }
1867
1868        var offset = 2,
1869            length = file.byteLength,
1870            marker;
1871
1872        while (offset < length) {
1873            if (dataView.getUint8(offset) !== 0xFF) {
1874                if (debug) {
1875                    console.log('Not a valid marker at offset ' + offset + ', found: ' + dataView.getUint8(offset));
1876                }
1877                return false; // not a valid marker, something is wrong
1878            }
1879
1880            marker = dataView.getUint8(offset + 1);
1881            if (debug) {
1882                console.log(marker);
1883            }
1884
1885            // we could implement handling for other markers here,
1886            // but we're only looking for 0xFFE1 for EXIF data
1887
1888            if (marker === 225) {
1889                if (debug) {
1890                    console.log('Found 0xFFE1 marker');
1891                }
1892
1893                return readEXIFData(dataView, offset + 4, dataView.getUint16(offset + 2) - 2);
1894
1895                // offset += 2 + file.getShortAt(offset+2, true);
1896
1897            } else {
1898                offset += 2 + dataView.getUint16(offset + 2);
1899            }
1900
1901        }
1902
1903    }
1904
1905    function findIPTCinJPEG(file) {
1906        var dataView = new DataView(file);
1907
1908        if (debug) {
1909            console.log('Got file of length ' + file.byteLength);
1910        }
1911        if ((dataView.getUint8(0) !== 0xFF) || (dataView.getUint8(1) !== 0xD8)) {
1912            if (debug) {
1913                console.log('Not a valid JPEG');
1914            }
1915            return false; // not a valid jpeg
1916        }
1917
1918        var offset = 2,
1919            length = file.byteLength;
1920
1921        var isFieldSegmentStart = function (dataView, offset) {
1922            return (
1923                dataView.getUint8(offset) === 0x38 &&
1924                dataView.getUint8(offset + 1) === 0x42 &&
1925                dataView.getUint8(offset + 2) === 0x49 &&
1926                dataView.getUint8(offset + 3) === 0x4D &&
1927                dataView.getUint8(offset + 4) === 0x04 &&
1928                dataView.getUint8(offset + 5) === 0x04
1929            );
1930        };
1931
1932        while (offset < length) {
1933
1934            if (isFieldSegmentStart(dataView, offset)) {
1935
1936                // Get the length of the name header (which is padded to an even number of bytes)
1937                var nameHeaderLength = dataView.getUint8(offset + 7);
1938                if (nameHeaderLength % 2 !== 0) {
1939                    nameHeaderLength += 1;
1940                }
1941                // Check for pre photoshop 6 format
1942                if (nameHeaderLength === 0) {
1943                    // Always 4
1944                    nameHeaderLength = 4;
1945                }
1946
1947                var startOffset = offset + 8 + nameHeaderLength;
1948                var sectionLength = dataView.getUint16(offset + 6 + nameHeaderLength);
1949
1950                return readIPTCData(file, startOffset, sectionLength);
1951            }
1952
1953            // Not the marker, continue searching
1954            offset++;
1955
1956        }
1957
1958    }
1959
1960    var IptcFieldMap = {
1961        0x78: 'caption',
1962        0x6E: 'credit',
1963        0x19: 'keywords',
1964        0x37: 'dateCreated',
1965        0x50: 'byline',
1966        0x55: 'bylineTitle',
1967        0x7A: 'captionWriter',
1968        0x69: 'headline',
1969        0x74: 'copyright',
1970        0x0F: 'category'
1971    };
1972
1973    function readIPTCData(file, startOffset, sectionLength) {
1974        var dataView = new DataView(file);
1975        var data = {};
1976        var fieldValue, fieldName, dataSize, segmentType, segmentSize;
1977        var segmentStartPos = startOffset;
1978        while (segmentStartPos < startOffset + sectionLength) {
1979            if (dataView.getUint8(segmentStartPos) === 0x1C && dataView.getUint8(segmentStartPos + 1) === 0x02) {
1980                segmentType = dataView.getUint8(segmentStartPos + 2);
1981                if (segmentType in IptcFieldMap) {
1982                    dataSize = dataView.getInt16(segmentStartPos + 3);
1983                    segmentSize = dataSize + 5;
1984                    fieldName = IptcFieldMap[segmentType];
1985                    fieldValue = getStringFromDB(dataView, segmentStartPos + 5, dataSize);
1986                    // Check if we already stored a value with this name
1987                    if (data.hasOwnProperty(fieldName)) {
1988                        // Value already stored with this name, create multivalue field
1989                        if (data[fieldName] instanceof Array) {
1990                            data[fieldName].push(fieldValue);
1991                        } else {
1992                            data[fieldName] = [data[fieldName], fieldValue];
1993                        }
1994                    } else {
1995                        data[fieldName] = fieldValue;
1996                    }
1997                }
1998
1999            }
2000            segmentStartPos++;
2001        }
2002        return data;
2003    }
2004
2005    function readTags(file, tiffStart, dirStart, strings, bigEnd) {
2006        var entries = file.getUint16(dirStart, !bigEnd),
2007            tags = {},
2008            entryOffset, tag,
2009            i;
2010
2011        for (i = 0; i < entries; i++) {
2012            entryOffset = dirStart + i * 12 + 2;
2013            tag = strings[file.getUint16(entryOffset, !bigEnd)];
2014            if (!tag && debug) {
2015                console.log('Unknown tag: ' + file.getUint16(entryOffset, !bigEnd));
2016            }
2017            tags[tag] = readTagValue(file, entryOffset, tiffStart, dirStart, bigEnd);
2018        }
2019        return tags;
2020    }
2021
2022    function readTagValue(file, entryOffset, tiffStart, dirStart, bigEnd) {
2023        var type = file.getUint16(entryOffset + 2, !bigEnd),
2024            numValues = file.getUint32(entryOffset + 4, !bigEnd),
2025            valueOffset = file.getUint32(entryOffset + 8, !bigEnd) + tiffStart,
2026            offset,
2027            vals, val, n,
2028            numerator, denominator;
2029
2030        switch (type) {
2031            case 1: // byte, 8-bit unsigned int
2032            case 7: // undefined, 8-bit byte, value depending on field
2033                if (numValues === 1) {
2034                    return file.getUint8(entryOffset + 8, !bigEnd);
2035                }
2036                offset = numValues > 4 ? valueOffset : (entryOffset + 8);
2037                vals = [];
2038                for (n = 0; n < numValues; n++) {
2039                    vals[n] = file.getUint8(offset + n);
2040                }
2041                return vals;
2042
2043            case 2: // ascii, 8-bit byte
2044                offset = numValues > 4 ? valueOffset : (entryOffset + 8);
2045                return getStringFromDB(file, offset, numValues - 1);
2046
2047            case 3: // short, 16 bit int
2048                if (numValues === 1) {
2049                    return file.getUint16(entryOffset + 8, !bigEnd);
2050                }
2051                offset = numValues > 2 ? valueOffset : (entryOffset + 8);
2052                vals = [];
2053                for (n = 0; n < numValues; n++) {
2054                    vals[n] = file.getUint16(offset + 2 * n, !bigEnd);
2055                }
2056                return vals;
2057
2058            case 4: // long, 32 bit int
2059                if (numValues === 1) {
2060                    return file.getUint32(entryOffset + 8, !bigEnd);
2061                }
2062                vals = [];
2063                for (n = 0; n < numValues; n++) {
2064                    vals[n] = file.getUint32(valueOffset + 4 * n, !bigEnd);
2065                }
2066                return vals;
2067
2068            case 5: // rational = two long values, first is numerator, second is denominator
2069                if (numValues === 1) {
2070                    numerator = file.getUint32(valueOffset, !bigEnd);
2071                    denominator = file.getUint32(valueOffset + 4, !bigEnd);
2072                    val = {}; //@TODO need to inspect if this fix is really working
2073                    val.numerator = numerator;
2074                    val.denominator = denominator;
2075                    return val;
2076                }
2077                vals = [];
2078                for (n = 0; n < numValues; n++) {
2079                    numerator = file.getUint32(valueOffset + 8 * n, !bigEnd);
2080                    denominator = file.getUint32(valueOffset + 4 + 8 * n, !bigEnd);
2081                    vals[n] = {}; //@TODO need to inspect if this fix is really working
2082                    vals[n].numerator = numerator;
2083                    vals[n].denominator = denominator;
2084                }
2085                return vals;
2086
2087            case 9: // slong, 32 bit signed int
2088                if (numValues === 1) {
2089                    return file.getInt32(entryOffset + 8, !bigEnd);
2090                }
2091                vals = [];
2092                for (n = 0; n < numValues; n++) {
2093                    vals[n] = file.getInt32(valueOffset + 4 * n, !bigEnd);
2094                }
2095                return vals;
2096
2097            case 10: // signed rational, two slongs, first is numerator, second is denominator
2098                if (numValues === 1) {
2099                    return file.getInt32(valueOffset, !bigEnd) / file.getInt32(valueOffset + 4, !bigEnd);
2100                }
2101                vals = [];
2102                for (n = 0; n < numValues; n++) {
2103                    vals[n] = file.getInt32(valueOffset + 8 * n, !bigEnd) / file.getInt32(valueOffset + 4 + 8 * n, !bigEnd);
2104                }
2105                return vals;
2106        }
2107    }
2108
2109    function getStringFromDB(buffer, start, length) {
2110        var outstr = '';
2111        for (var n = start; n < start + length; n++) {
2112            outstr += String.fromCharCode(buffer.getUint8(n));
2113        }
2114        return outstr;
2115    }
2116
2117    function readEXIFData(file, start) {
2118        if (getStringFromDB(file, start, 4) !== 'Exif') {
2119            if (debug) {
2120                console.log('Not valid EXIF data! ' + getStringFromDB(file, start, 4));
2121            }
2122            return false;
2123        }
2124
2125        var bigEnd,
2126            tags, tag,
2127            exifData, gpsData,
2128            tiffOffset = start + 6;
2129
2130        // test for TIFF validity and endianness
2131        if (file.getUint16(tiffOffset) === 0x4949) {
2132            bigEnd = false;
2133        } else if (file.getUint16(tiffOffset) === 0x4D4D) {
2134            bigEnd = true;
2135        } else {
2136            if (debug) {
2137                console.log('Not valid TIFF data! (no 0x4949 or 0x4D4D)');
2138            }
2139            return false;
2140        }
2141
2142        if (file.getUint16(tiffOffset + 2, !bigEnd) !== 0x002A) {
2143            if (debug) {
2144                console.log('Not valid TIFF data! (no 0x002A)');
2145            }
2146            return false;
2147        }
2148
2149        var firstIFDOffset = file.getUint32(tiffOffset + 4, !bigEnd);
2150
2151        if (firstIFDOffset < 0x00000008) {
2152            if (debug) {
2153                console.log('Not valid TIFF data! (First offset less than 8)', file.getUint32(tiffOffset + 4, !bigEnd));
2154            }
2155            return false;
2156        }
2157
2158        tags = readTags(file, tiffOffset, tiffOffset + firstIFDOffset, TiffTags, bigEnd);
2159
2160        if (tags.ExifIFDPointer) {
2161            exifData = readTags(file, tiffOffset, tiffOffset + tags.ExifIFDPointer, ExifTags, bigEnd);
2162            for (tag in exifData) {
2163                switch (tag) {
2164                    case 'LightSource':
2165                    case 'Flash':
2166                    case 'MeteringMode':
2167                    case 'ExposureProgram':
2168                    case 'SensingMethod':
2169                    case 'SceneCaptureType':
2170                    case 'SceneType':
2171                    case 'CustomRendered':
2172                    case 'WhiteBalance':
2173                    case 'GainControl':
2174                    case 'Contrast':
2175                    case 'Saturation':
2176                    case 'Sharpness':
2177                    case 'SubjectDistanceRange':
2178                    case 'FileSource':
2179                        exifData[tag] = StringValues[tag][exifData[tag]];
2180                        break;
2181
2182                    case 'ExifVersion':
2183                    case 'FlashpixVersion':
2184                        exifData[tag] = String.fromCharCode(exifData[tag][0], exifData[tag][1], exifData[tag][2], exifData[tag][3]);
2185                        break;
2186
2187                    case 'ComponentsConfiguration':
2188                        exifData[tag] =
2189                            StringValues.Components[exifData[tag][0]] +
2190                            StringValues.Components[exifData[tag][1]] +
2191                            StringValues.Components[exifData[tag][2]] +
2192                            StringValues.Components[exifData[tag][3]];
2193                        break;
2194                }
2195                tags[tag] = exifData[tag];
2196            }
2197        }
2198
2199        if (tags.GPSInfoIFDPointer) {
2200            gpsData = readTags(file, tiffOffset, tiffOffset + tags.GPSInfoIFDPointer, GPSTags, bigEnd);
2201            for (tag in gpsData) {
2202                switch (tag) {
2203                    case 'GPSVersionID':
2204                        gpsData[tag] = gpsData[tag][0] +
2205                            '.' + gpsData[tag][1] +
2206                            '.' + gpsData[tag][2] +
2207                            '.' + gpsData[tag][3];
2208                        break;
2209                }
2210                tags[tag] = gpsData[tag];
2211            }
2212        }
2213
2214        return tags;
2215    }
2216
2217    this.getData = function (img, callback) {
2218        if ((img instanceof Image || img instanceof HTMLImageElement) && !img.complete) {
2219            return false;
2220        }
2221
2222        if (!imageHasData(img)) {
2223            getImageData(img, callback);
2224        } else {
2225            if (callback) {
2226                callback.call(img);
2227            }
2228        }
2229        return true;
2230    };
2231
2232    this.getTag = function (img, tag) {
2233        if (!imageHasData(img)) {
2234            return;
2235        }
2236        return img.exifdata[tag];
2237    };
2238
2239    this.getAllTags = function (img) {
2240        if (!imageHasData(img)) {
2241            return {};
2242        }
2243        var a,
2244            data = img.exifdata,
2245            tags = {};
2246        for (a in data) {
2247            if (data.hasOwnProperty(a)) {
2248                tags[a] = data[a];
2249            }
2250        }
2251        return tags;
2252    };
2253
2254    this.pretty = function (img) {
2255        if (!imageHasData(img)) {
2256            return '';
2257        }
2258        var a,
2259            data = img.exifdata,
2260            strPretty = '';
2261        for (a in data) {
2262            if (data.hasOwnProperty(a)) {
2263                if (typeof data[a] === 'object') {
2264                    if (data[a] instanceof Number) {
2265                        strPretty += a + ' : ' + data[a] + ' [' + data[a].numerator + '/' + data[a].denominator + ']\r\n';
2266                    } else {
2267                        strPretty += a + ' : [' + data[a].length + ' values]\r\n';
2268                    }
2269                } else {
2270                    strPretty += a + ' : ' + data[a] + '\r\n';
2271                }
2272            }
2273        }
2274        return strPretty;
2275    };
2276
2277    this.readFromBinaryFile = function (file) {
2278        return findEXIFinJPEG(file);
2279    };
2280}]);
2281
2282angular.module('uiCropper').factory('cropHost', ['$document', '$q', 'cropAreaCircle', 'cropAreaSquare', 'cropAreaRectangle', 'cropEXIF', function ($document, $q, CropAreaCircle, CropAreaSquare, CropAreaRectangle, cropEXIF) {
2283    /* STATIC FUNCTIONS */
2284    var colorPaletteLength = 8;
2285
2286    // Get Element's Offset
2287    var getElementOffset = function (elem) {
2288        var box = elem.getBoundingClientRect();
2289
2290        var body = document.body;
2291        var docElem = document.documentElement;
2292
2293        var scrollTop = window.pageYOffset || docElem.scrollTop || body.scrollTop;
2294        var scrollLeft = window.pageXOffset || docElem.scrollLeft || body.scrollLeft;
2295
2296        var clientTop = docElem.clientTop || body.clientTop || 0;
2297        var clientLeft = docElem.clientLeft || body.clientLeft || 0;
2298
2299        var top = box.top + scrollTop - clientTop;
2300        var left = box.left + scrollLeft - clientLeft;
2301
2302        return {
2303            top: Math.round(top),
2304            left: Math.round(left)
2305        };
2306    };
2307
2308    return function (elCanvas, opts, events) {
2309        /* PRIVATE VARIABLES */
2310
2311        // Object Pointers
2312        var ctx = null,
2313            image = null,
2314            theArea = null,
2315            initMax = null,
2316            isAspectRatio = null,
2317            self = this,
2318
2319            // Dimensions
2320            minCanvasDims = [100, 100],
2321            maxCanvasDims = [300, 300],
2322
2323            scalemode = null,
2324
2325            // Result Image size
2326            resImgSizeArray = [],
2327            resImgSize = {
2328                w: 200,
2329                h: 200
2330            },
2331            areaMinRelativeSize = null,
2332
2333            // Result Image type
2334            resImgFormat = 'image/png',
2335
2336            // Result Image quality
2337            resImgQuality = null,
2338
2339            keys = {
2340                up: 38,
2341                down: 40,
2342                left: 37,
2343                right: 39
2344            },
2345
2346            forceAspectRatio = false;
2347
2348        /* PRIVATE FUNCTIONS */
2349        // Draw Scene
2350        function drawScene() {
2351            // clear canvas
2352            ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
2353
2354            if (image !== null) {
2355                // draw source image
2356                ctx.drawImage(image, 0, 0, ctx.canvas.width, ctx.canvas.height);
2357
2358                ctx.save();
2359
2360                // and make it darker
2361                if(!theArea._disableCrop){
2362                    ctx.fillStyle = 'rgba(0, 0, 0, 0.65)';
2363                    ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
2364
2365                    ctx.restore();
2366                }
2367                // draw Area
2368                theArea.draw();
2369            }
2370        }
2371
2372        this.setInitMax = function (bool) {
2373            initMax = bool;
2374        };
2375
2376        this.setAllowCropResizeOnCorners = function (bool) {
2377            theArea.setAllowCropResizeOnCorners(bool);
2378        };
2379
2380        this.setDisableCrop = function(value){
2381            theArea.setDisableCrop(value);
2382            drawScene();
2383        };
2384
2385        var focusOnCanvas = function () {
2386            elCanvas[0].focus();
2387        };
2388
2389        function fitCanvasDims(width, height) {
2390            var imageRatio = width / height;
2391            var canvasDims = [width, height];
2392            var margins = {left: 0, top: 0};
2393            if (canvasDims[0] > maxCanvasDims[0]) {
2394                canvasDims[0] = maxCanvasDims[0];
2395                canvasDims[1] = canvasDims[0] / imageRatio;
2396                margins.top = (maxCanvasDims[1] - canvasDims[1]) / 2;
2397            }
2398            else if (canvasDims[1] > maxCanvasDims[1]) {
2399                canvasDims[1] = maxCanvasDims[1];
2400                canvasDims[0] = canvasDims[1] * imageRatio;
2401                margins.left = (maxCanvasDims[0] - canvasDims[0]) / 2;
2402            }
2403            else {
2404                margins.top = (maxCanvasDims[1] - canvasDims[1]) / 2;
2405                margins.left = (maxCanvasDims[0] - canvasDims[0]) / 2;
2406            }
2407            return {
2408                dims: canvasDims,
2409                margins: margins,
2410            };
2411        }
2412
2413        // Resets CropHost
2414        var resetCropHost = function () {
2415            var canvasDims = [0, 0];
2416            if (image !== null) {
2417                focusOnCanvas();
2418                theArea.setImage(image);
2419                if (scalemode === 'fit') {
2420                    var results = fitCanvasDims(image.width, image.height);
2421                    canvasDims = results.dims;
2422                    var margins = results.margins;
2423                    elCanvas.css({
2424                        'margin-left': margins.left + 'px',
2425                        'margin-top': margins.top + 'px'
2426                    });
2427                }
2428                else {
2429                    var imageDims = [image.width, image.height],
2430                        imageRatio = image.width / image.height;
2431                    canvasDims = imageDims;
2432
2433                    if (canvasDims[0] > maxCanvasDims[0]) {
2434                        canvasDims[0] = maxCanvasDims[0];
2435                        canvasDims[1] = canvasDims[0] / imageRatio;
2436                    } else if (canvasDims[0] < minCanvasDims[0]) {
2437                        canvasDims[0] = minCanvasDims[0];
2438                        canvasDims[1] = canvasDims[0] / imageRatio;
2439                    }
2440                    if (scalemode === 'fixed-height' && canvasDims[1] > maxCanvasDims[1]) {
2441                        canvasDims[1] = maxCanvasDims[1];
2442                        canvasDims[0] = canvasDims[1] * imageRatio;
2443                    } else if (canvasDims[1] < minCanvasDims[1]) {
2444                        canvasDims[1] = minCanvasDims[1];
2445                        canvasDims[0] = canvasDims[1] * imageRatio;
2446                    }
2447                }
2448                elCanvas.prop('width', canvasDims[0]).prop('height', canvasDims[1]);
2449                if (scalemode === 'fixed-height') {
2450                    elCanvas.css({
2451                        'margin-left': -canvasDims[0] / 2 + 'px',
2452                        'margin-top': -canvasDims[1] / 2 + 'px'
2453                    });
2454                }
2455
2456                var cw = ctx.canvas.width;
2457                var ch = ctx.canvas.height;
2458
2459                var areaType = self.getAreaType();
2460                // enforce 1:1 aspect ratio for square-like selections
2461                if ((areaType === 'circle') || (areaType === 'square')) {
2462                    if (ch < cw) {
2463                        cw = ch;
2464                    }
2465                    ch = cw;
2466                } else if (areaType === 'rectangle' && isAspectRatio) {
2467                    var aspectRatio = theArea.getAspect(); // use `aspectRatio` instead of `resImgSize` dimensions bc `resImgSize` c
2467an be 'selection' string
2468                    if (cw / ch > aspectRatio) {
2469                        cw = aspectRatio * ch;
2470                    } else {
2471                        ch = aspectRatio * cw;
2472                    }
2473                }
2474
2475                if (initMax) {
2476                    theArea.setSize({
2477                        w: cw,
2478                        h: ch
2479                    });
2480                } else if (undefined !== theArea.getInitSize()) {
2481                    theArea.setSize({
2482                        w: Math.min(theArea.getInitSize().w, cw / 2),
2483                        h: Math.min(theArea.getInitSize().h, ch / 2)
2484                    });
2485                } else {
2486                    theArea.setSize({
2487                        w: Math.min(200, cw / 2),
2488                        h: Math.min(200, ch / 2)
2489                    });
2490                }
2491
2492                if (theArea.getInitCoords()) {
2493                    if (self.areaInitIsRelativeToImage) {
2494                        var ratio = image.width / canvasDims[0];
2495                        theArea.setSize({
2496                            w: theArea.getInitSize().w / ratio,
2497                            h: theArea.getInitSize().h / ratio,
2498                            x: theArea.getInitCoords().x / ratio,
2499                            y: theArea.getInitCoords().y / ratio
2500                        });
2501                    } else {
2502                        theArea.setSize({
2503                            w: theArea.getSize().w,
2504                            h: theArea.getSize().h,
2505                            x: theArea.getInitCoords().x,
2506                            y: theArea.getInitCoords().y
2507                        });
2508                    }
2509                } else {
2510                    theArea.setCenterPoint({
2511                        x: ctx.canvas.width / 2,
2512                        y: ctx.canvas.height / 2
2513                    });
2514                }
2515
2516            } else {
2517                elCanvas.prop('width', 0).prop('height', 0).css({
2518                    'margin-top': 0
2519                });
2520            }
2521
2522            drawScene();
2523        };
2524
2525        var getChangedTouches = function (event) {
2526            if (angular.isDefined(event.changedTouches)) {
2527                return event.changedTouches;
2528            } else {
2529                return event.originalEvent.changedTouches;
2530            }
2531        };
2532
2533        var onMouseMove = function (e) {
2534            if(theArea._disableCrop) {
2535                return;
2536            }
2537
2538            if (image !== null) {
2539                var offset = getElementOffset(ctx.canvas),
2540                    pageX, pageY;
2541                if (e.type === 'touchmove') {
2542                    pageX = getChangedTouches(e)[0].pageX;
2543                    pageY = getChangedTouches(e)[0].pageY;
2544                } else {
2545                    pageX = e.pageX;
2546                    pageY = e.pageY;
2547                }
2548
2549                theArea.processMouseMove(pageX - offset.left, pageY - offset.top);
2550                drawScene();
2551            }
2552        };
2553
2554        var onMouseDown = function (e) {
2555            e.preventDefault();
2556            if(theArea._disableCrop) {
2557                return;
2558            }
2559
2560            if (!opts.allowPropagation) {
2561                e.stopPropagation();
2562            }
2563
2564            if (image !== null) {
2565                focusOnCanvas();
2566                var offset = getElementOffset(ctx.canvas),
2567                    pageX, pageY;
2568                if (e.type === 'touchstart') {
2569                    pageX = getChangedTouches(e)[0].pageX;
2570                    pageY = getChangedTouches(e)[0].pageY;
2571                } else {
2572                    pageX = e.pageX;
2573                    pageY = e.pageY;
2574                }
2575                theArea.processMouseDown(pageX - offset.left, pageY - offset.top);
2576                drawScene();
2577            }
2578        };
2579
2580        var onMouseUp = function (e) {
2581            if(theArea._disableCrop) {
2582                return;
2583            }
2584
2585            if (image !== null) {
2586                var offset = getElementOffset(ctx.canvas),
2587                    pageX, pageY;
2588                if (e.type === 'touchend') {
2589                    pageX = getChangedTouches(e)[0].pageX;
2590                    pageY = getChangedTouches(e)[0].pageY;
2591                } else {
2592                    pageX = e.pageX;
2593                    pageY = e.pageY;
2594                }
2595                theArea.processMouseUp(pageX - offset.left, pageY - offset.top);
2596                drawScene();
2597            }
2598        };
2599
2600        var getDirectionByKey = function (key) {
2601            return Object.keys(keys).reduce(function(result, direction) {
2602                return key === keys[direction] ? direction : result;
2603            }, null);
2604        };
2605
2606        var resizeCropAreaByDirection = function (direction) {
2607            if(theArea._disableCrop) {
2608                return;
2609            }
2610
2611            var scale;
2612            switch (direction) {
2613                case 'up':
2614                case 'left':
2615                    scale = 0.95;
2616                    break;
2617                case 'down':
2618                case 'right':
2619                    scale = 1.05;
2620                    break;
2621                default:
2622                    return;
2623            }
2624            theArea.setSizeByScale(scale, direction);
2625            drawScene();
2626        };
2627
2628        var moveCropArea = function (direction) {
2629            if(theArea._disableCrop) {
2630                return;
2631            }
2632
2633            var center = theArea.getCenterPoint();
2634            var step = 5;
2635            var point = {
2636                x: center.x,
2637                y: center.y
2638            };
2639
2640            switch (direction) {
2641                case 'up':
2642                    point.y -= step;
2643                    break;
2644                case 'down':
2645                    point.y += step;
2646                    break;
2647                case 'left':
2648                    point.x -= step;
2649                    break;
2650                case 'right':
2651                    point.x += step;
2652                    break;
2653                default:
2654                    return;
2655            }
2656
2657            theArea.setCenterPointOnMove(point);
2658            drawScene();
2659        };
2660
2661        var onKeyDown = function (e) {
2662            if(theArea._disableCrop) {
2663                return;
2664            }
2665            
2666            if (image !== null && opts.disableKeyboardAccess !== true) {
2667                var key = e.which;
2668                switch (key) {
2669                    case keys.up:
2670                    case keys.down:
2671                    case keys.left:
2672                    case keys.right:
2673                        e.preventDefault();
2674                        e.stopPropagation();
2675                        var direction = getDirectionByKey(key);
2676                        if (e.shiftKey) {
2677                            resizeCropAreaByDirection(direction);
2678                        } else {
2679                            moveCropArea(direction);
2680                        }
2681                        break;
2682                    default:
2683                        return;
2684                }
2685            }
2686        };
2687
2688        var renderTempCanvas = function (ris, center) {
2689            var temp_ctx, temp_canvas;
2690            temp_canvas = angular.element('<canvas></canvas>')[0];
2691            temp_ctx = temp_canvas.getContext('2d');
2692            temp_canvas.width = ris.w;
2693            temp_canvas.height = ris.h;
2694            if (image !== null) {
2695                var x = (center.x - theArea.getSize().w / 2) * (image.
2695width / ctx.canvas.width),
2696                    y = (center.y - theArea.getSize().h / 2) * (image.height / ctx.canvas.height),
2697                    areaWidth = theArea.getSize().w * (image.width / ctx.canvas.width),
2698                    areaHeight = theArea.getSize().h * (image.height / ctx.canvas.height);
2699
2700                if (forceAspectRatio) {
2701                    temp_ctx.drawImage(image, x, y,
2702                        areaWidth,
2703                        areaHeight,
2704                        0,
2705                        0,
2706                        ris.w,
2707                        ris.h);
2708                } else {
2709                    var aspectRatio = areaWidth / areaHeight;
2710                    var resultHeight, resultWidth;
2711
2712                    if (aspectRatio > 1) {
2713                        resultWidth = ris.w;
2714                        resultHeight = resultWidth / aspectRatio;
2715                    } else {
2716                        resultHeight = ris.h;
2717                        resultWidth = resultHeight * aspectRatio;
2718                    }
2719
2720                    temp_canvas.width = resultWidth;
2721                    temp_canvas.height = resultHeight;
2722
2723                    temp_ctx.drawImage(image,
2724                        x,
2725                        y,
2726                        areaWidth,
2727                        areaHeight,
2728                        0,
2729                        0,
2730                        Math.round(resultWidth),
2731                        Math.round(resultHeight));
2732                }
2733
2734            }
2735            return temp_canvas;
2736
2737        };
2738
2739        var renderImageToDataURL = function (getResultImageSize) {
2740            var temp_canvas,
2741                retObj = {
2742                    dataURI: null,
2743                    imageData: null
2744                };
2745            temp_canvas = renderTempCanvas(getResultImageSize, theArea.getCenterPoint());
2746            if (image !== null) {
2747                if (resImgQuality !== null) {
2748                    retObj.dataURI = temp_canvas.toDataURL(resImgFormat, resImgQuality);
2749                } else {
2750                    retObj.dataURI = temp_canvas.toDataURL(resImgFormat);
2751                }
2752            }
2753            return retObj;
2754        };
2755
2756        this.getResultImage = function () {
2757            if (resImgSizeArray.length === 0) {
2758                return renderImageToDataURL(this.getResultImageSize());
2759            }
2760
2761            var arrayResultImages = [];
2762            for (var i = 0; i < resImgSizeArray.length; i++) {
2763                arrayResultImages.push({
2764                    dataURI: renderImageToDataURL(resImgSizeArray[i]).dataURI,
2765                    w: resImgSizeArray[i].w,
2766                    h: resImgSizeArray[i].h
2767                });
2768            }
2769
2770            return arrayResultImages;
2771        };
2772
2773        this.getResultImageDataBlob = function () {
2774            var temp_canvas,
2775                _p = $q.defer();
2776            temp_canvas = renderTempCanvas(this.getResultImageSize(), theArea.getCenterPoint());
2777            if (resImgQuality !== null) {
2778                temp_canvas.toBlob(function (blob) {
2779                    _p.resolve(blob);
2780                }, resImgFormat, resImgQuality);
2781            } else {
2782                temp_canvas.toBlob(function (blob) {
2783                    _p.resolve(blob);
2784                }, resImgFormat);
2785            }
2786
2787            return _p.promise;
2788        };
2789
2790        this.getAreaCoords = function () {
2791            return theArea.getSize();
2792        };
2793
2794        this.getArea = function () {
2795            return theArea;
2796        };
2797
2798        this.setNewImageSource = function (imageSource) {
2799            image = null;
2800            resetCropHost();
2801            if (imageSource) {
2802                var newImage = new Image();
2803                newImage.onload = function () {
2804                    events.trigger('load-done');
2805
2806                    cropEXIF.getData(newImage, function () {
2807                        var orientation = cropEXIF.getTag(newImage, 'Orientation');
2808
2809                        if ([3, 6, 8].indexOf(orientation) > -1) {
2810                            var canvas = document.createElement('canvas'),
2811                                ctx = canvas.getContext('2d'),
2812                                cw = newImage.width,
2813                                ch = newImage.height,
2814                                cx = 0,
2815                                cy = 0,
2816                                deg = 0,
2817                                rw = 0,
2818                                rh = 0;
2819                            rw = cw;
2820                            rh = ch;
2821                            switch (orientation) {
2822                                case 3:
2823                                    cx = -newImage.width;
2824                                    cy = -newImage.height;
2825                                    deg = 180;
2826                                    break;
2827                                case 6:
2828                                    cw = newImage.height;
2829                                    ch = newImage.width;
2830                                    cy = -newImage.height;
2831                                    rw = ch;
2832                                    rh = cw;
2833                                    deg = 90;
2834                                    break;
2835                                case 8:
2836                                    cw = newImage.height;
2837                                    ch = newImage.width;
2838                                    cx = -newImage.width;
2839                                    rw = ch;
2840                                    rh = cw;
2841                                    deg = 270;
2842                                    break;
2843                            }
2844
2845                            //// canvas.toDataURL will only work if the canvas isn't too large. Resize to 1000px.
2846                            var maxWorH = 1000;
2847                            if (cw > maxWorH || ch > maxWorH) {
2848                                var p = 0;
2849                                if (cw > maxWorH) {
2850                                    p = (maxWorH) / cw;
2851                                    cw = maxWorH;
2852                                    ch = p * ch;
2853                                } else if (ch > maxWorH) {
2854                                    p = (maxWorH) / ch;
2855                                    ch = maxWorH;
2856                                    cw = p * cw;
2857                                }
2858
2859                                cy = p * cy;
2860                                cx = p * cx;
2861                                rw = p * rw;
2862                                rh = p * rh;
2863                            }
2864
2865                            canvas.width = cw;
2866                            canvas.height = ch;
2867                            ctx.rotate(deg * Math.PI / 180);
2868                            ctx.drawImage(newImage, cx, cy, rw, rh);
2869
2870                            image = new Image();
2871                            image.onload = function () {
2872                                resetCropHost();
2873                                events.trigger('image-updated');
2874                            };
2875
2876                            image.src = canvas.toDataURL(resImgFormat);
2877                        } else {
2878                            image = newImage;
2879                            events.trigger('image-updated');
2880                        }
2881                        resetCropHost();
2882                    });
2883                };
2884                newImage.onerror = function () {
2885                    events.trigger('load-error');
2886                };
2887                events.trigger('load-start');
2888                if (imageSource instanceof window.Blob) {
2889                    newImage.src = URL.createObjectURL(imageSource);
2890                } else {
2891                    if (imageSource.substring(0, 4).toLowerCase() === 'http' || imageSource.substring(0, 2) === '//') {
2892                        newImage.crossOrigin = 'anonymous';
2893                    }
2894                    newImage.src = imageSource;
2895                }
2896            }
2897        };
2898
2899        this.setMaxDimensions = function (width, height) {
2900            maxCanvasDims = [width, height];
2901
2902            if (image !== null) {
2903                var curWidth = ctx.canvas.width,
2904                    curHeight = ctx.canvas.height;
2905                var canvasDims = [0, 0];
2906                if (scalemode === 'fit') {
2907                    var results = fitCanvasDims(image.width, image.height);
2908                    canvasDims = results.dims;
2909                    var margins = results.margins;
2910                    elCanvas.css({
2911                        'margin-left': margins.left + 'px',
2912                        'margin-top': margins.top + 'px'
2913                    });
2914                }
2915                else {
2916                    var imageDims = [image.width, image.height],
2917                        imageRatio = image.width / image.height;
2918                    canvasDims = imageDims;
2919
2920                    if (canvasDims[0] > maxCanvasDims[0]) {
2921                        canvasDims[0] = maxCanvasDims[0];
2922                        canvasDims[1] = canvasDims[0] / imageRatio;
2923                    } else if (canvasDims[0] < minCanvasDims[0]) {
2924                        canvasDims[0] = minCanvasDims[0];
2925                        canvasDims[1] = canvasDims[0] / imageRatio;
2926                    }
2927                    if (scalemode === 'fixed-height' && canvasDims[1] > maxCanvasDims[1]) {
2928                        canvasDims[1] = maxCanvasDims[1];
2929                        canvasDims[0] = canvasDims[1] * imageRatio;
2930                    } else if (canvasDims[1] < minCanvasDims[1]) {
2931                        canvasDims[1] = minCanvasDims[1];
2932                        canvasDims[0] = canvasDims[1] * imageRatio;
2933                    }
2934                }
2935                elCanvas.prop('width', canvasDims[0]).prop('height', canvasDims[1]);
2936
2937                if (scalemode === 'fixed-height') {
2938                    elCanvas.css({
2939                        'margin-left': -canvasDims[0] / 2 + 'px',
2940                        'margin-top': -canvasDims[1] / 2 + 'px'
2941                    });
2942                }
2943
2944                var ratioNewCurWidth = curWidth ? ctx.canvas.width / curWidth : 0,
2945                    ratioNewCurHeight = curHeight ? ctx.canvas.height / curHeight : 0,
2946                    ratioMin = Math.min(ratioNewCurWidth, ratioNewCurHeight);
2947
2948                //TODO: use top left corner point
2949                var center = theArea.getCenterPoint();
2950                theArea.setSize({
2951                    w: theArea.getSize().w * ratioMin,
2952                    h: theArea.getSize().h * ratioMin
2953                });
2954                theArea.setCenterPoint({
2955                    x: center.x * ratioNewCurWidth,
2956                    y: center.y * ratioNewCurHeight
2957                });
2958
2959            } else {
2960                elCanvas.prop('width', 0).prop('height', 0).css({
2961                    'margin-top': 0
2962                });
2963            }
2964
2965            drawScene();
2966
2967        };
2968
2969        this.setAreaMinSize = function (size) {
2970            if (angular.isUndefined(size)) {
2971                return;
2972            } else if (typeof size === 'number' || typeof size === 'string') {
2973                size = {
2974                    w: parseInt(parseInt(size), 10),
2975                    h: parseInt(parseInt(size), 10)
2976                };
2977            } else {
2978                size = {
2979                    w: parseInt(size.w, 10),
2980                    h: parseInt(size.h, 10)
2981                };
2982            }
2983            if (!isNaN(size.w) && !isNaN(size.h)) {
2984                theArea.setMinSize(size);
2985                drawScene();
2986            }
2987        };
2988
2989        this.setAreaMinRelativeSize = function (size) {
2990            if (image === null || angular.isUndefined(size)) {
2991                return;
2992            }
2993
2994            var canvasSize = theArea.getCanvasSize();
2995
2996            if (typeof size === 'number' || typeof size === 'string') {
2997                areaMinRelativeSize = {
2998                    w: size,
2999                    h: size
3000                };
3001                size = {
3002                    w: canvasSize.w / (image.width / parseInt(parseInt(size), 10)),
3003                    h: canvasSize.h / (image.height / parseInt(parseInt(size), 10))
3004                };
3005            } else {
3006                areaMinRelativeSize = size;
3007                size = {
3008                    w: canvasSize.w / (image.width / parseInt(parseInt(size.w), 10)),
3009                    h: canvasSize.h / (image.height / parseInt(parseInt(size.h), 10))
3010                };
3011            }
3012
3013            if (!isNaN(size.w) && !isNaN(size.h)) {
3014                theArea.setMinSize(size);
3015                drawScene();
3016            }
3017        };
3018
3019        this.setAreaInitSize = function (size) {
3020            if (angular.isUndefined(size)) {
3021                return;
3022            } else if (typeof size === 'number' || typeof size === 'string') {
3023                size = {
3024                    w: parseInt(parseInt(size), 10),
3025                    h: parseInt(parseInt(size), 10)
3026                };
3027            } else {
3028                size = {
3029                    w: parseInt(size.w, 10),
3030                    h: parseInt(size.h, 10)
3031                };
3032            }
3033            if (!isNaN(size.w) && !isNaN(size.h)) {
3034                theArea.setInitSize(size);
3035                drawScene();
3036            }
3037        };
3038
3039        this.setAreaInitCoords = function (coords) {
3040            if (angular.isUndefined(coords)) {
3041                return;
3042            } else {
3043                coords = {
3044                    x: parseInt(coords.x, 10),
3045                    y: parseInt(coords.y, 10)
3046                };
3047            }
3048            if (!isNaN(coords.x) && !isNaN(coords.y)) {
3049                theArea.setInitCoords(coords);
3050                drawScene();
3051            }
3052        };
3053
3054        this.setMaxCanvasDimensions = function (maxCanvasDimensions) {
3055            if (!angular.isUndefined(maxCanvasDimensions)) {
3056                var newMaxCanvasDims = [];
3057                if (typeof maxCanvasDimensions === 'number' || typeof maxCanvasDimensions === 'string') {
3058                    newMaxCanvasDims = [
3059                        parseInt(parseInt(maxCanvasDimensions), 10),
3060                        parseInt(parseInt(maxCanvasDimensions), 10)
3061                    ];
3062                } else {
3063                    newMaxCanvasDims = [
3064                        parseInt(maxCanvasDimensions.w, 10),
3065                        parseInt(maxCanvasDimensions.h, 10)
3066                    ];
3067                }
3068                if ((!isNaN(newMaxCanvasDims[0]) &&
3069                    newMaxCanvasDims[0] > 0 &&
3070                    newMaxCanvasDims[0] > minCanvasDims[0]) &&
3071                    (!isNaN(newMaxCanvasDims[1]) &&
3072                    newMaxCanvasDims[1] > 0 &&
3073                    newMaxCanvasDims[1] > minCanvasDims[1])) {
3074                    maxCanvasDims = newMaxCanvasDims;
3075                }
3076            }
3077        };
3078
3079        this.setMinCanvasDimensions = function (minCanvasDimensions) {
3080            if (!angular.isUndefined(minCanvasDimensions)) {
3081                var newMinCanvasDims = [];
3082                if (typeof minCanvasDimensions === 'number' || typeof minCanvasDimensions === 'string') {
3083                    newMinCanvasDims = [
3084                        parseInt(parseInt(minCanvasDimensions), 10),
3085                        parseInt(parseInt(minCanvasDimensions), 10)
3086                    ];
3087                } else {
3088                    newMinCanvasDims = [
3089                        parseInt(minCanvasDimensions.w, 10),
3090                        parseInt(minCanvasDimensions.h, 10)
3091                    ];
3092                }
3093                if ((!isNaN(newMinCanvasDims[0]) &&
3094                    newMinCanvasDims[0] >= 0) &&
3095                    (!isNaN(newMinCanvasDims[1]) &&
3096                    newMinCanvasDims[1] >= 0)) {
3097                    minCanvasDims = newMinCanvasDims;
3098                }
3099            }
3100        };
3101
3102        this.setScalemode = function (value) {
3103            scalemode = value;
3104        };
3105
3106        this.getScalemode = function () {
3107            return scalemode;
3108        };
3109
3110        this.getResultImageSize = function () {
3111            if (resImgSize === 'selection') {
3112                return theArea.getSize();
3113            }
3114
3115            if (resImgSize === 'max') {
3116                // We maximize the rendered size
3117                var zoom = 1;
3118                if (image && ctx && ctx.canvas) {
3119                    zoom = image.width / ctx.canvas.width;
3120                }
3121                var size = {
3122                    w: zoom * theArea.getSize().w,
3123                    h: zoom * theArea.getSize().h
3124                };
3125
3126                if (areaMinRelativeSize) {
3127                    if (size.w < areaMinRelativeSize.w) {
3128                        size.w = areaMinRelativeSize.w;
3129                    }
3130                    if (size.h < areaMinRelativeSize.h) {
3131                        size.h = areaMinRelativeSize.h;
3132                    }
3133                }
3134
3135                return size;
3136            }
3137
3138            return resImgSize;
3139        };
3140
3141        this.setResultImageSize = function (size) {
3142            if (angular.isArray(size)) {
3143                resImgSizeArray = size.slice();
3144                size = {
3145                    w: parseInt(size[0].w, 10),
3146                    h: parseInt(size[0].h, 10)
3147                };
3148                return;
3149            }
3150            if (angular.isUndefined(size)) {
3151                return;
3152            }
3153            //allow setting of size to "selection" for mirroring selection's dimensions
3154            if (angular.isString(size)) {
3155                resImgSize = size;
3156                return;
3157            }
3158            //allow scalar values for square-like selection shapes
3159            if (angular.isNumber(size)) {
3160                size = parseInt(size, 10);
3161                size = {
3162                    w: size,
3163                    h: size
3164                };
3165            }
3166            size = {
3167                w: parseInt(size.w, 10),
3168                h: parseInt(size.h, 10)
3169            };
3170            if (!isNaN(size.w) && !isNaN(size.h)) {
3171                resImgSize = size;
3172                drawScene();
3173            }
3174        };
3175
3176        this.setResultImageFormat = function (format) {
3177            resImgFormat = format;
3178        };
3179
3180        this.setResultImageQuality = function (quality) {
3181            quality = parseFloat(quality);
3182            if (!isNaN(quality) && quality >= 0 && quality <= 1) {
3183                resImgQuality = quality;
3184            }
3185        };
3186
3187        // returns a string of the selection area's type
3188        this.getAreaType = function () {
3189            return theArea.getType();
3190        };
3191
3192        this.setAreaType = function (type) {
3193            var center = theArea.getCenterPoint();
3194            var curSize = theArea.getSize(),
3195                curMinSize = theArea.getMinSize(),
3196                curX = center.x,
3197                curY = center.y;
3198
3199            var AreaClass = CropAreaCircle;
3200            if (type === 'square') {
3201                AreaClass = CropAreaSquare;
3202            } else if (type === 'rectangle') {
3203                AreaClass = CropAreaRectangle;
3204            }
3205            theArea = new AreaClass(ctx, events);
3206            theArea.setMinSize(curMinSize);
3207            theArea.setSize(curSize);
3208            if (type === 'square' || type === 'circle') {
3209                forceAspectRatio = true;
3210                theArea.setForceAspectRatio(true);
3211            } else {
3212                forceAspectRatio = false;
3213                theArea.setForceAspectRatio(false);
3214            }
3215
3216            //TODO: use top left point
3217            theArea.setCenterPoint({
3218                x: curX,
3219                y: curY
3220            });
3221
3222            // resetCropHost();
3223            if (image !== null) {
3224                theArea.setImage(image);
3225            }
3226
3227            drawScene();
3228        };
3229
3230        this.getDominantColor = function (uri) {
3231            var imageDC = new Image(),
3232                colorThief = new ColorThief(),
3233                dominantColor = null,
3234                _p = $q.defer();
3235            imageDC.src = uri;
3236            imageDC.onload = function () {
3237                dominantColor = colorThief.getColor(imageDC);
3238                _p.resolve(dominantColor);
3239            };
3240
3241            return _p.promise;
3242        };
3243
3244        this.getPalette = function (uri) {
3245            var imageDC = new Image(),
3246                colorThief = new ColorThief(),
3247                palette = null,
3248                _p = $q.defer();
3249            imageDC.src = uri;
3250            imageDC.onload = function () {
3251                palette = colorThief.getPalette(imageDC, colorPaletteLength);
3252                _p.resolve(palette);
3253            };
3254
3255            return _p.promise;
3256        };
3257
3258        this.setPaletteColorLength = function (lg) {
3259            colorPaletteLength = lg;
3260        };
3261
3262        this.setAspect = function (aspect) {
3263            isAspectRatio = true;
3264            theArea.setAspect(aspect);
3265            var minSize = theArea.getMinSize();
3266            minSize.w = minSize.h * aspect;
3267            theArea.setMinSize(minSize);
3268            var size = theArea.getSize();
3269            size.w = size.h * aspect;
3270            theArea.setSize(size);
3271        };
3272
3273        /* Life Cycle begins */
3274
3275        // Init Context var
3276        ctx = elCanvas[0].getContext('2d');
3277
3278        // Init CropArea
3279        theArea = new CropAreaCircle(ctx, events);
3280
3281        // Init Mouse Event Listeners
3282        $document.on('mousemove', onMouseMove);
3283        elCanvas.on('mousedown', onMouseDown);
3284        $document.on('mouseup', onMouseUp);
3285
3286        // Init Touch Event Listeners
3287        $document.on('touchmove', onMouseMove);
3288        elCanvas.on('touchstart', onMouseDown);
3289        $document.on('touchend', onMouseUp);
3290
3291        elCanvas.prop('tabindex', '0');
3292
3293        elCanvas.on('keydown', onKeyDown);
3294
3295        // CropHost Destructor
3296        this.destroy = function () {
3297            $document.off('mousemove', onMouseMove);
3298            elCanvas.off('mousedown', onMouseDown);
3299            $document.off('mouseup', onMouseUp);
3300
3301            $document.off('touchmove', onMouseMove);
3302            elCanvas.off('touchstart', onMouseDown);
3303            $document.off('touchend', onMouseUp);
3304
3305            elCanvas.off('keydown', onKeyDown);
3306
3307            elCanvas.remove();
3308        };
3309    };
3310}]);
3311
3312angular.module('uiCropper').factory('cropPubSub', [function() {
3313    return function() {
3314        var events = {};
3315        // Subscribe
3316        this.on = function(names, handler) {
3317            names.split(' ').forEach(function(name) {
3318                if (!events[name]) {
3319                    events[name] = [];
3320                }
3321                events[name].push(handler);
3322            });
3323            return this;
3324        };
3325        // Publish
3326        this.trigger = function(name, args) {
3327            angular.forEach(events[name], function(handler) {
3328                handler.call(null, args);
3329            });
3330            return this;
3331        };
3332    };
3333}]);
3334
3335angular.module('uiCropper').directive('uiCropper', ['$timeout', 'cropHost', 'cropPubSub', function ($timeout, CropHost, CropPubSub) {
3336    return {
3337        restrict: 'E',
3338        scope: {
3339            image: '=',
3340            resultImage: '=',
3341            resultArrayImage: '=?',
3342            resultBlob: '=?',
3343            urlBlob: '=?',
3344            chargement: '=?',
3345            cropject: '=?',
3346            maxCanvasDimensions: '=?',
3347            minCanvasDimensions: '=?',
3348            canvasScalemode: '@?', /* String. If set to 'full-width' the directive uses all width available */
3349            /* and the canvas expands in height as much as it need to maintain the aspect ratio */
3350            /* if set to 'fixed-height', the directive is restricted by a parent element in height */
3351            /* set to 'fit' to fit image into ui-cropper container */
3352
3353            changeOnFly: '=?',
3354            disableKeyboardAccess: '=?',
3355            allowPropagation: '=?',
3356            liveView: '=?',
3357            initMaxArea: '=?',
3358            areaCoords: '=?',
3359            areaType: '@',
3360            areaMinSize: '=?',
3361            areaInitSize: '=?',
3362            areaInitCoords: '=?',
3363            areaInitIsRelativeToImage: '=?', /* Boolean: If true the areaInitCoords and areaInitSize is scaled according to canvas size. */
3364            /* No matter how big/small the canvas is, the resultImage remains the same */
3365            /* Example: areaInitCoords are {x: 100, y: 100}, areaInitSize {w: 100, h: 100}   */
3366            /* Image is 1000x1000
3367             /* if canvas is 500x500 Crop coordinates will be x: 50, y: 50, w: 50, h: 50 */
3368            /* if canvas is 100x100 crop coordinates will be x: 10, y: 10, w: 10, h: 10 */
3369            areaMinRelativeSize: '=?',
3370            resultImageSize: '=?',
3371            resultImageFormat: '@',
3372            resultImageQuality: '=?',
3373
3374            aspectRatio: '=?',
3375            allowCropResizeOnCorners: '=?',
3376
3377            dominantColor: '=?',
3378            paletteColor: '=?',
3379            paletteColorLength: '=?',
3380            disableCrop: '=?',
3381
3382            onChange: '&',
3383            onLoadBegin: '&',
3384            onLoadDone: '&',
3385            onLoadError: '&'
3386        },
3387        template: '<canvas></canvas>',
3388        controller: ['$scope', function ($scope) {
3389            $scope.events = new CropPubSub();
3390        }],
3391        link: function (scope, element) {
3392            // Init Events Manager
3393            var events = scope.events;
3394
3395            // Init Crop Host
3396            var cropHost = new CropHost(element.find('canvas'), {
3397                disableKeyboardAccess: scope.disableKeyboardAccess,
3398                allowPropagation: scope.allowPropagation
3399            }, events);
3400
3401            if (scope.canvasScalemode) {
3402                cropHost.setScalemode(scope.canvasScalemode);
3403            } else {
3404                cropHost.setScalemode('fixed-height');
3405            }
3406
3407            element.addClass(cropHost.getScalemode());
3408
3409            // Store Result Image to check if it's changed
3410            var storedResultImage;
3411
3412            var updateAreaCoords = function (scope) {
3413                scope.areaCoords = cropHost.getAreaCoords();
3414            };
3415
3416            var updateResultImage = function (scope, force, callback) {
3417                if (scope.image !== '' && (!scope.liveView.block || force)) {
3418                    var resultImageObj = cropHost.getResultImage();
3419                    var resultImage;
3420                    if (angular.isArray(resultImageObj)) {
3421                        resultImage = resultImageObj[0].dataURI;
3422                        scope.resultArrayImage = resultImageObj;
3423                    } else {
3424                        resultImage = resultImageObj.dataURI;
3425                    }
3426
3427                    var urlCreator = window.URL || window.webkitURL;
3428                    if (storedResultImage !== resultImage) {
3429                        storedResultImage = resultImage;
3430                        scope.resultImage = resultImage;
3431                        if (scope.liveView.callback) {
3432                            scope.liveView.callback(resultImage);
3433                        }
3434                        if (callback) {
3435                            callback(resultImage);
3436                        }
3437                        cropHost.getResultImageDataBlob().then(function (blob) {
3438                            scope.resultBlob = blob;
3439                            scope.urlBlob = blob ? urlCreator.createObjectURL(blob) : null;
3440                        });
3441
3442                        if (scope.resultImage) {
3443                            cropHost.getDominantColor(scope.resultImage).then(function (dominantColor) {
3444                                scope.dominantColor = dominantColor;
3445                            });
3446                            cropHost.getPalette(scope.resultImage).then(function (palette) {
3447                                scope.paletteColor = palette;
3448                            });
3449                        }
3450
3451                        updateAreaCoords(scope);
3452                        scope.onChange({
3453                            $dataURI: scope.resultImage
3454                        });
3455                    }
3456                }
3457            };
3458
3459            if (scope.liveView && typeof scope.liveView.block === 'boolean') {
3460                scope.liveView.render = function (callback) {
3461                    updateResultImage(scope, true, callback);
3462                };
3463            } else {
3464                scope.liveView = {block: false};
3465            }
3466
3467            var updateCropject = function (scope) {
3468                var areaCoords = cropHost.getAreaCoords();
3469
3470                var dimRatio = {
3471                    x: cropHost.getArea().getImage().width / cropHost.getArea().getCanvasSize().w,
3472                    y: cropHost.getArea().getImage().height / cropHost.getArea().getCanvasSize().h
3473                };
3474
3475                scope.cropject = {
3476                    canvasSize: cropHost.getArea().getCanvasSize(),
3477                    areaCoords: areaCoords,
3478                    cropWidth: areaCoords.w,
3479                    cropHeight: areaCoords.h,
3480                    cropTop: areaCoords.y,
3481                    cropLeft: areaCoords.x,
3482                    cropImageWidth: Math.round(areaCoords.w * dimRatio.x),
3483                    cropImageHeight: Math.round(areaCoords.h * dimRatio.y),
3484                    cropImageTop: Math.round(areaCoords.y * dimRatio.y),
3485                    cropImageLeft: Math.round(areaCoords.x * dimRatio.x)
3486                };
3487            };
3488
3489            // Wrapper to safely exec functions within $apply on a running $digest cycle
3490            var fnSafeApply = function (fn) {
3491                return function () {
3492                    $timeout(function () {
3493                        scope.$apply(function (scope) {
3494                            fn(scope);
3495                        });
3496                    });
3497                };
3498            };
3499
3500            // Will get the users language settings, and return the appropriate loading text
3501            var printLoadMsg = function () {
3502                var language = window.navigator.userLanguage || window.navigator.language;
3503
3504                switch (language) {
3505                    case 'nl':
3506                    case 'nl_NL':
3507                        return 'Aan het laden';
3508
3509                    case 'fr':
3510                    case 'fr-FR':
3511                        return 'Chargement';
3512
3513                    case 'es':
3514                    case 'es-ES':
3515                        return 'Cargando';
3516
3517                    case 'ca':
3518                    case 'ca-ES':
3519                        return 'Càrrega';
3520
3521                    case 'de':
3522                    case 'de-DE':
3523                        return 'Laden';
3524
3525                    case 'pt':
3526                    case 'pt-BR':
3527                        return 'Carregando';
3528
3529                    default:
3530                        return 'Loading';
3531                }
3532            };
3533
3534            if (!scope.chargement) {
3535                scope.chargement = printLoadMsg();
3536            }
3537            var displayLoading = function () {
3538                element.append('<div class="loading"><span>' + scope.chargement + '...</span></div>');
3539            };
3540
3541            // Setup CropHost Event Handlers
3542            events
3543                .on('load-start', fnSafeApply(function (scope) {
3544                    scope.onLoadBegin({});
3545                }))
3546                .on('load-done', fnSafeApply(function (scope) {
3547                    var children = element.children();
3548                    angular.forEach(children, function (child) {
3549                        if (angular.element(child).hasClass('loading')) {
3550                            angular.element(child).remove();
3551                        }
3552                    });
3553                    updateCropject(scope);
3554                    scope.onLoadDone({});
3555                }))
3556                .on('load-error', fnSafeApply(function (scope) {
3557                    scope.onLoadError({});
3558                }))
3559                .on('area-move area-resize', fnSafeApply(function (scope) {
3560                    if (scope.changeOnFly === true) {
3561                        updateResultImage(scope);
3562                    }
3563                    updateCropject(scope);
3564                }))
3565                .on('image-updated', fnSafeApply(function (scope) {
3566                    cropHost.setAreaMinRelativeSize(scope.areaMinRelativeSize);
3567                }))
3568                .on('area-move-end area-resize-end image-updated', fnSafeApply(function (scope) {
3569                    updateResultImage(scope);
3570                    updateCropject(scope);
3571                }));
3572
3573            // Sync CropHost with Directive's options
3574            scope.$watch('image', function (newVal) {
3575                // reset the original size and position to 0
3576                // it's mandatory because if not reset the size of the crop area won't maximise when the image was replaced
3577                var area = cropHost.getArea();
3578                if (area) {
3579                    cropHost.getArea()._size = { x: 0, y: 0, w: 0, h: 0 };
3580                }
3581                if (newVal) {
3582                    displayLoading();
3583                }
3584                // cancel timeout if necessary
3585                if (scope.timeout !== null) {
3586                    $timeout.cancel(scope.timeout);
3587                }
3588                scope.timeout = $timeout(function () {
3589                    scope.timeout = null;
3590                    cropHost.setInitMax(scope.initMaxArea);
3591                    cropHost.setNewImageSource(scope.image);
3592                }, 100);
3593            });
3594            scope.$watch('areaType', function () {
3595                cropHost.setAreaType(scope.areaType);
3596                updateResultImage(scope);
3597            });
3598            scope.$watch('areaMinSize', function () {
3599                cropHost.setAreaMinSize(scope.areaMinSize);
3600                updateResultImage(scope);
3601            });
3602            scope.$watch('areaMinRelativeSize', function () {
3603                if (scope.image !== '') {
3604                    cropHost.setAreaMinRelativeSize(scope.areaMinRelativeSize);
3605                    updateResultImage(scope);
3606                }
3607            });
3608            scope.$watch('areaInitSize', function () {
3609                cropHost.setAreaInitSize(scope.areaInitSize);
3610                updateResultImage(scope);
3611            });
3612            scope.$watch('areaInitCoords', function () {
3613                cropHost.setAreaInitCoords(scope.areaInitCoords);
3614                cropHost.areaInitIsRelativeToImage = scope.areaInitIsRelativeToImage;
3615                updateResultImage(scope);
3616            });
3617            scope.$watch('maxCanvasDimensions', function () {
3618                cropHost.setMaxCanvasDimensions(scope.maxCanvasDimensions);
3619            });
3620            scope.$watch('minCanvasDimensions', function () {
3621                cropHost.setMinCanvasDimensions(scope.minCanvasDimensions);
3622            });
3623            scope.$watch('resultImageFormat', function () {
3624                cropHost.setResultImageFormat(scope.resultImageFormat);
3625                updateResultImage(scope);
3626            });
3627            scope.$watch('resultImageQuality', function () {
3628                cropHost.setResultImageQuality(scope.resultImageQuality);
3629                updateResultImage(scope);
3630            });
3631            scope.$watch('resultImageSize', function () {
3632                cropHost.setResultImageSize(scope.resultImageSize);
3633                updateResultImage(scope);
3634            });
3635            scope.$watch('paletteColorLength', function () {
3636                cropHost.setPaletteColorLength(scope.paletteColorLength);
3637            });
3638            scope.$watch('aspectRatio', function () {
3639                if (typeof scope.aspectRatio === 'string' && scope.aspectRatio !== '') {
3640                    scope.aspectRatio = parseFloat(scope.aspectRatio);
3641                }
3642                if (scope.aspectRatio) {
3643                    cropHost.setAspect(scope.aspectRatio);
3644                }
3645            });
3646            scope.$watch('allowCropResizeOnCorners', function () {
3647                if (scope.allowCropResizeOnCorners) {
3648                    cropHost.setAllowCropResizeOnCorners(scope.allowCropResizeOnCorners);
3649                }
3650            });
3651
3652            scope.$watch('disableCrop', function () {
3653                cropHost.setDisableCrop(scope.disableCrop);
3654            });
3655
3656            // Update CropHost dimensions when the directive element is resized
3657            scope.$watch(
3658                function () {
3659                    if (cropHost.getScalemode() === 'fit') {
3660                        return [element[0].clientWidth, element[0].clientHeight];
3661                    }
3662                    if (cropHost.getScalemode() === 'fixed-height') {
3663                        return [element[0].clientWidth, element[0].clientHeight];
3664                    }
3665                    if (cropHost.getScalemode() === 'full-width') {
3666                        return element[0].clientWidth;
3667                    }
3668                },
3669                function (value) {
3670
3671                    if (cropHost.getScalemode() === 'fit') {
3672                        cropHost.setMaxDimensions(value[0], value[1]);
3673                        updateResultImage(scope);
3674                    }
3675                    if (cropHost.getScalemode() === 'fixed-height') {
3676                        if (value[0] > 0 && value[1] > 0) {
3677                            cropHost.setMaxDimensions(value[0], value[1]);
3678                            updateResultImage(scope);
3679                        }
3680                    }
3681                    if (cropHost.getScalemode() === 'full-width') {
3682                        if (value > 0) {
3683                            cropHost.setMaxDimensions(value);
3684                        }
3685                    }
3686                },
3687                true
3688            );
3689
3690            // Destroy CropHost Instance when the directive is destroying
3691            scope.$on('$destroy', function () {
3692                cropHost.destroy();
3693            });
3694        }
3695    };
3696}]);
3697
3698}());
3699/* canvas-toBlob.js
3700 * A canvas.toBlob() implementation.
3701 * 2013-12-27
3702 * 
3703 * By Eli Grey, http://eligrey.com and Devin Samarin, https://github.com/eboyjr
3704 * License: X11/MIT
3705 *   See https://github.com/eligrey/canvas-toBlob.js/blob/master/LICENSE.md
3706 */
3707
3708/*global self */
3709/*jslint bitwise: true, regexp: true, confusion: true, es5: true, vars: true, white: true,
3710  plusplus: true */
3711
3712/*! @source http://purl.eligrey.com/github/canvas-toBlob.js/blob/master/canvas-toBlob.js */
3713
3714(function(view) {
3715    "use strict";
3716    var
3717        Uint8Array = view.Uint8Array,
3718        HTMLCanvasElement = view.HTMLCanvasElement,
3719        canvas_proto = HTMLCanvasElement && HTMLCanvasElement.prototype,
3720        is_base64_regex = /\s*;\s*base64\s*(?:;|$)/i,
3721        to_data_url = "toDataURL",
3722        base64_ranks, decode_base64 = function(base64) {
3723            var
3724                len = base64.length,
3725                buffer = new Uint8Array(len / 4 * 3 | 0),
3726                i = 0,
3727                outptr = 0,
3728                last = [0, 0],
3729                state = 0,
3730                save = 0,
3731                rank, code, undef;
3732            while (len--) {
3733                code = base64.charCodeAt(i++);
3734                rank = base64_ranks[code - 43];
3735                if (rank !== 255 && rank !== undef) {
3736                    last[1] = last[0];
3737                    last[0] = code;
3738                    save = (save << 6) | rank;
3739                    state++;
3740                    if (state === 4) {
3741                        buffer[outptr++] = save >>> 16;
3742                        if (last[1] !== 61 /* padding character */ ) {
3743                            buffer[outptr++] = save >>> 8;
3744                        }
3745                        if (last[0] !== 61 /* padding character */ ) {
3746                            buffer[outptr++] = save;
3747                        }
3748                        state = 0;
3749                    }
3750                }
3751            }
3752            // 2/3 chance there's going to be some null bytes at the end, but that
3753            // doesn't really matter with most image formats.
3754            // If it somehow matters for you, truncate the buffer up outptr.
3755            return buffer;
3756        };
3757    if (Uint8Array) {
3758        base64_ranks = new Uint8Array([
3759            62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, 0, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
3760        ]);
3761    }
3762    if (HTMLCanvasElement && !canvas_proto.toBlob) {
3763        canvas_proto.toBlob = function(callback, type /*, ...args*/ ) {
3764            if (!type) {
3765                type = "image/png";
3766            }
3767            if (this.mozGetAsFile) {
3768                callback(this.mozGetAsFile("canvas", type));
3769                return;
3770            }
3771            if (this.msToBlob && /^\s*image\/png\s*(?:$|;)/i.test(type)) {
3772                callback(this.msToBlob());
3773                return;
3774            }
3775
3776            var
3777                args = Array.prototype.slice.call(arguments, 1),
3778                dataURI = this[to_data_url].apply(this, args),
3779                header_end = dataURI.indexOf(","),
3780                data = dataURI.substring(header_end + 1),
3781                is_base64 = is_base64_regex.test(dataURI.substring(0, header_end)),
3782                blob;
3783            if (Blob.fake) {
3784                // no reason to decode a data: URI that's just going to become a data URI again
3785                blob = new Blob
3786                if (is_base64) {
3787                    blob.encoding = "base64";
3788                } else {
3789                    blob.encoding = "URI";
3790                }
3791                blob.data = data;
3792                blob.size = data.length;
3793            } else if (Uint8Array) {
3794                if (is_base64) {
3795                    blob = new Blob([decode_base64(data)], {
3796                        type: type
3797                    });
3798                } else {
3799                    blob = new Blob([decodeURIComponent(data)], {
3800                        type: type
3801                    });
3802                }
3803            }
3804            if (typeof callback !== 'undefined') {
3805                callback(blob);
3806            }
3807        };
3808
3809        if (canvas_proto.toDataURLHD) {
3810            canvas_proto.toBlobHD = function() {
3811                to_data_url = "toDataURLHD";
3812                var blob = this.toBlob();
3813                to_data_url = "toDataURL";
3814                return blob;
3815            }
3816        } else {
3817            canvas_proto.toBlobHD = canvas_proto.toBlob;
3818        }
3819    }
3820}(typeof self !== "undefined" && self || typeof window !== "undefined" && window || this.content || this));
3821
3822/*!
3823 * Color Thief v2.0
3824 * by Lokesh Dhakar - http://www.lokeshdhakar.com
3825 *
3826 * Thanks
3827 * ------
3828 * Nick Rabinowitz - For creating quantize.js.
3829 * John Schulz - For clean up and optimization. @JFSIII
3830 * Nathan Spady - For adding drag and drop support to the demo page.
3831 *
3832 * License
3833 * -------
3834 * Copyright 2011, 2015 Lokesh Dhakar
3835 * Released under the MIT license
3836 * https://raw.githubusercontent.com/lokesh/color-thief/master/LICENSE
3837 *
3838 */
3839(function() {
3840    /*!
3841     * Color Thief v2.0
3842     * by Lokesh Dhakar - http://www.lokeshdhakar.com
3843     *
3844     * Thanks
3845     * ------
3846     * Nick Rabinowitz - For creating quantize.js.
3847     * John Schulz - For clean up and optimization. @JFSIII
3848     * Nathan Spady - For adding drag and drop support to the demo page.
3849     *
3850     * License
3851     * -------
3852     * Copyright 2011, 2015 Lokesh Dhakar
3853     * Released under the MIT license
3854     * https://raw.githubusercontent.com/lokesh/color-thief/master/LICENSE
3855     *
3856     */
3857
3858    /*
3859      CanvasImage Class
3860      Class that wraps the html image element and canvas.
3861      It also simplifies some of the canvas context manipulation
3862      with a set of helper functions.
3863    */
3864    var CanvasImage = function(image) {
3865        this.canvas = document.createElement('canvas');
3866        this.context = this.canvas.getContext('2d');
3867
3868        document.body.appendChild(this.canvas);
3869
3870        this.width = this.canvas.width = image.width;
3871        this.height = this.canvas.height = image.height;
3872
3873        this.context.drawImage(image, 0, 0, this.width, this.height);
3874    };
3875
3876    CanvasImage.prototype.clear = function() {
3877        this.context.clearRect(0, 0, this.width, this.height);
3878    };
3879
3880    CanvasImage.prototype.update = function(imageData) {
3881        this.context.putImageData(imageData, 0, 0);
3882    };
3883
3884    CanvasImage.prototype.getPixelCount = function() {
3885        return this.width * this.height;
3886    };
3887
3888    CanvasImage.prototype.getImageData = function() {
3889        return this.context.getImageData(0, 0, this.width, this.height);
3890    };
3891
3892    CanvasImage.prototype.removeCanvas = function() {
3893        this.canvas.parentNode.removeChild(this.canvas);
3894    };
3895
3896    var ColorThief = function() {};
3897    window.ColorThief = ColorThief;
3898
3899    /*
3900     * getColor(sourceImage[, quality])
3901     * returns {r: num, g: num, b: num}
3902     *
3903     * Use the median cut algorithm provided by quantize.js to cluster similar
3904     * colors and return the base color from the largest cluster.
3905     *
3906     * Quality is an optional argument. It needs to be an integer. 1 is the highest quality settings.
3907     * 10 is the default. There is a trade-off between quality and speed. The bigger the number, the
3908     * faster a color will be returned but the greater the likelihood that it will not be the visually
3909     * most dominant color.
3910     *
3911     * */
3912    ColorThief.prototype.getColor = function(sourceImage, quality) {
3913        var palette = this.getPalette(sourceImage, 5, quality);
3914        var dominantColor = palette[0];
3915        return dominantColor;
3916    };
3917
3918    /*
3919     * getPalette(sourceImage[, colorCount, quality])
3920     * returns array[ {r: num, g: num, b: num}, {r: num, g: num, b: num}, ...]
3921     *
3922     * Use the median cut algorithm provided by quantize.js to cluster similar colors.
3923     *
3924     * colorCount determines the size of the palette; the number of colors returned. If not set, it
3925     * defaults to 10.
3926     *
3927     * BUGGY: Function does not always return the requested amount of colors. It can be +/- 2.
3928     *
3929     * quality is an optional argument. It needs to be an integer. 1 is the highest quality settings.
3930     * 10 is the default. There is a trade-off between quality and speed. The bigger the number, the
3931     * faster the palette generation but the greater the likelihood that colors will be missed.
3932     *
3933     *
3934     */
3935    ColorThief.prototype.getPalette = function(sourceImage, colorCount, quality) {
3936
3937        if (typeof colorCount === 'undefined') {
3938            colorCount = 10;
3939        }
3940        if (typeof quality === 'undefined' || quality < 1) {
3941            quality = 10;
3942        }
3943
3944        // Create custom CanvasImage object
3945        var image = new CanvasImage(sourceImage);
3946        var imageData = image.getImageData();
3947        var pixels = imageData.data;
3948        var pixelCount = image.getPixelCount();
3949
3950        // Store the RGB values in an array format suitable for quantize function
3951        var pixelArray = [];
3952        for (var i = 0, offset, r, g, b, a; i < pixelCount; i = i + quality) {
3953            offset = i * 4;
3954            r = pixels[offset + 0];
3955            g = pixels[offset + 1];
3956            b = pixels[offset + 2];
3957            a = pixels[offset + 3];
3958            // If pixel is mostly opaque and not white
3959            if (a >= 125) {
3960                if (!(r > 250 && g > 250 && b > 250)) {
3961                    pixelArray.push([r, g, b]);
3962                }
3963            }
3964        }
3965
3966        // Send array to quantize function which clusters values
3967        // using median cut algorithm
3968        var cmap = MMCQ.quantize(pixelArray, colorCount);
3969        var palette = cmap ? cmap.palette() : null;
3970
3971        // Clean up
3972        image.removeCanvas();
3973
3974        return palette;
3975    };
3976
3977    /*!
3978     * quantize.js Copyright 2008 Nick Rabinowitz.
3979     * Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
3980     */
3981
3982    // fill out a couple protovis dependencies
3983    /*!
3984     * Block below copied from Protovis: http://mbostock.github.com/protovis/
3985     * Copyright 2010 Stanford Visualization Group
3986     * Licensed under the BSD License: http://www.opensource.org/licenses/bsd-license.php
3987     */
3988    if (!pv) {
3989        var pv = {
3990            map: function(array, f) {
3991                var o = {};
3992                return f ? array.map(function(d, i) {
3993                    o.index = i;
3994                    return f.call(o, d);
3995                }) : array.slice();
3996            },
3997            naturalOrder: function(a, b) {
3998                return (a < b) ? -1 : ((a > b) ? 1 : 0);
3999            },
4000            sum: function(array, f) {
4001                var o = {};
4002                return array.reduce(f ? function(p, d, i) {
4003                    o.index = i;
4004                    return p + f.call(o, d);
4005                } : function(p, d) {
4006                    return p + d;
4007                }, 0);
4008            },
4009            max: function(array, f) {
4010                return Math.max.apply(null, f ? pv.map(array, f) : array);
4011            }
4012        };
4013    }
4014
4015    /**
4016     * Basic Javascript port of the MMCQ (modified median cut quantization)
4017     * algorithm from the Leptonica library (http://www.leptonica.com/).
4018     * Returns a color map you can use to map original pixels to the reduced
4019     * palette. Still a work in progress.
4020     *
4021     * @author Nick Rabinowitz
4022     * @example
4023
4024    // array of pixels as [R,G,B] arrays
4025    var myPixels = [[190,197,190], [202,204,200], [207,214,210], [211,214,211], [205,207,207]
4026                    // etc
4027                    ];
4028    var maxColors = 4;
4029
4030    var cmap = MMCQ.quantize(myPixels, maxColors);
4031    var newPalette = cmap.palette();
4032    var newPixels = myPixels.map(function(p) {
4033        return cmap.map(p);
4034    });
4035
4036     */
4037    var MMCQ = (function() {
4038        // private constants
4039        var sigbits = 5,
4040            rshift = 8 - sigbits,
4041            maxIterations = 1000,
4042            fractByPopulations = 0.75;
4043
4044        // get reduced-space color index for a pixel
4045        function getColorIndex(r, g, b) {
4046            return (r << (2 * sigbits)) + (g << sigbits) + b;
4047        }
4048
4049        // Simple priority queue
4050        function PQueue(comparator) {
4051            var contents = [],
4052                sorted = false;
4053
4054            function sort() {
4055                contents.sort(comparator);
4056                sorted = true;
4057            }
4058
4059            return {
4060                push: function(o) {
4061                    contents.push(o);
4062                    sorted = false;
4063                },
4064                peek: function(index) {
4065                    if (!sorted) sort();
4066                    if (index === undefined) index = contents.length - 1;
4067                    return contents[index];
4068                },
4069                pop: function() {
4070                    if (!sorted) sort();
4071                    return contents.pop();
4072                },
4073                size: function() {
4074                    return contents.length;
4075                },
4076                map: function(f) {
4077                    return contents.map(f);
4078                },
4079                debug: function() {
4080                    if (!sorted) sort();
4081                    return contents;
4082                }
4083            };
4084        }
4085
4086        // 3d color space box
4087        function VBox(r1, r2, g1, g2, b1, b2, histo) {
4088            var vbox = this;
4089            vbox.r1 = r1;
4090            vbox.r2 = r2;
4091            vbox.g1 = g1;
4092            vbox.g2 = g2;
4093            vbox.b1 = b1;
4094            vbox.b2 = b2;
4095            vbox.histo = histo;
4096        }
4097        VBox.prototype = {
4098            volume: function(force) {
4099                var vbox = this;
4100                if (!vbox._volume || force) {
4101                    vbox._volume = ((vbox.r2 - vbox.r1 + 1) * (vbox.g2 - vbox.g1 + 1) * (vbox.b2 - vbox.b1 + 1));
4102                }
4103                return vbox._volume;
4104            },
4105            count: function(force) {
4106                var vbox = this,
4107                    histo = vbox.histo;
4108                if (!vbox._count_set || force) {
4109                    var npix = 0,
4110                        i, j, k;
4111                    for (i = vbox.r1; i <= vbox.r2; i++) {
4112                        for (j = vbox.g1; j <= vbox.g2; j++) {
4113                            for (k = vbox.b1; k <= vbox.b2; k++) {
4114                                var index = getColorIndex(i, j, k);
4115                                npix += (histo[index] || 0);
4116                            }
4117                        }
4118                    }
4119                    vbox._count = npix;
4120                    vbox._count_set = true;
4121                }
4122                return vbox._count;
4123            },
4124            copy: function() {
4125                var vbox = this;
4126                return new VBox(vbox.r1, vbox.r2, vbox.g1, vbox.g2, vbox.b1, vbox.b2, vbox.histo);
4127            },
4128            avg: function(force) {
4129                var vbox = this,
4130                    histo = vbox.histo;
4131                if (!vbox._avg || force) {
4132                    var ntot = 0,
4133                        mult = 1 << (8 - sigbits),
4134                        rsum = 0,
4135                        gsum = 0,
4136                        bsum = 0,
4137                        hval,
4138                        i, j, k, histoindex;
4139                    for (i = vbox.r1; i <= vbox.r2; i++) {
4140                        for (j = vbox.g1; j <= vbox.g2; j++) {
4141                            for (k = vbox.b1; k <= vbox.b2; k++) {
4142                                histoindex = getColorIndex(i, j, k);
4143                                hval = histo[histoindex] || 0;
4144                                ntot += hval;
4145                                rsum += (hval * (i + 0.5) * mult);
4146                                gsum += (hval * (j + 0.5) * mult);
4147                                bsum += (hval * (k + 0.5) * mult);
4148                            }
4149                        }
4150                    }
4151                    if (ntot) {
4152                        vbox._avg = [~~(rsum / ntot), ~~(gsum / ntot), ~~(bsum / ntot)];
4153                    } else {
4154                        //                    console.log('empty box');
4155                        vbox._avg = [~~(mult * (vbox.r1 + vbox.r2 + 1) / 2), ~~(mult * (vbox.g1 + vbox.g2 + 1) / 2), ~~(mult * (vbox.b1 + vbox.b2 + 1) / 2)];
4156                    }
4157                }
4158                return vbox._avg;
4159            },
4160            contains: function(pixel) {
4161                var vbox = this,
4162                    rval = pixel[0] >> rshift;
4163                gval = pixel[1] >> rshift;
4164                bval = pixel[2] >> rshift;
4165                return (rval >= vbox.r1 && rval <= vbox.r2 &&
4166                    gval >= vbox.g1 && gval <= vbox.g2 &&
4167                    bval >= vbox.b1 && bval <= vbox.b2);
4168            }
4169        };
4170
4171        // Color map
4172        function CMap() {
4173            this.vboxes = new PQueue(function(a, b) {
4174                return pv.naturalOrder(
4175                    a.vbox.count() * a.vbox.volume(),
4176                    b.vbox.count() * b.vbox.volume()
4177                );
4178            });
4179        }
4180        CMap.prototype = {
4181            push: function(vbox) {
4182                this.vboxes.push({
4183                    vbox: vbox,
4184                    color: vbox.avg()
4185                });
4186            },
4187            palette: function() {
4188                return this.vboxes.map(function(vb) {
4189                    return vb.color;
4190                });
4191            },
4192            size: function() {
4193                return this.vboxes.size();
4194            },
4195            map: function(color) {
4196                var vboxes = this.vboxes;
4197                for (var i = 0; i < vboxes.size(); i++) {
4198                    if (vboxes.peek(i).vbox.contains(color)) {
4199                        return vboxes.peek(i).color;
4200                    }
4201                }
4202                return this.nearest(color);
4203            },
4204            nearest: function(color) {
4205                var vboxes = this.vboxes,
4206                    d1, d2, pColor;
4207                for (var i = 0; i < vboxes.size(); i++) {
4208                    d2 = Math.sqrt(
4209                        Math.pow(color[0] - vboxes.peek(i).color[0], 2) +
4210                        Math.pow(color[1] - vboxes.peek(i).color[1], 2) +
4211                        Math.pow(color[2] - vboxes.peek(i).color[2], 2)
4212                    );
4213                    if (d2 < d1 || d1 === undefined) {
4214                        d1 = d2;
4215                        pColor = vboxes.peek(i).color;
4216                    }
4217                }
4218                return pColor;
4219            },
4220            forcebw: function() {
4221                // XXX: won't  work yet
4222                var vboxes = this.vboxes;
4223                vboxes.sort(function(a, b) {
4224                    return pv.naturalOrder(pv.sum(a.color), pv.sum(b.color));
4225                });
4226
4227                // force darkest color to black if everything < 5
4228                var lowest = vboxes[0].color;
4229                if (lowest[0] < 5 && lowest[1] < 5 && lowest[2] < 5)
4230                    vboxes[0].color = [0, 0, 0];
4231
4232                // force lightest color to white if everything > 251
4233                var idx = vboxes.length - 1,
4234                    highest = vboxes[idx].color;
4235                if (highest[0] > 251 && highest[1] > 251 && highest[2] > 251)
4236                    vboxes[idx].color = [255, 255, 255];
4237            }
4238        };
4239
4240        // histo (1-d array, giving the number of pixels in
4241        // each quantized region of color space), or null on error
4242        function getHisto(pixels) {
4243            var histosize = 1 << (3 * sigbits),
4244                histo = new Array(histosize),
4245                index, rval, gval, bval;
4246            pixels.forEach(function(pixel) {
4247                rval = pixel[0] >> rshift;
4248                gval = pixel[1] >> rshift;
4249                bval = pixel[2] >> rshift;
4250                index = getColorIndex(rval, gval, bval);
4251                histo[index] = (histo[index] || 0) + 1;
4252            });
4253            return histo;
4254        }
4255
4256        function vboxFromPixels(pixels, histo) {
4257            var rmin = 1000000,
4258                rmax = 0,
4259                gmin = 1000000,
4260                gmax = 0,
4261                bmin = 1000000,
4262                bmax = 0,
4263                rval, gval, bval;
4264            // find min/max
4265            pixels.forEach(function(pixel) {
4266                rval = pixel[0] >> rshift;
4267                gval = pixel[1] >> rshift;
4268                bval = pixel[2] >> rshift;
4269                if (rval < rmin) rmin = rval;
4270                else if (rval > rmax) rmax = rval;
4271                if (gval < gmin) gmin = gval;
4272                else if (gval > gmax) gmax = gval;
4273                if (bval < bmin) bmin = bval;
4274                else if (bval > bmax) bmax = bval;
4275            });
4276            return new VBox(rmin, rmax, gmin, gmax, bmin, bmax, histo);
4277        }
4278
4279        function medianCutApply(histo, vbox) {
4280            if (!vbox.count()) return;
4281
4282            var rw = vbox.r2 - vbox.r1 + 1,
4283                gw = vbox.g2 - vbox.g1 + 1,
4284                bw = vbox.b2 - vbox.b1 + 1,
4285                maxw = pv.max([rw, gw, bw]);
4286            // only one pixel, no split
4287            if (vbox.count() == 1) {
4288                return [vbox.copy()];
4289            }
4290            /* Find the partial sum arrays along the selected axis. */
4291            var total = 0,
4292                partialsum = [],
4293                lookaheadsum = [],
4294                i, j, k, sum, index;
4295            if (maxw == rw) {
4296                for (i = vbox.r1; i <= vbox.r2; i++) {
4297                    sum = 0;
4298                    for (j = vbox.g1; j <= vbox.g2; j++) {
4299                        for (k = vbox.b1; k <= vbox.b2; k++) {
4300                            index = getColorIndex(i, j, k);
4301                            sum += (histo[index] || 0);
4302                        }
4303                    }
4304                    total += sum;
4305                    partialsum[i] = total;
4306                }
4307            } else if (maxw == gw) {
4308                for (i = vbox.g1; i <= vbox.g2; i++) {
4309                    sum = 0;
4310                    for (j = vbox.r1; j <= vbox.r2; j++) {
4311                        for (k = vbox.b1; k <= vbox.b2; k++) {
4312                            index = getColorIndex(j, i, k);
4313                            sum += (histo[index] || 0);
4314                        }
4315                    }
4316                    total += sum;
4317                    partialsum[i] = total;
4318                }
4319            } else { /* maxw == bw */
4320                for (i = vbox.b1; i <= vbox.b2; i++) {
4321                    sum = 0;
4322                    for (j = vbox.r1; j <= vbox.r2; j++) {
4323                        for (k = vbox.g1; k <= vbox.g2; k++) {
4324                            index = getColorIndex(j, k, i);
4325                            sum += (histo[index] || 0);
4326                        }
4327                    }
4328                    total += sum;
4329                    partialsum[i] = total;
4330                }
4331            }
4332            partialsum.forEach(function(d, i) {
4333                lookaheadsum[i] = total - d;
4334            });
4335
4336            function doCut(color) {
4337                var dim1 = color + '1',
4338                    dim2 = color + '2',
4339                    left, right, vbox1, vbox2, d2, count2 = 0;
4340                for (i = vbox[dim1]; i <= vbox[dim2]; i++) {
4341                    if (partialsum[i] > total / 2) {
4342                        vbox1 = vbox.copy();
4343                        vbox2 = vbox.copy();
4344                        left = i - vbox[dim1];
4345                        right = vbox[dim2] - i;
4346                        if (left <= right)
4347                            d2 = Math.min(vbox[dim2] - 1, ~~(i + right / 2));
4348                        else d2 = Math.max(vbox[dim1], ~~(i - 1 - left / 2));
4349                        // avoid 0-count boxes
4350                        while (!partialsum[d2]) d2++;
4351                        count2 = lookaheadsum[d2];
4352                        while (!count2 && partialsum[d2 - 1]) count2 = lookaheadsum[--d2];
4353                        // set dimensions
4354                        vbox1[dim2] = d2;
4355                        vbox2[dim1] = vbox1[dim2] + 1;
4356                        //                    console.log('vbox counts:', vbox.c
4356ount(), vbox1.count(), vbox2.count());
4357                        return [vbox1, vbox2];
4358                    }
4359                }
4360
4361            }
4362            // determine the cut planes
4363            return maxw == rw ? doCut('r') :
4364                maxw == gw ? doCut('g') :
4365                doCut('b');
4366        }
4367
4368        function quantize(pixels, maxcolors) {
4369            // short-circuit
4370            if (!pixels.length || maxcolors < 2 || maxcolors > 256) {
4371                //            console.log('wrong number of maxcolors');
4372                return false;
4373            }
4374
4375            // XXX: check color content and convert to grayscale if insufficient
4376
4377            var histo = getHisto(pixels),
4378                histosize = 1 << (3 * sigbits);
4379
4380            // check that we aren't below maxcolors already
4381            var nColors = 0;
4382            histo.forEach(function() {
4383                nColors++;
4384            });
4385            if (nColors <= maxcolors) {
4386                // XXX: generate the new colors from the histo and return
4387            }
4388
4389            // get the beginning vbox from the colors
4390            var vbox = vboxFromPixels(pixels, histo),
4391                pq = new PQueue(function(a, b) {
4392                    return pv.naturalOrder(a.count(), b.count());
4393                });
4394            pq.push(vbox);
4395
4396            // inner function to do the iteration
4397            function iter(lh, target) {
4398                var ncolors = 1,
4399                    niters = 0,
4400                    vbox;
4401                while (niters < maxIterations) {
4402                    vbox = lh.pop();
4403                    if (!vbox.count()) { /* just put it back */
4404                        lh.push(vbox);
4405                        niters++;
4406                        continue;
4407                    }
4408                    // do the cut
4409                    var vboxes = medianCutApply(histo, vbox),
4410                        vbox1 = vboxes[0],
4411                        vbox2 = vboxes[1];
4412
4413                    if (!vbox1) {
4414                        //                    console.log("vbox1 not defined; shouldn't happen!");
4415                        return;
4416                    }
4417                    lh.push(vbox1);
4418                    if (vbox2) { /* vbox2 can be null */
4419                        lh.push(vbox2);
4420                        ncolors++;
4421                    }
4422                    if (ncolors >= target) return;
4423                    if (niters++ > maxIterations) {
4424                        //                    console.log("infinite loop; perhaps too few pixels!");
4425                        return;
4426                    }
4427                }
4428            }
4429
4430            // first set of colors, sorted by population
4431            iter(pq, fractByPopulations * maxcolors);
4432
4433            // Re-sort by the product of pixel occupancy times the size in color space.
4434            var pq2 = new PQueue(function(a, b) {
4435                return pv.naturalOrder(a.count() * a.volume(), b.count() * b.volume());
4436            });
4437            while (pq.size()) {
4438                pq2.push(pq.pop());
4439            }
4440
4441            // next set - generate the median cuts using the (npix * vol) sorting.
4442            iter(pq2, maxcolors - pq2.size());
4443
4444            // calculate the actual colors
4445            var cmap = new CMap();
4446            while (pq2.size()) {
4447                cmap.push(pq2.pop());
4448            }
4449
4450            return cmap;
4451        }
4452
4453        return {
4454            quantize: quantize
4455        };
4456    })();
4457
4458    /**
4459     * Export class to global
4460     */
4461    if (typeof define === 'function' && define.amd) {
4462        define([], function() {
4463            return ColorThief;
4464        }); // for AMD loader
4465    } else if (typeof exports === 'object') {
4466        module.exports = ColorThief; // for CommonJS
4467    } else {
4468        this.ColorThief = ColorThief;
4469    }
4470}.call(this));
4471
4472/**
4473 * Mega pixel image rendering library for iOS6 Safari
4474 *
4475 * Fixes iOS6 Safari's image file rendering issue for large size image (over mega-pixel),
4476 * which causes unexpected subsampling when drawing it in canvas.
4477 * By using this library, you can safely render the image with proper stretching.
4478 *
4479 * Copyright (c) 2012 Shinichi Tomita <[email protected]>
4480 * Released under the MIT license
4481 */
4482(function () {
4483
4484    /**
4485     * Detect subsampling in loaded image.
4486     * In iOS, larger images than 2M pixels may be subsampled in rendering.
4487     */
4488    function detectSubsampling(img) {
4489        var iw = img.naturalWidth, ih = img.naturalHeight;
4490        if (iw * ih > 1024 * 1024) { // subsampling may happen over megapixel image
4491            var canvas = document.createElement('canvas');
4492            canvas.width = canvas.height = 1;
4493            var ctx = canvas.getContext('2d');
4494            ctx.drawImage(img, -iw + 1, 0);
4495            // subsampled image becomes half smaller in rendering size.
4496            // check alpha channel value to confirm image is covering edge pixel or not.
4497            // if alpha value is 0 image is not covering, hence subsampled.
4498            return ctx.getImageData(0, 0, 1, 1).data[3] === 0;
4499        } else {
4500            return false;
4501        }
4502    }
4503
4504    /**
4505     * Detecting vertical squash in loaded image.
4506     * Fixes a bug which squash image vertically while drawing into canvas for some images.
4507     */
4508    function detectVerticalSquash(img, iw, ih) {
4509        var canvas = document.createElement('canvas');
4510        canvas.width = 1;
4511        canvas.height = ih;
4512        var ctx = canvas.getContext('2d');
4513        ctx.drawImage(img, 0, 0);
4514        var data = ctx.getImageData(0, 0, 1, ih).data;
4515        // search image edge pixel position in case it is squashed vertically.
4516        var sy = 0;
4517        var ey = ih;
4518        var py = ih;
4519        while (py > sy) {
4520            var alpha = data[(py - 1) * 4 + 3];
4521            if (alpha === 0) {
4522                ey = py;
4523            } else {
4524                sy = py;
4525            }
4526            py = (ey + sy) >> 1;
4527        }
4528        var ratio = (py / ih);
4529        return (ratio === 0) ? 1 : ratio;
4530    }
4531
4532    /**
4533     * Rendering image element (with resizing) and get its data URL
4534     */
4535    function renderImageToDataURL(img, options, doSquash) {
4536        var canvas = document.createElement('canvas');
4537        renderImageToCanvas(img, canvas, options, doSquash);
4538        return canvas.toDataURL('image/jpeg', options.quality || 0.8);
4539    }
4540
4541    /**
4542     * Rendering image element (with resizing) into the canvas element
4543     */
4544    function renderImageToCanvas(img, canvas, options, doSquash) {
4545        var iw = img.naturalWidth, ih = img.naturalHeight;
4546        if ((iw + ih) === false) {
4547            return;
4548        }
4549        var width = options.width, height = options.height;
4550        var ctx = canvas.getContext('2d');
4551        ctx.save();
4552        transformCoordinate(canvas, ctx, width, height, options.orientation);
4553        var subsampled = detectSubsampling(img);
4554        if (subsampled) {
4555            iw /= 2;
4556            ih /= 2;
4557        }
4558        var d = 1024; // size of tiling canvas
4559        var tmpCanvas = document.createElement('canvas');
4560        tmpCanvas.width = tmpCanvas.height = d;
4561        var tmpCtx = tmpCanvas.getContext('2d');
4562        var vertSquashRatio = doSquash ? detectVerticalSquash(img, iw, ih) : 1;
4563        var dw = Math.ceil(d * width / iw);
4564        var dh = Math.ceil(d * height / ih / vertSquashRatio);
4565        var sy = 0;
4566        var dy = 0;
4567        while (sy < ih) {
4568            var sx = 0;
4569            var dx = 0;
4570            while (sx < iw) {
4571                tmpCtx.clearRect(0, 0, d, d);
4572                tmpCtx.drawImage(img, -sx, -sy);
4573                ctx.drawImage(tmpCanvas, 0, 0, d, d, dx, dy, dw, dh);
4574                sx += d;
4575                dx += dw;
4576            }
4577            sy += d;
4578            dy += dh;
4579        }
4580        ctx.restore();
4581        tmpCanvas = tmpCtx = null;
4582    }
4583
4584    /**
4585     * Transform canvas coordination according to specified frame size and orientation
4586     * Orientation value is from EXIF tag
4587     */
4588    function transformCoordinate(canvas, ctx, width, height, orientation) {
4589        switch (orientation) {
4590            case 5:
4591            case 6:
4592            case 7:
4593            case 8:
4594                canvas.width = height;
4595                canvas.height = width;
4596                break;
4597            default:
4598                canvas.width = width;
4599                canvas.height = height;
4600        }
4601        switch (orientation) {
4602            case 2:
4603                // horizontal flip
4604                ctx.translate(width, 0);
4605                ctx.scale(-1, 1);
4606                break;
4607            case 3:
4608                // 180 rotate left
4609                ctx.translate(width, height);
4610                ctx.rotate(Math.PI);
4611                break;
4612            case 4:
4613                // vertical flip
4614                ctx.translate(0, height);
4615                ctx.scale(1, -1);
4616                break;
4617            case 5:
4618                // vertical flip + 90 rotate right
4619                ctx.rotate(0.5 * Math.PI);
4620                ctx.scale(1, -1);
4621                break;
4622            case 6:
4623                // 90 rotate right
4624                ctx.rotate(0.5 * Math.PI);
4625                ctx.translate(0, -height);
4626                break;
4627            case 7:
4628                // horizontal flip + 90 rotate right
4629                ctx.rotate(0.5 * Math.PI);
4630                ctx.translate(width, -height);
4631                ctx.scale(-1, 1);
4632                break;
4633            case 8:
4634                // 90 rotate left
4635                ctx.rotate(-0.5 * Math.PI);
4636                ctx.translate(-width, 0);
4637                break;
4638            default:
4639                break;
4640        }
4641    }
4642
4643    var URL = window.URL && window.URL.createObjectURL ? window.URL :
4644        window.webkitURL && window.webkitURL.createObjectURL ? window.webkitURL :
4645            null;
4646
4647    /**
4648     * MegaPixImage class
4649     */
4650    function MegaPixImage(srcImage) {
4651        if (window.Blob && srcImage instanceof Blob) {
4652            if (!URL) {
4653                throw Error('No createObjectURL function found to create blob url');
4654            }
4655            var img = new Image();
4656            img.src = URL.createObjectURL(srcImage);
4657            this.blob = srcImage;
4658            srcImage = img;
4659        }
4660        if (!srcImage.naturalWidth && !srcImage.naturalHeight) {
4661            var _this = this;
4662            srcImage.onload = srcImage.onerror = function () {
4663                var listeners = _this.imageLoadListeners;
4664                if (listeners) {
4665                    _this.imageLoadListeners = null;
4666                    for (var i = 0, len = listeners.length; i < len; i++) {
4667                        listeners[i]();
4668                    }
4669                }
4670            };
4671            this.imageLoadListeners = [];
4672        }
4673        this.srcImage = srcImage;
4674    }
4675
4676    /**
4677     * Rendering megapix image into specified target element
4678     */
4679    MegaPixImage.prototype.render = function (target, options, callback) {
4680        if (this.imageLoadListeners) {
4681            var _this = this;
4682            this.imageLoadListeners.push(function () {
4683                _this.render(target, options, callback);
4684            });
4685            return;
4686        }
4687        options = options || {};
4688        var imgWidth = this.srcImage.naturalWidth, imgHeight = this.srcImage.naturalHeight,
4689            width = options.width, height = options.height,
4690            maxWidth = options.maxWidth, maxHeight = options.maxHeight,
4691            doSquash = !this.blob || this.blob.type === 'image/jpeg';
4692        if (width && !height) {
4693            height = (imgHeight * width / imgWidth) << 0;
4694        } else if (height && !width) {
4695            width = (imgWidth * height / imgHeight) << 0;
4696        } else {
4697            width = imgWidth;
4698            height = imgHeight;
4699        }
4700        if (maxWidth && width > maxWidth) {
4701            width = maxWidth;
4702            height = (imgHeight * width / imgWidth) << 0;
4703        }
4704        if (maxHeight && height > maxHeight) {
4705            height = maxHeight;
4706            width = (imgWidth * height / imgHeight) << 0;
4707        }
4708        var opt = {width: width, height: height};
4709        for (var k in options) {
4710            opt[k] = options[k];
4711        }
4712
4713        var tagName = target.tagName.toLowerCase();
4714        if (tagName === 'img') {
4715            target.src = renderImageToDataURL(this.srcImage, opt, doSquash);
4716        } else if (tagName === 'canvas') {
4717            renderImageToCanvas(this.srcImage, target, opt, doSquash);
4718        }
4719        if (typeof this.onrender === 'function') {
4720            this.onrender(target);
4721        }
4722        if (callback) {
4723            callback();
4724        }
4725        if (this.blob) {
4726            this.blob = null;
4727            URL.revokeObjectURL(this.srcImage.src);
4728        }
4729    };
4730
4731    /**
4732     * Export class to global
4733     */
4734    if (typeof define === 'function' && define.amd) {
4735        define([], function () {
4736            return MegaPixImage;
4737        }); // for AMD loader
4738    } else if (typeof exports === 'object') {
4739        module.exports = MegaPixImage; // for CommonJS
4740    } else {
4741        this.MegaPixImage = MegaPixImage;
4742    }
4743
4744}.call(this));

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