mirror of
https://github.com/storytold/lightcraft.git
synced 2026-11-02 06:27:53 -06:00
Visualize: the all-masks overlay shows the union of the masks, not the last one
`Overlay::AllMasks` painted each mask's plane in turn, and every mask view rewrites the whole image from its alpha (White on Black writes `255·a`, Image on Black multiplies by `a`…), so each plane wiped the ones before it and only the last visible mask showed. The planes are now merged (per-pixel max) and painted once; the GPU path goes through the same `apply`. The test added with #919 could not pass: besides the above, its hidden-mask check expected the plain pixel under White on Black, where everything outside the masks is black by definition (as the single-mask test asserts). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NH3itd85jPuM6wNjgcGfEH
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
b3d392b499
commit
5e9a1b6ec7
@@ -199,13 +199,18 @@ pub fn apply(img: &mut Rgba8, o: Overlay, plan: &Plan<'_>, mask: Option<&Plane>,
|
||||
}
|
||||
}
|
||||
Overlay::AllMasks { view, color, opacity } => {
|
||||
if let Some(planes) = masks {
|
||||
for a in planes {
|
||||
if (a.width, a.height) == (img.width, img.height) {
|
||||
mask_view(img, a, view, color, opacity);
|
||||
}
|
||||
// one view of the union of the masks: painting the planes one after another
|
||||
// overwrites (White on Black, Image on Black…) or compounds (Color) the earlier ones
|
||||
let mut union: Option<Plane> = None;
|
||||
for a in masks.unwrap_or_default().iter().filter(|a| (a.width, a.height) == (img.width, img.height)) {
|
||||
match &mut union {
|
||||
None => union = Some(a.clone()),
|
||||
Some(u) => u.data.iter_mut().zip(&a.data).for_each(|(u, a)| *u = u.max(*a)),
|
||||
}
|
||||
}
|
||||
if let Some(u) = &union {
|
||||
mask_view(img, u, view, color, opacity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -425,7 +430,6 @@ mod tests {
|
||||
components: vec![MaskComponent { name: None, op: MaskOp::Add, invert: false, shape: right }],
|
||||
..Default::default()
|
||||
});
|
||||
let plain = render(&src, &info, &s, &RenderRequest::fit(80, 40)).image;
|
||||
let o = Overlay::AllMasks { view: MaskView::WhiteOnBlack, color: [255, 0, 0], opacity: 100 };
|
||||
let img = render(&src, &info, &s, &RenderRequest { overlay: o, ..RenderRequest::fit(80, 40) }).image;
|
||||
let (in_l, in_r, mid) = (20 * 80 + 12, 20 * 80 + 60, 20 * 80 + 40);
|
||||
@@ -435,7 +439,7 @@ mod tests {
|
||||
// a hidden mask is skipped
|
||||
s.masks[1].visible = false;
|
||||
let img2 = render(&src, &info, &s, &RenderRequest { overlay: o, ..RenderRequest::fit(80, 40) }).image;
|
||||
assert_eq!(img2.data[in_r], plain.data[in_r], "hidden right mask not painted");
|
||||
assert_eq!(img2.data[in_r], [0, 0, 0, 255], "hidden right mask not painted: black like everything outside");
|
||||
assert_eq!(img2.data[in_l], [255; 4], "visible left mask still painted");
|
||||
// parts round trip and a stable, distinct cache key
|
||||
let (k, v) = o.to_parts();
|
||||
|
||||
Reference in New Issue
Block a user