vendor/imagine/imagine/src/Imagick/Image.php line 287

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of the Imagine package.
  4.  *
  5.  * (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE
  8.  * file that was distributed with this source code.
  9.  */
  10. namespace Imagine\Imagick;
  11. use Imagine\Driver\InfoProvider;
  12. use Imagine\Exception\InvalidArgumentException;
  13. use Imagine\Exception\OutOfBoundsException;
  14. use Imagine\Exception\RuntimeException;
  15. use Imagine\Factory\ClassFactoryInterface;
  16. use Imagine\Image\AbstractImage;
  17. use Imagine\Image\BoxInterface;
  18. use Imagine\Image\Fill\FillInterface;
  19. use Imagine\Image\Fill\Gradient\Horizontal;
  20. use Imagine\Image\Fill\Gradient\Linear;
  21. use Imagine\Image\Format;
  22. use Imagine\Image\ImageInterface;
  23. use Imagine\Image\Metadata\MetadataBag;
  24. use Imagine\Image\Palette\Color\ColorInterface;
  25. use Imagine\Image\Palette\PaletteInterface;
  26. use Imagine\Image\Point;
  27. use Imagine\Image\PointInterface;
  28. use Imagine\Image\ProfileInterface;
  29. use Imagine\Utils\ErrorHandling;
  30. /**
  31.  * Image implementation using the Imagick PHP extension.
  32.  */
  33. final class Image extends AbstractImage implements InfoProvider
  34. {
  35.     /**
  36.      * @var \Imagick
  37.      */
  38.     private $imagick;
  39.     /**
  40.      * @var \Imagine\Imagick\Layers|null
  41.      */
  42.     private $layers;
  43.     /**
  44.      * @var \Imagine\Image\Palette\PaletteInterface
  45.      */
  46.     private $palette;
  47.     /**
  48.      * @var array
  49.      */
  50.     private static $colorspaceMapping = array(
  51.         PaletteInterface::PALETTE_CMYK => \Imagick::COLORSPACE_CMYK,
  52.         PaletteInterface::PALETTE_RGB => \Imagick::COLORSPACE_RGB,
  53.         PaletteInterface::PALETTE_GRAYSCALE => \Imagick::COLORSPACE_GRAY,
  54.     );
  55.     /**
  56.      * Constructs a new Image instance.
  57.      *
  58.      * @param \Imagick $imagick
  59.      * @param \Imagine\Image\Palette\PaletteInterface $palette
  60.      * @param \Imagine\Image\Metadata\MetadataBag $metadata
  61.      */
  62.     public function __construct(\Imagick $imagickPaletteInterface $paletteMetadataBag $metadata)
  63.     {
  64.         $this->metadata $metadata;
  65.         $this->imagick $imagick;
  66.         if (static::getDriverInfo()->hasFeature(DriverInfo::FEATURE_COLORSPACECONVERSION)) {
  67.             $this->setColorspace($palette);
  68.         }
  69.         $this->palette $palette;
  70.     }
  71.     /**
  72.      * {@inheritdoc}
  73.      *
  74.      * @see \Imagine\Image\AbstractImage::__clone()
  75.      */
  76.     public function __clone()
  77.     {
  78.         parent::__clone();
  79.         if ($this->imagick instanceof \Imagick) {
  80.             $this->imagick $this->cloneImagick();
  81.         }
  82.         $this->palette = clone $this->palette;
  83.         if ($this->layers !== null) {
  84.             $this->layers $this->getClassFactory()->createLayers(ClassFactoryInterface::HANDLE_IMAGICK$this$this->layers->key());
  85.         }
  86.     }
  87.     /**
  88.      * Destroys allocated imagick resources.
  89.      */
  90.     public function __destruct()
  91.     {
  92.         if ($this->imagick instanceof \Imagick) {
  93.             $this->imagick->clear();
  94.             $this->imagick->destroy();
  95.         }
  96.     }
  97.     /**
  98.      * {@inheritdoc}
  99.      *
  100.      * @see \Imagine\Driver\InfoProvider::getDriverInfo()
  101.      * @since 1.3.0
  102.      */
  103.     public static function getDriverInfo($required true)
  104.     {
  105.         return DriverInfo::get($required);
  106.     }
  107.     /**
  108.      * Returns the underlying \Imagick instance.
  109.      *
  110.      * @return \Imagick
  111.      */
  112.     public function getImagick()
  113.     {
  114.         return $this->imagick;
  115.     }
  116.     /**
  117.      * {@inheritdoc}
  118.      *
  119.      * @see \Imagine\Image\ManipulatorInterface::copy()
  120.      */
  121.     public function copy()
  122.     {
  123.         try {
  124.             return clone $this;
  125.         } catch (\ImagickException $e) {
  126.             throw new RuntimeException('Copy operation failed'$e->getCode(), $e);
  127.         }
  128.     }
  129.     /**
  130.      * {@inheritdoc}
  131.      *
  132.      * @see \Imagine\Image\ManipulatorInterface::crop()
  133.      */
  134.     public function crop(PointInterface $startBoxInterface $size)
  135.     {
  136.         if (!$start->in($this->getSize())) {
  137.             throw new OutOfBoundsException('Crop coordinates must start at minimum 0, 0 position from top left corner, crop height and width must be positive integers and must not exceed the current image borders');
  138.         }
  139.         try {
  140.             if ($this->layers()->count() > 1) {
  141.                 // Crop each layer separately
  142.                 $this->imagick $this->imagick->coalesceImages();
  143.                 foreach ($this->imagick as $frame) {
  144.                     $frame->cropImage($size->getWidth(), $size->getHeight(), $start->getX(), $start->getY());
  145.                     // Reset canvas for gif format
  146.                     $frame->setImagePage(0000);
  147.                 }
  148.                 $this->imagick $this->imagick->deconstructImages();
  149.             } else {
  150.                 $this->imagick->cropImage($size->getWidth(), $size->getHeight(), $start->getX(), $start->getY());
  151.                 // Reset canvas for gif format
  152.                 $this->imagick->setImagePage(0000);
  153.             }
  154.         } catch (\ImagickException $e) {
  155.             throw new RuntimeException('Crop operation failed'$e->getCode(), $e);
  156.         }
  157.         return $this;
  158.     }
  159.     /**
  160.      * {@inheritdoc}
  161.      *
  162.      * @see \Imagine\Image\ManipulatorInterface::flipHorizontally()
  163.      */
  164.     public function flipHorizontally()
  165.     {
  166.         try {
  167.             $this->imagick->flopImage();
  168.         } catch (\ImagickException $e) {
  169.             throw new RuntimeException('Horizontal Flip operation failed'$e->getCode(), $e);
  170.         }
  171.         return $this;
  172.     }
  173.     /**
  174.      * {@inheritdoc}
  175.      *
  176.      * @see \Imagine\Image\ManipulatorInterface::flipVertically()
  177.      */
  178.     public function flipVertically()
  179.     {
  180.         try {
  181.             $this->imagick->flipImage();
  182.         } catch (\ImagickException $e) {
  183.             throw new RuntimeException('Vertical flip operation failed'$e->getCode(), $e);
  184.         }
  185.         return $this;
  186.     }
  187.     /**
  188.      * {@inheritdoc}
  189.      *
  190.      * @see \Imagine\Image\ManipulatorInterface::strip()
  191.      */
  192.     public function strip()
  193.     {
  194.         try {
  195.             try {
  196.                 $this->profile($this->palette->profile());
  197.             } catch (\Exception $e) {
  198.                 // here we discard setting the profile as the previous incorporated profile
  199.                 // is corrupted, let's now strip the image
  200.             }
  201.             $this->imagick->stripImage();
  202.         } catch (\ImagickException $e) {
  203.             throw new RuntimeException('Strip operation failed'$e->getCode(), $e);
  204.         }
  205.         return $this;
  206.     }
  207.     /**
  208.      * {@inheritdoc}
  209.      *
  210.      * @see \Imagine\Image\ManipulatorInterface::paste()
  211.      */
  212.     public function paste(ImageInterface $imagePointInterface $start$alpha 100)
  213.     {
  214.         if (!$image instanceof self) {
  215.             throw new InvalidArgumentException(sprintf('Imagick\Image can only paste() Imagick\Image instances, %s given'get_class($image)));
  216.         }
  217.         $alpha = (int) round($alpha);
  218.         if ($alpha || $alpha 100) {
  219.             throw new InvalidArgumentException(sprintf('The %1$s argument can range from %2$d to %3$d, but you specified %4$d.''$alpha'0100$alpha));
  220.         }
  221.         if ($alpha === 100) {
  222.             $pasteMe $image->imagick;
  223.         } elseif ($alpha 0) {
  224.             $pasteMe $image->cloneImagick();
  225.             // setImageOpacity was replaced with setImageAlpha in php-imagick v3.4.3
  226.             if (method_exists($pasteMe'setImageAlpha')) {
  227.                 $pasteMe->setImageAlpha($alpha 100);
  228.             } else {
  229.                 ErrorHandling::ignoring(E_DEPRECATED, function () use ($pasteMe$alpha) {
  230.                     $pasteMe->setImageOpacity($alpha 100);
  231.                 });
  232.             }
  233.         } else {
  234.             $pasteMe null;
  235.         }
  236.         if ($pasteMe !== null) {
  237.             try {
  238.                 $this->imagick->compositeImage($pasteMe, \Imagick::COMPOSITE_DEFAULT$start->getX(), $start->getY());
  239.                 $error null;
  240.             } catch (\ImagickException $e) {
  241.                 $error $e;
  242.             }
  243.             if ($pasteMe !== $image->imagick) {
  244.                 $pasteMe->clear();
  245.                 $pasteMe->destroy();
  246.             }
  247.             if ($error !== null) {
  248.                 throw new RuntimeException('Paste operation failed'$error->getCode(), $error);
  249.             }
  250.         }
  251.         return $this;
  252.     }
  253.     /**
  254.      * {@inheritdoc}
  255.      *
  256.      * @see \Imagine\Image\ManipulatorInterface::resize()
  257.      */
  258.     public function resize(BoxInterface $size$filter ImageInterface::FILTER_UNDEFINED)
  259.     {
  260.         try {
  261.             if ($this->layers()->count() > 1) {
  262.                 $this->imagick $this->imagick->coalesceImages();
  263.                 foreach ($this->imagick as $frame) {
  264.                     $frame->resizeImage($size->getWidth(), $size->getHeight(), $this->getFilter($filter), 1);
  265.                 }
  266.                 $this->imagick $this->imagick->deconstructImages();
  267.             } else {
  268.                 $this->imagick->resizeImage($size->getWidth(), $size->getHeight(), $this->getFilter($filter), 1);
  269.             }
  270.         } catch (\ImagickException $e) {
  271.             throw new RuntimeException('Resize operation failed'$e->getCode(), $e);
  272.         }
  273.         return $this;
  274.     }
  275.     /**
  276.      * {@inheritdoc}
  277.      *
  278.      * @see \Imagine\Image\ManipulatorInterface::rotate()
  279.      */
  280.     public function rotate($angleColorInterface $background null)
  281.     {
  282.         if ($background === null) {
  283.             $background $this->palette->color('fff');
  284.         }
  285.         try {
  286.             $pixel $this->getColor($background);
  287.             $this->imagick->rotateimage($pixel$angle);
  288.             $this->imagick->setImagePage(0000);
  289.             $pixel->clear();
  290.             $pixel->destroy();
  291.         } catch (\ImagickException $e) {
  292.             throw new RuntimeException('Rotate operation failed'$e->getCode(), $e);
  293.         }
  294.         return $this;
  295.     }
  296.     /**
  297.      * {@inheritdoc}
  298.      *
  299.      * @see \Imagine\Image\ManipulatorInterface::save()
  300.      */
  301.     public function save($path null, array $options = array())
  302.     {
  303.         $path $path === null $this->imagick->getImageFilename() : $path;
  304.         if ($path === null) {
  305.             throw new RuntimeException('You can omit save path only if image has been open from a file');
  306.         }
  307.         try {
  308.             $this->prepareOutput($options$path);
  309.             $this->imagick->writeImages($pathtrue);
  310.         } catch (\ImagickException $e) {
  311.             throw new RuntimeException('Save operation failed'$e->getCode(), $e);
  312.         }
  313.         return $this;
  314.     }
  315.     /**
  316.      * {@inheritdoc}
  317.      *
  318.      * @see \Imagine\Image\ManipulatorInterface::show()
  319.      */
  320.     public function show($format, array $options = array())
  321.     {
  322.         $formatInfo = static::getDriverInfo()->getSupportedFormats()->find($format);
  323.         if ($formatInfo === null) {
  324.             throw new InvalidArgumentException(sprintf(
  325.                 'Displaying an image in "%s" format is not supported, please use one of the following formats: "%s"',
  326.                 $format,
  327.                 implode('", "', static::getDriverInfo()->getSupportedFormats()->getAllIDs())
  328.             ));
  329.         }
  330.         header('Content-type: ' $formatInfo->getMimeType());
  331.         echo $this->get($format$options);
  332.         return $this;
  333.     }
  334.     /**
  335.      * {@inheritdoc}
  336.      *
  337.      * @see \Imagine\Image\ImageInterface::get()
  338.      */
  339.     public function get($format, array $options = array())
  340.     {
  341.         try {
  342.             $options['format'] = $format;
  343.             $this->prepareOutput($options);
  344.         } catch (\ImagickException $e) {
  345.             throw new RuntimeException('Get operation failed'$e->getCode(), $e);
  346.         }
  347.         return $this->imagick->getImagesBlob();
  348.     }
  349.     /**
  350.      * {@inheritdoc}
  351.      *
  352.      * @see \Imagine\Image\ImageInterface::interlace()
  353.      */
  354.     public function interlace($scheme)
  355.     {
  356.         static $supportedInterlaceSchemes = array(
  357.             ImageInterface::INTERLACE_NONE => \Imagick::INTERLACE_NO,
  358.             ImageInterface::INTERLACE_LINE => \Imagick::INTERLACE_LINE,
  359.             ImageInterface::INTERLACE_PLANE => \Imagick::INTERLACE_PLANE,
  360.             ImageInterface::INTERLACE_PARTITION => \Imagick::INTERLACE_PARTITION,
  361.         );
  362.         if (!array_key_exists($scheme$supportedInterlaceSchemes)) {
  363.             throw new InvalidArgumentException('Unsupported interlace type');
  364.         }
  365.         $this->imagick->setInterlaceScheme($supportedInterlaceSchemes[$scheme]);
  366.         return $this;
  367.     }
  368.     /**
  369.      * @param array $options
  370.      * @param string $path
  371.      */
  372.     private function prepareOutput(array $options$path null)
  373.     {
  374.         if (isset($options['animated']) && $options['animated'] === true) {
  375.             $format = isset($options['format']) ? $options['format'] : Format::ID_GIF;
  376.             $delay = isset($options['animated.delay']) ? $options['animated.delay'] : null;
  377.             $loops = isset($options['animated.loops']) ? $options['animated.loops'] : 0;
  378.             $options['flatten'] = false;
  379.             $this->layers()->animate($format$delay$loops);
  380.         } else {
  381.             $this->layers()->merge();
  382.         }
  383.         $this->imagick $this->applyImageOptions($this->imagick$options$path);
  384.         // flatten only if image has multiple layers
  385.         if ((!isset($options['flatten']) || $options['flatten'] === true) && $this->layers()->count() > 1) {
  386.             $this->flatten();
  387.         }
  388.         if (isset($options['format'])) {
  389.             $this->imagick->setImageFormat($options['format']);
  390.         }
  391.     }
  392.     /**
  393.      * {@inheritdoc}
  394.      *
  395.      * @see \Imagine\Image\ImageInterface::__toString()
  396.      */
  397.     public function __toString()
  398.     {
  399.         return $this->get(Format::ID_PNG);
  400.     }
  401.     /**
  402.      * {@inheritdoc}
  403.      *
  404.      * @see \Imagine\Image\ImageInterface::draw()
  405.      */
  406.     public function draw()
  407.     {
  408.         return $this->getClassFactory()->createDrawer(ClassFactoryInterface::HANDLE_IMAGICK$this->imagick);
  409.     }
  410.     /**
  411.      * {@inheritdoc}
  412.      *
  413.      * @see \Imagine\Image\ImageInterface::effects()
  414.      */
  415.     public function effects()
  416.     {
  417.         return $this->getClassFactory()->createEffects(ClassFactoryInterface::HANDLE_IMAGICK$this->imagick);
  418.     }
  419.     /**
  420.      * {@inheritdoc}
  421.      *
  422.      * @see \Imagine\Image\ImageInterface::getSize()
  423.      */
  424.     public function getSize()
  425.     {
  426.         try {
  427.             $i $this->imagick->getIteratorIndex();
  428.             $this->imagick->rewind();
  429.             $width $this->imagick->getImageWidth();
  430.             $height $this->imagick->getImageHeight();
  431.             $this->imagick->setIteratorIndex($i);
  432.         } catch (\ImagickException $e) {
  433.             throw new RuntimeException('Could not get size'$e->getCode(), $e);
  434.         }
  435.         return $this->getClassFactory()->createBox($width$height);
  436.     }
  437.     /**
  438.      * {@inheritdoc}
  439.      *
  440.      * @see \Imagine\Image\ManipulatorInterface::applyMask()
  441.      */
  442.     public function applyMask(ImageInterface $mask)
  443.     {
  444.         if (!$mask instanceof self) {
  445.             throw new InvalidArgumentException('Can only apply instances of Imagine\Imagick\Image as masks');
  446.         }
  447.         $size $this->getSize();
  448.         $maskSize $mask->getSize();
  449.         if ($size != $maskSize) {
  450.             throw new InvalidArgumentException(sprintf('The given mask doesn\'t match current image\'s size, Current mask\'s dimensions are %s, while image\'s dimensions are %s'$maskSize$size));
  451.         }
  452.         $mask $mask->mask();
  453.         $mask->imagick->negateImage(true);
  454.         try {
  455.             // remove transparent areas of the original from the mask
  456.             $mask->imagick->compositeImage($this->imagick, \Imagick::COMPOSITE_DSTIN00);
  457.             $this->imagick->compositeImage($mask->imagick, \Imagick::COMPOSITE_COPYOPACITY00);
  458.             $mask->imagick->clear();
  459.             $mask->imagick->destroy();
  460.         } catch (\ImagickException $e) {
  461.             throw new RuntimeException('Apply mask operation failed'$e->getCode(), $e);
  462.         }
  463.         return $this;
  464.     }
  465.     /**
  466.      * {@inheritdoc}
  467.      *
  468.      * @see \Imagine\Image\ImageInterface::mask()
  469.      */
  470.     public function mask()
  471.     {
  472.         $mask $this->copy();
  473.         try {
  474.             $mask->imagick->modulateImage(1000100);
  475.             $mask->imagick->setImageMatte(false);
  476.         } catch (\ImagickException $e) {
  477.             throw new RuntimeException('Mask operation failed'$e->getCode(), $e);
  478.         }
  479.         return $mask;
  480.     }
  481.     /**
  482.      * {@inheritdoc}
  483.      *
  484.      * @see \Imagine\Image\ManipulatorInterface::fill()
  485.      */
  486.     public function fill(FillInterface $fill)
  487.     {
  488.         try {
  489.             if ($this->isLinearOpaque($fill)) {
  490.                 $this->applyFastLinear($fill);
  491.             } else {
  492.                 $iterator $this->imagick->getPixelIterator();
  493.                 foreach ($iterator as $y => $pixels) {
  494.                     foreach ($pixels as $x => $pixel) {
  495.                         $color $fill->getColor(new Point($x$y));
  496.                         $pixel->setColor((string) $color);
  497.                         $pixel->setColorValue(\Imagick::COLOR_ALPHA$color->getAlpha() / 100);
  498.                     }
  499.                     $iterator->syncIterator();
  500.                 }
  501.             }
  502.         } catch (\ImagickException $e) {
  503.             throw new RuntimeException('Fill operation failed'$e->getCode(), $e);
  504.         }
  505.         return $this;
  506.     }
  507.     /**
  508.      * {@inheritdoc}
  509.      *
  510.      * @see \Imagine\Image\ImageInterface::histogram()
  511.      */
  512.     public function histogram()
  513.     {
  514.         try {
  515.             $pixels $this->imagick->getImageHistogram();
  516.         } catch (\ImagickException $e) {
  517.             throw new RuntimeException('Error while fetching histogram'$e->getCode(), $e);
  518.         }
  519.         $image $this;
  520.         return array_values(array_unique(array_map(function (\ImagickPixel $pixel) use ($image) {
  521.             return $image->pixelToColor($pixel);
  522.         }, $pixels)));
  523.     }
  524.     /**
  525.      * {@inheritdoc}
  526.      *
  527.      * @see \Imagine\Image\ImageInterface::getColorAt()
  528.      */
  529.     public function getColorAt(PointInterface $point)
  530.     {
  531.         if (!$point->in($this->getSize())) {
  532.             throw new RuntimeException(sprintf('Error getting color at point [%s,%s]. The point must be inside the image of size [%s,%s]'$point->getX(), $point->getY(), $this->getSize()->getWidth(), $this->getSize()->getHeight()));
  533.         }
  534.         try {
  535.             $pixel $this->imagick->getImagePixelColor($point->getX(), $point->getY());
  536.         } catch (\ImagickException $e) {
  537.             throw new RuntimeException('Error while getting image pixel color'$e->getCode(), $e);
  538.         }
  539.         return $this->pixelToColor($pixel);
  540.     }
  541.     /**
  542.      * Returns a color given a pixel, depending the Palette context.
  543.      *
  544.      * Note : this method is public for PHP 5.3 compatibility
  545.      *
  546.      * @param \ImagickPixel $pixel
  547.      *
  548.      * @throws \Imagine\Exception\InvalidArgumentException In case a unknown color is requested
  549.      *
  550.      * @return \Imagine\Image\Palette\Color\ColorInterface
  551.      */
  552.     public function pixelToColor(\ImagickPixel $pixel)
  553.     {
  554.         static $colorMapping = array(
  555.             ColorInterface::COLOR_RED => \Imagick::COLOR_RED,
  556.             ColorInterface::COLOR_GREEN => \Imagick::COLOR_GREEN,
  557.             ColorInterface::COLOR_BLUE => \Imagick::COLOR_BLUE,
  558.             ColorInterface::COLOR_CYAN => \Imagick::COLOR_CYAN,
  559.             ColorInterface::COLOR_MAGENTA => \Imagick::COLOR_MAGENTA,
  560.             ColorInterface::COLOR_YELLOW => \Imagick::COLOR_YELLOW,
  561.             ColorInterface::COLOR_KEYLINE => \Imagick::COLOR_BLACK,
  562.             // There is no gray component in \Imagick, let's use one of the RGB comp
  563.             ColorInterface::COLOR_GRAY => \Imagick::COLOR_RED,
  564.         );
  565.         $alpha $this->palette->supportsAlpha() ? (int) round($pixel->getColorValue(\Imagick::COLOR_ALPHA) * 100) : null;
  566.         if ($alpha) {
  567.             $alpha min(max($alpha0), 100);
  568.         }
  569.         $multiplier $this->palette()->getChannelsMaxValue();
  570.         return $this->palette->color(array_map(function ($color) use ($multiplier$pixel$colorMapping) {
  571.             if (!isset($colorMapping[$color])) {
  572.                 throw new InvalidArgumentException(sprintf('Color %s is not mapped in Imagick'$color));
  573.             }
  574.             return $pixel->getColorValue($colorMapping[$color]) * $multiplier;
  575.         }, $this->palette->pixelDefinition()), $alpha);
  576.     }
  577.     /**
  578.      * {@inheritdoc}
  579.      *
  580.      * @see \Imagine\Image\ImageInterface::layers()
  581.      */
  582.     public function layers()
  583.     {
  584.         if ($this->layers === null) {
  585.             $this->layers $this->getClassFactory()->createLayers(ClassFactoryInterface::HANDLE_IMAGICK$this);
  586.         }
  587.         return $this->layers;
  588.     }
  589.     /**
  590.      * {@inheritdoc}
  591.      *
  592.      * @see \Imagine\Image\ImageInterface::usePalette()
  593.      */
  594.     public function usePalette(PaletteInterface $palette)
  595.     {
  596.         if ($this->palette->name() === $palette->name()) {
  597.             return $this;
  598.         }
  599.         if (!isset(static::$colorspaceMapping[$palette->name()])) {
  600.             throw new InvalidArgumentException(sprintf('The palette %s is not supported by Imagick driver'$palette->name()));
  601.         }
  602.         static::getDriverInfo()->requireFeature(DriverInfo::FEATURE_COLORSPACECONVERSION);
  603.         try {
  604.             try {
  605.                 $hasICCProfile = (bool) $this->imagick->getImageProfile('icc');
  606.             } catch (\ImagickException $e) {
  607.                 $hasICCProfile false;
  608.             }
  609.             if (!$hasICCProfile) {
  610.                 $this->profile($this->palette->profile());
  611.             }
  612.             $this->profile($palette->profile());
  613.             $this->setColorspace($palette);
  614.         } catch (\ImagickException $e) {
  615.             throw new RuntimeException('Failed to set colorspace'$e->getCode(), $e);
  616.         }
  617.         return $this;
  618.     }
  619.     /**
  620.      * {@inheritdoc}
  621.      *
  622.      * @see \Imagine\Image\ImageInterface::palette()
  623.      */
  624.     public function palette()
  625.     {
  626.         return $this->palette;
  627.     }
  628.     /**
  629.      * {@inheritdoc}
  630.      *
  631.      * @see \Imagine\Image\ImageInterface::profile()
  632.      */
  633.     public function profile(ProfileInterface $profile)
  634.     {
  635.         static::getDriverInfo()->requireFeature(DriverInfo::FEATURE_COLORPROFILES);
  636.         try {
  637.             $this->imagick->profileImage('icc'$profile->data());
  638.         } catch (\ImagickException $e) {
  639.             throw new RuntimeException(sprintf('Unable to add profile %s to image'$profile->name()), $e->getCode(), $e);
  640.         }
  641.         return $this;
  642.     }
  643.     /**
  644.      * Flatten the image.
  645.      */
  646.     private function flatten()
  647.     {
  648.         // @see https://github.com/mkoppanen/imagick/issues/45
  649.         try {
  650.             if (method_exists($this->imagick'mergeImageLayers') && defined('Imagick::LAYERMETHOD_UNDEFINED')) {
  651.                 $this->imagick $this->imagick->mergeImageLayers(\Imagick::LAYERMETHOD_UNDEFINED);
  652.             } elseif (method_exists($this->imagick'flattenImages')) {
  653.                 $this->imagick $this->imagick->flattenImages();
  654.             }
  655.         } catch (\ImagickException $e) {
  656.             throw new RuntimeException('Flatten operation failed'$e->getCode(), $e);
  657.         }
  658.     }
  659.     /**
  660.      * Applies options before save or output.
  661.      *
  662.      * @param \Imagick $image
  663.      * @param array $options
  664.      * @param string $path
  665.      *
  666.      * @throws \Imagine\Exception\InvalidArgumentException
  667.      * @throws \Imagine\Exception\RuntimeException
  668.      *
  669.      * @return \Imagick
  670.      */
  671.     private function applyImageOptions(\Imagick $image, array $options$path)
  672.     {
  673.         if (isset($options['format'])) {
  674.             $format $options['format'];
  675.         } elseif ('' !== $extension pathinfo($path, \PATHINFO_EXTENSION)) {
  676.             $format $extension;
  677.         } else {
  678.             $format pathinfo($image->getImageFilename(), \PATHINFO_EXTENSION);
  679.         }
  680.         $formatInfo Format::get($format);
  681.         switch ($formatInfo === null '' $formatInfo->getID()) {
  682.             case Format::ID_JPEG:
  683.                 if (!isset($options['jpeg_quality'])) {
  684.                     if (isset($options['quality'])) {
  685.                         $options['jpeg_quality'] = $options['quality'];
  686.                     }
  687.                 }
  688.                 if (isset($options['jpeg_quality'])) {
  689.                     $image->setimagecompressionquality($options['jpeg_quality']);
  690.                     $image->setcompressionquality($options['jpeg_quality']);
  691.                 }
  692.                 if (isset($options['jpeg_sampling_factors'])) {
  693.                     if (!is_array($options['jpeg_sampling_factors']) || \count($options['jpeg_sampling_factors']) < 1) {
  694.                         throw new InvalidArgumentException('jpeg_sampling_factors option should be an array of integers');
  695.                     }
  696.                     $image->setSamplingFactors(array_map(function ($factor) {
  697.                         return (int) $factor;
  698.                     }, $options['jpeg_sampling_factors']));
  699.                 }
  700.                 break;
  701.             case Format::ID_PNG:
  702.                 if (!isset($options['png_compression_level'])) {
  703.                     if (isset($options['quality'])) {
  704.                         $options['png_compression_level'] = round((100 $options['quality']) * 100);
  705.                     }
  706.                 }
  707.                 if (isset($options['png_compression_level'])) {
  708.                     if ($options['png_compression_level'] < || $options['png_compression_level'] > 9) {
  709.                         throw new InvalidArgumentException('png_compression_level option should be an integer from 0 to 9');
  710.                     }
  711.                 }
  712.                 if (isset($options['png_compression_filter'])) {
  713.                     if ($options['png_compression_filter'] < || $options['png_compression_filter'] > 9) {
  714.                         throw new InvalidArgumentException('png_compression_filter option should be an integer from 0 to 9');
  715.                     }
  716.                 }
  717.                 if (isset($options['png_compression_level']) || isset($options['png_compression_filter'])) {
  718.                     // first digit: compression level (default: 7)
  719.                     $compression = isset($options['png_compression_level']) ? $options['png_compression_level'] * 10 70;
  720.                     // second digit: compression filter (default: 5)
  721.                     $compression += isset($options['png_compression_filter']) ? $options['png_compression_filter'] : 5;
  722.                     $image->setimagecompressionquality($compression);
  723.                     $image->setcompressionquality($compression);
  724.                 }
  725.                 break;
  726.             case Format::ID_WEBP:
  727.                 if (!isset($options['webp_quality'])) {
  728.                     if (isset($options['quality'])) {
  729.                         $options['webp_quality'] = $options['quality'];
  730.                     }
  731.                 }
  732.                 if (isset($options['webp_quality'])) {
  733.                     $image->setImageCompressionQuality($options['webp_quality']);
  734.                 }
  735.                 if (isset($options['webp_lossless'])) {
  736.                     $image->setOption('webp:lossless'$options['webp_lossless']);
  737.                 }
  738.                 break;
  739.             case Format::ID_AVIF:
  740.             case Format::ID_HEIC:
  741.                 if (!empty($options[$formatInfo->getID() . '_lossless'])) {
  742.                     $image->setimagecompressionquality(100);
  743.                     $image->setcompressionquality(100);
  744.                 } else {
  745.                     if (!isset($options[$formatInfo->getID() . '_quality'])) {
  746.                         if (isset($options['quality'])) {
  747.                             $options[$formatInfo->getID() . '_quality'] = $options['quality'];
  748.                         }
  749.                     }
  750.                     if (isset($options[$formatInfo->getID() . '_quality'])) {
  751.                         $options[$formatInfo->getID() . '_quality'] = max(1min(99$options[$formatInfo->getID() . '_quality']));
  752.                         $image->setimagecompressionquality($options[$formatInfo->getID() . '_quality']);
  753.                         $image->setcompressionquality($options[$formatInfo->getID() . '_quality']);
  754.                     }
  755.                 }
  756.                 break;
  757.             case Format::ID_JXL:
  758.                 if (!empty($options['jxl_lossless'])) {
  759.                     $image->setimagecompressionquality(100);
  760.                     $image->setcompressionquality(100);
  761.                 } else {
  762.                     if (!isset($options['jxl_quality'])) {
  763.                         if (isset($options['quality'])) {
  764.                             $options['jxl_quality'] = $options['quality'];
  765.                         }
  766.                     }
  767.                     if (isset($options['jxl_quality'])) {
  768.                         $options['jxl_quality'] = max(9min(99$options['jxl_quality']));
  769.                         $image->setimagecompressionquality($options['jxl_quality']);
  770.                         $image->setcompressionquality($options['jxl_quality']);
  771.                     }
  772.                 }
  773.                 break;
  774.         }
  775.         if (isset($options['resolution-units']) && isset($options['resolution-x']) && isset($options['resolution-y'])) {
  776.             if (empty($options['resampling-filter'])) {
  777.                 $filterName ImageInterface::FILTER_UNDEFINED;
  778.             } else {
  779.                 $filterName $options['resampling-filter'];
  780.             }
  781.             $filter $this->getFilter($filterName);
  782.             switch ($options['resolution-units']) {
  783.                 case ImageInterface::RESOLUTION_PIXELSPERCENTIMETER:
  784.                     $image->setImageUnits(\Imagick::RESOLUTION_PIXELSPERCENTIMETER);
  785.                     break;
  786.                 case ImageInterface::RESOLUTION_PIXELSPERINCH:
  787.                     $image->setImageUnits(\Imagick::RESOLUTION_PIXELSPERINCH);
  788.                     break;
  789.                 default:
  790.                     throw new RuntimeException('Unsupported image unit format');
  791.             }
  792.             $image->setImageResolution($options['resolution-x'], $options['resolution-y']);
  793.             $image->resampleImage($options['resolution-x'], $options['resolution-y'], $filter0);
  794.         }
  795.         if (!empty($options['optimize'])) {
  796.             try {
  797.                 $image $image->coalesceImages();
  798.                 $optimized $image->optimizeimagelayers();
  799.             } catch (\ImagickException $e) {
  800.                 throw new RuntimeException('Image optimization failed'$e->getCode(), $e);
  801.             }
  802.             if ($optimized === false) {
  803.                 throw new RuntimeException('Image optimization failed');
  804.             }
  805.             if ($optimized instanceof \Imagick) {
  806.                 $image $optimized;
  807.             }
  808.         }
  809.         return $image;
  810.     }
  811.     /**
  812.      * Gets specifically formatted color string from Color instance.
  813.      *
  814.      * @param \Imagine\Image\Palette\Color\ColorInterface $color
  815.      *
  816.      * @return \ImagickPixel
  817.      */
  818.     private function getColor(ColorInterface $color)
  819.     {
  820.         $pixel = new \ImagickPixel((string) $color);
  821.         $pixel->setColorValue(\Imagick::COLOR_ALPHA$color->getAlpha() / 100);
  822.         return $pixel;
  823.     }
  824.     /**
  825.      * Checks whether given $fill is linear and opaque.
  826.      *
  827.      * @param \Imagine\Image\Fill\FillInterface $fill
  828.      *
  829.      * @return bool
  830.      */
  831.     private function isLinearOpaque(FillInterface $fill)
  832.     {
  833.         return $fill instanceof Linear && $fill->getStart()->isOpaque() && $fill->getEnd()->isOpaque();
  834.     }
  835.     /**
  836.      * Performs optimized gradient fill for non-opaque linear gradients.
  837.      *
  838.      * @param \Imagine\Image\Fill\Gradient\Linear $fill
  839.      */
  840.     private function applyFastLinear(Linear $fill)
  841.     {
  842.         $gradient = new \Imagick();
  843.         $size $this->getSize();
  844.         $color sprintf('gradient:%s-%s', (string) $fill->getStart(), (string) $fill->getEnd());
  845.         if ($fill instanceof Horizontal) {
  846.             $gradient->newPseudoImage($size->getHeight(), $size->getWidth(), $color);
  847.             $gradient->rotateImage(new \ImagickPixel(), 90);
  848.         } else {
  849.             $gradient->newPseudoImage($size->getWidth(), $size->getHeight(), $color);
  850.         }
  851.         $this->imagick->compositeImage($gradient, \Imagick::COMPOSITE_OVER00);
  852.         $gradient->clear();
  853.         $gradient->destroy();
  854.     }
  855.     /**
  856.      * Sets colorspace and image type, assigns the palette.
  857.      *
  858.      * @param \Imagine\Image\Palette\PaletteInterface $palette
  859.      *
  860.      * @throws \Imagine\Exception\InvalidArgumentException
  861.      */
  862.     private function setColorspace(PaletteInterface $palette)
  863.     {
  864.         $typeMapping = array(
  865.             // We use Matte variants to preserve alpha
  866.             //
  867.             // (the IMGTYPE_...ALPHA constants are only available since ImageMagick 7 and Imagick 3.4.3, previously they were named
  868.             // IMGTYPE_...MATTE but in some combinations of different Imagick and ImageMagick versions none of them are avaiable at all,
  869.             // so we found no other way to fix it as to hard code the values here)
  870.             PaletteInterface::PALETTE_CMYK => defined('\Imagick::IMGTYPE_TRUECOLORALPHA') ? \Imagick::IMGTYPE_TRUECOLORALPHA : (defined('\Imagick::IMGTYPE_TRUECOLORMATTE') ? \Imagick::IMGTYPE_TRUECOLORMATTE 7),
  871.             PaletteInterface::PALETTE_RGB => defined('\Imagick::IMGTYPE_TRUECOLORALPHA') ? \Imagick::IMGTYPE_TRUECOLORALPHA : (defined('\Imagick::IMGTYPE_TRUECOLORMATTE') ? \Imagick::IMGTYPE_TRUECOLORMATTE 7),
  872.             PaletteInterface::PALETTE_GRAYSCALE => defined('\Imagick::IMGTYPE_GRAYSCALEALPHA') ? \Imagick::IMGTYPE_GRAYSCALEALPHA : (defined('\Imagick::IMGTYPE_GRAYSCALEMATTE') ? \Imagick::IMGTYPE_GRAYSCALEMATTE 3),
  873.         );
  874.         if (!isset(static::$colorspaceMapping[$palette->name()])) {
  875.             throw new InvalidArgumentException(sprintf('The palette %s is not supported by Imagick driver'$palette->name()));
  876.         }
  877.         $this->imagick->setType($typeMapping[$palette->name()]);
  878.         $this->imagick->setColorspace(static::$colorspaceMapping[$palette->name()]);
  879.         if ($this->imagick->getnumberimages() > && defined('Imagick::ALPHACHANNEL_REMOVE') && defined('Imagick::ALPHACHANNEL_SET')) {
  880.             $originalImageIndex $this->imagick->getiteratorindex();
  881.             foreach ($this->imagick as $frame) {
  882.                 if ($palette->supportsAlpha()) {
  883.                     $frame->setimagealphachannel(\Imagick::ALPHACHANNEL_SET);
  884.                 } else {
  885.                     $frame->setimagealphachannel(\Imagick::ALPHACHANNEL_REMOVE);
  886.                 }
  887.             }
  888.             $this->imagick->setiteratorindex($originalImageIndex);
  889.         }
  890.         $this->palette $palette;
  891.     }
  892.     /**
  893.      * Returns the filter if it's supported.
  894.      *
  895.      * @param string $filter
  896.      *
  897.      * @throws \Imagine\Exception\InvalidArgumentException if the filter is unsupported
  898.      *
  899.      * @return string
  900.      */
  901.     private function getFilter($filter ImageInterface::FILTER_UNDEFINED)
  902.     {
  903.         static $supportedFilters null;
  904.         if ($supportedFilters === null) {
  905.             $supportedFilters = array(
  906.                 ImageInterface::FILTER_UNDEFINED => \Imagick::FILTER_UNDEFINED,
  907.                 ImageInterface::FILTER_BESSEL => \Imagick::FILTER_BESSEL,
  908.                 ImageInterface::FILTER_BLACKMAN => \Imagick::FILTER_BLACKMAN,
  909.                 ImageInterface::FILTER_BOX => \Imagick::FILTER_BOX,
  910.                 ImageInterface::FILTER_CATROM => \Imagick::FILTER_CATROM,
  911.                 ImageInterface::FILTER_CUBIC => \Imagick::FILTER_CUBIC,
  912.                 ImageInterface::FILTER_GAUSSIAN => \Imagick::FILTER_GAUSSIAN,
  913.                 ImageInterface::FILTER_HANNING => \Imagick::FILTER_HANNING,
  914.                 ImageInterface::FILTER_HAMMING => \Imagick::FILTER_HAMMING,
  915.                 ImageInterface::FILTER_HERMITE => \Imagick::FILTER_HERMITE,
  916.                 ImageInterface::FILTER_LANCZOS => \Imagick::FILTER_LANCZOS,
  917.                 ImageInterface::FILTER_MITCHELL => \Imagick::FILTER_MITCHELL,
  918.                 ImageInterface::FILTER_POINT => \Imagick::FILTER_POINT,
  919.                 ImageInterface::FILTER_QUADRATIC => \Imagick::FILTER_QUADRATIC,
  920.                 ImageInterface::FILTER_SINC => \Imagick::FILTER_SINC,
  921.                 ImageInterface::FILTER_TRIANGLE => \Imagick::FILTER_TRIANGLE,
  922.             );
  923.             if (defined('Imagick::FILTER_SINCFAST')) {
  924.                 $supportedFilters[ImageInterface::FILTER_SINCFAST] = \Imagick::FILTER_SINCFAST;
  925.             }
  926.         }
  927.         if (!in_array($filter, static::getAllFilterValues(), true)) {
  928.             throw new InvalidArgumentException('Unsupported filter type');
  929.         }
  930.         if (!array_key_exists($filter$supportedFilters)) {
  931.             $filter ImageInterface::FILTER_UNDEFINED;
  932.         }
  933.         return $supportedFilters[$filter];
  934.     }
  935.     /**
  936.      * Clone the Imagick resource of this instance.
  937.      *
  938.      * @throws \ImagickException
  939.      *
  940.      * @return \Imagick
  941.      */
  942.     protected function cloneImagick()
  943.     {
  944.         // the clone method has been deprecated in imagick 3.1.0b1.
  945.         // we can't use phpversion('imagick') because it may return `@PACKAGE_VERSION@`
  946.         // so, let's check if ImagickDraw has the setResolution method, which has been introduced in the same version 3.1.0b1
  947.         if (method_exists('ImagickDraw''setResolution')) {
  948.             return clone $this->imagick;
  949.         }
  950.         return $this->imagick->clone();
  951.     }
  952. }