GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

third-party/angularjs-modules-plugins/ng-file-upload-12.2.12/demo/src/main/webapp/js/ng-img-crop.js   F
last analyzed

Complexity

Total Complexity 272
Complexity/F 2.18

Size

Lines of Code 1870
Function Count 125

Duplication

Duplicated Lines 18
Ratio 0.96 %

Importance

Changes 0
Metric Value
eloc 1309
dl 18
loc 1870
rs 0.8
c 0
b 0
f 0
wmc 272
mnd 147
bc 147
fnc 125
bpm 1.176
cpm 2.176
noi 20

13 Functions

Rating   Name   Duplication   Size   Complexity  
F ng-img-crop.js ➔ drawScene 0 20 22
A ng-img-crop.js ➔ base64ToArrayBuffer 0 12 2
A ng-img-crop.js ➔ objectURLToBlob 0 11 3
A ng-img-crop.js ➔ getStringFromDB 0 7 2
F ng-img-crop.js ➔ readEXIFData 0 91 16
A ng-img-crop.js ➔ imageHasData 0 3 1
A ng-img-crop.js ➔ readTags 0 14 3
D ng-img-crop.js ➔ getImageData 0 48 13
C ng-img-crop.js ➔ findIPTCinJPEG 0 51 9
C ng-img-crop.js ➔ findEXIFinJPEG 0 39 10
F ng-img-crop.js ➔ addEvent 0 7 91
F ng-img-crop.js ➔ readTagValue 18 92 17
B ng-img-crop.js ➔ readIPTCData 0 33 6

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complexity

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like third-party/angularjs-modules-plugins/ng-file-upload-12.2.12/demo/src/main/webapp/js/ng-img-crop.js often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
/*!
2
 * ngImgCrop v0.3.2
3
 * https://github.com/alexk111/ngImgCrop
4
 *
5
 * Copyright (c) 2014 Alex Kaul
6
 * License: MIT
7
 *
8
 * Generated at Wednesday, December 3rd, 2014, 3:54:12 PM
9
 */
