Inverity
Quality & Measurement

Why Quality 95 Means Nothing Across Image Encoders

Date Published

Three quality dials labeled JPEG, WebP and AVIF each read 95, yet each produces a visibly different output tile below it, with different file-size bars, showing the quality integer is not a shared unit.
TL;DR >- A quality setting is an encoder-specific scale that "cannot meaningfully be compared between codecs," so JPEG q90, WebP q90, and AVIF q90 are three different things (Malte Ubl, Vercel CTO).- Matched on DSSIM, JPEG q60 lines up with AVIF q50 and WebP q65, not with q60 everywhere (Malte Ubl).- libjxl does not use quality at all; its control unit is distance (-d), where 0.0 is lossless and 1.0 is visually lossless (libjxl encoder API).- Even JPEG XL's quality knob is a simulation: JxlEncoderDistanceFromQuality() maps quality 90 to distance 1.0, proving the quality number is a compatibility shim (libjxl encoder API).- The fix is methodological: target a perceptual metric, encode each format to it, then compare file sizes. Never compare on equal quality integers.

Somewhere in almost every image pipeline is a line that sets quality to 85, and almost everyone who wrote that line believes the number means something portable. It does not. The quality slider is one of the most misunderstood controls in media engineering, because it looks like a percentage and behaves like an encoder-internal quantizer index. Two codecs at the same quality number produce different perceptual results, different file sizes, and different artifacts. The number is real. Its portability is imaginary.

This post explains why. We cover what a quality setting actually controls, why it varies across and even within codecs, how libjxl's distance-based control is a genuinely different paradigm, and the correct methodology for comparing formats. For the full landscape, see our complete guide to image compression. Here we kill one specific myth: that quality 95 means the same thing twice.

Does JPEG q90 equal WebP q90 or AVIF q90?

No. Quality settings "use an encoder-specific quality scale that cannot meaningfully be compared between codecs," which means JPEG q90, WebP q90, and AVIF q90 are unrelated numbers that happen to share a range (Malte Ubl, Vercel CTO). The same integer maps to different perceptual quality and different bytes in each encoder.

The mismatch is measurable, not theoretical. When Ubl matched encoders on DSSIM, a perceptual distortion metric, the equivalent settings drifted apart fast: JPEG q50 lined up with AVIF q48 and WebP q55; JPEG q80 lined up with AVIF q64 and WebP q82 (Malte Ubl). To match a plain JPEG q60, you would set AVIF to about q50 and WebP to about q65 (the precise DSSIM-matched values are q51 and q64; round them in practice). Nothing about the number carries over.

The quality slider is a user-interface fiction layered over a quantizer index. Inside the codec, that number selects how coarsely coefficients get rounded, and each codec's transform, coefficient layout, and default tables differ, so identical indices produce non-identical rounding. The slider persists because it is familiar, not because it is meaningful. Treating it as a shared unit is like comparing two cars by the angle of their throttle pedals. The pillar-level version of this confusion, quantizer versus perceptual target, runs through content-adaptive per-image quality.

Citation capsule: A quality setting is an encoder-specific scale that "cannot meaningfully be compared between codecs" (Malte Ubl, Vercel CTO). Matched on the DSSIM perceptual metric, JPEG q60 corresponds to AVIF q50 and WebP q65, so identical quality integers describe different perceptual results.

The cross-codec equivalence table

Here is what "non-comparable" looks like in numbers. Matched on DSSIM so that perceived distortion is held constant, the quality settings that produce equal quality diverge steadily across JPEG, AVIF, and WebP (Malte Ubl). The table is the clearest proof that a quality integer is a per-encoder label, not a measurement.

JPEG quality

Equivalent AVIF

Equivalent WebP

50

48

55

60

51

64

70

56

72

80

64

82

DSSIM-matched equivalence: Malte Ubl, Vercel CTO.

Notice the shape. AVIF's equivalent quality sits well below JPEG's at every row, and the gap widens as quality climbs, because AVIF reaches the same perceptual quality with a lower nominal setting. That is not AVIF being "better at q64"; it is AVIF's scale being calibrated differently. The efficiency payoff is separate: at equal DSSIM, AVIF averaged 36% smaller files than JPEG and WebP 15% smaller (Malte Ubl).

