1/*! 2 * uiCropper v1.0.9 3 * https://crackerakiua.github.io/ui-cropper/ 4 * 5 * Copyright (c) 2019 Alex Kaul 6 * License: MIT 7 * 8 * Generated at Monday, May 20th, 2019, 10:17:03 PM 9 */ 10(function() { 11angular.module('uiCropper', []); 12 13angular.module('uiCropper').factory('cropAreaCircle', ['cropArea', function(CropArea) { 14 var CropAreaCircle = function() { 15 CropArea.apply(this, arguments); 16 17 this._boxResizeBaseSize = 25; 18 this._boxResizeNormalRatio = 1; 19 this._boxResizeHoverRatio = 1.2; 20 this._iconMoveNormalRatio = 0.9; 21 this._iconMoveHoverRatio = 1.2; 22 23 this._boxResizeNormalSize = this._boxResizeBaseSize * this._boxResizeNormalRatio; 24 this._boxResizeHoverSize = this._boxResizeBaseSize * this._boxResizeHoverRatio; 25 26 this._posDragStartX = 0; 27 this._posDragStartY = 0; 28 this._posResizeStartX = 0; 29 this._posResizeStartY = 0; 30 this._posResizeStartSize = 0; 31 32 this._boxResizeIsHover = false; 33 this._areaIsHover = false; 34 this._boxResizeIsDragging = false; 35 this._areaIsDragging = false; 36 }; 37 38 CropAreaCircle.prototype = new CropArea(); 39 40 CropAreaCircle.prototype.getType = function() { 41 return 'circle'; 42 }; 43 44 CropAreaCircle.prototype._calcCirclePerimeterCoords = function(angleDegrees) { 45 var hSize = this._size.w / 2; 46 var angleRadians = angleDegrees * (Math.PI / 180), 47 circlePerimeterX = this.getCenterPoint().x + hSize * Math.cos(angleRadians), 48 circlePerimeterY = this.getCenterPoint().y + hSize * Math.sin(angleRadians); 49 return [circlePerimeterX, circlePerimeterY]; 50 }; 51 52 CropAreaCircle.prototype._calcResizeIconCenterCoords = function() { 53 return this._calcCirclePerimeterCoords(-45); 54 }; 55 56 CropAreaCircle.prototype._isCoordWithinArea = function(coord) { 57 return Math.sqrt((coord[0] - this.getCenterPoint().x) * (coord[0] - this.getCenterPoint().x) + (coord[1] - this.getCenterPoint().y) * (coord[1] - this.getCenterPoint().y)) < this._size.w / 2; 58 }; 59 CropAreaCircle.prototype._isCoordWithinBoxResize = function(coord) { 60 var resizeIconCenterCoords = this._calcResizeIconCenterCoords(); 61 var hSize = this._boxResizeHoverSize / 2; 62 return (coord[0] > resizeIconCenterCoords[0] - hSize && coord[0] < resizeIconCenterCoords[0] + hSize && 63 coord[1] > resizeIconCenterCoords[1] - hSize && coord[1] < resizeIconCenterCoords[1] + hSize); 64 }; 65 66 CropAreaCircle.prototype._drawArea = function(ctx, centerCoords, size) { 67 ctx.arc(centerCoords.x, centerCoords.y, size.w / 2, 0, 2 * Math.PI); 68 }; 69 70 CropAreaCircle.prototype.draw = function() { 71 CropArea.prototype.draw.apply(this, arguments); 72 73 // draw move icon 74 var center = this.getCenterPoint(); 75 this._cropCanvas.drawIconMove([center.x, center.y], this._areaIsHover ? this._iconMoveHoverRatio : this._iconMoveNormalRatio); 76 77 // draw resize cubes 78 this._cropCanvas.drawIconResizeBoxNESW(this._calcResizeIconCenterCoords(), this._boxResizeBaseSize, this._boxResizeIsHover ? this._boxResizeHoverRatio : this._boxResizeNormalRatio); 79 }; 80 81 CropAreaCircle.prototype.setSizeByScale = function(scale) { 82 var center = this.getCenterPoint(); 83 var size = this.getSize(); 84 var newRadius = size.w * scale / 2; 85 var northWestPoint = { 86 x: center.x - newRadius, 87 y: center.y - newRadius 88 }; 89 var southEastPoint = { 90 x: center.x + newRadius, 91 y: center.y + newRadius 92 }; 93 this.circleOnMove(northWestPoint, southEastPoint); 94 this._events.trigger('area-resize'); 95 }; 96 97 CropAreaCircle.prototype.processMouseMove = function(mouseCurX, mouseCurY) { 98 var cursor = 'default'; 99 var res = false; 100 101 this._boxResizeIsHover = false; 102 this._areaIsHover = false; 103 104 if (this._areaIsDragging) { 105 this.setCenterPointOnMove({ 106 x: mouseCurX - this._posDragStartX, 107 y: mouseCurY - this._posDragStartY 108 }); 109 this._areaIsHover = true; 110 cursor = 'move'; 111 res = true; 112 this._events.trigger('area-move'); 113 } else if (this._boxResizeIsDragging) { 114 cursor = 'nesw-resize'; 115 var iFR, iFX, iFY; 116 iFX = mouseCurX - this._posResizeStartX; 117 iFY = this._posResizeStartY - mouseCurY; 118 if (iFX > iFY) { 119 iFR = this._posResizeStartSize.w + iFY * 2; 120 } else { 121 iFR = this._posResizeStartSize.w + iFX * 2; 122 } 123 124 var newNO = {}, 125 newSE = {}; 126 127 newNO.x = this.getCenterPoint().x - iFR * 0.5; 128 newSE.x = this.getCenterPoint().x + iFR * 0.5; 129 130 newNO.y = this.getCenterPoint().y - iFR * 0.5; 131 newSE.y = this.getCenterPoint().y + iFR * 0.5; 132 133 this.circleOnMove(newNO, newSE); 134 this._boxResizeIsHover = true; 135 res = true; 136 this._events.trigger('area-resize'); 137 } else if (this._isCoordWithinBoxResize([mouseCurX, mouseCurY])) { 138 cursor = 'nesw-resize'; 139 this._areaIsHover = false; 140 this._boxResizeIsHover = true; 141 res = true; 142 } else if (this._isCoordWithinArea([mouseCurX, mouseCurY])) { 143 cursor = 'move'; 144 this._areaIsHover = true; 145 res = true; 146 } 147 148 //this._dontDragOutside(); 149 angular.element(this._ctx.canvas).css({ 150 'cursor': cursor 151 }); 152 153 return res; 154 }; 155 156 CropAreaCircle.prototype.processMouseDown = function(mouseDownX, mouseDownY) { 157 if (this._isCoordWithinBoxResize([mouseDownX, mouseDownY])) { 158 this._areaIsDragging = false; 159 this._areaIsHover = false; 160 this._boxResizeIsDragging = true; 161 this._boxResizeIsHover = true; 162 this._posResizeStartX = mouseDownX; 163 this._posResizeStartY = mouseDownY; 164 this._posResizeStartSize = this._size; 165 this._events.trigger('area-resize-start'); 166 } else if (this._isCoordWithinArea([mouseDownX, mouseDownY])) { 167 this._areaIsDragging = true; 168 this._areaIsHover = true; 169 this._boxResizeIsDragging = false; 170 this._boxResizeIsHover = false; 171 var center = this.getCenterPoint(); 172 this._posDragStartX = mouseDownX - center.x; 173 this._posDragStartY = mouseDownY - center.y; 174 this._events.trigger('area-move-start'); 175 } 176 }; 177 178 CropAreaCircle.prototype.processMouseUp = function( /*mouseUpX, mouseUpY*/ ) { 179 if (this._areaIsDragging) { 180 this._areaIsDragging = false; 181 this._events.trigger('area-move-end'); 182 } 183 if (this._boxResizeIsDragging) { 184 this._boxResizeIsDragging = false; 185 this._events.trigger('area-resize-end'); 186 } 187 this._areaIsHover = false; 188 this._boxResizeIsHover = false; 189 190 this._posDragStartX = 0; 191 this._posDragStartY = 0; 192 }; 193 194 return CropAreaCircle; 195}]); 196 197angular.module('uiCropper').factory('cropAreaRectangle', ['cropArea', function (CropArea) { 198 var CropAreaRectangle = function () { 199 CropArea.apply(this, arguments); 200 201 this._resizeCtrlBaseRadius = 15; 202 this._resizeCtrlNormalRatio = 0.6; 203 this._resizeCtrlHoverRatio = 0.70; 204 this._iconMoveNormalRatio = 0.9; 205 this._iconMoveHoverRatio = 1.2; 206 207 this._resizeCtrlNormalRadius = this._resizeCtrlBaseRadius * this._resizeCtrlNormalRatio; 208 this._resizeCtrlHoverRadius = this._resizeCtrlBaseRadius * this._resizeCtrlHoverRatio; 209 210 this._posDragStartX = 0; 211 this._posDragStartY = 0; 212 this._posResizeStartX = 0; 213 this._posResizeStartY = 0; 214 this._posResizeStartSize = { 215 w: 0, 216 h: 0 217 }; 218 219 this._resizeCtrlIsHover = -1; 220 this._areaIsHover = false; 221 this._resizeCtrlIsDragging = -1; 222 this._areaIsDragging = false; 223 }; 224 225 CropAreaRectangle.prototype = new CropArea(); 226 227 // return a type string 228 CropAreaRectangle.prototype.getType = function () { 229 return 'rectangle'; 230 }; 231 232 CropAreaRectangle.prototype._calcRectangleCorners = function () {
233 var size = this.getSize(); 234 var se = this.getSouthEastBound(); 235 return [ 236 [size.x, size.y], //northwest 237 [se.x, size.y], //northeast 238 [size.x, se.y], //southwest 239 [se.x, se.y] //southeast 240 ]; 241 }; 242 243 CropAreaRectangle.prototype._calcRectangleDimensions = function () { 244 var size = this.getSize(); 245 var se = this.getSouthEastBound(); 246 return { 247 left: size.x, 248 top: size.y, 249 right: se.x, 250 bottom: se.y 251 }; 252 }; 253 254 CropAreaRectangle.prototype._isCoordWithinArea = function (coord) { 255 var rectangleDimensions = this._calcRectangleDimensions(); 256 return (coord[0] >= rectangleDimensions.left && coord[0] <= rectangleDimensions.right && coord[1] >= rectangleDimensions.top && coord[1] <= rectangleDimensions.bottom); 257 }; 258 259 CropAreaRectangle.prototype._isCoordWithinResizeCtrl = function (coord) { 260 var resizeIconsCenterCoords = this._calcRectangleCorners(); 261 var res = -1; 262 for (var i = 0, len = resizeIconsCenterCoords.length; i < len; i++) { 263 var resizeIconCenterCoords = resizeIconsCenterCoords[i]; 264 if (coord[0] > resizeIconCenterCoords[0] - this._resizeCtrlHoverRadius && coord[0] < resizeIconCenterCoords[0] + this._resizeCtrlHoverRadius && 265 coord[1] > resizeIconCenterCoords[1] - this._resizeCtrlHoverRadius && coord[1] < resizeIconCenterCoords[1] + this._resizeCtrlHoverRadius) { 266 res = i; 267 break; 268 } 269 } 270 return res; 271 }; 272 273 CropAreaRectangle.prototype._drawArea = function (ctx, center, size) { 274 ctx.rect(size.x, size.y, size.w, size.h); 275 }; 276 277 CropAreaRectangle.prototype.draw = function () { 278 CropArea.prototype.draw.apply(this, arguments); 279 280 var center = this.getCenterPoint(); 281 // draw move icon 282 this._cropCanvas.drawIconMove([center.x, center.y], this._areaIsHover ? this._iconMoveHoverRatio : this._iconMoveNormalRatio); 283 284 // draw resize thumbs 285 var resizeIconsCenterCoords = this._calcRectangleCorners(); 286 for (var i = 0, len = resizeIconsCenterCoords.length; i < len; i++) { 287 var resizeIconCenterCoords = resizeIconsCenterCoords[i]; 288 this._cropCanvas.drawIconResizeBoxBase(resizeIconCenterCoords, this._resizeCtrlBaseRadius, this._resizeCtrlIsHover === i ? this._resizeCtrlHoverRatio : this._resizeCtrlNormalRatio); 289 } 290 }; 291 292 CropAreaRectangle.prototype.setSizeByScale = function(scale, direction) { 293 var center = this.getCenterPoint(); 294 var size = this.getSize(); 295 var northWestPoint; 296 var southEastPoint; 297 298 if (this._aspect) { 299 var newWidth = size.w * scale; 300 var newHeight = size.h * scale; 301 northWestPoint = { 302 x: center.x - (newWidth / 2), 303 y: center.y - (newHeight / 2) 304 }; 305 southEastPoint = { 306 x: center.x + (newWidth / 2), 307 y: center.y + (newHeight / 2) 308 }; 309 } else { 310 northWestPoint = { 311 x: size.x, 312 y: size.y 313 }; 314 southEastPoint = { 315 x: size.x + size.w, 316 y: size.y + size.h 317 }; 318 switch (direction) { 319 case 'up': 320 case 'down': 321 southEastPoint.y = size.y + (size.h * scale); 322 break; 323 case 'left': 324 case 'right': 325 southEastPoint.x = size.x + (size.w * scale); 326 break; 327 } 328 } 329 330 this.setSizeByCorners(northWestPoint, southEastPoint); 331 this._events.trigger('area-resize'); 332 }; 333 334 CropAreaRectangle.prototype.processMouseMove = function (mouseCurX, mouseCurY) { 335 var cursor = 'default'; 336 var res = false; 337 338 this._resizeCtrlIsHover = -1; 339 this._areaIsHover = false; 340 341 if (this._areaIsDragging) { 342 this.setCenterPointOnMove({ 343 x: mouseCurX - this._posDragStartX, 344 y: mouseCurY - this._posDragStartY 345 }); 346 this._areaIsHover = true; 347 cursor = 'move'; 348 res = true; 349 this._events.trigger('area-move'); 350 } else if (this._resizeCtrlIsDragging > -1) {
351 var s = this.getSize(); 352 var se = this.getSouthEastBound(); 353 var posX = mouseCurX; 354 switch (this._resizeCtrlIsDragging) { 355 case 0: // Top Left 356 if (this._aspect) { 357 posX = se.x - ((se.y - mouseCurY) * this._aspect); 358 } 359 this.setSizeByCorners({ 360 x: posX, 361 y: mouseCurY 362 }, { 363 x: se.x, 364 y: se.y 365 }); 366 cursor = 'nwse-resize'; 367 break; 368 case 1: // Top Right 369 if (this._aspect) { 370 posX = s.x + ((se.y - mouseCurY) * this._aspect); 371 } 372 this.setSizeByCorners({ 373 x: s.x, 374 y: mouseCurY 375 }, { 376 x: posX, 377 y: se.y 378 }); 379 cursor = 'nesw-resize'; 380 break; 381 case 2: // Bottom Left 382 if (this._aspect) { 383 posX = se.x - ((mouseCurY - s.y) * this._aspect); 384 } 385 this.setSizeByCorners({ 386 x: posX, 387 y: s.y 388 }, { 389 x: se.x, 390 y: mouseCurY 391 }); 392 cursor = 'nesw-resize'; 393 break; 394 case 3: // Bottom Right 395 if (this._aspect) { 396 posX = s.x + ((mouseCurY - s.y) * this._aspect); 397 } 398 this.setSizeByCorners({ 399 x: s.x, 400 y: s.y 401 }, { 402 x: posX, 403 y: mouseCurY 404 }); 405 cursor = 'nwse-resize'; 406 break; 407 } 408 409 this._resizeCtrlIsHover = this._resizeCtrlIsDragging; 410 res = true; 411 this._events.trigger('area-resize'); 412 } else { 413 var hoveredResizeBox = this._isCoordWithinResizeCtrl([mouseCurX, mouseCurY]); 414 if (hoveredResizeBox > -1) { 415 switch (hoveredResizeBox) { 416 case 0: 417 cursor = 'nwse-resize'; 418 break; 419 case 1: 420 cursor = 'nesw-resize'; 421 break; 422 case 2: 423 cursor = 'nesw-resize'; 424 break; 425 case 3: 426 cursor = 'nwse-resize'; 427 break; 428 } 429 this._areaIsHover = false; 430 this._resizeCtrlIsHover = hoveredResizeBox; 431 res = true; 432 } else if (this._isCoordWithinArea([mouseCurX, mouseCurY])) { 433 cursor = 'move'; 434 this._areaIsHover = true; 435 res = true; 436 } 437 } 438 439 angular.element(this._ctx.canvas).css({ 440 'cursor': cursor 441 }); 442 443 return res; 444 }; 445 446 CropAreaRectangle.prototype.processMouseDown = function (mouseDownX, mouseDownY) { 447 var isWithinResizeCtrl = this._isCoordWithinResizeCtrl([mouseDownX, mouseDownY]); 448 if (isWithinResizeCtrl > -1) { 449 this._areaIsDragging = false; 450 this._areaIsHover = false; 451 this._resizeCtrlIsDragging = isWithinResizeCtrl; 452 this._resizeCtrlIsHover = isWithinResizeCtrl; 453 this._posResizeStartX = mouseDownX; 454 this._posResizeStartY = mouseDownY; 455 this._posResizeStartSize = this._size; 456 this._events.trigger('area-resize-start'); 457 } else if (this._isCoordWithinArea([mouseDownX, mouseDownY])) { 458 this._areaIsDragging = true; 459 this._areaIsHover = true; 460 this._resizeCtrlIsDragging = -1; 461 this._resizeCtrlIsHover = -1; 462 var center = this.getCenterPoint(); 463 this._posDragStartX = mouseDownX - center.x; 464 this._posDragStartY = mouseDownY - center.y; 465 this._events.trigger('area-move-start'); 466 } 467 }; 468 469 CropAreaRectangle.prototype.processMouseUp = function (/*mouseUpX, mouseUpY*/) { 470 if (this._areaIsDragging) { 471 this._areaIsDragging = false; 472 this._events.trigger('area-move-end'); 473 } 474 if (this._resizeCtrlIsDragging > -1) { 475 this._resizeCtrlIsDragging = -1; 476 this._events.trigger('area-resize-end'); 477 } 478 this._areaIsHover = false; 479 this._resizeCtrlIsHover = -1; 480 481 this._posDragStartX = 0; 482 this._posDragStartY = 0; 483 }; 484 485 return CropAreaRectangle; 486}]); 487 488angular.module('uiCropper').factory('cropAreaSquare', ['cropArea', function(CropArea) { 489 var CropAreaSquare = function() { 490 CropArea.apply(this, arguments); 491 492 this._resizeCtrlBaseRadius = 15; 493 this._resizeCtrlNormalRatio = 0.6; 494 this._resizeCtrlHoverRatio = 0.70; 495 this._iconMoveNormalRatio = 0.9; 496 this._iconMoveHoverRatio = 1.2; 497 498 this._resizeCtrlNormalRadius = this._resizeCtrlBaseRadius * this._resizeCtrlNormalRatio; 499 this._resizeCtrlHoverRadius = this._resizeCtrlBaseRadius * this._resizeCtrlHoverRatio; 500 501 this._posDragStartX = 0; 502 this._posDragStartY = 0; 503 this._posResizeStartX = 0; 504 this._posResizeStartY = 0; 505 this._posResizeStartSize = 0; 506 507 this._resizeCtrlIsHover = -1; 508 this._areaIsHover = false; 509 this._resizeCtrlIsDragging = -1; 510 this._areaIsDragging = false; 511 }; 512 513 CropAreaSquare.prototype = new CropArea(); 514 515 CropAreaSquare.prototype.getType = function() { 516 return 'square'; 517 }; 518 519 CropAreaSquare.prototype._calcSquareCorners = function() {
520 var size = this.getSize(), 521 se = this.getSouthEastBound(); 522 return [ 523 [size.x, size.y], //northwest 524 [se.x, size.y], //northeast 525 [size.x, se.y], //southwest 526 [se.x, se.y] //southeast 527 ]; 528 }; 529 530 CropAreaSquare.prototype._calcSquareDimensions = function() { 531 var size = this.getSize(), 532 se = this.getSouthEastBound(); 533 return { 534 left: size.x, 535 top: size.y, 536 right: se.x, 537 bottom: se.y 538 }; 539 }; 540 541 CropAreaSquare.prototype._isCoordWithinArea = function(coord) { 542 var squareDimensions = this._calcSquareDimensions(); 543 return (coord[0] >= squareDimensions.left && coord[0] <= squareDimensions.right && coord[1] >= squareDimensions.top && coord[1] <= squareDimensions.bottom); 544 }; 545 546 CropAreaSquare.prototype._isCoordWithinResizeCtrl = function(coord) { 547 var resizeIconsCenterCoords = this._calcSquareCorners(); 548 var res = -1; 549 for (var i = 0, len = resizeIconsCenterCoords.length; i < len; i++) { 550 var resizeIconCenterCoords = resizeIconsCenterCoords[i]; 551 if (coord[0] > resizeIconCenterCoords[0] - this._resizeCtrlHoverRadius && coord[0] < resizeIconCenterCoords[0] + this._resizeCtrlHoverRadius && 552 coord[1] > resizeIconCenterCoords[1] - this._resizeCtrlHoverRadius && coord[1] < resizeIconCenterCoords[1] + this._resizeCtrlHoverRadius) { 553 res = i; 554 break; 555 } 556 } 557 return res; 558 }; 559 560 CropAreaSquare.prototype._drawArea = function(ctx, centerCoords, size) { 561 ctx.rect(size.x, size.y, size.w, size.h); 562 }; 563 564 CropAreaSquare.prototype.draw = function() { 565 CropArea.prototype.draw.apply(this, arguments); 566 567 // draw move icon 568 var center = this.getCenterPoint(); 569 this._cropCanvas.drawIconMove([center.x, center.y], this._areaIsHover ? this._iconMoveHoverRatio : this._iconMoveNormalRatio); 570 571 // draw resize cubes 572 var resizeIconsCenterCoords = this._calcSquareCorners(); 573 for (var i = 0, len = resizeIconsCenterCoords.length; i < len; i++) { 574 var resizeIconCenterCoords = resizeIconsCenterCoords[i]; 575 this._cropCanvas.drawIconResizeBoxBase(resizeIconCenterCoords, this._resizeCtrlBaseRadius, this._resizeCtrlIsHover === i ? this._resizeCtrlHoverRatio : this._resizeCtrlNormalRatio); 576 } 577 }; 578 579 CropAreaSquare.prototype._clampPoint = function(x, y) { 580 var size = this._ctx.canvas.width; 581 582 if(x < 0) { 583 y -= Math.abs(x); 584 x = 0; 585 } 586 587 if(y < 0) { 588 x -= Math.abs(y); 589 y = 0; 590 } 591 592 if(x > size) { 593 y -= (size - x); 594 x = size; 595 } 596 597 if(y > size) { 598 x -= (size - y); 599 y = size; 600 } 601 602 return { 603 x: x, 604 y: y 605 }; 606 }; 607 608 CropAreaSquare.prototype.setSizeByScale = function(scale) { 609 var center = this.getCenterPoint(); 610 var size = this.getSize(); 611 var newSize = size.w * scale; 612 if (newSize < this._minSize.w) { 613 newSize = this._minSize.w; 614 } 615 var northWestPoint = { 616 x: center.x - (newSize / 2), 617 y: center.y - (newSize / 2) 618 }; 619 var southEastPoint = { 620 x: center.x + (newSize / 2), 621 y: center.y + (newSize / 2) 622 }; 623 this.setSizeByCorners(northWestPoint, southEastPoint); 624 this._events.trigger('area-resize'); 625 }; 626 627 CropAreaSquare.prototype.processMouseMove = function(mouseCurX, mouseCurY) { 628 var cursor = 'default'; 629 var res = false; 630 631 this._resizeCtrlIsHover = -1; 632 this._areaIsHover = false; 633 634 if (this._areaIsDragging) { 635 this.setCenterPointOnMove({ 636 x: mouseCurX - this._posDragStartX, 637 y: mouseCurY - this._posDragStartY 638 }); 639 this._areaIsHover = true; 640 cursor = 'move'; 641 res = true; 642 this._events.trigger('area-move'); 643 } else if (this._resizeCtrlIsDragging > -1) { 644 var xMulti, yMulti; 645 switch (this._resizeCtrlIsDragging) { 646 case 0: // Top Left 647 xMulti = -1; 648 yMulti = -1; 649 cursor = 'nwse-resize'; 650 break; 651 case 1: // Top Right 652 xMulti = 1; 653 yMulti = -1; 654 cursor = 'nesw-resize'; 655 break; 656 case 2: // Bottom Left 657 xMulti = -1; 658 yMulti = 1; 659 cursor = 'nesw-resize'; 660 break; 661 case 3: // Bottom Right 662 xMulti = 1; 663 yMulti = 1; 664 cursor = 'nwse-resize'; 665 break; 666 } 667 var iFX = (mouseCurX - this._posResizeStartX) * xMulti, 668 iFY = (mouseCurY - this._posResizeStartY) * yMulti, 669 iFR; 670 if (iFX > iFY) { 671 iFR = this._posResizeStartSize.w + iFY; 672 } else { 673 iFR = this._posResizeStartSize.w + iFX; 674 } 675 var newSize = Math.max(this._minSize.w, iFR), 676 newNO = {}, 677 newSE = {}, 678 newSO = {}, 679 newNE = {},
680 s = this.getSize(), 681 se = this.getSouthEastBound(); 682 683 switch (this._resizeCtrlIsDragging) { 684 case 0: // Top Left 685 newNO.x = se.x - newSize; 686 newNO.y = se.y - newSize; 687 688 newNO = this._clampPoint(newNO.x, newNO.y); 689 690 this.setSizeByCorners(newNO, { 691 x: se.x, 692 y: se.y 693 }); 694 695 cursor = 'nwse-resize'; 696 break; 697 case 1: // Top Right 698 699 newNE.x = s.x + newSize; 700 newNE.y = se.y - newSize; 701 702 newNE = this._clampPoint(newNE.x, newNE.y); 703 704 this.setSizeByCorners({ 705 x: s.x, 706 y: newNE.y 707 }, { 708 x: newNE.x, 709 y: se.y 710 }); 711 712 cursor = 'nesw-resize'; 713 break; 714 case 2: // Bottom Left 715 newSO.x = se.x - newSize; 716 newSO.y = s.y + newSize; 717 718 newSO = this._clampPoint(newSO.x, newSO.y); 719 720 this.setSizeByCorners({ 721 x: newSO.x, 722 y: s.y 723 }, { 724 x: se.x, 725 y: newSO.y 726 }); 727 728 cursor = 'nesw-resize'; 729 break; 730 case 3: // Bottom Right 731 732 newSE.x = s.x + newSize; 733 newSE.y = s.y + newSize; 734 735 newSE = this._clampPoint(newSE.x, newSE.y); 736 737 this.setSizeByCorners({ 738 x: s.x, 739 y: s.y 740 }, newSE); 741 742 cursor = 'nwse-resize'; 743 break; 744 } 745 this._resizeCtrlIsHover = this._resizeCtrlIsDragging; 746 res = true; 747 this._events.trigger('area-resize'); 748 } else { 749 var hoveredResizeBox = this._isCoordWithinResizeCtrl([mouseCurX, mouseCurY]); 750 if (hoveredResizeBox > -1) { 751 switch (hoveredResizeBox) { 752 case 0: 753 cursor = 'nwse-resize'; 754 break; 755 case 1: 756 cursor = 'nesw-resize'; 757 break; 758 case 2: 759 cursor = 'nesw-resize'; 760 break; 761 case 3: 762 cursor = 'nwse-resize'; 763 break; 764 } 765 this._areaIsHover = false; 766 this._resizeCtrlIsHover = hoveredResizeBox; 767 res = true; 768 } else if (this._isCoordWithinArea([mouseCurX, mouseCurY])) { 769 cursor = 'move'; 770 this._areaIsHover = true; 771 res = true; 772 } 773 } 774 775 angular.element(this._ctx.canvas).css({ 776 'cursor': cursor 777 }); 778 779 return res; 780 }; 781 782 CropAreaSquare.prototype.processMouseDown = function(mouseDownX, mouseDownY) { 783 var isWithinResizeCtrl = this._isCoordWithinResizeCtrl([mouseDownX, mouseDownY]); 784 if (isWithinResizeCtrl > -1) { 785 this._areaIsDragging = false; 786 this._areaIsHover = false; 787 this._resizeCtrlIsDragging = isWithinResizeCtrl; 788 this._resizeCtrlIsHover = isWithinResizeCtrl; 789 this._posResizeStartX = mouseDownX; 790 this._posResizeStartY = mouseDownY; 791 this._posResizeStartSize = this._size; 792 this._events.trigger('area-resize-start'); 793 } else if (this._isCoordWithinArea([mouseDownX, mouseDownY])) { 794 this._areaIsDragging = true; 795 this._areaIsHover = true; 796 this._resizeCtrlIsDragging = -1; 797 this._resizeCtrlIsHover = -1; 798 var center = this.getCenterPoint(); 799 this._posDragStartX = mouseDownX - center.x; 800 this._posDragStartY = mouseDownY - center.y; 801 this._events.trigger('area-move-start'); 802 } 803 }; 804 805 CropAreaSquare.prototype.processMouseUp = function( /*mouseUpX, mouseUpY*/ ) { 806 if (this._areaIsDragging) { 807 this._areaIsDragging = false; 808 this._events.trigger('area-move-end'); 809 } 810 if (this._resizeCtrlIsDragging > -1) { 811 this._resizeCtrlIsDragging = -1; 812 this._events.trigger('area-resize-end'); 813 } 814 this._areaIsHover = false; 815 this._resizeCtrlIsHover = -1; 816 817 this._posDragStartX = 0; 818 this._posDragStartY = 0; 819 }; 820 821 return CropAreaSquare; 822}]); 823 824angular.module('uiCropper').factory('cropArea', ['cropCanvas', function (CropCanvas) { 825 var CropArea = function (ctx, events) { 826 this._ctx = ctx; 827 this._events = events; 828 829 this._minSize = { 830 x: 0, 831 y: 0, 832 w: 80, 833 h: 80 834 }; 835 836 this._initSize = undefined; 837 this._initCoords = undefined; 838 this._allowCropResizeOnCorners = false; 839 840 this._forceAspectRatio = false; 841 this._aspect = null; 842 this._disableCrop = false; 843 844 this._cropCanvas = new CropCanvas(ctx, this._disableCrop); 845 846 this._image = new Image(); 847 this._size = { 848 x: 0, 849 y: 0, 850 w: 150, 851 h: 150 852 }; 853 }; 854 855 /* GETTERS/SETTERS */ 856 857 CropArea.prototype.setAllowCropResizeOnCorners = function (bool) { 858 this._allowCropResizeOnCorners = bool; 859 }; 860 CropArea.prototype.getImage = function () { 861 return this._image; 862 }; 863 CropArea.prototype.setImage = function (image) { 864 this._image = image; 865 }; 866 CropArea.prototype.setForceAspectRatio = function (force) { 867 this._forceAspectRatio = force; 868 }; 869 CropArea.prototype.setAspect = function (aspect) { 870 this._aspect = aspect; 871 }; 872 CropArea.prototype.getAspect = function () { 873 return this._aspect; 874 }; 875 CropArea.prototype.getCanvasSize = function () { 876 return { 877 w: this._ctx.canvas.width, 878 h: this._ctx.canvas.height 879 }; 880 }; 881 CropArea.prototype.getSize = function () { 882 return this._size; 883 }; 884 CropArea.prototype.setSize = function (size) { 885 size = this._processSize(size); 886 this._size = this._preventBoundaryCollision(size); 887 }; 888 CropArea.prototype.setSizeOnMove = function (size) { 889 size = this._processSize(size); 890 if (this._allowCropResizeOnCorners) { 891 this._size = this._preventBoundaryCollision(size); 892 } else { 893 this._size = this._allowMouseOutsideCanvas(size); 894 } 895 }; 896 CropArea.prototype.circleOnMove = function (northWestCorner, southEastCorner) { 897 var size = { 898 x: northWestCorner.x, 899 y: northWestCorner.y, 900 w: southEastCorner.x - northWestCorner.x, 901 h: southEastCorner.y - northWestCorner.y 902 }; 903 var canvasH = this._ctx.canvas.height, 904 canvasW = this._ctx.canvas.width; 905 if (size.w > canvasW || size.h > canvasH) { 906 if (canvasW < canvasH) { 907 size.w = canvasW; 908 size.h = canvasW; 909 } else { 910 size.w = canvasH; 911 size.h = canvasH; 912 } 913 } 914 if (size.x + size.w > canvasW) { 915 size.x = canvasW - size.w; 916 } 917 if (size.y + size.h > canvasH) { 918 size.y = canvasH - size.h; 919 } 920 if (size.x < 0) { 921 size.x = 0; 922 } 923 if (size.y < 0) { 924 size.y = 0; 925 } 926 if (this._minSize.w > size.w) { 927 size.w = this._minSize.w; 928 size.x = this._size.x; 929 } 930 if (this._minSize.h > size.h) { 931 size.h = this._minSize.h; 932 size.y = this._size.y; 933 } 934 this._size = size; 935 }; 936 937 CropArea.prototype.setSizeByCorners = function (northWestCorner, southEastCorner) { 938 939 var size = { 940 x: northWestCorner.x, 941 y: northWestCorner.y, 942 w: southEastCorner.x - northWestCorner.x, 943 h: southEastCorner.y - northWestCorner.y 944 }; 945 this.setSize(size); 946 }; 947 948 CropArea.prototype.getSouthEastBound = function () { 949 return this._southEastBound(this.getSize()); 950 }; 951 952 CropArea.prototype.setMinSize = function (size) { 953 this._minSize = this._processSize(size); 954 this.setSize(this._minSize); 955 }; 956 957 CropArea.prototype.getMinSize = function () { 958 return this._minSize; 959 }; 960 961 CropArea.prototype.getCenterPoint = function () {
962 var s = this.getSize(); 963 return { 964 x: s.x + (s.w / 2), 965 y: s.y + (s.h / 2) 966 }; 967 }; 968 969 CropArea.prototype.setCenterPoint = function (point) { 970 var s = this.getSize(); 971 this.setSize({ 972 x: point.x - s.w / 2, 973 y: point.y - s.h / 2, 974 w: s.w, 975 h: s.h 976 }); 977 }; 978 979 CropArea.prototype.setCenterPointOnMove = function (point) { 980 var s = this.getSize(); 981 this.setSizeOnMove({ 982 x: point.x - s.w / 2, 983 y: point.y - s.h / 2, 984 w: s.w, 985 h: s.h 986 }); 987 }; 988 989 CropArea.prototype.setInitSize = function (size) { 990 this._initSize = this._processSize(size); 991 this.setSize(this._initSize); 992 }; 993 994 CropArea.prototype.getInitSize = function () { 995 return this._initSize; 996 }; 997 998 CropArea.prototype.setInitCoords = function (coords) { 999 //add h/w-data to coords-object 1000 coords.h = this.getSize().h; 1001 coords.w = this.getSize().w; 1002 this._initCoords = this._processSize(coords); 1003 this.setSize(this._initCoords); 1004 }; 1005 1006 CropArea.prototype.setDisableCrop = function(value){ 1007 this._disableCrop = value; 1008 this._cropCanvas = new CropCanvas(this._ctx, this._disableCrop); 1009 }; 1010 1011 CropArea.prototype.getInitCoords = function () { 1012 return this._initCoords; 1013 }; 1014 1015 // return a type string 1016 CropArea.prototype.getType = function () { 1017 //default to circle 1018 return 'circle'; 1019 }; 1020 1021 /* FUNCTIONS */ 1022 CropArea.prototype._allowMouseOutsideCanvas = function (size) { 1023 var canvasH = this._ctx.canvas.height, 1024 canvasW = this._ctx.canvas.width; 1025 var newSize = { 1026 w: size.w, 1027 h: size.h, 1028 }; 1029 if (size.x < 0) { 1030 newSize.x = 0; 1031 } 1032 else if (size.x + size.w > canvasW) { 1033 newSize.x = canvasW - size.w; 1034 } 1035 else { 1036 newSize.x = size.x; 1037 } 1038 if (size.y < 0) { 1039 newSize.y = 0; 1040 } 1041 else if (size.y + size.h > canvasH) { 1042 newSize.y = canvasH - size.h; 1043 } 1044 else { 1045 newSize.y = size.y; 1046 } 1047 return newSize; 1048 }; 1049 1050 CropArea.prototype._preventBoundaryCollision = function (size) { 1051 var canvasH = this._ctx.canvas.height, 1052 canvasW = this._ctx.canvas.width; 1053 1054 var nw = { 1055 x: size.x, 1056 y: size.y 1057 }; 1058 var se = this._southEastBound(size); 1059 1060 // check northwest corner 1061 if (nw.x < 0) { 1062 nw.x = 0; 1063 } 1064 if (nw.y < 0) { 1065 nw.y = 0; 1066 } 1067 1068 // check southeast corner 1069 if (se.x > canvasW) { 1070 se.x = canvasW; 1071 } 1072 if (se.y > canvasH) { 1073 se.y = canvasH; 1074 } 1075 1076 var newSizeWidth = (this._forceAspectRatio) ? size.w : se.x - nw.x, 1077 newSizeHeight = (this._forceAspectRatio) ? size.h : se.y - nw.y; 1078 1079 if (newSizeHeight > canvasH) { 1080 newSizeHeight = canvasH; 1081 } 1082 1083 // save rectangle scale 1084 if (this._aspect) { 1085 newSizeWidth = newSizeHeight * this._aspect; 1086 if (nw.x + newSizeWidth > canvasW) { 1087 newSizeWidth = canvasW - nw.x; 1088 newSizeHeight = newSizeWidth / this._aspect; 1089 if (this._minSize.w > newSizeWidth) { 1090 newSizeWidth = this._minSize.w; 1091 } 1092 if (this._minSize.h > newSizeHeight) { 1093 newSizeHeight = this._minSize.h; 1094 } 1095 nw.x = canvasW - newSizeWidth; 1096 } 1097 if (nw.y + newSizeHeight > canvasH) { 1098 nw.y = canvasH - newSizeHeight; 1099 } 1100 } 1101 1102 // save square scale 1103 if (this._forceAspectRatio) { 1104 newSizeWidth = newSizeHeight; 1105 if (nw.x + newSizeWidth > canvasW) { 1106 newSizeWidth = canvasW - nw.x; 1107 if (newSizeWidth < this._minSize.w) { 1108 newSizeWidth = this._minSize.w; 1109 } 1110 newSizeHeight = newSizeWidth; 1111 } 1112 } 1113 1114 var newSize = { 1115 x: nw.x, 1116 y: nw.y, 1117 w: newSizeWidth, 1118 h: newSizeHeight 1119 }; 1120 1121 //check size (if < min, adjust nw corner) 1122 if ((newSize.w < this._minSize.w) && !this._forceAspectRatio) { 1123 newSize.w = this._minSize.w; 1124 se = this._southEastBound(newSize); 1125 //adjust se corner, if it's out of bounds 1126 if (se.x > canvasW) {
1127 se.x = canvasW; 1128 //adjust nw corner according to min width 1129 nw.x = Math.max(se.x - canvasW, se.x - this._minSize.w); 1130 newSize = { 1131 x: nw.x, 1132 y: nw.y, 1133 w: se.x - nw.x, 1134 h: se.y - nw.y 1135 }; 1136 } 1137 } 1138 1139 if ((newSize.h < this._minSize.h) && !this._forceAspectRatio) { 1140 newSize.h = this._minSize.h; 1141 se = this._southEastBound(newSize); 1142 1143 if (se.y > canvasH) { 1144 se.y = canvasH; 1145 //adjust nw corner according to min height 1146 nw.y = Math.max(se.y - canvasH, se.y - this._minSize.h); 1147 newSize = { 1148 x: nw.x, 1149 y: nw.y, 1150 w: se.x - nw.x, 1151 h: se.y - nw.y 1152 }; 1153 } 1154 } 1155 1156 if (this._forceAspectRatio) { 1157 //check if outside SE bound 1158 se = this._southEastBound(newSize); 1159 if (se.y > canvasH) { 1160 newSize.y = canvasH - newSize.h; 1161 } 1162 if (se.x > canvasW) { 1163 newSize.x = canvasW - newSize.w; 1164 } 1165 } 1166 1167 return newSize; 1168 }; 1169 1170 CropArea.prototype._dontDragOutside = function () { 1171 var h = this._ctx.canvas.height, 1172 w = this._ctx.canvas.width; 1173 1174 if (this._width > w) { 1175 this._width = w; 1176 } 1177 if (this._height > h) { 1178 this._height = h; 1179 } 1180 if (this._x < this._width / 2) { 1181 this._x = this._width / 2; 1182 } 1183 if (this._x > w - this._width / 2) { 1184 this._x = w - this._width / 2; 1185 } 1186 if (this._y < this._height / 2) { 1187 this._y = this._height / 2; 1188 } 1189 if (this._y > h - this._height / 2) { 1190 this._y = h - this._height / 2; 1191 } 1192 }; 1193 1194 CropArea.prototype._drawArea = function () { 1195 }; 1196 1197 CropArea.prototype._processSize = function (size) { 1198 // make this polymorphic to accept a single floating point number 1199 // for square-like sizes (including circle) 1200 if (typeof size === 'number') { 1201 size = { 1202 w: size, 1203 h: size 1204 }; 1205 } 1206 var width = size.w; 1207 var height = size.h; 1208 if (this._aspect) { 1209 // In order to apply the initMax from the crop-host we need to update the width only when the aspect ratio is above 1. 1210 if (this.aspect >= 1) { 1211 width = size.h * this._aspect; 1212 } else { 1213 height = size.w / this._aspect; 1214 } 1215 } 1216 return { 1217 x: (typeof size.x === 'undefined') ? this.getSize().x : size.x, 1218 y: (typeof size.y === 'undefined') ? this.getSize().y : size.y, 1219 w: width || this._minSize.w, 1220 h: height || this._minSize.h 1221 }; 1222 }; 1223 1224 CropArea.prototype._southEastBound = function (size) { 1225 return { 1226 x: size.x + size.w, 1227 y: size.y + size.h 1228 }; 1229 }; 1230 1231 CropArea.prototype.draw = function () { 1232 // draw crop area 1233 this._cropCanvas.drawCropArea(this._image, this.getCenterPoint(), this._size, this._drawArea); 1234 }; 1235 1236 CropArea.prototype.processMouseMove = function () { 1237 }; 1238 1239 CropArea.prototype.processMouseDown = function () { 1240 }; 1241 1242 CropArea.prototype.processMouseUp = function () { 1243 }; 1244 1245 return CropArea; 1246}]); 1247 1248angular.module('uiCropper').factory('cropCanvas', [function() { 1249 // Shape = Array of [x,y]; [0, 0] - center 1250 var shapeArrowNW = [ 1251 [-0.5, -2], 1252 [-3, -4.5], 1253 [-0.5, -7], 1254 [-7, -7], 1255 [-7, -0.5], 1256 [-4.5, -3], 1257 [-2, -0.5] 1258 ]; 1259 var shapeArrowNE = [ 1260 [0.5, -2], 1261 [3, -4.5], 1262 [0.5, -7], 1263 [7, -7], 1264 [7, -0.5], 1265 [4.5, -3], 1266 [2, -0.5] 1267 ]; 1268 var shapeArrowSW = [ 1269 [-0.5, 2], 1270 [-3, 4.5], 1271 [-0.5, 7], 1272 [-7, 7], 1273 [-7, 0.5], 1274 [-4.5, 3], 1275 [-2, 0.5] 1276 ]; 1277 var shapeArrowSE = [ 1278 [0.5, 2], 1279 [3, 4.5], 1280 [0.5, 7], 1281 [7, 7], 1282 [7, 0.5], 1283 [4.5, 3], 1284 [2, 0.5] 1285 ]; 1286 var shapeArrowN = [ 1287 [-1.5, -2.5], 1288 [-1.5, -6], 1289 [-5, -6], 1290 [0, -11], 1291 [5, -6], 1292 [1.5, -6], 1293 [1.5, -2.5] 1294 ]; 1295 var shapeArrowW = [ 1296 [-2.5, -1.5], 1297 [-6, -1.5], 1298 [-6, -5], 1299 [-11, 0], 1300 [-6, 5], 1301 [-6, 1.5], 1302 [-2.5, 1.5] 1303 ]; 1304 var shapeArrowS = [ 1305 [-1.5, 2.5], 1306 [-1.5, 6], 1307 [-5, 6], 1308 [0, 11], 1309 [5, 6], 1310 [1.5, 6], 1311 [1.5, 2.5] 1312 ]; 1313 var shapeArrowE = [ 1314 [2.5, -1.5], 1315 [6, -1.5], 1316 [6, -5], 1317 [11, 0], 1318 [6, 5], 1319 [6, 1.5], 1320 [2.5, 1.5] 1321 ]; 1322 1323 // Colors 1324 var colors = { 1325 areaOutline: '#fff', 1326 resizeBoxStroke: '#bababa', 1327 resizeBoxFill: '#444', 1328 resizeBoxArrowFill: '#fff', 1329 resizeCircleStroke: '#bababa', 1330 resizeCircleFill: '#444', 1331 moveIconFill: '#fff' 1332 }; 1333 1334 var cropper = { 1335 strokeWidth: 1 1336 }; 1337 1338 return function(ctx, disable) { 1339 1340 /* Base functions */ 1341 1342 // Calculate Point 1343 var calcPoint = function(point, offset, scale) { 1344 return [scale * point[0] + offset[0], scale * point[1] + offset[1]]; 1345 }; 1346 1347 // Draw Filled Polygon 1348 var drawFilledPolygon = function(shape, fillStyle, centerCoords, scale) { 1349 if(disable) { 1350 return; 1351 } 1352 1353 ctx.save(); 1354 ctx.fillStyle = fillStyle; 1355 ctx.beginPath(); 1356 var pc, pc0 = calcPoint(shape[0], centerCoords, scale); 1357 ctx.moveTo(pc0[0], pc0[1]); 1358 1359 for (var p in shape) { 1360 if (p > 0) { 1361 pc = calcPoint(shape[p], centerCoords, scale); 1362 ctx.lineTo(pc[0], pc[1]); 1363 } 1364 } 1365 1366 ctx.lineTo(pc0[0], pc0[1]); 1367 ctx.fill(); 1368 ctx.closePath();
1369 ctx.restore(); 1370 }; 1371 1372 /* Icons */ 1373 1374 this.drawIconMove = function(centerCoords, scale) { 1375 if(disable) { 1376 return; 1377 } 1378 1379 drawFilledPolygon(shapeArrowN, colors.moveIconFill, centerCoords, scale); 1380 drawFilledPolygon(shapeArrowW, colors.moveIconFill, centerCoords, scale); 1381 drawFilledPolygon(shapeArrowS, colors.moveIconFill, centerCoords, scale); 1382 drawFilledPolygon(shapeArrowE, colors.moveIconFill, centerCoords, scale); 1383 }; 1384 1385 this.drawIconResizeCircle = function(centerCoords, circleRadius, scale) { 1386 if(disable) { 1387 return; 1388 } 1389 1390 var scaledCircleRadius = circleRadius * scale; 1391 ctx.save(); 1392 ctx.strokeStyle = colors.resizeCircleStroke; 1393 ctx.lineWidth = cropper.strokeWidth; 1394 ctx.fillStyle = colors.resizeCircleFill; 1395 ctx.beginPath(); 1396 ctx.arc(centerCoords[0], centerCoords[1], scaledCircleRadius, 0, 2 * Math.PI); 1397 ctx.fill(); 1398 ctx.stroke(); 1399 ctx.closePath(); 1400 ctx.restore(); 1401 }; 1402 1403 this.drawIconResizeBoxBase = function(centerCoords, boxSize, scale) { 1404 if(disable) { 1405 return; 1406 } 1407 1408 var scaledBoxSize = boxSize * scale; 1409 ctx.save(); 1410 ctx.strokeStyle = colors.resizeBoxStroke; 1411 ctx.lineWidth = cropper.strokeWidth; 1412 ctx.fillStyle = colors.resizeBoxFill; 1413 ctx.fillRect(centerCoords[0] - scaledBoxSize / 2, centerCoords[1] - scaledBoxSize / 2, scaledBoxSize, scaledBoxSize); 1414 ctx.strokeRect(centerCoords[0] - scaledBoxSize / 2, centerCoords[1] - scaledBoxSize / 2, scaledBoxSize, scaledBoxSize); 1415 ctx.restore(); 1416 }; 1417 this.drawIconResizeBoxNESW = function(centerCoords, boxSize, scale) { 1418 if(disable) { 1419 return; 1420 } 1421 1422 this.drawIconResizeBoxBase(centerCoords, boxSize, scale); 1423 drawFilledPolygon(shapeArrowNE, colors.resizeBoxArrowFill, centerCoords, scale); 1424 drawFilledPolygon(shapeArrowSW, colors.resizeBoxArrowFill, centerCoords, scale); 1425 }; 1426 this.drawIconResizeBoxNWSE = function(centerCoords, boxSize, scale) { 1427 if(disable) { 1428 return; 1429 } 1430 1431 this.drawIconResizeBoxBase(centerCoords, boxSize, scale); 1432 drawFilledPolygon(shapeArrowNW, colors.resizeBoxArrowFill, centerCoords, scale); 1433 drawFilledPolygon(shapeArrowSE, colors.resizeBoxArrowFill, centerCoords, scale); 1434 }; 1435 1436 /* Crop Area */ 1437 1438 this.drawCropArea = function(image, centerCoords, size, fnDrawClipPath) { 1439 if(disable) { 1440 return; 1441 } 1442 1443 var xRatio = Math.abs(image.width / ctx.canvas.width), 1444 yRatio = Math.abs(image.height / ctx.canvas.height), 1445 xLeft = Math.abs(centerCoords.x - size.w / 2), 1446 yTop = Math.abs(centerCoords.y - size.h / 2); 1447 1448 ctx.save(); 1449 ctx.strokeStyle = colors.areaOutline; 1450 ctx.lineWidth = cropper.strokeWidth; 1451 ctx.setLineDash([5, 5]); 1452 ctx.beginPath(); 1453 fnDrawClipPath(ctx, centerCoords, size); 1454 ctx.stroke(); 1455 ctx.clip(); 1456 1457 // draw part of original image 1458 if (size.w > 0) { 1459 ctx.drawImage(image, xLeft * xRatio, yTop * yRatio, Math.abs(size.w * xRatio), Math.abs(size.h * yRatio), xLeft, yTop, Math.abs(size.w), Math.abs(size.h)); 1460 } 1461 1462 ctx.beginPath(); 1463 fnDrawClipPath(ctx, centerCoords, size); 1464 ctx.stroke(); 1465 ctx.clip(); 1466 1467 ctx.restore(); 1468 }; 1469 }; 1470}]); 1471 1472/** 1473 * EXIF service is based on the exif-js library (https://github.com/jseidelin/exif-js) 1474 */ 1475/*eslint-disable */ 1476//Disable eslint as this is a 3rd party lib 1477angular.module('uiCropper').service('cropEXIF', [function () { 1478 var debug = false; 1479 1480 var ExifTags = this.Tags = { 1481 1482 // version tags 1483 0x9000: 'ExifVersion', // EXIF version 1484 0xA000: 'FlashpixVersion', // Flashpix format version 1485 1486 // colorspace tags 1487 0xA001: 'ColorSpace', // Color space information tag 1488 1489 // image configuration
1490 0xA002: 'PixelXDimension', // Valid width of meaningful image 1491 0xA003: 'PixelYDimension', // Valid height of meaningful image 1492 0x9101: 'ComponentsConfiguration', // Information about channels 1493 0x9102: 'CompressedBitsPerPixel', // Compressed bits per pixel 1494 1495 // user information 1496 0x927C: 'MakerNote', // Any desired information written by the manufacturer 1497 0x9286: 'UserComment', // Comments by user 1498 1499 // related file 1500 0xA004: 'RelatedSoundFile', // Name of related sound file 1501 1502 // date and time 1503 0x9003: 'DateTimeOriginal', // Date and time when the original image was generated 1504 0x9004: 'DateTimeDigitized', // Date and time when the image was stored digitally 1505 0x9290: 'SubsecTime', // Fractions of seconds for DateTime 1506 0x9291: 'SubsecTimeOriginal', // Fractions of seconds for DateTimeOriginal 1507 0x9292: 'SubsecTimeDigitized', // Fractions of seconds for DateTimeDigitized 1508 1509 // picture-taking conditions 1510 0x829A: 'ExposureTime', // Exposure time (in seconds) 1511 0x829D: 'FNumber', // F number 1512 0x8822: 'ExposureProgram', // Exposure program 1513 0x8824: 'SpectralSensitivity', // Spectral sensitivity 1514 0x8827: 'ISOSpeedRatings', // ISO speed rating 1515 0x8828: 'OECF', // Optoelectric conversion factor 1516 0x9201: 'ShutterSpeedValue', // Shutter speed 1517 0x9202: 'ApertureValue', // Lens aperture 1518 0x9203: 'BrightnessValue', // Value of brightness 1519 0x9204: 'ExposureBias', // Exposure bias 1520 0x9205: 'MaxApertureValue', // Smallest F number of lens 1521 0x9206: 'SubjectDistance', // Distance to subject in meters 1522 0x9207: 'MeteringMode', // Metering mode 1523 0x9208: 'LightSource', // Kind of light source 1524 0x9209: 'Flash', // Flash status 1525 0x9214: 'SubjectArea', // Location and area of main subject 1526 0x920A: 'FocalLength', // Focal length of the lens in mm 1527 0xA20B: 'FlashEnergy', // Strobe energy in BCPS 1528 0xA20C: 'SpatialFrequencyResponse', // 1529 0xA20E: 'FocalPlaneXResolution', // Number of pixels in width direction per FocalPlaneResolutionUnit 1530 0xA20F: 'FocalPlaneYResolution', // Number of pixels in height direction per FocalPlaneResolutionUnit 1531 0xA210: 'FocalPlaneResolutionUnit', // Unit for measuring FocalPlaneXResolution and FocalPlaneYResolution 1532 0xA214: 'SubjectLocation', // Location of subject in image 1533 0xA215: 'ExposureIndex', // Exposure index selected on camera 1534 0xA217: 'SensingMethod', // Image sensor type 1535 0xA300: 'FileSource', // Image source (3 == DSC) 1536 0xA301: 'SceneType', // Scene type (1 == directly photographed) 1537 0xA302: 'CFAPattern', // Color filter array geometric pattern 1538 0xA401: 'CustomRendered', // Special processing 1539 0xA402: 'ExposureMode', // Exposure mode 1540 0xA403: 'WhiteBalance', // 1 = auto white balance, 2 = manual 1541 0xA404: 'DigitalZoomRation', // Digital zoom ratio 1542 0xA405: 'FocalLengthIn35mmFilm', // Equivalent foacl length assuming 35mm film camera (in mm) 1543 0xA406: 'SceneCaptureType', // Type of scene 1544 0xA407: 'GainControl', // Degree of overall image gain adjustment 1545 0xA408: 'Contrast', // Direction of contrast processing applied by camera 1546 0xA409: 'Saturation', // Direction of saturation processing applied by camera 1547 0xA40A: 'Sharpness', // Direction of sharpness processing applied by camera 1548 0xA40B: 'DeviceSettingDescription', // 1549 0xA40C: 'SubjectDistanceRange', // Distance to subject 1550 1551 // other tags 1552 0xA005: 'InteroperabilityIFDPointer', 1553 0xA420: 'ImageUniqueID' // Identifier assigned uniquely to each image 1554 }; 1555 1556 var TiffTags = this.TiffTags = { 1557 0x0100: 'ImageWidth', 1558 0x0101: 'ImageHeight', 1559 0x8769: 'ExifIFDPointer', 1560 0x8825: 'GPSInfoIFDPointer', 1561 0xA005: 'InteroperabilityIFDPointer', 1562 0x0102: 'BitsPerSample', 1563 0x0103: 'Compression', 1564 0x0106: 'PhotometricInterpretation', 1565 0x0112: 'Orientation', 1566 0x0115: 'SamplesPerPixel', 1567 0x011C: 'PlanarConfiguration', 1568 0x0212: 'YCbCrSubSampling', 1569 0x0213: 'YCbCrPositioning', 1570 0x011A: 'XResolution', 1571 0x011B: 'YResolution', 1572 0x0128: 'ResolutionUnit', 1573 0x0111: 'StripOffsets', 1574 0x0116: 'RowsPerStrip', 1575 0x0117: 'StripByteCounts', 1576 0x0201: 'JPEGInterchangeFormat', 1577 0x0202: 'JPEGInterchangeFormatLength', 1578 0x012D: 'TransferFunction', 1579 0x013E: 'WhitePoint', 1580 0x013F: 'PrimaryChromaticities', 1581 0x0211: 'YCbCrCoefficients', 1582 0x0214: 'ReferenceBlackWhite', 1583 0x0132: 'DateTime', 1584 0x010E: 'ImageDescription', 1585 0x010F: 'Make', 1586 0x0110: 'Model', 1587 0x0131: 'Software',
vendor: 6,769 bytes, lines 1588-1783
1588 0x013B: 'Artist', 1589 0x8298: 'Copyright' 1590 }; 1591 1592 var GPSTags = this.GPSTags = { 1593 0x0000: 'GPSVersionID', 1594 0x0001: 'GPSLatitudeRef', 1595 0x0002: 'GPSLatitude', 1596 0x0003: 'GPSLongitudeRef', 1597 0x0004: 'GPSLongitude', 1598 0x0005: 'GPSAltitudeRef', 1599 0x0006: 'GPSAltitude', 1600 0x0007: 'GPSTimeStamp', 1601 0x0008: 'GPSSatellites', 1602 0x0009: 'GPSStatus', 1603 0x000A: 'GPSMeasureMode', 1604 0x000B: 'GPSDOP', 1605 0x000C: 'GPSSpeedRef', 1606 0x000D: 'GPSSpeed', 1607 0x000E: 'GPSTrackRef', 1608 0x000F: 'GPSTrack', 1609 0x0010: 'GPSImgDirectionRef', 1610 0x0011: 'GPSImgDirection', 1611 0x0012: 'GPSMapDatum', 1612 0x0013: 'GPSDestLatitudeRef', 1613 0x0014: 'GPSDestLatitude', 1614 0x0015: 'GPSDestLongitudeRef', 1615 0x0016: 'GPSDestLongitude', 1616 0x0017: 'GPSDestBearingRef', 1617 0x0018: 'GPSDestBearing', 1618 0x0019: 'GPSDestDistanceRef', 1619 0x001A: 'GPSDestDistance', 1620 0x001B: 'GPSProcessingMethod', 1621 0x001C: 'GPSAreaInformation', 1622 0x001D: 'GPSDateStamp', 1623 0x001E: 'GPSDifferential' 1624 }; 1625 1626 var StringValues = this.StringValues = { 1627 ExposureProgram: { 1628 0: 'Not defined', 1629 1: 'Manual', 1630 2: 'Normal program', 1631 3: 'Aperture priority', 1632 4: 'Shutter priority', 1633 5: 'Creative program', 1634 6: 'Action program', 1635 7: 'Portrait mode', 1636 8: 'Landscape mode' 1637 }, 1638 MeteringMode: { 1639 0: 'Unknown', 1640 1: 'Average', 1641 2: 'CenterWeightedAverage', 1642 3: 'Spot', 1643 4: 'MultiSpot', 1644 5: 'Pattern', 1645 6: 'Partial', 1646 255: 'Other' 1647 }, 1648 LightSource: { 1649 0: 'Unknown', 1650 1: 'Daylight', 1651 2: 'Fluorescent', 1652 3: 'Tungsten (incandescent light)', 1653 4: 'Flash', 1654 9: 'Fine weather', 1655 10: 'Cloudy weather', 1656 11: 'Shade', 1657 12: 'Daylight fluorescent (D 5700 - 7100K)', 1658 13: 'Day white fluorescent (N 4600 - 5400K)', 1659 14: 'Cool white fluorescent (W 3900 - 4500K)', 1660 15: 'White fluorescent (WW 3200 - 3700K)', 1661 17: 'Standard light A', 1662 18: 'Standard light B', 1663 19: 'Standard light C', 1664 20: 'D55', 1665 21: 'D65', 1666 22: 'D75', 1667 23: 'D50', 1668 24: 'ISO studio tungsten', 1669 255: 'Other' 1670 }, 1671 Flash: { 1672 0x0000: 'Flash did not fire', 1673 0x0001: 'Flash fired', 1674 0x0005: 'Strobe return light not detected', 1675 0x0007: 'Strobe return light detected', 1676 0x0009: 'Flash fired, compulsory flash mode', 1677 0x000D: 'Flash fired, compulsory flash mode, return light not detected', 1678 0x000F: 'Flash fired, compulsory flash mode, return light detected', 1679 0x0010: 'Flash did not fire, compulsory flash mode', 1680 0x0018: 'Flash did not fire, auto mode', 1681 0x0019: 'Flash fired, auto mode', 1682 0x001D: 'Flash fired, auto mode, return light not detected', 1683 0x001F: 'Flash fired, auto mode, return light detected', 1684 0x0020: 'No flash function', 1685 0x0041: 'Flash fired, red-eye reduction mode', 1686 0x0045: 'Flash fired, red-eye reduction mode, return light not detected', 1687 0x0047: 'Flash fired, red-eye reduction mode, return light detected', 1688 0x0049: 'Flash fired, compulsory flash mode, red-eye reduction mode', 1689 0x004D: 'Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected', 1690 0x004F: 'Flash fired, compulsory flash mode, red-eye reduction mode, return light detected', 1691 0x0059: 'Flash fired, auto mode, red-eye reduction mode', 1692 0x005D: 'Flash fired, auto mode, return light not detected, red-eye reduction mode', 1693 0x005F: 'Flash fired, auto mode, return light detected, red-eye reduction mode' 1694 }, 1695 SensingMethod: { 1696 1: 'Not defined', 1697 2: 'One-chip color area sensor', 1698 3: 'Two-chip color area sensor', 1699 4: 'Three-chip color area sensor', 1700 5: 'Color sequential area sensor', 1701 7: 'Trilinear sensor', 1702 8: 'Color sequential linear sensor' 1703 }, 1704 SceneCaptureType: { 1705 0: 'Standard', 1706 1: 'Landscape', 1707 2: 'Portrait', 1708 3: 'Night scene' 1709 }, 1710 SceneType: { 1711 1: 'Directly photographed' 1712 }, 1713 CustomRendered: { 1714 0: 'Normal process', 1715 1: 'Custom process' 1716 }, 1717 WhiteBalance: { 1718 0: 'Auto white balance', 1719 1: 'Manual white balance' 1720 }, 1721 GainControl: { 1722 0: 'None', 1723 1: 'Low gain up', 1724 2: 'High gain up', 1725 3: 'Low gain down', 1726 4: 'High gain down' 1727 }, 1728 Contrast: { 1729 0: 'Normal', 1730 1: 'Soft', 1731 2: 'Hard' 1732 }, 1733 Saturation: { 1734 0: 'Normal', 1735 1: 'Low saturation', 1736 2: 'High saturation' 1737 }, 1738 Sharpness: { 1739 0: 'Normal', 1740 1: 'Soft', 1741 2: 'Hard' 1742 }, 1743 SubjectDistanceRange: { 1744 0: 'Unknown', 1745 1: 'Macro', 1746 2: 'Close view', 1747 3: 'Distant view' 1748 }, 1749 FileSource: { 1750 3: 'DSC' 1751 }, 1752 1753 Components: { 1754 0: '', 1755 1: 'Y', 1756 2: 'Cb', 1757 3: 'Cr', 1758 4: 'R', 1759 5: 'G', 1760 6: 'B' 1761 } 1762 }; 1763 1764 function addEvent(element, event, handler) { 1765 if (element.addEventListener) { 1766 element.addEventListener(event, handler, false); 1767 } else if (element.attachEvent) { 1768 element.attachEvent('on' + event, handler); 1769 } 1770 } 1771 1772 function imageHasData(img) { 1773 return !!(img.exifdata); 1774 } 1775 1776 function base64ToArrayBuffer(base64, contentType) { 1777 contentType = contentType || base64.match(/^data\:([^\;]+)\;base64,/mi)[1] || ''; // e.g. 'data:image/jpeg;base64,...' => 'image/jpeg' 1778 base64 = base64.replace(/^data\:([^\;]+)\;base64,/gmi, ''); 1779 var binary = atob(base64); 1780 var len = binary.length; 1781 var buffer = new ArrayBuffer(len); 1782 var view = new Uint8Array(buffer); 1783 for (var i = 0; i < len; i++) {
1784 view[i] = binary.charCodeAt(i); 1785 } 1786 return buffer; 1787 } 1788 1789 function objectURLToBlob(url, callback) { 1790 var http = new XMLHttpRequest(); 1791 http.open('GET', url, true); 1792 http.responseType = 'blob'; 1793 http.onload = function (e) { 1794 if (this.status === 200 || this.status === 0) { 1795 callback(this.response); 1796 } 1797 }; 1798 http.send(); 1799 } 1800 1801 function getImageData(img, callback) { 1802 function handleBinaryFile(binFile) { 1803 var data = findEXIFinJPEG(binFile); 1804 var iptcdata = findIPTCinJPEG(binFile); 1805 img.exifdata = data || {}; 1806 img.iptcdata = iptcdata || {}; 1807 if (callback) { 1808 callback.call(img); 1809 } 1810 } 1811 1812 var fileReader = new FileReader(); 1813 1814 if (img.src) { 1815 1816 if (/^data\:/i.test(img.src)) { // Data URI 1817 var arrayBuffer = base64ToArrayBuffer(img.src); 1818 handleBinaryFile(arrayBuffer); 1819 1820 } else if (/^blob\:/i.test(img.src)) { // Object URL 1821 fileReader.onload = function (e) { 1822 handleBinaryFile(e.target.result); 1823 }; 1824 objectURLToBlob(img.src, function (blob) { 1825 fileReader.readAsArrayBuffer(blob); 1826 }); 1827 } 1828 var http = new XMLHttpRequest(); 1829 http.onload = function () { 1830 if (this.status === 200 || this.status === 0) { 1831 handleBinaryFile(http.response); 1832 } else { 1833 throw 'Could not load image'; 1834 } 1835 http = null; 1836 }; 1837 http.open('GET', img.src, true); 1838 http.responseType = 'arraybuffer'; 1839 try { 1840 http.send(null); 1841 } catch(e) { 1842 } 1843 } else if (window.FileReader && (img instanceof window.Blob || img instanceof window.File)) { 1844 fileReader.onload = function (e) { 1845 if (debug) { 1846 console.log('Got file of length ' + e.target.result.byteLength); 1847 } 1848 handleBinaryFile(e.target.result); 1849 }; 1850 1851 fileReader.readAsArrayBuffer(img); 1852 } 1853 } 1854 1855 function findEXIFinJPEG(file) { 1856 var dataView = new DataView(file); 1857 1858 if (debug) { 1859 console.log('Got file of length ' + file.byteLength); 1860 } 1861 if ((dataView.getUint8(0) !== 0xFF) || (dataView.getUint8(1) !== 0xD8)) { 1862 if (debug) { 1863 console.log('Not a valid JPEG'); 1864 } 1865 return false; // not a valid jpeg 1866 } 1867 1868 var offset = 2, 1869 length = file.byteLength, 1870 marker; 1871 1872 while (offset < length) { 1873 if (dataView.getUint8(offset) !== 0xFF) { 1874 if (debug) { 1875 console.log('Not a valid marker at offset ' + offset + ', found: ' + dataView.getUint8(offset)); 1876 } 1877 return false; // not a valid marker, something is wrong 1878 } 1879 1880 marker = dataView.getUint8(offset + 1); 1881 if (debug) { 1882 console.log(marker); 1883 } 1884 1885 // we could implement handling for other markers here, 1886 // but we're only looking for 0xFFE1 for EXIF data 1887 1888 if (marker === 225) { 1889 if (debug) { 1890 console.log('Found 0xFFE1 marker'); 1891 } 1892 1893 return readEXIFData(dataView, offset + 4, dataView.getUint16(offset + 2) - 2); 1894 1895 // offset += 2 + file.getShortAt(offset+2, true); 1896 1897 } else { 1898 offset += 2 + dataView.getUint16(offset + 2); 1899 } 1900 1901 } 1902 1903 } 1904 1905 function findIPTCinJPEG(file) { 1906 var dataView = new DataView(file); 1907 1908 if (debug) { 1909 console.log('Got file of length ' + file.byteLength); 1910 } 1911 if ((dataView.getUint8(0) !== 0xFF) || (dataView.getUint8(1) !== 0xD8)) { 1912 if (debug) { 1913 console.log('Not a valid JPEG'); 1914 } 1915 return false; // not a valid jpeg 1916 } 1917 1918 var offset = 2, 1919 length = file.byteLength; 1920 1921 var isFieldSegmentStart = function (dataView, offset) { 1922 return ( 1923 dataView.getUint8(offset) === 0x38 && 1924 dataView.getUint8(offset + 1) === 0x42 && 1925 dataView.getUint8(offset + 2) === 0x49 && 1926 dataView.getUint8(offset + 3) === 0x4D && 1927 dataView.getUint8(offset + 4) === 0x04 && 1928 dataView.getUint8(offset + 5) === 0x04 1929 ); 1930 }; 1931 1932 while (offset < length) { 1933 1934 if (isFieldSegmentStart(dataView, offset)) { 1935 1936 // Get the length of the name header (which is padded to an even number of bytes) 1937 var nameHeaderLength = dataView.getUint8(offset + 7); 1938 if (nameHeaderLength % 2 !== 0) { 1939 nameHeaderLength += 1;
1940 } 1941 // Check for pre photoshop 6 format 1942 if (nameHeaderLength === 0) { 1943 // Always 4 1944 nameHeaderLength = 4; 1945 } 1946 1947 var startOffset = offset + 8 + nameHeaderLength; 1948 var sectionLength = dataView.getUint16(offset + 6 + nameHeaderLength); 1949 1950 return readIPTCData(file, startOffset, sectionLength); 1951 } 1952 1953 // Not the marker, continue searching 1954 offset++; 1955 1956 } 1957 1958 } 1959 1960 var IptcFieldMap = { 1961 0x78: 'caption', 1962 0x6E: 'credit', 1963 0x19: 'keywords', 1964 0x37: 'dateCreated', 1965 0x50: 'byline', 1966 0x55: 'bylineTitle', 1967 0x7A: 'captionWriter', 1968 0x69: 'headline', 1969 0x74: 'copyright', 1970 0x0F: 'category' 1971 }; 1972 1973 function readIPTCData(file, startOffset, sectionLength) { 1974 var dataView = new DataView(file); 1975 var data = {}; 1976 var fieldValue, fieldName, dataSize, segmentType, segmentSize; 1977 var segmentStartPos = startOffset; 1978 while (segmentStartPos < startOffset + sectionLength) { 1979 if (dataView.getUint8(segmentStartPos) === 0x1C && dataView.getUint8(segmentStartPos + 1) === 0x02) { 1980 segmentType = dataView.getUint8(segmentStartPos + 2); 1981 if (segmentType in IptcFieldMap) { 1982 dataSize = dataView.getInt16(segmentStartPos + 3); 1983 segmentSize = dataSize + 5; 1984 fieldName = IptcFieldMap[segmentType]; 1985 fieldValue = getStringFromDB(dataView, segmentStartPos + 5, dataSize); 1986 // Check if we already stored a value with this name 1987 if (data.hasOwnProperty(fieldName)) { 1988 // Value already stored with this name, create multivalue field 1989 if (data[fieldName] instanceof Array) { 1990 data[fieldName].push(fieldValue); 1991 } else { 1992 data[fieldName] = [data[fieldName], fieldValue]; 1993 } 1994 } else { 1995 data[fieldName] = fieldValue; 1996 } 1997 } 1998 1999 } 2000 segmentStartPos++; 2001 } 2002 return data; 2003 } 2004 2005 function readTags(file, tiffStart, dirStart, strings, bigEnd) { 2006 var entries = file.getUint16(dirStart, !bigEnd), 2007 tags = {}, 2008 entryOffset, tag, 2009 i; 2010 2011 for (i = 0; i < entries; i++) { 2012 entryOffset = dirStart + i * 12 + 2; 2013 tag = strings[file.getUint16(entryOffset, !bigEnd)]; 2014 if (!tag && debug) { 2015 console.log('Unknown tag: ' + file.getUint16(entryOffset, !bigEnd)); 2016 } 2017 tags[tag] = readTagValue(file, entryOffset, tiffStart, dirStart, bigEnd); 2018 } 2019 return tags; 2020 } 2021 2022 function readTagValue(file, entryOffset, tiffStart, dirStart, bigEnd) { 2023 var type = file.getUint16(entryOffset + 2, !bigEnd), 2024 numValues = file.getUint32(entryOffset + 4, !bigEnd), 2025 valueOffset = file.getUint32(entryOffset + 8, !bigEnd) + tiffStart, 2026 offset, 2027 vals, val, n, 2028 numerator, denominator; 2029 2030 switch (type) { 2031 case 1: // byte, 8-bit unsigned int 2032 case 7: // undefined, 8-bit byte, value depending on field 2033 if (numValues === 1) { 2034 return file.getUint8(entryOffset + 8, !bigEnd); 2035 } 2036 offset = numValues > 4 ? valueOffset : (entryOffset + 8); 2037 vals = []; 2038 for (n = 0; n < numValues; n++) { 2039 vals[n] = file.getUint8(offset + n); 2040 } 2041 return vals; 2042 2043 case 2: // ascii, 8-bit byte 2044 offset = numValues > 4 ? valueOffset : (entryOffset + 8); 2045 return getStringFromDB(file, offset, numValues - 1); 2046 2047 case 3: // short, 16 bit int 2048 if (numValues === 1) { 2049 return file.getUint16(entryOffset + 8, !bigEnd); 2050 } 2051 offset = numValues > 2 ? valueOffset : (entryOffset + 8); 2052 vals = []; 2053 for (n = 0; n < numValues; n++) { 2054 vals[n] = file.getUint16(offset + 2 * n, !bigEnd); 2055 } 2056 return vals; 2057 2058 case 4: // long, 32 bit int 2059 if (numValues === 1) { 2060 return file.getUint32(entryOffset + 8, !bigEnd); 2061 } 2062 vals = []; 2063 for (n = 0; n < numValues; n++) { 2064 vals[n] = file.getUint32(valueOffset + 4 * n, !bigEnd); 2065 } 2066 return vals; 2067 2068 case 5: // rational = two long values, first is numerator, second is denominator 2069 if (numValues === 1) { 2070 numerator = file.getUint32(valueOffset, !bigEnd); 2071 denominator = file.getUint32(valueOffset + 4, !bigEnd); 2072 val = {}; //@TODO need to inspect if this fix is really working 2073 val.numerator = numerator; 2074 val.denominator = denominator; 2075 return val; 2076 } 2077 vals = []; 2078 for (n = 0; n < numValues; n++) { 2079 numerator = file.getUint32(valueOffset + 8 * n, !bigEnd); 2080 denominator = file.getUint32(valueOffset + 4 + 8 * n, !bigEnd); 2081 vals[n] = {}; //@TODO need to inspect if this fix is really working 2082 vals[n].numerator = numerator; 2083 vals[n].denominator = denominator; 2084 } 2085 return vals; 2086 2087 case 9: // slong, 32 bit signed int 2088 if (numValues === 1) { 2089 return file.getInt32(entryOffset + 8, !bigEnd); 2090 } 2091 vals = []; 2092 for (n = 0; n < numValues; n++) { 2093 vals[n] = file.getInt32(valueOffset + 4 * n, !bigEnd); 2094 } 2095 return vals; 2096 2097 case 10: // signed rational, two slongs, first is numerator, second is denominator 2098 if (numValues === 1) { 2099 return file.getInt32(valueOffset, !bigEnd) / file.getInt32(valueOffset + 4, !bigEnd); 2100 } 2101 vals = []; 2102 for (n = 0; n < numValues; n++) { 2103 vals[n] = file.getInt32(valueOffset + 8 * n, !bigEnd) / file.getInt32(valueOffset + 4 + 8 * n, !bigEnd); 2104 } 2105 return vals; 2106 } 2107 } 2108 2109 function getStringFromDB(buffer, start, length) { 2110 var outstr = ''; 2111 for (var n = start; n < start + length; n++) { 2112 outstr += String.fromCharCode(buffer.getUint8(n)); 2113 } 2114 return outstr; 2115 } 2116 2117 function readEXIFData(file, start) { 2118 if (getStringFromDB(file, start, 4) !== 'Exif') { 2119 if (debug) { 2120 console.log('Not valid EXIF data! ' + getStringFromDB(file, start, 4)); 2121 } 2122 return false; 2123 } 2124 2125 var bigEnd, 2126 tags, tag, 2127 exifData, gpsData, 2128 tiffOffset = start + 6; 2129 2130 // test for TIFF validity and endianness 2131 if (file.getUint16(tiffOffset) === 0x4949) { 2132 bigEnd = false; 2133 } else if (file.getUint16(tiffOffset) === 0x4D4D) { 2134 bigEnd = true; 2135 } else { 2136 if (debug) { 2137 console.log('Not valid TIFF data! (no 0x4949 or 0x4D4D)'); 2138 } 2139 return false; 2140 } 2141 2142 if (file.getUint16(tiffOffset + 2, !bigEnd) !== 0x002A) { 2143 if (debug) { 2144 console.log('Not valid TIFF data! (no 0x002A)'); 2145 } 2146 return false; 2147 } 2148 2149 var firstIFDOffset = file.getUint32(tiffOffset + 4, !bigEnd); 2150 2151 if (firstIFDOffset < 0x00000008) { 2152 if (debug) { 2153 console.log('Not valid TIFF data! (First offset less than 8)', file.getUint32(tiffOffset + 4, !bigEnd)); 2154 } 2155 return false; 2156 } 2157 2158 tags = readTags(file, tiffOffset, tiffOffset + firstIFDOffset, TiffTags, bigEnd); 2159 2160 if (tags.ExifIFDPointer) { 2161 exifData = readTags(file, tiffOffset, tiffOffset + tags.ExifIFDPointer, ExifTags, bigEnd); 2162 for (tag in exifData) { 2163 switch (tag) { 2164 case 'LightSource': 2165 case 'Flash': 2166 case 'MeteringMode': 2167 case 'ExposureProgram': 2168 case 'SensingMethod': 2169 case 'SceneCaptureType': 2170 case 'SceneType': 2171 case 'CustomRendered': 2172 case 'WhiteBalance': 2173 case 'GainControl': 2174 case 'Contrast': 2175 case 'Saturation': 2176 case 'Sharpness': 2177 case 'SubjectDistanceRange': 2178 case 'FileSource': 2179 exifData[tag] = StringValues[tag][exifData[tag]]; 2180 break; 2181 2182 case 'ExifVersion': 2183 case 'FlashpixVersion': 2184 exifData[tag] = String.fromCharCode(exifData[tag][0], exifData[tag][1], exifData[tag][2], exifData[tag][3]); 2185 break; 2186 2187 case 'ComponentsConfiguration': 2188 exifData[tag] = 2189 StringValues.Components[exifData[tag][0]] + 2190 StringValues.Components[exifData[tag][1]] + 2191 StringValues.Components[exifData[tag][2]] + 2192 StringValues.Components[exifData[tag][3]]; 2193 break; 2194 }
2195 tags[tag] = exifData[tag]; 2196 } 2197 } 2198 2199 if (tags.GPSInfoIFDPointer) { 2200 gpsData = readTags(file, tiffOffset, tiffOffset + tags.GPSInfoIFDPointer, GPSTags, bigEnd); 2201 for (tag in gpsData) { 2202 switch (tag) { 2203 case 'GPSVersionID': 2204 gpsData[tag] = gpsData[tag][0] + 2205 '.' + gpsData[tag][1] + 2206 '.' + gpsData[tag][2] + 2207 '.' + gpsData[tag][3]; 2208 break; 2209 } 2210 tags[tag] = gpsData[tag]; 2211 } 2212 } 2213 2214 return tags; 2215 } 2216 2217 this.getData = function (img, callback) { 2218 if ((img instanceof Image || img instanceof HTMLImageElement) && !img.complete) { 2219 return false; 2220 } 2221 2222 if (!imageHasData(img)) { 2223 getImageData(img, callback); 2224 } else { 2225 if (callback) { 2226 callback.call(img); 2227 } 2228 } 2229 return true; 2230 }; 2231 2232 this.getTag = function (img, tag) { 2233 if (!imageHasData(img)) { 2234 return; 2235 } 2236 return img.exifdata[tag]; 2237 }; 2238 2239 this.getAllTags = function (img) { 2240 if (!imageHasData(img)) { 2241 return {}; 2242 } 2243 var a, 2244 data = img.exifdata, 2245 tags = {}; 2246 for (a in data) { 2247 if (data.hasOwnProperty(a)) { 2248 tags[a] = data[a]; 2249 } 2250 } 2251 return tags; 2252 }; 2253 2254 this.pretty = function (img) { 2255 if (!imageHasData(img)) { 2256 return ''; 2257 } 2258 var a, 2259 data = img.exifdata, 2260 strPretty = ''; 2261 for (a in data) { 2262 if (data.hasOwnProperty(a)) { 2263 if (typeof data[a] === 'object') { 2264 if (data[a] instanceof Number) { 2265 strPretty += a + ' : ' + data[a] + ' [' + data[a].numerator + '/' + data[a].denominator + ']\r\n'; 2266 } else { 2267 strPretty += a + ' : [' + data[a].length + ' values]\r\n'; 2268 } 2269 } else { 2270 strPretty += a + ' : ' + data[a] + '\r\n'; 2271 } 2272 } 2273 } 2274 return strPretty; 2275 }; 2276 2277 this.readFromBinaryFile = function (file) { 2278 return findEXIFinJPEG(file); 2279 }; 2280}]); 2281 2282angular.module('uiCropper').factory('cropHost', ['$document', '$q', 'cropAreaCircle', 'cropAreaSquare', 'cropAreaRectangle', 'cropEXIF', function ($document, $q, CropAreaCircle, CropAreaSquare, CropAreaRectangle, cropEXIF) { 2283 /* STATIC FUNCTIONS */ 2284 var colorPaletteLength = 8; 2285 2286 // Get Element's Offset 2287 var getElementOffset = function (elem) { 2288 var box = elem.getBoundingClientRect(); 2289 2290 var body = document.body; 2291 var docElem = document.documentElement; 2292 2293 var scrollTop = window.pageYOffset || docElem.scrollTop || body.scrollTop; 2294 var scrollLeft = window.pageXOffset || docElem.scrollLeft || body.scrollLeft; 2295 2296 var clientTop = docElem.clientTop || body.clientTop || 0; 2297 var clientLeft = docElem.clientLeft || body.clientLeft || 0; 2298 2299 var top = box.top + scrollTop - clientTop; 2300 var left = box.left + scrollLeft - clientLeft; 2301 2302 return { 2303 top: Math.round(top), 2304 left: Math.round(left) 2305 }; 2306 }; 2307 2308 return function (elCanvas, opts, events) { 2309 /* PRIVATE VARIABLES */ 2310 2311 // Object Pointers 2312 var ctx = null, 2313 image = null, 2314 theArea = null, 2315 initMax = null, 2316 isAspectRatio = null, 2317 self = this, 2318 2319 // Dimensions 2320 minCanvasDims = [100, 100], 2321 maxCanvasDims = [300, 300], 2322 2323 scalemode = null, 2324 2325 // Result Image size 2326 resImgSizeArray = [], 2327 resImgSize = { 2328 w: 200, 2329 h: 200 2330 }, 2331 areaMinRelativeSize = null, 2332 2333 // Result Image type 2334 resImgFormat = 'image/png', 2335 2336 // Result Image quality 2337 resImgQuality = null, 2338 2339 keys = { 2340 up: 38, 2341 down: 40, 2342 left: 37, 2343 right: 39 2344 }, 2345 2346 forceAspectRatio = false; 2347 2348 /* PRIVATE FUNCTIONS */ 2349 // Draw Scene 2350 function drawScene() { 2351 // clear canvas 2352 ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); 2353 2354 if (image !== null) { 2355 // draw source image 2356 ctx.drawImage(image, 0, 0, ctx.canvas.width, ctx.canvas.height); 2357 2358 ctx.save(); 2359 2360 // and make it darker 2361 if(!theArea._disableCrop){ 2362 ctx.fillStyle = 'rgba(0, 0, 0, 0.65)'; 2363 ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height); 2364
2365 ctx.restore(); 2366 } 2367 // draw Area 2368 theArea.draw(); 2369 } 2370 } 2371 2372 this.setInitMax = function (bool) { 2373 initMax = bool; 2374 }; 2375 2376 this.setAllowCropResizeOnCorners = function (bool) { 2377 theArea.setAllowCropResizeOnCorners(bool); 2378 }; 2379 2380 this.setDisableCrop = function(value){ 2381 theArea.setDisableCrop(value); 2382 drawScene(); 2383 }; 2384 2385 var focusOnCanvas = function () { 2386 elCanvas[0].focus(); 2387 }; 2388 2389 function fitCanvasDims(width, height) { 2390 var imageRatio = width / height; 2391 var canvasDims = [width, height]; 2392 var margins = {left: 0, top: 0}; 2393 if (canvasDims[0] > maxCanvasDims[0]) { 2394 canvasDims[0] = maxCanvasDims[0]; 2395 canvasDims[1] = canvasDims[0] / imageRatio; 2396 margins.top = (maxCanvasDims[1] - canvasDims[1]) / 2; 2397 } 2398 else if (canvasDims[1] > maxCanvasDims[1]) { 2399 canvasDims[1] = maxCanvasDims[1]; 2400 canvasDims[0] = canvasDims[1] * imageRatio; 2401 margins.left = (maxCanvasDims[0] - canvasDims[0]) / 2; 2402 } 2403 else { 2404 margins.top = (maxCanvasDims[1] - canvasDims[1]) / 2; 2405 margins.left = (maxCanvasDims[0] - canvasDims[0]) / 2; 2406 } 2407 return { 2408 dims: canvasDims, 2409 margins: margins, 2410 }; 2411 } 2412 2413 // Resets CropHost 2414 var resetCropHost = function () { 2415 var canvasDims = [0, 0]; 2416 if (image !== null) { 2417 focusOnCanvas(); 2418 theArea.setImage(image); 2419 if (scalemode === 'fit') { 2420 var results = fitCanvasDims(image.width, image.height); 2421 canvasDims = results.dims; 2422 var margins = results.margins; 2423 elCanvas.css({ 2424 'margin-left': margins.left + 'px', 2425 'margin-top': margins.top + 'px' 2426 }); 2427 } 2428 else { 2429 var imageDims = [image.width, image.height], 2430 imageRatio = image.width / image.height; 2431 canvasDims = imageDims; 2432 2433 if (canvasDims[0] > maxCanvasDims[0]) { 2434 canvasDims[0] = maxCanvasDims[0]; 2435 canvasDims[1] = canvasDims[0] / imageRatio; 2436 } else if (canvasDims[0] < minCanvasDims[0]) { 2437 canvasDims[0] = minCanvasDims[0]; 2438 canvasDims[1] = canvasDims[0] / imageRatio; 2439 } 2440 if (scalemode === 'fixed-height' && canvasDims[1] > maxCanvasDims[1]) { 2441 canvasDims[1] = maxCanvasDims[1]; 2442 canvasDims[0] = canvasDims[1] * imageRatio; 2443 } else if (canvasDims[1] < minCanvasDims[1]) { 2444 canvasDims[1] = minCanvasDims[1]; 2445 canvasDims[0] = canvasDims[1] * imageRatio; 2446 } 2447 } 2448 elCanvas.prop('width', canvasDims[0]).prop('height', canvasDims[1]); 2449 if (scalemode === 'fixed-height') { 2450 elCanvas.css({ 2451 'margin-left': -canvasDims[0] / 2 + 'px', 2452 'margin-top': -canvasDims[1] / 2 + 'px' 2453 }); 2454 } 2455 2456 var cw = ctx.canvas.width; 2457 var ch = ctx.canvas.height; 2458 2459 var areaType = self.getAreaType(); 2460 // enforce 1:1 aspect ratio for square-like selections 2461 if ((areaType === 'circle') || (areaType === 'square')) { 2462 if (ch < cw) { 2463 cw = ch; 2464 } 2465 ch = cw; 2466 } else if (areaType === 'rectangle' && isAspectRatio) { 2467 var aspectRatio = theArea.getAspect(); // use `aspectRatio` instead of `resImgSize` dimensions bc `resImgSize` c
2467an be 'selection' string 2468 if (cw / ch > aspectRatio) { 2469 cw = aspectRatio * ch; 2470 } else { 2471 ch = aspectRatio * cw; 2472 } 2473 } 2474 2475 if (initMax) { 2476 theArea.setSize({ 2477 w: cw, 2478 h: ch 2479 }); 2480 } else if (undefined !== theArea.getInitSize()) { 2481 theArea.setSize({ 2482 w: Math.min(theArea.getInitSize().w, cw / 2), 2483 h: Math.min(theArea.getInitSize().h, ch / 2) 2484 }); 2485 } else { 2486 theArea.setSize({ 2487 w: Math.min(200, cw / 2), 2488 h: Math.min(200, ch / 2) 2489 }); 2490 } 2491 2492 if (theArea.getInitCoords()) { 2493 if (self.areaInitIsRelativeToImage) { 2494 var ratio = image.width / canvasDims[0]; 2495 theArea.setSize({ 2496 w: theArea.getInitSize().w / ratio, 2497 h: theArea.getInitSize().h / ratio, 2498 x: theArea.getInitCoords().x / ratio, 2499 y: theArea.getInitCoords().y / ratio 2500 }); 2501 } else { 2502 theArea.setSize({ 2503 w: theArea.getSize().w, 2504 h: theArea.getSize().h, 2505 x: theArea.getInitCoords().x, 2506 y: theArea.getInitCoords().y 2507 }); 2508 } 2509 } else { 2510 theArea.setCenterPoint({ 2511 x: ctx.canvas.width / 2, 2512 y: ctx.canvas.height / 2 2513 }); 2514 } 2515 2516 } else { 2517 elCanvas.prop('width', 0).prop('height', 0).css({ 2518 'margin-top': 0 2519 }); 2520 } 2521 2522 drawScene(); 2523 }; 2524 2525 var getChangedTouches = function (event) { 2526 if (angular.isDefined(event.changedTouches)) { 2527 return event.changedTouches; 2528 } else { 2529 return event.originalEvent.changedTouches; 2530 } 2531 }; 2532 2533 var onMouseMove = function (e) { 2534 if(theArea._disableCrop) { 2535 return; 2536 } 2537 2538 if (image !== null) { 2539 var offset = getElementOffset(ctx.canvas), 2540 pageX, pageY; 2541 if (e.type === 'touchmove') { 2542 pageX = getChangedTouches(e)[0].pageX; 2543 pageY = getChangedTouches(e)[0].pageY; 2544 } else { 2545 pageX = e.pageX; 2546 pageY = e.pageY; 2547 } 2548 2549 theArea.processMouseMove(pageX - offset.left, pageY - offset.top); 2550 drawScene(); 2551 } 2552 }; 2553 2554 var onMouseDown = function (e) { 2555 e.preventDefault(); 2556 if(theArea._disableCrop) { 2557 return; 2558 } 2559 2560 if (!opts.allowPropagation) { 2561 e.stopPropagation(); 2562 } 2563 2564 if (image !== null) { 2565 focusOnCanvas(); 2566 var offset = getElementOffset(ctx.canvas), 2567 pageX, pageY; 2568 if (e.type === 'touchstart') { 2569 pageX = getChangedTouches(e)[0].pageX; 2570 pageY = getChangedTouches(e)[0].pageY; 2571 } else { 2572 pageX = e.pageX; 2573 pageY = e.pageY; 2574 } 2575 theArea.processMouseDown(pageX - offset.left, pageY - offset.top); 2576 drawScene(); 2577 } 2578 }; 2579 2580 var onMouseUp = function (e) { 2581 if(theArea._disableCrop) { 2582 return; 2583 } 2584 2585 if (image !== null) { 2586 var offset = getElementOffset(ctx.canvas), 2587 pageX, pageY; 2588 if (e.type === 'touchend') { 2589 pageX = getChangedTouches(e)[0].pageX; 2590 pageY = getChangedTouches(e)[0].pageY; 2591 } else { 2592 pageX = e.pageX; 2593 pageY = e.pageY; 2594 } 2595 theArea.processMouseUp(pageX - offset.left, pageY - offset.top); 2596 drawScene(); 2597 } 2598 }; 2599 2600 var getDirectionByKey = function (key) { 2601 return Object.keys(keys).reduce(function(result, direction) { 2602 return key === keys[direction] ? direction : result; 2603 }, null); 2604 }; 2605 2606 var resizeCropAreaByDirection = function (direction) { 2607 if(theArea._disableCrop) { 2608 return; 2609 } 2610 2611 var scale; 2612 switch (direction) { 2613 case 'up': 2614 case 'left': 2615 scale = 0.95; 2616 break; 2617 case 'down': 2618 case 'right': 2619 scale = 1.05; 2620 break; 2621 default: 2622 return; 2623 } 2624 theArea.setSizeByScale(scale, direction); 2625 drawScene(); 2626 }; 2627 2628 var moveCropArea = function (direction) { 2629 if(theArea._disableCrop) { 2630 return; 2631 } 2632 2633 var center = theArea.getCenterPoint(); 2634 var step = 5; 2635 var point = { 2636 x: center.x, 2637 y: center.y 2638 }; 2639 2640 switch (direction) { 2641 case 'up': 2642 point.y -= step; 2643 break; 2644 case 'down': 2645 point.y += step; 2646 break; 2647 case 'left': 2648 point.x -= step; 2649 break; 2650 case 'right': 2651 point.x += step; 2652 break; 2653 default: 2654 return; 2655 } 2656 2657 theArea.setCenterPointOnMove(point); 2658 drawScene(); 2659 }; 2660 2661 var onKeyDown = function (e) { 2662 if(theArea._disableCrop) { 2663 return; 2664 } 2665 2666 if (image !== null && opts.disableKeyboardAccess !== true) { 2667 var key = e.which; 2668 switch (key) { 2669 case keys.up: 2670 case keys.down: 2671 case keys.left: 2672 case keys.right: 2673 e.preventDefault(); 2674 e.stopPropagation(); 2675 var direction = getDirectionByKey(key); 2676 if (e.shiftKey) { 2677 resizeCropAreaByDirection(direction); 2678 } else { 2679 moveCropArea(direction); 2680 } 2681 break; 2682 default: 2683 return; 2684 } 2685 } 2686 }; 2687 2688 var renderTempCanvas = function (ris, center) { 2689 var temp_ctx, temp_canvas; 2690 temp_canvas = angular.element('<canvas></canvas>')[0]; 2691 temp_ctx = temp_canvas.getContext('2d'); 2692 temp_canvas.width = ris.w; 2693 temp_canvas.height = ris.h; 2694 if (image !== null) { 2695 var x = (center.x - theArea.getSize().w / 2) * (image.
2695width / ctx.canvas.width), 2696 y = (center.y - theArea.getSize().h / 2) * (image.height / ctx.canvas.height), 2697 areaWidth = theArea.getSize().w * (image.width / ctx.canvas.width), 2698 areaHeight = theArea.getSize().h * (image.height / ctx.canvas.height); 2699 2700 if (forceAspectRatio) { 2701 temp_ctx.drawImage(image, x, y, 2702 areaWidth, 2703 areaHeight, 2704 0, 2705 0, 2706 ris.w, 2707 ris.h); 2708 } else { 2709 var aspectRatio = areaWidth / areaHeight; 2710 var resultHeight, resultWidth; 2711 2712 if (aspectRatio > 1) { 2713 resultWidth = ris.w; 2714 resultHeight = resultWidth / aspectRatio; 2715 } else { 2716 resultHeight = ris.h; 2717 resultWidth = resultHeight * aspectRatio; 2718 } 2719 2720 temp_canvas.width = resultWidth; 2721 temp_canvas.height = resultHeight; 2722 2723 temp_ctx.drawImage(image, 2724 x, 2725 y, 2726 areaWidth, 2727 areaHeight, 2728 0, 2729 0, 2730 Math.round(resultWidth), 2731 Math.round(resultHeight)); 2732 } 2733 2734 } 2735 return temp_canvas; 2736 2737 }; 2738 2739 var renderImageToDataURL = function (getResultImageSize) { 2740 var temp_canvas, 2741 retObj = { 2742 dataURI: null, 2743 imageData: null 2744 }; 2745 temp_canvas = renderTempCanvas(getResultImageSize, theArea.getCenterPoint()); 2746 if (image !== null) { 2747 if (resImgQuality !== null) { 2748 retObj.dataURI = temp_canvas.toDataURL(resImgFormat, resImgQuality); 2749 } else { 2750 retObj.dataURI = temp_canvas.toDataURL(resImgFormat); 2751 } 2752 } 2753 return retObj; 2754 }; 2755 2756 this.getResultImage = function () { 2757 if (resImgSizeArray.length === 0) { 2758 return renderImageToDataURL(this.getResultImageSize()); 2759 } 2760 2761 var arrayResultImages = []; 2762 for (var i = 0; i < resImgSizeArray.length; i++) { 2763 arrayResultImages.push({ 2764 dataURI: renderImageToDataURL(resImgSizeArray[i]).dataURI, 2765 w: resImgSizeArray[i].w, 2766 h: resImgSizeArray[i].h 2767 }); 2768 } 2769 2770 return arrayResultImages; 2771 }; 2772 2773 this.getResultImageDataBlob = function () { 2774 var temp_canvas, 2775 _p = $q.defer(); 2776 temp_canvas = renderTempCanvas(this.getResultImageSize(), theArea.getCenterPoint()); 2777 if (resImgQuality !== null) { 2778 temp_canvas.toBlob(function (blob) { 2779 _p.resolve(blob); 2780 }, resImgFormat, resImgQuality); 2781 } else { 2782 temp_canvas.toBlob(function (blob) { 2783 _p.resolve(blob); 2784 }, resImgFormat); 2785 } 2786 2787 return _p.promise; 2788 }; 2789 2790 this.getAreaCoords = function () { 2791 return theArea.getSize(); 2792 }; 2793 2794 this.getArea = function () { 2795 return theArea; 2796 }; 2797 2798 this.setNewImageSource = function (imageSource) { 2799 image = null; 2800 resetCropHost(); 2801 if (imageSource) { 2802 var newImage = new Image(); 2803 newImage.onload = function () { 2804 events.trigger('load-done'); 2805 2806 cropEXIF.getData(newImage, function () { 2807 var orientation = cropEXIF.getTag(newImage, 'Orientation'); 2808 2809 if ([3, 6, 8].indexOf(orientation) > -1) { 2810 var canvas = document.createElement('canvas'), 2811 ctx = canvas.getContext('2d'), 2812 cw = newImage.width, 2813 ch = newImage.height, 2814 cx = 0, 2815 cy = 0, 2816 deg = 0, 2817 rw = 0, 2818 rh = 0; 2819 rw = cw; 2820 rh = ch; 2821 switch (orientation) { 2822 case 3: 2823 cx = -newImage.width; 2824 cy = -newImage.height; 2825 deg = 180; 2826 break; 2827 case 6: 2828 cw = newImage.height; 2829 ch = newImage.width; 2830 cy = -newImage.height; 2831 rw = ch; 2832 rh = cw; 2833 deg = 90; 2834 break; 2835 case 8: 2836 cw = newImage.height; 2837 ch = newImage.width; 2838 cx = -newImage.width; 2839 rw = ch; 2840 rh = cw; 2841 deg = 270; 2842 break; 2843 } 2844
2845 //// canvas.toDataURL will only work if the canvas isn't too large. Resize to 1000px. 2846 var maxWorH = 1000; 2847 if (cw > maxWorH || ch > maxWorH) { 2848 var p = 0; 2849 if (cw > maxWorH) { 2850 p = (maxWorH) / cw; 2851 cw = maxWorH; 2852 ch = p * ch; 2853 } else if (ch > maxWorH) { 2854 p = (maxWorH) / ch; 2855 ch = maxWorH; 2856 cw = p * cw; 2857 } 2858 2859 cy = p * cy; 2860 cx = p * cx; 2861 rw = p * rw; 2862 rh = p * rh; 2863 } 2864 2865 canvas.width = cw; 2866 canvas.height = ch; 2867 ctx.rotate(deg * Math.PI / 180); 2868 ctx.drawImage(newImage, cx, cy, rw, rh); 2869 2870 image = new Image(); 2871 image.onload = function () { 2872 resetCropHost(); 2873 events.trigger('image-updated'); 2874 }; 2875 2876 image.src = canvas.toDataURL(resImgFormat); 2877 } else { 2878 image = newImage; 2879 events.trigger('image-updated'); 2880 } 2881 resetCropHost(); 2882 }); 2883 }; 2884 newImage.onerror = function () { 2885 events.trigger('load-error'); 2886 }; 2887 events.trigger('load-start'); 2888 if (imageSource instanceof window.Blob) { 2889 newImage.src = URL.createObjectURL(imageSource); 2890 } else { 2891 if (imageSource.substring(0, 4).toLowerCase() === 'http' || imageSource.substring(0, 2) === '//') { 2892 newImage.crossOrigin = 'anonymous'; 2893 } 2894 newImage.src = imageSource; 2895 } 2896 } 2897 }; 2898 2899 this.setMaxDimensions = function (width, height) { 2900 maxCanvasDims = [width, height]; 2901 2902 if (image !== null) { 2903 var curWidth = ctx.canvas.width, 2904 curHeight = ctx.canvas.height; 2905 var canvasDims = [0, 0]; 2906 if (scalemode === 'fit') { 2907 var results = fitCanvasDims(image.width, image.height); 2908 canvasDims = results.dims; 2909 var margins = results.margins; 2910 elCanvas.css({ 2911 'margin-left': margins.left + 'px', 2912 'margin-top': margins.top + 'px' 2913 }); 2914 } 2915 else { 2916 var imageDims = [image.width, image.height], 2917 imageRatio = image.width / image.height; 2918 canvasDims = imageDims; 2919 2920 if (canvasDims[0] > maxCanvasDims[0]) { 2921 canvasDims[0] = maxCanvasDims[0]; 2922 canvasDims[1] = canvasDims[0] / imageRatio; 2923 } else if (canvasDims[0] < minCanvasDims[0]) { 2924 canvasDims[0] = minCanvasDims[0]; 2925 canvasDims[1] = canvasDims[0] / imageRatio; 2926 } 2927 if (scalemode === 'fixed-height' && canvasDims[1] > maxCanvasDims[1]) { 2928 canvasDims[1] = maxCanvasDims[1]; 2929 canvasDims[0] = canvasDims[1] * imageRatio; 2930 } else if (canvasDims[1] < minCanvasDims[1]) { 2931 canvasDims[1] = minCanvasDims[1]; 2932 canvasDims[0] = canvasDims[1] * imageRatio; 2933 } 2934 } 2935 elCanvas.prop('width', canvasDims[0]).prop('height', canvasDims[1]); 2936 2937 if (scalemode === 'fixed-height') { 2938 elCanvas.css({ 2939 'margin-left': -canvasDims[0] / 2 + 'px', 2940 'margin-top': -canvasDims[1] / 2 + 'px' 2941 }); 2942 } 2943 2944 var ratioNewCurWidth = curWidth ? ctx.canvas.width / curWidth : 0, 2945 ratioNewCurHeight = curHeight ? ctx.canvas.height / curHeight : 0, 2946 ratioMin = Math.min(ratioNewCurWidth, ratioNewCurHeight); 2947 2948 //TODO: use top left corner point 2949 var center = theArea.getCenterPoint(); 2950 theArea.setSize({ 2951 w: theArea.getSize().w * ratioMin, 2952 h: theArea.getSize().h * ratioMin 2953 }); 2954 theArea.setCenterPoint({ 2955 x: center.x * ratioNewCurWidth, 2956 y: center.y * ratioNewCurHeight 2957 }); 2958 2959 } else { 2960 elCanvas.prop('width', 0).prop('height', 0).css({ 2961 'margin-top': 0 2962 }); 2963 } 2964 2965 drawScene(); 2966 2967 }; 2968 2969 this.setAreaMinSize = function (size) { 2970 if (angular.isUndefined(size)) { 2971 return; 2972 } else if (typeof size === 'number' || typeof size === 'string') { 2973 size = { 2974 w: parseInt(parseInt(size), 10), 2975 h: parseInt(parseInt(size), 10) 2976 }; 2977 } else { 2978 size = { 2979 w: parseInt(size.w, 10), 2980 h: parseInt(size.h, 10) 2981 }; 2982 } 2983 if (!isNaN(size.w) && !isNaN(size.h)) { 2984 theArea.setMinSize(size); 2985 drawScene(); 2986 } 2987 }; 2988 2989 this.setAreaMinRelativeSize = function (size) { 2990 if (image === null || angular.isUndefined(size)) { 2991 return; 2992 } 2993 2994 var canvasSize = theArea.getCanvasSize(); 2995 2996 if (typeof size === 'number' || typeof size === 'string') { 2997 areaMinRelativeSize = { 2998 w: size, 2999 h: size 3000 }; 3001 size = {
3002 w: canvasSize.w / (image.width / parseInt(parseInt(size), 10)), 3003 h: canvasSize.h / (image.height / parseInt(parseInt(size), 10)) 3004 }; 3005 } else { 3006 areaMinRelativeSize = size; 3007 size = { 3008 w: canvasSize.w / (image.width / parseInt(parseInt(size.w), 10)), 3009 h: canvasSize.h / (image.height / parseInt(parseInt(size.h), 10)) 3010 }; 3011 } 3012 3013 if (!isNaN(size.w) && !isNaN(size.h)) { 3014 theArea.setMinSize(size); 3015 drawScene(); 3016 } 3017 }; 3018 3019 this.setAreaInitSize = function (size) { 3020 if (angular.isUndefined(size)) { 3021 return; 3022 } else if (typeof size === 'number' || typeof size === 'string') { 3023 size = { 3024 w: parseInt(parseInt(size), 10), 3025 h: parseInt(parseInt(size), 10) 3026 }; 3027 } else { 3028 size = { 3029 w: parseInt(size.w, 10), 3030 h: parseInt(size.h, 10) 3031 }; 3032 } 3033 if (!isNaN(size.w) && !isNaN(size.h)) { 3034 theArea.setInitSize(size); 3035 drawScene(); 3036 } 3037 }; 3038 3039 this.setAreaInitCoords = function (coords) { 3040 if (angular.isUndefined(coords)) { 3041 return; 3042 } else { 3043 coords = { 3044 x: parseInt(coords.x, 10), 3045 y: parseInt(coords.y, 10) 3046 }; 3047 } 3048 if (!isNaN(coords.x) && !isNaN(coords.y)) { 3049 theArea.setInitCoords(coords); 3050 drawScene(); 3051 } 3052 }; 3053 3054 this.setMaxCanvasDimensions = function (maxCanvasDimensions) { 3055 if (!angular.isUndefined(maxCanvasDimensions)) { 3056 var newMaxCanvasDims = []; 3057 if (typeof maxCanvasDimensions === 'number' || typeof maxCanvasDimensions === 'string') { 3058 newMaxCanvasDims = [ 3059 parseInt(parseInt(maxCanvasDimensions), 10), 3060 parseInt(parseInt(maxCanvasDimensions), 10) 3061 ]; 3062 } else { 3063 newMaxCanvasDims = [ 3064 parseInt(maxCanvasDimensions.w, 10), 3065 parseInt(maxCanvasDimensions.h, 10) 3066 ]; 3067 } 3068 if ((!isNaN(newMaxCanvasDims[0]) && 3069 newMaxCanvasDims[0] > 0 && 3070 newMaxCanvasDims[0] > minCanvasDims[0]) && 3071 (!isNaN(newMaxCanvasDims[1]) && 3072 newMaxCanvasDims[1] > 0 && 3073 newMaxCanvasDims[1] > minCanvasDims[1])) { 3074 maxCanvasDims = newMaxCanvasDims; 3075 } 3076 } 3077 }; 3078 3079 this.setMinCanvasDimensions = function (minCanvasDimensions) { 3080 if (!angular.isUndefined(minCanvasDimensions)) { 3081 var newMinCanvasDims = []; 3082 if (typeof minCanvasDimensions === 'number' || typeof minCanvasDimensions === 'string') { 3083 newMinCanvasDims = [ 3084 parseInt(parseInt(minCanvasDimensions), 10), 3085 parseInt(parseInt(minCanvasDimensions), 10) 3086 ]; 3087 } else { 3088 newMinCanvasDims = [ 3089 parseInt(minCanvasDimensions.w, 10), 3090 parseInt(minCanvasDimensions.h, 10) 3091 ]; 3092 } 3093 if ((!isNaN(newMinCanvasDims[0]) && 3094 newMinCanvasDims[0] >= 0) && 3095 (!isNaN(newMinCanvasDims[1]) && 3096 newMinCanvasDims[1] >= 0)) { 3097 minCanvasDims = newMinCanvasDims; 3098 } 3099 } 3100 }; 3101 3102 this.setScalemode = function (value) { 3103 scalemode = value; 3104 }; 3105
3106 this.getScalemode = function () { 3107 return scalemode; 3108 }; 3109 3110 this.getResultImageSize = function () { 3111 if (resImgSize === 'selection') { 3112 return theArea.getSize(); 3113 } 3114 3115 if (resImgSize === 'max') { 3116 // We maximize the rendered size 3117 var zoom = 1; 3118 if (image && ctx && ctx.canvas) { 3119 zoom = image.width / ctx.canvas.width; 3120 } 3121 var size = { 3122 w: zoom * theArea.getSize().w, 3123 h: zoom * theArea.getSize().h 3124 }; 3125 3126 if (areaMinRelativeSize) { 3127 if (size.w < areaMinRelativeSize.w) { 3128 size.w = areaMinRelativeSize.w; 3129 } 3130 if (size.h < areaMinRelativeSize.h) { 3131 size.h = areaMinRelativeSize.h; 3132 } 3133 } 3134 3135 return size; 3136 } 3137 3138 return resImgSize; 3139 }; 3140 3141 this.setResultImageSize = function (size) { 3142 if (angular.isArray(size)) { 3143 resImgSizeArray = size.slice(); 3144 size = { 3145 w: parseInt(size[0].w, 10), 3146 h: parseInt(size[0].h, 10) 3147 }; 3148 return; 3149 } 3150 if (angular.isUndefined(size)) { 3151 return; 3152 } 3153 //allow setting of size to "selection" for mirroring selection's dimensions 3154 if (angular.isString(size)) { 3155 resImgSize = size; 3156 return; 3157 } 3158 //allow scalar values for square-like selection shapes 3159 if (angular.isNumber(size)) { 3160 size = parseInt(size, 10); 3161 size = { 3162 w: size, 3163 h: size 3164 }; 3165 } 3166 size = { 3167 w: parseInt(size.w, 10), 3168 h: parseInt(size.h, 10) 3169 }; 3170 if (!isNaN(size.w) && !isNaN(size.h)) { 3171 resImgSize = size; 3172 drawScene(); 3173 } 3174 }; 3175 3176 this.setResultImageFormat = function (format) { 3177 resImgFormat = format; 3178 }; 3179 3180 this.setResultImageQuality = function (quality) { 3181 quality = parseFloat(quality); 3182 if (!isNaN(quality) && quality >= 0 && quality <= 1) { 3183 resImgQuality = quality; 3184 } 3185 }; 3186 3187 // returns a string of the selection area's type 3188 this.getAreaType = function () { 3189 return theArea.getType(); 3190 }; 3191 3192 this.setAreaType = function (type) { 3193 var center = theArea.getCenterPoint(); 3194 var curSize = theArea.getSize(), 3195 curMinSize = theArea.getMinSize(), 3196 curX = center.x, 3197 curY = center.y; 3198 3199 var AreaClass = CropAreaCircle; 3200 if (type === 'square') { 3201 AreaClass = CropAreaSquare; 3202 } else if (type === 'rectangle') { 3203 AreaClass = CropAreaRectangle; 3204 } 3205 theArea = new AreaClass(ctx, events); 3206 theArea.setMinSize(curMinSize); 3207 theArea.setSize(curSize); 3208 if (type === 'square' || type === 'circle') { 3209 forceAspectRatio = true; 3210 theArea.setForceAspectRatio(true); 3211 } else { 3212 forceAspectRatio = false; 3213 theArea.setForceAspectRatio(false); 3214 } 3215 3216 //TODO: use top left point 3217 theArea.setCenterPoint({ 3218 x: curX, 3219 y: curY 3220 }); 3221 3222 // resetCropHost(); 3223 if (image !== null) { 3224 theArea.setImage(image); 3225 } 3226 3227 drawScene(); 3228 }; 3229 3230 this.getDominantColor = function (uri) { 3231 var imageDC = new Image(), 3232 colorThief = new ColorThief(), 3233 dominantColor = null, 3234 _p = $q.defer(); 3235 imageDC.src = uri; 3236 imageDC.onload = function () { 3237 dominantColor = colorThief.getColor(imageDC); 3238 _p.resolve(dominantColor); 3239 }; 3240 3241 return _p.promise; 3242 }; 3243 3244 this.getPalette = function (uri) { 3245 var imageDC = new Image(), 3246 colorThief = new ColorThief(), 3247 palette = null, 3248 _p = $q.defer(); 3249 imageDC.src = uri; 3250 imageDC.onload = function () { 3251 palette = colorThief.getPalette(imageDC, colorPaletteLength); 3252 _p.resolve(palette); 3253 }; 3254 3255 return _p.promise; 3256 }; 3257 3258 this.setPaletteColorLength = function (lg) { 3259 colorPaletteLength = lg; 3260 }; 3261 3262 this.setAspect = function (aspect) { 3263 isAspectRatio = true; 3264 theArea.setAspect(aspect); 3265 var minSize = theArea.getMinSize(); 3266 minSize.w = minSize.h * aspect; 3267 theArea.setMinSize(minSize); 3268 var size = theArea.getSize(); 3269 size.w = size.h * aspect; 3270 theArea.setSize(size); 3271 }; 3272 3273 /* Life Cycle begins */ 3274 3275 // Init Context var 3276 ctx = elCanvas[0].getContext('2d'); 3277 3278 // Init CropArea 3279 theArea = new CropAreaCircle(ctx, events); 3280 3281 // Init Mouse Event Listeners 3282 $document.on('mousemove', onMouseMove); 3283 elCanvas.on('mousedown', onMouseDown);
3284 $document.on('mouseup', onMouseUp); 3285 3286 // Init Touch Event Listeners 3287 $document.on('touchmove', onMouseMove); 3288 elCanvas.on('touchstart', onMouseDown); 3289 $document.on('touchend', onMouseUp); 3290 3291 elCanvas.prop('tabindex', '0'); 3292 3293 elCanvas.on('keydown', onKeyDown); 3294 3295 // CropHost Destructor 3296 this.destroy = function () { 3297 $document.off('mousemove', onMouseMove); 3298 elCanvas.off('mousedown', onMouseDown); 3299 $document.off('mouseup', onMouseUp); 3300 3301 $document.off('touchmove', onMouseMove); 3302 elCanvas.off('touchstart', onMouseDown); 3303 $document.off('touchend', onMouseUp); 3304 3305 elCanvas.off('keydown', onKeyDown); 3306 3307 elCanvas.remove(); 3308 }; 3309 }; 3310}]); 3311 3312angular.module('uiCropper').factory('cropPubSub', [function() { 3313 return function() { 3314 var events = {}; 3315 // Subscribe 3316 this.on = function(names, handler) { 3317 names.split(' ').forEach(function(name) { 3318 if (!events[name]) { 3319 events[name] = []; 3320 } 3321 events[name].push(handler); 3322 }); 3323 return this; 3324 }; 3325 // Publish 3326 this.trigger = function(name, args) { 3327 angular.forEach(events[name], function(handler) { 3328 handler.call(null, args); 3329 }); 3330 return this; 3331 }; 3332 }; 3333}]); 3334 3335angular.module('uiCropper').directive('uiCropper', ['$timeout', 'cropHost', 'cropPubSub', function ($timeout, CropHost, CropPubSub) { 3336 return { 3337 restrict: 'E', 3338 scope: { 3339 image: '=', 3340 resultImage: '=', 3341 resultArrayImage: '=?', 3342 resultBlob: '=?', 3343 urlBlob: '=?', 3344 chargement: '=?', 3345 cropject: '=?', 3346 maxCanvasDimensions: '=?', 3347 minCanvasDimensions: '=?', 3348 canvasScalemode: '@?', /* String. If set to 'full-width' the directive uses all width available */ 3349 /* and the canvas expands in height as much as it need to maintain the aspect ratio */ 3350 /* if set to 'fixed-height', the directive is restricted by a parent element in height */ 3351 /* set to 'fit' to fit image into ui-cropper container */ 3352 3353 changeOnFly: '=?', 3354 disableKeyboardAccess: '=?', 3355 allowPropagation: '=?', 3356 liveView: '=?', 3357 initMaxArea: '=?', 3358 areaCoords: '=?', 3359 areaType: '@', 3360 areaMinSize: '=?', 3361 areaInitSize: '=?', 3362 areaInitCoords: '=?', 3363 areaInitIsRelativeToImage: '=?', /* Boolean: If true the areaInitCoords and areaInitSize is scaled according to canvas size. */ 3364 /* No matter how big/small the canvas is, the resultImage remains the same */ 3365 /* Example: areaInitCoords are {x: 100, y: 100}, areaInitSize {w: 100, h: 100} */ 3366 /* Image is 1000x1000 3367 /* if canvas is 500x500 Crop coordinates will be x: 50, y: 50, w: 50, h: 50 */ 3368 /* if canvas is 100x100 crop coordinates will be x: 10, y: 10, w: 10, h: 10 */ 3369 areaMinRelativeSize: '=?', 3370 resultImageSize: '=?', 3371 resultImageFormat: '@', 3372 resultImageQuality: '=?', 3373 3374 aspectRatio: '=?', 3375 allowCropResizeOnCorners: '=?', 3376 3377 dominantColor: '=?', 3378 paletteColor: '=?', 3379 paletteColorLength: '=?', 3380 disableCrop: '=?', 3381 3382 onChange: '&', 3383 onLoadBegin: '&', 3384 onLoadDone: '&', 3385 onLoadError: '&' 3386 }, 3387 template: '<canvas></canvas>', 3388 controller: ['$scope', function ($scope) { 3389 $scope.events = new CropPubSub(); 3390 }], 3391 link: function (scope, element) { 3392 // Init Events Manager 3393 var events = scope.events; 3394 3395 // Init Crop Host 3396 var cropHost = new CropHost(element.find('canvas'), { 3397 disableKeyboardAccess: scope.disableKeyboardAccess, 3398 allowPropagation: scope.allowPropagation 3399 }, events); 3400 3401 if (scope.canvasScalemode) { 3402 cropHost.setScalemode(scope.canvasScalemode); 3403 } else { 3404 cropHost.setScalemode('fixed-height'); 3405 } 3406 3407 element.addClass(cropHost.getScalemode()); 3408 3409 // Store Result Image to check if it's changed 3410 var storedResultImage; 3411 3412 var updateAreaCoords = function (scope) { 3413 scope.areaCoords = cropHost.getAreaCoords(); 3414 }; 3415 3416 var updateResultImage = function (scope, force, callback) { 3417 if (scope.image !== '' && (!scope.liveView.block || force)) { 3418 var resultImageObj = cropHost.getResultImage(); 3419 var resultImage; 3420 if (angular.isArray(resultImageObj)) { 3421 resultImage = resultImageObj[0].dataURI; 3422 scope.resultArrayImage = resultImageObj; 3423 } else { 3424 resultImage = resultImageObj.dataURI; 3425 } 3426 3427 var urlCreator = window.URL || window.webkitURL; 3428 if (storedResultImage !== resultImage) { 3429 storedResultImage = resultImage; 3430 scope.resultImage = resultImage; 3431 if (scope.liveView.callback) { 3432 scope.liveView.callback(resultImage); 3433 } 3434 if (callback) { 3435 callback(resultImage); 3436 } 3437 cropHost.getResultImageDataBlob().then(function (blob) { 3438 scope.resultBlob = blob; 3439 scope.urlBlob = blob ? urlCreator.createObjectURL(blob) : null; 3440 }); 3441 3442 if (scope.resultImage) { 3443 cropHost.getDominantColor(scope.resultImage).then(function (dominantColor) { 3444 scope.dominantColor = dominantColor; 3445 }); 3446 cropHost.getPalette(scope.resultImage).then(function (palette) { 3447 scope.paletteColor = palette; 3448 }); 3449 } 3450 3451 updateAreaCoords(scope); 3452 scope.onChange({ 3453 $dataURI: scope.resultImage 3454 }); 3455 } 3456 } 3457 }; 3458 3459 if (scope.liveView && typeof scope.liveView.block === 'boolean') { 3460 scope.liveView.render = function (callback) { 3461 updateResultImage(scope, true, callback); 3462 }; 3463 } else { 3464 scope.liveView = {block: false}; 3465 } 3466 3467 var updateCropject = function (scope) { 3468 var areaCoords = cropHost.getAreaCoords(); 3469 3470 var dimRatio = { 3471 x: cropHost.getArea().getImage().width / cropHost.getArea().getCanvasSize().w, 3472 y: cropHost.getArea().getImage().height / cropHost.getArea().getCanvasSize().h 3473 }; 3474 3475 scope.cropject = { 3476 canvasSize: cropHost.getArea().getCanvasSize(), 3477 areaCoords: areaCoords, 3478 cropWidth: areaCoords.w, 3479 cropHeight: areaCoords.h, 3480 cropTop: areaCoords.y, 3481 cropLeft: areaCoords.x, 3482 cropImageWidth: Math.round(areaCoords.w * dimRatio.x), 3483 cropImageHeight: Math.round(areaCoords.h * dimRatio.y), 3484 cropImageTop: Math.round(areaCoords.y * dimRatio.y), 3485 cropImageLeft: Math.round(areaCoords.x * dimRatio.x) 3486 }; 3487 }; 3488 3489 // Wrapper to safely exec functions within $apply on a running $digest cycle 3490 var fnSafeApply = function (fn) { 3491 return function () { 3492 $timeout(function () { 3493 scope.$apply(function (scope) { 3494 fn(scope); 3495 }); 3496 }); 3497 }; 3498 }; 3499
3500 // Will get the users language settings, and return the appropriate loading text 3501 var printLoadMsg = function () { 3502 var language = window.navigator.userLanguage || window.navigator.language; 3503 3504 switch (language) { 3505 case 'nl': 3506 case 'nl_NL': 3507 return 'Aan het laden'; 3508 3509 case 'fr': 3510 case 'fr-FR': 3511 return 'Chargement'; 3512 3513 case 'es': 3514 case 'es-ES': 3515 return 'Cargando'; 3516 3517 case 'ca': 3518 case 'ca-ES': 3519 return 'Càrrega'; 3520 3521 case 'de': 3522 case 'de-DE': 3523 return 'Laden'; 3524 3525 case 'pt': 3526 case 'pt-BR': 3527 return 'Carregando'; 3528 3529 default: 3530 return 'Loading'; 3531 } 3532 }; 3533 3534 if (!scope.chargement) { 3535 scope.chargement = printLoadMsg(); 3536 } 3537 var displayLoading = function () { 3538 element.append('<div class="loading"><span>' + scope.chargement + '...</span></div>'); 3539 }; 3540 3541 // Setup CropHost Event Handlers 3542 events 3543 .on('load-start', fnSafeApply(function (scope) { 3544 scope.onLoadBegin({}); 3545 })) 3546 .on('load-done', fnSafeApply(function (scope) { 3547 var children = element.children(); 3548 angular.forEach(children, function (child) { 3549 if (angular.element(child).hasClass('loading')) { 3550 angular.element(child).remove(); 3551 } 3552 }); 3553 updateCropject(scope); 3554 scope.onLoadDone({}); 3555 })) 3556 .on('load-error', fnSafeApply(function (scope) { 3557 scope.onLoadError({}); 3558 })) 3559 .on('area-move area-resize', fnSafeApply(function (scope) { 3560 if (scope.changeOnFly === true) { 3561 updateResultImage(scope); 3562 } 3563 updateCropject(scope); 3564 })) 3565 .on('image-updated', fnSafeApply(function (scope) { 3566 cropHost.setAreaMinRelativeSize(scope.areaMinRelativeSize); 3567 })) 3568 .on('area-move-end area-resize-end image-updated', fnSafeApply(function (scope) { 3569 updateResultImage(scope); 3570 updateCropject(scope); 3571 })); 3572 3573 // Sync CropHost with Directive's options 3574 scope.$watch('image', function (newVal) { 3575 // reset the original size and position to 0 3576 // it's mandatory because if not reset the size of the crop area won't maximise when the image was replaced 3577 var area = cropHost.getArea(); 3578 if (area) { 3579 cropHost.getArea()._size = { x: 0, y: 0, w: 0, h: 0 }; 3580 } 3581 if (newVal) { 3582 displayLoading(); 3583 } 3584 // cancel timeout if necessary 3585 if (scope.timeout !== null) { 3586 $timeout.cancel(scope.timeout); 3587 } 3588 scope.timeout = $timeout(function () { 3589 scope.timeout = null; 3590 cropHost.setInitMax(scope.initMaxArea); 3591 cropHost.setNewImageSource(scope.image); 3592 }, 100); 3593 }); 3594 scope.$watch('areaType', function () { 3595 cropHost.setAreaType(scope.areaType); 3596 updateResultImage(scope); 3597 }); 3598 scope.$watch('areaMinSize', function () { 3599 cropHost.setAreaMinSize(scope.areaMinSize); 3600 updateResultImage(scope); 3601 }); 3602 scope.$watch('areaMinRelativeSize', function () { 3603 if (scope.image !== '') { 3604 cropHost.setAreaMinRelativeSize(scope.areaMinRelativeSize); 3605 updateResultImage(scope); 3606 } 3607 }); 3608 scope.$watch('areaInitSize', function () { 3609 cropHost.setAreaInitSize(scope.areaInitSize); 3610 updateResultImage(scope); 3611 }); 3612 scope.$watch('areaInitCoords', function () { 3613 cropHost.setAreaInitCoords(scope.areaInitCoords);
3614 cropHost.areaInitIsRelativeToImage = scope.areaInitIsRelativeToImage; 3615 updateResultImage(scope); 3616 }); 3617 scope.$watch('maxCanvasDimensions', function () { 3618 cropHost.setMaxCanvasDimensions(scope.maxCanvasDimensions); 3619 }); 3620 scope.$watch('minCanvasDimensions', function () { 3621 cropHost.setMinCanvasDimensions(scope.minCanvasDimensions); 3622 }); 3623 scope.$watch('resultImageFormat', function () { 3624 cropHost.setResultImageFormat(scope.resultImageFormat); 3625 updateResultImage(scope); 3626 }); 3627 scope.$watch('resultImageQuality', function () { 3628 cropHost.setResultImageQuality(scope.resultImageQuality); 3629 updateResultImage(scope); 3630 }); 3631 scope.$watch('resultImageSize', function () { 3632 cropHost.setResultImageSize(scope.resultImageSize); 3633 updateResultImage(scope); 3634 }); 3635 scope.$watch('paletteColorLength', function () { 3636 cropHost.setPaletteColorLength(scope.paletteColorLength); 3637 }); 3638 scope.$watch('aspectRatio', function () { 3639 if (typeof scope.aspectRatio === 'string' && scope.aspectRatio !== '') { 3640 scope.aspectRatio = parseFloat(scope.aspectRatio); 3641 } 3642 if (scope.aspectRatio) { 3643 cropHost.setAspect(scope.aspectRatio); 3644 } 3645 }); 3646 scope.$watch('allowCropResizeOnCorners', function () { 3647 if (scope.allowCropResizeOnCorners) { 3648 cropHost.setAllowCropResizeOnCorners(scope.allowCropResizeOnCorners); 3649 } 3650 }); 3651 3652 scope.$watch('disableCrop', function () { 3653 cropHost.setDisableCrop(scope.disableCrop); 3654 }); 3655 3656 // Update CropHost dimensions when the directive element is resized 3657 scope.$watch( 3658 function () { 3659 if (cropHost.getScalemode() === 'fit') { 3660 return [element[0].clientWidth, element[0].clientHeight]; 3661 } 3662 if (cropHost.getScalemode() === 'fixed-height') { 3663 return [element[0].clientWidth, element[0].clientHeight]; 3664 } 3665 if (cropHost.getScalemode() === 'full-width') { 3666 return element[0].clientWidth; 3667 } 3668 }, 3669 function (value) { 3670 3671 if (cropHost.getScalemode() === 'fit') { 3672 cropHost.setMaxDimensions(value[0], value[1]); 3673 updateResultImage(scope); 3674 } 3675 if (cropHost.getScalemode() === 'fixed-height') { 3676 if (value[0] > 0 && value[1] > 0) { 3677 cropHost.setMaxDimensions(value[0], value[1]); 3678 updateResultImage(scope); 3679 } 3680 } 3681 if (cropHost.getScalemode() === 'full-width') { 3682 if (value > 0) { 3683 cropHost.setMaxDimensions(value); 3684 } 3685 } 3686 }, 3687 true 3688 ); 3689 3690 // Destroy CropHost Instance when the directive is destroying 3691 scope.$on('$destroy', function () { 3692 cropHost.destroy(); 3693 }); 3694 } 3695 }; 3696}]); 3697 3698}()); 3699/* canvas-toBlob.js 3700 * A canvas.toBlob() implementation. 3701 * 2013-12-27 3702 * 3703 * By Eli Grey, http://eligrey.com and Devin Samarin, https://github.com/eboyjr 3704 * License: X11/MIT 3705 * See https://github.com/eligrey/canvas-toBlob.js/blob/master/LICENSE.md 3706 */ 3707 3708/*global self */ 3709/*jslint bitwise: true, regexp: true, confusion: true, es5: true, vars: true, white: true, 3710 plusplus: true */ 3711 3712/*! @source http://purl.eligrey.com/github/canvas-toBlob.js/blob/master/canvas-toBlob.js */ 3713 3714(function(view) { 3715 "use strict"; 3716 var 3717 Uint8Array = view.Uint8Array, 3718 HTMLCanvasElement = view.HTMLCanvasElement, 3719 canvas_proto = HTMLCanvasElement && HTMLCanvasElement.prototype, 3720 is_base64_regex = /\s*;\s*base64\s*(?:;|$)/i, 3721 to_data_url = "toDataURL", 3722 base64_ranks, decode_base64 = function(base64) { 3723 var 3724 len = base64.length, 3725 buffer = new Uint8Array(len / 4 * 3 | 0), 3726 i = 0, 3727 outptr = 0, 3728 last = [0, 0], 3729 state = 0, 3730 save = 0, 3731 rank, code, undef; 3732 while (len--) { 3733 code = base64.charCodeAt(i++);
3734 rank = base64_ranks[code - 43]; 3735 if (rank !== 255 && rank !== undef) { 3736 last[1] = last[0]; 3737 last[0] = code; 3738 save = (save << 6) | rank; 3739 state++; 3740 if (state === 4) { 3741 buffer[outptr++] = save >>> 16; 3742 if (last[1] !== 61 /* padding character */ ) { 3743 buffer[outptr++] = save >>> 8; 3744 } 3745 if (last[0] !== 61 /* padding character */ ) { 3746 buffer[outptr++] = save; 3747 } 3748 state = 0; 3749 } 3750 } 3751 } 3752 // 2/3 chance there's going to be some null bytes at the end, but that 3753 // doesn't really matter with most image formats. 3754 // If it somehow matters for you, truncate the buffer up outptr. 3755 return buffer; 3756 }; 3757 if (Uint8Array) { 3758 base64_ranks = new Uint8Array([ 3759 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, 0, -1, -1, -1, 0, 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, -1, -1, -1, -1, -1, -1, 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 3760 ]); 3761 } 3762 if (HTMLCanvasElement && !canvas_proto.toBlob) { 3763 canvas_proto.toBlob = function(callback, type /*, ...args*/ ) { 3764 if (!type) { 3765 type = "image/png"; 3766 } 3767 if (this.mozGetAsFile) { 3768 callback(this.mozGetAsFile("canvas", type)); 3769 return; 3770 } 3771 if (this.msToBlob && /^\s*image\/png\s*(?:$|;)/i.test(type)) { 3772 callback(this.msToBlob()); 3773 return; 3774 } 3775 3776 var 3777 args = Array.prototype.slice.call(arguments, 1), 3778 dataURI = this[to_data_url].apply(this, args), 3779 header_end = dataURI.indexOf(","), 3780 data = dataURI.substring(header_end + 1), 3781 is_base64 = is_base64_regex.test(dataURI.substring(0, header_end)), 3782 blob; 3783 if (Blob.fake) { 3784 // no reason to decode a data: URI that's just going to become a data URI again 3785 blob = new Blob 3786 if (is_base64) { 3787 blob.encoding = "base64"; 3788 } else { 3789 blob.encoding = "URI"; 3790 } 3791 blob.data = data; 3792 blob.size = data.length; 3793 } else if (Uint8Array) { 3794 if (is_base64) { 3795 blob = new Blob([decode_base64(data)], { 3796 type: type 3797 }); 3798 } else { 3799 blob = new Blob([decodeURIComponent(data)], { 3800 type: type 3801 }); 3802 } 3803 } 3804 if (typeof callback !== 'undefined') { 3805 callback(blob); 3806 } 3807 }; 3808 3809 if (canvas_proto.toDataURLHD) { 3810 canvas_proto.toBlobHD = function() { 3811 to_data_url = "toDataURLHD"; 3812 var blob = this.toBlob(); 3813 to_data_url = "toDataURL"; 3814 return blob; 3815 } 3816 } else { 3817 canvas_proto.toBlobHD = canvas_proto.toBlob; 3818 } 3819 } 3820}(typeof self !== "undefined" && self || typeof window !== "undefined" && window || this.content || this)); 3821 3822/*! 3823 * Color Thief v2.0 3824 * by Lokesh Dhakar - http://www.lokeshdhakar.com 3825 * 3826 * Thanks 3827 * ------ 3828 * Nick Rabinowitz - For creating quantize.js. 3829 * John Schulz - For clean up and optimization. @JFSIII 3830 * Nathan Spady - For adding drag and drop support to the demo page. 3831 * 3832 * License 3833 * ------- 3834 * Copyright 2011, 2015 Lokesh Dhakar 3835 * Released under the MIT license 3836 * https://raw.githubusercontent.com/lokesh/color-thief/master/LICENSE 3837 * 3838 */ 3839(function() { 3840 /*! 3841 * Color Thief v2.0 3842 * by Lokesh Dhakar - http://www.lokeshdhakar.com 3843 * 3844 * Thanks 3845 * ------ 3846 * Nick Rabinowitz - For creating quantize.js. 3847 * John Schulz - For clean up and optimization. @JFSIII 3848 * Nathan Spady - For adding drag and drop support to the demo page. 3849 * 3850 * License 3851 * ------- 3852 * Copyright 2011, 2015 Lokesh Dhakar 3853 * Released under the MIT license 3854 * https://raw.githubusercontent.com/lokesh/color-thief/master/LICENSE 3855 * 3856 */ 3857 3858 /* 3859 CanvasImage Class 3860 Class that wraps the html image element and canvas. 3861 It also simplifies some of the canvas context manipulation 3862 with a set of helper functions. 3863 */ 3864 var CanvasImage = function(image) { 3865 this.canvas = document.createElement('canvas'); 3866 this.context = this.canvas.getContext('2d'); 3867 3868 document.body.appendChild(this.canvas); 3869 3870 this.width = this.canvas.width = image.width; 3871 this.height = this.canvas.height = image.height; 3872 3873 this.context.drawImage(image, 0, 0, this.width, this.height); 3874 }; 3875 3876 CanvasImage.prototype.clear = function() { 3877 this.context.clearRect(0, 0, this.width, this.height); 3878 }; 3879 3880 CanvasImage.prototype.update = function(imageData) { 3881 this.context.putImageData(imageData, 0, 0); 3882 }; 3883 3884 CanvasImage.prototype.getPixelCount = function() { 3885 return this.width * this.height; 3886 }; 3887 3888 CanvasImage.prototype.getImageData = function() { 3889 return this.context.getImageData(0, 0, this.width, this.height); 3890 }; 3891 3892 CanvasImage.prototype.removeCanvas = function() { 3893 this.canvas.parentNode.removeChild(this.canvas); 3894 }; 3895 3896 var ColorThief = function() {}; 3897 window.ColorThief = ColorThief; 3898 3899 /*
3900 * getColor(sourceImage[, quality]) 3901 * returns {r: num, g: num, b: num} 3902 * 3903 * Use the median cut algorithm provided by quantize.js to cluster similar 3904 * colors and return the base color from the largest cluster. 3905 * 3906 * Quality is an optional argument. It needs to be an integer. 1 is the highest quality settings. 3907 * 10 is the default. There is a trade-off between quality and speed. The bigger the number, the 3908 * faster a color will be returned but the greater the likelihood that it will not be the visually 3909 * most dominant color. 3910 * 3911 * */ 3912 ColorThief.prototype.getColor = function(sourceImage, quality) { 3913 var palette = this.getPalette(sourceImage, 5, quality); 3914 var dominantColor = palette[0]; 3915 return dominantColor; 3916 }; 3917 3918 /* 3919 * getPalette(sourceImage[, colorCount, quality]) 3920 * returns array[ {r: num, g: num, b: num}, {r: num, g: num, b: num}, ...] 3921 * 3922 * Use the median cut algorithm provided by quantize.js to cluster similar colors. 3923 * 3924 * colorCount determines the size of the palette; the number of colors returned. If not set, it 3925 * defaults to 10. 3926 * 3927 * BUGGY: Function does not always return the requested amount of colors. It can be +/- 2. 3928 * 3929 * quality is an optional argument. It needs to be an integer. 1 is the highest quality settings. 3930 * 10 is the default. There is a trade-off between quality and speed. The bigger the number, the 3931 * faster the palette generation but the greater the likelihood that colors will be missed. 3932 * 3933 * 3934 */ 3935 ColorThief.prototype.getPalette = function(sourceImage, colorCount, quality) { 3936 3937 if (typeof colorCount === 'undefined') { 3938 colorCount = 10; 3939 } 3940 if (typeof quality === 'undefined' || quality < 1) { 3941 quality = 10; 3942 } 3943 3944 // Create custom CanvasImage object 3945 var image = new CanvasImage(sourceImage); 3946 var imageData = image.getImageData(); 3947 var pixels = imageData.data; 3948 var pixelCount = image.getPixelCount(); 3949 3950 // Store the RGB values in an array format suitable for quantize function 3951 var pixelArray = []; 3952 for (var i = 0, offset, r, g, b, a; i < pixelCount; i = i + quality) { 3953 offset = i * 4; 3954 r = pixels[offset + 0]; 3955 g = pixels[offset + 1]; 3956 b = pixels[offset + 2]; 3957 a = pixels[offset + 3]; 3958 // If pixel is mostly opaque and not white 3959 if (a >= 125) { 3960 if (!(r > 250 && g > 250 && b > 250)) { 3961 pixelArray.push([r, g, b]); 3962 } 3963 } 3964 } 3965 3966 // Send array to quantize function which clusters values 3967 // using median cut algorithm 3968 var cmap = MMCQ.quantize(pixelArray, colorCount); 3969 var palette = cmap ? cmap.palette() : null; 3970 3971 // Clean up 3972 image.removeCanvas(); 3973 3974 return palette; 3975 }; 3976 3977 /*! 3978 * quantize.js Copyright 2008 Nick Rabinowitz. 3979 * Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php 3980 */ 3981 3982 // fill out a couple protovis dependencies 3983 /*! 3984 * Block below copied from Protovis: http://mbostock.github.com/protovis/ 3985 * Copyright 2010 Stanford Visualization Group 3986 * Licensed under the BSD License: http://www.opensource.org/licenses/bsd-license.php 3987 */ 3988 if (!pv) { 3989 var pv = { 3990 map: function(array, f) { 3991 var o = {}; 3992 return f ? array.map(function(d, i) { 3993 o.index = i; 3994 return f.call(o, d); 3995 }) : array.slice(); 3996 }, 3997 naturalOrder: function(a, b) { 3998 return (a < b) ? -1 : ((a > b) ? 1 : 0); 3999 }, 4000 sum: function(array, f) { 4001 var o = {}; 4002 return array.reduce(f ? function(p, d, i) { 4003 o.index = i; 4004 return p + f.call(o, d); 4005 } : function(p, d) { 4006 return p + d; 4007 }, 0); 4008 }, 4009 max: function(array, f) { 4010 return Math.max.apply(null, f ? pv.map(array, f) : array); 4011 } 4012 }; 4013 } 4014 4015 /** 4016 * Basic Javascript port of the MMCQ (modified median cut quantization) 4017 * algorithm from the Leptonica library (http://www.leptonica.com/). 4018 * Returns a color map you can use to map original pixels to the reduced 4019 * palette. Still a work in progress. 4020 * 4021 * @author Nick Rabinowitz 4022 * @example 4023 4024 // array of pixels as [R,G,B] arrays 4025 var myPixels = [[190,197,190], [202,204,200], [207,214,210], [211,214,211], [205,207,207] 4026 // etc 4027 ]; 4028 var maxColors = 4; 4029 4030 var cmap = MMCQ.quantize(myPixels, maxColors); 4031 var newPalette = cmap.palette(); 4032 var newPixels = myPixels.map(function(p) { 4033 return cmap.map(p); 4034 });
4035 4036 */ 4037 var MMCQ = (function() { 4038 // private constants 4039 var sigbits = 5, 4040 rshift = 8 - sigbits, 4041 maxIterations = 1000, 4042 fractByPopulations = 0.75; 4043 4044 // get reduced-space color index for a pixel 4045 function getColorIndex(r, g, b) { 4046 return (r << (2 * sigbits)) + (g << sigbits) + b; 4047 } 4048 4049 // Simple priority queue 4050 function PQueue(comparator) { 4051 var contents = [], 4052 sorted = false; 4053 4054 function sort() { 4055 contents.sort(comparator); 4056 sorted = true; 4057 } 4058 4059 return { 4060 push: function(o) { 4061 contents.push(o); 4062 sorted = false; 4063 }, 4064 peek: function(index) { 4065 if (!sorted) sort(); 4066 if (index === undefined) index = contents.length - 1; 4067 return contents[index]; 4068 }, 4069 pop: function() { 4070 if (!sorted) sort(); 4071 return contents.pop(); 4072 }, 4073 size: function() { 4074 return contents.length; 4075 }, 4076 map: function(f) { 4077 return contents.map(f); 4078 }, 4079 debug: function() { 4080 if (!sorted) sort(); 4081 return contents; 4082 } 4083 }; 4084 } 4085 4086 // 3d color space box 4087 function VBox(r1, r2, g1, g2, b1, b2, histo) { 4088 var vbox = this; 4089 vbox.r1 = r1; 4090 vbox.r2 = r2; 4091 vbox.g1 = g1; 4092 vbox.g2 = g2; 4093 vbox.b1 = b1; 4094 vbox.b2 = b2; 4095 vbox.histo = histo; 4096 } 4097 VBox.prototype = { 4098 volume: function(force) { 4099 var vbox = this; 4100 if (!vbox._volume || force) { 4101 vbox._volume = ((vbox.r2 - vbox.r1 + 1) * (vbox.g2 - vbox.g1 + 1) * (vbox.b2 - vbox.b1 + 1)); 4102 } 4103 return vbox._volume; 4104 }, 4105 count: function(force) { 4106 var vbox = this, 4107 histo = vbox.histo; 4108 if (!vbox._count_set || force) { 4109 var npix = 0, 4110 i, j, k; 4111 for (i = vbox.r1; i <= vbox.r2; i++) { 4112 for (j = vbox.g1; j <= vbox.g2; j++) { 4113 for (k = vbox.b1; k <= vbox.b2; k++) { 4114 var index = getColorIndex(i, j, k); 4115 npix += (histo[index] || 0); 4116 } 4117 } 4118 } 4119 vbox._count = npix; 4120 vbox._count_set = true; 4121 } 4122 return vbox._count; 4123 }, 4124 copy: function() { 4125 var vbox = this; 4126 return new VBox(vbox.r1, vbox.r2, vbox.g1, vbox.g2, vbox.b1, vbox.b2, vbox.histo); 4127 }, 4128 avg: function(force) { 4129 var vbox = this, 4130 histo = vbox.histo; 4131 if (!vbox._avg || force) { 4132 var ntot = 0, 4133 mult = 1 << (8 - sigbits), 4134 rsum = 0, 4135 gsum = 0, 4136 bsum = 0, 4137 hval, 4138 i, j, k, histoindex; 4139 for (i = vbox.r1; i <= vbox.r2; i++) { 4140 for (j = vbox.g1; j <= vbox.g2; j++) { 4141 for (k = vbox.b1; k <= vbox.b2; k++) { 4142 histoindex = getColorIndex(i, j, k); 4143 hval = histo[histoindex] || 0; 4144 ntot += hval; 4145 rsum += (hval * (i + 0.5) * mult); 4146 gsum += (hval * (j + 0.5) * mult); 4147 bsum += (hval * (k + 0.5) * mult); 4148 } 4149 } 4150 } 4151 if (ntot) { 4152 vbox._avg = [~~(rsum / ntot), ~~(gsum / ntot), ~~(bsum / ntot)]; 4153 } else { 4154 // console.log('empty box'); 4155 vbox._avg = [~~(mult * (vbox.r1 + vbox.r2 + 1) / 2), ~~(mult * (vbox.g1 + vbox.g2 + 1) / 2), ~~(mult * (vbox.b1 + vbox.b2 + 1) / 2)]; 4156 } 4157 } 4158 return vbox._avg; 4159 }, 4160 contains: function(pixel) { 4161 var vbox = this, 4162 rval = pixel[0] >> rshift; 4163 gval = pixel[1] >> rshift; 4164 bval = pixel[2] >> rshift; 4165 return (rval >= vbox.r1 && rval <= vbox.r2 && 4166 gval >= vbox.g1 && gval <= vbox.g2 && 4167 bval >= vbox.b1 && bval <= vbox.b2); 4168 } 4169 }; 4170 4171 // Color map 4172 function CMap() { 4173 this.vboxes = new PQueue(function(a, b) { 4174 return pv.naturalOrder(
4175 a.vbox.count() * a.vbox.volume(), 4176 b.vbox.count() * b.vbox.volume() 4177 ); 4178 }); 4179 } 4180 CMap.prototype = { 4181 push: function(vbox) { 4182 this.vboxes.push({ 4183 vbox: vbox, 4184 color: vbox.avg() 4185 }); 4186 }, 4187 palette: function() { 4188 return this.vboxes.map(function(vb) { 4189 return vb.color; 4190 }); 4191 }, 4192 size: function() { 4193 return this.vboxes.size(); 4194 }, 4195 map: function(color) { 4196 var vboxes = this.vboxes; 4197 for (var i = 0; i < vboxes.size(); i++) { 4198 if (vboxes.peek(i).vbox.contains(color)) { 4199 return vboxes.peek(i).color; 4200 } 4201 } 4202 return this.nearest(color); 4203 }, 4204 nearest: function(color) { 4205 var vboxes = this.vboxes, 4206 d1, d2, pColor; 4207 for (var i = 0; i < vboxes.size(); i++) { 4208 d2 = Math.sqrt( 4209 Math.pow(color[0] - vboxes.peek(i).color[0], 2) + 4210 Math.pow(color[1] - vboxes.peek(i).color[1], 2) + 4211 Math.pow(color[2] - vboxes.peek(i).color[2], 2) 4212 ); 4213 if (d2 < d1 || d1 === undefined) { 4214 d1 = d2; 4215 pColor = vboxes.peek(i).color; 4216 } 4217 } 4218 return pColor; 4219 }, 4220 forcebw: function() { 4221 // XXX: won't work yet 4222 var vboxes = this.vboxes; 4223 vboxes.sort(function(a, b) { 4224 return pv.naturalOrder(pv.sum(a.color), pv.sum(b.color)); 4225 }); 4226 4227 // force darkest color to black if everything < 5 4228 var lowest = vboxes[0].color; 4229 if (lowest[0] < 5 && lowest[1] < 5 && lowest[2] < 5) 4230 vboxes[0].color = [0, 0, 0]; 4231 4232 // force lightest color to white if everything > 251 4233 var idx = vboxes.length - 1, 4234 highest = vboxes[idx].color; 4235 if (highest[0] > 251 && highest[1] > 251 && highest[2] > 251) 4236 vboxes[idx].color = [255, 255, 255]; 4237 } 4238 }; 4239 4240 // histo (1-d array, giving the number of pixels in 4241 // each quantized region of color space), or null on error 4242 function getHisto(pixels) { 4243 var histosize = 1 << (3 * sigbits), 4244 histo = new Array(histosize), 4245 index, rval, gval, bval; 4246 pixels.forEach(function(pixel) { 4247 rval = pixel[0] >> rshift; 4248 gval = pixel[1] >> rshift; 4249 bval = pixel[2] >> rshift; 4250 index = getColorIndex(rval, gval, bval); 4251 histo[index] = (histo[index] || 0) + 1; 4252 }); 4253 return histo; 4254 } 4255 4256 function vboxFromPixels(pixels, histo) { 4257 var rmin = 1000000, 4258 rmax = 0, 4259 gmin = 1000000, 4260 gmax = 0, 4261 bmin = 1000000, 4262 bmax = 0, 4263 rval, gval, bval; 4264 // find min/max 4265 pixels.forEach(function(pixel) { 4266 rval = pixel[0] >> rshift; 4267 gval = pixel[1] >> rshift; 4268 bval = pixel[2] >> rshift; 4269 if (rval < rmin) rmin = rval; 4270 else if (rval > rmax) rmax = rval; 4271 if (gval < gmin) gmin = gval; 4272 else if (gval > gmax) gmax = gval; 4273 if (bval < bmin) bmin = bval; 4274 else if (bval > bmax) bmax = bval; 4275 }); 4276 return new VBox(rmin, rmax, gmin, gmax, bmin, bmax, histo); 4277 } 4278 4279 function medianCutApply(histo, vbox) { 4280 if (!vbox.count()) return; 4281 4282 var rw = vbox.r2 - vbox.r1 + 1, 4283 gw = vbox.g2 - vbox.g1 + 1, 4284 bw = vbox.b2 - vbox.b1 + 1, 4285 maxw = pv.max([rw, gw, bw]); 4286 // only one pixel, no split 4287 if (vbox.count() == 1) { 4288 return [vbox.copy()]; 4289 } 4290 /* Find the partial sum arrays along the selected axis. */ 4291 var total = 0, 4292 partialsum = [], 4293 lookaheadsum = [], 4294 i, j, k, sum, index; 4295 if (maxw == rw) { 4296 for (i = vbox.r1; i <= vbox.r2; i++) { 4297 sum = 0; 4298 for (j = vbox.g1; j <= vbox.g2; j++) { 4299 for (k = vbox.b1; k <= vbox.b2; k++) { 4300 index = getColorIndex(i, j, k); 4301 sum += (histo[index] || 0); 4302 } 4303 } 4304 total += sum; 4305 partialsum[i] = total; 4306 } 4307 } else if (maxw == gw) { 4308 for (i = vbox.g1; i <= vbox.g2; i++) { 4309 sum = 0; 4310 for (j = vbox.r1; j <= vbox.r2; j++) { 4311 for (k = vbox.b1; k <= vbox.b2; k++) { 4312 index = getColorIndex(j, i, k); 4313 sum += (histo[index] || 0); 4314 } 4315 } 4316 total += sum; 4317 partialsum[i] = total; 4318 } 4319 } else { /* maxw == bw */ 4320 for (i = vbox.b1; i <= vbox.b2; i++) { 4321 sum = 0; 4322 for (j = vbox.r1; j <= vbox.r2; j++) { 4323 for (k = vbox.g1; k <= vbox.g2; k++) { 4324 index = getColorIndex(j, k, i); 4325 sum += (histo[index] || 0); 4326 } 4327 } 4328 total += sum; 4329 partialsum[i] = total; 4330 } 4331 } 4332 partialsum.forEach(function(d, i) { 4333 lookaheadsum[i] = total - d; 4334 }); 4335 4336 function doCut(color) { 4337 var dim1 = color + '1', 4338 dim2 = color + '2', 4339 left, right, vbox1, vbox2, d2, count2 = 0; 4340 for (i = vbox[dim1]; i <= vbox[dim2]; i++) { 4341 if (partialsum[i] > total / 2) { 4342 vbox1 = vbox.copy(); 4343 vbox2 = vbox.copy(); 4344 left = i - vbox[dim1]; 4345 right = vbox[dim2] - i; 4346 if (left <= right) 4347 d2 = Math.min(vbox[dim2] - 1, ~~(i + right / 2)); 4348 else d2 = Math.max(vbox[dim1], ~~(i - 1 - left / 2)); 4349 // avoid 0-count boxes 4350 while (!partialsum[d2]) d2++; 4351 count2 = lookaheadsum[d2]; 4352 while (!count2 && partialsum[d2 - 1]) count2 = lookaheadsum[--d2]; 4353 // set dimensions 4354 vbox1[dim2] = d2; 4355 vbox2[dim1] = vbox1[dim2] + 1; 4356 // console.log('vbox counts:', vbox.c
4356ount(), vbox1.count(), vbox2.count()); 4357 return [vbox1, vbox2]; 4358 } 4359 } 4360 4361 } 4362 // determine the cut planes 4363 return maxw == rw ? doCut('r') : 4364 maxw == gw ? doCut('g') : 4365 doCut('b'); 4366 } 4367 4368 function quantize(pixels, maxcolors) { 4369 // short-circuit 4370 if (!pixels.length || maxcolors < 2 || maxcolors > 256) { 4371 // console.log('wrong number of maxcolors'); 4372 return false; 4373 } 4374 4375 // XXX: check color content and convert to grayscale if insufficient 4376 4377 var histo = getHisto(pixels), 4378 histosize = 1 << (3 * sigbits); 4379 4380 // check that we aren't below maxcolors already 4381 var nColors = 0; 4382 histo.forEach(function() { 4383 nColors++; 4384 }); 4385 if (nColors <= maxcolors) { 4386 // XXX: generate the new colors from the histo and return 4387 } 4388 4389 // get the beginning vbox from the colors 4390 var vbox = vboxFromPixels(pixels, histo), 4391 pq = new PQueue(function(a, b) { 4392 return pv.naturalOrder(a.count(), b.count()); 4393 }); 4394 pq.push(vbox); 4395 4396 // inner function to do the iteration 4397 function iter(lh, target) { 4398 var ncolors = 1, 4399 niters = 0, 4400 vbox; 4401 while (niters < maxIterations) { 4402 vbox = lh.pop(); 4403 if (!vbox.count()) { /* just put it back */ 4404 lh.push(vbox); 4405 niters++; 4406 continue; 4407 } 4408 // do the cut 4409 var vboxes = medianCutApply(histo, vbox), 4410 vbox1 = vboxes[0], 4411 vbox2 = vboxes[1]; 4412 4413 if (!vbox1) { 4414 // console.log("vbox1 not defined; shouldn't happen!"); 4415 return; 4416 } 4417 lh.push(vbox1); 4418 if (vbox2) { /* vbox2 can be null */ 4419 lh.push(vbox2); 4420 ncolors++; 4421 } 4422 if (ncolors >= target) return; 4423 if (niters++ > maxIterations) { 4424 // console.log("infinite loop; perhaps too few pixels!"); 4425 return; 4426 } 4427 } 4428 } 4429 4430 // first set of colors, sorted by population 4431 iter(pq, fractByPopulations * maxcolors); 4432 4433 // Re-sort by the product of pixel occupancy times the size in color space. 4434 var pq2 = new PQueue(function(a, b) { 4435 return pv.naturalOrder(a.count() * a.volume(), b.count() * b.volume()); 4436 }); 4437 while (pq.size()) { 4438 pq2.push(pq.pop()); 4439 } 4440 4441 // next set - generate the median cuts using the (npix * vol) sorting. 4442 iter(pq2, maxcolors - pq2.size()); 4443 4444 // calculate the actual colors 4445 var cmap = new CMap(); 4446 while (pq2.size()) { 4447 cmap.push(pq2.pop()); 4448 } 4449 4450 return cmap; 4451 } 4452 4453 return { 4454 quantize: quantize 4455 }; 4456 })(); 4457 4458 /** 4459 * Export class to global 4460 */ 4461 if (typeof define === 'function' && define.amd) { 4462 define([], function() { 4463 return ColorThief; 4464 }); // for AMD loader 4465 } else if (typeof exports === 'object') { 4466 module.exports = ColorThief; // for CommonJS 4467 } else { 4468 this.ColorThief = ColorThief; 4469 } 4470}.call(this)); 4471 4472/** 4473 * Mega pixel image rendering library for iOS6 Safari 4474 * 4475 * Fixes iOS6 Safari's image file rendering issue for large size image (over mega-pixel), 4476 * which causes unexpected subsampling when drawing it in canvas. 4477 * By using this library, you can safely render the image with proper stretching. 4478 * 4479 * Copyright (c) 2012 Shinichi Tomita <[email protected]> 4480 * Released under the MIT license 4481 */ 4482(function () { 4483 4484 /** 4485 * Detect subsampling in loaded image. 4486 * In iOS, larger images than 2M pixels may be subsampled in rendering. 4487 */ 4488 function detectSubsampling(img) {
4489 var iw = img.naturalWidth, ih = img.naturalHeight; 4490 if (iw * ih > 1024 * 1024) { // subsampling may happen over megapixel image 4491 var canvas = document.createElement('canvas'); 4492 canvas.width = canvas.height = 1; 4493 var ctx = canvas.getContext('2d'); 4494 ctx.drawImage(img, -iw + 1, 0); 4495 // subsampled image becomes half smaller in rendering size. 4496 // check alpha channel value to confirm image is covering edge pixel or not. 4497 // if alpha value is 0 image is not covering, hence subsampled. 4498 return ctx.getImageData(0, 0, 1, 1).data[3] === 0; 4499 } else { 4500 return false; 4501 } 4502 } 4503 4504 /** 4505 * Detecting vertical squash in loaded image. 4506 * Fixes a bug which squash image vertically while drawing into canvas for some images. 4507 */ 4508 function detectVerticalSquash(img, iw, ih) { 4509 var canvas = document.createElement('canvas'); 4510 canvas.width = 1; 4511 canvas.height = ih; 4512 var ctx = canvas.getContext('2d'); 4513 ctx.drawImage(img, 0, 0); 4514 var data = ctx.getImageData(0, 0, 1, ih).data; 4515 // search image edge pixel position in case it is squashed vertically. 4516 var sy = 0; 4517 var ey = ih; 4518 var py = ih; 4519 while (py > sy) { 4520 var alpha = data[(py - 1) * 4 + 3]; 4521 if (alpha === 0) { 4522 ey = py; 4523 } else { 4524 sy = py; 4525 } 4526 py = (ey + sy) >> 1; 4527 } 4528 var ratio = (py / ih); 4529 return (ratio === 0) ? 1 : ratio; 4530 } 4531 4532 /** 4533 * Rendering image element (with resizing) and get its data URL 4534 */ 4535 function renderImageToDataURL(img, options, doSquash) { 4536 var canvas = document.createElement('canvas'); 4537 renderImageToCanvas(img, canvas, options, doSquash); 4538 return canvas.toDataURL('image/jpeg', options.quality || 0.8); 4539 } 4540 4541 /** 4542 * Rendering image element (with resizing) into the canvas element 4543 */ 4544 function renderImageToCanvas(img, canvas, options, doSquash) { 4545 var iw = img.naturalWidth, ih = img.naturalHeight; 4546 if ((iw + ih) === false) { 4547 return; 4548 } 4549 var width = options.width, height = options.height; 4550 var ctx = canvas.getContext('2d'); 4551 ctx.save(); 4552 transformCoordinate(canvas, ctx, width, height, options.orientation); 4553 var subsampled = detectSubsampling(img); 4554 if (subsampled) { 4555 iw /= 2; 4556 ih /= 2; 4557 } 4558 var d = 1024; // size of tiling canvas 4559 var tmpCanvas = document.createElement('canvas'); 4560 tmpCanvas.width = tmpCanvas.height = d; 4561 var tmpCtx = tmpCanvas.getContext('2d'); 4562 var vertSquashRatio = doSquash ? detectVerticalSquash(img, iw, ih) : 1; 4563 var dw = Math.ceil(d * width / iw); 4564 var dh = Math.ceil(d * height / ih / vertSquashRatio); 4565 var sy = 0; 4566 var dy = 0; 4567 while (sy < ih) { 4568 var sx = 0; 4569 var dx = 0; 4570 while (sx < iw) { 4571 tmpCtx.clearRect(0, 0, d, d); 4572 tmpCtx.drawImage(img, -sx, -sy); 4573 ctx.drawImage(tmpCanvas, 0, 0, d, d, dx, dy, dw, dh); 4574 sx += d; 4575 dx += dw; 4576 } 4577 sy += d; 4578 dy += dh; 4579 } 4580 ctx.restore(); 4581 tmpCanvas = tmpCtx = null; 4582 } 4583 4584 /** 4585 * Transform canvas coordination according to specified frame size and orientation 4586 * Orientation value is from EXIF tag 4587 */ 4588 function transformCoordinate(canvas, ctx, width, height, orientation) { 4589 switch (orientation) { 4590 case 5: 4591 case 6: 4592 case 7: 4593 case 8: 4594 canvas.width = height; 4595 canvas.height = width; 4596 break; 4597 default: 4598 canvas.width = width; 4599 canvas.height = height; 4600 } 4601 switch (orientation) { 4602 case 2: 4603 // horizontal flip 4604 ctx.translate(width, 0); 4605 ctx.scale(-1, 1); 4606 break; 4607 case 3: 4608 // 180 rotate left 4609 ctx.translate(width, height); 4610 ctx.rotate(Math.PI); 4611 break; 4612 case 4: 4613 // vertical flip 4614 ctx.translate(0, height); 4615 ctx.scale(1, -1); 4616 break; 4617 case 5: 4618 // vertical flip + 90 rotate right 4619 ctx.rotate(0.5 * Math.PI); 4620 ctx.scale(1, -1); 4621 break; 4622 case 6: 4623 // 90 rotate right 4624 ctx.rotate(0.5 * Math.PI); 4625 ctx.translate(0, -height); 4626 break; 4627 case 7: 4628 // horizontal flip + 90 rotate right 4629 ctx.rotate(0.5 * Math.PI); 4630 ctx.translate(width, -height); 4631 ctx.scale(-1, 1); 4632 break; 4633 case 8: 4634 // 90 rotate left 4635 ctx.rotate(-0.5 * Math.PI); 4636 ctx.translate(-width, 0); 4637 break; 4638 default: 4639 break; 4640 } 4641 } 4642 4643 var URL = window.URL && window.URL.createObjectURL ? window.URL : 4644 window.webkitURL && window.webkitURL.createObjectURL ? window.webkitURL : 4645 null; 4646 4647 /**
4648 * MegaPixImage class 4649 */ 4650 function MegaPixImage(srcImage) { 4651 if (window.Blob && srcImage instanceof Blob) { 4652 if (!URL) { 4653 throw Error('No createObjectURL function found to create blob url'); 4654 } 4655 var img = new Image(); 4656 img.src = URL.createObjectURL(srcImage); 4657 this.blob = srcImage; 4658 srcImage = img; 4659 } 4660 if (!srcImage.naturalWidth && !srcImage.naturalHeight) { 4661 var _this = this; 4662 srcImage.onload = srcImage.onerror = function () { 4663 var listeners = _this.imageLoadListeners; 4664 if (listeners) { 4665 _this.imageLoadListeners = null; 4666 for (var i = 0, len = listeners.length; i < len; i++) { 4667 listeners[i](); 4668 } 4669 } 4670 }; 4671 this.imageLoadListeners = []; 4672 } 4673 this.srcImage = srcImage; 4674 } 4675 4676 /** 4677 * Rendering megapix image into specified target element 4678 */ 4679 MegaPixImage.prototype.render = function (target, options, callback) { 4680 if (this.imageLoadListeners) { 4681 var _this = this; 4682 this.imageLoadListeners.push(function () { 4683 _this.render(target, options, callback); 4684 }); 4685 return; 4686 } 4687 options = options || {}; 4688 var imgWidth = this.srcImage.naturalWidth, imgHeight = this.srcImage.naturalHeight, 4689 width = options.width, height = options.height, 4690 maxWidth = options.maxWidth, maxHeight = options.maxHeight, 4691 doSquash = !this.blob || this.blob.type === 'image/jpeg'; 4692 if (width && !height) { 4693 height = (imgHeight * width / imgWidth) << 0; 4694 } else if (height && !width) { 4695 width = (imgWidth * height / imgHeight) << 0; 4696 } else { 4697 width = imgWidth; 4698 height = imgHeight; 4699 } 4700 if (maxWidth && width > maxWidth) { 4701 width = maxWidth; 4702 height = (imgHeight * width / imgWidth) << 0; 4703 } 4704 if (maxHeight && height > maxHeight) { 4705 height = maxHeight; 4706 width = (imgWidth * height / imgHeight) << 0; 4707 } 4708 var opt = {width: width, height: height}; 4709 for (var k in options) { 4710 opt[k] = options[k]; 4711 } 4712 4713 var tagName = target.tagName.toLowerCase(); 4714 if (tagName === 'img') { 4715 target.src = renderImageToDataURL(this.srcImage, opt, doSquash); 4716 } else if (tagName === 'canvas') { 4717 renderImageToCanvas(this.srcImage, target, opt, doSquash); 4718 } 4719 if (typeof this.onrender === 'function') { 4720 this.onrender(target); 4721 } 4722 if (callback) { 4723 callback(); 4724 } 4725 if (this.blob) { 4726 this.blob = null; 4727 URL.revokeObjectURL(this.srcImage.src); 4728 } 4729 }; 4730 4731 /** 4732 * Export class to global 4733 */ 4734 if (typeof define === 'function' && define.amd) { 4735 define([], function () { 4736 return MegaPixImage; 4737 }); // for AMD loader 4738 } else if (typeof exports === 'object') { 4739 module.exports = MegaPixImage; // for CommonJS 4740 } else { 4741 this.MegaPixImage = MegaPixImage; 4742 } 4743 4744}.call(this));
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.