The .webp extension hides two unrelated formats, and only one of them can be perfect

JPEG is lossy. PNG is lossless. You know which you have from the extension.

WebP is both, and the extension tells you nothing. A .webp file might hold a format closely related to a video frame, or it might hold a format with no relation to video at all, built on completely different machinery. They share a container, four letters at the end of a filename, and essentially nothing else.

This matters more than a piece of trivia, because the choice between them is not a quality slider. On the wrong content, the lossy one costs more bytes than the lossless one while still throwing information away.

The same picture, saved twice

A WebP file is a RIFF container: the ASCII tag RIFF, a length, the tag WEBP, and then a series of chunks, each with a four-character name and a length. The chunk names are the whole story.

Saving one image both ways:

lossless.webp — 213,092 bytes
   chunk "VP8L"  213,071 bytes

lossy80.webp — 15,872 bytes
   chunk "VP8 "  15,852 bytes

One chunk each, and the name is different. VP8 — with a trailing space, because the name must be four characters — is a still frame of VP8, the video codec. VP8L is WebP Lossless, which is not VP8 at all and not a video codec: it is a predictive transform plus Huffman coding, far closer in spirit to PNG than to anything in a video file.

Two separate specifications. A program that reads one does not thereby read the other.

Where each one wins

The lossless file above is thirteen times larger than the lossy one, which looks like a settled argument until you ask what happens as quality rises. Lossless has no quality setting — it is perfect, and perfect costs whatever it costs. Lossy has a slider, and the slider has an end.

On an interface screenshot, where the best lossless WebP comes to 41,246 bytes:

lossy quality     bytes   vs lossless   PSNR
           80    36,448          88%   35.91 dB
           85    42,298         103%   36.30 dB
           90    50,274         122%   36.54 dB
           95    64,254         156%   36.72 dB
          100    77,140         187%   36.77 dB

At quality 85 the lines have already crossed. At quality 95 the lossy file is half as large again as the perfect one. At quality 100 — the setting whose name promises everything — it is 87% larger than lossless and still lossy, sitting at 36.77 dB when lossless is by definition exact.

Anyone who has ever reached for quality 100 expecting a clean copy has paid nearly double for a worse result.

The crossover is a property of the content, not a universal number. Running the same test on three kinds of picture:

app icon, flat colour       lossless 210,906 B   lossy never overtakes
interface screenshot        lossless  41,246 B   lossy overtakes at q85
photograph                  lossless 572,584 B   lossy never overtakes

On photographs the lossy mode wins by an enormous margin and keeps winning — its whole design is continuous tone. On flat graphics it also wins on raw size, but as the table above shows, it stops being the cheaper option exactly where people tend to push the slider to avoid artefacts on text.

The ceiling it cannot pass

The interesting number in that table is not the size. It is that the quality stops moving: 36.54 dB at quality 90, 36.77 at quality 100. Twenty-seven thousand extra bytes bought a quarter of a decibel.

Splitting the error into brightness and colour says exactly where the wall is:

quality      bytes        Y       Cb       Cr
     80     36,448    45.93    38.73    38.88
     90     50,274    50.05    39.11    39.27
    100     77,140    55.51    39.23    39.41
lossless    41,246    99.00    99.00    99.00

Between quality 80 and 100 the brightness channel improves by 9.6 dB. The two colour channels improve by half a decibel and then stop. The combined score is limited by the worst channel, so the whole file plateaus even though the encoder is clearly still working on the luminance.

The reason is structural rather than a tuning choice. Lossy WebP inherits VP8's picture format, and VP8 works in YUV with the colour planes at half resolution in each direction — three quarters of the colour samples are discarded before encoding begins. No quality setting reverses that, because by the time the quality setting applies, the samples are already gone.

Lossless WebP has no such step. It codes red, green and blue directly, which is why its row reads 99 across the board — the measurement's way of saying the files are identical.

So "lossy WebP at maximum quality" is not a near-lossless mode and cannot be made into one. If the requirement is an exact copy, the only WebP that provides it is the other one.

And against JPEG?

That is the question most people actually have, and it is the one case where the lossy mode's video ancestry pays off completely. Both encoders given the same photograph, both scored by PSNR, then matched on quality rather than on settings:

equal quality          JPEG              lossy WebP        saving
   38.0 dB        q46   49,162 B      q32   18,654 B        62%
   40.0 dB        q68   61,874 B      q52   23,802 B        62%
   42.0 dB        q80   83,642 B      q78   33,178 B        60%

Around 60% smaller, and it holds steady across the range rather than being an artefact of one operating point. VP8 predicts each block from its neighbours before coding anything, where JPEG transforms every block from scratch; on a photograph, where neighbouring blocks are nearly the same, that prediction is almost free information.

One caveat on these particular numbers: the source was itself a JPEG, so the JPEG column is partly re-encoding its own artefacts. That is the common real-world case, and it is the same picture given to both, but a pristine camera original would narrow the gap somewhat.

