227 lines
12 KiB
Rust
227 lines
12 KiB
Rust
//! GPU stylize and distort family effects vs the CPU effects (the oracle): direct on a buffer at full and half
|
|
//! resolution, as adjustment, and composited at 8 and 32 bpc.
|
|
|
|
use effectcraft_keyframe::{Keyframe, Value};
|
|
use effectcraft_project::BitDepth;
|
|
use effectcraft_render::RenderOpts;
|
|
use effectcraft_time::Tick;
|
|
|
|
use crate::tests::{Scene, c, check, compare_at, effect_case, n, opts, set};
|
|
|
|
fn e(v: u32) -> Value {
|
|
Value::Enum(v)
|
|
}
|
|
|
|
fn on() -> Value {
|
|
Value::Bool(true)
|
|
}
|
|
|
|
fn pt(x: f64, y: f64) -> Value {
|
|
Value::Vec2([x, y])
|
|
}
|
|
|
|
#[test]
|
|
fn posterize_thresholds_strobe_vignette() {
|
|
effect_case("ec.stylize.posterize", &[("level", n(4.0))]);
|
|
effect_case("ec.stylize.posterize", &[("level", n(9.0))]);
|
|
effect_case("ec.stylize.threshold", &[("level", n(100.0))]);
|
|
effect_case("ec.stylize.threshold", &[("level", n(181.0))]);
|
|
effect_case("ec.stylize.ccthreshold", &[]);
|
|
effect_case("ec.stylize.ccthreshold", &[("channel", e(1)), ("invert", on()), ("threshold", n(90.0))]);
|
|
effect_case("ec.stylize.ccthreshold", &[("channel", e(2)), ("blend", n(30.0))]);
|
|
effect_case("ec.stylize.ccthreshold", &[("channel", e(3)), ("threshold", n(60.0))]);
|
|
effect_case(
|
|
"ec.stylize.ccthresholdrgb",
|
|
&[("redThreshold", n(70.0)), ("greenThreshold", n(150.0)), ("blueThreshold", n(110.0)), ("invertGreen", on()), ("blend", n(25.0))],
|
|
);
|
|
effect_case(
|
|
"ec.stylize.strobe",
|
|
&[("strobeDuration", n(0.5)), ("strobeOperator", e(1)), ("strobeColor", c(0.2, 0.5, 0.9)), ("blendWithOriginal", n(30.0))],
|
|
);
|
|
effect_case("ec.stylize.strobe", &[("strobeDuration", n(0.5)), ("strobeOperator", e(4)), ("strobeColor", c(0.6, 0.1, 0.3))]);
|
|
effect_case("ec.stylize.strobe", &[("strobeDuration", n(0.5)), ("strobe", e(1)), ("blendWithOriginal", n(40.0))]);
|
|
effect_case("ec.stylize.strobe", &[("strobeDuration", n(0.1)), ("strobePeriod", n(0.2)), ("randomStrobeProbability", n(50.0))]);
|
|
effect_case("ec.stylize.ccvignette", &[("amount", n(80.0))]);
|
|
effect_case("ec.stylize.ccvignette", &[("amount", n(-60.0)), ("pinHighlights", n(50.0)), ("angleOfView", n(120.0)), ("center", pt(20.0, 15.0))]);
|
|
}
|
|
|
|
#[test]
|
|
fn scatter_brush_strokes() {
|
|
effect_case("ec.stylize.scatter", &[("amount", n(5.0))]);
|
|
effect_case("ec.stylize.scatter", &[("amount", n(3.0)), ("grain", e(1))]);
|
|
effect_case("ec.stylize.scatter", &[("amount", n(7.0)), ("grain", e(2)), ("randomizeEveryFrame", on())]);
|
|
effect_case("ec.stylize.brushstrokes", &[]);
|
|
effect_case(
|
|
"ec.stylize.brushstrokes",
|
|
&[("strokeAngle", n(40.0)), ("brushSize", n(3.0)), ("strokeLength", n(10.0)), ("paintSurface", e(2)), ("blendWithOriginal", n(20.0))],
|
|
);
|
|
effect_case("ec.stylize.brushstrokes", &[("paintSurface", e(1)), ("strokeDensity", n(0.6)), ("strokeRandomness", n(2.0)), ("brushSize", n(1.3))]);
|
|
effect_case("ec.stylize.brushstrokes", &[("paintSurface", e(3)), ("strokeLength", n(25.0))]);
|
|
}
|
|
|
|
#[test]
|
|
fn roughen_edges() {
|
|
effect_case("ec.stylize.roughenedges", &[]);
|
|
effect_case("ec.stylize.roughenedges", &[("edgeType", e(1)), ("border", n(4.0)), ("edgeColor", c(0.1, 0.6, 0.9))]);
|
|
effect_case(
|
|
"ec.stylize.roughenedges",
|
|
&[
|
|
("edgeType", e(5)),
|
|
("evolutionOptions/cycleEvolution", on()),
|
|
("evolutionOptions/cycle", n(2.0)),
|
|
("evolution", n(500.0)),
|
|
("stretch", n(2.0)),
|
|
("complexity", n(3.0)),
|
|
],
|
|
);
|
|
effect_case("ec.stylize.roughenedges", &[("edgeType", e(7)), ("border", n(0.0)), ("scale", n(60.0)), ("evolutionOptions/randomSeed", n(9.0))]);
|
|
}
|
|
|
|
#[test]
|
|
fn tiles_kaleida_repetile() {
|
|
effect_case("ec.stylize.motiontile", &[("tileWidth", n(50.0)), ("tileHeight", n(40.0))]);
|
|
effect_case(
|
|
"ec.stylize.motiontile",
|
|
&[("outputWidth", n(150.0)), ("outputHeight", n(130.0)), ("mirrorEdges", on()), ("phase", n(70.0)), ("tileWidth", n(45.0))],
|
|
);
|
|
effect_case(
|
|
"ec.stylize.motiontile",
|
|
&[("horizontalPhaseShift", on()), ("phase", n(33.0)), ("tileCenter", pt(30.0, 17.0)), ("tileHeight", n(55.0)), ("outputWidth", n(80.0))],
|
|
);
|
|
effect_case("ec.stylize.cckaleida", &[]);
|
|
effect_case("ec.stylize.cckaleida", &[("mirroring", e(2)), ("rotation", n(20.0)), ("size", n(150.0))]);
|
|
effect_case("ec.stylize.ccrepetile", &[("expandRight", n(10.0)), ("expandUp", n(4.0))]);
|
|
effect_case("ec.stylize.ccrepetile", &[("expandLeft", n(12.0)), ("expandDown", n(9.0)), ("tiling", e(1))]);
|
|
effect_case("ec.stylize.ccrepetile", &[("expandLeft", n(30.0)), ("expandRight", n(30.0)), ("expandUp", n(20.0)), ("tiling", e(4))]);
|
|
effect_case("ec.distort.cctiler", &[]);
|
|
effect_case("ec.distort.cctiler", &[("scale", n(70.0)), ("blendWithOriginal", n(30.0)), ("center", pt(30.0, 18.0))]);
|
|
effect_case("ec.distort.ccgriddler", &[("rotation", n(20.0))]);
|
|
effect_case("ec.distort.ccgriddler", &[("cutTiles", on()), ("horizontalScale", n(120.0)), ("verticalScale", n(80.0)), ("tileSize", n(17.0))]);
|
|
}
|
|
|
|
#[test]
|
|
fn mirror_offset_polar_spherize_corner_pin() {
|
|
effect_case("ec.distort.mirror", &[("angle", n(30.0))]);
|
|
effect_case("ec.distort.mirror", &[("angle", n(200.0)), ("center", pt(27.0, 19.0))]);
|
|
effect_case("ec.distort.offset", &[("shift", pt(10.0, 5.0))]);
|
|
effect_case("ec.distort.offset", &[("shift", pt(50.3, 20.7)), ("blend", n(40.0))]);
|
|
effect_case("ec.distort.polar", &[("interpolation", n(100.0))]);
|
|
effect_case("ec.distort.polar", &[("interpolation", n(60.0)), ("conversion", e(1))]);
|
|
effect_case("ec.distort.spherize", &[("radius", n(20.0))]);
|
|
effect_case("ec.distort.spherize", &[("radius", n(35.0)), ("center", pt(30.0, 25.0))]);
|
|
effect_case("ec.distort.cornerpin", &[("ul", pt(5.0, 3.0)), ("ur", pt(60.0, 8.0)), ("lr", pt(66.0, 40.0)), ("ll", pt(2.0, 44.0))]);
|
|
effect_case("ec.distort.cornerpin", &[("ul", pt(-8.0, 6.0)), ("ur", pt(75.0, -4.0)), ("lr", pt(50.0, 30.0)), ("ll", pt(12.0, 50.0))]);
|
|
}
|
|
|
|
#[test]
|
|
fn optics_magnify() {
|
|
effect_case("ec.distort.opticscompensation", &[("fieldOfView", n(60.0))]);
|
|
effect_case("ec.distort.opticscompensation", &[("fieldOfView", n(90.0)), ("reverseLensDistortion", on()), ("fovOrientation", e(2))]);
|
|
effect_case(
|
|
"ec.distort.opticscompensation",
|
|
&[("fieldOfView", n(110.0)), ("reverseLensDistortion", on()), ("resize", e(1)), ("viewCenter", pt(30.0, 20.0))],
|
|
);
|
|
effect_case("ec.distort.opticscompensation", &[("fieldOfView", n(120.0)), ("optimalPixels", on()), ("fovOrientation", e(1))]);
|
|
effect_case("ec.distort.magnify", &[("size", n(15.0))]);
|
|
effect_case(
|
|
"ec.distort.magnify",
|
|
&[("shape", e(1)), ("feather", n(6.0)), ("size", n(18.0)), ("scaling", e(1)), ("blendingMode", e(3)), ("magnification", n(270.0))],
|
|
);
|
|
effect_case("ec.distort.magnify", &[("scaling", e(2)), ("blendingMode", e(0)), ("size", n(20.0)), ("opacity", n(70.0))]);
|
|
effect_case("ec.distort.magnify", &[("size", n(25.0)), ("center", pt(60.0, 10.0)), ("resizeLayer", on()), ("blendingMode", e(5))]);
|
|
}
|
|
|
|
#[test]
|
|
fn cc_slant_smear_split() {
|
|
effect_case("ec.distort.ccslant", &[("slant", n(30.0))]);
|
|
effect_case("ec.distort.ccslant", &[("slant", n(-20.0)), ("stretching", on()), ("height", n(120.0))]);
|
|
effect_case("ec.distort.ccsmear", &[]);
|
|
effect_case("ec.distort.ccsmear", &[("reach", n(-40.0)), ("radius", n(20.0)), ("to", pt(40.0, 10.0))]);
|
|
effect_case("ec.distort.ccsplit", &[]);
|
|
effect_case("ec.distort.ccsplit2", &[("split1", n(30.0)), ("split2", n(80.0)), ("pointB", pt(60.0, 30.0))]);
|
|
}
|
|
|
|
#[test]
|
|
fn twirl_legacy_ripple_pulse_power_pin() {
|
|
effect_case("ec.distort.twirllegacy", &[("angle", n(90.0))]);
|
|
effect_case("ec.distort.twirllegacy", &[("angle", n(-250.0)), ("radius", n(80.0)), ("center", pt(30.0, 18.0))]);
|
|
effect_case("ec.distort.ccripplepulse", &[("pulseLevel", n(80.0)), ("timeSpan", n(0.5))]);
|
|
effect_case("ec.distort.ccripplepulse", &[("pulseLevel", n(-60.0)), ("amplitude", n(150.0)), ("renderBump", on()), ("center", pt(25.0, 20.0))]);
|
|
effect_case("ec.distort.ccpowerpin", &[("topLeft", pt(5.0, 3.0)), ("bottomRight", pt(62.0, 38.0))]);
|
|
effect_case("ec.distort.ccpowerpin", &[("topRight", pt(80.0, -6.0)), ("bottomLeft", pt(-4.0, 50.0)), ("expandLeft", n(20.0)), ("expandBottom", n(-10.0))]);
|
|
// Perspective below 100 %: blended toward the bilinear inverse.
|
|
effect_case("ec.distort.ccpowerpin", &[("topRight", pt(80.0, -6.0)), ("bottomLeft", pt(-4.0, 50.0)), ("perspective", n(0.0))]);
|
|
effect_case("ec.distort.ccpowerpin", &[("topLeft", pt(12.0, 6.0)), ("bottomRight", pt(75.0, 30.0)), ("perspective", n(40.0)), ("expandTop", n(15.0))]);
|
|
}
|
|
|
|
#[test]
|
|
fn flo_motion() {
|
|
effect_case("ec.distort.ccflomotion", &[]);
|
|
effect_case("ec.distort.ccflomotion", &[("antialiasing", e(2)), ("tileEdges", on()), ("amount1", n(-150.0)), ("falloff", n(0.7))]);
|
|
effect_case("ec.distort.ccflomotion", &[("antialiasing", e(0)), ("amount2", n(0.0)), ("knot1", pt(20.0, 30.0))]);
|
|
}
|
|
|
|
#[test]
|
|
fn liquify_render() {
|
|
let mesh = "warp 20 50 0 0 0 10,10 20,15 30,20\ntwirlClockwise 24 80 0 0 0 40,25 42,27";
|
|
effect_case("ec.distort.liquify", &[("distortionMesh", Value::Str(mesh.into()))]);
|
|
effect_case(
|
|
"ec.distort.liquify",
|
|
&[("distortionMesh", Value::Str(mesh.into())), ("distortionPercentage", n(60.0)), ("distortionMeshOffset", pt(3.0, -2.0))],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn glow_operations_and_arbitrary_map() {
|
|
effect_case("ec.stylize.glow", &[("glowOperation", e(2)), ("threshold", n(30.0)), ("radius", n(8.0))]);
|
|
effect_case("ec.stylize.glow", &[("glowOperation", e(3)), ("colors", e(1)), ("intensity", n(2.0))]);
|
|
effect_case("ec.stylize.glow", &[("colors", e(2)), ("arbitraryMap", Value::Str("0,0 0.5,0.8 1,1|0,0.2 1,0.6|".into())), ("threshold", n(20.0))]);
|
|
effect_case("ec.stylize.glow", &[("colors", e(2)), ("arbitraryMap", Value::Str("0,1 1,0||0,0 1,1".into())), ("operation", e(1)), ("glowOperation", e(4))]);
|
|
}
|
|
|
|
/// Texturize with a texture layer (Tile / Center / Stretch), composited.
|
|
#[test]
|
|
fn texturize_layer() {
|
|
for depth in [BitDepth::Bpc8, BitDepth::Bpc32] {
|
|
for placement in 0..3 {
|
|
let mut s = Scene::new(depth);
|
|
let bg = s.solid([0.15, 0.1, 0.2], 97, 61);
|
|
s.push(bg);
|
|
let mut tex = s.footage(50, 36);
|
|
tex.switches.video = false;
|
|
let tid = s.push(tex);
|
|
let mut l = s.footage(70, 44);
|
|
s.effect(
|
|
&mut l,
|
|
"ec.stylize.texturize",
|
|
&[("textureLayer", Value::Layer(Some(tid.0))), ("texturePlacement", e(placement)), ("textureContrast", n(1.5)), ("lightDirection", n(60.0))],
|
|
);
|
|
set(&mut l, "transform/rotation", Value::Scalar(8.0));
|
|
s.push(l);
|
|
let label = format!("texturize {placement} {depth:?}");
|
|
check(&label, compare_at(&s, opts(), Tick::ZERO), 0.0);
|
|
check(&format!("{label} half"), compare_at(&s, RenderOpts { scale: 0.5, ..opts() }, Tick::ZERO), 0.0);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Transform's own shutter angle: the effect's rotation and position animate.
|
|
#[test]
|
|
fn transform_motion_blur() {
|
|
for depth in [BitDepth::Bpc8, BitDepth::Bpc32] {
|
|
let mut s = Scene::new(depth);
|
|
let bg = s.solid([0.15, 0.1, 0.2], 97, 61);
|
|
s.push(bg);
|
|
let mut l = s.footage(70, 44);
|
|
s.effect(&mut l, "ec.distort.transform", &[("useCompositionShutterAngle", Value::Bool(false)), ("shutterAngle", n(180.0))]);
|
|
let rot = l.props.prop_mut("effects/#1/rotation").expect("transform rotation");
|
|
rot.keys = vec![Keyframe::new(Tick::ZERO, Value::Scalar(0.0)), Keyframe::new(Tick::from_seconds_f64(1.0), Value::Scalar(120.0))];
|
|
let pos = l.props.prop_mut("effects/#1/position").expect("transform position");
|
|
pos.keys = vec![Keyframe::new(Tick::ZERO, pt(35.0, 22.0)), Keyframe::new(Tick::from_seconds_f64(1.0), pt(95.0, 40.0))];
|
|
s.push(l);
|
|
let label = format!("transform motion blur {depth:?}");
|
|
check(&label, compare_at(&s, opts(), Tick::from_seconds_f64(0.3)), 0.0);
|
|
check(&format!("{label} half"), compare_at(&s, RenderOpts { scale: 0.5, ..opts() }, Tick::from_seconds_f64(0.3)), 0.0);
|
|
}
|
|
}
|