jquery.Jcrop.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197
  1. /**
  2. * jquery.Jcrop.js v0.9.8
  3. * jQuery Image Cropping Plugin
  4. * @author Kelly Hallman <khallman@gmail.com>
  5. * Copyright (c) 2008-2009 Kelly Hallman - released under MIT License {{{
  6. *
  7. * Permission is hereby granted, free of charge, to any person
  8. * obtaining a copy of this software and associated documentation
  9. * files (the "Software"), to deal in the Software without
  10. * restriction, including without limitation the rights to use,
  11. * copy, modify, merge, publish, distribute, sublicense, and/or sell
  12. * copies of the Software, and to permit persons to whom the
  13. * Software is furnished to do so, subject to the following
  14. * conditions:
  15. * The above copyright notice and this permission notice shall be
  16. * included in all copies or substantial portions of the Software.
  17. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  18. * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
  19. * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  20. * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  21. * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  22. * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  23. * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
  24. * OTHER DEALINGS IN THE SOFTWARE.
  25. * }}}
  26. */
  27. (function($) {
  28. $.Jcrop = function(obj,opt)
  29. {
  30. // Initialization {{{
  31. // Sanitize some options {{{
  32. var obj = obj, opt = opt;
  33. if (typeof(obj) !== 'object') obj = $(obj)[0];
  34. if (typeof(opt) !== 'object') opt = { };
  35. // Some on-the-fly fixes for MSIE...sigh
  36. if (!('trackDocument' in opt))
  37. {
  38. opt.trackDocument = $.browser.msie ? false : true;
  39. if ($.browser.msie && $.browser.version.split('.')[0] == '8')
  40. opt.trackDocument = true;
  41. }
  42. if (!('keySupport' in opt))
  43. opt.keySupport = $.browser.msie ? false : true;
  44. // }}}
  45. // Extend the default options {{{
  46. var defaults = {
  47. // Basic Settings
  48. trackDocument: false,
  49. baseClass: 'jcrop',
  50. addClass: null,
  51. // Styling Options
  52. bgColor: 'black',
  53. bgOpacity: .6,
  54. borderOpacity: .4,
  55. handleOpacity: .5,
  56. handlePad: 5,
  57. handleSize: 9,
  58. handleOffset: 5,
  59. edgeMargin: 14,
  60. aspectRatio: 0,
  61. keySupport: true,
  62. cornerHandles: true,
  63. sideHandles: true,
  64. drawBorders: true,
  65. dragEdges: true,
  66. boxWidth: 0,
  67. boxHeight: 0,
  68. boundary: 8,
  69. animationDelay: 20,
  70. swingSpeed: 3,
  71. allowSelect: true,
  72. allowMove: true,
  73. allowResize: true,
  74. minSelect: [ 0, 0 ],
  75. maxSize: [ 0, 0 ],
  76. minSize: [ 0, 0 ],
  77. // Callbacks / Event Handlers
  78. onChange: function() { },
  79. onSelect: function() { }
  80. };
  81. var options = defaults;
  82. setOptions(opt);
  83. // }}}
  84. // Initialize some jQuery objects {{{
  85. var $origimg = $(obj);
  86. var $img = $origimg.clone().removeAttr('id').css({ position: 'absolute' });
  87. $img.width($origimg.width());
  88. $img.height($origimg.height());
  89. $origimg.after($img).hide();
  90. presize($img,options.boxWidth,options.boxHeight);
  91. var boundx = $img.width(),
  92. boundy = $img.height(),
  93. $div = $('<div />')
  94. .width(boundx).height(boundy)
  95. .addClass(cssClass('holder'))
  96. .css({
  97. position: 'relative',
  98. backgroundColor: options.bgColor
  99. }).insertAfter($origimg).append($img);
  100. ;
  101. if (options.addClass) $div.addClass(options.addClass);
  102. //$img.wrap($div);
  103. var $img2 = $('<img />')/*{{{*/
  104. .attr('src',$img.attr('src'))
  105. .css('position','absolute')
  106. .width(boundx).height(boundy)
  107. ;/*}}}*/
  108. var $img_holder = $('<div />')/*{{{*/
  109. .width(pct(100)).height(pct(100))
  110. .css({
  111. zIndex: 310,
  112. position: 'absolute',
  113. overflow: 'hidden'
  114. })
  115. .append($img2)
  116. ;/*}}}*/
  117. var $hdl_holder = $('<div />')/*{{{*/
  118. .width(pct(100)).height(pct(100))
  119. .css('zIndex',320);
  120. /*}}}*/
  121. var $sel = $('<div />')/*{{{*/
  122. .css({
  123. position: 'absolute',
  124. zIndex: 300
  125. })
  126. .insertBefore($img)
  127. .append($img_holder,$hdl_holder)
  128. ;/*}}}*/
  129. var bound = options.boundary;
  130. var $trk = newTracker().width(boundx+(bound*2)).height(boundy+(bound*2))
  131. .css({ position: 'absolute', top: px(-bound), left: px(-bound), zIndex: 290 })
  132. .mousedown(newSelection);
  133. /* }}} */
  134. // Set more variables {{{
  135. var xlimit, ylimit, xmin, ymin;
  136. var xscale, yscale, enabled = true;
  137. var docOffset = getPos($img),
  138. // Internal states
  139. btndown, lastcurs, dimmed, animating,
  140. shift_down;
  141. // }}}
  142. // }}}
  143. // Internal Modules {{{
  144. var Coords = function()/*{{{*/
  145. {
  146. var x1 = 0, y1 = 0, x2 = 0, y2 = 0, ox, oy;
  147. function setPressed(pos)/*{{{*/
  148. {
  149. var pos = rebound(pos);
  150. x2 = x1 = pos[0];
  151. y2 = y1 = pos[1];
  152. };
  153. /*}}}*/
  154. function setCurrent(pos)/*{{{*/
  155. {
  156. var pos = rebound(pos);
  157. ox = pos[0] - x2;
  158. oy = pos[1] - y2;
  159. x2 = pos[0];
  160. y2 = pos[1];
  161. };
  162. /*}}}*/
  163. function getOffset()/*{{{*/
  164. {
  165. return [ ox, oy ];
  166. };
  167. /*}}}*/
  168. function moveOffset(offset)/*{{{*/
  169. {
  170. var ox = offset[0], oy = offset[1];
  171. if (0 > x1 + ox) ox -= ox + x1;
  172. if (0 > y1 + oy) oy -= oy + y1;
  173. if (boundy < y2 + oy) oy += boundy - (y2 + oy);
  174. if (boundx < x2 + ox) ox += boundx - (x2 + ox);
  175. x1 += ox;
  176. x2 += ox;
  177. y1 += oy;
  178. y2 += oy;
  179. };
  180. /*}}}*/
  181. function getCorner(ord)/*{{{*/
  182. {
  183. var c = getFixed();
  184. switch(ord)
  185. {
  186. case 'ne': return [ c.x2, c.y ];
  187. case 'nw': return [ c.x, c.y ];
  188. case 'se': return [ c.x2, c.y2 ];
  189. case 'sw': return [ c.x, c.y2 ];
  190. }
  191. };
  192. /*}}}*/
  193. function getFixed()/*{{{*/
  194. {
  195. if (!options.aspectRatio) return getRect();
  196. // This function could use some optimization I think...
  197. var aspect = options.aspectRatio,
  198. min_x = options.minSize[0]/xscale,
  199. min_y = options.minSize[1]/yscale,
  200. max_x = options.maxSize[0]/xscale,
  201. max_y = options.maxSize[1]/yscale,
  202. rw = x2 - x1,
  203. rh = y2 - y1,
  204. rwa = Math.abs(rw),
  205. rha = Math.abs(rh),
  206. real_ratio = rwa / rha,
  207. xx, yy
  208. ;
  209. if (max_x == 0) { max_x = boundx * 10 }
  210. if (max_y == 0) { max_y = boundy * 10 }
  211. if (real_ratio < aspect)
  212. {
  213. yy = y2;
  214. w = rha * aspect;
  215. xx = rw < 0 ? x1 - w : w + x1;
  216. if (xx < 0)
  217. {
  218. xx = 0;
  219. h = Math.abs((xx - x1) / aspect);
  220. yy = rh < 0 ? y1 - h: h + y1;
  221. }
  222. else if (xx > boundx)
  223. {
  224. xx = boundx;
  225. h = Math.abs((xx - x1) / aspect);
  226. yy = rh < 0 ? y1 - h : h + y1;
  227. }
  228. }
  229. else
  230. {
  231. xx = x2;
  232. h = rwa / aspect;
  233. yy = rh < 0 ? y1 - h : y1 + h;
  234. if (yy < 0)
  235. {
  236. yy = 0;
  237. w = Math.abs((yy - y1) * aspect);
  238. xx = rw < 0 ? x1 - w : w + x1;
  239. }
  240. else if (yy > boundy)
  241. {
  242. yy = boundy;
  243. w = Math.abs(yy - y1) * aspect;
  244. xx = rw < 0 ? x1 - w : w + x1;
  245. }
  246. }
  247. // Magic %-)
  248. if(xx > x1) { // right side
  249. if(xx - x1 < min_x) {
  250. xx = x1 + min_x;
  251. } else if (xx - x1 > max_x) {
  252. xx = x1 + max_x;
  253. }
  254. if(yy > y1) {
  255. yy = y1 + (xx - x1)/aspect;
  256. } else {
  257. yy = y1 - (xx - x1)/aspect;
  258. }
  259. } else if (xx < x1) { // left side
  260. if(x1 - xx < min_x) {
  261. xx = x1 - min_x
  262. } else if (x1 - xx > max_x) {
  263. xx = x1 - max_x;
  264. }
  265. if(yy > y1) {
  266. yy = y1 + (x1 - xx)/aspect;
  267. } else {
  268. yy = y1 - (x1 - xx)/aspect;
  269. }
  270. }
  271. if(xx < 0) {
  272. x1 -= xx;
  273. xx = 0;
  274. } else if (xx > boundx) {
  275. x1 -= xx - boundx;
  276. xx = boundx;
  277. }
  278. if(yy < 0) {
  279. y1 -= yy;
  280. yy = 0;
  281. } else if (yy > boundy) {
  282. y1 -= yy - boundy;
  283. yy = boundy;
  284. }
  285. return last = makeObj(flipCoords(x1,y1,xx,yy));
  286. };
  287. /*}}}*/
  288. function rebound(p)/*{{{*/
  289. {
  290. if (p[0] < 0) p[0] = 0;
  291. if (p[1] < 0) p[1] = 0;
  292. if (p[0] > boundx) p[0] = boundx;
  293. if (p[1] > boundy) p[1] = boundy;
  294. return [ p[0], p[1] ];
  295. };
  296. /*}}}*/
  297. function flipCoords(x1,y1,x2,y2)/*{{{*/
  298. {
  299. var xa = x1, xb = x2, ya = y1, yb = y2;
  300. if (x2 < x1)
  301. {
  302. xa = x2;
  303. xb = x1;
  304. }
  305. if (y2 < y1)
  306. {
  307. ya = y2;
  308. yb = y1;
  309. }
  310. return [ Math.round(xa), Math.round(ya), Math.round(xb), Math.round(yb) ];
  311. };
  312. /*}}}*/
  313. function getRect()/*{{{*/
  314. {
  315. var xsize = x2 - x1;
  316. var ysize = y2 - y1;
  317. if (xlimit && (Math.abs(xsize) > xlimit))
  318. x2 = (xsize > 0) ? (x1 + xlimit) : (x1 - xlimit);
  319. if (ylimit && (Math.abs(ysize) > ylimit))
  320. y2 = (ysize > 0) ? (y1 + ylimit) : (y1 - ylimit);
  321. if (ymin && (Math.abs(ysize) < ymin))
  322. y2 = (ysize > 0) ? (y1 + ymin) : (y1 - ymin);
  323. if (xmin && (Math.abs(xsize) < xmin))
  324. x2 = (xsize > 0) ? (x1 + xmin) : (x1 - xmin);
  325. if (x1 < 0) { x2 -= x1; x1 -= x1; }
  326. if (y1 < 0) { y2 -= y1; y1 -= y1; }
  327. if (x2 < 0) { x1 -= x2; x2 -= x2; }
  328. if (y2 < 0) { y1 -= y2; y2 -= y2; }
  329. if (x2 > boundx) { var delta = x2 - boundx; x1 -= delta; x2 -= delta; }
  330. if (y2 > boundy) { var delta = y2 - boundy; y1 -= delta; y2 -= delta; }
  331. if (x1 > boundx) { var delta = x1 - boundy; y2 -= delta; y1 -= delta; }
  332. if (y1 > boundy) { var delta = y1 - boundy; y2 -= delta; y1 -= delta; }
  333. return makeObj(flipCoords(x1,y1,x2,y2));
  334. };
  335. /*}}}*/
  336. function makeObj(a)/*{{{*/
  337. {
  338. return { x: a[0], y: a[1], x2: a[2], y2: a[3],
  339. w: a[2] - a[0], h: a[3] - a[1] };
  340. };
  341. /*}}}*/
  342. return {
  343. flipCoords: flipCoords,
  344. setPressed: setPressed,
  345. setCurrent: setCurrent,
  346. getOffset: getOffset,
  347. moveOffset: moveOffset,
  348. getCorner: getCorner,
  349. getFixed: getFixed
  350. };
  351. }();
  352. /*}}}*/
  353. var Selection = function()/*{{{*/
  354. {
  355. var start, end, dragmode, awake, hdep = 370;
  356. var borders = { };
  357. var handle = { };
  358. var seehandles = false;
  359. var hhs = options.handleOffset;
  360. /* Insert draggable elements {{{*/
  361. // Insert border divs for outline
  362. if (options.drawBorders) {
  363. borders = {
  364. top: insertBorder('hline')
  365. .css('top',$.browser.msie?px(-1):px(0)),
  366. bottom: insertBorder('hline'),
  367. left: insertBorder('vline'),
  368. right: insertBorder('vline')
  369. };
  370. }
  371. // Insert handles on edges
  372. if (options.dragEdges) {
  373. handle.t = insertDragbar('n');
  374. handle.b = insertDragbar('s');
  375. handle.r = insertDragbar('e');
  376. handle.l = insertDragbar('w');
  377. }
  378. // Insert side handles
  379. options.sideHandles &&
  380. createHandles(['n','s','e','w']);
  381. // Insert corner handles
  382. options.cornerHandles &&
  383. createHandles(['sw','nw','ne','se']);
  384. /*}}}*/
  385. // Private Methods
  386. function insertBorder(type)/*{{{*/
  387. {
  388. var jq = $('<div />')
  389. .css({position: 'absolute', opacity: options.borderOpacity })
  390. .addClass(cssClass(type));
  391. $img_holder.append(jq);
  392. return jq;
  393. };
  394. /*}}}*/
  395. function dragDiv(ord,zi)/*{{{*/
  396. {
  397. var jq = $('<div />')
  398. .mousedown(createDragger(ord))
  399. .css({
  400. cursor: ord+'-resize',
  401. position: 'absolute',
  402. zIndex: zi
  403. })
  404. ;
  405. $hdl_holder.append(jq);
  406. return jq;
  407. };
  408. /*}}}*/
  409. function insertHandle(ord)/*{{{*/
  410. {
  411. return dragDiv(ord,hdep++)
  412. .css({ top: px(-hhs+1), left: px(-hhs+1), opacity: options.handleOpacity })
  413. .addClass(cssClass('handle'));
  414. };
  415. /*}}}*/
  416. function insertDragbar(ord)/*{{{*/
  417. {
  418. var s = options.handleSize,
  419. o = hhs,
  420. h = s, w = s,
  421. t = o, l = o;
  422. switch(ord)
  423. {
  424. case 'n': case 's': w = pct(100); break;
  425. case 'e': case 'w': h = pct(100); break;
  426. }
  427. return dragDiv(ord,hdep++).width(w).height(h)
  428. .css({ top: px(-t+1), left: px(-l+1)});
  429. };
  430. /*}}}*/
  431. function createHandles(li)/*{{{*/
  432. {
  433. for(i in li) handle[li[i]] = insertHandle(li[i]);
  434. };
  435. /*}}}*/
  436. function moveHandles(c)/*{{{*/
  437. {
  438. var midvert = Math.round((c.h / 2) - hhs),
  439. midhoriz = Math.round((c.w / 2) - hhs),
  440. north = west = -hhs+1,
  441. east = c.w - hhs,
  442. south = c.h - hhs,
  443. x, y;
  444. 'e' in handle &&
  445. handle.e.css({ top: px(midvert), left: px(east) }) &&
  446. handle.w.css({ top: px(midvert) }) &&
  447. handle.s.css({ top: px(south), left: px(midhoriz) }) &&
  448. handle.n.css({ left: px(midhoriz) });
  449. 'ne' in handle &&
  450. handle.ne.css({ left: px(east) }) &&
  451. handle.se.css({ top: px(south), left: px(east) }) &&
  452. handle.sw.css({ top: px(south) });
  453. 'b' in handle &&
  454. handle.b.css({ top: px(south) }) &&
  455. handle.r.css({ left: px(east) });
  456. };
  457. /*}}}*/
  458. function moveto(x,y)/*{{{*/
  459. {
  460. $img2.css({ top: px(-y), left: px(-x) });
  461. $sel.css({ top: px(y), left: px(x) });
  462. };
  463. /*}}}*/
  464. function resize(w,h)/*{{{*/
  465. {
  466. $sel.width(w).height(h);
  467. };
  468. /*}}}*/
  469. function refresh()/*{{{*/
  470. {
  471. var c = Coords.getFixed();
  472. Coords.setPressed([c.x,c.y]);
  473. Coords.setCurrent([c.x2,c.y2]);
  474. updateVisible();
  475. };
  476. /*}}}*/
  477. // Internal Methods
  478. function updateVisible()/*{{{*/
  479. { if (awake) return update(); };
  480. /*}}}*/
  481. function update()/*{{{*/
  482. {
  483. var c = Coords.getFixed();
  484. resize(c.w,c.h);
  485. moveto(c.x,c.y);
  486. options.drawBorders &&
  487. borders['right'].css({ left: px(c.w-1) }) &&
  488. borders['bottom'].css({ top: px(c.h-1) });
  489. seehandles && moveHandles(c);
  490. awake || show();
  491. options.onChange(unscale(c));
  492. };
  493. /*}}}*/
  494. function show()/*{{{*/
  495. {
  496. $sel.show();
  497. $img.css('opacity',options.bgOpacity);
  498. awake = true;
  499. };
  500. /*}}}*/
  501. function release()/*{{{*/
  502. {
  503. disableHandles();
  504. $sel.hide();
  505. $img.css('opacity',1);
  506. awake = false;
  507. };
  508. /*}}}*/
  509. function showHandles()//{{{
  510. {
  511. if (seehandles)
  512. {
  513. moveHandles(Coords.getFixed());
  514. $hdl_holder.show();
  515. }
  516. };
  517. //}}}
  518. function enableHandles()/*{{{*/
  519. {
  520. seehandles = true;
  521. if (options.allowResize)
  522. {
  523. moveHandles(Coords.getFixed());
  524. $hdl_holder.show();
  525. return true;
  526. }
  527. };
  528. /*}}}*/
  529. function disableHandles()/*{{{*/
  530. {
  531. seehandles = false;
  532. $hdl_holder.hide();
  533. };
  534. /*}}}*/
  535. function animMode(v)/*{{{*/
  536. {
  537. (animating = v) ? disableHandles(): enableHandles();
  538. };
  539. /*}}}*/
  540. function done()/*{{{*/
  541. {
  542. animMode(false);
  543. refresh();
  544. };
  545. /*}}}*/
  546. var $track = newTracker().mousedown(createDragger('move'))
  547. .css({ cursor: 'move', position: 'absolute', zIndex: 360 })
  548. $img_holder.append($track);
  549. disableHandles();
  550. return {
  551. updateVisible: updateVisible,
  552. update: update,
  553. release: release,
  554. refresh: refresh,
  555. setCursor: function (cursor) { $track.css('cursor',cursor); },
  556. enableHandles: enableHandles,
  557. enableOnly: function() { seehandles = true; },
  558. showHandles: showHandles,
  559. disableHandles: disableHandles,
  560. animMode: animMode,
  561. done: done
  562. };
  563. }();
  564. /*}}}*/
  565. var Tracker = function()/*{{{*/
  566. {
  567. var onMove = function() { },
  568. onDone = function() { },
  569. trackDoc = options.trackDocument;
  570. if (!trackDoc)
  571. {
  572. $trk
  573. .mousemove(trackMove)
  574. .mouseup(trackUp)
  575. .mouseout(trackUp)
  576. ;
  577. }
  578. function toFront()/*{{{*/
  579. {
  580. $trk.css({zIndex:450});
  581. if (trackDoc)
  582. {
  583. $(document)
  584. .mousemove(trackMove)
  585. .mouseup(trackUp)
  586. ;
  587. }
  588. }
  589. /*}}}*/
  590. function toBack()/*{{{*/
  591. {
  592. $trk.css({zIndex:290});
  593. if (trackDoc)
  594. {
  595. $(document)
  596. .unbind('mousemove',trackMove)
  597. .unbind('mouseup',trackUp)
  598. ;
  599. }
  600. }
  601. /*}}}*/
  602. function trackMove(e)/*{{{*/
  603. {
  604. onMove(mouseAbs(e));
  605. };
  606. /*}}}*/
  607. function trackUp(e)/*{{{*/
  608. {
  609. e.preventDefault();
  610. e.stopPropagation();
  611. if (btndown)
  612. {
  613. btndown = false;
  614. onDone(mouseAbs(e));
  615. options.onSelect(unscale(Coords.getFixed()));
  616. toBack();
  617. onMove = function() { };
  618. onDone = function() { };
  619. }
  620. return false;
  621. };
  622. /*}}}*/
  623. function activateHandlers(move,done)/* {{{ */
  624. {
  625. btndown = true;
  626. onMove = move;
  627. onDone = done;
  628. toFront();
  629. return false;
  630. };
  631. /* }}} */
  632. function setCursor(t) { $trk.css('cursor',t); };
  633. $img.before($trk);
  634. return {
  635. activateHandlers: activateHandlers,
  636. setCursor: setCursor
  637. };
  638. }();
  639. /*}}}*/
  640. var KeyManager = function()/*{{{*/
  641. {
  642. var $keymgr = $('<input type="radio" />')
  643. .css({ position: 'absolute', left: '-30px' })
  644. .keypress(parseKey)
  645. .blur(onBlur),
  646. $keywrap = $('<div />')
  647. .css({
  648. position: 'absolute',
  649. overflow: 'hidden'
  650. })
  651. .append($keymgr)
  652. ;
  653. function watchKeys()/*{{{*/
  654. {
  655. if (options.keySupport)
  656. {
  657. $keymgr.show();
  658. $keymgr.focus();
  659. }
  660. };
  661. /*}}}*/
  662. function onBlur(e)/*{{{*/
  663. {
  664. $keymgr.hide();
  665. };
  666. /*}}}*/
  667. function doNudge(e,x,y)/*{{{*/
  668. {
  669. if (options.allowMove) {
  670. Coords.moveOffset([x,y]);
  671. Selection.updateVisible();
  672. };
  673. e.preventDefault();
  674. e.stopPropagation();
  675. };
  676. /*}}}*/
  677. function parseKey(e)/*{{{*/
  678. {
  679. if (e.ctrlKey) return true;
  680. shift_down = e.shiftKey ? true : false;
  681. var nudge = shift_down ? 10 : 1;
  682. switch(e.keyCode)
  683. {
  684. case 37: doNudge(e,-nudge,0); break;
  685. case 39: doNudge(e,nudge,0); break;
  686. case 38: doNudge(e,0,-nudge); break;
  687. case 40: doNudge(e,0,nudge); break;
  688. case 27: Selection.release(); break;
  689. case 9: return true;
  690. }
  691. return nothing(e);
  692. };
  693. /*}}}*/
  694. if (options.keySupport) $keywrap.insertBefore($img);
  695. return {
  696. watchKeys: watchKeys
  697. };
  698. }();
  699. /*}}}*/
  700. // }}}
  701. // Internal Methods {{{
  702. function px(n) { return '' + parseInt(n) + 'px'; };
  703. function pct(n) { return '' + parseInt(n) + '%'; };
  704. function cssClass(cl) { return options.baseClass + '-' + cl; };
  705. function getPos(obj)/*{{{*/
  706. {
  707. // Updated in v0.9.4 to use built-in dimensions plugin
  708. var pos = $(obj).offset();
  709. return [ pos.left, pos.top ];
  710. };
  711. /*}}}*/
  712. function mouseAbs(e)/*{{{*/
  713. {
  714. return [ (e.pageX - docOffset[0]), (e.pageY - docOffset[1]) ];
  715. };
  716. /*}}}*/
  717. function myCursor(type)/*{{{*/
  718. {
  719. if (type != lastcurs)
  720. {
  721. Tracker.setCursor(type);
  722. //Handles.xsetCursor(type);
  723. lastcurs = type;
  724. }
  725. };
  726. /*}}}*/
  727. function startDragMode(mode,pos)/*{{{*/
  728. {
  729. docOffset = getPos($img);
  730. Tracker.setCursor(mode=='move'?mode:mode+'-resize');
  731. if (mode == 'move')
  732. return Tracker.activateHandlers(createMover(pos), doneSelect);
  733. var fc = Coords.getFixed();
  734. var opp = oppLockCorner(mode);
  735. var opc = Coords.getCorner(oppLockCorner(opp));
  736. Coords.setPressed(Coords.getCorner(opp));
  737. Coords.setCurrent(opc);
  738. Tracker.activateHandlers(dragmodeHandler(mode,fc),doneSelect);
  739. };
  740. /*}}}*/
  741. function dragmodeHandler(mode,f)/*{{{*/
  742. {
  743. return function(pos) {
  744. if (!options.aspectRatio) switch(mode)
  745. {
  746. case 'e': pos[1] = f.y2; break;
  747. case 'w': pos[1] = f.y2; break;
  748. case 'n': pos[0] = f.x2; break;
  749. case 's': pos[0] = f.x2; break;
  750. }
  751. else switch(mode)
  752. {
  753. case 'e': pos[1] = f.y+1; break;
  754. case 'w': pos[1] = f.y+1; break;
  755. case 'n': pos[0] = f.x+1; break;
  756. case 's': pos[0] = f.x+1; break;
  757. }
  758. Coords.setCurrent(pos);
  759. Selection.update();
  760. };
  761. };
  762. /*}}}*/
  763. function createMover(pos)/*{{{*/
  764. {
  765. var lloc = pos;
  766. KeyManager.watchKeys();
  767. return function(pos)
  768. {
  769. Coords.moveOffset([pos[0] - lloc[0], pos[1] - lloc[1]]);
  770. lloc = pos;
  771. Selection.update();
  772. };
  773. };
  774. /*}}}*/
  775. function oppLockCorner(ord)/*{{{*/
  776. {
  777. switch(ord)
  778. {
  779. case 'n': return 'sw';
  780. case 's': return 'nw';
  781. case 'e': return 'nw';
  782. case 'w': return 'ne';
  783. case 'ne': return 'sw';
  784. case 'nw': return 'se';
  785. case 'se': return 'nw';
  786. case 'sw': return 'ne';
  787. };
  788. };
  789. /*}}}*/
  790. function createDragger(ord)/*{{{*/
  791. {
  792. return function(e) {
  793. if (options.disabled) return false;
  794. if ((ord == 'move') && !options.allowMove) return false;
  795. btndown = true;
  796. startDragMode(ord,mouseAbs(e));
  797. e.stopPropagation();
  798. e.preventDefault();
  799. return false;
  800. };
  801. };
  802. /*}}}*/
  803. function presize($obj,w,h)/*{{{*/
  804. {
  805. var nw = $obj.width(), nh = $obj.height();
  806. if ((nw > w) && w > 0)
  807. {
  808. nw = w;
  809. nh = (w/$obj.width()) * $obj.height();
  810. }
  811. if ((nh > h) && h > 0)
  812. {
  813. nh = h;
  814. nw = (h/$obj.height()) * $obj.width();
  815. }
  816. xscale = $obj.width() / nw;
  817. yscale = $obj.height() / nh;
  818. $obj.width(nw).height(nh);
  819. };
  820. /*}}}*/
  821. function unscale(c)/*{{{*/
  822. {
  823. return {
  824. x: parseInt(c.x * xscale), y: parseInt(c.y * yscale),
  825. x2: parseInt(c.x2 * xscale), y2: parseInt(c.y2 * yscale),
  826. w: parseInt(c.w * xscale), h: parseInt(c.h * yscale)
  827. };
  828. };
  829. /*}}}*/
  830. function doneSelect(pos)/*{{{*/
  831. {
  832. var c = Coords.getFixed();
  833. if (c.w > options.minSelect[0] && c.h > options.minSelect[1])
  834. {
  835. Selection.enableHandles();
  836. Selection.done();
  837. }
  838. else
  839. {
  840. Selection.release();
  841. }
  842. Tracker.setCursor( options.allowSelect?'crosshair':'default' );
  843. };
  844. /*}}}*/
  845. function newSelection(e)/*{{{*/
  846. {
  847. if (options.disabled) return false;
  848. if (!options.allowSelect) return false;
  849. btndown = true;
  850. docOffset = getPos($img);
  851. Selection.disableHandles();
  852. myCursor('crosshair');
  853. var pos = mouseAbs(e);
  854. Coords.setPressed(pos);
  855. Tracker.activateHandlers(selectDrag,doneSelect);
  856. KeyManager.watchKeys();
  857. Selection.update();
  858. e.stopPropagation();
  859. e.preventDefault();
  860. return false;
  861. };
  862. /*}}}*/
  863. function selectDrag(pos)/*{{{*/
  864. {
  865. Coords.setCurrent(pos);
  866. Selection.update();
  867. };
  868. /*}}}*/
  869. function newTracker()
  870. {
  871. var trk = $('<div></div>').addClass(cssClass('tracker'));
  872. $.browser.msie && trk.css({ opacity: 0, backgroundColor: 'white' });
  873. return trk;
  874. };
  875. // }}}
  876. // API methods {{{
  877. function animateTo(a)/*{{{*/
  878. {
  879. var x1 = a[0] / xscale,
  880. y1 = a[1] / yscale,
  881. x2 = a[2] / xscale,
  882. y2 = a[3] / yscale;
  883. if (animating) return;
  884. var animto = Coords.flipCoords(x1,y1,x2,y2);
  885. var c = Coords.getFixed();
  886. var animat = initcr = [ c.x, c.y, c.x2, c.y2 ];
  887. var interv = options.animationDelay;
  888. var x = animat[0];
  889. var y = animat[1];
  890. var x2 = animat[2];
  891. var y2 = animat[3];
  892. var ix1 = animto[0] - initcr[0];
  893. var iy1 = animto[1] - initcr[1];
  894. var ix2 = animto[2] - initcr[2];
  895. var iy2 = animto[3] - initcr[3];
  896. var pcent = 0;
  897. var velocity = options.swingSpeed;
  898. Selection.animMode(true);
  899. var animator = function()
  900. {
  901. return function()
  902. {
  903. pcent += (100 - pcent) / velocity;
  904. animat[0] = x + ((pcent / 100) * ix1);
  905. animat[1] = y + ((pcent / 100) * iy1);
  906. animat[2] = x2 + ((pcent / 100) * ix2);
  907. animat[3] = y2 + ((pcent / 100) * iy2);
  908. if (pcent < 100) animateStart();
  909. else Selection.done();
  910. if (pcent >= 99.8) pcent = 100;
  911. setSelectRaw(animat);
  912. };
  913. }();
  914. function animateStart()
  915. { window.setTimeout(animator,interv); };
  916. animateStart();
  917. };
  918. /*}}}*/
  919. function setSelect(rect)//{{{
  920. {
  921. setSelectRaw([rect[0]/xscale,rect[1]/yscale,rect[2]/xscale,rect[3]/yscale]);
  922. };
  923. //}}}
  924. function setSelectRaw(l) /*{{{*/
  925. {
  926. Coords.setPressed([l[0],l[1]]);
  927. Coords.setCurrent([l[2],l[3]]);
  928. Selection.update();
  929. };
  930. /*}}}*/
  931. function setOptions(opt)/*{{{*/
  932. {
  933. if (typeof(opt) != 'object') opt = { };
  934. options = $.extend(options,opt);
  935. if (typeof(options.onChange)!=='function')
  936. options.onChange = function() { };
  937. if (typeof(options.onSelect)!=='function')
  938. options.onSelect = function() { };
  939. };
  940. /*}}}*/
  941. function tellSelect()/*{{{*/
  942. {
  943. return unscale(Coords.getFixed());
  944. };
  945. /*}}}*/
  946. function tellScaled()/*{{{*/
  947. {
  948. return Coords.getFixed();
  949. };
  950. /*}}}*/
  951. function setOptionsNew(opt)/*{{{*/
  952. {
  953. setOptions(opt);
  954. interfaceUpdate();
  955. };
  956. /*}}}*/
  957. function disableCrop()//{{{
  958. {
  959. options.disabled = true;
  960. Selection.disableHandles();
  961. Selection.setCursor('default');
  962. Tracker.setCursor('default');
  963. };
  964. //}}}
  965. function enableCrop()//{{{
  966. {
  967. options.disabled = false;
  968. interfaceUpdate();
  969. };
  970. //}}}
  971. function cancelCrop()//{{{
  972. {
  973. Selection.done();
  974. Tracker.activateHandlers(null,null);
  975. };
  976. //}}}
  977. function destroy()//{{{
  978. {
  979. $div.remove();
  980. $origimg.show();
  981. };
  982. //}}}
  983. function interfaceUpdate(alt)//{{{
  984. // This method tweaks the interface based on options object.
  985. // Called when options are changed and at end of initialization.
  986. {
  987. options.allowResize ?
  988. alt?Selection.enableOnly():Selection.enableHandles():
  989. Selection.disableHandles();
  990. Tracker.setCursor( options.allowSelect? 'crosshair': 'default' );
  991. Selection.setCursor( options.allowMove? 'move': 'default' );
  992. $div.css('backgroundColor',options.bgColor);
  993. if ('setSelect' in options) {
  994. setSelect(opt.setSelect);
  995. Selection.done();
  996. delete(options.setSelect);
  997. }
  998. if ('trueSize' in options) {
  999. xscale = options.trueSize[0] / boundx;
  1000. yscale = options.trueSize[1] / boundy;
  1001. }
  1002. xlimit = options.maxSize[0] || 0;
  1003. ylimit = options.maxSize[1] || 0;
  1004. xmin = options.minSize[0] || 0;
  1005. ymin = options.minSize[1] || 0;
  1006. if ('outerImage' in options)
  1007. {
  1008. $img.attr('src',options.outerImage);
  1009. delete(options.outerImage);
  1010. }
  1011. Selection.refresh();
  1012. };
  1013. //}}}
  1014. // }}}
  1015. $hdl_holder.hide();
  1016. interfaceUpdate(true);
  1017. var api = {
  1018. animateTo: animateTo,
  1019. setSelect: setSelect,
  1020. setOptions: setOptionsNew,
  1021. tellSelect: tellSelect,
  1022. tellScaled: tellScaled,
  1023. disable: disableCrop,
  1024. enable: enableCrop,
  1025. cancel: cancelCrop,
  1026. focus: KeyManager.watchKeys,
  1027. getBounds: function() { return [ boundx * xscale, boundy * yscale ]; },
  1028. getWidgetSize: function() { return [ boundx, boundy ]; },
  1029. release: Selection.release,
  1030. destroy: destroy
  1031. };
  1032. $origimg.data('Jcrop',api);
  1033. return api;
  1034. };
  1035. $.fn.Jcrop = function(options)/*{{{*/
  1036. {
  1037. function attachWhenDone(from)/*{{{*/
  1038. {
  1039. var loadsrc = options.useImg || from.src;
  1040. var img = new Image();
  1041. img.onload = function() { $.Jcrop(from,options); };
  1042. img.src = loadsrc;
  1043. };
  1044. /*}}}*/
  1045. if (typeof(options) !== 'object') options = { };
  1046. // Iterate over each object, attach Jcrop
  1047. this.each(function()
  1048. {
  1049. // If we've already attached to this object
  1050. if ($(this).data('Jcrop'))
  1051. {
  1052. // The API can be requested this way (undocumented)
  1053. if (options == 'api') return $(this).data('Jcrop');
  1054. // Otherwise, we just reset the options...
  1055. else $(this).data('Jcrop').setOptions(options);
  1056. }
  1057. // If we haven't been attached, preload and attach
  1058. else attachWhenDone(this);
  1059. });
  1060. // Return "this" so we're chainable a la jQuery plugin-style!
  1061. return this;
  1062. };
  1063. /*}}}*/
  1064. })(jQuery);