Language

Safari Can't Encode WebP: Browser Encoder Benchmarks (2026)

The bug report that started this

We run an image compressor that never uploads your files — everything is decoded and re-encoded in the browser. One day a test failed in a way that made no sense: a 12 MP photo asked to compress to WebP at "quality 80" came out five times larger than the source — 20.55 MB.

The obvious suspect was the quality argument. It was wrong: Safari doesn't ignore the quality argument, because Safari has no WebP encoder at all. When you ask canvas.toBlob for image/webp, the spec says an unsupported type must silently fall back to image/png — so Safari hands you a PNG and moves on, no error, no exception.

That discovery turned into a full benchmark: 4 browsers on 2 machines, 4 images, 6 quality levels, native canvas.toBlob against a WASM reference stack, scored with DSSIM — 144 measured cells per browser. This article is what the data says.

Quick Answer: What Each Browser Actually Encodes

BrowserJPEGWebPAVIF
Chrome 153 (Windows)✅✅❌ silently returns PNG
Edge 153 (Windows)✅✅❌ silently returns PNG
Firefox 155 (Windows)✅✅❌ silently returns PNG
Safari 18.6 (macOS)✅❌ silently returns PNG❌ silently returns PNG
Safari 27.0 (macOS)✅❌ silently returns PNG❌ silently returns PNG

Safari 27.0 was checked on 2026-10-11 with a probe that reads the MIME type canvas.toBlob returns for each format and quality. Image quality and DSSIM were not measured for 27.0, so its row shows the fallback behaviour only.

Six findings worth knowing before you ship anything that encodes images in the browser:

  1. Safari has no WebP encoder. "Compressing" a 12 MP photo to WebP at quality 80 returns a 25.1 MB PNG.
  2. No browser can encode AVIF from toBlob. All four silently fall back to PNG.
  3. Firefox's WebP encoder is libwebp — byte-identical on every opaque cell (15 of 20 at q10–q92); only the alpha PNG and the q100 cells differ.
  4. "Quality 80" is not a portable number. The same value gives a 6.06 MB JPEG in Safari and a 3.53 MB JPEG in Chrome.
  5. 1.0 is a trap. For several encoders it means lossless — which shrinks flat graphics but turns a photo into a 16 MB file.
  6. Speed spans two orders of magnitude. The same 12 MP image: native JPEG in 209 ms, AVIF in 26.9 s.

How We Measured

This is the encode side of the browser, not the decode side, and it is the side developers get bitten by — because the API never tells you it ignored your request.

The harness is open source and you can run it on your own machine and your own photos: lab.pixkeep.app/encode-matrix (source on GitHub). Everything runs locally; nothing is uploaded.

Finding 1: Safari Has No WebP Encoder

Safari can display WebP (since Safari 14). It cannot produce WebP from a canvas. Ask anyway and WebKit follows the spec's fallback rule, which says an unsupported type must be replaced with image/png:

const canvas = document.createElement('canvas');
canvas.toBlob(blob => {
  console.log(blob.type); // "image/png" — on Safari, even though you asked for image/webp
}, 'image/webp', 0.8);

The sizes make it unmissable — this is what "WebP at quality 80" produces on Safari:

Source imageSafari result for image/webp
12 MP photo (flower)25.14 MB PNG
12 MP photo (grass)27.78 MB PNG
Graphic with text0.17 MB PNG
Transparent PNG0.14 MB PNG

Every quality level falls back to PNG — the table above shows the q80 result, the one cell where the fallback size was recorded. The quality argument is not "ignored": there is no WebP encoder for it to be passed to. This is also why the popular browser-image-compression library silently ignores fileType: 'image/webp' on Safari, and it is a long-standing gap: Can I Use marks toBlob's WebP type parameter as unsupported in Safari, and WebKit's own bug 183257 tracks canvas WebP encoding.

The fix is a feature check, not a user-agent guess — but note that a feature check has to look at the returned blob, not at whether you got a blob:

function canEncodeFromCanvas(type) {
  return new Promise(resolve => {
    const c = document.createElement('canvas');
    c.width = c.height = 1;
    c.toBlob(blob => resolve(blob?.type === type), type, 0.8);
  });
}

If blob.type is not the type you asked for, that browser needs a WASM encoder — which is exactly what our image compressor does on WebKit.

Finding 2: Nobody Can Encode AVIF From a Canvas

Across all four browsers, all four images and all six quality levels — 96 native AVIF cells, zero AVIF files. Not one browser produced an AVIF file from canvas.toBlob, including Chrome and Firefox, which both decode AVIF natively.

So "AVIF in the browser" today means WebAssembly, and AVIF has a price tag. Encoding one 12 MP photo at quality 80:

EncoderTimeFile sizeDSSIM
Chrome native WebP2.3 s3.25 MB0.00134
WASM libwebp4.0 s3.25 MB0.00133
WASM MozJPEG4.4 s2.94 MB0.00221
WASM AVIF26.9 s4.43 MB0.00072

