DEV Community

AsyncMonk
AsyncMonk

Posted on

The first AVIF export in a web tool is mostly a download

I'm adding AVIF export to a small image tool I run, and I started with the lazy option, which was asking the browser to do it. AVIF is the image format built on the AV1 video codec, and canvas.toBlob() is the call that turns a canvas into an image file of whatever type you name. I asked for image/avif on a test page. Chromium 149, Chrome 155 and Firefox 151 all answered without an error and handed me a blob whose type was image/png. Only WebKit 26.5 on macOS returned a real AVIF. In three of the four browsers I tried, then, I have to ship my own encoder, the piece of code that turns pixels into AVIF bytes, compiled to WebAssembly (WASM) so it can run inside the page.

The encoder weighs about a megabyte

I used @jsquash/avif 2.1.1, which wraps libavif v1.0.1. Its single-threaded .wasm file is 3,485,872 bytes raw. Compressing it myself gave 1,115,948 bytes with gzip and 842,416 with brotli, though what a real server sends depends on how it is configured.

Then I timed what happens once the bytes have arrived: importing the script and instantiating the module inside a Worker, which is a background thread, with the files served from my own machine so that no network was involved. On an Apple M4 running macOS, Chromium 149 took 14, 13 and 12 ms for the single-threaded build and 35, 33 and 30 ms for the multi-threaded one. The slowest of the four browsers I tried stayed near 100 ms. I had assumed compiling 3.5 MB of WASM would be the slow part of a first export, and on this machine it barely registers. What remains is the download, and I can't put a figure on that because it depends on each visitor's connection.

Encoding a 2 MP photo took under a second

Bar chart of encode() time for one lake photo at five sizes, single-threaded versus multi-threaded libavif WASM in Chromium 149

The chart shows the encode() call alone for one lake photo at five sizes, at quality 50 and speed 6, as the median of three runs in Chromium 149. At 2 MP the single-threaded build took 696 ms and the multi-threaded one 324 ms. At 12 MP it was 3,754 ms against 1,441 ms. The multi-threaded build only runs when the page is cross-origin isolated, which means serving it with two extra response headers that wall it off from other sites. The photo is "Lake in Dome Creek" by Jakub Fryš, from Wikimedia Commons, CC BY-SA 4.0. So at 2 MP the encode is a fraction of a second on my machine, and the download sits on top of it once, on the first export.

One tool fetched the encoder as soon as I picked AVIF

I wanted to see how a tool that is already live deals with that megabyte. The one I used is ImgIng (https://imging.ai/), a browser-based image compressor and converter, opened in Chromium 149 while I watched its network requests. The numbers here come from its Chinese-language site. Its AVIF button carries a small badge that reads On-demand WASM. The request for avif_enc_mt.wasm went out the moment I selected AVIF, before I had touched the convert button, and 1,135,337 bytes came over the wire gzipped. On my connection that took about 0.9 s, which describes my connection and nothing else. The first conversion of the 2 MP lake image took 1,678 ms from click to finished. That is the whole wait measured from outside the page, so it can't be compared with my encode() timings. I ran each size once, in one browser, and I haven't looked at how the tool works inside.

English interface of the tool with AVIF selected, an On-demand WASM badge on the AVIF button and the main button reading Downloading module

This screenshot is from the English interface on a separate run, where the module had not finished arriving when I clicked, so the button reads Downloading module. I'm not quoting how long that run took. Look at the dashed On-demand WASM badge on AVIF, beside the Instant badges on WEBP, JPG and PNG.

The timing of that request is what I plan to copy. It is like putting the kettle on when a guest says they want tea, without waiting for them to choose a mug. My guess, and it is only a guess, is that people spend a moment on the quality slider before converting, and any part of the download that overlaps with that moment is waiting they never see. Fetching on page load would make everyone who only wants a smaller PNG pull a megabyte they never use, and I pay for that bandwidth too. Fetching on the convert click puts the entire download in plain view. What I don't know yet is how many people pick AVIF and then back out, since each of those is a wasted megabyte, and I haven't checked what a returning visitor's cache does to any of this.

If you maintain a tool like this, open the Network panel, select AVIF, and note whether the .wasm request fires on selection or on the button. It takes a minute.

Top comments (0)