PageRenderTime 49ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/Varien/Image/Adapter/ImageMagick.php

https://bitbucket.org/jokusafet/magento2
PHP | 430 lines | 258 code | 43 blank | 129 comment | 31 complexity | 76e3ad73fc162afd938c3dc2757b8e74 MD5 | raw file
  1. <?php
  2. /**
  3. * Magento
  4. *
  5. * NOTICE OF LICENSE
  6. *
  7. * This source file is subject to the Open Software License (OSL 3.0)
  8. * that is bundled with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://opensource.org/licenses/osl-3.0.php
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@magentocommerce.com so we can send you a copy immediately.
  14. *
  15. * DISCLAIMER
  16. *
  17. * Do not edit or add to this file if you wish to upgrade Magento to newer
  18. * versions in the future. If you wish to customize Magento for your
  19. * needs please refer to http://www.magentocommerce.com for more information.
  20. *
  21. * @category Varien
  22. * @package Varien_Image
  23. * @copyright Copyright (c) 2012 X.commerce, Inc. (http://www.magentocommerce.com)
  24. * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
  25. */
  26. class Varien_Image_Adapter_ImageMagick extends Varien_Image_Adapter_Abstract
  27. {
  28. /**
  29. * The blur factor where > 1 is blurry, < 1 is sharp
  30. */
  31. const BLUR_FACTOR = 0.7;
  32. /**
  33. * Error messages
  34. */
  35. const ERROR_WATERMARK_IMAGE_ABSENT = 'Watermark Image absent.';
  36. const ERROR_WRONG_IMAGE = 'Image is not readable or file name is empty.';
  37. /**
  38. * Options Container
  39. *
  40. * @var array
  41. */
  42. protected $_options = array(
  43. 'resolution' => array(
  44. 'x' => 72,
  45. 'y' => 72
  46. ),
  47. 'small_image' => array(
  48. 'width' => 300,
  49. 'height' => 300
  50. ),
  51. 'sharpen' => array(
  52. 'radius' => 4,
  53. 'deviation' => 1
  54. )
  55. );
  56. /**
  57. * Set/get background color. Check Imagick::COLOR_* constants
  58. *
  59. * @param int|string|array $color
  60. * @return int
  61. */
  62. public function backgroundColor($color = null)
  63. {
  64. if ($color) {
  65. if (is_array($color)) {
  66. $color = "rgb(" . join(',', $color) . ")";
  67. }
  68. $pixel = new ImagickPixel;
  69. if (is_numeric($color)) {
  70. $pixel->setColorValue($color, 1);
  71. } else {
  72. $pixel->setColor($color);
  73. }
  74. if ($this->_imageHandler) {
  75. $this->_imageHandler->setImageBackgroundColor($color);
  76. }
  77. } else {
  78. $pixel = $this->_imageHandler->getImageBackgroundColor();
  79. }
  80. $this->imageBackgroundColor = $pixel->getColorAsString();
  81. return $this->imageBackgroundColor;
  82. }
  83. /**
  84. * Open image for processing
  85. *
  86. * @throws Exception
  87. * @param string $filename
  88. */
  89. public function open($filename)
  90. {
  91. $this->_fileName = $filename;
  92. $this->_checkCanProcess();
  93. $this->_getFileAttributes();
  94. try {
  95. $this->_imageHandler = new Imagick($this->_fileName);
  96. } catch (ImagickException $e) {
  97. throw new Exception('Unsupported image format.', $e->getCode(), $e);
  98. }
  99. $this->backgroundColor();
  100. $this->getMimeType();
  101. }
  102. /**
  103. * Save image to specific path.
  104. * If some folders of path does not exist they will be created
  105. *
  106. * @param string $destination
  107. * @param string $newName
  108. */
  109. public function save($destination = null, $newName = null)
  110. {
  111. $fileName = $this->_prepareDestination($destination, $newName);
  112. $this->_applyOptions();
  113. $this->_imageHandler->stripImage();
  114. $this->_imageHandler->writeImage($fileName);
  115. }
  116. /**
  117. * Apply options to image. Will be usable later when create an option container
  118. *
  119. * @return Varien_Image_Adapter_ImageMagick
  120. */
  121. protected function _applyOptions()
  122. {
  123. $this->_imageHandler->setImageCompressionQuality($this->quality());
  124. $this->_imageHandler->setImageCompression(Imagick::COMPRESSION_JPEG);
  125. $this->_imageHandler->setImageUnits(Imagick::RESOLUTION_PIXELSPERINCH);
  126. $this->_imageHandler->setImageResolution(
  127. $this->_options['resolution']['x'],
  128. $this->_options['resolution']['y']
  129. );
  130. if (method_exists($this->_imageHandler, 'optimizeImageLayers')) {
  131. $this->_imageHandler->optimizeImageLayers();
  132. }
  133. return $this;
  134. }
  135. /**
  136. * @see Varien_Image_Adapter_Abstract::getImage
  137. * @return string
  138. */
  139. public function getImage()
  140. {
  141. $this->_applyOptions();
  142. return (string)$this->_imageHandler;
  143. }
  144. /**
  145. * Change the image size
  146. *
  147. * @param int $frameWidth
  148. * @param int $frameHeight
  149. * @throws Exception
  150. */
  151. public function resize($frameWidth = null, $frameHeight = null)
  152. {
  153. $this->_checkCanProcess();
  154. $dims = $this->_adaptResizeValues($frameWidth, $frameHeight);
  155. $newImage = new Imagick();
  156. $newImage->newImage(
  157. $dims['frame']['width'],
  158. $dims['frame']['height'],
  159. $this->_imageHandler->getImageBackgroundColor()
  160. );
  161. $this->_imageHandler->resizeImage(
  162. $dims['dst']['width'],
  163. $dims['dst']['height'],
  164. Imagick::FILTER_CUBIC,
  165. self::BLUR_FACTOR
  166. );
  167. if ($this->_imageHandler->getImageWidth() < $this->_options['small_image']['width']
  168. || $this->_imageHandler->getImageHeight() < $this->_options['small_image']['height']
  169. ) {
  170. $this->_imageHandler->sharpenImage(
  171. $this->_options['sharpen']['radius'],
  172. $this->_options['sharpen']['deviation']
  173. );
  174. }
  175. $newImage->compositeImage(
  176. $this->_imageHandler,
  177. Imagick::COMPOSITE_OVER,
  178. $dims['dst']['x'],
  179. $dims['dst']['y']
  180. );
  181. $newImage->setImageFormat($this->_imageHandler->getImageFormat());
  182. $this->_imageHandler->clear();
  183. $this->_imageHandler->destroy();
  184. $this->_imageHandler = $newImage;
  185. $this->refreshImageDimensions();
  186. }
  187. /**
  188. * Rotate image on specific angle
  189. *
  190. * @param int $angle
  191. * @throws Exception
  192. */
  193. public function rotate($angle)
  194. {
  195. $this->_checkCanProcess();
  196. // compatibility with GD2 adapter
  197. $angle = 360 - $angle;
  198. $pixel = new ImagickPixel;
  199. $pixel->setColor("rgb(" . $this->imageBackgroundColor . ")");
  200. $this->_imageHandler->rotateImage($pixel, $angle);
  201. $this->refreshImageDimensions();
  202. }
  203. /**
  204. * Crop image
  205. *
  206. * @param int $top
  207. * @param int $left
  208. * @param int $right
  209. * @param int $bottom
  210. * @return bool
  211. */
  212. public function crop($top = 0, $left = 0, $right = 0, $bottom = 0)
  213. {
  214. if ($left == 0 && $top == 0 && $right == 0 && $bottom == 0
  215. || !$this->_canProcess()
  216. ) {
  217. return false;
  218. }
  219. $newWidth = $this->_imageSrcWidth - $left - $right;
  220. $newHeight = $this->_imageSrcHeight - $top - $bottom;
  221. $this->_imageHandler->cropImage($newWidth, $newHeight, $left, $top);
  222. $this->refreshImageDimensions();
  223. return true;
  224. }
  225. /**
  226. * Add watermark to image
  227. *
  228. * @param $imagePath
  229. * @param int $positionX
  230. * @param int $positionY
  231. * @param int $opacity
  232. * @param bool $tile
  233. * @throws Exception
  234. */
  235. public function watermark($imagePath, $positionX = 0, $positionY = 0, $opacity = 30, $tile = false)
  236. {
  237. if (empty($imagePath) || !file_exists($imagePath)) {
  238. throw new LogicException(self::ERROR_WATERMARK_IMAGE_ABSENT);
  239. }
  240. $this->_checkCanProcess();
  241. $opacity = $this->getWatermarkImageOpacity() ? $this->getWatermarkImageOpacity() : $opacity;
  242. $opacity = (float)number_format($opacity / 100, 1);
  243. $watermark = new Imagick($imagePath);
  244. if ($this->getWatermarkWidth() &&
  245. $this->getWatermarkHeight() &&
  246. $this->getWatermarkPosition() != self::POSITION_STRETCH
  247. ) {
  248. $watermark->resizeImage(
  249. $this->getWatermarkWidth(),
  250. $this->getWatermarkHeight(),
  251. Imagick::FILTER_CUBIC,
  252. self::BLUR_FACTOR
  253. );
  254. }
  255. if (method_exists($watermark, 'setImageOpacity')) {
  256. // available from imagick 6.3.1
  257. $watermark->setImageOpacity($opacity);
  258. } else {
  259. // go to each pixel and make it transparent
  260. $watermark->paintTransparentImage($watermark->getImagePixelColor(0, 0), 1, 65530);
  261. $watermark->evaluateImage(Imagick::EVALUATE_SUBTRACT, 1 - $opacity, Imagick::CHANNEL_ALPHA);
  262. }
  263. switch ($this->getWatermarkPosition()) {
  264. case self::POSITION_STRETCH:
  265. $watermark->sampleImage($this->_imageSrcWidth, $this->_imageSrcHeight);
  266. break;
  267. case self::POSITION_CENTER:
  268. $positionX = ($this->_imageSrcWidth - $watermark->getImageWidth())/2;
  269. $positionY = ($this->_imageSrcHeight - $watermark->getImageHeight())/2;
  270. break;
  271. case self::POSITION_TOP_RIGHT:
  272. $positionX = $this->_imageSrcWidth - $watermark->getImageWidth();
  273. break;
  274. case self::POSITION_BOTTOM_RIGHT:
  275. $positionX = $this->_imageSrcWidth - $watermark->getImageWidth();
  276. $positionY = $this->_imageSrcHeight - $watermark->getImageHeight();
  277. break;
  278. case self::POSITION_BOTTOM_LEFT:
  279. $positionY = $this->_imageSrcHeight - $watermark->getImageHeight();
  280. break;
  281. case self::POSITION_TILE:
  282. $positionX = 0;
  283. $positionY = 0;
  284. $tile = true;
  285. break;
  286. }
  287. try {
  288. if ($tile) {
  289. $offsetX = $positionX;
  290. $offsetY = $positionY;
  291. while($offsetY <= ($this->_imageSrcHeight + $watermark->getImageHeight())) {
  292. while($offsetX <= ($this->_imageSrcWidth + $watermark->getImageWidth())) {
  293. $this->_imageHandler->compositeImage(
  294. $watermark,
  295. Imagick::COMPOSITE_OVER,
  296. $offsetX,
  297. $offsetY
  298. );
  299. $offsetX += $watermark->getImageWidth();
  300. }
  301. $offsetX = $positionX;
  302. $offsetY += $watermark->getImageHeight();
  303. }
  304. } else {
  305. $this->_imageHandler->compositeImage(
  306. $watermark,
  307. Imagick::COMPOSITE_OVER,
  308. $positionX,
  309. $positionY
  310. );
  311. }
  312. } catch (ImagickException $e) {
  313. throw new Exception('Unable to create watermark.', $e->getCode(), $e);
  314. }
  315. // merge layers
  316. $this->_imageHandler->flattenImages();
  317. $watermark->clear();
  318. $watermark->destroy();
  319. }
  320. /**
  321. * Checks required dependecies
  322. *
  323. * @throws Exception if some of dependecies are missing
  324. */
  325. public function checkDependencies()
  326. {
  327. if (!class_exists('Imagick', false)) {
  328. throw new Exception("Required PHP extension 'Imagick' was not loaded.");
  329. }
  330. }
  331. /**
  332. * Reassign image dimensions
  333. */
  334. private function refreshImageDimensions()
  335. {
  336. $this->_imageSrcWidth = $this->_imageHandler->getImageWidth();
  337. $this->_imageSrcHeight = $this->_imageHandler->getImageHeight();
  338. $this->_imageHandler->setImagePage($this->_imageSrcWidth, $this->_imageSrcHeight, 0, 0);
  339. }
  340. /**
  341. * Standard destructor. Destroy stored information about image
  342. *
  343. */
  344. public function __destruct()
  345. {
  346. $this->destroy();
  347. }
  348. /**
  349. * Destroy stored information about image
  350. *
  351. * @return Varien_Image_Adapter_ImageMagick
  352. */
  353. public function destroy()
  354. {
  355. if (null !== $this->_imageHandler && $this->_imageHandler instanceof Imagick) {
  356. $this->_imageHandler->clear();
  357. $this->_imageHandler->destroy();
  358. $this->_imageHandler = null;
  359. }
  360. return $this;
  361. }
  362. /**
  363. * Returns rgb array of the specified pixel
  364. *
  365. * @param int $x
  366. * @param int $y
  367. * @return array
  368. */
  369. public function getColorAt($x, $y)
  370. {
  371. $pixel = $this->_imageHandler->getImagePixelColor($x, $y);
  372. return explode(',', $pixel->getColorAsString());
  373. }
  374. /**
  375. * Check whether the adapter can work with the image
  376. *
  377. * @throws LogicException
  378. * @return bool
  379. */
  380. protected function _checkCanProcess()
  381. {
  382. if (!$this->_canProcess()) {
  383. throw new LogicException(self::ERROR_WRONG_IMAGE);
  384. }
  385. return true;
  386. }
  387. }