mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-31 21:48:53 -06:00
Cap red eye corrections at 64 per photo (#2556)
Every output pixel goes through every eye, and nothing limited the list: a sidecar, preset or agent with 100,000 eyes made one preview take hours. The render resolves the first MAX_RED_EYES and redeye.add refuses more, as Point Color does with MAX_POINT_COLORS.
This commit is contained in:
@@ -134,6 +134,7 @@ pub struct DevelopSettings {
|
||||
pub orientation: Orientation,
|
||||
pub masks: Vec<Mask>,
|
||||
pub spots: Vec<Spot>,
|
||||
/// Red eye corrections; the first [`MAX_RED_EYES`] render.
|
||||
pub red_eye: Vec<RedEye>,
|
||||
pub lens_blur: LensBlur,
|
||||
pub enhance: Enhance,
|
||||
@@ -1069,6 +1070,10 @@ impl Default for Spot {
|
||||
}
|
||||
}
|
||||
|
||||
/// Most red eye / pet eye corrections a photo renders. Every output pixel goes through every eye, so render cost is
|
||||
/// pixels x eyes; a sidecar, preset or agent with thousands could stall a render for hours (issue #2556).
|
||||
pub const MAX_RED_EYES: usize = 64;
|
||||
|
||||
/// A red eye / pet eye correction: the user's ellipse (centre normalized, radii as fractions of
|
||||
/// the long edge); the pupil inside it is found automatically.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! Masking and Remove (spot) commands on the active photo.
|
||||
|
||||
use lightcraft_develop::{BrushStroke, LocalAdjustments, Mask, MaskComponent, MaskOp, MaskShape, RedEye, Spot, SpotMode};
|
||||
use lightcraft_develop::{BrushStroke, LocalAdjustments, MAX_RED_EYES, Mask, MaskComponent, MaskOp, MaskShape, RedEye, Spot, SpotMode};
|
||||
use lightcraft_geom::Point;
|
||||
use serde_json::{Value, json};
|
||||
|
||||
@@ -1025,6 +1025,9 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
catchlight: None,
|
||||
};
|
||||
let id = s.active().ok_or_else(|| bad(c, "no active photo"))?;
|
||||
if s.develop_of(id).is_some_and(|d| d.red_eye.len() >= MAX_RED_EYES) {
|
||||
return Err(bad(c, format!("at most {MAX_RED_EYES} red eye corrections")));
|
||||
}
|
||||
let mut dd = (*s.develop_of(id).unwrap_or_default()).clone();
|
||||
dd.red_eye.push(eye);
|
||||
let n = dd.red_eye.len();
|
||||
|
||||
@@ -75,6 +75,12 @@ fn red_eye_commands_and_controls() {
|
||||
assert_eq!(active_dev(&s).red_eye.len(), 1);
|
||||
assert!(s.execute("redeye.delete", &json!({"index": 5})).is_err());
|
||||
assert!(s.execute("redeye.add", &json!({"rx": 0.03})).is_err());
|
||||
// at most MAX_RED_EYES: every pixel goes through every eye (issue #2556)
|
||||
while active_dev(&s).red_eye.len() < lightcraft_develop::MAX_RED_EYES {
|
||||
s.execute("redeye.add", &json!({"center": [0.5, 0.5], "rx": 0.01, "ry": 0.01})).unwrap();
|
||||
}
|
||||
assert!(s.execute("redeye.add", &json!({"center": [0.5, 0.5], "rx": 0.01, "ry": 0.01})).is_err());
|
||||
assert_eq!(active_dev(&s).red_eye.len(), lightcraft_develop::MAX_RED_EYES);
|
||||
let d = active_dev(&s);
|
||||
assert_eq!(lightcraft_develop::DevelopSettings::from_json(&d.to_json()).unwrap(), d);
|
||||
}
|
||||
|
||||
@@ -142,6 +142,7 @@ pub fn resolve(src: &Rgb32f, eyes: &[RedEye], orientation: Orientation, frame: &
|
||||
}
|
||||
let to_out = frame.norm_to_out(w, h);
|
||||
eyes.iter()
|
||||
.take(lightcraft_develop::MAX_RED_EYES)
|
||||
.map(|e| {
|
||||
let p = detect(src, orientation, frame.ow, frame.oh, e);
|
||||
let r = (p.radius * (0.5 + e.pupil_size.clamp(0.0, 100.0) / 100.0)).min(e.rx.max(e.ry) * 1.1);
|
||||
@@ -246,6 +247,16 @@ mod tests {
|
||||
assert!(!detect(&plain, Orientation::Normal, 200.0, 100.0, &eye).found);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn at_most_max_red_eyes_render() {
|
||||
// a sidecar, preset or agent can hold any number; each one costs a pass over every pixel (issue #2556)
|
||||
let src = eye_image(40, 20, 18.0, 10.0, 2.0, [0.5, 0.04, 0.04]);
|
||||
let s = lightcraft_develop::DevelopSettings { red_eye: vec![RedEye::default(); 10_000], ..Default::default() };
|
||||
let frame = Frame::new(40, 20, &s, true);
|
||||
let eyes = resolve(&src, &s.red_eye, Orientation::Normal, &frame, 40, 20, 40.0);
|
||||
assert_eq!(eyes.len(), lightcraft_develop::MAX_RED_EYES);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn renders_fix_the_red_pupil_at_any_size() {
|
||||
use crate::{RenderRequest, SourceInfo, render};
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# What's new in LightCraft
|
||||
|
||||
## 2026-10-30
|
||||
|
||||
### At most 64 red eye corrections per photo
|
||||
- Every pixel of a render goes through every red eye correction, so a sidecar, preset or agent with thousands of
|
||||
them could stall a preview or export for hours. A photo now renders its first 64, and Red Eye refuses a 65th.
|
||||
|
||||
## 2026-10-24
|
||||
|
||||
### Relative-date filters and smart albums follow the clock
|
||||
|
||||
Reference in New Issue
Block a user