Convert to the working space before the edits, not after

Every point operation ran in camera RGB and the camera matrix came after
them all, against the order ARCH §5.2 draws. So `luminance()` applied
Rec.709 weights to a body's own primaries, a band in the colour mixer
was a different hue on every make of sensor, and the vibrance skin guard
tested channel order in a space where skin does not have one.

Operations now declare a `Stage`. White balance is the only camera-stage
node — its multipliers scale the sensor's channels, and after a matrix
that mixes them the same numbers are a different correction — and says
so with `stage: camera` in its YAML, a key both the build-time generator
and the load-time declared op read. The composer emits the camera nodes,
then the matrix, then the rest, each group in graph order; an empty
chain still gets the matrix.

Film simulation stops converting out of camera space itself, since it is
now handed working-space colour like every other scene node. The base
curve stays where it was, after the operations, and so now acts on
working-space colour; the next commit replaces it (D19).
This commit is contained in:
2026-09-27 16:52:53 -04:00
parent e8898a5c38
commit db7b84795c
9 changed files with 234 additions and 123 deletions
+18 -9
View File
@@ -246,12 +246,11 @@ scene twice — the camera's rendering, and then a film's rendering of *that*
which looks like neither and reads as a colour-management bug with no
colour-management bug to find.
So a node declaring `renders` takes camera RGB and hands back linear sRGB, and
in exchange the composer emits neither the base curve nor the conversion out of
camera space. Both halves move to the node, together: the base curve is defined
in camera RGB and the matrix is what leaves it, so a node replacing one has
necessarily replaced the other. `compose_full` keeps them as a single string
for exactly that reason — it is what makes getting half of it right impossible. `distortion` and
So a node declaring `renders` hands back display-referred linear sRGB, and in
exchange the composer does not emit the base curve. It is handed working-space
colour like every other node: the conversion out of camera space is no longer
the rendering's to take over, because since D19 it runs before every node but
white balance (see "Stages" below). `distortion` and
`aberration` are `Warp`s rather than operations: they rewrite coordinates
before sampling rather than transforming a colour after it.
@@ -326,9 +325,19 @@ second copy, so a profile author placing a control point and a photographer
dragging one mean the same thing by it.
The order still reads correctly from this directory: the base curve runs after
every node in the chain and before the conversion out of camera space. That is
the same reasoning `exposure` records under `placement:` — corrections to
capture are only meaningful on linear values, so the rendering goes last.
every node in the chain. That is the same reasoning `exposure` records under
`placement:` — corrections to capture are only meaningful on linear values, so
the rendering goes last.
## Stages
`stage: camera` puts a node in camera RGB, ahead of the camera matrix; the
default, `stage: scene`, hands it working-space colour — linear sRGB
primaries, scene-referred and unbounded. White balance is the only camera
node, because its multipliers scale the sensor's own channels. Everything else
belongs in the scene, where a hue or a luminance weight means the same thing
whichever body took the frame (D19). The composer emits the camera nodes, then
the matrix, then the scene nodes, each group in `order:`.
## Errors