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,
|
||||
/// 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]
|
||||
|
||||
Reference in New Issue
Block a user