PageRenderTime 24ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/vendor/magento/framework/Image/Adapter/ImageMagick.php

https://gitlab.com/yousafsyed/easternglamor
PHP | 480 lines | 284 code | 53 blank | 143 comment | 34 complexity | 17c775267b942d384899da8a7a0360e3 MD5 | raw file
  1. <?php
  2. /**
  3. * Copyright © 2016 Magento. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Framework\Image\Adapter;
  7. class ImageMagick extends \Magento\Framework\Image\Adapter\AbstractAdapter
  8. {
  9. /**
  10. * The blur factor where > 1 is blurry, < 1 is sharp
  11. */
  12. const BLUR_FACTOR = 0.7;
  13. /**
  14. * Error messages
  15. */
  16. const ERROR_WATERMARK_IMAGE_ABSENT = 'Watermark Image absent.';
  17. const ERROR_WRONG_IMAGE = 'Image is not readable or file name is empty.';
  18. /**
  19. * Options Container
  20. *
  21. * @var array
  22. */
  23. protected $_options = [
  24. 'resolution' => ['x' => 72, 'y' => 72],
  25. 'small_image' => ['width' => 300, 'height' => 300],
  26. 'sharpen' => ['radius' => 4, 'deviation' => 1],
  27. ];
  28. /**
  29. * Set/get background color. Check Imagick::COLOR_* constants
  30. *
  31. * @param int|string|array $color
  32. * @return int
  33. */
  34. public function backgroundColor($color = null)
  35. {
  36. if ($color) {
  37. if (is_array($color)) {
  38. $color = "rgb(" . join(',', $color) . ")";
  39. }
  40. $pixel = new \ImagickPixel();
  41. if (is_numeric($color)) {
  42. $pixel->setColorValue($color, 1);
  43. } else {
  44. $pixel->setColor($color);
  45. }
  46. if ($this->_imageHandler) {
  47. $this->_imageHandler->setImageBackgroundColor($color);
  48. }
  49. } else {
  50. $pixel = $this->_imageHandler->getImageBackgroundColor();
  51. }
  52. $this->imageBackgroundColor = $pixel->getColorAsString();
  53. return $this->imageBackgroundColor;
  54. }
  55. /**
  56. * Open image for processing
  57. *
  58. * @param string $filename
  59. * @return void
  60. * @throws \Exception
  61. */
  62. public function open($filename)
  63. {
  64. $this->_fileName = $filename;
  65. $this->_checkCanProcess();
  66. $this->_getFileAttributes();
  67. try {
  68. $this->_imageHandler = new \Imagick($this->_fileName);
  69. } catch (\ImagickException $e) {
  70. throw new \Exception('Unsupported image format.', $e->getCode(), $e);
  71. }
  72. $this->backgroundColor();
  73. $this->getMimeType();
  74. }
  75. /**
  76. * Save image to specific path.
  77. * If some folders of path does not exist they will be created
  78. *
  79. * @param null|string $destination
  80. * @param null|string $newName
  81. * @return void
  82. * @throws \Exception If destination path is not writable
  83. */
  84. public function save($destination = null, $newName = null)
  85. {
  86. $fileName = $this->_prepareDestination($destination, $newName);
  87. $this->_applyOptions();
  88. $this->_imageHandler->stripImage();
  89. $this->_imageHandler->writeImage($fileName);
  90. }
  91. /**
  92. * Apply options to image. Will be usable later when create an option container
  93. *
  94. * @return $this
  95. */
  96. protected function _applyOptions()
  97. {
  98. $this->_imageHandler->setImageCompressionQuality($this->quality());
  99. $this->_imageHandler->setImageCompression(\Imagick::COMPRESSION_JPEG);
  100. $this->_imageHandler->setImageUnits(\Imagick::RESOLUTION_PIXELSPERINCH);
  101. $this->_imageHandler->setImageResolution(
  102. $this->_options['resolution']['x'],
  103. $this->_options['resolution']['y']
  104. );
  105. if (method_exists($this->_imageHandler, 'optimizeImageLayers')) {
  106. $this->_imageHandler->optimizeImageLayers();
  107. }
  108. return $this;
  109. }
  110. /**
  111. * @see \Magento\Framework\Image\Adapter\AbstractAdapter::getImage
  112. * @return string
  113. */
  114. public function getImage()
  115. {
  116. $this->_applyOptions();
  117. return $this->_imageHandler->getImageBlob();
  118. }
  119. /**
  120. * Change the image size
  121. *
  122. * @param null|int $frameWidth
  123. * @param null|int $frameHeight
  124. * @return void
  125. */
  126. public function resize($frameWidth = null, $frameHeight = null)
  127. {
  128. $this->_checkCanProcess();
  129. $dims = $this->_adaptResizeValues($frameWidth, $frameHeight);
  130. $newImage = new \Imagick();
  131. $newImage->newImage(
  132. $dims['frame']['width'],
  133. $dims['frame']['height'],
  134. $this->_imageHandler->getImageBackgroundColor()
  135. );
  136. $this->_imageHandler->resizeImage(
  137. $dims['dst']['width'],
  138. $dims['dst']['height'],
  139. \Imagick::FILTER_CUBIC,
  140. self::BLUR_FACTOR
  141. );
  142. if ($this->_imageHandler->getImageWidth() < $this->_options['small_image']['width'] ||
  143. $this->_imageHandler->getImageHeight() < $this->_options['small_image']['height']
  144. ) {
  145. $this->_imageHandler->sharpenImage(
  146. $this->_options['sharpen']['radius'],
  147. $this->_options['sharpen']['deviation']
  148. );
  149. }
  150. $newImage->compositeImage(
  151. $this->_imageHandler,
  152. \Imagick::COMPOSITE_OVER,
  153. $dims['dst']['x'],
  154. $dims['dst']['y']
  155. );
  156. $newImage->setImageFormat($this->_imageHandler->getImageFormat());
  157. $this->_imageHandler->clear();
  158. $this->_imageHandler->destroy();
  159. $this->_imageHandler = $newImage;
  160. $this->refreshImageDimensions();
  161. }
  162. /**
  163. * Rotate image on specific angle
  164. *
  165. * @param int $angle
  166. * @return void
  167. */
  168. public function rotate($angle)
  169. {
  170. $this->_checkCanProcess();
  171. // compatibility with GD2 adapter
  172. $angle = 360 - $angle;
  173. $pixel = new \ImagickPixel();
  174. $pixel->setColor("rgb(" . $this->imageBackgroundColor . ")");
  175. $this->_imageHandler->rotateImage($pixel, $angle);
  176. $this->refreshImageDimensions();
  177. }
  178. /**
  179. * Crop image
  180. *
  181. * @param int $top
  182. * @param int $left
  183. * @param int $right
  184. * @param int $bottom
  185. * @return bool
  186. */
  187. public function crop($top = 0, $left = 0, $right = 0, $bottom = 0)
  188. {
  189. if ($left == 0 && $top == 0 && $right == 0 && $bottom == 0 || !$this->_canProcess()) {
  190. return false;
  191. }
  192. $newWidth = $this->_imageSrcWidth - $left - $right;
  193. $newHeight = $this->_imageSrcHeight - $top - $bottom;
  194. $this->_imageHandler->cropImage($newWidth, $newHeight, $left, $top);
  195. $this->refreshImageDimensions();
  196. return true;
  197. }
  198. /**
  199. * Add watermark to image
  200. *
  201. * @param string $imagePath
  202. * @param int $positionX
  203. * @param int $positionY
  204. * @param int $opacity
  205. * @param bool $tile
  206. * @return void
  207. * @throws \LogicException
  208. * @throws \Exception
  209. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  210. * @SuppressWarnings(PHPMD.NPathComplexity)
  211. */
  212. public function watermark($imagePath, $positionX = 0, $positionY = 0, $opacity = 30, $tile = false)
  213. {
  214. if (empty($imagePath) || !file_exists($imagePath)) {
  215. throw new \LogicException(self::ERROR_WATERMARK_IMAGE_ABSENT);
  216. }
  217. $this->_checkCanProcess();
  218. $opacity = $this->getWatermarkImageOpacity() ? $this->getWatermarkImageOpacity() : $opacity;
  219. $opacity = (double)number_format($opacity / 100, 1);
  220. $watermark = new \Imagick($imagePath);
  221. if ($this->getWatermarkWidth() &&
  222. $this->getWatermarkHeight() &&
  223. $this->getWatermarkPosition() != self::POSITION_STRETCH
  224. ) {
  225. $watermark->resizeImage(
  226. $this->getWatermarkWidth(),
  227. $this->getWatermarkHeight(),
  228. \Imagick::FILTER_CUBIC,
  229. self::BLUR_FACTOR
  230. );
  231. }
  232. if (method_exists($watermark, 'setImageOpacity')) {
  233. // available from imagick 6.3.1
  234. $watermark->setImageOpacity($opacity);
  235. } else {
  236. // go to each pixel and make it transparent
  237. $watermark->paintTransparentImage($watermark->getImagePixelColor(0, 0), 1, 65530);
  238. $watermark->evaluateImage(\Imagick::EVALUATE_SUBTRACT, 1 - $opacity, \Imagick::CHANNEL_ALPHA);
  239. }
  240. switch ($this->getWatermarkPosition()) {
  241. case self::POSITION_STRETCH:
  242. $watermark->sampleImage($this->_imageSrcWidth, $this->_imageSrcHeight);
  243. break;
  244. case self::POSITION_CENTER:
  245. $positionX = ($this->_imageSrcWidth - $watermark->getImageWidth()) / 2;
  246. $positionY = ($this->_imageSrcHeight - $watermark->getImageHeight()) / 2;
  247. break;
  248. case self::POSITION_TOP_RIGHT:
  249. $positionX = $this->_imageSrcWidth - $watermark->getImageWidth();
  250. break;
  251. case self::POSITION_BOTTOM_RIGHT:
  252. $positionX = $this->_imageSrcWidth - $watermark->getImageWidth();
  253. $positionY = $this->_imageSrcHeight - $watermark->getImageHeight();
  254. break;
  255. case self::POSITION_BOTTOM_LEFT:
  256. $positionY = $this->_imageSrcHeight - $watermark->getImageHeight();
  257. break;
  258. case self::POSITION_TILE:
  259. $positionX = 0;
  260. $positionY = 0;
  261. $tile = true;
  262. break;
  263. }
  264. try {
  265. if ($tile) {
  266. $offsetX = $positionX;
  267. $offsetY = $positionY;
  268. while ($offsetY <= $this->_imageSrcHeight + $watermark->getImageHeight()) {
  269. while ($offsetX <= $this->_imageSrcWidth + $watermark->getImageWidth()) {
  270. $this->_imageHandler->compositeImage($watermark, \Imagick::COMPOSITE_OVER, $offsetX, $offsetY);
  271. $offsetX += $watermark->getImageWidth();
  272. }
  273. $offsetX = $positionX;
  274. $offsetY += $watermark->getImageHeight();
  275. }
  276. } else {
  277. $this->_imageHandler->compositeImage($watermark, \Imagick::COMPOSITE_OVER, $positionX, $positionY);
  278. }
  279. } catch (\ImagickException $e) {
  280. throw new \Exception('Unable to create watermark.', $e->getCode(), $e);
  281. }
  282. // merge layers
  283. $this->_imageHandler->mergeImageLayers(\Imagick::LAYERMETHOD_FLATTEN);
  284. $watermark->clear();
  285. $watermark->destroy();
  286. }
  287. /**
  288. * Checks required dependencies
  289. *
  290. * @return void
  291. * @throws \Exception If some of dependencies are missing
  292. */
  293. public function checkDependencies()
  294. {
  295. if (!class_exists('\Imagick', false)) {
  296. throw new \Exception("Required PHP extension 'Imagick' was not loaded.");
  297. }
  298. }
  299. /**
  300. * Reassign image dimensions
  301. *
  302. * @return void
  303. */
  304. public function refreshImageDimensions()
  305. {
  306. $this->_imageSrcWidth = $this->_imageHandler->getImageWidth();
  307. $this->_imageSrcHeight = $this->_imageHandler->getImageHeight();
  308. $this->_imageHandler->setImagePage($this->_imageSrcWidth, $this->_imageSrcHeight, 0, 0);
  309. }
  310. /**
  311. * Standard destructor. Destroy stored information about image
  312. */
  313. public function __destruct()
  314. {
  315. $this->destroy();
  316. }
  317. /**
  318. * Destroy stored information about image
  319. *
  320. * @return $this
  321. */
  322. public function destroy()
  323. {
  324. if (null !== $this->_imageHandler && $this->_imageHandler instanceof \Imagick) {
  325. $this->_imageHandler->clear();
  326. $this->_imageHandler->destroy();
  327. $this->_imageHandler = null;
  328. }
  329. return $this;
  330. }
  331. /**
  332. * Returns rgba array of the specified pixel
  333. *
  334. * @param int $x
  335. * @param int $y
  336. * @return array
  337. */
  338. public function getColorAt($x, $y)
  339. {
  340. $pixel = $this->_imageHandler->getImagePixelColor($x, $y);
  341. $color = $pixel->getColor();
  342. $rgbaColor = [
  343. 'red' => $color['r'],
  344. 'green' => $color['g'],
  345. 'blue' => $color['b'],
  346. 'alpha' => (1 - $color['a']) * 127,
  347. ];
  348. return $rgbaColor;
  349. }
  350. /**
  351. * Check whether the adapter can work with the image
  352. *
  353. * @throws \LogicException
  354. * @return true
  355. */
  356. protected function _checkCanProcess()
  357. {
  358. if (!$this->_canProcess()) {
  359. throw new \LogicException(self::ERROR_WRONG_IMAGE);
  360. }
  361. return true;
  362. }
  363. /**
  364. * Create Image from string
  365. *
  366. * @param string $text
  367. * @param string $font
  368. * @return \Magento\Framework\Image\Adapter\AbstractAdapter
  369. */
  370. public function createPngFromString($text, $font = '')
  371. {
  372. $image = $this->_getImagickObject();
  373. $draw = $this->_getImagickDrawObject();
  374. $color = $this->_getImagickPixelObject('#000000');
  375. $background = $this->_getImagickPixelObject('#ffffff00');
  376. // Transparent
  377. if (!empty($font)) {
  378. if (method_exists($image, 'setFont')) {
  379. $image->setFont($font);
  380. } elseif (method_exists($draw, 'setFont')) {
  381. $draw->setFont($font);
  382. }
  383. }
  384. $draw->setFontSize($this->_fontSize);
  385. $draw->setFillColor($color);
  386. $draw->setStrokeAntialias(true);
  387. $draw->setTextAntialias(true);
  388. $metrics = $image->queryFontMetrics($draw, $text);
  389. $draw->annotation(0, $metrics['ascender'], $text);
  390. $height = abs($metrics['ascender']) + abs($metrics['descender']);
  391. $image->newImage($metrics['textWidth'], $height, $background);
  392. $this->_fileType = IMAGETYPE_PNG;
  393. $image->setImageFormat('png');
  394. $image->drawImage($draw);
  395. $this->_imageHandler = $image;
  396. return $this;
  397. }
  398. /**
  399. * Get Imagick object
  400. *
  401. * @param mixed $files
  402. * @return \Imagick
  403. */
  404. protected function _getImagickObject($files = null)
  405. {
  406. return new \Imagick($files);
  407. }
  408. /**
  409. * Get ImagickDraw object
  410. *
  411. * @return \ImagickDraw
  412. */
  413. protected function _getImagickDrawObject()
  414. {
  415. return new \ImagickDraw();
  416. }
  417. /**
  418. * Get ImagickPixel object
  419. *
  420. * @param string|null $color
  421. * @return \ImagickPixel
  422. */
  423. protected function _getImagickPixelObject($color = null)
  424. {
  425. return new \ImagickPixel($color);
  426. }
  427. }