2 * Image Cropper (v. 1.2.0 - 2006-10-30 )
\r
3 * Copyright (c) 2006 David Spurr (http://www.defusion.org.uk/)
\r
5 * The image cropper provides a way to draw a crop area on an image and capture
\r
6 * the coordinates of the drawn crop area.
\r
9 * - Based on Prototype and Scriptaculous
\r
10 * - Image editing package styling, the crop area functions and looks
\r
11 * like those found in popular image editing software
\r
12 * - Dynamic inclusion of required styles
\r
13 * - Drag to draw areas
\r
14 * - Shift drag to draw/resize areas as squares
\r
15 * - Selection area can be moved
\r
16 * - Seleciton area can be resized using resize handles
\r
17 * - Allows dimension ratio limited crop areas
\r
18 * - Allows minimum dimension crop areas
\r
19 * - Allows maximum dimesion crop areas
\r
20 * - If both min & max dimension options set to the same value for a single axis,then the cropper will not
\r
21 * display the resize handles as appropriate (when min & max dimensions are passed for both axes this
\r
22 * results in a 'fixed size' crop area)
\r
23 * - Allows dynamic preview of resultant crop ( if minimum width & height are provided ), this is
\r
24 * implemented as a subclass so can be excluded when not required
\r
25 * - Movement of selection area by arrow keys ( shift + arrow key will move selection area by
\r
27 * - All operations stay within bounds of image
\r
28 * - All functionality & display compatible with most popular browsers supported by Prototype:
\r
29 * PC: IE 7, 6 & 5.5, Firefox 1.5, Opera 8.5 (see known issues) & 9.0b
\r
30 * MAC: Camino 1.0, Firefox 1.5, Safari 2.0
\r
33 * - Prototype v. 1.5.0_rc0 > (as packaged with Scriptaculous 1.6.1)
\r
34 * - Scriptaculous v. 1.6.1 > modules: builder, dragdrop
\r
37 * - Safari animated gifs, only one of each will animate, this seems to be a known Safari issue
\r
39 * - After drawing an area and then clicking to start a new drag in IE 5.5 the rendered height
\r
40 * appears as the last height until the user drags, this appears to be the related to the error
\r
41 * that the forceReRender() method fixes for IE 6, i.e. IE 5.5 is not redrawing the box properly.
\r
43 * - Lack of CSS opacity support in Opera before version 9 mean we disable those style rules, these
\r
44 * could be fixed by using PNGs with transparency if Opera 8.5 support is high priority for you
\r
46 * - Marching ants keep reloading in IE <6 (not tested in IE7), it is a known issue in IE and I have
\r
47 * found no viable workarounds that can be included in the release. If this really is an issue for you
\r
48 * either try this post: http://mir.aculo.us/articles/2005/08/28/internet-explorer-and-ajax-image-caching-woes
\r
49 * or uncomment the 'FIX MARCHING ANTS IN IE' rules in the CSS file
\r
51 * - Styling & borders on image, any CSS styling applied directly to the image itself (floats, borders, padding, margin, etc.) will
\r
52 * cause problems with the cropper. The use of a wrapper element to apply these styles to is recommended.
\r
54 * - overflow: auto or overflow: scroll on parent will cause cropper to burst out of parent in IE and Opera (maybe Mac browsers too)
\r
55 * I'm not sure why yet.
\r
58 * See Cropper.Img & Cropper.ImgWithPreview for usage details
\r
61 * v1.2.0 - 2006-10-30
\r
62 * + Added id to the preview image element using 'imgCrop_[originalImageID]'
\r
63 * * #00001 - Fixed bug: Doesn't account for scroll offsets
\r
64 * * #00009 - Fixed bug: Placing the cropper inside differently positioned elements causes incorrect co-ordinates and display
\r
65 * * #00013 - Fixed bug: I-bar cursor appears on drag plane
\r
66 * * #00014 - Fixed bug: If ID for image tag is not found in document script throws error
\r
67 * * Fixed bug with drag start co-ordinates if wrapper element has moved in browser (e.g. dragged to a new position)
\r
68 * * Fixed bug with drag start co-ordinates if image contained in a wrapper with scrolling - this may be buggy if image
\r
69 * has other ancestors with scrolling applied (except the body)
\r
70 * * #00015 - Fixed bug: When cropper removed and then reapplied onEndCrop callback gets called multiple times, solution suggestion from Bill Smith
\r
71 * * Various speed increases & code cleanup which meant improved performance in Mac - which allowed removal of different overlay methods for
\r
72 * IE and all other browsers, which led to a fix for:
\r
73 * * #00010 - Fixed bug: Select area doesn't adhere to image size when image resized using img attributes
\r
74 * - #00006 - Removed default behaviour of automatically setting a ratio when both min width & height passed, the ratioDimensions must be passed in
\r
75 * + #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
\r
76 * and the resize handles will not be displayed as appropriate
\r
77 * * Switched keydown for keypress for moving select area with cursor keys (makes for nicer action) - doesn't appear to work in Safari
\r
79 * v1.1.3 - 2006-08-21
\r
80 * * Fixed wrong cursor on western handle in CSS
\r
81 * + #00008 & #00003 - Added feature: Allow to set dimensions & position for cropper on load
\r
82 * * #00002 - Fixed bug: Pressing 'remove cropper' twice removes image in IE
\r
84 * v1.1.2 - 2006-06-09
\r
85 * * Fixed bugs with ratios when GCD is low (patch submitted by Andy Skelton)
\r
87 * v1.1.1 - 2006-06-03
\r
88 * * Fixed bug with rendering issues fix in IE 5.5
\r
89 * * Fixed bug with endCrop callback issues once cropper had been removed & reset in IE
\r
91 * v1.1.0 - 2006-06-02
\r
92 * * Fixed bug with IE constantly trying to reload select area background image
\r
93 * * Applied more robust fix to Safari & IE rendering issues
\r
94 * + Added method to reset parameters - useful for when dynamically changing img cropper attached to
\r
95 * + Added method to remove cropper from image
\r
97 * v1.0.0 - 2006-05-18
\r
101 * Copyright (c) 2006, David Spurr (http://www.defusion.org.uk/)
\r
102 * All rights reserved.
\r
105 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
\r
107 * * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
\r
108 * * 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.
\r
109 * * 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.
\r
111 * 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 (INCLUDING, 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.
\r
113 * http://www.opensource.org/licenses/bsd-license.php
\r
115 * See scriptaculous.js for full scriptaculous licence
\r
119 * Extend the Draggable class to allow us to pass the rendering
\r
120 * down to the Cropper object.
\r
122 var CropDraggable = Class.create();
\r
124 Object.extend( Object.extend( CropDraggable.prototype, Draggable.prototype), {
\r
126 initialize: function(element) {
\r
127 this.options = Object.extend(
\r
130 * The draw method to defer drawing to
\r
132 drawMethod: function() {}
\r
137 this.element = $(element);
\r
139 this.handle = this.element;
\r
141 this.delta = this.currentDelta();
\r
142 this.dragging = false;
\r
144 this.eventMouseDown = this.initDrag.bindAsEventListener(this);
\r
145 Event.observe(this.handle, "mousedown", this.eventMouseDown);
\r
147 Draggables.register(this);
\r
151 * Defers the drawing of the draggable to the supplied method
\r
153 draw: function(point) {
\r
154 var pos = Position.cumulativeOffset(this.element);
\r
155 var d = this.currentDelta();
\r
159 var p = [0,1].map(function(i) {
\r
160 return (point[i]-pos[i]-this.offset[i])
\r
163 this.options.drawMethod( p );
\r
170 * The Cropper object, this will attach itself to the provided image by wrapping it with
\r
171 * the generated xHTML structure required by the cropper.
\r
174 * @param obj Image element to attach to
\r
175 * @param obj Optional options:
\r
177 * The pixel dimensions to apply as a restrictive ratio, with properties x & y
\r
180 * The minimum width for the select area in pixels
\r
183 * The mimimum height for the select area in pixels
\r
186 * The maximum width for the select areas in pixels (if both minWidth & maxWidth set to same the width of the cropper will be fixed)
\r
189 * The maximum height for the select areas in pixels (if both minHeight & maxHeight set to same the height of the cropper will be fixed)
\r
191 * - displayOnInit int
\r
192 * Whether to display the select area on initialisation, only used when providing minimum width & height or ratio
\r
195 * The callback function to provide the crop details to on end of a crop (see below)
\r
197 * - captureKeys boolean
\r
198 * Whether to capture the keys for moving the select area, as these can cause some problems at the moment
\r
200 * - onloadCoords obj
\r
201 * A coordinates object with properties x1, y1, x2 & y2; for the coordinates of the select area to display onload
\r
203 *----------------------------------------------
\r
205 * The callback function provided via the onEndCrop option should accept the following parameters:
\r
207 * The coordinates object with properties x1, y1, x2 & y2; for the coordinates of the select area
\r
210 * The dimensions object with properites width & height; for the dimensions of the select area
\r
214 * function onEndCrop( coords, dimensions ) {
\r
215 * $( 'x1' ).value = coords.x1;
\r
216 * $( 'y1' ).value = coords.y1;
\r
217 * $( 'x2' ).value = coords.x2;
\r
218 * $( 'y2' ).value = coords.y2;
\r
219 * $( 'width' ).value = dimensions.width;
\r
220 * $( 'height' ).value = dimensions.height;
\r
225 Cropper.Img = Class.create();
\r
226 Cropper.Img.prototype = {
\r
229 * Initialises the class
\r
232 * @param obj Image element to attach to
\r
233 * @param obj Options
\r
236 initialize: function(element, options) {
\r
237 this.options = Object.extend(
\r
241 * The pixel dimensions to apply as a restrictive ratio
\r
243 ratioDim: { x: 0, y: 0 },
\r
246 * The minimum pixel width, also used as restrictive ratio if min height passed too
\r
251 * The minimum pixel height, also used as restrictive ratio if min width passed too
\r
256 * Whether to display the select area on initialisation, only used when providing minimum width & height or ratio
\r
258 displayOnInit: false,
\r
261 * The call back function to pass the final values to
\r
263 onEndCrop: Prototype.emptyFunction,
\r
266 * Whether to capture key presses or not
\r
270 * @var obj Coordinate object x1, y1, x2, y2
\r
271 * The coordinates to optionally display the select area at onload
\r
273 onloadCoords: null,
\r
276 * The maximum width for the select areas in pixels (if both minWidth & maxWidth set to same the width of the cropper will be fixed)
\r
281 * The maximum height for the select areas in pixels (if both minHeight & maxHeight set to same the height of the cropper will be fixed)
\r
289 * The img node to attach to
\r
291 this.img = $( element );
\r
294 * The x & y coordinates of the click point
\r
296 this.clickCoords = { x: 0, y: 0 };
\r
299 * Whether the user is dragging
\r
301 this.dragging = false;
\r
304 * Whether the user is resizing
\r
306 this.resizing = false;
\r
309 * Whether the user is on a webKit browser
\r
311 this.isWebKit = /Konqueror|Safari|KHTML/.test( navigator.userAgent );
\r
314 * Whether the user is on IE
\r
316 this.isIE = /MSIE/.test( navigator.userAgent );
\r
319 * Whether the user is on Opera below version 9
\r
321 this.isOpera8 = /Opera\s[1-8]/.test( navigator.userAgent );
\r
334 * Whether we've attached sucessfully
\r
336 this.attached = false;
\r
339 * Whether we've got a fixed width (if minWidth EQ or GT maxWidth then we have a fixed width
\r
340 * in the case of minWidth > maxWidth maxWidth wins as the fixed width)
\r
342 this.fixedWidth = ( this.options.maxWidth > 0 && ( this.options.minWidth >= this.options.maxWidth ) );
\r
345 * Whether we've got a fixed height (if minHeight EQ or GT maxHeight then we have a fixed height
\r
346 * in the case of minHeight > maxHeight maxHeight wins as the fixed height)
\r
348 this.fixedHeight = ( this.options.maxHeight > 0 && ( this.options.minHeight >= this.options.maxHeight ) );
\r
350 // quit if the image element doesn't exist
\r
351 if( typeof this.img == 'undefined' ) return;
\r
353 // include the stylesheet
\r
354 $A( document.getElementsByTagName( 'script' ) ).each(
\r
356 if( s.src.match( /cropper\.js/ ) ) {
\r
357 var path = s.src.replace( /cropper\.js(.*)?/, '' );
\r
358 // '<link rel="stylesheet" type="text/css" href="' + path + 'cropper.css" media="screen" />';
\r
359 var style = document.createElement( 'link' );
\r
360 style.rel = 'stylesheet';
\r
361 style.type = 'text/css';
\r
362 style.href = path + 'cropper.css';
\r
363 style.media = 'screen';
\r
364 document.getElementsByTagName( 'head' )[0].appendChild( style );
\r
369 // calculate the ratio when neccessary
\r
370 if( this.options.ratioDim.x > 0 && this.options.ratioDim.y > 0 ) {
\r
371 var gcd = this.getGCD( this.options.ratioDim.x, this.options.ratioDim.y );
\r
372 this.ratioX = this.options.ratioDim.x / gcd;
\r
373 this.ratioY = this.options.ratioDim.y / gcd;
\r
374 // dump( 'RATIO : ' + this.ratioX + ':' + this.ratioY + '\n' );
\r
377 // initialise sub classes
\r
378 this.subInitialize();
\r
380 // only load the event observers etc. once the image is loaded
\r
381 // this is done after the subInitialize() call just in case the sub class does anything
\r
382 // that will affect the result of the call to onLoad()
\r
383 if( this.img.complete || this.isWebKit ) this.onLoad(); // for some reason Safari seems to support img.complete but returns 'undefined' on the this.img object
\r
384 else Event.observe( this.img, 'load', this.onLoad.bindAsEventListener( this) );
\r
388 * The Euclidean algorithm used to find the greatest common divisor
\r
391 * @param int Value 1
\r
392 * @param int Value 2
\r
395 getGCD : function( a , b ) {
\r
396 if( b == 0 ) return a;
\r
397 return this.getGCD(b, a % b );
\r
401 * Attaches the cropper to the image once it has loaded
\r
406 onLoad: function( ) {
\r
408 * Build the container and all related elements, will result in the following
\r
410 * <div class="imgCrop_wrap">
\r
411 * <img ... this.img ... />
\r
412 * <div class="imgCrop_dragArea">
\r
413 * <!-- the inner spans are only required for IE to stop it making the divs 1px high/wide -->
\r
414 * <div class="imgCrop_overlay imageCrop_north"><span></span></div>
\r
415 * <div class="imgCrop_overlay imageCrop_east"><span></span></div>
\r
416 * <div class="imgCrop_overlay imageCrop_south"><span></span></div>
\r
417 * <div class="imgCrop_overlay imageCrop_west"><span></span></div>
\r
418 * <div class="imgCrop_selArea">
\r
419 * <!-- marquees -->
\r
420 * <!-- the inner spans are only required for IE to stop it making the divs 1px high/wide -->
\r
421 * <div class="imgCrop_marqueeHoriz imgCrop_marqueeNorth"><span></span></div>
\r
422 * <div class="imgCrop_marqueeVert imgCrop_marqueeEast"><span></span></div>
\r
423 * <div class="imgCrop_marqueeHoriz imgCrop_marqueeSouth"><span></span></div>
\r
424 * <div class="imgCrop_marqueeVert imgCrop_marqueeWest"><span></span></div>
\r
426 * <div class="imgCrop_handle imgCrop_handleN"></div>
\r
427 * <div class="imgCrop_handle imgCrop_handleNE"></div>
\r
428 * <div class="imgCrop_handle imgCrop_handleE"></div>
\r
429 * <div class="imgCrop_handle imgCrop_handleSE"></div>
\r
430 * <div class="imgCrop_handle imgCrop_handleS"></div>
\r
431 * <div class="imgCrop_handle imgCrop_handleSW"></div>
\r
432 * <div class="imgCrop_handle imgCrop_handleW"></div>
\r
433 * <div class="imgCrop_handle imgCrop_handleNW"></div>
\r
434 * <div class="imgCrop_clickArea"></div>
\r
436 * <div class="imgCrop_clickArea"></div>
\r
440 var cNamePrefix = 'imgCrop_';
\r
442 // get the point to insert the container
\r
443 var insertPoint = this.img.parentNode;
\r
445 // apply an extra class to the wrapper to fix Opera below version 9
\r
446 var fixOperaClass = '';
\r
447 if( this.isOpera8 ) fixOperaClass = ' opera8';
\r
448 this.imgWrap = Builder.node( 'div', { 'class': cNamePrefix + 'wrap' + fixOperaClass } );
\r
450 this.north = Builder.node( 'div', { 'class': cNamePrefix + 'overlay ' + cNamePrefix + 'north' }, [Builder.node( 'span' )] );
\r
451 this.east = Builder.node( 'div', { 'class': cNamePrefix + 'overlay ' + cNamePrefix + 'east' } , [Builder.node( 'span' )] );
\r
452 this.south = Builder.node( 'div', { 'class': cNamePrefix + 'overlay ' + cNamePrefix + 'south' }, [Builder.node( 'span' )] );
\r
453 this.west = Builder.node( 'div', { 'class': cNamePrefix + 'overlay ' + cNamePrefix + 'west' } , [Builder.node( 'span' )] );
\r
455 var overlays = [ this.north, this.east, this.south, this.west ];
\r
457 this.dragArea = Builder.node( 'div', { 'class': cNamePrefix + 'dragArea' }, overlays );
\r
459 this.handleN = Builder.node( 'div', { 'class': cNamePrefix + 'handle ' + cNamePrefix + 'handleN' } );
\r
460 this.handleNE = Builder.node( 'div', { 'class': cNamePrefix + 'handle ' + cNamePrefix + 'handleNE' } );
\r
461 this.handleE = Builder.node( 'div', { 'class': cNamePrefix + 'handle ' + cNamePrefix + 'handleE' } );
\r
462 this.handleSE = Builder.node( 'div', { 'class': cNamePrefix + 'handle ' + cNamePrefix + 'handleSE' } );
\r
463 this.handleS = Builder.node( 'div', { 'class': cNamePrefix + 'handle ' + cNamePrefix + 'handleS' } );
\r
464 this.handleSW = Builder.node( 'div', { 'class': cNamePrefix + 'handle ' + cNamePrefix + 'handleSW' } );
\r
465 this.handleW = Builder.node( 'div', { 'class': cNamePrefix + 'handle ' + cNamePrefix + 'handleW' } );
\r
466 this.handleNW = Builder.node( 'div', { 'class': cNamePrefix + 'handle ' + cNamePrefix + 'handleNW' } );
\r
468 this.selArea = Builder.node( 'div', { 'class': cNamePrefix + 'selArea' },
\r
470 Builder.node( 'div', { 'class': cNamePrefix + 'marqueeHoriz ' + cNamePrefix + 'marqueeNorth' }, [Builder.node( 'span' )] ),
\r
471 Builder.node( 'div', { 'class': cNamePrefix + 'marqueeVert ' + cNamePrefix + 'marqueeEast' } , [Builder.node( 'span' )] ),
\r
472 Builder.node( 'div', { 'class': cNamePrefix + 'marqueeHoriz ' + cNamePrefix + 'marqueeSouth' }, [Builder.node( 'span' )] ),
\r
473 Builder.node( 'div', { 'class': cNamePrefix + 'marqueeVert ' + cNamePrefix + 'marqueeWest' } , [Builder.node( 'span' )] ),
\r
482 Builder.node( 'div', { 'class': cNamePrefix + 'clickArea' } )
\r
486 this.imgWrap.appendChild( this.img );
\r
487 this.imgWrap.appendChild( this.dragArea );
\r
488 this.dragArea.appendChild( this.selArea );
\r
489 this.dragArea.appendChild( Builder.node( 'div', { 'class': cNamePrefix + 'clickArea' } ) );
\r
491 insertPoint.appendChild( this.imgWrap );
\r
493 // add event observers
\r
494 this.startDragBind = this.startDrag.bindAsEventListener( this );
\r
495 Event.observe( this.dragArea, 'mousedown', this.startDragBind );
\r
497 this.onDragBind = this.onDrag.bindAsEventListener( this );
\r
498 Event.observe( document, 'mousemove', this.onDragBind );
\r
500 this.endCropBind = this.endCrop.bindAsEventListener( this );
\r
501 Event.observe( document, 'mouseup', this.endCropBind );
\r
503 this.resizeBind = this.startResize.bindAsEventListener( this );
\r
504 this.handles = [ this.handleN, this.handleNE, this.handleE, this.handleSE, this.handleS, this.handleSW, this.handleW, this.handleNW ];
\r
505 this.registerHandles( true );
\r
507 if( this.options.captureKeys ) {
\r
508 this.keysBind = this.handleKeys.bindAsEventListener( this );
\r
509 Event.observe( document, 'keypress', this.keysBind );
\r
512 // attach the dragable to the select area
\r
513 new CropDraggable( this.selArea, { drawMethod: this.moveArea.bindAsEventListener( this ) } );
\r
519 * Manages adding or removing the handle event handler and hiding or displaying them as appropriate
\r
522 * @param boolean registration true = add, false = remove
\r
525 registerHandles: function( registration ) {
\r
526 for( var i = 0; i < this.handles.length; i++ ) {
\r
527 var handle = $( this.handles[i] );
\r
529 if( registration ) {
\r
530 var hideHandle = false; // whether to hide the handle
\r
532 // disable handles asappropriate if we've got fixed dimensions
\r
533 // if both dimensions are fixed we don't need to do much
\r
534 if( this.fixedWidth && this.fixedHeight ) hideHandle = true;
\r
535 else if( this.fixedWidth || this.fixedHeight ) {
\r
536 // if one of the dimensions is fixed then just hide those handles
\r
537 var isCornerHandle = handle.className.match( /([S|N][E|W])$/ )
\r
538 var isWidthHandle = handle.className.match( /(E|W)$/ );
\r
539 var isHeightHandle = handle.className.match( /(N|S)$/ );
\r
540 if( isCornerHandle ) hideHandle = true;
\r
541 else if( this.fixedWidth && isWidthHandle ) hideHandle = true;
\r
542 else if( this.fixedHeight && isHeightHandle ) hideHandle = true;
\r
544 if( hideHandle ) handle.hide();
\r
545 else Event.observe( handle, 'mousedown', this.resizeBind );
\r
548 Event.stopObserving( handle, 'mousedown', this.resizeBind );
\r
554 * Sets up all the cropper parameters, this can be used to reset the cropper when dynamically
\r
555 * changing the images
\r
560 setParams: function() {
\r
565 this.imgW = this.img.width;
\r
570 this.imgH = this.img.height;
\r
572 $( this.north ).setStyle( { height: 0 } );
\r
573 $( this.east ).setStyle( { width: 0, height: 0 } );
\r
574 $( this.south ).setStyle( { height: 0 } );
\r
575 $( this.west ).setStyle( { width: 0, height: 0 } );
\r
577 // resize the container to fit the image
\r
578 $( this.imgWrap ).setStyle( { 'width': this.imgW + 'px', 'height': this.imgH + 'px' } );
\r
580 // hide the select area
\r
581 $( this.selArea ).hide();
\r
583 // setup the starting position of the select area
\r
584 var startCoords = { x1: 0, y1: 0, x2: 0, y2: 0 };
\r
585 var validCoordsSet = false;
\r
587 // display the select area
\r
588 if( this.options.onloadCoords != null ) {
\r
589 // if we've being given some coordinates to
\r
590 startCoords = this.cloneCoords( this.options.onloadCoords );
\r
591 validCoordsSet = true;
\r
592 } else if( this.options.ratioDim.x > 0 && this.options.ratioDim.y > 0 ) {
\r
593 // if there is a ratio limit applied and the then set it to initial ratio
\r
594 startCoords.x1 = Math.ceil( ( this.imgW - this.options.ratioDim.x ) / 2 );
\r
595 startCoords.y1 = Math.ceil( ( this.imgH - this.options.ratioDim.y ) / 2 );
\r
596 startCoords.x2 = startCoords.x1 + this.options.ratioDim.x;
\r
597 startCoords.y2 = startCoords.y1 + this.options.ratioDim.y;
\r
598 validCoordsSet = true;
\r
601 this.setAreaCoords( startCoords, false, false, 1 );
\r
603 if( this.options.displayOnInit && validCoordsSet ) {
\r
604 this.selArea.show();
\r
609 this.attached = true;
\r
613 * Removes the cropper
\r
618 remove: function() {
\r
619 if( this.attached ) {
\r
620 this.attached = false;
\r
622 // remove the elements we inserted
\r
623 this.imgWrap.parentNode.insertBefore( this.img, this.imgWrap );
\r
624 this.imgWrap.parentNode.removeChild( this.imgWrap );
\r
626 // remove the event observers
\r
627 Event.stopObserving( this.dragArea, 'mousedown', this.startDragBind );
\r
628 Event.stopObserving( document, 'mousemove', this.onDragBind );
\r
629 Event.stopObserving( document, 'mouseup', this.endCropBind );
\r
630 this.registerHandles( false );
\r
631 if( this.options.captureKeys ) Event.stopObserving( document, 'keypress', this.keysBind );
\r
636 * Resets the cropper, can be used either after being removed or any time you wish
\r
641 reset: function() {
\r
642 if( !this.attached ) this.onLoad();
\r
643 else this.setParams();
\r
648 * Handles the key functionality, currently just using arrow keys to move, if the user
\r
649 * presses shift then the area will move by 10 pixels
\r
651 handleKeys: function( e ) {
\r
652 var dir = { x: 0, y: 0 }; // direction to move it in & the amount in pixels
\r
653 if( !this.dragging ) {
\r
655 // catch the arrow keys
\r
656 switch( e.keyCode ) {
\r
657 case( 37 ) : // left
\r
663 case( 39 ) : // right
\r
666 case( 40 ) : // down
\r
671 if( dir.x != 0 || dir.y != 0 ) {
\r
672 // if shift is pressed then move by 10 pixels
\r
678 this.moveArea( [ this.areaCoords.x1 + dir.x, this.areaCoords.y1 + dir.y ] );
\r
685 * Calculates the width from the areaCoords
\r
690 calcW: function() {
\r
691 return (this.areaCoords.x2 - this.areaCoords.x1)
\r
695 * Calculates the height from the areaCoords
\r
700 calcH: function() {
\r
701 return (this.areaCoords.y2 - this.areaCoords.y1)
\r
705 * Moves the select area to the supplied point (assumes the point is x1 & y1 of the select area)
\r
708 * @param array Point for x1 & y1 to move select area to
\r
711 moveArea: function( point ) {
\r
712 // dump( 'moveArea : ' + point[0] + ',' + point[1] + ',' + ( point[0] + ( this.areaCoords.x2 - this.areaCoords.x1 ) ) + ',' + ( point[1] + ( this.areaCoords.y2 - this.areaCoords.y1 ) ) + '\n' );
\r
713 this.setAreaCoords(
\r
717 x2: point[0] + this.calcW(),
\r
718 y2: point[1] + this.calcH()
\r
727 * Clones a co-ordinates object, stops problems with handling them by reference
\r
730 * @param obj Coordinate object x1, y1, x2, y2
\r
731 * @return obj Coordinate object x1, y1, x2, y2
\r
733 cloneCoords: function( coords ) {
\r
734 return { x1: coords.x1, y1: coords.y1, x2: coords.x2, y2: coords.y2 };
\r
738 * Sets the select coords to those provided but ensures they don't go
\r
739 * outside the bounding box
\r
742 * @param obj Coordinates x1, y1, x2, y2
\r
743 * @param boolean Whether this is a move
\r
744 * @param boolean Whether to apply squaring
\r
745 * @param obj Direction of mouse along both axis x, y ( -1 = negative, 1 = positive ) only required when moving etc.
\r
746 * @param string The current resize handle || null
\r
749 setAreaCoords: function( coords, moving, square, direction, resizeHandle ) {
\r
750 // dump( 'setAreaCoords (in) : ' + coords.x1 + ',' + coords.y1 + ',' + coords.x2 + ',' + coords.y2 );
\r
753 var targW = coords.x2 - coords.x1;
\r
754 var targH = coords.y2 - coords.y1;
\r
756 // ensure we're within the bounds
\r
757 if( coords.x1 < 0 ) {
\r
761 if( coords.y1 < 0 ) {
\r
765 if( coords.x2 > this.imgW ) {
\r
766 coords.x2 = this.imgW;
\r
767 coords.x1 = this.imgW - targW;
\r
769 if( coords.y2 > this.imgH ) {
\r
770 coords.y2 = this.imgH;
\r
771 coords.y1 = this.imgH - targH;
\r
774 // ensure we're within the bounds
\r
775 if( coords.x1 < 0 ) coords.x1 = 0;
\r
776 if( coords.y1 < 0 ) coords.y1 = 0;
\r
777 if( coords.x2 > this.imgW ) coords.x2 = this.imgW;
\r
778 if( coords.y2 > this.imgH ) coords.y2 = this.imgH;
\r
780 // This is passed as null in onload
\r
781 if( direction != null ) {
\r
783 // apply the ratio or squaring where appropriate
\r
784 if( this.ratioX > 0 ) this.applyRatio( coords, { x: this.ratioX, y: this.ratioY }, direction, resizeHandle );
\r
785 else if( square ) this.applyRatio( coords, { x: 1, y: 1 }, direction, resizeHandle );
\r
787 var mins = [ this.options.minWidth, this.options.minHeight ]; // minimum dimensions [x,y]
\r
788 var maxs = [ this.options.maxWidth, this.options.maxHeight ]; // maximum dimensions [x,y]
\r
790 // apply dimensions where appropriate
\r
791 if( mins[0] > 0 || mins[1] > 0 || maxs[0] > 0 || maxs[1] > 0) {
\r
793 var coordsTransX = { a1: coords.x1, a2: coords.x2 };
\r
794 var coordsTransY = { a1: coords.y1, a2: coords.y2 };
\r
795 var boundsX = { min: 0, max: this.imgW };
\r
796 var boundsY = { min: 0, max: this.imgH };
\r
798 // handle squaring properly on single axis minimum dimensions
\r
799 if( (mins[0] != 0 || mins[1] != 0) && square ) {
\r
800 if( mins[0] > 0 ) mins[1] = mins[0];
\r
801 else if( mins[1] > 0 ) mins[0] = mins[1];
\r
804 if( (maxs[0] != 0 || maxs[0] != 0) && square ) {
\r
805 // 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 - if that makes sense)
\r
806 if( maxs[0] > 0 && maxs[0] <= maxs[1] ) maxs[1] = maxs[0];
\r
807 else if( maxs[1] > 0 && maxs[1] <= maxs[0] ) maxs[0] = maxs[1];
\r
810 if( mins[0] > 0 ) this.applyDimRestriction( coordsTransX, mins[0], direction.x, boundsX, 'min' );
\r
811 if( mins[1] > 1 ) this.applyDimRestriction( coordsTransY, mins[1], direction.y, boundsY, 'min' );
\r
813 if( maxs[0] > 0 ) this.applyDimRestriction( coordsTransX, maxs[0], direction.x, boundsX, 'max' );
\r
814 if( maxs[1] > 1 ) this.applyDimRestriction( coordsTransY, maxs[1], direction.y, boundsY, 'max' );
\r
816 coords = { x1: coordsTransX.a1, y1: coordsTransY.a1, x2: coordsTransX.a2, y2: coordsTransY.a2 };
\r
822 // dump( 'setAreaCoords (out) : ' + coords.x1 + ',' + coords.y1 + ',' + coords.x2 + ',' + coords.y2 + '\n' );
\r
823 this.areaCoords = coords;
\r
827 * Applies the supplied dimension restriction to the supplied coordinates along a single axis
\r
830 * @param obj Single axis coordinates, a1, a2 (e.g. for the x axis a1 = x1 & a2 = x2)
\r
831 * @param int The restriction value
\r
832 * @param int The direction ( -1 = negative, 1 = positive )
\r
833 * @param obj The bounds of the image ( for this axis )
\r
834 * @param string The dimension restriction type ( 'min' | 'max' )
\r
837 applyDimRestriction: function( coords, val, direction, bounds, type ) {
\r
839 if( type == 'min' ) check = ( ( coords.a2 - coords.a1 ) < val );
\r
840 else check = ( ( coords.a2 - coords.a1 ) > val );
\r
842 if( direction == 1 ) coords.a2 = coords.a1 + val;
\r
843 else coords.a1 = coords.a2 - val;
\r
845 // make sure we're still in the bounds (not too pretty for the user, but needed)
\r
846 if( coords.a1 < bounds.min ) {
\r
847 coords.a1 = bounds.min;
\r
849 } else if( coords.a2 > bounds.max ) {
\r
850 coords.a1 = bounds.max - val;
\r
851 coords.a2 = bounds.max;
\r
857 * Applies the supplied ratio to the supplied coordinates
\r
860 * @param obj Coordinates, x1, y1, x2, y2
\r
861 * @param obj Ratio, x, y
\r
862 * @param obj Direction of mouse, x & y : -1 == negative 1 == positive
\r
863 * @param string The current resize handle || null
\r
866 applyRatio : function( coords, ratio, direction, resizeHandle ) {
\r
867 // dump( 'direction.y : ' + direction.y + '\n');
\r
869 if( resizeHandle == 'N' || resizeHandle == 'S' ) {
\r
870 // dump( 'north south \n');
\r
871 // if moving on either the lone north & south handles apply the ratio on the y axis
\r
872 newCoords = this.applyRatioToAxis(
\r
873 { a1: coords.y1, b1: coords.x1, a2: coords.y2, b2: coords.x2 },
\r
874 { a: ratio.y, b: ratio.x },
\r
875 { a: direction.y, b: direction.x },
\r
876 { min: 0, max: this.imgW }
\r
878 coords.x1 = newCoords.b1;
\r
879 coords.y1 = newCoords.a1;
\r
880 coords.x2 = newCoords.b2;
\r
881 coords.y2 = newCoords.a2;
\r
883 // otherwise deal with it as if we're applying the ratio on the x axis
\r
884 newCoords = this.applyRatioToAxis(
\r
885 { a1: coords.x1, b1: coords.y1, a2: coords.x2, b2: coords.y2 },
\r
886 { a: ratio.x, b: ratio.y },
\r
887 { a: direction.x, b: direction.y },
\r
888 { min: 0, max: this.imgH }
\r
890 coords.x1 = newCoords.a1;
\r
891 coords.y1 = newCoords.b1;
\r
892 coords.x2 = newCoords.a2;
\r
893 coords.y2 = newCoords.b2;
\r
899 * Applies the provided ratio to the provided coordinates based on provided direction & bounds,
\r
900 * use to encapsulate functionality to make it easy to apply to either axis. This is probably
\r
901 * quite hard to visualise so see the x axis example within applyRatio()
\r
903 * Example in parameter details & comments is for requesting applying ratio to x axis.
\r
906 * @param obj Coords object (a1, b1, a2, b2) where a = x & b = y in example
\r
907 * @param obj Ratio object (a, b) where a = x & b = y in example
\r
908 * @param obj Direction object (a, b) where a = x & b = y in example
\r
909 * @param obj Bounds (min, max)
\r
910 * @return obj Coords object (a1, b1, a2, b2) where a = x & b = y in example
\r
912 applyRatioToAxis: function( coords, ratio, direction, bounds ) {
\r
913 var newCoords = Object.extend( coords, {} );
\r
914 var calcDimA = newCoords.a2 - newCoords.a1; // calculate dimension a (e.g. width)
\r
915 var targDimB = Math.floor( calcDimA * ratio.b / ratio.a ); // the target dimension b (e.g. height)
\r
916 var targB; // to hold target b (e.g. y value)
\r
917 var targDimA; // to hold target dimension a (e.g. width)
\r
918 var calcDimB = null; // to hold calculated dimension b (e.g. height)
\r
920 // dump( 'newCoords[0]: ' + newCoords.a1 + ',' + newCoords.b1 + ','+ newCoords.a2 + ',' + newCoords.b2 + '\n');
\r
922 if( direction.b == 1 ) { // if travelling in a positive direction
\r
923 // make sure we're not going out of bounds
\r
924 targB = newCoords.b1 + targDimB;
\r
925 if( targB > bounds.max ) {
\r
926 targB = bounds.max;
\r
927 calcDimB = targB - newCoords.b1; // calcuate dimension b (e.g. height)
\r
930 newCoords.b2 = targB;
\r
931 } else { // if travelling in a negative direction
\r
932 // make sure we're not going out of bounds
\r
933 targB = newCoords.b2 - targDimB;
\r
934 if( targB < bounds.min ) {
\r
935 targB = bounds.min;
\r
936 calcDimB = targB + newCoords.b2; // calcuate dimension b (e.g. height)
\r
938 newCoords.b1 = targB;
\r
941 // dump( 'newCoords[1]: ' + newCoords.a1 + ',' + newCoords.b1 + ','+ newCoords.a2 + ',' + newCoords.b2 + '\n');
\r
943 // apply the calculated dimensions
\r
944 if( calcDimB != null ) {
\r
945 targDimA = Math.floor( calcDimB * ratio.a / ratio.b );
\r
947 if( direction.a == 1 ) newCoords.a2 = newCoords.a1 + targDimA;
\r
948 else newCoords.a1 = newCoords.a1 = newCoords.a2 - targDimA;
\r
951 // dump( 'newCoords[2]: ' + newCoords.a1 + ',' + newCoords.b1 + ','+ newCoords.a2 + ',' + newCoords.b2 + '\n');
\r
957 * Draws the select area
\r
962 drawArea: function( ) {
\r
964 * NOTE: I'm not using the Element.setStyle() shortcut as they make it
\r
965 * quite sluggish on Mac based browsers
\r
967 // dump( 'drawArea : ' + this.areaCoords.x1 + ',' + this.areaCoords.y1 + ',' + this.areaCoords.x2 + ',' + this.areaCoords.y2 + '\n' );
\r
968 var areaWidth = this.calcW();
\r
969 var areaHeight = this.calcH();
\r
972 * Calculate all the style strings before we use them, allows reuse & produces quicker
\r
973 * rendering (especially noticable in Mac based browsers)
\r
977 this.areaCoords.x1 + px, // the left of the selArea
\r
978 this.areaCoords.y1 + px, // the top of the selArea
\r
979 areaWidth + px, // width of the selArea
\r
980 areaHeight + px, // height of the selArea
\r
981 this.areaCoords.x2 + px, // bottom of the selArea
\r
982 this.areaCoords.y2 + px, // right of the selArea
\r
983 (this.img.width - this.areaCoords.x2) + px, // right edge of selArea
\r
984 (this.img.height - this.areaCoords.y2) + px // bottom edge of selArea
\r
987 // do the select area
\r
988 var areaStyle = this.selArea.style;
\r
989 areaStyle.left = params[0];
\r
990 areaStyle.top = params[1];
\r
991 areaStyle.width = params[2];
\r
992 areaStyle.height = params[3];
\r
994 // position the north, east, south & west handles
\r
995 var horizHandlePos = Math.ceil( (areaWidth - 6) / 2 ) + px;
\r
996 var vertHandlePos = Math.ceil( (areaHeight - 6) / 2 ) + px;
\r
998 this.handleN.style.left = horizHandlePos;
\r
999 this.handleE.style.top = vertHandlePos;
\r
1000 this.handleS.style.left = horizHandlePos;
\r
1001 this.handleW.style.top = vertHandlePos;
\r
1003 // draw the four overlays
\r
1004 this.north.style.height = params[1];
\r
1006 var eastStyle = this.east.style;
\r
1007 eastStyle.top = params[1];
\r
1008 eastStyle.height = params[3];
\r
1009 eastStyle.left = params[4];
\r
1010 eastStyle.width = params[6];
\r
1012 var southStyle = this.south.style;
\r
1013 southStyle.top = params[5];
\r
1014 southStyle.height = params[7];
\r
1016 var westStyle = this.west.style;
\r
1017 westStyle.top = params[1];
\r
1018 westStyle.height = params[3];
\r
1019 westStyle.width = params[0];
\r
1021 // call the draw method on sub classes
\r
1022 this.subDrawArea();
\r
1024 this.forceReRender();
\r
1028 * Force the re-rendering of the selArea element which fixes rendering issues in Safari
\r
1029 * & IE PC, especially evident when re-sizing perfectly vertical using any of the south handles
\r
1034 forceReRender: function() {
\r
1035 if( this.isIE || this.isWebKit) {
\r
1036 var n = document.createTextNode(' ');
\r
1039 if( this.isIE ) fixEl = this.selArea;
\r
1040 else if( this.isWebKit ) {
\r
1041 fixEl = document.getElementsByClassName( 'imgCrop_marqueeSouth', this.imgWrap )[0];
\r
1042 /* we have to be a bit more forceful for Safari, otherwise the the marquee &
\r
1043 * the south handles still don't move
\r
1045 d = Builder.node( 'div', '' );
\r
1046 d.style.visibility = 'hidden';
\r
1048 var classList = ['SE','S','SW'];
\r
1049 for( i = 0; i < classList.length; i++ ) {
\r
1050 el = document.getElementsByClassName( 'imgCrop_handle' + classList[i], this.selArea )[0];
\r
1051 if( el.childNodes.length ) el.removeChild( el.childNodes[0] );
\r
1052 el.appendChild(d);
\r
1055 fixEl.appendChild(n);
\r
1056 fixEl.removeChild(n);
\r
1061 * Starts the resize
\r
1064 * @param obj Event
\r
1067 startResize: function( e ) {
\r
1068 this.startCoords = this.cloneCoords( this.areaCoords );
\r
1070 this.resizing = true;
\r
1071 this.resizeHandle = Event.element( e ).classNames().toString().replace(/([^N|NE|E|SE|S|SW|W|NW])+/, '');
\r
1072 // dump( 'this.resizeHandle : ' + this.resizeHandle + '\n' );
\r
1080 * @param obj Event
\r
1083 startDrag: function( e ) {
\r
1084 this.selArea.show();
\r
1085 this.clickCoords = this.getCurPos( e );
\r
1087 this.setAreaCoords( { x1: this.clickCoords.x, y1: this.clickCoords.y, x2: this.clickCoords.x, y2: this.clickCoords.y }, false, false, null );
\r
1089 this.dragging = true;
\r
1090 this.onDrag( e ); // incase the user just clicks once after already making a selection
\r
1095 * Gets the current cursor position relative to the image
\r
1098 * @param obj Event
\r
1099 * @return obj x,y pixels of the cursor
\r
1101 getCurPos: function( e ) {
\r
1102 // get the offsets for the wrapper within the document
\r
1103 var el = this.imgWrap, wrapOffsets = Position.cumulativeOffset( el );
\r
1104 // 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
\r
1105 while( el.nodeName != 'BODY' ) {
\r
1106 wrapOffsets[1] -= el.scrollTop || 0;
\r
1107 wrapOffsets[0] -= el.scrollLeft || 0;
\r
1108 el = el.parentNode;
\r
1110 return curPos = {
\r
1111 x: Event.pointerX(e) - wrapOffsets[0],
\r
1112 y: Event.pointerY(e) - wrapOffsets[1]
\r
1117 * Performs the drag for both resize & inital draw dragging
\r
1120 * @param obj Event
\r
1123 onDrag: function( e ) {
\r
1124 if( this.dragging || this.resizing ) {
\r
1126 var resizeHandle = null;
\r
1127 var curPos = this.getCurPos( e );
\r
1128 var newCoords = this.cloneCoords( this.areaCoords );
\r
1129 var direction = { x: 1, y: 1 };
\r
1131 if( this.dragging ) {
\r
1132 if( curPos.x < this.clickCoords.x ) direction.x = -1;
\r
1133 if( curPos.y < this.clickCoords.y ) direction.y = -1;
\r
1135 this.transformCoords( curPos.x, this.clickCoords.x, newCoords, 'x' );
\r
1136 this.transformCoords( curPos.y, this.clickCoords.y, newCoords, 'y' );
\r
1137 } else if( this.resizing ) {
\r
1138 resizeHandle = this.resizeHandle;
\r
1139 // do x movements first
\r
1140 if( resizeHandle.match(/E/) ) {
\r
1141 // if we're moving an east handle
\r
1142 this.transformCoords( curPos.x, this.startCoords.x1, newCoords, 'x' );
\r
1143 if( curPos.x < this.startCoords.x1 ) direction.x = -1;
\r
1144 } else if( resizeHandle.match(/W/) ) {
\r
1145 // if we're moving an west handle
\r
1146 this.transformCoords( curPos.x, this.startCoords.x2, newCoords, 'x' );
\r
1147 if( curPos.x < this.startCoords.x2 ) direction.x = -1;
\r
1150 // do y movements second
\r
1151 if( resizeHandle.match(/N/) ) {
\r
1152 // if we're moving an north handle
\r
1153 this.transformCoords( curPos.y, this.startCoords.y2, newCoords, 'y' );
\r
1154 if( curPos.y < this.startCoords.y2 ) direction.y = -1;
\r
1155 } else if( resizeHandle.match(/S/) ) {
\r
1156 // if we're moving an south handle
\r
1157 this.transformCoords( curPos.y, this.startCoords.y1, newCoords, 'y' );
\r
1158 if( curPos.y < this.startCoords.y1 ) direction.y = -1;
\r
1163 this.setAreaCoords( newCoords, false, e.shiftKey, direction, resizeHandle );
\r
1165 Event.stop( e ); // stop the default event (selecting images & text) in Safari & IE PC
\r
1170 * Applies the appropriate transform to supplied co-ordinates, on the
\r
1171 * defined axis, depending on the relationship of the supplied values
\r
1174 * @param int Current value of pointer
\r
1175 * @param int Base value to compare current pointer val to
\r
1176 * @param obj Coordinates to apply transformation on x1, x2, y1, y2
\r
1177 * @param string Axis to apply transformation on 'x' || 'y'
\r
1180 transformCoords : function( curVal, baseVal, coords, axis ) {
\r
1181 var newVals = [ curVal, baseVal ];
\r
1182 if( curVal > baseVal ) newVals.reverse();
\r
1183 coords[ axis + '1' ] = newVals[0];
\r
1184 coords[ axis + '2' ] = newVals[1];
\r
1188 * Ends the crop & passes the values of the select area on to the appropriate
\r
1189 * callback function on completion of a crop
\r
1194 endCrop : function() {
\r
1195 this.dragging = false;
\r
1196 this.resizing = false;
\r
1198 this.options.onEndCrop(
\r
1201 width: this.calcW(),
\r
1202 height: this.calcH()
\r
1208 * Abstract method called on the end of initialization
\r
1214 subInitialize: function() {},
\r
1217 * Abstract method called on the end of drawArea()
\r
1223 subDrawArea: function() {}
\r
1230 * Extend the Cropper.Img class to allow for presentation of a preview image of the resulting crop,
\r
1231 * the option for displayOnInit is always overridden to true when displaying a preview image
\r
1234 * @param obj Image element to attach to
\r
1235 * @param obj Optional options:
\r
1236 * - see Cropper.Img for base options
\r
1238 * - previewWrap obj
\r
1239 * HTML element that will be used as a container for the preview image
\r
1241 Cropper.ImgWithPreview = Class.create();
\r
1243 Object.extend( Object.extend( Cropper.ImgWithPreview.prototype, Cropper.Img.prototype ), {
\r
1246 * Implements the abstract method from Cropper.Img to initialize preview image settings.
\r
1247 * Will only attach a preview image is the previewWrap element is defined and the minWidth
\r
1248 * & minHeight options are set.
\r
1250 * @see Croper.Img.subInitialize
\r
1252 subInitialize: function() {
\r
1254 * Whether or not we've attached a preview image
\r
1257 this.hasPreviewImg = false;
\r
1258 if( typeof(this.options.previewWrap) != 'undefined'
\r
1259 && this.options.minWidth > 0
\r
1260 && this.options.minHeight > 0
\r
1263 * The preview image wrapper element
\r
1264 * @var obj HTML element
\r
1266 this.previewWrap = $( this.options.previewWrap );
\r
1268 * The preview image element
\r
1269 * @var obj HTML IMG element
\r
1271 this.previewImg = this.img.cloneNode( false );
\r
1272 // set the ID of the preview image to be unique
\r
1273 this.previewImg.id = 'imgCrop_' + this.previewImg.id;
\r
1276 // set the displayOnInit option to true so we display the select area at the same time as the thumbnail
\r
1277 this.options.displayOnInit = true;
\r
1279 this.hasPreviewImg = true;
\r
1281 this.previewWrap.addClassName( 'imgCrop_previewWrap' );
\r
1283 this.previewWrap.setStyle(
\r
1285 width: this.options.minWidth + 'px',
\r
1286 height: this.options.minHeight + 'px'
\r
1290 this.previewWrap.appendChild( this.previewImg );
\r
1295 * Implements the abstract method from Cropper.Img to draw the preview image
\r
1297 * @see Croper.Img.subDrawArea
\r
1299 subDrawArea: function() {
\r
1300 if( this.hasPreviewImg ) {
\r
1301 // get the ratio of the select area to the src image
\r
1302 var calcWidth = this.calcW();
\r
1303 var calcHeight = this.calcH();
\r
1304 // ratios for the dimensions of the preview image
\r
1306 x: this.imgW / calcWidth,
\r
1307 y: this.imgH / calcHeight
\r
1309 //ratios for the positions within the preview
\r
1311 x: calcWidth / this.options.minWidth,
\r
1312 y: calcHeight / this.options.minHeight
\r
1315 // setting the positions in an obj before apply styles for rendering speed increase
\r
1317 w: Math.ceil( this.options.minWidth * dimRatio.x ) + 'px',
\r
1318 h: Math.ceil( this.options.minHeight * dimRatio.y ) + 'px',
\r
1319 x: '-' + Math.ceil( this.areaCoords.x1 / posRatio.x ) + 'px',
\r
1320 y: '-' + Math.ceil( this.areaCoords.y1 / posRatio.y ) + 'px'
\r
1323 var previewStyle = this.previewImg.style;
\r
1324 previewStyle.width = calcPos.w;
\r
1325 previewStyle.height = calcPos.h;
\r
1326 previewStyle.left = calcPos.x;
\r
1327 previewStyle.top = calcPos.y;
\r