Adopting AVIF and WebP image formats

March 10, 2023 2 minute read

Earlier today I rolled out support for modern image formats on this website, specifically for images on my blog.

In effect, this means adopting the two most widely supported new standards, which are AVIF and WebP.

Because I can make use of the <picture> element, the effective adoption rate does not really matter: each browser will evaluate the different <source> tags inside the <picture> element and choose the first image format that they support. Older browsers will fall back to the original image, which I upload as a PNG or JPG.

To accomplish this, a picture element may look something like this:

<picture>
    <source srcset="/url/to/image.avif" type="image/avif">
    <source srcset="/url/to/image.webp" type="image/webp">
    <img src="/url/to/image.jpg" width="120" height="120" 
         alt="A picture of a thing.">
</picture>

Now, I run my own basic CMS here. In all honesty, I should probably migrate to something like Filament but I like my own project (“Atlas”) a lot since it’s great practice that allows me to tinker around with specific bleeding edge optimisations on newer versions of PHP. But I digress.

When I make posts, I upload images via drag-and-drop. When images are uploaded, they are now processed. I only upload normalized .jpg and .png files, and I have specific requirements for quality settings.

In my back-end, it is possible to see the size savings for each format. Depending on the format, the delta between the source format and the conversions can be rather large. The difference between WebP and AVIF is less pronounced, but it does matter.
In my back-end, it is possible to see the size savings for each format. Depending on the format, the delta between the source format and the conversions can be rather large. The difference between WebP and AVIF is less pronounced, but it does matter.

Here’s my method that does the work (it does require the GD extension):

public function generate(string $path): void
{
    $extension = pathinfo($path, PATHINFO_EXTENSION);

    $webp = str_replace($extension, 'webp', $path);
    $avif = str_replace($extension, 'avif', $path);

    $image = match ($extension) {
        'jpg' => imagecreatefromjpeg($path),
        'png' => imagecreatefrompng($path),
        default => null,
    };

    imagepalettetotruecolor($image);

    $quality = [
        'webp' => $extension == 'png' ? IMG_WEBP_LOSSLESS : 80,
        'avif' => $extension == 'png' ? 65 : 60,
    ];

    imageavif($image, $avif, $quality['avif']);
    imagewebp($image, $webp, $quality['webp']);
}

If you are looking for a more filesystem agnostic solution, you can always generate temporary conversions and then move them back using a Flysystem instance, for example. In fact, you might not need to write your own thing: there are already quite a few packages that allow you to do this.

However, since imageavif() is a recent addition to PHP, some of those packages may not support conversion to AVIF yet! Keep that in mind.

This basic approach is good enough for this blog, but you may also have success by setting up your CDN and having it serve optimized assets for you.

Tagged as: Programming