PHP Imagick JPEG Optimization

MaKR picture MaKR · Mar 20, 2015 · Viewed 10.9k times · Source

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

Answer

David Rojo picture David Rojo · Mar 23, 2015

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.