PageSourceSearch

https://protektor4x4.ru/public/js/3rd_party/cropper/cropper.uncompressed.js

js protektor4x4.ru collected 2026-10-04 14:44:08 UTC 50,974 bytes, 1,373 lines download raw bytes

1/**
2 * Image Cropper (v. 1.2.2 - 2011-07-04 )
3 * Copyright (c) 2006-2011 David Spurr (http://www.defusion.org.uk/)
4 * 
5 * The image cropper provides a way to draw a crop area on an image and capture
6 * the coordinates of the drawn crop area.
7 * 
8 * Features include:
9 *   - Based on Prototype and Scriptaculous
10 *   - Image editing package styling, the crop area functions and looks 
11 *     like those found in popular image editing software
12 *   - Dynamic inclusion of required styles
13 *   - Drag to draw areas
14 *   - Shift drag to draw/resize areas as squares
15 *   - Selection area can be moved 
16 *   - Seleciton area can be resized using resize handles
17 *   - Allows dimension ratio limited crop areas
18 *   - Allows minimum dimension crop areas
19 *   - Allows maximum dimesion crop areas
20 *   - If both min & max dimension options set to the same value for a single axis,then the cropper will not 
21 *     display the resize handles as appropriate (when min & max dimensions are passed for both axes this
22 *     results in a 'fixed size' crop area)
23 *   - Allows dynamic preview of resultant crop ( if minimum width & height are provided ), this is
24 *     implemented as a subclass so can be excluded when not required
25 *   - Movement of selection area by arrow keys ( shift + arrow key will move selection area by
26 *     10 pixels )
27 *   - All operations stay within bounds of image
28 *   - All functionality & display compatible with most popular browsers supported by Prototype:
29 *       PC:  IE 9, 8, 7, 6 & 5.5, Firefox 2+, Opera 8.5 (see known issues) & 9.0b +, Google Chrome
30 *       MAC: Camino 1.0, Firefox 2+, Safari 3.x+, Google Chrome
31 * 
32 * Requires:
33 *   - Prototype v. 1.5.0_rc0 > (as packaged with Scriptaculous 1.6.1)
34 *   - Scriptaculous v. 1.6.1 > modules: dragdrop 
35 * Recommended (for IE9+ support):
36 *   - Prototype v. 1.7.0 >
37 *   - Scriptaculous v. 1.9.0 >
38 *   
39 * Known issues:
40 *   - Safari animated gifs, only one of each will animate, this seems to be a known Safari issue
41 *   
42 *   - After drawing an area and then clicking to start a new drag in IE 5.5 the rendered height 
43 *       appears as the last height until the user drags, this appears to be the related to the error 
44 *       that the forceReRender() method fixes for IE 6, i.e. IE 5.5 is not redrawing the box properly.
45 *   
46 *   - Lack of CSS opacity support in Opera before version 9 mean we disable those style rules, these 
47 *     could be fixed by using PNGs with transparency if Opera 8.5 support is high priority for you
48 *   
49 *   - Marching ants keep reloading in IE <6, it is a known issue in IE and I have 
50 *       found no viable workarounds that can be included in the release. If this really is an issue for you
51 *       either try this post: http://mir.aculo.us/articles/2005/08/28/internet-explorer-and-ajax-image-caching-woes
52 *       or uncomment the 'FIX MARCHING ANTS IN IE' rules in the CSS file
53 *   
54 *   - Styling & borders on image, any CSS styling applied directly to the image itself (floats, borders, padding, margin, etc.) will 
55 *     cause problems with the cropper. The use of a wrapper element to apply these styles to is recommended.
56 *   
57 *   - overflow: auto or overflow: scroll on parent will cause cropper to burst out of parent in IE and Opera (maybe Mac browsers too)
58 *     I'm not sure why yet.
59 *   
60 * Usage:
61 *   See Cropper.Img & Cropper.ImgWithPreview for usage details
62 * 
63 * Changelog:
64 * v1.2.2 - 2011-07-04
65 *    + Support for latest versions of Prototype & script.aculo.us (1.7.0 & 1.9.0 respectively)
66 *    + Support notice for IE9
67 *  
68 * v1.2.1 - 2009-10-06
69 *    + Added support for latest versions of Prototype & script.aculo.us
70 *      (1.6.1.0 & 1.8.2 respectively). Changes provided by Tom Hirashima.
71 *    - No-longer package prototype & script.aculo.us with the release
72 *    * Changed tests to use google ajax libraries api to load prototype & script.aculo.us
73 *    + Added option to not auto include the cropper CSS file
74 *    * #00008 - Fixed bug: Dynamic include of cropper CSS expected cropper.js and failed when using cropper.uncompressed.js
75 *    * #00028 - Fixed bug: Doesn't work with latest script.aculo.us - Fix by Tom Hirashima
76 *    * #00030 - Fixed bug: Doesn't work in Firefox 3.5 (CSS include issue)
77 *    * #00007 - Fixed bug: onEndCrop isn't called when moving with keys
78 *    * #00011 - Fixed bug: The image that is to be cropped does not show in IE6.0 -- included CSS fix
79 *    * Tidied up source code & fixed issues that jslint found so it will compress better
80 * 
81 * v1.2.0 - 2006-10-30
82 *    + Added id to the preview image element using 'imgCrop_[originalImageID]'
83 *      * #00001 - Fixed bug: Doesn't account for scroll offsets
84 *      * #00009 - Fixed bug: Placing the cropper inside differently positioned elements causes incorrect co-ordinates and display
85 *      * #00013 - Fixed bug: I-bar cursor appears on drag plane
86 *      * #00014 - Fixed bug: If ID for image tag is not found in document script throws error
87 *      * Fixed bug with drag start co-ordinates if wrapper element has moved in browser (e.g. dragged to a new position)
88 *      * Fixed bug with drag start co-ordinates if image contained in a wrapper with scrolling - this may be buggy if image 
89 *      has other ancestors with scrolling applied (except the body)
90 *      * #00015 - Fixed bug: When cropper removed and then reapplied onEndCrop callback gets called multiple times, solution suggestion from Bill Smith
91 *      * Various speed increases & code cleanup which meant improved performance in Mac - which allowed removal of different overlay methods for
92 *        IE and all other browsers, which led to a fix for:
93 *    * #00010 - Fixed bug: Select area doesn't adhere to image size when image resized using img attributes
94 *      - #00006 - Removed default behaviour of automatically setting a ratio when both min width & height passed, the ratioDimensions must be passed in
95 *    + #00005 - Added ability to set maximum crop dimensions, if both min & max set as the same value then we'll get a fixed cropper size on the axes as appropriate
96 *        and the resize handles will not be displayed as appropriate
97 *    * Switched keydown for keypress for moving select area with cursor keys (makes for nicer action) - doesn't appear to work in Safari
98 *    
99 * v1.1.3 - 2006-08-21
100 *    * Fixed wrong cursor on western handle in CSS
101 *    + #00008 & #00003 - Added feature: Allow to set dimensions & position for cropper on load
102 *      * #00002 - Fixed bug: Pressing 'remove cropper' twice removes image in IE
103 *    
104 * v1.1.2 - 2006-06-09
105 *    * Fixed bugs with ratios when GCD is low (patch submitted by Andy Skelton)
106 * 
107 * v1.1.1 - 2006-06-03
108 *    * Fixed bug with rendering issues fix in IE 5.5
109 *    * Fixed bug with endCrop callback issues once cropper had been removed & reset in IE
110 * 
111 * v1.1.0 - 2006-06-02
112 *    * Fixed bug with IE constantly trying to reload select area background image
113 *    * Applied more robust fix to Safari & IE rendering issues
114 *    + Added method to reset parameters - useful for when dynamically changing img cropper attached to
115 *    + Added method to remove cropper from image
116 *    
117 * v1.0.0 - 2006-05-18 
118 *    + Initial verison
119 * 
120 * 
121 * Copyright (c) 2006-2011, David Spurr (http://www.defusion.org.uk/)
122 * 
123 * 
124 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
125 * 
126 *     * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
127 *     * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
128 *     * Neither the name of the David Spurr nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
129 * 
130 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (
130INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
131 * 
132 * http://www.opensource.org/licenses/bsd-license.php
133 * 
134 * See scriptaculous.js for full scriptaculous licence
135 */
136 
137/**
138 * Extend the Draggable class to allow us to pass the rendering
139 * down to the Cropper object.
140 */
141var CropDraggable = Class.create(Draggable, {
142	
143	initialize: function(element) {
144		this.options = Object.extend(
145			{
146				/**
147				 * The draw method to defer drawing to
148				 */
149				drawMethod: function() {}
150			}, 
151			arguments[1] || {}
152		);
153
154		this.element = $(element);
155
156		this.handle = this.element;
157
158		this.delta    = this.currentDelta();
159		this.dragging = false;   
160
161		this.eventMouseDown = this.initDrag.bindAsEventListener(this);
162		Event.observe(this.handle, "mousedown", this.eventMouseDown);
163
164		Draggables.register(this);
165	},
166	
167	/**
168	 * Defers the drawing of the draggable to the supplied method
169	 */
170	draw: function(point) {
171		var pos = Element.cumulativeOffset(this.element),
172		    d = this.currentDelta();
173		pos[0] -= d[0]; 
174		pos[1] -= d[1];
175				
176		var p = [0,1].map(function(i) { 
177			return (point[i]-pos[i]-this.offset[i]);
178		}.bind(this));
179				
180		this.options.drawMethod( p );
181	}
182	
183});
184
185
186/**
187 * The Cropper object, this will attach itself to the provided image by wrapping it with 
188 * the generated xHTML structure required by the cropper.
189 * 
190 * Usage:
191 *  @param obj Image element to attach to
192 *  @param obj Optional options:
193 *     - ratioDim obj 
194 *       The pixel dimensions to apply as a restrictive ratio, with properties x & y
195 *     
196 *     - minWidth int 
197 *       The minimum width for the select area in pixels
198 *     
199 *     - minHeight	int 
200 *       The mimimum height for the select area in pixels
201 *     
202 *     - maxWidth int
203 *       The maximum width for the select areas in pixels (if both minWidth & maxWidth set to same the width of the cropper will be fixed)
204 *     
205 *     - maxHeight int
206 *       The maximum height for the select areas in pixels (if both minHeight & maxHeight set to same the height of the cropper will be fixed)
207 *     
208 *     - displayOnInit int 
209 *       Whether to display the select area on initialisation, only used when providing minimum width & height or ratio
210 *     
211 *     - onEndCrop func
212 *       The callback function to provide the crop details to on end of a crop (see below)
213 *     
214 *     - captureKeys boolean
215 *       Whether to capture the keys for moving the select area, as these can cause some problems at the moment
216 *     
217 *     - onloadCoords obj
218 *       A coordinates object with properties x1, y1, x2 & y2; for the coordinates of the select area to display onload
219 *     
220 *     - autoIncludeCSS boolean
221 *       Whether to automatically include the stylesheet (assumes it lives in the same location as the cropper JS file)
222 *-    ---------------------------------------------
223 * 
224 * The callback function provided via the onEndCrop option should accept the following parameters:
225 *   - coords obj
226 *     The coordinates object with properties x1, y1, x2 & y2; for the coordinates of the select area
227 *   
228 *   - dimensions obj
229 *     The dimensions object with properites width & height; for the dimensions of the select area
230 *   
231 *   
232 *   Example:
233 *     function onEndCrop( coords, dimensions ) {
234 *         $( 'x1' ).value = coords.x1;
235 *         $( 'y1' ).value = coords.y1;
236 *         $( 'x2' ).value = coords.x2;
237 *         $( 'y2' ).value = coords.y2;
238 *         $( 'width' ).value = dimensions.width;
239 *         $( 'height' ).value = dimensions.height;
240 *     }
241 * 
242 */
243var Cropper = {};
244Cropper.Img = Class.create({
245	
246	/**
247	 * Initialises the class
248	 * 
249	 * @access public
250	 * @param obj Image element to attach to
251	 * @param obj Options
252	 * @return void
253	 */
254	initialize: function(element, options) {
255		this.options = Object.extend(
256			{
257				/**
258				 * @var obj
259				 * The pixel dimensions to apply as a restrictive ratio
260				 */
261				ratioDim: { x: 0, y: 0 },
262				/**
263				 * @var int
264				 * The minimum pixel width, also used as restrictive ratio if min height passed too
265				 */
266				minWidth:		0,
267				/**
268				 * @var int
269				 * The minimum pixel height, also used as restrictive ratio if min width passed too
270				 */
271				minHeight:		0,
272				/**
273				 * @var boolean
274				 * Whether to display the select area on initialisation, only used when providing minimum width & height or ratio
275				 */
276				displayOnInit:	false,
277				/**
278				 * @var function
279				 * The call back function to pass the final values to
280				 */
281				onEndCrop: Prototype.emptyFunction,
282				/**
283				 * @var boolean
284				 * Whether to capture key presses or not
285				 */
286				captureKeys: true,
287				/**
288				 * @var obj Coordinate object x1, y1, x2, y2
289				 * The coordinates to optionally display the select area at onload
290				 */
291				onloadCoords: null,
292				/**
293				 * @var int
294				 * The maximum width for the select areas in pixels (if both minWidth & maxWidth set to same the width of the cropper will be fixed)
295				 */
296				maxWidth: 0,
297				/**
298				 * @var int
299				 * The maximum height for the select areas in pixels (if both minHeight & maxHeight set to same the height of the cropper will be fixed)
300				 */
301				maxHeight: 0,
302				/**
303				 * @var boolean - default true
304				 * Whether to automatically include the stylesheet (assumes it lives in the same location as the cropper JS file)
305				 */
306				autoIncludeCSS: true
307			}, 
308			options || {}
309		);				
310		/**
311		 * @var obj
312		 * The img node to attach to
313		 */
314		this.img = $( element );
315		/**
316		 * @var obj
317		 * The x & y coordinates of the click point
318		 */
319		this.clickCoords = { x: 0, y: 0 };
320		/**
321		 * @var boolean
322		 * Whether the user is dragging
323		 */
324		this.dragging = false;
325		/**
326		 * @var boolean
327		 * Whether the user is resizing
328		 */
329		this.resizing = false;
330		/**
331		 * @var boolean
332		 * Whether the user is on a webKit browser
333		 */
334		this.isWebKit = /Konqueror|Safari|KHTML/.test( navigator.userAgent );
335		/**
336		 * @var boolean
337		 * Whether the user is on IE
338		 */
339		this.isIE = /MSIE/.test( navigator.userAgent );
340		/**
341		 * @var boolean
342		 * Whether the user is on Opera below version 9
343		 */
344		this.isOpera8 = /Opera\s[1-8]/.test( navigator.userAgent );
345		/**
346		 * @var int
347		 * The x ratio 
348		 */
349		this.ratioX = 0;
350		/**
351		 * @var int
352		 * The y ratio
353		 */
354		this.ratioY = 0;
355		/**
356		 * @var boolean
357		 * Whether we've attached sucessfully
358		 */
359		this.attached = false;
360		/**
361		 * @var boolean
362		 * Whether we've got a fixed width (if minWidth EQ or GT maxWidth then we have a fixed width
363		 * in the case of minWidth > maxWidth maxWidth wins as the fixed width)
364		 */
365		this.fixedWidth = ( this.options.maxWidth > 0 && ( this.options.minWidth >= this.options.maxWidth ) );
366		/**
367		 * @var boolean
368		 * Whether we've got a fixed height (if minHeight EQ or GT maxHeight then we have a fixed height
369		 * in the case of minHeight > maxHeight maxHeight wins as the fixed height)
370		 */
371		this.fixedHeight = ( this.options.maxHeight > 0 && ( this.options.minHeight >= this.options.maxHeight ) );
372		
373		// quit if the image element doesn't exist
374		if( typeof this.img == 'undefined' ) { return; }
375		
376		// include the stylesheet
377		if( this.options.autoIncludeCSS ) {
378			$$('script').each(function(s) {
379				if( s.src.match( /\/cropper([^\/]*)\.js/ ) ) {
380					var path    = s.src.replace( /\/cropper([^\/]*)\.js.*/, '' ),
381					    style   = document.createElement( 'link' );
382					style.rel   = 'stylesheet';
383					style.type  = 'text/css';
384					style.href  = path + '/cropper.css';
385					style.media = 'screen';
386					document.getElementsByTagName( 'head' )[0].appendChild( style );
387				}
388			});   
389		}
390	
391		// calculate the ratio when neccessary
392		if( this.options.ratioDim.x > 0 && this.options.ratioDim.y > 0 ) {
393			var gcd = this.getGCD( this.options.ratioDim.x, this.options.ratioDim.y );
394			this.ratioX = this.options.ratioDim.x / gcd;
395			this.ratioY = this.options.ratioDim.y / gcd;
396			// dump( 'RATIO : ' + this.ratioX + ':' + this.ratioY + '\n' );
397		}
398							
399		// initialise sub classes
400		this.subInitialize();
401
402		// only load the event observers etc. once the image is loaded
403		// this is done after the subInitialize() call just in case the sub class does anything
404		// that will affect the result of the call to onLoad()
405		if( this.img.complete || this.isWebKit ) {
406			this.onLoad(); // for some reason Safari seems to support img.complete but returns 'undefined' on the this.img object
407		} else {
408			Event.observe( this.img, 'load', this.onLoad.bindAsEventListener( this) );
409		}
410	},
411	
412	/**
413	 * The Euclidean algorithm used to find the greatest common divisor
414	 * 
415	 * @acces private
416	 * @param int Value 1
417	 * @param int Value 2
418	 * @return int
419	 */
420	getGCD : function( a , b ) {
421		if( b === 0 ) { return a; }
422		return this.getGCD(b, a % b );
423	},
424	
425	/**
426	 * Attaches the cropper to the image once it has loaded
427	 * 
428	 * @access private
429	 * @return void
430	 */
431	onLoad: function( ) {
432		/*
433		 * Build the container and all related elements, will result in the following
434		 *
435		 * <div class="imgCrop_wrap">
436		 *   <img ... this.img ... />
437		 *   <div class="imgCrop_dragArea">
438		 *     <!-- the inner spans are only required for IE to stop it making the divs 1px high/wide -->
439		 *     <div class="imgCrop_overlay imageCrop_north"><span></span></div>
440		 *     <div class="imgCrop_overlay imageCrop_east"><span></span></div>
441		 *     <div class="imgCrop_overlay imageCrop_south"><span></span></div>
442		 *     <div class="imgCrop_overlay imageCrop_west"><span></span></div>
443		 *     <div class="imgCrop_selArea">
444		 *       <!-- marquees -->
445		 *       <!-- the inner spans are only required for IE to stop it making the divs 1px high/wide -->
446		 *       <div class="imgCrop_marqueeHoriz imgCrop_marqueeNorth"><span></span></div>
447		 *       <div class="imgCrop_marqueeVert imgCrop_marqueeEast"><span></span></div>
448		 *       <div class="imgCrop_marqueeHoriz imgCrop_marqueeSouth"><span></span></div>
449		 *       <div class="imgCrop_marqueeVert imgCrop_marqueeWest"><span></span></div>
450		 *       <!-- handles -->
451		 *       <div class="imgCrop_handle imgCrop_handleN"></div>
452		 *       <div class="imgCrop_handle imgCrop_handleNE"></div>
453		 *       <div class="imgCrop_handle imgCrop_handleE"></div>
454		 *       <div class="imgCrop_handle imgCrop_handleSE"></div>
455		 *       <div class="imgCrop_handle imgCrop_handleS"></div>
456		 *       <div class="imgCrop_handle imgCrop_handleSW"></div>
457		 *       <div class="imgCrop_handle imgCrop_handleW"></div>
458		 *       <div class="imgCrop_handle imgCrop_handleNW"></div>
459		 *       <div class="imgCrop_clickArea"></div>
460		 *     </div>
461		 *     <div class="imgCrop_clickArea"></div>
462		 *   </div>
463		 * </div>
464		 */
465		var cNamePrefix = 'imgCrop_';
466		
467		// get the point to insert the container
468		var insertPoint = this.img.parentNode;
469		
470		// apply an extra class to the wrapper to fix Opera below version 9
471		var fixOperaClass = '';
472		if( this.isOpera8 ) { fixOperaClass = ' opera8'; }
473		this.imgWrap = new Element( 'div', { 'class': cNamePrefix + 'wrap' + fixOperaClass } );
474		
475		this.north = new Element( 'div', { 'class': cNamePrefix + 'overlay ' + cNamePrefix + 'north' }).insert(new Element( 'span' ));
476		this.east  = new Element( 'div', { 'class': cNamePrefix + 'overlay ' + cNamePrefix + 'east' }).insert(new Element( 'span' ));
477		this.south = new Element( 'div', { 'class': cNamePrefix + 'overlay ' + cNamePrefix + 'south' }).insert(new Element( 'span' ));
478		this.west  = new Element( 'div', { 'class': cNamePrefix + 'overlay ' + cNamePrefix + 'west' }).insert(new Element( 'span' ));
479		
480		var overlays = [ this.north, this.east, this.south, this.west ];
481
482		this.dragArea = new Element( 'div', { 'class': cNamePrefix + 'dragArea' } );
483		
484		overlays.each(function(o){this.dragArea.insert(o);}, this);
485		
486		this.handleN  = new Element( 'div', { 'class': cNamePrefix + 'handle ' + cNamePrefix + 'handleN' } );
487		this.handleNE = new Element( 'div', { 'class': cNamePrefix + 'handle ' + cNamePrefix + 'handleNE' } );
488		this.handleE  = new Element( 'div', { 'class': cNamePrefix + 'handle ' + cNamePrefix + 'handleE' } );
489		this.handleSE = new Element( 'div', { 'class': cNamePrefix + 'handle ' + cNamePrefix + 'handleSE' } );
490		this.handleS  = new Element( 'div', { 'class': cNamePrefix + 'handle ' + cNamePrefix + 'handleS' } );
491		this.handleSW = new Element( 'div', { 'class': cNamePrefix + 'handle ' + cNamePrefix + 'handleSW' } );
492		this.handleW  = new Element( 'div', { 'class': cNamePrefix + 'handle ' + cNamePrefix + 'handleW' } );
493		this.handleNW = new Element( 'div', { 'class': cNamePrefix + 'handle ' + cNamePrefix + 'handleNW' } );
494				
495		this.selArea = new Element( 'div', { 'class': cNamePrefix + 'selArea' });
496			[
497				new Element( 'div', { 'class': cNamePrefix + 'marqueeHoriz ' + cNamePrefix + 'marqueeNorth' }).insert(new Element( 'span' )),
498				new Element( 'div', { 'class': cNamePrefix + 'marqueeVert ' + cNamePrefix + 'marqueeEast' }).insert(new Element( 'span' )),
499				new Element( 'div', { 'class': cNamePrefix + 'marqueeHoriz ' + cNamePrefix + 'marqueeSouth' }).insert(new Element( 'span' )),
500				new Element( 'div', { 'class': cNamePrefix + 'marqueeVert ' + cNamePrefix + 'marqueeWest' }).insert(new Element( 'span' )),
501				this.handleN,
502				this.handleNE,
503				this.handleE,
504				this.handleSE,
505				this.handleS,
506				this.handleSW,
507				this.handleW,
508				this.handleNW,
509				new Element( 'div', { 'class': cNamePrefix + 'clickArea' } )
510			].each(function(o){this.selArea.insert(o);}, this);
511		
512				
513		this.imgWrap.appendChild( this.img );
514		this.imgWrap.appendChild( this.dragArea );
515		this.dragArea.appendChild( this.selArea );
516		this.dragArea.appendChild( new Element( 'div', { 'class': cNamePrefix + 'clickArea' } ) );
517
518		insertPoint.appendChild( this.imgWrap );
519
520		// add event observers
521		this.startDragBind = this.startDrag.bindAsEventListener( this );
522		Event.observe( this.dragArea, 'mousedown', this.startDragBind );
523		
524		this.onDragBind = this.onDrag.bindAsEventListener( this );
525		Event.observe( document, 'mousemove', this.onDragBind );
526		
527		this.endCropBind = this.endCrop.bindAsEventListener( this );
528		Event.observe( document, 'mouseup', this.endCropBind );
529		
530		this.resizeBind = this.startResize.bindAsEventListener( this );
531		this.handles = [ this.handleN, this.handleNE, this.handleE, this.handleSE, this.handleS, this.handleSW, this.handleW, this.handleNW ];
532		this.registerHandles( true );
533		
534		if( this.options.captureKeys ) {
535			this.keysBind = this.handleKeys.bindAsEventListener( this );
536			Event.observe( document, 'keypress', this.keysBind );
537		}
538
539		// attach the dragable to the select area
540		var x = new CropDraggable( this.selArea, { drawMethod: this.moveArea.bindAsEventListener( this ) } );
541		
542		this.setParams();
543	},
544	
545	/**
546	 * Manages adding or removing the handle event handler and hiding or displaying them as appropriate
547	 * 
548	 * @access private
549	 * @param boolean registration true = add, false = remove
550	 * @return void
551	 */
552	registerHandles: function( registration ) {	
553		for( var i = 0; i < this.handles.length; i++ ) {
554			var handle = $( this.handles[i] );
555			
556			if( registration ) {
557				var hideHandle	= false;	// whether to hide the handle
558				
559				// disable handles asappropriate if we've got fixed dimensions
560				// if both dimensions are fixed we don't need to do much
561				if( this.fixedWidth && this.fixedHeight ) {
562					hideHandle = true;
563				} else if( this.fixedWidth || this.fixedHeight ) {
564					// if one of the dimensions is fixed then just hide those handles
565					var isCornerHandle = handle.className.match( /([S|N][E|W])$/ ),
566					    isWidthHandle  = handle.className.match( /(E|W)$/ ),
567					    isHeightHandle = handle.className.match( /(N|S)$/ );
568					if( isCornerHandle || ( this.fixedWidth && isWidthHandle ) || ( this.fixedHeight && isHeightHandle ) ) {
569						hideHandle = true;
570					}
571				}
572				if( hideHandle ) { 
573					handle.hide(); 
574				} else {
575					Event.observe( handle, 'mousedown', this.resizeBind );
576				}
577			} else {
578				handle.show();
579				Event.stopObserving( handle, 'mousedown', this.resizeBind );
580			}
581		}
582	},
583		
584	/**
585	 * Sets up all the cropper parameters, this can be used to reset the cropper when dynamically
586	 * changing the images
587	 * 
588	 * @access private
589	 * @return void
590	 */
591	setParams: function() {
592		/**
593		 * @var int
594		 * The image width
595		 */
596		this.imgW = this.img.width;
597		/**
598		 * @var int
599		 * The image height
600		 */
601		this.imgH = this.img.height;			
602
603		$( this.north ).setStyle( { height: 0 } );
604		$( this.east ).setStyle( { width: 0, height: 0 } );
605		$( this.south ).setStyle( { height: 0 } );
606		$( this.west ).setStyle( { width: 0, height: 0 } );
607		
608		// resize the container to fit the image
609		$( this.imgWrap ).setStyle( { 'width': this.imgW + 'px', 'height': this.imgH + 'px' } );
610		
611		// hide the select area
612		$( this.selArea ).hide();
613						
614		// setup the starting position of the select area
615		var startCoords = { x1: 0, y1: 0, x2: 0, y2: 0 },
616		    validCoordsSet = false;
617		
618		// display the select area 
619		if( this.options.onloadCoords !== null ) {
620			// if we've being given some coordinates to 
621			startCoords = this.cloneCoords( this.options.onloadCoords );
622			validCoordsSet = true;
623		} else if( this.options.ratioDim.x > 0 && this.options.ratioDim.y > 0 ) {
624			// if there is a ratio limit applied and the then set it to initial ratio
625			startCoords.x1 = Math.ceil( ( this.imgW - this.options.ratioDim.x ) / 2 );
626			startCoords.y1 = Math.ceil( ( this.imgH - this.options.ratioDim.y ) / 2 );
627			startCoords.x2 = startCoords.x1 + this.options.ratioDim.x;
628			startCoords.y2 = startCoords.y1 + this.options.ratioDim.y;
629			validCoordsSet = true;
630		}
631		
632		this.setAreaCoords( startCoords, false, false, 1 );
633		
634		if( this.options.displayOnInit && validCoordsSet ) {
635			this.selArea.show();
636			this.drawArea();
637			this.endCrop();
638		}
639		
640		this.attached = true;
641	},
642	
643	/**
644	 * Removes the cropper
645	 * 
646	 * @access public
647	 * @return void
648	 */
649	remove: function() {
650		if( this.attached ) {
651			this.attached = false;
652			
653			// remove the elements we inserted
654			this.imgWrap.parentNode.insertBefore( this.img, this.imgWrap );
655			this.imgWrap.parentNode.removeChild( this.imgWrap );
656			
657			// remove the event observers
658			Event.stopObserving( this.dragArea, 'mousedown', this.startDragBind );
659			Event.stopObserving( document, 'mousemove', this.onDragBind );		
660			Event.stopObserving( document, 'mouseup', this.endCropBind );
661			this.registerHandles( false );
662			if( this.options.captureKeys ) {
663				Event.stopObserving( document, 'keypress', this.keysBind );
664			}
665		}
666	},
667	
668	/**
669	 * Resets the cropper, can be used either after being removed or any time you wish
670	 * 
671	 * @access public
672	 * @return void
673	 */
674	reset: function() {
675		if( !this.attached ) {
676			this.onLoad();
677		} else {
678			this.setParams();
679		}
680		this.endCrop();
681	},
682	
683	/**
684	 * Handles the key functionality, currently just using arrow keys to move, if the user
685	 * presses shift then the area will move by 10 pixels
686	 */
687	handleKeys: function( e ) {
688		var dir = { x: 0, y: 0 }; // direction to move it in & the amount in pixels
689		if( !this.dragging ) {
690			
691			// IPS modification
692			if( !e.shiftKey )
693			{
694				// catch the arrow keys
695				switch( e.keyCode ) {
696					case( 37 ) : // left
697						dir.x = -1;
698						break;
699					case( 38 ) : // up
700						dir.y = -1;
701						break;
702					case( 39 ) : // right
703						dir.x = 1;
704						break;
705					case( 40 ) : // down
706						dir.y = 1;
707						break;
708				}
709			}
710			
711			if( dir.x !== 0 || dir.y !== 0 ) {
712				// if shift is pressed then move by 10 pixels
713				if( e.shiftKey ) {
714					dir.x *= 10;
715					dir.y *= 10;
716				}
717				
718				this.moveArea( [ this.areaCoords.x1 + dir.x, this.areaCoords.y1 + dir.y ] );
719				this.endCrop();
720				Event.stop( e ); 
721			}
722		}
723	},
724	
725	/**
726	 * Calculates the width from the areaCoords
727	 * 
728	 * @access private
729	 * @return int
730	 */
731	calcW: function() {
732		return (this.areaCoords.x2 - this.areaCoords.x1);
733	},
734	
735	/**
736	 * Calculates the height from the areaCoords
737	 * 
738	 * @access private
739	 * @return int
740	 */
741	calcH: function() {
742		return (this.areaCoords.y2 - this.areaCoords.y1);
743	},
744	
745	/**
746	 * Moves the select area to the supplied point (assumes the point is x1 & y1 of the select area)
747	 * 
748	 * @access public
749	 * @param array Point for x1 & y1 to move select area to
750	 * @return void
751	 */
752	moveArea: function( point ) {
753		// dump( 'moveArea        : ' + point[0] + ',' + point[1] + ',' + ( point[0] + ( this.areaCoords.x2 - this.areaCoords.x1 ) ) + ',' + ( point[1] + ( this.areaCoords.y2 - this.areaCoords.y1 ) ) + '\n' );
754		this.setAreaCoords( 
755			{
756				x1: point[0], 
757				y1: point[1],
758				x2: point[0] + this.calcW(),
759				y2: point[1] + this.calcH()
760			},
761			true,
762			false
763		);
764		this.drawArea();
765	},
766
767	/**
768	 * Clones a co-ordinates object, stops problems with handling them by reference
769	 * 
770	 * @access private
771	 * @param obj Coordinate object x1, y1, x2, y2
772	 * @return obj Coordinate object x1, y1, x2, y2
773	 */
774	cloneCoords: function( coords ) {
775		return { x1: coords.x1, y1: coords.y1, x2: coords.x2, y2: coords.y2 };
776	},
777
778	/**
779	 * Sets the select coords to those provided but ensures they don't go
780	 * outside the bounding box
781	 * 
782	 * @access private
783	 * @param obj Coordinates x1, y1, x2, y2
784	 * @param boolean Whether this is a move
785	 * @param boolean Whether to apply squaring
786	 * @param obj Direction of mouse along both axis x, y ( -1 = negative, 1 = positive ) only required when moving etc.
787	 * @param string The current resize handle || null
788	 * @return void
789	 */
790	setAreaCoords: function( coords, moving, square, direction, resizeHandle ) {
791		// dump( 'setAreaCoords (in) : ' + coords.x1 + ',' + coords.y1 + ',' + coords.x2 + ',' + coords.y2 );
792		if( moving ) {
793			// if moving
794			var targW = coords.x2 - coords.x1,
795			    targH = coords.y2 - coords.y1;
796			
797			// ensure we're within the bounds
798			if( coords.x1 < 0 ) {
799				coords.x1 = 0;
800				coords.x2 = targW;
801			}
802			if( coords.y1 < 0 ) {
803				coords.y1 = 0;
804				coords.y2 = targH;
805			}
806			if( coords.x2 > this.imgW ) {
807				coords.x2 = this.imgW;
808				coords.x1 = this.imgW - targW;
809			}
810			if( coords.y2 > this.imgH ) {
811				coords.y2 = this.imgH;
812				coords.y1 = this.imgH - targH;
813			}			
814		} else {
815			// ensure we're within the bounds
816			if( coords.x1 < 0 ) { coords.x1 = 0; }
817			if( coords.y1 < 0 ) { coords.y1 = 0; }
818			if( coords.x2 > this.imgW ) { coords.x2 = this.imgW; }
819			if( coords.y2 > this.imgH ) { coords.y2 = this.imgH; }
820			
821			// This is passed as null in onload
822			if( direction !== null ) {
823								
824				// apply the ratio or squaring where appropriate
825				if( this.ratioX > 0 ) {
826					this.applyRatio( coords, { x: this.ratioX, y: this.ratioY }, direction, resizeHandle );
827				} else if( square ) {
828					this.applyRatio( coords, { x: 1, y: 1 }, direction, resizeHandle );
829				}
830										
831				var mins = [ this.options.minWidth, this.options.minHeight ], // minimum dimensions [x,y]			
832				    maxs = [ this.options.maxWidth, this.options.maxHeight ]; // maximum dimensions [x,y]
833		
834				// apply dimensions where appropriate
835				if( mins[0] > 0 || mins[1] > 0 || maxs[0] > 0 || maxs[1] > 0) {
836				
837					var coordsTransX = { a1: coords.x1, a2: coords.x2 },
838					    coordsTransY = { a1: coords.y1, a2: coords.y2 },
839					    boundsX      = { min: 0, max: this.imgW },
840					    boundsY      = { min: 0, max: this.imgH };
841
842					// handle squaring properly on single axis minimum dimensions
843					if( (mins[0] !== 0 || mins[1] !== 0) && square ) {
844						if( mins[0] > 0 ) {
845							mins[1] = mins[0];
846						} else if( mins[1] > 0 ) {
847							mins[0] = mins[1];
848						}
849					}
850					
851					if( (maxs[0] !== 0 || maxs[0] !== 0) && square ) {
852						// if we have a max x value & it is less than the max y value then we set the y max to the max x (so we don't go over the minimum maximum of one of the axes 
852- if that makes sense)
853						if( maxs[0] > 0 && maxs[0] <= maxs[1] ) {
854							maxs[1] = maxs[0];
855						} else if( maxs[1] > 0 && maxs[1] <= maxs[0] ) {
856							maxs[0] = maxs[1];
857						}
858					}
859					
860					if( mins[0] > 0 ) { this.applyDimRestriction( coordsTransX, mins[0], direction.x, boundsX, 'min' ); }
861					if( mins[1] > 1 ) { this.applyDimRestriction( coordsTransY, mins[1], direction.y, boundsY, 'min' ); }
862					
863					if( maxs[0] > 0 ) { this.applyDimRestriction( coordsTransX, maxs[0], direction.x, boundsX, 'max' ); }
864					if( maxs[1] > 1 ) { this.applyDimRestriction( coordsTransY, maxs[1], direction.y, boundsY, 'max' ); }
865					
866					coords = { x1: coordsTransX.a1, y1: coordsTransY.a1, x2: coordsTransX.a2, y2: coordsTransY.a2 };
867				}
868				
869			}
870		}
871		
872		// dump( 'setAreaCoords (out) : ' + coords.x1 + ',' + coords.y1 + ',' + coords.x2 + ',' + coords.y2 + '\n' );
873		this.areaCoords = coords;
874	},
875	
876	/**
877	* Applies the supplied dimension restriction to the supplied coordinates along a single axis
878	 * 
879	 * @access private
880	 * @param obj Single axis coordinates, a1, a2 (e.g. for the x axis a1 = x1 & a2 = x2)
881	 * @param int The restriction value
882	 * @param int The direction ( -1 = negative, 1 = positive )
883	 * @param obj The bounds of the image ( for this axis )
884	 * @param string The dimension restriction type ( 'min' | 'max' )
885	 * @return void
886	 */
887	applyDimRestriction: function( coords, val, direction, bounds, type ) {
888		var check;
889		if( type == 'min' ) { check = ( ( coords.a2 - coords.a1 ) < val ); }
890		else { check = ( ( coords.a2 - coords.a1 ) > val ); }
891		if( check ) {
892			if( direction == 1 ) { coords.a2 = coords.a1 + val; }
893			else { coords.a1 = coords.a2 - val; }
894			
895			// make sure we're still in the bounds (not too pretty for the user, but needed)
896			if( coords.a1 < bounds.min ) {
897				coords.a1 = bounds.min;
898				coords.a2 = val;
899			} else if( coords.a2 > bounds.max ) {
900				coords.a1 = bounds.max - val;
901				coords.a2 = bounds.max;
902			}
903		}
904	},
905		
906	/**
907	 * Applies the supplied ratio to the supplied coordinates
908	 * 
909	 * @access private
910	 * @param obj Coordinates, x1, y1, x2, y2
911	 * @param obj Ratio, x, y
912	 * @param obj Direction of mouse, x & y : -1 == negative 1 == positive
913	 * @param string The current resize handle || null
914	 * @return void
915	 */
916	applyRatio : function( coords, ratio, direction, resizeHandle ) {
917		// dump( 'direction.y : ' + direction.y + '\n');
918		var newCoords;
919		if( resizeHandle == 'N' || resizeHandle == 'S' ) {
920			// dump( 'north south \n');
921			// if moving on either the lone north & south handles apply the ratio on the y axis
922			newCoords = this.applyRatioToAxis( 
923				{ a1: coords.y1, b1: coords.x1, a2: coords.y2, b2: coords.x2 },
924				{ a: ratio.y, b: ratio.x },
925				{ a: direction.y, b: direction.x },
926				{ min: 0, max: this.imgW }
927			);
928			coords.x1 = newCoords.b1;
929			coords.y1 = newCoords.a1;
930			coords.x2 = newCoords.b2;
931			coords.y2 = newCoords.a2;
932		} else {
933			// otherwise deal with it as if we're applying the ratio on the x axis
934			newCoords = this.applyRatioToAxis( 
935				{ a1: coords.x1, b1: coords.y1, a2: coords.x2, b2: coords.y2 },
936				{ a: ratio.x, b: ratio.y },
937				{ a: direction.x, b: direction.y },
938				{ min: 0, max: this.imgH }
939			);
940			coords.x1 = newCoords.a1;
941			coords.y1 = newCoords.b1;
942			coords.x2 = newCoords.a2;
943			coords.y2 = newCoords.b2;
944		}
945		
946	},
947	
948	/**
949	 * Applies the provided ratio to the provided coordinates based on provided direction & bounds,
950	 * use to encapsulate functionality to make it easy to apply to either axis. This is probably
951	 * quite hard to visualise so see the x axis example within applyRatio()
952	 * 
953	 * Example in parameter details & comments is for requesting applying ratio to x axis.
954	 * 
955	 * @access private
956	 * @param obj Coords object (a1, b1, a2, b2) where a = x & b = y in example
957	 * @param obj Ratio object (a, b) where a = x & b = y in example
958	 * @param obj Direction object (a, b) where a = x & b = y in example
959	 * @param obj Bounds (min, max)
960	 * @return obj Coords object (a1, b1, a2, b2) where a = x & b = y in example
961	 */
962	applyRatioToAxis: function( coords, ratio, direction, bounds ) {
963		var newCoords = Object.extend( coords, {} ),
964				calcDimA = newCoords.a2 - newCoords.a1, // calculate dimension a (e.g. width)
965				targDimB = Math.floor( calcDimA * ratio.b / ratio.a ), // the target dimension b 
965(e.g. height)
966				targB = null, // to hold target b (e.g. y value)
967				targDimA = null, // to hold target dimension a (e.g. width)
968				calcDimB = null; // to hold calculated dimension b (e.g. height)
969		
970		// dump( 'newCoords[0]: ' + newCoords.a1 + ',' + newCoords.b1 + ','+ newCoords.a2 + ',' + newCoords.b2 + '\n');
971				
972		if( direction.b == 1 ) { // if travelling in a positive direction
973			// make sure we're not going out of bounds
974			targB = newCoords.b1 + targDimB;
975			if( targB > bounds.max ) {
976				targB = bounds.max;
977				calcDimB = targB - newCoords.b1; // calcuate dimension b (e.g. height)
978			}
979			
980			newCoords.b2 = targB;
981		} else { // if travelling in a negative direction
982			// make sure we're not going out of bounds
983			targB = newCoords.b2 - targDimB;
984			if( targB < bounds.min ) {
985				targB = bounds.min;
986				calcDimB = targB + newCoords.b2; // calcuate dimension b (e.g. height)
987			}
988			newCoords.b1 = targB;
989		}
990		
991		// dump( 'newCoords[1]: ' + newCoords.a1 + ',' + newCoords.b1 + ','+ newCoords.a2 + ',' + newCoords.b2 + '\n');
992			
993		// apply the calculated dimensions
994		if( calcDimB !== null ) {
995			targDimA = Math.floor( calcDimB * ratio.a / ratio.b );
996			
997			if( direction.a == 1 ) { newCoords.a2 = newCoords.a1 + targDimA; }
998			else { newCoords.a1 = newCoords.a1 = newCoords.a2 - targDimA; }
999		}
1000		
1001		// dump( 'newCoords[2]: ' + newCoords.a1 + ',' + newCoords.b1 + ','+ newCoords.a2 + ',' + newCoords.b2 + '\n');
1002			
1003		return newCoords;
1004	},
1005	
1006	/**
1007	 * Draws the select area
1008	 * 
1009	 * @access private
1010	 * @return void
1011	 */
1012	drawArea: function( ) {	
1013		/*
1014			NOTE: I'm not using the Element.setStyle() shortcut as they make it 
1015			quite sluggish on Mac based browsers
1016		*/
1017		// dump( 'drawArea        : ' + this.areaCoords.x1 + ',' + this.areaCoords.y1 + ',' + this.areaCoords.x2 + ',' + this.areaCoords.y2 + '\n' );
1018		var areaWidth     = this.calcW(),
1019		    areaHeight    = this.calcH();
1020		
1021		/*
1022			Calculate all the style strings before we use them, allows reuse & produces quicker
1023			rendering (especially noticable in Mac based browsers)
1024		*/
1025		var px = 'px',
1026		    params = [
1027			this.areaCoords.x1 + px, // the left of the selArea
1028			this.areaCoords.y1 + px, // the top of the selArea
1029			areaWidth + px,          // width of the selArea
1030			areaHeight + px,         // height of the selArea
1031			this.areaCoords.x2 + px, // bottom of the selArea
1032			this.areaCoords.y2 + px, // right of the selArea
1033			(this.img.width - this.areaCoords.x2) + px, // right edge of selArea
1034			(this.img.height - this.areaCoords.y2) + px // bottom edge of selArea
1035		];
1036				
1037		// do the select area
1038		var areaStyle    = this.selArea.style;
1039		areaStyle.left   = params[0];
1040		areaStyle.top    = params[1];
1041		areaStyle.width  = params[2];
1042		areaStyle.height = params[3];
1043
1044		// position the north, east, south & west handles
1045		var horizHandlePos = Math.ceil( (areaWidth - 6) / 2 ) + px,
1046		    vertHandlePos = Math.ceil( (areaHeight - 6) / 2 ) + px;
1047		
1048		this.handleN.style.left = horizHandlePos;
1049		this.handleE.style.top  = vertHandlePos;
1050		this.handleS.style.left = horizHandlePos;
1051		this.handleW.style.top  = vertHandlePos;
1052		
1053		// draw the four overlays
1054		this.north.style.height = params[1];
1055		
1056		var eastStyle     = this.east.style;
1057		eastStyle.top     = params[1];
1058		eastStyle.height  = params[3];
1059		eastStyle.left    = params[4];
1060		eastStyle.width   = params[6];
1061
1062		var southStyle    = this.south.style;
1063		southStyle.top    = params[5];
1064		southStyle.height = params[7];
1065
1066		var westStyle     = this.west.style;
1067		westStyle.top     = params[1];
1068		westStyle.height  = params[3];
1069		westStyle.width   = params[0];
1070
1071		// call the draw method on sub classes
1072		this.subDrawArea();
1073		
1074		this.forceReRender();
1075	},
1076	
1077	/**
1078	 * Force the re-rendering of the selArea element which fixes rendering issues in Safari 
1079	 * & IE PC, especially evident when re-sizing perfectly vertical using any of the south handles
1080	 * 
1081	 * @access private
1082	 * @return void
1083	 */
1084	forceReRender: function() {
1085		if( this.isIE || this.isWebKit) {
1086			var n = document.createTextNode(' ');
1087			var d,el,fixEL,i;
1088		
1089			if( this.isIE ) { fixEl = this.selArea; }
1090			else if( this.isWebKit ) {
1091				fixEl = document.getElementsByClassName( 'imgCrop_marqueeSouth', this.imgWrap )[0];
1092				/* 
1093					we have to be a bit more forceful for Safari, otherwise the the marquee &
1094					the south handles still don't move
1095				*/ 
1096				d = new Element( 'div' );
1097				d.style.visibility = 'hidden';
1098				
1099				var classList = ['SE','S','SW'];
1100				for( i = 0; i < classList.length; i++ ) {
1101					el = document.getElementsByClassName( 'imgCrop_handle' + classList[i], this.selArea )[0];
1102					if( el.childNodes.length ) { el.removeChild( el.childNodes[0] ); }
1103					el.appendChild(d);
1104				}
1105			}
1106			fixEl.appendChild(n);
1107			fixEl.removeChild(n);
1108		}
1109	},
1110	
1111	/**
1112	 * Starts the resize
1113	 * 
1114	 * @access private
1115	 * @param obj Event
1116	 * @return void
1117	 */
1118	startResize: function( e ) {
1119		this.startCoords = this.cloneCoords( this.areaCoords );
1120		
1121		this.resizing = true;
1122		this.resizeHandle = Event.element( e ).classNames().toString().replace(/([^N|NE|E|SE|S|SW|W|NW])+/, '');
1123		// dump( 'this.resizeHandle : ' + this.resizeHandle + '\n' );
1124		Event.stop( e );
1125	},
1126	
1127	/**
1128	 * Starts the drag
1129	 * 
1130	 * @access private
1131	 * @param obj Event
1132	 * @return void
1133	 */
1134	startDrag: function( e ) {
1135		this.selArea.show();
1136		this.clickCoords = this.getCurPos( e );
1137
1138		this.setAreaCoords( { x1: this.clickCoords.x, y1: this.clickCoords.y, x2: this.clickCoords.x, y2: this.clickCoords.y }, false, false, null );
1139
1140		this.dragging = true;
1141		this.onDrag( e ); // incase the user just clicks once after already making a selection
1142		Event.stop( e );
1143	},
1144	
1145	/**
1146	 * Gets the current cursor position relative to the image
1147	 * 
1148	 * @access private
1149	 * @param obj Event
1150	 * @return obj x,y pixels of the cursor
1151	 */
1152	getCurPos: function( e ) {
1153		// get the offsets for the wrapper within the document
1154		// get the offsets for the wrapper within the document
1155		var el = this.imgWrap, wrapOffsets = Element.cumulativeOffset( el );
1156		// remove any scrolling that is applied to the wrapper (this may be buggy) - don't count the scroll on the body as that won't affect us
1157		while( el.nodeName != 'BODY' ) {
1158			wrapOffsets[1] -= el.scrollTop  || 0;
1159			wrapOffsets[0] -= el.scrollLeft || 0;
1160			el = el.parentNode;
1161		}
1162		return { 
1163			x: Event.pointerX(e) - wrapOffsets[0],
1164			y: Event.pointerY(e) - wrapOffsets[1]
1165		};
1166	},
1167	                               
1168	/**                            
1169	 * Performs the drag for both   resize & inital draw dragging
1170	 *
1171	 * @access private             
1172	 * @param obj Event
1173	 * @return void
1174	 */
1175	onDrag: function( e ) {
1176		if( this.dragging || this.resizing ) {
1177		
1178			var resizeHandle = null,
1179					curPos = this.getCurPos( e ),
1180					newCoords = this.cloneCoords( this.areaCoords ),
1181					direction = { x: 1, y: 1 };
1182			
1183			if( this.dragging ) {
1184				if( curPos.x < this.clickCoords.x ) { direction.x = -1; }
1185				if( curPos.y < this.clickCoords.y ) { direction.y = -1; }
1186				
1187				this.transformCoords( curPos.x, this.clickCoords.x, newCoords, 'x' );
1188				this.transformCoords( curPos.y, this.clickCoords.y, newCoords, 'y' );
1189			} else if( this.resizing ) {
1190				resizeHandle = this.resizeHandle;
1191				// do x movements first
1192				if( resizeHandle.match(/E/) ) {
1193					// if we're moving an east handle
1194					this.transformCoords( curPos.x, this.startCoords.x1, newCoords, 'x' );
1195					if( curPos.x < this.startCoords.x1 ) { direction.x = -1; }
1196				} else if( resizeHandle.match(/W/) ) {
1197					// if we're moving an west handle
1198					this.transformCoords( curPos.x, this.startCoords.x2, newCoords, 'x' );
1199					if( curPos.x < this.startCoords.x2 ) { direction.x = -1; }
1200				}
1201									
1202				// do y movements second
1203				if( resizeHandle.match(/N/) ) {
1204					// if we're moving an north handle	
1205					this.transformCoords( curPos.y, this.startCoords.y2, newCoords, 'y' );
1206					if( curPos.y < this.startCoords.y2 ) { direction.y = -1; }
1207				} else if( resizeHandle.match(/S/) ) {
1208					// if we're moving an south handle
1209					this.transformCoords( curPos.y, this.startCoords.y1, newCoords, 'y' );
1210					if( curPos.y < this.startCoords.y1 ) { direction.y = -1; }
1211				}
1212				
1213			}
1214		
1215			this.setAreaCoords( newCoords, false, e.shiftKey, direction, resizeHandle );
1216			this.drawArea();
1217			Event.stop( e ); // stop the default event (selecting images & text) in Safari & IE PC
1218		}
1219	},
1220	
1221	/**
1222	 * Applies the appropriate transform to supplied co-ordinates, on the
1223	 * defined axis, depending on the relationship of the supplied values
1224	 * 
1225	 * @access private
1226	 * @param int Current value of pointer
1227	 * @param int Base value to compare current pointer val to
1228	 * @param obj Coordinates to apply transformation on x1, x2, y1, y2
1229	 * @param string Axis to apply transformation on 'x' || 'y'
1230	 * @return void
1231	 */
1232	transformCoords : function( curVal, baseVal, coords, axis ) {
1233		var newVals = [ curVal, baseVal ];
1234		if( curVal > baseVal ) { newVals.reverse(); }
1235		coords[ axis + '1' ] = newVals[0];
1236		coords[ axis + '2' ] = newVals[1];
1237	},
1238	
1239	/**
1240	 * Ends the crop & passes the values of the select area on to the appropriate 
1241	 * callback function on completion of a crop
1242	 * 
1243	 * @access private
1244	 * @return void
1245	 */
1246	endCrop : function() {
1247		this.dragging = false;
1248		this.resizing = false;
1249		
1250		this.options.onEndCrop(
1251			this.areaCoords,
1252			{
1253				width: this.calcW(), 
1254				height: this.calcH() 
1255			}
1256		);
1257	},
1258	
1259	/**
1260	 * Abstract method called on the end of initialization
1261	 * 
1262	 * @access private
1263	 * @abstract
1264	 * @return void
1265	 */
1266	subInitialize: function() {},
1267	
1268	/**
1269	 * Abstract method called on the end of drawArea()
1270	 * 
1271	 * @access private
1272	 * @abstract
1273	 * @return void
1274	 */
1275	subDrawArea: function() {}
1276});
1277
1278
1279
1280
1281/**
1282 *	Extend the Cropper.Img class to allow for presentation of a preview image of the resulting crop,
1283 *	the option for displayOnInit is always overridden to true when displaying a preview image
1284 * 
1285 *	Usage:
1286 *		@param obj Image element to attach to
1287 *		@param obj Optional options:
1288 *			- see Cropper.Img for base options
1289 *			- previewWrap obj HTML element that will be used as a container for the preview image
1290 */
1291Cropper.ImgWithPreview = Class.create(Cropper.Img, {
1292	
1293	/**
1294	 * Implements the abstract method from Cropper.Img to initialize preview image settings.
1295	 * Will only attach a preview image is the previewWrap element is defined and the minWidth
1296	 * & minHeight options are set.
1297	 * 
1298	 * @see Croper.Img.subInitialize
1299	 */
1300	subInitialize: function() {
1301		/**
1302		* Whether or not we've attached a preview image
1303		* @var boolean
1304		*/
1305		this.hasPreviewImg = false;
1306		if( typeof(this.options.previewWrap) != 'undefined' && this.options.minWidth > 0 && this.options.minHeight > 0 ) {
1307			/**
1308			 * The preview image wrapper element
1309			 * @var obj HTML element
1310			 */
1311			this.previewWrap = $( this.options.previewWrap );
1312			/**
1313			 * The preview image element
1314			 * @var obj HTML IMG element
1315			 */
1316			this.previewImg = this.img.cloneNode( false );
1317			// set the ID of the preview image to be unique
1318			this.previewImg.id = 'imgCrop_' + this.previewImg.id;
1319			
1320			// set the displayOnInit option to true so we display the select area at the same time as the thumbnail
1321			this.options.displayOnInit = true;
1322
1323			this.hasPreviewImg = true;
1324			
1325			this.previewWrap.addClassName( 'imgCrop_previewWrap' );
1326			
1327			this.previewWrap.setStyle({ 
1328				width: this.options.minWidth + 'px',
1329				height: this.options.minHeight + 'px'
1330			});
1331			
1332			this.previewWrap.appendChild( this.previewImg );
1333		}
1334	},
1335	
1336	/**
1337	 * Implements the abstract method from Cropper.Img to draw the preview image
1338	 * 
1339	 * @see Croper.Img.subDrawArea
1340	 */
1341	subDrawArea: function() {
1342		if( this.hasPreviewImg ) {
1343			// get the ratio of the select area to the src image
1344			var calcWidth = this.calcW(),
1345			    calcHeight = this.calcH();
1346			// ratios for the dimensions of the preview image
1347			var dimRatio = { 
1348				x: this.imgW / calcWidth, 
1349				y: this.imgH / calcHeight 
1350			}; 
1351			//ratios for the positions within the preview
1352			var posRatio = { 
1353				x: calcWidth / this.options.minWidth, 
1354				y: calcHeight / this.options.minHeight 
1355			};
1356			
1357			// setting the positions in an obj before apply styles for rendering speed increase
1358			var calcPos = {
1359				w: Math.ceil( this.options.minWidth * dimRatio.x ) + 'px',
1360				h: Math.ceil( this.options.minHeight * dimRatio.y ) + 'px',
1361				x: '-' + Math.ceil( this.areaCoords.x1 / posRatio.x )  + 'px',
1362				y: '-' + Math.ceil( this.areaCoords.y1 / posRatio.y ) + 'px'
1363			};
1364			
1365			var previewStyle = this.previewImg.style;
1366			previewStyle.width = calcPos.w;
1367			previewStyle.height= calcPos.h;
1368			previewStyle.left = calcPos.x;
1369			previewStyle.top = calcPos.y;
1370		}
1371	}
1372	
1373});

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.