将PHP/GD包装器移植到Imagick时遇到的问题

15

我最近发现Imagick可以支持色彩配置文件,因此与GD相比可以产生更高质量的图像(有关更多详细信息,请参见此问题 / 答案),因此我正在尝试将我的GD包装器移植为使用Imagick类,我的当前GD实现如下:

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;
}

我一直在尝试使用Imagick类的方法,目前为止这是我得到的结果:

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;
}

基本功能(裁剪/调整大小/水印)已经支持,但是我仍然遇到一些问题。由于PHP Imagick文档有点糟糕,我别无选择,只能尝试所有可用方法和参数的组合,这需要很长时间。
我目前的问题/疑虑是:

1 - 保持透明度

在我的原始实现中,以下行:

ImageFill($image, 0, 0, IMG_COLOR_TRANSPARENT);
ImageSaveAlpha($image, true);
ImageAlphaBlending($image, true);

当你将一个透明的PNG图像转换成PNG输出时,具有保留透明度的效果。然而,如果你尝试将透明的PNG图像转换成JPEG格式,则透明像素应该被设置为白色。到目前为止,使用ImageMagick,我只能将所有透明像素转换成白色,但如果输出格式支持透明度,则无法保留透明度。


2 - 压缩输出格式(即JPEG和PNG)

我的原始实现在PNG上使用9级压缩,而在JPEG上使用90的质量:

foreach (array('gif' => 0, 'png' => 9, 'jpe?g' => 90) as $key => $value)

这行代码:

$image->setImageCompression(Imagick::COMPRESSION_JPEG);
$image->setImageCompressionQuality($value);
$image->stripImage();

看起来能够压缩JPEG图像 - 但是,使用相同的$value作为质量参数时,GD可以更有效地压缩它 - 为什么?我对以下区别也不清楚:

我应该使用哪一个?它们有什么区别?此外,最关键的问题与PNG压缩有关,Imagick压缩常量列表似乎不支持PNG格式:

imagick::COMPRESSION_UNDEFINED (integer)
imagick::COMPRESSION_NO (integer)
imagick::COMPRESSION_BZIP (integer)
imagick::COMPRESSION_FAX (integer)
imagick::COMPRESSION_GROUP4 (integer)
imagick::COMPRESSION_JPEG (integer)
imagick::COMPRESSION_JPEG2000 (integer)
imagick::COMPRESSION_LOSSLESSJPEG (integer)
imagick::COMPRESSION_LZW (integer)
imagick::COMPRESSION_RLE (integer)
imagick::COMPRESSION_ZIP (integer)
imagick::COMPRESSION_DXT1 (integer)
imagick::COMPRESSION_DXT3 (integer)
imagick::COMPRESSION_DXT5 (integer)

这真是个大问题,因为使用 Imagick 输出具有 100-200 KB 大小的 GD PNG 输出时,输出结果会变得非常臃肿(大小约为 2 MB)...
关于此问题在 SO 网站上有一些 问题 讨论 和回答,但我还没有找到任何不依赖外部应用程序的可行解决方案。使用 ImageMagick 真的做不到吗?!

3 - 图像卷积

在GD的实现中,我调用ImageConvolution()来稍微锐化图像,我知道Imagick有内置方法来锐化图像(我还没有尝试过),但我想知道Imagick是否有ImageConvolution()函数的等效方法。


4 - 色彩配置文件

这与原始实现无关,但我也希望做得正确。

我是否应该在所有图像中始终添加Imagick / International Color Consortium sRGB颜色配置文件?还是只有在存在(或不存在)特定的颜色配置文件时才添加?

此外,我应该删除现有的颜色配置文件吗?

我理解这可能是一个广泛的问题,但我对颜色配置文件的了解非常有限,因此对此的一些一般指导将不胜感激。


5 - 打开远程图片

GD原生支持打开远程图片,可以通过ImageCreateFrom*函数或者使用file_get_contents()ImageCreateFromString()结合使用,就像我所做的一样。

Imagick似乎只能打开本地图片或打开文件句柄。有没有简单的方法让Imagick读取远程图片(而不必打开和关闭文件句柄)?


