Total Complexity | 272 |
Complexity/F | 2.18 |
Lines of Code | 1870 |
Function Count | 125 |
Duplicated Lines | 18 |
Ratio | 0.96 % |
Changes | 0 |
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:
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 | /*! |
||
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) { |
||
|
|||
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) { |
||
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(); |
||
960 | fileReader.onload = function(e) { |
||
961 | if (debug) console.log("Got file of length " + e.target.result.byteLength); |
||
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); |
||
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); |
||
998 | |||
999 | // offset += 2 + file.getShortAt(offset+2, true); |
||
1000 | |||
1001 | } else { |
||
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); |
||
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; |
||
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; |
||
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)); |
||
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 { |
||
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 { |
||
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 { |
||
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 { |
||
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 { |
||
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 { |
||
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 | } |
||
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)); |
||
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{ |
||
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 | }()); |
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: