#![allow(clippy::cast_possible_truncation)]
#![allow(clippy::cast_sign_loss)]
#[allow(clippy::cast_possible_truncation)]
pub(super) fn composite_over(base: &mut [u8], overlay: &[u8]) {
if base.len() != overlay.len() {
return;
}
for (b, o) in base.chunks_exact_mut(4).zip(overlay.chunks_exact(4)) {
let a = f32::from(o[3]) / 255.0;
if a > 0.0 {
let ia = 1.0_f32 - a;
b[0] = (f32::from(b[0]) * ia + f32::from(o[0]) * a) as u8;
b[1] = (f32::from(b[1]) * ia + f32::from(o[1]) * a) as u8;
b[2] = (f32::from(b[2]) * ia + f32::from(o[2]) * a) as u8;
b[3] = 255;
}
}
}
pub(super) fn blend_rgba(a: &[u8], b: &[u8], alpha: f32, dst: &mut Vec<u8>) {
if a.len() != b.len() {
dst.resize(a.len(), 0);
dst.copy_from_slice(a);
return;
}
dst.resize(a.len(), 0);
let inv = 1.0_f32 - alpha;
for ((d, av), bv) in dst.iter_mut().zip(a.iter()).zip(b.iter()) {
*d = (f32::from(*av) * inv + f32::from(*bv) * alpha) as u8;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn blend_rgba_at_zero_alpha_should_return_a() {
let a = vec![200u8, 100, 50, 255];
let b = vec![0u8, 0, 0, 255];
let mut dst = Vec::new();
blend_rgba(&a, &b, 0.0, &mut dst);
assert_eq!(dst, a);
}
#[test]
fn blend_rgba_at_full_alpha_should_return_b() {
let a = vec![0u8, 0, 0, 255];
let b = vec![200u8, 100, 50, 255];
let mut dst = Vec::new();
blend_rgba(&a, &b, 1.0, &mut dst);
assert_eq!(dst, b);
}
#[test]
fn blend_rgba_at_half_alpha_should_average() {
let a = vec![100u8, 200, 0, 255];
let b = vec![200u8, 0, 100, 255];
let mut dst = Vec::new();
blend_rgba(&a, &b, 0.5, &mut dst);
assert_eq!(dst[0], 150);
assert_eq!(dst[1], 100);
}
#[test]
fn blend_rgba_mismatched_lengths_should_copy_a() {
let a = vec![1u8, 2, 3, 4];
let b = vec![5u8, 6];
let mut dst = Vec::new();
blend_rgba(&a, &b, 0.5, &mut dst);
assert_eq!(dst, a);
}
}