1 <?php
defined('SYSPATH') or die('No direct script access.');
3 * Image manipulation class using {@link http://php.net/gd GD}.
5 * @package Kohana/Image
8 * @copyright (c) 2008-2009 Kohana Team
9 * @license http://kohanaphp.com/license.html
11 class Kohana_Image_GD
extends Image
{
13 // Is GD bundled or separate?
14 protected static $_bundled;
16 public static function check()
18 if ( ! function_exists('gd_info'))
20 throw new Kohana_Exception('GD is either not installed or not enabled, check your configuration');
23 if (defined('GD_BUNDLED'))
25 // Get the version via a constant, available in PHP 5.
26 Image_GD
::$_bundled = GD_BUNDLED
;
30 // Get the version information
33 // Extract the bundled status
34 Image_GD
::$_bundled = (bool) preg_match('/\bbundled\b/i', $info['GD Version']);
37 if (defined('GD_VERSION'))
39 // Get the version via a constant, available in PHP 5.2.4+
40 $version = GD_VERSION
;
44 // Get the version information
47 // Extract the version number
48 preg_match('/\d+\.\d+(?:\.\d+)?/', $info['GD Version'], $matches);
50 // Get the major version
51 $version = $matches[0];
54 if ( ! version_compare($version, '2.0.1', '>='))
56 throw new Kohana_Exception('Image_GD requires GD version :required or greater, you have :version',
57 array('required' => '2.0.1', ':version' => $version));
60 return Image_GD
::$_checked = TRUE;
63 // Temporary image resource
66 // Function name to open Image
67 protected $_create_function;
69 public function __construct($file)
71 if ( ! Image_GD
::$_checked)
73 // Run the install check
77 parent
::__construct($file);
79 // Set the image creation function name
83 $create = 'imagecreatefromjpeg';
86 $create = 'imagecreatefromgif';
89 $create = 'imagecreatefrompng';
93 if ( ! isset($create) OR ! function_exists($create))
95 throw new Kohana_Exception('Installed GD does not support :type images',
96 array(':type' => image_type_to_extension($this->type
, FALSE)));
99 // Save function for future use
100 $this->_create_function
= $create;
102 // Save filename for lazy loading
103 $this->_image
= $this->file
;
106 public function __destruct()
108 if (is_resource($this->_image
))
110 // Free all resources
111 imagedestroy($this->_image
);
115 protected function _load_image()
117 if ( ! is_resource($this->_image
))
119 // Gets create function
120 $create = $this->_create_function
;
122 // Open the temporary image
123 $this->_image
= $create($this->file
);
125 // Preserve transparency when saving
126 imagesavealpha($this->_image
, TRUE);
130 protected function _do_resize($width, $height)
132 // Presize width and height
133 $pre_width = $this->width
;
134 $pre_height = $this->height
;
136 // Loads image if not yet loaded
137 $this->_load_image();
139 // Test if we can do a resize without resampling to speed up the final resize
140 if ($width > ($this->width
/ 2) AND $height > ($this->height
/ 2))
142 // The maximum reduction is 10% greater than the final size
143 $reduction_width = round($width * 1.1);
144 $reduction_height = round($height * 1.1);
146 while ($pre_width / 2 > $reduction_width AND $pre_height / 2 > $reduction_height)
148 // Reduce the size using an O(2n) algorithm, until it reaches the maximum reduction
153 // Create the temporary image to copy to
154 $image = $this->_create($pre_width, $pre_height);
156 if (imagecopyresized($image, $this->_image
, 0, 0, 0, 0, $pre_width, $pre_height, $this->width
, $this->height
))
158 // Swap the new image for the old one
159 imagedestroy($this->_image
);
160 $this->_image
= $image;
164 // Create the temporary image to copy to
165 $image = $this->_create($width, $height);
167 // Execute the resize
168 if (imagecopyresampled($image, $this->_image
, 0, 0, 0, 0, $width, $height, $pre_width, $pre_height))
170 // Swap the new image for the old one
171 imagedestroy($this->_image
);
172 $this->_image
= $image;
174 // Reset the width and height
175 $this->width
= imagesx($image);
176 $this->height
= imagesy($image);
180 protected function _do_crop($width, $height, $offset_x, $offset_y)
182 // Create the temporary image to copy to
183 $image = $this->_create($width, $height);
185 // Loads image if not yet loaded
186 $this->_load_image();
189 if (imagecopyresampled($image, $this->_image
, 0, 0, $offset_x, $offset_y, $width, $height, $width, $height))
191 // Swap the new image for the old one
192 imagedestroy($this->_image
);
193 $this->_image
= $image;
195 // Reset the width and height
196 $this->width
= imagesx($image);
197 $this->height
= imagesy($image);
201 protected function _do_rotate($degrees)
203 if ( ! Image_GD
::$_bundled)
205 throw new Kohana_Exception('This method requires :function, which is only available in the bundled version of GD',
206 array(':function' => 'imagerotate'));
209 // Loads image if not yet loaded
210 $this->_load_image();
212 // Transparent black will be used as the background for the uncovered region
213 $transparent = imagecolorallocatealpha($this->_image
, 0, 0, 0, 127);
215 // Rotate, setting the transparent color
216 $image = imagerotate($this->_image
, 360 - $degrees, $transparent, 1);
218 // Save the alpha of the rotated image
219 imagesavealpha($image, TRUE);
221 // Get the width and height of the rotated image
222 $width = imagesx($image);
223 $height = imagesy($image);
225 if (imagecopymerge($this->_image
, $image, 0, 0, 0, 0, $width, $height, 100))
227 // Swap the new image for the old one
228 imagedestroy($this->_image
);
229 $this->_image
= $image;
231 // Reset the width and height
232 $this->width
= $width;
233 $this->height
= $height;
237 protected function _do_flip($direction)
239 // Create the flipped image
240 $flipped = $this->_create($this->width
, $this->height
);
242 // Loads image if not yet loaded
243 $this->_load_image();
245 if ($direction === Image
::HORIZONTAL
)
247 for ($x = 0; $x < $this->width
; $x++
)
249 // Flip each row from top to bottom
250 imagecopy($flipped, $this->_image
, $x, 0, $this->width
- $x - 1, 0, 1, $this->height
);
255 for ($y = 0; $y < $this->height
; $y++
)
257 // Flip each column from left to right
258 imagecopy($flipped, $this->_image
, 0, $y, 0, $this->height
- $y - 1, $this->width
, 1);
262 // Swap the new image for the old one
263 imagedestroy($this->_image
);
264 $this->_image
= $flipped;
266 // Reset the width and height
267 $this->width
= imagesx($flipped);
268 $this->height
= imagesy($flipped);
271 protected function _do_sharpen($amount)
273 if ( ! Image_GD
::$_bundled)
275 throw new Kohana_Exception('This method requires :function, which is only available in the bundled version of GD',
276 array(':function' => 'imageconvolution'));
279 // Loads image if not yet loaded
280 $this->_load_image();
282 // Amount should be in the range of 18-10
283 $amount = round(abs(-18 +
($amount * 0.08)), 2);
285 // Gaussian blur matrix
289 array(-1, $amount, -1),
293 // Perform the sharpen
294 if (imageconvolution($this->_image
, $matrix, $amount - 8, 0))
296 // Reset the width and height
297 $this->width
= imagesx($this->_image
);
298 $this->height
= imagesy($this->_image
);
302 protected function _do_reflection($height, $opacity, $fade_in)
304 if ( ! Image_GD
::$_bundled)
306 throw new Kohana_Exception('This method requires :function, which is only available in the bundled version of GD',
307 array(':function' => 'imagefilter'));
310 // Loads image if not yet loaded
311 $this->_load_image();
313 // Convert an opacity range of 0-100 to 127-0
314 $opacity = round(abs(($opacity * 127 / 100) - 127));
318 // Calculate the opacity stepping
319 $stepping = (127 - $opacity) / $height;
323 // Avoid a "divide by zero" error
324 $stepping = 127 / $height;
327 // Create the reflection image
328 $reflection = $this->_create($this->width
, $this->height +
$height);
330 // Copy the image to the reflection
331 imagecopy($reflection, $this->_image
, 0, 0, 0, 0, $this->width
, $this->height
);
333 for ($offset = 0; $height >= $offset; $offset++
)
335 // Read the next line down
336 $src_y = $this->height
- $offset - 1;
338 // Place the line at the bottom of the reflection
339 $dst_y = $this->height +
$offset;
341 if ($fade_in === TRUE)
343 // Start with the most transparent line first
344 $dst_opacity = round($opacity +
($stepping * ($height - $offset)));
348 // Start with the most opaque line first
349 $dst_opacity = round($opacity +
($stepping * $offset));
352 // Create a single line of the image
353 $line = $this->_create($this->width
, 1);
355 // Copy a single line from the current image into the line
356 imagecopy($line, $this->_image
, 0, 0, 0, $src_y, $this->width
, 1);
358 // Colorize the line to add the correct alpha level
359 imagefilter($line, IMG_FILTER_COLORIZE
, 0, 0, 0, $dst_opacity);
361 // Copy a the line into the reflection
362 imagecopy($reflection, $line, 0, $dst_y, 0, 0, $this->width
, 1);
365 // Swap the new image for the old one
366 imagedestroy($this->_image
);
367 $this->_image
= $reflection;
369 // Reset the width and height
370 $this->width
= imagesx($reflection);
371 $this->height
= imagesy($reflection);
374 protected function _do_watermark(Image
$watermark, $offset_x, $offset_y, $opacity)
376 if ( ! Image_GD
::$_bundled)
378 throw new Kohana_Exception('This method requires :function, which is only available in the bundled version of GD',
379 array(':function' => 'imagelayereffect'));
382 // Loads image if not yet loaded
383 $this->_load_image();
385 // Create the watermark image resource
386 $overlay = imagecreatefromstring($watermark->render());
388 // Get the width and height of the watermark
389 $width = imagesx($overlay);
390 $height = imagesy($overlay);
394 // Convert an opacity range of 0-100 to 127-0
395 $opacity = round(abs(($opacity * 127 / 100) - 127));
397 // Allocate transparent white
398 $color = imagecolorallocatealpha($overlay, 255, 255, 255, $opacity);
400 // The transparent image will overlay the watermark
401 imagelayereffect($overlay, IMG_EFFECT_OVERLAY
);
403 // Fill the background with transparent white
404 imagefilledrectangle($overlay, 0, 0, $width, $height, $color);
407 // Alpha blending must be enabled on the background!
408 imagealphablending($this->_image
, TRUE);
410 if (imagecopy($this->_image
, $overlay, $offset_x, $offset_y, 0, 0, $width, $height))
412 // Destroy the overlay image
413 imagedestroy($overlay);
417 protected function _do_background($r, $g, $b, $opacity)
419 // Loads image if not yet loaded
420 $this->_load_image();
422 // Convert an opacity range of 0-100 to 127-0
423 $opacity = round(abs(($opacity * 127 / 100) - 127));
425 // Create a new background
426 $background = $this->_create($this->width
, $this->height
);
428 // Allocate the color
429 $color = imagecolorallocatealpha($background, $r, $g, $b, $opacity);
431 // Fill the image with white
432 imagefilledrectangle($background, 0, 0, $this->width
, $this->height
, $color);
434 // Alpha blending must be enabled on the background!
435 imagealphablending($background, TRUE);
437 // Copy the image onto a white background to remove all transparency
438 if (imagecopy($background, $this->_image
, 0, 0, 0, 0, $this->width
, $this->height
))
440 // Swap the new image for the old one
441 imagedestroy($this->_image
);
442 $this->_image
= $background;
446 protected function _do_save($file, $quality)
448 // Loads image if not yet loaded
449 $this->_load_image();
451 // Get the extension of the file
452 $extension = pathinfo($file, PATHINFO_EXTENSION
);
454 // Get the save function and IMAGETYPE
455 list($save, $type) = $this->_save_function($extension, $quality);
457 // Save the image to a file
458 $status = isset($quality) ?
$save($this->_image
, $file, $quality) : $save($this->_image
, $file);
460 if ($status === TRUE AND $type !== $this->type
)
462 // Reset the image type and mime type
464 $this->mime
= image_type_to_mime_type($type);
470 protected function _do_render($type, $quality)
472 // Loads image if not yet loaded
473 $this->_load_image();
475 // Get the save function and IMAGETYPE
476 list($save, $type) = $this->_save_function($type, $quality);
478 // Capture the output
482 $status = isset($quality) ?
$save($this->_image
, NULL, $quality) : $save($this->_image
, NULL);
484 if ($status === TRUE AND $type !== $this->type
)
486 // Reset the image type and mime type
488 $this->mime
= image_type_to_mime_type($type);
491 return ob_get_clean();
495 * Get the GD saving function and image type for this extension.
496 * Also normalizes the quality setting
498 * @throws Kohana_Exception
499 * @param string image type: png, jpg, etc
500 * @param integer image quality
501 * @return array save function, IMAGETYPE_* constant
503 protected function _save_function($extension, & $quality)
505 switch (strtolower($extension))
511 $type = IMAGETYPE_JPEG
;
516 $type = IMAGETYPE_GIF
;
518 // GIFs do not a quality setting
524 $type = IMAGETYPE_PNG
;
526 // Use a compression level of 9 (does not affect quality!)
530 throw new Kohana_Exception('Installed GD does not support :type images',
531 array(':type' => $extension));
535 return array($save, $type);
539 * Create an empty image with the given width and height.
541 * @param integer image width
542 * @param integer image height
545 protected function _create($width, $height)
547 // Create an empty image
548 $image = imagecreatetruecolor($width, $height);
550 // Do not apply alpha blending
551 imagealphablending($image, FALSE);
554 imagesavealpha($image, TRUE);