Imagick.php 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. <?php
  2. /**
  3. * Support for image manipulation using [Imagick](http://php.net/Imagick).
  4. *
  5. * @package Kohana/Image
  6. * @category Drivers
  7. * @author Tamas Mihalik tamas.mihalik@gmail.com
  8. * @copyright (c) Kohana Team
  9. * @license https://koseven.ga/LICENSE.md
  10. */
  11. class Kohana_Image_Imagick extends Image {
  12. /**
  13. * @var Imagick image magick object
  14. */
  15. protected $im;
  16. /**
  17. * Checks if ImageMagick is enabled.
  18. *
  19. * @throws Kohana_Exception
  20. * @return boolean
  21. */
  22. public static function check()
  23. {
  24. if ( ! extension_loaded('imagick'))
  25. {
  26. throw new Kohana_Exception('Imagick is not installed, or the extension is not loaded');
  27. }
  28. return Image_Imagick::$_checked = TRUE;
  29. }
  30. /**
  31. * Runs [Image_Imagick::check] and loads the image.
  32. *
  33. * @return void
  34. * @throws Kohana_Exception
  35. */
  36. public function __construct($file)
  37. {
  38. if ( ! Image_Imagick::$_checked)
  39. {
  40. // Run the install check
  41. Image_Imagick::check();
  42. }
  43. parent::__construct($file);
  44. $this->im = new Imagick;
  45. $this->im->readImage($file);
  46. if ( ! $this->im->getImageAlphaChannel())
  47. {
  48. // Force the image to have an alpha channel
  49. $this->im->setImageAlphaChannel(Imagick::ALPHACHANNEL_SET);
  50. }
  51. }
  52. /**
  53. * Destroys the loaded image to free up resources.
  54. *
  55. * @return void
  56. */
  57. public function __destruct()
  58. {
  59. $this->im->clear();
  60. $this->im->destroy();
  61. }
  62. protected function _do_resize($width, $height)
  63. {
  64. if ($this->im->scaleImage($width, $height))
  65. {
  66. // Reset the width and height
  67. $this->width = $this->im->getImageWidth();
  68. $this->height = $this->im->getImageHeight();
  69. return TRUE;
  70. }
  71. return FALSE;
  72. }
  73. protected function _do_crop($width, $height, $offset_x, $offset_y)
  74. {
  75. if ($this->im->cropImage($width, $height, $offset_x, $offset_y))
  76. {
  77. // Reset the width and height
  78. $this->width = $this->im->getImageWidth();
  79. $this->height = $this->im->getImageHeight();
  80. // Trim off hidden areas
  81. $this->im->setImagePage($this->width, $this->height, 0, 0);
  82. return TRUE;
  83. }
  84. return FALSE;
  85. }
  86. protected function _do_rotate($degrees)
  87. {
  88. if ($this->im->rotateImage(new ImagickPixel('transparent'), $degrees))
  89. {
  90. // Reset the width and height
  91. $this->width = $this->im->getImageWidth();
  92. $this->height = $this->im->getImageHeight();
  93. // Trim off hidden areas
  94. $this->im->setImagePage($this->width, $this->height, 0, 0);
  95. return TRUE;
  96. }
  97. return FALSE;
  98. }
  99. protected function _do_flip($direction)
  100. {
  101. if ($direction === Image::HORIZONTAL)
  102. return $this->im->flopImage();
  103. else
  104. return $this->im->flipImage();
  105. }
  106. protected function _do_sharpen($amount)
  107. {
  108. // IM not support $amount under 5 (0.15)
  109. $amount = ($amount < 5) ? 5 : $amount;
  110. // Amount should be in the range of 0.0 to 3.0
  111. $amount = ($amount * 3.0) / 100;
  112. return $this->im->sharpenImage(0, $amount);
  113. }
  114. protected function _do_reflection($height, $opacity, $fade_in)
  115. {
  116. // Clone the current image and flip it for reflection
  117. $reflection = $this->im->clone();
  118. $reflection->flipImage();
  119. // Crop the reflection to the selected height
  120. $reflection->cropImage($this->width, $height, 0, 0);
  121. $reflection->setImagePage($this->width, $height, 0, 0);
  122. // Select the fade direction
  123. $direction = ['transparent', 'black'];
  124. if ($fade_in)
  125. {
  126. // Change the direction of the fade
  127. $direction = array_reverse($direction);
  128. }
  129. // Create a gradient for fading
  130. $fade = new Imagick;
  131. $fade->newPseudoImage($reflection->getImageWidth(), $reflection->getImageHeight(), vsprintf('gradient:%s-%s', $direction));
  132. // Apply the fade alpha channel to the reflection
  133. $reflection->compositeImage($fade, Imagick::COMPOSITE_DSTOUT, 0, 0);
  134. // NOTE: Using setImageOpacity will destroy alpha channels!
  135. $reflection->evaluateImage(Imagick::EVALUATE_MULTIPLY, $opacity / 100, Imagick::CHANNEL_ALPHA);
  136. // Create a new container to hold the image and reflection
  137. $image = new Imagick;
  138. $image->newImage($this->width, $this->height + $height, new ImagickPixel);
  139. // Force the image to have an alpha channel
  140. $image->setImageAlphaChannel(Imagick::ALPHACHANNEL_SET);
  141. // Force the background color to be transparent
  142. // $image->setImageBackgroundColor(new ImagickPixel('transparent'));
  143. // Match the colorspace between the two images before compositing
  144. $image->setColorspace($this->im->getColorspace());
  145. // Place the image and reflection into the container
  146. if ($image->compositeImage($this->im, Imagick::COMPOSITE_SRC, 0, 0)
  147. AND $image->compositeImage($reflection, Imagick::COMPOSITE_OVER, 0, $this->height))
  148. {
  149. // Replace the current image with the reflected image
  150. $this->im = $image;
  151. // Reset the width and height
  152. $this->width = $this->im->getImageWidth();
  153. $this->height = $this->im->getImageHeight();
  154. return TRUE;
  155. }
  156. return FALSE;
  157. }
  158. protected function _do_watermark(Image $image, $offset_x, $offset_y, $opacity)
  159. {
  160. // Convert the Image intance into an Imagick instance
  161. $watermark = new Imagick;
  162. $watermark->readImageBlob($image->render(), $image->file);
  163. if ($watermark->getImageAlphaChannel() !== Imagick::ALPHACHANNEL_ACTIVATE)
  164. {
  165. // Force the image to have an alpha channel
  166. $watermark->setImageAlphaChannel(Imagick::ALPHACHANNEL_OPAQUE);
  167. }
  168. if ($opacity < 100)
  169. {
  170. // NOTE: Using setImageOpacity will destroy current alpha channels!
  171. $watermark->evaluateImage(Imagick::EVALUATE_MULTIPLY, $opacity / 100, Imagick::CHANNEL_ALPHA);
  172. }
  173. // Match the colorspace between the two images before compositing
  174. // $watermark->setColorspace($this->im->getColorspace());
  175. // Apply the watermark to the image
  176. return $this->im->compositeImage($watermark, Imagick::COMPOSITE_DISSOLVE, $offset_x, $offset_y);
  177. }
  178. protected function _do_background($r, $g, $b, $opacity)
  179. {
  180. // Create a RGB color for the background
  181. $color = sprintf('rgb(%d, %d, %d)', $r, $g, $b);
  182. // Create a new image for the background
  183. $background = new Imagick;
  184. $background->newImage($this->width, $this->height, new ImagickPixel($color));
  185. if ( ! $background->getImageAlphaChannel())
  186. {
  187. // Force the image to have an alpha channel
  188. $background->setImageAlphaChannel(Imagick::ALPHACHANNEL_SET);
  189. }
  190. // Clear the background image
  191. $background->setImageBackgroundColor(new ImagickPixel('transparent'));
  192. // NOTE: Using setImageOpacity will destroy current alpha channels!
  193. $background->evaluateImage(Imagick::EVALUATE_MULTIPLY, $opacity / 100, Imagick::CHANNEL_ALPHA);
  194. // Match the colorspace between the two images before compositing
  195. $background->setColorspace($this->im->getColorspace());
  196. if ($background->compositeImage($this->im, Imagick::COMPOSITE_DISSOLVE, 0, 0))
  197. {
  198. // Replace the current image with the new image
  199. $this->im = $background;
  200. return TRUE;
  201. }
  202. return FALSE;
  203. }
  204. protected function _do_save($file, $quality)
  205. {
  206. // Get the image format and type
  207. list($format, $type) = $this->_get_imagetype(pathinfo($file, PATHINFO_EXTENSION));
  208. // Set the output image type
  209. $this->im->setFormat($format);
  210. // Set the output quality
  211. $this->im->setImageCompressionQuality($quality);
  212. if ($this->im->writeImage($file))
  213. {
  214. // Reset the image type and mime type
  215. $this->type = $type;
  216. $this->mime = $this->image_type_to_mime_type($type);
  217. return TRUE;
  218. }
  219. return FALSE;
  220. }
  221. protected function _do_render($type, $quality)
  222. {
  223. // Get the image format and type
  224. list($format, $type) = $this->_get_imagetype($type);
  225. // Set the output image type
  226. $this->im->setFormat($format);
  227. // Set the output quality
  228. $this->im->setImageCompressionQuality($quality);
  229. // Reset the image type and mime type
  230. $this->type = $type;
  231. $this->mime = $this->image_type_to_mime_type($type);
  232. return (string) $this->im;
  233. }
  234. /**
  235. * Get the image type and format for an extension.
  236. *
  237. * @param string $extension image extension: png, jpg, etc
  238. * @return string IMAGETYPE_* constant
  239. * @throws Kohana_Exception
  240. */
  241. protected function _get_imagetype($extension)
  242. {
  243. // Normalize the extension to a format
  244. $format = strtolower($extension);
  245. switch ($format)
  246. {
  247. case 'jpg':
  248. case 'jpe':
  249. case 'jpeg':
  250. $type = IMAGETYPE_JPEG;
  251. break;
  252. case 'gif':
  253. $type = IMAGETYPE_GIF;
  254. break;
  255. case 'png':
  256. $type = IMAGETYPE_PNG;
  257. break;
  258. case 'webp':
  259. $type = SELF::IMAGETYPE_WEBP;
  260. break;
  261. default:
  262. throw new Kohana_Exception('Installed ImageMagick does not support :type images',
  263. [':type' => $extension]);
  264. break;
  265. }
  266. return [$format, $type];
  267. }
  268. }