From 3b5952769b7812e1d8a2bebd2a51375f408a7ea3 Mon Sep 17 00:00:00 2001 From: Duncan Tourolle Date: Tue, 25 Aug 2026 22:28:14 +0200 Subject: [PATCH] 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 --- core/dr-film/src/bake.rs | 63 ++++- core/dr-film/src/built_in.rs | 135 +++++++-- core/dr-film/src/lib.rs | 8 +- core/dr-film/src/profile.rs | 19 +- core/dr-film/src/spectrum.rs | 11 +- core/dr-film/src/tables.rs | 399 +++++++++++++-------------- core/dr-gpu/src/adjust.rs | 5 - core/dr-gpu/tests/film_sim.rs | 12 +- core/dr-gpu/tests/local_contrast.rs | 2 +- core/dr-pipeline/src/lib.rs | 10 +- core/dr-pipeline/src/operation.rs | 10 +- core/dr-pipeline/src/ops/film_sim.rs | 30 +- core/dr-pipeline/src/sidecar.rs | 21 +- tools/film-profiles/convert.py | 14 +- ui/dr-ui/src/develop.rs | 32 ++- ui/dr-ui/src/lib.rs | 14 +- ui/dr-ui/src/library.rs | 3 +- ui/dr-ui/src/presets.rs | 17 +- 18 files changed, 490 insertions(+), 315 deletions(-) diff --git a/core/dr-film/src/bake.rs b/core/dr-film/src/bake.rs index 9a6ee69..67fc298 100644 --- a/core/dr-film/src/bake.rs +++ b/core/dr-film/src/bake.rs @@ -65,7 +65,12 @@ impl<'a> Recipe<'a> { /// The straightforward reading of a stock: reversal viewed directly, /// negative printed on the paper its datasheet names. pub fn new(film: &'a Profile, print: Option<&'a Profile>) -> Self { - Self { film, print, exposure_ev: 0.0, print_exposure_ev: 0.0 } + Self { + film, + print, + exposure_ev: 0.0, + print_exposure_ev: 0.0, + } } } @@ -140,8 +145,7 @@ impl Baked { let w = if dx == 0 { 1.0 - frac[0] } else { frac[0] } * if dy == 0 { 1.0 - frac[1] } else { frac[1] } * if dz == 0 { 1.0 - frac[2] } else { frac[2] }; - let e = self.lut - [((base[2] + dz) * n + base[1] + dy) * n + base[0] + dx]; + let e = self.lut[((base[2] + dz) * n + base[1] + dy) * n + base[0] + dx]; for c in 0..3 { out[c] += w * e[c]; } @@ -192,7 +196,12 @@ pub fn exposure_matrix(film: &Profile) -> [[f32; 3]; 3] { /// goes: the mask is a fixed density, so balancing mid-grey to neutral cancels /// it — which is why a printed negative looks like a photograph while a scanned /// one looks orange. -fn print_balance(film: &Profile, paper: &Profile, exposure_ev: f32, print_exposure_ev: f32) -> [f32; 3] { +fn print_balance( + film: &Profile, + paper: &Profile, + exposure_ev: f32, + print_exposure_ev: f32, +) -> [f32; 3] { let matrix = exposure_matrix(film); let scene = MID_GREY * 2f32.powf(exposure_ev); let mut log_exposure = [0.0f32; 3]; @@ -320,7 +329,11 @@ fn invert_mean_curve(paper: &Profile, density: f32) -> f32 { let ascending = at(last) >= at(0); for i in 0..last { let (lo, hi) = (at(i), at(i + 1)); - let brackets = if ascending { lo <= density && density <= hi } else { hi <= density && density <= lo }; + let brackets = if ascending { + lo <= density && density <= hi + } else { + hi <= density && density <= lo + }; if brackets && (hi - lo).abs() > f32::EPSILON { let f = (density - lo) / (hi - lo); return log_at(i) + (log_at(i + 1) - log_at(i)) * f; @@ -329,7 +342,11 @@ fn invert_mean_curve(paper: &Profile, density: f32) -> f32 { // Off the end of the curve: the nearest end is the honest answer, and it // keeps a badly-scaled contributed profile from producing a NaN that would // propagate silently through the whole LUT. - if (density <= at(0)) == ascending { paper.log_exposure_min } else { paper.log_exposure_max } + if (density <= at(0)) == ascending { + paper.log_exposure_min + } else { + paper.log_exposure_max + } } #[cfg(test)] @@ -386,7 +403,10 @@ mod tests { let shadow = baked.apply([0.02; 3]); let mid = baked.apply([MID_GREY; 3]); let highlight = baked.apply([0.8; 3]); - assert!(shadow[1] < mid[1] && mid[1] < highlight[1], "{shadow:?} {mid:?} {highlight:?}"); + assert!( + shadow[1] < mid[1] && mid[1] < highlight[1], + "{shadow:?} {mid:?} {highlight:?}" + ); } #[test] @@ -397,7 +417,10 @@ mod tests { let baked = bake(&Recipe::new(&film, None)); let shadow = baked.apply([0.02; 3]); let highlight = baked.apply([0.8; 3]); - assert!(shadow[1] > highlight[1], "not inverted: {shadow:?} -> {highlight:?}"); + assert!( + shadow[1] > highlight[1], + "not inverted: {shadow:?} -> {highlight:?}" + ); let mid = baked.apply([MID_GREY; 3]); assert!(mid[0] > mid[2] * 4.0, "no orange mask: {mid:?}"); } @@ -418,7 +441,10 @@ mod tests { "print is not a positive: {shadow:?} {mid:?} {highlight:?}" ); for grey in [shadow, mid, highlight] { - assert!(spread(grey) < 0.06, "print of a neutral is not neutral: {grey:?}"); + assert!( + spread(grey) < 0.06, + "print of a neutral is not neutral: {grey:?}" + ); } } @@ -426,7 +452,10 @@ mod tests { fn exposure_moves_the_print_the_way_it_moves_a_photograph() { let film = portra(); let paper = endura(); - let brighter = bake(&Recipe { exposure_ev: 1.0, ..Recipe::new(&film, Some(&paper)) }); + let brighter = bake(&Recipe { + exposure_ev: 1.0, + ..Recipe::new(&film, Some(&paper)) + }); let base = bake(&Recipe::new(&film, Some(&paper))); assert!(brighter.apply([MID_GREY; 3])[1] > base.apply([MID_GREY; 3])[1]); } @@ -443,11 +472,16 @@ mod tests { let mut worst = 0.0f32; for i in 0..40 { for j in 0..40 { - let rgb = [i as f32 / 39.0, j as f32 / 39.0, ((i + j) % 40) as f32 / 39.0]; + let rgb = [ + i as f32 / 39.0, + j as f32 / 39.0, + ((i + j) % 40) as f32 / 39.0, + ]; let mut log_exposure = [0.0f32; 3]; for (l, slot) in log_exposure.iter_mut().enumerate() { let m = baked.exposure_matrix[l]; - *slot = ((m[0] * rgb[0] + m[1] * rgb[1] + m[2] * rgb[2]).max(0.0) + 1e-10).log10(); + *slot = + ((m[0] * rgb[0] + m[1] * rgb[1] + m[2] * rgb[2]).max(0.0) + 1e-10).log10(); } let exact = viewing.to_srgb(&film.transmittance(film.density_at(log_exposure))); let approx = baked.apply(rgb); @@ -456,7 +490,10 @@ mod tests { } } } - assert!(worst < 1.0 / 255.0, "worst LUT error {worst} exceeds one code value"); + assert!( + worst < 1.0 / 255.0, + "worst LUT error {worst} exceeds one code value" + ); } #[test] diff --git a/core/dr-film/src/built_in.rs b/core/dr-film/src/built_in.rs index 918afd8..d515f2d 100644 --- a/core/dr-film/src/built_in.rs +++ b/core/dr-film/src/built_in.rs @@ -6,35 +6,116 @@ /// Each stock's id and its YAML, in the order the converter ran. pub static BUILT_IN: &[(&str, &str)] = &[ - ("fujifilm_c200", include_str!("../profiles/fujifilm_c200.yaml")), - ("fujifilm_crystal_archive_typeii", include_str!("../profiles/fujifilm_crystal_archive_typeii.yaml")), - ("fujifilm_pro_400h", include_str!("../profiles/fujifilm_pro_400h.yaml")), - ("fujifilm_provia_100f", include_str!("../profiles/fujifilm_provia_100f.yaml")), - ("fujifilm_velvia_100", include_str!("../profiles/fujifilm_velvia_100.yaml")), - ("fujifilm_xtra_400", include_str!("../profiles/fujifilm_xtra_400.yaml")), + ( + "fujifilm_c200", + include_str!("../profiles/fujifilm_c200.yaml"), + ), + ( + "fujifilm_crystal_archive_typeii", + include_str!("../profiles/fujifilm_crystal_archive_typeii.yaml"), + ), + ( + "fujifilm_pro_400h", + include_str!("../profiles/fujifilm_pro_400h.yaml"), + ), + ( + "fujifilm_provia_100f", + include_str!("../profiles/fujifilm_provia_100f.yaml"), + ), + ( + "fujifilm_velvia_100", + include_str!("../profiles/fujifilm_velvia_100.yaml"), + ), + ( + "fujifilm_xtra_400", + include_str!("../profiles/fujifilm_xtra_400.yaml"), + ), ("kodak_2383", include_str!("../profiles/kodak_2383.yaml")), ("kodak_2393", include_str!("../profiles/kodak_2393.yaml")), - ("kodak_ektachrome_100", include_str!("../profiles/kodak_ektachrome_100.yaml")), - ("kodak_ektacolor_edge", include_str!("../profiles/kodak_ektacolor_edge.yaml")), - ("kodak_ektar_100", include_str!("../profiles/kodak_ektar_100.yaml")), - ("kodak_endura_premier", include_str!("../profiles/kodak_endura_premier.yaml")), - ("kodak_gold_200", include_str!("../profiles/kodak_gold_200.yaml")), - ("kodak_kodachrome_64", include_str!("../profiles/kodak_kodachrome_64.yaml")), - ("kodak_portra_160", include_str!("../profiles/kodak_portra_160.yaml")), - ("kodak_portra_400", include_str!("../profiles/kodak_portra_400.yaml")), - ("kodak_portra_800", include_str!("../profiles/kodak_portra_800.yaml")), - ("kodak_portra_800_push1", include_str!("../profiles/kodak_portra_800_push1.yaml")), - ("kodak_portra_800_push2", include_str!("../profiles/kodak_portra_800_push2.yaml")), - ("kodak_portra_endura", include_str!("../profiles/kodak_portra_endura.yaml")), - ("kodak_supra_endura", include_str!("../profiles/kodak_supra_endura.yaml")), - ("kodak_ultra_endura", include_str!("../profiles/kodak_ultra_endura.yaml")), - ("kodak_ultramax_400", include_str!("../profiles/kodak_ultramax_400.yaml")), - ("kodak_verita_200d", include_str!("../profiles/kodak_verita_200d.yaml")), - ("kodak_vision3_50d", include_str!("../profiles/kodak_vision3_50d.yaml")), - ("kodak_vision3_200t", include_str!("../profiles/kodak_vision3_200t.yaml")), - ("kodak_vision3_250d", include_str!("../profiles/kodak_vision3_250d.yaml")), - ("kodak_vision3_500t", include_str!("../profiles/kodak_vision3_500t.yaml")), + ( + "kodak_ektachrome_100", + include_str!("../profiles/kodak_ektachrome_100.yaml"), + ), + ( + "kodak_ektacolor_edge", + include_str!("../profiles/kodak_ektacolor_edge.yaml"), + ), + ( + "kodak_ektar_100", + include_str!("../profiles/kodak_ektar_100.yaml"), + ), + ( + "kodak_endura_premier", + include_str!("../profiles/kodak_endura_premier.yaml"), + ), + ( + "kodak_gold_200", + include_str!("../profiles/kodak_gold_200.yaml"), + ), + ( + "kodak_kodachrome_64", + include_str!("../profiles/kodak_kodachrome_64.yaml"), + ), + ( + "kodak_portra_160", + include_str!("../profiles/kodak_portra_160.yaml"), + ), + ( + "kodak_portra_400", + include_str!("../profiles/kodak_portra_400.yaml"), + ), + ( + "kodak_portra_800", + include_str!("../profiles/kodak_portra_800.yaml"), + ), + ( + "kodak_portra_800_push1", + include_str!("../profiles/kodak_portra_800_push1.yaml"), + ), + ( + "kodak_portra_800_push2", + include_str!("../profiles/kodak_portra_800_push2.yaml"), + ), + ( + "kodak_portra_endura", + include_str!("../profiles/kodak_portra_endura.yaml"), + ), + ( + "kodak_supra_endura", + include_str!("../profiles/kodak_supra_endura.yaml"), + ), + ( + "kodak_ultra_endura", + include_str!("../profiles/kodak_ultra_endura.yaml"), + ), + ( + "kodak_ultramax_400", + include_str!("../profiles/kodak_ultramax_400.yaml"), + ), + ( + "kodak_verita_200d", + include_str!("../profiles/kodak_verita_200d.yaml"), + ), + ( + "kodak_vision3_50d", + include_str!("../profiles/kodak_vision3_50d.yaml"), + ), + ( + "kodak_vision3_200t", + include_str!("../profiles/kodak_vision3_200t.yaml"), + ), + ( + "kodak_vision3_250d", + include_str!("../profiles/kodak_vision3_250d.yaml"), + ), + ( + "kodak_vision3_500t", + include_str!("../profiles/kodak_vision3_500t.yaml"), + ), ("kodak_trix", include_str!("../profiles/kodak_trix.yaml")), - ("kodak_doublex", include_str!("../profiles/kodak_doublex.yaml")), + ( + "kodak_doublex", + include_str!("../profiles/kodak_doublex.yaml"), + ), ("kodak_2302", include_str!("../profiles/kodak_2302.yaml")), ]; diff --git a/core/dr-film/src/lib.rs b/core/dr-film/src/lib.rs index 448d162..ea130d7 100644 --- a/core/dr-film/src/lib.rs +++ b/core/dr-film/src/lib.rs @@ -37,8 +37,8 @@ //! `tools/film-profiles/convert.py` rather than pasted, so what changed is //! auditable. The sRGB reflectance basis is Mallett & Yuksel (2019). -mod built_in; pub mod bake; +mod built_in; pub mod profile; pub mod spectrum; pub mod tables; @@ -97,7 +97,11 @@ mod tests { #[test] fn every_built_in_profile_parses() { - assert_eq!(built_in().len(), BUILT_IN.len(), "a built-in profile failed to parse"); + assert_eq!( + built_in().len(), + BUILT_IN.len(), + "a built-in profile failed to parse" + ); } #[test] diff --git a/core/dr-film/src/profile.rs b/core/dr-film/src/profile.rs index 2dff9a6..cfd481a 100644 --- a/core/dr-film/src/profile.rs +++ b/core/dr-film/src/profile.rs @@ -155,7 +155,10 @@ impl Profile { )); } if self.density_curves.len() < 2 { - return Err(format!("{}: density_curves needs at least two samples", self.stock)); + return Err(format!( + "{}: density_curves needs at least two samples", + self.stock + )); } if self.log_exposure_max <= self.log_exposure_min { return Err(format!( @@ -184,8 +187,8 @@ impl Profile { let span = self.log_exposure_max - self.log_exposure_min; let mut out = [0.0f32; 3]; for (c, slot) in out.iter_mut().enumerate() { - let t = ((log_exposure[c] - self.log_exposure_min) / span).clamp(0.0, 1.0) - * last as f32; + let t = + ((log_exposure[c] - self.log_exposure_min) / span).clamp(0.0, 1.0) * last as f32; let i = (t.floor() as usize).min(last - 1); let f = t - i as f32; *slot = self.density_curves[i][c] * (1.0 - f) + self.density_curves[i + 1][c] * f; @@ -232,8 +235,9 @@ where { use serde::de::Error as _; let v = Vec::::deserialize(d)?; - v.try_into() - .map_err(|v: Vec| D::Error::custom(format!("expected {SPECTRUM} samples, got {}", v.len()))) + v.try_into().map_err(|v: Vec| { + D::Error::custom(format!("expected {SPECTRUM} samples, got {}", v.len())) + }) } #[cfg(test)] @@ -338,7 +342,10 @@ mod tests { let p = portra(); let blue = p.base_density[16]; // 460nm let red = p.base_density[64]; // 700nm - assert!(blue > red + 0.2, "base density blue {blue} red {red} is not a mask"); + assert!( + blue > red + 0.2, + "base density blue {blue} red {red} is not a mask" + ); } #[test] diff --git a/core/dr-film/src/spectrum.rs b/core/dr-film/src/spectrum.rs index 8dd8864..3a432ea 100644 --- a/core/dr-film/src/spectrum.rs +++ b/core/dr-film/src/spectrum.rs @@ -15,7 +15,7 @@ pub type Spectrum = [f32; SPECTRUM]; /// happens before this is applied — see [`Viewing::to_srgb`]. const XYZ_TO_SRGB: [[f32; 3]; 3] = [ [3.2404542, -1.5371385, -0.4985314], - [-0.9692660, 1.8760108, 0.0415560], + [-0.969266, 1.8760108, 0.041556], [0.0556434, -0.2040259, 1.0572252], ]; @@ -81,8 +81,8 @@ pub fn blackbody(kelvin: f32) -> Spectrum { let mut out = [0.0f32; SPECTRUM]; let mut total = 0.0f64; for (i, slot) in out.iter_mut().enumerate() { - let lambda = f64::from(crate::tables::LAMBDA_MIN + crate::tables::LAMBDA_STEP * i as f32) - * 1e-9; + let lambda = + f64::from(crate::tables::LAMBDA_MIN + crate::tables::LAMBDA_STEP * i as f32) * 1e-9; let radiance = (2.0 * H * C * C) / (lambda.powi(5) * ((H * C / (lambda * KB * f64::from(kelvin))).exp() - 1.0)); *slot = radiance as f32; @@ -208,7 +208,10 @@ mod tests { for r in 0..3 { for c in 0..3 { let expected = if r == c { 1.0 } else { 0.0 }; - assert!((m[r][c] - expected).abs() < 1e-5, "{m:?} is not the identity"); + assert!( + (m[r][c] - expected).abs() < 1e-5, + "{m:?} is not the identity" + ); } } } diff --git a/core/dr-film/src/tables.rs b/core/dr-film/src/tables.rs index ac71b92..5408ddc 100644 --- a/core/dr-film/src/tables.rs +++ b/core/dr-film/src/tables.rs @@ -16,90 +16,89 @@ pub const LAMBDA_STEP: f32 = 5.0; /// nothing in this crate resamples anything. pub const SPECTRUM: usize = 81; - /// CIE 1931 2-degree standard observer, x-bar/y-bar/z-bar per wavelength. pub static OBSERVER: [[f32; 3]; SPECTRUM] = [ - [0.001368, 3.9e-05, 0.006450001], // 380nm - [0.002236, 6.4e-05, 0.01054999], // 385nm - [0.004243, 0.00012, 0.02005001], // 390nm - [0.00765, 0.000217, 0.03621], // 395nm - [0.01431, 0.000396, 0.06785001], // 400nm - [0.02319, 0.00064, 0.1102], // 405nm - [0.04351, 0.00121, 0.2074], // 410nm - [0.07763, 0.00218, 0.3713], // 415nm - [0.13438, 0.004, 0.6456], // 420nm - [0.21477, 0.0073, 1.0390501], // 425nm - [0.2839, 0.0116, 1.3856], // 430nm - [0.3285, 0.01684, 1.62296], // 435nm - [0.34828, 0.023, 1.74706], // 440nm - [0.34806, 0.0298, 1.7826], // 445nm - [0.3362, 0.038, 1.77211], // 450nm - [0.3187, 0.048, 1.7441], // 455nm - [0.2908, 0.06, 1.6692], // 460nm - [0.2511, 0.0739, 1.5281], // 465nm - [0.19536, 0.09098, 1.28764], // 470nm - [0.1421, 0.1126, 1.0419], // 475nm - [0.09564, 0.13902, 0.8129501], // 480nm - [0.05795001, 0.1693, 0.6162], // 485nm - [0.03201, 0.20802, 0.46518], // 490nm - [0.0147, 0.2586, 0.3533], // 495nm - [0.0049, 0.323, 0.272], // 500nm - [0.0024, 0.4073, 0.2123], // 505nm - [0.0093, 0.503, 0.1582], // 510nm - [0.0291, 0.6082, 0.1117], // 515nm - [0.06327, 0.71, 0.07824999], // 520nm - [0.1096, 0.7932, 0.05725001], // 525nm - [0.1655, 0.862, 0.04216], // 530nm - [0.2257499, 0.9148501, 0.02984], // 535nm - [0.2904, 0.954, 0.0203], // 540nm - [0.3597, 0.9803, 0.0134], // 545nm - [0.4334499, 0.9949501, 0.008749999], // 550nm - [0.5120501, 1.0, 0.005749999], // 555nm - [0.5945, 0.995, 0.0039], // 560nm - [0.6784, 0.9786, 0.002749999], // 565nm - [0.7621, 0.952, 0.0021], // 570nm - [0.8425, 0.9154, 0.0018], // 575nm - [0.9163, 0.87, 0.001650001], // 580nm - [0.9786, 0.8163, 0.0014], // 585nm - [1.0263, 0.757, 0.0011], // 590nm - [1.0567, 0.6949, 0.001], // 595nm - [1.0622, 0.631, 0.0008], // 600nm - [1.0456, 0.5668, 0.0006], // 605nm - [1.0026, 0.503, 0.00034], // 610nm - [0.9384, 0.4412, 0.00024], // 615nm - [0.8544499, 0.381, 0.00019], // 620nm - [0.7514, 0.321, 0.0001], // 625nm - [0.6424, 0.265, 4.999999e-05], // 630nm - [0.5419, 0.217, 3e-05], // 635nm - [0.4479, 0.175, 2e-05], // 640nm - [0.3608, 0.1382, 1e-05], // 645nm - [0.2835, 0.107, 2.1175824e-22], // 650nm - [0.2187, 0.0816, 0.0], // 655nm - [0.1649, 0.061, 0.0], // 660nm - [0.1212, 0.04458, 0.0], // 665nm - [0.0874, 0.032, 0.0], // 670nm - [0.0636, 0.0232, 0.0], // 675nm - [0.04677, 0.017, 0.0], // 680nm - [0.0329, 0.01192, 0.0], // 685nm - [0.0227, 0.00821, 0.0], // 690nm - [0.01584, 0.005723, 0.0], // 695nm - [0.01135916, 0.004102, 0.0], // 700nm - [0.008110916, 0.002929, 0.0], // 705nm - [0.005790346, 0.002091, 0.0], // 710nm - [0.004109457, 0.001484, 0.0], // 715nm - [0.002899327, 0.001047, 0.0], // 720nm - [0.00204919, 0.00074, 0.0], // 725nm - [0.001439971, 0.00052, 0.0], // 730nm - [0.0009999493, 0.0003611, 0.0], // 735nm - [0.0006900786, 0.0002492, 0.0], // 740nm - [0.0004760213, 0.0001719, 0.0], // 745nm - [0.0003323011, 0.00012, 0.0], // 750nm - [0.0002348261, 8.48e-05, 0.0], // 755nm - [0.0001661505, 6e-05, 0.0], // 760nm - [0.000117413, 4.24e-05, 0.0], // 765nm - [8.307527e-05, 3e-05, 0.0], // 770nm - [5.870652e-05, 2.12e-05, 0.0], // 775nm - [4.150994e-05, 1.499e-05, 0.0], // 780nm + [0.001368, 3.9e-05, 0.006450001], // 380nm + [0.002236, 6.4e-05, 0.01054999], // 385nm + [0.004243, 0.00012, 0.02005001], // 390nm + [0.00765, 0.000217, 0.03621], // 395nm + [0.01431, 0.000396, 0.06785001], // 400nm + [0.02319, 0.00064, 0.1102], // 405nm + [0.04351, 0.00121, 0.2074], // 410nm + [0.07763, 0.00218, 0.3713], // 415nm + [0.13438, 0.004, 0.6456], // 420nm + [0.21477, 0.0073, 1.03905], // 425nm + [0.2839, 0.0116, 1.3856], // 430nm + [0.3285, 0.01684, 1.62296], // 435nm + [0.34828, 0.023, 1.74706], // 440nm + [0.34806, 0.0298, 1.7826], // 445nm + [0.3362, 0.038, 1.77211], // 450nm + [0.3187, 0.048, 1.7441], // 455nm + [0.2908, 0.06, 1.6692], // 460nm + [0.2511, 0.0739, 1.5281], // 465nm + [0.19536, 0.09098, 1.28764], // 470nm + [0.1421, 0.1126, 1.0419], // 475nm + [0.09564, 0.13902, 0.8129501], // 480nm + [0.05795001, 0.1693, 0.6162], // 485nm + [0.03201, 0.20802, 0.46518], // 490nm + [0.0147, 0.2586, 0.3533], // 495nm + [0.0049, 0.323, 0.272], // 500nm + [0.0024, 0.4073, 0.2123], // 505nm + [0.0093, 0.503, 0.1582], // 510nm + [0.0291, 0.6082, 0.1117], // 515nm + [0.06327, 0.71, 0.07824999], // 520nm + [0.1096, 0.7932, 0.05725001], // 525nm + [0.1655, 0.862, 0.04216], // 530nm + [0.2257499, 0.9148501, 0.02984], // 535nm + [0.2904, 0.954, 0.0203], // 540nm + [0.3597, 0.9803, 0.0134], // 545nm + [0.4334499, 0.9949501, 0.008749999], // 550nm + [0.5120501, 1.0, 0.005749999], // 555nm + [0.5945, 0.995, 0.0039], // 560nm + [0.6784, 0.9786, 0.002749999], // 565nm + [0.7621, 0.952, 0.0021], // 570nm + [0.8425, 0.9154, 0.0018], // 575nm + [0.9163, 0.87, 0.001650001], // 580nm + [0.9786, 0.8163, 0.0014], // 585nm + [1.0263, 0.757, 0.0011], // 590nm + [1.0567, 0.6949, 0.001], // 595nm + [1.0622, 0.631, 0.0008], // 600nm + [1.0456, 0.5668, 0.0006], // 605nm + [1.0026, 0.503, 0.00034], // 610nm + [0.9384, 0.4412, 0.00024], // 615nm + [0.8544499, 0.381, 0.00019], // 620nm + [0.7514, 0.321, 0.0001], // 625nm + [0.6424, 0.265, 4.999999e-05], // 630nm + [0.5419, 0.217, 3e-05], // 635nm + [0.4479, 0.175, 2e-05], // 640nm + [0.3608, 0.1382, 1e-05], // 645nm + [0.2835, 0.107, 2.117582e-22], // 650nm + [0.2187, 0.0816, 0.0], // 655nm + [0.1649, 0.061, 0.0], // 660nm + [0.1212, 0.04458, 0.0], // 665nm + [0.0874, 0.032, 0.0], // 670nm + [0.0636, 0.0232, 0.0], // 675nm + [0.04677, 0.017, 0.0], // 680nm + [0.0329, 0.01192, 0.0], // 685nm + [0.0227, 0.00821, 0.0], // 690nm + [0.01584, 0.005723, 0.0], // 695nm + [0.01135916, 0.004102, 0.0], // 700nm + [0.008110916, 0.002929, 0.0], // 705nm + [0.005790346, 0.002091, 0.0], // 710nm + [0.004109457, 0.001484, 0.0], // 715nm + [0.002899327, 0.001047, 0.0], // 720nm + [0.00204919, 0.00074, 0.0], // 725nm + [0.001439971, 0.00052, 0.0], // 730nm + [0.0009999493, 0.0003611, 0.0], // 735nm + [0.0006900786, 0.0002492, 0.0], // 740nm + [0.0004760213, 0.0001719, 0.0], // 745nm + [0.0003323011, 0.00012, 0.0], // 750nm + [0.0002348261, 8.48e-05, 0.0], // 755nm + [0.0001661505, 6e-05, 0.0], // 760nm + [0.000117413, 4.24e-05, 0.0], // 765nm + [8.307527e-05, 3e-05, 0.0], // 770nm + [5.870652e-05, 2.12e-05, 0.0], // 775nm + [4.150994e-05, 1.499e-05, 0.0], // 780nm ]; /// Mallett & Yuksel (2019) sRGB reflectance basis: the three smooth, @@ -108,139 +107,129 @@ pub static OBSERVER: [[f32; 3]; SPECTRUM] = [ /// This is what makes the exposure step a 3x3 matrix rather than a /// per-pixel spectral integration -- see [`crate::bake`]. pub static SRGB_BASIS: [[f32; 3]; SPECTRUM] = [ - [0.32745741, 0.33186171, 0.34068079], // 380nm - [0.32375058, 0.32968819, 0.34656119], // 385nm - [0.31343946, 0.32786002, 0.35870049], // 390nm - [0.28887938, 0.31917358, 0.39194703], // 395nm - [0.23920568, 0.29432258, 0.46647173], // 400nm - [0.18970204, 0.25869706, 0.5516009], // 405nm - [0.12174607, 0.18889432, 0.68935961], // 410nm - [0.074578271, 0.12538838, 0.80003335], // 415nm - [0.044433159, 0.07868706, 0.87687978], // 420nm - [0.028928632, 0.053143271, 0.9179281], // 425nm - [0.022316653, 0.042288146, 0.9353952], // 430nm - [0.016911307, 0.033318346, 0.94977035], // 435nm - [0.014181107, 0.029755948, 0.95606294], // 440nm - [0.013053143, 0.030331251, 0.95661561], // 445nm - [0.011986164, 0.030988572, 0.95702526], // 450nm - [0.011288715, 0.031686355, 0.95702493], // 455nm - [0.010906066, 0.034669962, 0.95442397], // 460nm - [0.010400713, 0.034551957, 0.95504733], // 465nm - [0.01063736, 0.040684806, 0.94867783], // 470nm - [0.010907663, 0.054460037, 0.9346323], // 475nm - [0.011032712, 0.080905287, 0.908062], // 480nm - [0.011310657, 0.1463483, 0.84234104], // 485nm - [0.011154642, 0.37967964, 0.60916572], // 490nm - [0.01014877, 0.76674427, 0.22310696], // 495nm - [0.0089185821, 0.87621475, 0.11486667], // 500nm - [0.0076855763, 0.91849166, 0.073822768], // 505nm - [0.0067057083, 0.94065556, 0.052638729], // 510nm - [0.005995806, 0.95373188, 0.040272309], // 515nm - [0.0055372566, 0.96164328, 0.032819463], // 520nm - [0.0051937842, 0.96720002, 0.027606196], // 525nm - [0.0050253623, 0.97098975, 0.023984891], // 530nm - [0.0051363628, 0.9728523, 0.022011333], // 535nm - [0.0054332003, 0.97311659, 0.021450205], // 540nm - [0.0058199859, 0.97335107, 0.020828945], // 545nm - [0.0064005728, 0.97335112, 0.020248311], // 550nm - [0.0074495287, 0.97226108, 0.020289391], // 555nm - [0.0085836358, 0.97335102, 0.018065342], // 560nm - [0.010395762, 0.9731485, 0.016455742], // 565nm - [0.013565434, 0.97106131, 0.01537326], // 570nm - [0.019384516, 0.96637131, 0.014244178], // 575nm - [0.032084071, 0.95494197, 0.012973962], // 580nm - [0.074356038, 0.91357899, 0.012064974], // 585nm - [0.62439372, 0.3643488, 0.011257478], // 590nm - [0.91831003, 0.071507243, 0.010182725], // 595nm - [0.94925303, 0.041230434, 0.0095165354], // 600nm - [0.95818783, 0.032423874, 0.0093882927], // 605nm - [0.95818775, 0.03192463, 0.0098876191], // 610nm - [0.95818763, 0.031276033, 0.010536342], // 615nm - [0.95567906, 0.03263037, 0.011690569], // 620nm - [0.95800615, 0.029530872, 0.012462973], // 625nm - [0.95410157, 0.031561761, 0.014336665], // 630nm - [0.94760761, 0.035674218, 0.016718175], // 635nm - [0.93868133, 0.041403005, 0.019915666], // 640nm - [0.92446668, 0.05060426, 0.024929056], // 645nm - [0.90460603, 0.0634343, 0.031959674], // 650nm - [0.8804122, 0.078918245, 0.040669554], // 655nm - [0.84778787, 0.099542743, 0.052669382], // 660nm - [0.80577913, 0.12559576, 0.068625111], // 665nm - [0.75253185, 0.15759091, 0.089877232], // 670nm - [0.6864394, 0.19539824, 0.11816236], // 675nm - [0.61869457, 0.23147447, 0.14983095], // 680nm - [0.54026444, 0.26885214, 0.19088341], // 685nm - [0.47296442, 0.29602916, 0.2310064], // 690nm - [0.4327016, 0.30975499, 0.25754339], // 695nm - [0.40535805, 0.31781588, 0.27682604], // 700nm - [0.38549183, 0.32299035, 0.29151777], // 705nm - [0.37098358, 0.32635385, 0.30266251], // 710nm - [0.3576087, 0.3291439, 0.3132473], // 715nm - [0.3487128, 0.33080873, 0.32047833], // 720nm - [0.34488012, 0.33148269, 0.32363699], // 725nm - [0.34191788, 0.33198455, 0.32609731], // 730nm - [0.33953109, 0.33234117, 0.32812737], // 735nm - [0.3371695, 0.33291201, 0.32991798], // 740nm - [0.33617202, 0.33291928, 0.3309079], // 745nm - [0.33516744, 0.33302767, 0.33180363], // 750nm - [0.33442163, 0.3331797, 0.33239663], // 755nm - [0.33400876, 0.33324703, 0.33274078], // 760nm - [0.33391579, 0.33325935, 0.33282086], // 765nm - [0.33381845, 0.33327505, 0.33290173], // 770nm - [0.33367277, 0.33329433, 0.33302597], // 775nm - [0.33356951, 0.33330942, 0.33311108], // 780nm + [0.3274574, 0.3318617, 0.3406808], // 380nm + [0.3237506, 0.3296882, 0.3465612], // 385nm + [0.3134395, 0.32786, 0.3587005], // 390nm + [0.2888794, 0.3191736, 0.391947], // 395nm + [0.2392057, 0.2943226, 0.4664717], // 400nm + [0.189702, 0.2586971, 0.5516009], // 405nm + [0.1217461, 0.1888943, 0.6893596], // 410nm + [0.07457827, 0.1253884, 0.8000333], // 415nm + [0.04443316, 0.07868706, 0.8768798], // 420nm + [0.02892863, 0.05314327, 0.9179281], // 425nm + [0.02231665, 0.04228815, 0.9353952], // 430nm + [0.01691131, 0.03331835, 0.9497703], // 435nm + [0.01418111, 0.02975595, 0.9560629], // 440nm + [0.01305314, 0.03033125, 0.9566156], // 445nm + [0.01198616, 0.03098857, 0.9570253], // 450nm + [0.01128871, 0.03168635, 0.9570249], // 455nm + [0.01090607, 0.03466996, 0.954424], // 460nm + [0.01040071, 0.03455196, 0.9550473], // 465nm + [0.01063736, 0.04068481, 0.9486778], // 470nm + [0.01090766, 0.05446004, 0.9346323], // 475nm + [0.01103271, 0.08090529, 0.908062], // 480nm + [0.01131066, 0.1463483, 0.842341], // 485nm + [0.01115464, 0.3796796, 0.6091657], // 490nm + [0.01014877, 0.7667443, 0.223107], // 495nm + [0.008918582, 0.8762148, 0.1148667], // 500nm + [0.007685576, 0.9184917, 0.07382277], // 505nm + [0.006705708, 0.9406556, 0.05263873], // 510nm + [0.005995806, 0.9537319, 0.04027231], // 515nm + [0.005537257, 0.9616433, 0.03281946], // 520nm + [0.005193784, 0.9672, 0.0276062], // 525nm + [0.005025362, 0.9709897, 0.02398489], // 530nm + [0.005136363, 0.9728523, 0.02201133], // 535nm + [0.0054332, 0.9731166, 0.0214502], // 540nm + [0.005819986, 0.9733511, 0.02082895], // 545nm + [0.006400573, 0.9733511, 0.02024831], // 550nm + [0.007449529, 0.9722611, 0.02028939], // 555nm + [0.008583636, 0.973351, 0.01806534], // 560nm + [0.01039576, 0.9731485, 0.01645574], // 565nm + [0.01356543, 0.9710613, 0.01537326], // 570nm + [0.01938452, 0.9663713, 0.01424418], // 575nm + [0.03208407, 0.954942, 0.01297396], // 580nm + [0.07435604, 0.913579, 0.01206497], // 585nm + [0.6243937, 0.3643488, 0.01125748], // 590nm + [0.91831, 0.07150724, 0.01018273], // 595nm + [0.949253, 0.04123043, 0.009516535], // 600nm + [0.9581878, 0.03242387, 0.009388293], // 605nm + [0.9581878, 0.03192463, 0.009887619], // 610nm + [0.9581876, 0.03127603, 0.01053634], // 615nm + [0.9556791, 0.03263037, 0.01169057], // 620nm + [0.9580062, 0.02953087, 0.01246297], // 625nm + [0.9541016, 0.03156176, 0.01433667], // 630nm + [0.9476076, 0.03567422, 0.01671817], // 635nm + [0.9386813, 0.041403, 0.01991567], // 640nm + [0.9244667, 0.05060426, 0.02492906], // 645nm + [0.904606, 0.0634343, 0.03195967], // 650nm + [0.8804122, 0.07891824, 0.04066955], // 655nm + [0.8477879, 0.09954274, 0.05266938], // 660nm + [0.8057791, 0.1255958, 0.06862511], // 665nm + [0.7525319, 0.1575909, 0.08987723], // 670nm + [0.6864394, 0.1953982, 0.1181624], // 675nm + [0.6186946, 0.2314745, 0.1498309], // 680nm + [0.5402644, 0.2688521, 0.1908834], // 685nm + [0.4729644, 0.2960292, 0.2310064], // 690nm + [0.4327016, 0.309755, 0.2575434], // 695nm + [0.4053581, 0.3178159, 0.276826], // 700nm + [0.3854918, 0.3229903, 0.2915178], // 705nm + [0.3709836, 0.3263539, 0.3026625], // 710nm + [0.3576087, 0.3291439, 0.3132473], // 715nm + [0.3487128, 0.3308087, 0.3204783], // 720nm + [0.3448801, 0.3314827, 0.323637], // 725nm + [0.3419179, 0.3319845, 0.3260973], // 730nm + [0.3395311, 0.3323412, 0.3281274], // 735nm + [0.3371695, 0.332912, 0.329918], // 740nm + [0.336172, 0.3329193, 0.3309079], // 745nm + [0.3351674, 0.3330277, 0.3318036], // 750nm + [0.3344216, 0.3331797, 0.3323966], // 755nm + [0.3340088, 0.333247, 0.3327408], // 760nm + [0.3339158, 0.3332594, 0.3328209], // 765nm + [0.3338184, 0.3332751, 0.3329017], // 770nm + [0.3336728, 0.3332943, 0.333026], // 775nm + [0.3335695, 0.3333094, 0.3331111], // 780nm ]; /// CIE standard illuminant D50, normalised to unit mean. pub static ILLUMINANT_D50: [f32; SPECTRUM] = [ - 0.28500907, 0.31632887, 0.34766031, 0.46076543, 0.57388219, 0.61580487, - 0.65773919, 0.67822334, 0.69871914, 0.68582343, 0.67292773, 0.77189179, - 0.8708675, 0.94315561, 1.0154437, 1.0350317, 1.054608, 1.0590075, - 1.0634069, 1.0851713, 1.1069474, 1.0886397, 1.0703319, 1.0922127, - 1.1141052, 1.1192844, 1.124452, 1.1274548, 1.1304576, 1.1593798, - 1.188302, 1.1804808, 1.1726596, 1.1817494, 1.1908393, 1.1773616, - 1.1638724, 1.1506973, 1.1375107, 1.1444008, 1.1512793, 1.1197383, - 1.088209, 1.1125805, 1.1369636, 1.1461582, 1.1553645, 1.1540376, - 1.1527225, 1.1334022, 1.1140819, 1.1323314, 1.1505693, 1.1320055, - 1.1134418, 1.1281298, 1.1428063, 1.1708207, 1.1988235, 1.1763025, - 1.1537816, 1.0853925, 1.0170033, 1.0415727, 1.0661536, 1.0736257, - 1.0811094, 0.98780176, 0.89448247, 0.95068587, 1.0068776, 1.0422011, - 1.077513, 0.9940052, 0.91049735, 0.7909793, 0.67146125, 0.81828375, - 0.96511788, 0.93806949, 0.91100946, + 0.2850091, 0.3163289, 0.3476603, 0.4607654, 0.5738822, 0.6158049, 0.6577392, 0.6782233, + 0.6987191, 0.6858234, 0.6729277, 0.7718918, 0.8708675, 0.9431556, 1.015444, 1.035032, + 1.054608, 1.059008, 1.063407, 1.085171, 1.106947, 1.08864, 1.070332, 1.092213, + 1.114105, 1.119284, 1.124452, 1.127455, 1.130458, 1.15938, 1.188302, 1.180481, + 1.17266, 1.181749, 1.190839, 1.177362, 1.163872, 1.150697, 1.137511, 1.144401, + 1.151279, 1.119738, 1.088209, 1.11258, 1.136964, 1.146158, 1.155364, 1.154038, + 1.152723, 1.133402, 1.114082, 1.132331, 1.150569, 1.132005, 1.113442, 1.12813, + 1.142806, 1.170821, 1.198824, 1.176303, 1.153782, 1.085392, 1.017003, 1.041573, + 1.066154, 1.073626, 1.081109, 0.9878018, 0.8944825, 0.9506859, 1.006878, 1.042201, + 1.077513, 0.9940052, 0.9104973, 0.7909793, 0.6714613, 0.8182837, 0.9651179, 0.9380695, + 0.9110095, ]; /// CIE standard illuminant D55, normalised to unit mean. pub static ILLUMINANT_D55: [f32; SPECTRUM] = [ - 0.37928266, 0.41130471, 0.44333841, 0.57639697, 0.70945552, 0.75371138, - 0.79797887, 0.81556713, 0.83316704, 0.81185393, 0.79052917, 0.89349794, - 0.99645507, 1.0685542, 1.1406533, 1.1550288, 1.1694044, 1.1662034, - 1.1630023, 1.1794499, 1.1958974, 1.1687759, 1.1416427, 1.1567865, - 1.1719303, 1.1720235, 1.1721049, 1.1679843, 1.1638637, 1.1884361, - 1.2130201, 1.2007514, 1.1884826, 1.1935228, 1.198563, 1.181289, - 1.164015, 1.1478119, 1.1316089, 1.1347051, 1.1378131, 1.1010418, - 1.0642822, 1.0816726, 1.0990514, 1.1032535, 1.1074439, 1.1020894, - 1.096735, 1.0747816, 1.0528283, 1.0637817, 1.0747351, 1.0545045, - 1.0342739, 1.0427945, 1.0513035, 1.072442, 1.0935921, 1.0703467, - 1.0471014, 0.98728263, 0.92745226, 0.94585534, 0.96427006, 0.97593349, - 0.98759692, 0.90256562, 0.81754596, 0.87031076, 0.92307556, 0.95620343, - 0.9893313, 0.91301847, 0.83669401, 0.72561205, 0.61451846, 0.74916008, - 0.8838017, 0.85988119, 0.83597232, + 0.3792827, 0.4113047, 0.4433384, 0.576397, 0.7094555, 0.7537114, 0.7979789, 0.8155671, + 0.833167, 0.8118539, 0.7905292, 0.8934979, 0.9964551, 1.068554, 1.140653, 1.155029, + 1.169404, 1.166203, 1.163002, 1.17945, 1.195897, 1.168776, 1.141643, 1.156786, + 1.17193, 1.172024, 1.172105, 1.167984, 1.163864, 1.188436, 1.21302, 1.200751, + 1.188483, 1.193523, 1.198563, 1.181289, 1.164015, 1.147812, 1.131609, 1.134705, 1.137813, + 1.101042, 1.064282, 1.081673, 1.099051, 1.103254, 1.107444, 1.102089, 1.096735, + 1.074782, 1.052828, 1.063782, 1.074735, 1.054504, 1.034274, 1.042795, 1.051303, + 1.072442, 1.093592, 1.070347, 1.047101, 0.9872826, 0.9274523, 0.9458553, 0.9642701, + 0.9759335, 0.9875969, 0.9025656, 0.817546, 0.8703108, 0.9230756, 0.9562034, 0.9893313, + 0.9130185, 0.836694, 0.725612, 0.6145185, 0.7491601, 0.8838017, 0.8598812, 0.8359723, ]; /// CIE standard illuminant D65, normalised to unit mean. pub static ILLUMINANT_D65: [f32; SPECTRUM] = [ - 0.57072795, 0.59740886, 0.62409091, 0.78458177, 0.94507378, 0.99492846, - 1.0447843, 1.055895, 1.0670056, 1.0284649, 0.98992529, 1.0937493, - 1.1975745, 1.2669063, 1.3362495, 1.3408404, 1.3454313, 1.3285751, - 1.3117304, 1.3177945, 1.3238586, 1.2832485, 1.2426385, 1.2457333, - 1.2488396, 1.2399776, 1.2311155, 1.2139168, 1.196718, 1.2132658, - 1.2298251, 1.2110732, 1.1923213, 1.1902656, 1.1882214, 1.1651185, - 1.1420155, 1.1210835, 1.1001515, 1.0970326, 1.0939138, 1.0533585, - 1.0128033, 1.020344, 1.0278847, 1.0255596, 1.0232356, 1.0123842, - 1.0015327, 0.97635016, 0.95116871, 0.95351327, 0.95585783, 0.93488814, - 0.91391845, 0.91499081, 0.91606316, 0.92784419, 0.93962522, 0.91682146, - 0.89401769, 0.8451223, 0.79622805, 0.80700753, 0.81778702, 0.83343149, - 0.8490771, 0.77630216, 0.70352722, 0.7508158, 0.79810438, 0.82780478, - 0.85750517, 0.79187126, 0.72623849, 0.62817019, 0.53010303, 0.64651553, - 0.76292802, 0.74338471, 0.72384139, + 0.5707279, 0.5974089, 0.6240909, 0.7845818, 0.9450738, 0.9949285, 1.044784, 1.055895, + 1.067006, 1.028465, 0.9899253, 1.093749, 1.197574, 1.266906, 1.33625, 1.34084, + 1.345431, 1.328575, 1.31173, 1.317794, 1.323859, 1.283249, 1.242638, 1.245733, + 1.24884, 1.239978, 1.231116, 1.213917, 1.196718, 1.213266, 1.229825, 1.211073, + 1.192321, 1.190266, 1.188221, 1.165118, 1.142016, 1.121083, 1.100151, 1.097033, + 1.093914, 1.053359, 1.012803, 1.020344, 1.027885, 1.02556, 1.023236, 1.012384, + 1.001533, 0.9763502, 0.9511687, 0.9535133, 0.9558578, 0.9348881, 0.9139185, 0.9149908, + 0.9160632, 0.9278442, 0.9396252, 0.9168215, 0.8940177, 0.8451223, 0.796228, 0.8070075, + 0.817787, 0.8334315, 0.8490771, 0.7763022, 0.7035272, 0.7508158, 0.7981044, 0.8278048, + 0.8575052, 0.7918713, 0.7262385, 0.6281702, 0.530103, 0.6465155, 0.762928, 0.7433847, + 0.7238414, ]; diff --git a/core/dr-gpu/src/adjust.rs b/core/dr-gpu/src/adjust.rs index dc4ae4a..774be26 100644 --- a/core/dr-gpu/src/adjust.rs +++ b/core/dr-gpu/src/adjust.rs @@ -1153,7 +1153,6 @@ mod tests { // asks the chain which of its operations are neighbourhood operations // rather than being told a list. Imported anonymously: nothing here names // the trait, only calls through it. - use dr_pipeline::Operation as _; use crate::Demosaicer; @@ -1744,10 +1743,6 @@ mod tests { })); pass.set_film(Some(&tables)); - // Cropped, so the render is against an output size that is not the - // source size — the case where a wrong dispatch or a wrong texture - // allocation would show up. - let (w, h) = g.output_size(32, 32); let shader = g.compose(); // At 512 rather than the 32 this test used before the detail stage // existed, and the size is load-bearing twice over. A compositional diff --git a/core/dr-gpu/tests/film_sim.rs b/core/dr-gpu/tests/film_sim.rs index 15dd58c..1c0fb21 100644 --- a/core/dr-gpu/tests/film_sim.rs +++ b/core/dr-gpu/tests/film_sim.rs @@ -46,7 +46,12 @@ fn flat_raw(level: u16) -> RawImage { // leaving a curve here would test the suppression rather than the // film. `dr-pipeline` asserts the suppression on the generated source. base_curve: BaseCurve::IDENTITY, - crop: CropRect { x: 0, y: 0, width: SIZE, height: SIZE }, + crop: CropRect { + x: 0, + y: 0, + width: SIZE, + height: SIZE, + }, } } @@ -166,5 +171,8 @@ fn a_negative_and_its_print_are_not_the_same_picture() { ); let spread = printed.iter().cloned().fold(f32::MIN, f32::max) - printed.iter().cloned().fold(f32::MAX, f32::min); - assert!(spread < 0.06, "the print of a neutral is not neutral: {printed:?}"); + assert!( + spread < 0.06, + "the print of a neutral is not neutral: {printed:?}" + ); } diff --git a/core/dr-gpu/tests/local_contrast.rs b/core/dr-gpu/tests/local_contrast.rs index df940a1..3feffdf 100644 --- a/core/dr-gpu/tests/local_contrast.rs +++ b/core/dr-gpu/tests/local_contrast.rs @@ -27,7 +27,7 @@ use dr_gpu::{AdjustPass, DemosaicedImage, GpuContext}; use dr_pipeline::descriptor::{OpId, ParamId}; -use dr_pipeline::ops::local_contrast::{Clarity, Texture}; +use dr_pipeline::ops::local_contrast::Clarity; use dr_pipeline::{Affects, EditGraph, OutputMode}; use dr_types::ColourSpace; diff --git a/core/dr-pipeline/src/lib.rs b/core/dr-pipeline/src/lib.rs index 0c3a2d2..960fe69 100644 --- a/core/dr-pipeline/src/lib.rs +++ b/core/dr-pipeline/src/lib.rs @@ -112,11 +112,11 @@ mod tests { stock: "test_stock".into(), print: None, tables: crate::ops::FilmTables { - exposure_matrix: [[5.0, 0.5, 0.2], [0.1, 5.0, 0.3], [0.2, 0.5, 4.0]], - curves: vec![[0.5, 0.5, 0.5]; crate::ops::film_sim::CURVE_SAMPLES], - curve_log_min: -3.0, - curve_log_max: 4.0, - lut: vec![[0.5, 0.5, 0.5]; 32 * 32 * 32], + exposure_matrix: [[5.0, 0.5, 0.2], [0.1, 5.0, 0.3], [0.2, 0.5, 4.0]], + curves: vec![[0.5, 0.5, 0.5]; crate::ops::film_sim::CURVE_SAMPLES], + curve_log_min: -3.0, + curve_log_max: 4.0, + lut: vec![[0.5, 0.5, 0.5]; 32 * 32 * 32], density_max: 3.0, lut_size: 32, }, diff --git a/core/dr-pipeline/src/operation.rs b/core/dr-pipeline/src/operation.rs index e4193eb..b5391b9 100644 --- a/core/dr-pipeline/src/operation.rs +++ b/core/dr-pipeline/src/operation.rs @@ -707,8 +707,7 @@ pub fn compose_full( " // The camera profile's base curve and the conversion out of camera\n // space are both absent: an operation declaring `Operation::renders`\n // has done both, and doing them again would render the picture twice.\n" .to_string() } else { - format!( -" // ==== camera profile: the base curve (FR-DEV-3e) ==== + " // ==== camera profile: the base curve (FR-DEV-3e) ==== // // Marked with `====` and not the `----` an operation block carries: this // is not one, and the difference is what several tests count on to tell @@ -740,7 +739,7 @@ pub fn compose_full( // It is off for a JPEG and any other already-rendered source, which must // not be rendered twice, and for a body the profile database declines to // offer any curve for at all. - if (u.base_curve_last.z > 0.5) {{ + if (u.base_curve_last.z > 0.5) { c = vec3( curve_eval( u.base_curve_x.x, u.base_curve_y.x, u.base_curve_x.y, u.base_curve_y.y, @@ -758,7 +757,7 @@ pub fn compose_full( u.base_curve_last.x, u.base_curve_last.y, c.b, ), ); - }} + } // Camera space -> linear sRGB. Applied after the adjustments so white // balance and exposure act on sensor-native values, which is where they @@ -770,7 +769,8 @@ pub fn compose_full( dot(u.cam_to_srgb_1.rgb, c), dot(u.cam_to_srgb_2.rgb, c), ); -") +" + .to_string() }; let source = format!( diff --git a/core/dr-pipeline/src/ops/film_sim.rs b/core/dr-pipeline/src/ops/film_sim.rs index cf4a428..3700dc5 100644 --- a/core/dr-pipeline/src/ops/film_sim.rs +++ b/core/dr-pipeline/src/ops/film_sim.rs @@ -201,14 +201,32 @@ impl Operation for FilmSim { let mut out = Vec::with_capacity(MATRIX_FIELDS.len() + 5); for (l, row) in m.iter().enumerate() { for (c, v) in row.iter().enumerate() { - out.push(Uniform { name: MATRIX_FIELDS[l][c], value: *v }); + out.push(Uniform { + name: MATRIX_FIELDS[l][c], + value: *v, + }); } } - out.push(Uniform { name: "log_min", value: t.curve_log_min }); - out.push(Uniform { name: "log_max", value: t.curve_log_max }); - out.push(Uniform { name: "density_max", value: t.density_max }); - out.push(Uniform { name: "lut_size", value: t.lut_size as f32 }); - out.push(Uniform { name: "print_exposure", value: self.print_exposure }); + out.push(Uniform { + name: "log_min", + value: t.curve_log_min, + }); + out.push(Uniform { + name: "log_max", + value: t.curve_log_max, + }); + out.push(Uniform { + name: "density_max", + value: t.density_max, + }); + out.push(Uniform { + name: "lut_size", + value: t.lut_size as f32, + }); + out.push(Uniform { + name: "print_exposure", + value: self.print_exposure, + }); out } diff --git a/core/dr-pipeline/src/sidecar.rs b/core/dr-pipeline/src/sidecar.rs index db68e52..16e0ec0 100644 --- a/core/dr-pipeline/src/sidecar.rs +++ b/core/dr-pipeline/src/sidecar.rs @@ -1667,7 +1667,10 @@ mod tests { // stock's default paper, or "show me the negative" would be // unrepresentable. let mut v = version_of(&edited()); - v.film = Some(FilmRef { stock: "kodak_portra_400".into(), print: None }); + v.film = Some(FilmRef { + stock: "kodak_portra_400".into(), + print: None, + }); let mut side = Sidecar::default(); side.versions.insert(v.uuid.clone(), v); @@ -1708,7 +1711,11 @@ mod tests { let text = "drsc 1\n\n[version u1]\nname = Default\nrevision = 1\nmodified = 0\n\ film = ilford_hp5_plus\n"; let side = Sidecar::parse(text).expect("parses"); - assert!(side.to_text().contains("film = ilford_hp5_plus"), "{}", side.to_text()); + assert!( + side.to_text().contains("film = ilford_hp5_plus"), + "{}", + side.to_text() + ); } #[test] @@ -1730,7 +1737,10 @@ mod tests { local.revision = 2; let mut remote = base.clone(); - remote.film = Some(FilmRef { stock: "kodak_kodachrome_64".into(), print: None }); + remote.film = Some(FilmRef { + stock: "kodak_kodachrome_64".into(), + print: None, + }); remote.revision = 9; local.merge(&remote, Some(&base)); @@ -1750,7 +1760,10 @@ mod tests { // there "unset" and "set to zero" are indistinguishable; here the // revision says which happened. let mut base = version_of(&edited()); - base.film = Some(FilmRef { stock: "kodak_portra_400".into(), print: None }); + base.film = Some(FilmRef { + stock: "kodak_portra_400".into(), + print: None, + }); base.revision = 1; let mut local = base.clone(); diff --git a/tools/film-profiles/convert.py b/tools/film-profiles/convert.py index b115d09..d34e2e9 100644 --- a/tools/film-profiles/convert.py +++ b/tools/film-profiles/convert.py @@ -268,9 +268,17 @@ def emit_tables(dest): basis = colour.recovery.MSDS_BASIS_FUNCTIONS_sRGB_MALLETT2019.copy().align(grid).values def lit(v): - """Always a float literal: `%g` renders an exact zero as `0`, which is - an integer in Rust and will not compile in an `[f32; _]`.""" - s = f"{v:.8g}" + """A float literal an `f32` can actually hold. + + Seven significant figures, not eight: `f32` carries about 7.2 decimal + digits, so an eighth is noise that rounds away at compile time -- and + clippy's `excessive_precision` says so, which under CI's `-D warnings` + is a failed build rather than a note. + + Always a literal, too: `%g` renders an exact zero as `0`, which is an + integer in Rust and will not compile in an `[f32; _]`. + """ + s = f"{v:.7g}" return s if any(ch in s for ch in ".eE") else s + ".0" def table(name, doc, values): diff --git a/ui/dr-ui/src/develop.rs b/ui/dr-ui/src/develop.rs index c797280..aa82003 100644 --- a/ui/dr-ui/src/develop.rs +++ b/ui/dr-ui/src/develop.rs @@ -2350,9 +2350,7 @@ impl DevelopSession { /// filter asks about the stage rather than the support. pub fn film_choices() -> Vec<(Option<&'static str>, String)> { let mut out = vec![(None, "None".to_string())]; - out.extend( - dr_film::camera_stocks().map(|p| (Some(p.stock.as_str()), p.name.clone())), - ); + out.extend(dr_film::camera_stocks().map(|p| (Some(p.stock.as_str()), p.name.clone()))); out } @@ -2382,18 +2380,28 @@ impl DevelopSession { // Only a negative has a paper. Asking to print a reversal stock is not // an error to report, it is a request that has no meaning — so it is // quietly the same as not asking. - let paper = if print { dr_film::default_print(profile) } else { None }; + let paper = if print { + dr_film::default_print(profile) + } else { + None + }; let baked = dr_film::bake(&dr_film::Recipe { film: profile, print: paper, - exposure_ev: self.graph.param( - dr_pipeline::ops::film_sim::ID, - dr_pipeline::ops::film_sim::EXPOSURE, - ).unwrap_or(0.0), - print_exposure_ev: self.graph.param( - dr_pipeline::ops::film_sim::ID, - dr_pipeline::ops::film_sim::PRINT_EXPOSURE, - ).unwrap_or(0.0), + exposure_ev: self + .graph + .param( + dr_pipeline::ops::film_sim::ID, + dr_pipeline::ops::film_sim::EXPOSURE, + ) + .unwrap_or(0.0), + print_exposure_ev: self + .graph + .param( + dr_pipeline::ops::film_sim::ID, + dr_pipeline::ops::film_sim::PRINT_EXPOSURE, + ) + .unwrap_or(0.0), }); self.set_film(Some(dr_pipeline::graph::Film { diff --git a/ui/dr-ui/src/lib.rs b/ui/dr-ui/src/lib.rs index 4375edf..d0f9ebe 100644 --- a/ui/dr-ui/src/lib.rs +++ b/ui/dr-ui/src/lib.rs @@ -518,17 +518,21 @@ fn apply_when_ready( pub(crate) fn sync_film(window: &AppWindow, session: &Rc>>) { let choices = DevelopSession::film_choices(); - let chosen = session.borrow().as_ref().and_then(|s| { - s.film() - .map(|(stock, print)| (stock.to_string(), print)) - }); + let chosen = session + .borrow() + .as_ref() + .and_then(|s| s.film().map(|(stock, print)| (stock.to_string(), print))); // A stock this build has no profile for leaves the picker on "None" rather // than inventing an entry for it. The sidecar still carries the name — it // is only the *control* that cannot show what it does not have. let selected = chosen .as_ref() - .and_then(|(stock, _)| choices.iter().position(|(id, _)| *id == Some(stock.as_str()))) + .and_then(|(stock, _)| { + choices + .iter() + .position(|(id, _)| *id == Some(stock.as_str())) + }) .unwrap_or(0); let can_print = choices diff --git a/ui/dr-ui/src/library.rs b/ui/dr-ui/src/library.rs index c61fa51..04a9944 100644 --- a/ui/dr-ui/src/library.rs +++ b/ui/dr-ui/src/library.rs @@ -3120,8 +3120,7 @@ pub fn read_ids_span( /// Named rather than repeated so the three readers cannot drift — and so that /// the one column that is *not* here stays conspicuous. See /// [`attach_file_ids`] for why the server's file id is fetched separately. -const CELL_COLUMNS: &str = - "i.id, i.source_ref, i.file_size, i.metadata_state, i.captured_at"; +const CELL_COLUMNS: &str = "i.id, i.source_ref, i.file_size, i.metadata_state, i.captured_at"; /// Shared row mapping, so the scoped and unscoped queries cannot drift. /// diff --git a/ui/dr-ui/src/presets.rs b/ui/dr-ui/src/presets.rs index 1b2ce04..507df69 100644 --- a/ui/dr-ui/src/presets.rs +++ b/ui/dr-ui/src/presets.rs @@ -277,15 +277,16 @@ pub fn save_open_edit( // note above gives: they are one edit, and a stock read from a // session that had already moved on would be a different // photograph's. - s.film().map(|(stock, print)| dr_pipeline::sidecar::FilmRef { - stock: stock.to_string(), - print: print.then(|| { - dr_film::find(stock) - .and_then(dr_film::default_print) - .map(|p| p.stock.clone()) - .unwrap_or_default() + s.film() + .map(|(stock, print)| dr_pipeline::sidecar::FilmRef { + stock: stock.to_string(), + print: print.then(|| { + dr_film::find(stock) + .and_then(dr_film::default_print) + .map(|p| p.stock.clone()) + .unwrap_or_default() + }), }), - }), ) }) else { return;