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