# 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.
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.imagerx.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.imagerx.transformImage(image, 'myTransform') %}
{% set transformedWebp = craft.imagerx.transformImage(image, 'myTransformWebp') %}
{% set transformedAvif = craft.imagerx.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.imagerx.clientSupports('avif') %}
{% set transformed = craft.imagerx.transformImage(image, 'myTransformAvif') %}
{% elseif craft.imagerx.clientSupports('webp') %}
{% set transformed = craft.imagerx.transformImage(image, 'myTransformWebp') %}
{% else %}
{% set transformed = craft.imagerx.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>
!