mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-30 04:57:54 -06:00
Radial Blur: samples outside the layer repeat its edge, as After Effects
After Effects' Radial Blur keeps a layer's edges: Zoom 20 on a flat grey 960x540 solid over red (case 713) stays flat and opaque to its edge, and a zoomed full-frame ramp (case 829) keeps the frame opaque (2/255 at x = 2, 253/255 at x = 1917 on row 540). EffectCraft sampled transparency outside the layer, so its edges faded and the red below showed through (713: 3.9 % of the frame off, max 64/255). Now 713 is identical to AE (max 0/255), CPU and GPU. CC Radial Blur (already matching AE) is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
a8d4514a1b
commit
ae7081077f
@@ -842,6 +842,33 @@ mod tests {
|
||||
assert!((p[1] / p[3] - 3.69).abs() < 0.05, "{p:?}");
|
||||
}
|
||||
|
||||
/// After Effects 2026: Radial Blur keeps the layer's edges. Zoom 20 on a flat grey 960 × 540
|
||||
/// solid (case 713) leaves it flat and opaque to the edge (EffectCraft faded the edges toward
|
||||
/// transparency, letting the red layer below show through); on a full-frame horizontal ramp
|
||||
/// (case 829, centre [960, 540]) row 540 reads 2/255 at x = 2 and 253/255 at x = 1917.
|
||||
#[test]
|
||||
fn radial_blur_keeps_the_layer_edges_as_after_effects() {
|
||||
let zoom = |c: [f64; 2]| [("amount", num(20.0)), ("type", Value::Enum(1)), ("center", Value::Vec2(c))];
|
||||
let grey = effectcraft_raster::Image::filled(960, 540, [0.5, 0.5, 0.5, 1.0]);
|
||||
let o = run_fx("ec.blur.radial", &zoom([600.0, 300.0]), grey, 0.0, EffectEnv::default());
|
||||
for (x, y) in [(0, 270), (1, 270), (959, 270), (480, 0), (480, 539), (0, 0), (959, 539)] {
|
||||
let p = o.img.get(x, y);
|
||||
assert!((p[3] - 1.0).abs() < 1e-4 && (p[0] - 0.5).abs() < 1e-4, "({x}, {y}): {p:?}");
|
||||
}
|
||||
let mut ramp = effectcraft_raster::Image::new(1920, 1080);
|
||||
ramp.rows_mut().for_each(|(_, row)| {
|
||||
for (x, px) in row.iter_mut().enumerate() {
|
||||
let v = (x as f32 + 0.5) / 1920.0;
|
||||
*px = [v, v, v, 1.0];
|
||||
}
|
||||
});
|
||||
let o = run_fx("ec.blur.radial", &zoom([960.0, 540.0]), ramp, 0.0, EffectEnv::default());
|
||||
for (x, ae) in [(2, 2.0), (1917, 253.0)] {
|
||||
let p = o.img.get(x, 540);
|
||||
assert!((p[0] * 255.0 - ae).abs() <= 2.0 && (p[3] - 1.0).abs() < 1e-4, "x {x}: {p:?} vs AE {ae}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn glow_operation_and_arbitrary_map() {
|
||||
// Pixels addressed in layer space (Glow pads its buffer).
|
||||
|
||||
@@ -67,7 +67,10 @@ fn gen_radial_blur(@builtin(global_invocation_id) gid: vec3<u32>) {
|
||||
let cs = data[2u * i + 1u];
|
||||
s = vec2<f32>(c.x + v.x * cs - v.y * sn, c.y + v.x * sn + v.y * cs);
|
||||
}
|
||||
acc += sample_bilinear(src, s.x, s.y);
|
||||
// Edge samples repeat (raster::radial_blur).
|
||||
let hi = vec2<f32>(f32(dims.x), f32(dims.y)) - vec2<f32>(0.5);
|
||||
let sc = clamp(s, vec2<f32>(0.5), max(hi, vec2<f32>(0.5)));
|
||||
acc += sample_bilinear(src, sc.x, sc.y);
|
||||
}
|
||||
textureStore(out, p, acc / f32(n));
|
||||
}
|
||||
|
||||
@@ -203,7 +203,9 @@ pub fn radial_blur(img: &Image, center: (f64, f64), amount: f64, zoom: bool) ->
|
||||
let (s, c) = (t * amount).to_radians().sin_cos();
|
||||
(center.0 + vx * c - vy * s, center.1 + vx * s + vy * c)
|
||||
};
|
||||
let p = img.sample_bilinear(sx, sy);
|
||||
// Samples outside the image repeat its edge: After Effects' Radial Blur keeps a
|
||||
// layer's edges (cases 713, 829) instead of fading them toward transparency.
|
||||
let p = img.sample_bilinear(sx.clamp(0.5, (img.width as f64 - 0.5).max(0.5)), sy.clamp(0.5, (img.height as f64 - 0.5).max(0.5)));
|
||||
for ch in 0..4 {
|
||||
acc[ch] += p[ch];
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ corpus compares renders, each effect is assumed to differ somewhere, and users k
|
||||
|---|---:|---:|---|
|
||||
| 3D Channel | 8 | 8 | Cryptomatte selection can't be set in the UI and the GPU path is slow (#473; quoted names with commas fixed); multi-layer EXR decode shared (#474 open: f16 storage and cache sizing remain) |
|
||||
| Audio | 13 | 0 | CPU by design (applied in the mixdown) |
|
||||
| Blur & Sharpen | 16 | 15 | Gaussian Blur interaction and output to be measured first ([fidelity/ae-2026-acceptance.md](fidelity/ae-2026-acceptance.md)) |
|
||||
| Blur & Sharpen | 16 | 15 | Radial Blur keeps the layer edges as After Effects 2026 (713 now pixel-identical); Gaussian Blur interaction and output to be measured first ([fidelity/ae-2026-acceptance.md](fidelity/ae-2026-acceptance.md)) |
|
||||
| Channel | 13 | 13 | — |
|
||||
| Color Correction | 40 | 40 | Tint has Swap Colors (#963); ACES 2.0 / OCIO 2.5 (26.5); Apply Color LUT (#514) and 32-bpc blending (#494) are closed |
|
||||
| Distort | 39 | 35 | Liquify has no viewer brush; #303 (Mesh Warp drag) and #534 (CC Power Pin Unstretch) are closed |
|
||||
@@ -66,6 +66,7 @@ the GPU is performance, not parity.
|
||||
|---|---|---|
|
||||
| 2026-10-19 | minor | Glow (original colours): bright pass per channel, 8 × (value − threshold + 0.061), measured against After Effects 2026 (CPU and GPU) |
|
||||
| 2026-10-19 | minor | Glow (colour channels): the glow covers the layer's blurred alpha, blur σ = 0.384 × radius, measured against After Effects 2026 renders (CPU and GPU) |
|
||||
| 2026-10-19 | minor | Radial Blur repeats the edge instead of sampling transparency outside the layer, measured against After Effects 2026 renders (CPU and GPU) |
|
||||
| 2026-10-12 | minor | Parameters 3,014 to 3,017 (effects.md); #303, #514, #526, #533–#536 marked closed instead of pending |
|
||||
| 2026-10-12 | minor | Effect colour labels (26.5) added in Effect Controls |
|
||||
| 2026-10-12 | minor | Tint's Swap Colors button and agent-drivable command (#963); undo/redo, instance targeting, animated layer-time edits and invalid-input tests |
|
||||
|
||||
Reference in New Issue
Block a user