Note that there are some explanatory texts on larger screens.

plurals
  1. POIssues porting PHP/GD wrapper to Imagick
    primarykey
    data
    text
    <p>I've recently discovered that Imagick can support color profiles and thus produce images of better quality compared to GD (see this <a href="https://stackoverflow.com/q/5773032/89771">question</a> / <a href="https://stackoverflow.com/questions/5773032/how-to-stop-gd2-from-washing-away-the-colors-upon-resizing-images/5775030#5775030">answer</a> 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:</p> <pre><code>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] &gt; $crop[1]) { $size[0] = round($size[1] * $crop[1]); } else if ($crop[0] &lt; $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) &gt;= 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) &amp;&amp; (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) &amp;&amp; (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' =&gt; 0, 'png' =&gt; 9, 'jpe?g' =&gt; 90) as $key =&gt; $value) { if (preg_match('~' . $key . '$~i', $output) &gt; 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)) &gt;= 2) { return array_map('intval', array_slice($result, 0, 2)); } return false; } </code></pre> <p>I've been experimenting with the Imagick class methods and this is what I got so far:</p> <pre><code>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-&gt;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] &gt; $crop[1]) { $size[0] = round($size[1] * $crop[1]); } else if ($crop[0] &lt; $crop[1]) { $size[1] = round($size[0] / $crop[1]); } $crop = array($input-&gt;getImageWidth() - $size[0], $input-&gt;getImageHeight() - $size[1]); } else { $crop = array(0, 0); } if (count($scale) &gt;= 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-&gt;newImage($scale[0], $scale[1], new ImagickPixel('white')); $input-&gt;cropImage($size[0], $size[1], round($crop[0] / 2), round($crop[1] / 2)); $input-&gt;resizeImage($scale[0], $scale[1], Imagick::FILTER_LANCZOS, 1); // $image-&gt;scaleImage($scale[0], $scale[1]); //if (in_array('icc', $image-&gt;getImageProfiles('*', false)) === true) { $version = preg_replace('~([^-]*).*~', '$1', ph()-&gt;Value($image-&gt;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-&gt;profileImage('icc', file_get_contents($profile)) === true) { $input-&gt;setImageColorSpace(Imagick::COLORSPACE_SRGB); } } $image-&gt;compositeImage($input, Imagick::COMPOSITE_OVER, 0, 0); if ((isset($merge) === true) &amp;&amp; (is_object($merge = new Imagick($merge)) === true)) { $image-&gt;compositeImage($merge, Imagick::COMPOSITE_OVER, round(0.95 * $scale[0] - $merge-&gt;getImageWidth()), round(0.95 * $scale[1] - $merge-&gt;getImageHeight())); } foreach (array('gif' =&gt; 0, 'png' =&gt; 9, 'jpe?g' =&gt; 90) as $key =&gt; $value) { if (preg_match('~' . $key . '$~i', $output) &gt; 0) { $type = str_replace('?', '', $key); $output = preg_replace('~^[.]?' . $key . '$~i', '', $output); if (empty($output) === true) { header('Content-Type: image/' . $type); } $image-&gt;setImageFormat($type); if (strcmp('jpeg', $type) === 0) { $image-&gt;setImageCompression(Imagick::COMPRESSION_JPEG); $image-&gt;setImageCompressionQuality($value); $image-&gt;stripImage(); } if (strlen($output) &gt; 0) { $image-&gt;writeImage($output); } else { echo $image-&gt;getImageBlob(); } } } return (empty($output) === true) ? $result : self::Chmod($output); } } else if (count($result = @GetImageSize($input)) &gt;= 2) { return array_map('intval', array_slice($result, 0, 2)); } return false; } </code></pre> <p>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.</p> <p>My current problems / doubts are:</p> <hr> <h1>1 - Preserving Transparency</h1> <p>In my original implementation, the lines:</p> <pre><code>ImageFill($image, 0, 0, IMG_COLOR_TRANSPARENT); ImageSaveAlpha($image, true); ImageAlphaBlending($image, true); </code></pre> <p>Have the effect of <a href="https://stackoverflow.com/questions/1025228/php-gd-imagesavealpha-and-imagealphablending">preserving the transparency</a> 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 <a href="https://stackoverflow.com/questions/4973184/transparent-to-white-in-imagick-for-php">convert all transparent pixels to white</a>, but I can't preserve the transparency if the output format supports it.</p> <hr> <h1>2 - Compressing Output Formats (Namely JPEG and PNG)</h1> <p>My original implementation uses a compression level of 9 on PNGs and a quality of 90 on JPEGs:</p> <pre><code>foreach (array('gif' =&gt; 0, 'png' =&gt; 9, 'jpe?g' =&gt; 90) as $key =&gt; $value) </code></pre> <p>The lines:</p> <pre><code>$image-&gt;setImageCompression(Imagick::COMPRESSION_JPEG); $image-&gt;setImageCompressionQuality($value); $image-&gt;stripImage(); </code></pre> <p>Seem to compress JPEG images - GD however, is able to compress it much more using the same <code>$value</code> as a quality argument - why? I'm also in the dark regarding the differences between:</p> <ul> <li><a href="http://pt2.php.net/manual/en/function.imagick-setcompression.php" rel="nofollow noreferrer"><code>Imagick::setCompression()</code></a> / <a href="http://pt2.php.net/manual/en/function.imagick-setimagecompression.php" rel="nofollow noreferrer"><code>Imagick::setImageCompression()</code></a> and</li> <li><a href="http://pt2.php.net/manual/en/function.imagick-setcompressionquality.php" rel="nofollow noreferrer"><code>Imagick::setCompressionQuality()</code></a> / <a href="http://pt2.php.net/manual/en/function.imagick-setimagecompressionquality.php" rel="nofollow noreferrer"><code>Imagick::setImageCompressionQuality()</code></a></li> </ul> <p>Which one should I use and what are their differences? Also, the most critical problem has to do with PNG compression, the list of <a href="http://pt2.php.net/manual/en/imagick.constants.php" rel="nofollow noreferrer">Imagick compression constants</a> seem to not support PNG formats:</p> <pre><code>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) </code></pre> <p>This is being a paint in the ass, since a GD PNG output that happens to have a size of 100-200 KB gets extremely fatter if outputted with Imagick instead (size in the order of 2 MB)...</p> <p>There are <a href="https://stackoverflow.com/questions/3790263/why-does-convert-x-png-y-png-creates-an-image-with-a-different-size-kb">a couple</a> <a href="https://stackoverflow.com/questions/4962020/graphicsmagick-is-doubling-the-size-of-some-png-files">of questions</a> <a href="https://stackoverflow.com/questions/1492056/imagemagick-png-resize-increases-file-size">on SO</a> <a href="https://stackoverflow.com/questions/4203823/reducing-colors-in-a-png-image-is-making-the-file-size-bigger">regarding this issue</a>, but I haven't been able to find any working solution that doesn't rely on external applications. Is this really impossible to do with ImageMagick?!</p> <hr> <h1>3 - Image Convolutions</h1> <p>In the GD implementation I call <code>ImageConvolution()</code> to sharpen the image a bit, I know that Imagick has built-in methods to sharpen images (I haven't had the chance to try them out yet) but I'd like to know if Imagick has an equivalent of the <code>ImageConvolution()</code> function.</p> <hr> <h1>4 - Color Profiles</h1> <p>This is not related to the original implementation, but I would also like to get it right.</p> <p>Should I always add the Imagick / International Color Consortium sRGB color profile to all images? Or should this only be added when there is (or isn't) a specific color profile?</p> <p>Also, should I delete the existing color profiles?</p> <p>I understand that this may be a broad question but my understanding of color profiles is very limited and some general guidance on this would be very much appreciated.</p> <hr> <h1>5 - Opening Remote Images</h1> <p>GD natively supports opening remote images, either via the <code>ImageCreateFrom*</code> functions, or using <code>file_get_contents()</code> in combination with <code>ImageCreateFromString()</code> like I am doing.</p> <p>Imagick seems to only be able to open local images, or open file handles. Is there any straightforward way to make Imagick read remote images (without having to open and close file handles)?</p> <hr> <p>If someone could shed some light into any of these questions I will be very grateful.</p> <p>Thanks in advance!</p>
    singulars
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload