Avail feature updated
[ninja.git] / system / libraries / Image.php
blobf64a1edbbfafc435ca55d50fdf1b4ab53079ec3d
1 <?php defined('SYSPATH') OR die('No direct access allowed.');
2 /**
3 * Manipulate images using standard methods such as resize, crop, rotate, etc.
4 * This class must be re-initialized for every image you wish to manipulate.
6 * $Id: Image.php 3917 2009-01-21 03:06:22Z zombor $
8 * @package Image
9 * @author Kohana Team
10 * @copyright (c) 2007-2008 Kohana Team
11 * @license http://kohanaphp.com/license.html
13 class Image {
15 // Master Dimension
16 const NONE = 1;
17 const AUTO = 2;
18 const HEIGHT = 3;
19 const WIDTH = 4;
20 // Flip Directions
21 const HORIZONTAL = 5;
22 const VERTICAL = 6;
24 // Allowed image types
25 public static $allowed_types = array
27 IMAGETYPE_GIF => 'gif',
28 IMAGETYPE_JPEG => 'jpg',
29 IMAGETYPE_PNG => 'png',
30 IMAGETYPE_TIFF_II => 'tiff',
31 IMAGETYPE_TIFF_MM => 'tiff',
34 // Driver instance
35 protected $driver;
37 // Driver actions
38 protected $actions = array();
40 // Reference to the current image filename
41 protected $image = '';
43 /**
44 * Creates a new Image instance and returns it.
46 * @param string filename of image
47 * @param array non-default configurations
48 * @return object
50 public static function factory($image, $config = NULL)
52 return new Image($image, $config);
55 /**
56 * Creates a new image editor instance.
58 * @throws Kohana_Exception
59 * @param string filename of image
60 * @param array non-default configurations
61 * @return void
63 public function __construct($image, $config = NULL)
65 static $check;
67 // Make the check exactly once
68 ($check === NULL) and $check = function_exists('getimagesize');
70 if ($check === FALSE)
71 throw new Kohana_Exception('image.getimagesize_missing');
73 // Check to make sure the image exists
74 if ( ! is_file($image))
75 throw new Kohana_Exception('image.file_not_found', $image);
77 // Disable error reporting, to prevent PHP warnings
78 $ER = error_reporting(0);
80 // Fetch the image size and mime type
81 $image_info = getimagesize($image);
83 // Turn on error reporting again
84 error_reporting($ER);
86 // Make sure that the image is readable and valid
87 if ( ! is_array($image_info) OR count($image_info) < 3)
88 throw new Kohana_Exception('image.file_unreadable', $image);
90 // Check to make sure the image type is allowed
91 if ( ! isset(Image::$allowed_types[$image_info[2]]))
92 throw new Kohana_Exception('image.type_not_allowed', $image);
94 // Image has been validated, load it
95 $this->image = array
97 'file' => str_replace('\\', '/', realpath($image)),
98 'width' => $image_info[0],
99 'height' => $image_info[1],
100 'type' => $image_info[2],
101 'ext' => Image::$allowed_types[$image_info[2]],
102 'mime' => $image_info['mime']
105 // Load configuration
106 $this->config = (array) $config + Kohana::config('image');
108 // Set driver class name
109 $driver = 'Image_'.ucfirst($this->config['driver']).'_Driver';
111 // Load the driver
112 if ( ! Kohana::auto_load($driver))
113 throw new Kohana_Exception('core.driver_not_found', $this->config['driver'], get_class($this));
115 // Initialize the driver
116 $this->driver = new $driver($this->config['params']);
118 // Validate the driver
119 if ( ! ($this->driver instanceof Image_Driver))
120 throw new Kohana_Exception('core.driver_implements', $this->config['driver'], get_class($this), 'Image_Driver');
124 * Handles retrieval of pre-save image properties
126 * @param string property name
127 * @return mixed
129 public function __get($property)
131 if (isset($this->image[$property]))
133 return $this->image[$property];
135 else
137 throw new Kohana_Exception('core.invalid_property', $column, get_class($this));
142 * Resize an image to a specific width and height. By default, Kohana will
143 * maintain the aspect ratio using the width as the master dimension. If you
144 * wish to use height as master dim, set $image->master_dim = Image::HEIGHT
145 * This method is chainable.
147 * @throws Kohana_Exception
148 * @param integer width
149 * @param integer height
150 * @param integer one of: Image::NONE, Image::AUTO, Image::WIDTH, Image::HEIGHT
151 * @return object
153 public function resize($width, $height, $master = NULL)
155 if ( ! $this->valid_size('width', $width))
156 throw new Kohana_Exception('image.invalid_width', $width);
158 if ( ! $this->valid_size('height', $height))
159 throw new Kohana_Exception('image.invalid_height', $height);
161 if (empty($width) AND empty($height))
162 throw new Kohana_Exception('image.invalid_dimensions', __FUNCTION__);
164 if ($master === NULL)
166 // Maintain the aspect ratio by default
167 $master = Image::AUTO;
169 elseif ( ! $this->valid_size('master', $master))
170 throw new Kohana_Exception('image.invalid_master');
172 $this->actions['resize'] = array
174 'width' => $width,
175 'height' => $height,
176 'master' => $master,
179 return $this;
183 * Crop an image to a specific width and height. You may also set the top
184 * and left offset.
185 * This method is chainable.
187 * @throws Kohana_Exception
188 * @param integer width
189 * @param integer height
190 * @param integer top offset, pixel value or one of: top, center, bottom
191 * @param integer left offset, pixel value or one of: left, center, right
192 * @return object
194 public function crop($width, $height, $top = 'center', $left = 'center')
196 if ( ! $this->valid_size('width', $width))
197 throw new Kohana_Exception('image.invalid_width', $width);
199 if ( ! $this->valid_size('height', $height))
200 throw new Kohana_Exception('image.invalid_height', $height);
202 if ( ! $this->valid_size('top', $top))
203 throw new Kohana_Exception('image.invalid_top', $top);
205 if ( ! $this->valid_size('left', $left))
206 throw new Kohana_Exception('image.invalid_left', $left);
208 if (empty($width) AND empty($height))
209 throw new Kohana_Exception('image.invalid_dimensions', __FUNCTION__);
211 $this->actions['crop'] = array
213 'width' => $width,
214 'height' => $height,
215 'top' => $top,
216 'left' => $left,
219 return $this;
223 * Allows rotation of an image by 180 degrees clockwise or counter clockwise.
225 * @param integer degrees
226 * @return object
228 public function rotate($degrees)
230 $degrees = (int) $degrees;
232 if ($degrees > 180)
236 // Keep subtracting full circles until the degrees have normalized
237 $degrees -= 360;
239 while($degrees > 180);
242 if ($degrees < -180)
246 // Keep adding full circles until the degrees have normalized
247 $degrees += 360;
249 while($degrees < -180);
252 $this->actions['rotate'] = $degrees;
254 return $this;
258 * Flip an image horizontally or vertically.
260 * @throws Kohana_Exception
261 * @param integer direction
262 * @return object
264 public function flip($direction)
266 if ($direction !== self::HORIZONTAL AND $direction !== self::VERTICAL)
267 throw new Kohana_Exception('image.invalid_flip');
269 $this->actions['flip'] = $direction;
271 return $this;
275 * Change the quality of an image.
277 * @param integer quality as a percentage
278 * @return object
280 public function quality($amount)
282 $this->actions['quality'] = max(1, min($amount, 100));
284 return $this;
288 * Sharpen an image.
290 * @param integer amount to sharpen, usually ~20 is ideal
291 * @return object
293 public function sharpen($amount)
295 $this->actions['sharpen'] = max(1, min($amount, 100));
297 return $this;
301 * Save the image to a new image or overwrite this image.
303 * @throws Kohana_Exception
304 * @param string new image filename
305 * @param integer permissions for new image
306 * @param boolean keep or discard image process actions
307 * @return object
309 public function save($new_image = FALSE, $chmod = 0644, $keep_actions = FALSE)
311 // If no new image is defined, use the current image
312 empty($new_image) and $new_image = $this->image['file'];
314 // Separate the directory and filename
315 $dir = pathinfo($new_image, PATHINFO_DIRNAME);
316 $file = pathinfo($new_image, PATHINFO_BASENAME);
318 // Normalize the path
319 $dir = str_replace('\\', '/', realpath($dir)).'/';
321 if ( ! is_writable($dir))
322 throw new Kohana_Exception('image.directory_unwritable', $dir);
324 if ($status = $this->driver->process($this->image, $this->actions, $dir, $file))
326 if ($chmod !== FALSE)
328 // Set permissions
329 chmod($new_image, $chmod);
333 // Reset actions. Subsequent save() or render() will not apply previous actions.
334 if ($keep_actions === FALSE)
335 $this->actions = array();
337 return $status;
340 /**
341 * Output the image to the browser.
343 * @param boolean keep or discard image process actions
344 * @return object
346 public function render($keep_actions = FALSE)
348 $new_image = $this->image['file'];
350 // Separate the directory and filename
351 $dir = pathinfo($new_image, PATHINFO_DIRNAME);
352 $file = pathinfo($new_image, PATHINFO_BASENAME);
354 // Normalize the path
355 $dir = str_replace('\\', '/', realpath($dir)).'/';
357 // Process the image with the driver
358 $status = $this->driver->process($this->image, $this->actions, $dir, $file, $render = TRUE);
360 // Reset actions. Subsequent save() or render() will not apply previous actions.
361 if ($keep_actions === FALSE)
362 $this->actions = array();
364 return $status;
368 * Sanitize a given value type.
370 * @param string type of property
371 * @param mixed property value
372 * @return boolean
374 protected function valid_size($type, & $value)
376 if (is_null($value))
377 return TRUE;
379 if ( ! is_scalar($value))
380 return FALSE;
382 switch ($type)
384 case 'width':
385 case 'height':
386 if (is_string($value) AND ! ctype_digit($value))
388 // Only numbers and percent signs
389 if ( ! preg_match('/^[0-9]++%$/D', $value))
390 return FALSE;
392 else
394 $value = (int) $value;
396 break;
397 case 'top':
398 if (is_string($value) AND ! ctype_digit($value))
400 if ( ! in_array($value, array('top', 'bottom', 'center')))
401 return FALSE;
403 else
405 $value = (int) $value;
407 break;
408 case 'left':
409 if (is_string($value) AND ! ctype_digit($value))
411 if ( ! in_array($value, array('left', 'right', 'center')))
412 return FALSE;
414 else
416 $value = (int) $value;
418 break;
419 case 'master':
420 if ($value !== Image::NONE AND
421 $value !== Image::AUTO AND
422 $value !== Image::WIDTH AND
423 $value !== Image::HEIGHT)
424 return FALSE;
425 break;
428 return TRUE;
431 } // End Image