M4.10: a nested comp shows nothing before it starts or after it ends
A precomp layer trimmed out (or time-remapped) past its nested comp's end kept drawing the nested layers whose bars ran on. Like After Effects, the nested comp now shows nothing outside its own span (`Comp::covers`), flattened, through a proxy or with Collapse Transformations, on the CPU and the GPU (both go through `Renderer::source` / `collapse_into`). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
0fe6db00e8
commit
0b5cff1743
|
|
@ -374,6 +374,11 @@ impl Comp {
|
|||
essential: None,
|
||||
}
|
||||
}
|
||||
/// Whether the composition has a frame at its time `t`: nested, it shows nothing before it
|
||||
/// starts or after it ends (a precomp layer trimmed or remapped past it).
|
||||
pub fn covers(&self, t: Tick) -> bool {
|
||||
t >= Tick::ZERO && t < self.duration
|
||||
}
|
||||
/// A new, empty composition with this one's settings (frame rate, pixel aspect, background,
|
||||
/// motion blur, frame blending and the 3D renderer), as Pre-compose makes.
|
||||
pub fn nested_like(&self, width: u32, height: u32, duration: Tick) -> Comp {
|
||||
|
|
|
|||
|
|
@ -558,7 +558,11 @@ impl<'a> Renderer<'a> {
|
|||
/// 3D layers use this comp's camera and lights.
|
||||
pub fn collapse_into(&self, ctx: &EvalCtx<'a>, layer: &Layer, item: ItemId, opacity: f32) -> Option<(Renderer<'a>, EvalCtx<'a>)> {
|
||||
let nc = self.project.comp(item)?;
|
||||
let nctx = EvalCtx { comp_id: item, comp: nc, time: ctx.nested_time(layer), ..*ctx };
|
||||
let t = ctx.nested_time(layer);
|
||||
if !nc.covers(t) {
|
||||
return None;
|
||||
}
|
||||
let nctx = EvalCtx { comp_id: item, comp: nc, time: t, ..*ctx };
|
||||
let (l2c, _) = ctx.layer_to_comp(layer);
|
||||
let outer = Some(self.outer.map_or(l2c, |o| o * l2c));
|
||||
let world = ctx.world_matrix(layer);
|
||||
|
|
@ -925,10 +929,15 @@ impl<'a> Renderer<'a> {
|
|||
return None;
|
||||
}
|
||||
// A composition proxy (a rendered still or movie) stands in for the comp.
|
||||
// Nothing before the nested comp starts or after it ends.
|
||||
let lt = ctx.nested_time(layer);
|
||||
if !self.project.comp(*item).is_some_and(|nc| nc.covers(lt)) {
|
||||
return None;
|
||||
}
|
||||
if let Some(pf) = self.proxy_for(*item)
|
||||
&& let Some(nc) = self.project.comp(*item)
|
||||
{
|
||||
let img = self.footage.frame(*item, pf, ctx.nested_time(layer))?;
|
||||
let img = self.footage.frame(*item, pf, lt)?;
|
||||
return Some(self.footage_buf(&img, pf, nc.width, nc.height));
|
||||
}
|
||||
// Essential Properties overrides render the nested comp with this instance's
|
||||
|
|
@ -945,7 +954,6 @@ impl<'a> Renderer<'a> {
|
|||
Some(p) => Renderer { project: p, ..base },
|
||||
None => base,
|
||||
};
|
||||
let lt = ctx.nested_time(layer);
|
||||
// Frame blending between the nested comp's frames (time-stretched/remapped).
|
||||
let mode = frame_blend_mode(ctx, layer);
|
||||
if mode != FrameBlend::Off
|
||||
|
|
|
|||
|
|
@ -65,3 +65,26 @@ fn a_comp_preserving_its_resolution_renders_full_size_and_lands_in_place() {
|
|||
assert!(img.get(20, 25)[3] < 0.01 && img.get(5, 40)[3] < 0.01, "preserve {preserve}: outside");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_nested_comp_shows_nothing_past_its_end() {
|
||||
for collapse in [false, true] {
|
||||
let mut p = Project::default();
|
||||
let outer = Comp::new(100, 100, FrameRate::FPS_30, s(4.0));
|
||||
// A 1 s comp with a solid covering it for 4 s (longer than the comp itself).
|
||||
let mut inner = Comp::new(100, 100, FrameRate::FPS_30, s(1.0));
|
||||
let sid = p.add_item("S", Label::Red, None, ItemKind::Solid(Solid { color: [1.0, 1.0, 1.0], width: 100, height: 100, pixel_aspect: 1.0 }));
|
||||
let mut l = build::layer(&mut p, &inner, "S", LayerSource::Solid { item: sid }, (100, 100), None);
|
||||
l.out_point = s(4.0);
|
||||
inner.layers.push(l);
|
||||
let iid = p.add_item("Inner", Label::Sandstone, None, ItemKind::Comp(inner.into()));
|
||||
let cid = p.add_item("Outer", Label::Sandstone, None, ItemKind::Comp(outer.clone().into()));
|
||||
// The precomp layer is trimmed out to 3 s, past the nested comp's end.
|
||||
let mut pre = build::layer(&mut p, &outer, "Inner", LayerSource::Comp { item: iid }, (100, 100), None);
|
||||
pre.out_point = s(3.0);
|
||||
pre.switches.collapse = collapse;
|
||||
p.comp_mut(cid).unwrap().layers.push(pre);
|
||||
assert!(render_frame(&p, cid, s(0.5), 1.0).get(50, 50)[3] > 0.99, "collapse {collapse}: inside its span");
|
||||
assert!(render_frame(&p, cid, s(2.0), 1.0).get(50, 50)[3] < 0.01, "collapse {collapse}: past its end");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue