mirror of
https://github.com/storytold/photocraft.git
synced 2026-11-12 21:23:02 +00:00
Color Lookup: an extreme .3dl shaper can't bake NaN into the table
CodeRabbit finding on #9170 ("Check the baked table for non-finite values"): a finite but extreme shaper such as 0 1e308 -1e308 0 overflowed to infinity when interpolated over the resampled grid, and infinity times 0 put NaN in the table. The shaper is scaled to 0..1 before interpolating, and the baked table is checked like any parsed one. New test: three_dl_extreme_shaper_bakes_only_finite_values (NaN without the fix). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
759edaa3c7
commit
216b53301f
1 file changed
+20
-7
@@ -320,7 +320,7 @@ pub fn parse_3dl(text: &str) -> Result<LutFile, LutError> {
|
||||
}
|
||||
let lut = LutFile { title: String::new(), size: n, data, domain_min: [0.0; 3], domain_max: [1.0; 3] }.check()?;
|
||||
Ok(match shaper {
|
||||
Some(shaper) => apply_shaper(lut, &shaper),
|
||||
Some(shaper) => apply_shaper(lut, &shaper)?,
|
||||
None => lut,
|
||||
})
|
||||
}
|
||||
@@ -331,10 +331,10 @@ pub fn parse_3dl(text: &str) -> Result<LutFile, LutError> {
|
||||
/// value at the shaped input. An even ramp (the usual shaper) changes nothing and is skipped.
|
||||
/// The table is baked with at least as many nodes as the shaper has points (up to [`MAX_SIZE`]):
|
||||
/// exact for an identity table, an approximation at that resolution otherwise.
|
||||
fn apply_shaper(lut: LutFile, shaper: &[f64]) -> LutFile {
|
||||
fn apply_shaper(lut: LutFile, shaper: &[f64]) -> Result<LutFile, LutError> {
|
||||
let m = shaper.len();
|
||||
if m < 2 || shaper.iter().any(|v| !v.is_finite()) {
|
||||
return lut;
|
||||
return Ok(lut);
|
||||
}
|
||||
// The shaper's own depth, from 10 bits up: a 0..1023 ramp is the even ramp with or without a header.
|
||||
let scale = inferred_3dl_scale(shaper.iter().fold(0.0f64, |a, &b| a.max(b)), true);
|
||||
@@ -343,14 +343,16 @@ fn apply_shaper(lut: LutFile, shaper: &[f64]) -> LutFile {
|
||||
// is 1; a float shaper (0..1) is held to one 10-bit step.
|
||||
let code = if scale > 1.0 { 1.0 } else { 1.0 / 1023.0 };
|
||||
if shaper.iter().enumerate().all(|(k, &v)| (v - even(k)).abs() <= code) {
|
||||
return lut;
|
||||
return Ok(lut);
|
||||
}
|
||||
// Scaled to 0..1 first, so interpolating between two extreme values can't overflow.
|
||||
let shaped: Vec<f64> = shaper.iter().map(|v| v / scale).collect();
|
||||
let curve = |x: f64| -> f32 {
|
||||
let p = x.clamp(0.0, 1.0) * (m - 1) as f64;
|
||||
let i = (p.floor() as usize).min(m - 2);
|
||||
let f = p - i as f64;
|
||||
let (a, b) = (shaper.get(i).copied().unwrap_or(0.0), shaper.get(i + 1).copied().unwrap_or(0.0));
|
||||
((a + (b - a) * f) / scale).clamp(0.0, 1.0) as f32
|
||||
let (a, b) = (shaped.get(i).copied().unwrap_or(0.0), shaped.get(i + 1).copied().unwrap_or(0.0));
|
||||
(a + (b - a) * f).clamp(0.0, 1.0) as f32
|
||||
};
|
||||
// A shaper finer than the table is baked at the shaper's resolution (up to MAX_SIZE), so its
|
||||
// curve survives a small table: a 2-node table would otherwise read it only at its two ends.
|
||||
@@ -368,7 +370,7 @@ fn apply_shaper(lut: LutFile, shaper: &[f64]) -> LutFile {
|
||||
}
|
||||
}
|
||||
}
|
||||
out
|
||||
out.check()
|
||||
}
|
||||
|
||||
/// Trilinear lookup of `rgb` (0..1) in a [`LutFile`] (red fastest).
|
||||
@@ -590,6 +592,17 @@ mod tests {
|
||||
assert_eq!(sample(&small, [1.0, 0.0, 1.0]), [1.0, 0.0, 1.0], "the ends stay put");
|
||||
}
|
||||
|
||||
/// #9170 review: a finite but extreme shaper can't overflow into NaN when it is baked.
|
||||
#[test]
|
||||
fn three_dl_extreme_shaper_bakes_only_finite_values() {
|
||||
let table = identity_3dl(&[0, 1, 2, 3], 2);
|
||||
let text = format!("0 1e308 -1e308 0\n{}", table.split_once('\n').unwrap().1);
|
||||
// Refused or baked, but never a table holding NaN.
|
||||
if let Ok(l) = parse_3dl(&text) {
|
||||
assert!(l.data.iter().all(|v| v.is_finite()), "baked a non-finite value");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cube_title_with_hash_round_trips() {
|
||||
let l = LutFile::from_fn("Look #3", 2, |c| c);
|
||||
|
||||
Reference in new issue
Block a user