Проблемы при переносе оболочки PHP / GD на Imagick - PullRequest
15 голосов
/ 28 апреля 2011

Я недавно обнаружил, что 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)

Моя оригинальная реализация использует уровень сжатия 9 для PNG и качество 90 для JPEG:

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

Строки:

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

Кажется, для сжатия изображений JPEG - GD, однако, может сжать его намного больше, используя тот же $value в качестве аргумента качества - почему?Я также в неведении относительно различий между:

Какой из них следует использовать и в чем их отличия?Кроме того, наиболее критическая проблема связана со сжатием 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)

Это - зарисовка в заднице,поскольку вывод в формате GD PNG размером 100-200 КБ становится намного толще, если вместо этого выводится с помощью Imagick (размер порядка 2 МБ) ...

Есть пара вопросов по SO относительно этой проблемы , но я не смог найти ни одного рабочего решения, которое бы не зависело от внешних приложений.Неужели это невозможно сделать с помощью ImageMagick?!


3 - Свертки изображений

В реализации GD я вызываю ImageConvolution(), чтобы немного повысить резкость изображения, я знаю, что Imagick имеетвстроенные методы повышения резкости изображений (у меня еще не было возможности их опробовать), но хотелось бы знать, есть ли у Imagick эквивалент функции ImageConvolution().


4- Цветовые профили

Это не связано с исходной реализацией, но я также хотел бы получить ее правильную.

Должен ли я всегда добавлять цветовой профиль Imagick / International Color Consortium sRGB ко всем изображениям?Или это следует добавить только при наличии (или отсутствии) определенного цветового профиля?

Кроме того, следует ли удалять существующие цветовые профили?

Я понимаю, что это может быть широкимвопрос, но мое понимание цветовых профилей очень ограничено, и некоторые общие рекомендации по этому вопросу будут весьма полезны.


5 - Открытие удаленных изображений

GD изначально поддерживает открытие удаленных изображений, либос помощью функций ImageCreateFrom* или с использованием file_get_contents() в сочетании с ImageCreateFromString(), как я делаю.

Кажется, что Imagick может открывать только локальные изображения или дескрипторы файлов.Есть ли простой способ заставить Imagick читать удаленные изображения (без необходимости открывать и закрывать файловые маркеры)?


Если кто-нибудь сможет пролить свет на любой из этих вопросов, я буду очень признателен.

Заранее спасибо!

Ответы [ 4 ]

13 голосов
/ 29 апреля 2011

Существует ряд причин, по которым размер ваших PNG-изображений может увеличиваться в размерах, наиболее очевидной из которых вы столкнетесь с неспособностью GM / IM передать прозрачность в виде фрагмента tRNS (в основном, логическая прозрачность для изображений PNG). К сожалению, разработчики GraphicsMagick и ImageMagick еще не реализовали эту функцию. Я обменялся с ними электронной почтой, поэтому я точно знаю это.

Я знаю, что вы не хотите использовать внешние инструменты, но поверьте мне, что вы делаете. Image / GraphicsMagick действительно плохо сжимают изображения PNG. Решением, которое я использую, является использование GraphicsMagick для управления изображением, а также для проверки, содержит ли изображение прозрачные пиксели, если оно содержит прозрачные пиксели, затем запустите OptiPNG на изображении. OptiPNG увидит, что прозрачность может быть передана как фрагмент tRNS, и будет действовать соответственно. На самом деле вы должны запускать OptiPNG на всех изображениях PNG после использования Image / GraphicsMagick, потому что я обнаружил, что вы можете добиться гораздо большего сжатия. Вы также можете сэкономить место, выключив сглаживание и используя цветовое пространство YUV.

Что касается GM, уменьшающего размер изображений лучше, чем IM, вы должны знать, что GM по умолчанию использует 8-битное цветовое пространство при уменьшении цвета изображений, в то время как ImageMagick по умолчанию использует 16 бит. Вот почему GM намного быстрее, чем IM, когда цвета уменьшают изображения до значения более 255 цветов. Возможно, вам следует проверить количество цветов в каждом изображении после сжатия, чтобы подтвердить.

2 голосов
/ 03 мая 2011

Вы можете использовать optipng (еще один инструмент командной строки PNG) для оптимизации размера файлов PNG.

1 голос
/ 26 мая 2011

Поскольку не поддерживает ICM в браузерах , профили, по сути, являются пустой тратой пропускной способности. Таким образом, если ваши изображения находятся в sRGB, вы можете безопасно удалить профиль, в противном случае лучше преобразовать изображение в sRGB и затем удалить его профиль.

Причина удаления профиля изображений sRGB состоит в том, что sRGB фактически является стандартом в Интернете, на компьютерах и на принтерах, и даже Firefox применяет цветовой профиль sRGB к нетегированные изображения.

Существует еще одна причина полного удаления всех профилей, но я не уверен, относится ли это к вашему случаю: планируете ли вы смешивать изображения со встроенными профилями с другими изображениями без профиля, например, Изображения GIF, которые по определению не могут содержать профиль, в браузере с поддержкой ICC приводят к грязным результатам. Некоторые изображения будут отображаться в соответствии с их встроенным цветовым пространством, а другие - с помощью с другим цветовым профилем , что приведет к ситуации, в которой вы увидите границу между изображением со встроенным профилем ICC с сплошной цвет фона, примыкающий к другому изображению без профиля с тем же цветом фона. Даже если вам удастся получить профиль для каждого изображения на вашей странице, есть много пользователей, которые используют древние браузеры с отключенным ICC.

Итог: цветовые профили - это зло. Используйте их только в том случае, если они вам действительно нужны.

То, что я сказал, правильно, только если вы нацелены на свой сайт для максимально широкой аудитории. В противном случае YMMV.

0 голосов
/ 10 июля 2016

Я не уверен, что вам все еще нужен ответ, но я писал библиотеку обработки изображений, которая охватывает GD и Imagick, поэтому я столкнулся с некоторыми из ваших проблем.

2 - Сжатие выходных форматов (а именно JPEG и PNG)

ImageMagick не обеспечивает сжатие для PNG по той простой причине, что PNG - это формат без потерь, в отличие от JPEG, который является форматом с потерями. Я бы сказал, что ImageMagick понял это правильно.

Просто отбросьте опцию сжатия в PNG и просто укажите разумное значение по умолчанию для imagepng в GD.

3 - Свертки изображений

Просто зациклите каждый пиксель с помощью getPixelIterator и выполните ручную свертку. В Википедии есть хорошая статья об этом с псевдокодом.

5 - Открытие удаленных изображений

Вы можете открыть изображение отдельно и передать его в Imagick

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