1 2/** 3 * This file is part of the Phalcon Framework. 4 * 5 * (c) Phalcon Team <team@phalcon.io> 6 * 7 * For the full copyright and license information, please view the LICENSE.txt 8 * file that was distributed with this source code. 9 */ 10 11namespace Phalcon\Image\Adapter; 12 13use Phalcon\Image\Enum; 14use Phalcon\Image\Exception; 15 16/** 17 * Phalcon\Image\Adapter\Imagick 18 * 19 * Image manipulation support. Allows images to be resized, cropped, etc. 20 * 21 *```php 22 * $image = new \Phalcon\Image\Adapter\Imagick("upload/test.jpg"); 23 * 24 * $image->resize(200, 200)->rotate(90)->crop(100, 100); 25 * 26 * if ($image->save()) { 27 * echo "success"; 28 29 * } 30 *``` 31 */ 32class Imagick extends AbstractAdapter 33{ 34 protected static checked = false; 35 protected static version = 0; 36 37 /** 38 * \Phalcon\Image\Adapter\Imagick constructor 39 */ 40 public function __construct(string! file, int width = null, int height = null) 41 { 42 var image; 43 44 if !self::checked { 45 self::check(); 46 } 47 48 let this->file = file; 49 50 let this->image = new \Imagick(); 51 52 if file_exists(this->file) { 53 let this->realpath = realpath(this->file); 54 55 if unlikely !this->image->readImage(this->realpath) { 56 throw new Exception( 57 "Imagick::readImage " . this->file . " failed" 58 ); 59 } 60 61 if !this->image->getImageAlphaChannel() { 62 this->image->setImageAlphaChannel( 63 constant("Imagick::ALPHACHANNEL_SET") 64 ); 65 } 66 67 if this->type == 1 { 68 let image = this->image->coalesceImages(); 69 70 this->image->clear(); 71 this->image->destroy(); 72 73 let this->image = image; 74 } 75 } else { 76 if unlikely (!width || !height) { 77 throw new Exception( 78 "Failed to create image from file " . this->file 79 ); 80 } 81 82 this->image->newImage( 83 width, 84 height, 85 new \ImagickPixel("transparent") 86 ); 87 88 this->image->setFormat("png"); 89 this->image->setImageFormat("png"); 90 91 let this->realpath = this->file; 92 } 93 94 let this->width = this->image->getImageWidth(); 95 let this->height = this->image->getImageHeight(); 96 let this->type = this->image->getImageType(); 97 let this->mime = "image/" . this->image->getImageFormat(); 98 } 99 100 /** 101 * Destroys the loaded image to free up resources. 102 */ 103 public function __destruct() 104 { 105 if this->image instanceof \Imagick { 106 this->image->clear(); 107 this->image->destroy(); 108 } 109 } 110 111 /** 112 * Checks if Imagick is enabled 113 */ 114 public static function check() -> bool 115 { 116 if self::checked { 117 return true; 118 } 119 120 if unlikely !class_exists("imagick") { 121 throw new Exception( 122 "Imagick is not installed, or the extension is not loaded" 123 ); 124 } 125 126 if defined("Imagick::IMAGICK_EXTNUM") { 127 let self::version = constant("Imagick::IMAGICK_EXTNUM"); 128 } 129 130 let self::checked = true; 131 132 return self::checked; 133 } 134 135 /** 136 * Get instance 137 */ 138 public function getInternalImInstance() -> <\Imagick> 139 { 140 return this->image; 141 } 142 143 /** 144 * Sets the limit for a particular resource in megabytes 145 * 146 * @link http://php.net/manual/ru/imagick.constants.php#imagick.constants.resourcetypes 147 */ 148 public function setResourceLimit(int type, int limit) 149 { 150 this->image->setResourceLimit(type, limit); 151 } 152 153 /** 154 * Execute a background. 155 */ 156 protected function processBackground(int r, int g, int b, int opacity) 157 { 158 var background, color, pixel1, pixel2, ret; 159 160 let color = sprintf("rgb(%d, %d, %d)", r, g, b); 161 let pixel1 = new \ImagickPixel(color); 162 let opacity = opacity / 100; 163 164 let pixel2 = new \ImagickPixel("transparent"); 165 166 let background = new \Imagick(); 167 168 this->image->setIteratorIndex(0); 169 170 loop { 171 background->newImage(this->width, this->height, pixel1); 172 173 if !background->getImageAlphaChannel() { 174 background->setImageAlphaChannel( 175 constant("Imagick::ALPHACHANNEL_SET") 176 ); 177 } 178 179 background->setImageBackgroundColor(pixel2); 180 181 background->evaluateImage( 182 constant("Imagick::EVALUATE_MULTIPLY"), 183 opacity, 184 constant("Imagick::CHANNEL_ALPHA") 185 ); 186 187 background->setColorspace( 188 this->image->getColorspace() 189 ); 190 191 let ret = background->compositeImage( 192 this->image, 193 constant("Imagick::COMPOSITE_DISSOLVE"), 194 0, 195 0 196 ); 197 198 if unlikely ret !== true { 199 throw new Exception("Imagick::compositeImage failed"); 200 } 201 202 if this->image->nextImage() === false { 203 break; 204 } 205 } 206 207 this->image->clear(); 208 this->image->destroy(); 209 210 let this->image = background; 211 } 212 213 /** 214 * Blur image 215 * 216 * @param int $radius Blur radius 217 */ 218 protected function processBlur(int radius) 219 { 220 this->image->setIteratorIndex(0); 221 222 loop { 223 this->image->blurImage(radius, 100); 224 225 if this->image->nextImage() === false { 226 break; 227 } 228 } 229 } 230 231 /** 232 * Execute a crop. 233 */ 234 protected function processCrop(int width, int height, int offsetX, int offsetY) 235 { 236 var image; 237 238 let image = this->image; 239 240 image->setIteratorIndex(0); 241 242 loop { 243 image->cropImage(width, height, offsetX, offsetY); 244 image->setImagePage(width, height, 0, 0); 245 246 if !image->nextImage() { 247 break; 248 } 249 } 250 251 let this->width = image->getImageWidth(); 252 let this->height = image->getImageHeight(); 253 } 254 255 /** 256 * Execute a flip. 257 */ 258 protected function processFlip(int direction) 259 { 260 var func; 261 262 let func = "flipImage"; 263 264 if direction == Enum::HORIZONTAL { 265 let func = "flopImage"; 266 } 267 268 this->image->setIteratorIndex(0); 269 270 loop { 271 this->image->{func}(); 272 273 if this->image->nextImage() === false { 274 break; 275 } 276 } 277 } 278 279 /** 280 * This method scales the images using liquid rescaling method. Only support 281 * Imagick 282 * 283 * @param int $width new width 284 * @param int $height new height 285 * @param int $deltaX How much the seam can traverse on x-axis. Passing 0 causes the seams to be straight. 286 * @param int $rigidity Introduces a bias for non-straight seams. This parameter is typically 0. 287 */ 288 protected function processLiquidRescale(int width, int height, int deltaX, int rigidity) 289 { 290 var ret, image; 291 292 let image = this->image; 293 294 image->setIteratorIndex(0); 295 296 loop { 297 let ret = image->liquidRescaleImage( 298 width, 299 height, 300 deltaX, 301 rigidity 302 ); 303 304 if unlikely ret !== true { 305 throw new Exception("Imagick::liquidRescale failed"); 306 } 307 308 if image->nextImage() === false { 309 break; 310 } 311 } 312 313 let this->width = image->getImageWidth(); 314 let this->height = image->getImageHeight(); 315 } 316 317 /** 318 * Composite one image onto another 319 */ 320 protected function processMask(<AdapterInterface> image) 321 { 322 var mask, ret; 323 324 let mask = new \Imagick(); 325 326 mask->readImageBlob( 327 image->render() 328 ); 329 330 this->image->setIteratorIndex(0); 331 332 loop { 333 this->image->setImageMatte(1); 334 335 let ret = this->image->compositeImage( 336 mask, 337 constant("Imagick::COMPOSITE_DSTIN"), 338 0, 339 0 340 ); 341 342 if unlikely ret !== true { 343 throw new Exception("Imagick::compositeImage failed"); 344 } 345 346 if this->image->nextImage() === false { 347 break; 348 } 349 } 350 351 mask->clear(); 352 mask->destroy(); 353 } 354 355 /** 356 * Pixelate image 357 * 358 * @param int $amount amount to pixelate 359 */ 360 protected function processPixelate(int amount) 361 { 362 int width, height; 363 364 let width = this->width / amount; 365 let height = this->height / amount; 366 367 this->image->setIteratorIndex(0); 368 369 loop { 370 this->image->scaleImage(width, height); 371 this->image->scaleImage(this->width, this->height); 372 373 if this->image->nextImage() === false{ 374 break; 375 } 376 } 377 } 378 379 /** 380 * Execute a reflection. 381 */ 382 protected function processReflection(int height, int opacity, bool fadeIn) 383 { 384 var reflection, fade, pseudo, image, pixel, ret; 385 386 if self::version >= 30100 { 387 let reflection = clone this->image; 388 } else { 389 let reflection = clone this->image->$clone(); 390 } 391 392 reflection->setIteratorIndex(0); 393 394 loop { 395 reflection->flipImage(); 396 397 reflection->cropImage( 398 reflection->getImageWidth(), 399 height, 400 0, 401 0 402 ); 403 404 reflection->setImagePage( 405 reflection->getImageWidth(), 406 height, 407 0, 408 0 409 ); 410 411 if reflection->nextImage() === false { 412 break; 413 } 414 } 415 416 let pseudo = fadeIn ? "gradient:black-transparent" : "gradient:transparent-black", 417 fade = new \Imagick(); 418 419 fade->newPseudoImage( 420 reflection->getImageWidth(), 421 reflection->getImageHeight(), 422 pseudo 423 ); 424 425 let opacity /= 100; 426 427 reflection->setIteratorIndex(0); 428 429 loop { 430 let ret = reflection->compositeImage( 431 fade, 432 constant("Imagick::COMPOSITE_DSTOUT"), 433 0, 434 0 435 ); 436 437 if unlikely ret !== true { 438 throw new Exception("Imagick::compositeImage failed"); 439 } 440 441 reflection->evaluateImage( 442 constant("Imagick::EVALUATE_MULTIPLY"), 443 opacity, 444 constant("Imagick::CHANNEL_ALPHA") 445 ); 446 447 if reflection->nextImage() === false { 448 break; 449 } 450 } 451 452 fade->destroy(); 453 454 let image = new \Imagick(), 455 pixel = new \ImagickPixel(), 456 height = this->image->getImageHeight() + height; 457 458 this->image->setIteratorIndex(0); 459 460 loop { 461 image->newImage(this->width, height, pixel); 462 463 image->setImageAlphaChannel( 464 constant("Imagick::ALPHACHANNEL_SET") 465 ); 466 467 image->setColorspace( 468 this->image->getColorspace() 469 ); 470 471 image->setImageDelay( 472 this->image->getImageDelay() 473 ); 474 475 let ret = image->compositeImage( 476 this->image, 477 constant("Imagick::COMPOSITE_SRC"), 478 0, 479 0 480 ); 481 482 if unlikely ret !== true { 483 throw new Exception("Imagick::compositeImage failed"); 484 } 485 486 if this->image->nextImage() === false { 487 break; 488 } 489 } 490 491 image->setIteratorIndex(0); 492 reflection->setIteratorIndex(0); 493 494 loop { 495 let ret = image->compositeImage( 496 reflection, 497 constant("Imagick::COMPOSITE_OVER"), 498 0, 499 this->height 500 ); 501 502 if unlikely ret !== true { 503 throw new Exception("Imagick::compositeImage failed"); 504 } 505 506 if image->nextImage() === false || reflection->nextImage() === false { 507 break; 508 } 509 } 510 511 reflection->destroy(); 512 513 this->image->clear(); 514 this->image->destroy(); 515 516 let this->image = image; 517 let this->width = this->image->getImageWidth(); 518 let this->height = this->image->getImageHeight(); 519 } 520 521 /** 522 * Execute a render. 523 */ 524 protected function processRender(string extension, int quality) -> string 525 { 526 var image; 527 528 let image = this->image; 529 530 image->setFormat(extension); 531 image->setImageFormat(extension); 532 image->stripImage(); 533 534 let this->type = image->getImageType(), 535 this->mime = "image/" . image->getImageFormat(); 536 537 if strcasecmp(extension, "gif") === 0 { 538 image->optimizeImageLayers(); 539 } else { 540 if strcasecmp(extension, "jpg") === 0 || strcasecmp(extension, "jpeg") === 0 { 541 image->setImageCompression( 542 constant("Imagick::COMPRESSION_JPEG") 543 ); 544 } 545 546 image->setImageCompressionQuality(quality); 547 } 548 549 return image->getImageBlob(); 550 } 551 552 /** 553 * Execute a resize. 554 */ 555 protected function processResize(int width, int height) 556 { 557 var image; 558 559 let image = this->image; 560 561 image->setIteratorIndex(0); 562 563 loop { 564 image->scaleImage(width, height); 565 566 if image->nextImage() === false { 567 break; 568 } 569 } 570 571 let this->width = image->getImageWidth(); 572 let this->height = image->getImageHeight(); 573 } 574 575 /** 576 * Execute a rotation. 577 */ 578 protected function processRotate(int degrees) 579 { 580 var pixel; 581 582 this->image->setIteratorIndex(0); 583 584 let pixel = new \ImagickPixel(); 585 586 loop { 587 this->image->rotateImage(pixel, degrees); 588 589 this->image->setImagePage( 590 this->width, 591 this->height, 592 0, 593 0 594 ); 595 596 if this->image->nextImage() === false { 597 break; 598 } 599 } 600 601 let this->width = this->image->getImageWidth(); 602 let this->height = this->image->getImageHeight(); 603 } 604 605 /** 606 * Execute a save. 607 */ 608 protected function processSave(string file, int quality) 609 { 610 var ext, fp; 611 612 let ext = pathinfo(file, PATHINFO_EXTENSION); 613 614 this->image->setFormat(ext); 615 this->image->setImageFormat(ext); 616 617 let this->type = this->image->getImageType(); 618 let this->mime = "image/" . this->image->getImageFormat(); 619 620 if strcasecmp(ext, "gif") == 0 { 621 this->image->optimizeImageLayers(); 622 623 let fp = fopen(file, "w"); 624 625 this->image->writeImagesFile(fp); 626 627 fclose(fp); 628 629 return; 630 } else { 631 if strcasecmp(ext, "jpg") == 0 || strcasecmp(ext, "jpeg") == 0 { 632 this->image->setImageCompression( 633 constant("Imagick::COMPRESSION_JPEG") 634 ); 635 } 636 637 if quality >= 0 { 638 if quality < 1 { 639 let quality = 1; 640 } elseif quality > 100 { 641 let quality = 100; 642 } 643 644 this->image->setImageCompressionQuality(quality); 645 } 646 647 this->image->writeImage(file); 648 } 649 } 650 651 /** 652 * Execute a sharpen. 653 */ 654 protected function processSharpen(int amount) 655 { 656 let amount = (amount < 5) ? 5 : amount; 657 let amount = (amount * 3.0) / 100; 658 659 this->image->setIteratorIndex(0); 660 661 loop { 662 this->image->sharpenImage(0, amount); 663 664 if this->image->nextImage() === false { 665 break; 666 } 667 } 668 } 669 670 /** 671 * Execute a text 672 */ 673 protected function processText(string text, var offsetX, var offsetY, int opacity, int r, int g, int b, int size, string fontfile) 674 { 675 var x, y, draw, color, gravity; 676 677 let opacity = opacity / 100, 678 draw = new \ImagickDraw(), 679 color = sprintf("rgb(%d, %d, %d)", r, g, b); 680 681 draw->setFillColor( 682 new \ImagickPixel(color) 683 ); 684 685 if fontfile { 686 draw->setFont(fontfile); 687 } 688 689 if size { 690 draw->setFontSize(size); 691 } 692 693 if opacity { 694 draw->setfillopacity(opacity); 695 } 696 697 let gravity = null; 698 699 if typeof offsetX == "boolean" { 700 if typeof offsetY == "boolean" { 701 let offsetX = 0, 702 offsetY = 0; 703 704 let gravity = constant("Imagick::GRAVITY_CENTER"); 705 } else { 706 if typeof offsetY == "int" { 707 let y = (int) offsetY; 708 709 if offsetX { 710 if y < 0 { 711 let offsetX = 0, 712 offsetY = y * -1, 713 gravity = constant("Imagick::GRAVITY_SOUTHEAST"); 714 } else { 715 let offsetX = 0, 716 gravity = constant("Imagick::GRAVITY_NORTHEAST"); 717 } 718 } else { 719 if y < 0 { 720 let offsetX = 0, 721 offsetY = y * -1, 722 gravity = constant("Imagick::GRAVITY_SOUTH"); 723 } else { 724 let offsetX = 0, 725 gravity = constant("Imagick::GRAVITY_NORTH"); 726 } 727 } 728 } 729 } 730 } else { 731 if typeof offsetX == "int" { 732 let x = (int) offsetX; 733 734 if offsetX { 735 if typeof offsetY == "boolean" { 736 if offsetY { 737 if x < 0 { 738 let offsetX = x * -1, 739 offsetY = 0, 740 gravity = constant("Imagick::GRAVITY_SOUTHEAST"); 741 } else { 742 let offsetY = 0, 743 gravity = constant("Imagick::GRAVITY_SOUTH"); 744 } 745 } else { 746 if x < 0 { 747 let offsetX = x * -1, 748 offsetY = 0, 749 gravity = constant("Imagick::GRAVITY_EAST"); 750 } else { 751 let offsetY = 0, 752 gravity = constant("Imagick::GRAVITY_WEST"); 753 } 754 } 755 } else { 756 if typeof offsetY == "long" { 757 let x = (int) offsetX, 758 y = (int) offsetY; 759 760 if x < 0 { 761 if y < 0 { 762 let offsetX = x * -1, 763 offsetY = y * -1, 764 gravity = constant("Imagick::GRAVITY_SOUTHEAST"); 765 } else { 766 let offsetX = x * -1, 767 gravity = constant("Imagick::GRAVITY_NORTHEAST"); 768 } 769 } else { 770 if y < 0 { 771 let offsetX = 0, 772 offsetY = y * -1, 773 gravity = constant("Imagick::GRAVITY_SOUTHWEST"); 774 } else { 775 let offsetX = 0, 776 gravity = constant("Imagick::GRAVITY_NORTHWEST"); 777 } 778 } 779 } 780 } 781 } 782 } 783 } 784 785 draw->setGravity(gravity); 786 787 this->image->setIteratorIndex(0); 788 789 loop { 790 this->image->annotateImage(draw, offsetX, offsetY, 0, text); 791 792 if this->image->nextImage() === false { 793 break; 794 } 795 } 796 797 draw->destroy(); 798 } 799 800 /** 801 * Execute a watermarking. 802 */ 803 protected function processWatermark(<AdapterInterface> image, int offsetX, int offsetY, int opacity) 804 { 805 var watermark, ret; 806 807 let opacity = opacity / 100, 808 watermark = new \Imagick(); 809 810 watermark->readImageBlob( 811 image->render() 812 ); 813 814 watermark->evaluateImage( 815 constant("Imagick::EVALUATE_MULTIPLY"), 816 opacity, 817 constant("Imagick::CHANNEL_ALPHA") 818 ); 819 820 this->image->setIteratorIndex(0); 821 822 loop { 823 let ret = this->image->compositeImage( 824 watermark, 825 constant("Imagick::COMPOSITE_OVER"), 826 offsetX, 827 offsetY 828 ); 829 830 if unlikely ret !== true { 831 throw new Exception("Imagick::compositeImage failed"); 832 } 833 834 if this->image->nextImage() === false { 835 break; 836 } 837 } 838 839 watermark->clear(); 840 watermark->destroy(); 841 } 842} 843