1pub use denise::Paint;
9pub use denise::paint::scale_premul;
10use denise::paint::{LANES, mul_lanes};
11
12pub fn premultiply(pixels: &mut [u32]) {
19 for px in pixels {
20 match *px >> 24 {
21 255 => {}
22 0 => *px = 0,
23 a => {
24 let rb = mul_lanes(*px & LANES, a);
25 let g = mul_lanes((*px >> 8) & 0xFF, a);
26 *px = (a << 24) | rb | (g << 8);
27 }
28 }
29 }
30}
31
32#[inline(always)]
37pub const fn source_over(dst: u32, src_premul: u32, alpha: u32) -> u32 {
38 let inv = 255 - alpha;
39 let rb = mul_lanes(dst & LANES, inv);
40 let ag = mul_lanes((dst >> 8) & LANES, inv);
41 src_premul + (rb | (ag << 8))
44}
45
46#[inline]
48pub fn fill_span(span: &mut [u32], word: u32) {
49 span.fill(word);
50}
51
52#[inline]
54pub fn blend_span(span: &mut [u32], paint: Paint) {
55 if paint.is_invisible() {
56 return;
57 }
58 if paint.is_opaque() {
59 span.fill(paint.premultiplied());
60 return;
61 }
62 let src = paint.premultiplied();
63 let alpha = paint.alpha();
64 for px in span {
65 *px = source_over(*px, src, alpha);
66 }
67}
68
69#[inline]
71pub fn blend_pixel(dst: &mut u32, paint: Paint, coverage: u32) {
72 if coverage == 0 {
73 return;
74 }
75 let paint = if coverage == 255 {
76 paint
77 } else {
78 paint.scaled(coverage)
79 };
80 *dst = source_over(*dst, paint.premultiplied(), paint.alpha());
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86 use denise::Color;
87
88 #[test]
89 fn opaque_paint_replaces_destination() {
90 let p = Paint::new(Color::rgb(10, 20, 30));
91 assert!(p.is_opaque());
92 assert_eq!(
93 source_over(0xFFFF_FFFF, p.premultiplied(), p.alpha()),
94 0xFF0A_141E
95 );
96 }
97
98 #[test]
99 fn transparent_paint_preserves_destination() {
100 let p = Paint::new(Color::rgba(10, 20, 30, 0));
101 assert_eq!(
102 source_over(0xFF12_3456, p.premultiplied(), p.alpha()),
103 0xFF12_3456
104 );
105 }
106
107 #[test]
108 fn premultiply_is_exact_at_the_endpoints() {
109 let c = Color::rgba(0xAB, 0xCD, 0xEF, 255);
111 assert_eq!(Paint::new(c).premultiplied(), 0xFFAB_CDEF);
112 assert_eq!(
113 Paint::new(Color::rgba(0xAB, 0xCD, 0xEF, 0)).premultiplied(),
114 0
115 );
116 }
117
118 #[test]
119 fn half_alpha_over_black_is_half_the_colour() {
120 let p = Paint::new(Color::rgba(200, 100, 50, 128));
121 let out = source_over(0xFF00_0000, p.premultiplied(), p.alpha());
122 assert_eq!(out & 0x00FF_FFFF, 0x0064_3219);
124 }
125
126 #[test]
127 fn no_lane_ever_carries_into_its_neighbour() {
128 for a in 0..=255u32 {
131 let p = Paint::new(Color::rgba(255, 255, 255, a as u8));
132 let out = source_over(0xFFFF_FFFF, p.premultiplied(), p.alpha());
133 assert_eq!(out, 0xFFFF_FFFF, "alpha {a} carried");
134 }
135 }
136
137 #[test]
138 fn blending_is_monotonic_in_alpha() {
139 let mut previous = 0u32;
140 for a in 0..=255u32 {
141 let p = Paint::new(Color::rgba(255, 0, 0, a as u8));
142 let red = source_over(0xFF00_0000, p.premultiplied(), p.alpha()) >> 16 & 0xFF;
143 assert!(red >= previous, "alpha {a} went backwards");
144 previous = red;
145 }
146 assert_eq!(previous, 255);
147 }
148
149 #[test]
150 fn coverage_scaling_matches_direct_alpha() {
151 for c in [0u32, 1, 64, 127, 128, 200, 254, 255] {
153 let scaled = Paint::new(Color::rgb(200, 100, 50)).scaled(c);
154 let direct = Paint::new(Color::rgba(200, 100, 50, c as u8));
155 assert_eq!(scaled.alpha(), direct.alpha(), "coverage {c}");
156 assert_eq!(
157 scaled.premultiplied(),
158 direct.premultiplied(),
159 "coverage {c}"
160 );
161 }
162 }
163}