Slope chart of DSSIM-matched equivalent quality across JPEG, AVIF and WebP for rows 50, 60, 70 and 80, with lines fanning apart

The same perceptual quality is a different setting per codec: JPEG q80 equals AVIF q64 and WebP q82 (Malte Ubl, Vercel CTO).

Quality is not even one number inside a single codec

Even within one encoder, quality does not fully determine the output. cwebp's quality runs 0 to 100 with a default of 75, but a separate -m method parameter from 0 to 6, default 4, independently trades encoding speed against file size, so two files at the same quality can differ in bytes (cwebp docs, Google). Quality is one input among several.

AVIF makes the point even more sharply. Its AV1-based encoders, aom and SVT-AV1, natively expose a CRF or cq-level control on roughly a 0-to-63 scale, not a 0-to-100 quality, so any "AVIF quality 90" you see has already been remapped by tooling onto a scale the encoder does not natively use. That remapping is another place the number quietly loses meaning before it reaches the codec.

The same is true across JPEG implementations. mozjpeg produces meaningfully smaller files than baseline libjpeg at the same nominal quality, because it uses trellis quantization and better tables, so even "JPEG q90" depends on which JPEG encoder you ran (the exact percentage varies by content and source, so treat any single figure with caution). The takeaway is blunt: a quality integer under-specifies the encode. Speed presets, method flags, and encoder implementation all move the result, a theme we develop in encoder speed presets and tradeoffs and tuning mozjpeg, libjxl, and libaom.

What distance-based control actually changes

libjxl throws out the quality metaphor entirely. Its control unit is distance, set with -d, on a scale from 0 to 25 where 0.0 is mathematically lossless, 1.0 is visually lossless, the recommended range is 0.5 to 3.0, and the default is 1.0 (libjxl encoder API). Distance is not a quantizer index. It is a target maximum Butteraugli distance, a perceptual quantity.

This inverts the whole relationship between the knob and the result. A quality slider says "round this hard and see what quality you get." A distance target says "reach this perceptual quality and spend whatever bits that takes." The first controls the process; the second controls the outcome. That is why distance is portable in a way quality is not: distance 1.0 means the same visually-lossless target regardless of image content, because it is defined in perceptual units, not quantizer units.

Distance-based control is the same paradigm shift that per-title video encoding went through, just applied to a still-image knob. Instead of picking a setting and accepting the quality, you pick the quality and accept the setting. libjxl's own documentation warns that quality "has no consistent psychovisual meaning across different codecs and libraries," which is a codec author telling you plainly that the slider you have trusted for years does not mean what you think (libjxl encoder API). The JND-based meaning of "distance 1.0" is unpacked in just-noticeable difference in image quality.

Citation capsule: libjxl controls output by distance, not quality: 0.0 is mathematically lossless, 1.0 is visually lossless, and the recommended range is 0.5 to 3.0, with distance defined as a target maximum Butteraugli distance (libjxl encoder API). Distance is a perceptual target; quality is a quantizer index.

Why does JPEG XL still expose a quality knob at all?

For compatibility, and the mapping proves it. libjxl offers a quality parameter, but internally JxlEncoderDistanceFromQuality() converts it to distance: quality 100 becomes distance 0.0, quality 90 becomes distance 1.0, and the recommended distance range of 0.5 to 3.0 corresponds to roughly quality 96 down to 68 (libjxl encoder API). The quality knob is a thin shim over the real control.

This is the cleanest possible evidence that quality is a legacy interface. Even the codec that abandoned quality still simulates one, purely so tools and users expecting a 0-to-100 dial are not stranded. Under the hood, that dial is immediately translated into the distance the encoder actually optimizes. The number you set never reaches the compression math; its perceptual translation does.

Quality (JXL shim)

Maps to distance

Meaning

100

0.0

Mathematically lossless

96 (approx)

0.5

Near-visually-lossless

90

1.0

Visually lossless

68 (approx)

3.0

Lower bound of recommended range

Mapping: libjxl encoder API.

