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