fix(effects): clear Cut Away 100 opaque edges

The gradient antialias could leave a half-covered pixel where an opaque source reaches h = 1. Add an endpoint guard on CPU and GPU and cover an opaque edge in the regression test.
This commit is contained in:
Camille Moine
2026-10-21 14:46:21 +02:00
parent fda7eef73f
commit 374c3bf6b9
3 changed files with 22 additions and 0 deletions
+15
View File
@@ -123,6 +123,12 @@ fn dot3f(a: [f32; 3], b: [f32; 3]) -> f32 {
fn cc_blobbylize(ctx: &EffectCtx, mut b: Buf) -> Buf {
let soft = ctx.params.f("softness").max(0.0) * b.scale;
let cut = (ctx.params.f("cutAway") / 100.0).clamp(0.0, 1.0) as f32;
if cut >= 1.0 {
// Endpoint guard: at Cut Away 100 the gradient antialias must not leave a half-covered
// pixel where an opaque source reaches exactly 1.0.
b.img.data.fill([0.0; 4]);
return b;
}
let blob = layer_or_self(ctx, &b, "blobLayer", true, false);
let prop = src_at(ctx.params.e("property"));
let raw = Plane::from_image(&blob, |px| {
@@ -1212,6 +1218,15 @@ mod tests {
assert!(o.img.data.iter().all(|p| p[3] == 0.0), "Cut Away 100 left opaque pixels");
}
#[test]
fn blobbylize_cut_away_100_clears_an_opaque_edge() {
// An opaque source reaches h = 1 on the high side of an edge; the gradient antialias
// used to leave m = 0.5 there, so Cut Away 100 still produced visible pixels.
let im = crate::util::gen_image(64, 8, |x, _| if x < 32 { [1.0; 4] } else { [0.0; 4] });
let o = run("ec.distort.ccblobbylize", &[("property", Value::Enum(3)), ("softness", num(0.0)), ("cutAway", num(100.0))], im, &[]);
assert!(o.img.data.iter().all(|p| p[3] == 0.0), "Cut Away 100 left an opaque edge");
}
#[test]
fn blobbylize_softness_warps_a_step_matte_inward() {
// AE oracle T2: a self alpha step at x = 960, Softness 20, Cut Away 0 puts the matte
@@ -582,6 +582,12 @@ fn ftr_blob(@builtin(global_invocation_id) gid: vec3<u32>) {
return;
}
let cut = P.f[0].x;
if (cut >= 1.0) {
// Endpoint guard: an opaque source can reach h = 1.0, where the gradient antialias
// would otherwise leave a half-covered pixel at Cut Away 100.
textureStore(out, p, vec4<f32>(0.0));
return;
}
let hv = textureLoad(aux, p, 0).x;
let g = ftr_grad(p);
let grad = length(g);
+1
View File
@@ -126,6 +126,7 @@ fn distort() {
"ec.distort.ccblobbylize",
&[("property", e(4)), ("softness", n(3.0)), ("cutAway", n(30.0)), ("lightType", e(1)), ("lightPosition", pt(20.0, 10.0))],
);
effect_case("ec.distort.ccblobbylize", &[("cutAway", n(100.0))]);
effect_case("ec.distort.ccblobbylize", &[("property", e(5)), ("metal", n(30.0)), ("roughness", n(0.2))]);
}