PageRenderTime 48ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/modules/image/classes/Kohana/Image.php

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