Add test with lowest PHPUnit version to Travis matrix
[kohana-image.git] / classes / Kohana / Image / Imagick.php
blobaa060c2cc3f5e7df53229bdce56b40149e7cdd1c
1 <?php defined('SYSPATH') OR die('No direct script access.');
2 /**
3 * Support for image manipulation using [Imagick](http://php.net/Imagick).
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
11 class Kohana_Image_Imagick extends Image {
13 /**
14 * @var Imagick image magick object
16 protected $im;
18 /**
19 * Checks if ImageMagick is enabled.
21 * @throws Kohana_Exception
22 * @return boolean
24 public static function check()
26 if ( ! extension_loaded('imagick'))
28 throw new Kohana_Exception('Imagick is not installed, or the extension is not loaded');
31 return Image_Imagick::$_checked = TRUE;
34 /**
35 * Runs [Image_Imagick::check] and loads the image.
37 * @return void
38 * @throws Kohana_Exception
40 public function __construct($file)
42 if ( ! Image_Imagick::$_checked)
44 // Run the install check
45 Image_Imagick::check();
48 parent::__construct($file);
50 $this->im = new Imagick;
51 $this->im->readImage($file);
53 if ( ! $this->im->getImageAlphaChannel())
55 // Force the image to have an alpha channel
56 $this->im->setImageAlphaChannel(Imagick::ALPHACHANNEL_SET);
60 /**
61 * Destroys the loaded image to free up resources.
63 * @return void
65 public function __destruct()
67 $this->im->clear();
68 $this->im->destroy();
71 protected function _do_resize($width, $height)
73 if ($this->im->scaleImage($width, $height))
75 // Reset the width and height
76 $this->width = $this->im->getImageWidth();
77 $this->height = $this->im->getImageHeight();
79 return TRUE;
82 return FALSE;
85 protected function _do_crop($width, $height, $offset_x, $offset_y)
87 if ($this->im->cropImage($width, $height, $offset_x, $offset_y))
89 // Reset the width and height
90 $this->width = $this->im->getImageWidth();
91 $this->height = $this->im->getImageHeight();
93 // Trim off hidden areas
94 $this->im->setImagePage($this->width, $this->height, 0, 0);
96 return TRUE;
99 return FALSE;
102 protected function _do_rotate($degrees)
104 if ($this->im->rotateImage(new ImagickPixel('transparent'), $degrees))
106 // Reset the width and height
107 $this->width = $this->im->getImageWidth();
108 $this->height = $this->im->getImageHeight();
110 // Trim off hidden areas
111 $this->im->setImagePage($this->width, $this->height, 0, 0);
113 return TRUE;
116 return FALSE;
119 protected function _do_flip($direction)
121 if ($direction === Image::HORIZONTAL)
123 return $this->im->flopImage();
125 else
127 return $this->im->flipImage();
131 protected function _do_sharpen($amount)
133 // IM not support $amount under 5 (0.15)
134 $amount = ($amount < 5) ? 5 : $amount;
136 // Amount should be in the range of 0.0 to 3.0
137 $amount = ($amount * 3.0) / 100;
139 return $this->im->sharpenImage(0, $amount);
142 protected function _do_reflection($height, $opacity, $fade_in)
144 // Clone the current image and flip it for reflection
145 $reflection = $this->im->clone();
146 $reflection->flipImage();
148 // Crop the reflection to the selected height
149 $reflection->cropImage($this->width, $height, 0, 0);
150 $reflection->setImagePage($this->width, $height, 0, 0);
152 // Select the fade direction
153 $direction = array('transparent', 'black');
155 if ($fade_in)
157 // Change the direction of the fade
158 $direction = array_reverse($direction);
161 // Create a gradient for fading
162 $fade = new Imagick;
163 $fade->newPseudoImage($reflection->getImageWidth(), $reflection->getImageHeight(), vsprintf('gradient:%s-%s', $direction));
165 // Apply the fade alpha channel to the reflection
166 $reflection->compositeImage($fade, Imagick::COMPOSITE_DSTOUT, 0, 0);
168 // NOTE: Using setImageOpacity will destroy alpha channels!
169 $reflection->evaluateImage(Imagick::EVALUATE_MULTIPLY, $opacity / 100, Imagick::CHANNEL_ALPHA);
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);
175 // Force the image to have an alpha channel
176 $image->setImageAlphaChannel(Imagick::ALPHACHANNEL_SET);
178 // Force the background color to be transparent
179 // $image->setImageBackgroundColor(new ImagickPixel('transparent'));
181 // Match the colorspace between the two images before compositing
182 $image->setColorspace($this->im->getColorspace());
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))
188 // Replace the current image with the reflected image
189 $this->im = $image;
191 // Reset the width and height
192 $this->width = $this->im->getImageWidth();
193 $this->height = $this->im->getImageHeight();
195 return TRUE;
198 return FALSE;
201 protected function _do_watermark(Image $image, $offset_x, $offset_y, $opacity)
203 // Convert the Image intance into an Imagick instance
204 $watermark = new Imagick;
205 $watermark->readImageBlob($image->render(), $image->file);
207 if ($watermark->getImageAlphaChannel() !== Imagick::ALPHACHANNEL_ACTIVATE)
209 // Force the image to have an alpha channel
210 $watermark->setImageAlphaChannel(Imagick::ALPHACHANNEL_OPAQUE);
213 if ($opacity < 100)
215 // NOTE: Using setImageOpacity will destroy current alpha channels!
216 $watermark->evaluateImage(Imagick::EVALUATE_MULTIPLY, $opacity / 100, Imagick::CHANNEL_ALPHA);
219 // Match the colorspace between the two images before compositing
220 // $watermark->setColorspace($this->im->getColorspace());
222 // Apply the watermark to the image
223 return $this->im->compositeImage($watermark, Imagick::COMPOSITE_DISSOLVE, $offset_x, $offset_y);
226 protected function _do_background($r, $g, $b, $opacity)
228 // Create a RGB color for the background
229 $color = sprintf('rgb(%d, %d, %d)', $r, $g, $b);
231 // Create a new image for the background
232 $background = new Imagick;
233 $background->newImage($this->width, $this->height, new ImagickPixel($color));
235 if ( ! $background->getImageAlphaChannel())
237 // Force the image to have an alpha channel
238 $background->setImageAlphaChannel(Imagick::ALPHACHANNEL_SET);
241 // Clear the background image
242 $background->setImageBackgroundColor(new ImagickPixel('transparent'));
244 // NOTE: Using setImageOpacity will destroy current alpha channels!
245 $background->evaluateImage(Imagick::EVALUATE_MULTIPLY, $opacity / 100, Imagick::CHANNEL_ALPHA);
247 // Match the colorspace between the two images before compositing
248 $background->setColorspace($this->im->getColorspace());
250 if ($background->compositeImage($this->im, Imagick::COMPOSITE_DISSOLVE, 0, 0))
252 // Replace the current image with the new image
253 $this->im = $background;
255 return TRUE;
258 return FALSE;
261 protected function _do_save($file, $quality)
263 // Get the image format and type
264 list($format, $type) = $this->_get_imagetype(pathinfo($file, PATHINFO_EXTENSION));
266 // Set the output image type
267 $this->im->setFormat($format);
269 // Set the output quality
270 $this->im->setImageCompressionQuality($quality);
272 if ($this->im->writeImage($file))
274 // Reset the image type and mime type
275 $this->type = $type;
276 $this->mime = image_type_to_mime_type($type);
278 return TRUE;
281 return FALSE;
284 protected function _do_render($type, $quality)
286 // Get the image format and type
287 list($format, $type) = $this->_get_imagetype($type);
289 // Set the output image type
290 $this->im->setFormat($format);
292 // Set the output quality
293 $this->im->setImageCompressionQuality($quality);
295 // Reset the image type and mime type
296 $this->type = $type;
297 $this->mime = image_type_to_mime_type($type);
299 return (string) $this->im;
303 * Get the image type and format for an extension.
305 * @param string $extension image extension: png, jpg, etc
306 * @return string IMAGETYPE_* constant
307 * @throws Kohana_Exception
309 protected function _get_imagetype($extension)
311 // Normalize the extension to a format
312 $format = strtolower($extension);
314 switch ($format)
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;
333 return array($format, $type);
335 } // End Kohana_Image_Imagick