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()))); +}