What happens when a browser compresses an image
Decoding, colour transforms, discrete cosine transforms and quantisation — where the quality actually goes, and why doing it in a tab is now realistic.
Compressing an image inside a browser tab used to be a compromise: you got whatever the browser’s built-in canvas.toBlob() decided to give you, with a single quality number and no control over anything else. That has changed, and the reason is WebAssembly. This is what the pipeline actually does, and where each stage spends its bytes.
Stage 1: decode
Whatever you dropped in — JPEG, PNG, WebP, AVIF — has to become a plain grid of pixel values before anything else can happen. That is decoding, and it is more work than it sounds: a 12-megapixel photograph becomes roughly 48 MB of raw RGBA in memory, four bytes per pixel.
This is the first place a browser tab hits a wall, and it is a memory wall rather than a processing one. A phone with a couple of gigabytes of available memory can decode one large photograph comfortably and three simultaneously not at all.
Decoding is also where orientation must be resolved. Cameras record the image in sensor order and store a rotation flag in EXIF metadata. If you re-encode without applying that rotation to the actual pixels first, you produce a file that is correct according to a flag you then discarded — which is exactly why so many compressed photos come out sideways.
Stage 2: colour transform
Human vision resolves brightness far more finely than colour. Lossy image formats exploit this by converting from RGB into a luma-plus-chroma space — brightness in one channel, colour in two others — so that the colour channels can be treated more roughly than the brightness channel.
Most JPEG encoders then apply chroma subsampling, storing colour information at half resolution horizontally and vertically. That is a 50 per cent reduction in the colour data before any real compression has happened, and in a photograph it is close to invisible.
It is not invisible in every image. Sharp red text on a white background is a worst case: the edge lives almost entirely in the colour channels, and subsampling smears it. This is one of the mechanical reasons screenshots of text compress so poorly as JPEG.
Stage 3: the frequency transform
Now the interesting part. The image is divided into small blocks — 8 × 8 pixels for JPEG — and each block is converted from “here is the value of each pixel” into “here is how much of each spatial frequency this block contains”.
The transform itself, a discrete cosine transform, loses nothing. What it does is rearrange the information so that the parts the eye cares about least are separated out and can be discarded cheaply. After the transform, a typical block has most of its energy concentrated in a few low-frequency coefficients — the broad tonal shape — while the high-frequency coefficients describing fine texture are small.
Stage 4: quantisation — where quality actually goes
This is the lossy step. Every other stage is reversible; this one is not.
Each frequency coefficient is divided by a value from a quantisation table and rounded to an integer. Small coefficients — the fine detail — round to zero and are gone forever. Large ones survive with reduced precision.
The “quality” number you set in any tool is, essentially, a scale factor on that table. Quality 90 divides gently and keeps most detail; quality 40 divides aggressively and zeroes out most of the high frequencies. The characteristic look of an over-compressed JPEG — 8 × 8 blocks becoming visible, sharp edges surrounded by faint ripples — is the direct visual signature of high-frequency coefficients having been rounded away.
Two consequences follow, and both matter in practice:
Quality is not linear in file size. Dropping from 90 to 80 might halve the file. Dropping from 40 to 30 might change it by a few per cent, because most of the coefficients are already zero. Nobody can guess where that curve sits for a particular photograph, which is why searching for the right quality beats estimating it.
Loss compounds. Re-compressing an already-compressed image quantises coefficients that were already quantised, on block boundaries that may no longer align. Each pass costs more than the last.
Stage 5: entropy coding
What remains is a great many zeros and a few significant numbers. Entropy coding — Huffman in classic JPEG, arithmetic coding in newer formats — packs that efficiently, and losslessly. Nothing more is discarded here; this stage simply writes the surviving data compactly.
Hitting an exact size
There is no formula from “quality 74” to “48,912 bytes”. The relationship depends on the image’s content — how much fine texture it holds, how much flat area, how noisy the sensor was.
So the practical method is a search. Encode at a mid-range quality, measure the actual output, and adjust: too big, go lower; too small, go higher. Halve the remaining range each time. Six or seven encodes narrow a 1–100 range to a single value, and because each encode of a moderate image takes tens of milliseconds in compiled code, the whole search finishes faster than a single upload would have started.
If no quality level reaches the target — which happens when the target is very small and the image very large — the only remaining lever is pixel count, and the search restarts at reduced dimensions.
Why WebAssembly matters here
Doing this in JavaScript is possible and unpleasant: the arithmetic is tight numeric work over large arrays, which is exactly what a garbage-collected dynamic language is worst at. Six or seven full encodes of a 12-megapixel image would take long enough that a server round-trip starts to look attractive.
Compiled code changes that calculation. A codec written in a systems language and compiled to WebAssembly runs at close to native speed inside the tab, which makes a multi-encode search practical on a mid-range phone. That is the technical reason ToolZool’s processing is Rust rather than JavaScript — not preference, but the difference between a search that finishes in a second and one that does not finish at all.
The second reason is architectural. The heavy work belongs on a Web Worker, a separate thread from the one drawing the page. Without that, a two-second encode freezes scrolling, animation and every button on the page. With it, the interface stays responsive and can report progress honestly.
What this means for your file
- Fine texture is expensive. Grass, foliage, fabric, sensor noise: all high-frequency, all the first thing quantisation removes.
- Flat areas are nearly free. A studio backdrop costs almost nothing to store.
- Sharp edges are expensive in a lossy format and free in a lossless one. That single fact explains most format advice.
- Fewer pixels beats lower quality. Halving both dimensions removes three quarters of the coefficients before quantisation touches anything.
Which is the same practical conclusion the 50 KB guide reaches from the other direction: resize first, then let a search find the quality.
Tools mentioned here
Keep reading
Comparison · 9 min
JPEG, PNG, WebP or AVIF: choosing by content
A decision path based on what is actually in the image, rather than on which format is newest.
How-to · 7 min
How to hit a 50 KB limit without wrecking the photo
The order of operations that keeps a heavily compressed photo looking like a photo, and the one mistake almost everyone makes first.
Spotted something wrong? Tell us — we correct guides and update the revision date rather than editing quietly. See our editorial policy.