#[inline(always)]
pub(crate) fn pack_premul(r: u8, g: u8, b: u8, a: u8) -> u32 {
((a as u32) << 24) | ((r as u32) << 16) | ((g as u32) << 8) | (b as u32)
}
#[inline(always)]
pub(crate) fn pack_opaque(r: u8, g: u8, b: u8) -> u32 {
0xff00_0000 | ((r as u32) << 16) | ((g as u32) << 8) | (b as u32)
}
#[inline]
pub(crate) fn blend_premul_over(src: [u8; 4], dst: u32) -> u32 {
let sa = src[3] as u32;
if sa == 0 {
return dst;
}
if sa == 255 {
return pack_opaque(src[0], src[1], src[2]);
}
let inv = 255 - sa;
let dr = (dst >> 16) & 0xff;
let dg = (dst >> 8) & 0xff;
let db = dst & 0xff;
let da = (dst >> 24) & 0xff;
let or = src[0] as u32 + (dr * inv + 127) / 255;
let og = src[1] as u32 + (dg * inv + 127) / 255;
let ob = src[2] as u32 + (db * inv + 127) / 255;
let oa = sa + (da * inv + 127) / 255;
pack_premul(
or.min(255) as u8,
og.min(255) as u8,
ob.min(255) as u8,
oa.min(255) as u8,
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn blend_premul_over_propagates_alpha() {
assert_eq!(pack_opaque(1, 2, 3) >> 24, 0xff);
let dst = 0xff20_4060;
for sa in 0..=255u8 {
let src = [sa / 2, sa / 3, sa, sa];
assert_eq!(blend_premul_over(src, dst) >> 24, 0xff, "sa={sa}");
}
let translucent = 0x9912_3456;
assert_eq!(blend_premul_over([0, 0, 0, 0], translucent), translucent);
let out = blend_premul_over([0x40, 0x20, 0x10, 0x80], translucent);
let expected = 0x80u32 + (0x99 * (255 - 0x80) + 127) / 255;
assert_eq!(out >> 24, expected);
}
}