Files
craft-libs/crates/gainmap/README.md
Dylan M. TaylorandTed Mao 305bc3c971 Add craft-gainmap: ISO 21496-1, hdrgm and Apple gain maps, from LightCraft (#904)
* Add craft-gainmap: ISO 21496-1 and hdrgm gain map metadata, compute and apply, from LightCraft

Moved from lightcraft-codecs' gainmap.rs at 8ef35987 with its tests: the metadata reader and
writer and the arithmetic. The JPEG container (MPF index, two encoded images) stays in the app.

* craft-gainmap: Apple legacy gain-map reconstruction

Apple's legacy single-channel gain maps interpolate a linear gain ratio, not the log2 gain of
ISO 21496-1: apple::apply rebuilds the HDR rendition from a decoded base and the map, with the
headroom taken from the maker-note pair or the HDRGainMapHeadroom tag, checked inputs and
pixel-centre sampling.

Ported from storytold/lightcraft#3760 (which carries #1978).

---------

Co-authored-by: Ted Mao <tedmao@gmail.com>
2026-11-11 17:07:37 -05:00

2.0 KiB

craft-gainmap

HDR gain maps for the Craft apps, from LightCraft's lightcraft-codecs: an SDR base image plus a small map of gains that rebuilds the HDR rendition on a display with headroom. This crate is the metadata and the arithmetic. It does not encode or decode images and does not know a container. No unsafe, no panics on malformed input; the only dependency is thiserror.

craft-gainmap = { git = "https://github.com/storytold/craft-libs", rev = "<commit>" }
GainMapMeta::from_iso / to_iso ISO 21496-1 binary metadata (the JPEG APP2 payload after ISO_URN; the same bytes HEIF and AVIF carry)
GainMapMeta::from_hdrgm_xmp / to_gain_map_xmp Adobe hdrgm XMP (Ultra HDR), attributes or rdf:Seq lists; the writer adds Apple's description of the same map
compute measures a 1 or 3-channel 8-bit gain map from SDR and HDR renditions in linear light
apply rebuilds the HDR rendition from a base and its map, at a weight for the display's headroom
primary_iso_marker, primary_xmp what the primary image carries to say the file has a gain map
apple::apply, apple::Metadata Apple's legacy single-channel gain maps (a linear ratio, headroom from the maker note)
use craft_gainmap::{GainMapOptions, apply, compute};

// `sdr` and `hdr`: the same picture, linear RGB in the same primaries.
let (map, meta) = compute(&sdr, &hdr, width, height, [0.2126, 0.7152, 0.0722], &GainMapOptions::default())?;
let mut rendition = sdr.clone();
apply(&mut rendition, width, height, &map, &meta, 1.0)?;

Metadata readers return None for anything a decoder could not use: unknown versions, an HDR base, zero denominators, non-finite values, gammas that are not positive. compute and apply check every size (with overflow) against the buffers they are given and return Error instead of indexing past them.

What stays with the caller: encoding the base and the map, the container (for a JPEG, the MPF index that locates the second image), and colour management of the base.