1<?php defined('SYSPATH') OR die('No direct script access.'); 2/** 3 * Support for image manipulation using [Imagick](http://php.net/Imagick). 4 * 5 * @package Kohana/Image 6 * @category Drivers 7 * @author Tamas Mihalik tamas.mihalik@gmail.com 8 * @copyright (c) 2009-2012 Kohana Team 9 * @license http://kohanaphp.com/license.html 10 */ 11class Kohana_Image_Imagick extends Image { 12 13 /** 14 * @var Imagick image magick object 15 */ 16 protected $im; 17 18 /** 19 * Checks if ImageMagick is enabled. 20 * 21 * @throws Kohana_Exception 22 * @return boolean 23 */ 24 public static function check() 25 { 26 if ( ! extension_loaded('imagick')) 27 { 28 throw new Kohana_Exception('Imagick is not installed, or the extension is not loaded'); 29 } 30 31 return Image_Imagick::$_checked = TRUE; 32 } 33 34 /** 35 * Runs [Image_Imagick::check] and loads the image. 36 * 37 * @return void 38 * @throws Kohana_Exception 39 */ 40 public function __construct($file) 41 { 42 if ( ! Image_Imagick::$_checked) 43 { 44 // Run the install check 45 Image_Imagick::check(); 46 } 47 48 parent::__construct($file); 49 50 $this->im = new Imagick; 51 $this->im->readImage($file); 52 53 if ( ! $this->im->getImageAlphaChannel()) 54 { 55 // Force the image to have an alpha channel 56 $this->im->setImageAlphaChannel(Imagick::ALPHACHANNEL_SET); 57 } 58 } 59 60 /** 61 * Destroys the loaded image to free up resources. 62 * 63 * @return void 64 */ 65 public function __destruct() 66 { 67 $this->im->clear(); 68 $this->im->destroy(); 69 } 70 71 protected function _do_resize($width, $height) 72 { 73 if ($this->im->scaleImage($width, $height)) 74 { 75 // Reset the width and height 76 $this->width = $this->im->getImageWidth(); 77 $this->height = $this->im->getImageHeight(); 78 79 return TRUE; 80 } 81 82 return FALSE; 83 } 84 85 protected function _do_crop($width, $height, $offset_x, $offset_y) 86 { 87 if ($this->im->cropImage($width, $height, $offset_x, $offset_y)) 88 { 89 // Reset the width and height 90 $this->width = $this->im->getImageWidth(); 91 $this->height = $this->im->getImageHeight(); 92 93 // Trim off hidden areas 94 $this->im->setImagePage($this->width, $this->height, 0, 0); 95 96 return TRUE; 97 } 98 99 return FALSE; 100 } 101 102 protected function _do_rotate($degrees) 103 { 104 if ($this->im->rotateImage(new ImagickPixel('transparent'), $degrees)) 105 { 106 // Reset the width and height 107 $this->width = $this->im->getImageWidth(); 108 $this->height = $this->im->getImageHeight(); 109 110 // Trim off hidden areas 111 $this->im->setImagePage($this->width, $this->height, 0, 0); 112 113 return TRUE; 114 } 115 116 return FALSE; 117 } 118 119 protected function _do_flip($direction) 120 { 121 if ($direction === Image::HORIZONTAL) 122 { 123 return $this->im->flopImage(); 124 } 125 else 126 { 127 return $this->im->flipImage(); 128 } 129 } 130 131 protected function _do_sharpen($amount) 132 { 133 // IM not support $amount under 5 (0.15) 134 $amount = ($amount < 5) ? 5 : $amount; 135 136 // Amount should be in the range of 0.0 to 3.0 137 $amount = ($amount * 3.0) / 100; 138 139 return $this->im->sharpenImage(0, $amount); 140 } 141 142 protected function _do_reflection($height, $opacity, $fade_in) 143 { 144 // Clone the current image and flip it for reflection 145 $reflection = $this->im->clone(); 146 $reflection->flipImage(); 147 148 // Crop the reflection to the selected height 149 $reflection->cropImage($this->width, $height, 0, 0); 150 $reflection->setImagePage($this->width, $height, 0, 0); 151 152 // Select the fade direction 153 $direction = array('transparent', 'black'); 154 155 if ($fade_in) 156 { 157 // Change the direction of the fade 158 $direction = array_reverse($direction); 159 } 160 161 // Create a gradient for fading 162 $fade = new Imagick; 163 $fade->newPseudoImage($reflection->getImageWidth(), $reflection->getImageHeight(), vsprintf('gradient:%s-%s', $direction)); 164 165 // Apply the fade alpha channel to the reflection 166 $reflection->compositeImage($fade, Imagick::COMPOSITE_DSTOUT, 0, 0); 167 168 // NOTE: Using setImageOpacity will destroy alpha channels! 169 $reflection->evaluateImage(Imagick::EVALUATE_MULTIPLY, $opacity / 100, Imagick::CHANNEL_ALPHA); 170 171 // Create a new container to hold the image and reflection 172 $image = new Imagick; 173 $image->newImage($this->width, $this->height + $height, new ImagickPixel); 174 175 // Force the image to have an alpha channel 176 $image->setImageAlphaChannel(Imagick::ALPHACHANNEL_SET); 177 178 // Force the background color to be transparent 179 // $image->setImageBackgroundColor(new ImagickPixel('transparent')); 180 181 // Match the colorspace between the two images before compositing 182 $image->setColorspace($this->im->getColorspace()); 183 184 // Place the image and reflection into the container 185 if ($image->compositeImage($this->im, Imagick::COMPOSITE_SRC, 0, 0) 186 AND $image->compositeImage($reflection, Imagick::COMPOSITE_OVER, 0, $this->height)) 187 { 188 // Replace the current image with the reflected image 189 $this->im = $image; 190 191 // Reset the width and height 192 $this->width = $this->im->getImageWidth(); 193 $this->height = $this->im->getImageHeight(); 194 195 return TRUE; 196 } 197 198 return FALSE; 199 } 200 201 protected function _do_watermark(Image $image, $offset_x, $offset_y, $opacity) 202 { 203 // Convert the Image intance into an Imagick instance 204 $watermark = new Imagick; 205 $watermark->readImageBlob($image->render(), $image->file); 206 207 if ($watermark->getImageAlphaChannel() !== Imagick::ALPHACHANNEL_ACTIVATE) 208 { 209 // Force the image to have an alpha channel 210 $watermark->setImageAlphaChannel(Imagick::ALPHACHANNEL_OPAQUE); 211 } 212 213 if ($opacity < 100) 214 { 215 // NOTE: Using setImageOpacity will destroy current alpha channels! 216 $watermark->evaluateImage(Imagick::EVALUATE_MULTIPLY, $opacity / 100, Imagick::CHANNEL_ALPHA); 217 } 218 219 // Match the colorspace between the two images before compositing 220 // $watermark->setColorspace($this->im->getColorspace()); 221 222 // Apply the watermark to the image 223 return $this->im->compositeImage($watermark, Imagick::COMPOSITE_DISSOLVE, $offset_x, $offset_y); 224 } 225 226 protected function _do_background($r, $g, $b, $opacity) 227 { 228 // Create a RGB color for the background 229 $color = sprintf('rgb(%d, %d, %d)', $r, $g, $b); 230 231 // Create a new image for the background 232 $background = new Imagick; 233 $background->newImage($this->width, $this->height, new ImagickPixel($color)); 234 235 if ( ! $background->getImageAlphaChannel()) 236 { 237 // Force the image to have an alpha channel 238 $background->setImageAlphaChannel(Imagick::ALPHACHANNEL_SET); 239 } 240 241 // Clear the background image 242 $background->setImageBackgroundColor(new ImagickPixel('transparent')); 243 244 // NOTE: Using setImageOpacity will destroy current alpha channels! 245 $background->evaluateImage(Imagick::EVALUATE_MULTIPLY, $opacity / 100, Imagick::CHANNEL_ALPHA); 246 247 // Match the colorspace between the two images before compositing 248 $background->setColorspace($this->im->getColorspace()); 249 250 if ($background->compositeImage($this->im, Imagick::COMPOSITE_DISSOLVE, 0, 0)) 251 { 252 // Replace the current image with the new image 253 $this->im = $background; 254 255 return TRUE; 256 } 257 258 return FALSE; 259 } 260 261 protected function _do_save($file, $quality) 262 { 263 // Get the image format and type 264 list($format, $type) = $this->_get_imagetype(pathinfo($file, PATHINFO_EXTENSION)); 265 266 // Set the output image type 267 $this->im->setFormat($format); 268 269 // Set the output quality 270 $this->im->setImageCompressionQuality($quality); 271 272 if ($this->im->writeImage($file)) 273 { 274 // Reset the image type and mime type 275 $this->type = $type; 276 $this->mime = image_type_to_mime_type($type); 277 278 return TRUE; 279 } 280 281 return FALSE; 282 } 283 284 protected function _do_render($type, $quality) 285 { 286 // Get the image format and type 287 list($format, $type) = $this->_get_imagetype($type); 288 289 // Set the output image type 290 $this->im->setFormat($format); 291 292 // Set the output quality 293 $this->im->setImageCompressionQuality($quality); 294 295 // Reset the image type and mime type 296 $this->type = $type; 297 $this->mime = image_type_to_mime_type($type); 298 299 return (string) $this->im; 300 } 301 302 /** 303 * Get the image type and format for an extension. 304 * 305 * @param string $extension image extension: png, jpg, etc 306 * @return string IMAGETYPE_* constant 307 * @throws Kohana_Exception 308 */ 309 protected function _get_imagetype($extension) 310 { 311 // Normalize the extension to a format 312 $format = strtolower($extension); 313 314 switch ($format) 315 { 316 case 'jpg': 317 case 'jpe': 318 case 'jpeg': 319 $type = IMAGETYPE_JPEG; 320 break; 321 case 'gif': 322 $type = IMAGETYPE_GIF; 323 break; 324 case 'png': 325 $type = IMAGETYPE_PNG; 326 break; 327 default: 328 throw new Kohana_Exception('Installed ImageMagick does not support :type images', 329 array(':type' => $extension)); 330 break; 331 } 332 333 return array($format, $type); 334 } 335} // End Kohana_Image_Imagick