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
117 * Modified 2013/06/01 Zach P to change $() to $PR() for eliminating conflicts with jQuery
\r
121 * Extend the Draggable class to allow us to pass the rendering
\r
122 * down to the Cropper object.
\r
124 var CropDraggable = Class.create();
\r
126 Object.extend( Object.extend( CropDraggable.prototype, Draggable.prototype), {
\r
128 initialize: function(element) {
\r
129 this.options = Object.extend(
\r
132 * The draw method to defer drawing to
\r
134 drawMethod: function() {}
\r
139 this.element = $PR(element);
\r
141 this.handle = this.element;
\r
143 this.delta = this.currentDelta();
\r
144 this.dragging = false;
\r
146 this.eventMouseDown = this.initDrag.bindAsEventListener(this);
\r
147 Event.observe(this.handle, "mousedown", this.eventMouseDown);
\r
149 Draggables.register(this);
\r
153 * Defers the drawing of the draggable to the supplied method
\r
155 draw: function(point) {
\r
156 var pos = Position.cumulativeOffset(this.element);
\r
157 var d = this.currentDelta();
\r
161 var p = [0,1].map(function(i) {
\r
162 return (point[i]-pos[i]-this.offset[i])
\r
165 this.options.drawMethod( p );
\r
172 * The Cropper object, this will attach itself to the provided image by wrapping it with
\r
173 * the generated xHTML structure required by the cropper.
\r
176 * @param obj Image element to attach to
\r
177 * @param obj Optional options:
\r
179 * The pixel dimensions to apply as a restrictive ratio, with properties x & y
\r
182 * The minimum width for the select area in pixels
\r
185 * The mimimum height for the select area in pixels
\r
188 * 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
191 * 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
193 * - displayOnInit int
\r
194 * Whether to display the select area on initialisation, only used when providing minimum width & height or ratio
\r
197 * The callback function to provide the crop details to on end of a crop (see below)
\r
199 * - captureKeys boolean
\r
200 * Whether to capture the keys for moving the select area, as these can cause some problems at the moment
\r
202 * - onloadCoords obj
\r
203 * A coordinates object with properties x1, y1, x2 & y2; for the coordinates of the select area to display onload
\r
205 *----------------------------------------------
\r
207 * The callback function provided via the onEndCrop option should accept the following parameters:
\r
209 * The coordinates object with properties x1, y1, x2 & y2; for the coordinates of the select area
\r
212 * The dimensions object with properites width & height; for the dimensions of the select area
\r
216 * function onEndCrop( coords, dimensions ) {
\r
217 * $PR( 'x1' ).value = coords.x1;
\r
218 * $PR( 'y1' ).value = coords.y1;
\r
219 * $PR( 'x2' ).value = coords.x2;
\r
220 * $PR( 'y2' ).value = coords.y2;
\r
221 * $PR( 'width' ).value = dimensions.width;
\r
222 * $PR( 'height' ).value = dimensions.height;
\r
227 Cropper.Img = Class.create();
\r
228 Cropper.Img.prototype = {
\r
231 * Initialises the class
\r
234 * @param obj Image element to attach to
\r
235 * @param obj Options
\r
238 initialize: function(element, options) {
\r
239 this.options = Object.extend(
\r
243 * The pixel dimensions to apply as a restrictive ratio
\r
245 ratioDim: { x: 0, y: 0 },
\r
248 * The minimum pixel width, also used as restrictive ratio if min height passed too
\r
253 * The minimum pixel height, also used as restrictive ratio if min width passed too
\r
258 * Whether to display the select area on initialisation, only used when providing minimum width & height or ratio
\r
260 displayOnInit: false,
\r
263 * The call back function to pass the final values to
\r
265 onEndCrop: Prototype.emptyFunction,
\r
268 * Whether to capture key presses or not
\r
272 * @var obj Coordinate object x1, y1, x2, y2
\r
273 * The coordinates to optionally display the select area at onload
\r
275 onloadCoords: null,
\r
278 * 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
283 * 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
291 * The img node to attach to
\r
293 this.img = $PR( element );
\r
296 * The x & y coordinates of the click point
\r
298 this.clickCoords = { x: 0, y: 0 };
\r
301 * Whether the user is dragging
\r
303 this.dragging = false;
\r
306 * Whether the user is resizing
\r
308 this.resizing = false;
\r
311 * Whether the user is on a webKit browser
\r
313 this.isWebKit = /Konqueror|Safari|KHTML/.test( navigator.userAgent );
\r
316 * Whether the user is on IE
\r
318 this.isIE = /MSIE/.test( navigator.userAgent );
\r
321 * Whether the user is on Opera below version 9
\r
323 this.isOpera8 = /Opera\s[1-8]/.test( navigator.userAgent );
\r
336 * Whether we've attached sucessfully
\r
338 this.attached = false;
\r
341 * Whether we've got a fixed width (if minWidth EQ or GT maxWidth then we have a fixed width
\r
342 * in the case of minWidth > maxWidth maxWidth wins as the fixed width)
\r
344 this.fixedWidth = ( this.options.maxWidth > 0 && ( this.options.minWidth >= this.options.maxWidth ) );
\r
347 * Whether we've got a fixed height (if minHeight EQ or GT maxHeight then we have a fixed height
\r
348 * in the case of minHeight > maxHeight maxHeight wins as the fixed height)
\r
350 this.fixedHeight = ( this.options.maxHeight > 0 && ( this.options.minHeight >= this.options.maxHeight ) );
\r
352 // quit if the image element doesn't exist
\r
353 if( typeof this.img == 'undefined' ) return;
\r
355 // include the stylesheet
\r
356 $A( document.getElementsByTagName( 'script' ) ).each(
\r
358 if( s.src.match( /cropper\.js/ ) ) {
\r
359 var path = s.src.replace( /cropper\.js(.*)?/, '' );
\r
360 // '<link rel="stylesheet" type="text/css" href="' + path + 'cropper.css" media="screen" />';
\r
361 var style = document.createElement( 'link' );
\r
362 style.rel = 'stylesheet';
\r
363 style.type = 'text/css';
\r
364 style.href = path + 'cropper.css';
\r
365 style.media = 'screen';
\r
366 document.getElementsByTagName( 'head' )[0].appendChild( style );
\r
371 // calculate the ratio when neccessary
\r
372 if( this.options.ratioDim.x > 0 && this.options.ratioDim.y > 0 ) {
\r
373 var gcd = this.getGCD( this.options.ratioDim.x, this.options.ratioDim.y );
\r
374 this.ratioX = this.options.ratioDim.x / gcd;
\r
375 this.ratioY = this.options.ratioDim.y / gcd;
\r
376 // dump( 'RATIO : ' + this.ratioX + ':' + this.ratioY + '\n' );
\r
379 // initialise sub classes
\r
380 this.subInitialize();
\r
382 // only load the event observers etc. once the image is loaded
\r
383 // this is done after the subInitialize() call just in case the sub class does anything
\r
384 // that will affect the result of the call to onLoad()
\r
385 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
386 else Event.observe( this.img, 'load', this.onLoad.bindAsEventListener( this) );
\r
390 * The Euclidean algorithm used to find the greatest common divisor
\r
393 * @param int Value 1
\r
394 * @param int Value 2
\r
397 getGCD : function( a , b ) {
\r
398 if( b == 0 ) return a;
\r
399 return this.getGCD(b, a % b );
\r
403 * Attaches the cropper to the image once it has loaded
\r
408 onLoad: function( ) {
\r
410 * Build the container and all related elements, will result in the following
\r
412 * <div class="imgCrop_wrap">
\r
413 * <img ... this.img ... />
\r
414 * <div class="imgCrop_dragArea">
\r
415 * <!-- the inner spans are only required for IE to stop it making the divs 1px high/wide -->
\r
416 * <div class="imgCrop_overlay imageCrop_north"><span></span></div>
\r
417 * <div class="imgCrop_overlay imageCrop_east"><span></span></div>
\r
418 * <div class="imgCrop_overlay imageCrop_south"><span></span></div>
\r
419 * <div class="imgCrop_overlay imageCrop_west"><span></span></div>
\r
420 * <div class="imgCrop_selArea">
\r
421 * <!-- marquees -->
\r
422 * <!-- the inner spans are only required for IE to stop it making the divs 1px high/wide -->
\r
423 * <div class="imgCrop_marqueeHoriz imgCrop_marqueeNorth"><span></span></div>
\r
424 * <div class="imgCrop_marqueeVert imgCrop_marqueeEast"><span></span></div>
\r
425 * <div class="imgCrop_marqueeHoriz imgCrop_marqueeSouth"><span></span></div>
\r
426 * <div class="imgCrop_marqueeVert imgCrop_marqueeWest"><span></span></div>
\r
428 * <div class="imgCrop_handle imgCrop_handleN"></div>
\r
429 * <div class="imgCrop_handle imgCrop_handleNE"></div>
\r
430 * <div class="imgCrop_handle imgCrop_handleE"></div>
\r
431 * <div class="imgCrop_handle imgCrop_handleSE"></div>
\r
432 * <div class="imgCrop_handle imgCrop_handleS"></div>
\r
433 * <div class="imgCrop_handle imgCrop_handleSW"></div>
\r
434 * <div class="imgCrop_handle imgCrop_handleW"></div>
\r
435 * <div class="imgCrop_handle imgCrop_handleNW"></div>
\r
436 * <div class="imgCrop_clickArea"></div>
\r
438 * <div class="imgCrop_clickArea"></div>
\r
442 var cNamePrefix = 'imgCrop_';
\r
444 // get the point to insert the container
\r
445 var insertPoint = this.img.parentNode;
\r
447 // apply an extra class to the wrapper to fix Opera below version 9
\r
448 var fixOperaClass = '';
\r
449 if( this.isOpera8 ) fixOperaClass = ' opera8';
\r
450 this.imgWrap = Builder.node( 'div', { 'class': cNamePrefix + 'wrap' + fixOperaClass } );
\r
452 this.north = Builder.node( 'div', { 'class': cNamePrefix + 'overlay ' + cNamePrefix + 'north' }, [Builder.node( 'span' )] );
\r
453 this.east = Builder.node( 'div', { 'class': cNamePrefix + 'overlay ' + cNamePrefix + 'east' } , [Builder.node( 'span' )] );
\r
454 this.south = Builder.node( 'div', { 'class': cNamePrefix + 'overlay ' + cNamePrefix + 'south' }, [Builder.node( 'span' )] );
\r
455 this.west = Builder.node( 'div', { 'class': cNamePrefix + 'overlay ' + cNamePrefix + 'west' } , [Builder.node( 'span' )] );
\r
457 var overlays = [ this.north, this.east, this.south, this.west ];
\r
459 this.dragArea = Builder.node( 'div', { 'class': cNamePrefix + 'dragArea' }, overlays );
\r
461 this.handleN = Builder.node( 'div', { 'class': cNamePrefix + 'handle ' + cNamePrefix + 'handleN' } );
\r
462 this.handleNE = Builder.node( 'div', { 'class': cNamePrefix + 'handle ' + cNamePrefix + 'handleNE' } );
\r
463 this.handleE = Builder.node( 'div', { 'class': cNamePrefix + 'handle ' + cNamePrefix + 'handleE' } );
\r
464 this.handleSE = Builder.node( 'div', { 'class': cNamePrefix + 'handle ' + cNamePrefix + 'handleSE' } );
\r
465 this.handleS = Builder.node( 'div', { 'class': cNamePrefix + 'handle ' + cNamePrefix + 'handleS' } );
\r
466 this.handleSW = Builder.node( 'div', { 'class': cNamePrefix + 'handle ' + cNamePrefix + 'handleSW' } );
\r
467 this.handleW = Builder.node( 'div', { 'class': cNamePrefix + 'handle ' + cNamePrefix + 'handleW' } );
\r
468 this.handleNW = Builder.node( 'div', { 'class': cNamePrefix + 'handle ' + cNamePrefix + 'handleNW' } );
\r
470 this.selArea = Builder.node( 'div', { 'class': cNamePrefix + 'selArea' },
\r
472 Builder.node( 'div', { 'class': cNamePrefix + 'marqueeHoriz ' + cNamePrefix + 'marqueeNorth' }, [Builder.node( 'span' )] ),
\r
473 Builder.node( 'div', { 'class': cNamePrefix + 'marqueeVert ' + cNamePrefix + 'marqueeEast' } , [Builder.node( 'span' )] ),
\r
474 Builder.node( 'div', { 'class': cNamePrefix + 'marqueeHoriz ' + cNamePrefix + 'marqueeSouth' }, [Builder.node( 'span' )] ),
\r
475 Builder.node( 'div', { 'class': cNamePrefix + 'marqueeVert ' + cNamePrefix + 'marqueeWest' } , [Builder.node( 'span' )] ),
\r
484 Builder.node( 'div', { 'class': cNamePrefix + 'clickArea' } )
\r
488 this.imgWrap.appendChild( this.img );
\r
489 this.imgWrap.appendChild( this.dragArea );
\r
490 this.dragArea.appendChild( this.selArea );
\r
491 this.dragArea.appendChild( Builder.node( 'div', { 'class': cNamePrefix + 'clickArea' } ) );
\r
493 insertPoint.appendChild( this.imgWrap );
\r
495 // add event observers
\r
496 this.startDragBind = this.startDrag.bindAsEventListener( this );
\r
497 Event.observe( this.dragArea, 'mousedown', this.startDragBind );
\r
499 this.onDragBind = this.onDrag.bindAsEventListener( this );
\r
500 Event.observe( document, 'mousemove', this.onDragBind );
\r
502 this.endCropBind = this.endCrop.bindAsEventListener( this );
\r
503 Event.observe( document, 'mouseup', this.endCropBind );
\r
505 this.resizeBind = this.startResize.bindAsEventListener( this );
\r
506 this.handles = [ this.handleN, this.handleNE, this.handleE, this.handleSE, this.handleS, this.handleSW, this.handleW, this.handleNW ];
\r
507 this.registerHandles( true );
\r
509 if( this.options.captureKeys ) {
\r
510 this.keysBind = this.handleKeys.bindAsEventListener( this );
\r
511 Event.observe( document, 'keypress', this.keysBind );
\r
514 // attach the dragable to the select area
\r
515 new CropDraggable( this.selArea, { drawMethod: this.moveArea.bindAsEventListener( this ) } );
\r
521 * Manages adding or removing the handle event handler and hiding or displaying them as appropriate
\r
524 * @param boolean registration true = add, false = remove
\r
527 registerHandles: function( registration ) {
\r
528 for( var i = 0; i < this.handles.length; i++ ) {
\r
529 var handle = $PR( this.handles[i] );
\r
531 if( registration ) {
\r
532 var hideHandle = false; // whether to hide the handle
\r
534 // disable handles asappropriate if we've got fixed dimensions
\r
535 // if both dimensions are fixed we don't need to do much
\r
536 if( this.fixedWidth && this.fixedHeight ) hideHandle = true;
\r
537 else if( this.fixedWidth || this.fixedHeight ) {
\r
538 // if one of the dimensions is fixed then just hide those handles
\r
539 var isCornerHandle = handle.className.match( /([S|N][E|W])$/ )
\r
540 var isWidthHandle = handle.className.match( /(E|W)$/ );
\r
541 var isHeightHandle = handle.className.match( /(N|S)$/ );
\r
542 if( isCornerHandle ) hideHandle = true;
\r
543 else if( this.fixedWidth && isWidthHandle ) hideHandle = true;
\r
544 else if( this.fixedHeight && isHeightHandle ) hideHandle = true;
\r
546 if( hideHandle ) handle.hide();
\r
547 else Event.observe( handle, 'mousedown', this.resizeBind );
\r
550 Event.stopObserving( handle, 'mousedown', this.resizeBind );
\r
556 * Sets up all the cropper parameters, this can be used to reset the cropper when dynamically
\r
557 * changing the images
\r
562 setParams: function() {
\r
567 this.imgW = this.img.width;
\r
572 this.imgH = this.img.height;
\r
574 $PR( this.north ).setStyle( { height: 0 } );
\r
575 $PR( this.east ).setStyle( { width: 0, height: 0 } );
\r
576 $PR( this.south ).setStyle( { height: 0 } );
\r
577 $PR( this.west ).setStyle( { width: 0, height: 0 } );
\r
579 // resize the container to fit the image
\r
580 $PR( this.imgWrap ).setStyle( { 'width': this.imgW + 'px', 'height': this.imgH + 'px' } );
\r
582 // hide the select area
\r
583 $PR( this.selArea ).hide();
\r
585 // setup the starting position of the select area
\r
586 var startCoords = { x1: 0, y1: 0, x2: 0, y2: 0 };
\r
587 var validCoordsSet = false;
\r
589 // display the select area
\r
590 if( this.options.onloadCoords != null ) {
\r
591 // if we've being given some coordinates to
\r
592 startCoords = this.cloneCoords( this.options.onloadCoords );
\r
593 validCoordsSet = true;
\r
594 } else if( this.options.ratioDim.x > 0 && this.options.ratioDim.y > 0 ) {
\r
595 // if there is a ratio limit applied and the then set it to initial ratio
\r
596 startCoords.x1 = Math.ceil( ( this.imgW - this.options.ratioDim.x ) / 2 );
\r
597 startCoords.y1 = Math.ceil( ( this.imgH - this.options.ratioDim.y ) / 2 );
\r
598 startCoords.x2 = startCoords.x1 + this.options.ratioDim.x;
\r
599 startCoords.y2 = startCoords.y1 + this.options.ratioDim.y;
\r
600 validCoordsSet = true;
\r
603 this.setAreaCoords( startCoords, false, false, 1 );
\r
605 if( this.options.displayOnInit && validCoordsSet ) {
\r
606 this.selArea.show();
\r
611 this.attached = true;
\r
615 * Removes the cropper
\r
620 remove: function() {
\r
621 if( this.attached ) {
\r
622 this.attached = false;
\r
624 // remove the elements we inserted
\r
625 this.imgWrap.parentNode.insertBefore( this.img, this.imgWrap );
\r
626 this.imgWrap.parentNode.removeChild( this.imgWrap );
\r
628 // remove the event observers
\r
629 Event.stopObserving( this.dragArea, 'mousedown', this.startDragBind );
\r
630 Event.stopObserving( document, 'mousemove', this.onDragBind );
\r
631 Event.stopObserving( document, 'mouseup', this.endCropBind );
\r
632 this.registerHandles( false );
\r
633 if( this.options.captureKeys ) Event.stopObserving( document, 'keypress', this.keysBind );
\r
638 * Resets the cropper, can be used either after being removed or any time you wish
\r
643 reset: function() {
\r
644 if( !this.attached ) this.onLoad();
\r
645 else this.setParams();
\r
650 * Handles the key functionality, currently just using arrow keys to move, if the user
\r
651 * presses shift then the area will move by 10 pixels
\r
653 handleKeys: function( e ) {
\r
654 var dir = { x: 0, y: 0 }; // direction to move it in & the amount in pixels
\r
655 if( !this.dragging ) {
\r
657 // catch the arrow keys
\r
658 switch( e.keyCode ) {
\r
659 case( 37 ) : // left
\r
665 case( 39 ) : // right
\r
668 case( 40 ) : // down
\r
673 if( dir.x != 0 || dir.y != 0 ) {
\r
674 // if shift is pressed then move by 10 pixels
\r
680 this.moveArea( [ this.areaCoords.x1 + dir.x, this.areaCoords.y1 + dir.y ] );
\r
687 * Calculates the width from the areaCoords
\r
692 calcW: function() {
\r
693 return (this.areaCoords.x2 - this.areaCoords.x1)
\r
697 * Calculates the height from the areaCoords
\r
702 calcH: function() {
\r
703 return (this.areaCoords.y2 - this.areaCoords.y1)
\r
707 * Moves the select area to the supplied point (assumes the point is x1 & y1 of the select area)
\r
710 * @param array Point for x1 & y1 to move select area to
\r
713 moveArea: function( point ) {
\r
714 // dump( 'moveArea : ' + point[0] + ',' + point[1] + ',' + ( point[0] + ( this.areaCoords.x2 - this.areaCoords.x1 ) ) + ',' + ( point[1] + ( this.areaCoords.y2 - this.areaCoords.y1 ) ) + '\n' );
\r
715 this.setAreaCoords(
\r
719 x2: point[0] + this.calcW(),
\r
720 y2: point[1] + this.calcH()
\r
729 * Clones a co-ordinates object, stops problems with handling them by reference
\r
732 * @param obj Coordinate object x1, y1, x2, y2
\r
733 * @return obj Coordinate object x1, y1, x2, y2
\r
735 cloneCoords: function( coords ) {
\r
736 return { x1: coords.x1, y1: coords.y1, x2: coords.x2, y2: coords.y2 };
\r
740 * Sets the select coords to those provided but ensures they don't go
\r
741 * outside the bounding box
\r
744 * @param obj Coordinates x1, y1, x2, y2
\r
745 * @param boolean Whether this is a move
\r
746 * @param boolean Whether to apply squaring
\r
747 * @param obj Direction of mouse along both axis x, y ( -1 = negative, 1 = positive ) only required when moving etc.
\r
748 * @param string The current resize handle || null
\r
751 setAreaCoords: function( coords, moving, square, direction, resizeHandle ) {
\r
752 // dump( 'setAreaCoords (in) : ' + coords.x1 + ',' + coords.y1 + ',' + coords.x2 + ',' + coords.y2 );
\r
755 var targW = coords.x2 - coords.x1;
\r
756 var targH = coords.y2 - coords.y1;
\r
758 // ensure we're within the bounds
\r
759 if( coords.x1 < 0 ) {
\r
763 if( coords.y1 < 0 ) {
\r
767 if( coords.x2 > this.imgW ) {
\r
768 coords.x2 = this.imgW;
\r
769 coords.x1 = this.imgW - targW;
\r
771 if( coords.y2 > this.imgH ) {
\r
772 coords.y2 = this.imgH;
\r
773 coords.y1 = this.imgH - targH;
\r
776 // ensure we're within the bounds
\r
777 if( coords.x1 < 0 ) coords.x1 = 0;
\r
778 if( coords.y1 < 0 ) coords.y1 = 0;
\r
779 if( coords.x2 > this.imgW ) coords.x2 = this.imgW;
\r
780 if( coords.y2 > this.imgH ) coords.y2 = this.imgH;
\r
782 // This is passed as null in onload
\r
783 if( direction != null ) {
\r
785 // apply the ratio or squaring where appropriate
\r
786 if( this.ratioX > 0 ) this.applyRatio( coords, { x: this.ratioX, y: this.ratioY }, direction, resizeHandle );
\r
787 else if( square ) this.applyRatio( coords, { x: 1, y: 1 }, direction, resizeHandle );
\r
789 var mins = [ this.options.minWidth, this.options.minHeight ]; // minimum dimensions [x,y]
\r
790 var maxs = [ this.options.maxWidth, this.options.maxHeight ]; // maximum dimensions [x,y]
\r
792 // apply dimensions where appropriate
\r
793 if( mins[0] > 0 || mins[1] > 0 || maxs[0] > 0 || maxs[1] > 0) {
\r
795 var coordsTransX = { a1: coords.x1, a2: coords.x2 };
\r
796 var coordsTransY = { a1: coords.y1, a2: coords.y2 };
\r
797 var boundsX = { min: 0, max: this.imgW };
\r
798 var boundsY = { min: 0, max: this.imgH };
\r
800 // handle squaring properly on single axis minimum dimensions
\r
801 if( (mins[0] != 0 || mins[1] != 0) && square ) {
\r
802 if( mins[0] > 0 ) mins[1] = mins[0];
\r
803 else if( mins[1] > 0 ) mins[0] = mins[1];
\r
806 if( (maxs[0] != 0 || maxs[0] != 0) && square ) {
\r
807 // 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
808 if( maxs[0] > 0 && maxs[0] <= maxs[1] ) maxs[1] = maxs[0];
\r
809 else if( maxs[1] > 0 && maxs[1] <= maxs[0] ) maxs[0] = maxs[1];
\r
812 if( mins[0] > 0 ) this.applyDimRestriction( coordsTransX, mins[0], direction.x, boundsX, 'min' );
\r
813 if( mins[1] > 1 ) this.applyDimRestriction( coordsTransY, mins[1], direction.y, boundsY, 'min' );
\r
815 if( maxs[0] > 0 ) this.applyDimRestriction( coordsTransX, maxs[0], direction.x, boundsX, 'max' );
\r
816 if( maxs[1] > 1 ) this.applyDimRestriction( coordsTransY, maxs[1], direction.y, boundsY, 'max' );
\r
818 coords = { x1: coordsTransX.a1, y1: coordsTransY.a1, x2: coordsTransX.a2, y2: coordsTransY.a2 };
\r
824 // dump( 'setAreaCoords (out) : ' + coords.x1 + ',' + coords.y1 + ',' + coords.x2 + ',' + coords.y2 + '\n' );
\r
825 this.areaCoords = coords;
\r
829 * Applies the supplied dimension restriction to the supplied coordinates along a single axis
\r
832 * @param obj Single axis coordinates, a1, a2 (e.g. for the x axis a1 = x1 & a2 = x2)
\r
833 * @param int The restriction value
\r
834 * @param int The direction ( -1 = negative, 1 = positive )
\r
835 * @param obj The bounds of the image ( for this axis )
\r
836 * @param string The dimension restriction type ( 'min' | 'max' )
\r
839 applyDimRestriction: function( coords, val, direction, bounds, type ) {
\r
841 if( type == 'min' ) check = ( ( coords.a2 - coords.a1 ) < val );
\r
842 else check = ( ( coords.a2 - coords.a1 ) > val );
\r
844 if( direction == 1 ) coords.a2 = coords.a1 + val;
\r
845 else coords.a1 = coords.a2 - val;
\r
847 // make sure we're still in the bounds (not too pretty for the user, but needed)
\r
848 if( coords.a1 < bounds.min ) {
\r
849 coords.a1 = bounds.min;
\r
851 } else if( coords.a2 > bounds.max ) {
\r
852 coords.a1 = bounds.max - val;
\r
853 coords.a2 = bounds.max;
\r
859 * Applies the supplied ratio to the supplied coordinates
\r
862 * @param obj Coordinates, x1, y1, x2, y2
\r
863 * @param obj Ratio, x, y
\r
864 * @param obj Direction of mouse, x & y : -1 == negative 1 == positive
\r
865 * @param string The current resize handle || null
\r
868 applyRatio : function( coords, ratio, direction, resizeHandle ) {
\r
869 // dump( 'direction.y : ' + direction.y + '\n');
\r
871 if( resizeHandle == 'N' || resizeHandle == 'S' ) {
\r
872 // dump( 'north south \n');
\r
873 // if moving on either the lone north & south handles apply the ratio on the y axis
\r
874 newCoords = this.applyRatioToAxis(
\r
875 { a1: coords.y1, b1: coords.x1, a2: coords.y2, b2: coords.x2 },
\r
876 { a: ratio.y, b: ratio.x },
\r
877 { a: direction.y, b: direction.x },
\r
878 { min: 0, max: this.imgW }
\r
880 coords.x1 = newCoords.b1;
\r
881 coords.y1 = newCoords.a1;
\r
882 coords.x2 = newCoords.b2;
\r
883 coords.y2 = newCoords.a2;
\r
885 // otherwise deal with it as if we're applying the ratio on the x axis
\r
886 newCoords = this.applyRatioToAxis(
\r
887 { a1: coords.x1, b1: coords.y1, a2: coords.x2, b2: coords.y2 },
\r
888 { a: ratio.x, b: ratio.y },
\r
889 { a: direction.x, b: direction.y },
\r
890 { min: 0, max: this.imgH }
\r
892 coords.x1 = newCoords.a1;
\r
893 coords.y1 = newCoords.b1;
\r
894 coords.x2 = newCoords.a2;
\r
895 coords.y2 = newCoords.b2;
\r
901 * Applies the provided ratio to the provided coordinates based on provided direction & bounds,
\r
902 * use to encapsulate functionality to make it easy to apply to either axis. This is probably
\r
903 * quite hard to visualise so see the x axis example within applyRatio()
\r
905 * Example in parameter details & comments is for requesting applying ratio to x axis.
\r
908 * @param obj Coords object (a1, b1, a2, b2) where a = x & b = y in example
\r
909 * @param obj Ratio object (a, b) where a = x & b = y in example
\r
910 * @param obj Direction object (a, b) where a = x & b = y in example
\r
911 * @param obj Bounds (min, max)
\r
912 * @return obj Coords object (a1, b1, a2, b2) where a = x & b = y in example
\r
914 applyRatioToAxis: function( coords, ratio, direction, bounds ) {
\r
915 var newCoords = Object.extend( coords, {} );
\r
916 var calcDimA = newCoords.a2 - newCoords.a1; // calculate dimension a (e.g. width)
\r
917 var targDimB = Math.floor( calcDimA * ratio.b / ratio.a ); // the target dimension b (e.g. height)
\r
918 var targB; // to hold target b (e.g. y value)
\r
919 var targDimA; // to hold target dimension a (e.g. width)
\r
920 var calcDimB = null; // to hold calculated dimension b (e.g. height)
\r
922 // dump( 'newCoords[0]: ' + newCoords.a1 + ',' + newCoords.b1 + ','+ newCoords.a2 + ',' + newCoords.b2 + '\n');
\r
924 if( direction.b == 1 ) { // if travelling in a positive direction
\r
925 // make sure we're not going out of bounds
\r
926 targB = newCoords.b1 + targDimB;
\r
927 if( targB > bounds.max ) {
\r
928 targB = bounds.max;
\r
929 calcDimB = targB - newCoords.b1; // calcuate dimension b (e.g. height)
\r
932 newCoords.b2 = targB;
\r
933 } else { // if travelling in a negative direction
\r
934 // make sure we're not going out of bounds
\r
935 targB = newCoords.b2 - targDimB;
\r
936 if( targB < bounds.min ) {
\r
937 targB = bounds.min;
\r
938 calcDimB = targB + newCoords.b2; // calcuate dimension b (e.g. height)
\r
940 newCoords.b1 = targB;
\r
943 // dump( 'newCoords[1]: ' + newCoords.a1 + ',' + newCoords.b1 + ','+ newCoords.a2 + ',' + newCoords.b2 + '\n');
\r
945 // apply the calculated dimensions
\r
946 if( calcDimB != null ) {
\r
947 targDimA = Math.floor( calcDimB * ratio.a / ratio.b );
\r
949 if( direction.a == 1 ) newCoords.a2 = newCoords.a1 + targDimA;
\r
950 else newCoords.a1 = newCoords.a1 = newCoords.a2 - targDimA;
\r
953 // dump( 'newCoords[2]: ' + newCoords.a1 + ',' + newCoords.b1 + ','+ newCoords.a2 + ',' + newCoords.b2 + '\n');
\r
959 * Draws the select area
\r
964 drawArea: function( ) {
\r
966 * NOTE: I'm not using the Element.setStyle() shortcut as they make it
\r
967 * quite sluggish on Mac based browsers
\r
969 // dump( 'drawArea : ' + this.areaCoords.x1 + ',' + this.areaCoords.y1 + ',' + this.areaCoords.x2 + ',' + this.areaCoords.y2 + '\n' );
\r
970 var areaWidth = this.calcW();
\r
971 var areaHeight = this.calcH();
\r
974 * Calculate all the style strings before we use them, allows reuse & produces quicker
\r
975 * rendering (especially noticable in Mac based browsers)
\r
979 this.areaCoords.x1 + px, // the left of the selArea
\r
980 this.areaCoords.y1 + px, // the top of the selArea
\r
981 areaWidth + px, // width of the selArea
\r
982 areaHeight + px, // height of the selArea
\r
983 this.areaCoords.x2 + px, // bottom of the selArea
\r
984 this.areaCoords.y2 + px, // right of the selArea
\r
985 (this.img.width - this.areaCoords.x2) + px, // right edge of selArea
\r
986 (this.img.height - this.areaCoords.y2) + px // bottom edge of selArea
\r
989 // do the select area
\r
990 var areaStyle = this.selArea.style;
\r
991 areaStyle.left = params[0];
\r
992 areaStyle.top = params[1];
\r
993 areaStyle.width = params[2];
\r
994 areaStyle.height = params[3];
\r
996 // position the north, east, south & west handles
\r
997 var horizHandlePos = Math.ceil( (areaWidth - 6) / 2 ) + px;
\r
998 var vertHandlePos = Math.ceil( (areaHeight - 6) / 2 ) + px;
\r
1000 this.handleN.style.left = horizHandlePos;
\r
1001 this.handleE.style.top = vertHandlePos;
\r
1002 this.handleS.style.left = horizHandlePos;
\r
1003 this.handleW.style.top = vertHandlePos;
\r
1005 // draw the four overlays
\r
1006 this.north.style.height = params[1];
\r
1008 var eastStyle = this.east.style;
\r
1009 eastStyle.top = params[1];
\r
1010 eastStyle.height = params[3];
\r
1011 eastStyle.left = params[4];
\r
1012 eastStyle.width = params[6];
\r
1014 var southStyle = this.south.style;
\r
1015 southStyle.top = params[5];
\r
1016 southStyle.height = params[7];
\r
1018 var westStyle = this.west.style;
\r
1019 westStyle.top = params[1];
\r
1020 westStyle.height = params[3];
\r
1021 westStyle.width = params[0];
\r
1023 // call the draw method on sub classes
\r
1024 this.subDrawArea();
\r
1026 this.forceReRender();
\r
1030 * Force the re-rendering of the selArea element which fixes rendering issues in Safari
\r
1031 * & IE PC, especially evident when re-sizing perfectly vertical using any of the south handles
\r
1036 forceReRender: function() {
\r
1037 if( this.isIE || this.isWebKit) {
\r
1038 var n = document.createTextNode(' ');
\r
1041 if( this.isIE ) fixEl = this.selArea;
\r
1042 else if( this.isWebKit ) {
\r
1043 fixEl = document.getElementsByClassName( 'imgCrop_marqueeSouth', this.imgWrap )[0];
\r
1044 /* we have to be a bit more forceful for Safari, otherwise the the marquee &
\r
1045 * the south handles still don't move
\r
1047 d = Builder.node( 'div', '' );
\r
1048 d.style.visibility = 'hidden';
\r
1050 var classList = ['SE','S','SW'];
\r
1051 for( i = 0; i < classList.length; i++ ) {
\r
1052 el = document.getElementsByClassName( 'imgCrop_handle' + classList[i], this.selArea )[0];
\r
1053 if( el.childNodes.length ) el.removeChild( el.childNodes[0] );
\r
1054 el.appendChild(d);
\r
1057 fixEl.appendChild(n);
\r
1058 fixEl.removeChild(n);
\r
1063 * Starts the resize
\r
1066 * @param obj Event
\r
1069 startResize: function( e ) {
\r
1070 this.startCoords = this.cloneCoords( this.areaCoords );
\r
1072 this.resizing = true;
\r
1073 this.resizeHandle = Event.element( e ).classNames().toString().replace(/([^N|NE|E|SE|S|SW|W|NW])+/, '');
\r
1074 // dump( 'this.resizeHandle : ' + this.resizeHandle + '\n' );
\r
1082 * @param obj Event
\r
1085 startDrag: function( e ) {
\r
1086 this.selArea.show();
\r
1087 this.clickCoords = this.getCurPos( e );
\r
1089 this.setAreaCoords( { x1: this.clickCoords.x, y1: this.clickCoords.y, x2: this.clickCoords.x, y2: this.clickCoords.y }, false, false, null );
\r
1091 this.dragging = true;
\r
1092 this.onDrag( e ); // incase the user just clicks once after already making a selection
\r
1097 * Gets the current cursor position relative to the image
\r
1100 * @param obj Event
\r
1101 * @return obj x,y pixels of the cursor
\r
1103 getCurPos: function( e ) {
\r
1104 // get the offsets for the wrapper within the document
\r
1105 var el = this.imgWrap, wrapOffsets = Position.cumulativeOffset( el );
\r
1106 // 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
1107 while( el.nodeName != 'BODY' ) {
\r
1108 wrapOffsets[1] -= el.scrollTop || 0;
\r
1109 wrapOffsets[0] -= el.scrollLeft || 0;
\r
1110 el = el.parentNode;
\r
1112 return curPos = {
\r
1113 x: Event.pointerX(e) - wrapOffsets[0],
\r
1114 y: Event.pointerY(e) - wrapOffsets[1]
\r
1119 * Performs the drag for both resize & inital draw dragging
\r
1122 * @param obj Event
\r
1125 onDrag: function( e ) {
\r
1126 if( this.dragging || this.resizing ) {
\r
1128 var resizeHandle = null;
\r
1129 var curPos = this.getCurPos( e );
\r
1130 var newCoords = this.cloneCoords( this.areaCoords );
\r
1131 var direction = { x: 1, y: 1 };
\r
1133 if( this.dragging ) {
\r
1134 if( curPos.x < this.clickCoords.x ) direction.x = -1;
\r
1135 if( curPos.y < this.clickCoords.y ) direction.y = -1;
\r
1137 this.transformCoords( curPos.x, this.clickCoords.x, newCoords, 'x' );
\r
1138 this.transformCoords( curPos.y, this.clickCoords.y, newCoords, 'y' );
\r
1139 } else if( this.resizing ) {
\r
1140 resizeHandle = this.resizeHandle;
\r
1141 // do x movements first
\r
1142 if( resizeHandle.match(/E/) ) {
\r
1143 // if we're moving an east handle
\r
1144 this.transformCoords( curPos.x, this.startCoords.x1, newCoords, 'x' );
\r
1145 if( curPos.x < this.startCoords.x1 ) direction.x = -1;
\r
1146 } else if( resizeHandle.match(/W/) ) {
\r
1147 // if we're moving an west handle
\r
1148 this.transformCoords( curPos.x, this.startCoords.x2, newCoords, 'x' );
\r
1149 if( curPos.x < this.startCoords.x2 ) direction.x = -1;
\r
1152 // do y movements second
\r
1153 if( resizeHandle.match(/N/) ) {
\r
1154 // if we're moving an north handle
\r
1155 this.transformCoords( curPos.y, this.startCoords.y2, newCoords, 'y' );
\r
1156 if( curPos.y < this.startCoords.y2 ) direction.y = -1;
\r
1157 } else if( resizeHandle.match(/S/) ) {
\r
1158 // if we're moving an south handle
\r
1159 this.transformCoords( curPos.y, this.startCoords.y1, newCoords, 'y' );
\r
1160 if( curPos.y < this.startCoords.y1 ) direction.y = -1;
\r
1165 this.setAreaCoords( newCoords, false, e.shiftKey, direction, resizeHandle );
\r
1167 Event.stop( e ); // stop the default event (selecting images & text) in Safari & IE PC
\r
1172 * Applies the appropriate transform to supplied co-ordinates, on the
\r
1173 * defined axis, depending on the relationship of the supplied values
\r
1176 * @param int Current value of pointer
\r
1177 * @param int Base value to compare current pointer val to
\r
1178 * @param obj Coordinates to apply transformation on x1, x2, y1, y2
\r
1179 * @param string Axis to apply transformation on 'x' || 'y'
\r
1182 transformCoords : function( curVal, baseVal, coords, axis ) {
\r
1183 var newVals = [ curVal, baseVal ];
\r
1184 if( curVal > baseVal ) newVals.reverse();
\r
1185 coords[ axis + '1' ] = newVals[0];
\r
1186 coords[ axis + '2' ] = newVals[1];
\r
1190 * Ends the crop & passes the values of the select area on to the appropriate
\r
1191 * callback function on completion of a crop
\r
1196 endCrop : function() {
\r
1197 this.dragging = false;
\r
1198 this.resizing = false;
\r
1200 this.options.onEndCrop(
\r
1203 width: this.calcW(),
\r
1204 height: this.calcH()
\r
1210 * Abstract method called on the end of initialization
\r
1216 subInitialize: function() {},
\r
1219 * Abstract method called on the end of drawArea()
\r
1225 subDrawArea: function() {}
\r
1232 * Extend the Cropper.Img class to allow for presentation of a preview image of the resulting crop,
\r
1233 * the option for displayOnInit is always overridden to true when displaying a preview image
\r
1236 * @param obj Image element to attach to
\r
1237 * @param obj Optional options:
\r
1238 * - see Cropper.Img for base options
\r
1240 * - previewWrap obj
\r
1241 * HTML element that will be used as a container for the preview image
\r
1243 Cropper.ImgWithPreview = Class.create();
\r
1245 Object.extend( Object.extend( Cropper.ImgWithPreview.prototype, Cropper.Img.prototype ), {
\r
1248 * Implements the abstract method from Cropper.Img to initialize preview image settings.
\r
1249 * Will only attach a preview image is the previewWrap element is defined and the minWidth
\r
1250 * & minHeight options are set.
\r
1252 * @see Croper.Img.subInitialize
\r
1254 subInitialize: function() {
\r
1256 * Whether or not we've attached a preview image
\r
1259 this.hasPreviewImg = false;
\r
1260 if( typeof(this.options.previewWrap) != 'undefined'
\r
1261 && this.options.minWidth > 0
\r
1262 && this.options.minHeight > 0
\r
1265 * The preview image wrapper element
\r
1266 * @var obj HTML element
\r
1268 this.previewWrap = $PR( this.options.previewWrap );
\r
1270 * The preview image element
\r
1271 * @var obj HTML IMG element
\r
1273 this.previewImg = this.img.cloneNode( false );
\r
1274 // set the ID of the preview image to be unique
\r
1275 this.previewImg.id = 'imgCrop_' + this.previewImg.id;
\r
1278 // set the displayOnInit option to true so we display the select area at the same time as the thumbnail
\r
1279 this.options.displayOnInit = true;
\r
1281 this.hasPreviewImg = true;
\r
1283 this.previewWrap.addClassName( 'imgCrop_previewWrap' );
\r
1285 this.previewWrap.setStyle(
\r
1287 width: this.options.minWidth + 'px',
\r
1288 height: this.options.minHeight + 'px'
\r
1292 this.previewWrap.appendChild( this.previewImg );
\r
1297 * Implements the abstract method from Cropper.Img to draw the preview image
\r
1299 * @see Croper.Img.subDrawArea
\r
1301 subDrawArea: function() {
\r
1302 if( this.hasPreviewImg ) {
\r
1303 // get the ratio of the select area to the src image
\r
1304 var calcWidth = this.calcW();
\r
1305 var calcHeight = this.calcH();
\r
1306 // ratios for the dimensions of the preview image
\r
1308 x: this.imgW / calcWidth,
\r
1309 y: this.imgH / calcHeight
\r
1311 //ratios for the positions within the preview
\r
1313 x: calcWidth / this.options.minWidth,
\r
1314 y: calcHeight / this.options.minHeight
\r
1317 // setting the positions in an obj before apply styles for rendering speed increase
\r
1319 w: Math.ceil( this.options.minWidth * dimRatio.x ) + 'px',
\r
1320 h: Math.ceil( this.options.minHeight * dimRatio.y ) + 'px',
\r
1321 x: '-' + Math.ceil( this.areaCoords.x1 / posRatio.x ) + 'px',
\r
1322 y: '-' + Math.ceil( this.areaCoords.y1 / posRatio.y ) + 'px'
\r
1325 var previewStyle = this.previewImg.style;
\r
1326 previewStyle.width = calcPos.w;
\r
1327 previewStyle.height = calcPos.h;
\r
1328 previewStyle.left = calcPos.x;
\r
1329 previewStyle.top = calcPos.y;
\r