mirror of
https://github.com/storytold/photocraft.git
synced 2026-11-02 18:13:39 +00:00
raw: a non-finite black level no longer panics the AHD demosaic
A DNG whose BlackLevel is a DOUBLE beyond f32 reads as inf. The periodic fill then computed (raw - inf) * 0 = NaN, and AHD's f32::clamp with NaN bounds asserts. The fill now returns (0, 0) for a non-finite black, as Scale::value does, and AHD uses a compare-and-select clamp that gives the same result for every non-NaN bound. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -472,6 +472,14 @@ pub(crate) fn ahd_bands(h: usize) -> usize {
|
||||
|
||||
pub(crate) const AHD_TABLE_BYTES: u64 = (LABF_STEPS as u64 + 2) * 4;
|
||||
|
||||
/// `v` limited to `lo..=hi` like `f32::clamp`, without its panic on NaN bounds (hostile black
|
||||
/// levels can put NaN in the plane): same result for every non-NaN bound.
|
||||
#[inline(always)]
|
||||
fn clampf(v: f32, lo: f32, hi: f32) -> f32 {
|
||||
let v = if v < lo { lo } else { v };
|
||||
if v > hi { hi } else { v }
|
||||
}
|
||||
|
||||
fn ahd(p: &Padded, phase: [u8; 4], to_xyz: &Mat3, out: &mut [f32], jobs: usize) {
|
||||
let (w, s) = (p.w, p.stride);
|
||||
let d = &p.data;
|
||||
@@ -500,10 +508,10 @@ fn ahd(p: &Padded, phase: [u8; 4], to_xyz: &Mat3, out: &mut [f32], jobs: usize)
|
||||
}
|
||||
let (l, r) = (d[i - 1], d[i + 1]);
|
||||
let gh = (l + r) * 0.5 + (2.0 * c - d[i - 2] - d[i + 2]) * 0.25;
|
||||
g[0][k] = gh.clamp(l.min(r), l.max(r));
|
||||
g[0][k] = clampf(gh, l.min(r), l.max(r));
|
||||
let (u, dn) = (d[i - s], d[i + s]);
|
||||
let gv = (u + dn) * 0.5 + (2.0 * c - d[i - 2 * s] - d[i + 2 * s]) * 0.25;
|
||||
g[1][k] = gv.clamp(u.min(dn), u.max(dn));
|
||||
g[1][k] = clampf(gv, u.min(dn), u.max(dn));
|
||||
}
|
||||
}
|
||||
// 2. RGB candidates and their Lab, band rows 1..n-1, columns 3..s-3.
|
||||
@@ -600,6 +608,12 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ahd_survives_nan_planes() {
|
||||
let p = Padded::from_plane(&[f32::NAN; 64], 8, 8);
|
||||
assert_eq!(demosaic(&p, [0, 1, 1, 2], Demosaic::Ahd, &IDENTITY, 1).len(), 192);
|
||||
}
|
||||
|
||||
/// Mosaics an RGB image with the given phase.
|
||||
fn mosaic(rgb: &[[f32; 3]], w: usize, h: usize, phase: [u8; 4]) -> Vec<f32> {
|
||||
(0..w * h).map(|i| rgb[i][phase[((i / w) & 1) * 2 + ((i % w) & 1)] as usize]).collect()
|
||||
|
||||
@@ -279,8 +279,12 @@ pub fn develop_sensor(s: &Sensor, opts: &DevelopOptions) -> Result<Developed> {
|
||||
let x = c.x + p;
|
||||
let black = bl.at(x.saturating_sub(s.active.x), y.saturating_sub(s.active.y), 0, 1);
|
||||
let range = s.white[0] - black;
|
||||
let gain = if range >= 1.0 { mult[usize::from(cfa.color(x, y)).min(2)] / range } else { 0.0 };
|
||||
(black, gain)
|
||||
// A non-finite black (a BlackLevel DOUBLE beyond f32) gives 0, as
|
||||
// `Scale::value` does, instead of (raw - inf) * 0 = NaN.
|
||||
if !(range >= 1.0 && black.is_finite()) {
|
||||
return (0.0, 0.0);
|
||||
}
|
||||
(black, mult[usize::from(cfa.color(x, y)).min(2)] / range)
|
||||
})
|
||||
.collect();
|
||||
let src = &s.data[y * s.width + c.x..y * s.width + c.x + w];
|
||||
|
||||
@@ -270,3 +270,18 @@ fn lossless_jpeg_frame_larger_than_the_budget_is_refused() {
|
||||
let budget = Limits { max_alloc: 256 << 10, ..Limits::default() };
|
||||
assert!(matches!(decode(&b, &budget), Err(RawError::LimitExceeded(_))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_non_finite_black_level_develops_without_panicking() {
|
||||
// A BlackLevel DOUBLE beyond f32 reads as inf: the planes must not turn into NaN and trip
|
||||
// the AHD demosaic.
|
||||
let (w, h) = (64, 48);
|
||||
let mut sensor = decode(&DngSpec::cfa(w, h, vec![1000; w * h]).build(), &Limits::default()).unwrap();
|
||||
for black in [f32::INFINITY, f32::NEG_INFINITY, f32::NAN] {
|
||||
sensor.black = BlackLevels::uniform(black);
|
||||
for demosaic in [Demosaic::Ahd, Demosaic::Bilinear, Demosaic::Mhc] {
|
||||
let d = develop_sensor(&sensor, &DevelopOptions { demosaic, ..DevelopOptions::default() });
|
||||
assert!(d.is_ok(), "{black} {demosaic:?}: {:?}", d.err());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user