mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-14 19:27:01 -04:00
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:
1 parent
b3d392b499
commit
307843b2ad
2 files changed
+38
-3
No files matched your search
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user