PageRenderTime 36ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 0ms

/system/libraries/Image.php

https://github.com/yamamoto123/Ushahidi_Web
PHP | 431 lines | 358 code | 23 blank | 50 comment | 8 complexity | 17ace034207fd9ffffb65e32f31ff361 MD5 | raw file
  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 3917 2009-01-21 03:06:22Z zombor $
  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. // Master Dimension
  15. const NONE = 1;
  16. const AUTO = 2;
  17. const HEIGHT = 3;
  18. const WIDTH = 4;
  19. // Flip Directions
  20. const HORIZONTAL = 5;
  21. const VERTICAL = 6;
  22. // Allowed image types
  23. public static $allowed_types = array
  24. (
  25. IMAGETYPE_GIF => 'gif',
  26. IMAGETYPE_JPEG => 'jpg',
  27. IMAGETYPE_PNG => 'png',
  28. IMAGETYPE_TIFF_II => 'tiff',
  29. IMAGETYPE_TIFF_MM => 'tiff',
  30. );
  31. // Driver instance
  32. protected $driver;
  33. // Driver actions
  34. protected $actions = array();
  35. // Reference to the current image filename
  36. protected $image = '';
  37. /**
  38. * Creates a new Image instance and returns it.
  39. *
  40. * @param string filename of image
  41. * @param array non-default configurations
  42. * @return object
  43. */
  44. public static function factory($image, $config = NULL)
  45. {
  46. return new Image($image, $config);
  47. }
  48. /**
  49. * Creates a new image editor instance.
  50. *
  51. * @throws Kohana_Exception
  52. * @param string filename of image
  53. * @param array non-default configurations
  54. * @return void
  55. */
  56. public function __construct($image, $config = NULL)
  57. {
  58. static $check;
  59. // Make the check exactly once
  60. ($check === NULL) and $check = function_exists('getimagesize');
  61. if ($check === FALSE)
  62. throw new Kohana_Exception('image.getimagesize_missing');
  63. // Check to make sure the image exists
  64. if ( ! is_file($image))
  65. throw new Kohana_Exception('image.file_not_found', $image);
  66. // Disable error reporting, to prevent PHP warnings
  67. $ER = error_reporting(0);
  68. // Fetch the image size and mime type
  69. $image_info = getimagesize($image);
  70. // Turn on error reporting again
  71. error_reporting($ER);
  72. // Make sure that the image is readable and valid
  73. if ( ! is_array($image_info) OR count($image_info) < 3)
  74. throw new Kohana_Exception('image.file_unreadable', $image);
  75. // Check to make sure the image type is allowed
  76. if ( ! isset(Image::$allowed_types[$image_info[2]]))
  77. throw new Kohana_Exception('image.type_not_allowed', $image);
  78. // Image has been validated, load it
  79. $this->image = array
  80. (
  81. 'file' => str_replace('\\', '/', realpath($image)),
  82. 'width' => $image_info[0],
  83. 'height' => $image_info[1],
  84. 'type' => $image_info[2],
  85. 'ext' => Image::$allowed_types[$image_info[2]],
  86. 'mime' => $image_info['mime']
  87. );
  88. // Load configuration
  89. $this->config = (array) $config + Kohana::config('image');
  90. // Set driver class name
  91. $driver = 'Image_'.ucfirst($this->config['driver']).'_Driver';
  92. // Load the driver
  93. if ( ! Kohana::auto_load($driver))
  94. throw new Kohana_Exception('core.driver_not_found', $this->config['driver'], get_class($this));
  95. // Initialize the driver
  96. $this->driver = new $driver($this->config['params']);
  97. // Validate the driver
  98. if ( ! ($this->driver instanceof Image_Driver))
  99. throw new Kohana_Exception('core.driver_implements', $this->config['driver'], get_class($this), 'Image_Driver');
  100. }
  101. /**
  102. * Handles retrieval of pre-save image properties
  103. *
  104. * @param string property name
  105. * @return mixed
  106. */
  107. public function __get($property)
  108. {
  109. if (isset($this->image[$property]))
  110. {
  111. return $this->image[$property];
  112. }
  113. else
  114. {
  115. throw new Kohana_Exception('core.invalid_property', $column, get_class($this));
  116. }
  117. }
  118. /**
  119. * Resize an image to a specific width and height. By default, Kohana will
  120. * maintain the aspect ratio using the width as the master dimension. If you
  121. * wish to use height as master dim, set $image->master_dim = Image::HEIGHT
  122. * This method is chainable.
  123. *
  124. * @throws Kohana_Exception
  125. * @param integer width
  126. * @param integer height
  127. * @param integer one of: Image::NONE, Image::AUTO, Image::WIDTH, Image::HEIGHT
  128. * @return object
  129. */
  130. public function resize($width, $height, $master = NULL)
  131. {
  132. if ( ! $this->valid_size('width', $width))
  133. throw new Kohana_Exception('image.invalid_width', $width);
  134. if ( ! $this->valid_size('height', $height))
  135. throw new Kohana_Exception('image.invalid_height', $height);
  136. if (empty($width) AND empty($height))
  137. throw new Kohana_Exception('image.invalid_dimensions', __FUNCTION__);
  138. if ($master === NULL)
  139. {
  140. // Maintain the aspect ratio by default
  141. $master = Image::AUTO;
  142. }
  143. elseif ( ! $this->valid_size('master', $master))
  144. throw new Kohana_Exception('image.invalid_master');
  145. $this->actions['resize'] = array
  146. (
  147. 'width' => $width,
  148. 'height' => $height,
  149. 'master' => $master,
  150. );
  151. return $this;
  152. }
  153. /**
  154. * Crop an image to a specific width and height. You may also set the top
  155. * and left offset.
  156. * This method is chainable.
  157. *
  158. * @throws Kohana_Exception
  159. * @param integer width
  160. * @param integer height
  161. * @param integer top offset, pixel value or one of: top, center, bottom
  162. * @param integer left offset, pixel value or one of: left, center, right
  163. * @return object
  164. */
  165. public function crop($width, $height, $top = 'center', $left = 'center')
  166. {
  167. if ( ! $this->valid_size('width', $width))
  168. throw new Kohana_Exception('image.invalid_width', $width);
  169. if ( ! $this->valid_size('height', $height))
  170. throw new Kohana_Exception('image.invalid_height', $height);
  171. if ( ! $this->valid_size('top', $top))
  172. throw new Kohana_Exception('image.invalid_top', $top);
  173. if ( ! $this->valid_size('left', $left))
  174. throw new Kohana_Exception('image.invalid_left', $left);
  175. if (empty($width) AND empty($height))
  176. throw new Kohana_Exception('image.invalid_dimensions', __FUNCTION__);
  177. $this->actions['crop'] = array
  178. (
  179. 'width' => $width,
  180. 'height' => $height,
  181. 'top' => $top,
  182. 'left' => $left,
  183. );
  184. return $this;
  185. }
  186. /**
  187. * Allows rotation of an image by 180 degrees clockwise or counter clockwise.
  188. *
  189. * @param integer degrees
  190. * @return object
  191. */
  192. public function rotate($degrees)
  193. {
  194. $degrees = (int) $degrees;
  195. if ($degrees > 180)
  196. {
  197. do
  198. {
  199. // Keep subtracting full circles until the degrees have normalized
  200. $degrees -= 360;
  201. }
  202. while($degrees > 180);
  203. }
  204. if ($degrees < -180)
  205. {
  206. do
  207. {
  208. // Keep adding full circles until the degrees have normalized
  209. $degrees += 360;
  210. }
  211. while($degrees < -180);
  212. }
  213. $this->actions['rotate'] = $degrees;
  214. return $this;
  215. }
  216. /**
  217. * Flip an image horizontally or vertically.
  218. *
  219. * @throws Kohana_Exception
  220. * @param integer direction
  221. * @return object
  222. */
  223. public function flip($direction)
  224. {
  225. if ($direction !== self::HORIZONTAL AND $direction !== self::VERTICAL)
  226. throw new Kohana_Exception('image.invalid_flip');
  227. $this->actions['flip'] = $direction;
  228. return $this;
  229. }
  230. /**
  231. * Change the quality of an image.
  232. *
  233. * @param integer quality as a percentage
  234. * @return object
  235. */
  236. public function quality($amount)
  237. {
  238. $this->actions['quality'] = max(1, min($amount, 100));
  239. return $this;
  240. }
  241. /**
  242. * Sharpen an image.
  243. *
  244. * @param integer amount to sharpen, usually ~20 is ideal
  245. * @return object
  246. */
  247. public function sharpen($amount)
  248. {
  249. $this->actions['sharpen'] = max(1, min($amount, 100));
  250. return $this;
  251. }
  252. /**
  253. * Save the image to a new image or overwrite this image.
  254. *
  255. * @throws Kohana_Exception
  256. * @param string new image filename
  257. * @param integer permissions for new image
  258. * @param boolean keep or discard image process actions
  259. * @return object
  260. */
  261. public function save($new_image = FALSE, $chmod = 0644, $keep_actions = FALSE)
  262. {
  263. // If no new image is defined, use the current image
  264. empty($new_image) and $new_image = $this->image['file'];
  265. // Separate the directory and filename
  266. $dir = pathinfo($new_image, PATHINFO_DIRNAME);
  267. $file = pathinfo($new_image, PATHINFO_BASENAME);
  268. // Normalize the path
  269. $dir = str_replace('\\', '/', realpath($dir)).'/';
  270. if ( ! is_writable($dir))
  271. throw new Kohana_Exception('image.directory_unwritable', $dir);
  272. if ($status = $this->driver->process($this->image, $this->actions, $dir, $file))
  273. {
  274. if ($chmod !== FALSE)
  275. {
  276. // Set permissions
  277. chmod($new_image, $chmod);
  278. }
  279. }
  280. // Reset actions. Subsequent save() or render() will not apply previous actions.
  281. if ($keep_actions === FALSE)
  282. $this->actions = array();
  283. return $status;
  284. }
  285. /**
  286. * Output the image to the browser.
  287. *
  288. * @param boolean keep or discard image process actions
  289. * @return object
  290. */
  291. public function render($keep_actions = FALSE)
  292. {
  293. $new_image = $this->image['file'];
  294. // Separate the directory and filename
  295. $dir = pathinfo($new_image, PATHINFO_DIRNAME);
  296. $file = pathinfo($new_image, PATHINFO_BASENAME);
  297. // Normalize the path
  298. $dir = str_replace('\\', '/', realpath($dir)).'/';
  299. // Process the image with the driver
  300. $status = $this->driver->process($this->image, $this->actions, $dir, $file, $render = TRUE);
  301. // Reset actions. Subsequent save() or render() will not apply previous actions.
  302. if ($keep_actions === FALSE)
  303. $this->actions = array();
  304. return $status;
  305. }
  306. /**
  307. * Sanitize a given value type.
  308. *
  309. * @param string type of property
  310. * @param mixed property value
  311. * @return boolean
  312. */
  313. protected function valid_size($type, & $value)
  314. {
  315. if (is_null($value))
  316. return TRUE;
  317. if ( ! is_scalar($value))
  318. return FALSE;
  319. switch ($type)
  320. {
  321. case 'width':
  322. case 'height':
  323. if (is_string($value) AND ! ctype_digit($value))
  324. {
  325. // Only numbers and percent signs
  326. if ( ! preg_match('/^[0-9]++%$/D', $value))
  327. return FALSE;
  328. }
  329. else
  330. {
  331. $value = (int) $value;
  332. }
  333. break;
  334. case 'top':
  335. if (is_string($value) AND ! ctype_digit($value))
  336. {
  337. if ( ! in_array($value, array('top', 'bottom', 'center')))
  338. return FALSE;
  339. }
  340. else
  341. {
  342. $value = (int) $value;
  343. }
  344. break;
  345. case 'left':
  346. if (is_string($value) AND ! ctype_digit($value))
  347. {
  348. if ( ! in_array($value, array('left', 'right', 'center')))
  349. return FALSE;
  350. }
  351. else
  352. {
  353. $value = (int) $value;
  354. }
  355. break;
  356. case 'master':
  357. if ($value !== Image::NONE AND
  358. $value !== Image::AUTO AND
  359. $value !== Image::WIDTH AND
  360. $value !== Image::HEIGHT)
  361. return FALSE;
  362. break;
  363. }
  364. return TRUE;
  365. }
  366. } // End Image