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.
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.
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.