Files
DarkRoom/core/dr-film
dtourolleandClaude Opus 5 e9b3598841
Build and test / Desktop (Linux) (push) Successful in 20m48s
Build and test / Layer separation (push) Successful in 27s
Traceability / Requirement traces (push) Failing after 25s
🐳 Android image / Build and push (push) Successful in 2s
Build and test / android-image (push) Successful in 2s
Build and test / Android (aarch64) (push) Failing after 33m33s
Add five Ilford stocks, and say plainly that they are constructed
They were asked for and they are here, but not on the same footing as the
Kodak profiles, and the files say so in their first line.

What I had claimed, and had to withdraw: that Delta 100 is "quoted around 9"
and HP5 "around 12". Ilford publish no such figures. The word granularity does
not occur anywhere in their technical information -- grain is described as
"fine" and "finest" and nothing more. That claim was in this crate's
documentation as though it came from a datasheet; it is corrected there too.

Two further traps found while looking:

  - Kodak colour negatives publish Print Grain Index, not RMS granularity.
    PGI is a perceptual scale from viewer surveys -- 25 is roughly the
    threshold of visibility, four units a just-noticeable difference -- and
    Kodak state it cannot be compared to RMS. So a Portra number cannot be
    dropped into the granularity field, and none has been.
  - RMS proper is published mostly for black-and-white, reversal and motion
    picture stocks. Every shipped stock therefore still carries the same
    default, which means grain does not yet tell one film from another. That
    is per-stock data, not code, and is now written down where somebody will
    find it.

So the Ilford profiles are built rather than extracted, and each part rests on
something different:

  speed        published and exact -- ISO 400/27 for HP5 is a fact
  contrast     ISO 6:1993's normal development, average gradient 0.62
  spectral     borrowed from Kodak Double-X, a *measured* panchromatic
               negative, shifted by the speed difference. Conventional
               panchromatic sensitisation is much alike across black-and-white
               films, and this is far better founded than reading pixels off a
               printed curve
  silver       neutral, which is not an approximation: developed silver
               absorbs flat, and Double-X's measurement is flat
  granularity  estimated, ordered by each film's known relative grain

They render as a film of that speed and contrast. They are not a measurement
of that emulsion, and the two stocks that share a speed differ only in the
estimated part.

`every_shipped_stock_bakes` is tightened to match, because a constructed
profile fails in a way a measured one does not: the curve parses, bakes, and
sits entirely off one end of its own exposure range, rendering every frame
black or blown while passing a finiteness check. It now asserts mid-grey lands
somewhere photographic and that the tone response runs the way the stock's
kind says it should.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 10:08:51 +02:00
..

Film stocks

One file per stock in profiles/. Adding a stock is adding a file — no code change, no shader, no new operation — for the same reason dr-decode's base curves work that way: under the GPLv3 a stock should be contributable without a release.

What a profile is

Three measured tables, all of them published in the manufacturer's datasheet:

Field What it decides
log_sensitivity what each emulsion layer sees, per wavelength
density_curves contrast, latitude, and where the stock clips
dye_density what the developed stock looks like, per wavelength
base_density the support: film base, and a colour negative's orange mask

Plus kind (negative or positive), support (film or paper), and the two illuminants the data is referenced to. A print paper is a stock like any other; support exists so an interface can offer papers separately, not because the renderer treats them differently.

Why it is not a LUT

Because the parameters stay physical. Opening up a stop moves the picture along the film's own characteristic curve — toe, shoulder and all — instead of scaling a number somebody baked at one exposure. A scanned negative comes out orange and inverted because that is what a negative is, and it becomes a photograph when a paper profile prints it, exactly as it would in a darkroom.

The data cost runs the other way from a LUT collection too: a stock is about 17 kB of measurements, where one HaldCLUT is roughly 800 kB of one person's grade.

How it runs

The spectral chain reduces to three tables, and the reduction is exact where it matters — see src/bake.rs for the argument:

  1. A 3×3 matrix, linear sRGB to the three layers' exposure. Exact, not an approximation: the reconstructed scene spectrum is linear in the sRGB triple, so the integral collapses into nine numbers.
  2. Three 1D curves, log exposure to density, sampled at 256 points.
  3. One 32³ lookup, density to linear sRGB — dye absorption, the print through the negative, the paper, the viewing illuminant and the chromatic adaptation, all of which take exactly three numbers in.

Per pixel that is a matrix multiply, three curve taps and one texture fetch. Splitting 2 from 3, rather than baking one LUT over exposure, is measured rather than assumed: the curve carries all the sharp shape and the dye mixing is smooth, so folding the curve into the 3D lookup would need it three times larger for the same error. At 32³ the worst interpolation error is about 0.003 in linear sRGB, below one 8-bit code value, and there is a test that says so.

Adding a stock

If spektrafilm has it, add its name to STOCKS in tools/film-profiles/convert.py and re-run it. Otherwise write the YAML by hand from the datasheet; the loader validates the table lengths and says which file and field is wrong.

Either way, list it in BUILT_IN in src/lib.rs to compile it in — or drop it in the profile directory at runtime, which is the path meant for stocks that ship separately from the binary.

Provenance

The shipped profiles are converted from spektrafilm by Andrea Volpato, licensed CC BY-SA 4.0. See profiles/LICENSE-PROFILES.txt for the licence and profiles/CHANGELOG.txt for what the conversion changed and what it deliberately did not.

The sRGB reflectance basis is Mallett & Yuksel (2019); the observer is the CIE 1931 2°.