I've recently discovered that Imagick can support color profiles and thus produce images of better quality compared to GD (see this question / answer for more details), so I'm trying to port my GD wrapper to use the Imagick class instead, my current GD implementation looks like this:
function Image($input, $crop = null, $scale = null, $merge = null, $output = null, $sharp = true)
{
if (isset($input, $output) === true)
{
if (is_string($input) === true)
{
$input = @ImageCreateFromString(@file_get_contents($input));
}
if (is_resource($input) === true)
{
$size = array(ImageSX($input), ImageSY($input));
$crop = array_values(array_filter(explode('/', $crop), 'is_numeric'));
$scale = array_values(array_filter(explode('*', $scale), 'is_numeric'));
if (count($crop) == 2)
{
$crop = array($size[0] / $size[1], $crop[0] / $crop[1]);
if ($crop[0] > $crop[1])
{
$size[0] = round($size[1] * $crop[1]);
}
else if ($crop[0] < $crop[1])
{
$size[1] = round($size[0] / $crop[1]);
}
$crop = array(ImageSX($input) - $size[0], ImageSY($input) - $size[1]);
}
else
{
$crop = array(0, 0);
}
if (count($scale) >= 1)
{
if (empty($scale[0]) === true)
{
$scale[0] = round($scale[1] * $size[0] / $size[1]);
}
else if (empty($scale[1]) === true)
{
$scale[1] = round($scale[0] * $size[1] / $size[0]);
}
}
else
{
$scale = array($size[0], $size[1]);
}
$image = ImageCreateTrueColor($scale[0], $scale[1]);
if (is_resource($image) === true)
{
ImageFill($image, 0, 0, IMG_COLOR_TRANSPARENT);
ImageSaveAlpha($image, true);
ImageAlphaBlending($image, true);
if (ImageCopyResampled($image, $input, 0, 0, round($crop[0] / 2), round($crop[1] / 2), $scale[0], $scale[1], $size[0], $size[1]) === true)
{
$result = false;
if ((empty($sharp) !== true) && (is_array($matrix = array_fill(0, 9, -1)) === true))
{
array_splice($matrix, 4, 1, (is_int($sharp) === true) ? $sharp : 16);
if (function_exists('ImageConvolution') === true)
{
ImageConvolution($image, array_chunk($matrix, 3), array_sum($matrix), 0);
}
}
if ((isset($merge) === true) && (is_resource($merge = @ImageCreateFromString(@file_get_contents($merge))) === true))
{
ImageCopy($image, $merge, round(0.95 * $scale[0] - ImageSX($merge)), round(0.95 * $scale[1] - ImageSY($merge)), 0, 0, ImageSX($merge), ImageSY($merge));
}
foreach (array('gif' => 0, 'png' => 9, 'jpe?g' => 90) as $key => $value)
{
if (preg_match('~' . $key . '$~i', $output) > 0)
{
$type = str_replace('?', '', $key);
$output = preg_replace('~^[.]?' . $key . '$~i', '', $output);
if (empty($output) === true)
{
header('Content-Type: image/' . $type);
}
$result = call_user_func_array('Image' . $type, array($image, $output, $value));
}
}
return (empty($output) === true) ? $result : self::Chmod($output);
}
}
}
}
else if (count($result = @GetImageSize($input)) >= 2)
{
return array_map('intval', array_slice($result, 0, 2));
}
return false;
}
I've been experimenting with the Imagick class methods and this is what I got so far:
function Imagick($input, $crop = null, $scale = null, $merge = null, $output = null, $sharp = true)
{
if (isset($input, $output) === true)
{
if (is_file($input) === true)
{
$input = new Imagick($input);
}
if (is_object($input) === true)
{
$size = array_values($input->getImageGeometry());
$crop = array_values(array_filter(explode('/', $crop), 'is_numeric'));
$scale = array_values(array_filter(explode('*', $scale), 'is_numeric'));
if (count($crop) == 2)
{
$crop = array($size[0] / $size[1], $crop[0] / $crop[1]);
if ($crop[0] > $crop[1])
{
$size[0] = round($size[1] * $crop[1]);
}
else if ($crop[0] < $crop[1])
{
$size[1] = round($size[0] / $crop[1]);
}
$crop = array($input->getImageWidth() - $size[0], $input->getImageHeight() - $size[1]);
}
else
{
$crop = array(0, 0);
}
if (count($scale) >= 1)
{
if (empty($scale[0]) === true)
{
$scale[0] = round($scale[1] * $size[0] / $size[1]);
}
else if (empty($scale[1]) === true)
{
$scale[1] = round($scale[0] * $size[1] / $size[0]);
}
}
else
{
$scale = array($size[0], $size[1]);
}
$image = new IMagick();
$image->newImage($scale[0], $scale[1], new ImagickPixel('white'));
$input->cropImage($size[0], $size[1], round($crop[0] / 2), round($crop[1] / 2));
$input->resizeImage($scale[0], $scale[1], Imagick::FILTER_LANCZOS, 1); // $image->scaleImage($scale[0], $scale[1]);
//if (in_array('icc', $image->getImageProfiles('*', false)) === true)
{
$version = preg_replace('~([^-]*).*~', '$1', ph()->Value($image->getVersion(), 'versionString'));
if (is_file($profile = sprintf('/usr/share/%s/config/sRGB.icm', str_replace(' ', '-', $version))) !== true)
{
$profile = 'http://www.color.org/sRGB_v4_ICC_preference.icc';
}
if ($input->profileImage('icc', file_get_contents($profile)) === true)
{
$input->setImageColorSpace(Imagick::COLORSPACE_SRGB);
}
}
$image->compositeImage($input, Imagick::COMPOSITE_OVER, 0, 0);
if ((isset($merge) === true) && (is_object($merge = new Imagick($merge)) === true))
{
$image->compositeImage($merge, Imagick::COMPOSITE_OVER, round(0.95 * $scale[0] - $merge->getImageWidth()), round(0.95 * $scale[1] - $merge->getImageHeight()));
}
foreach (array('gif' => 0, 'png' => 9, 'jpe?g' => 90) as $key => $value)
{
if (preg_match('~' . $key . '$~i', $output) > 0)
{
$type = str_replace('?', '', $key);
$output = preg_replace('~^[.]?' . $key . '$~i', '', $output);
if (empty($output) === true)
{
header('Content-Type: image/' . $type);
}
$image->setImageFormat($type);
if (strcmp('jpeg', $type) === 0)
{
$image->setImageCompression(Imagick::COMPRESSION_JPEG);
$image->setImageCompressionQuality($value);
$image->stripImage();
}
if (strlen($output) > 0)
{
$image->writeImage($output);
}
else
{
echo $image->getImageBlob();
}
}
}
return (empty($output) === true) ? $result : self::Chmod($output);
}
}
else if (count($result = @GetImageSize($input)) >= 2)
{
return array_map('intval', array_slice($result, 0, 2));
}
return false;
}
The basic functionality (crop / resize / watermark) is already supported, however, I'm still having some issues. Since the PHP Imagick documentation kinda sucks I've no other choice than to try a trial and error approach combination of all the available methods and arguments, which takes a lot of time.
My current problems / doubts are:
1 - Preserving Transparency
In my original implementation, the lines:
ImageFill($image, 0, 0, IMG_COLOR_TRANSPARENT);
ImageSaveAlpha($image, true);
ImageAlphaBlending($image, true);
Have the effect of preserving the transparency when you are converting a transparent PNG image to a PNG output. If, however, you try to convert a transparent PNG image to a JPEG format, the transparent pixels should have their color set to white. So far, with ImageMagick, I've only been able to convert all transparent pixels to white, but I can't preserve the transparency if the output format supports it.
2 - Compressing Output Formats (Namely JPEG and PNG)
My original implementation uses a compression level of 9 on PNGs and a quality of 90 on JPEGs:
foreach (array('gif' => 0, 'png' => 9, 'jpe?g' => 90) as $key => $value)
The lines:
$image->setImageCompression(Imagick::COMPRESSION_JPEG);
$image->setImageCompressionQuality($value);
$image->stripImage();
Seem to compress JPEG images - GD however, is able to compress it much more using the same $value
as a quality argument - why? I'm also in the dark regarding the differences between:
Which one should I use and what are their diff