ng-img-crop.js 77 KB

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