10
(function() {
11
'use strict';
12
13
var crop = angular.module('ngImgCrop', []);
14
15
crop.factory('cropAreaCircle', ['cropArea', function(CropArea) {
16
  var CropAreaCircle = function() {
17
    CropArea.apply(this, arguments);
18
19
    this._boxResizeBaseSize = 20;
20
    this._boxResizeNormalRatio = 0.9;
21
    this._boxResizeHoverRatio = 1.2;
22
    this._iconMoveNormalRatio = 0.9;
23
    this._iconMoveHoverRatio = 1.2;
24
25
    this._boxResizeNormalSize = this._boxResizeBaseSize*this._boxResizeNormalRatio;
26
    this._boxResizeHoverSize = this._boxResizeBaseSize*this._boxResizeHoverRatio;
27
28
    this._posDragStartX=0;
29
    this._posDragStartY=0;
30
    this._posResizeStartX=0;
31
    this._posResizeStartY=0;
32
    this._posResizeStartSize=0;
33
34
    this._boxResizeIsHover = false;
35
    this._areaIsHover = false;
36
    this._boxResizeIsDragging = false;
37
    this._areaIsDragging = false;
38
  };
39
40
  CropAreaCircle.prototype = new CropArea();
41
42
  CropAreaCircle.prototype._calcCirclePerimeterCoords=function(angleDegrees) {
43
    var hSize=this._size/2;
44
    var angleRadians=angleDegrees * (Math.PI / 180),
45
        circlePerimeterX=this._x + hSize * Math.cos(angleRadians),
46
        circlePerimeterY=this._y + hSize * Math.sin(angleRadians);
47
    return [circlePerimeterX, circlePerimeterY];
48
  };
49
50
  CropAreaCircle.prototype._calcResizeIconCenterCoords=function() {
51
    return this._calcCirclePerimeterCoords(-45);
52
  };
53
54
  CropAreaCircle.prototype._isCoordWithinArea=function(coord) {
55
    return Math.sqrt((coord[0]-this._x)*(coord[0]-this._x) + (coord[1]-this._y)*(coord[1]-this._y)) < this._size/2;
56
  };
57
  CropAreaCircle.prototype._isCoordWithinBoxResize=function(coord) {
58
    var resizeIconCenterCoords=this._calcResizeIconCenterCoords();
59
    var hSize=this._boxResizeHoverSize/2;
60
    return(coord[0] > resizeIconCenterCoords[0] - hSize && coord[0] < resizeIconCenterCoords[0] + hSize &&
61
           coord[1] > resizeIconCenterCoords[1] - hSize && coord[1] < resizeIconCenterCoords[1] + hSize);
62
  };
63
64
  CropAreaCircle.prototype._drawArea=function(ctx,centerCoords,size){
65
    ctx.arc(centerCoords[0],centerCoords[1],size/2,0,2*Math.PI);
66
  };
67
68
  CropAreaCircle.prototype.draw=function() {
69
    CropArea.prototype.draw.apply(this, arguments);
70
71
    // draw move icon
72
    this._cropCanvas.drawIconMove([this._x,this._y], this._areaIsHover?this._iconMoveHoverRatio:this._iconMoveNormalRatio);
73
74
    // draw resize cubes
75
    this._cropCanvas.drawIconResizeBoxNESW(this._calcResizeIconCenterCoords(), this._boxResizeBaseSize, this._boxResizeIsHover?this._boxResizeHoverRatio:this._boxResizeNormalRatio);
76
  };
77
78
  CropAreaCircle.prototype.processMouseMove=function(mouseCurX, mouseCurY) {
79
    var cursor='default';
80
    var res=false;
81
82
    this._boxResizeIsHover = false;
83
    this._areaIsHover = false;
84
85
    if (this._areaIsDragging) {
86
      this._x = mouseCurX - this._posDragStartX;
87
      this._y = mouseCurY - this._posDragStartY;
88
      this._areaIsHover = true;
89
      cursor='move';
90
      res=true;
91
      this._events.trigger('area-move');
92
    } else if (this._boxResizeIsDragging) {
93
        cursor = 'nesw-resize';
94
        var iFR, iFX, iFY;
95
        iFX = mouseCurX - this._posResizeStartX;
96
        iFY = this._posResizeStartY - mouseCurY;
97
        if(iFX>iFY) {
98
          iFR = this._posResizeStartSize + iFY*2;
99
        } else {
100
          iFR = this._posResizeStartSize + iFX*2;
101
        }
102
103
        this._size = Math.max(this._minSize, iFR);
104
        this._boxResizeIsHover = true;
105
        res=true;
106
        this._events.trigger('area-resize');
107
    } else if (this._isCoordWithinBoxResize([mouseCurX,mouseCurY])) {
108
        cursor = 'nesw-resize';
109
        this._areaIsHover = false;
110
        this._boxResizeIsHover = true;
111
        res=true;
112
    } else if(this._isCoordWithinArea([mouseCurX,mouseCurY])) {
113
        cursor = 'move';
114
        this._areaIsHover = true;
115
        res=true;
116
    }
117
118
    this._dontDragOutside();
119
    angular.element(this._ctx.canvas).css({'cursor': cursor});
120
121
    return res;
122
  };
123
124
  CropAreaCircle.prototype.processMouseDown=function(mouseDownX, mouseDownY) {
125
    if (this._isCoordWithinBoxResize([mouseDownX,mouseDownY])) {
126
      this._areaIsDragging = false;
127
      this._areaIsHover = false;
128
      this._boxResizeIsDragging = true;
129
      this._boxResizeIsHover = true;
130
      this._posResizeStartX=mouseDownX;
131
      this._posResizeStartY=mouseDownY;
132
      this._posResizeStartSize = this._size;
133
      this._events.trigger('area-resize-start');
134
    } else if (this._isCoordWithinArea([mouseDownX,mouseDownY])) {
135
      this._areaIsDragging = true;
136
      this._areaIsHover = true;
137
      this._boxResizeIsDragging = false;
138
      this._boxResizeIsHover = false;
139
      this._posDragStartX = mouseDownX - this._x;
140
      this._posDragStartY = mouseDownY - this._y;
141
      this._events.trigger('area-move-start');
142
    }
143
  };
144
145
  CropAreaCircle.prototype.processMouseUp=function(/*mouseUpX, mouseUpY*/) {
146
    if(this._areaIsDragging) {
147
      this._areaIsDragging = false;
148
      this._events.trigger('area-move-end');
149
    }
150
    if(this._boxResizeIsDragging) {
151
      this._boxResizeIsDragging = false;
152
      this._events.trigger('area-resize-end');
153
    }
154
    this._areaIsHover = false;
155
    this._boxResizeIsHover = false;
156
157
    this._posDragStartX = 0;
158
    this._posDragStartY = 0;
159
  };
160
161
  return CropAreaCircle;
162
}]);
163
164
165
166
crop.factory('cropAreaSquare', ['cropArea', function(CropArea) {
167
  var CropAreaSquare = function() {
168
    CropArea.apply(this, arguments);
169
170
    this._resizeCtrlBaseRadius = 10;
171
    this._resizeCtrlNormalRatio = 0.75;
172
    this._resizeCtrlHoverRatio = 1;
173
    this._iconMoveNormalRatio = 0.9;
174
    this._iconMoveHoverRatio = 1.2;
175
176
    this._resizeCtrlNormalRadius = this._resizeCtrlBaseRadius*this._resizeCtrlNormalRatio;
177
    this._resizeCtrlHoverRadius = this._resizeCtrlBaseRadius*this._resizeCtrlHoverRatio;
178
179
    this._posDragStartX=0;
180
    this._posDragStartY=0;
181
    this._posResizeStartX=0;
182
    this._posResizeStartY=0;
183
    this._posResizeStartSize=0;
184
185
    this._resizeCtrlIsHover = -1;
186
    this._areaIsHover = false;
187
    this._resizeCtrlIsDragging = -1;
188
    this._areaIsDragging = false;
189
  };
190
191
  CropAreaSquare.prototype = new CropArea();
192
193
  CropAreaSquare.prototype._calcSquareCorners=function() {
194
    var hSize=this._size/2;
195
    return [
196
      [this._x-hSize, this._y-hSize],
197
      [this._x+hSize, this._y-hSize],
198
      [this._x-hSize, this._y+hSize],
199
      [this._x+hSize, this._y+hSize]
200
    ];
201
  };
202
203
  CropAreaSquare.prototype._calcSquareDimensions=function() {
204
    var hSize=this._size/2;
205
    return {
206
      left: this._x-hSize,
207
      top: this._y-hSize,
208
      right: this._x+hSize,
209
      bottom: this._y+hSize
210
    };
211
  };
212
213
  CropAreaSquare.prototype._isCoordWithinArea=function(coord) {
214
    var squareDimensions=this._calcSquareDimensions();
215
    return (coord[0]>=squareDimensions.left&&coord[0]<=squareDimensions.right&&coord[1]>=squareDimensions.top&&coord[1]<=squareDimensions.bottom);
216
  };
217
218
  CropAreaSquare.prototype._isCoordWithinResizeCtrl=function(coord) {
219
    var resizeIconsCenterCoords=this._calcSquareCorners();
220
    var res=-1;
221
    for(var i=0,len=resizeIconsCenterCoords.length;i<len;i++) {
222
      var resizeIconCenterCoords=resizeIconsCenterCoords[i];
223
      if(coord[0] > resizeIconCenterCoords[0] - this._resizeCtrlHoverRadius && coord[0] < resizeIconCenterCoords[0] + this._resizeCtrlHoverRadius &&
224
         coord[1] > resizeIconCenterCoords[1] - this._resizeCtrlHoverRadius && coord[1] < resizeIconCenterCoords[1] + this._resizeCtrlHoverRadius) {
225
        res=i;
226
        break;
227
      }
228
    }
229
    return res;
230
  };
231
232
  CropAreaSquare.prototype._drawArea=function(ctx,centerCoords,size){
233
    var hSize=size/2;
234
    ctx.rect(centerCoords[0]-hSize,centerCoords[1]-hSize,size,size);
235
  };
236
237
  CropAreaSquare.prototype.draw=function() {
238
    CropArea.prototype.draw.apply(this, arguments);
239
240
    // draw move icon
241
    this._cropCanvas.drawIconMove([this._x,this._y], this._areaIsHover?this._iconMoveHoverRatio:this._iconMoveNormalRatio);
242
243
    // draw resize cubes
244
    var resizeIconsCenterCoords=this._calcSquareCorners();
245
    for(var i=0,len=resizeIconsCenterCoords.length;i<len;i++) {
246
      var resizeIconCenterCoords=resizeIconsCenterCoords[i];
247
      this._cropCanvas.drawIconResizeCircle(resizeIconCenterCoords, this._resizeCtrlBaseRadius, this._resizeCtrlIsHover===i?this._resizeCtrlHoverRatio:this._resizeCtrlNormalRatio);
248
    }
249
  };
250
251
  CropAreaSquare.prototype.processMouseMove=function(mouseCurX, mouseCurY) {
252
    var cursor='default';
253
    var res=false;
254
255
    this._resizeCtrlIsHover = -1;
256
    this._areaIsHover = false;
257
258
    if (this._areaIsDragging) {
259
      this._x = mouseCurX - this._posDragStartX;
260
      this._y = mouseCurY - this._posDragStartY;
261
      this._areaIsHover = true;
262
      cursor='move';
263
      res=true;
264
      this._events.trigger('area-move');
265
    } else if (this._resizeCtrlIsDragging>-1) {
266
      var xMulti, yMulti;
267
      switch(this._resizeCtrlIsDragging) {
268
        case 0: // Top Left
269
          xMulti=-1;
270
          yMulti=-1;
271
          cursor = 'nwse-resize';
272
          break;
273
        case 1: // Top Right
274
          xMulti=1;
275
          yMulti=-1;
276
          cursor = 'nesw-resize';
277
          break;
278
        case 2: // Bottom Left
279
          xMulti=-1;
280
          yMulti=1;
281
          cursor = 'nesw-resize';
282
          break;
283
        case 3: // Bottom Right
284
          xMulti=1;
285
          yMulti=1;
286
          cursor = 'nwse-resize';
287
          break;
288
      }
289
      var iFX = (mouseCurX - this._posResizeStartX)*xMulti;
290
      var iFY = (mouseCurY - this._posResizeStartY)*yMulti;
291
      var iFR;
292
      if(iFX>iFY) {
293
        iFR = this._posResizeStartSize + iFY;
294
      } else {
295
        iFR = this._posResizeStartSize + iFX;
296
      }
297
      var wasSize=this._size;
298
      this._size = Math.max(this._minSize, iFR);
299
      var posModifier=(this._size-wasSize)/2;
300
      this._x+=posModifier*xMulti;
301
      this._y+=posModifier*yMulti;
302
      this._resizeCtrlIsHover = this._resizeCtrlIsDragging;
303
      res=true;
304
      this._events.trigger('area-resize');
305
    } else {
306
      var hoveredResizeBox=this._isCoordWithinResizeCtrl([mouseCurX,mouseCurY]);
307
      if (hoveredResizeBox>-1) {
308
        switch(hoveredResizeBox) {
309
          case 0:
310
            cursor = 'nwse-resize';
311
            break;
312
          case 1:
313
            cursor = 'nesw-resize';
314
            break;
315
          case 2:
316
            cursor = 'nesw-resize';
317
            break;
318
          case 3:
319
            cursor = 'nwse-resize';
320
            break;
321
        }
322
        this._areaIsHover = false;
323
        this._resizeCtrlIsHover = hoveredResizeBox;
324
        res=true;
325
      } else if(this._isCoordWithinArea([mouseCurX,mouseCurY])) {
326
        cursor = 'move';
327
        this._areaIsHover = true;
328
        res=true;
329
      }
330
    }
331
332
    this._dontDragOutside();
333
    angular.element(this._ctx.canvas).css({'cursor': cursor});
334
335
    return res;
336
  };
337
338
  CropAreaSquare.prototype.processMouseDown=function(mouseDownX, mouseDownY) {
339
    var isWithinResizeCtrl=this._isCoordWithinResizeCtrl([mouseDownX,mouseDownY]);
340
    if (isWithinResizeCtrl>-1) {
341
      this._areaIsDragging = false;
342
      this._areaIsHover = false;
343
      this._resizeCtrlIsDragging = isWithinResizeCtrl;
344
      this._resizeCtrlIsHover = isWithinResizeCtrl;
345
      this._posResizeStartX=mouseDownX;
346
      this._posResizeStartY=mouseDownY;
347
      this._posResizeStartSize = this._size;
348
      this._events.trigger('area-resize-start');
349
    } else if (this._isCoordWithinArea([mouseDownX,mouseDownY])) {
350
      this._areaIsDragging = true;
351
      this._areaIsHover = true;
352
      this._resizeCtrlIsDragging = -1;
353
      this._resizeCtrlIsHover = -1;
354
      this._posDragStartX = mouseDownX - this._x;
355
      this._posDragStartY = mouseDownY - this._y;
356
      this._events.trigger('area-move-start');
357
    }
358
  };
359
360
  CropAreaSquare.prototype.processMouseUp=function(/*mouseUpX, mouseUpY*/) {
361
    if(this._areaIsDragging) {
362
      this._areaIsDragging = false;
363
      this._events.trigger('area-move-end');
364
    }
365
    if(this._resizeCtrlIsDragging>-1) {
366
      this._resizeCtrlIsDragging = -1;
367
      this._events.trigger('area-resize-end');
368
    }
369
    this._areaIsHover = false;
370
    this._resizeCtrlIsHover = -1;
371
372
    this._posDragStartX = 0;
373
    this._posDragStartY = 0;
374
  };
375
376
  return CropAreaSquare;
377
}]);
378
379
crop.factory('cropArea', ['cropCanvas', function(CropCanvas) {
380
  var CropArea = function(ctx, events) {
381
    this._ctx=ctx;
382
    this._events=events;
383
384
    this._minSize=80;
385
386
    this._cropCanvas=new CropCanvas(ctx);
387
388
    this._image=new Image();
389
    this._x = 0;
390
    this._y = 0;
391
    this._size = 200;
392
  };
393
394
  /* GETTERS/SETTERS */
395
396
  CropArea.prototype.getImage = function () {
397
    return this._image;
398
  };
399
  CropArea.prototype.setImage = function (image) {
400
    this._image = image;
401
  };
402
403
  CropArea.prototype.getX = function () {
404
    return this._x;
405
  };
406
  CropArea.prototype.setX = function (x) {
407
    this._x = x;
408
    this._dontDragOutside();
409
  };
410
411
  CropArea.prototype.getY = function () {
412
    return this._y;
413
  };
414
  CropArea.prototype.setY = function (y) {
415
    this._y = y;
416
    this._dontDragOutside();
417
  };
418
419
  CropArea.prototype.getSize = function () {
420
    return this._size;
421
  };
422
  CropArea.prototype.setSize = function (size) {
423
    this._size = Math.max(this._minSize, size);
424
    this._dontDragOutside();
425
  };
426
427
  CropArea.prototype.getMinSize = function () {
428
    return this._minSize;
429
  };
430
  CropArea.prototype.setMinSize = function (size) {
431
    this._minSize = size;
432
    this._size = Math.max(this._minSize, this._size);
433
    this._dontDragOutside();
434
  };
435
436
  /* FUNCTIONS */
437
  CropArea.prototype._dontDragOutside=function() {
438
    var h=this._ctx.canvas.height,
439
        w=this._ctx.canvas.width;
440
    if(this._size>w) { this._size=w; }
441
    if(this._size>h) { this._size=h; }
442
    if(this._x<this._size/2) { this._x=this._size/2; }
443
    if(this._x>w-this._size/2) { this._x=w-this._size/2; }
444
    if(this._y<this._size/2) { this._y=this._size/2; }
445
    if(this._y>h-this._size/2) { this._y=h-this._size/2; }
446
  };
447
448
  CropArea.prototype._drawArea=function() {};
449
450
  CropArea.prototype.draw=function() {
451
    // draw crop area
452
    this._cropCanvas.drawCropArea(this._image,[this._x,this._y],this._size,this._drawArea);
453
  };
454
455
  CropArea.prototype.processMouseMove=function() {};
456
457
  CropArea.prototype.processMouseDown=function() {};
458
459
  CropArea.prototype.processMouseUp=function() {};
460
461
  return CropArea;
462
}]);
463
464
crop.factory('cropCanvas', [function() {
465
  // Shape = Array of [x,y]; [0, 0] - center
466
  var shapeArrowNW=[[-0.5,-2],[-3,-4.5],[-0.5,-7],[-7,-7],[-7,-0.5],[-4.5,-3],[-2,-0.5]];
467
  var shapeArrowNE=[[0.5,-2],[3,-4.5],[0.5,-7],[7,-7],[7,-0.5],[4.5,-3],[2,-0.5]];
468
  var shapeArrowSW=[[-0.5,2],[-3,4.5],[-0.5,7],[-7,7],[-7,0.5],[-4.5,3],[-2,0.5]];
469
  var shapeArrowSE=[[0.5,2],[3,4.5],[0.5,7],[7,7],[7,0.5],[4.5,3],[2,0.5]];
470
  var shapeArrowN=[[-1.5,-2.5],[-1.5,-6],[-5,-6],[0,-11],[5,-6],[1.5,-6],[1.5,-2.5]];
471
  var shapeArrowW=[[-2.5,-1.5],[-6,-1.5],[-6,-5],[-11,0],[-6,5],[-6,1.5],[-2.5,1.5]];
472
  var shapeArrowS=[[-1.5,2.5],[-1.5,6],[-5,6],[0,11],[5,6],[1.5,6],[1.5,2.5]];
473
  var shapeArrowE=[[2.5,-1.5],[6,-1.5],[6,-5],[11,0],[6,5],[6,1.5],[2.5,1.5]];
474
475
  // Colors
476
  var colors={
477
    areaOutline: '#fff',
478
    resizeBoxStroke: '#fff',
479
    resizeBoxFill: '#444',
480
    resizeBoxArrowFill: '#fff',
481
    resizeCircleStroke: '#fff',
482
    resizeCircleFill: '#444',
483
    moveIconFill: '#fff'
484
  };
485
486
  return function(ctx){
487
488
    /* Base functions */
489
490
    // Calculate Point
491
    var calcPoint=function(point,offset,scale) {
492
        return [scale*point[0]+offset[0], scale*point[1]+offset[1]];
493
    };
494
495
    // Draw Filled Polygon
496
    var drawFilledPolygon=function(shape,fillStyle,centerCoords,scale) {
497
        ctx.save();
498
        ctx.fillStyle = fillStyle;
499
        ctx.beginPath();
500
        var pc, pc0=calcPoint(shape[0],centerCoords,scale);
501
        ctx.moveTo(pc0[0],pc0[1]);
502
503
        for(var p in shape) {
0 ignored issues
show
Complexity introduced by
A for in loop automatically includes the property of any prototype object, consider checking the key using hasOwnProperty.

When iterating over the keys of an object, this includes not only the keys of the object, but also keys contained in the prototype of that object. It is generally a best practice to check for these keys specifically:

var someObject;
for (var key in someObject) {
    if ( ! someObject.hasOwnProperty(key)) {
        continue; // Skip keys from the prototype.
    }

    doSomethingWith(key);
}
Loading history...
504
            if (p > 0) {
505
                pc=calcPoint(shape[p],centerCoords,scale);
506
                ctx.lineTo(pc[0],pc[1]);
507
            }
508
        }
509
510
        ctx.lineTo(pc0[0],pc0[1]);
511
        ctx.fill();
512
        ctx.closePath();
513
        ctx.restore();
514
    };
515
516
    /* Icons */
517
518
    this.drawIconMove=function(centerCoords, scale) {
519
      drawFilledPolygon(shapeArrowN, colors.moveIconFill, centerCoords, scale);
520
      drawFilledPolygon(shapeArrowW, colors.moveIconFill, centerCoords, scale);
521
      drawFilledPolygon(shapeArrowS, colors.moveIconFill, centerCoords, scale);
522
      drawFilledPolygon(shapeArrowE, colors.moveIconFill, centerCoords, scale);
523
    };
524
525
    this.drawIconResizeCircle=function(centerCoords, circleRadius, scale) {
526
      var scaledCircleRadius=circleRadius*scale;
527
      ctx.save();
528
      ctx.strokeStyle = colors.resizeCircleStroke;
529
      ctx.lineWidth = 2;
530
      ctx.fillStyle = colors.resizeCircleFill;
531
      ctx.beginPath();
532
      ctx.arc(centerCoords[0],centerCoords[1],scaledCircleRadius,0,2*Math.PI);
533
      ctx.fill();
534
      ctx.stroke();
535
      ctx.closePath();
536
      ctx.restore();
537
    };
538
539
    this.drawIconResizeBoxBase=function(centerCoords, boxSize, scale) {
540
      var scaledBoxSize=boxSize*scale;
541
      ctx.save();
542
      ctx.strokeStyle = colors.resizeBoxStroke;
543
      ctx.lineWidth = 2;
544
      ctx.fillStyle = colors.resizeBoxFill;
545
      ctx.fillRect(centerCoords[0] - scaledBoxSize/2, centerCoords[1] - scaledBoxSize/2, scaledBoxSize, scaledBoxSize);
546
      ctx.strokeRect(centerCoords[0] - scaledBoxSize/2, centerCoords[1] - scaledBoxSize/2, scaledBoxSize, scaledBoxSize);
547
      ctx.restore();
548
    };
549
    this.drawIconResizeBoxNESW=function(centerCoords, boxSize, scale) {
550
      this.drawIconResizeBoxBase(centerCoords, boxSize, scale);
551
      drawFilledPolygon(shapeArrowNE, colors.resizeBoxArrowFill, centerCoords, scale);
552
      drawFilledPolygon(shapeArrowSW, colors.resizeBoxArrowFill, centerCoords, scale);
553
    };
554
    this.drawIconResizeBoxNWSE=function(centerCoords, boxSize, scale) {
555
      this.drawIconResizeBoxBase(centerCoords, boxSize, scale);
556
      drawFilledPolygon(shapeArrowNW, colors.resizeBoxArrowFill, centerCoords, scale);
557
      drawFilledPolygon(shapeArrowSE, colors.resizeBoxArrowFill, centerCoords, scale);
558
    };
559
560
    /* Crop Area */
561
562
    this.drawCropArea=function(image, centerCoords, size, fnDrawClipPath) {
563
      var xRatio=image.width/ctx.canvas.width,
564
          yRatio=image.height/ctx.canvas.height,
565
          xLeft=centerCoords[0]-size/2,
566
          yTop=centerCoords[1]-size/2;
567
568
      ctx.save();
569
      ctx.strokeStyle = colors.areaOutline;
570
      ctx.lineWidth = 2;
571
      ctx.beginPath();
572
      fnDrawClipPath(ctx, centerCoords, size);
573
      ctx.stroke();
574
      ctx.clip();
575
576
      // draw part of original image
577
      if (size > 0) {
578
          ctx.drawImage(image, xLeft*xRatio, yTop*yRatio, size*xRatio, size*yRatio, xLeft, yTop, size, size);
579
      }
580
581
      ctx.beginPath();
582
      fnDrawClipPath(ctx, centerCoords, size);
583
      ctx.stroke();
584
      ctx.clip();
585
586
      ctx.restore();
587
    };
588
589
  };
590
}]);
591
592
/**
593
 * EXIF service is based on the exif-js library (https://github.com/jseidelin/exif-js)
594
 */
595
596
crop.service('cropEXIF', [function() {
597
  var debug = false;
598
599
  var ExifTags = this.Tags = {
600
601
      // version tags
602
      0x9000 : "ExifVersion",             // EXIF version
603
      0xA000 : "FlashpixVersion",         // Flashpix format version
604
605
      // colorspace tags
606
      0xA001 : "ColorSpace",              // Color space information tag
607
608
      // image configuration
609
      0xA002 : "PixelXDimension",         // Valid width of meaningful image
610
      0xA003 : "PixelYDimension",         // Valid height of meaningful image
611
      0x9101 : "ComponentsConfiguration", // Information about channels
612
      0x9102 : "CompressedBitsPerPixel",  // Compressed bits per pixel
613
614
      // user information
615
      0x927C : "MakerNote",               // Any desired information written by the manufacturer
616
      0x9286 : "UserComment",             // Comments by user
617
618
      // related file
619
      0xA004 : "RelatedSoundFile",        // Name of related sound file
620
621
      // date and time
622
      0x9003 : "DateTimeOriginal",        // Date and time when the original image was generated
623
      0x9004 : "DateTimeDigitized",       // Date and time when the image was stored digitally
624
      0x9290 : "SubsecTime",              // Fractions of seconds for DateTime
625
      0x9291 : "SubsecTimeOriginal",      // Fractions of seconds for DateTimeOriginal
626
      0x9292 : "SubsecTimeDigitized",     // Fractions of seconds for DateTimeDigitized
627
628
      // picture-taking conditions
629
      0x829A : "ExposureTime",            // Exposure time (in seconds)
630
      0x829D : "FNumber",                 // F number
631
      0x8822 : "ExposureProgram",         // Exposure program
632
      0x8824 : "SpectralSensitivity",     // Spectral sensitivity
633
      0x8827 : "ISOSpeedRatings",         // ISO speed rating
634
      0x8828 : "OECF",                    // Optoelectric conversion factor
635
      0x9201 : "ShutterSpeedValue",       // Shutter speed
636
      0x9202 : "ApertureValue",           // Lens aperture
637
      0x9203 : "BrightnessValue",         // Value of brightness
638
      0x9204 : "ExposureBias",            // Exposure bias
639
      0x9205 : "MaxApertureValue",        // Smallest F number of lens
640
      0x9206 : "SubjectDistance",         // Distance to subject in meters
641
      0x9207 : "MeteringMode",            // Metering mode
642
      0x9208 : "LightSource",             // Kind of light source
643
      0x9209 : "Flash",                   // Flash status
644
      0x9214 : "SubjectArea",             // Location and area of main subject
645
      0x920A : "FocalLength",             // Focal length of the lens in mm
646
      0xA20B : "FlashEnergy",             // Strobe energy in BCPS
647
      0xA20C : "SpatialFrequencyResponse",    //
648
      0xA20E : "FocalPlaneXResolution",   // Number of pixels in width direction per FocalPlaneResolutionUnit
649
      0xA20F : "FocalPlaneYResolution",   // Number of pixels in height direction per FocalPlaneResolutionUnit
650
      0xA210 : "FocalPlaneResolutionUnit",    // Unit for measuring FocalPlaneXResolution and FocalPlaneYResolution
651
      0xA214 : "SubjectLocation",         // Location of subject in image
652
      0xA215 : "ExposureIndex",           // Exposure index selected on camera
653
      0xA217 : "SensingMethod",           // Image sensor type
654
      0xA300 : "FileSource",              // Image source (3 == DSC)
655
      0xA301 : "SceneType",               // Scene type (1 == directly photographed)
656
      0xA302 : "CFAPattern",              // Color filter array geometric pattern
657
      0xA401 : "CustomRendered",          // Special processing
658
      0xA402 : "ExposureMode",            // Exposure mode
659
      0xA403 : "WhiteBalance",            // 1 = auto white balance, 2 = manual
660
      0xA404 : "DigitalZoomRation",       // Digital zoom ratio
661
      0xA405 : "FocalLengthIn35mmFilm",   // Equivalent foacl length assuming 35mm film camera (in mm)
662
      0xA406 : "SceneCaptureType",        // Type of scene
663
      0xA407 : "GainControl",             // Degree of overall image gain adjustment
664
      0xA408 : "Contrast",                // Direction of contrast processing applied by camera
665
      0xA409 : "Saturation",              // Direction of saturation processing applied by camera
666
      0xA40A : "Sharpness",               // Direction of sharpness processing applied by camera
667
      0xA40B : "DeviceSettingDescription",    //
668
      0xA40C : "SubjectDistanceRange",    // Distance to subject
669
670
      // other tags
671
      0xA005 : "InteroperabilityIFDPointer",
672
      0xA420 : "ImageUniqueID"            // Identifier assigned uniquely to each image
673
  };
674
675
  var TiffTags = this.TiffTags = {
676
      0x0100 : "ImageWidth",
677
      0x0101 : "ImageHeight",
678
      0x8769 : "ExifIFDPointer",
679
      0x8825 : "GPSInfoIFDPointer",
680
      0xA005 : "InteroperabilityIFDPointer",
681
      0x0102 : "BitsPerSample",
682
      0x0103 : "Compression",
683
      0x0106 : "PhotometricInterpretation",
684
      0x0112 : "Orientation",
685
      0x0115 : "SamplesPerPixel",
686
      0x011C : "PlanarConfiguration",
687
      0x0212 : "YCbCrSubSampling",
688
      0x0213 : "YCbCrPositioning",
689
      0x011A : "XResolution",
690
      0x011B : "YResolution",
691
      0x0128 : "ResolutionUnit",
692
      0x0111 : "StripOffsets",
693
      0x0116 : "RowsPerStrip",
694
      0x0117 : "StripByteCounts",
695
      0x0201 : "JPEGInterchangeFormat",
696
      0x0202 : "JPEGInterchangeFormatLength",
697
      0x012D : "TransferFunction",
698
      0x013E : "WhitePoint",
699
      0x013F : "PrimaryChromaticities",
700
      0x0211 : "YCbCrCoefficients",
701
      0x0214 : "ReferenceBlackWhite",
702
      0x0132 : "DateTime",
703
      0x010E : "ImageDescription",
704
      0x010F : "Make",
705
      0x0110 : "Model",
706
      0x0131 : "Software",
707
      0x013B : "Artist",
708
      0x8298 : "Copyright"
709
  };
710
711
  var GPSTags = this.GPSTags = {
712
      0x0000 : "GPSVersionID",
713
      0x0001 : "GPSLatitudeRef",
714
      0x0002 : "GPSLatitude",
715
      0x0003 : "GPSLongitudeRef",
716
      0x0004 : "GPSLongitude",
717
      0x0005 : "GPSAltitudeRef",
718
      0x0006 : "GPSAltitude",
719
      0x0007 : "GPSTimeStamp",
720
      0x0008 : "GPSSatellites",
721
      0x0009 : "GPSStatus",
722
      0x000A : "GPSMeasureMode",
723
      0x000B : "GPSDOP",
724
      0x000C : "GPSSpeedRef",
725
      0x000D : "GPSSpeed",
726
      0x000E : "GPSTrackRef",
727
      0x000F : "GPSTrack",
728
      0x0010 : "GPSImgDirectionRef",
729
      0x0011 : "GPSImgDirection",
730
      0x0012 : "GPSMapDatum",
731
      0x0013 : "GPSDestLatitudeRef",
732
      0x0014 : "GPSDestLatitude",
733
      0x0015 : "GPSDestLongitudeRef",
734
      0x0016 : "GPSDestLongitude",
735
      0x0017 : "GPSDestBearingRef",
736
      0x0018 : "GPSDestBearing",
737
      0x0019 : "GPSDestDistanceRef",
738
      0x001A : "GPSDestDistance",
739
      0x001B : "GPSProcessingMethod",
740
      0x001C : "GPSAreaInformation",
741
      0x001D : "GPSDateStamp",
742
      0x001E : "GPSDifferential"
743
  };
744
745
  var StringValues = this.StringValues = {
746
      ExposureProgram : {
747
          0 : "Not defined",
748
          1 : "Manual",
749
          2 : "Normal program",
750
          3 : "Aperture priority",
751
          4 : "Shutter priority",
752
          5 : "Creative program",
753
          6 : "Action program",
754
          7 : "Portrait mode",
755
          8 : "Landscape mode"
756
      },
757
      MeteringMode : {
758
          0 : "Unknown",
759
          1 : "Average",
760
          2 : "CenterWeightedAverage",
761
          3 : "Spot",
762
          4 : "MultiSpot",
763
          5 : "Pattern",
764
          6 : "Partial",
765
          255 : "Other"
766
      },
767
      LightSource : {
768
          0 : "Unknown",
769
          1 : "Daylight",
770
          2 : "Fluorescent",
771
          3 : "Tungsten (incandescent light)",
772
          4 : "Flash",
773
          9 : "Fine weather",
774
          10 : "Cloudy weather",
775
          11 : "Shade",
776
          12 : "Daylight fluorescent (D 5700 - 7100K)",
777
          13 : "Day white fluorescent (N 4600 - 5400K)",
778
          14 : "Cool white fluorescent (W 3900 - 4500K)",
779
          15 : "White fluorescent (WW 3200 - 3700K)",
780
          17 : "Standard light A",
781
          18 : "Standard light B",
782
          19 : "Standard light C",
783
          20 : "D55",
784
          21 : "D65",
785
          22 : "D75",
786
          23 : "D50",
787
          24 : "ISO studio tungsten",
788
          255 : "Other"
789
      },
790
      Flash : {
791
          0x0000 : "Flash did not fire",
792
          0x0001 : "Flash fired",
793
          0x0005 : "Strobe return light not detected",
794
          0x0007 : "Strobe return light detected",
795
          0x0009 : "Flash fired, compulsory flash mode",
796
          0x000D : "Flash fired, compulsory flash mode, return light not detected",
797
          0x000F : "Flash fired, compulsory flash mode, return light detected",
798
          0x0010 : "Flash did not fire, compulsory flash mode",
799
          0x0018 : "Flash did not fire, auto mode",
800
          0x0019 : "Flash fired, auto mode",
801
          0x001D : "Flash fired, auto mode, return light not detected",
802
          0x001F : "Flash fired, auto mode, return light detected",
803
          0x0020 : "No flash function",
804
          0x0041 : "Flash fired, red-eye reduction mode",
805
          0x0045 : "Flash fired, red-eye reduction mode, return light not detected",
806
          0x0047 : "Flash fired, red-eye reduction mode, return light detected",
807
          0x0049 : "Flash fired, compulsory flash mode, red-eye reduction mode",
808
          0x004D : "Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected",
809
          0x004F : "Flash fired, compulsory flash mode, red-eye reduction mode, return light detected",
810
          0x0059 : "Flash fired, auto mode, red-eye reduction mode",
811
          0x005D : "Flash fired, auto mode, return light not detected, red-eye reduction mode",
812
          0x005F : "Flash fired, auto mode, return light detected, red-eye reduction mode"
813
      },
814
      SensingMethod : {
815
          1 : "Not defined",
816
          2 : "One-chip color area sensor",
817
          3 : "Two-chip color area sensor",
818
          4 : "Three-chip color area sensor",
819
          5 : "Color sequential area sensor",
820
          7 : "Trilinear sensor",
821
          8 : "Color sequential linear sensor"
822
      },
823
      SceneCaptureType : {
824
          0 : "Standard",
825
          1 : "Landscape",
826
          2 : "Portrait",
827
          3 : "Night scene"
828
      },
829
      SceneType : {
830
          1 : "Directly photographed"
831
      },
832
      CustomRendered : {
833
          0 : "Normal process",
834
          1 : "Custom process"
835
      },
836
      WhiteBalance : {
837
          0 : "Auto white balance",
838
          1 : "Manual white balance"
839
      },
840
      GainControl : {
841
          0 : "None",
842
          1 : "Low gain up",
843
          2 : "High gain up",
844
          3 : "Low gain down",
845
          4 : "High gain down"
846
      },
847
      Contrast : {
848
          0 : "Normal",
849
          1 : "Soft",
850
          2 : "Hard"
851
      },
852
      Saturation : {
853
          0 : "Normal",
854
          1 : "Low saturation",
855
          2 : "High saturation"
856
      },
857
      Sharpness : {
858
          0 : "Normal",
859
          1 : "Soft",
860
          2 : "Hard"
861
      },
862
      SubjectDistanceRange : {
863
          0 : "Unknown",
864
          1 : "Macro",
865
          2 : "Close view",
866
          3 : "Distant view"
867
      },
868
      FileSource : {
869
          3 : "DSC"
870
      },
871
872
      Components : {
873
          0 : "",
874
          1 : "Y",
875
          2 : "Cb",
876
          3 : "Cr",
877
          4 : "R",
878
          5 : "G",
879
          6 : "B"
880
      }
881
  };
882
883
  function addEvent(element, event, handler) {
0 ignored issues
show
introduced by
The function addEvent does not seem to be used and can be removed.
Loading history...
884
      if (element.addEventListener) {
885
          element.addEventListener(event, handler, false);
886
      } else if (element.attachEvent) {
887
          element.attachEvent("on" + event, handler);
888
      }
889
  }
890
891
  function imageHasData(img) {
892
      return !!(img.exifdata);
893
  }
894
895
  function base64ToArrayBuffer(base64, contentType) {
896
      contentType = contentType || base64.match(/^data\:([^\;]+)\;base64,/mi)[1] || ''; // e.g. 'data:image/jpeg;base64,...' => 'image/jpeg'
897
      base64 = base64.replace(/^data\:([^\;]+)\;base64,/gmi, '');
898
      var binary = atob(base64);
899
      var len = binary.length;
900
      var buffer = new ArrayBuffer(len);
901
      var view = new Uint8Array(buffer);
902
      for (var i = 0; i < len; i++) {
903
          view[i] = binary.charCodeAt(i);
904
      }
905
      return buffer;
906
  }
907
908
  function objectURLToBlob(url, callback) {
909
      var http = new XMLHttpRequest();
910
      http.open("GET", url, true);
911
      http.responseType = "blob";
912
      http.onload = function(e) {
913
          if (this.status == 200 || this.status === 0) {
914
              callback(this.response);
915
          }
916
      };
917
      http.send();
918
  }
919
920
  function getImageData(img, callback) {
921
      function handleBinaryFile(binFile) {
922
          var data = findEXIFinJPEG(binFile);
923
          var iptcdata = findIPTCinJPEG(binFile);
924
          img.exifdata = data || {};
925
          img.iptcdata = iptcdata || {};
926
          if (callback) {
927
              callback.call(img);
928
          }
929
      }
930
931
      if (img.src) {
932
          if (/^data\:/i.test(img.src)) { // Data URI
933
              var arrayBuffer = base64ToArrayBuffer(img.src);
934
              handleBinaryFile(arrayBuffer);
935
936
          } else if (/^blob\:/i.test(img.src)) { // Object URL
937
              var fileReader = new FileReader();
938
              fileReader.onload = function(e) {
939
                  handleBinaryFile(e.target.result);
940
              };
941
              objectURLToBlob(img.src, function (blob) {
942
                  fileReader.readAsArrayBuffer(blob);
943
              });
944
          } else {
945
              var http = new XMLHttpRequest();
946
              http.onload = function() {
947
                  if (this.status == 200 || this.status === 0) {
948
                      handleBinaryFile(http.response);
949
                  } else {
950
                      throw "Could not load image";
951
                  }
952
                  http = null;
953
              };
954
              http.open("GET", img.src, true);
955
              http.responseType = "arraybuffer";
956
              http.send(null);
957
          }
958
      } else if (window.FileReader && (img instanceof window.Blob || img instanceof window.File)) {
959
          var fileReader = new FileReader();
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable fileReader already seems to be declared on line 937. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
960
          fileReader.onload = function(e) {
961
              if (debug) console.log("Got file of length " + e.target.result.byteLength);
0 ignored issues
show
Debugging Code introduced by
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
962
              handleBinaryFile(e.target.result);
963
          };
964
965
          fileReader.readAsArrayBuffer(img);
966
      }
967
  }
968
969
  function findEXIFinJPEG(file) {
970
      var dataView = new DataView(file);
971
972
      if (debug) console.log("Got file of length " + file.byteLength);
0 ignored issues
show
Debugging Code introduced by
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
973
      if ((dataView.getUint8(0) != 0xFF) || (dataView.getUint8(1) != 0xD8)) {
974
          if (debug) console.log("Not a valid JPEG");
975
          return false; // not a valid jpeg
976
      }
977
978
      var offset = 2,
979
          length = file.byteLength,
980
          marker;
981
982
      while (offset < length) {
983
          if (dataView.getUint8(offset) != 0xFF) {
984
              if (debug) console.log("Not a valid marker at offset " + offset + ", found: " + dataView.getUint8(offset));
985
              return false; // not a valid marker, something is wrong
986
          }
987
988
          marker = dataView.getUint8(offset + 1);
989
          if (debug) console.log(marker);
990
991
          // we could implement handling for other markers here,
992
          // but we're only looking for 0xFFE1 for EXIF data
993
994
          if (marker == 225) {
995
              if (debug) console.log("Found 0xFFE1 marker");
996
997
              return readEXIFData(dataView, offset + 4, dataView.getUint16(offset + 2) - 2);
0 ignored issues
show
Bug introduced by
The call to readEXIFData seems to have too many arguments starting with dataView.getUint16(offset + 2) - 2.
Loading history...
998
999
              // offset += 2 + file.getShortAt(offset+2, true);
1000
1001
          } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
1002
              offset += 2 + dataView.getUint16(offset+2);
1003
          }
1004
1005
      }
1006
1007
  }
1008
1009
  function findIPTCinJPEG(file) {
1010
      var dataView = new DataView(file);
1011
1012
      if (debug) console.log("Got file of length " + file.byteLength);
0 ignored issues
show
Debugging Code introduced by
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
1013
      if ((dataView.getUint8(0) != 0xFF) || (dataView.getUint8(1) != 0xD8)) {
1014
          if (debug) console.log("Not a valid JPEG");
1015
          return false; // not a valid jpeg
1016
      }
1017
1018
      var offset = 2,
1019
          length = file.byteLength;
1020
1021
      var isFieldSegmentStart = function(dataView, offset){
1022
          return (
1023
              dataView.getUint8(offset) === 0x38 &&
1024
              dataView.getUint8(offset+1) === 0x42 &&
1025
              dataView.getUint8(offset+2) === 0x49 &&
1026
              dataView.getUint8(offset+3) === 0x4D &&
1027
              dataView.getUint8(offset+4) === 0x04 &&
1028
              dataView.getUint8(offset+5) === 0x04
1029
          );
1030
      };
1031
1032
      while (offset < length) {
1033
1034
          if ( isFieldSegmentStart(dataView, offset )){
1035
1036
              // Get the length of the name header (which is padded to an even number of bytes)
1037
              var nameHeaderLength = dataView.getUint8(offset+7);
1038
              if(nameHeaderLength % 2 !== 0) nameHeaderLength += 1;
1039
              // Check for pre photoshop 6 format
1040
              if(nameHeaderLength === 0) {
1041
                  // Always 4
1042
                  nameHeaderLength = 4;
1043
              }
1044
1045
              var startOffset = offset + 8 + nameHeaderLength;
1046
              var sectionLength = dataView.getUint16(offset + 6 + nameHeaderLength);
1047
1048
              return readIPTCData(file, startOffset, sectionLength);
1049
1050
              break;
0 ignored issues
show
Unused Code introduced by
This break statement is unnecessary and may be removed.
Loading history...
1051
1052
          }
1053
1054
          // Not the marker, continue searching
1055
          offset++;
1056
1057
      }
1058
1059
  }
1060
  var IptcFieldMap = {
1061
      0x78 : 'caption',
1062
      0x6E : 'credit',
1063
      0x19 : 'keywords',
1064
      0x37 : 'dateCreated',
1065
      0x50 : 'byline',
1066
      0x55 : 'bylineTitle',
1067
      0x7A : 'captionWriter',
1068
      0x69 : 'headline',
1069
      0x74 : 'copyright',
1070
      0x0F : 'category'
1071
  };
1072
  function readIPTCData(file, startOffset, sectionLength){
1073
      var dataView = new DataView(file);
1074
      var data = {};
1075
      var fieldValue, fieldName, dataSize, segmentType, segmentSize;
1076
      var segmentStartPos = startOffset;
1077
      while(segmentStartPos < startOffset+sectionLength) {
1078
          if(dataView.getUint8(segmentStartPos) === 0x1C && dataView.getUint8(segmentStartPos+1) === 0x02){
1079
              segmentType = dataView.getUint8(segmentStartPos+2);
1080
              if(segmentType in IptcFieldMap) {
1081
                  dataSize = dataView.getInt16(segmentStartPos+3);
1082
                  segmentSize = dataSize + 5;
0 ignored issues
show
Unused Code introduced by
The variable segmentSize seems to be never used. Consider removing it.
Loading history...
1083
                  fieldName = IptcFieldMap[segmentType];
1084
                  fieldValue = getStringFromDB(dataView, segmentStartPos+5, dataSize);
1085
                  // Check if we already stored a value with this name
1086
                  if(data.hasOwnProperty(fieldName)) {
1087
                      // Value already stored with this name, create multivalue field
1088
                      if(data[fieldName] instanceof Array) {
1089
                          data[fieldName].push(fieldValue);
1090
                      }
1091
                      else {
1092
                          data[fieldName] = [data[fieldName], fieldValue];
1093
                      }
1094
                  }
1095
                  else {
1096
                      data[fieldName] = fieldValue;
1097
                  }
1098
              }
1099
1100
          }
1101
          segmentStartPos++;
1102
      }
1103
      return data;
1104
  }
1105
1106
  function readTags(file, tiffStart, dirStart, strings, bigEnd) {
1107
      var entries = file.getUint16(dirStart, !bigEnd),
1108
          tags = {},
1109
          entryOffset, tag,
1110
          i;
1111
1112
      for (i=0;i<entries;i++) {
1113
          entryOffset = dirStart + i*12 + 2;
1114
          tag = strings[file.getUint16(entryOffset, !bigEnd)];
1115
          if (!tag && debug) console.log("Unknown tag: " + file.getUint16(entryOffset, !bigEnd));
0 ignored issues
show
Debugging Code introduced by
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
1116
          tags[tag] = readTagValue(file, entryOffset, tiffStart, dirStart, bigEnd);
1117
      }
1118
      return tags;
1119
  }
1120
1121
  function readTagValue(file, entryOffset, tiffStart, dirStart, bigEnd) {
1122
      var type = file.getUint16(entryOffset+2, !bigEnd),
1123
          numValues = file.getUint32(entryOffset+4, !bigEnd),
1124
          valueOffset = file.getUint32(entryOffset+8, !bigEnd) + tiffStart,
1125
          offset,
1126
          vals, val, n,
1127
          numerator, denominator;
1128
1129
      switch (type) {
1130
          case 1: // byte, 8-bit unsigned int
1131
          case 7: // undefined, 8-bit byte, value depending on field
1132
              if (numValues == 1) {
1133
                  return file.getUint8(entryOffset + 8, !bigEnd);
1134
              } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
1135
                  offset = numValues > 4 ? valueOffset : (entryOffset + 8);
1136
                  vals = [];
1137
                  for (n=0;n<numValues;n++) {
1138
                      vals[n] = file.getUint8(offset + n);
1139
                  }
1140
                  return vals;
1141
              }
1142
1143
          case 2: // ascii, 8-bit byte
1144
              offset = numValues > 4 ? valueOffset : (entryOffset + 8);
1145
              return getStringFromDB(file, offset, numValues-1);
1146
1147
          case 3: // short, 16 bit int
1148
              if (numValues == 1) {
1149
                  return file.getUint16(entryOffset + 8, !bigEnd);
1150
              } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
1151
                  offset = numValues > 2 ? valueOffset : (entryOffset + 8);
1152
                  vals = [];
1153
                  for (n=0;n<numValues;n++) {
1154
                      vals[n] = file.getUint16(offset + 2*n, !bigEnd);
1155
                  }
1156
                  return vals;
1157
              }
1158
1159
          case 4: // long, 32 bit int
1160
              if (numValues == 1) {
1161
                  return file.getUint32(entryOffset + 8, !bigEnd);
1162
              } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
1163
                  vals = [];
1164
                  for (n=0;n<numValues;n++) {
1165
                      vals[n] = file.getUint32(valueOffset + 4*n, !bigEnd);
1166
                  }
1167
                  return vals;
1168
              }
1169
1170
          case 5:    // rational = two long values, first is numerator, second is denominator
1171
              if (numValues == 1) {
1172
                  numerator = file.getUint32(valueOffset, !bigEnd);
1173
                  denominator = file.getUint32(valueOffset+4, !bigEnd);
1174
                  val = new Number(numerator / denominator);
1175
                  val.numerator = numerator;
1176
                  val.denominator = denominator;
1177
                  return val;
1178
              } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
1179
                  vals = [];
1180
                  for (n=0;n<numValues;n++) {
1181
                      numerator = file.getUint32(valueOffset + 8*n, !bigEnd);
1182
                      denominator = file.getUint32(valueOffset+4 + 8*n, !bigEnd);
1183
                      vals[n] = new Number(numerator / denominator);
1184
                      vals[n].numerator = numerator;
1185
                      vals[n].denominator = denominator;
1186
                  }
1187
                  return vals;
1188
              }
1189
1190
          case 9: // slong, 32 bit signed int
1191 View Code Duplication
              if (numValues == 1) {
1192
                  return file.getInt32(entryOffset + 8, !bigEnd);
1193
              } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
1194
                  vals = [];
1195
                  for (n=0;n<numValues;n++) {
1196
                      vals[n] = file.getInt32(valueOffset + 4*n, !bigEnd);
1197
                  }
1198
                  return vals;
1199
              }
1200
1201
          case 10: // signed rational, two slongs, first is numerator, second is denominator
1202 View Code Duplication
              if (numValues == 1) {
1203
                  return file.getInt32(valueOffset, !bigEnd) / file.getInt32(valueOffset+4, !bigEnd);
1204
              } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
1205
                  vals = [];
1206
                  for (n=0;n<numValues;n++) {
1207
                      vals[n] = file.getInt32(valueOffset + 8*n, !bigEnd) / file.getInt32(valueOffset+4 + 8*n, !bigEnd);
1208
                  }
1209
                  return vals;
1210
              }
1211
      }
0 ignored issues
show
Comprehensibility introduced by
There is no default case in this switch, so nothing gets returned when all cases fail. You might want to consider adding a default or return undefined explicitly.
Loading history...
1212
  }
1213
1214
  function getStringFromDB(buffer, start, length) {
1215
      var outstr = "";
1216
      for (var n = start; n < start+length; n++) {
1217
          outstr += String.fromCharCode(buffer.getUint8(n));
1218
      }
1219
      return outstr;
1220
  }
1221
1222
  function readEXIFData(file, start) {
1223
      if (getStringFromDB(file, start, 4) != "Exif") {
1224
          if (debug) console.log("Not valid EXIF data! " + getStringFromDB(file, start, 4));
0 ignored issues
show
Debugging Code introduced by
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
1225
          return false;
1226
      }
1227
1228
      var bigEnd,
1229
          tags, tag,
1230
          exifData, gpsData,
1231
          tiffOffset = start + 6;
1232
1233
      // test for TIFF validity and endianness
1234
      if (file.getUint16(tiffOffset) == 0x4949) {
1235
          bigEnd = false;
1236
      } else if (file.getUint16(tiffOffset) == 0x4D4D) {
1237
          bigEnd = true;
1238
      } else {
1239
          if (debug) console.log("Not valid TIFF data! (no 0x4949 or 0x4D4D)");
1240
          return false;
1241
      }
1242
1243
      if (file.getUint16(tiffOffset+2, !bigEnd) != 0x002A) {
1244
          if (debug) console.log("Not valid TIFF data! (no 0x002A)");
1245
          return false;
1246
      }
1247
1248
      var firstIFDOffset = file.getUint32(tiffOffset+4, !bigEnd);
1249
1250
      if (firstIFDOffset < 0x00000008) {
1251
          if (debug) console.log("Not valid TIFF data! (First offset less than 8)", file.getUint32(tiffOffset+4, !bigEnd));
1252
          return false;
1253
      }
1254
1255
      tags = readTags(file, tiffOffset, tiffOffset + firstIFDOffset, TiffTags, bigEnd);
1256
1257
      if (tags.ExifIFDPointer) {
1258
          exifData = readTags(file, tiffOffset, tiffOffset + tags.ExifIFDPointer, ExifTags, bigEnd);
1259
          for (tag in exifData) {
1260
              switch (tag) {
1261
                  case "LightSource" :
1262
                  case "Flash" :
1263
                  case "MeteringMode" :
1264
                  case "ExposureProgram" :
1265
                  case "SensingMethod" :
1266
                  case "SceneCaptureType" :
1267
                  case "SceneType" :
1268
                  case "CustomRendered" :
1269
                  case "WhiteBalance" :
1270
                  case "GainControl" :
1271
                  case "Contrast" :
1272
                  case "Saturation" :
1273
                  case "Sharpness" :
1274
                  case "SubjectDistanceRange" :
1275
                  case "FileSource" :
1276
                      exifData[tag] = StringValues[tag][exifData[tag]];
1277
                      break;
1278
1279
                  case "ExifVersion" :
1280
                  case "FlashpixVersion" :
1281
                      exifData[tag] = String.fromCharCode(exifData[tag][0], exifData[tag][1], exifData[tag][2], exifData[tag][3]);
1282
                      break;
1283
1284
                  case "ComponentsConfiguration" :
1285
                      exifData[tag] =
1286
                          StringValues.Components[exifData[tag][0]] +
1287
                          StringValues.Components[exifData[tag][1]] +
1288
                          StringValues.Components[exifData[tag][2]] +
1289
                          StringValues.Components[exifData[tag][3]];
1290
                      break;
1291
              }
1292
              tags[tag] = exifData[tag];
1293
          }
1294
      }
1295
1296
      if (tags.GPSInfoIFDPointer) {
1297
          gpsData = readTags(file, tiffOffset, tiffOffset + tags.GPSInfoIFDPointer, GPSTags, bigEnd);
1298
          for (tag in gpsData) {
1299
              switch (tag) {
1300
                  case "GPSVersionID" :
1301
                      gpsData[tag] = gpsData[tag][0] +
1302
                          "." + gpsData[tag][1] +
1303
                          "." + gpsData[tag][2] +
1304
                          "." + gpsData[tag][3];
1305
                      break;
1306
              }
1307
              tags[tag] = gpsData[tag];
1308
          }
1309
      }
1310
1311
      return tags;
1312
  }
