fix(codecs): read the resolution from EXIF, XMP and Photoshop APP13; write it consistently (#1691) (#1744)

A camera JPEG (e.g. Nikon D3200) records its resolution only in EXIF IFD0, so it
opened at 72 ppi instead of 300, and saving wrote JFIF 72 next to the source's
EXIF 300.

Reading: a JPEG takes the first usable of APP13 Photoshop ResolutionInfo
(0x03ED), XMP tiff:X/YResolution, EXIF IFD0 X/YResolution + ResolutionUnit
(2 inch, also when absent; 3 cm), and only then the JFIF density. That is the
order Photoshop resolves them in; JFIF comes last because cameras write EXIF
only while many tools write a default JFIF 72. Other formats (WebP, HEIF, PNG
without pHYs, ...) fall back to XMP then EXIF when they have no resolution of
their own. All parsing is bounds-checked; malformed data reads as no
resolution.

Writing: JPEG, PNG, WebP, TIFF (XMP), PSD and Save for Web JPEG rewrite the
EXIF/XMP resolution to the image's: RATIONAL values in place, ResolutionUnit 2,
and an entry that can't be rewritten safely is removed from IFD0 without moving
any other data.
This commit is contained in:
Gert Pellin - GPE authored and GitHub committed 2026-10-09 22:07:59 +07:00
1 parent e39cacd895
commit 58289b2822
14 files changed
+1060 -29

No files matched your search

+9
View File
@@ -106,6 +106,15 @@ the same.
size (5–8 swap width and height), and the rotated buffer is allocated fallibly.
`Image::oriented` turns any layout and depth in parallel bands (about 10 ms for 24 MP RGB8 in
release).
* **Resolution** (#1691, module `resolution`): a file without a resolution of its own format
(no PNG `pHYs`, a WebP, HEIF, …) takes it from its XMP `tiff:XResolution` / `YResolution` /
`ResolutionUnit`, else from EXIF IFD0 (RATIONAL X/YResolution, unit 2 inch — the default when
absent — or 3 cm; unit 1 or anything malformed counts as no resolution). A JPEG uses, like
Photoshop, the first usable of: APP13 Photoshop ResolutionInfo (0x03ED), XMP, EXIF, and only
then the JFIF density (cameras write EXIF only, many tools write a default JFIF 72). On export
the EXIF and XMP resolution are rewritten to the image's (`export_exif`, `export_xmp`): the
RATIONAL values in place, ResolutionUnit set to 2, an entry that can't be rewritten removed
from IFD0 without moving any other data. IFD1 (the thumbnail's) is left as it is.
* **JPEG**
* 8-bit only. Neither decoder backend supports 12-bit.
* A file cut off inside its image data decodes leniently (a baseline JPEG's missing rows come