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

/modules/image/classes/kohana/image.php

https://bitbucket.org/Ahineya/trn_dev
PHP | 747 lines | 307 code | 82 blank | 358 comment | 31 complexity | dee7bc15dcada0087343467d5763e896 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. <?php defined('SYSPATH') or die('No direct script access.');
  2. /**
  3. * Image manipulation support. Allows images to be resized, cropped, etc.
  4. *
  5. * @package Kohana/Image
  6. * @category Base
  7. * @author Kohana Team
  8. * @copyright (c) 2008-2009 Kohana Team
  9. * @license http://kohanaphp.com/license.html
  10. */
  11. abstract class Kohana_Image {
  12. // Resizing constraints
  13. const NONE = 0x01;
  14. const WIDTH = 0x02;
  15. const HEIGHT = 0x03;
  16. const AUTO = 0x04;
  17. const INVERSE = 0x05;
  18. // Flipping directions
  19. const HORIZONTAL = 0x11;
  20. const VERTICAL = 0x12;
  21. /**
  22. * @var string default driver: GD, ImageMagick, etc
  23. */
  24. public static $default_driver = 'GD';
  25. // Status of the driver check
  26. protected static $_checked = FALSE;
  27. /**
  28. * Loads an image and prepares it for manipulation.
  29. *
  30. * $image = Image::factory('upload/test.jpg');
  31. *
  32. * @param string image file path
  33. * @param string driver type: GD, ImageMagick, etc
  34. * @return Image
  35. * @uses Image::$default_driver
  36. */
  37. public static function factory($file, $driver = NULL)
  38. {
  39. if ($driver === NULL)
  40. {
  41. // Use the default driver
  42. $driver = Image::$default_driver;
  43. }
  44. // Set the class name
  45. $class = 'Image_'.$driver;
  46. return new $class($file);
  47. }
  48. /**
  49. * @var string image file path
  50. */
  51. public $file;
  52. /**
  53. * @var integer image width
  54. */
  55. public $width;
  56. /**
  57. * @var integer image height
  58. */
  59. public $height;
  60. /**
  61. * @var integer one of the IMAGETYPE_* constants
  62. */
  63. public $type;
  64. /**
  65. * @var string mime type of the image
  66. */
  67. public $mime;
  68. /**
  69. * Loads information about the image. Will throw an exception if the image
  70. * does not exist or is not an image.
  71. *
  72. * @param string image file path
  73. * @return void
  74. * @throws Kohana_Exception
  75. */
  76. public function __construct($file)
  77. {
  78. try
  79. {
  80. // Get the real path to the file
  81. $file = realpath($file);
  82. // Get the image information
  83. $info = getimagesize($file);
  84. }
  85. catch (Exception $e)
  86. {
  87. // Ignore all errors while reading the image
  88. }
  89. if (empty($file) OR empty($info))
  90. {
  91. throw new Kohana_Exception('Not an image or invalid image: :file',
  92. array(':file' => Debug::path($file)));
  93. }
  94. // Store the image information
  95. $this->file = $file;
  96. $this->width = $info[0];
  97. $this->height = $info[1];
  98. $this->type = $info[2];
  99. $this->mime = image_type_to_mime_type($this->type);
  100. }
  101. /**
  102. * Render the current image.
  103. *
  104. * echo $image;
  105. *
  106. * [!!] The output of this function is binary and must be rendered with the
  107. * appropriate Content-Type header or it will not be displayed correctly!
  108. *
  109. * @return string
  110. */
  111. public function __toString()
  112. {
  113. try
  114. {
  115. // Render the current image
  116. return $this->render();
  117. }
  118. catch (Exception $e)
  119. {
  120. if (is_object(Kohana::$log))
  121. {
  122. // Get the text of the exception
  123. $error = Kohana_Exception::text($e);
  124. // Add this exception to the log
  125. Kohana::$log->add(Log::ERROR, $error);
  126. }
  127. // Showing any kind of error will be "inside" image data
  128. return '';
  129. }
  130. }
  131. /**
  132. * Resize the image to the given size. Either the width or the height can
  133. * be omitted and the image will be resized proportionally.
  134. *
  135. * // Resize to 200 pixels on the shortest side
  136. * $image->resize(200, 200);
  137. *
  138. * // Resize to 200x200 pixels, keeping aspect ratio
  139. * $image->resize(200, 200, Image::INVERSE);
  140. *
  141. * // Resize to 500 pixel width, keeping aspect ratio
  142. * $image->resize(500, NULL);
  143. *
  144. * // Resize to 500 pixel height, keeping aspect ratio
  145. * $image->resize(NULL, 500);
  146. *
  147. * // Resize to 200x500 pixels, ignoring aspect ratio
  148. * $image->resize(200, 500, Image::NONE);
  149. *
  150. * @param integer new width
  151. * @param integer new height
  152. * @param integer master dimension
  153. * @return $this
  154. * @uses Image::_do_resize
  155. */
  156. public function resize($width = NULL, $height = NULL, $master = NULL)
  157. {
  158. if ($master === NULL)
  159. {
  160. // Choose the master dimension automatically
  161. $master = Image::AUTO;
  162. }
  163. // Image::WIDTH and Image::HEIGHT deprecated. You can use it in old projects,
  164. // but in new you must pass empty value for non-master dimension
  165. elseif ($master == Image::WIDTH AND ! empty($width))
  166. {
  167. $master = Image::AUTO;
  168. // Set empty height for backward compatibility
  169. $height = NULL;
  170. }
  171. elseif ($master == Image::HEIGHT AND ! empty($height))
  172. {
  173. $master = Image::AUTO;
  174. // Set empty width for backward compatibility
  175. $width = NULL;
  176. }
  177. if (empty($width))
  178. {
  179. if ($master === Image::NONE)
  180. {
  181. // Use the current width
  182. $width = $this->width;
  183. }
  184. else
  185. {
  186. // If width not set, master will be height
  187. $master = Image::HEIGHT;
  188. }
  189. }
  190. if (empty($height))
  191. {
  192. if ($master === Image::NONE)
  193. {
  194. // Use the current height
  195. $height = $this->height;
  196. }
  197. else
  198. {
  199. // If height not set, master will be width
  200. $master = Image::WIDTH;
  201. }
  202. }
  203. switch ($master)
  204. {
  205. case Image::AUTO:
  206. // Choose direction with the greatest reduction ratio
  207. $master = ($this->width / $width) > ($this->height / $height) ? Image::WIDTH : Image::HEIGHT;
  208. break;
  209. case Image::INVERSE:
  210. // Choose direction with the minimum reduction ratio
  211. $master = ($this->width / $width) > ($this->height / $height) ? Image::HEIGHT : Image::WIDTH;
  212. break;
  213. }
  214. switch ($master)
  215. {
  216. case Image::WIDTH:
  217. // Recalculate the height based on the width proportions
  218. $height = $this->height * $width / $this->width;
  219. break;
  220. case Image::HEIGHT:
  221. // Recalculate the width based on the height proportions
  222. $width = $this->width * $height / $this->height;
  223. break;
  224. }
  225. // Convert the width and height to integers, minimum value is 1px
  226. $width = max(round($width), 1);
  227. $height = max(round($height), 1);
  228. $this->_do_resize($width, $height);
  229. return $this;
  230. }
  231. /**
  232. * Crop an image to the given size. Either the width or the height can be
  233. * omitted and the current width or height will be used.
  234. *
  235. * If no offset is specified, the center of the axis will be used.
  236. * If an offset of TRUE is specified, the bottom of the axis will be used.
  237. *
  238. * // Crop the image to 200x200 pixels, from the center
  239. * $image->crop(200, 200);
  240. *
  241. * @param integer new width
  242. * @param integer new height
  243. * @param mixed offset from the left
  244. * @param mixed offset from the top
  245. * @return $this
  246. * @uses Image::_do_crop
  247. */
  248. public function crop($width, $height, $offset_x = NULL, $offset_y = NULL)
  249. {
  250. if ($width > $this->width)
  251. {
  252. // Use the current width
  253. $width = $this->width;
  254. }
  255. if ($height > $this->height)
  256. {
  257. // Use the current height
  258. $height = $this->height;
  259. }
  260. if ($offset_x === NULL)
  261. {
  262. // Center the X offset
  263. $offset_x = round(($this->width - $width) / 2);
  264. }
  265. elseif ($offset_x === TRUE)
  266. {
  267. // Bottom the X offset
  268. $offset_x = $this->width - $width;
  269. }
  270. elseif ($offset_x < 0)
  271. {
  272. // Set the X offset from the right
  273. $offset_x = $this->width - $width + $offset_x;
  274. }
  275. if ($offset_y === NULL)
  276. {
  277. // Center the Y offset
  278. $offset_y = round(($this->height - $height) / 2);
  279. }
  280. elseif ($offset_y === TRUE)
  281. {
  282. // Bottom the Y offset
  283. $offset_y = $this->height - $height;
  284. }
  285. elseif ($offset_y < 0)
  286. {
  287. // Set the Y offset from the bottom
  288. $offset_y = $this->height - $height + $offset_y;
  289. }
  290. // Determine the maximum possible width and height
  291. $max_width = $this->width - $offset_x;
  292. $max_height = $this->height - $offset_y;
  293. if ($width > $max_width)
  294. {
  295. // Use the maximum available width
  296. $width = $max_width;
  297. }
  298. if ($height > $max_height)
  299. {
  300. // Use the maximum available height
  301. $height = $max_height;
  302. }
  303. $this->_do_crop($width, $height, $offset_x, $offset_y);
  304. return $this;
  305. }
  306. /**
  307. * Rotate the image by a given amount.
  308. *
  309. * // Rotate 45 degrees clockwise
  310. * $image->rotate(45);
  311. *
  312. * // Rotate 90% counter-clockwise
  313. * $image->rotate(-90);
  314. *
  315. * @param integer degrees to rotate: -360-360
  316. * @return $this
  317. * @uses Image::_do_rotate
  318. */
  319. public function rotate($degrees)
  320. {
  321. // Make the degrees an integer
  322. $degrees = (int) $degrees;
  323. if ($degrees > 180)
  324. {
  325. do
  326. {
  327. // Keep subtracting full circles until the degrees have normalized
  328. $degrees -= 360;
  329. }
  330. while($degrees > 180);
  331. }
  332. if ($degrees < -180)
  333. {
  334. do
  335. {
  336. // Keep adding full circles until the degrees have normalized
  337. $degrees += 360;
  338. }
  339. while($degrees < -180);
  340. }
  341. $this->_do_rotate($degrees);
  342. return $this;
  343. }
  344. /**
  345. * Flip the image along the horizontal or vertical axis.
  346. *
  347. * // Flip the image from top to bottom
  348. * $image->flip(Image::HORIZONTAL);
  349. *
  350. * // Flip the image from left to right
  351. * $image->flip(Image::VERTICAL);
  352. *
  353. * @param integer direction: Image::HORIZONTAL, Image::VERTICAL
  354. * @return $this
  355. * @uses Image::_do_flip
  356. */
  357. public function flip($direction)
  358. {
  359. if ($direction !== Image::HORIZONTAL)
  360. {
  361. // Flip vertically
  362. $direction = Image::VERTICAL;
  363. }
  364. $this->_do_flip($direction);
  365. return $this;
  366. }
  367. /**
  368. * Sharpen the image by a given amount.
  369. *
  370. * // Sharpen the image by 20%
  371. * $image->sharpen(20);
  372. *
  373. * @param integer amount to sharpen: 1-100
  374. * @return $this
  375. * @uses Image::_do_sharpen
  376. */
  377. public function sharpen($amount)
  378. {
  379. // The amount must be in the range of 1 to 100
  380. $amount = min(max($amount, 1), 100);
  381. $this->_do_sharpen($amount);
  382. return $this;
  383. }
  384. /**
  385. * Add a reflection to an image. The most opaque part of the reflection
  386. * will be equal to the opacity setting and fade out to full transparent.
  387. * Alpha transparency is preserved.
  388. *
  389. * // Create a 50 pixel reflection that fades from 0-100% opacity
  390. * $image->reflection(50);
  391. *
  392. * // Create a 50 pixel reflection that fades from 100-0% opacity
  393. * $image->reflection(50, 100, TRUE);
  394. *
  395. * // Create a 50 pixel reflection that fades from 0-60% opacity
  396. * $image->reflection(50, 60, TRUE);
  397. *
  398. * [!!] By default, the reflection will be go from transparent at the top
  399. * to opaque at the bottom.
  400. *
  401. * @param integer reflection height
  402. * @param integer reflection opacity: 0-100
  403. * @param boolean TRUE to fade in, FALSE to fade out
  404. * @return $this
  405. * @uses Image::_do_reflection
  406. */
  407. public function reflection($height = NULL, $opacity = 100, $fade_in = FALSE)
  408. {
  409. if ($height === NULL OR $height > $this->height)
  410. {
  411. // Use the current height
  412. $height = $this->height;
  413. }
  414. // The opacity must be in the range of 0 to 100
  415. $opacity = min(max($opacity, 0), 100);
  416. $this->_do_reflection($height, $opacity, $fade_in);
  417. return $this;
  418. }
  419. /**
  420. * Add a watermark to an image with a specified opacity. Alpha transparency
  421. * will be preserved.
  422. *
  423. * If no offset is specified, the center of the axis will be used.
  424. * If an offset of TRUE is specified, the bottom of the axis will be used.
  425. *
  426. * // Add a watermark to the bottom right of the image
  427. * $mark = Image::factory('upload/watermark.png');
  428. * $image->watermark($mark, TRUE, TRUE);
  429. *
  430. * @param object watermark Image instance
  431. * @param integer offset from the left
  432. * @param integer offset from the top
  433. * @param integer opacity of watermark: 1-100
  434. * @return $this
  435. * @uses Image::_do_watermark
  436. */
  437. public function watermark(Image $watermark, $offset_x = NULL, $offset_y = NULL, $opacity = 100)
  438. {
  439. if ($offset_x === NULL)
  440. {
  441. // Center the X offset
  442. $offset_x = round(($this->width - $watermark->width) / 2);
  443. }
  444. elseif ($offset_x === TRUE)
  445. {
  446. // Bottom the X offset
  447. $offset_x = $this->width - $watermark->width;
  448. }
  449. elseif ($offset_x < 0)
  450. {
  451. // Set the X offset from the right
  452. $offset_x = $this->width - $watermark->width + $offset_x;
  453. }
  454. if ($offset_y === NULL)
  455. {
  456. // Center the Y offset
  457. $offset_y = round(($this->height - $watermark->height) / 2);
  458. }
  459. elseif ($offset_y === TRUE)
  460. {
  461. // Bottom the Y offset
  462. $offset_y = $this->height - $watermark->height;
  463. }
  464. elseif ($offset_y < 0)
  465. {
  466. // Set the Y offset from the bottom
  467. $offset_y = $this->height - $watermark->height + $offset_y;
  468. }
  469. // The opacity must be in the range of 1 to 100
  470. $opacity = min(max($opacity, 1), 100);
  471. $this->_do_watermark($watermark, $offset_x, $offset_y, $opacity);
  472. return $this;
  473. }
  474. /**
  475. * Set the background color of an image. This is only useful for images
  476. * with alpha transparency.
  477. *
  478. * // Make the image background black
  479. * $image->background('#000');
  480. *
  481. * // Make the image background black with 50% opacity
  482. * $image->background('#000', 50);
  483. *
  484. * @param string hexadecimal color value
  485. * @param integer background opacity: 0-100
  486. * @return $this
  487. * @uses Image::_do_background
  488. */
  489. public function background($color, $opacity = 100)
  490. {
  491. if ($color[0] === '#')
  492. {
  493. // Remove the pound
  494. $color = substr($color, 1);
  495. }
  496. if (strlen($color) === 3)
  497. {
  498. // Convert shorthand into longhand hex notation
  499. $color = preg_replace('/./', '$0$0', $color);
  500. }
  501. // Convert the hex into RGB values
  502. list ($r, $g, $b) = array_map('hexdec', str_split($color, 2));
  503. // The opacity must be in the range of 0 to 100
  504. $opacity = min(max($opacity, 0), 100);
  505. $this->_do_background($r, $g, $b, $opacity);
  506. return $this;
  507. }
  508. /**
  509. * Save the image. If the filename is omitted, the original image will
  510. * be overwritten.
  511. *
  512. * // Save the image as a PNG
  513. * $image->save('saved/cool.png');
  514. *
  515. * // Overwrite the original image
  516. * $image->save();
  517. *
  518. * [!!] If the file exists, but is not writable, an exception will be thrown.
  519. *
  520. * [!!] If the file does not exist, and the directory is not writable, an
  521. * exception will be thrown.
  522. *
  523. * @param string new image path
  524. * @param integer quality of image: 1-100
  525. * @return boolean
  526. * @uses Image::_save
  527. * @throws Kohana_Exception
  528. */
  529. public function save($file = NULL, $quality = 100)
  530. {
  531. if ($file === NULL)
  532. {
  533. // Overwrite the file
  534. $file = $this->file;
  535. }
  536. if (is_file($file))
  537. {
  538. if ( ! is_writable($file))
  539. {
  540. throw new Kohana_Exception('File must be writable: :file',
  541. array(':file' => Debug::path($file)));
  542. }
  543. }
  544. else
  545. {
  546. // Get the directory of the file
  547. $directory = realpath(pathinfo($file, PATHINFO_DIRNAME));
  548. if ( ! is_dir($directory) OR ! is_writable($directory))
  549. {
  550. throw new Kohana_Exception('Directory must be writable: :directory',
  551. array(':directory' => Debug::path($directory)));
  552. }
  553. }
  554. // The quality must be in the range of 1 to 100
  555. $quality = min(max($quality, 1), 100);
  556. return $this->_do_save($file, $quality);
  557. }
  558. /**
  559. * Render the image and return the binary string.
  560. *
  561. * // Render the image at 50% quality
  562. * $data = $image->render(NULL, 50);
  563. *
  564. * // Render the image as a PNG
  565. * $data = $image->render('png');
  566. *
  567. * @param string image type to return: png, jpg, gif, etc
  568. * @param integer quality of image: 1-100
  569. * @return string
  570. * @uses Image::_do_render
  571. */
  572. public function render($type = NULL, $quality = 100)
  573. {
  574. if ($type === NULL)
  575. {
  576. // Use the current image type
  577. $type = image_type_to_extension($this->type, FALSE);
  578. }
  579. return $this->_do_render($type, $quality);
  580. }
  581. /**
  582. * Execute a resize.
  583. *
  584. * @param integer new width
  585. * @param integer new height
  586. * @return void
  587. */
  588. abstract protected function _do_resize($width, $height);
  589. /**
  590. * Execute a crop.
  591. *
  592. * @param integer new width
  593. * @param integer new height
  594. * @param integer offset from the left
  595. * @param integer offset from the top
  596. * @return void
  597. */
  598. abstract protected function _do_crop($width, $height, $offset_x, $offset_y);
  599. /**
  600. * Execute a rotation.
  601. *
  602. * @param integer degrees to rotate
  603. * @return void
  604. */
  605. abstract protected function _do_rotate($degrees);
  606. /**
  607. * Execute a flip.
  608. *
  609. * @param integer direction to flip
  610. * @return void
  611. */
  612. abstract protected function _do_flip($direction);
  613. /**
  614. * Execute a sharpen.
  615. *
  616. * @param integer amount to sharpen
  617. * @return void
  618. */
  619. abstract protected function _do_sharpen($amount);
  620. /**
  621. * Execute a reflection.
  622. *
  623. * @param integer reflection height
  624. * @param integer reflection opacity
  625. * @param boolean TRUE to fade out, FALSE to fade in
  626. * @return void
  627. */
  628. abstract protected function _do_reflection($height, $opacity, $fade_in);
  629. /**
  630. * Execute a watermarking.
  631. *
  632. * @param object watermarking Image
  633. * @param integer offset from the left
  634. * @param integer offset from the top
  635. * @param integer opacity of watermark
  636. * @return void
  637. */
  638. abstract protected function _do_watermark(Image $image, $offset_x, $offset_y, $opacity);
  639. /**
  640. * Execute a background.
  641. *
  642. * @param integer red
  643. * @param integer green
  644. * @param integer blue
  645. * @param integer opacity
  646. * @return void
  647. */
  648. abstract protected function _do_background($r, $g, $b, $opacity);
  649. /**
  650. * Execute a save.
  651. *
  652. * @param string new image filename
  653. * @param integer quality
  654. * @return boolean
  655. */
  656. abstract protected function _do_save($file, $quality);
  657. /**
  658. * Execute a render.
  659. *
  660. * @param string image type: png, jpg, gif, etc
  661. * @param integer quality
  662. * @return string
  663. */
  664. abstract protected function _do_render($type, $quality);
  665. } // End Image