mirror of
https://github.com/storytold/photocraft.git
synced 2026-11-12 21:23:02 +00:00
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 <noreply@anthropic.com>
This commit is contained in:
1 parent
c70ab0a0c8
commit
41d6490ef2
1 file changed
+36
-1
@@ -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() })]
|
||||
|
||||
|
||||
Reference in new issue
Block a user