DEV Community

Convertilo
Convertilo

Posted on

I benchmarked 6 WASM image codecs in the browser. Here is what beats the server.

TL;DR

Modern WebAssembly codecs (MozJPEG, libavif, OxiPNG/imagequant, libwebp, gifsicle, SVGO) now run fast enough in the browser that you can ship a real image-compression tool without uploading anything to a server. I built one (convertilo.io) and benchmarked the codec mix on 100 real-world images. Results below — and a couple of surprises about what doesn't work.

Raw data + repo: github.com/convertilo/wasm-image-benchmarks

The setup

  • Corpus: 100 mixed real-world images (photos, screenshots, transparent PNGs, animated GIFs, vector icons)
  • Quality: 75 (sane default for lossy)
  • Environment: Chrome 130, M-series Mac, warm WASM cache
  • Median reduction (per-image variance is ~±15%)

Results

Format Engine Mode Median size reduction
JPEG MozJPEG (@jsquash/jpeg) lossy q=75 −53%
PNG imagequant + @jsquash/png lossy palette −75%
PNG OxiPNG (@jsquash/png) lossless varies*
WebP libwebp (@jsquash/webp) lossy re-encode −17%
AVIF libavif (@jsquash/avif) lossy q=75 −59%
GIF (static) gifsicle-wasm-browser lossy −O3 −75%
GIF (animated) gifsicle-wasm-browser lossy −O3 −27%
SVG SVGO v4 browser bundle lossless multipass −42%

*OxiPNG depends on whether the input was already optimized — anything from 0% to 60%.

PNG via imagequant and JPEG via MozJPEG hit numbers competitive with server-side TinyPNG. That was the moment I stopped looking for a server.

What actually shipped this

  1. @jsquash — Sam Sneddon's WASM bindings around mozjpeg, libwebp, libavif, OxiPNG, imagequant. Lazy-loaded per format so the bundle stays tiny: ~250 KB only when the user picks a real file.
  2. gifsicle-wasm-browser — full gifsicle 1.92 CLI compiled to WASM. Preserves animation frames properly (more on this below).
  3. SVGO v4 — works fine in the browser via the bundled svgo/browser import. No need for a server.

Three things that didn't work (and saved you the time)

imagequant for WebP/JPEG

imagequant gives PNG the −75% number, so my first instinct was "apply the same quantization to WebP/JPEG before encoding." It actively hurts there: the palette quantization introduces dithering noise that wrecks lossy entropy coding. WebP went from −17% to −12%.

Rule: imagequant for lossless palette PNG only. Don't cross-apply.

WebP knob-twisting

I sunk a few hours into method: 4, pass: 3, sns_strength: 75, use_sharp_yuv: 1 for libwebp. Total impact: <1% extra reduction on already-optimized WebPs. If you're re-encoding WebP that's been touched by another tool, you're squeezing a stone.

GIF via canvas/ImageData

I tried decoding GIF, processing as ImageData, re-encoding. Animation frame timings die in transit. Just use gifsicle as a File → File pipeline and pass -O3 --lossy=80 directly. Don't reimplement what's already a 30-year-old C tool.

The privacy-first angle

The reason I cared at all: image compressors historically upload your files. Receipts, IDs, family photos, contracts — your stuff sits on someone's server. With WASM you can run the same codecs (MozJPEG, libavif, OxiPNG) entirely in the user's tab. Nothing leaves the device.

I learned the hard way that this also requires not loading Yandex.Metrika or Google Analytics before consent, which is a whole other post. The codec part is the easy half.

Reproduce

git clone https://github.com/convertilo/wasm-image-benchmarks
cd wasm-image-benchmarks
# results.json has the medians I cite above
cat results.json
Enter fullscreen mode Exit fullscreen mode

If you want a live implementation rather than a benchmark, all of these codecs are wired up at convertilo.io — drop a file in and watch network: no upload happens.


If you've squeezed more out of any of these (especially WebP — I'm convinced there's more there), I'd love the numbers. Drop a comment.

Top comments (0)