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