PageRenderTime 45ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/extensions/image/Image.php

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