From ff1a3e0e8032ad18c49da0026f9236881f284d89 Mon Sep 17 00:00:00 2001 From: Duncan Tourolle Date: Wed, 26 Aug 2026 15:24:04 +0200 Subject: [PATCH] Print the black-and-white negatives instead of showing the scan Choosing Ilford HP5 Plus showed an inverted grey frame. So did Double-X. They are negatives, and upstream leaves `target_print` null on every monochrome stock, so nothing was ever printed and the scan was all there was. A colour negative at least announces itself -- the orange mask says plainly that you are looking at a negative. A monochrome one just looks broken. They print on Kodak 2302 now, which is a monochrome print film and is what such a negative is actually printed onto; Double-X onto 2302 is the standard cine chain. For the Ilford stocks it stands in for an Ilford paper, which nobody has measured, and is at least the right kind of material. The scan is still reachable through the Scanned/Printed toggle. It is a thing to choose now rather than the only thing on offer. `every_shipped_stock_bakes` did not catch this, and could not: it derives "should this be inverted?" from the stock's kind *and whether it names a paper*, so it looked at an inverted HP5, concluded that was right for an unprinted negative, and passed. The assertion was self-consistent and the situation was still wrong. The new test asserts the thing that actually matters -- a camera negative must name a paper, that paper must be a printing stock, and it must be the same kind of material, so a monochrome negative cannot end up on colour paper. Co-Authored-By: Claude Opus 5 --- core/dr-film/profiles/ilford_delta_100.yaml | 1 + core/dr-film/profiles/ilford_delta_400.yaml | 1 + core/dr-film/profiles/ilford_fp4_plus.yaml | 1 + core/dr-film/profiles/ilford_hp5_plus.yaml | 1 + core/dr-film/profiles/ilford_pan_f_plus.yaml | 1 + core/dr-film/profiles/kodak_doublex.yaml | 1 + core/dr-film/src/lib.rs | 38 ++++++++++++++++++++ tools/film-profiles/convert.py | 28 +++++++++++++-- 8 files changed, 70 insertions(+), 2 deletions(-) diff --git a/core/dr-film/profiles/ilford_delta_100.yaml b/core/dr-film/profiles/ilford_delta_100.yaml index b166cbe..742d20d 100644 --- a/core/dr-film/profiles/ilford_delta_100.yaml +++ b/core/dr-film/profiles/ilford_delta_100.yaml @@ -21,6 +21,7 @@ kind: negative # negative | positive support: film # film | paper stage: filming # filming | printing monochrome: true +target_print: kodak_2302 reference_illuminant: D55 viewing_illuminant: D50 diff --git a/core/dr-film/profiles/ilford_delta_400.yaml b/core/dr-film/profiles/ilford_delta_400.yaml index 7694c53..450581f 100644 --- a/core/dr-film/profiles/ilford_delta_400.yaml +++ b/core/dr-film/profiles/ilford_delta_400.yaml @@ -21,6 +21,7 @@ kind: negative # negative | positive support: film # film | paper stage: filming # filming | printing monochrome: true +target_print: kodak_2302 reference_illuminant: D55 viewing_illuminant: D50 diff --git a/core/dr-film/profiles/ilford_fp4_plus.yaml b/core/dr-film/profiles/ilford_fp4_plus.yaml index c118750..13113f4 100644 --- a/core/dr-film/profiles/ilford_fp4_plus.yaml +++ b/core/dr-film/profiles/ilford_fp4_plus.yaml @@ -21,6 +21,7 @@ kind: negative # negative | positive support: film # film | paper stage: filming # filming | printing monochrome: true +target_print: kodak_2302 reference_illuminant: D55 viewing_illuminant: D50 diff --git a/core/dr-film/profiles/ilford_hp5_plus.yaml b/core/dr-film/profiles/ilford_hp5_plus.yaml index 7ba23c5..17dd119 100644 --- a/core/dr-film/profiles/ilford_hp5_plus.yaml +++ b/core/dr-film/profiles/ilford_hp5_plus.yaml @@ -21,6 +21,7 @@ kind: negative # negative | positive support: film # film | paper stage: filming # filming | printing monochrome: true +target_print: kodak_2302 reference_illuminant: D55 viewing_illuminant: D50 diff --git a/core/dr-film/profiles/ilford_pan_f_plus.yaml b/core/dr-film/profiles/ilford_pan_f_plus.yaml index ef9a178..ae9451e 100644 --- a/core/dr-film/profiles/ilford_pan_f_plus.yaml +++ b/core/dr-film/profiles/ilford_pan_f_plus.yaml @@ -21,6 +21,7 @@ kind: negative # negative | positive support: film # film | paper stage: filming # filming | printing monochrome: true +target_print: kodak_2302 reference_illuminant: D55 viewing_illuminant: D50 diff --git a/core/dr-film/profiles/kodak_doublex.yaml b/core/dr-film/profiles/kodak_doublex.yaml index 691bac1..7af7750 100644 --- a/core/dr-film/profiles/kodak_doublex.yaml +++ b/core/dr-film/profiles/kodak_doublex.yaml @@ -14,6 +14,7 @@ stage: filming # filming | printing monochrome: true reference_illuminant: D55 viewing_illuminant: D50 +target_print: kodak_2302 # log10 spectral sensitivity per layer, 380-780nm at 5nm, in R,G,B layer # order. A null upstream means the datasheet has no reading there, which is diff --git a/core/dr-film/src/lib.rs b/core/dr-film/src/lib.rs index d23be23..109d3af 100644 --- a/core/dr-film/src/lib.rs +++ b/core/dr-film/src/lib.rs @@ -153,6 +153,44 @@ mod tests { } } + #[test] + fn every_camera_negative_names_a_paper() { + // TRACES: FR-DEV-3f + // **The bug this exists to stop.** A negative with no paper renders as + // a negative: inverted, and for a black-and-white stock without even a + // colour negative's orange cast to say so. Choosing "Ilford HP5 Plus" + // and being shown an inverted grey frame reads as broken, not as a + // scan, and every black-and-white stock shipped that way because + // upstream leaves `target_print` null on all of them. + // + // The scan is still reachable — the Scanned/Printed toggle asks for it + // — but it is a thing to choose rather than the only thing on offer. + for film in camera_stocks() { + if film.kind != Kind::Negative { + continue; + } + let paper = default_print(film); + assert!( + paper.is_some(), + "{} is a negative and names no paper, so it renders inverted", + film.stock + ); + let paper = paper.unwrap(); + assert_eq!( + paper.stage, + Stage::Printing, + "{} names {} as its paper, which is not a printing stock", + film.stock, + paper.stock + ); + assert_eq!( + paper.monochrome, film.monochrome, + "{} is printed on {}, which is the wrong kind of material", + film.stock, paper.stock + ); + } + } + #[test] fn a_projection_print_stock_is_not_offered_as_a_camera_film() { // Kodak 2383 is `support: film` and is nevertheless the Vision3 diff --git a/tools/film-profiles/convert.py b/tools/film-profiles/convert.py index 67a9a2e..62abc06 100644 --- a/tools/film-profiles/convert.py +++ b/tools/film-profiles/convert.py @@ -119,6 +119,19 @@ PARAMETRIC_BW = [ ("ilford_hp5_plus", "Ilford HP5 Plus", 400, 2.6, 11.0), ] +# The black-and-white print stock every monochrome negative here is printed on. +# +# Upstream leaves `target_print` null on the black-and-white negatives, which +# left them rendering as *negatives* -- inverted, and without a colour +# negative's orange cast to make it obvious that was what you were looking at. +# 2302 is a monochrome print film and is the material such a negative is +# actually printed onto; Double-X onto 2302 is the standard cine chain. +# +# For the Ilford stocks it is a stand-in rather than the historical pairing -- +# those would have gone onto an Ilford paper -- but it is the right *kind* of +# material and the only monochrome print stock measured here. +BW_PRINT_STOCK = "kodak_2302" + # The stock whose measured panchromatic response the constructed ones borrow. BW_REFERENCE = "kodak_doublex" BW_REFERENCE_ISO = 250 @@ -256,8 +269,18 @@ def convert(stock): f"reference_illuminant: {info['reference_illuminant']}", f"viewing_illuminant: {info['viewing_illuminant']}", ] - if info.get("target_print"): - out.append(f"target_print: {info['target_print']}") + target_print = info.get("target_print") + if ( + target_print is None + and monochrome + and info["type"] == "negative" + and info["stage"] == "filming" + ): + # See BW_PRINT_STOCK: upstream leaves this null, which showed the + # negative rather than the photograph. + target_print = BW_PRINT_STOCK + if target_print: + out.append(f"target_print: {target_print}") out += [ "", "# log10 spectral sensitivity per layer, 380-780nm at 5nm, in R,G,B layer", @@ -377,6 +400,7 @@ def build_parametric_bw(stock, name, iso, density_max, granularity): "support: film # film | paper", "stage: filming # filming | printing", "monochrome: true", + "target_print: " + BW_PRINT_STOCK, "reference_illuminant: D55", "viewing_illuminant: D50", "",