use denise::Color;
const LANES: u32 = 0x00FF_00FF;
#[inline(always)]
const fn mul_lanes(x: u32, a: u32) -> u32 {
let t = x * a + 0x0080_0080;
((t + ((t >> 8) & LANES)) >> 8) & LANES
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Paint {
premul: u32,
alpha: u32,
}
impl Paint {
#[inline]
pub const fn new(color: Color) -> Self {
let a = color.a as u32;
let rb = mul_lanes(((color.r as u32) << 16) | color.b as u32, a);
let g = mul_lanes(color.g as u32, a);
Self {
premul: (a << 24) | rb | (g << 8),
alpha: a,
}
}
#[inline]
pub const fn premultiplied(self) -> u32 {
self.premul
}
#[inline]
pub const fn alpha(self) -> u32 {
self.alpha
}
#[inline]
pub const fn is_opaque(self) -> bool {
self.alpha == 255
}
#[inline]
pub const fn is_invisible(self) -> bool {
self.alpha == 0
}
#[inline]
pub const fn scaled(self, coverage: u32) -> Self {
let rb = mul_lanes(self.premul & LANES, coverage);
let ag = mul_lanes((self.premul >> 8) & LANES, coverage);
let premul = rb | (ag << 8);
Self {
premul,
alpha: premul >> 24,
}
}
}
impl From<Color> for Paint {
#[inline]
fn from(color: Color) -> Self {
Paint::new(color)
}
}
#[inline(always)]
pub const fn source_over(dst: u32, src_premul: u32, alpha: u32) -> u32 {
let inv = 255 - alpha;
let rb = mul_lanes(dst & LANES, inv);
let ag = mul_lanes((dst >> 8) & LANES, inv);
src_premul + (rb | (ag << 8))
}
#[inline]
pub fn fill_span(span: &mut [u32], word: u32) {
span.fill(word);
}
#[inline]
pub fn blend_span(span: &mut [u32], paint: Paint) {
if paint.is_invisible() {
return;
}
if paint.is_opaque() {
span.fill(paint.premultiplied());
return;
}
let src = paint.premultiplied();
let alpha = paint.alpha();
for px in span {
*px = source_over(*px, src, alpha);
}
}
#[inline]
pub fn blend_pixel(dst: &mut u32, paint: Paint, coverage: u32) {
if coverage == 0 {
return;
}
let paint = if coverage == 255 {
paint
} else {
paint.scaled(coverage)
};
*dst = source_over(*dst, paint.premultiplied(), paint.alpha());
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn opaque_paint_replaces_destination() {
let p = Paint::new(Color::rgb(10, 20, 30));
assert!(p.is_opaque());
assert_eq!(
source_over(0xFFFF_FFFF, p.premultiplied(), p.alpha()),
0xFF0A_141E
);
}
#[test]
fn transparent_paint_preserves_destination() {
let p = Paint::new(Color::rgba(10, 20, 30, 0));
assert_eq!(
source_over(0xFF12_3456, p.premultiplied(), p.alpha()),
0xFF12_3456
);
}
#[test]
fn premultiply_is_exact_at_the_endpoints() {
let c = Color::rgba(0xAB, 0xCD, 0xEF, 255);
assert_eq!(Paint::new(c).premultiplied(), 0xFFAB_CDEF);
assert_eq!(
Paint::new(Color::rgba(0xAB, 0xCD, 0xEF, 0)).premultiplied(),
0
);
}
#[test]
fn half_alpha_over_black_is_half_the_colour() {
let p = Paint::new(Color::rgba(200, 100, 50, 128));
let out = source_over(0xFF00_0000, p.premultiplied(), p.alpha());
assert_eq!(out & 0x00FF_FFFF, 0x0064_3219);
}
#[test]
fn no_lane_ever_carries_into_its_neighbour() {
for a in 0..=255u32 {
let p = Paint::new(Color::rgba(255, 255, 255, a as u8));
let out = source_over(0xFFFF_FFFF, p.premultiplied(), p.alpha());
assert_eq!(out, 0xFFFF_FFFF, "alpha {a} carried");
}
}
#[test]
fn blending_is_monotonic_in_alpha() {
let mut previous = 0u32;
for a in 0..=255u32 {
let p = Paint::new(Color::rgba(255, 0, 0, a as u8));
let red = source_over(0xFF00_0000, p.premultiplied(), p.alpha()) >> 16 & 0xFF;
assert!(red >= previous, "alpha {a} went backwards");
previous = red;
}
assert_eq!(previous, 255);
}
#[test]
fn coverage_scaling_matches_direct_alpha() {
for c in [0u32, 1, 64, 127, 128, 200, 254, 255] {
let scaled = Paint::new(Color::rgb(200, 100, 50)).scaled(c);
let direct = Paint::new(Color::rgba(200, 100, 50, c as u8));
assert_eq!(scaled.alpha(), direct.alpha(), "coverage {c}");
assert_eq!(
scaled.premultiplied(),
direct.premultiplied(),
"coverage {c}"
);
}
}
}