How we detect a JPEG's existing quality before re-compressing
When you upload a JPEG and ask us to compress it at quality 80, the obvious behaviour — re-encode at quality 80, return the result — is often wrong. If the input is already at quality 72, re-encoding at 80 produces a file that's bigger than the input and a generation worse in fidelity. To do the right thing we need to know what quality the input actually is, before we touch it. There's no field in the JPEG file that stores "I was saved at quality 80". But there's a way to back-compute it from what's there.
Quantization tables are visible
JPEG files carry their quantization tables in the file itself, in DQT (Define Quantization Table) markers. The decoder needs them to reverse the encoder's per-block rounding — without them, the file is unreadable. They're not hidden, encrypted, or proprietary. Every JPEG has them right there in the header.
Each table is 64 bytes (an 8 × 8 grid of values). A typical photo has two: one for luma (brightness) and one for chroma (the two colour channels share a table). The values are how aggressively each frequency coefficient gets rounded.
Parsing the DQT marker
To find the tables we walk the JPEG byte stream looking for the marker pattern FF DB. When we find one we read its length, then loop through the table entries until the marker's region ends or we hit the start-of-scan marker FF DA:
for i in [0 .. data.length - 70]:
if data[i] == 0xFF and data[i+1] == 0xDB:
length = (data[i+2] << 8) | data[i+3]
offset = i + 4
end = i + 2 + length
while offset < end:
precision = (data[offset] >> 4) & 0x0F
tableId = data[offset] & 0x0F
if precision == 0: # 8-bit values
table = data[offset+1 .. offset+65] # 64 bytes
tables[tableId] = table
offset += 65
if data[i] == 0xFF and data[i+1] == 0xDA: break
After this loop runs, tables[0] is the luma quantization table and tables[1] is the chroma table (if present).
Reversing the quality multiplier
The IJG quality mapping is well known: for a quality setting Q, the encoder scales a fixed reference table by a factor that depends on Q. To recover Q we do the reverse. Divide each entry of the reference table by the corresponding entry of the found table, take the median ratio, and invert the scaling formula:
median = median ratio of (ref_table[i] / found_table[i])
if median ≥ 1: # higher than Q=50 reference
Q = round(50 + 50 × (1 − 1/median))
else: # lower than Q=50
Q = round(50 × median)
Median, not mean: the quantization tables can have a few outlier entries (especially in the corners) that throw a mean estimate off. Median is robust against those. We also skip entries where the reference value is exactly 99, which is a sentinel for "round everything to zero anyway" and doesn't carry quality information.
Why it mostly works (and when it doesn't)
Almost every JPEG encoder uses the standard reference tables, with the standard IJG quality mapping. For those — phone cameras, web export tools, every common image editor — the back-computation is accurate to within a quality unit or two.
The detector returns null in cases where the math doesn't apply:
- Custom quantization tables. Some professional tools use hand-tuned tables that don't match the reference. The back-computation gives a number, but it isn't the "Q" that produced the file.
- 16-bit precision tables. Rare but legal in JPEG. We only handle the common 8-bit precision and skip 16-bit entries.
- JPEG 2000, CMYK JPEG, arithmetic-coded JPEG. Different formats with different table schemes. The detector just doesn't return a useful answer.
The skip-if-already-good guard
With a measured input quality in hand, the compress path becomes:
detected = extractJpegQuality(input.bytes)
if detected != null and detected ≤ slider:
return input unchanged # nothing to gain
else:
re-encode at slider quality
This guard saves a generation of damage on photos that were already smaller than the requested target. It also speeds up the page — there's no need to spin up the encoder for a no-op. About 30% of real-world inputs hit the skip path, depending on what people are uploading.
Why we average luma and chroma
If both quantization tables are present, we compute Q from each separately and combine: (2 × Q_luma + Q_chroma) / 3. The luma table is weighted heavier because it dominates the perceived quality of a photo — chroma quantization with its all-99 corners is mostly noise even at high Q, and reading too much into its variations leads to less stable estimates. Two-thirds luma, one-third chroma is a heuristic that holds up across a wide range of real-world JPEGs.
For the size-search and the content-aware re-encode decisions downstream, this single Q estimate is what gets passed forward.