# WebP, AVIF, and JPEG XL support

For almost two decades now, JPEG, GIF and PNG have been the de-facto image formats of the web. These days, things are changing and several new, modern formats are positioned to break into the mainstream.

As browsers have finally started to support modern formats, support for converting images to these formats using GD/Imagick have become the limiting factor for adoption.

In addition to supporting these formats through the image driver as they become available, Imager X aims to speed up the transition by providing an additional interface that converts to modern image formats using a combination of GD/Imagick and command line tools, configured through the customEncoders config setting.

TIP

customEncoders was introduced in Imager X 3.5, and replaces several config settings which has been deprecated: useCwebp, cwebpPath, cwebpOptions, avifEncoderPath, avifEncoderOptions and avifConvertString.

When using customEncoders, what happens under the hood is that Imager will first transform the image to a temporary file in its original format with maximum quality, and then use the specified tool to convert the image to the modern format and compress it accordingly.

This will result in slightly less compression/lower quality, compared to converting it using an image driver directly. It also requires some extra processing on the server because each image is processed twice. But, the results are still really good, especially if the source image is of good quality and not heavily compressed in advance.

TIP

One of the features of JPEG XL is that it can optimize already compressed JPEG images, so when support for it lands in browsers, it will be a no-brainer to use cjxl or similar to optimize already generated JPEGs.

Here is a complete example for adding support for WebP, AVIF and JPEG XL through customEncoders:

'customEncoders' => [
    'webp' => [
        'path' => '/usr/local/bin/cwebp',
        'options' => [
            'quality' => 80,
            'effort' => 4,
        ],
        'paramsString' => '-q {quality} -m {effort} {src} -o {dest}'
    ],
    'avif' => [
        'path' => '/usr/local/bin/cavif',
        'options' => [
            'quality' => 80,
            'speed' => 7,
        ],
        'paramsString' => '--quality {quality} --speed {speed}  --overwrite -o {dest} {src}'
    ],
    'jxl' => [
        'path' => '/usr/local/bin/cjxl',
        'options' => [
            'quality' => 80,
            'effort' => 7,
        ],
        'paramsString' => '-q {quality} -e {effort} {src} {dest}'
    ]
]

These values are just an example, you can point whatever file extension at whatever command line tool path you have installed on your server. The options can also be whatever you want, they will be merged into the paramsString as shown above.

{src} and {dest} in the paramsString will automatically be replaced with the input and output file paths, and should always be present in the paramsString.

WARNING

Please note that if a custom encoder is present, it will be used to convert to a given format, even if GD or Imagick has support for it. You should always strive to use the encoder in the image driver when present, so make sure to remove the customEncoders setting once a file format receives support.

At the transform level, you can use the customEncoderOptions transform parameter to override one or more options for the encoder being used, as shown below.

{% set transformedImage = craft.imager.transformImage(image, { 
    width: 600,
    format: 'jxl', 
    customEncoderOptions: { quality: 55 }
}) %}

# Installing an encoder

The installation of an encoder in your dev environment and on your server, will vary depending on your environment. But here are some pointers to tools I've used in the past.

# WebP

Google has created a refernece tool for converting images to WebP, called cwebp (opens new window), which is available on a wide range of platforms (opens new window). On Ubuntu, it's as easy as running apt-get install webp, and on OSX it's available through Homebrew and MacPorts.

cwebp is a no-breainer for adding WebP support, it's fast and reliable and easy to install.

# AVIF

For AVIF, there is no reference tool. There are various tools available (opens new window), but I've found cavif by @kornelski (opens new window) to be the easiest option by far. The releases (opens new window) contain executables with no dependecies for any Linux distro, and also MacOS and Windows.

# JPEG XL

JPEG XL comes with a reference encoder cjxl (opens new window) that is a no-brainer. It currently has to be compiled from source, but the documentation in the repo is detailed and easy to follow.

# Other formats

The custom encoder functionality in Imager X is completely format agnostic. If you want to convert an image to some experimental format you want to test, all you need to do is find a tool that can convert from a legacy format to the new format, and add it to the customEncoder config.

# Delivering modern image formats using progressive enhancement

Some browsers don't support these modern image formats yet. Luckily, there's no reason you can't deliver it to the browsers that do. The easiest approach is to use the <picture> tag with a separate source for each file format.

Here's an example that delivers either an image in the source format, or an AVIF or WebP version, depending on what the user's browser support:

{% set transformedJpeg = craft.imager.transformImage(image, 'myTransform') %}
{% set transformedWebp = craft.imager.transformImage(image, 'myTransformWebp') %}
{% set transformedAvif = craft.imager.transformImage(image, 'myTransformAvif') %}

<picture>
    <source srcset="{{ transformedAvif | srcset }}" type="image/avif">
    <source srcset="{{ transformedWebp | srcset }}" type="image/webp">
    <img srcset="{{ transformedJpeg | srcset }}" 
         src="{{ transformedJpeg[0].url }}" 
         alt="A very progressive image">
</picture>

This is the approach I strongly recommend. It's standards compliant, you deliver the same markup to all visitors (which makes caching easy), and the only minor drawback is a sligthly bigger and more complex markup.

Another approach though, is to check what the browser supports before generating the transforms:

{% if craft.imager.clientSupports('avif') %}
    {% set transformed = craft.imager.transformImage(image, 'myTransformAvif') %}
{% elseif craft.imager.clientSupports('webp') %}
    {% set transformed = craft.imager.transformImage(image, 'myTransformWebp') %}
{% else %}
    {% set transformed = craft.imager.transformImage(image, 'myTransform') %}
{% endif %}

<img srcset="{{ transformed | srcset }}" 
         src="{{ transformed[0].url }}" 
         alt="A very progressive image">

With this solution, you'll be delivering different markup to different users, depending on what format they support. If you go down this route, make sure your caching accounts for this.

A third solution would be to do this at the webserver level. You'll typically create all the different transforms at once, but only output an <img> tag that only serves the jpg file. Then, in your webserver config, you check what the browser support, and append .avif or .webp to the filename conditionally if such a file exists. You can achieve this kind of naming schema in Imager by using the filenamePattern config setting at the template level. In practice... I find this a bit overly complex, unless you're an image transform SAAS (opens new window) that does this for a living.

Use <picture>!