Advertisement
Guest User

Untitled

a guest
Jul 23rd, 2013
201
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 18.63 KB | None | 0 0
  1. <?php
  2.  
  3. /**
  4. * This file is part of the Nette Framework.
  5. *
  6. * Copyright (c) 2004, 2010 David Grudl (http://davidgrudl.com)
  7. *
  8. * This source file is subject to the "Nette license", and/or
  9. * GPL license. For more information please see http://nette.org
  10. */
  11.  
  12. include("Object.php");
  13.  
  14. /**
  15. * Basic manipulation with images.
  16. *
  17. * <code>
  18. * $image = Image::fromFile('nette.jpg');
  19. * $image->resize(150, 100);
  20. * $image->sharpen();
  21. * $image->send();
  22. * </code>
  23. *
  24. * @author David Grudl
  25. *
  26. * @method void alphaBlending(bool $on)
  27. * @method void antialias(bool $on)
  28. * @method void arc($x, $y, $w, $h, $s, $e, $color)
  29. * @method void char($font, $x, $y, $char, $color)
  30. * @method void charUp($font, $x, $y, $char, $color)
  31. * @method int colorAllocate($red, $green, $blue)
  32. * @method int colorAllocateAlpha($red, $green, $blue, $alpha)
  33. * @method int colorAt($x, $y)
  34. * @method int colorClosest($red, $green, $blue)
  35. * @method int colorClosestAlpha($red, $green, $blue, $alpha)
  36. * @method int colorClosestHWB($red, $green, $blue)
  37. * @method void colorDeallocate($color)
  38. * @method int colorExact($red, $green, $blue)
  39. * @method int colorExactAlpha($red, $green, $blue, $alpha)
  40. * @method void colorMatch(Image $image2)
  41. * @method int colorResolve($red, $green, $blue)
  42. * @method int colorResolveAlpha($red, $green, $blue, $alpha)
  43. * @method void colorSet($index, $red, $green, $blue)
  44. * @method array colorsForIndex($index)
  45. * @method int colorsTotal()
  46. * @method int colorTransparent([$color])
  47. * @method void convolution(array $matrix, float $div, float $offset)
  48. * @method void copy(Image $src, $dstX, $dstY, $srcX, $srcY, $srcW, $srcH)
  49. * @method void copyMerge(Image $src, $dstX, $dstY, $srcX, $srcY, $srcW, $srcH, $opacity)
  50. * @method void copyMergeGray(Image $src, $dstX, $dstY, $srcX, $srcY, $srcW, $srcH, $opacity)
  51. * @method void copyResampled(Image $src, $dstX, $dstY, $srcX, $srcY, $dstW, $dstH, $srcW, $srcH)
  52. * @method void copyResized(Image $src, $dstX, $dstY, $srcX, $srcY, $dstW, $dstH, $srcW, $srcH)
  53. * @method void dashedLine($x1, $y1, $x2, $y2, $color)
  54. * @method void ellipse($cx, $cy, $w, $h, $color)
  55. * @method void fill($x, $y, $color)
  56. * @method void filledArc($cx, $cy, $w, $h, $s, $e, $color, $style)
  57. * @method void filledEllipse($cx, $cy, $w, $h, $color)
  58. * @method void filledPolygon(array $points, $numPoints, $color)
  59. * @method void filledRectangle($x1, $y1, $x2, $y2, $color)
  60. * @method void fillToBorder($x, $y, $border, $color)
  61. * @method void filter($filtertype [, ...])
  62. * @method int fontHeight($font)
  63. * @method int fontWidth($font)
  64. * @method array ftBBox($size, $angle, string $fontFile, string $text [, array $extrainfo])
  65. * @method array ftText($size, $angle, $x, $y, $col, string $fontFile, string $text [, array $extrainfo])
  66. * @method void gammaCorrect(float $inputgamma, float $outputgamma)
  67. * @method int interlace([$interlace])
  68. * @method bool isTrueColor()
  69. * @method void layerEffect($effect)
  70. * @method void line($x1, $y1, $x2, $y2, $color)
  71. * @method int loadFont(string $file)
  72. * @method void paletteCopy(Image $source)
  73. * @method void polygon(array $points, $numPoints, $color)
  74. * @method array psBBox(string $text, $font, $size [, $space] [, $tightness] [, float $angle])
  75. * @method void psEncodeFont($fontIndex, string $encodingfile)
  76. * @method void psExtendFont($fontIndex, float $extend)
  77. * @method void psFreeFont($fontindex)
  78. * @method resource psLoadFont(string $filename)
  79. * @method void psSlantFont($fontIndex, float $slant)
  80. * @method array psText(string $text, $font, $size, $color, $backgroundColor, $x, $y [, $space] [, $tightness] [, float $angle] [, $antialiasSteps])
  81. * @method void rectangle($x1, $y1, $x2, $y2, $col)
  82. * @method Image rotate(float $angle, $backgroundColor)
  83. * @method void saveAlpha(bool $saveflag)
  84. * @method void setBrush(Image $brush)
  85. * @method void setPixel($x, $y, $color)
  86. * @method void setStyle(array $style)
  87. * @method void setThickness($thickness)
  88. * @method void setTile(Image $tile)
  89. * @method void string($font, $x, $y, string $s, $col)
  90. * @method void stringUp($font, $x, $y, string $s, $col)
  91. * @method void trueColorToPalette(bool $dither, $ncolors)
  92. * @method array ttfBBox($size, $angle, string $fontfile, string $text)
  93. * @method array ttfText($size, $angle, $x, $y, $color, string $fontfile, string $text)
  94. * @method int types()
  95. * @property-read int $width
  96. * @property-read int $height
  97. * @property-read resource $imageResource
  98. */
  99. class Image extends Object
  100. {
  101. /** {@link resize()} only shrinks images */
  102. const SHRINK_ONLY = 1;
  103.  
  104. /** {@link resize()} will ignore aspect ratio */
  105. const STRETCH = 2;
  106.  
  107. /** {@link resize()} fits in given area so its dimensions are less than or equal to the required dimensions */
  108. const FIT = 0;
  109.  
  110. /** {@link resize()} fills given area so its dimensions are greater than or equal to the required dimensions */
  111. const FILL = 4;
  112.  
  113. /** {@link resize()} fills given area exactly */
  114. const EXACT = 8;
  115.  
  116. /** @int image types {@link send()} */
  117. const JPEG = IMAGETYPE_JPEG,
  118. PNG = IMAGETYPE_PNG,
  119. GIF = IMAGETYPE_GIF;
  120.  
  121. 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;";
  122.  
  123. /** @deprecated */
  124. const ENLARGE = 0;
  125.  
  126. /** @var resource */
  127. private $image;
  128.  
  129.  
  130.  
  131. /**
  132. * Returns RGB color.
  133. * @param int red 0..255
  134. * @param int green 0..255
  135. * @param int blue 0..255
  136. * @param int transparency 0..127
  137. * @return array
  138. */
  139. public static function rgb($red, $green, $blue, $transparency = 0)
  140. {
  141. return array(
  142. 'red' => max(0, min(255, (int) $red)),
  143. 'green' =>
  144. (0, min(255, (int) $green)),
  145. 'blue' =>
  146. (0, min(255, (int) $blue)),
  147. 'alpha' => max(0, min(127, (int) $transparency)),
  148. );
  149. }
  150.  
  151.  
  152.  
  153. /**
  154. * Opens image from file.
  155. * @param string
  156. * @param mixed detected image format
  157. * @return Image
  158. */
  159. public static function fromFile($file, & $format = NULL)
  160. {
  161. if (!extension_loaded('gd')) {
  162. throw new NotSupportedException("PHP extension GD is not loaded.");
  163. }
  164.  
  165. $info = @getimagesize($file); // @ - files smaller than 12 bytes causes read error
  166.  
  167. switch ($format = $info[2]) {
  168. case self::JPEG:
  169. return new static(imagecreatefromjpeg($file));
  170.  
  171. case self::PNG:
  172. return new static(imagecreatefrompng($file));
  173.  
  174. case self::GIF:
  175. return new static(imagecreatefromgif($file));
  176.  
  177. default:
  178. throw new UnknownImageFileException("Unknown image type or file '$file' not found.");
  179. }
  180. }
  181.  
  182.  
  183.  
  184. /**
  185. * Get format from the image stream in the string.
  186. * @param string
  187. * @return mixed detected image format
  188. */
  189. public static function getFormatFromString($s)
  190. {
  191. $types = array('image/jpeg' => self::JPEG, 'image/gif' => self::GIF, 'image/png' => self::PNG);
  192. $type = Utils\MimeTypeDetector::fromString($s);
  193. return isset($types[$type]) ? $types[$type] : NULL;
  194. }
  195.  
  196.  
  197.  
  198. /**
  199. * Create a new image from the image stream in the string.
  200. * @param string
  201. * @param mixed detected image format
  202. * @return Image
  203. */
  204. public static function fromString($s, & $format = NULL)
  205. {
  206. if (!extension_loaded('gd')) {
  207. throw new NotSupportedException("PHP extension GD is not loaded.");
  208. }
  209.  
  210. $format = static::getFormatFromString($s);
  211.  
  212. return new static(imagecreatefromstring($s));
  213. }
  214.  
  215.  
  216.  
  217. /**
  218. * Creates blank image.
  219. * @param int
  220. * @param int
  221. * @param array
  222. * @return Image
  223. */
  224. public static function fromBlank($width, $height, $color = NULL)
  225. {
  226. if (!extension_loaded('gd')) {
  227. throw new NotSupportedException("PHP extension GD is not loaded.");
  228. }
  229.  
  230. $width = (int) $width;
  231. $height = (int) $height;
  232. if ($width < 1 || $height < 1) {
  233. throw new InvalidArgumentException('Image width and height must be greater than zero.');
  234. }
  235.  
  236. $image = imagecreatetruecolor($width, $height);
  237. if (is_array($color)) {
  238. $color += array('alpha' => 0);
  239. $color = imagecolorallocatealpha($image, $color['red'], $color['green'], $color['blue'], $color['alpha']);
  240. imagealphablending($image, FALSE);
  241. imagefilledrectangle($image, 0, 0, $width - 1, $height - 1, $color);
  242. imagealphablending($image, TRUE);
  243. }
  244. return new static($image);
  245. }
  246.  
  247.  
  248.  
  249. /**
  250. * Wraps GD image.
  251. * @param resource
  252. */
  253. public function __construct($image)
  254. {
  255. $this->setImageResource($image);
  256. imagesavealpha($image, TRUE);
  257. }
  258.  
  259.  
  260.  
  261. /**
  262. * Returns image width.
  263. * @return int
  264. */
  265. public function getWidth()
  266. {
  267. return imagesx($this->image);
  268. }
  269.  
  270.  
  271.  
  272. /**
  273. * Returns image height.
  274. * @return int
  275. */
  276. public function getHeight()
  277. {
  278. return imagesy($this->image);
  279. }
  280.  
  281.  
  282.  
  283. /**
  284. * Sets image resource.
  285. * @param resource
  286. * @return Image provides a fluent interface
  287. */
  288. protected function setImageResource($image)
  289. {
  290. if (!is_resource($image) || get_resource_type($image) !== 'gd') {
  291. throw new InvalidArgumentException('Image is not valid.');
  292. }
  293. $this->image = $image;
  294. return $this;
  295. }
  296.  
  297.  
  298.  
  299. /**
  300. * Returns image GD resource.
  301. * @return resource
  302. */
  303. public function getImageResource()
  304. {
  305. return $this->image;
  306. }
  307.  
  308.  
  309.  
  310. /**
  311. * Resizes image.
  312. * @param mixed width in pixels or percent
  313. * @param mixed height in pixels or percent
  314. * @param int flags
  315. * @return Image provides a fluent interface
  316. */
  317. public function resize($width, $height, $flags = self::FIT)
  318. {
  319. if ($flags & self::EXACT) {
  320. return $this->resize($width, $height, self::FILL)->crop('50%', '50%', $width, $height);
  321. }
  322.  
  323. list($newWidth, $newHeight) = static::calculateSize($this->getWidth(), $this->getHeight(), $width, $height, $flags);
  324.  
  325. if ($newWidth !== $this->getWidth() || $newHeight !== $this->getHeight()) { // resize
  326. $newImage = static::fromBlank($newWidth, $newHeight, self::RGB(0, 0, 0, 127))->getImageResource();
  327. imagecopyresampled(
  328. $newImage, $this->getImageResource(),
  329. 0, 0, 0, 0,
  330. $newWidth, $newHeight, $this->getWidth(), $this->getHeight()
  331. );
  332. $this->image = $newImage;
  333. }
  334.  
  335. if ($width < 0 || $height < 0) { // flip is processed in two steps for better quality
  336. $newImage = static::fromBlank($newWidth, $newHeight, self::RGB(0, 0, 0, 127))->getImageResource();
  337. imagecopyresampled(
  338. $newImage, $this->getImageResource(),
  339. 0, 0, $width < 0 ? $newWidth - 1 : 0, $height < 0 ? $newHeight - 1 : 0,
  340. $newWidth, $newHeight, $width < 0 ? -$newWidth : $newWidth, $height < 0 ? -$newHeight : $newHeight
  341. );
  342. $this->image = $newImage;
  343. }
  344. return $this;
  345. }
  346.  
  347.  
  348.  
  349. /**
  350. * Calculates dimensions of resized image.
  351. * @param mixed source width
  352. * @param mixed source height
  353. * @param mixed width in pixels or percent
  354. * @param mixed height in pixels or percent
  355. * @param int flags
  356. * @return array
  357. */
  358. public static function calculateSize($srcWidth, $srcHeight, $newWidth, $newHeight, $flags = self::FIT)
  359. {
  360. if (substr($newWidth, -1) === '%') {
  361. $newWidth = round($srcWidth / 100 * abs($newWidth));
  362. $percents = TRUE;
  363. } else {
  364. $newWidth = (int) abs($newWidth);
  365. }
  366.  
  367. if (substr($newHeight, -1) === '%') {
  368. $newHeight = round($srcHeight / 100 * abs($newHeight));
  369. $flags |= empty($percents) ? 0 : self::STRETCH;
  370. } else {
  371. $newHeight = (int) abs($newHeight);
  372. }
  373.  
  374. if ($flags & self::STRETCH) { // non-proportional
  375. if (empty($newWidth) || empty($newHeight)) {
  376. throw new InvalidArgumentException('For stretching must be both width and height specified.');
  377. }
  378.  
  379. if ($flags & self::SHRINK_ONLY) {
  380. $newWidth = round($srcWidth * min(1, $newWidth / $srcWidth));
  381. $newHeight = round($srcHeight * min(1, $newHeight / $srcHeight));
  382. }
  383.  
  384. } else { // proportional
  385. if (empty($newWidth) && empty($newHeight)) {
  386. throw new InvalidArgumentException('At least width or height must be specified.');
  387. }
  388.  
  389. $scale = array();
  390. if ($newWidth > 0) { // fit width
  391. $scale[] = $newWidth / $srcWidth;
  392. }
  393.  
  394. if ($newHeight > 0) { // fit height
  395. $scale[] = $newHeight / $srcHeight;
  396. }
  397.  
  398. if ($flags & self::FILL) {
  399. $scale = array(max($scale));
  400. }
  401.  
  402. if ($flags & self::SHRINK_ONLY) {
  403. $scale[] = 1;
  404. }
  405.  
  406. $scale = min($scale);
  407. $newWidth = round($srcWidth * $scale);
  408. $newHeight = round($srcHeight * $scale);
  409. }
  410.  
  411. return array(max((int) $newWidth, 1), max((int) $newHeight, 1));
  412. }
  413.  
  414.  
  415.  
  416. /**
  417. * Crops image.
  418. * @param mixed x-offset in pixels or percent
  419. * @param mixed y-offset in pixels or percent
  420. * @param mixed width in pixels or percent
  421. * @param mixed height in pixels or percent
  422. * @return Image provides a fluent interface
  423. */
  424. public function crop($left, $top, $width, $height)
  425. {
  426. list($left, $top, $width, $height) = static::calculateCutout($this->getWidth(), $this->getHeight(), $left, $top, $width, $height);
  427. $newImage = static::fromBlank($width, $height, self::RGB(0, 0, 0, 127))->getImageResource();
  428. imagecopy($newImage, $this->getImageResource(), 0, 0, $left, $top, $width, $height);
  429. $this->image = $newImage;
  430. return $this;
  431. }
  432.  
  433.  
  434.  
  435. /**
  436. * Calculates dimensions of cutout in image.
  437. * @param mixed source width
  438. * @param mixed source height
  439. * @param mixed x-offset in pixels or percent
  440. * @param mixed y-offset in pixels or percent
  441. * @param mixed width in pixels or percent
  442. * @param mixed height in pixels or percent
  443. * @return array
  444. */
  445. public static function calculateCutout($srcWidth, $srcHeight, $left, $top, $newWidth, $newHeight)
  446. {
  447. if (substr($newWidth, -1) === '%') {
  448. $newWidth = round($srcWidth / 100 * $newWidth);
  449. }
  450. if (substr($newHeight, -1) === '%') {
  451. $newHeight = round($srcHeight / 100 * $newHeight);
  452. }
  453. if (substr($left, -1) === '%') {
  454. $left = round(($srcWidth - $newWidth) / 100 * $left);
  455. }
  456. if (substr($top, -1) === '%') {
  457. $top = round(($srcHeight - $newHeight) / 100 * $top);
  458. }
  459. if ($left < 0) {
  460. $newWidth += $left; $left = 0;
  461. }
  462. if ($top < 0) {
  463. $newHeight += $top; $top = 0;
  464. }
  465. $newWidth = min((int) $newWidth, $srcWidth - $left);
  466. $newHeight = min((int) $newHeight, $srcHeight - $top);
  467. return array($left, $top, $newWidth, $newHeight);
  468. }
  469.  
  470.  
  471.  
  472. /**
  473. * Sharpen image.
  474. * @return Image provides a fluent interface
  475. */
  476. public function sharpen()
  477. {
  478. imageconvolution($this->getImageResource(), array( // my magic numbers ;)
  479. array( -1, -1, -1 ),
  480. array( -1, 24, -1 ),
  481. array( -1, -1, -1 ),
  482. ), 16, 0);
  483. return $this;
  484. }
  485.  
  486.  
  487.  
  488. /**
  489. * Puts another image into this image.
  490. * @param Image
  491. * @param mixed x-coordinate in pixels or percent
  492. * @param mixed y-coordinate in pixels or percent
  493. * @param int opacity 0..100
  494. * @return Image provides a fluent interface
  495. */
  496. public function place(Image $image, $left = 0, $top = 0, $opacity = 100)
  497. {
  498. $opacity = max(0, min(100, (int) $opacity));
  499.  
  500. if (substr($left, -1) === '%') {
  501. $left = round(($this->getWidth() - $image->getWidth()) / 100 * $left);
  502. }
  503.  
  504. if (substr($top, -1) === '%') {
  505. $top = round(($this->getHeight() - $image->getHeight()) / 100 * $top);
  506. }
  507.  
  508. if ($opacity === 100) {
  509. imagecopy(
  510. $this->getImageResource(), $image->getImageResource(),
  511. $left, $top, 0, 0, $image->getWidth(), $image->getHeight()
  512. );
  513.  
  514. } elseif ($opacity <> 0) {
  515. imagecopymerge(
  516. $this->getImageResource(), $image->getImageResource(),
  517. $left, $top, 0, 0, $image->getWidth(), $image->getHeight(),
  518. $opacity
  519. );
  520. }
  521. return $this;
  522. }
  523.  
  524.  
  525.  
  526. /**
  527. * Saves image to the file.
  528. * @param string filename
  529. * @param int quality 0..100 (for JPEG and PNG)
  530. * @param int optional image type
  531. * @return bool TRUE on success or FALSE on failure.
  532. */
  533. public function save($file = NULL, $quality = NULL, $type = NULL)
  534. {
  535. if ($type === NULL) {
  536. switch (strtolower(pathinfo($file, PATHINFO_EXTENSION))) {
  537. case 'jpg':
  538. case 'jpeg':
  539. $type = self::JPEG;
  540. break;
  541. case 'png':
  542. $type = self::PNG;
  543. break;
  544. case 'gif':
  545. $type = self::GIF;
  546. }
  547. }
  548.  
  549. switch ($type) {
  550. case self::JPEG:
  551. $quality = $quality === NULL ? 85 : max(0, min(100, (int) $quality));
  552. return imagejpeg($this->getImageResource(), $file, $quality);
  553.  
  554. case self::PNG:
  555. $quality = $quality === NULL ? 9 : max(0, min(9, (int) $quality));
  556. return imagepng($this->getImageResource(), $file, $quality);
  557.  
  558. case self::GIF:
  559. return $file === NULL ? imagegif($this->getImageResource()) : imagegif($this->getImageResource(), $file); // PHP bug #44591
  560.  
  561. default:
  562. throw new InvalidArgumentException("Unsupported image type.");
  563. }
  564. }
  565.  
  566.  
  567.  
  568. /**
  569. * Outputs image to string.
  570. * @param int image type
  571. * @param int quality 0..100 (for JPEG and PNG)
  572. * @return string
  573. */
  574. public function toString($type = self::JPEG, $quality = NULL)
  575. {
  576. ob_start();
  577. $this->save(NULL, $quality, $type);
  578. return ob_get_clean();
  579. }
  580.  
  581.  
  582.  
  583. /**
  584. * Outputs image to string.
  585. * @return string
  586. */
  587. public function __toString()
  588. {
  589. try {
  590. return $this->toString();
  591.  
  592. } catch (\Exception $e) {
  593. trigger_error("Exception in " . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR);
  594. }
  595. }
  596.  
  597.  
  598.  
  599. /**
  600. * Outputs image to browser.
  601. * @param int image type
  602. * @param int quality 0..100 (for JPEG and PNG)
  603. * @return bool TRUE on success or FALSE on failure.
  604. */
  605. public function send($type = self::JPEG, $quality = NULL)
  606. {
  607. if ($type !== self::GIF && $type !== self::PNG && $type !== self::JPEG) {
  608. throw new InvalidArgumentException("Unsupported image type.");
  609. }
  610. header('Content-Type: ' . image_type_to_mime_type($type));
  611. return $this->save(NULL, $quality, $type);
  612. }
  613.  
  614.  
  615.  
  616. /**
  617. * Call to undefined method.
  618. *
  619. * @param string method name
  620. * @param array arguments
  621. * @return mixed
  622. * @throws MemberAccessException
  623. */
  624. public function __call($name, $args)
  625. {
  626. $function = 'image' . $name;
  627. if (function_exists($function)) {
  628. foreach ($args as $key => $value) {
  629. if ($value instanceof self) {
  630. $args[$key] = $value->getImageResource();
  631.  
  632. } elseif (is_array($value) && isset($value['red'])) { // rgb
  633. $args[$key] = imagecolorallocatealpha(
  634. $this->getImageResource(),
  635. $value['red'], $value['green'], $value['blue'], $value['alpha']
  636. );
  637. }
  638. }
  639. array_unshift($args, $this->getImageResource());
  640.  
  641. $res = call_user_func_array($function, $args);
  642. return is_resource($res) && get_resource_type($res) === 'gd' ? $this->setImageResource($res) : $res;
  643. }
  644.  
  645. return parent::__call($name, $args);
  646. }
  647.  
  648. }
  649.  
  650.  
  651.  
  652. /**
  653. * The exception that indicates invalid image file.
  654. */
  655. class UnknownImageFileException extends \Exception
  656. {
  657. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement