PageRenderTime 47ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/obsolete/web_client/system/libraries/Image.php

https://github.com/mihalyf/gallery3-contrib
PHP | 503 lines | 427 code | 25 blank | 51 comment | 12 complexity | 8faecadf24f1f673bec729e11dcc4eda 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 4679 2009-11-10 01:45:52Z isaiah $
  7. *
  8. * @package Image
  9. * @author Kohana Team
  10. * @copyright (c) 2007-2009 Kohana Team
  11. * @license http://kohanaphp.com/license
  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. // Orientations
  23. const PORTRAIT = 7;
  24. const LANDSCAPE = 8;
  25. const SQUARE = 9;
  26. // Allowed image types
  27. public static $allowed_types = array
  28. (
  29. IMAGETYPE_GIF => 'gif',
  30. IMAGETYPE_JPEG => 'jpg',
  31. IMAGETYPE_PNG => 'png',
  32. IMAGETYPE_TIFF_II => 'tiff',
  33. IMAGETYPE_TIFF_MM => 'tiff',
  34. );
  35. // Driver instance
  36. protected $driver;
  37. // Driver actions
  38. protected $actions = array();
  39. // Reference to the current image filename
  40. protected $image = '';
  41. /**
  42. * Creates a new Image instance and returns it.
  43. *
  44. * @param string filename of image
  45. * @param array non-default configurations
  46. * @return object
  47. */
  48. public static function factory($image, $config = NULL)
  49. {
  50. return new Image($image, $config);
  51. }
  52. /**
  53. * Creates a new image editor instance.
  54. *
  55. * @throws Kohana_Exception
  56. * @param string filename of image
  57. * @param array non-default configurations
  58. * @return void
  59. */
  60. public function __construct($image, $config = NULL)
  61. {
  62. static $check;
  63. // Make the check exactly once
  64. ($check === NULL) and $check = function_exists('getimagesize');
  65. if ($check === FALSE)
  66. throw new Kohana_Exception('The Image library requires the getimagesize() PHP function, which is not available in your installation.');
  67. // Check to make sure the image exists
  68. if ( ! is_file($image))
  69. throw new Kohana_Exception('The specified image, :image:, was not found. Please verify that images exist by using file_exists() before manipulating them.', array(':image:' => $image));
  70. // Disable error reporting, to prevent PHP warnings
  71. $ER = error_reporting(0);
  72. // Fetch the image size and mime type
  73. $image_info = getimagesize($image);
  74. // Turn on error reporting again
  75. error_reporting($ER);
  76. // Make sure that the image is readable and valid
  77. if ( ! is_array($image_info) OR count($image_info) < 3)
  78. throw new Kohana_Exception('The file specified, :file:, is not readable or is not an image', array(':file:' => $image));
  79. // Check to make sure the image type is allowed
  80. if ( ! isset(Image::$allowed_types[$image_info[2]]))
  81. throw new Kohana_Exception('The specified image, :type:, is not an allowed image type.', array(':type:' => $image));
  82. // Image has been validated, load it
  83. $this->image = array
  84. (
  85. 'file' => str_replace('\\', '/', realpath($image)),
  86. 'width' => $image_info[0],
  87. 'height' => $image_info[1],
  88. 'type' => $image_info[2],
  89. 'ext' => Image::$allowed_types[$image_info[2]],
  90. 'mime' => $image_info['mime']
  91. );
  92. $this->determine_orientation();
  93. // Load configuration
  94. $this->config = (array) $config + Kohana::config('image');
  95. // Set driver class name
  96. $driver = 'Image_'.ucfirst($this->config['driver']).'_Driver';
  97. // Load the driver
  98. if ( ! Kohana::auto_load($driver))
  99. throw new Kohana_Exception('The :driver: driver for the :library: library could not be found',
  100. array(':driver:' => $this->config['driver'], ':library:' => get_class($this)));
  101. // Initialize the driver
  102. $this->driver = new $driver($this->config['params']);
  103. // Validate the driver
  104. if ( ! ($this->driver instanceof Image_Driver))
  105. throw new Kohana_Exception('The :driver: driver for the :library: library must implement the :interface: interface',
  106. array(':driver:' => $this->config['driver'], ':library:' => get_class($this), ':interface:' => 'Image_Driver'));
  107. }
  108. /**
  109. * Works out the correct orientation for the image
  110. *
  111. * @return void
  112. */
  113. protected function determine_orientation()
  114. {
  115. switch (TRUE)
  116. {
  117. case $this->image['height'] > $this->image['width']:
  118. $orientation = Image::PORTRAIT;
  119. break;
  120. case $this->image['height'] < $this->image['width']:
  121. $orientation = Image::LANDSCAPE;
  122. break;
  123. default:
  124. $orientation = Image::SQUARE;
  125. }
  126. $this->image['orientation'] = $orientation;
  127. }
  128. /**
  129. * Handles retrieval of pre-save image properties
  130. *
  131. * @param string property name
  132. * @return mixed
  133. */
  134. public function __get($property)
  135. {
  136. if (isset($this->image[$property]))
  137. {
  138. return $this->image[$property];
  139. }
  140. else
  141. {
  142. throw new Kohana_Exception('The :property: property does not exist in the :class: class.',
  143. array(':property:' => $property, ':class:' => get_class($this)));
  144. }
  145. }
  146. /**
  147. * Resize an image to a specific width and height. By default, Kohana will
  148. * maintain the aspect ratio using the width as the master dimension. If you
  149. * wish to use height as master dim, set $image->master_dim = Image::HEIGHT
  150. * This method is chainable.
  151. *
  152. * @throws Kohana_Exception
  153. * @param integer width
  154. * @param integer height
  155. * @param integer one of: Image::NONE, Image::AUTO, Image::WIDTH, Image::HEIGHT
  156. * @return object
  157. */
  158. public function resize($width, $height, $master = NULL)
  159. {
  160. if ( ! $this->valid_size('width', $width))
  161. throw new Kohana_Exception('The width you specified, :width:, is not valid.', array(':width:' => $width));
  162. if ( ! $this->valid_size('height', $height))
  163. throw new Kohana_Exception('The height you specified, :height:, is not valid.', array(':height:' => $height));
  164. if (empty($width) AND empty($height))
  165. throw new Kohana_Exception('The dimensions specified for :function: are not valid.', array(':function:' => __FUNCTION__));
  166. if ($master === NULL)
  167. {
  168. // Maintain the aspect ratio by default
  169. $master = Image::AUTO;
  170. }
  171. elseif ( ! $this->valid_size('master', $master))
  172. throw new Kohana_Exception('The master dimension specified is not valid.');
  173. $this->actions['resize'] = array
  174. (
  175. 'width' => $width,
  176. 'height' => $height,
  177. 'master' => $master,
  178. );
  179. $this->determine_orientation();
  180. return $this;
  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('The width you specified, :width:, is not valid.', array(':width:' => $width));
  198. if ( ! $this->valid_size('height', $height))
  199. throw new Kohana_Exception('The height you specified, :height:, is not valid.', array(':height:' => $height));
  200. if ( ! $this->valid_size('top', $top))
  201. throw new Kohana_Exception('The top offset you specified, :top:, is not valid.', array(':top:' => $top));
  202. if ( ! $this->valid_size('left', $left))
  203. throw new Kohana_Exception('The left offset you specified, :left:, is not valid.', array(':left:' => $left));
  204. if (empty($width) AND empty($height))
  205. throw new Kohana_Exception('The dimensions specified for :function: are not valid.', array(':function:' => __FUNCTION__));
  206. $this->actions['crop'] = array
  207. (
  208. 'width' => $width,
  209. 'height' => $height,
  210. 'top' => $top,
  211. 'left' => $left,
  212. );
  213. $this->determine_orientation();
  214. return $this;
  215. }
  216. /**
  217. * Allows rotation of an image by 180 degrees clockwise or counter clockwise.
  218. *
  219. * @param integer degrees
  220. * @return object
  221. */
  222. public function rotate($degrees)
  223. {
  224. $degrees = (int) $degrees;
  225. if ($degrees > 180)
  226. {
  227. do
  228. {
  229. // Keep subtracting full circles until the degrees have normalized
  230. $degrees -= 360;
  231. }
  232. while($degrees > 180);
  233. }
  234. if ($degrees < -180)
  235. {
  236. do
  237. {
  238. // Keep adding full circles until the degrees have normalized
  239. $degrees += 360;
  240. }
  241. while($degrees < -180);
  242. }
  243. $this->actions['rotate'] = $degrees;
  244. return $this;
  245. }
  246. /**
  247. * Overlay a second image on top of this one.
  248. *
  249. * @throws Kohana_Exception
  250. * @param string $overlay_file path to an image file
  251. * @param integer $x x offset for the overlay
  252. * @param integer $y y offset for the overlay
  253. * @param integer $transparency transparency percent
  254. */
  255. public function composite($overlay_file, $x, $y, $transparency)
  256. {
  257. $image_info = getimagesize($overlay_file);
  258. // Check to make sure the image type is allowed
  259. if ( ! isset(Image::$allowed_types[$image_info[2]]))
  260. throw new Kohana_Exception('The specified image, :type:, is not an allowed image type.', array(':type:' => $overlay_file));
  261. $this->actions['composite'] = array
  262. (
  263. 'overlay_file' => $overlay_file,
  264. 'mime' => $image_info['mime'],
  265. 'x' => $x,
  266. 'y' => $y,
  267. 'transparency' => $transparency
  268. );
  269. return $this;
  270. }
  271. /**
  272. * Flip an image horizontally or vertically.
  273. *
  274. * @throws Kohana_Exception
  275. * @param integer direction
  276. * @return object
  277. */
  278. public function flip($direction)
  279. {
  280. if ($direction !== Image::HORIZONTAL AND $direction !== Image::VERTICAL)
  281. throw new Kohana_Exception('The flip direction specified is not valid.');
  282. $this->actions['flip'] = $direction;
  283. return $this;
  284. }
  285. /**
  286. * Change the quality of an image.
  287. *
  288. * @param integer quality as a percentage
  289. * @return object
  290. */
  291. public function quality($amount)
  292. {
  293. $this->actions['quality'] = max(1, min($amount, 100));
  294. return $this;
  295. }
  296. /**
  297. * Sharpen an image.
  298. *
  299. * @param integer amount to sharpen, usually ~20 is ideal
  300. * @return object
  301. */
  302. public function sharpen($amount)
  303. {
  304. $this->actions['sharpen'] = max(1, min($amount, 100));
  305. return $this;
  306. }
  307. /**
  308. * Save the image to a new image or overwrite this image.
  309. *
  310. * @throws Kohana_Exception
  311. * @param string new image filename
  312. * @param integer permissions for new image
  313. * @param boolean keep or discard image process actions
  314. * @return object
  315. */
  316. public function save($new_image = FALSE, $chmod = 0644, $keep_actions = FALSE, $background = NULL)
  317. {
  318. // If no new image is defined, use the current image
  319. empty($new_image) and $new_image = $this->image['file'];
  320. // Separate the directory and filename
  321. $dir = pathinfo($new_image, PATHINFO_DIRNAME);
  322. $file = pathinfo($new_image, PATHINFO_BASENAME);
  323. // Normalize the path
  324. $dir = str_replace('\\', '/', realpath($dir)).'/';
  325. if ( ! is_writable($dir))
  326. throw new Kohana_Exception('The specified directory, :dir:, is not writable.', array(':dir:' => $dir));
  327. if ($status = $this->driver->process($this->image, $this->actions, $dir, $file, FALSE, $background))
  328. {
  329. if ($chmod !== FALSE)
  330. {
  331. // Set permissions
  332. chmod($new_image, $chmod);
  333. }
  334. }
  335. if ($keep_actions !== TRUE)
  336. {
  337. // Reset actions. Subsequent save() or render() will not apply previous actions.
  338. $this->actions = array();
  339. }
  340. return $status;
  341. }
  342. /**
  343. * Output the image to the browser.
  344. *
  345. * @param boolean keep or discard image process actions
  346. * @return object
  347. */
  348. public function render($keep_actions = FALSE, $background = NULL)
  349. {
  350. $new_image = $this->image['file'];
  351. // Separate the directory and filename
  352. $dir = pathinfo($new_image, PATHINFO_DIRNAME);
  353. $file = pathinfo($new_image, PATHINFO_BASENAME);
  354. // Normalize the path
  355. $dir = str_replace('\\', '/', realpath($dir)).'/';
  356. // Process the image with the driver
  357. $status = $this->driver->process($this->image, $this->actions, $dir, $file, TRUE, $background);
  358. if ($keep_actions !== TRUE)
  359. {
  360. // Reset actions. Subsequent save() or render() will not apply previous actions.
  361. $this->actions = array();
  362. }
  363. return $status;
  364. }
  365. /**
  366. * Sanitize a given value type.
  367. *
  368. * @param string type of property
  369. * @param mixed property value
  370. * @return boolean
  371. */
  372. protected function valid_size($type, & $value)
  373. {
  374. if (is_null($value))
  375. return TRUE;
  376. if ( ! is_scalar($value))
  377. return FALSE;
  378. switch ($type)
  379. {
  380. case 'width':
  381. case 'height':
  382. if (is_string($value) AND ! ctype_digit($value))
  383. {
  384. // Only numbers and percent signs
  385. if ( ! preg_match('/^[0-9]++%$/D', $value))
  386. return FALSE;
  387. }
  388. else
  389. {
  390. $value = (int) $value;
  391. }
  392. break;
  393. case 'top':
  394. if (is_string($value) AND ! ctype_digit($value))
  395. {
  396. if ( ! in_array($value, array('top', 'bottom', 'center')))
  397. return FALSE;
  398. }
  399. else
  400. {
  401. $value = (int) $value;
  402. }
  403. break;
  404. case 'left':
  405. if (is_string($value) AND ! ctype_digit($value))
  406. {
  407. if ( ! in_array($value, array('left', 'right', 'center')))
  408. return FALSE;
  409. }
  410. else
  411. {
  412. $value = (int) $value;
  413. }
  414. break;
  415. case 'master':
  416. if ($value !== Image::NONE AND
  417. $value !== Image::AUTO AND
  418. $value !== Image::WIDTH AND
  419. $value !== Image::HEIGHT)
  420. return FALSE;
  421. break;
  422. }
  423. return TRUE;
  424. }
  425. } // End Image