mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-31 05:27:08 -06:00
78 lines
3.9 KiB
Rust
78 lines
3.9 KiB
Rust
//! Mask motion blur follows the renderer's motion blur gate: the render options (Render
|
||
//! Settings ▸ Motion Blur, previews), the comp's switch and the precomp layers above.
|
||
|
||
use effectcraft_color::Label;
|
||
use effectcraft_keyframe::{Keyframe, ShapePath, Value};
|
||
use effectcraft_project::build::{self, Ids};
|
||
use effectcraft_project::{Comp, GroupKind, ItemId, ItemKind, LayerSource, MaskMode, MaskMotionBlur, Node, Project, Solid};
|
||
use effectcraft_time::{FrameRate, Tick};
|
||
|
||
use crate::{NoFootage, RenderOpts, Renderer};
|
||
|
||
/// A white 100×100 solid whose rect mask jumps from x 10 (frame 0) to x 50 (frame 1), Mask
|
||
/// Motion Blur On, the layer's own switch off. Rendered half way, unblurred the mask covers x
|
||
/// 30..50; blurred over the shutter it reaches down to x ≈ 20.
|
||
fn project(comp_blur: bool) -> (Project, ItemId) {
|
||
let mut p = Project::default();
|
||
let mut comp = Comp::new(100, 100, FrameRate::FPS_30, Tick::from_seconds_f64(1.0));
|
||
comp.enable_motion_blur = comp_blur;
|
||
let sid = p.add_item("S", Label::Red, None, ItemKind::Solid(Solid { color: [1.0; 3], width: 100, height: 100, pixel_aspect: 1.0 }));
|
||
let mut l = build::layer(&mut p, &comp, "S", LayerSource::Solid { item: sid }, (100, 100), None);
|
||
let mut next = p.next_id;
|
||
let mut m = build::mask(&mut Ids(&mut next), "Mask 1", ShapePath::rect([20.0, 50.0], 20.0, 100.0), MaskMode::Add, [255, 0, 0]);
|
||
p.next_id = next;
|
||
if let GroupKind::Mask { motion_blur, .. } = &mut m.kind {
|
||
*motion_blur = MaskMotionBlur::On;
|
||
}
|
||
let path = m.get_mut("path").unwrap();
|
||
path.keys = vec![
|
||
Keyframe::new(Tick::ZERO, Value::Path(ShapePath::rect([20.0, 50.0], 20.0, 100.0))),
|
||
Keyframe::new(FrameRate::FPS_30.tick_of(1), Value::Path(ShapePath::rect([60.0, 50.0], 20.0, 100.0))),
|
||
];
|
||
l.props.sub_mut("masks").unwrap().children.push(Node::Group(m));
|
||
l.switches.motion_blur = false;
|
||
let cid = p.add_item("C", Label::Sandstone, None, ItemKind::Comp(comp.into()));
|
||
p.comp_mut(cid).unwrap().layers.push(l);
|
||
(p, cid)
|
||
}
|
||
|
||
fn alpha_at_25(p: &Project, cid: ItemId, motion_blur: bool) -> f32 {
|
||
let half = Tick::from_seconds_f64(0.5 / 30.0);
|
||
Renderer::new(p, &NoFootage, RenderOpts { motion_blur, ..Default::default() }).comp_frame(cid, half).get(25, 50)[3]
|
||
}
|
||
|
||
/// A mask that holds from frame 0 to 2, then moves: frames 1 and 2 have the same mask value, but at
|
||
/// frame 2 the shutter already sees it move. The layer cache must not serve frame 1's layer there.
|
||
#[test]
|
||
fn mask_motion_blur_keys_the_layer_cache_by_time() {
|
||
let (mut p, cid) = project(true);
|
||
let rect = |x: f64| Value::Path(ShapePath::rect([x, 50.0], 20.0, 100.0));
|
||
let l = &mut p.comp_mut(cid).unwrap().layers[0];
|
||
let Some(Node::Group(m)) = l.props.sub_mut("masks").unwrap().children.first_mut() else { panic!("mask") };
|
||
m.get_mut("path").unwrap().keys = vec![
|
||
Keyframe::new(Tick::ZERO, rect(20.0)),
|
||
Keyframe::new(FrameRate::FPS_30.tick_of(2), rect(20.0)),
|
||
Keyframe::new(FrameRate::FPS_30.tick_of(3), rect(60.0)),
|
||
];
|
||
let cache = crate::LayerCache::default();
|
||
let render = |t: Tick, cached: bool| {
|
||
let mut r = Renderer::new(&p, &NoFootage, RenderOpts { motion_blur: true, ..Default::default() });
|
||
r.cache = cached.then_some(&cache);
|
||
r.comp_frame(cid, t)
|
||
};
|
||
let (f1, f2) = (FrameRate::FPS_30.tick_of(1), FrameRate::FPS_30.tick_of(2));
|
||
assert_ne!(render(f1, false), render(f2, false), "the shutter at frame 2 sees the move");
|
||
render(f1, true);
|
||
assert_eq!(render(f2, true), render(f2, false));
|
||
}
|
||
|
||
#[test]
|
||
fn mask_motion_blur_follows_the_render_and_the_comp() {
|
||
let (p, cid) = project(true);
|
||
let a = alpha_at_25(&p, cid, true);
|
||
assert!(a > 0.05 && a < 0.95, "blurred: {a}");
|
||
assert!(alpha_at_25(&p, cid, false) < 0.01, "Render Settings ▸ Motion Blur off");
|
||
let (p, cid) = project(false);
|
||
assert!(alpha_at_25(&p, cid, true) < 0.01, "the comp's switch off");
|
||
}
|