Optimizing Images in PHP: Handling GIFs Without Breaking Animation

Optimizing Images in PHP: Handling GIFs Without Breaking Animation

After reading Steven Fox’s post on A Hack for Efficient Infinite Scrolling in Your Livewire App I started to think on what’s been bothering me most on the Pinkary platform. As you can guess it was the broken gifs! So I’ve decided to get to the bottom of this.

This example is based on the Pinkary project, but as long as your environment has the Imagick extension, it can be used in any PHP code.

💡
If you don’t know what Pinkary is it’s a fully open sourced microblog-ish linkshare-ish platform and the community is full of php and laravel developers. https://github.com/pinkary-project/pinkary.com

I don’t want this post to turn into a 'what and why' explanation instead of focusing on the 'how.' But just to give a quick overview in case you're unfamiliar with image optimization: it's all about reducing the file size of an image without losing quality, making it more suitable for the web. Fortunately, PHP provides powerful libraries like Imagick (the PHP extension for ImageMagick) that enable us to optimize images on the server side.

For me, the most important effects are the impact on page speed and bandwidth usage, but it's really useful from many aspects. Google it. Please.

Let’s take a look at what we’ve been using in Pinkary so far (I’ll update if my PR gets approved).

\App\Livewire\Questions\Create::optimizeImage :

public function optimizeImage(string $path): void
{
    $imagePath = Storage::disk('public')->path($path);
    $imagick = new Imagick($imagePath);

    $imagick->resizeImage(1000, 1000, Imagick::FILTER_LANCZOS, 1, true);

    $imagick->stripImage();

    $imagick->setImageCompressionQuality(80);
    $imagick->writeImage($imagePath);

    $imagick->clear();
    $imagick->destroy();
}

This function does a great job for static images (JPEG, PNG, etc.), but it won't work for animated visuals like GIFs since they have multiple frames. If a GIF is passed into this function, it will resize and optimize only the first frame, turning the animation into a single, static image.

💡
So, we had to come up with a solution to detect if the visual, in our case the image, has multiple images or not, and then decide whether to optimize all frames or just the static visual if that’s the case. But how do we understand if the given image has frames or not? It’s simple. The Imagick extension provides a wide set of functions and features to detect and manipulate almost everything about the image, such as frames.

Even though we knew it was a multiple-frame image, we needed to prepare the image object to optimize every frame. Otherwise, we would encounter the same issue of optimizing only the first frame.

Now we have another feature to use for this it’s Imagick::coalesceImages. It returns a new Imagick object on success which all frames prepared to be processed like:

$image = new Imagick($path);

$image = $image->coalesceImages();

foreach ($image as $frame) {
    //handle the frame optimization process
}

Let’s see the final version of the optimization and examine how it works
\App\Livewire\Questions\Create::optimizeImage :

public function optimizeImage(string $path): void
{
    $imagePath = Storage::disk('public')->path($path);
    $imagick = new Imagick($imagePath);

    // Check if the image is animated by looking at the number of frames
    if ($imagick->getNumberImages() > 1) {
        $imagick = $imagick->coalesceImages(); // Prepare to work with all frames

        foreach ($imagick as $frame) {
            $frame->resizeImage(1000, 1000, Imagick::FILTER_LANCZOS, 1, true);
            $frame->stripImage();
            $frame->setImageCompressionQuality(80);
        }

        $imagick = $imagick->deconstructImages(); // Optimize the animation
        $imagick->writeImages($imagePath, true); // Save all frames back into the file
    } else {
        // Process the image normally if it is not animated
        $imagick->resizeImage(1000, 1000, Imagick::FILTER_LANCZOS, 1, true);
        $imagick->stripImage();
        $imagick->setImageCompressionQuality(80);
        $imagick->writeImage($imagePath);
    }

    $imagick->clear();
    $imagick->destroy();
}

Before:

End Result:

That’s how you can optimize every frame of your animated visuals on your php application. I learned a lot while working on it, and I hope you did too.

😈
If you are a PHP developer and still don’t have a pinkary.com account, I curse you. See you next time.