AVIF Progressive Decoding Hacks!
Background
AVIF and WebP are designed to be final web delivery formats, meaning they prioritize being efficient over having a large feature set that might be desirable for working on the image. Unfortunately for some web developers, that means they also don't have progressive decoding like JPEG and JPEG-XL can have. That means that users won't get a low-quality preview of the image before it fully loads, which may impact the page experience on very slow connections. However, using these hacks, you can add a low-quality preview to your images for any format that doesn't support progressive decoding for a very low size cost. To test the methods used in the article, use Dev Tools to throttle networking and refresh the page. If the image isn't visible until the full quality version loads, it failed to be progressive. All images in this article are my own photos.
A Progressive JPEG
The below image is a plain old JPEG that has been encoded progressively at quality 75 with MozJPEG. The coding efficiency isn't good when you use JPEG, so it will take much longer to fully load the image than an AVIF of the same quality. However, as a consolation prize, you at least should see the image in low quality before it's fully loaded. This doesn't require any hacks or special effort to make it work other than encoding the JPEG with MozJPEG.
A Non-Progressive AVIF
The below image is a regular AVIF that has been encoded with AVIFenc. If you can't see this image, get a better browser. Microsoft Edge and older versions of Safari may not support AVIF. The coding efficiency with AVIF is a significant improvement over JPEG, such that you can often make AVIFs that are half the size and the same quality as a JPEG. However, you won't get the progressive loading of JPEG. Despite this, the page experience on a slow connection should still be better than a JPEG in almost all cases, but with some clever tricks, we can get progressive decoding back - even with AVIF.
The Animated AVIF Hack
All of the latest versions of browsers that support AVIF should now also support animated AVIF. We can exploit animated AVIF as a means for "progressive loading" in AVIF images. To do this, we want to create an animated AVIF that plays only once at 1000 fps so that it plays as quickly as it loads. We can choose how the loading progresses in any way we desire by putting any number of lower quality frames before the final high quality frame, which is displayed to the user when the image loads fully. However, there's just one small wrinkle: not all browsers play frames of an animated image before it is fully loaded. We deliberately want the browser to play the first frame, then stall playback until subsequent frames are loaded. Some browsers, however, will preload the entire animation before playing any frames to prevent perceived animation lag during playback. This means that the "progressive" animated AVIF will load exactly like a non-progressive image would. Chrome is known to play animations while loading, stalling the animation if necessary (works with this hack), and Firefox preloads animations (this hack won't work). Additionally, if a browser supports only still AVIF, the user may only see the first (and thus lowest quality) frame, which is a cringe user experience.
The size cost of this hack is the size of any lower quality frames you choose to add to the animations. For a single CQ-level 63 frame, the size cost would be 1-4% depending on what reasonable quality you choose for the main image. Here are the pros and cons of using this hack:
-
Pros:
- No modification to the HTML is needed to use this hack, just drop in the new AVIF files
- You can customize exactly what sequence of progressive loading you want, even with multiple levels
- If this hack is formally adopted as a solution and tooling is made, inter prediction would be possible, which would even outperform traditional progressive image decoding
-
Cons:
- All frames have to be the same resolution, bit depth, and chroma subsampling, so CQ 63 is the lowest you can go, even though a small CQ 48 image would be more efficient
- Older browsers that support only still AVIF may display only the first (and therefore worst) frame
- Not all modern browsers choose to actually load it progressively, if they don't, it will behave similarly to a normal AVIF
#!/bin/bash
# Progressive AVIF hack: 1000 fps animated avif with a loop count of 1
# Usage: progressive-avifenc.sh input.png output.avif quality speed
# e.g. progressive-avifenc.sh input.png output.avif 24 4
# Limitations: Probably won't preserve XMP/ICC/EXIF as it uses MKV intermediates
noextension=${2%.*}
noextension=${noextension##*/}
# Determine whether 4:4:4 should be used
yuv="420"
if [[ $3 -le 12 ]] ; then
yuv="444"
fi
# Make the two AVIFs
avifenc -c aom -s $4 -j all -y $yuv -d 10 --min 63 --max 63 --minalpha 63 --maxalpha 63 "$1" "$noextension.bh.avif" # blurhash
avifenc -c aom -s $4 -j all -y $yuv -d 10 --min $3 --max $3 --minalpha $3 --maxalpha $3 "$1" "$noextension.hq.avif" # high quality
# Convert them to MKVs so that they can be concatenated by mkvtoolnix (it doesn't read an AVIF)
ffmpeg -i "$noextension.bh.avif" -c copy "$noextension.bh.mkv"
ffmpeg -i "$noextension.hq.avif" -c copy "$noextension.hq.mkv"
# Nuke the original AVIFs
rm "$noextension.bh.avif" "$noextension.hq.avif"
# Concatenate the MKVs with MKVToolNix
mkvmerge --default-duration 0:1000fps "$noextension.bh.mkv" + "$noextension.hq.mkv" -o "$noextension.final.mkv"
# Nuke the original MKVs
rm "$noextension.bh.mkv" "$noextension.hq.mkv"
# Convert the MKV to the final AVIF
ffmpeg -i "$noextension.final.mkv" -c copy -loop 1 "$2"
# Nuke that MKV
rm "$noextension.final.mkv"
echo "Done encoding $2!"
Below is an example of an image encoded using this hack. Remember to have a look at how it works by throttling your network through DevTools:
The HTML DataURL Blurhash Hack
Using some clever HTML and CSS, we can add a DataURL for a very small AVIF image that displays underneath the main image. This means the user will see a low quality preview instantly, and then it will be covered by the full quality image when it loads in full. This will work in all browsers that support AVIF. On the encoding side, all you have to do is encode a small (resized to roughly 25%x25%) AVIF image at around CQ-level 48 with the slowest speed setting possible, then convert it into a valid DataURL. However, you will have to modify your CSS and HTML to insert this type of preview, so it won't work if your site is controlled by a content delivery network.
The size cost for this hack is about 500-8000 bytes per image depending on the size of the original. Here are the pros and cons of using this hack:
-
Pros:
- Good efficiency with the preview due to the lower res and lower CQ level closer to the ideal point
- Works in all compliant browsers
- Previews render faster than with traditional progressive decoding due to all previews being baked into the document
-
Cons:
- Difficult to automate across pages
- PageSpeed Insights HATES this trick: it thinks the preview is a real image and complains of low resolution
- You can only add a single preview frame
Conclusion
If you think that progressive decoding is a mandatory feature for any format you put onto your website, these tricks should alleviate that concern. However, since the AVM (AOM Video Model for features that will be in AV2) is already showing large improvements over AVIF for image encoding despite being unoptimized for it, it's clear that when it's released within a couple of years, "AV2F" will be the new dominant web image format. It'd be really nice if that included a formal way to add basic progressive image decoding since these hacks are more difficult to implement.