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
| Browser | JPEG | WebP | AVIF |
|---|---|---|---|
| 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:
- Safari has no WebP encoder. "Compressing" a 12 MP photo to WebP at quality 80 returns a 25.1 MB PNG.
- No browser can encode AVIF from
toBlob. All four silently fall back to PNG. - 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.
- "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.
1.0is a trap. For several encoders it means lossless — which shrinks flat graphics but turns a photo into a 16 MB file.- 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.
- Browsers: Chrome 153, Edge 153 and Firefox 155 on Windows (Intel UHD Graphics 620, 8 cores, 16 GB RAM); Safari 18.6 on macOS (full measurement) and Safari 27.0 on macOS (MIME-type check only, see the note under the table).
- Images: two 12 MP photos (3024 × 4032, flower and grass), a synthetic graphic with flat colors, sharp edges and text (2400 × 1600), and a transparent PNG (2000 × 2000, alpha).
- Quality levels: 10, 40, 60, 80, 92, 100.
- Control group: the same images encoded with MozJPEG, libwebp and AVIF compiled to WebAssembly, running in workers.
- Quality metric: DSSIM (
dssim-core3.4.0, viadssim-wasm), computed on a shared 1200 px center-crop analysis frame, so every encoder is judged on identical framing. Lower is better. - Protocol: one measured full-resolution encode per cell, foreground tab, single run per browser.
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 image | Safari result for image/webp |
|---|---|
| 12 MP photo (flower) | 25.14 MB PNG |
| 12 MP photo (grass) | 27.78 MB PNG |
| Graphic with text | 0.17 MB PNG |
| Transparent PNG | 0.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:
| Encoder | Time | File size | DSSIM |
|---|---|---|---|
| Chrome native WebP | 2.3 s | 3.25 MB | 0.00134 |
| WASM libwebp | 4.0 s | 3.25 MB | 0.00133 |
| WASM MozJPEG | 4.4 s | 2.94 MB | 0.00221 |
| WASM AVIF | 26.9 s | 4.43 MB | 0.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 bytes | libwebp (WASM) bytes | Identical |
|---|---|---|---|
| Firefox 155 | 3,411,308 | 3,411,308 | ✅ |
| Chrome 153 | 3,408,350 | 3,411,476 | ❌ (0.09% apart) |
| Edge 153 | 3,408,350 | 3,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 photo | File size | DSSIM |
|---|---|---|
| Chrome 153 | 3.53 MB | 0.00205 |
| Edge 153 | 3.53 MB | 0.00205 |
| Firefox 155 | 3.56 MB | 0.00205 |
| Safari 18.6 | 6.06 MB | 0.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 photo | DSSIM | vs Chrome native |
|---|---|---|
| Chrome 153 native | 0.00205 | — |
| Firefox 155 native | 0.00209 | 2% worse |
| Safari 18.6 native | 0.00188 | 8% better |
| MozJPEG (WASM) | 0.00155 | 24% 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 text | Size | DSSIM |
|---|---|---|
| Firefox 155 native | 28.8 KB | 0 (lossless) |
| Firefox 155 @ q92 (same image) | 70.8 KB | 0.00012 |
| Chrome 153 native | 410.2 KB | 0 (lossless) |
| libwebp WASM (our stack) | 99.2 KB | 0.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) | Chrome | Firefox | Safari |
|---|---|---|---|
| Native JPEG | 209 ms | 82 ms | 247 ms |
| Native WebP | 2.3 s | 1.8 s | — (unsupported) |
| WASM libwebp | 4.0 s | 4.5 s | 5.4 s |
| WASM MozJPEG | 4.4 s | 5.3 s | 4.9 s |
| WASM AVIF | 26.9 s | 27.8 s | 32.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
- Detect by returned type, never by UA string.
blob.typeis the only reliable signal; UA sniffing breaks with every browser update. - Never assume a WebP/AVIF request succeeded. A 25 MB "WebP" is a PNG.
- Have a WASM fallback for WebKit if you need WebP at all — native is not an option there.
- Treat AVIF as an explicit choice, not a default: it is the slowest path by an order of magnitude on large photos.
- Clamp quality to ~92 and never expose 1.0 as "best quality" without warning about lossless.
- Do not compare encoders at equal slider values. Match size or DSSIM.
Reproduce It Yourself
The benchmark harness is open source (MIT) and runs entirely in your browser — your photos never leave your device:
- Run it: lab.pixkeep.app/encode-matrix — pick the reference images or your own, choose formats, and download the raw JSON when it finishes.
- Source: github.com/pixkeep/lab — including the Playwright driver we used for the automated runs.
- Raw data: the unmodified JSON exports behind every table in this article — one file per browser, plus the generated aggregate report and the run protocol. They are also served directly, e.g. Chrome 153 on Windows (
lab.pixkeep.app/bench-data/<file>.json). Licensed CC BY 4.0.
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:
- On WebKit-family browsers, WebP and JPEG encoding is routed through WASM workers (libwebp and MozJPEG) instead of the native encoder — the only way Safari can produce a WebP at all, and it makes Safari's JPEG output match the other engines instead of costing 1.7× the bytes.
- Quality is clamped where it matters, and the default sits at 92 — below the q100 lossless cliff described above.
- AVIF is offered as an explicit choice, encoded in a worker with a progress indicator, never as a silent default.
- And it all still runs locally: decode, encode, compare — nothing is uploaded, and the tool works offline after the first visit. Try it with one of your own photos and watch what your browser actually produces.