1313
1314
  this.getData = function(img, callback) {
1315
      if ((img instanceof Image || img instanceof HTMLImageElement) && !img.complete) return false;
1316
1317
      if (!imageHasData(img)) {
1318
          getImageData(img, callback);
1319
      } else {
1320
          if (callback) {
1321
              callback.call(img);
1322
          }
1323
      }
1324
      return true;
1325
  }
1326
1327
  this.getTag = function(img, tag) {
1328
      if (!imageHasData(img)) return;
1329
      return img.exifdata[tag];
1330
  }
1331
1332
  this.getAllTags = function(img) {
1333
      if (!imageHasData(img)) return {};
1334
      var a,
1335
          data = img.exifdata,
1336
          tags = {};
1337
      for (a in data) {
1338
          if (data.hasOwnProperty(a)) {
1339
              tags[a] = data[a];
1340
          }
1341
      }
1342
      return tags;
1343
  }
1344
1345
  this.pretty = function(img) {
1346
      if (!imageHasData(img)) return "";
1347
      var a,
1348
          data = img.exifdata,
1349
          strPretty = "";
1350
      for (a in data) {
1351
          if (data.hasOwnProperty(a)) {
1352
              if (typeof data[a] == "object") {
1353
                  if (data[a] instanceof Number) {
1354
                      strPretty += a + " : " + data[a] + " [" + data[a].numerator + "/" + data[a].denominator + "]\r\n";
1355
                  } else {
1356
                      strPretty += a + " : [" + data[a].length + " values]\r\n";
1357
                  }
1358
              } else {
1359
                  strPretty += a + " : " + data[a] + "\r\n";
1360
              }
1361
          }
1362
      }
1363
      return strPretty;
1364
  }
1365
1366
  this.readFromBinaryFile = function(file) {
1367
      return findEXIFinJPEG(file);
1368
  }
1369
}]);
1370
1371
crop.factory('cropHost', ['$document', 'cropAreaCircle', 'cropAreaSquare', 'cropEXIF', function($document, CropAreaCircle, CropAreaSquare, cropEXIF) {
1372
  /* STATIC FUNCTIONS */
1373
1374
  // Get Element's Offset
1375
  var getElementOffset=function(elem) {
1376
      var box = elem.getBoundingClientRect();
1377
1378
      var body = document.body;
1379
      var docElem = document.documentElement;
1380
1381
      var scrollTop = window.pageYOffset || docElem.scrollTop || body.scrollTop;
1382
      var scrollLeft = window.pageXOffset || docElem.scrollLeft || body.scrollLeft;
1383
1384
      var clientTop = docElem.clientTop || body.clientTop || 0;
1385
      var clientLeft = docElem.clientLeft || body.clientLeft || 0;
1386
1387
      var top  = box.top +  scrollTop - clientTop;
1388
      var left = box.left + scrollLeft - clientLeft;
1389
1390
      return { top: Math.round(top), left: Math.round(left) };
1391
  };
1392
1393
  return function(elCanvas, opts, events){
1394
    /* PRIVATE VARIABLES */
1395
1396
    // Object Pointers
1397
    var ctx=null,
1398
        image=null,
1399
        theArea=null;
1400
1401
    // Dimensions
1402
    var minCanvasDims=[100,100],
1403
        maxCanvasDims=[300,300];
1404
1405
    // Result Image size
1406
    var resImgSize=200;
1407
1408
    // Result Image type
1409
    var resImgFormat='image/png';
1410
1411
    // Result Image quality
1412
    var resImgQuality=null;
1413
1414
    /* PRIVATE FUNCTIONS */
1415
1416
    // Draw Scene
1417
    function drawScene() {
1418
      // clear canvas
1419
      ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
1420
1421
      if(image!==null) {
1422
        // draw source image
1423
        ctx.drawImage(image, 0, 0, ctx.canvas.width, ctx.canvas.height);
1424
1425
        ctx.save();
1426
1427
        // and make it darker
1428
        ctx.fillStyle = 'rgba(0, 0, 0, 0.65)';
1429
        ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
1430
1431
        ctx.restore();
1432
1433
        // draw Area
1434
        theArea.draw();
1435
      }
1436
    }
1437
1438
    // Resets CropHost
1439
    var resetCropHost=function() {
1440
      if(image!==null) {
1441
        theArea.setImage(image);
1442
        var imageDims=[image.width, image.height],
1443
            imageRatio=image.width/image.height,
1444
            canvasDims=imageDims;
1445
1446
        if(canvasDims[0]>maxCanvasDims[0]) {
1447
          canvasDims[0]=maxCanvasDims[0];
1448
          canvasDims[1]=canvasDims[0]/imageRatio;
1449
        } else if(canvasDims[0]<minCanvasDims[0]) {
1450
          canvasDims[0]=minCanvasDims[0];
1451
          canvasDims[1]=canvasDims[0]/imageRatio;
1452
        }
1453
        if(canvasDims[1]>maxCanvasDims[1]) {
1454
          canvasDims[1]=maxCanvasDims[1];
1455
          canvasDims[0]=canvasDims[1]*imageRatio;
1456
        } else if(canvasDims[1]<minCanvasDims[1]) {
1457
          canvasDims[1]=minCanvasDims[1];
1458
          canvasDims[0]=canvasDims[1]*imageRatio;
1459
        }
1460
        elCanvas.prop('width',canvasDims[0]).prop('height',canvasDims[1]).css({'margin-left': -canvasDims[0]/2+'px', 'margin-top': -canvasDims[1]/2+'px'});
1461
1462
        theArea.setX(ctx.canvas.width/2);
1463
        theArea.setY(ctx.canvas.height/2);
1464
        theArea.setSize(Math.min(200, ctx.canvas.width/2, ctx.canvas.height/2));
1465
      } else {
1466
        elCanvas.prop('width',0).prop('height',0).css({'margin-top': 0});
1467
      }
1468
1469
      drawScene();
1470
    };
1471
1472
    /**
1473
     * Returns event.changedTouches directly if event is a TouchEvent.
1474
     * If event is a jQuery event, return changedTouches of event.originalEvent
1475
     */
1476
    var getChangedTouches=function(event){
1477
      if(angular.isDefined(event.changedTouches)){
1478
        return event.changedTouches;
1479
      }else{
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
1480
        return event.originalEvent.changedTouches;
1481
      }
1482
    };
1483
1484
    var onMouseMove=function(e) {
1485
      if(image!==null) {
1486
        var offset=getElementOffset(ctx.canvas),
1487
            pageX, pageY;
1488
        if(e.type === 'touchmove') {
1489
          pageX=getChangedTouches(e)[0].pageX;
1490
          pageY=getChangedTouches(e)[0].pageY;
1491
        } else {
1492
          pageX=e.pageX;
1493
          pageY=e.pageY;
1494
        }
1495
        theArea.processMouseMove(pageX-offset.left, pageY-offset.top);
1496
        drawScene();
1497
      }
1498
    };
1499
1500
    var onMouseDown=function(e) {
1501
      e.preventDefault();
1502
      e.stopPropagation();
1503
      if(image!==null) {
1504
        var offset=getElementOffset(ctx.canvas),
1505
            pageX, pageY;
1506
        if(e.type === 'touchstart') {
1507
          pageX=getChangedTouches(e)[0].pageX;
1508
          pageY=getChangedTouches(e)[0].pageY;
1509
        } else {
1510
          pageX=e.pageX;
1511
          pageY=e.pageY;
1512
        }
1513
        theArea.processMouseDown(pageX-offset.left, pageY-offset.top);
1514
        drawScene();
1515
      }
1516
    };
1517
1518
    var onMouseUp=function(e) {
1519
      if(image!==null) {
1520
        var offset=getElementOffset(ctx.canvas),
1521
            pageX, pageY;
1522
        if(e.type === 'touchend') {
1523
          pageX=getChangedTouches(e)[0].pageX;
1524
          pageY=getChangedTouches(e)[0].pageY;
1525
        } else {
1526
          pageX=e.pageX;
1527
          pageY=e.pageY;
1528
        }
1529
        theArea.processMouseUp(pageX-offset.left, pageY-offset.top);
1530
        drawScene();
1531
      }
1532
    };
1533
1534
    this.getResultImageDataURI=function() {
1535
      var temp_ctx, temp_canvas;
1536
      temp_canvas = angular.element('<canvas></canvas>')[0];
1537
      temp_ctx = temp_canvas.getContext('2d');
1538
      temp_canvas.width = resImgSize;
1539
      temp_canvas.height = resImgSize;
1540
      if(image!==null){
1541
        temp_ctx.drawImage(image, (theArea.getX()-theArea.getSize()/2)*(image.width/ctx.canvas.width), (theArea.getY()-theArea.getSize()/2)*(image.height/ctx.canvas.height), theArea.getSize()*(image.width/ctx.canvas.width), theArea.getSize()*(image.height/ctx.canvas.height), 0, 0, resImgSize, resImgSize);
1542
      }
1543
      if (resImgQuality!==null ){
1544
        return temp_canvas.toDataURL(resImgFormat, resImgQuality);
1545
      }
1546
      return temp_canvas.toDataURL(resImgFormat);
1547
    };
1548
1549
    this.setNewImageSource=function(imageSource) {
1550
      image=null;
1551
      resetCropHost();
1552
      events.trigger('image-updated');
1553
      if(!!imageSource) {
1554
        var newImage = new Image();
1555
        if(imageSource.substring(0,4).toLowerCase()==='http') {
1556
          newImage.crossOrigin = 'anonymous';
1557
        }
1558
        newImage.onload = function(){
1559
          events.trigger('load-done');
1560
1561
          cropEXIF.getData(newImage,function(){
1562
            var orientation=cropEXIF.getTag(newImage,'Orientation');
1563
1564
            if([3,6,8].indexOf(orientation)>-1) {
1565
              var canvas = document.createElement("canvas"),
1566
                  ctx=canvas.getContext("2d"),
1567
                  cw = newImage.width, ch = newImage.height, cx = 0, cy = 0, deg=0;
1568
              switch(orientation) {
1569
                case 3:
1570
                  cx=-newImage.width;
1571
                  cy=-newImage.height;
1572
                  deg=180;
1573
                  break;
1574
                case 6:
1575
                  cw = newImage.height;
1576
                  ch = newImage.width;
1577
                  cy=-newImage.height;
1578
                  deg=90;
1579
                  break;
1580
                case 8:
1581
                  cw = newImage.height;
1582
                  ch = newImage.width;
1583
                  cx=-newImage.width;
1584
                  deg=270;
1585
                  break;
1586
              }
1587
1588
              canvas.width = cw;
1589
              canvas.height = ch;
1590
              ctx.rotate(deg*Math.PI/180);
1591
              ctx.drawImage(newImage, cx, cy);
1592
1593
              image=new Image();
1594
              image.src = canvas.toDataURL("image/png");
1595
            } else {
1596
              image=newImage;
1597
            }
1598
            resetCropHost();
1599
            events.trigger('image-updated');
1600
          });
1601
        };
1602
        newImage.onerror=function() {
1603
          events.trigger('load-error');
1604
        };
1605
        events.trigger('load-start');
1606
        newImage.src=imageSource;
1607
      }
1608
    };
1609
1610
    this.setMaxDimensions=function(width, height) {
1611
      maxCanvasDims=[width,height];
1612
1613
      if(image!==null) {
1614
        var curWidth=ctx.canvas.width,
1615
            curHeight=ctx.canvas.height;
1616
1617
        var imageDims=[image.width, image.height],
1618
            imageRatio=image.width/image.height,
1619
            canvasDims=imageDims;
1620
1621
        if(canvasDims[0]>maxCanvasDims[0]) {
1622
          canvasDims[0]=maxCanvasDims[0];
1623
          canvasDims[1]=canvasDims[0]/imageRatio;
1624
        } else if(canvasDims[0]<minCanvasDims[0]) {
1625
          canvasDims[0]=minCanvasDims[0];
1626
          canvasDims[1]=canvasDims[0]/imageRatio;
1627
        }
1628
        if(canvasDims[1]>maxCanvasDims[1]) {
1629
          canvasDims[1]=maxCanvasDims[1];
1630
          canvasDims[0]=canvasDims[1]*imageRatio;
1631
        } else if(canvasDims[1]<minCanvasDims[1]) {
1632
          canvasDims[1]=minCanvasDims[1];
1633
          canvasDims[0]=canvasDims[1]*imageRatio;
1634
        }
1635
        elCanvas.prop('width',canvasDims[0]).prop('height',canvasDims[1]).css({'margin-left': -canvasDims[0]/2+'px', 'margin-top': -canvasDims[1]/2+'px'});
1636
1637
        var ratioNewCurWidth=ctx.canvas.width/curWidth,
1638
            ratioNewCurHeight=ctx.canvas.height/curHeight,
1639
            ratioMin=Math.min(ratioNewCurWidth, ratioNewCurHeight);
1640
1641
        theArea.setX(theArea.getX()*ratioNewCurWidth);
1642
        theArea.setY(theArea.getY()*ratioNewCurHeight);
1643
        theArea.setSize(theArea.getSize()*ratioMin);
1644
      } else {
1645
        elCanvas.prop('width',0).prop('height',0).css({'margin-top': 0});
1646
      }
1647
1648
      drawScene();
1649
1650
    };
1651
1652
    this.setAreaMinSize=function(size) {
1653
      size=parseInt(size,10);
1654
      if(!isNaN(size)) {
1655
        theArea.setMinSize(size);
1656
        drawScene();
1657
      }
1658
    };
1659
1660
    this.setResultImageSize=function(size) {
1661
      size=parseInt(size,10);
1662
      if(!isNaN(size)) {
1663
        resImgSize=size;
1664
      }
1665
    };
1666
1667
    this.setResultImageFormat=function(format) {
1668
      resImgFormat = format;
1669
    };
1670
1671
    this.setResultImageQuality=function(quality){
1672
      quality = parseFloat(quality);
1673
      if (!isNaN(quality) && quality>=0 && quality<=1){
1674
        resImgQuality = quality;
1675
      }
1676
    };
1677
1678
    this.setAreaType=function(type) {
1679
      var curSize=theArea.getSize(),
1680
          curMinSize=theArea.getMinSize(),
1681
          curX=theArea.getX(),
1682
          curY=theArea.getY();
1683
1684
      var AreaClass=CropAreaCircle;
1685
      if(type==='square') {
1686
        AreaClass=CropAreaSquare;
1687
      }
1688
      theArea = new AreaClass(ctx, events);
1689
      theArea.setMinSize(curMinSize);
1690
      theArea.setSize(curSize);
1691
      theArea.setX(curX);
1692
      theArea.setY(curY);
1693
1694
      // resetCropHost();
1695
      if(image!==null) {
1696
        theArea.setImage(image);
1697
      }
1698
1699
      drawScene();
1700
    };
1701
1702
    /* Life Cycle begins */
1703
1704
    // Init Context var
1705
    ctx = elCanvas[0].getContext('2d');
1706
1707
    // Init CropArea
1708
    theArea = new CropAreaCircle(ctx, events);
1709
1710
    // Init Mouse Event Listeners
1711
    $document.on('mousemove',onMouseMove);
1712
    elCanvas.on('mousedown',onMouseDown);
1713
    $document.on('mouseup',onMouseUp);
1714
1715
    // Init Touch Event Listeners
1716
    $document.on('touchmove',onMouseMove);
1717
    elCanvas.on('touchstart',onMouseDown);
1718
    $document.on('touchend',onMouseUp);
1719
1720
    // CropHost Destructor
1721
    this.destroy=function() {
1722
      $document.off('mousemove',onMouseMove);
1723
      elCanvas.off('mousedown',onMouseDown);
1724
      $document.off('mouseup',onMouseMove);
1725
1726
      $document.off('touchmove',onMouseMove);
1727
      elCanvas.off('touchstart',onMouseDown);
1728
      $document.off('touchend',onMouseMove);
1729
1730
      elCanvas.remove();
1731
    };
1732
  };
1733
1734
}]);
1735
1736
1737
crop.factory('cropPubSub', [function() {
1738
  return function() {
1739
    var events = {};
1740
    // Subscribe
1741
    this.on = function(names, handler) {
1742
      names.split(' ').forEach(function(name) {
1743
        if (!events[name]) {
1744
          events[name] = [];
1745
        }
1746
        events[name].push(handler);
1747
      });
1748
      return this;
1749
    };
1750
    // Publish
1751
    this.trigger = function(name, args) {
1752
      angular.forEach(events[name], function(handler) {
1753
        handler.call(null, args);
1754
      });
1755
      return this;
1756
    };
1757
  };
1758
}]);
1759
1760
crop.directive('imgCrop', ['$timeout', 'cropHost', 'cropPubSub', function($timeout, CropHost, CropPubSub) {
1761
  return {
1762
    restrict: 'E',
1763
    scope: {
1764
      image: '=',
1765
      resultImage: '=',
1766
1767
      changeOnFly: '=',
1768
      areaType: '@',
1769
      areaMinSize: '=',
1770
      resultImageSize: '=',
1771
      resultImageFormat: '@',
1772
      resultImageQuality: '=',
1773
1774
      onChange: '&',
1775
      onLoadBegin: '&',
1776
      onLoadDone: '&',
1777
      onLoadError: '&'
1778
    },
1779
    template: '<canvas></canvas>',
1780
    controller: ['$scope', function($scope) {
1781
      $scope.events = new CropPubSub();
1782
    }],
1783
    link: function(scope, element/*, attrs*/) {
1784
      // Init Events Manager
1785
      var events = scope.events;
1786
1787
      // Init Crop Host
1788
      var cropHost=new CropHost(element.find('canvas'), {}, events);
1789
1790
      // Store Result Image to check if it's changed
1791
      var storedResultImage;
1792
1793
      var updateResultImage=function(scope) {
1794
        var resultImage=cropHost.getResultImageDataURI();
1795
        if(storedResultImage!==resultImage) {
1796
          storedResultImage=resultImage;
1797
          if(angular.isDefined(scope.resultImage)) {
1798
            scope.resultImage=resultImage;
1799
          }
1800
          scope.onChange({$dataURI: scope.resultImage});
1801
        }
1802
      };
1803
1804
      // Wrapper to safely exec functions within $apply on a running $digest cycle
1805
      var fnSafeApply=function(fn) {
1806
        return function(){
1807
          $timeout(function(){
1808
            scope.$apply(function(scope){
1809
              fn(scope);
1810
            });
1811
          });
1812
        };
1813
      };
1814
1815
      // Setup CropHost Event Handlers
1816
      events
1817
        .on('load-start', fnSafeApply(function(scope){
1818
          scope.onLoadBegin({});
1819
        }))
1820
        .on('load-done', fnSafeApply(function(scope){
1821
          scope.onLoadDone({});
1822
        }))
1823
        .on('load-error', fnSafeApply(function(scope){
1824
          scope.onLoadError({});
1825
        }))
1826
        .on('area-move area-resize', fnSafeApply(function(scope){
1827
          if(!!scope.changeOnFly) {
1828
            updateResultImage(scope);
1829
          }
1830
        }))
1831
        .on('area-move-end area-resize-end image-updated', fnSafeApply(function(scope){
1832
          updateResultImage(scope);
1833
        }));
1834
1835
      // Sync CropHost with Directive's options
1836
      scope.$watch('image',function(){
1837
        cropHost.setNewImageSource(scope.image);
1838
      });
1839
      scope.$watch('areaType',function(){
1840
        cropHost.setAreaType(scope.areaType);
1841
        updateResultImage(scope);
1842
      });
1843
      scope.$watch('areaMinSize',function(){
1844
        cropHost.setAreaMinSize(scope.areaMinSize);
1845
        updateResultImage(scope);
1846
      });
1847
      scope.$watch('resultImageSize',function(){
1848
        cropHost.setResultImageSize(scope.resultImageSize);
1849
        updateResultImage(scope);
1850
      });
1851
      scope.$watch('resultImageFormat',function(){
1852
        cropHost.setResultImageFormat(scope.resultImageFormat);
1853
        updateResultImage(scope);
1854
      });
1855
      scope.$watch('resultImageQuality',function(){
1856
        cropHost.setResultImageQuality(scope.resultImageQuality);
1857
        updateResultImage(scope);
1858
      });
1859
1860
      // Update CropHost dimensions when the directive element is resized
1861
      scope.$watch(
1862
        function () {
1863
          return [element[0].clientWidth, element[0].clientHeight];
1864
        },
1865
        function (value) {
1866
          cropHost.setMaxDimensions(value[0],value[1]);
1867
          updateResultImage(scope);
1868
        },
1869
        true
1870
      );
1871
1872
      // Destroy CropHost Instance when the directive is destroying
1873
      scope.$on('$destroy', function(){
1874
          cropHost.destroy();
1875
      });
1876
    }
1877
  };
1878
}]);
1879
}());