Route and Results buttons updated.
[speedfreak] / Server / system / libraries / Image.php
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.
5  *
6  * $Id: Image.php 4072 2009-03-13 17:20:38Z jheathco $
7  *
8  * @package    Image
9  * @author     Kohana Team
10  * @copyright  (c) 2007-2008 Kohana Team
11  * @license    http://kohanaphp.com/license.html
12  */
13 class Image_Core {
14
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;
23
24         // Allowed image types
25         public static $allowed_types = array
26         (
27                 IMAGETYPE_GIF => 'gif',
28                 IMAGETYPE_JPEG => 'jpg',
29                 IMAGETYPE_PNG => 'png',
30                 IMAGETYPE_TIFF_II => 'tiff',
31                 IMAGETYPE_TIFF_MM => 'tiff',
32         );
33
34         // Driver instance
35         protected $driver;
36
37         // Driver actions
38         protected $actions = array();
39
40         // Reference to the current image filename
41         protected $image = '';
42
43         /**
44          * Creates a new Image instance and returns it.
45          *
46          * @param   string   filename of image
47          * @param   array    non-default configurations
48          * @return  object
49          */
50         public static function factory($image, $config = NULL)
51         {
52                 return new Image($image, $config);
53         }
54
55         /**
56          * Creates a new image editor instance.
57          *
58          * @throws  Kohana_Exception
59          * @param   string   filename of image
60          * @param   array    non-default configurations
61          * @return  void
62          */
63         public function __construct($image, $config = NULL)
64         {
65                 static $check;
66
67                 // Make the check exactly once
68                 ($check === NULL) and $check = function_exists('getimagesize');
69
70                 if ($check === FALSE)
71                         throw new Kohana_Exception('image.getimagesize_missing');
72
73                 // Check to make sure the image exists
74                 if ( ! is_file($image))
75                         throw new Kohana_Exception('image.file_not_found', $image);
76
77                 // Disable error reporting, to prevent PHP warnings
78                 $ER = error_reporting(0);
79
80                 // Fetch the image size and mime type
81                 $image_info = getimagesize($image);
82
83                 // Turn on error reporting again
84                 error_reporting($ER);
85
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);
89
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);
93
94                 // Image has been validated, load it
95                 $this->image = array
96                 (
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']
103                 );
104
105                 // Load configuration
106                 $this->config = (array) $config + Kohana::config('image');
107
108                 // Set driver class name
109                 $driver = 'Image_'.ucfirst($this->config['driver']).'_Driver';
110
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));
114
115                 // Initialize the driver
116                 $this->driver = new $driver($this->config['params']);
117
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');
121         }
122
123         /**
124          * Handles retrieval of pre-save image properties
125          *
126          * @param   string  property name
127          * @return  mixed
128          */
129         public function __get($property)
130         {
131                 if (isset($this->image[$property]))
132                 {
133                         return $this->image[$property];
134                 }
135                 else
136                 {
137                         throw new Kohana_Exception('core.invalid_property', $property, get_class($this));
138                 }
139         }
140
141         /**
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.
146          *
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
152          */
153         public function resize($width, $height, $master = NULL)
154         {
155                 if ( ! $this->valid_size('width', $width))
156                         throw new Kohana_Exception('image.invalid_width', $width);
157
158                 if ( ! $this->valid_size('height', $height))
159                         throw new Kohana_Exception('image.invalid_height', $height);
160
161                 if (empty($width) AND empty($height))
162                         throw new Kohana_Exception('image.invalid_dimensions', __FUNCTION__);
163
164                 if ($master === NULL)
165                 {
166                         // Maintain the aspect ratio by default
167                         $master = Image::AUTO;
168                 }
169                 elseif ( ! $this->valid_size('master', $master))
170                         throw new Kohana_Exception('image.invalid_master');
171
172                 $this->actions['resize'] = array
173                 (
174                         'width'  => $width,
175                         'height' => $height,
176                         'master' => $master,
177                 );
178
179                 return $this;
180         }
181
182         /**
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.
186          *
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
193          */
194         public function crop($width, $height, $top = 'center', $left = 'center')
195         {
196                 if ( ! $this->valid_size('width', $width))
197                         throw new Kohana_Exception('image.invalid_width', $width);
198
199                 if ( ! $this->valid_size('height', $height))
200                         throw new Kohana_Exception('image.invalid_height', $height);
201
202                 if ( ! $this->valid_size('top', $top))
203                         throw new Kohana_Exception('image.invalid_top', $top);
204
205                 if ( ! $this->valid_size('left', $left))
206                         throw new Kohana_Exception('image.invalid_left', $left);
207
208                 if (empty($width) AND empty($height))
209                         throw new Kohana_Exception('image.invalid_dimensions', __FUNCTION__);
210
211                 $this->actions['crop'] = array
212                 (
213                         'width'  => $width,
214                         'height' => $height,
215                         'top'    => $top,
216                         'left'   => $left,
217                 );
218
219                 return $this;
220         }
221
222         /**
223          * Allows rotation of an image by 180 degrees clockwise or counter clockwise.
224          *
225          * @param   integer  degrees
226          * @return  object
227          */
228         public function rotate($degrees)
229         {
230                 $degrees = (int) $degrees;
231
232                 if ($degrees > 180)
233                 {
234                         do
235                         {
236                                 // Keep subtracting full circles until the degrees have normalized
237                                 $degrees -= 360;
238                         }
239                         while($degrees > 180);
240                 }
241
242                 if ($degrees < -180)
243                 {
244                         do
245                         {
246                                 // Keep adding full circles until the degrees have normalized
247                                 $degrees += 360;
248                         }
249                         while($degrees < -180);
250                 }
251
252                 $this->actions['rotate'] = $degrees;
253
254                 return $this;
255         }
256
257         /**
258          * Flip an image horizontally or vertically.
259          *
260          * @throws  Kohana_Exception
261          * @param   integer  direction
262          * @return  object
263          */
264         public function flip($direction)
265         {
266                 if ($direction !== Image::HORIZONTAL AND $direction !== Image::VERTICAL)
267                         throw new Kohana_Exception('image.invalid_flip');
268
269                 $this->actions['flip'] = $direction;
270
271                 return $this;
272         }
273
274         /**
275          * Change the quality of an image.
276          *
277          * @param   integer  quality as a percentage
278          * @return  object
279          */
280         public function quality($amount)
281         {
282                 $this->actions['quality'] = max(1, min($amount, 100));
283
284                 return $this;
285         }
286
287         /**
288          * Sharpen an image.
289          *
290          * @param   integer  amount to sharpen, usually ~20 is ideal
291          * @return  object
292          */
293         public function sharpen($amount)
294         {
295                 $this->actions['sharpen'] = max(1, min($amount, 100));
296
297                 return $this;
298         }
299
300         /**
301          * Save the image to a new image or overwrite this image.
302          *
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
308          */
309         public function save($new_image = FALSE, $chmod = 0644, $keep_actions = FALSE)
310         {
311                 // If no new image is defined, use the current image
312                 empty($new_image) and $new_image = $this->image['file'];
313
314                 // Separate the directory and filename
315                 $dir  = pathinfo($new_image, PATHINFO_DIRNAME);
316                 $file = pathinfo($new_image, PATHINFO_BASENAME);
317
318                 // Normalize the path
319                 $dir = str_replace('\\', '/', realpath($dir)).'/';
320
321                 if ( ! is_writable($dir))
322                         throw new Kohana_Exception('image.directory_unwritable', $dir);
323
324                 if ($status = $this->driver->process($this->image, $this->actions, $dir, $file))
325                 {
326                         if ($chmod !== FALSE)
327                         {
328                                 // Set permissions
329                                 chmod($new_image, $chmod);
330                         }
331                 }
332
333                 // Reset actions. Subsequent save() or render() will not apply previous actions.
334                 if ($keep_actions === FALSE)
335                         $this->actions = array();
336
337                 return $status;
338         }
339
340         /**
341          * Output the image to the browser.
342          *
343          * @param   boolean  keep or discard image process actions
344          * @return      object
345          */
346         public function render($keep_actions = FALSE)
347         {
348                 $new_image = $this->image['file'];
349
350                 // Separate the directory and filename
351                 $dir  = pathinfo($new_image, PATHINFO_DIRNAME);
352                 $file = pathinfo($new_image, PATHINFO_BASENAME);
353
354                 // Normalize the path
355                 $dir = str_replace('\\', '/', realpath($dir)).'/';
356
357                 // Process the image with the driver
358                 $status = $this->driver->process($this->image, $this->actions, $dir, $file, $render = TRUE);
359
360                 // Reset actions. Subsequent save() or render() will not apply previous actions.
361                 if ($keep_actions === FALSE)
362                         $this->actions = array();
363
364                 return $status;
365         }
366
367         /**
368          * Sanitize a given value type.
369          *
370          * @param   string   type of property
371          * @param   mixed    property value
372          * @return  boolean
373          */
374         protected function valid_size($type, & $value)
375         {
376                 if (is_null($value))
377                         return TRUE;
378
379                 if ( ! is_scalar($value))
380                         return FALSE;
381
382                 switch ($type)
383                 {
384                         case 'width':
385                         case 'height':
386                                 if (is_string($value) AND ! ctype_digit($value))
387                                 {
388                                         // Only numbers and percent signs
389                                         if ( ! preg_match('/^[0-9]++%$/D', $value))
390                                                 return FALSE;
391                                 }
392                                 else
393                                 {
394                                         $value = (int) $value;
395                                 }
396                         break;
397                         case 'top':
398                                 if (is_string($value) AND ! ctype_digit($value))
399                                 {
400                                         if ( ! in_array($value, array('top', 'bottom', 'center')))
401                                                 return FALSE;
402                                 }
403                                 else
404                                 {
405                                         $value = (int) $value;
406                                 }
407                         break;
408                         case 'left':
409                                 if (is_string($value) AND ! ctype_digit($value))
410                                 {
411                                         if ( ! in_array($value, array('left', 'right', 'center')))
412                                                 return FALSE;
413                                 }
414                                 else
415                                 {
416                                         $value = (int) $value;
417                                 }
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;
426                 }
427
428                 return TRUE;
429         }
430
431 } // End Image