jquery.Jcrop.js 25 KB

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