#1063: keep the Gradient Ramp inside the layer's alpha

The Gradient Ramp wrote alpha 1.0 to every pixel, so a shape layer's transparent area filled up with
the ramp and the layer's bounds showed instead of the shape. The ramp now uses the pixel's own alpha
on the CPU and in the GPU kernel, as the #939 fix does for the noise effects. A regression test
checks that transparent pixels stay transparent at two Blend With Original values.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
Kristian Myram 2026-10-12 19:41:35 +02:00
parent 470ea7a3af
commit 8eece41c7e
2 changed files with 23 additions and 2 deletions

View File

@ -44,7 +44,9 @@ fn gradient_ramp(ctx: &EffectCtx, mut b: Buf) -> Buf {
}
let t = t.clamp(0.0, 1.0);
let c = [c0[0] + (c1[0] - c0[0]) * t, c0[1] + (c1[1] - c0[1]) * t, c0[2] + (c1[2] - c0[2]) * t];
put(px, c, 1.0, blend);
// The ramp fills only where the layer has alpha (#1063), so it keeps the source alpha.
let a = px[3];
put(px, c, a, blend);
}
});
b
@ -400,6 +402,24 @@ mod generator_tests {
Image::filled(32, 32, [1.0, 0.0, 0.0, 1.0])
}
#[test]
fn gradient_ramp_stays_inside_the_layer_alpha() {
// #1063: a shape's transparent pixels stay transparent; the ramp fills only the shape.
let mut img = Image::filled(40, 24, [1.0, 0.0, 0.0, 1.0]);
for (i, p) in img.data.iter_mut().enumerate() {
let a = if i % 40 < 10 { 0.0 } else { 1.0 };
*p = p.map(|c| c * a);
}
for blend in [0.0, 50.0] {
let o = run_fx("ec.generate.gradientramp", &[("blend", num(blend))], img.clone(), 0.0, EffectEnv::default());
for (src, out) in img.data.iter().zip(&o.img.data) {
assert!((src[3] - out[3]).abs() < 1e-6, "blend {blend}: alpha {} became {}", src[3], out[3]);
assert!(out[..3].iter().all(|&c| c <= out[3] + 1e-6), "blend {blend}: premultiplied colour above alpha");
}
assert!(o.img.data.iter().any(|p| p[3] > 0.99), "blend {blend}: the ramp is drawn inside the shape");
}
}
#[test]
fn checkerboard_blending_none_shows_pattern_only_and_size_from_width() {
let set = [("width", num(8.0)), ("height", num(2.0))];

View File

@ -806,7 +806,8 @@ fn pointwise(@builtin(global_invocation_id) gid: vec3<u32>) {
t = clamp(t, 0.0, 1.0);
let c0 = P.f[1].xyz;
let c1 = P.f[2].xyz;
px = put(px, c0 + (c1 - c0) * t, 1.0, P.f[3].z);
// Keep the source alpha, as the CPU path does (#1063): the ramp fills only the shape.
px = put(px, c0 + (c1 - c0) * t, px.w, P.f[3].z);
}
case 10u: {
let contrast = P.f[0].x;