Updated comments, changed all @package links to "Kohana/Image" and added @category...
[kohana-image.git] / classes / kohana / image.php
blob9a3033d5ecf1598b1bb7b2b89bb9226a234aa382
1 <?php defined('SYSPATH') or die('No direct script access.');
2 /**
3 * Image manipulation abstract class.
5 * @package Kohana/Image
6 * @category Base
7 * @author Kohana Team
8 * @copyright (c) 2008-2009 Kohana Team
9 * @license http://kohanaphp.com/license.html
11 abstract class Kohana_Image {
13 // Resizing contraints
14 const NONE = 0x01;
15 const WIDTH = 0x02;
16 const HEIGHT = 0x03;
17 const AUTO = 0x04;
18 const INVERSE = 0x05;
20 // Flipping directions
21 const HORIZONTAL = 0x11;
22 const VERTICAL = 0x12;
24 /**
25 * @var string default driver: GD, ImageMagick, etc
27 public static $default_driver = 'GD';
29 // Status of the driver check
30 protected static $_checked = FALSE;
32 /**
33 * Creates an image wrapper.
35 * @param string image file path
36 * @param string driver type: GD, ImageMagick, etc
37 * @return Image
39 public static function factory($file, $driver = NULL)
41 if ($driver === NULL)
43 // Use the default driver
44 $driver = Image::$default_driver;
47 // Set the class name
48 $class = 'Image_'.$driver;
50 return new $class($file);
53 /**
54 * @var string image file path
56 public $file;
58 /**
59 * @var integer image width
61 public $width;
63 /**
64 * @var integer image height
66 public $height;
68 /**
69 * @var integer one of the IMAGETYPE_* constants
71 public $type;
73 /**
74 * Loads information about the image.
76 * @throws Kohana_Exception
77 * @param string image file path
78 * @return void
80 public function __construct($file)
82 try
84 // Get the real path to the file
85 $file = realpath($file);
87 // Get the image information
88 $info = getimagesize($file);
90 catch (Exception $e)
92 // Ignore all errors while reading the image
95 if (empty($file) OR empty($info))
97 throw new Kohana_Exception('Not an image or invalid image: :file',
98 array(':file' => Kohana::debug_path($file)));
101 // Store the image information
102 $this->file = $file;
103 $this->width = $info[0];
104 $this->height = $info[1];
105 $this->type = $info[2];
106 $this->mime = image_type_to_mime_type($this->type);
110 * Render the current image.
112 * The output of this function is binary and must be rendered with the
113 * appropriate Content-Type header or it will not be displayed correctly!
115 * @return string
117 public function __toString()
121 // Render the current image
122 return $this->render();
124 catch (Exception $e)
126 if (is_object(Kohana::$log))
128 // Get the text of the exception
129 $error = Kohana::exception_text($e);
131 // Add this exception to the log
132 Kohana::$log->add(Kohana::ERROR, $error);
135 // Showing any kind of error will be "inside" image data
136 return '';
141 * Resize the image to the given size. Either the width or the height can
142 * be omitted and the image will be resized proportionally.
144 * @param integer new width
145 * @param integer new height
146 * @param integer master dimension
147 * @return $this
149 public function resize($width = NULL, $height = NULL, $master = NULL)
151 if ($master === NULL)
153 // Choose the master dimension automatically
154 $master = Image::AUTO;
156 // Image::WIDTH and Image::HEIGHT depricated. You can use it in old projects,
157 // but in new you must pass empty value for non-master dimension
158 elseif ($master == Image::WIDTH AND ! empty($width))
160 $master = Image::AUTO;
162 // Set empty height for backvard compatibility
163 $height = NULL;
165 elseif ($master == Image::HEIGHT AND ! empty($height))
167 $master = Image::AUTO;
169 // Set empty width for backvard compatibility
170 $width = NULL;
174 if (empty($width))
176 if ($master === Image::NONE)
178 // Use the current width
179 $width = $this->width;
181 else
183 // If width not set, master will be height
184 $master = Image::HEIGHT;
188 if (empty($height))
190 if ($master === Image::NONE)
192 // Use the current height
193 $height = $this->height;
195 else
197 // If height not set, master will be width
198 $master = Image::WIDTH;
202 switch ($master)
204 case Image::AUTO:
205 // Choose direction with the greatest reduction ratio
206 $master = ($this->width / $width) > ($this->height / $height) ? Image::WIDTH : Image::HEIGHT;
207 break;
208 case Image::INVERSE:
209 // Choose direction with the minimum reduction ratio
210 $master = ($this->width / $width) > ($this->height / $height) ? Image::HEIGHT : Image::WIDTH;
211 break;
214 switch ($master)
216 case Image::WIDTH:
217 // Recalculate the height based on the width proportions
218 $height = $this->height * $width / $this->width;
219 break;
220 case Image::HEIGHT:
221 // Recalculate the width based on the height proportions
222 $width = $this->width * $height / $this->height;
223 break;
226 // Convert the width and height to integers
227 $width = round($width);
228 $height = round($height);
230 $this->_do_resize($width, $height);
232 return $this;
236 * Crop an image to the given size. Either the width or the height can be
237 * omitted and the current width or height will be used.
239 * If no offset is specified, the center of the axis will be used.
240 * If an offset of TRUE is specified, the bottom of the axis will be used.
242 * @param integer new width
243 * @param integer new height
244 * @param mixed offset from the left
245 * @param mixed offset from the top
246 * @return $this
248 public function crop($width, $height, $offset_x = NULL, $offset_y = NULL)
250 if ($width > $this->width)
252 // Use the current width
253 $width = $this->width;
256 if ($height > $this->height)
258 // Use the current height
259 $height = $this->height;
262 if ($offset_x === NULL)
264 // Center the X offset
265 $offset_x = round(($this->width - $width) / 2);
267 elseif ($offset_x === TRUE)
269 // Bottom the X offset
270 $offset_x = $this->width - $width;
272 elseif ($offset_x < 0)
274 // Set the X offset from the right
275 $offset_x = $this->width - $width + $offset_x;
278 if ($offset_y === NULL)
280 // Center the Y offset
281 $offset_y = round(($this->height - $height) / 2);
283 elseif ($offset_y === TRUE)
285 // Bottom the Y offset
286 $offset_y = $this->height - $height;
288 elseif ($offset_y < 0)
290 // Set the Y offset from the bottom
291 $offset_y = $this->height - $height + $offset_y;
294 // Determine the maximum possible width and height
295 $max_width = $this->width - $offset_x;
296 $max_height = $this->height - $offset_y;
298 if ($width > $max_width)
300 // Use the maximum available width
301 $width = $max_width;
304 if ($height > $max_height)
306 // Use the maximum available height
307 $height = $max_height;
310 $this->_do_crop($width, $height, $offset_x, $offset_y);
312 return $this;
316 * Rotate the image.
318 * @param integer degrees to rotate: -360-360
319 * @return $this
321 public function rotate($degrees)
323 // Make the degrees an integer
324 $degrees = (int) $degrees;
326 if ($degrees > 180)
330 // Keep subtracting full circles until the degrees have normalized
331 $degrees -= 360;
333 while($degrees > 180);
336 if ($degrees < -180)
340 // Keep adding full circles until the degrees have normalized
341 $degrees += 360;
343 while($degrees < -180);
346 $this->_do_rotate($degrees);
348 return $this;
352 * Flip the image along the horizontal or vertical axis.
354 * @param integer direction: Image::HORIZONTAL, Image::VERTICAL
355 * @return $this
357 public function flip($direction)
359 if ($direction !== Image::HORIZONTAL)
361 // Flip vertically
362 $direction = Image::VERTICAL;
365 $this->_do_flip($direction);
367 return $this;
371 * Sharpen the image.
373 * @param integer amount to sharpen: 1-100
374 * @return $this
376 public function sharpen($amount)
378 // The amount must be in the range of 1 to 100
379 $amount = min(max($amount, 1), 100);
381 $this->_do_sharpen($amount);
383 return $this;
387 * Add a reflection to an image. The most opaque part of the reflection
388 * will be equal to the opacity setting and fade out to full transparent.
389 * By default, the reflection will be most transparent at the top
391 * @param integer reflection height
392 * @param integer reflection opacity: 0-100
393 * @param boolean TRUE to fade in, FALSE to fade out
394 * @return $this
396 public function reflection($height = NULL, $opacity = 100, $fade_in = FALSE)
398 if ($height === NULL OR $height > $this->height)
400 // Use the current height
401 $height = $this->height;
404 // The opacity must be in the range of 0 to 100
405 $opacity = min(max($opacity, 0), 100);
407 $this->_do_reflection($height, $opacity, $fade_in);
409 return $this;
413 * Add a watermark to an image with a specified opacity.
415 * If no offset is specified, the center of the axis will be used.
416 * If an offset of TRUE is specified, the bottom of the axis will be used.
418 * @param object watermark Image instance
419 * @param integer offset from the left
420 * @param integer offset from the top
421 * @param integer opacity of watermark: 1-100
422 * @return $this
424 public function watermark(Image $watermark, $offset_x = NULL, $offset_y = NULL, $opacity = 100)
426 if ($offset_x === NULL)
428 // Center the X offset
429 $offset_x = round(($this->width - $watermark->width) / 2);
431 elseif ($offset_x === TRUE)
433 // Bottom the X offset
434 $offset_x = $this->width - $watermark->width;
436 elseif ($offset_x < 0)
438 // Set the X offset from the right
439 $offset_x = $this->width - $watermark->width + $offset_x;
442 if ($offset_y === NULL)
444 // Center the Y offset
445 $offset_y = round(($this->height - $watermark->height) / 2);
447 elseif ($offset_y === TRUE)
449 // Bottom the Y offset
450 $offset_y = $this->height - $watermark->height;
452 elseif ($offset_y < 0)
454 // Set the Y offset from the bottom
455 $offset_y = $this->height - $watermark->height + $offset_y;
458 // The opacity must be in the range of 1 to 100
459 $opacity = min(max($opacity, 1), 100);
461 $this->_do_watermark($watermark, $offset_x, $offset_y, $opacity);
463 return $this;
467 * Set the background color of an image.
469 * @param string hexadecimal color value
470 * @param integer background opacity: 0-100
471 * @return $this
473 public function background($color, $opacity = 100)
475 if ($color[0] === '#')
477 // Remove the pound
478 $color = substr($color, 1);
481 if (strlen($color) === 3)
483 // Convert shorthand into longhand hex notation
484 $color = preg_replace('/./', '$0$0', $color);
487 // Convert the hex into RGB values
488 list ($r, $g, $b) = array_map('hexdec', str_split($color, 2));
490 // The opacity must be in the range of 0 to 100
491 $opacity = min(max($opacity, 0), 100);
493 $this->_do_background($r, $g, $b, $opacity);
495 return $this;
499 * Save the image. If the filename is omitted, the original image will
500 * be overwritten.
502 * @param string new image path
503 * @param integer quality of image: 1-100
504 * @return boolean
506 public function save($file = NULL, $quality = 100)
508 if ($file === NULL)
510 // Overwrite the file
511 $file = $this->file;
514 if (is_file($file))
516 if ( ! is_writable($file))
518 throw new Kohana_Exception('File must be writable: :file',
519 array(':file' => Kohana::debug_path($file)));
522 else
524 // Get the directory of the file
525 $directory = realpath(pathinfo($file, PATHINFO_DIRNAME));
527 if ( ! is_dir($directory) OR ! is_writable($directory))
529 throw new Kohana_Exception('Directory must be writable: :directory',
530 array(':directory' => Kohana::debug_path($directory)));
534 // The quality must be in the range of 1 to 100
535 $quality = min(max($quality, 1), 100);
537 return $this->_do_save($file, $quality);
541 * Render the image and return the data.
543 * @param string image type to return: png, jpg, gif, etc
544 * @param integer quality of image: 1-100
545 * @return string
547 public function render($type = NULL, $quality = 100)
549 if ($type === NULL)
551 // Use the current image type
552 $type = image_type_to_extension($this->type, FALSE);
555 return $this->_do_render($type, $quality);
559 * Execute a resize.
561 * @param integer new width
562 * @param integer new height
563 * @return void
565 abstract protected function _do_resize($width, $height);
568 * Execute a crop.
570 * @param integer new width
571 * @param integer new height
572 * @param integer offset from the left
573 * @param integer offset from the top
574 * @return void
576 abstract protected function _do_crop($width, $height, $offset_x, $offset_y);
579 * Execute a rotation.
581 * @param integer degrees to rotate
582 * @return void
584 abstract protected function _do_rotate($degrees);
587 * Execute a flip.
589 * @param integer direction to flip
590 * @return void
592 abstract protected function _do_flip($direction);
595 * Execute a sharpen.
597 * @param integer amount to sharpen
598 * @return void
600 abstract protected function _do_sharpen($amount);
603 * Execute a reflection.
605 * @param integer reflection height
606 * @param integer reflection opacity
607 * @param boolean TRUE to fade out, FALSE to fade in
608 * @return void
610 abstract protected function _do_reflection($height, $opacity, $fade_in);
613 * Execute a watermarking.
615 * @param object watermarking Image
616 * @param integer offset from the left
617 * @param integer offset from the top
618 * @param integer opacity of watermark
619 * @return void
621 abstract protected function _do_watermark(Image $image, $offset_x, $offset_y, $opacity);
624 * Execute a background.
626 * @param integer red
627 * @param integer green
628 * @param integer blue
629 * @param integer opacity
630 * @return void
632 abstract protected function _do_background($r, $g, $b, $opacity);
635 * Execute a save.
637 * @param string new image filename
638 * @param integer quality
639 * @return boolean
641 abstract protected function _do_save($file, $quality);
644 * Execute a render.
646 * @param string image type: png, jpg, gif, etc
647 * @param integer quality
648 * @return string
650 abstract protected function _do_render($type, $quality);
652 } // End Image