1use denise::Color;
9
10const LANES: u32 = 0x00FF_00FF;
12
13#[inline(always)]
21const fn mul_lanes(x: u32, a: u32) -> u32 {
22 let t = x * a + 0x0080_0080;
23 ((t + ((t >> 8) & LANES)) >> 8) & LANES
24}
25
26#[derive(Clone, Copy, Debug, PartialEq, Eq)]
31pub struct Paint {
32 premul: u32,
34 alpha: u32,
36}
37
38impl Paint {
39 #[inline]
41 pub const fn new(color: Color) -> Self {
42 let a = color.a as u32;
43 let rb = mul_lanes(((color.r as u32) << 16) | color.b as u32, a);
44 let g = mul_lanes(color.g as u32, a);
45 Self {
46 premul: (a << 24) | rb | (g << 8),
47 alpha: a,
48 }
49 }
50
51 #[inline]
53 pub const fn premultiplied(self) -> u32 {
54 self.premul
55 }
56
57 #[inline]
59 pub const fn alpha(self) -> u32 {
60 self.alpha
61 }
62
63 #[inline]
65 pub const fn is_opaque(self) -> bool {
66 self.alpha == 255
67 }
68
69 #[inline]
71 pub const fn is_invisible(self) -> bool {
72 self.alpha == 0
73 }
74
75 #[inline]
77 pub const fn scaled(self, coverage: u32) -> Self {
78 let premul = scale_premul(self.premul, coverage);
79 Self {
80 premul,
81 alpha: premul >> 24,
82 }
83 }
84}
85
86impl From<Color> for Paint {
87 #[inline]
88 fn from(color: Color) -> Self {
89 Paint::new(color)
90 }
91}
92
93#[inline(always)]
96pub(crate) const fn scale_premul(px: u32, coverage: u32) -> u32 {
97 let rb = mul_lanes(px & LANES, coverage);
98 let ag = mul_lanes((px >> 8) & LANES, coverage);
99 rb | (ag << 8)
100}
101
102pub fn premultiply(pixels: &mut [u32]) {
109 for px in pixels {
110 match *px >> 24 {
111 255 => {}
112 0 => *px = 0,
113 a => {
114 let rb = mul_lanes(*px & LANES, a);
115 let g = mul_lanes((*px >> 8) & 0xFF, a);
116 *px = (a << 24) | rb | (g << 8);
117 }
118 }
119 }
120}
121
122#[inline(always)]
127pub const fn source_over(dst: u32, src_premul: u32, alpha: u32) -> u32 {
128 let inv = 255 - alpha;
129 let rb = mul_lanes(dst & LANES, inv);
130 let ag = mul_lanes((dst >> 8) & LANES, inv);
131 src_premul + (rb | (ag << 8))
134}
135
136#[inline]
138pub fn fill_span(span: &mut [u32], word: u32) {
139 span.fill(word);
140}
141
142#[inline]
144pub fn blend_span(span: &mut [u32], paint: Paint) {
145 if paint.is_invisible() {
146 return;
147 }
148 if paint.is_opaque() {
149 span.fill(paint.premultiplied());
150 return;
151 }
152 let src = paint.premultiplied();
153 let alpha = paint.alpha();
154 for px in span {
155 *px = source_over(*px, src, alpha);
156 }
157}
158
159#[inline]
161pub fn blend_pixel(dst: &mut u32, paint: Paint, coverage: u32) {
162 if coverage == 0 {
163 return;
164 }
165 let paint = if coverage == 255 {
166 paint
167 } else {
168 paint.scaled(coverage)
169 };
170 *dst = source_over(*dst, paint.premultiplied(), paint.alpha());
171}
172
173#[cfg(test)]
174mod tests {
175 use super::*;
176
177 #[test]
178 fn opaque_paint_replaces_destination() {
179 let p = Paint::new(Color::rgb(10, 20, 30));
180 assert!(p.is_opaque());
181 assert_eq!(
182 source_over(0xFFFF_FFFF, p.premultiplied(), p.alpha()),
183 0xFF0A_141E
184 );
185 }
186
187 #[test]
188 fn transparent_paint_preserves_destination() {
189 let p = Paint::new(Color::rgba(10, 20, 30, 0));
190 assert_eq!(
191 source_over(0xFF12_3456, p.premultiplied(), p.alpha()),
192 0xFF12_3456
193 );
194 }
195
196 #[test]
197 fn premultiply_is_exact_at_the_endpoints() {
198 let c = Color::rgba(0xAB, 0xCD, 0xEF, 255);
200 assert_eq!(Paint::new(c).premultiplied(), 0xFFAB_CDEF);
201 assert_eq!(
202 Paint::new(Color::rgba(0xAB, 0xCD, 0xEF, 0)).premultiplied(),
203 0
204 );
205 }
206
207 #[test]
208 fn half_alpha_over_black_is_half_the_colour() {
209 let p = Paint::new(Color::rgba(200, 100, 50, 128));
210 let out = source_over(0xFF00_0000, p.premultiplied(), p.alpha());
211 assert_eq!(out & 0x00FF_FFFF, 0x0064_3219);
213 }
214
215 #[test]
216 fn no_lane_ever_carries_into_its_neighbour() {
217 for a in 0..=255u32 {
220 let p = Paint::new(Color::rgba(255, 255, 255, a as u8));
221 let out = source_over(0xFFFF_FFFF, p.premultiplied(), p.alpha());
222 assert_eq!(out, 0xFFFF_FFFF, "alpha {a} carried");
223 }
224 }
225
226 #[test]
227 fn blending_is_monotonic_in_alpha() {
228 let mut previous = 0u32;
229 for a in 0..=255u32 {
230 let p = Paint::new(Color::rgba(255, 0, 0, a as u8));
231 let red = source_over(0xFF00_0000, p.premultiplied(), p.alpha()) >> 16 & 0xFF;
232 assert!(red >= previous, "alpha {a} went backwards");
233 previous = red;
234 }
235 assert_eq!(previous, 255);
236 }
237
238 #[test]
239 fn coverage_scaling_matches_direct_alpha() {
240 for c in [0u32, 1, 64, 127, 128, 200, 254, 255] {
242 let scaled = Paint::new(Color::rgb(200, 100, 50)).scaled(c);
243 let direct = Paint::new(Color::rgba(200, 100, 50, c as u8));
244 assert_eq!(scaled.alpha(), direct.alpha(), "coverage {c}");
245 assert_eq!(
246 scaled.premultiplied(),
247 direct.premultiplied(),
248 "coverage {c}"
249 );
250 }
251 }
252}