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

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

https://bitbucket.org/waqar4at/scrumie
PHP | 584 lines | 327 code | 106 blank | 151 comment | 32 complexity | 15cea6411d6d17e1f35abaf3f3c0da17 MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-2.1
  1. <?php defined('SYSPATH') or die('No direct script access.');
  2. /**
  3. * Support for image manipulation using [GD](http://php.net/GD).
  4. *
  5. * @package Kohana/Image
  6. * @category Drivers
  7. * @author Kohana Team
  8. * @copyright (c) 2008-2009 Kohana Team
  9. * @license http://kohanaphp.com/license.html
  10. */
  11. class Kohana_Image_GD extends Image {
  12. // Is GD bundled or separate?
  13. protected static $_bundled;
  14. /**
  15. * Checks if GD is enabled and bundled. Bundled GD is required for some
  16. * methods to work. Exceptions will be thrown from those methods when GD is
  17. * not bundled.
  18. *
  19. * @return boolean
  20. */
  21. public static function check()
  22. {
  23. if ( ! function_exists('gd_info'))
  24. {
  25. throw new Kohana_Exception('GD is either not installed or not enabled, check your configuration');
  26. }
  27. if (defined('GD_BUNDLED'))
  28. {
  29. // Get the version via a constant, available in PHP 5.
  30. Image_GD::$_bundled = GD_BUNDLED;
  31. }
  32. else
  33. {
  34. // Get the version information
  35. $info = gd_info();
  36. // Extract the bundled status
  37. Image_GD::$_bundled = (bool) preg_match('/\bbundled\b/i', $info['GD Version']);
  38. }
  39. if (defined('GD_VERSION'))
  40. {
  41. // Get the version via a constant, available in PHP 5.2.4+
  42. $version = GD_VERSION;
  43. }
  44. else
  45. {
  46. // Get the version information
  47. $info = gd_info();
  48. // Extract the version number
  49. preg_match('/\d+\.\d+(?:\.\d+)?/', $info['GD Version'], $matches);
  50. // Get the major version
  51. $version = $matches[0];
  52. }
  53. if ( ! version_compare($version, '2.0.1', '>='))
  54. {
  55. throw new Kohana_Exception('Image_GD requires GD version :required or greater, you have :version',
  56. array('required' => '2.0.1', ':version' => $version));
  57. }
  58. return Image_GD::$_checked = TRUE;
  59. }
  60. // Temporary image resource
  61. protected $_image;
  62. // Function name to open Image
  63. protected $_create_function;
  64. /**
  65. * Runs [Image_GD::check] and loads the image.
  66. *
  67. * @return void
  68. * @throws Kohana_Exception
  69. */
  70. public function __construct($file)
  71. {
  72. if ( ! Image_GD::$_checked)
  73. {
  74. // Run the install check
  75. Image_GD::check();
  76. }
  77. parent::__construct($file);
  78. // Set the image creation function name
  79. switch ($this->type)
  80. {
  81. case IMAGETYPE_JPEG:
  82. $create = 'imagecreatefromjpeg';
  83. break;
  84. case IMAGETYPE_GIF:
  85. $create = 'imagecreatefromgif';
  86. break;
  87. case IMAGETYPE_PNG:
  88. $create = 'imagecreatefrompng';
  89. break;
  90. }
  91. if ( ! isset($create) OR ! function_exists($create))
  92. {
  93. throw new Kohana_Exception('Installed GD does not support :type images',
  94. array(':type' => image_type_to_extension($this->type, FALSE)));
  95. }
  96. // Save function for future use
  97. $this->_create_function = $create;
  98. // Save filename for lazy loading
  99. $this->_image = $this->file;
  100. }
  101. /**
  102. * Destroys the loaded image to free up resources.
  103. *
  104. * @return void
  105. */
  106. public function __destruct()
  107. {
  108. if (is_resource($this->_image))
  109. {
  110. // Free all resources
  111. imagedestroy($this->_image);
  112. }
  113. }
  114. /**
  115. * Loads an image into GD.
  116. *
  117. * @return void
  118. */
  119. protected function _load_image()
  120. {
  121. if ( ! is_resource($this->_image))
  122. {
  123. // Gets create function
  124. $create = $this->_create_function;
  125. // Open the temporary image
  126. $this->_image = $create($this->file);
  127. // Preserve transparency when saving
  128. imagesavealpha($this->_image, TRUE);
  129. }
  130. }
  131. protected function _do_resize($width, $height)
  132. {
  133. // Presize width and height
  134. $pre_width = $this->width;
  135. $pre_height = $this->height;
  136. // Loads image if not yet loaded
  137. $this->_load_image();
  138. // Test if we can do a resize without resampling to speed up the final resize
  139. if ($width > ($this->width / 2) AND $height > ($this->height / 2))
  140. {
  141. // The maximum reduction is 10% greater than the final size
  142. $reduction_width = round($width * 1.1);
  143. $reduction_height = round($height * 1.1);
  144. while ($pre_width / 2 > $reduction_width AND $pre_height / 2 > $reduction_height)
  145. {
  146. // Reduce the size using an O(2n) algorithm, until it reaches the maximum reduction
  147. $pre_width /= 2;
  148. $pre_height /= 2;
  149. }
  150. // Create the temporary image to copy to
  151. $image = $this->_create($pre_width, $pre_height);
  152. if (imagecopyresized($image, $this->_image, 0, 0, 0, 0, $pre_width, $pre_height, $this->width, $this->height))
  153. {
  154. // Swap the new image for the old one
  155. imagedestroy($this->_image);
  156. $this->_image = $image;
  157. }
  158. }
  159. // Create the temporary image to copy to
  160. $image = $this->_create($width, $height);
  161. // Execute the resize
  162. if (imagecopyresampled($image, $this->_image, 0, 0, 0, 0, $width, $height, $pre_width, $pre_height))
  163. {
  164. // Swap the new image for the old one
  165. imagedestroy($this->_image);
  166. $this->_image = $image;
  167. // Reset the width and height
  168. $this->width = imagesx($image);
  169. $this->height = imagesy($image);
  170. }
  171. }
  172. protected function _do_crop($width, $height, $offset_x, $offset_y)
  173. {
  174. // Create the temporary image to copy to
  175. $image = $this->_create($width, $height);
  176. // Loads image if not yet loaded
  177. $this->_load_image();
  178. // Execute the crop
  179. if (imagecopyresampled($image, $this->_image, 0, 0, $offset_x, $offset_y, $width, $height, $width, $height))
  180. {
  181. // Swap the new image for the old one
  182. imagedestroy($this->_image);
  183. $this->_image = $image;
  184. // Reset the width and height
  185. $this->width = imagesx($image);
  186. $this->height = imagesy($image);
  187. }
  188. }
  189. protected function _do_rotate($degrees)
  190. {
  191. if ( ! Image_GD::$_bundled)
  192. {
  193. throw new Kohana_Exception('This method requires :function, which is only available in the bundled version of GD',
  194. array(':function' => 'imagerotate'));
  195. }
  196. // Loads image if not yet loaded
  197. $this->_load_image();
  198. // Transparent black will be used as the background for the uncovered region
  199. $transparent = imagecolorallocatealpha($this->_image, 0, 0, 0, 127);
  200. // Rotate, setting the transparent color
  201. $image = imagerotate($this->_image, 360 - $degrees, $transparent, 1);
  202. // Save the alpha of the rotated image
  203. imagesavealpha($image, TRUE);
  204. // Get the width and height of the rotated image
  205. $width = imagesx($image);
  206. $height = imagesy($image);
  207. if (imagecopymerge($this->_image, $image, 0, 0, 0, 0, $width, $height, 100))
  208. {
  209. // Swap the new image for the old one
  210. imagedestroy($this->_image);
  211. $this->_image = $image;
  212. // Reset the width and height
  213. $this->width = $width;
  214. $this->height = $height;
  215. }
  216. }
  217. protected function _do_flip($direction)
  218. {
  219. // Create the flipped image
  220. $flipped = $this->_create($this->width, $this->height);
  221. // Loads image if not yet loaded
  222. $this->_load_image();
  223. if ($direction === Image::HORIZONTAL)
  224. {
  225. for ($x = 0; $x < $this->width; $x++)
  226. {
  227. // Flip each row from top to bottom
  228. imagecopy($flipped, $this->_image, $x, 0, $this->width - $x - 1, 0, 1, $this->height);
  229. }
  230. }
  231. else
  232. {
  233. for ($y = 0; $y < $this->height; $y++)
  234. {
  235. // Flip each column from left to right
  236. imagecopy($flipped, $this->_image, 0, $y, 0, $this->height - $y - 1, $this->width, 1);
  237. }
  238. }
  239. // Swap the new image for the old one
  240. imagedestroy($this->_image);
  241. $this->_image = $flipped;
  242. // Reset the width and height
  243. $this->width = imagesx($flipped);
  244. $this->height = imagesy($flipped);
  245. }
  246. protected function _do_sharpen($amount)
  247. {
  248. if ( ! Image_GD::$_bundled)
  249. {
  250. throw new Kohana_Exception('This method requires :function, which is only available in the bundled version of GD',
  251. array(':function' => 'imageconvolution'));
  252. }
  253. // Loads image if not yet loaded
  254. $this->_load_image();
  255. // Amount should be in the range of 18-10
  256. $amount = round(abs(-18 + ($amount * 0.08)), 2);
  257. // Gaussian blur matrix
  258. $matrix = array
  259. (
  260. array(-1, -1, -1),
  261. array(-1, $amount, -1),
  262. array(-1, -1, -1),
  263. );
  264. // Perform the sharpen
  265. if (imageconvolution($this->_image, $matrix, $amount - 8, 0))
  266. {
  267. // Reset the width and height
  268. $this->width = imagesx($this->_image);
  269. $this->height = imagesy($this->_image);
  270. }
  271. }
  272. protected function _do_reflection($height, $opacity, $fade_in)
  273. {
  274. if ( ! Image_GD::$_bundled)
  275. {
  276. throw new Kohana_Exception('This method requires :function, which is only available in the bundled version of GD',
  277. array(':function' => 'imagefilter'));
  278. }
  279. // Loads image if not yet loaded
  280. $this->_load_image();
  281. // Convert an opacity range of 0-100 to 127-0
  282. $opacity = round(abs(($opacity * 127 / 100) - 127));
  283. if ($opacity < 127)
  284. {
  285. // Calculate the opacity stepping
  286. $stepping = (127 - $opacity) / $height;
  287. }
  288. else
  289. {
  290. // Avoid a "divide by zero" error
  291. $stepping = 127 / $height;
  292. }
  293. // Create the reflection image
  294. $reflection = $this->_create($this->width, $this->height + $height);
  295. // Copy the image to the reflection
  296. imagecopy($reflection, $this->_image, 0, 0, 0, 0, $this->width, $this->height);
  297. for ($offset = 0; $height >= $offset; $offset++)
  298. {
  299. // Read the next line down
  300. $src_y = $this->height - $offset - 1;
  301. // Place the line at the bottom of the reflection
  302. $dst_y = $this->height + $offset;
  303. if ($fade_in === TRUE)
  304. {
  305. // Start with the most transparent line first
  306. $dst_opacity = round($opacity + ($stepping * ($height - $offset)));
  307. }
  308. else
  309. {
  310. // Start with the most opaque line first
  311. $dst_opacity = round($opacity + ($stepping * $offset));
  312. }
  313. // Create a single line of the image
  314. $line = $this->_create($this->width, 1);
  315. // Copy a single line from the current image into the line
  316. imagecopy($line, $this->_image, 0, 0, 0, $src_y, $this->width, 1);
  317. // Colorize the line to add the correct alpha level
  318. imagefilter($line, IMG_FILTER_COLORIZE, 0, 0, 0, $dst_opacity);
  319. // Copy a the line into the reflection
  320. imagecopy($reflection, $line, 0, $dst_y, 0, 0, $this->width, 1);
  321. }
  322. // Swap the new image for the old one
  323. imagedestroy($this->_image);
  324. $this->_image = $reflection;
  325. // Reset the width and height
  326. $this->width = imagesx($reflection);
  327. $this->height = imagesy($reflection);
  328. }
  329. protected function _do_watermark(Image $watermark, $offset_x, $offset_y, $opacity)
  330. {
  331. if ( ! Image_GD::$_bundled)
  332. {
  333. throw new Kohana_Exception('This method requires :function, which is only available in the bundled version of GD',
  334. array(':function' => 'imagelayereffect'));
  335. }
  336. // Loads image if not yet loaded
  337. $this->_load_image();
  338. // Create the watermark image resource
  339. $overlay = imagecreatefromstring($watermark->render());
  340. imagesavealpha($overlay, TRUE);
  341. // Get the width and height of the watermark
  342. $width = imagesx($overlay);
  343. $height = imagesy($overlay);
  344. if ($opacity < 100)
  345. {
  346. // Convert an opacity range of 0-100 to 127-0
  347. $opacity = round(abs(($opacity * 127 / 100) - 127));
  348. // Allocate transparent gray
  349. $color = imagecolorallocatealpha($overlay, 127, 127, 127, $opacity);
  350. // The transparent image will overlay the watermark
  351. imagelayereffect($overlay, IMG_EFFECT_OVERLAY);
  352. // Fill the background with the transparent color
  353. imagefilledrectangle($overlay, 0, 0, $width, $height, $color);
  354. }
  355. // Alpha blending must be enabled on the background!
  356. imagealphablending($this->_image, TRUE);
  357. if (imagecopy($this->_image, $overlay, $offset_x, $offset_y, 0, 0, $width, $height))
  358. {
  359. // Destroy the overlay image
  360. imagedestroy($overlay);
  361. }
  362. }
  363. protected function _do_background($r, $g, $b, $opacity)
  364. {
  365. // Loads image if not yet loaded
  366. $this->_load_image();
  367. // Convert an opacity range of 0-100 to 127-0
  368. $opacity = round(abs(($opacity * 127 / 100) - 127));
  369. // Create a new background
  370. $background = $this->_create($this->width, $this->height);
  371. // Allocate the color
  372. $color = imagecolorallocatealpha($background, $r, $g, $b, $opacity);
  373. // Fill the image with white
  374. imagefilledrectangle($background, 0, 0, $this->width, $this->height, $color);
  375. // Alpha blending must be enabled on the background!
  376. imagealphablending($background, TRUE);
  377. // Copy the image onto a white background to remove all transparency
  378. if (imagecopy($background, $this->_image, 0, 0, 0, 0, $this->width, $this->height))
  379. {
  380. // Swap the new image for the old one
  381. imagedestroy($this->_image);
  382. $this->_image = $background;
  383. }
  384. }
  385. protected function _do_save($file, $quality)
  386. {
  387. // Loads image if not yet loaded
  388. $this->_load_image();
  389. // Get the extension of the file
  390. $extension = pathinfo($file, PATHINFO_EXTENSION);
  391. // Get the save function and IMAGETYPE
  392. list($save, $type) = $this->_save_function($extension, $quality);
  393. // Save the image to a file
  394. $status = isset($quality) ? $save($this->_image, $file, $quality) : $save($this->_image, $file);
  395. if ($status === TRUE AND $type !== $this->type)
  396. {
  397. // Reset the image type and mime type
  398. $this->type = $type;
  399. $this->mime = image_type_to_mime_type($type);
  400. }
  401. return TRUE;
  402. }
  403. protected function _do_render($type, $quality)
  404. {
  405. // Loads image if not yet loaded
  406. $this->_load_image();
  407. // Get the save function and IMAGETYPE
  408. list($save, $type) = $this->_save_function($type, $quality);
  409. // Capture the output
  410. ob_start();
  411. // Render the image
  412. $status = isset($quality) ? $save($this->_image, NULL, $quality) : $save($this->_image, NULL);
  413. if ($status === TRUE AND $type !== $this->type)
  414. {
  415. // Reset the image type and mime type
  416. $this->type = $type;
  417. $this->mime = image_type_to_mime_type($type);
  418. }
  419. return ob_get_clean();
  420. }
  421. /**
  422. * Get the GD saving function and image type for this extension.
  423. * Also normalizes the quality setting
  424. *
  425. * @param string image type: png, jpg, etc
  426. * @param integer image quality
  427. * @return array save function, IMAGETYPE_* constant
  428. * @throws Kohana_Exception
  429. */
  430. protected function _save_function($extension, & $quality)
  431. {
  432. switch (strtolower($extension))
  433. {
  434. case 'jpg':
  435. case 'jpeg':
  436. // Save a JPG file
  437. $save = 'imagejpeg';
  438. $type = IMAGETYPE_JPEG;
  439. break;
  440. case 'gif':
  441. // Save a GIF file
  442. $save = 'imagegif';
  443. $type = IMAGETYPE_GIF;
  444. // GIFs do not a quality setting
  445. $quality = NULL;
  446. break;
  447. case 'png':
  448. // Save a PNG file
  449. $save = 'imagepng';
  450. $type = IMAGETYPE_PNG;
  451. // Use a compression level of 9 (does not affect quality!)
  452. $quality = 9;
  453. break;
  454. default:
  455. throw new Kohana_Exception('Installed GD does not support :type images',
  456. array(':type' => $extension));
  457. break;
  458. }
  459. return array($save, $type);
  460. }
  461. /**
  462. * Create an empty image with the given width and height.
  463. *
  464. * @param integer image width
  465. * @param integer image height
  466. * @return resource
  467. */
  468. protected function _create($width, $height)
  469. {
  470. // Create an empty image
  471. $image = imagecreatetruecolor($width, $height);
  472. // Do not apply alpha blending
  473. imagealphablending($image, FALSE);
  474. // Save alpha levels
  475. imagesavealpha($image, TRUE);
  476. return $image;
  477. }
  478. } // End Image_GD