A transparent lossy WebP is two codecs in one file

VP8 came from video, and video has no alpha channel. WebP still needed transparency, so the container does something unusual: it stores the colour and the transparency with different algorithms, in different chunks.

The same image with an alpha ramp, saved both ways:

lossy q80      16,052 bytes
   VP8X         10 B   extended header, required once there is alpha
   ALPH        124 B   the alpha channel, compressed separately and LOSSLESSLY
   VP8      15,882 B   the colour, lossy

lossless      233,926 bytes
   VP8L    233,905 B   colour and alpha together, one codec

A lossy WebP with transparency therefore contains a lossy codec and a lossless codec side by side, plus an extended header to announce that the file is not the simple case. The transparency in a "lossy" WebP is, in fact, exact.

That is a sensible design — soft edges would look terrible with lossy alpha — but it is worth knowing when a file is larger than expected, and it explains why a reader that handles plain lossy WebP can still fail on a transparent one. It is a different arrangement of chunks and needs code for all three.

Why lossless WebP beats PNG, and one explanation that turned out to be wrong

Against the smallest PNGs we could produce — the unfiltered ones from the previous article, which were already 23% below what a normal encoder writes — lossless WebP still wins comfortably:

screenshot-home    best PNG  60,647    lossless WebP  41,246    32% smaller
screenshot-tool    best PNG  61,907    lossless WebP  39,272    37% smaller
screenshot-why     best PNG  99,005    lossless WebP  58,162    41% smaller

The obvious candidate explanation is VP8L's subtract-green transform. Before coding anything it replaces red with red-minus-green and blue with blue-minus-green, on the theory that the three channels of a natural image move together, so the differences are smaller and more compressible than the originals.

It is a tidy story. It also does not survive being measured. Deflating the raw planes both ways, as-is against subtract-green:

UI screenshot      59,596 -> 61,341    2% WORSE
app icon          457,183 -> 470,458    2% WORSE
photograph      1,091,838 -> 1,070,341  2% better

Two percent either way is nothing, and on two of the three images the transform made things worse. Whatever lossless WebP is doing to beat PNG by a third, decorrelating the colour channels is not the main part of it.

This is not quite a fair test of VP8L — deflate is not its entropy coder, and the transform is designed to feed something else. But it is a fair test of the explanation, which was that the channels are correlated enough for the subtraction alone to pay. On this content they are not.

The likelier answer is the one that connects to the previous article. PNG picks one filter per scanline from five. VP8L picks a predictor per block from fourteen, and carries its own entropy coder rather than handing the result to DEFLATE. Per-row choice was exactly what was shown to cost PNG real bytes on this kind of image; per-block choice is the fix, and it is not available inside PNG.

So which one

The measurements give a rule that does not need judgement:

Photographs: lossy. It is what VP8 was built for, it wins by five to ten times, and the losses land where the eye is least sensitive.

Screenshots, diagrams, logos, anything with text: lossless. Not only because artefacts around text are obvious, but because the lossy mode stops being cheaper somewhere around quality 85 on exactly this content. You can pay more and still get less.

Anything that must survive another edit: lossless. The ceiling above is not a setting; it is the format.

Transparency: check the size either way. The separate alpha chunk is cheap when the alpha is simple and not cheap when it is not.

When this does not apply

One encoder, one set of defaults. These files came from libwebp through a standard imaging library at its usual settings. The crossover point in particular will move with encoder effort settings — a slower lossless pass finds more, which pushes the crossover down, not up.

The crossover is content-specific. Quality 85 is where it landed for one screenshot. A busier screenshot crosses later, a flatter one earlier. The method is the portable part, not the number.

PSNR is a proxy. It is reproducible and it is what makes the ceiling visible as a number, but it does not model how text artefacts look, which is the thing that actually matters on a screenshot. In that respect PSNR understates the case for lossless here.

Lossless WebP is not always smaller than PNG. It was by 29 to 38% on the files measured here, but a PNG with a small palette can be very hard to beat.

Animation is a third thing again. Animated WebP uses an ANMF chunk structure and can mix lossy and lossless frames in one file, which is outside everything above.

Checking it yourself

You do not need a tool. Open any .webp in a hex viewer and read the first sixteen bytes:

lossless:  52 49 46 46 50 d5 08 00 57 45 42 50 56 50 38 4c
lossy:     52 49 46 46 f8 89 00 00 57 45 42 50 56 50 38 20
           R  I  F  F  (length)    W  E  B  P  V  P  8  ?

If the sixteenth byte is 4C — the letter L — the file is lossless. If it is 20, an ASCII space, it is lossy. If instead the four bytes read VP8X, the file is the extended kind and the real codec chunk comes a little later, after the alpha or metadata chunks.

Three bytes into a file, available in any editor, and it settles a question the filename cannot answer.

Sources

Tools mentioned

Advertisements

← All articles · corrections: editorial policy