gd.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551
  1. <?php
  2. /**
  3. * Part of the Fuel framework.
  4. *
  5. * @package Fuel
  6. * @version 1.5
  7. * @author Fuel Development Team
  8. * @license MIT License
  9. * @copyright 2010 - 2013 Fuel Development Team
  10. * @link http://fuelphp.com
  11. */
  12. namespace Fuel\Core;
  13. class Image_Gd extends \Image_Driver
  14. {
  15. protected $image_data = null;
  16. protected $accepted_extensions = array('png', 'gif', 'jpg', 'jpeg');
  17. protected $gdresizefunc = "imagecopyresampled";
  18. public function load($filename, $return_data = false, $force_extension = false)
  19. {
  20. extract(parent::load($filename, $return_data, $force_extension));
  21. $return = false;
  22. $image_extension == 'jpg' and $image_extension = 'jpeg';
  23. if ( ! $return_data)
  24. {
  25. $this->image_data !== null and imagedestroy($this->image_data);
  26. $this->image_data = null;
  27. }
  28. // Check if the function exists
  29. if (function_exists('imagecreatefrom'.$image_extension))
  30. {
  31. // Create a new transparent image.
  32. $sizes = $this->sizes($image_fullpath);
  33. $tmpImage = call_user_func('imagecreatefrom'.$image_extension, $image_fullpath);
  34. $image = $this->create_transparent_image($sizes->width, $sizes->height, $tmpImage);
  35. if ( ! $return_data)
  36. {
  37. $this->image_data = $image;
  38. $return = true;
  39. }
  40. else
  41. {
  42. $return = $image;
  43. }
  44. $this->debug('', "<strong>Loaded</strong> <code>".$image_fullpath."</code> with size of ".$sizes->width."x".$sizes->height);
  45. }
  46. else
  47. {
  48. throw new \RuntimeException("Function imagecreatefrom".$image_extension."() does not exist (Missing GD?)");
  49. }
  50. return $return_data ? $return : $this;
  51. }
  52. protected function _crop($x1, $y1, $x2, $y2)
  53. {
  54. extract(parent::_crop($x1, $y1, $x2, $y2));
  55. $width = $x2 - $x1;
  56. $height = $y2 - $y1;
  57. $this->debug("Cropping image ".$width."x".$height."+$x1+$y1 based on coords ($x1, $y1), ($x2, $y2)");
  58. $image = $this->create_transparent_image($width, $height);
  59. imagecopy($image, $this->image_data, 0, 0, $x1, $y1, $width, $height);
  60. $this->image_data = $image;
  61. }
  62. protected function _resize($width, $height = null, $keepar = true, $pad = true)
  63. {
  64. extract(parent::_resize($width, $height, $keepar, $pad));
  65. $sizes = $this->sizes();
  66. $this->debug("Resizing image to $width, $height with" . ($keepar ? '' : 'out') . " keeping AR and with" . ($pad ? '' : 'out') . " padding.");
  67. // Add the original image.
  68. $image = $this->create_transparent_image($cwidth, $cheight);
  69. call_user_func($this->gdresizefunc, $image, $this->image_data, $x, $y, 0, 0, $width, $height, $sizes->width, $sizes->height);
  70. $this->image_data = $image;
  71. }
  72. protected function _rotate($degrees)
  73. {
  74. extract(parent::_rotate($degrees));
  75. $degrees = 360 - $degrees;
  76. $bgcolor = $this->config['bgcolor'] !== null ? $this->config['bgcolor'] : '#000';
  77. $color = $this->create_color($this->image_data, $bgcolor, 100);
  78. $this->image_data = imagerotate($this->image_data, $degrees, $color, false);
  79. }
  80. protected function _watermark($filename, $position, $padding = 5)
  81. {
  82. $values = parent::_watermark($filename, $position, $padding);
  83. if ($values == false)
  84. {
  85. throw new \InvalidArgumentException("Watermark image not found or invalid filetype.");
  86. }
  87. else
  88. {
  89. extract($values);
  90. $wsizes = $this->sizes($filename);
  91. $sizes = $this->sizes();
  92. // Load the watermark preserving transparency
  93. $watermark = $this->load($filename, true);
  94. // Below is to prevent glitch in GD with negative $x coords
  95. if ($x < 0 || $y < 0)
  96. {
  97. $this->debug("Modifying watermark to remove negative coords.");
  98. // Generate a new width and height for the watermark.
  99. $newwidth = ($x < 0 ? $wsizes->width + $x : $wsizes->width);
  100. $newheight = ($y < 0 ? $wsizes->height + $y : $wsizes->height);
  101. // Create a transparent image the size of the new watermark.
  102. $tmpwatermark = $this->create_transparent_image($newwidth, $newheight);
  103. $this->debug("New size is $newwidth x $newheight and coords are $x , $y");
  104. // Call the resize function based on image format
  105. imagecopy(
  106. $tmpwatermark, $watermark, // Copy the new image into the tmp watermark
  107. 0, 0,
  108. $x < 0 ? abs($x) : 0,
  109. $y < 0 ? abs($y) : 0,
  110. $newwidth, $newheight
  111. );
  112. // Set the variables for the image_merge
  113. $watermark = $tmpwatermark;
  114. $x = $x < 0 ? 0 : $x;
  115. $y = $y < 0 ? 0 : $y;
  116. }
  117. // Used as a workaround for lack of alpha support in imagecopymerge.
  118. $this->debug("Coords for watermark are $x , $y");
  119. $this->image_merge($this->image_data, $watermark, $x, $y, $this->config['watermark_alpha']);
  120. }
  121. }
  122. protected function _flip($mode)
  123. {
  124. $sizes = (array)$this->sizes();
  125. $source = array_merge($sizes, array('x' => 0, 'y' => 0));
  126. switch ($mode)
  127. {
  128. case 'vertical':
  129. $source['y'] = $sizes['height'] - 1;
  130. $source['height'] = -$sizes['height'];
  131. break;
  132. case 'horizontal':
  133. $source['x'] = $sizes['width'] - 1;
  134. $source['width'] = -$sizes['width'];
  135. break;
  136. case 'both':
  137. $source['y'] = $sizes['height'] - 1;
  138. $source['x'] = $sizes['width'] - 1;
  139. $source['height'] = -$sizes['height'];
  140. $source['width'] = -$sizes['width'];
  141. break;
  142. default: return false;
  143. }
  144. $image = imagecreatetruecolor($sizes['width'], $sizes['height']);
  145. imagecopyresampled(
  146. $image,
  147. $this->image_data,
  148. 0,
  149. 0,
  150. $source['x'],
  151. $source['y'],
  152. $sizes['width'],
  153. $sizes['height'],
  154. $source['width'],
  155. $source['height']
  156. );
  157. $this->image_data = $image;
  158. }
  159. protected function _border($size, $color = null)
  160. {
  161. extract(parent::_border($size, $color));
  162. $sizes = $this->sizes();
  163. $image = $this->create_transparent_image($sizes->width + ($size * 2), $sizes->height + ($size * 2));
  164. $color = $this->create_color($image, $color, 100);
  165. $this->image_merge($image, $this->image_data, $size, $size, 100);
  166. for ($s = 0; $s < $size; $s++)
  167. {
  168. imagerectangle($image, $s, $s, $sizes->width + ($size * 2) - $s - 1, $sizes->height + ($size * 2) - $s - 1, $color);
  169. }
  170. $this->image_data = $image;
  171. }
  172. protected function _mask($maskimage)
  173. {
  174. extract(parent::_mask($maskimage));
  175. // Get size and width of image
  176. $sizes = $this->sizes();
  177. $masksizes = $this->sizes($maskimage);
  178. // Create new blank image
  179. $image = $this->create_transparent_image($sizes->width, $sizes->height);
  180. if (is_resource($maskimage))
  181. {
  182. $maskim = $maskimage;
  183. }
  184. else
  185. {
  186. $maskim = $this->load($maskimage, true);
  187. }
  188. $masksizes->width > $sizes->width and $masksizes->width = $sizes->width;
  189. $masksizes->height > $sizes->width and $masksizes->height = $sizes->height;
  190. // Loop through all the pixels
  191. for ($x = 0; $x < $masksizes->width; $x++)
  192. {
  193. for ($y = 0; $y < $masksizes->height; $y++)
  194. {
  195. $maskcolor = imagecolorat($maskim, $x, $y);
  196. $maskcolor = imagecolorsforindex($maskim, $maskcolor);
  197. $maskalpha = 127 - floor(($maskcolor['red'] + $maskcolor['green'] + $maskcolor['blue']) / 6);
  198. if ($maskalpha == 127)
  199. {
  200. continue;
  201. }
  202. if ($maskalpha == 0)
  203. {
  204. $ourcolor = array(
  205. 'red' => 0,
  206. 'green' => 0,
  207. 'blue' => 0,
  208. 'alpha' => 0
  209. );
  210. }
  211. else
  212. {
  213. $ourcolor = imagecolorat($this->image_data, $x, $y);
  214. $ourcolor = imagecolorsforindex($this->image_data, $ourcolor);
  215. }
  216. $ouralpha = 127 - $ourcolor['alpha'];
  217. if ($ouralpha == 0)
  218. {
  219. continue;
  220. }
  221. $newalpha = floor($ouralpha - (($maskalpha / 127) * $ouralpha));
  222. $newcolor = imagecolorallocatealpha($image, $ourcolor['red'], $ourcolor['green'], $ourcolor['blue'], 127 - $newalpha);
  223. imagesetpixel($image, $x, $y, $newcolor);
  224. }
  225. }
  226. $this->image_data = $image;
  227. }
  228. protected function _rounded($radius, $sides, $antialias)
  229. {
  230. extract(parent::_rounded($radius, $sides, $antialias));
  231. $tl and $this->round_corner($this->image_data, $radius, $antialias, true, true);
  232. $tr and $this->round_corner($this->image_data, $radius, $antialias, true, false);
  233. $bl and $this->round_corner($this->image_data, $radius, $antialias, false, true);
  234. $br and $this->round_corner($this->image_data, $radius, $antialias, false, false);
  235. }
  236. protected function _grayscale()
  237. {
  238. $sizes = $this->sizes();
  239. // Create the 256 color palette
  240. $bwpalette = array();
  241. for ($i = 0; $i < 256; $i++)
  242. {
  243. $bwpalette[$i] = imagecolorallocate($this->image_data, $i, $i, $i);
  244. }
  245. for ($x = 0; $x < $sizes->width; $x++)
  246. {
  247. for ($y = 0; $y < $sizes->height; $y++)
  248. {
  249. $color = imagecolorat($this->image_data, $x, $y);
  250. $red = ($color >> 16) & 0xFF;
  251. $green = ($color >> 8) & 0xFF;
  252. $blue = $color & 0xFF;
  253. // If its black or white, theres no use in setting the pixel
  254. if (($red == 0 && $green == 0 && $blue == 0) || ($red == 255 && $green == 255 && $blue == 255))
  255. {
  256. continue;
  257. }
  258. // Now set the color
  259. $shade = (($red*0.299)+($green*0.587)+($blue*0.114));
  260. imagesetpixel($this->image_data, $x, $y, $bwpalette[$shade]);
  261. }
  262. }
  263. }
  264. public function sizes($filename = null)
  265. {
  266. if (empty($filename) && !empty($this->image_fullpath))
  267. {
  268. $filename = $this->image_fullpath;
  269. }
  270. if ($filename == $this->image_fullpath && is_resource($this->image_data))
  271. {
  272. $width = imagesx($this->image_data);
  273. $height = imagesy($this->image_data);
  274. }
  275. else if (is_resource($filename))
  276. {
  277. $width = imagesx($filename);
  278. $height = imagesy($filename);
  279. }
  280. else
  281. {
  282. list($width, $height) = getimagesize($filename);
  283. }
  284. return (object) array('width' => $width, 'height' => $height);
  285. }
  286. public function save($filename, $permissions = null)
  287. {
  288. extract(parent::save($filename, $permissions));
  289. $this->run_queue();
  290. $this->add_background();
  291. $vars = array(&$this->image_data, $filename);
  292. $filetype = $this->image_extension;
  293. if ($filetype == 'jpg' || $filetype == 'jpeg')
  294. {
  295. $vars[] = $this->config['quality'];
  296. $filetype = 'jpeg';
  297. }
  298. elseif ($filetype == 'png')
  299. {
  300. $vars[] = floor(($this->config['quality'] / 100) * 9);
  301. }
  302. call_user_func_array('image'.$filetype, $vars);
  303. if ($this->config['persistence'] === false)
  304. {
  305. $this->reload();
  306. }
  307. return $this;
  308. }
  309. public function output($filetype = null)
  310. {
  311. $this->gdresizefunc = ($filetype == 'gif') ? 'imagecopyresized': $this->gdresizefunc = 'imagecopyresampled';
  312. extract(parent::output($filetype));
  313. $this->run_queue();
  314. $this->add_background();
  315. $vars = array($this->image_data, null);
  316. if ($filetype == 'jpg' || $filetype == 'jpeg')
  317. {
  318. $vars[] = $this->config['quality'];
  319. $filetype = 'jpeg';
  320. }
  321. elseif ($filetype == 'png')
  322. {
  323. $vars[] = floor(($this->config['quality'] / 100) * 9);
  324. }
  325. call_user_func_array('image'.$filetype, $vars);
  326. if ($this->config['persistence'] === false)
  327. {
  328. $this->reload();
  329. }
  330. return $this;
  331. }
  332. /**
  333. * Creates a new color usable by GD.
  334. *
  335. * @param resource $image The image to create the color from
  336. * @param string $hex The hex code of the color
  337. * @param integer $alpha The alpha of the color, 0 (trans) to 100 (opaque)
  338. * @return integer The color
  339. */
  340. protected function create_color(&$image, $hex, $alpha)
  341. {
  342. extract($this->create_hex_color($hex));
  343. // Handling alpha is different among drivers
  344. if ($hex == null)
  345. {
  346. $alpha = 127;
  347. }
  348. else
  349. {
  350. $alpha = 127 - floor($alpha * 1.27);
  351. }
  352. // Check if the transparency is allowed
  353. return imagecolorallocatealpha($image, $red, $green, $blue, $alpha);
  354. }
  355. protected function add_background()
  356. {
  357. if ($this->config['bgcolor'] != null || ($this->new_extension == 'jpg' || $this->new_extension == 'jpeg'))
  358. {
  359. $bgcolor = $this->config['bgcolor'] == null ? '#000' : $this->config['bgcolor'];
  360. $this->debug("Adding background color $bgcolor");
  361. $sizes = $this->sizes();
  362. $bgimg = $this->create_transparent_image($sizes->width, $sizes->height);
  363. $color = $this->create_color($bgimg, $bgcolor, 100);
  364. imagefill($bgimg, 0, 0, $color);
  365. $this->image_merge($bgimg, $this->image_data, 0, 0, 100);
  366. $this->image_data = $bgimg;
  367. }
  368. }
  369. /**
  370. * Creates a new transparent image.
  371. *
  372. * @param integer $width The width of the image.
  373. * @param integer $height The height of the image.
  374. * @param resource $resource Optionally add an image to the new transparent image.
  375. * @return resource Returns the image in resource form.
  376. */
  377. private function create_transparent_image($width, $height, $resource = null)
  378. {
  379. $image = imagecreatetruecolor($width, $height);
  380. $color = $this->create_color($image, null, 0);
  381. imagesavealpha($image, true);
  382. if ($this->image_extension == 'gif' || $this->image_extension == 'png')
  383. {
  384. // Get the current transparent color if possible...
  385. $transcolor = imagecolortransparent($image);
  386. if ($transcolor > 0)
  387. {
  388. $color = $transcolor;
  389. }
  390. imagecolortransparent($image, $color);
  391. }
  392. // Set the blending mode to false, add the bgcolor, then switch it back.
  393. imagealphablending($image, false);
  394. imagefilledrectangle($image, 0, 0, $width, $height, $color);
  395. imagealphablending($image, true);
  396. if (is_resource($resource))
  397. {
  398. imagecopy($image, $resource, 0, 0, 0, 0, $width, $height);
  399. }
  400. return $image;
  401. }
  402. /**
  403. * Creates a rounded corner on the image.
  404. *
  405. * @param resource $image
  406. * @param integer $radius
  407. * @param integer $antialias
  408. * @param boolean $top
  409. * @param boolean $left
  410. */
  411. private function round_corner(&$image, $radius, $antialias, $top, $left)
  412. {
  413. $this->debug("Rounding ".($top ? 'top' : 'bottom')." ".($left ? 'left' : 'right')." corner with a radius of ".$radius."px.");
  414. $sX = $left ? -$radius : 0;
  415. $sY = $top ? -$radius : 0;
  416. $eX = $left ? 0 : $radius;
  417. $eY = $top ? 0 : $radius;
  418. // Get this images size
  419. $sizes = $this->sizes();
  420. $offsetX = ($left ? $radius : $sizes->width - $radius - 1);
  421. $offsetY = ($top ? $radius : $sizes->height - $radius - 1);
  422. // Set the images alpha blend to false
  423. imagealphablending($image, false);
  424. // Make this color ahead time
  425. $transparent = $this->create_color($image, null, 0);
  426. for ($x = $sX; $x <= $eX; $x++)
  427. {
  428. for ($y = $sY; $y <= $eY; $y++)
  429. {
  430. $dist = sqrt(($x * $x) + ($y * $y));
  431. if ($dist <= $radius + $antialias)
  432. {
  433. // Decide if anything needs to be changed
  434. // We subtract from antialias so the transparency makes sense.
  435. $fromCirc = $dist - $radius;
  436. if ($fromCirc > 0)
  437. {
  438. if ($fromCirc == 0)
  439. {
  440. imagesetpixel($image, $x + $offsetX, $y + $offsetY, $transparent);
  441. }
  442. else
  443. {
  444. // Get color information from this spot on the image
  445. $rgba = imagecolorat($image, $x + $offsetX, $y + $offsetY);
  446. $tmpColor = imagecolorallocatealpha(
  447. $image,
  448. ($rgba >> 16) & 0xFF, // Red
  449. ($rgba >> 8) & 0xFF, // Green
  450. $rgba & 0xFF, // Blue
  451. (127 - (($rgba >> 24) & 0xFF)) * ($fromCirc / $antialias) // Alpha
  452. );
  453. imagesetpixel($image, $x + $offsetX, $y + $offsetY, $tmpColor);
  454. }
  455. }
  456. }
  457. else
  458. {
  459. // Clear this area out...
  460. imagesetpixel($image, $x + $offsetX, $y + $offsetY, $transparent);
  461. }
  462. }
  463. }
  464. // Reset alpha blending
  465. imagealphablending($image, true);
  466. }
  467. /**
  468. * Merges to images together, using a fix for transparency
  469. *
  470. * @param resource $image The bottom image
  471. * @param resource $watermark The image to be placed on top
  472. * @param integer $x The position of the watermark on the X-axis
  473. * @param integer $y The position of the watermark on the Y-axis
  474. * @param integer $alpha The transparency of the watermark, 0 (trans) to 100 (opaque)
  475. */
  476. private function image_merge(&$image, $watermark, $x, $y, $alpha)
  477. {
  478. $wsizes = $this->sizes($watermark);
  479. $tmpimage = $this->create_transparent_image($wsizes->width, $wsizes->height);
  480. imagecopy($tmpimage, $image, 0, 0, $x, $y, $wsizes->width, $wsizes->height);
  481. imagecopy($tmpimage, $watermark, 0, 0, 0, 0, $wsizes->width, $wsizes->height);
  482. imagealphablending($image, false);
  483. imagecopymerge($image, $tmpimage, $x, $y, 0, 0, $wsizes->width, $wsizes->height, $alpha);
  484. imagealphablending($image, true);
  485. }
  486. }