From c0387ea18ada7a31870705a583c9f12aa991ab88 Mon Sep 17 00:00:00 2001 From: arnav-hiwarkar Date: Thu, 12 Nov 2026 16:24:17 +0530 Subject: [PATCH] #3470, #3472, #3473, #3474, #3475, #3476, #3477, #3480, #3483, #3484, #3487-#3491: test the ends of the new ranges on the GPU CPU / GPU comparisons at the new ends for Venetian Blinds (width 1, width and feather 32000), Radial Wipe (feather 32000), Grid (border 2000), Scatter (1000), Beam (4000), Change Color (1710 degrees) and Turbulent Displace (seed 100000). The comparison counts infinity on both sides as equal, so Levels (Individual Controls) gets a direct check that the GPU's output is finite at the ends of its ranges. The Noise HLS test asserts the spec's ranges, so it fails without them. --- crates/effects/src/noise.rs | 4 ++++ crates/gpu/src/tests/fx_distort.rs | 2 ++ crates/gpu/src/tests/fx_gen2.rs | 2 ++ crates/gpu/src/tests/fx_generate.rs | 7 +++++++ crates/gpu/src/tests/fx_stylize.rs | 2 ++ crates/gpu/src/tests/fx_tone.rs | 32 +++++++++++++++++++++++++++++ 6 files changed, 49 insertions(+) diff --git a/crates/effects/src/noise.rs b/crates/effects/src/noise.rs index 246910cd5..fde24b537 100644 --- a/crates/effects/src/noise.rs +++ b/crates/effects/src/noise.rs @@ -978,6 +978,10 @@ mod tests { *p = premul([0.6, 0.3, (i % 7) as f32 / 7.0], 1.0); } for id in ["ec.noise.noisehls", "ec.noise.noisehlsauto"] { + for (param, lo, hi) in [("hue", 0.0, 1000.0), ("lightness", 0.0, 1000.0), ("saturation", 0.0, 1000.0), ("grainSize", 0.0, 100.0)] { + let ui = &crate::find(id).unwrap().params.iter().find(|p| p.id == param).unwrap().ui; + assert!(matches!(ui, ParamUi::Slider { min, max, .. } if *min == lo && *max == hi), "{id} {param}: {ui:?}"); + } let at = |amount: f64, grain: f64| { let vals = [("noise", Value::Enum(2)), ("hue", num(amount)), ("lightness", num(amount)), ("saturation", num(amount)), ("grainSize", num(grain))]; diff --git a/crates/gpu/src/tests/fx_distort.rs b/crates/gpu/src/tests/fx_distort.rs index 1b8621408..6fc18f3c9 100644 --- a/crates/gpu/src/tests/fx_distort.rs +++ b/crates/gpu/src/tests/fx_distort.rs @@ -38,6 +38,8 @@ fn twirl_bulge_ripple_wave_warp() { #[test] fn turbulent_displace_and_cc_lens() { effect_case("ec.distort.turbulentdisplace", &[("size", n(30.0)), ("amount", n(20.0))]); + // #3470: After Effects' maximum Random Seed. + effect_case("ec.distort.turbulentdisplace", &[("size", n(30.0)), ("amount", n(20.0)), ("evolutionOptions/randomSeed", n(100000.0))]); effect_case( "ec.distort.turbulentdisplace", &[("displacement", e(1)), ("complexity", n(3.5)), ("evolution", n(90.0)), ("size", n(25.0)), ("pinning", e(2)), ("resizeLayer", on())], diff --git a/crates/gpu/src/tests/fx_gen2.rs b/crates/gpu/src/tests/fx_gen2.rs index 620cced13..1c19725e1 100644 --- a/crates/gpu/src/tests/fx_gen2.rs +++ b/crates/gpu/src/tests/fx_gen2.rs @@ -40,6 +40,8 @@ fn closed_form() { env_case("ec.generate.beam", &[]); env_case("ec.generate.beam", &[("length", n(70.0)), ("time", n(40.0)), ("softness", n(0.0)), ("perspective3d", off()), ("compositeOnOriginal", off())]); env_case("ec.generate.beam", &[("startThickness", n(3.0)), ("endThickness", n(20.0)), ("insideColor", c(0.3, 0.9, 1.0))]); + // #3475: After Effects' maximum thickness. + env_case("ec.generate.beam", &[("startThickness", n(0.0)), ("endThickness", n(4000.0))]); env_case("ec.generate.lensflare", &[]); env_case("ec.generate.lensflare", &[("lensType", e(1)), ("flareCenter", pt(50.0, 30.0)), ("blendWithOriginal", n(30.0))]); env_case("ec.generate.lensflare", &[("lensType", e(2)), ("flareBrightness", n(160.0))]); diff --git a/crates/gpu/src/tests/fx_generate.rs b/crates/gpu/src/tests/fx_generate.rs index d7e05e047..f8482b5ab 100644 --- a/crates/gpu/src/tests/fx_generate.rs +++ b/crates/gpu/src/tests/fx_generate.rs @@ -83,6 +83,11 @@ fn wipes() { effect_case("ec.transition.radialwipe", &[("completion", n(45.0)), ("wipe", e(1)), ("center", pt(30.2, 19.6))]); effect_case("ec.transition.radialwipe", &[("completion", n(60.0)), ("wipe", e(2)), ("startAngle", n(-40.0)), ("feather", n(3.0))]); effect_case("ec.transition.radialwipe", &[("completion", n(0.0)), ("feather", n(20.0))]); + // #3472-#3474: the ends of After Effects' ranges (a Feather wider than 8 blinds is the CPU's + // even veil). + effect_case("ec.transition.venetian", &[("completion", n(40.0)), ("width", n(1.0)), ("feather", n(6.0))]); + effect_case("ec.transition.venetian", &[("completion", n(40.0)), ("width", n(32000.0)), ("feather", n(32000.0))]); + effect_case("ec.transition.radialwipe", &[("completion", n(40.0)), ("feather", n(32000.0))]); effect_case("ec.transition.gradientwipe", &[("completion", n(40.0))]); effect_case("ec.transition.gradientwipe", &[("completion", n(55.0)), ("softness", n(30.0)), ("invert", b(true))]); effect_case("ec.transition.gradientwipe", &[("completion", n(100.0))]); @@ -147,6 +152,8 @@ fn generators() { ); effect_case("ec.generate.checkerboard", &[("sizeFrom", e(0)), ("corner", pt(16.0, 11.0)), ("color", c(0.9, 0.3, 0.1)), ("blendingMode", e(1))]); effect_case("ec.generate.grid", &[("width", n(12.0)), ("border", n(3.0))]); + // #3476: a border wider than the cells (After Effects' maximum). + effect_case("ec.generate.grid", &[("width", n(12.0)), ("border", n(2000.0))]); effect_case( "ec.generate.grid", &[ diff --git a/crates/gpu/src/tests/fx_stylize.rs b/crates/gpu/src/tests/fx_stylize.rs index 63d93f207..8c6c7be33 100644 --- a/crates/gpu/src/tests/fx_stylize.rs +++ b/crates/gpu/src/tests/fx_stylize.rs @@ -109,6 +109,8 @@ fn arbitrary_map_signed_fractional_and_near_zero_wraps() { #[test] fn scatter_brush_strokes() { effect_case("ec.stylize.scatter", &[("amount", n(5.0))]); + // #3477: After Effects' maximum, far beyond the layer. + effect_case("ec.stylize.scatter", &[("amount", n(1000.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", &[]); diff --git a/crates/gpu/src/tests/fx_tone.rs b/crates/gpu/src/tests/fx_tone.rs index ca80f3836..3083f5949 100644 --- a/crates/gpu/src/tests/fx_tone.rs +++ b/crates/gpu/src/tests/fx_tone.rs @@ -81,6 +81,8 @@ fn colour_replacement() { &[("amount", n(100.0)), ("color", c(0.8, 0.4, 0.3)), ("matchColors", e(1)), ("tolerance", n(5.0)), ("edgeSoftness", n(30.0))], ); effect_case("ec.color.changecolor", &[("hueTransform", n(90.0)), ("colorToChange", c(0.7, 0.5, 0.4)), ("matchingSoftness", n(20.0))]); + // #3480: near the top of After Effects' range (four and three quarter turns). + effect_case("ec.color.changecolor", &[("hueTransform", n(1710.0)), ("colorToChange", c(0.7, 0.5, 0.4)), ("matchingSoftness", n(20.0))]); effect_case( "ec.color.changecolor", &[ @@ -199,3 +201,33 @@ fn color_stabilizer() { fn gradient_ramp_matches_the_cpu() { effect_case("ec.generate.gradientramp", &[]); } + +/// #3487-#3491: at the ends of the valid ranges the GPU's Levels (Individual Controls) output is +/// finite (the CPU / GPU comparison counts infinity on both sides as equal). +#[test] +fn levels_ic_is_finite_at_the_ends_of_its_ranges() { + let Some(g) = crate::tests::gpu() else { return }; + let spec = effectcraft_effects::find("ec.color.levelsic").unwrap(); + let size = [16.0, 8.0]; + let mut params = + effectcraft_effects::Params { values: spec.params.iter().map(|ps| (ps.id.to_string(), effectcraft_effects::default_value(ps, size))).collect() }; + for (k, v) in [ + ("rgb/rgbInWhite", n(0.2)), + ("rgb/rgbGamma", n(0.0)), + ("red/redOutBlack", n(-10000.0)), + ("blue/blueOutWhite", n(10000.0)), + ("green/greenOutWhite", n(0.0)), + ("clipToOutputWhite", e(0)), + ] { + params.values.insert(k.to_string(), v); + } + let mut img = crate::tests::pattern(7, 16, 8); + for p in &mut img.data { + *p = p.map(|v| v * 3.0); + } + let buf = effectcraft_effects::Buf { img, offset: [0.0, 0.0], scale: 1.0 }; + let ctx = effectcraft_effects::EffectCtx { params: ¶ms, time: 0.0, layer_size: size, seed: 1, adjustment: false, env: Default::default() }; + let out = effectcraft_render::Accelerator::effects(g, &[effectcraft_render::FxStep { spec, ctx }], &buf, None) + .expect("the GPU runs Levels (Individual Controls)"); + assert!(out.img.data.iter().flatten().all(|v| v.is_finite()), "{:?}", out.img.data.iter().find(|p| p.iter().any(|v| !v.is_finite()))); +}