# How JPEG quantization tables identify the device that wrote a file

Source: https://passreal.com/guides/jpeg-quantization-tables

Every JPEG stores its quantization tables in the file header, because a decoder needs them to reconstruct the image. Camera manufacturers use tables specific to their bodies and firmware, distinct from the reference table in the JPEG standard that most software libraries use. Reading the table therefore narrows down what wrote the file, independently of anything EXIF claims.

## What the tables are for

JPEG compresses by transforming each 8x8 block of pixels into frequency
coefficients, then dividing those coefficients by a table of divisors and
rounding. Larger divisors throw away more information. The high-frequency
coefficients, which the eye is least sensitive to, get the largest divisors.

The decoder cannot reverse this without knowing the divisors, so they travel
with the file in the DQT marker. Two tables are typical: one for luminance, one
shared by the chrominance channels, each holding 64 values.

That is the entire mechanism. The forensic value is a side effect of the format
needing to be self-describing.

## Why the values differ between devices

The standard includes an example table in its informative Annex K. It is an
example, not a mandate, and it was derived from perceptual experiments decades
ago.

Manufacturers derive their own instead, tuned against their own sensors, their
own demosaicing and their own noise characteristics, and adjusted for the
quality settings they expose in the menu. Firmware updates change them. Phone
makers running computational pipelines produce different tables again, because
the image reaching the encoder has already been through more processing.

Software libraries, meanwhile, scale the Annex K table by a quality factor.
This is what libjpeg does, which means it is effectively what Pillow, most
editors and most upload pipelines do.

## Reading the table on a file

- `djpeg -verbose -verbose file.jpg > /dev/null` prints both tables.
- `exiftool -htmldump file.jpg` shows the marker structure with the DQT segment
  expanded, which is easier to read when comparing two files side by side.
- Published databases map tables to device families, and searching a table's
  first row is usually enough to find the family it belongs to.

The interesting comparison is rarely the table alone. It is the table against
the EXIF. Agreement means the file's compression is consistent with its claimed
origin. Contradiction means something re-encoded it, which may be an editor, an
upload pipeline, or a claim that was never true.

## What the table cannot tell you

The signal is real and it is narrow, and overreading it is easy.

- It identifies an encoder, not a photographer. A camera file opened in an
  editor and exported carries the editor's tables, which is the normal outcome
  of ordinary editing rather than evidence of anything.
- Tables are shared. A single table may belong to a whole product line across
  several years, so a match indicates a family and not a serial number.
- Phone pipelines complicate it further. Computational photography re-encodes
  along the way, and what reaches storage may not carry the tables the sensor's
  own encoder would have used.
- Chat applications re-encode on upload. An image received through a messaging
  app carries that app's tables regardless of where it started.

What survives all those caveats is the comparison with EXIF. A file whose table
and whose claimed body disagree has been through something, and knowing that is
useful even when it does not say what.

## Why this matters for generated images

An image produced by a model and saved by a Python library carries the library's
scaled Annex K table. Writing camera EXIF onto that file produces a
contradiction: metadata claiming a specific body, compression matching a generic
software encoder.

The pipeline behind this site avoids that by writing the measured table for the
make it declares. Where no measured table is available for a make, it falls back
to the scaled standard table and declares accordingly, because a table that
contradicts the claim is a louder signal than a generic one.

The sensor and frequency stages run before this: grain at intensity 0.018,
lateral chromatic aberration reaching 1.25 px at the corners, and a
low-pass filter at 0.88 of Nyquist. The encode is the last thing that
happens, so everything upstream is what gets compressed.

## Questions

### Where is the table stored?

In the DQT marker segment, before the scan data. There are usually two tables, one for luminance and one for chrominance, each 64 values arranged for an 8x8 block. `djpeg -verbose` and `exiftool -htmldump` both display them.

### Why do manufacturers use different tables?

Because the standard's example table is a starting point, not a requirement. Manufacturers tune their own against their own sensors and processing, trading file size against the artefacts their pipeline produces, and firmware versions change them.

### How specific is the identification?

It narrows, rather than pinpoints. Tables are shared across bodies in a range and across firmware versions, so a match indicates a family. Combined with the EXIF make and model, agreement or contradiction between the two is what carries the information.

### What does a library-encoded file look like?

It carries a scaled version of the standard's Annex K table, which is what libjpeg produces at a given quality setting. That is normal for anything exported by software, and abnormal for a file whose EXIF claims it came straight out of a camera.

### Does resaving a camera JPEG destroy this?

Yes. Re-encoding writes the new encoder's tables, so a camera file edited and exported by an image editor carries the editor's tables and the camera's EXIF. That mismatch is extremely common and innocent, which limits how much weight the signal can carry.

Try it: https://passreal.com/app (three images free)
