12

I'm using PHP Imagick to resize images at runtime. The site has an image upload feature and we can't trust the user to use web-friendly JPEGs, as during the mass-import there are many 3 to 5MB images, and even a few as large as 13MB. Each image gets turned into a thumbnail (200x200), and when going from list view into detailed view only one image is shown, so performance isn't a huge deal although we can't completely throw it by the wayside. Here's what we're doing so far:

$iMagick = new Imagick($file);
$iMagick->setImageResolution(72,72);
$iMagick->resampleImage(72,72,imagick::FILTER_UNDEFINED,1);
$geometry = $iMagick->getImageGeometry();
if ($geometry['height'] > 1920 || $geometry['width'] > 1080) {
    $iMagick->scaleImage(1920, 0);
    if($geometry['height'] > $resizeHeight) {
        $iMagick->scaleImage(0, 1080);
    }
}
$iMagick->setImageCompression(Imagick::COMPRESSION_JPEG);
$iMagick->setImageCompressionQuality($compression);
$iMagick->writeImage($file);
$Imagick->clear();

Side note: I just realized the flaw in my conditional logic here about height/width, so ignore that for the time being. I'll edit the question soon to reflect the correct condition comparisons. For those who didn't catch it it's scaling all images to 1920 wide even if it's supposed to be scaling the height, then after the scale it's sizing it back down to 1080 height.

The image I've used to test starts as a 3MB 2398×2400 image. Scaling it to 1079x1080 results in a 1.5MB image, then adding JPEG compression at quality 70 brings it down to 750KB. Using kraken.io the image was able to be compressed to just under 60KB. Are there any additional things I can do to this script since it needs to optimize the image at runtime?

I've read suggestions to use libjpeg, which is installed, but I can't find any documentation on what functions it enables or if there's a way to force Imagick to use it specifically. I'm not even sure that Imagick isn't using it already.

Edit: Solution

function itm_optimizeImage($file, $compression = 70, $maxDimensions = ['width' => null, 'height' => null]) {
    $save = false;
    $fi = new finfo(FILEINFO_MIME);
    $mime = explode(';', $fi->file($file));
    switch ($mime[0]) {
        // possible to optimize other image types in the future
        case 'image/jpeg':
            try {
                $iMagick = new Imagick($file);
                if ($iMagick->getImageCompressionQuality() > $compression) {
                    $file = !itm_compressJPEG($file, $compression, $maxDimensions, $iMagick);
                }
            }
            catch (Exception $e) {
                error_log(__FUNCTION__ . " $path/$file failed: " . $e->getMessage());
                return false;
            }
            if ($file) {
                $pathParts = pathinfo($file);
                rename($file, $pathParts['dirname'] . '/' . $pathParts['filename'] . '.large.' . $pathParts['extension']);
                $iMagick->writeImage($file);
            }
            $iMagick->clear();
            break;
    }

    return $file;
}

function itm_compressJPEG($file, $compression = 70, $maxDimensions = ['width' => null, 'height' => null], &$iMagick = null) {
    try {
        $iMagickCreated = true;
        if ($iMagick) $iMagickCreated = false;
        else $iMagick = new Imagick($file);

        $iMagick->setImageResolution(72,72);
        $iMagick->resampleImage(72,72,imagick::FILTER_UNDEFINED,1);
        $geometry = $iMagick->getImageGeometry();
        if (($geometry['width'] / $maxDimensions['width']) > ($geometry['height'] / $maxDimensions['height'])) {
            // scale by width
            $iMagick->scaleImage($maxDimensions['width'], 0);
        } else {
            // scale by height
            $iMagick->scaleImage(0, $maxDimensions['height']);
        }
        $iMagick->setImageCompression(Imagick::COMPRESSION_JPEG);
        $iMagick->setImageCompressionQuality($compression);
        $iMagick->setImageFormat('jpg');
        $iMagick->stripImage();

        if ($iMagickCreated) {
            $pathParts = pathinfo($file);
            rename($file, $pathParts['dirname'] . '/' . $pathParts['filename'] . '.large.' . $pathParts['extension']);
            $iMagick->writeImage($file);
            $Imagick->clear();
        }
        return $file;
    }
    catch (Exception $e) {
        error_log(__FUNCTION__ . " $path/$file failed: " . $e->getMessage());
        return false;
    }
}
MaKR
  • 1,882
  • 2
  • 17
  • 29
  • 60KB for a 1079x1080 image seems pretty decent. I don't think you can get it much smaller without decreasing pixel dimensions or seriously lowering the quality. – GolezTrol Mar 23 '15 at 14:36
  • The problem is that I'm not getting anywhere close to that with my script. It's at ~750KB right now, the 60KB was a comparison (what the kraken.io tool was capable of on the same image). I'm not looking at 60KB as an exact target, but under 200KB would be nice. – MaKR Mar 23 '15 at 14:39

1 Answers1

14

Set the setImageCompressionQuality to a value of 70 and add these two lines after it.

$image->setImageFormat("jpg");
$image->stripImage();

This will dramatically reduce the size of the images.

David Rojo
  • 2,337
  • 1
  • 22
  • 44
  • 1
    I had used stripImage() before and it had no effect. Adding setImageFormat('jpg') without stripImage() reduced the size by ~20KB more (we were attempting to save exif/meta data but it's secondary to performance). Using both together the final result of this test image was 178KB, which is about the perfect combination of quality and compression. Thanks! – MaKR Mar 23 '15 at 19:42
  • 1
    Does anybody have an explanation to the strange behavior described by @MaKR? Why should setImageFormat('jpg') be needed at all if the file name ends with .jpg? Why should setImageCompression() be needed in the first place? Is there an explanation to all of this Imagick nonsense? – matteo Sep 30 '16 at 17:46
  • Excellent solution... I was compressing 5MB images only down to 788KB, with these extra methods the files are into double digit KB sizes. – John the Ripper Nov 01 '17 at 11:43
  • This STILL works really well. Everything was reduced dramatically. A sample jpg went from 45747kb to 12818kb. Have to be careful though. The original file was uploaded by mistake as PNG and ballooned file size to 121778kb! So, keep it away from pngs, I guess... – LizardKG Jun 05 '20 at 21:58