fix(compose): mix group coverage in premultiplied space (#108)

This commit is contained in:
MikeeI 2026-10-05 07:00:31 +02:00 committed by GitHub
parent 7100b0d617
commit a65b5ca80f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 26 additions and 3 deletions

View File

@ -847,7 +847,16 @@ fn composite_layer_plain(layer: &Layer, clipped: &[Layer], backdrop: &mut Buffer
for (i, (p, a)) in backdrop.px.iter_mut().zip(&before.px).enumerate() {
let k = opacity * mask_k(&mv, i);
let b = *p;
*p = std::array::from_fn(|c| a[c] + (b[c] - a[c]) * k);
// Coverage mixes premultiplied colour; the stored buffer remains straight alpha.
let wa = a[3] * (1.0 - k);
let wb = b[3] * k;
let alpha = wa + wb;
*p = if alpha > 0.0 {
let c = |c: usize| (a[c] * wa + b[c] * wb) / alpha;
[c(0), c(1), c(2), alpha]
} else {
[0.0; 4]
};
}
}
// Layers clipped to a pass-through group sit atop the group's

View File

@ -173,6 +173,16 @@ fn pass_through_group_opacity_mixes() {
g.opacity = 0.25;
d.layers.push(g);
assert!((px(&d, 0, 0)[0] - 0.75).abs() <= E);
// Opaque backdrops hide straight-alpha interpolation errors.
let mut transparent = Document::new("coverage", Size::new(2, 2), ColorMode::Rgb, SampleType::F32);
let mut red = Layer::raster("HDR red", transparent.pixel_format());
red.surface_mut().unwrap().fill_rect(transparent.bounds(), &[2.0, 0.0, 0.0, 1.0]);
let mut group = Layer::group("half", vec![red]);
group.opacity = 0.5;
transparent.layers.push(group);
let actual = px(&transparent, 0, 0);
assert!(close4(actual, [2.0, 0.0, 0.0, 0.5]), "{actual:?}");
}
#[test]

View File

@ -687,7 +687,7 @@ fn fs_adjmix(in: VOut) -> @location(0) vec4<f32> {
return vec4(b.rgb + (bl - b.rgb) * k, b.a);
}
// Pass-through group opacity/mask: lerp(A, B, opacity × mask) on all channels.
// Pass-through group coverage mixes premultiplied colour, then returns straight alpha.
@fragment
fn fs_lerp(in: VOut) -> @location(0) vec4<f32> {
let p = local(in.pos);
@ -708,7 +708,11 @@ fn fs_lerp(in: VOut) -> @location(0) vec4<f32> {
return vec4(clamp(pm.rgb / al, vec3(0.0), vec3(1.0)), al);
}
let k = op.opacity * mask_value(doc_px(p));
return a + (b - a) * k;
let wa = a.a * (1.0 - k);
let wb = b.a * k;
let alpha = wa + wb;
if (alpha <= 0.0) { return vec4(0.0); }
return vec4((a.rgb * wa + b.rgb * wb) / alpha, alpha);
}
// ---- layer effects (compose::effects::composite_with_effects) -------------------------------