JPEG XL has now landed in all major browsers, so I figured I'd take a closer look at what it's good/bad at compared to other formats.
I'll go into full detail, with examples and benchmarks, but for those of you who don't have the time, here are the headlines:
For lossy photos on the web, AVIF almost always produces smaller files than JPEG XL. At default effort settings, the best JPEG XL version is typically 15–25% larger than the equivalent AVIF. At maximum effort settings, the gap narrows a little, to typically 6–20%. There are outliers, where JPEG XL is close to, or even beats AVIF, so if you want the absolute best result for a particular image, it's worth trying both. See the F1 image for a typical example, and the weasel for an outlier.
The JPEG XL encoder matters a lot. jixel, a newer JPEG XL encoder, usually produces smaller files than libjxl, except when the image has detailed transparency. The people image is one example.
For screenshots and illustrations, AVIF is much better than JPEG XL. The equivalent JPEG XL is 30–90% larger at default effort settings, and 20–70% larger at maximum effort settings. Lossy AVIF handles these well - you don't need to use lossless formats unless you really need bit-exact results. See the screenshot and illustration examples.
For images with transparency, AVIF is much better than JPEG XL. The libjxl version is 31–44% larger at default effort settings, and 61–70% larger at maximum effort settings. jixel always encodes transparency losslessly, which makes its files 2–3x the size of AVIF's. See the data.
AVIF is generally faster to encode, and uses less memory, when you factor in file size. jixel is faster than AVIF and uses less memory, but its files are typically significantly larger, so AVIF is better on balance. See the F1 image for one example.
AVIF is faster to decode. JPEG XL typically takes 2–3x as long to decode at medium quality, but the gap narrows at very high qualities, where Chrome can sometimes decode JPEG XL faster. The people image is one example.
Higher effort settings are worth it for AVIF and jixel, but not libjxl. libjxl's higher efforts are much slower, use a lot more memory, and barely improve the result. Sometimes they produce larger files than the default effort. The screenshot is an example of this.
AVIF's 4:2:0 mode (half colour resolution) shouldn't be your default. It rarely reduces file size, and it makes higher qualities harder to reach. Although, it is quicker to encode and decode. Stick with 4:4:4 if you don't have time to check both. The F1 image is one example, and the screenshot is an exception.
Quality settings are reliable for all the encoders. With a single quality setting, the resulting quality varies a bit between images, but all the encoders vary in the same direction, and in ways that seem reasonable. See the data.
Progressive JPEG XL is basically free at high effort. jixel recently added support for early progressive previews, and at its slowest setting they cost little or nothing, but at its default setting the files are 12% bigger. Firefox and Chrome support progressive JPEG XL. Safari still renders the final image, but doesn't show progressive previews. However, is this type of progressive rendering a good user experience? See the examples and data.
AVIF supports progressive rendering! It's Chrome-only for now, but it's in progress for Firefox. It's more complex than progressive JPEG XL, but it gives you more control over what the preview looks like, the files are usually smaller, and they're much faster to decode. See for yourself.
For HDR, JPEG XL lacks gain maps, which is a significant limitation compared to AVIF. The JPEG XL format supports gain maps, but they aren't supported by browser decoders, and the encoding story is messy/experimental. When comparing non-gain mapped HDR content, the two quality metrics I used disagree a lot, but they agree that JPEG XL produces smaller files compared to AVIF. See the examples.
JPEG XL can make existing JPEGs 9–20% smaller, without further loss. It's also as fast or faster to decode, except for small images. See the data.
For lossless, JPEG XL is the clear winner. Lossless WebP is 9–17% larger for photos, and lossless AVIF is 30–120% larger. But lossless images are almost never what you want on the web. If you do use them on the web (like I do on this page), watch out for jixel's higher efforts - it often produces files that are too slow to decode. See the data.
Animated image formats continue to be a dumb idea. Please don't use them. Learn more, if you must.
For web use-cases, I'd currently default to AVIF 4:4:4, with the highest effort level I can be bothered with. If I felt AVIF was producing an unusually large file, I'd take a look at JPEG XL via jixel, and see if it did better.
Both AVIF and JPEG XL (via jixel) have seen massive improvements recently, so it's possible that this recommendation could change in future. There's been a lot of misinformation around this stuff, and your AI will happily deliver that misinformation to you. Instead, test things with your own images.
AVIF folks suggest that JPEG XL is technically limited compared to AVIF. That might be true, but in this article I'm focusing on results with today's implementations.
I 'created' a benchmarking script, that encodes images using various codecs at various settings, and records various details.
I'm using the latest released versions of the image codecs, which at time of writing is:
cjxl encoder)f487299), a second JPEG XL encoderI chose a selection of images, and encoded most of them to a width of 1598 pixels - which is 2x the maximum width images appear on this blog. This means they look sharp on high density displays, which most users have. As a result, if you're viewing this on a small or low density screen, you're not getting the full experience.
I ran these benchmarks on my 2024 MacBook Pro (M4 Pro).
When I first wrote about AVIF, I used an F1 car as the first image, so I figured I'd do that again!
I encoded the image at various codec qualities, and chose versions that were closest to particular SSIMULACRA2 scores - a perceptual image quality metric by the folks behind JPEG XL. 50 is considered medium quality, 70 high, 80 very high, and 90 visually lossless. Although, I'm not sure if these ratings take high density displays into account.
Throughout the article, the visual comparisons, like above, use each encoder's maximum effort, except in cases where maximum effort results in a larger file (yes, that happens).
For archival, I'd tend towards near-lossless qualities, but if I'm just displaying an illustrative image on my blog, performance is more important to me. I'm personally happy with the 70 result above. If you zoom in, you can see the quality loss, but if I was really concerned about the zoom-in case, I'd use something like the VisualViewport API to load in a higher resolution image when that happens, rather than make the download bigger for everyone.
At lower qualities you can see the difference in strategy between the codecs. The detail loss is around the same, but AVIF tends to smooth things out compared to the original, whereas JPEG XL seems to introduce rougher patterns.
Also, I feel that, at medium quality (50), the AVIF and libjxl look better than the jixel version. The jixel version has a lot of visible noise around the BWT logo, and the Microsoft text on the front wing is way less legible. It makes me wonder about the metric a bit.
Anyway, here's my interpretation of the results:
Here's the full data, so you can make up your own mind:
Ok, let's get to our first graph, the SSIMULACRA2 scores:
File size
20 kB40 kB60 kB80 kB100 kB120 kB140 kB160 kB
45505560657075808590
SSIMULACRA2 (higher is closer to the original)
There's going to be a lot of these graphs, so here's what's going on:
Here are the quality results using a different quality metric, ColorVideoVDP:
File size
20 kB40 kB60 kB80 kB100 kB120 kB140 kB160 kB
9.409.509.609.709.809.9010.00
ColorVideoVDP, JOD (10 = no visible difference)
The result is roughly the same except this metric puts more of a gap between AVIF and the JPEG XL images.
It also suggests there's some advantage to AVIF with 4:2:0 chroma subsampling at lower qualities, but it's minor.
When it comes to images on my blog, I encode with maximum effort settings. I'm happy to wait a couple of seconds to get an image that's faster to load for every user. However, if you're doing a lot of encoding on a server, encode time becomes more of a concern.
Encode time
0 ms500 ms1 s1.5 s2 s2.5 s3 s3.5 s
45505560657075808590
SSIMULACRA2 (higher is closer to the original)
Another thing that might be a concern if you're encoding images on a server is peak memory usage:
Peak encoder memory
0 MB100 MB200 MB300 MB400 MB500 MB600 MB700 MB800 MB900 MB1000 MB
45505560657075808590
SSIMULACRA2 (higher is closer to the original)
It's worth considering decoding performance. If decoding is particularly slow, then it might make the file size benefit less valuable, especially when images are loaded from a cache.
Decode time
4 ms6 ms8 ms10 ms12 ms14 ms16 ms18 ms20 ms22 ms
45505560657075808590
SSIMULACRA2 (higher is closer to the original)
JPEG XL becomes suddenly faster to decode at very high quality settings (but still slower than AVIF). I think this is due to an edge preserving filter that does more work at lower quality settings.
I've heard people say that AVIF's tendency to smooth out details makes it bad for human faces, so I picked this image from Unsplash.
The results of the benchmark for this image:
At lower qualities, you can see the smoothing on the face, but it's the same for both AVIF and JPEG XL.
File size
20 kB40 kB60 kB80 kB100 kB120 kB140 kB160 kB180 kB200 kB220 kB
4045505560657075808590
SSIMULACRA2 (higher is closer to the original)
File size
20 kB40 kB60 kB80 kB100 kB120 kB140 kB160 kB180 kB200 kB220 kB
9.459.509.559.609.659.709.759.809.859.909.9510.00
ColorVideoVDP, JOD (10 = no visible difference)
Encode time
0 ms500 ms1 s1.5 s2 s2.5 s3 s3.5 s4 s
4045505560657075808590
SSIMULACRA2 (higher is closer to the original)
Peak encoder memory
0 MB100 MB200 MB300 MB400 MB500 MB600 MB700 MB800 MB900 MB1000 MB1100 MB
4045505560657075808590
SSIMULACRA2 (higher is closer to the original)
Decode time
4 ms6 ms8 ms10 ms12 ms14 ms16 ms18 ms20 ms
4045505560657075808590
SSIMULACRA2 (higher is closer to the original)
It's interesting that, at lower qualities, jixel decodes much slower in Safari.
I threw this one in because it's the default image in Squoosh, but I guess the sharp lines of the hairs make it different to the previous images. In fact, even at medium quality, the difference from the original is much less perceptible to my eyes compared to the other images.
The results are much closer than the previous two:
File size
0 kB50 kB100 kB150 kB200 kB250 kB300 kB350 kB400 kB450 kB
4045505560657075808590
SSIMULACRA2 (higher is closer to the original)
File size
0 kB50 kB100 kB150 kB200 kB250 kB300 kB350 kB400 kB450 kB
9.309.409.509.609.709.809.9010.00
ColorVideoVDP, JOD (10 = no visible difference)
Encode time
0 ms1 s2 s3 s4 s5 s6 s
4045505560657075808590
SSIMULACRA2 (higher is closer to the original)
Peak encoder memory
0 MB200 MB400 MB600 MB800 MB1000 MB1200 MB1400 MB
4045505560657075808590
SSIMULACRA2 (higher is closer to the original)
Decode time
6 ms8 ms10 ms12 ms14 ms16 ms18 ms
4045505560657075808590
SSIMULACRA2 (higher is closer to the original)
I've played with both codecs a lot, and for photos, what I've shown so far is representative, where AVIF beats JPEG XL. However, every now and then there's an outlier, such as this image.
File size
0 kB100 kB200 kB300 kB400 kB500 kB600 kB700 kB800 kB900 kB
4045505560657075808590
SSIMULACRA2 (higher is closer to the original)
File size
0 kB100 kB200 kB300 kB400 kB500 kB600 kB700 kB800 kB900 kB
9.309.409.509.609.709.809.9010.00
ColorVideoVDP, JOD (10 = no visible difference)
Encode time
0 ms1 s2 s3 s4 s5 s6 s7 s8 s9 s
4045505560657075808590
SSIMULACRA2 (higher is closer to the original)
Peak encoder memory
0 MB200 MB400 MB600 MB800 MB1000 MB1200 MB1400 MB1600 MB1800 MB2000 MB2200 MB
4045505560657075808590
SSIMULACRA2 (higher is closer to the original)
Decode time
10 ms12 ms14 ms16 ms18 ms20 ms22 ms24 ms26 ms28 ms30 ms32 ms
4045505560657075808590
SSIMULACRA2 (higher is closer to the original)
Ok, let's try something other than a photo:
The results of the benchmark:
Some developers, especially those who've been around as long as I have, seem to think things like screenshots need to be delivered in a lossless format. But I think this is because they're used to formats like JPEG and WebP (and seemingly JPEG XL) which handle this kind of image badly, whereas lossy AVIF handles them really well.
File size
0 kB10 kB20 kB30 kB40 kB50 kB60 kB70 kB
2030405060708090
SSIMULACRA2 (higher is closer to the original)
I'm not including the ColorVideoVDP chart because the metric doesn't support alpha channels.
Encode time
0 ms500 ms1 s1.5 s2 s2.5 s3 s3.5 s4 s4.5 s5 s
2030405060708090
SSIMULACRA2 (higher is closer to the original)
Peak encoder memory
0 MB100 MB200 MB300 MB400 MB500 MB600 MB700 MB800 MB
2030405060708090
SSIMULACRA2 (higher is closer to the original)
Decode time
4 ms6 ms8 ms10 ms12 ms14 ms16 ms18 ms20 ms22 ms
2030405060708090
SSIMULACRA2 (higher is closer to the original)
The high JPEG XL decoding times here are likely a side effect of it producing much larger files for a given quality.
In many cases, for largely flat-colour images, you'd use SVG. But sometimes that isn't possible (it's a screenshot), or undesirable, such as this case. This image, also from Unsplash, is still 1.1 MB as a compressed and minified SVG. This is due to the complexity of the shaded areas. As a result, a raster format wins on file size and decode/draw time.
At quality 50 in the demo above, the jixel has some pretty severe colour blocks, e.g. on the cat's face. Again, it makes me question the metric a bit.
The results of the benchmark:
File size
0 kB50 kB100 kB150 kB200 kB250 kB300 kB350 kB400 kB
30405060708090
SSIMULACRA2 (higher is closer to the original)
File size
0 kB50 kB100 kB150 kB200 kB250 kB300 kB350 kB400 kB
9.29.39.49.59.69.79.89.910.0
ColorVideoVDP, JOD (10 = no visible difference)
Encode time
0 ms1 s2 s3 s4 s5 s6 s7 s8 s
30405060708090
SSIMULACRA2 (higher is closer to the original)
Peak encoder memory
0 MB200 MB400 MB600 MB800 MB1000 MB1200 MB1400 MB1600 MB1800 MB2000 MB2200 MB
30405060708090
SSIMULACRA2 (higher is closer to the original)
Decode time
5 ms10 ms15 ms20 ms25 ms30 ms35 ms40 ms
30405060708090
SSIMULACRA2 (higher is closer to the original)
So far, all the images have been mostly opaque. What about an image with transparency?
There are a few things that make this one different:
The results of the benchmark:
File size
20 kB40 kB60 kB80 kB100 kB120 kB140 kB160 kB180 kB
30405060708090
SSIMULACRA2 (higher is closer to the original)
Encode time
0 ms500 ms1 s1.5 s2 s2.5 s3 s
30405060708090
SSIMULACRA2 (higher is closer to the original)
Peak encoder memory
0 MB50 MB100 MB150 MB200 MB250 MB300 MB350 MB400 MB450 MB
30405060708090
SSIMULACRA2 (higher is closer to the original)
Decode time
3 ms4 ms5 ms6 ms7 ms8 ms9 ms10 ms11 ms12 ms13 ms14 ms
30405060708090
SSIMULACRA2 (higher is closer to the original)
The images so far have been a similar resolution. Are the results different with smaller images?
Not really. The results are pretty similar to the full-size F1 image:
File size
4 kB6 kB8 kB10 kB12 kB14 kB16 kB18 kB20 kB22 kB
354045505560657075808590
SSIMULACRA2 (higher is closer to the original)
File size
4 kB6 kB8 kB10 kB12 kB14 kB16 kB18 kB20 kB22 kB
9.109.209.309.409.509.609.709.809.9010.00
ColorVideoVDP, JOD (10 = no visible difference)
Encode time
0 ms200 ms400 ms600 ms800 ms1 s1.2 s
354045505560657075808590
SSIMULACRA2 (higher is closer to the original)
Peak encoder memory
10 MB20 MB30 MB40 MB50 MB60 MB70 MB80 MB90 MB100 MB110 MB
354045505560657075808590
SSIMULACRA2 (higher is closer to the original)
Decode time
0.5 ms1 ms1.5 ms2 ms2.5 ms3 ms3.5 ms4 ms4.5 ms
354045505560657075808590
SSIMULACRA2 (higher is closer to the original)
The numbers Safari gives are rounded, so the data is really noisy.
What about an 18 megapixel image?
Mostly the same story as the regular F1 image, but with a few differences:
File size
0 kB100 kB200 kB300 kB400 kB500 kB600 kB700 kB800 kB
30405060708090
SSIMULACRA2 (higher is closer to the original)
File size
0 kB100 kB200 kB300 kB400 kB500 kB600 kB700 kB800 kB
9.29.39.49.59.69.79.89.910.0
ColorVideoVDP, JOD (10 = no visible difference)
Encode time
0 ms5 s10 s15 s20 s25 s30 s35 s40 s
30405060708090
SSIMULACRA2 (higher is closer to the original)
Peak encoder memory
0 MB1000 MB2000 MB3000 MB4000 MB5000 MB6000 MB7000 MB8000 MB9000 MB
30405060708090
SSIMULACRA2 (higher is closer to the original)
Decode time
40 ms60 ms80 ms100 ms120 ms140 ms160 ms180 ms
30405060708090
SSIMULACRA2 (higher is closer to the original)
Another criticism I've heard levelled at AVIF is that its quality is unreliable. As in, a single quality setting is great for some images, but no good for others, meaning for automatically encoded images, you need to pick a higher quality setting to be safe, resulting in larger file sizes than necessary for some images.
For the images I've looked at so far, I encoded them with avifenc -q 50, cjxl -q 70, and jixel at quality 80, and recorded the resulting SSIMULACRA2 scores:
| Image | AVIF | JPEG XL | JPEG XL (jixel) | |||||
|---|---|---|---|---|---|---|---|---|
| s6 4:4:4 | s0 4:4:4 | s6 4:2:0 | s0 4:2:0 | e7 | e10 | fast | extraslow | |
| F1 | 74.6 | 75.9 | 73.4 | 73.8 | 79.2 | 78.0 | 75.3 | 77.3 |
| F1 (small) | 70.9 | 73.5 | 68.4 | 69.5 | 75.6 | 73.9 | 70.6 | 72.6 |
| F1 (large) | 77.0 | 77.4 | 76.2 | 76.6 | 80.1 | 79.2 | 77.5 | 78.9 |
| People | 70.7 | 72.0 | 69.8 | 70.5 | 69.6 | 70.3 | 69.1 | 72.3 |
| Red panda | 67.5 | 68.8 | 67.0 | 67.2 | 69.5 | 68.4 | 69.2 | 72.3 |
| Weasel | 56.7 | 57.2 | 56.0 | 56.3 | 56.6 | 55.4 | 54.9 | 59.4 |
| Screenshot | 80.2 | 80.4 | 82.3 | 81.9 | 86.5 | 87.6 | 76.6 | 80.3 |
| Illustration | 78.6 | 79.7 | 76.8 | 77.9 | 80.3 | 78.5 | 75.4 | 77.7 |
| Alpha | 67.3 | 69.8 | 63.6 | 64.9 | 69.4 | 66.9 | 66.8 | 69.6 |
The behaviour of the codecs here seems really similar. All produce a range of SSIMULACRA2 scores, and they all go higher or lower for the same images.
And I think this is ok? Like, all three go for a lower quality for the Weasel picture, which removed noise without reducing quality of the image subject. With the screenshot, all go for a higher quality, which seems justified to my eyes.
Obviously this is a small sample size, but I haven't seen anything that suggests using a single quality setting is a risk, for any of the three codecs.
Also see Halide Compression's deeper dive into this.
Both AVIF and JPEG XL are capable of showing something progressively as the image loads. Chrome and Firefox support progressive JPEG XL, whereas only Chrome supports progressive AVIF. So, and I say this through gritted teeth, this next demo is best viewed in Chrome (I have a Firefox patch in progress):
This is the 18 megapixel image again, so if you're viewing this on a device with a smaller screen, you're not getting the full effect.
Progressive AVIF and JPEG XL work very differently. With JPEG XL, you can opt into the data being stored in such a way that a decoder can render low resolution versions, before it brings in the higher resolution tiles. jixel recently (like, yesterday at time of writing) added support for this too. It's called "progressive DC", and it's the most important kind of progressive rendering for JPEG XL.
With AVIF, progressions work more like frames of a video. You provide up to four frames, so that's three phases before the final image. Again, like a video, each frame can use data from the previous frame as an optimisation. An advantage of this is you can choose exactly how those frames look. In the example above, I went with a single preview frame: a 1/8th resolution version, with a bit of blurring. But, your preview frames can be completely different images if you really want.
There's a lack of data when it comes to progressive images and user experience, so these are just my reckons:
If you're on a bad connection, data doesn't arrive gradually like some throttling tools will have you believe. It tends to come in bursts. So, there's little benefit to progressive rendering on smaller images, and I'm not sure that many progressive phases is particularly useful.
I think that it's nice to have a representative preview for images that may take some time to load. I've experienced this personally, where a site or app has given me something clearly not-loaded, but with enough detail to know what's coming. That's useful to me, as I can decide things like, if it's worth waiting for the whole thing. This is also why I think things like BlurHash are cute, but useless. They're not a representative preview, so there's little a user can do with the information provided.
The preview frame I chose for the AVIF is enough to tell it's an open-wheel race car. If you know F1, you'll know what team it is. You might even know where the photo was taken. And it delivers that in ~11.5 kB.
The JPEG XL gets a BlurHash-like preview on screen in ~2 kB, but it takes until ~63 kB to match the preview quality of the AVIF. After that, it continues to improve gradually, providing a more detailed and higher quality image as more data arrives.
However, I'm not convinced this is a good user experience, as it presents the user with something they may mistake for fully-loaded, but poor quality. For example:
Here's a crop of the JPEG XL, 80% loaded. It seems reasonable that someone would look at that, and just assume I took the picture badly, leaving parts out of focus. You could probably convince me that blurring the ApeCoin logo is a societal benefit, but it still feels like a suboptimal user experience.
AVIF's system gives me more control, but it's also more complex.
I encoded the large F1 image with and without progressive rendering enabled, adjusting the quality setting to keep the SSIMULACRA2 score around 70:
| Encoding | Quality | File size | SSIMU2 | Encode | Decode | |||
|---|---|---|---|---|---|---|---|---|
| Time | Memory | Firefox | Chrome | Safari | ||||
| AVIF s6 | 41 | 175 kB | 69.9 | 405 ms | 381 MB | 46 ms | 41 ms | 53 ms |
| AVIF s6 prog | 42 | 173 kB | 70.2 | 815 ms | 764 MB | 49 ms | 53 ms | 56 ms |
| Change | +1 | −1% | +0.3 | +101% | +101% | +7% | +29% | +6% |
AVIF s6 prog -k | 42 | 181 kB | 70.3 | 1.09 s | 740 MB | 45 ms | 35 ms | 49 ms |
| Change | +1 | +3% | +0.4 | +169% | +94% | −2% | −15% | −8% |
| AVIF s0 | 41 | 145 kB | 69.9 | 8.00 s | 450 MB | 47 ms | 41 ms | 48 ms |
| AVIF s0 prog | 42 | 165 kB | 70.5 | 8.71 s | 847 MB | 54 ms | 59 ms | 52 ms |
| Change | +1 | +14% | +0.6 | +9% | +88% | +15% | +44% | +8% |
AVIF s0 prog -k | 41 | 152 kB | 69.6 | 10.72 s | 798 MB | 47 ms | 40 ms | 46 ms |
| Change | ±0 | +5% | −0.3 | +34% | +77% | ±0% | −2% | −4% |
| JPEG XL e7 | 35.5 | 258 kB | 70.0 | 1.62 s | 2.49 GB | 131 ms | 105 ms | 162 ms |
| JPEG XL e7 prog | 25.3 | 233 kB | 70.0 | 1.40 s | 1.90 GB | 119 ms | 121 ms | 183 ms |
| Change | −10.2 | −10% | ±0.0 | −14% | −24% | −9% | +15% | +13% |
| JPEG XL e10 | 41.2 | 258 kB | 70.0 | 22.74 s | 5.67 GB | 132 ms | 107 ms | 162 ms |
| JPEG XL e10 prog | 30.2 | 242 kB | 70.0 | 25.54 s | 6.08 GB | 119 ms | 128 ms | 181 ms |
| Change | −11.0 | −6% | ±0.0 | +12% | +7% | −10% | +20% | +12% |
| jixel fast | 66.1 | 225 kB | 70.0 | 304 ms | 361 MB | 131 ms | 89 ms | 281 ms |
| jixel fast prog | 60.1 | 252 kB | 70.0 | 333 ms | 393 MB | 132 ms | 97 ms | 290 ms |
| Change | −6.0 | +12% | ±0.0 | +10% | +9% | +1% | +9% | +3% |
| jixel extraslow | 65.1 | 185 kB | 70.0 | 16.75 s | 7.61 GB | 141 ms | 92 ms | 294 ms |
| jixel extraslow prog | 55.4 | 166 kB | 70.0 | 14.90 s | 8.94 GB | 140 ms | 98 ms | 293 ms |
| Change | −9.7 | −10% | ±0.0 | −11% | +17% | −1% | +7% | ±0% |
Let's get back to lossy compression, but this time HDR! There are a few ways to encode HDR images, and we're going to look at two.
Perceptual Quantization (PQ) tells each pixel what to be. Each value maps to an absolute brightness, up to 10,000 nits, with the curve shaped so the data is spent where our eyes are most sensitive. If the image asks for colours and brightnesses the display can't show (no consumer display can hit 10,000 nits), the browser/OS has to tone-map it down, and how well that's done varies between platforms.
Gain maps are different. The main image is a regular SDR image (usually), and alongside it is a 'gain map', which applies a brightness and colour change to each pixel to achieve the HDR version.
This has two benefits:
CSS and <picture> let us serve an SDR or PQ image to the user via the dynamic-range media query, but then you're relying on tone mapping in cases where the browser signals HDR support, but it doesn't support the full range.
Although the JPEG XL format supports gain maps, no browser handles them, and the encoding story is messy.
AVIF supports gain maps, and you encode them by providing two images: the SDR image and the HDR image. It uses those to calculate and encode the gain map.
Your browser/screen doesn't support HDR, so the PQ images will look bad.
At medium brightnesses, on an HDR screen, the gain map and PQ images look the same. At maximum brightnesses, on my Pixel 10 Pro and 2024 MacBook Pro, the PQ images get a bit blown out, and the gain map image looks better. At low brightness on my Pixel 10 Pro, again the gain map image looks better.
This is because, at extreme brightness levels, the device doesn't have the full HDR headroom to display the image, and gain maps can handle these cases better. So, although gain maps seem to add a lot to the file size, I think it's worth it.
Maybe there's a future where tone mapping gets much better, and the advantage of a gain map becomes less significant.
Regular SSIMULACRA2 only understands SDR, so for this image, the scores come from an experimental HDR mode in fast-ssim2. That means these scores can't be compared to the SSIMULACRA2 scores elsewhere in this article. ColorVideoVDP handles HDR natively, and here it's modelling a 1,500 nit HDR monitor.
The two metrics disagree a lot with this image. And they don't factor in the benefits of gain maps, so treat these results as a comparison between the PQ variants:
It will be interesting to see how JPEG XL handles gain maps once they're fully supported.
By the way, these are signs that indicate whether my cats are inside or outside, and the little display indicates how long until their next automated feed.
File size
0 kB100 kB200 kB300 kB400 kB500 kB600 kB
0102030405060708090
SSIMULACRA2 (higher is closer to the original)
File size
0 kB100 kB200 kB300 kB400 kB500 kB600 kB
9.009.109.209.309.409.509.609.709.809.9010.00
ColorVideoVDP, JOD (10 = no visible difference)
Encode time
0 ms2 s4 s6 s8 s10 s12 s
0102030405060708090
SSIMULACRA2 (higher is closer to the original)
Peak encoder memory
0 MB200 MB400 MB600 MB800 MB1000 MB1200 MB1400 MB1600 MB1800 MB2000 MB
0102030405060708090
SSIMULACRA2 (higher is closer to the original)
Decode time
10 ms20 ms30 ms40 ms50 ms60 ms70 ms80 ms
0102030405060708090
SSIMULACRA2 (higher is closer to the original)
JPEG XL allows you to convert an existing JPEG to JPEG XL without additional loss. I tested this with the F1 image at a range of sizes and JPEG qualities:
Of course, if you have the original, you'd be way better off creating a proper new lossy version with AVIF or JPEG XL, but it's pretty cool that the JPEGs end up smaller, and sometimes faster to decode. Although, I suppose this is just pointing to a missing optimisation in browser JPEG decoders.
File size
0 kB100 kB200 kB300 kB400 kB500 kB600 kB
0246810121416
Megapixels
Decode time
0 ms10 ms20 ms30 ms40 ms50 ms60 ms
0246810121416
Megapixels
It is very rare to need lossless images on the web. As we've already seen, even for things like screenshots and illustrations, lossy AVIF handles them really well. Ironically, this page is one of the outliers, because I wanted to show the compressed versions vs their lossless counterparts. Thankfully, JPEG XL is great at lossless!
The results:
| Image | Default effort | Highest effort | ||||||
|---|---|---|---|---|---|---|---|---|
| libjxl | jixel | WebP | AVIF | libjxl | jixel | WebP | AVIF | |
| F1 | 1.07 MB | +32% | +16% | +49% | 1.01 MB | −1% | +20% | +47% |
| People | 1.39 MB | +22% | +17% | +49% | 1.34 MB | −1% | +19% | +44% |
| Red panda | 1.78 MB | +17% | +9% | +40% | 1.74 MB | −1% | +11% | +31% |
| Weasel | 2.91 MB | +16% | +13% | +96% | 2.79 MB | −2% | +17% | +29% |
| Screenshot | 92 kB | +81% | +9% | +122% | 68 kB | +6% | +44% | +176% |
| Illustration | 361 kB | +78% | −1% | +100% | 273 kB | −6% | +5% | +139% |
| Alpha | 522 kB | +21% | +12% | +33% | 487 kB | −3% | +13% | +32% |
File size
1 MB1.1 MB1.2 MB1.3 MB1.4 MB1.5 MB1.6 MB
0 ms5 s10 s15 s20 s25 s30 s
Encode time
File size
1 MB1.1 MB1.2 MB1.3 MB1.4 MB1.5 MB1.6 MB
0 MB100 MB200 MB300 MB400 MB500 MB600 MB700 MB800 MB900 MB1000 MB
Peak encoder memory
File size
1 MB1.1 MB1.2 MB1.3 MB1.4 MB1.5 MB1.6 MB
0 ms20 ms40 ms60 ms80 ms100 ms120 ms140 ms160 ms180 ms200 ms220 ms240 ms
Decode time
Animated image formats are dumb and animated JPEG XL is no exception. Video formats are better at video. They're built for it. They're optimised for it. Not just in terms of encoding, but also in terms of playback.
I encoded a short clip, and the animated AVIF was 725 kB, and the JPEG XL was 2.17 MB for the same ColorVideoVDP score (9.5).
The JPEG XL is super noisy compared to the AVIF, because each frame is encoded individually, and arttifacts land in different places per frame.
AVIF is basically the keyframe format of AV1 video, with a bunch of still-image-specific optimisations thrown in. Animated AVIF however, is pretty much just a repackaging of AV1 video. You can convert an AV1 MP4 to AVIF and back losslessly - it's just a container change. That's why it performs so much better, because it's built for motion.
However, animated AVIF plays like shit in Chrome and Safari. It's fine in Firefox, so this is purely an implementation problem. But… <video muted autoplay> is right there.
The interesting detail of animated AVIF is that it supports transparency, whereas AV1 video doesn't. But really, that just highlights that it's dumb that AV1 didn't support transparency.
No, I'm not adding a demo. I don't want it on my server. Use video formats for video.
I already gave you my conclusions at the start, so I guess I'm ready to hit publish.
I'm a little nervous about this, because discourse around JPEG XL can get a little… weird. I see a lot of conspiracy theories about Google trying to 'silence' JPEG XL, even though one of the leads of the project worked at Google, as part of a team that were working on JPEG XL. That same leader claimed that the post-COVID layoffs wouldn't have happened if companies simply used JPEG XL. I was recently called a racist for asking about JPEG XL use-cases. This kind of thing just doesn't happen with other technologies and standards I follow. So, urm, I'm kinda bracing for impact here.
My first version of this article was very negative about JPEG XL, but then I heard about jixel, and honestly, I'm really excited about it. There's serious competition between AVIF and JPEG XL now, and whoever wins, we'll all benefit from better image compression.