Never crash: Fix defringe radius overflow for amounts past the sliders

Treat the amount as the slider's end and keep the radius within the picture.
This commit is contained in:
Michael Anderson committed 2026-10-13 10:46:23 -05:00
1 parent b3d392b499
commit 307843b2ad
2 files changed
+38 -3

No files matched your search

+34 -3
View File
@@ -578,9 +578,13 @@ pub fn defringe(img: &mut Rgb32f, s: &DevelopSettings, scale: f64) {
return;
}
let (w, h) = (img.width, img.height);
let max_amt = o.defringe_purple_amount.max(o.defringe_green_amount);
// fringe width in source px grows with the amount (1..7 px), in output px at least one
let radius = ((1.0 + max_amt * 0.3) * scale).round().max(1.0) as usize;
// as far as the sliders go (0–20): an amount from a damaged catalog or sidecar made the
// radius overflow
let max_amt = o.defringe_purple_amount.max(o.defringe_green_amount).min(20.0);
// fringe width in source px grows with the amount (1..7 px), in output px at least one (and
// never wider than the picture)
let radius = ((1.0 + max_amt * 0.3) * scale).round();
let radius = if radius.is_finite() { radius.clamp(1.0, w.max(h).max(1) as f64) as usize } else { 1 };
// perceptual lightness and its local range (dilated contrast)
let lab: Vec<[f32; 3]> = img.data.iter().map(|p| oklab_from_2020([p[0].max(0.0), p[1].max(0.0), p[2].max(0.0)])).collect();
let l = Plane { width: w, height: h, data: lab.iter().map(|p| p[0]).collect() };
@@ -1095,4 +1099,31 @@ mod tests {
assert!(chroma(img.get(20, 10)) < chroma(purple) * 0.3);
assert!((chroma(flat2.get(20, 10)) - chroma(purple)).abs() < 1e-4);
}
/// Found by fuzzing catalog snapshots: a defringe amount far past the sliders' 20 (from a
/// damaged catalog or sidecar) made the filter's radius overflow (`x + r`), which panics.
/// It acts as the sliders' end.
#[test]
fn defringe_amounts_past_the_sliders_act_as_their_end() {
let purple = [0.25f32, 0.05, 0.45];
let scene = Rgb32f::from_fn(40, 20, |x, _| {
if x < 20 {
[0.9; 3]
} else if x == 20 {
purple
} else {
[0.02; 3]
}
});
let at = |amount: f64, scale: f64| {
let mut s = DevelopSettings::default();
s.optics.defringe_purple_amount = amount;
s.optics.defringe_green_amount = amount / 2.0;
let mut img = scene.clone();
defringe(&mut img, &s, scale);
img
};
assert_eq!(at(1e300, 1.0).data, at(20.0, 1.0).data);
assert_eq!(at(f64::MAX, 1e300).data.len(), 800, "a huge scale too");
}
}
+4
View File
@@ -2,6 +2,10 @@
## 2026-10-13
### Reliability
- A photo whose Defringe amount was stored far past the sliders' 20 (in a damaged catalog or sidecar) no longer
crashes LightCraft each time it renders: the amount acts as 20.
### Slider drags on large raws
- Dragging a slider no longer re-renders the edited photo's filmstrip thumbnail on every frame; it follows once you
let go. On a 64 MP raw the first of those renders decoded the whole file again, which held up the loupe's