Line chart of the JxlEncoderDistanceFromQuality mapping, quality 100 to 68 on the x-axis against distance 0.0 to 3.0

JPEG XL's quality knob is a shim over distance: q90 maps to distance 1.0, visually lossless (libjxl encoder API).

So when someone reports "I used JPEG XL at quality 90," what actually happened is distance 1.0, a visually-lossless perceptual target. The quality number was a convenience label the encoder discarded on line one. If even JXL only pretends to have a quality knob, ask yourself what your JPEG encoder's quality knob has been hiding.

How do you compare formats fairly?

Fix a perceptual target, encode each format to hit it, then compare file sizes. This is the only methodology that produces an honest comparison, because it holds quality constant in perceptual terms rather than in each encoder's private units. A defensible target is SSIMULACRA2 at or above 80, or equivalently cjxl at distance 1.0, applied identically to every format under test.

The procedure is simple and rarely followed. Pick the metric target. Encode the same source to JPEG, WebP, and AVIF, adjusting each encoder's own settings until every output lands at the target score. Now the file sizes are comparable, because every file carries the same perceptual quality. Under this method a synthesis pinned to SSIMULACRA2 80 reported WebP needing 33% and AVIF 46% fewer bits than JPEG for equal perceptual quality (codec-wiki, x266.mov). Treat those exact percentages as indicative, not gospel: the source is a synthesis and the figures warrant independent verification.

The comparison mistake we see most often is a benchmark that sets every codec to "quality 80" and then reports file sizes as if that were fair. It is not a comparison; it is three encoders answering three different questions. The result usually flatters whichever codec happens to be stingiest at that particular integer, and it tells you nothing about real efficiency. Fixing the metric first is the single change that makes a codec benchmark trustworthy, which is why how we benchmark starts from the target, not the setting.

Rhetorical question to close the method: if you would not compare two thermostats by the position of the dial instead of the temperature in the room, why compare two codecs by the quality integer instead of the perceptual quality on screen? The related metric mechanics live in Butteraugli and SSIMULACRA2 explained, and the reason file size alone misleads is in why file size is the wrong metric.

At Inverity we treat the perceptual target as the contract and every encoder setting as a means to satisfy it, encoding to a pinned SSIMULACRA2 or distance target and recording the result, so a cross-format claim can be re-checked instead of taken on faith. The methodology is the product of the argument: measure the outcome, not the knob.

FAQ

Does JPEG quality 90 equal WebP or AVIF quality 90?

No. Quality is an encoder-specific scale that cannot be compared across codecs (Malte Ubl, Vercel CTO). Matched on the DSSIM perceptual metric, JPEG q80 corresponds to AVIF q64 and WebP q82. The same integer selects different quantization in each encoder, so it produces different quality and different file sizes.

What WebP or AVIF quality matches my JPEG q80?

By DSSIM-matched equivalence, JPEG q80 lines up with AVIF q64 and WebP q82 (Malte Ubl, Vercel CTO). But do not hardcode those numbers; they hold for one dataset and encoder version. The durable method is to target a perceptual score like SSIMULACRA2 80 and let each encoder find its own setting.

Why does JPEG XL use distance instead of quality?

Because distance is a perceptual target, not a quantizer index. libjxl's distance runs 0 to 25, where 0.0 is lossless and 1.0 is visually lossless (libjxl encoder API). Distance means the same thing regardless of image content, so it is portable in a way that quality integers are not.

What does libjxl distance 1.0 mean?

Distance 1.0 is the visually-lossless target: a maximum Butteraugli distance at which differences are not visible in normal viewing (libjxl encoder API). It maps to roughly quality 90 on JPEG XL's compatibility shim. The recommended lossy range is 0.5 to 3.0, spanning near-lossless down to visibly compressed.

How do I compare file size across formats fairly?

Fix a perceptual target, encode each format to reach it, then compare sizes. Targeting SSIMULACRA2 80, one synthesis reported WebP needing 33% and AVIF 46% fewer bits than JPEG for equal quality (codec-wiki, x266.mov); verify those figures for your content. Never compare codecs at equal quality integers.