use crate::space::Srgb;
#[must_use]
#[allow(clippy::suboptimal_flops)]
pub fn alpha_over(src: Srgb, src_alpha: f32, dst: Srgb, dst_alpha: f32) -> (Srgb, f32) {
let out_alpha = src_alpha + dst_alpha * (1.0 - src_alpha);
if out_alpha < f32::EPSILON {
return (Srgb::BLACK, 0.0);
}
let inv = 1.0 / out_alpha;
let w_src = src_alpha * inv;
let w_dst = dst_alpha * (1.0 - src_alpha) * inv;
let out = Srgb {
r: src.r * w_src + dst.r * w_dst,
g: src.g * w_src + dst.g * w_dst,
b: src.b * w_src + dst.b * w_dst,
};
(out, out_alpha)
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct Premultiplied {
pub color: Srgb,
pub alpha: f32,
}
impl Premultiplied {
#[must_use]
pub fn new(color: Srgb, alpha: f32) -> Self {
Self {
color: Srgb::new(color.r * alpha, color.g * alpha, color.b * alpha),
alpha,
}
}
#[must_use]
pub fn straight(self) -> (Srgb, f32) {
if self.alpha < f32::EPSILON {
return (Srgb::BLACK, 0.0);
}
let inv = 1.0 / self.alpha;
(
Srgb::new(self.color.r * inv, self.color.g * inv, self.color.b * inv),
self.alpha,
)
}
}
#[must_use]
#[allow(clippy::suboptimal_flops)]
pub fn lerp(ca: Srgb, aa: f32, cb: Srgb, ab: f32, t: f32) -> (Srgb, f32) {
(ca.lerp(cb, t), aa + (ab - aa) * t)
}
#[cfg(test)]
#[allow(clippy::float_cmp)]
mod tests {
use super::*;
#[test]
fn alpha_over_opaque_src_covers_dst() {
let (out, a) = alpha_over(Srgb::RED, 1.0, Srgb::BLUE, 1.0);
assert!((out.r - 1.0).abs() < 1e-5);
assert!(out.b.abs() < 1e-5);
assert!((a - 1.0).abs() < 1e-5);
}
#[test]
fn alpha_over_transparent_src_shows_dst() {
let (out, a) = alpha_over(Srgb::RED, 0.0, Srgb::BLUE, 1.0);
assert!((out.b - 1.0).abs() < 1e-5);
assert!(out.r.abs() < 1e-5);
assert!((a - 1.0).abs() < 1e-5);
}
#[test]
fn alpha_over_half_alpha_blends() {
let (out, out_a) = alpha_over(Srgb::RED, 0.5, Srgb::BLUE, 1.0);
assert!((out_a - 1.0).abs() < 1e-5);
assert!((out.r - 0.5).abs() < 1e-5, "r={}", out.r);
assert!((out.b - 0.5).abs() < 1e-5, "b={}", out.b);
}
#[test]
fn alpha_over_both_transparent_returns_black() {
let (out, a) = alpha_over(Srgb::RED, 0.0, Srgb::BLUE, 0.0);
assert_eq!(a, 0.0);
assert_eq!(out, Srgb::BLACK);
}
#[test]
fn premultiply_half() {
let pre = Premultiplied::new(Srgb::RED, 0.5);
assert!((pre.color.r - 0.5).abs() < 1e-5);
assert!(pre.color.g.abs() < 1e-5);
assert!(pre.color.b.abs() < 1e-5);
assert!((pre.alpha - 0.5).abs() < 1e-5);
}
#[test]
fn premultiply_straight_roundtrip() {
let pre = Premultiplied::new(Srgb::new(0.8, 0.4, 0.2), 0.75);
let (color, alpha) = pre.straight();
assert!((color.r - 0.8).abs() < 1e-5);
assert!((color.g - 0.4).abs() < 1e-5);
assert!((color.b - 0.2).abs() < 1e-5);
assert!((alpha - 0.75).abs() < 1e-5);
}
#[test]
fn straight_zero_alpha() {
let pre = Premultiplied {
color: Srgb::new(0.5, 0.5, 0.5),
alpha: 0.0,
};
let (color, alpha) = pre.straight();
assert_eq!(alpha, 0.0);
assert_eq!(color, Srgb::BLACK);
}
#[test]
fn lerp_colors() {
let (c, a) = lerp(Srgb::RED, 1.0, Srgb::BLUE, 0.0, 0.5);
assert!((c.r - 0.5).abs() < 1e-5);
assert!((c.b - 0.5).abs() < 1e-5);
assert!((a - 0.5).abs() < 1e-5);
}
}