<?php
namespace XF\Image;
use Exception;
use ImagickException;
use ImagickPixel;
use InvalidArgumentException;
class Imagick extends AbstractDriver
{
/**
* @var \Imagick
*/
protected $imagick;
public static function isDriverUsable()
{
return class_exists('Imagick');
}
protected function _imageFromFile($file, $type)
{
switch ($type)
{
case IMAGETYPE_GIF:
case IMAGETYPE_JPEG:
case IMAGETYPE_PNG:
$image = new \Imagick($file);
break;
default:
throw new InvalidArgumentException("Unknown image type '$type'");
}
$this->setImage($image);
return true;
}
protected function _createImage($width, $height)
{
$image = new \Imagick();
$background = new ImagickPixel('white');
$image->newImage($width, $height, $background);
$this->setImage($image);
return true;
}
public function getImage()
{
return $this->imagick;
}
public function setImage(\Imagick $image)
{
if (method_exists($image, 'setRegistry'))
{
$image::setRegistry('temporary-path', \XF\Util\File::getTempDir());
}
$image->setImageBackgroundColor(new ImagickPixel('transparent'));
$image = $image->coalesceImages();
$this->imagick = $image;
$this->updateDimensions();
}
protected function updateDimensions()
{
$this->width = $this->imagick->getImageWidth();
$this->height = $this->imagick->getImageHeight();
}
protected function isOldImagick()
{
// imagick module < 3 or ImageMagick < 6.3.2 don't support the 4th thumbnailImage param
$oldImagick = version_compare(phpversion('imagick'), '3', '<');
$version = $this->imagick->getVersion();
if (preg_match('#ImageMagick (\d+\.\d+\.\d+)#i', $version['versionString'], $match))
{
if (version_compare($match[1], '6.3.2', '<'))
{
$oldImagick = true;
}
}
return $oldImagick;
}
public function resizeTo($width, $height)
{
$scaleUp = ($width > $this->width || $height > $this->height);
try
{
foreach ($this->imagick AS $frame)
{
if ($scaleUp)
{
$frame->resizeImage($width, $height, \Imagick::FILTER_QUADRATIC, .5, true);
}
else if ($this->isOldImagick())
{
$frame->thumbnailImage($width, $height, false);
}
else
{
$frame->thumbnailImage($width, $height, false, true);
}
$frame->setImagePage($width, $height, 0, 0);
}
$this->updateDimensions();
}
catch (Exception $e) {}
return $this;
}
public function crop($width, $height, $x = 0, $y = 0, $srcWidth = null, $srcHeight = null)
{
foreach ($this->imagick AS $frame)
{
$frame->cropImage($srcWidth ?: $width, $srcHeight ?: $height, $x, $y);
if ($this->isOldImagick())
{
$frame->thumbnailImage($width, $height, false);
}
else
{
$frame->thumbnailImage($width, $height, false, true);
}
$frame->setImagePage($frame->getImageWidth(), $frame->getImageHeight(), 0, 0);
}
$this->updateDimensions();
return $this;
}
public function rotate($angle)
{
foreach ($this->imagick AS $frame)
{
$frame->rotateImage(new ImagickPixel('none'), $angle);
}
$this->updateDimensions();
return $this;
}
public function flip($mode)
{
foreach ($this->imagick AS $frame)
{
switch ($mode)
{
case self::FLIP_HORIZONTAL:
$frame->flopImage();
break;
case self::FLIP_VERTICAL:
$frame->flipImage();
break;
case self::FLIP_BOTH:
$frame->flopImage();
$frame->flipImage();
break;
default:
throw new InvalidArgumentException("Unknown flip mode");
}
}
$this->updateDimensions();
return $this;
}
public function setOpacity($opacity)
{
foreach ($this->imagick AS $frame)
{
$frame->evaluateImage(\Imagick::EVALUATE_MULTIPLY, $opacity, \Imagick::CHANNEL_ALPHA);
}
return $this;
}
public function appendImageAt($x, $y, $toAppend)
{
if (!($toAppend instanceof \Imagick))
{
throw new InvalidArgumentException('Image to append must be a valid Imagick object.');
}
foreach ($this->imagick AS $frame)
{
$frame->compositeImage($toAppend, $toAppend->getImageCompose(), $x, $y);
}
return $this;
}
protected function _unsharpMask($radius, $sigma, $amount, $threshold)
{
foreach ($this->imagick AS $frame)
{
$frame->unsharpMaskImage($radius, $sigma, $amount, $threshold);
}
return $this;
}
protected function getPixelColorArray($x, $y)
{
$pixel = $this->imagick->getImagePixelColor($x, $y);
$array = $pixel->getColor();
$lut = [
'r' => 'red',
'g' => 'blue',
'b' => 'green',
'a' => 'alpha',
];
$rgba = [];
foreach ($lut AS $key => $value)
{
$rgba[$value] = $array[$key] ?? 0;
}
return $rgba;
}
public function save($file, $format = null, $quality = null)
{
if ($format === null)
{
$format = $this->type;
}
if ($quality === null)
{
$quality = 85;
}
if (method_exists($this->imagick, 'getImageProfiles'))
{
$profiles = $this->imagick->getImageProfiles('icc');
$this->imagick->stripImage();
if ($profiles && !empty($profiles['icc']))
{
$this->imagick->setImageProfile('icc', $profiles['icc']);
}
}
else
{
$this->imagick->stripImage();
}
switch ($format)
{
case IMAGETYPE_GIF:
if (is_callable(array($this->imagick, 'optimizeimagelayers')))
{
$optimized = @$this->imagick->optimizeimagelayers();
if ($optimized instanceof \Imagick)
{
$this->imagick = $optimized;
}
$deconstructed = @$this->imagick->deconstructImages();
if ($deconstructed instanceof \Imagick)
{
$this->imagick = $deconstructed;
}
}
$success = $this->imagick->setImageFormat('gif');
break;
case IMAGETYPE_JPEG:
$success = $this->imagick->setImageFormat('jpeg')
&& $this->imagick->setImageCompression(\Imagick::COMPRESSION_JPEG)
&& $this->imagick->setImageCompressionQuality($quality);
break;
case IMAGETYPE_PNG:
$success = $this->imagick->setImageFormat('png');
break;
default:
throw new InvalidArgumentException('Invalid format given. Expects IMAGETYPE_XXX constant.');
}
if ($success)
{
try
{
return $this->imagick->writeImages($file, true);
}
catch (ImagickException $e) {}
}
return false;
}
public function output($format = null, $quality = null)
{
if ($format === null)
{
$format = $this->type;
}
if ($quality === null)
{
$quality = 85;
}
$this->imagick->stripImage();
switch ($format)
{
case IMAGETYPE_GIF:
$success = $this->imagick->optimizeImageLayers();
break;
case IMAGETYPE_JPEG:
$success = $this->imagick->setImageFormat('jpeg')
&& $this->imagick->setImageCompression(\Imagick::COMPRESSION_JPEG)
&& $this->imagick->setImageCompressionQuality($quality);
break;
case IMAGETYPE_PNG:
$success = $this->imagick->setImageFormat('png');
break;
default:
throw new InvalidArgumentException('Invalid format given. Expects IMAGETYPE_XXX constant.');
}
if ($success)
{
try
{
echo $this->imagick->getImagesBlob();
}
catch (ImagickException $e) {}
}
}
public function isValid()
{
$this->imagick->setFirstIterator();
return $this->imagick->valid();
}
public function __destruct()
{
if ($this->imagick)
{
$this->imagick->destroy();
$this->imagick = null;
}
}
}