From 41d6490ef2c40723401717659e36099cd1dcdbe8 Mon Sep 17 00:00:00 2001 From: David Mann Date: Sat, 7 Nov 2026 16:34:06 +0100 Subject: [PATCH] test(compose): CMYK reference keeps the source ink over nothing (#12079) The reference's source-over computed s * sa / sa over an empty backdrop, one ulp off the stored ink. Behind a Paint.NET Color Burn of a Posterize that lands exactly on 73.5/255, the ulp flipped the 8-bit rounding and random_documents_match_the_reference failed on main. Keep the source ink when nothing shows through or both inks agree, as cmyk::normal does. Co-Authored-By: Claude Opus 5.5 --- crates/compose/src/cmyk/tests.rs | 37 +++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/crates/compose/src/cmyk/tests.rs b/crates/compose/src/cmyk/tests.rs index 8deaa68de3..952d29be63 100644 --- a/crates/compose/src/cmyk/tests.rs +++ b/crates/compose/src/cmyk/tests.rs @@ -60,7 +60,11 @@ fn over(b: Cmyka, s: Cmyka, opacity: f32) -> Cmyka { } let mut o = [0.0; 5]; for i in 0..4 { - o[i] = (s[i] * sa + b[i] * b[4] * (1.0 - sa)) / a; + // Over nothing, under a covering source, or where both inks agree, the result is the + // source's ink exactly: `s * sa / sa` can be one ulp off, which a later mode or + // rounding step can turn into a whole level. + let shows_through = b[4] > 0.0 && sa < 1.0; + o[i] = if !shows_through || b[i] == s[i] { s[i] } else { (s[i] * sa + b[i] * b[4] * (1.0 - sa)) / a }; } o[4] = a; o @@ -762,6 +766,37 @@ fn clips_onto_pass_through(layers: &[LayerSpec]) -> bool { }) || layers.iter().any(|l| matches!(l, LayerSpec::Group { children, .. } if clips_onto_pass_through(children))) } +/// A proptest case from main's CI: a masked shape below 100% Fill, then Posterize in Color +/// Burn (Paint.NET) inside an isolated group. The burned brightness is exactly 73.5/255. +#[test] +fn posterize_in_paint_net_color_burn_on_a_rounding_tie_matches_the_reference() { + let shape = LayerSpec::Pixels { + kind: Kind::Shape, + blend: BlendMode::Normal, + fills: vec![(Rect::new(0, 0, 1, 1), [0.47422728, 0.0, 0.0, 0.0, 1.0])], + opacity: 1.0, + fill: 0.876696, + clipped: false, + visible: true, + mask: Some(MaskSpec { rects: vec![], default: 0.06425183, density: 1.0, enabled: true }), + }; + let posterize = LayerSpec::Pixels { + kind: Kind::Adjust(AdjSpec::Posterize(4)), + blend: BlendMode::PaintNetColorBurn, + fills: vec![(Rect::new(0, 0, 1, 1), [0.0; 5])], + opacity: 1.0, + fill: 1.0, + clipped: false, + visible: true, + mask: None, + }; + let group = LayerSpec::Group { children: vec![shape, posterize], pass_through: false, blend: BlendMode::Normal, opacity: 1.0, fill: 1.0, mask: None }; + let s = Spec { w: 8, h: 6, layers: vec![group] }; + let mut d = doc(s.w as u32, s.h as u32); + d.layers = s.layers.iter().map(build).collect(); + assert_matches_reference(&d); +} + proptest! { #![proptest_config(ProptestConfig { cases: 400, ..ProptestConfig::default() })]