Filed from the review of #542, which added IccProfile::builtin(BuiltinProfile::Bt2020Pq). Recorded there and in crates/gamut-icc/STATUS.md as a known limit; not changed silently, because the alternative is a colour-appearance decision with its own consequences.
The behaviour
gamut-icc encodes the BT.2100 PQ tone curve as a sampled curveType (ICC.1:2022 §10.6, 1024 uInt16 points) of gamut_color::transfer::pq_eotf normalized to the transfer's own peak: pq_eotf returns absolute luminance in cd/m², and the samples are divided by pq_eotf(1.0) = 10 000 cd/m² to land in the [0, 1] range the encoding allows.
That is what "peak-referred" means here, and the consequence is arithmetic:
- The BT.2408 reference (diffuse) white for HDR production is ~203 cd/m².
- 203 / 10 000 = 0.0203 media-relative.
So a CMM applying this profile maps a diffuse-white signal to ~2 % of media white, and content graded against diffuse white renders near black. Nothing is wrong with the encoding — for a profile that declares its media white to be the 10 000 cd/m² peak, that is the correct media-relative value — but it is unlikely to be what a caller who embeds the profile next to SDR-referred content expects.
The alternative, and why it is not obviously right
Normalizing to diffuse white instead (dividing by pq_eotf(V) at the ~203 cd/m² signal, or applying a scale in the chad/colorant matrix) makes diffuse white land on media white, at the cost of clipping every specular highlight above it — the top ~5.7 stops of the PQ range — unless a tone curve is applied. That trades one wrong answer for another, and which one is wanted depends on what the profile is for:
- Embedding beside HDR content for an HDR-aware pipeline: peak-referred is right.
- Embedding so an SDR CMM produces something viewable: neither is right; that wants tone mapping (
gamut-tonemap already owns the curves — Reinhard/ACES/Hable/Drago).
What this issue should decide
- Whether
BuiltinProfile::Bt2020Pq keeps the peak-referred normalization as its only behaviour, gains a sibling variant, or is parameterized.
- What a caller is told to do when the goal is an SDR rendering — likely: do not embed a PQ profile, tone-map through
gamut-tonemap and embed an SDR profile.
- Whether the limit belongs in
validate() output in any form (probably not: the profile is conforming).
Any change here is breaking for the bytes builtin(Bt2020Pq) emits, so it should be decided before the constructor has downstream consumers.
Acceptance
Whatever is decided, it is decided against the Little-CMS oracle: a transform of a known diffuse-white PQ signal through the chosen profile, compared with what lcms2 produces through its own equivalent, with the rendering intent stated. A decision to keep the current behaviour closes this issue, provided the reasoning and the measurement are recorded.
Refs #424, #542.
Filed from the review of #542, which added
IccProfile::builtin(BuiltinProfile::Bt2020Pq). Recorded there and incrates/gamut-icc/STATUS.mdas a known limit; not changed silently, because the alternative is a colour-appearance decision with its own consequences.The behaviour
gamut-iccencodes the BT.2100 PQ tone curve as a sampledcurveType(ICC.1:2022 §10.6, 1024uInt16points) ofgamut_color::transfer::pq_eotfnormalized to the transfer's own peak:pq_eotfreturns absolute luminance in cd/m², and the samples are divided bypq_eotf(1.0)= 10 000 cd/m² to land in the[0, 1]range the encoding allows.That is what "peak-referred" means here, and the consequence is arithmetic:
So a CMM applying this profile maps a diffuse-white signal to ~2 % of media white, and content graded against diffuse white renders near black. Nothing is wrong with the encoding — for a profile that declares its media white to be the 10 000 cd/m² peak, that is the correct media-relative value — but it is unlikely to be what a caller who embeds the profile next to SDR-referred content expects.
The alternative, and why it is not obviously right
Normalizing to diffuse white instead (dividing by
pq_eotf(V)at the ~203 cd/m² signal, or applying a scale in thechad/colorant matrix) makes diffuse white land on media white, at the cost of clipping every specular highlight above it — the top ~5.7 stops of the PQ range — unless a tone curve is applied. That trades one wrong answer for another, and which one is wanted depends on what the profile is for:gamut-tonemapalready owns the curves — Reinhard/ACES/Hable/Drago).What this issue should decide
BuiltinProfile::Bt2020Pqkeeps the peak-referred normalization as its only behaviour, gains a sibling variant, or is parameterized.gamut-tonemapand embed an SDR profile.validate()output in any form (probably not: the profile is conforming).Any change here is breaking for the bytes
builtin(Bt2020Pq)emits, so it should be decided before the constructor has downstream consumers.Acceptance
Whatever is decided, it is decided against the Little-CMS oracle: a transform of a known diffuse-white PQ signal through the chosen profile, compared with what lcms2 produces through its own equivalent, with the rendering intent stated. A decision to keep the current behaviour closes this issue, provided the reasoning and the measurement are recorded.
Refs #424, #542.