Magento 2 Documentation  2.3
Documentation for Magento 2 CMS v2.3 (December 2018)
ImageMagick.php
Go to the documentation of this file.
1 <?php
7 
8 class ImageMagick extends \Magento\Framework\Image\Adapter\AbstractAdapter
9 {
13  const BLUR_FACTOR = 0.7;
14 
18  const ERROR_WATERMARK_IMAGE_ABSENT = 'Watermark Image absent.';
19 
20  const ERROR_WRONG_IMAGE = 'Image is not readable or file name is empty.';
21 
27  protected $_options = [
28  'resolution' => ['x' => 72, 'y' => 72],
29  'small_image' => ['width' => 300, 'height' => 300],
30  'sharpen' => ['radius' => 4, 'deviation' => 1],
31  ];
32 
39  public function backgroundColor($color = null)
40  {
41  if ($color) {
42  if (is_array($color)) {
43  $color = "rgb(" . join(',', $color) . ")";
44  }
45 
46  $pixel = new \ImagickPixel();
47  if (is_numeric($color)) {
48  $pixel->setColorValue($color, 1);
49  } else {
50  $pixel->setColor($color);
51  }
52  if ($this->_imageHandler) {
53  $this->_imageHandler->setImageBackgroundColor($color);
54  }
55  } else {
56  $pixel = $this->_imageHandler->getImageBackgroundColor();
57  }
58 
59  $this->imageBackgroundColor = $pixel->getColorAsString();
60 
62  }
63 
71  public function open($filename)
72  {
73  $this->_fileName = $filename;
74  $this->_checkCanProcess();
75  $this->_getFileAttributes();
76 
77  try {
78  $this->_imageHandler = new \Imagick($this->_fileName);
79  } catch (\ImagickException $e) {
80  throw new \Exception(sprintf('Unsupported image format. File: %s', $this->_fileName), $e->getCode(), $e);
81  }
82 
83  $this->backgroundColor();
84  $this->getMimeType();
85  }
86 
96  public function save($destination = null, $newName = null)
97  {
98  $fileName = $this->_prepareDestination($destination, $newName);
99 
100  $this->_applyOptions();
101  $this->_imageHandler->stripImage();
102  $this->_imageHandler->writeImage($fileName);
103  }
104 
110  protected function _applyOptions()
111  {
112  $this->_imageHandler->setImageCompressionQuality($this->quality());
113  $this->_imageHandler->setImageCompression(\Imagick::COMPRESSION_JPEG);
114  $this->_imageHandler->setImageUnits(\Imagick::RESOLUTION_PIXELSPERINCH);
115  $this->_imageHandler->setImageResolution(
116  $this->_options['resolution']['x'],
117  $this->_options['resolution']['y']
118  );
119  if (method_exists($this->_imageHandler, 'optimizeImageLayers')) {
120  $this->_imageHandler->optimizeImageLayers();
121  }
122 
123  return $this;
124  }
125 
130  public function getImage()
131  {
132  $this->_applyOptions();
133  return $this->_imageHandler->getImageBlob();
134  }
135 
143  public function resize($frameWidth = null, $frameHeight = null)
144  {
145  $this->_checkCanProcess();
146  $dims = $this->_adaptResizeValues($frameWidth, $frameHeight);
147 
148  $newImage = new \Imagick();
149  $newImage->newImage(
150  $dims['frame']['width'],
151  $dims['frame']['height'],
152  $this->_imageHandler->getImageBackgroundColor()
153  );
154 
155  $this->_imageHandler->resizeImage(
156  $dims['dst']['width'],
157  $dims['dst']['height'],
158  \Imagick::FILTER_CUBIC,
159  self::BLUR_FACTOR
160  );
161 
162  if ($this->_imageHandler->getImageWidth() < $this->_options['small_image']['width'] ||
163  $this->_imageHandler->getImageHeight() < $this->_options['small_image']['height']
164  ) {
165  $this->_imageHandler->sharpenImage(
166  $this->_options['sharpen']['radius'],
167  $this->_options['sharpen']['deviation']
168  );
169  }
170 
171  $newImage->compositeImage(
172  $this->_imageHandler,
173  \Imagick::COMPOSITE_COPYOPACITY,
174  $dims['dst']['x'],
175  $dims['dst']['y']
176  );
177 
178  $newImage->compositeImage(
179  $this->_imageHandler,
180  \Imagick::COMPOSITE_OVER,
181  $dims['dst']['x'],
182  $dims['dst']['y']
183  );
184 
185  $newImage->setImageFormat($this->_imageHandler->getImageFormat());
186  $this->_imageHandler->clear();
187  $this->_imageHandler->destroy();
188  $this->_imageHandler = $newImage;
189 
190  $this->refreshImageDimensions();
191  }
192 
199  public function rotate($angle)
200  {
201  $this->_checkCanProcess();
202  // compatibility with GD2 adapter
203  $angle = 360 - $angle;
204  $pixel = new \ImagickPixel();
205  $pixel->setColor("rgb(" . $this->imageBackgroundColor . ")");
206 
207  $this->_imageHandler->rotateImage($pixel, $angle);
208  $this->refreshImageDimensions();
209  }
210 
220  public function crop($top = 0, $left = 0, $right = 0, $bottom = 0)
221  {
222  if ($left == 0 && $top == 0 && $right == 0 && $bottom == 0 || !$this->_canProcess()) {
223  return false;
224  }
225 
226  $newWidth = $this->_imageSrcWidth - $left - $right;
227  $newHeight = $this->_imageSrcHeight - $top - $bottom;
228 
229  $this->_imageHandler->cropImage($newWidth, $newHeight, $left, $top);
230  $this->refreshImageDimensions();
231  return true;
232  }
233 
248  public function watermark($imagePath, $positionX = 0, $positionY = 0, $opacity = 30, $tile = false)
249  {
250  if (empty($imagePath) || !file_exists($imagePath)) {
251  throw new \LogicException(self::ERROR_WATERMARK_IMAGE_ABSENT);
252  }
253  $this->_checkCanProcess();
254 
255  $opacity = $this->getWatermarkImageOpacity() ? $this->getWatermarkImageOpacity() : $opacity;
256 
257  $opacity = (double)number_format($opacity / 100, 1);
258  $watermark = new \Imagick($imagePath);
259 
260  if ($this->getWatermarkWidth() &&
261  $this->getWatermarkHeight() &&
262  $this->getWatermarkPosition() != self::POSITION_STRETCH
263  ) {
264  $watermark->resizeImage(
265  $this->getWatermarkWidth(),
266  $this->getWatermarkHeight(),
267  \Imagick::FILTER_CUBIC,
268  self::BLUR_FACTOR
269  );
270  }
271 
272  if (method_exists($watermark, 'getImageAlphaChannel')) {
273  // available from imagick 6.4.0
274  if ($watermark->getImageAlphaChannel() == 0) {
275  $watermark->setImageAlphaChannel(\Imagick::ALPHACHANNEL_OPAQUE);
276  }
277  }
278 
279  $compositeChannels = \Imagick::CHANNEL_ALL;
280  $watermark->evaluateImage(\Imagick::EVALUATE_MULTIPLY, $opacity, \Imagick::CHANNEL_OPACITY);
281  $compositeChannels &= ~(\Imagick::CHANNEL_OPACITY);
282 
283  switch ($this->getWatermarkPosition()) {
285  $watermark->sampleImage($this->_imageSrcWidth, $this->_imageSrcHeight);
286  break;
288  $positionX = ($this->_imageSrcWidth - $watermark->getImageWidth()) / 2;
289  $positionY = ($this->_imageSrcHeight - $watermark->getImageHeight()) / 2;
290  break;
292  $positionX = $this->_imageSrcWidth - $watermark->getImageWidth();
293  break;
295  $positionX = $this->_imageSrcWidth - $watermark->getImageWidth();
296  $positionY = $this->_imageSrcHeight - $watermark->getImageHeight();
297  break;
299  $positionY = $this->_imageSrcHeight - $watermark->getImageHeight();
300  break;
301  case self::POSITION_TILE:
302  $positionX = 0;
303  $positionY = 0;
304  $tile = true;
305  break;
306  }
307 
308  try {
309  if ($tile) {
310  $offsetX = $positionX;
311  $offsetY = $positionY;
312  while ($offsetY <= $this->_imageSrcHeight + $watermark->getImageHeight()) {
313  while ($offsetX <= $this->_imageSrcWidth + $watermark->getImageWidth()) {
314  $this->_imageHandler->compositeImage(
315  $watermark,
316  \Imagick::COMPOSITE_OVER,
317  $offsetX,
318  $offsetY,
319  $compositeChannels
320  );
321  $offsetX += $watermark->getImageWidth();
322  }
323  $offsetX = $positionX;
324  $offsetY += $watermark->getImageHeight();
325  }
326  } else {
327  $this->_imageHandler->compositeImage(
328  $watermark,
329  \Imagick::COMPOSITE_OVER,
330  $positionX,
331  $positionY,
332  $compositeChannels
333  );
334  }
335  } catch (\ImagickException $e) {
336  throw new \Exception('Unable to create watermark.', $e->getCode(), $e);
337  }
338 
339  // merge layers
340  $this->_imageHandler->mergeImageLayers(\Imagick::LAYERMETHOD_FLATTEN);
341  $watermark->clear();
342  $watermark->destroy();
343  }
344 
351  public function checkDependencies()
352  {
353  if (!class_exists('\Imagick', false)) {
354  throw new \Exception("Required PHP extension 'Imagick' was not loaded.");
355  }
356  }
357 
363  public function refreshImageDimensions()
364  {
365  $this->_imageSrcWidth = $this->_imageHandler->getImageWidth();
366  $this->_imageSrcHeight = $this->_imageHandler->getImageHeight();
367  $this->_imageHandler->setImagePage($this->_imageSrcWidth, $this->_imageSrcHeight, 0, 0);
368  }
369 
373  public function __destruct()
374  {
375  $this->destroy();
376  }
377 
383  public function destroy()
384  {
385  if (null !== $this->_imageHandler && $this->_imageHandler instanceof \Imagick) {
386  $this->_imageHandler->clear();
387  $this->_imageHandler->destroy();
388  $this->_imageHandler = null;
389  }
390  return $this;
391  }
392 
400  public function getColorAt($x, $y)
401  {
402  $pixel = $this->_imageHandler->getImagePixelColor($x, $y);
403 
404  $color = $pixel->getColor();
405  $rgbaColor = [
406  'red' => $color['r'],
407  'green' => $color['g'],
408  'blue' => $color['b'],
409  'alpha' => (1 - $color['a']) * 127,
410  ];
411  return $rgbaColor;
412  }
413 
420  protected function _checkCanProcess()
421  {
422  if (!$this->_canProcess()) {
423  throw new \LogicException(self::ERROR_WRONG_IMAGE);
424  }
425  return true;
426  }
427 
435  public function createPngFromString($text, $font = '')
436  {
437  $image = $this->_getImagickObject();
438  $draw = $this->_getImagickDrawObject();
439  $color = $this->_getImagickPixelObject('#000000');
440  $background = $this->_getImagickPixelObject('#ffffff00');
441  // Transparent
442 
443  if (!empty($font)) {
444  if (method_exists($image, 'setFont')) {
445  $image->setFont($font);
446  } elseif (method_exists($draw, 'setFont')) {
447  $draw->setFont($font);
448  }
449  }
450 
451  // Font size for ImageMagick is set in pixels, while the for GD2 it is in points. 3/4 is ratio between them
452  $draw->setFontSize($this->_fontSize * 4 / 3);
453  $draw->setFillColor($color);
454  $draw->setStrokeAntialias(true);
455  $draw->setTextAntialias(true);
456 
457  $metrics = $image->queryFontMetrics($draw, $text);
458 
459  $draw->annotation(0, $metrics['ascender'], $text);
460 
461  $height = abs($metrics['ascender']) + abs($metrics['descender']);
462  $image->newImage($metrics['textWidth'], $height, $background);
463  $this->_fileType = IMAGETYPE_PNG;
464  $image->setImageFormat('png');
465  $image->drawImage($draw);
466  $this->_imageHandler = $image;
467 
468  return $this;
469  }
470 
477  protected function _getImagickObject($files = null)
478  {
479  return new \Imagick($files);
480  }
481 
487  protected function _getImagickDrawObject()
488  {
489  return new \ImagickDraw();
490  }
491 
498  protected function _getImagickPixelObject($color = null)
499  {
500  return new \ImagickPixel($color);
501  }
502 }
crop($top=0, $left=0, $right=0, $bottom=0)
elseif(isset( $params[ 'redirect_parent']))
Definition: iframe.phtml:17
save($destination=null, $newName=null)
Definition: ImageMagick.php:96
watermark($imagePath, $positionX=0, $positionY=0, $opacity=30, $tile=false)
endifif( $block->getLastPageNum()>1)( 'Page') ?></strong >< ul class $text
Definition: pager.phtml:43
$fileName
Definition: translate.phtml:15
_prepareDestination($destination=null, $newName=null)
foreach($appDirs as $dir) $files
resize($frameWidth=null, $frameHeight=null)