如果有人能够解答这些问题中的任意一个,我将不胜感激。
提前致谢!

考虑将此问题拆分。例如,您的问题的第四部分完全可以单独提出,因为它与PHP或ImageMagick没有任何关系,除了由您提出。 - sanmai
4个回答

13

你的PNG图片大小增加的原因有很多,其中最明显的一个是GM/IM无法将透明度作为tRNS块传递(基本上是用于PNG图像的布尔透明度)。不幸的是,GraphicsMagick和ImageMagick的维护者尚未实现此功能。我曾与他们交换过电子邮件,所以我确定这一点。

我知道你不想使用外部工具,但请相信我需要。Image/GraphicsMagick在压缩PNG图像方面非常糟糕。我使用的解决方案是,使用GraphicsMagick来操作图像,并检查图像是否包含透明像素,如果包含,则在图像上运行OptiPNG。OptiPNG会看到透明度可以作为tRNS块传递并据此采取行动。实际上,你应该在使用Image/GraphicsMagick后对所有PNG图像运行OptiPNG,因为我发现可以获得更大的压缩比。你也可以通过关闭抖动和使用YUV颜色空间来节省空间。

至于GM比IM更好地减小图像大小,你应该知道当减少图像的颜色时,GM默认使用8位颜色空间,而ImageMagick默认使用16位。这就是为什么当将图像的颜色减少到超过255个颜色时,GM比IM快得多的原因。也许你应该在压缩后检查每个图像中的颜色数量以确认。


哦,我一直以为Imagick是图像处理的瑞士军刀...看来在转换为PNG格式时我得绕过它。谢谢! - Alix Axel
1
非常欢迎。至于你的第一个问题,我认为唯一的方法是使用你的脚本来决定输出图像格式是否支持透明度,如果不支持,则将透明像素设置为白色。另外,请查看http://www.imagemagick.org/Usage/formats/#color_profile了解如何在IM中处理颜色配置文件的信息。 - toc777

2
您可以使用optipng(另一个PNG命令行工具)来优化PNG文件的大小。

1

由于浏览器对ICM支持不够,因此配置文件基本上是带宽的浪费。因此,如果您的图像在sRGB中,则可以安全地删除配置文件;否则最好将图像转换为sRGB,然后再删除其配置文件。

删除sRGB图像的配置文件的原因是,sRGB在互联网、计算机和打印机上实际上是一种标准,甚至Firefox也会对未打标记的图像应用sRGB颜色配置文件。

删除所有配置文件的另一个原因是,如果您计划将带有嵌入式配置文件的图像与其他无配置文件的图像(例如GIF图像)混合使用,则在启用ICC的浏览器上会产生混乱的结果。它将按照其嵌入的颜色空间呈现某些图像,并使用a some other color profile呈现其他图像,这导致您将看到具有嵌入式ICC配置文件和相同背景颜色的其他无配置文件图像之间的边界。即使您设法为页面上的每个图像获取配置文件,仍有许多用户使用古老的禁用ICC的浏览器。

底线:颜色配置文件是邪恶的。只有在实际需要时才使用它们。

我所说的只适用于您将站点面向尽可能广泛的受众的情况。否则,您的情况可能会有所不同。


0

我不确定您是否还需要答案,但我一直在编写一个图像处理库,它包装了GD和Imagick,所以我遇到了您的一些问题。

2 - 压缩输出格式(主要是JPEG和PNG)

ImageMagick不为PNG提供压缩功能,因为PNG是一种无损格式,而JPEG是一种“有损”格式。我敢说ImageMagick做对了这一点。

只需去掉PNG中的压缩选项,并为GD中的imagepng提供一个合理的默认值即可。

3 - 图像卷积

只需使用getPixelIterator循环每个像素,并进行手动卷积。维基百科上有一个好的article,其中包含伪代码。

5 - 打开远程图像

您可以单独打开图像并将其传递给 Imagick。

$handle = fopen('http://example.com/foo.jpg', 'rb');
$img = new Imagick();
$img->readImageFile($handle);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接