├── .gitignore ├── README.md ├── composer.json └── yii └── image ├── ImageDriver.php └── drivers ├── Image.php ├── Image ├── GD.php └── Imagick.php └── Kohana ├── Image.php └── Image ├── GD.php └── Imagick.php /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /vendor/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | yii2-image 2 | ========== 3 | 4 | Simple to use Yii2 Framework extension for image manipulating using powerful Kohana Image Library. Inspired by old yii extension 5 | http://www.yiiframework.com/extension/image/ and Kohana Image Library https://github.com/kohana/image 6 | 7 | Installation 8 | ------------ 9 | **Install as a [composer package](https://packagist.org/packages/yurkinx/yii2-image)** 10 | > Use this method to get continuous updates. 11 | 12 | ``` 13 | composer require yurkinx/yii2-image 14 | ``` 15 | or include the dependency in the `composer.json` file: 16 | ```json 17 | { 18 | "require": { 19 | "yurkinx/yii2-image": "^1.2" 20 | 21 | } 22 | } 23 | ``` 24 | Configuration 25 | ------------- 26 | In config file 27 | ```code 28 | /config/web.php 29 | ``` 30 | Add image component 31 | ```code 32 | 'components' => array( 33 | ... 34 | 'image' => array( 35 | 'class' => 'yii\image\ImageDriver', 36 | 'driver' => 'GD', //GD or Imagick 37 | ), 38 | ) 39 | ``` 40 | Usage 41 | ----- 42 | ```php 43 | $file=Yii::getAlias('@app/pass/to/file'); 44 | $image=Yii::$app->image->load($file); 45 | header("Content-Type: image/png"); 46 | echo $image->resize($width,$height)->rotate(30)->render(); 47 | ``` 48 | 49 | Supported methods out of the box from Kohana Image Library: 50 | ```php 51 | $image->resize($width = NULL, $height = NULL, $master = NULL); 52 | $image->crop($width, $height, $offset_x = NULL, $offset_y = NULL); 53 | $image->sharpen($amount); 54 | $image->rotate($degrees); 55 | $image->save($file = NULL, $quality = 100); 56 | $image->render($type = NULL, $quality = 100); 57 | $image->reflection($height = NULL, $opacity = 100, $fade_in = FALSE); 58 | $image->flip($direction); 59 | $image->background($color, $opacity = 100); 60 | $image->watermark(Image $watermark, $offset_x = NULL, $offset_y = NULL, $opacity = 100); 61 | ``` 62 | **Using resize with resize constrains** 63 | ```php 64 | $image->resize($width, $height, \yii\image\drivers\Image::HEIGHT); 65 | $image->resize($width, $height, \yii\image\drivers\Image::ADAPT)->background('#fff'); 66 | ``` 67 | **Using resize with resize constrains and best quality output image [for Imagick driver only]** 68 | 69 | Use 1 for best speed and lower quality, 100 for best quality and lower speed. Only values 1,100 currently supported 70 | ```php 71 | $image->resize($width, NULL, \yii\image\drivers\Image::WIDTH, 100); 72 | ``` 73 | 74 | Possible resize constrains: 75 | ```php 76 | // Resizing constraints ($master) 77 | const NONE = 0x01; 78 | const WIDTH = 0x02; 79 | const HEIGHT = 0x03; 80 | const AUTO = 0x04; 81 | const INVERSE = 0x05; 82 | const PRECISE = 0x06; 83 | const ADAPT = 0x07; 84 | const CROP = 0x08; 85 | ``` 86 | **Using flip with flipping directions** 87 | ```php 88 | // Flipping directions ($direction) 89 | $image->flip(\yii\image\drivers\Image::HORIZONTAL); 90 | ``` 91 | Possible flipping directions: 92 | ```php 93 | const HORIZONTAL = 0x11; 94 | const VERTICAL = 0x12; 95 | ``` 96 | 97 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "yurkinx/yii2-image", 3 | "description": "Yii2 extension for image manipulating using Kohana Image Library.", 4 | "keywords": ["yii2", "extension", "image"], 5 | "homepage": "https://github.com/yurkinx/yii2-image", 6 | "type": "yii2-extension", 7 | "license": "BSD-3-Clause", 8 | "authors": [ 9 | { 10 | "name": "Yuri Kileev", 11 | "email": "kileev@gmail.com" 12 | } 13 | ], 14 | "minimum-stability": "dev", 15 | "require": { 16 | "php": ">=5.4.0", 17 | "yiisoft/yii2": "*" 18 | }, 19 | "autoload": { 20 | "psr-0": { 21 | "yii\\image\\": "" 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /yii/image/ImageDriver.php: -------------------------------------------------------------------------------- 1 | 5 | * @date : 17.10.2013 6 | */ 7 | 8 | namespace yii\image; 9 | 10 | use Yii; 11 | use yii\base\Component; 12 | use yii\base\ErrorException; 13 | use yii\image\drivers\Image; 14 | 15 | /** 16 | * Class ImageDriver 17 | * The main class to wrap Kohana Image Extension 18 | * @package yii\image 19 | */ 20 | class ImageDriver extends Component 21 | { 22 | 23 | /** 24 | * @var string default driver: GD or ImageMagick 25 | */ 26 | public $driver; 27 | 28 | /** 29 | * Loads the image to Kohana_Image object 30 | * @param string $file the file path to the image 31 | * @param string $driver the image driver to use: GD or ImageMagick 32 | * @throws ErrorException if filename is empty or file doesn't exist 33 | * @return mixed object Image_GD or object Image_Imagick 34 | */ 35 | public function load($file = null, $driver = null){ 36 | 37 | if(empty($file)){ 38 | throw new ErrorException('File name can not be empty'); 39 | } 40 | if(!realpath($file)){ 41 | throw new ErrorException(sprintf('The file doesn\'t exist: %s',$file)); 42 | } 43 | return Image::factory($file, $driver ? $driver : $this->driver); 44 | 45 | } 46 | } 47 | ?> -------------------------------------------------------------------------------- /yii/image/drivers/Image.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /yii/image/drivers/Image/GD.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /yii/image/drivers/Image/Imagick.php: -------------------------------------------------------------------------------- 1 | file = $file; 118 | $this->width = $info[0]; 119 | $this->height = $info[1]; 120 | $this->type = $info[2]; 121 | $this->mime = image_type_to_mime_type($this->type); 122 | } 123 | 124 | /** 125 | * Render the current image. 126 | * 127 | * echo $image; 128 | * 129 | * [!!] The output of this function is binary and must be rendered with the 130 | * appropriate Content-Type header or it will not be displayed correctly! 131 | * 132 | * @return string 133 | */ 134 | public function __toString() 135 | { 136 | try 137 | { 138 | // Render the current image 139 | return $this->render(); 140 | } 141 | catch (ErrorException $e) 142 | { 143 | /* 144 | if (is_object(Kohana::$log)) 145 | { 146 | // Get the text of the exception 147 | $error = ErrorException::text($e); 148 | 149 | // Add this exception to the log 150 | Yii::error($error); 151 | } 152 | */ 153 | 154 | // Showing any kind of error will be "inside" image data 155 | return ''; 156 | } 157 | } 158 | 159 | /** 160 | * Resize the image to the given size. Either the width or the height can 161 | * be omitted and the image will be resized proportionally. 162 | * 163 | * // Resize to 200 pixels on the shortest side 164 | * $image->resize(200, 200); 165 | * 166 | * // Resize to 200x200 pixels, keeping aspect ratio 167 | * $image->resize(200, 200, Image::INVERSE); 168 | * 169 | * // Resize to 500 pixel width, keeping aspect ratio 170 | * $image->resize(500, NULL); 171 | * 172 | * // Resize to 500 pixel height, keeping aspect ratio 173 | * $image->resize(NULL, 500); 174 | * 175 | * // Resize to 200x500 pixels, ignoring aspect ratio 176 | * $image->resize(200, 500, Image::NONE); 177 | * 178 | * // Resize to 400 pixels on the shortest side, puts it in the center 179 | * // of the image with the transparent edges, keeping aspect ratio, 180 | * // output size will be 400x400 pixels 181 | * $image->resize(400, 400, Image::ADAPT); 182 | * 183 | * // Resize to 500 pixels width, keeping aspect ratio 184 | * // and trying to achieve best quality, using slower filters 185 | * 186 | * $image->resize(500, NULL, NULL, 100) 187 | * 188 | * @param integer $width new width 189 | * @param integer $height new height 190 | * @param integer $master master dimension 191 | * @param integer $quality quality of the output image 1-100 (range is reserved for future use) use 1 for best speed, 100 for best quality. Default 1, best speed 192 | * @return $this 193 | * @uses Image::_do_resize 194 | */ 195 | public function resize($width = NULL, $height = NULL, $master = NULL, $quality = NULL) 196 | { 197 | if($quality === NULL){ 198 | $quality = 1; 199 | } 200 | if ($master === NULL) 201 | { 202 | // Choose the master dimension automatically 203 | $master = Image::AUTO; 204 | } 205 | elseif ($master === Image::CROP) 206 | { 207 | if (empty($width) || empty($height)) 208 | { 209 | return $this->resize($width, $height, Image::AUTO); 210 | } 211 | 212 | $master = $this->width / $this->height > $width / $height ? Image::HEIGHT : Image::WIDTH; 213 | $this->resize($width, $height, $master); 214 | 215 | if ($this->width !== $width || $this->height !== $height) 216 | { 217 | $offset_x = round(($this->width - $width) / 2); 218 | $offset_y = round(($this->height - $height) / 2); 219 | $this->crop($width, $height, $offset_x, $offset_y); 220 | } 221 | 222 | return $this; 223 | } 224 | // Image::WIDTH and Image::HEIGHT deprecated. You can use it in old projects, 225 | // but in new you must pass empty value for non-master dimension 226 | elseif ($master == Image::WIDTH AND ! empty($width)) 227 | { 228 | $master = Image::AUTO; 229 | 230 | // Set empty height for backward compatibility 231 | $height = NULL; 232 | } 233 | elseif ($master == Image::HEIGHT AND ! empty($height)) 234 | { 235 | $master = Image::AUTO; 236 | 237 | // Set empty width for backward compatibility 238 | $width = NULL; 239 | } 240 | elseif ($master === Image::ADAPT) 241 | { 242 | if (empty($width)) 243 | { 244 | $width = $this->width * $height / $this->height; 245 | } 246 | elseif (empty($height)) 247 | { 248 | $height = $this->height * $width / $this->width; 249 | } 250 | } 251 | 252 | if (empty($width)) 253 | { 254 | if ($master === Image::NONE) 255 | { 256 | // Use the current width 257 | $width = $this->width; 258 | } 259 | else 260 | { 261 | // If width not set, master will be height 262 | $master = Image::HEIGHT; 263 | } 264 | } 265 | 266 | if (empty($height)) 267 | { 268 | if ($master === Image::NONE) 269 | { 270 | // Use the current height 271 | $height = $this->height; 272 | } 273 | else 274 | { 275 | // If height not set, master will be width 276 | $master = Image::WIDTH; 277 | } 278 | } 279 | 280 | switch ($master) 281 | { 282 | case Image::AUTO: 283 | // Choose direction with the greatest reduction ratio 284 | $master = ($this->width / $width) > ($this->height / $height) ? Image::WIDTH : Image::HEIGHT; 285 | break; 286 | case Image::INVERSE: 287 | // Choose direction with the minimum reduction ratio 288 | $master = ($this->width / $width) > ($this->height / $height) ? Image::HEIGHT : Image::WIDTH; 289 | break; 290 | } 291 | 292 | switch ($master) 293 | { 294 | case Image::WIDTH: 295 | // Recalculate the height based on the width proportions 296 | $height = $this->height * $width / $this->width; 297 | break; 298 | case Image::HEIGHT: 299 | // Recalculate the width based on the height proportions 300 | $width = $this->width * $height / $this->height; 301 | break; 302 | case Image::PRECISE: 303 | // Resize to precise size 304 | $ratio = $this->width / $this->height; 305 | 306 | if ($width / $height > $ratio) 307 | { 308 | $height = $this->height * $width / $this->width; 309 | } 310 | else 311 | { 312 | $width = $this->width * $height / $this->height; 313 | } 314 | break; 315 | } 316 | 317 | // Convert the width and height to integers, minimum value is 1px 318 | $width = max(round($width), 1); 319 | $height = max(round($height), 1); 320 | 321 | // Adapt the image if the ratios are not equivalent 322 | if ($master === Image::ADAPT && $width / $height !== $this->width / $this->height) 323 | { 324 | $image_width = $bg_width = $this->width; 325 | $image_height = $bg_height = $this->height; 326 | 327 | $offset_x = $offset_y = 0; 328 | 329 | if ($width / $height > $image_width / $image_height) 330 | { 331 | $bg_width = floor($image_height * $width / $height); 332 | $offset_x = abs(floor(($bg_width - $image_width) / 2)); 333 | } 334 | else 335 | { 336 | $bg_height = floor($image_width * $height / $width); 337 | $offset_y = abs(floor(($bg_height - $image_height) / 2)); 338 | } 339 | 340 | $this->_do_adapt($image_width, $image_height, $bg_width, $bg_height, $offset_x, $offset_y); 341 | } 342 | 343 | // The quality must be in the range of 1 to 100 344 | $quality = min(max($quality, 1), 100); 345 | 346 | $this->_do_resize($width, $height, $quality); 347 | 348 | return $this; 349 | } 350 | 351 | /** 352 | * Crop an image to the given size. Either the width or the height can be 353 | * omitted and the current width or height will be used. 354 | * 355 | * If no offset is specified, the center of the axis will be used. 356 | * If an offset of TRUE is specified, the bottom of the axis will be used. 357 | * 358 | * // Crop the image to 200x200 pixels, from the center 359 | * $image->crop(200, 200); 360 | * 361 | * @param integer $width new width 362 | * @param integer $height new height 363 | * @param mixed $offset_x offset from the left 364 | * @param mixed $offset_y offset from the top 365 | * @return $this 366 | * @uses Image::_do_crop 367 | */ 368 | public function crop($width, $height, $offset_x = NULL, $offset_y = NULL) 369 | { 370 | if ($width > $this->width) 371 | { 372 | // Use the current width 373 | $width = $this->width; 374 | } 375 | 376 | if ($height > $this->height) 377 | { 378 | // Use the current height 379 | $height = $this->height; 380 | } 381 | 382 | if ($offset_x === NULL) 383 | { 384 | // Center the X offset 385 | $offset_x = round(($this->width - $width) / 2); 386 | } 387 | elseif ($offset_x === TRUE) 388 | { 389 | // Bottom the X offset 390 | $offset_x = $this->width - $width; 391 | } 392 | elseif ($offset_x < 0) 393 | { 394 | // Set the X offset from the right 395 | $offset_x = $this->width - $width + $offset_x; 396 | } 397 | 398 | if ($offset_y === NULL) 399 | { 400 | // Center the Y offset 401 | $offset_y = round(($this->height - $height) / 2); 402 | } 403 | elseif ($offset_y === TRUE) 404 | { 405 | // Bottom the Y offset 406 | $offset_y = $this->height - $height; 407 | } 408 | elseif ($offset_y < 0) 409 | { 410 | // Set the Y offset from the bottom 411 | $offset_y = $this->height - $height + $offset_y; 412 | } 413 | 414 | // Determine the maximum possible width and height 415 | $max_width = $this->width - $offset_x; 416 | $max_height = $this->height - $offset_y; 417 | 418 | if ($width > $max_width) 419 | { 420 | // Use the maximum available width 421 | $width = $max_width; 422 | } 423 | 424 | if ($height > $max_height) 425 | { 426 | // Use the maximum available height 427 | $height = $max_height; 428 | } 429 | 430 | $this->_do_crop($width, $height, $offset_x, $offset_y); 431 | 432 | return $this; 433 | } 434 | 435 | /** 436 | * Rotate the image by a given amount. 437 | * 438 | * // Rotate 45 degrees clockwise 439 | * $image->rotate(45); 440 | * 441 | * // Rotate 90% counter-clockwise 442 | * $image->rotate(-90); 443 | * 444 | * @param integer $degrees degrees to rotate: -360-360 445 | * @return $this 446 | * @uses Image::_do_rotate 447 | */ 448 | public function rotate($degrees) 449 | { 450 | // Make the degrees an integer 451 | $degrees = (int) $degrees; 452 | 453 | if ($degrees > 180) 454 | { 455 | do 456 | { 457 | // Keep subtracting full circles until the degrees have normalized 458 | $degrees -= 360; 459 | } 460 | while ($degrees > 180); 461 | } 462 | 463 | if ($degrees < -180) 464 | { 465 | do 466 | { 467 | // Keep adding full circles until the degrees have normalized 468 | $degrees += 360; 469 | } 470 | while ($degrees < -180); 471 | } 472 | 473 | $this->_do_rotate($degrees); 474 | 475 | return $this; 476 | } 477 | 478 | /** 479 | * Flip the image along the horizontal or vertical axis. 480 | * 481 | * // Flip the image from top to bottom 482 | * $image->flip(Image::HORIZONTAL); 483 | * 484 | * // Flip the image from left to right 485 | * $image->flip(Image::VERTICAL); 486 | * 487 | * @param integer $direction direction: Image::HORIZONTAL, Image::VERTICAL 488 | * @return $this 489 | * @uses Image::_do_flip 490 | */ 491 | public function flip($direction) 492 | { 493 | if ($direction !== Image::HORIZONTAL) 494 | { 495 | // Flip vertically 496 | $direction = Image::VERTICAL; 497 | } 498 | 499 | $this->_do_flip($direction); 500 | 501 | return $this; 502 | } 503 | 504 | /** 505 | * Sharpen the image by a given amount. 506 | * 507 | * // Sharpen the image by 20% 508 | * $image->sharpen(20); 509 | * 510 | * @param integer $amount amount to sharpen: 1-100 511 | * @return $this 512 | * @uses Image::_do_sharpen 513 | */ 514 | public function sharpen($amount) 515 | { 516 | // The amount must be in the range of 1 to 100 517 | $amount = min(max($amount, 1), 100); 518 | 519 | $this->_do_sharpen($amount); 520 | 521 | return $this; 522 | } 523 | 524 | /** 525 | * Add a reflection to an image. The most opaque part of the reflection 526 | * will be equal to the opacity setting and fade out to full transparent. 527 | * Alpha transparency is preserved. 528 | * 529 | * // Create a 50 pixel reflection that fades from 0-100% opacity 530 | * $image->reflection(50); 531 | * 532 | * // Create a 50 pixel reflection that fades from 100-0% opacity 533 | * $image->reflection(50, 100, TRUE); 534 | * 535 | * // Create a 50 pixel reflection that fades from 0-60% opacity 536 | * $image->reflection(50, 60, TRUE); 537 | * 538 | * [!!] By default, the reflection will be go from transparent at the top 539 | * to opaque at the bottom. 540 | * 541 | * @param integer $height reflection height 542 | * @param integer $opacity reflection opacity: 0-100 543 | * @param boolean $fade_in TRUE to fade in, FALSE to fade out 544 | * @return $this 545 | * @uses Image::_do_reflection 546 | */ 547 | public function reflection($height = NULL, $opacity = 100, $fade_in = FALSE) 548 | { 549 | if ($height === NULL OR $height > $this->height) 550 | { 551 | // Use the current height 552 | $height = $this->height; 553 | } 554 | 555 | // The opacity must be in the range of 0 to 100 556 | $opacity = min(max($opacity, 0), 100); 557 | 558 | $this->_do_reflection($height, $opacity, $fade_in); 559 | 560 | return $this; 561 | } 562 | 563 | /** 564 | * Add a watermark to an image with a specified opacity. Alpha transparency 565 | * will be preserved. 566 | * 567 | * If no offset is specified, the center of the axis will be used. 568 | * If an offset of TRUE is specified, the bottom of the axis will be used. 569 | * 570 | * // Add a watermark to the bottom right of the image 571 | * $mark = Image::factory('upload/watermark.png'); 572 | * $image->watermark($mark, TRUE, TRUE); 573 | * 574 | * @param Kohana_Image $watermark watermark Image instance 575 | * @param integer $offset_x offset from the left 576 | * @param integer $offset_y offset from the top 577 | * @param integer $opacity opacity of watermark: 1-100 578 | * @return $this 579 | * @uses Image::_do_watermark 580 | */ 581 | public function watermark(Kohana_Image $watermark, $offset_x = NULL, $offset_y = NULL, $opacity = 100) 582 | { 583 | if ($offset_x === NULL) 584 | { 585 | // Center the X offset 586 | $offset_x = round(($this->width - $watermark->width) / 2); 587 | } 588 | elseif ($offset_x === TRUE) 589 | { 590 | // Bottom the X offset 591 | $offset_x = $this->width - $watermark->width; 592 | } 593 | elseif ($offset_x < 0) 594 | { 595 | // Set the X offset from the right 596 | $offset_x = $this->width - $watermark->width + $offset_x; 597 | } 598 | 599 | if ($offset_y === NULL) 600 | { 601 | // Center the Y offset 602 | $offset_y = round(($this->height - $watermark->height) / 2); 603 | } 604 | elseif ($offset_y === TRUE) 605 | { 606 | // Bottom the Y offset 607 | $offset_y = $this->height - $watermark->height; 608 | } 609 | elseif ($offset_y < 0) 610 | { 611 | // Set the Y offset from the bottom 612 | $offset_y = $this->height - $watermark->height + $offset_y; 613 | } 614 | 615 | // The opacity must be in the range of 1 to 100 616 | $opacity = min(max($opacity, 1), 100); 617 | 618 | $this->_do_watermark($watermark, $offset_x, $offset_y, $opacity); 619 | 620 | return $this; 621 | } 622 | 623 | /** 624 | * Set the background color of an image. This is only useful for images 625 | * with alpha transparency. 626 | * 627 | * // Make the image background black 628 | * $image->background('#000'); 629 | * 630 | * // Make the image background black with 50% opacity 631 | * $image->background('#000', 50); 632 | * 633 | * @param string $color hexadecimal color value 634 | * @param integer $opacity background opacity: 0-100 635 | * @return $this 636 | * @uses Image::_do_background 637 | */ 638 | public function background($color, $opacity = 100) 639 | { 640 | if ($color[0] === '#') 641 | { 642 | // Remove the pound 643 | $color = substr($color, 1); 644 | } 645 | 646 | if (strlen($color) === 3) 647 | { 648 | // Convert shorthand into longhand hex notation 649 | $color = preg_replace('/./', '$0$0', $color); 650 | } 651 | 652 | // Convert the hex into RGB values 653 | list ($r, $g, $b) = array_map('hexdec', str_split($color, 2)); 654 | 655 | // The opacity must be in the range of 0 to 100 656 | $opacity = min(max($opacity, 0), 100); 657 | 658 | $this->_do_background($r, $g, $b, $opacity); 659 | 660 | return $this; 661 | } 662 | 663 | /** 664 | * Save the image. If the filename is omitted, the original image will 665 | * be overwritten. 666 | * 667 | * // Save the image as a PNG 668 | * $image->save('saved/cool.png'); 669 | * 670 | * // Overwrite the original image 671 | * $image->save(); 672 | * 673 | * [!!] If the file exists, but is not writable, an exception will be thrown. 674 | * 675 | * [!!] If the file does not exist, and the directory is not writable, an 676 | * exception will be thrown. 677 | * 678 | * @param string $file new image path 679 | * @param integer $quality quality of image: 1-100 680 | * @return boolean 681 | * @uses Image::_save 682 | * @throws ErrorException 683 | */ 684 | public function save($file = NULL, $quality = 100) 685 | { 686 | if ($file === NULL) 687 | { 688 | // Overwrite the file 689 | $file = $this->file; 690 | } 691 | 692 | if (is_file($file)) 693 | { 694 | if ( ! is_writable($file)) 695 | { 696 | throw new ErrorException(sprintf('File must be writable: %s',$file)); 697 | } 698 | } 699 | else 700 | { 701 | // Get the directory of the file 702 | $directory = realpath(pathinfo($file, PATHINFO_DIRNAME)); 703 | 704 | if ( ! is_dir($directory) OR ! is_writable($directory)) 705 | { 706 | throw new ErrorException(sprintf('Directory must be writable: %s',$directory)); 707 | } 708 | } 709 | 710 | // The quality must be in the range of 1 to 100 711 | $quality = min(max($quality, 1), 100); 712 | 713 | return $this->_do_save($file, $quality); 714 | } 715 | 716 | /** 717 | * Enable or disable interlace for progressive images 718 | * 719 | * GD 720 | * Just use true or false for enable or disable interlace for jpeg files. 721 | * 722 | * Imagick 723 | * Use INTERLACE constants from http://php.net/manual/en/imagick.constants.php 724 | * 725 | * @param $scheme bool|int 726 | * @return mixed 727 | */ 728 | public function interlace($scheme) 729 | { 730 | return $this->_do_interlace($scheme); 731 | } 732 | 733 | /** 734 | * Render the image and return the binary string. 735 | * 736 | * // Render the image at 50% quality 737 | * $data = $image->render(NULL, 50); 738 | * 739 | * // Render the image as a PNG 740 | * $data = $image->render('png'); 741 | * 742 | * @param string $type image type to return: png, jpg, gif, etc 743 | * @param integer $quality quality of image: 1-100 744 | * @return string 745 | * @uses Image::_do_render 746 | */ 747 | public function render($type = NULL, $quality = 100) 748 | { 749 | if ($type === NULL) 750 | { 751 | // Use the current image type 752 | $type = image_type_to_extension($this->type, FALSE); 753 | } 754 | 755 | return $this->_do_render($type, $quality); 756 | } 757 | 758 | /** 759 | * Execute a resize. 760 | * 761 | * @param integer $width new width 762 | * @param integer $height new height 763 | * @param integer $quality quality of output image: 1-100. default 1 for best resizing speed 764 | * @return void 765 | */ 766 | abstract protected function _do_resize($width, $height, $quality); 767 | 768 | /** 769 | * Adaptation the image. 770 | * 771 | * @param integer $width image width 772 | * @param integer $height image height 773 | * @param integer $bg_width background width 774 | * @param integer $bg_height background height 775 | * @param integer $offset_x offset from the left 776 | * @param integer $offset_y offset from the top 777 | */ 778 | abstract protected function _do_adapt($width, $height, $bg_width, $bg_height, $offset_x, $offset_y); 779 | 780 | /** 781 | * Execute a crop. 782 | * 783 | * @param integer $width new width 784 | * @param integer $height new height 785 | * @param integer $offset_x offset from the left 786 | * @param integer $offset_y offset from the top 787 | * @return void 788 | */ 789 | abstract protected function _do_crop($width, $height, $offset_x, $offset_y); 790 | 791 | /** 792 | * Execute a rotation. 793 | * 794 | * @param integer $degrees degrees to rotate 795 | * @return void 796 | */ 797 | abstract protected function _do_rotate($degrees); 798 | 799 | /** 800 | * Execute a flip. 801 | * 802 | * @param integer $direction direction to flip 803 | * @return void 804 | */ 805 | abstract protected function _do_flip($direction); 806 | 807 | /** 808 | * Execute a sharpen. 809 | * 810 | * @param integer $amount amount to sharpen 811 | * @return void 812 | */ 813 | abstract protected function _do_sharpen($amount); 814 | 815 | /** 816 | * Execute a reflection. 817 | * 818 | * @param integer $height reflection height 819 | * @param integer $opacity reflection opacity 820 | * @param boolean $fade_in TRUE to fade out, FALSE to fade in 821 | * @return void 822 | */ 823 | abstract protected function _do_reflection($height, $opacity, $fade_in); 824 | 825 | /** 826 | * Execute a watermarking. 827 | * 828 | * @param Kohana_Image $image watermarking Kohana_Image 829 | * @param integer $offset_x offset from the left 830 | * @param integer $offset_y offset from the top 831 | * @param integer $opacity opacity of watermark 832 | * @return void 833 | */ 834 | abstract protected function _do_watermark(Kohana_Image $image, $offset_x, $offset_y, $opacity); 835 | 836 | /** 837 | * Execute a background. 838 | * 839 | * @param integer $r red 840 | * @param integer $g green 841 | * @param integer $b blue 842 | * @param integer $opacity opacity 843 | * @return void 844 | */ 845 | abstract protected function _do_background($r, $g, $b, $opacity); 846 | 847 | /** 848 | * Execute a save. 849 | * 850 | * @param string $file new image filename 851 | * @param integer $quality quality 852 | * @return boolean 853 | */ 854 | abstract protected function _do_save($file, $quality); 855 | 856 | /** 857 | * Execute a render. 858 | * 859 | * @param string $type image type: png, jpg, gif, etc 860 | * @param integer $quality quality 861 | * @return string 862 | */ 863 | abstract protected function _do_render($type, $quality); 864 | 865 | /** 866 | * Execute an interlace. 867 | * 868 | * @param $scheme 869 | * @return mixed 870 | */ 871 | abstract protected function _do_interlace($scheme); 872 | 873 | } // End Image 874 | -------------------------------------------------------------------------------- /yii/image/drivers/Kohana/Image/GD.php: -------------------------------------------------------------------------------- 1 | =')) 65 | { 66 | throw new ErrorException(sprintf('Image_GD requires GD version 2.0.1 or greater, you have %s',$version)); 67 | } 68 | 69 | return Image_GD::$_checked = TRUE; 70 | } 71 | 72 | /* @var resource Temporary image resource */ 73 | protected $_image; 74 | 75 | /* @var string Function name to open Image */ 76 | protected $_create_function; 77 | 78 | /** 79 | * Runs [Image_GD::check] and loads the image. 80 | * 81 | * @param string $file image file path 82 | * @return void 83 | * @throws ErrorException 84 | */ 85 | public function __construct($file) 86 | { 87 | if ( ! Image_GD::$_checked) 88 | { 89 | // Run the install check 90 | Image_GD::check(); 91 | } 92 | 93 | parent::__construct($file); 94 | 95 | // Set the image creation function name 96 | switch ($this->type) 97 | { 98 | case IMAGETYPE_JPEG: 99 | $create = 'imagecreatefromjpeg'; 100 | break; 101 | case IMAGETYPE_GIF: 102 | $create = 'imagecreatefromgif'; 103 | break; 104 | case IMAGETYPE_PNG: 105 | $create = 'imagecreatefrompng'; 106 | break; 107 | } 108 | 109 | if ( ! isset($create) OR ! function_exists($create)) 110 | { 111 | throw new ErrorException(sprintf('Installed GD does not support %s images',image_type_to_extension($this->type, FALSE))); 112 | } 113 | 114 | // Save function for future use 115 | $this->_create_function = $create; 116 | 117 | // Save filename for lazy loading 118 | $this->_image = $this->file; 119 | } 120 | 121 | /** 122 | * Destroys the loaded image to free up resources. 123 | * 124 | * @return void 125 | */ 126 | public function __destruct() 127 | { 128 | if (is_resource($this->_image)) 129 | { 130 | // Free all resources 131 | imagedestroy($this->_image); 132 | } 133 | } 134 | 135 | /** 136 | * Loads an image into GD. 137 | * 138 | * @return void 139 | */ 140 | protected function _load_image() 141 | { 142 | if ( ! is_resource($this->_image)) 143 | { 144 | // Gets create function 145 | $create = $this->_create_function; 146 | 147 | // Open the temporary image 148 | $this->_image = $create($this->file); 149 | 150 | // Preserve transparency when saving 151 | imagesavealpha($this->_image, TRUE); 152 | } 153 | } 154 | 155 | /** 156 | * Execute a resize. 157 | * 158 | * @param integer $width new width 159 | * @param integer $height new height 160 | * @param integer $quality - reserved for future use 161 | * @return void 162 | */ 163 | protected function _do_resize($width, $height, $quality) 164 | { 165 | // Presize width and height 166 | $pre_width = $this->width; 167 | $pre_height = $this->height; 168 | 169 | // Loads image if not yet loaded 170 | $this->_load_image(); 171 | 172 | // Test if we can do a resize without resampling to speed up the final resize 173 | if ($width > ($this->width / 2) AND $height > ($this->height / 2)) 174 | { 175 | // The maximum reduction is 10% greater than the final size 176 | $reduction_width = round($width * 1.1); 177 | $reduction_height = round($height * 1.1); 178 | 179 | while ($pre_width / 2 > $reduction_width AND $pre_height / 2 > $reduction_height) 180 | { 181 | // Reduce the size using an O(2n) algorithm, until it reaches the maximum reduction 182 | $pre_width /= 2; 183 | $pre_height /= 2; 184 | } 185 | 186 | // Create the temporary image to copy to 187 | $image = $this->_create($pre_width, $pre_height); 188 | 189 | if (imagecopyresized($image, $this->_image, 0, 0, 0, 0, $pre_width, $pre_height, $this->width, $this->height)) 190 | { 191 | // Swap the new image for the old one 192 | imagedestroy($this->_image); 193 | $this->_image = $image; 194 | } 195 | } 196 | 197 | // Create the temporary image to copy to 198 | $image = $this->_create($width, $height); 199 | 200 | // Execute the resize 201 | if (imagecopyresampled($image, $this->_image, 0, 0, 0, 0, $width, $height, $pre_width, $pre_height)) 202 | { 203 | // Swap the new image for the old one 204 | imagedestroy($this->_image); 205 | $this->_image = $image; 206 | 207 | // Reset the width and height 208 | $this->width = imagesx($image); 209 | $this->height = imagesy($image); 210 | } 211 | } 212 | 213 | /** 214 | * Adaptation the image. 215 | * 216 | * @param integer $width image width 217 | * @param integer $height image height 218 | * @param integer $bg_width background width 219 | * @param integer $bg_height background height 220 | * @param integer $offset_x offset from the left 221 | * @param integer $offset_y offset from the top 222 | */ 223 | protected function _do_adapt($width, $height, $bg_width, $bg_height, $offset_x, $offset_y) 224 | { 225 | $this->_load_image(); 226 | $image = $this->_image; 227 | $this->_image = $this->_create($bg_width, $bg_height); 228 | $this->width = $bg_width; 229 | $this->height = $bg_height; 230 | imagealphablending($this->_image, false); 231 | $col = imagecolorallocatealpha($this->_image, 0, 255, 0, 127); 232 | imagefilledrectangle($this->_image, 0, 0, $bg_width, $bg_height, $col); 233 | imagealphablending($this->_image, true); 234 | imagecopy($this->_image, $image, $offset_x, $offset_y, 0, 0, $width, $height); 235 | imagealphablending($this->_image, false); 236 | imagesavealpha($this->_image, true); 237 | imagedestroy($image); 238 | } 239 | 240 | /** 241 | * Execute a crop. 242 | * 243 | * @param integer $width new width 244 | * @param integer $height new height 245 | * @param integer $offset_x offset from the left 246 | * @param integer $offset_y offset from the top 247 | * @return void 248 | */ 249 | protected function _do_crop($width, $height, $offset_x, $offset_y) 250 | { 251 | // Create the temporary image to copy to 252 | $image = $this->_create($width, $height); 253 | 254 | // Loads image if not yet loaded 255 | $this->_load_image(); 256 | 257 | // Execute the crop 258 | if (imagecopyresampled($image, $this->_image, 0, 0, $offset_x, $offset_y, $width, $height, $width, $height)) 259 | { 260 | // Swap the new image for the old one 261 | imagedestroy($this->_image); 262 | $this->_image = $image; 263 | 264 | // Reset the width and height 265 | $this->width = imagesx($image); 266 | $this->height = imagesy($image); 267 | } 268 | } 269 | 270 | /** 271 | * Execute a rotation. 272 | * 273 | * @param integer $degrees degrees to rotate 274 | * @return void 275 | */ 276 | protected function _do_rotate($degrees) 277 | { 278 | if (empty(Image_GD::$_available_functions[Image_GD::IMAGEROTATE])) 279 | { 280 | throw new ErrorException('This method requires imagerotate, which is only available in the bundled version of GD'); 281 | } 282 | 283 | // Loads image if not yet loaded 284 | $this->_load_image(); 285 | 286 | // Transparent black will be used as the background for the uncovered region 287 | $transparent = imagecolorallocatealpha($this->_image, 0, 0, 0, 127); 288 | 289 | // Rotate, setting the transparent color 290 | $image = imagerotate($this->_image, 360 - $degrees, $transparent, 1); 291 | 292 | // Save the alpha of the rotated image 293 | imagesavealpha($image, TRUE); 294 | 295 | // Get the width and height of the rotated image 296 | $width = imagesx($image); 297 | $height = imagesy($image); 298 | 299 | if (imagecopymerge($this->_image, $image, 0, 0, 0, 0, $width, $height, 100)) 300 | { 301 | // Swap the new image for the old one 302 | imagedestroy($this->_image); 303 | $this->_image = $image; 304 | 305 | // Reset the width and height 306 | $this->width = $width; 307 | $this->height = $height; 308 | } 309 | } 310 | 311 | /** 312 | * Execute a flip. 313 | * 314 | * @param integer $direction direction to flip 315 | * @return void 316 | */ 317 | protected function _do_flip($direction) 318 | { 319 | // Create the flipped image 320 | $flipped = $this->_create($this->width, $this->height); 321 | 322 | // Loads image if not yet loaded 323 | $this->_load_image(); 324 | 325 | if ($direction === Image::HORIZONTAL) 326 | { 327 | for ($x = 0; $x < $this->width; $x++) 328 | { 329 | // Flip each row from top to bottom 330 | imagecopy($flipped, $this->_image, $x, 0, $this->width - $x - 1, 0, 1, $this->height); 331 | } 332 | } 333 | else 334 | { 335 | for ($y = 0; $y < $this->height; $y++) 336 | { 337 | // Flip each column from left to right 338 | imagecopy($flipped, $this->_image, 0, $y, 0, $this->height - $y - 1, $this->width, 1); 339 | } 340 | } 341 | 342 | // Swap the new image for the old one 343 | imagedestroy($this->_image); 344 | $this->_image = $flipped; 345 | 346 | // Reset the width and height 347 | $this->width = imagesx($flipped); 348 | $this->height = imagesy($flipped); 349 | } 350 | 351 | /** 352 | * Execute a sharpen. 353 | * 354 | * @param integer $amount amount to sharpen 355 | * @return void 356 | */ 357 | protected function _do_sharpen($amount) 358 | { 359 | if (empty(Image_GD::$_available_functions[Image_GD::IMAGECONVOLUTION])) 360 | { 361 | throw new ErrorException('This method requires imageconvolution, which is only available in the bundled version of GD'); 362 | } 363 | 364 | // Loads image if not yet loaded 365 | $this->_load_image(); 366 | 367 | // Amount should be in the range of 18-10 368 | $amount = round(abs(-18 + ($amount * 0.08)), 2); 369 | 370 | // Gaussian blur matrix 371 | $matrix = array 372 | ( 373 | array(-1, -1, -1), 374 | array(-1, $amount, -1), 375 | array(-1, -1, -1), 376 | ); 377 | 378 | // Perform the sharpen 379 | if (imageconvolution($this->_image, $matrix, $amount - 8, 0)) 380 | { 381 | // Reset the width and height 382 | $this->width = imagesx($this->_image); 383 | $this->height = imagesy($this->_image); 384 | } 385 | } 386 | 387 | /** 388 | * Execute a reflection. 389 | * 390 | * @param integer $height reflection height 391 | * @param integer $opacity reflection opacity 392 | * @param boolean $fade_in TRUE to fade out, FALSE to fade in 393 | * @return void 394 | */ 395 | protected function _do_reflection($height, $opacity, $fade_in) 396 | { 397 | if (empty(Image_GD::$_available_functions[Image_GD::IMAGEFILTER])) 398 | { 399 | throw new ErrorException('This method requires imagefilter, which is only available in the bundled version of GD'); 400 | } 401 | 402 | // Loads image if not yet loaded 403 | $this->_load_image(); 404 | 405 | // Convert an opacity range of 0-100 to 127-0 406 | $opacity = round(abs(($opacity * 127 / 100) - 127)); 407 | 408 | if ($opacity < 127) 409 | { 410 | // Calculate the opacity stepping 411 | $stepping = (127 - $opacity) / $height; 412 | } 413 | else 414 | { 415 | // Avoid a "divide by zero" error 416 | $stepping = 127 / $height; 417 | } 418 | 419 | // Create the reflection image 420 | $reflection = $this->_create($this->width, $this->height + $height); 421 | 422 | // Copy the image to the reflection 423 | imagecopy($reflection, $this->_image, 0, 0, 0, 0, $this->width, $this->height); 424 | 425 | for ($offset = 0; $height >= $offset; $offset++) 426 | { 427 | // Read the next line down 428 | $src_y = $this->height - $offset - 1; 429 | 430 | // Place the line at the bottom of the reflection 431 | $dst_y = $this->height + $offset; 432 | 433 | if ($fade_in === TRUE) 434 | { 435 | // Start with the most transparent line first 436 | $dst_opacity = round($opacity + ($stepping * ($height - $offset))); 437 | } 438 | else 439 | { 440 | // Start with the most opaque line first 441 | $dst_opacity = round($opacity + ($stepping * $offset)); 442 | } 443 | 444 | // Create a single line of the image 445 | $line = $this->_create($this->width, 1); 446 | 447 | // Copy a single line from the current image into the line 448 | imagecopy($line, $this->_image, 0, 0, 0, $src_y, $this->width, 1); 449 | 450 | // Colorize the line to add the correct alpha level 451 | imagefilter($line, IMG_FILTER_COLORIZE, 0, 0, 0, $dst_opacity); 452 | 453 | // Copy a the line into the reflection 454 | imagecopy($reflection, $line, 0, $dst_y, 0, 0, $this->width, 1); 455 | } 456 | 457 | // Swap the new image for the old one 458 | imagedestroy($this->_image); 459 | $this->_image = $reflection; 460 | 461 | // Reset the width and height 462 | $this->width = imagesx($reflection); 463 | $this->height = imagesy($reflection); 464 | } 465 | 466 | /** 467 | * Execute a watermarking. 468 | * 469 | * @param Kohana_Image $image watermarking Kohana_Image 470 | * @param integer $offset_x offset from the left 471 | * @param integer $offset_y offset from the top 472 | * @param integer $opacity opacity of watermark 473 | * @return void 474 | */ 475 | protected function _do_watermark(Kohana_Image $watermark, $offset_x, $offset_y, $opacity) 476 | { 477 | if (empty(Image_GD::$_available_functions[Image_GD::IMAGELAYEREFFECT])) 478 | { 479 | throw new ErrorException('This method requires imagelayereffect, which is only available in the bundled version of GD'); 480 | } 481 | 482 | // Loads image if not yet loaded 483 | $this->_load_image(); 484 | 485 | // Create the watermark image resource 486 | $overlay = imagecreatefromstring($watermark->render()); 487 | 488 | imagesavealpha($overlay, TRUE); 489 | 490 | // Get the width and height of the watermark 491 | $width = imagesx($overlay); 492 | $height = imagesy($overlay); 493 | 494 | if ($opacity < 100) 495 | { 496 | // Convert an opacity range of 0-100 to 127-0 497 | $opacity = round(abs(($opacity * 127 / 100) - 127)); 498 | 499 | // Allocate transparent gray 500 | $color = imagecolorallocatealpha($overlay, 127, 127, 127, $opacity); 501 | 502 | // The transparent image will overlay the watermark 503 | imagelayereffect($overlay, IMG_EFFECT_OVERLAY); 504 | 505 | // Fill the background with the transparent color 506 | imagefilledrectangle($overlay, 0, 0, $width, $height, $color); 507 | } 508 | 509 | // Alpha blending must be enabled on the background! 510 | imagealphablending($this->_image, TRUE); 511 | 512 | if (imagecopy($this->_image, $overlay, $offset_x, $offset_y, 0, 0, $width, $height)) 513 | { 514 | // Destroy the overlay image 515 | imagedestroy($overlay); 516 | } 517 | } 518 | 519 | /** 520 | * Execute a background. 521 | * 522 | * @param integer $r red 523 | * @param integer $g green 524 | * @param integer $b blue 525 | * @param integer $opacity opacity 526 | * @return void 527 | */ 528 | protected function _do_background($r, $g, $b, $opacity) 529 | { 530 | // Loads image if not yet loaded 531 | $this->_load_image(); 532 | 533 | // Convert an opacity range of 0-100 to 127-0 534 | $opacity = round(abs(($opacity * 127 / 100) - 127)); 535 | 536 | // Create a new background 537 | $background = $this->_create($this->width, $this->height); 538 | 539 | // Allocate the color 540 | $color = imagecolorallocatealpha($background, $r, $g, $b, $opacity); 541 | 542 | // Fill the image with white 543 | imagefilledrectangle($background, 0, 0, $this->width, $this->height, $color); 544 | 545 | // Alpha blending must be enabled on the background! 546 | imagealphablending($background, TRUE); 547 | 548 | // Copy the image onto a white background to remove all transparency 549 | if (imagecopy($background, $this->_image, 0, 0, 0, 0, $this->width, $this->height)) 550 | { 551 | // Swap the new image for the old one 552 | imagedestroy($this->_image); 553 | $this->_image = $background; 554 | } 555 | } 556 | 557 | /** 558 | * Execute a save. 559 | * 560 | * @param string $file new image filename 561 | * @param integer $quality quality 562 | * @return boolean 563 | */ 564 | protected function _do_save($file, $quality) 565 | { 566 | // Loads image if not yet loaded 567 | $this->_load_image(); 568 | 569 | // Get the extension of the file 570 | $extension = pathinfo($file, PATHINFO_EXTENSION); 571 | 572 | // Get the save function and IMAGETYPE 573 | list($save, $type) = $this->_save_function($extension, $quality); 574 | 575 | // Save the image to a file 576 | $status = isset($quality) ? $save($this->_image, $file, $quality) : $save($this->_image, $file); 577 | 578 | if ($status === TRUE AND $type !== $this->type) 579 | { 580 | // Reset the image type and mime type 581 | $this->type = $type; 582 | $this->mime = image_type_to_mime_type($type); 583 | } 584 | 585 | return TRUE; 586 | } 587 | 588 | /** 589 | * Execute a render. 590 | * 591 | * @param string $type image type: png, jpg, gif, etc 592 | * @param integer $quality quality 593 | * @return string 594 | */ 595 | protected function _do_render($type, $quality) 596 | { 597 | // Loads image if not yet loaded 598 | $this->_load_image(); 599 | 600 | // Get the save function and IMAGETYPE 601 | list($save, $type) = $this->_save_function($type, $quality); 602 | 603 | // Capture the output 604 | ob_start(); 605 | 606 | // Render the image 607 | $status = isset($quality) ? $save($this->_image, NULL, $quality) : $save($this->_image, NULL); 608 | 609 | if ($status === TRUE AND $type !== $this->type) 610 | { 611 | // Reset the image type and mime type 612 | $this->type = $type; 613 | $this->mime = image_type_to_mime_type($type); 614 | } 615 | 616 | return ob_get_clean(); 617 | } 618 | 619 | /** 620 | * Get the GD saving function and image type for this extension. 621 | * Also normalizes the quality setting 622 | * 623 | * @param string $extension image type: png, jpg, etc 624 | * @param integer $quality image quality 625 | * @return array save function, IMAGETYPE_* constant 626 | * @throws ErrorException 627 | */ 628 | protected function _save_function($extension, & $quality) 629 | { 630 | if ( ! $extension) 631 | { 632 | // Use the current image type 633 | $extension = image_type_to_extension($this->type, FALSE); 634 | } 635 | 636 | switch (strtolower($extension)) 637 | { 638 | case 'jpg': 639 | case 'jpeg': 640 | // Save a JPG file 641 | $save = 'imagejpeg'; 642 | $type = IMAGETYPE_JPEG; 643 | break; 644 | case 'gif': 645 | // Save a GIF file 646 | $save = 'imagegif'; 647 | $type = IMAGETYPE_GIF; 648 | 649 | // GIFs do not a quality setting 650 | $quality = NULL; 651 | break; 652 | case 'png': 653 | // Save a PNG file 654 | $save = 'imagepng'; 655 | $type = IMAGETYPE_PNG; 656 | 657 | // Use a compression level of 9 (does not affect quality!) 658 | $quality = 9; 659 | break; 660 | default: 661 | throw new ErrorException(sprintf('Installed GD does not support %s images',$extension)); 662 | break; 663 | } 664 | 665 | return array($save, $type); 666 | } 667 | 668 | /** 669 | * Create an empty image with the given width and height. 670 | * 671 | * @param integer $width image width 672 | * @param integer $height image height 673 | * @return resource 674 | */ 675 | protected function _create($width, $height) 676 | { 677 | // Create an empty image 678 | $image = imagecreatetruecolor($width, $height); 679 | 680 | // Do not apply alpha blending 681 | imagealphablending($image, FALSE); 682 | 683 | // Save alpha levels 684 | imagesavealpha($image, TRUE); 685 | 686 | return $image; 687 | } 688 | 689 | protected function _do_interlace($scheme) 690 | { 691 | // Loads image if not yet loaded 692 | $this->_load_image(); 693 | 694 | return imageinterlace($this->_image, $scheme); 695 | } 696 | 697 | } // End Image_GD 698 | -------------------------------------------------------------------------------- /yii/image/drivers/Kohana/Image/Imagick.php: -------------------------------------------------------------------------------- 1 | im = new Imagick; 57 | $this->im->readImage($file); 58 | 59 | // Force RGB colorspace if we are using non-RGB 60 | $imageColorspace = $this->im->getImageColorspace(); 61 | if ($imageColorspace !== Imagick::COLORSPACE_RGB && 62 | $imageColorspace !== Imagick::COLORSPACE_SRGB 63 | ) { 64 | $imageColorspace = Imagick::COLORSPACE_RGB; 65 | } 66 | if ($this->im->getImageColorspace() !== $imageColorspace) { 67 | $this->im->transformImageColorspace($imageColorspace); 68 | } 69 | $this->im->setColorspace($imageColorspace); 70 | 71 | if ( ! $this->im->getImageAlphaChannel()) 72 | { 73 | // Force the image to have an alpha channel 74 | $this->im->setImageAlphaChannel(Imagick::ALPHACHANNEL_SET); 75 | } 76 | } 77 | 78 | /** 79 | * Destroys the loaded image to free up resources. 80 | * 81 | * @return void 82 | */ 83 | public function __destruct() 84 | { 85 | $this->im->clear(); 86 | $this->im->destroy(); 87 | } 88 | 89 | /** 90 | * Scales an image using resizeImage for best quality @link http://php.net/manual/en/imagick.resizeimage.php 91 | * but lower speed, or scaleImage for best speed and lower quality @link http://php.net/manual/en/imagick.scaleimage.php 92 | * @param int $width 93 | * @param int $height 94 | * @param int $quality 95 | * @return bool 96 | */ 97 | protected function _do_resize($width, $height, $quality) 98 | { 99 | if($quality == 100) { 100 | if ($this->im->resizeImage($width, $height, Imagick::FILTER_LANCZOS, 1)) { 101 | // Reset the width and height 102 | $this->width = $this->im->getImageWidth(); 103 | $this->height = $this->im->getImageHeight(); 104 | 105 | return TRUE; 106 | } 107 | }else{ 108 | if ($this->im->sampleImage($width, $height)) { 109 | // Reset the width and height 110 | $this->width = $this->im->getImageWidth(); 111 | $this->height = $this->im->getImageHeight(); 112 | 113 | return TRUE; 114 | } 115 | } 116 | 117 | return FALSE; 118 | } 119 | 120 | /** 121 | * Adaptation the image. 122 | * 123 | * @param integer $width image width 124 | * @param integer $height image height 125 | * @param integer $bg_width background width 126 | * @param integer $bg_height background height 127 | * @param integer $offset_x offset from the left 128 | * @param integer $offset_y offset from the top 129 | */ 130 | protected function _do_adapt($width, $height, $bg_width, $bg_height, $offset_x, $offset_y) 131 | { 132 | $image = new Imagick(); 133 | $image->newImage($bg_width, $bg_height, "none"); 134 | $image->compositeImage($this->im, Imagick::COMPOSITE_ADD, $offset_x, $offset_y); 135 | $this->im->clear(); 136 | $this->im->destroy(); 137 | $this->im = $image; 138 | $this->width = $bg_width; 139 | $this->height = $bg_height; 140 | } 141 | 142 | protected function _do_crop($width, $height, $offset_x, $offset_y) 143 | { 144 | if ($this->im->cropImage($width, $height, $offset_x, $offset_y)) 145 | { 146 | // Reset the width and height 147 | $this->width = $this->im->getImageWidth(); 148 | $this->height = $this->im->getImageHeight(); 149 | 150 | // Trim off hidden areas 151 | $this->im->setImagePage($this->width, $this->height, 0, 0); 152 | 153 | return TRUE; 154 | } 155 | 156 | return FALSE; 157 | } 158 | 159 | protected function _do_rotate($degrees) 160 | { 161 | if ($this->im->rotateImage(new ImagickPixel('transparent'), $degrees)) 162 | { 163 | // Reset the width and height 164 | $this->width = $this->im->getImageWidth(); 165 | $this->height = $this->im->getImageHeight(); 166 | 167 | // Trim off hidden areas 168 | $this->im->setImagePage($this->width, $this->height, 0, 0); 169 | 170 | return TRUE; 171 | } 172 | 173 | return FALSE; 174 | } 175 | 176 | protected function _do_flip($direction) 177 | { 178 | if ($direction === Image::HORIZONTAL) 179 | { 180 | return $this->im->flopImage(); 181 | } 182 | else 183 | { 184 | return $this->im->flipImage(); 185 | } 186 | } 187 | 188 | protected function _do_sharpen($amount) 189 | { 190 | // IM not support $amount under 5 (0.15) 191 | $amount = ($amount < 5) ? 5 : $amount; 192 | 193 | // Amount should be in the range of 0.0 to 3.0 194 | $amount = ($amount * 3.0) / 100; 195 | 196 | return $this->im->sharpenImage(0, $amount); 197 | } 198 | 199 | protected function _do_reflection($height, $opacity, $fade_in) 200 | { 201 | // Clone the current image and flip it for reflection 202 | $reflection = $this->im->clone(); 203 | $reflection->flipImage(); 204 | 205 | // Crop the reflection to the selected height 206 | $reflection->cropImage($this->width, $height, 0, 0); 207 | $reflection->setImagePage($this->width, $height, 0, 0); 208 | 209 | // Select the fade direction 210 | $direction = array('transparent', 'black'); 211 | 212 | if ($fade_in) 213 | { 214 | // Change the direction of the fade 215 | $direction = array_reverse($direction); 216 | } 217 | 218 | // Create a gradient for fading 219 | $fade = new Imagick; 220 | $fade->newPseudoImage($reflection->getImageWidth(), $reflection->getImageHeight(), vsprintf('gradient:%s-%s', $direction)); 221 | 222 | // Apply the fade alpha channel to the reflection 223 | $reflection->compositeImage($fade, Imagick::COMPOSITE_DSTOUT, 0, 0); 224 | 225 | // NOTE: Using setImageOpacity will destroy alpha channels! 226 | $reflection->evaluateImage(Imagick::EVALUATE_MULTIPLY, $opacity / 100, Imagick::CHANNEL_ALPHA); 227 | 228 | // Create a new container to hold the image and reflection 229 | $image = new Imagick; 230 | $image->newImage($this->width, $this->height + $height, new ImagickPixel); 231 | 232 | // Force the image to have an alpha channel 233 | $image->setImageAlphaChannel(Imagick::ALPHACHANNEL_SET); 234 | 235 | // Force the background color to be transparent 236 | // $image->setImageBackgroundColor(new ImagickPixel('transparent')); 237 | 238 | // Place the image and reflection into the container 239 | if ($image->compositeImage($this->im, Imagick::COMPOSITE_SRC, 0, 0) 240 | AND $image->compositeImage($reflection, Imagick::COMPOSITE_OVER, 0, $this->height)) 241 | { 242 | // Replace the current image with the reflected image 243 | $this->im = $image; 244 | 245 | // Reset the width and height 246 | $this->width = $this->im->getImageWidth(); 247 | $this->height = $this->im->getImageHeight(); 248 | 249 | return TRUE; 250 | } 251 | 252 | return FALSE; 253 | } 254 | 255 | protected function _do_watermark(Kohana_Image $image, $offset_x, $offset_y, $opacity) 256 | { 257 | // Convert the Image intance into an Imagick instance 258 | $watermark = new Imagick; 259 | $watermark->readImageBlob($image->render(), $image->file); 260 | if ($this->im->getImageColorspace() !== $this->im->getColorspace()) { 261 | $watermark->transformImageColorspace($this->im->getColorspace()); 262 | } 263 | 264 | if ($watermark->getImageAlphaChannel() !== Imagick::ALPHACHANNEL_ACTIVATE) 265 | { 266 | // Force the image to have an alpha channel 267 | $watermark->setImageAlphaChannel(Imagick::ALPHACHANNEL_OPAQUE); 268 | } 269 | 270 | if ($opacity < 100) 271 | { 272 | // NOTE: Using setImageOpacity will destroy current alpha channels! 273 | $watermark->evaluateImage(Imagick::EVALUATE_MULTIPLY, $opacity / 100, Imagick::CHANNEL_ALPHA); 274 | } 275 | 276 | // Apply the watermark to the image 277 | return $this->im->compositeImage($watermark, Imagick::COMPOSITE_DISSOLVE, $offset_x, $offset_y); 278 | } 279 | 280 | protected function _do_background($r, $g, $b, $opacity) 281 | { 282 | // Create a RGB color for the background 283 | $color = sprintf('rgb(%d, %d, %d)', $r, $g, $b); 284 | 285 | // Create a new image for the background 286 | $background = new Imagick; 287 | $background->newImage($this->width, $this->height, new ImagickPixel($color)); 288 | 289 | if ( ! $background->getImageAlphaChannel()) 290 | { 291 | // Force the image to have an alpha channel 292 | $background->setImageAlphaChannel(Imagick::ALPHACHANNEL_SET); 293 | } 294 | 295 | // Clear the background image 296 | $background->setImageBackgroundColor(new ImagickPixel('transparent')); 297 | 298 | // NOTE: Using setImageOpacity will destroy current alpha channels! 299 | $background->evaluateImage(Imagick::EVALUATE_MULTIPLY, $opacity / 100, Imagick::CHANNEL_ALPHA); 300 | 301 | if ($background->compositeImage($this->im, Imagick::COMPOSITE_DISSOLVE, 0, 0)) 302 | { 303 | // Replace the current image with the new image 304 | $this->im = $background; 305 | 306 | return TRUE; 307 | } 308 | 309 | return FALSE; 310 | } 311 | 312 | protected function _do_save($file, $quality) 313 | { 314 | // Get the image format and type 315 | list($format, $type) = $this->_get_imagetype(pathinfo($file, PATHINFO_EXTENSION)); 316 | 317 | // Set the output image type 318 | $this->im->setFormat($format); 319 | 320 | // Set the output quality 321 | $this->im->setImageCompressionQuality($quality); 322 | 323 | if ($this->im->writeImage($file)) 324 | { 325 | // Reset the image type and mime type 326 | $this->type = $type; 327 | $this->mime = image_type_to_mime_type($type); 328 | 329 | return TRUE; 330 | } 331 | 332 | return FALSE; 333 | } 334 | 335 | protected function _do_render($type, $quality) 336 | { 337 | // Get the image format and type 338 | list($format, $type) = $this->_get_imagetype($type); 339 | 340 | // Set the output image type 341 | $this->im->setFormat($format); 342 | 343 | // Set the output quality 344 | $this->im->setImageCompressionQuality($quality); 345 | 346 | // Reset the image type and mime type 347 | $this->type = $type; 348 | $this->mime = image_type_to_mime_type($type); 349 | 350 | return (string) $this->im; 351 | } 352 | 353 | /** 354 | * Get the image type and format for an extension. 355 | * 356 | * @param string $extension image extension: png, jpg, etc 357 | * @return string IMAGETYPE_* constant 358 | * @throws ErrorException 359 | */ 360 | protected function _get_imagetype($extension) 361 | { 362 | // Normalize the extension to a format 363 | $format = strtolower($extension); 364 | 365 | switch ($format) 366 | { 367 | case 'jpg': 368 | case 'jpeg': 369 | $type = IMAGETYPE_JPEG; 370 | break; 371 | case 'gif': 372 | $type = IMAGETYPE_GIF; 373 | break; 374 | case 'png': 375 | $type = IMAGETYPE_PNG; 376 | break; 377 | default: 378 | throw new ErrorException(sprintf('Installed ImageMagick does not support %s images',$extension)); 379 | break; 380 | } 381 | 382 | return array($format, $type); 383 | } 384 | 385 | protected function _do_interlace($scheme) 386 | { 387 | return $this->im->setInterlaceScheme($scheme); 388 | } 389 | } // End Kohana_Image_Imagick 390 | --------------------------------------------------------------------------------