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.