From 244f9bd846d1b94f47a392e4f46618b6db3f85c1 Mon Sep 17 00:00:00 2001 From: arnav-hiwarkar Date: Thu, 12 Nov 2026 15:46:48 +0530 Subject: [PATCH] #3483, #3484: Noise HLS: amounts and Grain Size take After Effects' ranges Hue, Lightness and Saturation go up to 1000 % and Grain Size down to 0 (it keeps its wider maximum, 100), After Effects' recorded valid ranges in the same units. Noise HLS Auto shares the controls and widens with them. Defaults and sliders are unchanged. The renderers need no change: hue wraps, lightness and saturation clamp per pixel after the noise is added, and Grain Size is floored at 0.1 px on the CPU and the GPU, so the new values cost the same and stay finite. The amounts were written as "percent" in keyframe text because their range was 0..100, and Grain Size would become "percent" at 0..100; UNITS rows keep the units they had. --- crates/aep/src/export/fx_defs.rs | 4 --- crates/effects/src/noise.rs | 32 ++++++++++++++++--- .../src/interchange/keyframe_text_effects.rs | 9 ++++++ crates/engine/src/tests/effect_ranges.rs | 8 +++++ crates/engine/src/tests/keyframe_text.rs | 8 +++++ crates/gpu/src/tests/fx_noise.rs | 2 ++ 6 files changed, 55 insertions(+), 8 deletions(-) diff --git a/crates/aep/src/export/fx_defs.rs b/crates/aep/src/export/fx_defs.rs index cc80eaaaa..90db58758 100644 --- a/crates/aep/src/export/fx_defs.rs +++ b/crates/aep/src/export/fx_defs.rs @@ -746,10 +746,6 @@ mod tests { ("ec.color.changecolor", "hueTransform", 3480), ("ec.time.pixelmotionblur", "shutterAngle", 3481), ("ec.time.pixelmotionblur", "shutterSamples", 3482), - ("ec.noise.noisehls", "hue", 3483), - ("ec.noise.noisehls", "lightness", 3483), - ("ec.noise.noisehls", "saturation", 3483), - ("ec.noise.noisehls", "grainSize", 3484), ("ec.noise.fractal", "subSettings/subInfluence", 3485), ("ec.noise.fractal", "subSettings/subScaling", 3486), ("ec.distort.ccpowerpin", "expandTop", 3492), diff --git a/crates/effects/src/noise.rs b/crates/effects/src/noise.rs index 09217f831..246910cd5 100644 --- a/crates/effects/src/noise.rs +++ b/crates/effects/src/noise.rs @@ -766,10 +766,12 @@ pub fn specs() -> Vec { let hls_common = || { vec![ p("noise", "Noise", Value::Enum(0), popup(&["Uniform", "Squared", "Grain"])), - p("hue", "Hue", num(0.0), pct()), - p("lightness", "Lightness", num(0.0), pct()), - p("saturation", "Saturation", num(0.0), pct()), - p("grainSize", "Grain Size", num(1.0), slider(0.1, 100.0, 0.5, 10.0, 2)), + // After Effects' valid ranges (#3483, #3484): the amounts go to 1000 %, Grain Size + // down to 0 (the renderer floors it at 0.1 px) and keeps its wider maximum. + p("hue", "Hue", num(0.0), slider(0.0, 1000.0, 0.0, 100.0, 1)), + p("lightness", "Lightness", num(0.0), slider(0.0, 1000.0, 0.0, 100.0, 1)), + p("saturation", "Saturation", num(0.0), slider(0.0, 1000.0, 0.0, 100.0, 1)), + p("grainSize", "Grain Size", num(1.0), slider(0.0, 100.0, 0.5, 10.0, 2)), ] }; let mut hls = hls_common(); @@ -967,6 +969,28 @@ mod tests { assert_eq!(still.img, img); } + /// #3483, #3484: amounts above 100 % keep adding noise, and Grain Size 0 renders (floored at + /// 0.1 px) with finite output, for both effects. + #[test] + fn noise_hls_takes_after_effects_ranges() { + let mut img = Image::new(16, 16); + for (i, p) in img.data.iter_mut().enumerate() { + *p = premul([0.6, 0.3, (i % 7) as f32 / 7.0], 1.0); + } + for id in ["ec.noise.noisehls", "ec.noise.noisehlsauto"] { + let at = |amount: f64, grain: f64| { + let vals = + [("noise", Value::Enum(2)), ("hue", num(amount)), ("lightness", num(amount)), ("saturation", num(amount)), ("grainSize", num(grain))]; + run(id, &vals, img.clone(), 0.0).img + }; + let (low, high) = (at(100.0, 1.0), at(1000.0, 1.0)); + assert_ne!(low, high, "{id}"); + for out in [high, at(1000.0, 0.0)] { + assert!(out.data.iter().flatten().all(|v| v.is_finite() && (0.0..=1.0).contains(v)), "{id}"); + } + } + } + #[test] fn add_grain_keeps_flat_mean() { let out = run("ec.noise.addgrain", &[("tweaking/intensity", num(2.0)), ("application/blendingMode", Value::Enum(2))], gray(64, 64, 0.5), 0.0); diff --git a/crates/engine/src/interchange/keyframe_text_effects.rs b/crates/engine/src/interchange/keyframe_text_effects.rs index 94d81afcc..7b0658f61 100644 --- a/crates/engine/src/interchange/keyframe_text_effects.rs +++ b/crates/engine/src/interchange/keyframe_text_effects.rs @@ -318,6 +318,15 @@ pub(crate) const UNITS: &[(&str, &str, &str)] = &[ ("ec.matte.rotobrush", "refineEdgeMatte/shiftEdge", "percent"), ("ec.matte.rotobrush", "refineEdgeMatte/decontamination/increaseDecontaminationRadius", ""), ("ec.matte.simplechoker", "chokeMatte", "pixels"), + // Their ranges were 0..100 (#3483, #3484). + ("ec.noise.noisehls", "hue", "percent"), + ("ec.noise.noisehls", "lightness", "percent"), + ("ec.noise.noisehls", "saturation", "percent"), + ("ec.noise.noisehls", "grainSize", ""), + ("ec.noise.noisehlsauto", "hue", "percent"), + ("ec.noise.noisehlsauto", "lightness", "percent"), + ("ec.noise.noisehlsauto", "saturation", "percent"), + ("ec.noise.noisehlsauto", "grainSize", ""), ("ec.noise.fractal", "transform/scale", "percent"), ("ec.noise.fractal", "transform/scaleWidth", "percent"), ("ec.noise.fractal", "transform/scaleHeight", "percent"), diff --git a/crates/engine/src/tests/effect_ranges.rs b/crates/engine/src/tests/effect_ranges.rs index cb4d4ea24..9c0c336ec 100644 --- a/crates/engine/src/tests/effect_ranges.rs +++ b/crates/engine/src/tests/effect_ranges.rs @@ -41,6 +41,14 @@ const ROWS: &[(u32, &str, &str, f64, f64)] = &[ (3491, "ec.color.levelsic", "alpha/alphaGamma", 0.0, 10.0), (3478, "ec.generate.fourcolor", "blend", 1.0, 10000.0), (3479, "ec.generate.fourcolor", "jitter", 0.0, 500.0), + (3483, "ec.noise.noisehls", "hue", 0.0, 1000.0), + (3483, "ec.noise.noisehls", "lightness", 0.0, 1000.0), + (3483, "ec.noise.noisehls", "saturation", 0.0, 1000.0), + (3484, "ec.noise.noisehls", "grainSize", 0.0, 100.0), + (3483, "ec.noise.noisehlsauto", "hue", 0.0, 1000.0), + (3483, "ec.noise.noisehlsauto", "lightness", 0.0, 1000.0), + (3483, "ec.noise.noisehlsauto", "saturation", 0.0, 1000.0), + (3484, "ec.noise.noisehlsauto", "grainSize", 0.0, 100.0), ]; fn path(effect: &str, param: &str) -> String { diff --git a/crates/engine/src/tests/keyframe_text.rs b/crates/engine/src/tests/keyframe_text.rs index 81d9487d9..a299899eb 100644 --- a/crates/engine/src/tests/keyframe_text.rs +++ b/crates/engine/src/tests/keyframe_text.rs @@ -497,4 +497,12 @@ fn widened_effect_ranges_keep_their_keyframe_text_units() { let text = copy_props(&mut s, l, &["effects/#1/blend", "effects/#1/jitter"]); assert_eq!(unit_of(&text, "Blend"), "percent"); assert_eq!(unit_of(&text, "Jitter"), "percent"); + for fx in ["ec.noise.noisehls", "ec.noise.noisehlsauto"] { + let (mut s, l) = session(); + s.execute("effect.apply", json!({"layer": l.0, "effect": fx})).unwrap(); + let text = copy_props(&mut s, l, &["effects/#1/hue", "effects/#1/lightness", "effects/#1/saturation", "effects/#1/grainSize"]); + for (name, unit) in [("Hue", "percent"), ("Lightness", "percent"), ("Saturation", "percent"), ("Grain Size", "")] { + assert_eq!(unit_of(&text, name), unit, "{fx} {name}"); + } + } } diff --git a/crates/gpu/src/tests/fx_noise.rs b/crates/gpu/src/tests/fx_noise.rs index 7d13004ee..006128c73 100644 --- a/crates/gpu/src/tests/fx_noise.rs +++ b/crates/gpu/src/tests/fx_noise.rs @@ -164,6 +164,8 @@ fn noise_alpha_and_hls() { effect_case("ec.noise.noisehls", &[("hue", n(30.0)), ("lightness", n(20.0)), ("saturation", n(40.0))]); effect_case("ec.noise.noisehls", &[("noise", e(1)), ("lightness", n(50.0)), ("noisePhase", n(200.0))]); effect_case("ec.noise.noisehls", &[("noise", e(2)), ("hue", n(60.0)), ("saturation", n(25.0)), ("grainSize", n(3.0))]); + // #3483, #3484: the ends of After Effects' ranges. + effect_case("ec.noise.noisehls", &[("noise", e(2)), ("hue", n(1000.0)), ("lightness", n(1000.0)), ("saturation", n(1000.0)), ("grainSize", n(0.0))]); effect_case("ec.noise.noisehlsauto", &[("noise", e(0)), ("hue", n(20.0)), ("lightness", n(30.0)), ("noiseAnimationSpeed", n(7.0))]); }