AVIF wins on quality per byte — but on a 12 MP photo it takes ~27 seconds per image on a laptop, and over 32 seconds on Safari (which routes everything through WASM). That is the honest reason client-side AVIF conversion needs a progress bar, a worker and a queue, not a spinner.

(For a deeper look at AVIF's efficiency on the same data, see AVIF vs JPG.)

Finding 3: Firefox's WebP Encoder Is libwebp — Byte for Byte on Opaque Cells

Comparing the native encoders against the WASM reference encoders turned up something we did not expect: Firefox's native WebP output is identical to a stock libwebp build on every opaque cell we measured — 15 of 20 cells at q10–q92 across four images. The alpha-transparent PNG and all four q100 cells differ, which tells us Firefox routes transparency and quality-100 through a different path.

Browser, WebP @ q80 (12 MP photo)Native byteslibwebp (WASM) bytesIdentical
Firefox 1553,411,3083,411,308✅
Chrome 1533,408,3503,411,476❌ (0.09% apart)
Edge 1533,408,3503,411,476❌ (0.09% apart)

Chromium's WebP is libwebp-derived as well (same encoder family, near-identical sizes and identical DSSIM), but not the same build or settings — so if you are testing "native vs library", Firefox is the clean control and Chromium is not.

Finding 4: "Quality 80" Is Not a Portable Number

This is the trap that breaks naive image tools. Ask four browsers for JPEG at quality 80 and you get four different quality/size trade-offs, because each encoder maps the 0–1 slider to its own internal quantization:

Browser, JPEG @ q80, 12 MP photoFile sizeDSSIM
Chrome 1533.53 MB0.00205
Edge 1533.53 MB0.00205
Firefox 1553.56 MB0.00205
Safari 18.66.06 MB0.00063

Safari is not "worse" here — it is stricter. It spends 1.7× the bytes to land at a noticeably higher quality. Compare encoders at matched file size instead, and the picture changes again:

JPEG at 3.53 MB (matched size), 12 MP photoDSSIMvs Chrome native
Chrome 153 native0.00205—
Firefox 155 native0.002092% worse
Safari 18.6 native0.001888% better
MozJPEG (WASM)0.0015524% better

Two practical consequences. First, a "quality" slider means something different in every browser, so the same setting gives visibly different results — and the only honest cross-encoder comparison is at matched size or matched DSSIM. Second, MozJPEG's advantage is real but content-dependent: 24% lower DSSIM on the flower photo at matched size, but only 2% on the grass photo — and on the flat-color graphic it is 32% ahead of native, while Safari's and Firefox's native JPEG encoders are 2.5× behind Chrome there.

Finding 5: 1.0 Is a Trap

At quality 100, every implementation decides to do something different — and "maximum quality" stops being a safe default:

WebP @ q100, graphic with textSizeDSSIM
Firefox 155 native28.8 KB0 (lossless)
Firefox 155 @ q92 (same image)70.8 KB0.00012
Chrome 153 native410.2 KB0 (lossless)
libwebp WASM (our stack)99.2 KB0.00010 (still lossy)

Firefox switches to lossless, which makes flat graphics smaller than at q92 — while on a 12 MP photo the same setting produces a 15.9 MB file (Chrome: 16.8 MB), roughly 5× the q92 size. JPEG at q100 lands between 15.6 MB and 20.9 MB across the four browsers.

The practical rule: cap your quality at ~92. Nothing above it buys visible quality on photos, and the file-size cliff is steep.

Finding 6: Speed Spans Two Orders of Magnitude

Same image, same machine, single full-resolution encode:

Encoder (12 MP photo)ChromeFirefoxSafari
Native JPEG209 ms82 ms247 ms
Native WebP2.3 s1.8 s— (unsupported)
WASM libwebp4.0 s4.5 s5.4 s
WASM MozJPEG4.4 s5.3 s4.9 s
WASM AVIF26.9 s27.8 s32.5 s

Native WebP is ~10× slower than native JPEG; the WASM encoders pay another 2×; AVIF pays 10× that again. If you build an in-browser converter, that range decides your architecture: a worker, a per-file progress indicator, and a bundle that does not load 27 seconds of AVIF machinery before the first JPEG is out.

What This Means If You Build In-Browser Image Tools

Reproduce It Yourself

The benchmark harness is open source (MIT) and runs entirely in your browser — your photos never leave your device:

Honest limitations. Timing numbers are single-run and depend on your machine, thermal state and what else is running: they are comparable within one machine and browser version, not across machines. Size and quality results are deterministic for a given browser version — those are the numbers worth citing. This dataset is pinned to Safari 18.6 and Chrome 153 (September 2026): browser encoders change between releases, and the "no WebP in Safari" result is exactly the kind of thing that can be fixed in an update — re-run the harness if you are reading this much later. And a note on the transparent PNG: flattening alpha for JPEG makes that image a pathological case (DSSIM ≈ 1.09) that we excluded from encoder comparisons — if you only need transparency, use WebP or AVIF, where it costs nothing.

What We Changed In Our Own Tool

Everything above is why our image compressor does not trust canvas.toBlob: