104 lines
4.7 KiB
Rust
104 lines
4.7 KiB
Rust
//! Region of interest: a ROI render equals the matching crop of the full render.
|
|
|
|
use effectcraft_color::{BlendMode, Label};
|
|
use effectcraft_keyframe::Value;
|
|
use effectcraft_project::build;
|
|
use effectcraft_project::{Comp, ItemKind, LayerSource, Project, Solid};
|
|
use effectcraft_time::{FrameRate, Tick};
|
|
|
|
use crate::{Image, NoFootage, RenderOpts, Renderer};
|
|
|
|
fn project(three_d: bool) -> (Project, effectcraft_project::ItemId) {
|
|
let mut p = Project::default();
|
|
let comp = Comp::new(160, 120, FrameRate::FPS_30, Tick::from_seconds_f64(1.0));
|
|
let cid = p.add_item("Comp", Label::Sandstone, None, ItemKind::Comp(comp.clone().into()));
|
|
let mut layers = vec![];
|
|
for (i, (c, w, h, pos, rot)) in
|
|
[([1.0, 0.2, 0.1], 90, 50, [60.0, 50.0], 17.0), ([0.1, 0.4, 1.0], 70, 70, [100.0, 75.0], -30.0), ([0.9, 0.9, 0.2], 40, 30, [30.0, 95.0], 0.0)]
|
|
.into_iter()
|
|
.enumerate()
|
|
{
|
|
let sid = p.add_item("Solid", Label::Red, None, ItemKind::Solid(Solid { color: c, width: w, height: h, pixel_aspect: 1.0 }));
|
|
let mut l = build::layer(&mut p, &comp, "Solid", LayerSource::Solid { item: sid }, (w, h), None);
|
|
l.props.prop_mut("transform/position").unwrap().value = Value::Vec3([pos[0], pos[1], 0.0]);
|
|
l.props.prop_mut("transform/rotation").unwrap().value = Value::Scalar(rot);
|
|
l.props.prop_mut("transform/opacity").unwrap().value = Value::Scalar(80.0);
|
|
if i == 1 {
|
|
l.blend_mode = BlendMode::Screen;
|
|
l.switches.three_d = three_d;
|
|
}
|
|
layers.push(l);
|
|
}
|
|
p.comp_mut(cid).unwrap().layers = layers;
|
|
(p, cid)
|
|
}
|
|
|
|
fn crop(img: &Image, x: u32, y: u32, w: u32, h: u32) -> Image {
|
|
let mut out = Image::new(w, h);
|
|
for yy in 0..h {
|
|
for xx in 0..w {
|
|
out.data[(yy * w + xx) as usize] = img.data[((yy + y) * img.width + xx + x) as usize];
|
|
}
|
|
}
|
|
out
|
|
}
|
|
|
|
fn assert_same(a: &Image, b: &Image) {
|
|
assert_eq!((a.width, a.height), (b.width, b.height));
|
|
for (i, (p, q)) in a.data.iter().zip(&b.data).enumerate() {
|
|
for c in 0..4 {
|
|
assert!((p[c] - q[c]).abs() < 1e-4, "pixel {i} channel {c}: {} vs {}", p[c], q[c]);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn roi_render_equals_cropped_full_render() {
|
|
for three_d in [false, true] {
|
|
let (p, cid) = project(three_d);
|
|
let full = Renderer::new(&p, &NoFootage, RenderOpts::default()).comp_frame(cid, Tick::ZERO);
|
|
let roi = Renderer::new(&p, &NoFootage, RenderOpts { roi: Some([30.0, 20.0, 80.0, 60.0]), ..Default::default() }).comp_frame(cid, Tick::ZERO);
|
|
assert_eq!((roi.width, roi.height), (80, 60));
|
|
assert_same(&roi, &crop(&full, 30, 20, 80, 60));
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn roi_at_half_resolution() {
|
|
let (p, cid) = project(false);
|
|
let full = Renderer::new(&p, &NoFootage, RenderOpts { scale: 0.5, ..Default::default() }).comp_frame(cid, Tick::ZERO);
|
|
let roi = Renderer::new(&p, &NoFootage, RenderOpts { scale: 0.5, roi: Some([40.0, 20.0, 60.0, 40.0]), ..Default::default() }).comp_frame(cid, Tick::ZERO);
|
|
assert_eq!((roi.width, roi.height), (30, 20));
|
|
assert_same(&roi, &crop(&full, 20, 10, 30, 20));
|
|
}
|
|
|
|
/// Extended Viewer: a region reaching past the comp frame shows the 3D layer's pixels on the
|
|
/// pasteboard, and its inner part equals the normal frame.
|
|
#[test]
|
|
fn extended_region_renders_3d_layers_beyond_the_frame() {
|
|
let (mut p, cid) = project(true);
|
|
// Push the 3D (blue) layer half out of the frame on the right.
|
|
let comp = p.comp_mut(cid).unwrap();
|
|
comp.layers[1].props.prop_mut("transform/position").unwrap().value = Value::Vec3([150.0, 60.0, 0.0]);
|
|
comp.layers[1].props.prop_mut("transform/rotation").unwrap().value = Value::Scalar(0.0);
|
|
let full = Renderer::new(&p, &NoFootage, RenderOpts::default()).comp_frame(cid, Tick::ZERO);
|
|
let region = crate::extended_region(160, 120, [-100.0, -100.0, 260.0, 220.0], 0.5).unwrap();
|
|
assert_eq!(region, [-80.0, -64.0, 320.0, 248.0]);
|
|
let ext = Renderer::new(&p, &NoFootage, RenderOpts { roi: Some(region), ..Default::default() }).comp_frame(cid, Tick::ZERO);
|
|
assert_eq!((ext.width, ext.height), (320, 248));
|
|
// Comp x 175 (past the 160 px frame) at y 60: the layer covers x 115..185.
|
|
let out = ext.get(175 + 80, 60 + 64);
|
|
assert!(out[3] > 0.5 && out[2] > 0.5, "pasteboard pixel {out:?}");
|
|
// Nothing is drawn far outside every layer.
|
|
assert_eq!(ext.get(5, 5)[3], 0.0);
|
|
// The frame part is the normal frame.
|
|
assert_same(&crop(&ext, 80, 64, 160, 120), &full);
|
|
}
|
|
|
|
#[test]
|
|
fn extended_region_is_none_inside_the_frame() {
|
|
assert_eq!(crate::extended_region(160, 120, [10.0, 10.0, 150.0, 110.0], 1.0), None);
|
|
// Clamped to the margin.
|
|
assert_eq!(crate::extended_region(100, 100, [-1000.0, 0.0, 50.0, 100.0], 1.0), Some([-112.0, 0.0, 212.0, 100.0]));
|
|
}
|