trying out avif transcoding
In honor of YUV420 day, I thought it would be fun to transcode JPEG images to AVIF in honk, or anywhere. I got lost in the weeds a few times along the way, but eventually found all the eggs.
honk predates widespread AVIF support by a year or two, but after a look at browser support, I think it’s now worth evaluating the potential benefit and required effort.
convert
Started by running a quick test with convert. Without any options, it converted my 500k sample.jpg to a 200k sample.avif. It didn’t look noticeably different, but that’s a pretty healthy size savings. (Without much tuning of JPEG quality parameters.)
libavif
There’s sample code for an encoder included in libavif that does just what we need. It’s also easy to access via cgo. Hook it in, and... wait.
Unfortunately, this was taking about six seconds to transcode an image. Saving some bytes won’t be worth it if it takes longer to get them. Need to figure out what convert is doing.
I wasn’t sure what encoder convert was using, but took a guess it was rav1e. Converted my code to using rav1e, which wasn’t hard, but then I discovered that it only outputs the bitstream. Not the needed file header, so the image wasn’t recognized. Misunderstanding on my part of how the pieces fit together.
Went back to libavif for a look at what the header writing code did, and noticed that it can use rav1e itself, but the important discovery was the speed option to the encoder. Turn this up to 10. The output is a little larger, but now it’s fast enough. I think we’re good.
prod
Checked in some code and pushed it to my testing server, liveprod. Was about to brag to the world, hey, check out this 500k image that’s now only 200k, but when I looked again, the avif version was 2000k. Hyperinflation! A little more back and forth with some debug code, and the difference is the result of using a much older libavif. Well, okay, this is a me problem, and it’ll get fixed in due time. Different quantifier presets or defaults or whatever, not really worth investigating further. We’re still good, or will be.
integration
I don’t want the complications of linking with libavif (and its transitive dependencies) all the time, but that’s what dlopen is for. I wrapped this up in something called lazif, the lazy AVIF encoder. If libavif is available, we can use it, but if it’s not, no worries.
I don’t like web servers that change image formats based on the Accept header. I right click to save an image expecting one format, and then a different format lands, and now I can’t open this stupid webp. And it would require me to configure the Vary header correctly, which sounds like work. So we’re definitely not doing that.
I found the <picture>
tag much nicer to work with. The <img>
tag is still the original JPEG, but now there’s a <source>
for the AVIF version. I named it filename.jpg.avif which is probably slightly triggering, but it makes it easy to find the original input, and it’s maybe a tiny bit informative that this is the second compression.