Commit Graph
3 Commits
Author SHA1 Message Date
dtourolleandClaude Opus 5 3b5952769b Emit floats an f32 can hold, and drop the format! that formats nothing
CI runs cargo fmt --check and clippy -D warnings, and this branch had
never been through either. Both would have failed it.

The bulk was the generated colour tables: eight significant figures where
an f32 carries about 7.2, so the eighth is noise that rounds away at
compile time and clippy's excessive_precision says so 109 times over.
Fixed in the generator rather than only in the file, so it stays fixed --
and the file is trimmed in place rather than re-derived, because
regenerating it needs a colour-science stack that has nothing to do with
the defect.

The format! in the composer is mine too, from extracting the rendering
tail: the braces in it were escaped because the text used to live inside a
larger template, and once extracted the escapes are noise and the call
formats nothing.

Also here, and clearly not mine: an unused import and a shadowed binding
in dr-gpu, and an unused import in a test. They are pre-existing --
clippy has been failing on master before this branch existed, on lints
like is_multiple_of that arrived with a toolchain rather than with
anyone's code. Fixed because CI cannot go green around them, and called
out because a merge commit is a bad place to quietly edit someone else's
crate.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 22:28:14 +02:00
dtourolleandClaude Opus 5 6d18517d28 Ship every stock that exists, black and white included
Three profiles was what the first cut needed to prove the model. This is
the rest of the open data: 23 camera stocks and 9 papers, which is all of
spektrafilm.

Black and white was the gap, and it turned out not to be a gap in the
data -- it was a gap in where I looked. Upstream's `main` has 28 colour
profiles and nothing monochrome; `dev` has three more, and they are
Tri-X, Double-X and the 2302 print film they go onto. So the answer to
"do we have B&W" was yes all along, and it needed the dev branch rather
than a fortnight digitising Ilford's datasheet graphs by eye. Those three
are pinned to `dev` per stock; the colour stocks stay on the released
branch.

A monochrome profile is single-channel -- one emulsion, not three -- and
spreading that one layer across all three is exact rather than an
approximation: three layers with identical sensitivity and identical
curves respond identically, which is what one layer does. The dye is the
trap. The renderer *sums* the three layers' contributions, so replicating
it unchanged renders every frame three times too dense -- neutrally, and
therefore plausibly. A third each reconstructs the single emulsion, and
two tests hold both halves: that the densities stay equal, and that they
sum to one emulsion and not three.

Double-X and 2302 ship five curves apiece, measured at five development
times -- 4 to 12 minutes for Double-X. That is push and pull processing as
measured data. The standard 6.5 minutes is what ships; the rest is in the
upstream file waiting for a control to ask for it.

Two stocks are `support: film` and are nevertheless what a negative is
printed *onto*: the cine projection films 2383 and 2393, which the
Vision3 stocks print to. Filtering the picker on support alone offered a
projection stock as something to load in a camera, so it filters on stage,
with a test saying so.

The picker had to change shape twice over. Chips were right for three
stocks and off the edge of a 280px column at twenty-four, and the column
that replaced them was a thousand pixels standing between the
photographer and every slider below. It is a disclosure now: one row
carrying the answer, opened to change it, closed again on choosing. That
is the opposite of the argument this panel used to take the lids off its
sliders, and deliberately so -- an instrument you compare wants to be
visible, and a list you consult once wants to be out of the way.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 20:30:11 +02:00
dtourolleandClaude Opus 5 b6a95e1965 Simulate a film stock from its measurements, not from someone's grade
FR-DEV-3f asks for look emulation and proposes HaldCLUT import to inherit
the free film-simulation ecosystem. This takes the other road for the
stocks where the measurements exist: run the physics.

A stock here is its manufacturer's own datasheet -- spectral sensitivity,
characteristic curves, dye densities. Light exposes three emulsion layers,
the layers develop to densities, the densities are dyes that absorb, and
what is left is what reaches the eye. A colour negative comes out orange
and upside down because that is what a colour negative is; it becomes a
photograph when a paper profile prints it, with the enlarger's filtration
solved rather than dialled.

What that buys over a LUT is that the parameters stay physical. Opening up
a stop moves the picture along the film's real characteristic curve,
shoulder and all, instead of scaling a number baked at one exposure. The
data cost runs the other way too: a stock is 17 kB of published
measurements where one HaldCLUT is 800 kB of one person's grade.

It looks like it needs a spectral integration per pixel. It does not, and
that is the whole design:

  - Exposure is a 3x3 matrix. The reconstructed scene spectrum is linear
    in the sRGB triple, so the integral collapses into nine numbers,
    exactly -- no approximation.
  - The characteristic curve is three 1D functions, sampled exactly.
  - Everything after that -- dye absorption, the print through the
    negative, the paper, the viewing illuminant, the adaptation -- takes
    exactly three numbers in, so it bakes into one 32^3 lookup.

Per pixel: a matrix multiply, three curve taps, one fetch. Splitting the
curve out of the 3D lookup rather than baking one LUT over exposure is
measured, not assumed: the curve carries the sharp shape and the dye
mixing is smooth, so folding them together would need three times the
resolution for the same error. At 32^3 the worst error is 0.003 in linear
sRGB, under one 8-bit code value, and a test says so.

No wgpu dependency, deliberately, and the same isolation argument dr-lens
makes: the model is plain f32 with a documented layout, so every property
worth asserting is asserted on the CPU. Binding it to a texture is dr-gpu's
job and is not done here yet.

The expected values in tests/ came from a Python prototype running against
a different colour-science stack. Agreement to three decimals is evidence
about the model rather than about one implementation of it -- a transposed
matrix or a mispasted observer row would pass every unit test and fail
that one.

Profiles are converted from spektrafilm by Andrea Volpato, CC BY-SA 4.0.
The converter is in the tree and runnable, so what was changed from
upstream is auditable rather than taken on trust; profiles/CHANGELOG.txt
records it, including the one deliberate deviation -- Mallett & Yuksel's
1 kB basis instead of Hanatos's 4 MB table, which costs accuracy at the
gamut edge and saves four megabytes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 14:44:54 +02:00