]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - js/extlib/jquery-jcrop/jcrop.js
updated and moved jquery-jcrop (no longer .min.js)
[quix0rs-gnu-social.git] / js / extlib / jquery-jcrop / jcrop.js
1 /**
2  * jquery.Jcrop.js v0.9.12
3  * jQuery Image Cropping Plugin - released under MIT License 
4  * Author: Kelly Hallman <khallman@gmail.com>
5  * http://github.com/tapmodo/Jcrop
6  * Copyright (c) 2008-2013 Tapmodo Interactive LLC {{{
7  *
8  * Permission is hereby granted, free of charge, to any person
9  * obtaining a copy of this software and associated documentation
10  * files (the "Software"), to deal in the Software without
11  * restriction, including without limitation the rights to use,
12  * copy, modify, merge, publish, distribute, sublicense, and/or sell
13  * copies of the Software, and to permit persons to whom the
14  * Software is furnished to do so, subject to the following
15  * conditions:
16  *
17  * The above copyright notice and this permission notice shall be
18  * included in all copies or substantial portions of the Software.
19  *
20  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
22  * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
24  * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
25  * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
27  * OTHER DEALINGS IN THE SOFTWARE.
28  *
29  * }}}
30  */
31
32 (function ($) {
33
34   $.Jcrop = function (obj, opt) {
35     var options = $.extend({}, $.Jcrop.defaults),
36         docOffset,
37         _ua = navigator.userAgent.toLowerCase(),
38         is_msie = /msie/.test(_ua),
39         ie6mode = /msie [1-6]\./.test(_ua);
40
41     // Internal Methods {{{
42     function px(n) {
43       return Math.round(n) + 'px';
44     }
45     function cssClass(cl) {
46       return options.baseClass + '-' + cl;
47     }
48     function supportsColorFade() {
49       return $.fx.step.hasOwnProperty('backgroundColor');
50     }
51     function getPos(obj) //{{{
52     {
53       var pos = $(obj).offset();
54       return [pos.left, pos.top];
55     }
56     //}}}
57     function mouseAbs(e) //{{{
58     {
59       return [(e.pageX - docOffset[0]), (e.pageY - docOffset[1])];
60     }
61     //}}}
62     function setOptions(opt) //{{{
63     {
64       if (typeof(opt) !== 'object') opt = {};
65       options = $.extend(options, opt);
66
67       $.each(['onChange','onSelect','onRelease','onDblClick'],function(i,e) {
68         if (typeof(options[e]) !== 'function') options[e] = function () {};
69       });
70     }
71     //}}}
72     function startDragMode(mode, pos, touch) //{{{
73     {
74       docOffset = getPos($img);
75       Tracker.setCursor(mode === 'move' ? mode : mode + '-resize');
76
77       if (mode === 'move') {
78         return Tracker.activateHandlers(createMover(pos), doneSelect, touch);
79       }
80
81       var fc = Coords.getFixed();
82       var opp = oppLockCorner(mode);
83       var opc = Coords.getCorner(oppLockCorner(opp));
84
85       Coords.setPressed(Coords.getCorner(opp));
86       Coords.setCurrent(opc);
87
88       Tracker.activateHandlers(dragmodeHandler(mode, fc), doneSelect, touch);
89     }
90     //}}}
91     function dragmodeHandler(mode, f) //{{{
92     {
93       return function (pos) {
94         if (!options.aspectRatio) {
95           switch (mode) {
96           case 'e':
97             pos[1] = f.y2;
98             break;
99           case 'w':
100             pos[1] = f.y2;
101             break;
102           case 'n':
103             pos[0] = f.x2;
104             break;
105           case 's':
106             pos[0] = f.x2;
107             break;
108           }
109         } else {
110           switch (mode) {
111           case 'e':
112             pos[1] = f.y + 1;
113             break;
114           case 'w':
115             pos[1] = f.y + 1;
116             break;
117           case 'n':
118             pos[0] = f.x + 1;
119             break;
120           case 's':
121             pos[0] = f.x + 1;
122             break;
123           }
124         }
125         Coords.setCurrent(pos);
126         Selection.update();
127       };
128     }
129     //}}}
130     function createMover(pos) //{{{
131     {
132       var lloc = pos;
133       KeyManager.watchKeys();
134
135       return function (pos) {
136         Coords.moveOffset([pos[0] - lloc[0], pos[1] - lloc[1]]);
137         lloc = pos;
138
139         Selection.update();
140       };
141     }
142     //}}}
143     function oppLockCorner(ord) //{{{
144     {
145       switch (ord) {
146       case 'n':
147         return 'sw';
148       case 's':
149         return 'nw';
150       case 'e':
151         return 'nw';
152       case 'w':
153         return 'ne';
154       case 'ne':
155         return 'sw';
156       case 'nw':
157         return 'se';
158       case 'se':
159         return 'nw';
160       case 'sw':
161         return 'ne';
162       }
163     }
164     //}}}
165     function createDragger(ord) //{{{
166     {
167       return function (e) {
168         if (options.disabled) {
169           return false;
170         }
171         if ((ord === 'move') && !options.allowMove) {
172           return false;
173         }
174         
175         // Fix position of crop area when dragged the very first time.
176         // Necessary when crop image is in a hidden element when page is loaded.
177         docOffset = getPos($img);
178
179         btndown = true;
180         startDragMode(ord, mouseAbs(e));
181         e.stopPropagation();
182         e.preventDefault();
183         return false;
184       };
185     }
186     //}}}
187     function presize($obj, w, h) //{{{
188     {
189       var nw = $obj.width(),
190           nh = $obj.height();
191       if ((nw > w) && w > 0) {
192         nw = w;
193         nh = (w / $obj.width()) * $obj.height();
194       }
195       if ((nh > h) && h > 0) {
196         nh = h;
197         nw = (h / $obj.height()) * $obj.width();
198       }
199       xscale = $obj.width() / nw;
200       yscale = $obj.height() / nh;
201       $obj.width(nw).height(nh);
202     }
203     //}}}
204     function unscale(c) //{{{
205     {
206       return {
207         x: c.x * xscale,
208         y: c.y * yscale,
209         x2: c.x2 * xscale,
210         y2: c.y2 * yscale,
211         w: c.w * xscale,
212         h: c.h * yscale
213       };
214     }
215     //}}}
216     function doneSelect(pos) //{{{
217     {
218       var c = Coords.getFixed();
219       if ((c.w > options.minSelect[0]) && (c.h > options.minSelect[1])) {
220         Selection.enableHandles();
221         Selection.done();
222       } else {
223         Selection.release();
224       }
225       Tracker.setCursor(options.allowSelect ? 'crosshair' : 'default');
226     }
227     //}}}
228     function newSelection(e) //{{{
229     {
230       if (options.disabled) {
231         return false;
232       }
233       if (!options.allowSelect) {
234         return false;
235       }
236       btndown = true;
237       docOffset = getPos($img);
238       Selection.disableHandles();
239       Tracker.setCursor('crosshair');
240       var pos = mouseAbs(e);
241       Coords.setPressed(pos);
242       Selection.update();
243       Tracker.activateHandlers(selectDrag, doneSelect, e.type.substring(0,5)==='touch');
244       KeyManager.watchKeys();
245
246       e.stopPropagation();
247       e.preventDefault();
248       return false;
249     }
250     //}}}
251     function selectDrag(pos) //{{{
252     {
253       Coords.setCurrent(pos);
254       Selection.update();
255     }
256     //}}}
257     function newTracker() //{{{
258     {
259       var trk = $('<div></div>').addClass(cssClass('tracker'));
260       if (is_msie) {
261         trk.css({
262           opacity: 0,
263           backgroundColor: 'white'
264         });
265       }
266       return trk;
267     }
268     //}}}
269
270     // }}}
271     // Initialization {{{
272     // Sanitize some options {{{
273     if (typeof(obj) !== 'object') {
274       obj = $(obj)[0];
275     }
276     if (typeof(opt) !== 'object') {
277       opt = {};
278     }
279     // }}}
280     setOptions(opt);
281     // Initialize some jQuery objects {{{
282     // The values are SET on the image(s) for the interface
283     // If the original image has any of these set, they will be reset
284     // However, if you destroy() the Jcrop instance the original image's
285     // character in the DOM will be as you left it.
286     var img_css = {
287       border: 'none',
288       visibility: 'visible',
289       margin: 0,
290       padding: 0,
291       position: 'absolute',
292       top: 0,
293       left: 0
294     };
295
296     var $origimg = $(obj),
297       img_mode = true;
298
299     if (obj.tagName == 'IMG') {
300       // Fix size of crop image.
301       // Necessary when crop image is within a hidden element when page is loaded.
302       if ($origimg[0].width != 0 && $origimg[0].height != 0) {
303         // Obtain dimensions from contained img element.
304         $origimg.width($origimg[0].width);
305         $origimg.height($origimg[0].height);
306       } else {
307         // Obtain dimensions from temporary image in case the original is not loaded yet (e.g. IE 7.0). 
308         var tempImage = new Image();
309         tempImage.src = $origimg[0].src;
310         $origimg.width(tempImage.width);
311         $origimg.height(tempImage.height);
312       } 
313
314       var $img = $origimg.clone().removeAttr('id').css(img_css).show();
315
316       $img.width($origimg.width());
317       $img.height($origimg.height());
318       $origimg.after($img).hide();
319
320     } else {
321       $img = $origimg.css(img_css).show();
322       img_mode = false;
323       if (options.shade === null) { options.shade = true; }
324     }
325
326     presize($img, options.boxWidth, options.boxHeight);
327
328     var boundx = $img.width(),
329         boundy = $img.height(),
330         
331         
332         $div = $('<div />').width(boundx).height(boundy).addClass(cssClass('holder')).css({
333         position: 'relative',
334         backgroundColor: options.bgColor
335       }).insertAfter($origimg).append($img);
336
337     if (options.addClass) {
338       $div.addClass(options.addClass);
339     }
340
341     var $img2 = $('<div />'),
342
343         $img_holder = $('<div />') 
344         .width('100%').height('100%').css({
345           zIndex: 310,
346           position: 'absolute',
347           overflow: 'hidden'
348         }),
349
350         $hdl_holder = $('<div />') 
351         .width('100%').height('100%').css('zIndex', 320), 
352
353         $sel = $('<div />') 
354         .css({
355           position: 'absolute',
356           zIndex: 600
357         }).dblclick(function(){
358           var c = Coords.getFixed();
359           options.onDblClick.call(api,c);
360         }).insertBefore($img).append($img_holder, $hdl_holder); 
361
362     if (img_mode) {
363
364       $img2 = $('<img />')
365           .attr('src', $img.attr('src')).css(img_css).width(boundx).height(boundy),
366
367       $img_holder.append($img2);
368
369     }
370
371     if (ie6mode) {
372       $sel.css({
373         overflowY: 'hidden'
374       });
375     }
376
377     var bound = options.boundary;
378     var $trk = newTracker().width(boundx + (bound * 2)).height(boundy + (bound * 2)).css({
379       position: 'absolute',
380       top: px(-bound),
381       left: px(-bound),
382       zIndex: 290
383     }).mousedown(newSelection);
384
385     /* }}} */
386     // Set more variables {{{
387     var bgcolor = options.bgColor,
388         bgopacity = options.bgOpacity,
389         xlimit, ylimit, xmin, ymin, xscale, yscale, enabled = true,
390         btndown, animating, shift_down;
391
392     docOffset = getPos($img);
393     // }}}
394     // }}}
395     // Internal Modules {{{
396     // Touch Module {{{ 
397     var Touch = (function () {
398       // Touch support detection function adapted (under MIT License)
399       // from code by Jeffrey Sambells - http://github.com/iamamused/
400       function hasTouchSupport() {
401         var support = {}, events = ['touchstart', 'touchmove', 'touchend'],
402             el = document.createElement('div'), i;
403
404         try {
405           for(i=0; i<events.length; i++) {
406             var eventName = events[i];
407             eventName = 'on' + eventName;
408             var isSupported = (eventName in el);
409             if (!isSupported) {
410               el.setAttribute(eventName, 'return;');
411               isSupported = typeof el[eventName] == 'function';
412             }
413             support[events[i]] = isSupported;
414           }
415           return support.touchstart && support.touchend && support.touchmove;
416         }
417         catch(err) {
418           return false;
419         }
420       }
421
422       function detectSupport() {
423         if ((options.touchSupport === true) || (options.touchSupport === false)) return options.touchSupport;
424           else return hasTouchSupport();
425       }
426       return {
427         createDragger: function (ord) {
428           return function (e) {
429             if (options.disabled) {
430               return false;
431             }
432             if ((ord === 'move') && !options.allowMove) {
433               return false;
434             }
435             docOffset = getPos($img);
436             btndown = true;
437             startDragMode(ord, mouseAbs(Touch.cfilter(e)), true);
438             e.stopPropagation();
439             e.preventDefault();
440             return false;
441           };
442         },
443         newSelection: function (e) {
444           return newSelection(Touch.cfilter(e));
445         },
446         cfilter: function (e){
447           e.pageX = e.originalEvent.changedTouches[0].pageX;
448           e.pageY = e.originalEvent.changedTouches[0].pageY;
449           return e;
450         },
451         isSupported: hasTouchSupport,
452         support: detectSupport()
453       };
454     }());
455     // }}}
456     // Coords Module {{{
457     var Coords = (function () {
458       var x1 = 0,
459           y1 = 0,
460           x2 = 0,
461           y2 = 0,
462           ox, oy;
463
464       function setPressed(pos) //{{{
465       {
466         pos = rebound(pos);
467         x2 = x1 = pos[0];
468         y2 = y1 = pos[1];
469       }
470       //}}}
471       function setCurrent(pos) //{{{
472       {
473         pos = rebound(pos);
474         ox = pos[0] - x2;
475         oy = pos[1] - y2;
476         x2 = pos[0];
477         y2 = pos[1];
478       }
479       //}}}
480       function getOffset() //{{{
481       {
482         return [ox, oy];
483       }
484       //}}}
485       function moveOffset(offset) //{{{
486       {
487         var ox = offset[0],
488             oy = offset[1];
489
490         if (0 > x1 + ox) {
491           ox -= ox + x1;
492         }
493         if (0 > y1 + oy) {
494           oy -= oy + y1;
495         }
496
497         if (boundy < y2 + oy) {
498           oy += boundy - (y2 + oy);
499         }
500         if (boundx < x2 + ox) {
501           ox += boundx - (x2 + ox);
502         }
503
504         x1 += ox;
505         x2 += ox;
506         y1 += oy;
507         y2 += oy;
508       }
509       //}}}
510       function getCorner(ord) //{{{
511       {
512         var c = getFixed();
513         switch (ord) {
514         case 'ne':
515           return [c.x2, c.y];
516         case 'nw':
517           return [c.x, c.y];
518         case 'se':
519           return [c.x2, c.y2];
520         case 'sw':
521           return [c.x, c.y2];
522         }
523       }
524       //}}}
525       function getFixed() //{{{
526       {
527         if (!options.aspectRatio) {
528           return getRect();
529         }
530         // This function could use some optimization I think...
531         var aspect = options.aspectRatio,
532             min_x = options.minSize[0] / xscale,
533             
534             
535             //min_y = options.minSize[1]/yscale,
536             max_x = options.maxSize[0] / xscale,
537             max_y = options.maxSize[1] / yscale,
538             rw = x2 - x1,
539             rh = y2 - y1,
540             rwa = Math.abs(rw),
541             rha = Math.abs(rh),
542             real_ratio = rwa / rha,
543             xx, yy, w, h;
544
545         if (max_x === 0) {
546           max_x = boundx * 10;
547         }
548         if (max_y === 0) {
549           max_y = boundy * 10;
550         }
551         if (real_ratio < aspect) {
552           yy = y2;
553           w = rha * aspect;
554           xx = rw < 0 ? x1 - w : w + x1;
555
556           if (xx < 0) {
557             xx = 0;
558             h = Math.abs((xx - x1) / aspect);
559             yy = rh < 0 ? y1 - h : h + y1;
560           } else if (xx > boundx) {
561             xx = boundx;
562             h = Math.abs((xx - x1) / aspect);
563             yy = rh < 0 ? y1 - h : h + y1;
564           }
565         } else {
566           xx = x2;
567           h = rwa / aspect;
568           yy = rh < 0 ? y1 - h : y1 + h;
569           if (yy < 0) {
570             yy = 0;
571             w = Math.abs((yy - y1) * aspect);
572             xx = rw < 0 ? x1 - w : w + x1;
573           } else if (yy > boundy) {
574             yy = boundy;
575             w = Math.abs(yy - y1) * aspect;
576             xx = rw < 0 ? x1 - w : w + x1;
577           }
578         }
579
580         // Magic %-)
581         if (xx > x1) { // right side
582           if (xx - x1 < min_x) {
583             xx = x1 + min_x;
584           } else if (xx - x1 > max_x) {
585             xx = x1 + max_x;
586           }
587           if (yy > y1) {
588             yy = y1 + (xx - x1) / aspect;
589           } else {
590             yy = y1 - (xx - x1) / aspect;
591           }
592         } else if (xx < x1) { // left side
593           if (x1 - xx < min_x) {
594             xx = x1 - min_x;
595           } else if (x1 - xx > max_x) {
596             xx = x1 - max_x;
597           }
598           if (yy > y1) {
599             yy = y1 + (x1 - xx) / aspect;
600           } else {
601             yy = y1 - (x1 - xx) / aspect;
602           }
603         }
604
605         if (xx < 0) {
606           x1 -= xx;
607           xx = 0;
608         } else if (xx > boundx) {
609           x1 -= xx - boundx;
610           xx = boundx;
611         }
612
613         if (yy < 0) {
614           y1 -= yy;
615           yy = 0;
616         } else if (yy > boundy) {
617           y1 -= yy - boundy;
618           yy = boundy;
619         }
620
621         return makeObj(flipCoords(x1, y1, xx, yy));
622       }
623       //}}}
624       function rebound(p) //{{{
625       {
626         if (p[0] < 0) p[0] = 0;
627         if (p[1] < 0) p[1] = 0;
628
629         if (p[0] > boundx) p[0] = boundx;
630         if (p[1] > boundy) p[1] = boundy;
631
632         return [Math.round(p[0]), Math.round(p[1])];
633       }
634       //}}}
635       function flipCoords(x1, y1, x2, y2) //{{{
636       {
637         var xa = x1,
638             xb = x2,
639             ya = y1,
640             yb = y2;
641         if (x2 < x1) {
642           xa = x2;
643           xb = x1;
644         }
645         if (y2 < y1) {
646           ya = y2;
647           yb = y1;
648         }
649         return [xa, ya, xb, yb];
650       }
651       //}}}
652       function getRect() //{{{
653       {
654         var xsize = x2 - x1,
655             ysize = y2 - y1,
656             delta;
657
658         if (xlimit && (Math.abs(xsize) > xlimit)) {
659           x2 = (xsize > 0) ? (x1 + xlimit) : (x1 - xlimit);
660         }
661         if (ylimit && (Math.abs(ysize) > ylimit)) {
662           y2 = (ysize > 0) ? (y1 + ylimit) : (y1 - ylimit);
663         }
664
665         if (ymin / yscale && (Math.abs(ysize) < ymin / yscale)) {
666           y2 = (ysize > 0) ? (y1 + ymin / yscale) : (y1 - ymin / yscale);
667         }
668         if (xmin / xscale && (Math.abs(xsize) < xmin / xscale)) {
669           x2 = (xsize > 0) ? (x1 + xmin / xscale) : (x1 - xmin / xscale);
670         }
671
672         if (x1 < 0) {
673           x2 -= x1;
674           x1 -= x1;
675         }
676         if (y1 < 0) {
677           y2 -= y1;
678           y1 -= y1;
679         }
680         if (x2 < 0) {
681           x1 -= x2;
682           x2 -= x2;
683         }
684         if (y2 < 0) {
685           y1 -= y2;
686           y2 -= y2;
687         }
688         if (x2 > boundx) {
689           delta = x2 - boundx;
690           x1 -= delta;
691           x2 -= delta;
692         }
693         if (y2 > boundy) {
694           delta = y2 - boundy;
695           y1 -= delta;
696           y2 -= delta;
697         }
698         if (x1 > boundx) {
699           delta = x1 - boundy;
700           y2 -= delta;
701           y1 -= delta;
702         }
703         if (y1 > boundy) {
704           delta = y1 - boundy;
705           y2 -= delta;
706           y1 -= delta;
707         }
708
709         return makeObj(flipCoords(x1, y1, x2, y2));
710       }
711       //}}}
712       function makeObj(a) //{{{
713       {
714         return {
715           x: a[0],
716           y: a[1],
717           x2: a[2],
718           y2: a[3],
719           w: a[2] - a[0],
720           h: a[3] - a[1]
721         };
722       }
723       //}}}
724
725       return {
726         flipCoords: flipCoords,
727         setPressed: setPressed,
728         setCurrent: setCurrent,
729         getOffset: getOffset,
730         moveOffset: moveOffset,
731         getCorner: getCorner,
732         getFixed: getFixed
733       };
734     }());
735
736     //}}}
737     // Shade Module {{{
738     var Shade = (function() {
739       var enabled = false,
740           holder = $('<div />').css({
741             position: 'absolute',
742             zIndex: 240,
743             opacity: 0
744           }),
745           shades = {
746             top: createShade(),
747             left: createShade().height(boundy),
748             right: createShade().height(boundy),
749             bottom: createShade()
750           };
751
752       function resizeShades(w,h) {
753         shades.left.css({ height: px(h) });
754         shades.right.css({ height: px(h) });
755       }
756       function updateAuto()
757       {
758         return updateShade(Coords.getFixed());
759       }
760       function updateShade(c)
761       {
762         shades.top.css({
763           left: px(c.x),
764           width: px(c.w),
765           height: px(c.y)
766         });
767         shades.bottom.css({
768           top: px(c.y2),
769           left: px(c.x),
770           width: px(c.w),
771           height: px(boundy-c.y2)
772         });
773         shades.right.css({
774           left: px(c.x2),
775           width: px(boundx-c.x2)
776         });
777         shades.left.css({
778           width: px(c.x)
779         });
780       }
781       function createShade() {
782         return $('<div />').css({
783           position: 'absolute',
784           backgroundColor: options.shadeColor||options.bgColor
785         }).appendTo(holder);
786       }
787       function enableShade() {
788         if (!enabled) {
789           enabled = true;
790           holder.insertBefore($img);
791           updateAuto();
792           Selection.setBgOpacity(1,0,1);
793           $img2.hide();
794
795           setBgColor(options.shadeColor||options.bgColor,1);
796           if (Selection.isAwake())
797           {
798             setOpacity(options.bgOpacity,1);
799           }
800             else setOpacity(1,1);
801         }
802       }
803       function setBgColor(color,now) {
804         colorChangeMacro(getShades(),color,now);
805       }
806       function disableShade() {
807         if (enabled) {
808           holder.remove();
809           $img2.show();
810           enabled = false;
811           if (Selection.isAwake()) {
812             Selection.setBgOpacity(options.bgOpacity,1,1);
813           } else {
814             Selection.setBgOpacity(1,1,1);
815             Selection.disableHandles();
816           }
817           colorChangeMacro($div,0,1);
818         }
819       }
820       function setOpacity(opacity,now) {
821         if (enabled) {
822           if (options.bgFade && !now) {
823             holder.animate({
824               opacity: 1-opacity
825             },{
826               queue: false,
827               duration: options.fadeTime
828             });
829           }
830           else holder.css({opacity:1-opacity});
831         }
832       }
833       function refreshAll() {
834         options.shade ? enableShade() : disableShade();
835         if (Selection.isAwake()) setOpacity(options.bgOpacity);
836       }
837       function getShades() {
838         return holder.children();
839       }
840
841       return {
842         update: updateAuto,
843         updateRaw: updateShade,
844         getShades: getShades,
845         setBgColor: setBgColor,
846         enable: enableShade,
847         disable: disableShade,
848         resize: resizeShades,
849         refresh: refreshAll,
850         opacity: setOpacity
851       };
852     }());
853     // }}}
854     // Selection Module {{{
855     var Selection = (function () {
856       var awake,
857           hdep = 370,
858           borders = {},
859           handle = {},
860           dragbar = {},
861           seehandles = false;
862
863       // Private Methods
864       function insertBorder(type) //{{{
865       {
866         var jq = $('<div />').css({
867           position: 'absolute',
868           opacity: options.borderOpacity
869         }).addClass(cssClass(type));
870         $img_holder.append(jq);
871         return jq;
872       }
873       //}}}
874       function dragDiv(ord, zi) //{{{
875       {
876         var jq = $('<div />').mousedown(createDragger(ord)).css({
877           cursor: ord + '-resize',
878           position: 'absolute',
879           zIndex: zi
880         }).addClass('ord-'+ord);
881
882         if (Touch.support) {
883           jq.bind('touchstart.jcrop', Touch.createDragger(ord));
884         }
885
886         $hdl_holder.append(jq);
887         return jq;
888       }
889       //}}}
890       function insertHandle(ord) //{{{
891       {
892         var hs = options.handleSize,
893
894           div = dragDiv(ord, hdep++).css({
895             opacity: options.handleOpacity
896           }).addClass(cssClass('handle'));
897
898         if (hs) { div.width(hs).height(hs); }
899
900         return div;
901       }
902       //}}}
903       function insertDragbar(ord) //{{{
904       {
905         return dragDiv(ord, hdep++).addClass('jcrop-dragbar');
906       }
907       //}}}
908       function createDragbars(li) //{{{
909       {
910         var i;
911         for (i = 0; i < li.length; i++) {
912           dragbar[li[i]] = insertDragbar(li[i]);
913         }
914       }
915       //}}}
916       function createBorders(li) //{{{
917       {
918         var cl,i;
919         for (i = 0; i < li.length; i++) {
920           switch(li[i]){
921             case'n': cl='hline'; break;
922             case's': cl='hline bottom'; break;
923             case'e': cl='vline right'; break;
924             case'w': cl='vline'; break;
925           }
926           borders[li[i]] = insertBorder(cl);
927         }
928       }
929       //}}}
930       function createHandles(li) //{{{
931       {
932         var i;
933         for (i = 0; i < li.length; i++) {
934           handle[li[i]] = insertHandle(li[i]);
935         }
936       }
937       //}}}
938       function moveto(x, y) //{{{
939       {
940         if (!options.shade) {
941           $img2.css({
942             top: px(-y),
943             left: px(-x)
944           });
945         }
946         $sel.css({
947           top: px(y),
948           left: px(x)
949         });
950       }
951       //}}}
952       function resize(w, h) //{{{
953       {
954         $sel.width(Math.round(w)).height(Math.round(h));
955       }
956       //}}}
957       function refresh() //{{{
958       {
959         var c = Coords.getFixed();
960
961         Coords.setPressed([c.x, c.y]);
962         Coords.setCurrent([c.x2, c.y2]);
963
964         updateVisible();
965       }
966       //}}}
967
968       // Internal Methods
969       function updateVisible(select) //{{{
970       {
971         if (awake) {
972           return update(select);
973         }
974       }
975       //}}}
976       function update(select) //{{{
977       {
978         var c = Coords.getFixed();
979
980         resize(c.w, c.h);
981         moveto(c.x, c.y);
982         if (options.shade) Shade.updateRaw(c);
983
984         awake || show();
985
986         if (select) {
987           options.onSelect.call(api, unscale(c));
988         } else {
989           options.onChange.call(api, unscale(c));
990         }
991       }
992       //}}}
993       function setBgOpacity(opacity,force,now) //{{{
994       {
995         if (!awake && !force) return;
996         if (options.bgFade && !now) {
997           $img.animate({
998             opacity: opacity
999           },{
1000             queue: false,
1001             duration: options.fadeTime
1002           });
1003         } else {
1004           $img.css('opacity', opacity);
1005         }
1006       }
1007       //}}}
1008       function show() //{{{
1009       {
1010         $sel.show();
1011
1012         if (options.shade) Shade.opacity(bgopacity);
1013           else setBgOpacity(bgopacity,true);
1014
1015         awake = true;
1016       }
1017       //}}}
1018       function release() //{{{
1019       {
1020         disableHandles();
1021         $sel.hide();
1022
1023         if (options.shade) Shade.opacity(1);
1024           else setBgOpacity(1);
1025
1026         awake = false;
1027         options.onRelease.call(api);
1028       }
1029       //}}}
1030       function showHandles() //{{{
1031       {
1032         if (seehandles) {
1033           $hdl_holder.show();
1034         }
1035       }
1036       //}}}
1037       function enableHandles() //{{{
1038       {
1039         seehandles = true;
1040         if (options.allowResize) {
1041           $hdl_holder.show();
1042           return true;
1043         }
1044       }
1045       //}}}
1046       function disableHandles() //{{{
1047       {
1048         seehandles = false;
1049         $hdl_holder.hide();
1050       } 
1051       //}}}
1052       function animMode(v) //{{{
1053       {
1054         if (v) {
1055           animating = true;
1056           disableHandles();
1057         } else {
1058           animating = false;
1059           enableHandles();
1060         }
1061       } 
1062       //}}}
1063       function done() //{{{
1064       {
1065         animMode(false);
1066         refresh();
1067       } 
1068       //}}}
1069       // Insert draggable elements {{{
1070       // Insert border divs for outline
1071
1072       if (options.dragEdges && $.isArray(options.createDragbars))
1073         createDragbars(options.createDragbars);
1074
1075       if ($.isArray(options.createHandles))
1076         createHandles(options.createHandles);
1077
1078       if (options.drawBorders && $.isArray(options.createBorders))
1079         createBorders(options.createBorders);
1080
1081       //}}}
1082
1083       // This is a hack for iOS5 to support drag/move touch functionality
1084       $(document).bind('touchstart.jcrop-ios',function(e) {
1085         if ($(e.currentTarget).hasClass('jcrop-tracker')) e.stopPropagation();
1086       });
1087
1088       var $track = newTracker().mousedown(createDragger('move')).css({
1089         cursor: 'move',
1090         position: 'absolute',
1091         zIndex: 360
1092       });
1093
1094       if (Touch.support) {
1095         $track.bind('touchstart.jcrop', Touch.createDragger('move'));
1096       }
1097
1098       $img_holder.append($track);
1099       disableHandles();
1100
1101       return {
1102         updateVisible: updateVisible,
1103         update: update,
1104         release: release,
1105         refresh: refresh,
1106         isAwake: function () {
1107           return awake;
1108         },
1109         setCursor: function (cursor) {
1110           $track.css('cursor', cursor);
1111         },
1112         enableHandles: enableHandles,
1113         enableOnly: function () {
1114           seehandles = true;
1115         },
1116         showHandles: showHandles,
1117         disableHandles: disableHandles,
1118         animMode: animMode,
1119         setBgOpacity: setBgOpacity,
1120         done: done
1121       };
1122     }());
1123     
1124     //}}}
1125     // Tracker Module {{{
1126     var Tracker = (function () {
1127       var onMove = function () {},
1128           onDone = function () {},
1129           trackDoc = options.trackDocument;
1130
1131       function toFront(touch) //{{{
1132       {
1133         $trk.css({
1134           zIndex: 450
1135         });
1136
1137         if (touch)
1138           $(document)
1139             .bind('touchmove.jcrop', trackTouchMove)
1140             .bind('touchend.jcrop', trackTouchEnd);
1141
1142         else if (trackDoc)
1143           $(document)
1144             .bind('mousemove.jcrop',trackMove)
1145             .bind('mouseup.jcrop',trackUp);
1146       } 
1147       //}}}
1148       function toBack() //{{{
1149       {
1150         $trk.css({
1151           zIndex: 290
1152         });
1153         $(document).unbind('.jcrop');
1154       } 
1155       //}}}
1156       function trackMove(e) //{{{
1157       {
1158         onMove(mouseAbs(e));
1159         return false;
1160       } 
1161       //}}}
1162       function trackUp(e) //{{{
1163       {
1164         e.preventDefault();
1165         e.stopPropagation();
1166
1167         if (btndown) {
1168           btndown = false;
1169
1170           onDone(mouseAbs(e));
1171
1172           if (Selection.isAwake()) {
1173             options.onSelect.call(api, unscale(Coords.getFixed()));
1174           }
1175
1176           toBack();
1177           onMove = function () {};
1178           onDone = function () {};
1179         }
1180
1181         return false;
1182       }
1183       //}}}
1184       function activateHandlers(move, done, touch) //{{{
1185       {
1186         btndown = true;
1187         onMove = move;
1188         onDone = done;
1189         toFront(touch);
1190         return false;
1191       }
1192       //}}}
1193       function trackTouchMove(e) //{{{
1194       {
1195         onMove(mouseAbs(Touch.cfilter(e)));
1196         return false;
1197       }
1198       //}}}
1199       function trackTouchEnd(e) //{{{
1200       {
1201         return trackUp(Touch.cfilter(e));
1202       }
1203       //}}}
1204       function setCursor(t) //{{{
1205       {
1206         $trk.css('cursor', t);
1207       }
1208       //}}}
1209
1210       if (!trackDoc) {
1211         $trk.mousemove(trackMove).mouseup(trackUp).mouseout(trackUp);
1212       }
1213
1214       $img.before($trk);
1215       return {
1216         activateHandlers: activateHandlers,
1217         setCursor: setCursor
1218       };
1219     }());
1220     //}}}
1221     // KeyManager Module {{{
1222     var KeyManager = (function () {
1223       var $keymgr = $('<input type="radio" />').css({
1224         position: 'fixed',
1225         left: '-120px',
1226         width: '12px'
1227       }).addClass('jcrop-keymgr'),
1228
1229         $keywrap = $('<div />').css({
1230           position: 'absolute',
1231           overflow: 'hidden'
1232         }).append($keymgr);
1233
1234       function watchKeys() //{{{
1235       {
1236         if (options.keySupport) {
1237           $keymgr.show();
1238           $keymgr.focus();
1239         }
1240       }
1241       //}}}
1242       function onBlur(e) //{{{
1243       {
1244         $keymgr.hide();
1245       }
1246       //}}}
1247       function doNudge(e, x, y) //{{{
1248       {
1249         if (options.allowMove) {
1250           Coords.moveOffset([x, y]);
1251           Selection.updateVisible(true);
1252         }
1253         e.preventDefault();
1254         e.stopPropagation();
1255       }
1256       //}}}
1257       function parseKey(e) //{{{
1258       {
1259         if (e.ctrlKey || e.metaKey) {
1260           return true;
1261         }
1262         shift_down = e.shiftKey ? true : false;
1263         var nudge = shift_down ? 10 : 1;
1264
1265         switch (e.keyCode) {
1266         case 37:
1267           doNudge(e, -nudge, 0);
1268           break;
1269         case 39:
1270           doNudge(e, nudge, 0);
1271           break;
1272         case 38:
1273           doNudge(e, 0, -nudge);
1274           break;
1275         case 40:
1276           doNudge(e, 0, nudge);
1277           break;
1278         case 27:
1279           if (options.allowSelect) Selection.release();
1280           break;
1281         case 9:
1282           return true;
1283         }
1284
1285         return false;
1286       }
1287       //}}}
1288
1289       if (options.keySupport) {
1290         $keymgr.keydown(parseKey).blur(onBlur);
1291         if (ie6mode || !options.fixedSupport) {
1292           $keymgr.css({
1293             position: 'absolute',
1294             left: '-20px'
1295           });
1296           $keywrap.append($keymgr).insertBefore($img);
1297         } else {
1298           $keymgr.insertBefore($img);
1299         }
1300       }
1301
1302
1303       return {
1304         watchKeys: watchKeys
1305       };
1306     }());
1307     //}}}
1308     // }}}
1309     // API methods {{{
1310     function setClass(cname) //{{{
1311     {
1312       $div.removeClass().addClass(cssClass('holder')).addClass(cname);
1313     }
1314     //}}}
1315     function animateTo(a, callback) //{{{
1316     {
1317       var x1 = a[0] / xscale,
1318           y1 = a[1] / yscale,
1319           x2 = a[2] / xscale,
1320           y2 = a[3] / yscale;
1321
1322       if (animating) {
1323         return;
1324       }
1325
1326       var animto = Coords.flipCoords(x1, y1, x2, y2),
1327           c = Coords.getFixed(),
1328           initcr = [c.x, c.y, c.x2, c.y2],
1329           animat = initcr,
1330           interv = options.animationDelay,
1331           ix1 = animto[0] - initcr[0],
1332           iy1 = animto[1] - initcr[1],
1333           ix2 = animto[2] - initcr[2],
1334           iy2 = animto[3] - initcr[3],
1335           pcent = 0,
1336           velocity = options.swingSpeed;
1337
1338       x1 = animat[0];
1339       y1 = animat[1];
1340       x2 = animat[2];
1341       y2 = animat[3];
1342
1343       Selection.animMode(true);
1344       var anim_timer;
1345
1346       function queueAnimator() {
1347         window.setTimeout(animator, interv);
1348       }
1349       var animator = (function () {
1350         return function () {
1351           pcent += (100 - pcent) / velocity;
1352
1353           animat[0] = Math.round(x1 + ((pcent / 100) * ix1));
1354           animat[1] = Math.round(y1 + ((pcent / 100) * iy1));
1355           animat[2] = Math.round(x2 + ((pcent / 100) * ix2));
1356           animat[3] = Math.round(y2 + ((pcent / 100) * iy2));
1357
1358           if (pcent >= 99.8) {
1359             pcent = 100;
1360           }
1361           if (pcent < 100) {
1362             setSelectRaw(animat);
1363             queueAnimator();
1364           } else {
1365             Selection.done();
1366             Selection.animMode(false);
1367             if (typeof(callback) === 'function') {
1368               callback.call(api);
1369             }
1370           }
1371         };
1372       }());
1373       queueAnimator();
1374     }
1375     //}}}
1376     function setSelect(rect) //{{{
1377     {
1378       setSelectRaw([rect[0] / xscale, rect[1] / yscale, rect[2] / xscale, rect[3] / yscale]);
1379       options.onSelect.call(api, unscale(Coords.getFixed()));
1380       Selection.enableHandles();
1381     }
1382     //}}}
1383     function setSelectRaw(l) //{{{
1384     {
1385       Coords.setPressed([l[0], l[1]]);
1386       Coords.setCurrent([l[2], l[3]]);
1387       Selection.update();
1388     }
1389     //}}}
1390     function tellSelect() //{{{
1391     {
1392       return unscale(Coords.getFixed());
1393     }
1394     //}}}
1395     function tellScaled() //{{{
1396     {
1397       return Coords.getFixed();
1398     }
1399     //}}}
1400     function setOptionsNew(opt) //{{{
1401     {
1402       setOptions(opt);
1403       interfaceUpdate();
1404     }
1405     //}}}
1406     function disableCrop() //{{{
1407     {
1408       options.disabled = true;
1409       Selection.disableHandles();
1410       Selection.setCursor('default');
1411       Tracker.setCursor('default');
1412     }
1413     //}}}
1414     function enableCrop() //{{{
1415     {
1416       options.disabled = false;
1417       interfaceUpdate();
1418     }
1419     //}}}
1420     function cancelCrop() //{{{
1421     {
1422       Selection.done();
1423       Tracker.activateHandlers(null, null);
1424     }
1425     //}}}
1426     function destroy() //{{{
1427     {
1428       $div.remove();
1429       $origimg.show();
1430       $origimg.css('visibility','visible');
1431       $(obj).removeData('Jcrop');
1432     }
1433     //}}}
1434     function setImage(src, callback) //{{{
1435     {
1436       Selection.release();
1437       disableCrop();
1438       var img = new Image();
1439       img.onload = function () {
1440         var iw = img.width;
1441         var ih = img.height;
1442         var bw = options.boxWidth;
1443         var bh = options.boxHeight;
1444         $img.width(iw).height(ih);
1445         $img.attr('src', src);
1446         $img2.attr('src', src);
1447         presize($img, bw, bh);
1448         boundx = $img.width();
1449         boundy = $img.height();
1450         $img2.width(boundx).height(boundy);
1451         $trk.width(boundx + (bound * 2)).height(boundy + (bound * 2));
1452         $div.width(boundx).height(boundy);
1453         Shade.resize(boundx,boundy);
1454         enableCrop();
1455
1456         if (typeof(callback) === 'function') {
1457           callback.call(api);
1458         }
1459       };
1460       img.src = src;
1461     }
1462     //}}}
1463     function colorChangeMacro($obj,color,now) {
1464       var mycolor = color || options.bgColor;
1465       if (options.bgFade && supportsColorFade() && options.fadeTime && !now) {
1466         $obj.animate({
1467           backgroundColor: mycolor
1468         }, {
1469           queue: false,
1470           duration: options.fadeTime
1471         });
1472       } else {
1473         $obj.css('backgroundColor', mycolor);
1474       }
1475     }
1476     function interfaceUpdate(alt) //{{{
1477     // This method tweaks the interface based on options object.
1478     // Called when options are changed and at end of initialization.
1479     {
1480       if (options.allowResize) {
1481         if (alt) {
1482           Selection.enableOnly();
1483         } else {
1484           Selection.enableHandles();
1485         }
1486       } else {
1487         Selection.disableHandles();
1488       }
1489
1490       Tracker.setCursor(options.allowSelect ? 'crosshair' : 'default');
1491       Selection.setCursor(options.allowMove ? 'move' : 'default');
1492
1493       if (options.hasOwnProperty('trueSize')) {
1494         xscale = options.trueSize[0] / boundx;
1495         yscale = options.trueSize[1] / boundy;
1496       }
1497
1498       if (options.hasOwnProperty('setSelect')) {
1499         setSelect(options.setSelect);
1500         Selection.done();
1501         delete(options.setSelect);
1502       }
1503
1504       Shade.refresh();
1505
1506       if (options.bgColor != bgcolor) {
1507         colorChangeMacro(
1508           options.shade? Shade.getShades(): $div,
1509           options.shade?
1510             (options.shadeColor || options.bgColor):
1511             options.bgColor
1512         );
1513         bgcolor = options.bgColor;
1514       }
1515
1516       if (bgopacity != options.bgOpacity) {
1517         bgopacity = options.bgOpacity;
1518         if (options.shade) Shade.refresh();
1519           else Selection.setBgOpacity(bgopacity);
1520       }
1521
1522       xlimit = options.maxSize[0] || 0;
1523       ylimit = options.maxSize[1] || 0;
1524       xmin = options.minSize[0] || 0;
1525       ymin = options.minSize[1] || 0;
1526
1527       if (options.hasOwnProperty('outerImage')) {
1528         $img.attr('src', options.outerImage);
1529         delete(options.outerImage);
1530       }
1531
1532       Selection.refresh();
1533     }
1534     //}}}
1535     //}}}
1536
1537     if (Touch.support) $trk.bind('touchstart.jcrop', Touch.newSelection);
1538
1539     $hdl_holder.hide();
1540     interfaceUpdate(true);
1541
1542     var api = {
1543       setImage: setImage,
1544       animateTo: animateTo,
1545       setSelect: setSelect,
1546       setOptions: setOptionsNew,
1547       tellSelect: tellSelect,
1548       tellScaled: tellScaled,
1549       setClass: setClass,
1550
1551       disable: disableCrop,
1552       enable: enableCrop,
1553       cancel: cancelCrop,
1554       release: Selection.release,
1555       destroy: destroy,
1556
1557       focus: KeyManager.watchKeys,
1558
1559       getBounds: function () {
1560         return [boundx * xscale, boundy * yscale];
1561       },
1562       getWidgetSize: function () {
1563         return [boundx, boundy];
1564       },
1565       getScaleFactor: function () {
1566         return [xscale, yscale];
1567       },
1568       getOptions: function() {
1569         // careful: internal values are returned
1570         return options;
1571       },
1572
1573       ui: {
1574         holder: $div,
1575         selection: $sel
1576       }
1577     };
1578
1579     if (is_msie) $div.bind('selectstart', function () { return false; });
1580
1581     $origimg.data('Jcrop', api);
1582     return api;
1583   };
1584   $.fn.Jcrop = function (options, callback) //{{{
1585   {
1586     var api;
1587     // Iterate over each object, attach Jcrop
1588     this.each(function () {
1589       // If we've already attached to this object
1590       if ($(this).data('Jcrop')) {
1591         // The API can be requested this way (undocumented)
1592         if (options === 'api') return $(this).data('Jcrop');
1593         // Otherwise, we just reset the options...
1594         else $(this).data('Jcrop').setOptions(options);
1595       }
1596       // If we haven't been attached, preload and attach
1597       else {
1598         if (this.tagName == 'IMG')
1599           $.Jcrop.Loader(this,function(){
1600             $(this).css({display:'block',visibility:'hidden'});
1601             api = $.Jcrop(this, options);
1602             if ($.isFunction(callback)) callback.call(api);
1603           });
1604         else {
1605           $(this).css({display:'block',visibility:'hidden'});
1606           api = $.Jcrop(this, options);
1607           if ($.isFunction(callback)) callback.call(api);
1608         }
1609       }
1610     });
1611
1612     // Return "this" so the object is chainable (jQuery-style)
1613     return this;
1614   };
1615   //}}}
1616   // $.Jcrop.Loader - basic image loader {{{
1617
1618   $.Jcrop.Loader = function(imgobj,success,error){
1619     var $img = $(imgobj), img = $img[0];
1620
1621     function completeCheck(){
1622       if (img.complete) {
1623         $img.unbind('.jcloader');
1624         if ($.isFunction(success)) success.call(img);
1625       }
1626       else window.setTimeout(completeCheck,50);
1627     }
1628
1629     $img
1630       .bind('load.jcloader',completeCheck)
1631       .bind('error.jcloader',function(e){
1632         $img.unbind('.jcloader');
1633         if ($.isFunction(error)) error.call(img);
1634       });
1635
1636     if (img.complete && $.isFunction(success)){
1637       $img.unbind('.jcloader');
1638       success.call(img);
1639     }
1640   };
1641
1642   //}}}
1643   // Global Defaults {{{
1644   $.Jcrop.defaults = {
1645
1646     // Basic Settings
1647     allowSelect: true,
1648     allowMove: true,
1649     allowResize: true,
1650
1651     trackDocument: true,
1652
1653     // Styling Options
1654     baseClass: 'jcrop',
1655     addClass: null,
1656     bgColor: 'black',
1657     bgOpacity: 0.6,
1658     bgFade: false,
1659     borderOpacity: 0.4,
1660     handleOpacity: 0.5,
1661     handleSize: null,
1662
1663     aspectRatio: 0,
1664     keySupport: true,
1665     createHandles: ['n','s','e','w','nw','ne','se','sw'],
1666     createDragbars: ['n','s','e','w'],
1667     createBorders: ['n','s','e','w'],
1668     drawBorders: true,
1669     dragEdges: true,
1670     fixedSupport: true,
1671     touchSupport: null,
1672
1673     shade: null,
1674
1675     boxWidth: 0,
1676     boxHeight: 0,
1677     boundary: 2,
1678     fadeTime: 400,
1679     animationDelay: 20,
1680     swingSpeed: 3,
1681
1682     minSelect: [0, 0],
1683     maxSize: [0, 0],
1684     minSize: [0, 0],
1685
1686     // Callbacks / Event Handlers
1687     onChange: function () {},
1688     onSelect: function () {},
1689     onDblClick: function () {},
1690     onRelease: function () {}
1691   };
1692
1693   // }}}
1694 }(jQuery));