mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-31 15:48:33 +00:00
Merge pull request #2653 from storytold/fix/2618-adjustment-blend-modes
Adjustment layers blend in their blend mode (#2618)
This commit is contained in:
4 files changed
+54
-4
No files matched your search
@@ -510,6 +510,12 @@ fn layer_styles_collapsed_precomps_and_cpu_fallbacks() {
|
||||
adj.switches.adjustment = true;
|
||||
s.effect(&mut adj, "ec.channel.invert", &[]);
|
||||
s.push(adj);
|
||||
// An adjustment layer in another blend mode blends the adjusted comp in that mode (#2618).
|
||||
let mut adj = s.solid([1.0, 1.0, 1.0], 70, 40);
|
||||
adj.switches.adjustment = true;
|
||||
adj.blend_mode = BlendMode::Screen;
|
||||
set(&mut adj, "transform/opacity", Value::Scalar(60.0));
|
||||
s.push(adj);
|
||||
check(&format!("collapse + adjustment {depth:?}"), compare_at(&s, opts(), Tick::ZERO), 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -352,8 +352,8 @@ fn draw_precomp(e: &mut Enc, r: &Renderer, ctx: &EvalCtx, layer: &Layer, canvas:
|
||||
|
||||
/// Adjustment layer (`Renderer::draw_adjustment`): the effect stack runs on the comp below,
|
||||
/// resident on the GPU (non-GPU effects read back, run on the CPU and upload), and the result
|
||||
/// replaces the canvas inside the layer's footprint × track matte × opacity. `None` = draw it
|
||||
/// on the CPU.
|
||||
/// replaces the canvas inside the layer's footprint × track matte × opacity, or blends onto it in
|
||||
/// the layer's blend mode when that isn't Normal. `None` = draw it on the CPU.
|
||||
fn draw_adjustment(e: &mut Enc, r: &Renderer, ctx: &EvalCtx, layer: &Layer, canvas: &mut GpuImage) -> Option<()> {
|
||||
let opacity = r.layer_opacity(ctx, layer);
|
||||
let Some(foot) = r.adjustment_footprint(ctx, layer) else { return Some(()) };
|
||||
@@ -379,7 +379,13 @@ fn draw_adjustment(e: &mut Enc, r: &Renderer, ctx: &EvalCtx, layer: &Layer, canv
|
||||
if let Some(c) = pipe.to_blend() {
|
||||
adjusted = ops::convert(e, &adjusted, &c);
|
||||
}
|
||||
*canvas = ops::adjust_mix(e, canvas, &adjusted, &matte, opacity);
|
||||
*canvas = if layer.blend_mode == BlendMode::Normal {
|
||||
ops::adjust_mix(e, canvas, &adjusted, &matte, opacity)
|
||||
} else {
|
||||
// The adjusted comp is a layer in that mode over the comp below (#2618).
|
||||
let iso = ops::matte(e, &adjusted, &matte, effectcraft_project::MatteKind::Alpha);
|
||||
ops::blend_full(e, canvas, &iso, layer.blend_mode, opacity, layer.id.0 as u32, None)
|
||||
};
|
||||
Some(())
|
||||
}
|
||||
|
||||
|
||||
@@ -522,7 +522,7 @@ impl<'a> Renderer<'a> {
|
||||
}
|
||||
|
||||
/// Adjustment layer: effects applied to the comp below, limited to the layer's footprint
|
||||
/// and its track matte.
|
||||
/// and its track matte, and blended back in the layer's blend mode.
|
||||
fn draw_adjustment(&self, ctx: &EvalCtx, layer: &Layer, canvas: &mut Image, opacity: f32) {
|
||||
let Some(mut foot) = self.source(ctx, layer) else { return };
|
||||
let _ = masks::apply(ctx, layer, &mut foot, self.mask_blur(ctx, layer));
|
||||
@@ -548,6 +548,18 @@ impl<'a> Renderer<'a> {
|
||||
if adjusted.img.width != canvas.width || adjusted.img.height != canvas.height {
|
||||
return;
|
||||
}
|
||||
if layer.blend_mode != BlendMode::Normal {
|
||||
// The adjusted comp is a layer in that mode over the comp below, as in After Effects:
|
||||
// an adjustment layer with no effects in Screen screens the comp with itself (#2618).
|
||||
let mut iso = adjusted.img;
|
||||
iso.data.par_iter_mut().zip(matte.data.par_iter()).for_each(|(p, m)| {
|
||||
for c in p.iter_mut() {
|
||||
*c *= m[3];
|
||||
}
|
||||
});
|
||||
canvas.blend_from(&iso, layer.blend_mode, opacity, layer.id.0 as u32);
|
||||
return;
|
||||
}
|
||||
canvas.data.par_iter_mut().zip(adjusted.img.data.par_iter()).zip(matte.data.par_iter()).for_each(|((c, a), m)| {
|
||||
let k = m[3] * opacity;
|
||||
for i in 0..4 {
|
||||
|
||||
@@ -206,6 +206,32 @@ fn track_matte_limits_an_adjustment_layer() {
|
||||
}
|
||||
}
|
||||
|
||||
/// An adjustment layer's blend mode blends the adjusted comp onto the comp below, inside its
|
||||
/// footprint: with no effects, Screen screens a 0.25 grey with itself, Multiply darkens it,
|
||||
/// Normal leaves it, and Opacity mixes toward the result (#2618).
|
||||
#[test]
|
||||
fn adjustment_layer_blends_in_its_mode() {
|
||||
for (mode, opacity, want) in
|
||||
[(BlendMode::Normal, 100.0, 0.25), (BlendMode::Screen, 100.0, 0.4375), (BlendMode::Multiply, 100.0, 0.0625), (BlendMode::Screen, 50.0, 0.34375)]
|
||||
{
|
||||
let (mut p, cid, comp) = setup();
|
||||
let mut adj = solid(&mut p, &comp, [1.0, 1.0, 1.0], 100, 100);
|
||||
adj.props.prop_mut("transform/position").unwrap().value = Value::Vec3([50.0, 50.0, 0.0]);
|
||||
adj.props.prop_mut("transform/opacity").unwrap().value = Value::Scalar(opacity);
|
||||
adj.switches.adjustment = true;
|
||||
adj.blend_mode = mode;
|
||||
let bg = solid(&mut p, &comp, [0.25, 0.25, 0.25], 200, 100);
|
||||
let c = p.comp_mut(cid).unwrap();
|
||||
c.layers.push(adj);
|
||||
c.layers.push(bg);
|
||||
let img = render_frame(&p, cid, Tick::ZERO, 1.0);
|
||||
let (inside, outside) = (img.get(50, 50), img.get(150, 50));
|
||||
assert!((inside[0] - want).abs() < 1e-4, "{mode:?} {opacity}: {inside:?}");
|
||||
assert!((outside[0] - 0.25).abs() < 1e-4, "{mode:?} {opacity}: outside the footprint {outside:?}");
|
||||
assert_eq!(inside[3], 1.0, "{mode:?} keeps the comp opaque");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shape_layer_draws_fill_and_stroke() {
|
||||
let (mut p, cid, comp) = setup();
|
||||
|
||||
Reference in new issue
Block a user