# WASM WebP for batches

> WebKit cannot encode WebP, but a WASM encoder can. Measured — 41% smaller for 4.5× the time, and why a batch changes that answer completely.

WebKit cannot encode WebP from a canvas, so on Safari and every iOS browser the
compressor falls back to JPEG. Online converters produce WebP in those same
browsers anyway. They ship their own encoder, compiled to **WebAssembly**, and
never touch `canvas.toBlob`.

We could too. Here is the measurement, because the answer changes depending on
how many files are coming.

## The numbers

Same 4.9 MP source, same target dimensions (2560 px), same quality (82).
`@jsquash/webp` — the same libwebp build that would run in the browser:

| | size | time |
|---|---|---|
| **JPEG**, native encoder — what WebKit gives you today | 621 kB | **109 ms** |
| **WebP**, WASM encoder | **364 kB** | 489 ms |
| | **−41.4 %** | **4.5× slower** |

And the encoder itself, fetched once and then cached:

| file | size |
|---|---|
| `webp_enc_simd.wasm` | **337 kB** |
| `webp_enc.wasm` (no SIMD) | 275 kB |

## One photo: not worth it

You save 257 kB and download a 337 kB encoder. **Net loss on bytes**, plus 380
extra milliseconds. For a single upload the JPEG fallback is simply the right
answer.

## Thirty photos: worth it, and by a lot

This is the case that matters, and it is the common one — someone picking a
camera roll selection.

| | |
|---|---|
| saved | 30 × 257 kB = **7.5 MB** |
| encoder cost | 337 kB, **once** |
| **net data saved** | **≈ 7.2 MB** |
| extra encode time | 30 × 380 ms ≈ **11 s** on a laptop |

**Break-even is the second photo.** After two images the encoder has paid for
itself in bytes, and everything after that is pure saving.

### The time cost mostly disappears, and exactly when it matters

11 seconds of extra encoding sounds bad until you notice that **encoding and
uploading can overlap**: photo N+1 encodes while photo N is still uploading.

Do the arithmetic on a realistic uplink. A 364 kB WebP on 4G at ~250 kB/s takes
**1.45 s to upload**, against **489 ms to encode**. The encode finishes long
before the upload does, so in a pipelined batch the WASM encoder is **free** —
it runs in dead time the network was going to waste anyway.

And the slower the connection, the more true that gets:

| uplink | 7.2 MB saved ≈ | encode overhead | verdict |
|---|---|---|---|
| 3G / congested cellular (~60 kB/s) | **2 min** | hidden behind uploads | **big win** |
| 4G (~250 kB/s) | **29 s** | hidden behind uploads | **clear win** |
| fast wifi (~10 MB/s) | 0.7 s | ~11 s, partly exposed | not worth it |

So the rule is not really "how many files" — it is **"is the network slower than
the encoder?"** For a batch on cellular, always yes. On fast wifi, no, and there
the bytes did not matter anyway.

## Why the SDK is the right place to decide this

The SDK already knows everything the decision needs, before a single byte
moves:

- **how many files** — `compressImages()` takes the array;
- **the total size** — it has hashed them;
- **whether the engine can encode WebP natively** — it already probes this to
  choose the fallback;
- **the connection**, via `navigator.connection` where available.

That is the whole argument for putting this in an SDK rather than leaving it to
each app: the caller says *"here are 30 photos"*, and the library is the only
layer that can see the batch, the engine and the link at the same time.

:::note[Status: measured, not implemented]
Nothing here ships today — the JPEG fallback is what runs, and for a single
upload it is correct.

The shape it should take when it does: **automatic for a qualifying batch**
(WebKit + more than ~2 files + not on a fast link), with an explicit override
either way, and the encode pipelined behind the uploads so the extra time
stays hidden. The 337 kB is fetched lazily, so a single-photo upload never
pays for it.
:::

## What it does not change

**Delivery is WebP either way.** The server re-encodes every variant, so this
only ever affects upload bytes and the format of the archived
`original`. Nobody's page gets faster — the user's upload gets cheaper, which
on a metered phone plan is the thing they actually feel.