use crate::space::Srgb;
#[must_use]
#[allow(clippy::suboptimal_flops)]
pub fn alpha_over(dst: Srgb, dst_alpha: f32, src: Srgb, src_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)
}
#[must_use]
pub fn premultiply(color: Srgb, alpha: f32) -> [f32; 4] {
[color.r * alpha, color.g * alpha, color.b * alpha, alpha]
}
#[must_use]
pub fn unpremultiply([r, g, b, a]: [f32; 4]) -> (Srgb, f32) {
if a < f32::EPSILON {
return (Srgb::BLACK, 0.0);
}
let inv = 1.0 / a;
(Srgb::new(r * inv, g * inv, b * inv), a)
}
#[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::BLUE, 1.0, Srgb::RED, 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::BLUE, 1.0, Srgb::RED, 0.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::BLUE, 1.0, Srgb::RED, 0.5);
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::BLUE, 0.0, Srgb::RED, 0.0);
assert_eq!(a, 0.0);
assert_eq!(out, Srgb::BLACK);
}
#[test]
fn premultiply_half() {
let [r, g, b, a] = premultiply(Srgb::RED, 0.5);
assert!((r - 0.5).abs() < 1e-5);
assert!(g.abs() < 1e-5);
assert!(b.abs() < 1e-5);
assert!((a - 0.5).abs() < 1e-5);
}
#[test]
fn premultiply_unpremultiply_roundtrip() {
let pre = premultiply(Srgb::new(0.8, 0.4, 0.2), 0.75);
let (color, alpha) = unpremultiply(pre);
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 unpremultiply_zero_alpha() {
let (color, alpha) = unpremultiply([0.5, 0.5, 0.5, 0.0]);
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);
}
}