mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-15 03:37:04 -04:00
Merge pull request #608 from vbasky/fix/527-first-mask-mode
This commit is contained in:
2 files changed
+46
No files matched your search
@@ -145,9 +145,16 @@ pub fn apply(ctx: &EvalCtx, layer: &Layer, buf: &mut Buf, blur: MaskBlur) -> boo
|
||||
let n = (buf.img.width * buf.img.height) as usize;
|
||||
let first_sub = matches!(list[0].kind, GroupKind::Mask { mode: MaskMode::Subtract, .. });
|
||||
let mut acc = vec![if first_sub { 1.0f32 } else { 0.0 }; n];
|
||||
// The first mask has nothing to combine with. Intersect and Darken would multiply or
|
||||
// minimise against that empty coverage and hide the layer; they cover like Add instead.
|
||||
// Lighten and Difference already do. Filling the accumulator first would make Add show
|
||||
// the whole layer.
|
||||
let mut first = true;
|
||||
for g in list {
|
||||
let GroupKind::Mask { mode, inverted, .. } = g.kind else { continue };
|
||||
let Some(mut cov) = coverage(ctx, layer, g, buf, blur) else { continue };
|
||||
let mode = if first && matches!(mode, MaskMode::Intersect | MaskMode::Darken) { MaskMode::Add } else { mode };
|
||||
first = false;
|
||||
let op = (ctx.f(layer, g, "opacity", 100.0) / 100.0) as f32;
|
||||
cov.data.par_iter_mut().for_each(|c| {
|
||||
if inverted {
|
||||
|
||||
@@ -80,6 +80,45 @@ fn mask_cuts_layer() {
|
||||
assert!(img.get(150, 50)[3] < 0.01);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn first_intersect_and_darken_keep_their_rectangle() {
|
||||
let render = |modes: &[MaskMode], centers: &[[f64; 2]]| {
|
||||
let (mut p, cid, comp) = setup();
|
||||
let mut l = solid(&mut p, &comp, [1.0, 1.0, 1.0], 200, 100);
|
||||
for (i, (mode, center)) in modes.iter().zip(centers).enumerate() {
|
||||
let mut next = p.next_id;
|
||||
let m = build::mask(&mut Ids(&mut next), &format!("Mask {}", i + 1), ShapePath::rect(*center, 40.0, 40.0), *mode, [255, 255, 0]);
|
||||
p.next_id = next;
|
||||
l.props.sub_mut("masks").unwrap().children.push(m.into());
|
||||
}
|
||||
p.comp_mut(cid).unwrap().layers.push(l);
|
||||
render_frame(&p, cid, Tick::ZERO, 1.0)
|
||||
};
|
||||
// A first Intersect or Darken has nothing to combine with, so it covers like Add.
|
||||
// Lighten and Difference already do; Subtract still starts from the whole layer.
|
||||
for mode in [MaskMode::Intersect, MaskMode::Darken, MaskMode::Add, MaskMode::Lighten, MaskMode::Difference] {
|
||||
let img = render(&[mode], &[[50.0, 50.0]]);
|
||||
assert!(img.get(50, 50)[3] > 0.99, "{mode:?} keeps the rectangle");
|
||||
assert!(img.get(150, 50)[3] < 0.01, "{mode:?} stays outside it");
|
||||
}
|
||||
let hole = render(&[MaskMode::Subtract], &[[50.0, 50.0]]);
|
||||
assert!(hole.get(50, 50)[3] < 0.01, "subtract still cuts its rectangle");
|
||||
assert!(hole.get(150, 50)[3] > 0.99, "subtract leaves the rest of the layer");
|
||||
|
||||
// Add, then Intersect with a shifted rectangle: only the overlap remains.
|
||||
let overlap = render(&[MaskMode::Add, MaskMode::Intersect], &[[50.0, 50.0], [70.0, 50.0]]);
|
||||
assert!(overlap.get(50, 50)[3] > 0.99, "overlap stays");
|
||||
assert!(overlap.get(35, 50)[3] < 0.01, "the first rectangle's exclusive side is cut");
|
||||
assert!(overlap.get(85, 50)[3] < 0.01, "the second rectangle's exclusive side is cut");
|
||||
let darkened = render(&[MaskMode::Add, MaskMode::Darken], &[[50.0, 50.0], [70.0, 50.0]]);
|
||||
assert!(darkened.get(50, 50)[3] > 0.99 && darkened.get(35, 50)[3] < 0.01 && darkened.get(85, 50)[3] < 0.01);
|
||||
|
||||
// A later disjoint Add still contributes after a first Intersect.
|
||||
let both = render(&[MaskMode::Intersect, MaskMode::Add], &[[50.0, 50.0], [150.0, 50.0]]);
|
||||
assert!(both.get(50, 50)[3] > 0.99 && both.get(150, 50)[3] > 0.99);
|
||||
assert!(both.get(100, 50)[3] < 0.01);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn track_matte_alpha() {
|
||||
let (mut p, cid, comp) = setup();
|
||||
|
||||
Reference in new issue
Block a user