From 2720ae5fe29d721622502e34eb0504b05a46d726 Mon Sep 17 00:00:00 2001 From: Vikram Bhaskaran Date: Tue, 13 Oct 2026 23:40:32 +1100 Subject: [PATCH] Wave Warp: cover the Top/Bottom edge pinning paths end to end (#1181) The pinning regression only exercised All Edges (and Left Edge in the CPU unit test), so the reported 'does not work with Top/Bottom Edge' had no coverage to catch or clear it. This holds the pinned first/last row in a CPU test at Direction 90, and adds CPU/GPU parity cases for Top (4), Bottom (6), diagonal directions, and single-edge pinning on shape layers. CPU pinning verified correct on main: the guards in dst_wave_pin/wave_pin agree; the gap was that neither edge path was tested. --- crates/effects/src/distort.rs | 14 +++++++++++++- crates/gpu/src/tests_fx_distort.rs | 8 ++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/crates/effects/src/distort.rs b/crates/effects/src/distort.rs index a68de109..195ac898 100644 --- a/crates/effects/src/distort.rs +++ b/crates/effects/src/distort.rs @@ -556,8 +556,20 @@ mod tests { let (a, b) = (o.img.get(pad, y + pad), im.get(0, y)); assert!((0..4).all(|i| (a[i] - b[i]).abs() < 0.01), "row {y}: {a:?} vs {b:?}"); } - let free = run("ec.distort.wavewarp", &[("direction", num(0.0)), ("height", num(5.0))], im); + let free = run("ec.distort.wavewarp", &[("direction", num(0.0)), ("height", num(5.0))], im.clone()); assert!(free.img.data.iter().zip(&o.img.data).any(|(a, b)| differs(*a, *b))); + // #1181: direction 90° → vertical displacement. Top Edge (4) holds the first row, + // Bottom Edge (6) the last, and the rest of the layer still moves. + for (pin, edge_row) in [(Value::Enum(4), 0i64), (Value::Enum(6), 29i64)] { + let set = [("direction", num(90.0)), ("height", num(5.0)), ("pinning", pin)]; + let o = run("ec.distort.wavewarp", &set, im.clone()); + let pad = ((o.img.width - im.width) / 2) as i64; + for x in 0..40 { + let (a, b) = (o.img.get(x + pad, edge_row + pad), im.get(x, edge_row)); + assert!((0..4).all(|i| (a[i] - b[i]).abs() < 0.01), "pin {edge_row} col {x}: {a:?} vs {b:?}"); + } + assert!(o.img.data.iter().zip(im.data.iter()).any(|(a, b)| differs(*a, *b)), "pin {edge_row}: layer must still distort"); + } } #[test] diff --git a/crates/gpu/src/tests_fx_distort.rs b/crates/gpu/src/tests_fx_distort.rs index e0e89fb6..19cdcd60 100644 --- a/crates/gpu/src/tests_fx_distort.rs +++ b/crates/gpu/src/tests_fx_distort.rs @@ -28,6 +28,11 @@ fn twirl_bulge_ripple_wave_warp() { effect_case("ec.distort.wavewarp", &[("waveType", e(2)), ("pinning", e(1)), ("phase", n(400.0)), ("speed", n(-2.0))]); effect_case("ec.distort.wavewarp", &[("waveType", e(8)), ("pinning", e(2)), ("height", n(-5.0)), ("direction", n(200.0))]); effect_case("ec.distort.wavewarp", &[("waveType", e(4)), ("pinning", e(7)), ("width", n(33.0))]); + // #1181: the single-edge pinning paths, with the default vertical displacement direction. + effect_case("ec.distort.wavewarp", &[("direction", n(90.0)), ("height", n(7.0)), ("width", n(25.0)), ("pinning", e(4))]); + effect_case("ec.distort.wavewarp", &[("direction", n(90.0)), ("height", n(7.0)), ("width", n(19.0)), ("pinning", e(6)), ("waveType", e(1))]); + effect_case("ec.distort.wavewarp", &[("direction", n(45.0)), ("height", n(-6.0)), ("pinning", e(4)), ("speed", n(2.0))]); + effect_case("ec.distort.wavewarp", &[("direction", n(45.0)), ("height", n(-6.0)), ("pinning", e(6)), ("speed", n(2.0))]); } #[test] @@ -79,6 +84,9 @@ fn shape_case(id: &str, vals: &[(&str, Value)]) { fn edge_pinning_on_a_shape_layer() { shape_case("ec.distort.turbulentdisplace", &[("size", n(30.0)), ("amount", n(20.0))]); shape_case("ec.distort.wavewarp", &[("pinning", e(1)), ("height", n(6.0)), ("width", n(25.0))]); + // #1181: single-edge pinning over the layer rect, not just All Edges. + shape_case("ec.distort.wavewarp", &[("pinning", e(4)), ("direction", n(90.0)), ("height", n(6.0)), ("width", n(25.0))]); + shape_case("ec.distort.wavewarp", &[("pinning", e(6)), ("direction", n(90.0)), ("height", n(6.0)), ("width", n(25.0))]); shape_case( "ec.distort.bulge", &[("pinAllEdges", on()), ("height", n(2.0)), ("hradius", n(40.0)), ("vradius", n(30.0)), ("center", Value::Vec2([28.5, 20.5]))],