Jcrop.js 74 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859
  1. /*! Jcrop.js v2.0.4 - build: 20151117
  2. * @copyright 2008-2015 Tapmodo Interactive LLC
  3. * @license Free software under MIT License
  4. * @website http://jcrop.org/
  5. **/
  6. (function($){
  7. 'use strict';
  8. // Jcrop constructor
  9. var Jcrop = function(element,opt){
  10. var _ua = navigator.userAgent.toLowerCase();
  11. this.opt = $.extend({},Jcrop.defaults,opt || {});
  12. this.container = $(element);
  13. this.opt.is_msie = /msie/.test(_ua);
  14. this.opt.is_ie_lt9 = /msie [1-8]\./.test(_ua);
  15. this.container.addClass(this.opt.css_container);
  16. this.ui = {};
  17. this.state = null;
  18. this.ui.multi = [];
  19. this.ui.selection = null;
  20. this.filter = {};
  21. this.init();
  22. this.setOptions(opt);
  23. this.applySizeConstraints();
  24. this.container.trigger('cropinit',this);
  25. // IE<9 doesn't work if mouse events are attached to window
  26. if (this.opt.is_ie_lt9)
  27. this.opt.dragEventTarget = document.body;
  28. };
  29. // Jcrop static functions
  30. $.extend(Jcrop,{
  31. component: { },
  32. filter: { },
  33. stage: { },
  34. registerComponent: function(name,component){
  35. Jcrop.component[name] = component;
  36. },
  37. registerFilter: function(name,filter){
  38. Jcrop.filter[name] = filter;
  39. },
  40. registerStageType: function(name,stage){
  41. Jcrop.stage[name] = stage;
  42. },
  43. // attach: function(element,opt){{{
  44. attach: function(element,opt){
  45. var obj = new $.Jcrop(element,opt);
  46. return obj;
  47. },
  48. // }}}
  49. // imgCopy: function(imgel){{{
  50. imgCopy: function(imgel){
  51. var img = new Image;
  52. img.src = imgel.src;
  53. return img;
  54. },
  55. // }}}
  56. // imageClone: function(imgel){{{
  57. imageClone: function(imgel){
  58. return $.Jcrop.supportsCanvas?
  59. Jcrop.canvasClone(imgel):
  60. Jcrop.imgCopy(imgel);
  61. },
  62. // }}}
  63. // canvasClone: function(imgel){{{
  64. canvasClone: function(imgel){
  65. var canvas = document.createElement('canvas'),
  66. ctx = canvas.getContext('2d');
  67. $(canvas).width(imgel.width).height(imgel.height),
  68. canvas.width = imgel.naturalWidth;
  69. canvas.height = imgel.naturalHeight;
  70. ctx.drawImage(imgel,0,0,imgel.naturalWidth,imgel.naturalHeight);
  71. return canvas;
  72. },
  73. // }}}
  74. // propagate: function(plist,config,obj){{{
  75. propagate: function(plist,config,obj){
  76. for(var i=0,l=plist.length;i<l;i++)
  77. if (config.hasOwnProperty(plist[i]))
  78. obj[plist[i]] = config[plist[i]];
  79. },
  80. // }}}
  81. // getLargestBox: function(ratio,w,h){{{
  82. getLargestBox: function(ratio,w,h){
  83. if ((w/h) > ratio)
  84. return [ h * ratio, h ];
  85. else return [ w, w / ratio ];
  86. },
  87. // }}}
  88. // stageConstructor: function(el,options,callback){{{
  89. stageConstructor: function(el,options,callback){
  90. // Get a priority-ordered list of available stages
  91. var stages = [];
  92. $.each(Jcrop.stage,function(i,e){
  93. stages.push(e);
  94. });
  95. stages.sort(function(a,b){ return a.priority - b.priority; });
  96. // Find the first one that supports this element
  97. for(var i=0,l=stages.length;i<l;i++){
  98. if (stages[i].isSupported(el,options)){
  99. stages[i].create(el,options,function(obj,opt){
  100. if (typeof callback == 'function') callback(obj,opt);
  101. });
  102. break;
  103. }
  104. }
  105. },
  106. // }}}
  107. // supportsColorFade: function(){{{
  108. supportsColorFade: function(){
  109. return $.fx.step.hasOwnProperty('backgroundColor');
  110. },
  111. // }}}
  112. // wrapFromXywh: function(xywh){{{
  113. wrapFromXywh: function(xywh){
  114. var b = { x: xywh[0], y: xywh[1], w: xywh[2], h: xywh[3] };
  115. b.x2 = b.x + b.w;
  116. b.y2 = b.y + b.h;
  117. return b;
  118. }
  119. // }}}
  120. });
  121. var AbstractStage = function(){
  122. };
  123. $.extend(AbstractStage,{
  124. isSupported: function(el,o){
  125. // @todo: should actually check if it's an HTML element
  126. return true;
  127. },
  128. // A higher priority means less desirable
  129. // AbstractStage is the last one we want to use
  130. priority: 100,
  131. create: function(el,options,callback){
  132. var obj = new AbstractStage;
  133. obj.element = el;
  134. callback.call(this,obj,options);
  135. },
  136. prototype: {
  137. attach: function(core){
  138. this.init(core);
  139. core.ui.stage = this;
  140. },
  141. triggerEvent: function(ev){
  142. $(this.element).trigger(ev);
  143. return this;
  144. },
  145. getElement: function(){
  146. return this.element;
  147. }
  148. }
  149. });
  150. Jcrop.registerStageType('Block',AbstractStage);
  151. var ImageStage = function(){
  152. };
  153. ImageStage.prototype = new AbstractStage();
  154. $.extend(ImageStage,{
  155. isSupported: function(el,o){
  156. if (el.tagName == 'IMG') return true;
  157. },
  158. priority: 90,
  159. create: function(el,options,callback){
  160. $.Jcrop.component.ImageLoader.attach(el,function(w,h){
  161. var obj = new ImageStage;
  162. obj.element = $(el).wrap('<div />').parent();
  163. obj.element.width(w).height(h);
  164. obj.imgsrc = el;
  165. if (typeof callback == 'function')
  166. callback.call(this,obj,options);
  167. });
  168. }
  169. });
  170. Jcrop.registerStageType('Image',ImageStage);
  171. var CanvasStage = function(){
  172. this.angle = 0;
  173. this.scale = 1;
  174. this.scaleMin = 0.2;
  175. this.scaleMax = 1.25;
  176. this.offset = [0,0];
  177. };
  178. CanvasStage.prototype = new ImageStage();
  179. $.extend(CanvasStage,{
  180. isSupported: function(el,o){
  181. if ($.Jcrop.supportsCanvas && (el.tagName == 'IMG')) return true;
  182. },
  183. priority: 60,
  184. create: function(el,options,callback){
  185. var $el = $(el);
  186. var opt = $.extend({},options);
  187. $.Jcrop.component.ImageLoader.attach(el,function(w,h){
  188. var obj = new CanvasStage;
  189. $el.hide();
  190. obj.createCanvas(el,w,h);
  191. $el.before(obj.element);
  192. obj.imgsrc = el;
  193. opt.imgsrc = el;
  194. if (typeof callback == 'function'){
  195. callback(obj,opt);
  196. obj.redraw();
  197. }
  198. });
  199. }
  200. });
  201. $.extend(CanvasStage.prototype,{
  202. init: function(core){
  203. this.core = core;
  204. },
  205. // setOffset: function(x,y) {{{
  206. setOffset: function(x,y) {
  207. this.offset = [x,y];
  208. return this;
  209. },
  210. // }}}
  211. // setAngle: function(v) {{{
  212. setAngle: function(v) {
  213. this.angle = v;
  214. return this;
  215. },
  216. // }}}
  217. // setScale: function(v) {{{
  218. setScale: function(v) {
  219. this.scale = this.boundScale(v);
  220. return this;
  221. },
  222. // }}}
  223. boundScale: function(v){
  224. if (v<this.scaleMin) v = this.scaleMin;
  225. else if (v>this.scaleMax) v = this.scaleMax;
  226. return v;
  227. },
  228. createCanvas: function(img,w,h){
  229. this.width = w;
  230. this.height = h;
  231. this.canvas = document.createElement('canvas');
  232. this.canvas.width = w;
  233. this.canvas.height = h;
  234. this.$canvas = $(this.canvas).width('100%').height('100%');
  235. this.context = this.canvas.getContext('2d');
  236. this.fillstyle = "rgb(0,0,0)";
  237. this.element = this.$canvas.wrap('<div />').parent().width(w).height(h);
  238. },
  239. triggerEvent: function(ev){
  240. this.$canvas.trigger(ev);
  241. return this;
  242. },
  243. // clear: function() {{{
  244. clear: function() {
  245. this.context.fillStyle = this.fillstyle;
  246. this.context.fillRect(0, 0, this.canvas.width, this.canvas.height);
  247. return this;
  248. },
  249. // }}}
  250. // redraw: function() {{{
  251. redraw: function() {
  252. // Save the current context
  253. this.context.save();
  254. this.clear();
  255. // Translate to the center point of our image
  256. this.context.translate(parseInt(this.width * 0.5), parseInt(this.height * 0.5));
  257. // Perform the rotation and scaling
  258. this.context.translate(this.offset[0]/this.core.opt.xscale,this.offset[1]/this.core.opt.yscale);
  259. this.context.rotate(this.angle * (Math.PI/180));
  260. this.context.scale(this.scale,this.scale);
  261. // Translate back to the top left of our image
  262. this.context.translate(-parseInt(this.width * 0.5), -parseInt(this.height * 0.5));
  263. // Finally we draw the image
  264. this.context.drawImage(this.imgsrc,0,0,this.width,this.height);
  265. // And restore the updated context
  266. this.context.restore();
  267. this.$canvas.trigger('cropredraw');
  268. return this;
  269. },
  270. // }}}
  271. // setFillStyle: function(v) {{{
  272. setFillStyle: function(v) {
  273. this.fillstyle = v;
  274. return this;
  275. }
  276. // }}}
  277. });
  278. Jcrop.registerStageType('Canvas',CanvasStage);
  279. /**
  280. * BackoffFilter
  281. * move out-of-bounds selection into allowed position at same size
  282. */
  283. var BackoffFilter = function(){
  284. this.minw = 40;
  285. this.minh = 40;
  286. this.maxw = 0;
  287. this.maxh = 0;
  288. this.core = null;
  289. };
  290. $.extend(BackoffFilter.prototype,{
  291. tag: 'backoff',
  292. priority: 22,
  293. filter: function(b){
  294. var r = this.bound;
  295. if (b.x < r.minx) { b.x = r.minx; b.x2 = b.w + b.x; }
  296. if (b.y < r.miny) { b.y = r.miny; b.y2 = b.h + b.y; }
  297. if (b.x2 > r.maxx) { b.x2 = r.maxx; b.x = b.x2 - b.w; }
  298. if (b.y2 > r.maxy) { b.y2 = r.maxy; b.y = b.y2 - b.h; }
  299. return b;
  300. },
  301. refresh: function(sel){
  302. this.elw = sel.core.container.width();
  303. this.elh = sel.core.container.height();
  304. this.bound = {
  305. minx: 0 + sel.edge.w,
  306. miny: 0 + sel.edge.n,
  307. maxx: this.elw + sel.edge.e,
  308. maxy: this.elh + sel.edge.s
  309. };
  310. }
  311. });
  312. Jcrop.registerFilter('backoff',BackoffFilter);
  313. /**
  314. * ConstrainFilter
  315. * a filter to constrain crop selection to bounding element
  316. */
  317. var ConstrainFilter = function(){
  318. this.core = null;
  319. };
  320. $.extend(ConstrainFilter.prototype,{
  321. tag: 'constrain',
  322. priority: 5,
  323. filter: function(b,ord){
  324. if (ord == 'move') {
  325. if (b.x < this.minx) { b.x = this.minx; b.x2 = b.w + b.x; }
  326. if (b.y < this.miny) { b.y = this.miny; b.y2 = b.h + b.y; }
  327. if (b.x2 > this.maxx) { b.x2 = this.maxx; b.x = b.x2 - b.w; }
  328. if (b.y2 > this.maxy) { b.y2 = this.maxy; b.y = b.y2 - b.h; }
  329. } else {
  330. if (b.x < this.minx) { b.x = this.minx; }
  331. if (b.y < this.miny) { b.y = this.miny; }
  332. if (b.x2 > this.maxx) { b.x2 = this.maxx; }
  333. if (b.y2 > this.maxy) { b.y2 = this.maxy; }
  334. }
  335. b.w = b.x2 - b.x;
  336. b.h = b.y2 - b.y;
  337. return b;
  338. },
  339. refresh: function(sel){
  340. this.elw = sel.core.container.width();
  341. this.elh = sel.core.container.height();
  342. this.minx = 0 + sel.edge.w;
  343. this.miny = 0 + sel.edge.n;
  344. this.maxx = this.elw + sel.edge.e;
  345. this.maxy = this.elh + sel.edge.s;
  346. }
  347. });
  348. Jcrop.registerFilter('constrain',ConstrainFilter);
  349. /**
  350. * ExtentFilter
  351. * a filter to implement minimum or maximum size
  352. */
  353. var ExtentFilter = function(){
  354. this.core = null;
  355. };
  356. $.extend(ExtentFilter.prototype,{
  357. tag: 'extent',
  358. priority: 12,
  359. offsetFromCorner: function(corner,box,b){
  360. var w = box[0], h = box[1];
  361. switch(corner){
  362. case 'bl': return [ b.x2 - w, b.y, w, h ];
  363. case 'tl': return [ b.x2 - w , b.y2 - h, w, h ];
  364. case 'br': return [ b.x, b.y, w, h ];
  365. case 'tr': return [ b.x, b.y2 - h, w, h ];
  366. }
  367. },
  368. getQuadrant: function(s){
  369. var relx = s.opposite[0]-s.offsetx
  370. var rely = s.opposite[1]-s.offsety;
  371. if ((relx < 0) && (rely < 0)) return 'br';
  372. else if ((relx >= 0) && (rely >= 0)) return 'tl';
  373. else if ((relx < 0) && (rely >= 0)) return 'tr';
  374. return 'bl';
  375. },
  376. filter: function(b,ord,sel){
  377. if (ord == 'move') return b;
  378. var w = b.w, h = b.h, st = sel.state, r = this.limits;
  379. var quad = st? this.getQuadrant(st): 'br';
  380. if (r.minw && (w < r.minw)) w = r.minw;
  381. if (r.minh && (h < r.minh)) h = r.minh;
  382. if (r.maxw && (w > r.maxw)) w = r.maxw;
  383. if (r.maxh && (h > r.maxh)) h = r.maxh;
  384. if ((w == b.w) && (h == b.h)) return b;
  385. return Jcrop.wrapFromXywh(this.offsetFromCorner(quad,[w,h],b));
  386. },
  387. refresh: function(sel){
  388. this.elw = sel.core.container.width();
  389. this.elh = sel.core.container.height();
  390. this.limits = {
  391. minw: sel.minSize[0],
  392. minh: sel.minSize[1],
  393. maxw: sel.maxSize[0],
  394. maxh: sel.maxSize[1]
  395. };
  396. }
  397. });
  398. Jcrop.registerFilter('extent',ExtentFilter);
  399. /**
  400. * GridFilter
  401. * a rudimentary grid effect
  402. */
  403. var GridFilter = function(){
  404. this.stepx = 1;
  405. this.stepy = 1;
  406. this.core = null;
  407. };
  408. $.extend(GridFilter.prototype,{
  409. tag: 'grid',
  410. priority: 19,
  411. filter: function(b){
  412. var n = {
  413. x: Math.round(b.x / this.stepx) * this.stepx,
  414. y: Math.round(b.y / this.stepy) * this.stepy,
  415. x2: Math.round(b.x2 / this.stepx) * this.stepx,
  416. y2: Math.round(b.y2 / this.stepy) * this.stepy
  417. };
  418. n.w = n.x2 - n.x;
  419. n.h = n.y2 - n.y;
  420. return n;
  421. }
  422. });
  423. Jcrop.registerFilter('grid',GridFilter);
  424. /**
  425. * RatioFilter
  426. * implements aspectRatio locking
  427. */
  428. var RatioFilter = function(){
  429. this.ratio = 0;
  430. this.core = null;
  431. };
  432. $.extend(RatioFilter.prototype,{
  433. tag: 'ratio',
  434. priority: 15,
  435. offsetFromCorner: function(corner,box,b){
  436. var w = box[0], h = box[1];
  437. switch(corner){
  438. case 'bl': return [ b.x2 - w, b.y, w, h ];
  439. case 'tl': return [ b.x2 - w , b.y2 - h, w, h ];
  440. case 'br': return [ b.x, b.y, w, h ];
  441. case 'tr': return [ b.x, b.y2 - h, w, h ];
  442. }
  443. },
  444. getBoundRatio: function(b,quad){
  445. var box = Jcrop.getLargestBox(this.ratio,b.w,b.h);
  446. return Jcrop.wrapFromXywh(this.offsetFromCorner(quad,box,b));
  447. },
  448. getQuadrant: function(s){
  449. var relx = s.opposite[0]-s.offsetx
  450. var rely = s.opposite[1]-s.offsety;
  451. if ((relx < 0) && (rely < 0)) return 'br';
  452. else if ((relx >= 0) && (rely >= 0)) return 'tl';
  453. else if ((relx < 0) && (rely >= 0)) return 'tr';
  454. return 'bl';
  455. },
  456. filter: function(b,ord,sel){
  457. if (!this.ratio) return b;
  458. var rt = b.w / b.h;
  459. var st = sel.state;
  460. var quad = st? this.getQuadrant(st): 'br';
  461. ord = ord || 'se';
  462. if (ord == 'move') return b;
  463. switch(ord) {
  464. case 'n':
  465. b.x2 = this.elw;
  466. b.w = b.x2 - b.x;
  467. quad = 'tr';
  468. break;
  469. case 's':
  470. b.x2 = this.elw;
  471. b.w = b.x2 - b.x;
  472. quad = 'br';
  473. break;
  474. case 'e':
  475. b.y2 = this.elh;
  476. b.h = b.y2 - b.y;
  477. quad = 'br';
  478. break;
  479. case 'w':
  480. b.y2 = this.elh;
  481. b.h = b.y2 - b.y;
  482. quad = 'bl';
  483. break;
  484. }
  485. return this.getBoundRatio(b,quad);
  486. },
  487. refresh: function(sel){
  488. this.ratio = sel.aspectRatio;
  489. this.elw = sel.core.container.width();
  490. this.elh = sel.core.container.height();
  491. }
  492. });
  493. Jcrop.registerFilter('ratio',RatioFilter);
  494. /**
  495. * RoundFilter
  496. * rounds coordinate values to integers
  497. */
  498. var RoundFilter = function(){
  499. this.core = null;
  500. };
  501. $.extend(RoundFilter.prototype,{
  502. tag: 'round',
  503. priority: 90,
  504. filter: function(b){
  505. var n = {
  506. x: Math.round(b.x),
  507. y: Math.round(b.y),
  508. x2: Math.round(b.x2),
  509. y2: Math.round(b.y2)
  510. };
  511. n.w = n.x2 - n.x;
  512. n.h = n.y2 - n.y;
  513. return n;
  514. }
  515. });
  516. Jcrop.registerFilter('round',RoundFilter);
  517. /**
  518. * ShadeFilter
  519. * A filter that implements div-based shading on any element
  520. *
  521. * The shading you see is actually four semi-opaque divs
  522. * positioned inside the container, around the selection
  523. */
  524. var ShadeFilter = function(opacity,color){
  525. this.color = color || 'black';
  526. this.opacity = opacity || 0.5;
  527. this.core = null;
  528. this.shades = {};
  529. };
  530. $.extend(ShadeFilter.prototype,{
  531. tag: 'shader',
  532. fade: true,
  533. fadeEasing: 'swing',
  534. fadeSpeed: 320,
  535. priority: 95,
  536. init: function(){
  537. var t = this;
  538. if (!t.attached) {
  539. t.visible = false;
  540. t.container = $('<div />').addClass(t.core.opt.css_shades)
  541. .prependTo(this.core.container).hide();
  542. t.elh = this.core.container.height();
  543. t.elw = this.core.container.width();
  544. t.shades = {
  545. top: t.createShade(),
  546. right: t.createShade(),
  547. left: t.createShade(),
  548. bottom: t.createShade()
  549. };
  550. t.attached = true;
  551. }
  552. },
  553. destroy: function(){
  554. this.container.remove();
  555. },
  556. setColor: function(color,instant){
  557. var t = this;
  558. if (color == t.color) return t;
  559. this.color = color;
  560. var colorfade = Jcrop.supportsColorFade();
  561. $.each(t.shades,function(u,i){
  562. if (!t.fade || instant || !colorfade) i.css('backgroundColor',color);
  563. else i.animate({backgroundColor:color},{queue:false,duration:t.fadeSpeed,easing:t.fadeEasing});
  564. });
  565. return t;
  566. },
  567. setOpacity: function(opacity,instant){
  568. var t = this;
  569. if (opacity == t.opacity) return t;
  570. t.opacity = opacity;
  571. $.each(t.shades,function(u,i){
  572. if (!t.fade || instant) i.css({opacity:opacity});
  573. else i.animate({opacity:opacity},{queue:false,duration:t.fadeSpeed,easing:t.fadeEasing});
  574. });
  575. return t;
  576. },
  577. createShade: function(){
  578. return $('<div />').css({
  579. position: 'absolute',
  580. backgroundColor: this.color,
  581. opacity: this.opacity
  582. }).appendTo(this.container);
  583. },
  584. refresh: function(sel){
  585. var m = this.core, s = this.shades;
  586. this.setColor(sel.bgColor?sel.bgColor:this.core.opt.bgColor);
  587. this.setOpacity(sel.bgOpacity?sel.bgOpacity:this.core.opt.bgOpacity);
  588. this.elh = m.container.height();
  589. this.elw = m.container.width();
  590. s.right.css('height',this.elh+'px');
  591. s.left.css('height',this.elh+'px');
  592. },
  593. filter: function(b,ord,sel){
  594. if (!sel.active) return b;
  595. var t = this,
  596. s = t.shades;
  597. s.top.css({
  598. left: Math.round(b.x)+'px',
  599. width: Math.round(b.w)+'px',
  600. height: Math.round(b.y)+'px'
  601. });
  602. s.bottom.css({
  603. top: Math.round(b.y2)+'px',
  604. left: Math.round(b.x)+'px',
  605. width: Math.round(b.w)+'px',
  606. height: (t.elh-Math.round(b.y2))+'px'
  607. });
  608. s.right.css({
  609. left: Math.round(b.x2)+'px',
  610. width: (t.elw-Math.round(b.x2))+'px'
  611. });
  612. s.left.css({
  613. width: Math.round(b.x)+'px'
  614. });
  615. if (!t.visible) {
  616. t.container.show();
  617. t.visible = true;
  618. }
  619. return b;
  620. }
  621. });
  622. Jcrop.registerFilter('shader',ShadeFilter);
  623. /**
  624. * CanvasAnimator
  625. * manages smooth cropping animation
  626. *
  627. * This object is called internally to manage animation.
  628. * An in-memory div is animated and a progress callback
  629. * is used to update the selection coordinates of the
  630. * visible selection in realtime.
  631. */
  632. var CanvasAnimator = function(stage){
  633. this.stage = stage;
  634. this.core = stage.core;
  635. this.cloneStagePosition();
  636. };
  637. CanvasAnimator.prototype = {
  638. cloneStagePosition: function(){
  639. var s = this.stage;
  640. this.angle = s.angle;
  641. this.scale = s.scale;
  642. this.offset = s.offset;
  643. },
  644. getElement: function(){
  645. var s = this.stage;
  646. return $('<div />')
  647. .css({
  648. position: 'absolute',
  649. top: s.offset[0]+'px',
  650. left: s.offset[1]+'px',
  651. width: s.angle+'px',
  652. height: s.scale+'px'
  653. });
  654. },
  655. animate: function(cb){
  656. var t = this;
  657. this.scale = this.stage.boundScale(this.scale);
  658. t.stage.triggerEvent('croprotstart');
  659. t.getElement().animate({
  660. top: t.offset[0]+'px',
  661. left: t.offset[1]+'px',
  662. width: t.angle+'px',
  663. height: t.scale+'px'
  664. },{
  665. easing: t.core.opt.animEasing,
  666. duration: t.core.opt.animDuration,
  667. complete: function(){
  668. t.stage.triggerEvent('croprotend');
  669. (typeof cb == 'function') && cb.call(this);
  670. },
  671. progress: function(anim){
  672. var props = {}, i, tw = anim.tweens;
  673. for(i=0;i<tw.length;i++){
  674. props[tw[i].prop] = tw[i].now; }
  675. t.stage.setAngle(props.width)
  676. .setScale(props.height)
  677. .setOffset(props.top,props.left)
  678. .redraw();
  679. }
  680. });
  681. }
  682. };
  683. Jcrop.stage.Canvas.prototype.getAnimator = function(){
  684. return new CanvasAnimator(this);
  685. };
  686. Jcrop.registerComponent('CanvasAnimator',CanvasAnimator);
  687. /**
  688. * CropAnimator
  689. * manages smooth cropping animation
  690. *
  691. * This object is called internally to manage animation.
  692. * An in-memory div is animated and a progress callback
  693. * is used to update the selection coordinates of the
  694. * visible selection in realtime.
  695. */
  696. // var CropAnimator = function(selection){{{
  697. var CropAnimator = function(selection){
  698. this.selection = selection;
  699. this.core = selection.core;
  700. };
  701. // }}}
  702. CropAnimator.prototype = {
  703. getElement: function(){
  704. var b = this.selection.get();
  705. return $('<div />')
  706. .css({
  707. position: 'absolute',
  708. top: b.y+'px',
  709. left: b.x+'px',
  710. width: b.w+'px',
  711. height: b.h+'px'
  712. });
  713. },
  714. animate: function(x,y,w,h,cb){
  715. var t = this;
  716. t.selection.allowResize(false);
  717. t.getElement().animate({
  718. top: y+'px',
  719. left: x+'px',
  720. width: w+'px',
  721. height: h+'px'
  722. },{
  723. easing: t.core.opt.animEasing,
  724. duration: t.core.opt.animDuration,
  725. complete: function(){
  726. t.selection.allowResize(true);
  727. cb && cb.call(this);
  728. },
  729. progress: function(anim){
  730. var props = {}, i, tw = anim.tweens;
  731. for(i=0;i<tw.length;i++){
  732. props[tw[i].prop] = tw[i].now; }
  733. var b = {
  734. x: parseInt(props.left),
  735. y: parseInt(props.top),
  736. w: parseInt(props.width),
  737. h: parseInt(props.height)
  738. };
  739. b.x2 = b.x + b.w;
  740. b.y2 = b.y + b.h;
  741. t.selection.updateRaw(b,'se');
  742. }
  743. });
  744. }
  745. };
  746. Jcrop.registerComponent('Animator',CropAnimator);
  747. /**
  748. * DragState
  749. * an object that handles dragging events
  750. *
  751. * This object is used by the built-in selection object to
  752. * track a dragging operation on a selection
  753. */
  754. // var DragState = function(e,selection,ord){{{
  755. var DragState = function(e,selection,ord){
  756. var t = this;
  757. t.x = e.pageX;
  758. t.y = e.pageY;
  759. t.selection = selection;
  760. t.eventTarget = selection.core.opt.dragEventTarget;
  761. t.orig = selection.get();
  762. selection.callFilterFunction('refresh');
  763. var p = selection.core.container.position();
  764. t.elx = p.left;
  765. t.ely = p.top;
  766. t.offsetx = 0;
  767. t.offsety = 0;
  768. t.ord = ord;
  769. t.opposite = t.getOppositeCornerOffset();
  770. t.initEvents(e);
  771. };
  772. // }}}
  773. DragState.prototype = {
  774. // getOppositeCornerOffset: function(){{{
  775. // Calculate relative offset of locked corner
  776. getOppositeCornerOffset: function(){
  777. var o = this.orig;
  778. var relx = this.x - this.elx - o.x;
  779. var rely = this.y - this.ely - o.y;
  780. switch(this.ord){
  781. case 'nw':
  782. case 'w':
  783. return [ o.w - relx, o.h - rely ];
  784. return [ o.x + o.w, o.y + o.h ];
  785. case 'sw':
  786. return [ o.w - relx, -rely ];
  787. return [ o.x + o.w, o.y ];
  788. case 'se':
  789. case 's':
  790. case 'e':
  791. return [ -relx, -rely ];
  792. return [ o.x, o.y ];
  793. case 'ne':
  794. case 'n':
  795. return [ -relx, o.h - rely ];
  796. return [ o.w, o.y + o.h ];
  797. }
  798. return [ null, null ];
  799. },
  800. // }}}
  801. // initEvents: function(e){{{
  802. initEvents: function(e){
  803. $(this.eventTarget)
  804. .on('mousemove.jcrop',this.createDragHandler())
  805. .on('mouseup.jcrop',this.createStopHandler());
  806. },
  807. // }}}
  808. // dragEvent: function(e){{{
  809. dragEvent: function(e){
  810. this.offsetx = e.pageX - this.x;
  811. this.offsety = e.pageY - this.y;
  812. this.selection.updateRaw(this.getBox(),this.ord);
  813. },
  814. // }}}
  815. // endDragEvent: function(e){{{
  816. endDragEvent: function(e){
  817. var sel = this.selection;
  818. sel.core.container.removeClass('jcrop-dragging');
  819. sel.element.trigger('cropend',[sel,sel.core.unscale(sel.get())]);
  820. sel.focus();
  821. },
  822. // }}}
  823. // createStopHandler: function(){{{
  824. createStopHandler: function(){
  825. var t = this;
  826. return function(e){
  827. $(t.eventTarget).off('.jcrop');
  828. t.endDragEvent(e);
  829. return false;
  830. };
  831. },
  832. // }}}
  833. // createDragHandler: function(){{{
  834. createDragHandler: function(){
  835. var t = this;
  836. return function(e){
  837. t.dragEvent(e);
  838. return false;
  839. };
  840. },
  841. // }}}
  842. //update: function(x,y){{{
  843. update: function(x,y){
  844. var t = this;
  845. t.offsetx = x - t.x;
  846. t.offsety = y - t.y;
  847. },
  848. //}}}
  849. //resultWrap: function(d){{{
  850. resultWrap: function(d){
  851. var b = {
  852. x: Math.min(d[0],d[2]),
  853. y: Math.min(d[1],d[3]),
  854. x2: Math.max(d[0],d[2]),
  855. y2: Math.max(d[1],d[3])
  856. };
  857. b.w = b.x2 - b.x;
  858. b.h = b.y2 - b.y;
  859. return b;
  860. },
  861. //}}}
  862. //getBox: function(){{{
  863. getBox: function(){
  864. var t = this;
  865. var o = t.orig;
  866. var _c = { x2: o.x + o.w, y2: o.y + o.h };
  867. switch(t.ord){
  868. case 'n': return t.resultWrap([ o.x, t.offsety + o.y, _c.x2, _c.y2 ]);
  869. case 's': return t.resultWrap([ o.x, o.y, _c.x2, t.offsety + _c.y2 ]);
  870. case 'e': return t.resultWrap([ o.x, o.y, t.offsetx + _c.x2, _c.y2 ]);
  871. case 'w': return t.resultWrap([ o.x + t.offsetx, o.y, _c.x2, _c.y2 ]);
  872. case 'sw': return t.resultWrap([ t.offsetx + o.x, o.y, _c.x2, t.offsety + _c.y2 ]);
  873. case 'se': return t.resultWrap([ o.x, o.y, t.offsetx + _c.x2, t.offsety + _c.y2 ]);
  874. case 'ne': return t.resultWrap([ o.x, t.offsety + o.y, t.offsetx + _c.x2, _c.y2 ]);
  875. case 'nw': return t.resultWrap([ t.offsetx + o.x, t.offsety + o.y, _c.x2, _c.y2 ]);
  876. case 'move':
  877. _c.nx = o.x + t.offsetx;
  878. _c.ny = o.y + t.offsety;
  879. return t.resultWrap([ _c.nx, _c.ny, _c.nx + o.w, _c.ny + o.h ]);
  880. }
  881. }
  882. //}}}
  883. };
  884. Jcrop.registerComponent('DragState',DragState);
  885. /**
  886. * EventManager
  887. * provides internal event support
  888. */
  889. var EventManager = function(core){
  890. this.core = core;
  891. };
  892. EventManager.prototype = {
  893. on: function(n,cb){ $(this).on(n,cb); },
  894. off: function(n){ $(this).off(n); },
  895. trigger: function(n){ $(this).trigger(n); }
  896. };
  897. Jcrop.registerComponent('EventManager',EventManager);
  898. /**
  899. * Image Loader
  900. * Reliably pre-loads images
  901. */
  902. // var ImageLoader = function(src,element,cb){{{
  903. var ImageLoader = function(src,element,cb){
  904. this.src = src;
  905. if (!element) element = new Image;
  906. this.element = element;
  907. this.callback = cb;
  908. this.load();
  909. };
  910. // }}}
  911. $.extend(ImageLoader,{
  912. // attach: function(el,cb){{{
  913. attach: function(el,cb){
  914. return new ImageLoader(el.src,el,cb);
  915. },
  916. // }}}
  917. // prototype: {{{
  918. prototype: {
  919. getDimensions: function(){
  920. var el = this.element;
  921. if (el.naturalWidth)
  922. return [ el.naturalWidth, el. naturalHeight ];
  923. if (el.width)
  924. return [ el.width, el.height ];
  925. return null;
  926. },
  927. fireCallback: function(){
  928. this.element.onload = null;
  929. if (typeof this.callback == 'function')
  930. this.callback.apply(this,this.getDimensions());
  931. },
  932. isLoaded: function(){
  933. return this.element.complete;
  934. },
  935. load: function(){
  936. var t = this;
  937. var el = t.element;
  938. el.src = t.src;
  939. if (t.isLoaded()) t.fireCallback();
  940. else t.element.onload = function(e){
  941. t.fireCallback();
  942. };
  943. }
  944. }
  945. // }}}
  946. });
  947. Jcrop.registerComponent('ImageLoader',ImageLoader);
  948. /**
  949. * JcropTouch
  950. * Detects and enables mobile touch support
  951. */
  952. // var JcropTouch = function(core){{{
  953. var JcropTouch = function(core){
  954. this.core = core;
  955. this.init();
  956. };
  957. // }}}
  958. $.extend(JcropTouch,{
  959. // support: function(){{{
  960. support: function(){
  961. if(('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch)
  962. return true;
  963. },
  964. // }}}
  965. prototype: {
  966. // init: function(){{{
  967. init: function(){
  968. var t = this,
  969. p = $.Jcrop.component.DragState.prototype;
  970. // A bit of an ugly hack to make sure we modify prototype
  971. // only once, store a key on the prototype
  972. if (!p.touch) {
  973. t.initEvents();
  974. t.shimDragState();
  975. t.shimStageDrag();
  976. p.touch = true;
  977. }
  978. },
  979. // }}}
  980. // shimDragState: function(){{{
  981. shimDragState: function(){
  982. var t = this;
  983. $.Jcrop.component.DragState.prototype.initEvents = function(e){
  984. // Attach subsequent drag event handlers based on initial
  985. // event type - avoids collecting "pseudo-mouse" events
  986. // generated by some mobile browsers in some circumstances
  987. if (e.type.substr(0,5) == 'touch') {
  988. $(this.eventTarget)
  989. .on('touchmove.jcrop.jcrop-touch',t.dragWrap(this.createDragHandler()))
  990. .on('touchend.jcrop.jcrop-touch',this.createStopHandler());
  991. }
  992. // For other events, use the mouse handlers that
  993. // the default DragState.initEvents() method sets...
  994. else {
  995. $(this.eventTarget)
  996. .on('mousemove.jcrop',this.createDragHandler())
  997. .on('mouseup.jcrop',this.createStopHandler());
  998. }
  999. };
  1000. },
  1001. // }}}
  1002. // shimStageDrag: function(){{{
  1003. shimStageDrag: function(){
  1004. this.core.container
  1005. .addClass('jcrop-touch')
  1006. .on('touchstart.jcrop.jcrop-stage',this.dragWrap(this.core.ui.manager.startDragHandler()));
  1007. },
  1008. // }}}
  1009. // dragWrap: function(cb){{{
  1010. dragWrap: function(cb){
  1011. return function(e){
  1012. e.preventDefault();
  1013. e.stopPropagation();
  1014. if (e.type.substr(0,5) == 'touch') {
  1015. e.pageX = e.originalEvent.changedTouches[0].pageX;
  1016. e.pageY = e.originalEvent.changedTouches[0].pageY;
  1017. return cb(e);
  1018. }
  1019. return false;
  1020. };
  1021. },
  1022. // }}}
  1023. // initEvents: function(){{{
  1024. initEvents: function(){
  1025. var t = this, c = t.core;
  1026. c.container.on(
  1027. 'touchstart.jcrop.jcrop-touch',
  1028. '.'+c.opt.css_drag,
  1029. t.dragWrap(c.startDrag())
  1030. );
  1031. }
  1032. // }}}
  1033. }
  1034. });
  1035. Jcrop.registerComponent('Touch',JcropTouch);
  1036. /**
  1037. * KeyWatcher
  1038. * provides keyboard support
  1039. */
  1040. // var KeyWatcher = function(core){{{
  1041. var KeyWatcher = function(core){
  1042. this.core = core;
  1043. this.init();
  1044. };
  1045. // }}}
  1046. $.extend(KeyWatcher,{
  1047. // defaults: {{{
  1048. defaults: {
  1049. eventName: 'keydown.jcrop',
  1050. passthru: [ 9 ],
  1051. debug: false
  1052. },
  1053. // }}}
  1054. prototype: {
  1055. // init: function(){{{
  1056. init: function(){
  1057. $.extend(this,KeyWatcher.defaults);
  1058. this.enable();
  1059. },
  1060. // }}}
  1061. // disable: function(){{{
  1062. disable: function(){
  1063. this.core.container.off(this.eventName);
  1064. },
  1065. // }}}
  1066. // enable: function(){{{
  1067. enable: function(){
  1068. var t = this, m = t.core;
  1069. m.container.on(t.eventName,function(e){
  1070. var nudge = e.shiftKey? 16: 2;
  1071. if ($.inArray(e.keyCode,t.passthru) >= 0)
  1072. return true;
  1073. switch(e.keyCode){
  1074. case 37: m.nudge(-nudge,0); break;
  1075. case 38: m.nudge(0,-nudge); break;
  1076. case 39: m.nudge(nudge,0); break;
  1077. case 40: m.nudge(0,nudge); break;
  1078. case 46:
  1079. case 8:
  1080. m.requestDelete();
  1081. return false;
  1082. break;
  1083. default:
  1084. if (t.debug) console.log('keycode: ' + e.keyCode);
  1085. break;
  1086. }
  1087. if (!e.metaKey && !e.ctrlKey)
  1088. e.preventDefault();
  1089. });
  1090. }
  1091. // }}}
  1092. }
  1093. });
  1094. Jcrop.registerComponent('Keyboard',KeyWatcher);
  1095. /**
  1096. * Selection
  1097. * Built-in selection object
  1098. */
  1099. var Selection = function(){};
  1100. $.extend(Selection,{
  1101. // defaults: {{{
  1102. defaults: {
  1103. minSize: [ 8, 8 ],
  1104. maxSize: [ 0, 0 ],
  1105. aspectRatio: 0,
  1106. edge: { n: 0, s: 0, e: 0, w: 0 },
  1107. bgColor: null,
  1108. bgOpacity: null,
  1109. last: null,
  1110. state: null,
  1111. active: true,
  1112. linked: true,
  1113. canDelete: true,
  1114. canDrag: true,
  1115. canResize: true,
  1116. canSelect: true
  1117. },
  1118. // }}}
  1119. prototype: {
  1120. // init: function(core){{{
  1121. init: function(core){
  1122. this.core = core;
  1123. this.startup();
  1124. this.linked = this.core.opt.linked;
  1125. this.attach();
  1126. this.setOptions(this.core.opt);
  1127. core.container.trigger('cropcreate',[this]);
  1128. },
  1129. // }}}
  1130. // attach: function(){{{
  1131. attach: function(){
  1132. // For extending init() sequence
  1133. },
  1134. // }}}
  1135. // startup: function(){{{
  1136. startup: function(){
  1137. var t = this, o = t.core.opt;
  1138. $.extend(t,Selection.defaults);
  1139. t.filter = t.core.getDefaultFilters();
  1140. t.element = $('<div />').addClass(o.css_selection).data({ selection: t });
  1141. t.frame = $('<button />').addClass(o.css_button).data('ord','move').attr('type','button');
  1142. t.element.append(t.frame).appendTo(t.core.container);
  1143. // IE background/draggable hack
  1144. if (t.core.opt.is_msie) t.frame.css({
  1145. opacity: 0,
  1146. backgroundColor: 'white'
  1147. });
  1148. t.insertElements();
  1149. // Bind focus and blur events for this selection
  1150. t.frame.on('focus.jcrop',function(e){
  1151. t.core.setSelection(t);
  1152. t.element.trigger('cropfocus',t);
  1153. t.element.addClass('jcrop-focus');
  1154. }).on('blur.jcrop',function(e){
  1155. t.element.removeClass('jcrop-focus');
  1156. t.element.trigger('cropblur',t);
  1157. });
  1158. },
  1159. // }}}
  1160. // propagate: [{{{
  1161. propagate: [
  1162. 'canDelete', 'canDrag', 'canResize', 'canSelect',
  1163. 'minSize', 'maxSize', 'aspectRatio', 'edge'
  1164. ],
  1165. // }}}
  1166. // setOptions: function(opt){{{
  1167. setOptions: function(opt){
  1168. Jcrop.propagate(this.propagate,opt,this);
  1169. this.refresh();
  1170. return this;
  1171. },
  1172. // }}}
  1173. // refresh: function(){{{
  1174. refresh: function(){
  1175. this.allowResize();
  1176. this.allowDrag();
  1177. this.allowSelect();
  1178. this.callFilterFunction('refresh');
  1179. this.updateRaw(this.get(),'se');
  1180. },
  1181. // }}}
  1182. // callFilterFunction: function(f,args){{{
  1183. callFilterFunction: function(f,args){
  1184. for(var i=0;i<this.filter.length;i++)
  1185. if (this.filter[i][f]) this.filter[i][f](this);
  1186. return this;
  1187. },
  1188. // }}}
  1189. //addFilter: function(filter){{{
  1190. addFilter: function(filter){
  1191. filter.core = this.core;
  1192. if (!this.hasFilter(filter)) {
  1193. this.filter.push(filter);
  1194. this.sortFilters();
  1195. if (filter.init) filter.init();
  1196. this.refresh();
  1197. }
  1198. },
  1199. //}}}
  1200. // hasFilter: function(filter){{{
  1201. hasFilter: function(filter){
  1202. var i, f = this.filter, n = [];
  1203. for(i=0;i<f.length;i++) if (f[i] === filter) return true;
  1204. },
  1205. // }}}
  1206. // sortFilters: function(){{{
  1207. sortFilters: function(){
  1208. this.filter.sort(
  1209. function(x,y){ return x.priority - y.priority; }
  1210. );
  1211. },
  1212. // }}}
  1213. //clearFilters: function(){{{
  1214. clearFilters: function(){
  1215. var i, f = this.filter;
  1216. for(var i=0;i<f.length;i++)
  1217. if (f[i].destroy) f[i].destroy();
  1218. this.filter = [];
  1219. },
  1220. //}}}
  1221. // removeFiltersByTag: function(tag){{{
  1222. removeFilter: function(tag){
  1223. var i, f = this.filter, n = [];
  1224. for(var i=0;i<f.length;i++)
  1225. if ((f[i].tag && (f[i].tag == tag)) || (tag === f[i])){
  1226. if (f[i].destroy) f[i].destroy();
  1227. }
  1228. else n.push(f[i]);
  1229. this.filter = n;
  1230. },
  1231. // }}}
  1232. // runFilters: function(b,ord){{{
  1233. runFilters: function(b,ord){
  1234. for(var i=0;i<this.filter.length;i++)
  1235. b = this.filter[i].filter(b,ord,this);
  1236. return b;
  1237. },
  1238. // }}}
  1239. //endDrag: function(){{{
  1240. endDrag: function(){
  1241. if (this.state) {
  1242. $(document.body).off('.jcrop');
  1243. this.focus();
  1244. this.state = null;
  1245. }
  1246. },
  1247. //}}}
  1248. // startDrag: function(e,ord){{{
  1249. startDrag: function(e,ord){
  1250. var t = this;
  1251. var m = t.core;
  1252. ord = ord || $(e.target).data('ord');
  1253. this.focus();
  1254. if ((ord == 'move') && t.element.hasClass(t.core.opt.css_nodrag))
  1255. return false;
  1256. this.state = new Jcrop.component.DragState(e,this,ord);
  1257. return false;
  1258. },
  1259. // }}}
  1260. // allowSelect: function(v){{{
  1261. allowSelect: function(v){
  1262. if (v === undefined) v = this.canSelect;
  1263. if (v && this.canSelect) this.frame.attr('disabled',false);
  1264. else this.frame.attr('disabled','disabled');
  1265. return this;
  1266. },
  1267. // }}}
  1268. // allowDrag: function(v){{{
  1269. allowDrag: function(v){
  1270. var t = this, o = t.core.opt;
  1271. if (v == undefined) v = t.canDrag;
  1272. if (v && t.canDrag) t.element.removeClass(o.css_nodrag);
  1273. else t.element.addClass(o.css_nodrag);
  1274. return this;
  1275. },
  1276. // }}}
  1277. // allowResize: function(v){{{
  1278. allowResize: function(v){
  1279. var t = this, o = t.core.opt;
  1280. if (v == undefined) v = t.canResize;
  1281. if (v && t.canResize) t.element.removeClass(o.css_noresize);
  1282. else t.element.addClass(o.css_noresize);
  1283. return this;
  1284. },
  1285. // }}}
  1286. // remove: function(){{{
  1287. remove: function(){
  1288. this.element.trigger('cropremove',this);
  1289. this.element.remove();
  1290. },
  1291. // }}}
  1292. // toBack: function(){{{
  1293. toBack: function(){
  1294. this.active = false;
  1295. this.element.removeClass('jcrop-current jcrop-focus');
  1296. },
  1297. // }}}
  1298. // toFront: function(){{{
  1299. toFront: function(){
  1300. this.active = true;
  1301. this.element.addClass('jcrop-current');
  1302. this.callFilterFunction('refresh');
  1303. this.refresh();
  1304. },
  1305. // }}}
  1306. // redraw: function(b){{{
  1307. redraw: function(b){
  1308. this.moveTo(b.x,b.y);
  1309. this.resize(b.w,b.h);
  1310. this.last = b;
  1311. return this;
  1312. },
  1313. // }}}
  1314. // update: function(b,ord){{{
  1315. update: function(b,ord){
  1316. return this.updateRaw(this.core.scale(b),ord);
  1317. },
  1318. // }}}
  1319. // update: function(b,ord){{{
  1320. updateRaw: function(b,ord){
  1321. b = this.runFilters(b,ord);
  1322. this.redraw(b);
  1323. this.element.trigger('cropmove',[this,this.core.unscale(b)]);
  1324. return this;
  1325. },
  1326. // }}}
  1327. // animateTo: function(box,cb){{{
  1328. animateTo: function(box,cb){
  1329. var ca = new Jcrop.component.Animator(this),
  1330. b = this.core.scale(Jcrop.wrapFromXywh(box));
  1331. ca.animate(b.x,b.y,b.w,b.h,cb);
  1332. },
  1333. // }}}
  1334. // center: function(instant){{{
  1335. center: function(instant){
  1336. var b = this.get(), m = this.core;
  1337. var elw = m.container.width(), elh = m.container.height();
  1338. var box = [ (elw-b.w)/2, (elh-b.h)/2, b.w, b.h ];
  1339. return this[instant?'setSelect':'animateTo'](box);
  1340. },
  1341. // }}}
  1342. //createElement: function(type,ord){{{
  1343. createElement: function(type,ord){
  1344. return $('<div />').addClass(type+' ord-'+ord).data('ord',ord);
  1345. },
  1346. //}}}
  1347. //moveTo: function(x,y){{{
  1348. moveTo: function(x,y){
  1349. this.element.css({top: y+'px', left: x+'px'});
  1350. },
  1351. //}}}
  1352. // blur: function(){{{
  1353. blur: function(){
  1354. this.element.blur();
  1355. return this;
  1356. },
  1357. // }}}
  1358. // focus: function(){{{
  1359. focus: function(){
  1360. this.core.setSelection(this);
  1361. this.frame.focus();
  1362. return this;
  1363. },
  1364. // }}}
  1365. //resize: function(w,h){{{
  1366. resize: function(w,h){
  1367. this.element.css({width: w+'px', height: h+'px'});
  1368. },
  1369. //}}}
  1370. //get: function(){{{
  1371. get: function(){
  1372. var b = this.element,
  1373. o = b.position(),
  1374. w = b.width(),
  1375. h = b.height(),
  1376. rv = { x: o.left, y: o.top };
  1377. rv.x2 = rv.x + w;
  1378. rv.y2 = rv.y + h;
  1379. rv.w = w;
  1380. rv.h = h;
  1381. return rv;
  1382. },
  1383. //}}}
  1384. //insertElements: function(){{{
  1385. insertElements: function(){
  1386. var t = this, i,
  1387. m = t.core,
  1388. fr = t.element,
  1389. o = t.core.opt,
  1390. b = o.borders,
  1391. h = o.handles,
  1392. d = o.dragbars;
  1393. for(i=0; i<d.length; i++)
  1394. fr.append(t.createElement(o.css_dragbars,d[i]));
  1395. for(i=0; i<h.length; i++)
  1396. fr.append(t.createElement(o.css_handles,h[i]));
  1397. for(i=0; i<b.length; i++)
  1398. fr.append(t.createElement(o.css_borders,b[i]));
  1399. }
  1400. //}}}
  1401. }
  1402. });
  1403. Jcrop.registerComponent('Selection',Selection);
  1404. /**
  1405. * StageDrag
  1406. * Facilitates dragging
  1407. */
  1408. // var StageDrag = function(manager,opt){{{
  1409. var StageDrag = function(manager,opt){
  1410. $.extend(this,StageDrag.defaults,opt || {});
  1411. this.manager = manager;
  1412. this.core = manager.core;
  1413. };
  1414. // }}}
  1415. // StageDrag.defaults = {{{
  1416. StageDrag.defaults = {
  1417. offset: [ -8, -8 ],
  1418. active: true,
  1419. minsize: [ 20, 20 ]
  1420. };
  1421. // }}}
  1422. $.extend(StageDrag.prototype,{
  1423. // start: function(e){{{
  1424. start: function(e){
  1425. var c = this.core;
  1426. // Do nothing if allowSelect is off
  1427. if (!c.opt.allowSelect) return;
  1428. // Also do nothing if we can't draw any more selections
  1429. if (c.opt.multi && c.opt.multiMax && (c.ui.multi.length >= c.opt.multiMax)) return false;
  1430. // calculate a few variables for this drag operation
  1431. var o = $(e.currentTarget).offset();
  1432. var origx = e.pageX - o.left + this.offset[0];
  1433. var origy = e.pageY - o.top + this.offset[1];
  1434. var m = c.ui.multi;
  1435. // Determine newly dragged crop behavior if multi disabled
  1436. if (!c.opt.multi) {
  1437. // For multiCleaanup true, remove all existing selections
  1438. if (c.opt.multiCleanup){
  1439. for(var i=0;i<m.length;i++) m[i].remove();
  1440. c.ui.multi = [];
  1441. }
  1442. // If not, only remove the currently active selection
  1443. else {
  1444. c.removeSelection(c.ui.selection);
  1445. }
  1446. }
  1447. c.container.addClass('jcrop-dragging');
  1448. // Create the new selection
  1449. var sel = c.newSelection()
  1450. // and position it
  1451. .updateRaw(Jcrop.wrapFromXywh([origx,origy,1,1]));
  1452. sel.element.trigger('cropstart',[sel,this.core.unscale(sel.get())]);
  1453. return sel.startDrag(e,'se');
  1454. },
  1455. // }}}
  1456. // end: function(x,y){{{
  1457. end: function(x,y){
  1458. this.drag(x,y);
  1459. var b = this.sel.get();
  1460. this.core.container.removeClass('jcrop-dragging');
  1461. if ((b.w < this.minsize[0]) || (b.h < this.minsize[1]))
  1462. this.core.requestDelete();
  1463. else this.sel.focus();
  1464. }
  1465. // }}}
  1466. });
  1467. Jcrop.registerComponent('StageDrag',StageDrag);
  1468. /**
  1469. * StageManager
  1470. * Provides basic stage-specific functionality
  1471. */
  1472. // var StageManager = function(core){{{
  1473. var StageManager = function(core){
  1474. this.core = core;
  1475. this.ui = core.ui;
  1476. this.init();
  1477. };
  1478. // }}}
  1479. $.extend(StageManager.prototype,{
  1480. // init: function(){{{
  1481. init: function(){
  1482. this.setupEvents();
  1483. this.dragger = new StageDrag(this);
  1484. },
  1485. // }}}
  1486. // tellConfigUpdate: function(options){{{
  1487. tellConfigUpdate: function(options){
  1488. for(var i=0,m=this.ui.multi,l=m.length;i<l;i++)
  1489. if (m[i].setOptions && (m[i].linked || (this.core.opt.linkCurrent && m[i] == this.ui.selection)))
  1490. m[i].setOptions(options);
  1491. },
  1492. // }}}
  1493. // startDragHandler: function(){{{
  1494. startDragHandler: function(){
  1495. var t = this;
  1496. return function(e){
  1497. if (!e.button || t.core.opt.is_ie_lt9) return t.dragger.start(e);
  1498. };
  1499. },
  1500. // }}}
  1501. // removeEvents: function(){{{
  1502. removeEvents: function(){
  1503. this.core.event.off('.jcrop-stage');
  1504. this.core.container.off('.jcrop-stage');
  1505. },
  1506. // }}}
  1507. // shimLegacyHandlers: function(options){{{
  1508. // This method uses the legacyHandlers configuration object to
  1509. // gracefully wrap old-style Jcrop events with new ones
  1510. shimLegacyHandlers: function(options){
  1511. var _x = {}, core = this.core, tmp;
  1512. $.each(core.opt.legacyHandlers,function(k,i){
  1513. if (k in options) {
  1514. tmp = options[k];
  1515. core.container.off('.jcrop-'+k)
  1516. .on(i+'.jcrop.jcrop-'+k,function(e,s,c){
  1517. tmp.call(core,c);
  1518. });
  1519. delete options[k];
  1520. }
  1521. });
  1522. },
  1523. // }}}
  1524. // setupEvents: function(){{{
  1525. setupEvents: function(){
  1526. var t = this, c = t.core;
  1527. c.event.on('configupdate.jcrop-stage',function(e){
  1528. t.shimLegacyHandlers(c.opt);
  1529. t.tellConfigUpdate(c.opt)
  1530. c.container.trigger('cropconfig',[c,c.opt]);
  1531. });
  1532. this.core.container
  1533. .on('mousedown.jcrop.jcrop-stage',this.startDragHandler());
  1534. }
  1535. // }}}
  1536. });
  1537. Jcrop.registerComponent('StageManager',StageManager);
  1538. var Thumbnailer = function(){
  1539. };
  1540. $.extend(Thumbnailer,{
  1541. defaults: {
  1542. // Set to a specific Selection object
  1543. // If this value is set, the preview will only track that Selection
  1544. selection: null,
  1545. fading: true,
  1546. fadeDelay: 1000,
  1547. fadeDuration: 1000,
  1548. autoHide: false,
  1549. width: 80,
  1550. height: 80,
  1551. _hiding: null
  1552. },
  1553. prototype: {
  1554. recopyCanvas: function(){
  1555. var s = this.core.ui.stage, cxt = s.context;
  1556. this.context.putImageData(cxt.getImageData(0,0,s.canvas.width,s.canvas.height),0,0);
  1557. },
  1558. init: function(core,options){
  1559. var t = this;
  1560. this.core = core;
  1561. $.extend(this,Thumbnailer.defaults,options);
  1562. t.initEvents();
  1563. t.refresh();
  1564. t.insertElements();
  1565. if (t.selection) {
  1566. t.renderSelection(t.selection);
  1567. t.selectionTarget = t.selection.element[0];
  1568. } else if (t.core.ui.selection) {
  1569. t.renderSelection(t.core.ui.selection);
  1570. }
  1571. if (t.core.ui.stage.canvas) {
  1572. t.context = t.preview[0].getContext('2d');
  1573. t.core.container.on('cropredraw',function(e){
  1574. t.recopyCanvas();
  1575. t.refresh();
  1576. });
  1577. }
  1578. },
  1579. updateImage: function(imgel){
  1580. this.preview.remove();
  1581. this.preview = $($.Jcrop.imageClone(imgel));
  1582. this.element.append(this.preview);
  1583. this.refresh();
  1584. return this;
  1585. },
  1586. insertElements: function(){
  1587. this.preview = $($.Jcrop.imageClone(this.core.ui.stage.imgsrc));
  1588. this.element = $('<div />').addClass('jcrop-thumb')
  1589. .width(this.width).height(this.height)
  1590. .append(this.preview)
  1591. .appendTo(this.core.container);
  1592. },
  1593. resize: function(w,h){
  1594. this.width = w;
  1595. this.height = h;
  1596. this.element.width(w).height(h);
  1597. this.renderCoords(this.last);
  1598. },
  1599. refresh: function(){
  1600. this.cw = (this.core.opt.xscale * this.core.container.width());
  1601. this.ch = (this.core.opt.yscale * this.core.container.height());
  1602. if (this.last) {
  1603. this.renderCoords(this.last);
  1604. }
  1605. },
  1606. renderCoords: function(c){
  1607. var rx = this.width / c.w;
  1608. var ry = this.height / c.h;
  1609. this.preview.css({
  1610. width: Math.round(rx * this.cw) + 'px',
  1611. height: Math.round(ry * this.ch) + 'px',
  1612. marginLeft: '-' + Math.round(rx * c.x) + 'px',
  1613. marginTop: '-' + Math.round(ry * c.y) + 'px'
  1614. });
  1615. this.last = c;
  1616. return this;
  1617. },
  1618. renderSelection: function(s){
  1619. return this.renderCoords(s.core.unscale(s.get()));
  1620. },
  1621. selectionStart: function(s){
  1622. this.renderSelection(s);
  1623. },
  1624. show: function(){
  1625. if (this._hiding) clearTimeout(this._hiding);
  1626. if (!this.fading) this.element.stop().css({ opacity: 1 });
  1627. else this.element.stop().animate({ opacity: 1 },{ duration: 80, queue: false });
  1628. },
  1629. hide: function(){
  1630. var t = this;
  1631. if (!t.fading) t.element.hide();
  1632. else t._hiding = setTimeout(function(){
  1633. t._hiding = null;
  1634. t.element.stop().animate({ opacity: 0 },{ duration: t.fadeDuration, queue: false });
  1635. },t.fadeDelay);
  1636. },
  1637. initEvents: function(){
  1638. var t = this;
  1639. t.core.container.on('croprotstart croprotend cropimage cropstart cropmove cropend',function(e,s,c){
  1640. if (t.selectionTarget && (t.selectionTarget !== e.target)) return false;
  1641. switch(e.type){
  1642. case 'cropimage':
  1643. t.updateImage(c);
  1644. break;
  1645. case 'cropstart':
  1646. t.selectionStart(s);
  1647. case 'croprotstart':
  1648. t.show();
  1649. break;
  1650. case 'cropend':
  1651. t.renderCoords(c);
  1652. case 'croprotend':
  1653. if (t.autoHide) t.hide();
  1654. break;
  1655. case 'cropmove':
  1656. t.renderCoords(c);
  1657. break;
  1658. }
  1659. });
  1660. }
  1661. }
  1662. });
  1663. Jcrop.registerComponent('Thumbnailer',Thumbnailer);
  1664. /**
  1665. * DialDrag component
  1666. * This is a little hacky, it was adapted from some previous/old code
  1667. * Plan to update this API in the future
  1668. */
  1669. var DialDrag = function() { };
  1670. DialDrag.prototype = {
  1671. init: function(core,actuator,callback){
  1672. var that = this;
  1673. if (!actuator) actuator = core.container;
  1674. this.$btn = $(actuator);
  1675. this.$targ = $(actuator);
  1676. this.core = core;
  1677. this.$btn
  1678. .addClass('dialdrag')
  1679. .on('mousedown.dialdrag',this.mousedown())
  1680. .data('dialdrag',this);
  1681. if (!$.isFunction(callback)) callback = function(){ };
  1682. this.callback = callback;
  1683. this.ondone = callback;
  1684. },
  1685. remove: function(){
  1686. this.$btn
  1687. .removeClass('dialdrag')
  1688. .off('.dialdrag')
  1689. .data('dialdrag',null);
  1690. return this;
  1691. },
  1692. setTarget: function(obj){
  1693. this.$targ = $(obj);
  1694. return this;
  1695. },
  1696. getOffset: function(){
  1697. var targ = this.$targ, pos = targ.offset();
  1698. return [
  1699. pos.left + (targ.width()/2),
  1700. pos.top + (targ.height()/2)
  1701. ];
  1702. },
  1703. relMouse: function(e){
  1704. var x = e.pageX - this.offset[0],
  1705. y = e.pageY - this.offset[1],
  1706. ang = Math.atan2(y,x) * (180 / Math.PI),
  1707. vec = Math.sqrt(Math.pow(x,2)+Math.pow(y,2));
  1708. return [ x, y, ang, vec ];
  1709. },
  1710. mousedown: function(){
  1711. var that = this;
  1712. function mouseUp(e){
  1713. $(window).off('.dialdrag');
  1714. that.ondone.call(that,that.relMouse(e));
  1715. that.core.container.trigger('croprotend');
  1716. }
  1717. function mouseMove(e){
  1718. that.callback.call(that,that.relMouse(e));
  1719. }
  1720. return function(e) {
  1721. that.offset = that.getOffset();
  1722. var rel = that.relMouse(e);
  1723. that.angleOffset = -that.core.ui.stage.angle+rel[2];
  1724. that.distOffset = rel[3];
  1725. that.dragOffset = [rel[0],rel[1]];
  1726. that.core.container.trigger('croprotstart');
  1727. $(window)
  1728. .on('mousemove.dialdrag',mouseMove)
  1729. .on('mouseup.dialdrag',mouseUp);
  1730. that.callback.call(that,that.relMouse(e));
  1731. return false;
  1732. };
  1733. }
  1734. };
  1735. Jcrop.registerComponent('DialDrag',DialDrag);
  1736. /////////////////////////////////
  1737. // DEFAULT SETTINGS
  1738. Jcrop.defaults = {
  1739. // Selection Behavior
  1740. edge: { n: 0, s: 0, e: 0, w: 0 },
  1741. setSelect: null,
  1742. linked: true,
  1743. linkCurrent: true,
  1744. canDelete: true,
  1745. canSelect: true,
  1746. canDrag: true,
  1747. canResize: true,
  1748. // Component constructors
  1749. eventManagerComponent: Jcrop.component.EventManager,
  1750. keyboardComponent: Jcrop.component.Keyboard,
  1751. dragstateComponent: Jcrop.component.DragState,
  1752. stagemanagerComponent: Jcrop.component.StageManager,
  1753. animatorComponent: Jcrop.component.Animator,
  1754. selectionComponent: Jcrop.component.Selection,
  1755. // This is a function that is called, which returns a stage object
  1756. stageConstructor: Jcrop.stageConstructor,
  1757. // Stage Behavior
  1758. allowSelect: true,
  1759. multi: false,
  1760. multiMax: false,
  1761. multiCleanup: true,
  1762. animation: true,
  1763. animEasing: 'swing',
  1764. animDuration: 400,
  1765. fading: true,
  1766. fadeDuration: 300,
  1767. fadeEasing: 'swing',
  1768. bgColor: 'black',
  1769. bgOpacity: .5,
  1770. // Startup options
  1771. applyFilters: [ 'constrain', 'extent', 'backoff', 'ratio', 'shader', 'round' ],
  1772. borders: [ 'e', 'w', 's', 'n' ],
  1773. handles: [ 'n', 's', 'e', 'w', 'sw', 'ne', 'nw', 'se' ],
  1774. dragbars: [ 'n', 'e', 'w', 's' ],
  1775. dragEventTarget: window,
  1776. xscale: 1,
  1777. yscale: 1,
  1778. boxWidth: null,
  1779. boxHeight: null,
  1780. // CSS Classes
  1781. // @todo: These need to be moved to top-level object keys
  1782. // for better customization. Currently if you try to extend one
  1783. // via an options object to Jcrop, it will wipe out all
  1784. // the others you don't specify. Be careful for now!
  1785. css_nodrag: 'jcrop-nodrag',
  1786. css_drag: 'jcrop-drag',
  1787. css_container: 'jcrop-active',
  1788. css_shades: 'jcrop-shades',
  1789. css_selection: 'jcrop-selection',
  1790. css_borders: 'jcrop-border',
  1791. css_handles: 'jcrop-handle jcrop-drag',
  1792. css_button: 'jcrop-box jcrop-drag',
  1793. css_noresize: 'jcrop-noresize',
  1794. css_dragbars: 'jcrop-dragbar jcrop-drag',
  1795. legacyHandlers: {
  1796. onChange: 'cropmove',
  1797. onSelect: 'cropend'
  1798. }
  1799. };
  1800. // Jcrop API methods
  1801. $.extend(Jcrop.prototype,{
  1802. //init: function(){{{
  1803. init: function(){
  1804. this.event = new this.opt.eventManagerComponent(this);
  1805. this.ui.keyboard = new this.opt.keyboardComponent(this);
  1806. this.ui.manager = new this.opt.stagemanagerComponent(this);
  1807. this.applyFilters();
  1808. if ($.Jcrop.supportsTouch)
  1809. new $.Jcrop.component.Touch(this);
  1810. this.initEvents();
  1811. },
  1812. //}}}
  1813. // applySizeConstraints: function(){{{
  1814. applySizeConstraints: function(){
  1815. var o = this.opt,
  1816. img = this.opt.imgsrc;
  1817. if (img){
  1818. var iw = img.naturalWidth || img.width,
  1819. ih = img.naturalHeight || img.height,
  1820. bw = o.boxWidth || iw,
  1821. bh = o.boxHeight || ih;
  1822. if (img && ((iw > bw) || (ih > bh))){
  1823. var bx = Jcrop.getLargestBox(iw/ih,bw,bh);
  1824. $(img).width(bx[0]).height(bx[1]);
  1825. this.resizeContainer(bx[0],bx[1]);
  1826. this.opt.xscale = iw / bx[0];
  1827. this.opt.yscale = ih / bx[1];
  1828. }
  1829. }
  1830. if (this.opt.trueSize){
  1831. var dw = this.opt.trueSize[0];
  1832. var dh = this.opt.trueSize[1];
  1833. var cs = this.getContainerSize();
  1834. this.opt.xscale = dw / cs[0];
  1835. this.opt.yscale = dh / cs[1];
  1836. }
  1837. },
  1838. // }}}
  1839. initComponent: function(name){
  1840. if (Jcrop.component[name]) {
  1841. var args = Array.prototype.slice.call(arguments);
  1842. var obj = new Jcrop.component[name];
  1843. args.shift();
  1844. args.unshift(this);
  1845. obj.init.apply(obj,args);
  1846. return obj;
  1847. }
  1848. },
  1849. // setOptions: function(opt){{{
  1850. setOptions: function(opt,proptype){
  1851. if (!$.isPlainObject(opt)) opt = {};
  1852. $.extend(this.opt,opt);
  1853. // Handle a setSelect value
  1854. if (this.opt.setSelect) {
  1855. // If there is no current selection
  1856. // passing setSelect will create one
  1857. if (!this.ui.multi.length)
  1858. this.newSelection();
  1859. // Use these values to update the current selection
  1860. this.setSelect(this.opt.setSelect);
  1861. // Set to null so it doesn't get called again
  1862. this.opt.setSelect = null;
  1863. }
  1864. this.event.trigger('configupdate');
  1865. return this;
  1866. },
  1867. // }}}
  1868. //destroy: function(){{{
  1869. destroy: function(){
  1870. if (this.opt.imgsrc) {
  1871. this.container.before(this.opt.imgsrc);
  1872. this.container.remove();
  1873. $(this.opt.imgsrc).removeData('Jcrop').show();
  1874. } else {
  1875. // @todo: more elegant destroy() process for non-image containers
  1876. this.container.remove();
  1877. }
  1878. },
  1879. // }}}
  1880. // applyFilters: function(){{{
  1881. applyFilters: function(){
  1882. var obj;
  1883. for(var i=0,f=this.opt.applyFilters,l=f.length; i<l; i++){
  1884. if ($.Jcrop.filter[f[i]])
  1885. obj = new $.Jcrop.filter[f[i]];
  1886. obj.core = this;
  1887. if (obj.init) obj.init();
  1888. this.filter[f[i]] = obj;
  1889. }
  1890. },
  1891. // }}}
  1892. // getDefaultFilters: function(){{{
  1893. getDefaultFilters: function(){
  1894. var rv = [];
  1895. for(var i=0,f=this.opt.applyFilters,l=f.length; i<l; i++)
  1896. if(this.filter.hasOwnProperty(f[i]))
  1897. rv.push(this.filter[f[i]]);
  1898. rv.sort(function(x,y){ return x.priority - y.priority; });
  1899. return rv;
  1900. },
  1901. // }}}
  1902. // setSelection: function(sel){{{
  1903. setSelection: function(sel){
  1904. var m = this.ui.multi;
  1905. var n = [];
  1906. for(var i=0;i<m.length;i++) {
  1907. if (m[i] !== sel) n.push(m[i]);
  1908. m[i].toBack();
  1909. }
  1910. n.unshift(sel);
  1911. this.ui.multi = n;
  1912. this.ui.selection = sel;
  1913. sel.toFront();
  1914. return sel;
  1915. },
  1916. // }}}
  1917. // getSelection: function(raw){{{
  1918. getSelection: function(raw){
  1919. var b = this.ui.selection.get();
  1920. return b;
  1921. },
  1922. // }}}
  1923. // newSelection: function(){{{
  1924. newSelection: function(sel){
  1925. if (!sel)
  1926. sel = new this.opt.selectionComponent();
  1927. sel.init(this);
  1928. this.setSelection(sel);
  1929. return sel;
  1930. },
  1931. // }}}
  1932. // hasSelection: function(sel){{{
  1933. hasSelection: function(sel){
  1934. for(var i=0;i<this.ui.multi;i++)
  1935. if (sel === this.ui.multi[i]) return true;
  1936. },
  1937. // }}}
  1938. // removeSelection: function(sel){{{
  1939. removeSelection: function(sel){
  1940. var i, n = [], m = this.ui.multi;
  1941. for(var i=0;i<m.length;i++){
  1942. if (sel !== m[i])
  1943. n.push(m[i]);
  1944. else m[i].remove();
  1945. }
  1946. return this.ui.multi = n;
  1947. },
  1948. // }}}
  1949. //addFilter: function(filter){{{
  1950. addFilter: function(filter){
  1951. for(var i=0,m=this.ui.multi,l=m.length; i<l; i++)
  1952. m[i].addFilter(filter);
  1953. return this;
  1954. },
  1955. //}}}
  1956. // removeFiltersByTag: function(tag){{{
  1957. removeFilter: function(filter){
  1958. for(var i=0,m=this.ui.multi,l=m.length; i<l; i++)
  1959. m[i].removeFilter(filter);
  1960. return this;
  1961. },
  1962. // }}}
  1963. // blur: function(){{{
  1964. blur: function(){
  1965. this.ui.selection.blur();
  1966. return this;
  1967. },
  1968. // }}}
  1969. // focus: function(){{{
  1970. focus: function(){
  1971. this.ui.selection.focus();
  1972. return this;
  1973. },
  1974. // }}}
  1975. //initEvents: function(){{{
  1976. initEvents: function(){
  1977. var t = this;
  1978. t.container.on('selectstart',function(e){ return false; })
  1979. .on('mousedown','.'+t.opt.css_drag,t.startDrag());
  1980. },
  1981. //}}}
  1982. // maxSelect: function(){{{
  1983. maxSelect: function(){
  1984. this.setSelect([0,0,this.elw,this.elh]);
  1985. },
  1986. // }}}
  1987. // nudge: function(x,y){{{
  1988. nudge: function(x,y){
  1989. var s = this.ui.selection, b = s.get();
  1990. b.x += x;
  1991. b.x2 += x;
  1992. b.y += y;
  1993. b.y2 += y;
  1994. if (b.x < 0) { b.x2 = b.w; b.x = 0; }
  1995. else if (b.x2 > this.elw) { b.x2 = this.elw; b.x = b.x2 - b.w; }
  1996. if (b.y < 0) { b.y2 = b.h; b.y = 0; }
  1997. else if (b.y2 > this.elh) { b.y2 = this.elh; b.y = b.y2 - b.h; }
  1998. s.element.trigger('cropstart',[s,this.unscale(b)]);
  1999. s.updateRaw(b,'move');
  2000. s.element.trigger('cropend',[s,this.unscale(b)]);
  2001. },
  2002. // }}}
  2003. // refresh: function(){{{
  2004. refresh: function(){
  2005. for(var i=0,s=this.ui.multi,l=s.length;i<l;i++)
  2006. s[i].refresh();
  2007. },
  2008. // }}}
  2009. // blurAll: function(){{{
  2010. blurAll: function(){
  2011. var m = this.ui.multi;
  2012. for(var i=0;i<m.length;i++) {
  2013. if (m[i] !== sel) n.push(m[i]);
  2014. m[i].toBack();
  2015. }
  2016. },
  2017. // }}}
  2018. // scale: function(b){{{
  2019. scale: function(b){
  2020. var xs = this.opt.xscale,
  2021. ys = this.opt.yscale;
  2022. return {
  2023. x: b.x / xs,
  2024. y: b.y / ys,
  2025. x2: b.x2 / xs,
  2026. y2: b.y2 / ys,
  2027. w: b.w / xs,
  2028. h: b.h / ys
  2029. };
  2030. },
  2031. // }}}
  2032. // unscale: function(b){{{
  2033. unscale: function(b){
  2034. var xs = this.opt.xscale,
  2035. ys = this.opt.yscale;
  2036. return {
  2037. x: b.x * xs,
  2038. y: b.y * ys,
  2039. x2: b.x2 * xs,
  2040. y2: b.y2 * ys,
  2041. w: b.w * xs,
  2042. h: b.h * ys
  2043. };
  2044. },
  2045. // }}}
  2046. // requestDelete: function(){{{
  2047. requestDelete: function(){
  2048. if ((this.ui.multi.length > 1) && (this.ui.selection.canDelete))
  2049. return this.deleteSelection();
  2050. },
  2051. // }}}
  2052. // deleteSelection: function(){{{
  2053. deleteSelection: function(){
  2054. if (this.ui.selection) {
  2055. this.removeSelection(this.ui.selection);
  2056. if (this.ui.multi.length) this.ui.multi[0].focus();
  2057. this.ui.selection.refresh();
  2058. }
  2059. },
  2060. // }}}
  2061. // animateTo: function(box){{{
  2062. animateTo: function(box){
  2063. if (this.ui.selection)
  2064. this.ui.selection.animateTo(box);
  2065. return this;
  2066. },
  2067. // }}}
  2068. // setselect: function(box){{{
  2069. setSelect: function(box){
  2070. if (this.ui.selection)
  2071. this.ui.selection.update(Jcrop.wrapFromXywh(box));
  2072. return this;
  2073. },
  2074. // }}}
  2075. //startDrag: function(){{{
  2076. startDrag: function(){
  2077. var t = this;
  2078. return function(e){
  2079. var $targ = $(e.target);
  2080. var selection = $targ.closest('.'+t.opt.css_selection).data('selection');
  2081. var ord = $targ.data('ord');
  2082. t.container.trigger('cropstart',[selection,t.unscale(selection.get())]);
  2083. selection.startDrag(e,ord);
  2084. return false;
  2085. };
  2086. },
  2087. //}}}
  2088. // getContainerSize: function(){{{
  2089. getContainerSize: function(){
  2090. return [ this.container.width(), this.container.height() ];
  2091. },
  2092. // }}}
  2093. // resizeContainer: function(w,h){{{
  2094. resizeContainer: function(w,h){
  2095. this.container.width(w).height(h);
  2096. this.refresh();
  2097. },
  2098. // }}}
  2099. // setImage: function(src,cb){{{
  2100. setImage: function(src,cb){
  2101. var t = this, targ = t.opt.imgsrc;
  2102. if (!targ) return false;
  2103. new $.Jcrop.component.ImageLoader(src,null,function(w,h){
  2104. t.resizeContainer(w,h);
  2105. targ.src = src;
  2106. $(targ).width(w).height(h);
  2107. t.applySizeConstraints();
  2108. t.refresh();
  2109. t.container.trigger('cropimage',[t,targ]);
  2110. if (typeof cb == 'function')
  2111. cb.call(t,w,h);
  2112. });
  2113. },
  2114. // }}}
  2115. // update: function(b){{{
  2116. update: function(b){
  2117. if (this.ui.selection)
  2118. this.ui.selection.update(b);
  2119. }
  2120. // }}}
  2121. });
  2122. // Jcrop jQuery plugin function
  2123. $.fn.Jcrop = function(options,callback){
  2124. options = options || {};
  2125. var first = this.eq(0).data('Jcrop');
  2126. var args = Array.prototype.slice.call(arguments);
  2127. // Return API if requested
  2128. if (options == 'api') { return first; }
  2129. // Allow calling API methods (with arguments)
  2130. else if (first && (typeof options == 'string')) {
  2131. // Call method if it exists
  2132. if (first[options]) {
  2133. args.shift();
  2134. first[options].apply(first,args);
  2135. return first;
  2136. }
  2137. // Unknown input/method does not exist
  2138. return false;
  2139. }
  2140. // Otherwise, loop over selected elements
  2141. this.each(function(){
  2142. var t = this, $t = $(this);
  2143. var exists = $t.data('Jcrop');
  2144. var obj;
  2145. // If Jcrop already exists on this element only setOptions()
  2146. if (exists)
  2147. exists.setOptions(options);
  2148. else {
  2149. if (!options.stageConstructor)
  2150. options.stageConstructor = $.Jcrop.stageConstructor;
  2151. options.stageConstructor(this,options,function(stage,options){
  2152. var selection = options.setSelect;
  2153. if (selection) delete(options.setSelect);
  2154. var obj = $.Jcrop.attach(stage.element,options);
  2155. if (typeof stage.attach == 'function')
  2156. stage.attach(obj);
  2157. $t.data('Jcrop',obj);
  2158. if (selection) {
  2159. obj.newSelection();
  2160. obj.setSelect(selection);
  2161. }
  2162. if (typeof callback == 'function')
  2163. callback.call(obj);
  2164. });
  2165. }
  2166. return this;
  2167. });
  2168. };
  2169. /* Modernizr 2.7.1 (Custom Build) | MIT & BSD
  2170. * Build: http://modernizr.com/download/#-csstransforms-canvas-canvastext-draganddrop-inlinesvg-svg-svgclippaths-touch-teststyles-testprop-testallprops-hasevent-prefixes-domprefixes-url_data_uri
  2171. */
  2172. ;
  2173. var Modernizr = (function( window, document, undefined ) {
  2174. var version = '2.7.1',
  2175. Modernizr = {},
  2176. docElement = document.documentElement,
  2177. mod = 'modernizr',
  2178. modElem = document.createElement(mod),
  2179. mStyle = modElem.style,
  2180. inputElem ,
  2181. toString = {}.toString,
  2182. prefixes = ' -webkit- -moz- -o- -ms- '.split(' '),
  2183. omPrefixes = 'Webkit Moz O ms',
  2184. cssomPrefixes = omPrefixes.split(' '),
  2185. domPrefixes = omPrefixes.toLowerCase().split(' '),
  2186. ns = {'svg': 'http://www.w3.org/2000/svg'},
  2187. tests = {},
  2188. inputs = {},
  2189. attrs = {},
  2190. classes = [],
  2191. slice = classes.slice,
  2192. featureName,
  2193. injectElementWithStyles = function( rule, callback, nodes, testnames ) {
  2194. var style, ret, node, docOverflow,
  2195. div = document.createElement('div'),
  2196. body = document.body,
  2197. fakeBody = body || document.createElement('body');
  2198. if ( parseInt(nodes, 10) ) {
  2199. while ( nodes-- ) {
  2200. node = document.createElement('div');
  2201. node.id = testnames ? testnames[nodes] : mod + (nodes + 1);
  2202. div.appendChild(node);
  2203. }
  2204. }
  2205. style = ['&#173;','<style id="s', mod, '">', rule, '</style>'].join('');
  2206. div.id = mod;
  2207. (body ? div : fakeBody).innerHTML += style;
  2208. fakeBody.appendChild(div);
  2209. if ( !body ) {
  2210. fakeBody.style.background = '';
  2211. fakeBody.style.overflow = 'hidden';
  2212. docOverflow = docElement.style.overflow;
  2213. docElement.style.overflow = 'hidden';
  2214. docElement.appendChild(fakeBody);
  2215. }
  2216. ret = callback(div, rule);
  2217. if ( !body ) {
  2218. fakeBody.parentNode.removeChild(fakeBody);
  2219. docElement.style.overflow = docOverflow;
  2220. } else {
  2221. div.parentNode.removeChild(div);
  2222. }
  2223. return !!ret;
  2224. },
  2225. isEventSupported = (function() {
  2226. var TAGNAMES = {
  2227. 'select': 'input', 'change': 'input',
  2228. 'submit': 'form', 'reset': 'form',
  2229. 'error': 'img', 'load': 'img', 'abort': 'img'
  2230. };
  2231. function isEventSupported( eventName, element ) {
  2232. element = element || document.createElement(TAGNAMES[eventName] || 'div');
  2233. eventName = 'on' + eventName;
  2234. var isSupported = eventName in element;
  2235. if ( !isSupported ) {
  2236. if ( !element.setAttribute ) {
  2237. element = document.createElement('div');
  2238. }
  2239. if ( element.setAttribute && element.removeAttribute ) {
  2240. element.setAttribute(eventName, '');
  2241. isSupported = is(element[eventName], 'function');
  2242. if ( !is(element[eventName], 'undefined') ) {
  2243. element[eventName] = undefined;
  2244. }
  2245. element.removeAttribute(eventName);
  2246. }
  2247. }
  2248. element = null;
  2249. return isSupported;
  2250. }
  2251. return isEventSupported;
  2252. })(),
  2253. _hasOwnProperty = ({}).hasOwnProperty, hasOwnProp;
  2254. if ( !is(_hasOwnProperty, 'undefined') && !is(_hasOwnProperty.call, 'undefined') ) {
  2255. hasOwnProp = function (object, property) {
  2256. return _hasOwnProperty.call(object, property);
  2257. };
  2258. }
  2259. else {
  2260. hasOwnProp = function (object, property) {
  2261. return ((property in object) && is(object.constructor.prototype[property], 'undefined'));
  2262. };
  2263. }
  2264. if (!Function.prototype.bind) {
  2265. Function.prototype.bind = function bind(that) {
  2266. var target = this;
  2267. if (typeof target != "function") {
  2268. throw new TypeError();
  2269. }
  2270. var args = slice.call(arguments, 1),
  2271. bound = function () {
  2272. if (this instanceof bound) {
  2273. var F = function(){};
  2274. F.prototype = target.prototype;
  2275. var self = new F();
  2276. var result = target.apply(
  2277. self,
  2278. args.concat(slice.call(arguments))
  2279. );
  2280. if (Object(result) === result) {
  2281. return result;
  2282. }
  2283. return self;
  2284. } else {
  2285. return target.apply(
  2286. that,
  2287. args.concat(slice.call(arguments))
  2288. );
  2289. }
  2290. };
  2291. return bound;
  2292. };
  2293. }
  2294. function setCss( str ) {
  2295. mStyle.cssText = str;
  2296. }
  2297. function setCssAll( str1, str2 ) {
  2298. return setCss(prefixes.join(str1 + ';') + ( str2 || '' ));
  2299. }
  2300. function is( obj, type ) {
  2301. return typeof obj === type;
  2302. }
  2303. function contains( str, substr ) {
  2304. return !!~('' + str).indexOf(substr);
  2305. }
  2306. function testProps( props, prefixed ) {
  2307. for ( var i in props ) {
  2308. var prop = props[i];
  2309. if ( !contains(prop, "-") && mStyle[prop] !== undefined ) {
  2310. return prefixed == 'pfx' ? prop : true;
  2311. }
  2312. }
  2313. return false;
  2314. }
  2315. function testDOMProps( props, obj, elem ) {
  2316. for ( var i in props ) {
  2317. var item = obj[props[i]];
  2318. if ( item !== undefined) {
  2319. if (elem === false) return props[i];
  2320. if (is(item, 'function')){
  2321. return item.bind(elem || obj);
  2322. }
  2323. return item;
  2324. }
  2325. }
  2326. return false;
  2327. }
  2328. function testPropsAll( prop, prefixed, elem ) {
  2329. var ucProp = prop.charAt(0).toUpperCase() + prop.slice(1),
  2330. props = (prop + ' ' + cssomPrefixes.join(ucProp + ' ') + ucProp).split(' ');
  2331. if(is(prefixed, "string") || is(prefixed, "undefined")) {
  2332. return testProps(props, prefixed);
  2333. } else {
  2334. props = (prop + ' ' + (domPrefixes).join(ucProp + ' ') + ucProp).split(' ');
  2335. return testDOMProps(props, prefixed, elem);
  2336. }
  2337. }
  2338. tests['canvas'] = function() {
  2339. var elem = document.createElement('canvas');
  2340. return !!(elem.getContext && elem.getContext('2d'));
  2341. };
  2342. tests['canvastext'] = function() {
  2343. return !!(Modernizr['canvas'] && is(document.createElement('canvas').getContext('2d').fillText, 'function'));
  2344. };
  2345. tests['touch'] = function() {
  2346. var bool;
  2347. if(('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch) {
  2348. bool = true;
  2349. } else {
  2350. injectElementWithStyles(['@media (',prefixes.join('touch-enabled),('),mod,')','{#modernizr{top:9px;position:absolute}}'].join(''), function( node ) {
  2351. bool = node.offsetTop === 9;
  2352. });
  2353. }
  2354. return bool;
  2355. };
  2356. tests['draganddrop'] = function() {
  2357. var div = document.createElement('div');
  2358. return ('draggable' in div) || ('ondragstart' in div && 'ondrop' in div);
  2359. };
  2360. tests['csstransforms'] = function() {
  2361. return !!testPropsAll('transform');
  2362. };
  2363. tests['svg'] = function() {
  2364. return !!document.createElementNS && !!document.createElementNS(ns.svg, 'svg').createSVGRect;
  2365. };
  2366. tests['inlinesvg'] = function() {
  2367. var div = document.createElement('div');
  2368. div.innerHTML = '<svg/>';
  2369. return (div.firstChild && div.firstChild.namespaceURI) == ns.svg;
  2370. };
  2371. tests['svgclippaths'] = function() {
  2372. return !!document.createElementNS && /SVGClipPath/.test(toString.call(document.createElementNS(ns.svg, 'clipPath')));
  2373. };
  2374. for ( var feature in tests ) {
  2375. if ( hasOwnProp(tests, feature) ) {
  2376. featureName = feature.toLowerCase();
  2377. Modernizr[featureName] = tests[feature]();
  2378. classes.push((Modernizr[featureName] ? '' : 'no-') + featureName);
  2379. }
  2380. }
  2381. Modernizr.addTest = function ( feature, test ) {
  2382. if ( typeof feature == 'object' ) {
  2383. for ( var key in feature ) {
  2384. if ( hasOwnProp( feature, key ) ) {
  2385. Modernizr.addTest( key, feature[ key ] );
  2386. }
  2387. }
  2388. } else {
  2389. feature = feature.toLowerCase();
  2390. if ( Modernizr[feature] !== undefined ) {
  2391. return Modernizr;
  2392. }
  2393. test = typeof test == 'function' ? test() : test;
  2394. if (typeof enableClasses !== "undefined" && enableClasses) {
  2395. docElement.className += ' ' + (test ? '' : 'no-') + feature;
  2396. }
  2397. Modernizr[feature] = test;
  2398. }
  2399. return Modernizr;
  2400. };
  2401. setCss('');
  2402. modElem = inputElem = null;
  2403. Modernizr._version = version;
  2404. Modernizr._prefixes = prefixes;
  2405. Modernizr._domPrefixes = domPrefixes;
  2406. Modernizr._cssomPrefixes = cssomPrefixes;
  2407. Modernizr.hasEvent = isEventSupported;
  2408. Modernizr.testProp = function(prop){
  2409. return testProps([prop]);
  2410. };
  2411. Modernizr.testAllProps = testPropsAll;
  2412. Modernizr.testStyles = injectElementWithStyles;
  2413. return Modernizr;
  2414. })(window, window.document);
  2415. // data uri test.
  2416. // https://github.com/Modernizr/Modernizr/issues/14
  2417. // This test is asynchronous. Watch out.
  2418. // in IE7 in HTTPS this can cause a Mixed Content security popup.
  2419. // github.com/Modernizr/Modernizr/issues/362
  2420. // To avoid that you can create a new iframe and inject this.. perhaps..
  2421. (function(){
  2422. var datauri = new Image();
  2423. datauri.onerror = function() {
  2424. Modernizr.addTest('datauri', function () { return false; });
  2425. };
  2426. datauri.onload = function() {
  2427. Modernizr.addTest('datauri', function () { return (datauri.width == 1 && datauri.height == 1); });
  2428. };
  2429. datauri.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==";
  2430. })();
  2431. ;
  2432. // Attach to jQuery object
  2433. $.Jcrop = Jcrop;
  2434. $.Jcrop.supportsCanvas = Modernizr.canvas;
  2435. $.Jcrop.supportsCanvasText = Modernizr.canvastext;
  2436. $.Jcrop.supportsDragAndDrop = Modernizr.draganddrop;
  2437. $.Jcrop.supportsDataURI = Modernizr.datauri;
  2438. $.Jcrop.supportsSVG = Modernizr.svg;
  2439. $.Jcrop.supportsInlineSVG = Modernizr.inlinesvg;
  2440. $.Jcrop.supportsSVGClipPaths = Modernizr.svgclippaths;
  2441. $.Jcrop.supportsCSSTransforms = Modernizr.csstransforms;
  2442. $.Jcrop.supportsTouch = Modernizr.touch;
  2443. })(jQuery);