ng-img-crop.js 61 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879
  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. var crop = angular.module('ngImgCrop', []);
  13. crop.factory('cropAreaCircle', ['cropArea', function(CropArea) {
  14. var CropAreaCircle = function() {
  15. CropArea.apply(this, arguments);
  16. this._boxResizeBaseSize = 20;
  17. this._boxResizeNormalRatio = 0.9;
  18. this._boxResizeHoverRatio = 1.2;
  19. this._iconMoveNormalRatio = 0.9;
  20. this._iconMoveHoverRatio = 1.2;
  21. this._boxResizeNormalSize = this._boxResizeBaseSize*this._boxResizeNormalRatio;
  22. this._boxResizeHoverSize = this._boxResizeBaseSize*this._boxResizeHoverRatio;
  23. this._posDragStartX=0;
  24. this._posDragStartY=0;
  25. this._posResizeStartX=0;
  26. this._posResizeStartY=0;
  27. this._posResizeStartSize=0;
  28. this._boxResizeIsHover = false;
  29. this._areaIsHover = false;
  30. this._boxResizeIsDragging = false;
  31. this._areaIsDragging = false;
  32. };
  33. CropAreaCircle.prototype = new CropArea();
  34. CropAreaCircle.prototype._calcCirclePerimeterCoords=function(angleDegrees) {
  35. var hSize=this._size/2;
  36. var angleRadians=angleDegrees * (Math.PI / 180),
  37. circlePerimeterX=this._x + hSize * Math.cos(angleRadians),
  38. circlePerimeterY=this._y + hSize * Math.sin(angleRadians);
  39. return [circlePerimeterX, circlePerimeterY];
  40. };
  41. CropAreaCircle.prototype._calcResizeIconCenterCoords=function() {
  42. return this._calcCirclePerimeterCoords(-45);
  43. };
  44. CropAreaCircle.prototype._isCoordWithinArea=function(coord) {
  45. return Math.sqrt((coord[0]-this._x)*(coord[0]-this._x) + (coord[1]-this._y)*(coord[1]-this._y)) < this._size/2;
  46. };
  47. CropAreaCircle.prototype._isCoordWithinBoxResize=function(coord) {
  48. var resizeIconCenterCoords=this._calcResizeIconCenterCoords();
  49. var hSize=this._boxResizeHoverSize/2;
  50. return(coord[0] > resizeIconCenterCoords[0] - hSize && coord[0] < resizeIconCenterCoords[0] + hSize &&
  51. coord[1] > resizeIconCenterCoords[1] - hSize && coord[1] < resizeIconCenterCoords[1] + hSize);
  52. };
  53. CropAreaCircle.prototype._drawArea=function(ctx,centerCoords,size){
  54. ctx.arc(centerCoords[0],centerCoords[1],size/2,0,2*Math.PI);
  55. };
  56. CropAreaCircle.prototype.draw=function() {
  57. CropArea.prototype.draw.apply(this, arguments);
  58. // draw move icon
  59. this._cropCanvas.drawIconMove([this._x,this._y], this._areaIsHover?this._iconMoveHoverRatio:this._iconMoveNormalRatio);
  60. // draw resize cubes
  61. this._cropCanvas.drawIconResizeBoxNESW(this._calcResizeIconCenterCoords(), this._boxResizeBaseSize, this._boxResizeIsHover?this._boxResizeHoverRatio:this._boxResizeNormalRatio);
  62. };
  63. CropAreaCircle.prototype.processMouseMove=function(mouseCurX, mouseCurY) {
  64. var cursor='default';
  65. var res=false;
  66. this._boxResizeIsHover = false;
  67. this._areaIsHover = false;
  68. if (this._areaIsDragging) {
  69. this._x = mouseCurX - this._posDragStartX;
  70. this._y = mouseCurY - this._posDragStartY;
  71. this._areaIsHover = true;
  72. cursor='move';
  73. res=true;
  74. this._events.trigger('area-move');
  75. } else if (this._boxResizeIsDragging) {
  76. cursor = 'nesw-resize';
  77. var iFR, iFX, iFY;
  78. iFX = mouseCurX - this._posResizeStartX;
  79. iFY = this._posResizeStartY - mouseCurY;
  80. if(iFX>iFY) {
  81. iFR = this._posResizeStartSize + iFY*2;
  82. } else {
  83. iFR = this._posResizeStartSize + iFX*2;
  84. }
  85. this._size = Math.max(this._minSize, iFR);
  86. this._boxResizeIsHover = true;
  87. res=true;
  88. this._events.trigger('area-resize');
  89. } else if (this._isCoordWithinBoxResize([mouseCurX,mouseCurY])) {
  90. cursor = 'nesw-resize';
  91. this._areaIsHover = false;
  92. this._boxResizeIsHover = true;
  93. res=true;
  94. } else if(this._isCoordWithinArea([mouseCurX,mouseCurY])) {
  95. cursor = 'move';
  96. this._areaIsHover = true;
  97. res=true;
  98. }
  99. this._dontDragOutside();
  100. angular.element(this._ctx.canvas).css({'cursor': cursor});
  101. return res;
  102. };
  103. CropAreaCircle.prototype.processMouseDown=function(mouseDownX, mouseDownY) {
  104. if (this._isCoordWithinBoxResize([mouseDownX,mouseDownY])) {
  105. this._areaIsDragging = false;
  106. this._areaIsHover = false;
  107. this._boxResizeIsDragging = true;
  108. this._boxResizeIsHover = true;
  109. this._posResizeStartX=mouseDownX;
  110. this._posResizeStartY=mouseDownY;
  111. this._posResizeStartSize = this._size;
  112. this._events.trigger('area-resize-start');
  113. } else if (this._isCoordWithinArea([mouseDownX,mouseDownY])) {
  114. this._areaIsDragging = true;
  115. this._areaIsHover = true;
  116. this._boxResizeIsDragging = false;
  117. this._boxResizeIsHover = false;
  118. this._posDragStartX = mouseDownX - this._x;
  119. this._posDragStartY = mouseDownY - this._y;
  120. this._events.trigger('area-move-start');
  121. }
  122. };
  123. CropAreaCircle.prototype.processMouseUp=function(/*mouseUpX, mouseUpY*/) {
  124. if(this._areaIsDragging) {
  125. this._areaIsDragging = false;
  126. this._events.trigger('area-move-end');
  127. }
  128. if(this._boxResizeIsDragging) {
  129. this._boxResizeIsDragging = false;
  130. this._events.trigger('area-resize-end');
  131. }
  132. this._areaIsHover = false;
  133. this._boxResizeIsHover = false;
  134. this._posDragStartX = 0;
  135. this._posDragStartY = 0;
  136. };
  137. return CropAreaCircle;
  138. }]);
  139. crop.factory('cropAreaSquare', ['cropArea', function(CropArea) {
  140. var CropAreaSquare = function() {
  141. CropArea.apply(this, arguments);
  142. this._resizeCtrlBaseRadius = 10;
  143. this._resizeCtrlNormalRatio = 0.75;
  144. this._resizeCtrlHoverRatio = 1;
  145. this._iconMoveNormalRatio = 0.9;
  146. this._iconMoveHoverRatio = 1.2;
  147. this._resizeCtrlNormalRadius = this._resizeCtrlBaseRadius*this._resizeCtrlNormalRatio;
  148. this._resizeCtrlHoverRadius = this._resizeCtrlBaseRadius*this._resizeCtrlHoverRatio;
  149. this._posDragStartX=0;
  150. this._posDragStartY=0;
  151. this._posResizeStartX=0;
  152. this._posResizeStartY=0;
  153. this._posResizeStartSize=0;
  154. this._resizeCtrlIsHover = -1;
  155. this._areaIsHover = false;
  156. this._resizeCtrlIsDragging = -1;
  157. this._areaIsDragging = false;
  158. };
  159. CropAreaSquare.prototype = new CropArea();
  160. CropAreaSquare.prototype._calcSquareCorners=function() {
  161. var hSize=this._size/2;
  162. return [
  163. [this._x-hSize, this._y-hSize],
  164. [this._x+hSize, this._y-hSize],
  165. [this._x-hSize, this._y+hSize],
  166. [this._x+hSize, this._y+hSize]
  167. ];
  168. };
  169. CropAreaSquare.prototype._calcSquareDimensions=function() {
  170. var hSize=this._size/2;
  171. return {
  172. left: this._x-hSize,
  173. top: this._y-hSize,
  174. right: this._x+hSize,
  175. bottom: this._y+hSize
  176. };
  177. };
  178. CropAreaSquare.prototype._isCoordWithinArea=function(coord) {
  179. var squareDimensions=this._calcSquareDimensions();
  180. return (coord[0]>=squareDimensions.left&&coord[0]<=squareDimensions.right&&coord[1]>=squareDimensions.top&&coord[1]<=squareDimensions.bottom);
  181. };
  182. CropAreaSquare.prototype._isCoordWithinResizeCtrl=function(coord) {
  183. var resizeIconsCenterCoords=this._calcSquareCorners();
  184. var res=-1;
  185. for(var i=0,len=resizeIconsCenterCoords.length;i<len;i++) {
  186. var resizeIconCenterCoords=resizeIconsCenterCoords[i];
  187. if(coord[0] > resizeIconCenterCoords[0] - this._resizeCtrlHoverRadius && coord[0] < resizeIconCenterCoords[0] + this._resizeCtrlHoverRadius &&
  188. coord[1] > resizeIconCenterCoords[1] - this._resizeCtrlHoverRadius && coord[1] < resizeIconCenterCoords[1] + this._resizeCtrlHoverRadius) {
  189. res=i;
  190. break;
  191. }
  192. }
  193. return res;
  194. };
  195. CropAreaSquare.prototype._drawArea=function(ctx,centerCoords,size){
  196. var hSize=size/2;
  197. ctx.rect(centerCoords[0]-hSize,centerCoords[1]-hSize,size,size);
  198. };
  199. CropAreaSquare.prototype.draw=function() {
  200. CropArea.prototype.draw.apply(this, arguments);
  201. // draw move icon
  202. this._cropCanvas.drawIconMove([this._x,this._y], this._areaIsHover?this._iconMoveHoverRatio:this._iconMoveNormalRatio);
  203. // draw resize cubes
  204. var resizeIconsCenterCoords=this._calcSquareCorners();
  205. for(var i=0,len=resizeIconsCenterCoords.length;i<len;i++) {
  206. var resizeIconCenterCoords=resizeIconsCenterCoords[i];
  207. this._cropCanvas.drawIconResizeCircle(resizeIconCenterCoords, this._resizeCtrlBaseRadius, this._resizeCtrlIsHover===i?this._resizeCtrlHoverRatio:this._resizeCtrlNormalRatio);
  208. }
  209. };
  210. CropAreaSquare.prototype.processMouseMove=function(mouseCurX, mouseCurY) {
  211. var cursor='default';
  212. var res=false;
  213. this._resizeCtrlIsHover = -1;
  214. this._areaIsHover = false;
  215. if (this._areaIsDragging) {
  216. this._x = mouseCurX - this._posDragStartX;
  217. this._y = mouseCurY - this._posDragStartY;
  218. this._areaIsHover = true;
  219. cursor='move';
  220. res=true;
  221. this._events.trigger('area-move');
  222. } else if (this._resizeCtrlIsDragging>-1) {
  223. var xMulti, yMulti;
  224. switch(this._resizeCtrlIsDragging) {
  225. case 0: // Top Left
  226. xMulti=-1;
  227. yMulti=-1;
  228. cursor = 'nwse-resize';
  229. break;
  230. case 1: // Top Right
  231. xMulti=1;
  232. yMulti=-1;
  233. cursor = 'nesw-resize';
  234. break;
  235. case 2: // Bottom Left
  236. xMulti=-1;
  237. yMulti=1;
  238. cursor = 'nesw-resize';
  239. break;
  240. case 3: // Bottom Right
  241. xMulti=1;
  242. yMulti=1;
  243. cursor = 'nwse-resize';
  244. break;
  245. }
  246. var iFX = (mouseCurX - this._posResizeStartX)*xMulti;
  247. var iFY = (mouseCurY - this._posResizeStartY)*yMulti;
  248. var iFR;
  249. if(iFX>iFY) {
  250. iFR = this._posResizeStartSize + iFY;
  251. } else {
  252. iFR = this._posResizeStartSize + iFX;
  253. }
  254. var wasSize=this._size;
  255. this._size = Math.max(this._minSize, iFR);
  256. var posModifier=(this._size-wasSize)/2;
  257. this._x+=posModifier*xMulti;
  258. this._y+=posModifier*yMulti;
  259. this._resizeCtrlIsHover = this._resizeCtrlIsDragging;
  260. res=true;
  261. this._events.trigger('area-resize');
  262. } else {
  263. var hoveredResizeBox=this._isCoordWithinResizeCtrl([mouseCurX,mouseCurY]);
  264. if (hoveredResizeBox>-1) {
  265. switch(hoveredResizeBox) {
  266. case 0:
  267. cursor = 'nwse-resize';
  268. break;
  269. case 1:
  270. cursor = 'nesw-resize';
  271. break;
  272. case 2:
  273. cursor = 'nesw-resize';
  274. break;
  275. case 3:
  276. cursor = 'nwse-resize';
  277. break;
  278. }
  279. this._areaIsHover = false;
  280. this._resizeCtrlIsHover = hoveredResizeBox;
  281. res=true;
  282. } else if(this._isCoordWithinArea([mouseCurX,mouseCurY])) {
  283. cursor = 'move';
  284. this._areaIsHover = true;
  285. res=true;
  286. }
  287. }
  288. this._dontDragOutside();
  289. angular.element(this._ctx.canvas).css({'cursor': cursor});
  290. return res;
  291. };
  292. CropAreaSquare.prototype.processMouseDown=function(mouseDownX, mouseDownY) {
  293. var isWithinResizeCtrl=this._isCoordWithinResizeCtrl([mouseDownX,mouseDownY]);
  294. if (isWithinResizeCtrl>-1) {
  295. this._areaIsDragging = false;
  296. this._areaIsHover = false;
  297. this._resizeCtrlIsDragging = isWithinResizeCtrl;
  298. this._resizeCtrlIsHover = isWithinResizeCtrl;
  299. this._posResizeStartX=mouseDownX;
  300. this._posResizeStartY=mouseDownY;
  301. this._posResizeStartSize = this._size;
  302. this._events.trigger('area-resize-start');
  303. } else if (this._isCoordWithinArea([mouseDownX,mouseDownY])) {
  304. this._areaIsDragging = true;
  305. this._areaIsHover = true;
  306. this._resizeCtrlIsDragging = -1;
  307. this._resizeCtrlIsHover = -1;
  308. this._posDragStartX = mouseDownX - this._x;
  309. this._posDragStartY = mouseDownY - this._y;
  310. this._events.trigger('area-move-start');
  311. }
  312. };
  313. CropAreaSquare.prototype.processMouseUp=function(/*mouseUpX, mouseUpY*/) {
  314. if(this._areaIsDragging) {
  315. this._areaIsDragging = false;
  316. this._events.trigger('area-move-end');
  317. }
  318. if(this._resizeCtrlIsDragging>-1) {
  319. this._resizeCtrlIsDragging = -1;
  320. this._events.trigger('area-resize-end');
  321. }
  322. this._areaIsHover = false;
  323. this._resizeCtrlIsHover = -1;
  324. this._posDragStartX = 0;
  325. this._posDragStartY = 0;
  326. };
  327. return CropAreaSquare;
  328. }]);
  329. crop.factory('cropArea', ['cropCanvas', function(CropCanvas) {
  330. var CropArea = function(ctx, events) {
  331. this._ctx=ctx;
  332. this._events=events;
  333. this._minSize=80;
  334. this._cropCanvas=new CropCanvas(ctx);
  335. this._image=new Image();
  336. this._x = 0;
  337. this._y = 0;
  338. this._size = 200;
  339. };
  340. /* GETTERS/SETTERS */
  341. CropArea.prototype.getImage = function () {
  342. return this._image;
  343. };
  344. CropArea.prototype.setImage = function (image) {
  345. this._image = image;
  346. };
  347. CropArea.prototype.getX = function () {
  348. return this._x;
  349. };
  350. CropArea.prototype.setX = function (x) {
  351. this._x = x;
  352. this._dontDragOutside();
  353. };
  354. CropArea.prototype.getY = function () {
  355. return this._y;
  356. };
  357. CropArea.prototype.setY = function (y) {
  358. this._y = y;
  359. this._dontDragOutside();
  360. };
  361. CropArea.prototype.getSize = function () {
  362. return this._size;
  363. };
  364. CropArea.prototype.setSize = function (size) {
  365. this._size = Math.max(this._minSize, size);
  366. this._dontDragOutside();
  367. };
  368. CropArea.prototype.getMinSize = function () {
  369. return this._minSize;
  370. };
  371. CropArea.prototype.setMinSize = function (size) {
  372. this._minSize = size;
  373. this._size = Math.max(this._minSize, this._size);
  374. this._dontDragOutside();
  375. };
  376. /* FUNCTIONS */
  377. CropArea.prototype._dontDragOutside=function() {
  378. var h=this._ctx.canvas.height,
  379. w=this._ctx.canvas.width;
  380. if(this._size>w) { this._size=w; }
  381. if(this._size>h) { this._size=h; }
  382. if(this._x<this._size/2) { this._x=this._size/2; }
  383. if(this._x>w-this._size/2) { this._x=w-this._size/2; }
  384. if(this._y<this._size/2) { this._y=this._size/2; }
  385. if(this._y>h-this._size/2) { this._y=h-this._size/2; }
  386. };
  387. CropArea.prototype._drawArea=function() {};
  388. CropArea.prototype.draw=function() {
  389. // draw crop area
  390. this._cropCanvas.drawCropArea(this._image,[this._x,this._y],this._size,this._drawArea);
  391. };
  392. CropArea.prototype.processMouseMove=function() {};
  393. CropArea.prototype.processMouseDown=function() {};
  394. CropArea.prototype.processMouseUp=function() {};
  395. return CropArea;
  396. }]);
  397. crop.factory('cropCanvas', [function() {
  398. // Shape = Array of [x,y]; [0, 0] - center
  399. var shapeArrowNW=[[-0.5,-2],[-3,-4.5],[-0.5,-7],[-7,-7],[-7,-0.5],[-4.5,-3],[-2,-0.5]];
  400. var shapeArrowNE=[[0.5,-2],[3,-4.5],[0.5,-7],[7,-7],[7,-0.5],[4.5,-3],[2,-0.5]];
  401. var shapeArrowSW=[[-0.5,2],[-3,4.5],[-0.5,7],[-7,7],[-7,0.5],[-4.5,3],[-2,0.5]];
  402. var shapeArrowSE=[[0.5,2],[3,4.5],[0.5,7],[7,7],[7,0.5],[4.5,3],[2,0.5]];
  403. var shapeArrowN=[[-1.5,-2.5],[-1.5,-6],[-5,-6],[0,-11],[5,-6],[1.5,-6],[1.5,-2.5]];
  404. var shapeArrowW=[[-2.5,-1.5],[-6,-1.5],[-6,-5],[-11,0],[-6,5],[-6,1.5],[-2.5,1.5]];
  405. var shapeArrowS=[[-1.5,2.5],[-1.5,6],[-5,6],[0,11],[5,6],[1.5,6],[1.5,2.5]];
  406. var shapeArrowE=[[2.5,-1.5],[6,-1.5],[6,-5],[11,0],[6,5],[6,1.5],[2.5,1.5]];
  407. // Colors
  408. var colors={
  409. areaOutline: '#fff',
  410. resizeBoxStroke: '#fff',
  411. resizeBoxFill: '#444',
  412. resizeBoxArrowFill: '#fff',
  413. resizeCircleStroke: '#fff',
  414. resizeCircleFill: '#444',
  415. moveIconFill: '#fff'
  416. };
  417. return function(ctx){
  418. /* Base functions */
  419. // Calculate Point
  420. var calcPoint=function(point,offset,scale) {
  421. return [scale*point[0]+offset[0], scale*point[1]+offset[1]];
  422. };
  423. // Draw Filled Polygon
  424. var drawFilledPolygon=function(shape,fillStyle,centerCoords,scale) {
  425. ctx.save();
  426. ctx.fillStyle = fillStyle;
  427. ctx.beginPath();
  428. var pc, pc0=calcPoint(shape[0],centerCoords,scale);
  429. ctx.moveTo(pc0[0],pc0[1]);
  430. for(var p in shape) {
  431. if (p > 0) {
  432. pc=calcPoint(shape[p],centerCoords,scale);
  433. ctx.lineTo(pc[0],pc[1]);
  434. }
  435. }
  436. ctx.lineTo(pc0[0],pc0[1]);
  437. ctx.fill();
  438. ctx.closePath();
  439. ctx.restore();
  440. };
  441. /* Icons */
  442. this.drawIconMove=function(centerCoords, scale) {
  443. drawFilledPolygon(shapeArrowN, colors.moveIconFill, centerCoords, scale);
  444. drawFilledPolygon(shapeArrowW, colors.moveIconFill, centerCoords, scale);
  445. drawFilledPolygon(shapeArrowS, colors.moveIconFill, centerCoords, scale);
  446. drawFilledPolygon(shapeArrowE, colors.moveIconFill, centerCoords, scale);
  447. };
  448. this.drawIconResizeCircle=function(centerCoords, circleRadius, scale) {
  449. var scaledCircleRadius=circleRadius*scale;
  450. ctx.save();
  451. ctx.strokeStyle = colors.resizeCircleStroke;
  452. ctx.lineWidth = 2;
  453. ctx.fillStyle = colors.resizeCircleFill;
  454. ctx.beginPath();
  455. ctx.arc(centerCoords[0],centerCoords[1],scaledCircleRadius,0,2*Math.PI);
  456. ctx.fill();
  457. ctx.stroke();
  458. ctx.closePath();
  459. ctx.restore();
  460. };
  461. this.drawIconResizeBoxBase=function(centerCoords, boxSize, scale) {
  462. var scaledBoxSize=boxSize*scale;
  463. ctx.save();
  464. ctx.strokeStyle = colors.resizeBoxStroke;
  465. ctx.lineWidth = 2;
  466. ctx.fillStyle = colors.resizeBoxFill;
  467. ctx.fillRect(centerCoords[0] - scaledBoxSize/2, centerCoords[1] - scaledBoxSize/2, scaledBoxSize, scaledBoxSize);
  468. ctx.strokeRect(centerCoords[0] - scaledBoxSize/2, centerCoords[1] - scaledBoxSize/2, scaledBoxSize, scaledBoxSize);
  469. ctx.restore();
  470. };
  471. this.drawIconResizeBoxNESW=function(centerCoords, boxSize, scale) {
  472. this.drawIconResizeBoxBase(centerCoords, boxSize, scale);
  473. drawFilledPolygon(shapeArrowNE, colors.resizeBoxArrowFill, centerCoords, scale);
  474. drawFilledPolygon(shapeArrowSW, colors.resizeBoxArrowFill, centerCoords, scale);
  475. };
  476. this.drawIconResizeBoxNWSE=function(centerCoords, boxSize, scale) {
  477. this.drawIconResizeBoxBase(centerCoords, boxSize, scale);
  478. drawFilledPolygon(shapeArrowNW, colors.resizeBoxArrowFill, centerCoords, scale);
  479. drawFilledPolygon(shapeArrowSE, colors.resizeBoxArrowFill, centerCoords, scale);
  480. };
  481. /* Crop Area */
  482. this.drawCropArea=function(image, centerCoords, size, fnDrawClipPath) {
  483. var xRatio=image.width/ctx.canvas.width,
  484. yRatio=image.height/ctx.canvas.height,
  485. xLeft=centerCoords[0]-size/2,
  486. yTop=centerCoords[1]-size/2;
  487. ctx.save();
  488. ctx.strokeStyle = colors.areaOutline;
  489. ctx.lineWidth = 2;
  490. ctx.beginPath();
  491. fnDrawClipPath(ctx, centerCoords, size);
  492. ctx.stroke();
  493. ctx.clip();
  494. // draw part of original image
  495. if (size > 0) {
  496. ctx.drawImage(image, xLeft*xRatio, yTop*yRatio, size*xRatio, size*yRatio, xLeft, yTop, size, size);
  497. }
  498. ctx.beginPath();
  499. fnDrawClipPath(ctx, centerCoords, size);
  500. ctx.stroke();
  501. ctx.clip();
  502. ctx.restore();
  503. };
  504. };
  505. }]);
  506. /**
  507. * EXIF service is based on the exif-js library (https://github.com/jseidelin/exif-js)
  508. */
  509. crop.service('cropEXIF', [function() {
  510. var debug = false;
  511. var ExifTags = this.Tags = {
  512. // version tags
  513. 0x9000 : "ExifVersion", // EXIF version
  514. 0xA000 : "FlashpixVersion", // Flashpix format version
  515. // colorspace tags
  516. 0xA001 : "ColorSpace", // Color space information tag
  517. // image configuration
  518. 0xA002 : "PixelXDimension", // Valid width of meaningful image
  519. 0xA003 : "PixelYDimension", // Valid height of meaningful image
  520. 0x9101 : "ComponentsConfiguration", // Information about channels
  521. 0x9102 : "CompressedBitsPerPixel", // Compressed bits per pixel
  522. // user information
  523. 0x927C : "MakerNote", // Any desired information written by the manufacturer
  524. 0x9286 : "UserComment", // Comments by user
  525. // related file
  526. 0xA004 : "RelatedSoundFile", // Name of related sound file
  527. // date and time
  528. 0x9003 : "DateTimeOriginal", // Date and time when the original image was generated
  529. 0x9004 : "DateTimeDigitized", // Date and time when the image was stored digitally
  530. 0x9290 : "SubsecTime", // Fractions of seconds for DateTime
  531. 0x9291 : "SubsecTimeOriginal", // Fractions of seconds for DateTimeOriginal
  532. 0x9292 : "SubsecTimeDigitized", // Fractions of seconds for DateTimeDigitized
  533. // picture-taking conditions
  534. 0x829A : "ExposureTime", // Exposure time (in seconds)
  535. 0x829D : "FNumber", // F number
  536. 0x8822 : "ExposureProgram", // Exposure program
  537. 0x8824 : "SpectralSensitivity", // Spectral sensitivity
  538. 0x8827 : "ISOSpeedRatings", // ISO speed rating
  539. 0x8828 : "OECF", // Optoelectric conversion factor
  540. 0x9201 : "ShutterSpeedValue", // Shutter speed
  541. 0x9202 : "ApertureValue", // Lens aperture
  542. 0x9203 : "BrightnessValue", // Value of brightness
  543. 0x9204 : "ExposureBias", // Exposure bias
  544. 0x9205 : "MaxApertureValue", // Smallest F number of lens
  545. 0x9206 : "SubjectDistance", // Distance to subject in meters
  546. 0x9207 : "MeteringMode", // Metering mode
  547. 0x9208 : "LightSource", // Kind of light source
  548. 0x9209 : "Flash", // Flash status
  549. 0x9214 : "SubjectArea", // Location and area of main subject
  550. 0x920A : "FocalLength", // Focal length of the lens in mm
  551. 0xA20B : "FlashEnergy", // Strobe energy in BCPS
  552. 0xA20C : "SpatialFrequencyResponse", //
  553. 0xA20E : "FocalPlaneXResolution", // Number of pixels in width direction per FocalPlaneResolutionUnit
  554. 0xA20F : "FocalPlaneYResolution", // Number of pixels in height direction per FocalPlaneResolutionUnit
  555. 0xA210 : "FocalPlaneResolutionUnit", // Unit for measuring FocalPlaneXResolution and FocalPlaneYResolution
  556. 0xA214 : "SubjectLocation", // Location of subject in image
  557. 0xA215 : "ExposureIndex", // Exposure index selected on camera
  558. 0xA217 : "SensingMethod", // Image sensor type
  559. 0xA300 : "FileSource", // Image source (3 == DSC)
  560. 0xA301 : "SceneType", // Scene type (1 == directly photographed)
  561. 0xA302 : "CFAPattern", // Color filter array geometric pattern
  562. 0xA401 : "CustomRendered", // Special processing
  563. 0xA402 : "ExposureMode", // Exposure mode
  564. 0xA403 : "WhiteBalance", // 1 = auto white balance, 2 = manual
  565. 0xA404 : "DigitalZoomRation", // Digital zoom ratio
  566. 0xA405 : "FocalLengthIn35mmFilm", // Equivalent foacl length assuming 35mm film camera (in mm)
  567. 0xA406 : "SceneCaptureType", // Type of scene
  568. 0xA407 : "GainControl", // Degree of overall image gain adjustment
  569. 0xA408 : "Contrast", // Direction of contrast processing applied by camera
  570. 0xA409 : "Saturation", // Direction of saturation processing applied by camera
  571. 0xA40A : "Sharpness", // Direction of sharpness processing applied by camera
  572. 0xA40B : "DeviceSettingDescription", //
  573. 0xA40C : "SubjectDistanceRange", // Distance to subject
  574. // other tags
  575. 0xA005 : "InteroperabilityIFDPointer",
  576. 0xA420 : "ImageUniqueID" // Identifier assigned uniquely to each image
  577. };
  578. var TiffTags = this.TiffTags = {
  579. 0x0100 : "ImageWidth",
  580. 0x0101 : "ImageHeight",
  581. 0x8769 : "ExifIFDPointer",
  582. 0x8825 : "GPSInfoIFDPointer",
  583. 0xA005 : "InteroperabilityIFDPointer",
  584. 0x0102 : "BitsPerSample",
  585. 0x0103 : "Compression",
  586. 0x0106 : "PhotometricInterpretation",
  587. 0x0112 : "Orientation",
  588. 0x0115 : "SamplesPerPixel",
  589. 0x011C : "PlanarConfiguration",
  590. 0x0212 : "YCbCrSubSampling",
  591. 0x0213 : "YCbCrPositioning",
  592. 0x011A : "XResolution",
  593. 0x011B : "YResolution",
  594. 0x0128 : "ResolutionUnit",
  595. 0x0111 : "StripOffsets",
  596. 0x0116 : "RowsPerStrip",
  597. 0x0117 : "StripByteCounts",
  598. 0x0201 : "JPEGInterchangeFormat",
  599. 0x0202 : "JPEGInterchangeFormatLength",
  600. 0x012D : "TransferFunction",
  601. 0x013E : "WhitePoint",
  602. 0x013F : "PrimaryChromaticities",
  603. 0x0211 : "YCbCrCoefficients",
  604. 0x0214 : "ReferenceBlackWhite",
  605. 0x0132 : "DateTime",
  606. 0x010E : "ImageDescription",
  607. 0x010F : "Make",
  608. 0x0110 : "Model",
  609. 0x0131 : "Software",
  610. 0x013B : "Artist",
  611. 0x8298 : "Copyright"
  612. };
  613. var GPSTags = this.GPSTags = {
  614. 0x0000 : "GPSVersionID",
  615. 0x0001 : "GPSLatitudeRef",
  616. 0x0002 : "GPSLatitude",
  617. 0x0003 : "GPSLongitudeRef",
  618. 0x0004 : "GPSLongitude",
  619. 0x0005 : "GPSAltitudeRef",
  620. 0x0006 : "GPSAltitude",
  621. 0x0007 : "GPSTimeStamp",
  622. 0x0008 : "GPSSatellites",
  623. 0x0009 : "GPSStatus",
  624. 0x000A : "GPSMeasureMode",
  625. 0x000B : "GPSDOP",
  626. 0x000C : "GPSSpeedRef",
  627. 0x000D : "GPSSpeed",
  628. 0x000E : "GPSTrackRef",
  629. 0x000F : "GPSTrack",
  630. 0x0010 : "GPSImgDirectionRef",
  631. 0x0011 : "GPSImgDirection",
  632. 0x0012 : "GPSMapDatum",
  633. 0x0013 : "GPSDestLatitudeRef",
  634. 0x0014 : "GPSDestLatitude",
  635. 0x0015 : "GPSDestLongitudeRef",
  636. 0x0016 : "GPSDestLongitude",
  637. 0x0017 : "GPSDestBearingRef",
  638. 0x0018 : "GPSDestBearing",
  639. 0x0019 : "GPSDestDistanceRef",
  640. 0x001A : "GPSDestDistance",
  641. 0x001B : "GPSProcessingMethod",
  642. 0x001C : "GPSAreaInformation",
  643. 0x001D : "GPSDateStamp",
  644. 0x001E : "GPSDifferential"
  645. };
  646. var StringValues = this.StringValues = {
  647. ExposureProgram : {
  648. 0 : "Not defined",
  649. 1 : "Manual",
  650. 2 : "Normal program",
  651. 3 : "Aperture priority",
  652. 4 : "Shutter priority",
  653. 5 : "Creative program",
  654. 6 : "Action program",
  655. 7 : "Portrait mode",
  656. 8 : "Landscape mode"
  657. },
  658. MeteringMode : {
  659. 0 : "Unknown",
  660. 1 : "Average",
  661. 2 : "CenterWeightedAverage",
  662. 3 : "Spot",
  663. 4 : "MultiSpot",
  664. 5 : "Pattern",
  665. 6 : "Partial",
  666. 255 : "Other"
  667. },
  668. LightSource : {
  669. 0 : "Unknown",
  670. 1 : "Daylight",
  671. 2 : "Fluorescent",
  672. 3 : "Tungsten (incandescent light)",
  673. 4 : "Flash",
  674. 9 : "Fine weather",
  675. 10 : "Cloudy weather",
  676. 11 : "Shade",
  677. 12 : "Daylight fluorescent (D 5700 - 7100K)",
  678. 13 : "Day white fluorescent (N 4600 - 5400K)",
  679. 14 : "Cool white fluorescent (W 3900 - 4500K)",
  680. 15 : "White fluorescent (WW 3200 - 3700K)",
  681. 17 : "Standard light A",
  682. 18 : "Standard light B",
  683. 19 : "Standard light C",
  684. 20 : "D55",
  685. 21 : "D65",
  686. 22 : "D75",
  687. 23 : "D50",
  688. 24 : "ISO studio tungsten",
  689. 255 : "Other"
  690. },
  691. Flash : {
  692. 0x0000 : "Flash did not fire",
  693. 0x0001 : "Flash fired",
  694. 0x0005 : "Strobe return light not detected",
  695. 0x0007 : "Strobe return light detected",
  696. 0x0009 : "Flash fired, compulsory flash mode",
  697. 0x000D : "Flash fired, compulsory flash mode, return light not detected",
  698. 0x000F : "Flash fired, compulsory flash mode, return light detected",
  699. 0x0010 : "Flash did not fire, compulsory flash mode",
  700. 0x0018 : "Flash did not fire, auto mode",
  701. 0x0019 : "Flash fired, auto mode",
  702. 0x001D : "Flash fired, auto mode, return light not detected",
  703. 0x001F : "Flash fired, auto mode, return light detected",
  704. 0x0020 : "No flash function",
  705. 0x0041 : "Flash fired, red-eye reduction mode",
  706. 0x0045 : "Flash fired, red-eye reduction mode, return light not detected",
  707. 0x0047 : "Flash fired, red-eye reduction mode, return light detected",
  708. 0x0049 : "Flash fired, compulsory flash mode, red-eye reduction mode",
  709. 0x004D : "Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected",
  710. 0x004F : "Flash fired, compulsory flash mode, red-eye reduction mode, return light detected",
  711. 0x0059 : "Flash fired, auto mode, red-eye reduction mode",
  712. 0x005D : "Flash fired, auto mode, return light not detected, red-eye reduction mode",
  713. 0x005F : "Flash fired, auto mode, return light detected, red-eye reduction mode"
  714. },
  715. SensingMethod : {
  716. 1 : "Not defined",
  717. 2 : "One-chip color area sensor",
  718. 3 : "Two-chip color area sensor",
  719. 4 : "Three-chip color area sensor",
  720. 5 : "Color sequential area sensor",
  721. 7 : "Trilinear sensor",
  722. 8 : "Color sequential linear sensor"
  723. },
  724. SceneCaptureType : {
  725. 0 : "Standard",
  726. 1 : "Landscape",
  727. 2 : "Portrait",
  728. 3 : "Night scene"
  729. },
  730. SceneType : {
  731. 1 : "Directly photographed"
  732. },
  733. CustomRendered : {
  734. 0 : "Normal process",
  735. 1 : "Custom process"
  736. },
  737. WhiteBalance : {
  738. 0 : "Auto white balance",
  739. 1 : "Manual white balance"
  740. },
  741. GainControl : {
  742. 0 : "None",
  743. 1 : "Low gain up",
  744. 2 : "High gain up",
  745. 3 : "Low gain down",
  746. 4 : "High gain down"
  747. },
  748. Contrast : {
  749. 0 : "Normal",
  750. 1 : "Soft",
  751. 2 : "Hard"
  752. },
  753. Saturation : {
  754. 0 : "Normal",
  755. 1 : "Low saturation",
  756. 2 : "High saturation"
  757. },
  758. Sharpness : {
  759. 0 : "Normal",
  760. 1 : "Soft",
  761. 2 : "Hard"
  762. },
  763. SubjectDistanceRange : {
  764. 0 : "Unknown",
  765. 1 : "Macro",
  766. 2 : "Close view",
  767. 3 : "Distant view"
  768. },
  769. FileSource : {
  770. 3 : "DSC"
  771. },
  772. Components : {
  773. 0 : "",
  774. 1 : "Y",
  775. 2 : "Cb",
  776. 3 : "Cr",
  777. 4 : "R",
  778. 5 : "G",
  779. 6 : "B"
  780. }
  781. };
  782. function addEvent(element, event, handler) {
  783. if (element.addEventListener) {
  784. element.addEventListener(event, handler, false);
  785. } else if (element.attachEvent) {
  786. element.attachEvent("on" + event, handler);
  787. }
  788. }
  789. function imageHasData(img) {
  790. return !!(img.exifdata);
  791. }
  792. function base64ToArrayBuffer(base64, contentType) {
  793. contentType = contentType || base64.match(/^data\:([^\;]+)\;base64,/mi)[1] || ''; // e.g. 'data:image/jpeg;base64,...' => 'image/jpeg'
  794. base64 = base64.replace(/^data\:([^\;]+)\;base64,/gmi, '');
  795. var binary = atob(base64);
  796. var len = binary.length;
  797. var buffer = new ArrayBuffer(len);
  798. var view = new Uint8Array(buffer);
  799. for (var i = 0; i < len; i++) {
  800. view[i] = binary.charCodeAt(i);
  801. }
  802. return buffer;
  803. }
  804. function objectURLToBlob(url, callback) {
  805. var http = new XMLHttpRequest();
  806. http.open("GET", url, true);
  807. http.responseType = "blob";
  808. http.onload = function(e) {
  809. if (this.status == 200 || this.status === 0) {
  810. callback(this.response);
  811. }
  812. };
  813. http.send();
  814. }
  815. function getImageData(img, callback) {
  816. function handleBinaryFile(binFile) {
  817. var data = findEXIFinJPEG(binFile);
  818. var iptcdata = findIPTCinJPEG(binFile);
  819. img.exifdata = data || {};
  820. img.iptcdata = iptcdata || {};
  821. if (callback) {
  822. callback.call(img);
  823. }
  824. }
  825. if (img.src) {
  826. if (/^data\:/i.test(img.src)) { // Data URI
  827. var arrayBuffer = base64ToArrayBuffer(img.src);
  828. handleBinaryFile(arrayBuffer);
  829. } else if (/^blob\:/i.test(img.src)) { // Object URL
  830. var fileReader = new FileReader();
  831. fileReader.onload = function(e) {
  832. handleBinaryFile(e.target.result);
  833. };
  834. objectURLToBlob(img.src, function (blob) {
  835. fileReader.readAsArrayBuffer(blob);
  836. });
  837. } else {
  838. var http = new XMLHttpRequest();
  839. http.onload = function() {
  840. if (this.status == 200 || this.status === 0) {
  841. handleBinaryFile(http.response);
  842. } else {
  843. throw "Could not load image";
  844. }
  845. http = null;
  846. };
  847. http.open("GET", img.src, true);
  848. http.responseType = "arraybuffer";
  849. http.send(null);
  850. }
  851. } else if (window.FileReader && (img instanceof window.Blob || img instanceof window.File)) {
  852. var fileReader = new FileReader();
  853. fileReader.onload = function(e) {
  854. if (debug) console.log("Got file of length " + e.target.result.byteLength);
  855. handleBinaryFile(e.target.result);
  856. };
  857. fileReader.readAsArrayBuffer(img);
  858. }
  859. }
  860. function findEXIFinJPEG(file) {
  861. var dataView = new DataView(file);
  862. if (debug) console.log("Got file of length " + file.byteLength);
  863. if ((dataView.getUint8(0) != 0xFF) || (dataView.getUint8(1) != 0xD8)) {
  864. if (debug) console.log("Not a valid JPEG");
  865. return false; // not a valid jpeg
  866. }
  867. var offset = 2,
  868. length = file.byteLength,
  869. marker;
  870. while (offset < length) {
  871. if (dataView.getUint8(offset) != 0xFF) {
  872. if (debug) console.log("Not a valid marker at offset " + offset + ", found: " + dataView.getUint8(offset));
  873. return false; // not a valid marker, something is wrong
  874. }
  875. marker = dataView.getUint8(offset + 1);
  876. if (debug) console.log(marker);
  877. // we could implement handling for other markers here,
  878. // but we're only looking for 0xFFE1 for EXIF data
  879. if (marker == 225) {
  880. if (debug) console.log("Found 0xFFE1 marker");
  881. return readEXIFData(dataView, offset + 4, dataView.getUint16(offset + 2) - 2);
  882. // offset += 2 + file.getShortAt(offset+2, true);
  883. } else {
  884. offset += 2 + dataView.getUint16(offset+2);
  885. }
  886. }
  887. }
  888. function findIPTCinJPEG(file) {
  889. var dataView = new DataView(file);
  890. if (debug) console.log("Got file of length " + file.byteLength);
  891. if ((dataView.getUint8(0) != 0xFF) || (dataView.getUint8(1) != 0xD8)) {
  892. if (debug) console.log("Not a valid JPEG");
  893. return false; // not a valid jpeg
  894. }
  895. var offset = 2,
  896. length = file.byteLength;
  897. var isFieldSegmentStart = function(dataView, offset){
  898. return (
  899. dataView.getUint8(offset) === 0x38 &&
  900. dataView.getUint8(offset+1) === 0x42 &&
  901. dataView.getUint8(offset+2) === 0x49 &&
  902. dataView.getUint8(offset+3) === 0x4D &&
  903. dataView.getUint8(offset+4) === 0x04 &&
  904. dataView.getUint8(offset+5) === 0x04
  905. );
  906. };
  907. while (offset < length) {
  908. if ( isFieldSegmentStart(dataView, offset )){
  909. // Get the length of the name header (which is padded to an even number of bytes)
  910. var nameHeaderLength = dataView.getUint8(offset+7);
  911. if(nameHeaderLength % 2 !== 0) nameHeaderLength += 1;
  912. // Check for pre photoshop 6 format
  913. if(nameHeaderLength === 0) {
  914. // Always 4
  915. nameHeaderLength = 4;
  916. }
  917. var startOffset = offset + 8 + nameHeaderLength;
  918. var sectionLength = dataView.getUint16(offset + 6 + nameHeaderLength);
  919. return readIPTCData(file, startOffset, sectionLength);
  920. break;
  921. }
  922. // Not the marker, continue searching
  923. offset++;
  924. }
  925. }
  926. var IptcFieldMap = {
  927. 0x78 : 'caption',
  928. 0x6E : 'credit',
  929. 0x19 : 'keywords',
  930. 0x37 : 'dateCreated',
  931. 0x50 : 'byline',
  932. 0x55 : 'bylineTitle',
  933. 0x7A : 'captionWriter',
  934. 0x69 : 'headline',
  935. 0x74 : 'copyright',
  936. 0x0F : 'category'
  937. };
  938. function readIPTCData(file, startOffset, sectionLength){
  939. var dataView = new DataView(file);
  940. var data = {};
  941. var fieldValue, fieldName, dataSize, segmentType, segmentSize;
  942. var segmentStartPos = startOffset;
  943. while(segmentStartPos < startOffset+sectionLength) {
  944. if(dataView.getUint8(segmentStartPos) === 0x1C && dataView.getUint8(segmentStartPos+1) === 0x02){
  945. segmentType = dataView.getUint8(segmentStartPos+2);
  946. if(segmentType in IptcFieldMap) {
  947. dataSize = dataView.getInt16(segmentStartPos+3);
  948. segmentSize = dataSize + 5;
  949. fieldName = IptcFieldMap[segmentType];
  950. fieldValue = getStringFromDB(dataView, segmentStartPos+5, dataSize);
  951. // Check if we already stored a value with this name
  952. if(data.hasOwnProperty(fieldName)) {
  953. // Value already stored with this name, create multivalue field
  954. if(data[fieldName] instanceof Array) {
  955. data[fieldName].push(fieldValue);
  956. }
  957. else {
  958. data[fieldName] = [data[fieldName], fieldValue];
  959. }
  960. }
  961. else {
  962. data[fieldName] = fieldValue;
  963. }
  964. }
  965. }
  966. segmentStartPos++;
  967. }
  968. return data;
  969. }
  970. function readTags(file, tiffStart, dirStart, strings, bigEnd) {
  971. var entries = file.getUint16(dirStart, !bigEnd),
  972. tags = {},
  973. entryOffset, tag,
  974. i;
  975. for (i=0;i<entries;i++) {
  976. entryOffset = dirStart + i*12 + 2;
  977. tag = strings[file.getUint16(entryOffset, !bigEnd)];
  978. if (!tag && debug) console.log("Unknown tag: " + file.getUint16(entryOffset, !bigEnd));
  979. tags[tag] = readTagValue(file, entryOffset, tiffStart, dirStart, bigEnd);
  980. }
  981. return tags;
  982. }
  983. function readTagValue(file, entryOffset, tiffStart, dirStart, bigEnd) {
  984. var type = file.getUint16(entryOffset+2, !bigEnd),
  985. numValues = file.getUint32(entryOffset+4, !bigEnd),
  986. valueOffset = file.getUint32(entryOffset+8, !bigEnd) + tiffStart,
  987. offset,
  988. vals, val, n,
  989. numerator, denominator;
  990. switch (type) {
  991. case 1: // byte, 8-bit unsigned int
  992. case 7: // undefined, 8-bit byte, value depending on field
  993. if (numValues == 1) {
  994. return file.getUint8(entryOffset + 8, !bigEnd);
  995. } else {
  996. offset = numValues > 4 ? valueOffset : (entryOffset + 8);
  997. vals = [];
  998. for (n=0;n<numValues;n++) {
  999. vals[n] = file.getUint8(offset + n);
  1000. }
  1001. return vals;
  1002. }
  1003. case 2: // ascii, 8-bit byte
  1004. offset = numValues > 4 ? valueOffset : (entryOffset + 8);
  1005. return getStringFromDB(file, offset, numValues-1);
  1006. case 3: // short, 16 bit int
  1007. if (numValues == 1) {
  1008. return file.getUint16(entryOffset + 8, !bigEnd);
  1009. } else {
  1010. offset = numValues > 2 ? valueOffset : (entryOffset + 8);
  1011. vals = [];
  1012. for (n=0;n<numValues;n++) {
  1013. vals[n] = file.getUint16(offset + 2*n, !bigEnd);
  1014. }
  1015. return vals;
  1016. }
  1017. case 4: // long, 32 bit int
  1018. if (numValues == 1) {
  1019. return file.getUint32(entryOffset + 8, !bigEnd);
  1020. } else {
  1021. vals = [];
  1022. for (n=0;n<numValues;n++) {
  1023. vals[n] = file.getUint32(valueOffset + 4*n, !bigEnd);
  1024. }
  1025. return vals;
  1026. }
  1027. case 5: // rational = two long values, first is numerator, second is denominator
  1028. if (numValues == 1) {
  1029. numerator = file.getUint32(valueOffset, !bigEnd);
  1030. denominator = file.getUint32(valueOffset+4, !bigEnd);
  1031. val = new Number(numerator / denominator);
  1032. val.numerator = numerator;
  1033. val.denominator = denominator;
  1034. return val;
  1035. } else {
  1036. vals = [];
  1037. for (n=0;n<numValues;n++) {
  1038. numerator = file.getUint32(valueOffset + 8*n, !bigEnd);
  1039. denominator = file.getUint32(valueOffset+4 + 8*n, !bigEnd);
  1040. vals[n] = new Number(numerator / denominator);
  1041. vals[n].numerator = numerator;
  1042. vals[n].denominator = denominator;
  1043. }
  1044. return vals;
  1045. }
  1046. case 9: // slong, 32 bit signed int
  1047. if (numValues == 1) {
  1048. return file.getInt32(entryOffset + 8, !bigEnd);
  1049. } else {
  1050. vals = [];
  1051. for (n=0;n<numValues;n++) {
  1052. vals[n] = file.getInt32(valueOffset + 4*n, !bigEnd);
  1053. }
  1054. return vals;
  1055. }
  1056. case 10: // signed rational, two slongs, first is numerator, second is denominator
  1057. if (numValues == 1) {
  1058. return file.getInt32(valueOffset, !bigEnd) / file.getInt32(valueOffset+4, !bigEnd);
  1059. } else {
  1060. vals = [];
  1061. for (n=0;n<numValues;n++) {
  1062. vals[n] = file.getInt32(valueOffset + 8*n, !bigEnd) / file.getInt32(valueOffset+4 + 8*n, !bigEnd);
  1063. }
  1064. return vals;
  1065. }
  1066. }
  1067. }
  1068. function getStringFromDB(buffer, start, length) {
  1069. var outstr = "";
  1070. for (var n = start; n < start+length; n++) {
  1071. outstr += String.fromCharCode(buffer.getUint8(n));
  1072. }
  1073. return outstr;
  1074. }
  1075. function readEXIFData(file, start) {
  1076. if (getStringFromDB(file, start, 4) != "Exif") {
  1077. if (debug) console.log("Not valid EXIF data! " + getStringFromDB(file, start, 4));
  1078. return false;
  1079. }
  1080. var bigEnd,
  1081. tags, tag,
  1082. exifData, gpsData,
  1083. tiffOffset = start + 6;
  1084. // test for TIFF validity and endianness
  1085. if (file.getUint16(tiffOffset) == 0x4949) {
  1086. bigEnd = false;
  1087. } else if (file.getUint16(tiffOffset) == 0x4D4D) {
  1088. bigEnd = true;
  1089. } else {
  1090. if (debug) console.log("Not valid TIFF data! (no 0x4949 or 0x4D4D)");
  1091. return false;
  1092. }
  1093. if (file.getUint16(tiffOffset+2, !bigEnd) != 0x002A) {
  1094. if (debug) console.log("Not valid TIFF data! (no 0x002A)");
  1095. return false;
  1096. }
  1097. var firstIFDOffset = file.getUint32(tiffOffset+4, !bigEnd);
  1098. if (firstIFDOffset < 0x00000008) {
  1099. if (debug) console.log("Not valid TIFF data! (First offset less than 8)", file.getUint32(tiffOffset+4, !bigEnd));
  1100. return false;
  1101. }
  1102. tags = readTags(file, tiffOffset, tiffOffset + firstIFDOffset, TiffTags, bigEnd);
  1103. if (tags.ExifIFDPointer) {
  1104. exifData = readTags(file, tiffOffset, tiffOffset + tags.ExifIFDPointer, ExifTags, bigEnd);
  1105. for (tag in exifData) {
  1106. switch (tag) {
  1107. case "LightSource" :
  1108. case "Flash" :
  1109. case "MeteringMode" :
  1110. case "ExposureProgram" :
  1111. case "SensingMethod" :
  1112. case "SceneCaptureType" :
  1113. case "SceneType" :
  1114. case "CustomRendered" :
  1115. case "WhiteBalance" :
  1116. case "GainControl" :
  1117. case "Contrast" :
  1118. case "Saturation" :
  1119. case "Sharpness" :
  1120. case "SubjectDistanceRange" :
  1121. case "FileSource" :
  1122. exifData[tag] = StringValues[tag][exifData[tag]];
  1123. break;
  1124. case "ExifVersion" :
  1125. case "FlashpixVersion" :
  1126. exifData[tag] = String.fromCharCode(exifData[tag][0], exifData[tag][1], exifData[tag][2], exifData[tag][3]);
  1127. break;
  1128. case "ComponentsConfiguration" :
  1129. exifData[tag] =
  1130. StringValues.Components[exifData[tag][0]] +
  1131. StringValues.Components[exifData[tag][1]] +
  1132. StringValues.Components[exifData[tag][2]] +
  1133. StringValues.Components[exifData[tag][3]];
  1134. break;
  1135. }
  1136. tags[tag] = exifData[tag];
  1137. }
  1138. }
  1139. if (tags.GPSInfoIFDPointer) {
  1140. gpsData = readTags(file, tiffOffset, tiffOffset + tags.GPSInfoIFDPointer, GPSTags, bigEnd);
  1141. for (tag in gpsData) {
  1142. switch (tag) {
  1143. case "GPSVersionID" :
  1144. gpsData[tag] = gpsData[tag][0] +
  1145. "." + gpsData[tag][1] +
  1146. "." + gpsData[tag][2] +
  1147. "." + gpsData[tag][3];
  1148. break;
  1149. }
  1150. tags[tag] = gpsData[tag];
  1151. }
  1152. }
  1153. return tags;
  1154. }
  1155. this.getData = function(img, callback) {
  1156. if ((img instanceof Image || img instanceof HTMLImageElement) && !img.complete) return false;
  1157. if (!imageHasData(img)) {
  1158. getImageData(img, callback);
  1159. } else {
  1160. if (callback) {
  1161. callback.call(img);
  1162. }
  1163. }
  1164. return true;
  1165. }
  1166. this.getTag = function(img, tag) {
  1167. if (!imageHasData(img)) return;
  1168. return img.exifdata[tag];
  1169. }
  1170. this.getAllTags = function(img) {
  1171. if (!imageHasData(img)) return {};
  1172. var a,
  1173. data = img.exifdata,
  1174. tags = {};
  1175. for (a in data) {
  1176. if (data.hasOwnProperty(a)) {
  1177. tags[a] = data[a];
  1178. }
  1179. }
  1180. return tags;
  1181. }
  1182. this.pretty = function(img) {
  1183. if (!imageHasData(img)) return "";
  1184. var a,
  1185. data = img.exifdata,
  1186. strPretty = "";
  1187. for (a in data) {
  1188. if (data.hasOwnProperty(a)) {
  1189. if (typeof data[a] == "object") {
  1190. if (data[a] instanceof Number) {
  1191. strPretty += a + " : " + data[a] + " [" + data[a].numerator + "/" + data[a].denominator + "]\r\n";
  1192. } else {
  1193. strPretty += a + " : [" + data[a].length + " values]\r\n";
  1194. }
  1195. } else {
  1196. strPretty += a + " : " + data[a] + "\r\n";
  1197. }
  1198. }
  1199. }
  1200. return strPretty;
  1201. }
  1202. this.readFromBinaryFile = function(file) {
  1203. return findEXIFinJPEG(file);
  1204. }
  1205. }]);
  1206. crop.factory('cropHost', ['$document', 'cropAreaCircle', 'cropAreaSquare', 'cropEXIF', function($document, CropAreaCircle, CropAreaSquare, cropEXIF) {
  1207. /* STATIC FUNCTIONS */
  1208. // Get Element's Offset
  1209. var getElementOffset=function(elem) {
  1210. var box = elem.getBoundingClientRect();
  1211. var body = document.body;
  1212. var docElem = document.documentElement;
  1213. var scrollTop = window.pageYOffset || docElem.scrollTop || body.scrollTop;
  1214. var scrollLeft = window.pageXOffset || docElem.scrollLeft || body.scrollLeft;
  1215. var clientTop = docElem.clientTop || body.clientTop || 0;
  1216. var clientLeft = docElem.clientLeft || body.clientLeft || 0;
  1217. var top = box.top + scrollTop - clientTop;
  1218. var left = box.left + scrollLeft - clientLeft;
  1219. return { top: Math.round(top), left: Math.round(left) };
  1220. };
  1221. return function(elCanvas, opts, events){
  1222. /* PRIVATE VARIABLES */
  1223. // Object Pointers
  1224. var ctx=null,
  1225. image=null,
  1226. theArea=null;
  1227. // Dimensions
  1228. var minCanvasDims=[100,100],
  1229. maxCanvasDims=[300,300];
  1230. // Result Image size
  1231. var resImgSize=200;
  1232. // Result Image type
  1233. var resImgFormat='image/png';
  1234. // Result Image quality
  1235. var resImgQuality=null;
  1236. /* PRIVATE FUNCTIONS */
  1237. // Draw Scene
  1238. function drawScene() {
  1239. // clear canvas
  1240. ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
  1241. if(image!==null) {
  1242. // draw source image
  1243. ctx.drawImage(image, 0, 0, ctx.canvas.width, ctx.canvas.height);
  1244. ctx.save();
  1245. // and make it darker
  1246. ctx.fillStyle = 'rgba(0, 0, 0, 0.65)';
  1247. ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
  1248. ctx.restore();
  1249. // draw Area
  1250. theArea.draw();
  1251. }
  1252. }
  1253. // Resets CropHost
  1254. var resetCropHost=function() {
  1255. if(image!==null) {
  1256. theArea.setImage(image);
  1257. var imageDims=[image.width, image.height],
  1258. imageRatio=image.width/image.height,
  1259. canvasDims=imageDims;
  1260. if(canvasDims[0]>maxCanvasDims[0]) {
  1261. canvasDims[0]=maxCanvasDims[0];
  1262. canvasDims[1]=canvasDims[0]/imageRatio;
  1263. } else if(canvasDims[0]<minCanvasDims[0]) {
  1264. canvasDims[0]=minCanvasDims[0];
  1265. canvasDims[1]=canvasDims[0]/imageRatio;
  1266. }
  1267. if(canvasDims[1]>maxCanvasDims[1]) {
  1268. canvasDims[1]=maxCanvasDims[1];
  1269. canvasDims[0]=canvasDims[1]*imageRatio;
  1270. } else if(canvasDims[1]<minCanvasDims[1]) {
  1271. canvasDims[1]=minCanvasDims[1];
  1272. canvasDims[0]=canvasDims[1]*imageRatio;
  1273. }
  1274. elCanvas.prop('width',canvasDims[0]).prop('height',canvasDims[1]).css({'margin-left': -canvasDims[0]/2+'px', 'margin-top': -canvasDims[1]/2+'px'});
  1275. theArea.setX(ctx.canvas.width/2);
  1276. theArea.setY(ctx.canvas.height/2);
  1277. theArea.setSize(Math.min(200, ctx.canvas.width/2, ctx.canvas.height/2));
  1278. } else {
  1279. elCanvas.prop('width',0).prop('height',0).css({'margin-top': 0});
  1280. }
  1281. drawScene();
  1282. };
  1283. /**
  1284. * Returns event.changedTouches directly if event is a TouchEvent.
  1285. * If event is a jQuery event, return changedTouches of event.originalEvent
  1286. */
  1287. var getChangedTouches=function(event){
  1288. if(angular.isDefined(event.changedTouches)){
  1289. return event.changedTouches;
  1290. }else{
  1291. return event.originalEvent.changedTouches;
  1292. }
  1293. };
  1294. var onMouseMove=function(e) {
  1295. if(image!==null) {
  1296. var offset=getElementOffset(ctx.canvas),
  1297. pageX, pageY;
  1298. if(e.type === 'touchmove') {
  1299. pageX=getChangedTouches(e)[0].pageX;
  1300. pageY=getChangedTouches(e)[0].pageY;
  1301. } else {
  1302. pageX=e.pageX;
  1303. pageY=e.pageY;
  1304. }
  1305. theArea.processMouseMove(pageX-offset.left, pageY-offset.top);
  1306. drawScene();
  1307. }
  1308. };
  1309. var onMouseDown=function(e) {
  1310. e.preventDefault();
  1311. e.stopPropagation();
  1312. if(image!==null) {
  1313. var offset=getElementOffset(ctx.canvas),
  1314. pageX, pageY;
  1315. if(e.type === 'touchstart') {
  1316. pageX=getChangedTouches(e)[0].pageX;
  1317. pageY=getChangedTouches(e)[0].pageY;
  1318. } else {
  1319. pageX=e.pageX;
  1320. pageY=e.pageY;
  1321. }
  1322. theArea.processMouseDown(pageX-offset.left, pageY-offset.top);
  1323. drawScene();
  1324. }
  1325. };
  1326. var onMouseUp=function(e) {
  1327. if(image!==null) {
  1328. var offset=getElementOffset(ctx.canvas),
  1329. pageX, pageY;
  1330. if(e.type === 'touchend') {
  1331. pageX=getChangedTouches(e)[0].pageX;
  1332. pageY=getChangedTouches(e)[0].pageY;
  1333. } else {
  1334. pageX=e.pageX;
  1335. pageY=e.pageY;
  1336. }
  1337. theArea.processMouseUp(pageX-offset.left, pageY-offset.top);
  1338. drawScene();
  1339. }
  1340. };
  1341. this.getResultImageDataURI=function() {
  1342. var temp_ctx, temp_canvas;
  1343. temp_canvas = angular.element('<canvas></canvas>')[0];
  1344. temp_ctx = temp_canvas.getContext('2d');
  1345. temp_canvas.width = resImgSize;
  1346. temp_canvas.height = resImgSize;
  1347. if(image!==null){
  1348. 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);
  1349. }
  1350. if (resImgQuality!==null ){
  1351. return temp_canvas.toDataURL(resImgFormat, resImgQuality);
  1352. }
  1353. return temp_canvas.toDataURL(resImgFormat);
  1354. };
  1355. this.setNewImageSource=function(imageSource) {
  1356. image=null;
  1357. resetCropHost();
  1358. events.trigger('image-updated');
  1359. if(!!imageSource) {
  1360. var newImage = new Image();
  1361. if(imageSource.substring(0,4).toLowerCase()==='http') {
  1362. newImage.crossOrigin = 'anonymous';
  1363. }
  1364. newImage.onload = function(){
  1365. events.trigger('load-done');
  1366. cropEXIF.getData(newImage,function(){
  1367. var orientation=cropEXIF.getTag(newImage,'Orientation');
  1368. if([3,6,8].indexOf(orientation)>-1) {
  1369. var canvas = document.createElement("canvas"),
  1370. ctx=canvas.getContext("2d"),
  1371. cw = newImage.width, ch = newImage.height, cx = 0, cy = 0, deg=0;
  1372. switch(orientation) {
  1373. case 3:
  1374. cx=-newImage.width;
  1375. cy=-newImage.height;
  1376. deg=180;
  1377. break;
  1378. case 6:
  1379. cw = newImage.height;
  1380. ch = newImage.width;
  1381. cy=-newImage.height;
  1382. deg=90;
  1383. break;
  1384. case 8:
  1385. cw = newImage.height;
  1386. ch = newImage.width;
  1387. cx=-newImage.width;
  1388. deg=270;
  1389. break;
  1390. }
  1391. canvas.width = cw;
  1392. canvas.height = ch;
  1393. ctx.rotate(deg*Math.PI/180);
  1394. ctx.drawImage(newImage, cx, cy);
  1395. image=new Image();
  1396. image.src = canvas.toDataURL("image/png");
  1397. } else {
  1398. image=newImage;
  1399. }
  1400. resetCropHost();
  1401. events.trigger('image-updated');
  1402. });
  1403. };
  1404. newImage.onerror=function() {
  1405. events.trigger('load-error');
  1406. };
  1407. events.trigger('load-start');
  1408. newImage.src=imageSource;
  1409. }
  1410. };
  1411. this.setMaxDimensions=function(width, height) {
  1412. maxCanvasDims=[width,height];
  1413. if(image!==null) {
  1414. var curWidth=ctx.canvas.width,
  1415. curHeight=ctx.canvas.height;
  1416. var imageDims=[image.width, image.height],
  1417. imageRatio=image.width/image.height,
  1418. canvasDims=imageDims;
  1419. if(canvasDims[0]>maxCanvasDims[0]) {
  1420. canvasDims[0]=maxCanvasDims[0];
  1421. canvasDims[1]=canvasDims[0]/imageRatio;
  1422. } else if(canvasDims[0]<minCanvasDims[0]) {
  1423. canvasDims[0]=minCanvasDims[0];
  1424. canvasDims[1]=canvasDims[0]/imageRatio;
  1425. }
  1426. if(canvasDims[1]>maxCanvasDims[1]) {
  1427. canvasDims[1]=maxCanvasDims[1];
  1428. canvasDims[0]=canvasDims[1]*imageRatio;
  1429. } else if(canvasDims[1]<minCanvasDims[1]) {
  1430. canvasDims[1]=minCanvasDims[1];
  1431. canvasDims[0]=canvasDims[1]*imageRatio;
  1432. }
  1433. elCanvas.prop('width',canvasDims[0]).prop('height',canvasDims[1]).css({'margin-left': -canvasDims[0]/2+'px', 'margin-top': -canvasDims[1]/2+'px'});
  1434. var ratioNewCurWidth=ctx.canvas.width/curWidth,
  1435. ratioNewCurHeight=ctx.canvas.height/curHeight,
  1436. ratioMin=Math.min(ratioNewCurWidth, ratioNewCurHeight);
  1437. theArea.setX(theArea.getX()*ratioNewCurWidth);
  1438. theArea.setY(theArea.getY()*ratioNewCurHeight);
  1439. theArea.setSize(theArea.getSize()*ratioMin);
  1440. } else {
  1441. elCanvas.prop('width',0).prop('height',0).css({'margin-top': 0});
  1442. }
  1443. drawScene();
  1444. };
  1445. this.setAreaMinSize=function(size) {
  1446. size=parseInt(size,10);
  1447. if(!isNaN(size)) {
  1448. theArea.setMinSize(size);
  1449. drawScene();
  1450. }
  1451. };
  1452. this.setResultImageSize=function(size) {
  1453. size=parseInt(size,10);
  1454. if(!isNaN(size)) {
  1455. resImgSize=size;
  1456. }
  1457. };
  1458. this.setResultImageFormat=function(format) {
  1459. resImgFormat = format;
  1460. };
  1461. this.setResultImageQuality=function(quality){
  1462. quality = parseFloat(quality);
  1463. if (!isNaN(quality) && quality>=0 && quality<=1){
  1464. resImgQuality = quality;
  1465. }
  1466. };
  1467. this.setAreaType=function(type) {
  1468. var curSize=theArea.getSize(),
  1469. curMinSize=theArea.getMinSize(),
  1470. curX=theArea.getX(),
  1471. curY=theArea.getY();
  1472. var AreaClass=CropAreaCircle;
  1473. if(type==='square') {
  1474. AreaClass=CropAreaSquare;
  1475. }
  1476. theArea = new AreaClass(ctx, events);
  1477. theArea.setMinSize(curMinSize);
  1478. theArea.setSize(curSize);
  1479. theArea.setX(curX);
  1480. theArea.setY(curY);
  1481. // resetCropHost();
  1482. if(image!==null) {
  1483. theArea.setImage(image);
  1484. }
  1485. drawScene();
  1486. };
  1487. /* Life Cycle begins */
  1488. // Init Context var
  1489. ctx = elCanvas[0].getContext('2d');
  1490. // Init CropArea
  1491. theArea = new CropAreaCircle(ctx, events);
  1492. // Init Mouse Event Listeners
  1493. $document.on('mousemove',onMouseMove);
  1494. elCanvas.on('mousedown',onMouseDown);
  1495. $document.on('mouseup',onMouseUp);
  1496. // Init Touch Event Listeners
  1497. $document.on('touchmove',onMouseMove);
  1498. elCanvas.on('touchstart',onMouseDown);
  1499. $document.on('touchend',onMouseUp);
  1500. // CropHost Destructor
  1501. this.destroy=function() {
  1502. $document.off('mousemove',onMouseMove);
  1503. elCanvas.off('mousedown',onMouseDown);
  1504. $document.off('mouseup',onMouseMove);
  1505. $document.off('touchmove',onMouseMove);
  1506. elCanvas.off('touchstart',onMouseDown);
  1507. $document.off('touchend',onMouseMove);
  1508. elCanvas.remove();
  1509. };
  1510. };
  1511. }]);
  1512. crop.factory('cropPubSub', [function() {
  1513. return function() {
  1514. var events = {};
  1515. // Subscribe
  1516. this.on = function(names, handler) {
  1517. names.split(' ').forEach(function(name) {
  1518. if (!events[name]) {
  1519. events[name] = [];
  1520. }
  1521. events[name].push(handler);
  1522. });
  1523. return this;
  1524. };
  1525. // Publish
  1526. this.trigger = function(name, args) {
  1527. angular.forEach(events[name], function(handler) {
  1528. handler.call(null, args);
  1529. });
  1530. return this;
  1531. };
  1532. };
  1533. }]);
  1534. crop.directive('imgCrop', ['$timeout', 'cropHost', 'cropPubSub', function($timeout, CropHost, CropPubSub) {
  1535. return {
  1536. restrict: 'E',
  1537. scope: {
  1538. image: '=',
  1539. resultImage: '=',
  1540. changeOnFly: '=',
  1541. areaType: '@',
  1542. areaMinSize: '=',
  1543. resultImageSize: '=',
  1544. resultImageFormat: '@',
  1545. resultImageQuality: '=',
  1546. onChange: '&',
  1547. onLoadBegin: '&',
  1548. onLoadDone: '&',
  1549. onLoadError: '&'
  1550. },
  1551. template: '<canvas></canvas>',
  1552. controller: ['$scope', function($scope) {
  1553. $scope.events = new CropPubSub();
  1554. }],
  1555. link: function(scope, element/*, attrs*/) {
  1556. // Init Events Manager
  1557. var events = scope.events;
  1558. // Init Crop Host
  1559. var cropHost=new CropHost(element.find('canvas'), {}, events);
  1560. // Store Result Image to check if it's changed
  1561. var storedResultImage;
  1562. var updateResultImage=function(scope) {
  1563. var resultImage=cropHost.getResultImageDataURI();
  1564. if(storedResultImage!==resultImage) {
  1565. storedResultImage=resultImage;
  1566. if(angular.isDefined(scope.resultImage)) {
  1567. scope.resultImage=resultImage;
  1568. }
  1569. scope.onChange({$dataURI: scope.resultImage});
  1570. }
  1571. };
  1572. // Wrapper to safely exec functions within $apply on a running $digest cycle
  1573. var fnSafeApply=function(fn) {
  1574. return function(){
  1575. $timeout(function(){
  1576. scope.$apply(function(scope){
  1577. fn(scope);
  1578. });
  1579. });
  1580. };
  1581. };
  1582. // Setup CropHost Event Handlers
  1583. events
  1584. .on('load-start', fnSafeApply(function(scope){
  1585. scope.onLoadBegin({});
  1586. }))
  1587. .on('load-done', fnSafeApply(function(scope){
  1588. scope.onLoadDone({});
  1589. }))
  1590. .on('load-error', fnSafeApply(function(scope){
  1591. scope.onLoadError({});
  1592. }))
  1593. .on('area-move area-resize', fnSafeApply(function(scope){
  1594. if(!!scope.changeOnFly) {
  1595. updateResultImage(scope);
  1596. }
  1597. }))
  1598. .on('area-move-end area-resize-end image-updated', fnSafeApply(function(scope){
  1599. updateResultImage(scope);
  1600. }));
  1601. // Sync CropHost with Directive's options
  1602. scope.$watch('image',function(){
  1603. cropHost.setNewImageSource(scope.image);
  1604. });
  1605. scope.$watch('areaType',function(){
  1606. cropHost.setAreaType(scope.areaType);
  1607. updateResultImage(scope);
  1608. });
  1609. scope.$watch('areaMinSize',function(){
  1610. cropHost.setAreaMinSize(scope.areaMinSize);
  1611. updateResultImage(scope);
  1612. });
  1613. scope.$watch('resultImageSize',function(){
  1614. cropHost.setResultImageSize(scope.resultImageSize);
  1615. updateResultImage(scope);
  1616. });
  1617. scope.$watch('resultImageFormat',function(){
  1618. cropHost.setResultImageFormat(scope.resultImageFormat);
  1619. updateResultImage(scope);
  1620. });
  1621. scope.$watch('resultImageQuality',function(){
  1622. cropHost.setResultImageQuality(scope.resultImageQuality);
  1623. updateResultImage(scope);
  1624. });
  1625. // Update CropHost dimensions when the directive element is resized
  1626. scope.$watch(
  1627. function () {
  1628. return [element[0].clientWidth, element[0].clientHeight];
  1629. },
  1630. function (value) {
  1631. cropHost.setMaxDimensions(value[0],value[1]);
  1632. updateResultImage(scope);
  1633. },
  1634. true
  1635. );
  1636. // Destroy CropHost Instance when the directive is destroying
  1637. scope.$on('$destroy', function(){
  1638. cropHost.destroy();
  1639. });
  1640. }
  1641. };
  1642. }]);
  1643. }());