PageRenderTime 46ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 1ms

/Nette/Image.php

https://github.com/DocX/nette
PHP | 516 lines | 259 code | 103 blank | 154 comment | 40 complexity | 03d7b4c650ca0c57b1a79b38cb415cd0 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. <?php
  2. /**
  3. * Nette Framework
  4. *
  5. * Copyright (c) 2004, 2009 David Grudl (http://davidgrudl.com)
  6. *
  7. * This source file is subject to the "Nette license" that is bundled
  8. * with this package in the file license.txt.
  9. *
  10. * For more information please see http://nettephp.com
  11. *
  12. * @copyright Copyright (c) 2004, 2009 David Grudl
  13. * @license http://nettephp.com/license Nette license
  14. * @link http://nettephp.com
  15. * @category Nette
  16. * @package Nette
  17. */
  18. /*namespace Nette;*/
  19. require_once dirname(__FILE__) . '/Object.php';
  20. /**
  21. * Basic manipulation with images.
  22. *
  23. * <code>
  24. * $image = Image::fromFile('nette.jpg');
  25. * $image->resize(150, 100);
  26. * $image->sharpen();
  27. * $image->send();
  28. * </code>
  29. *
  30. * @author David Grudl
  31. * @copyright Copyright (c) 2004, 2009 David Grudl
  32. * @package Nette
  33. *
  34. * @property-read int $width
  35. * @property-read int $height
  36. * @property-read resource $imageResource
  37. */
  38. class Image extends Object
  39. {
  40. /**#@+ resizing flags {@link resize()} */
  41. const ENLARGE = 1;
  42. const STRETCH = 2;
  43. /**#@-*/
  44. /**#@+ @int image types {@link send()} */
  45. const JPEG = IMAGETYPE_JPEG;
  46. const PNG = IMAGETYPE_PNG;
  47. const GIF = IMAGETYPE_GIF;
  48. /**#@-*/
  49. const EMPTY_GIF = "GIF89a\x01\x00\x01\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00!\xf9\x04\x01\x00\x00\x00\x00,\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02D\x01\x00;";
  50. /** @var bool */
  51. public static $useImageMagick = FALSE;
  52. /** @var resource */
  53. private $image;
  54. /**
  55. * Returns RGB color.
  56. * @param int red 0..255
  57. * @param int green 0..255
  58. * @param int blue 0..255
  59. * @param int transparency 0..127
  60. * @return array
  61. */
  62. public static function rgb($red, $green, $blue, $transparency = 0)
  63. {
  64. return array(
  65. 'red' => max(0, min(255, (int) $red)),
  66. 'green' => max(0, min(255, (int) $green)),
  67. 'blue' => max(0, min(255, (int) $blue)),
  68. 'alpha' => max(0, min(127, (int) $transparency)),
  69. );
  70. }
  71. /**
  72. * Opens image from file.
  73. * @param string
  74. * @param mixed detected image format
  75. * @return Image
  76. */
  77. public static function fromFile($file, & $format = NULL)
  78. {
  79. if (!extension_loaded('gd')) {
  80. throw new /*\*/Exception("PHP extension GD is not loaded.");
  81. }
  82. $info = @getimagesize($file); // intentionally @
  83. if (self::$useImageMagick && (empty($info) || $info[0] * $info[1] > 2e6)) {
  84. return new ImageMagick($file, $format);
  85. }
  86. switch ($format = $info[2]) {
  87. case self::JPEG:
  88. return new self(imagecreatefromjpeg($file));
  89. case self::PNG:
  90. return new self(imagecreatefrompng($file));
  91. case self::GIF:
  92. return new self(imagecreatefromgif($file));
  93. default:
  94. if (self::$useImageMagick) {
  95. return new ImageMagick($file, $format);
  96. }
  97. throw new /*\*/Exception("Unknown image type or file '$file' not found.");
  98. }
  99. }
  100. /**
  101. * Create a new image from the image stream in the string.
  102. * @param string
  103. * @param mixed detected image format
  104. * @return Image
  105. */
  106. public static function fromString($s, & $format = NULL)
  107. {
  108. if (strncmp($s, "\xff\xd8", 2) === 0) {
  109. $format = self::JPEG;
  110. } elseif (strncmp($s, "\x89PNG", 4) === 0) {
  111. $format = self::PNG;
  112. } elseif (strncmp($s, "GIF", 3) === 0) {
  113. $format = self::GIF;
  114. } else {
  115. $format = NULL;
  116. }
  117. return new self(imagecreatefromstring($s));
  118. }
  119. /**
  120. * Creates blank image.
  121. * @param int
  122. * @param int
  123. * @param array
  124. * @return Image
  125. */
  126. public static function fromBlank($width, $height, $color = NULL)
  127. {
  128. if (!extension_loaded('gd')) {
  129. throw new /*\*/Exception("PHP extension GD is not loaded.");
  130. }
  131. $width = (int) $width;
  132. $height = (int) $height;
  133. if ($width < 1 || $height < 1) {
  134. throw new /*\*/InvalidArgumentException('Image width and height must be greater than zero.');
  135. }
  136. $image = imagecreatetruecolor($width, $height);
  137. if (is_array($color)) {
  138. $color += array('alpha' => 0);
  139. $color = imagecolorallocatealpha($image, $color['red'], $color['green'], $color['blue'], $color['alpha']);
  140. imagealphablending($image, FALSE);
  141. imagefilledrectangle($image, 0, 0, $width - 1, $height - 1, $color);
  142. imagealphablending($image, TRUE);
  143. }
  144. return new self($image);
  145. }
  146. /**
  147. * Wraps GD image.
  148. * @param resource
  149. */
  150. public function __construct($image)
  151. {
  152. $this->setImageResource($image);
  153. }
  154. /**
  155. * Returns image width.
  156. * @return int
  157. */
  158. public function getWidth()
  159. {
  160. return imagesx($this->image);
  161. }
  162. /**
  163. * Returns image height.
  164. * @return int
  165. */
  166. public function getHeight()
  167. {
  168. return imagesy($this->image);
  169. }
  170. /**
  171. * Sets image resource.
  172. * @param resource
  173. * @return Image provides a fluent interface
  174. */
  175. protected function setImageResource($image)
  176. {
  177. if (!is_resource($image) || get_resource_type($image) !== 'gd') {
  178. throw new /*\*/InvalidArgumentException('Image is not valid.');
  179. }
  180. $this->image = $image;
  181. return $this;
  182. }
  183. /**
  184. * Returns image GD resource.
  185. * @return resource
  186. */
  187. public function getImageResource()
  188. {
  189. return $this->image;
  190. }
  191. /**
  192. * Resizes image.
  193. * @param mixed width in pixels or percent
  194. * @param mixed height in pixels or percent
  195. * @param int flags
  196. * @return Image provides a fluent interface
  197. */
  198. public function resize($newWidth, $newHeight, $flags = 0)
  199. {
  200. list($newWidth, $newHeight) = $this->calculateSize($newWidth, $newHeight, $flags);
  201. $newImage = self::fromBlank($newWidth, $newHeight, self::RGB(0, 0, 0, 127))->getImageResource();
  202. imagecopyresampled($newImage, $this->getImageResource(), 0, 0, 0, 0, $newWidth, $newHeight, $this->getWidth(), $this->getHeight());
  203. $this->image = $newImage;
  204. return $this;
  205. }
  206. /**
  207. * Calculates dimensions of resized image.
  208. * @param mixed width in pixels or percent
  209. * @param mixed height in pixels or percent
  210. * @param int flags
  211. * @return array
  212. */
  213. public function calculateSize($newWidth, $newHeight, $flags = 0)
  214. {
  215. $width = $this->getWidth();
  216. $height = $this->getHeight();
  217. if (substr($newWidth, -1) === '%') {
  218. $newWidth = round($width / 100 * $newWidth);
  219. $flags |= self::ENLARGE;
  220. $percents = TRUE;
  221. } else {
  222. $newWidth = (int) $newWidth;
  223. }
  224. if (substr($newHeight, -1) === '%') {
  225. $newHeight = round($height / 100 * $newHeight);
  226. $flags |= empty($percents) ? self::ENLARGE : self::STRETCH;
  227. } else {
  228. $newHeight = (int) $newHeight;
  229. }
  230. if ($flags & self::STRETCH) { // non-proportional
  231. if ($newWidth < 1 || $newHeight < 1) {
  232. throw new /*\*/InvalidArgumentException('For stretching must be both width and height specified.');
  233. }
  234. if (($flags & self::ENLARGE) === 0) {
  235. $newWidth = round($width * min(1, $newWidth / $width));
  236. $newHeight = round($height * min(1, $newHeight / $height));
  237. }
  238. } else { // proportional
  239. if ($newWidth < 1 && $newHeight < 1) {
  240. throw new /*\*/InvalidArgumentException('At least width or height must be specified.');
  241. }
  242. $scale = array();
  243. if ($newWidth > 0) { // fit width
  244. $scale[] = $newWidth / $width;
  245. }
  246. if ($newHeight > 0) { // fit height
  247. $scale[] = $newHeight / $height;
  248. }
  249. if (($flags & self::ENLARGE) === 0) {
  250. $scale[] = 1;
  251. }
  252. $scale = min($scale);
  253. $newWidth = round($width * $scale);
  254. $newHeight = round($height * $scale);
  255. }
  256. return array($newWidth, $newHeight);
  257. }
  258. /**
  259. * Crops image.
  260. * @param int x-coordinate
  261. * @param int y-coordinate
  262. * @param int width
  263. * @param int height
  264. * @return Image provides a fluent interface
  265. */
  266. public function crop($left, $top, $width, $height)
  267. {
  268. $left = max(0, (int) $left);
  269. $top = max(0, (int) $top);
  270. $width = min((int) $width, $this->getWidth() - $left);
  271. $height = min((int) $height, $this->getHeight() - $top);
  272. $newImage = self::fromBlank($width, $height, self::RGB(0, 0, 0, 127))->getImageResource();
  273. imagecopy($newImage, $this->getImageResource(), 0, 0, $left, $top, $width, $height);
  274. $this->image = $newImage;
  275. return $this;
  276. }
  277. /**
  278. * Sharpen image.
  279. * @return Image provides a fluent interface
  280. */
  281. public function sharpen()
  282. {
  283. imageconvolution($this->getImageResource(), array( // my magic numbers ;)
  284. array( -1, -1, -1 ),
  285. array( -1, 24, -1 ),
  286. array( -1, -1, -1 ),
  287. ), 16, 0);
  288. return $this;
  289. }
  290. /**
  291. * Puts another image into this image.
  292. * @param Image
  293. * @param mixed x-coordinate in pixels or percent
  294. * @param mixed y-coordinate in pixels or percent
  295. * @param int opacity 0..100
  296. * @return Image provides a fluent interface
  297. */
  298. public function place(Image $image, $left = 0, $top = 0, $opacity = 100)
  299. {
  300. $opacity = max(0, min(100, (int) $opacity));
  301. if (substr($left, -1) === '%') {
  302. $left = round(($this->getWidth() - $image->getWidth()) / 100 * $left);
  303. }
  304. if (substr($top, -1) === '%') {
  305. $top = round(($this->getHeight() - $image->getHeight()) / 100 * $top);
  306. }
  307. if ($opacity === 100) {
  308. imagecopy($this->getImageResource(), $image->getImageResource(), $left, $top, 0, 0, $image->getWidth(), $image->getHeight());
  309. } elseif ($opacity <> 0) {
  310. imagecopymerge($this->getImageResource(), $image->getImageResource(), $left, $top, 0, 0, $image->getWidth(), $image->getHeight(), $opacity);
  311. }
  312. return $this;
  313. }
  314. /**
  315. * Saves image to the file.
  316. * @param string filename
  317. * @param int quality 0..100 (for JPEG and PNG)
  318. * @param int optional image type
  319. * @return bool TRUE on success or FALSE on failure.
  320. */
  321. public function save($file = NULL, $quality = NULL, $type = NULL)
  322. {
  323. if ($type === NULL) {
  324. switch (strtolower(pathinfo($file, PATHINFO_EXTENSION))) {
  325. case 'jpg':
  326. case 'jpeg':
  327. $type = self::JPEG;
  328. break;
  329. case 'png':
  330. $type = self::PNG;
  331. break;
  332. case 'gif':
  333. $type = self::GIF;
  334. }
  335. }
  336. switch ($type) {
  337. case self::JPEG:
  338. $quality = $quality === NULL ? 85 : max(0, min(100, (int) $quality));
  339. return imagejpeg($this->getImageResource(), $file, $quality);
  340. case self::PNG:
  341. $quality = $quality === NULL ? 9 : max(0, min(9, (int) $quality));
  342. return imagepng($this->getImageResource(), $file, $quality);
  343. case self::GIF:
  344. return imagegif($this->getImageResource(), $file);
  345. default:
  346. throw new /*\*/Exception("Unsupported image type.");
  347. }
  348. }
  349. /**
  350. * Outputs image to string.
  351. * @param int image type
  352. * @param int quality 0..100 (for JPEG and PNG)
  353. * @return string
  354. */
  355. public function toString($type = self::JPEG, $quality = NULL)
  356. {
  357. ob_start();
  358. $this->save(NULL, $quality, $type);
  359. return ob_get_clean();
  360. }
  361. /**
  362. * Outputs image to string.
  363. * @return string
  364. */
  365. public function __toString()
  366. {
  367. try {
  368. return $this->toString();
  369. } catch (/*\*/Exception $e) {
  370. trigger_error($e->getMessage(), E_USER_WARNING);
  371. return '';
  372. }
  373. }
  374. /**
  375. * Outputs image to browser.
  376. * @param int image type
  377. * @param int quality 0..100 (for JPEG and PNG)
  378. * @return bool TRUE on success or FALSE on failure.
  379. */
  380. public function send($type = self::JPEG, $quality = NULL)
  381. {
  382. if ($type !== self::GIF && $type !== self::PNG && $type !== self::JPEG) {
  383. throw new /*\*/Exception("Unsupported image type.");
  384. }
  385. header('Content-Type: ' . image_type_to_mime_type($type));
  386. return $this->save(NULL, $quality, $type);
  387. }
  388. /**
  389. * Call to undefined method.
  390. *
  391. * @param string method name
  392. * @param array arguments
  393. * @return mixed
  394. * @throws \MemberAccessException
  395. */
  396. public function __call($name, $args)
  397. {
  398. $function = 'image' . $name;
  399. if (function_exists($function)) {
  400. foreach ($args as $key => $value) {
  401. if ($value instanceof self) {
  402. $args[$key] = $value->getImageResource();
  403. } elseif (is_array($value) && isset($value['red'])) { // rgb
  404. $args[$key] = imagecolorallocatealpha($this->getImageResource(), $value['red'], $value['green'], $value['blue'], $value['alpha']);
  405. }
  406. }
  407. array_unshift($args, $this->getImageResource());
  408. $res = call_user_func_array($function, $args);
  409. return is_resource($res) ? new self($res) : $res;
  410. }
  411. return parent::__call($name, $args);
  412. }
  413. }