#[inline]
fn clamp01(v: f32) -> f32 {
v.clamp(0.0, 1.0)
}
#[inline]
fn to_u8(v: f32) -> u8 {
(clamp01(v) * 255.0 + 0.5) as u8
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Color {
r: f32,
g: f32,
b: f32,
a: f32,
}
impl Color {
pub const TRANSPARENT: Color = Color { r: 0.0, g: 0.0, b: 0.0, a: 0.0 };
pub const BLACK: Color = Color { r: 0.0, g: 0.0, b: 0.0, a: 1.0 };
pub const WHITE: Color = Color { r: 1.0, g: 1.0, b: 1.0, a: 1.0 };
pub fn from_rgba(r: f32, g: f32, b: f32, a: f32) -> Color {
Color { r: clamp01(r), g: clamp01(g), b: clamp01(b), a: clamp01(a) }
}
pub fn from_rgba8(r: u8, g: u8, b: u8, a: u8) -> Color {
Color {
r: r as f32 / 255.0,
g: g as f32 / 255.0,
b: b as f32 / 255.0,
a: a as f32 / 255.0,
}
}
#[inline]
pub fn red(&self) -> f32 {
self.r
}
#[inline]
pub fn green(&self) -> f32 {
self.g
}
#[inline]
pub fn blue(&self) -> f32 {
self.b
}
#[inline]
pub fn alpha(&self) -> f32 {
self.a
}
#[inline]
pub fn is_opaque(&self) -> bool {
self.a >= 1.0
}
pub fn set_alpha(&mut self, a: f32) {
self.a = clamp01(a);
}
#[inline]
pub fn premultiply(&self) -> PremultipliedColor {
PremultipliedColor { r: self.r * self.a, g: self.g * self.a, b: self.b * self.a, a: self.a }
}
pub fn to_color_u8(&self) -> ColorU8 {
ColorU8 { r: to_u8(self.r), g: to_u8(self.g), b: to_u8(self.b), a: to_u8(self.a) }
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct ColorU8 {
r: u8,
g: u8,
b: u8,
a: u8,
}
impl ColorU8 {
pub const fn from_rgba(r: u8, g: u8, b: u8, a: u8) -> ColorU8 {
ColorU8 { r, g, b, a }
}
#[inline]
pub fn red(&self) -> u8 {
self.r
}
#[inline]
pub fn green(&self) -> u8 {
self.g
}
#[inline]
pub fn blue(&self) -> u8 {
self.b
}
#[inline]
pub fn alpha(&self) -> u8 {
self.a
}
pub fn to_color(self) -> Color {
Color::from_rgba8(self.r, self.g, self.b, self.a)
}
pub fn premultiply(self) -> PremultipliedColorU8 {
let a = self.a as u16;
let m = |c: u8| ((c as u16 * a + 127) / 255) as u8;
PremultipliedColorU8 { r: m(self.r), g: m(self.g), b: m(self.b), a: self.a }
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct PremultipliedColor {
pub r: f32,
pub g: f32,
pub b: f32,
pub a: f32,
}
impl PremultipliedColor {
pub fn to_color_u8(&self) -> PremultipliedColorU8 {
let a = clamp01(self.a);
PremultipliedColorU8 {
r: to_u8(self.r.min(a)),
g: to_u8(self.g.min(a)),
b: to_u8(self.b.min(a)),
a: to_u8(a),
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct PremultipliedColorU8 {
r: u8,
g: u8,
b: u8,
a: u8,
}
impl PremultipliedColorU8 {
pub const TRANSPARENT: PremultipliedColorU8 = PremultipliedColorU8 { r: 0, g: 0, b: 0, a: 0 };
pub const fn from_rgba_unchecked(r: u8, g: u8, b: u8, a: u8) -> PremultipliedColorU8 {
PremultipliedColorU8 { r, g, b, a }
}
#[inline]
pub fn red(&self) -> u8 {
self.r
}
#[inline]
pub fn green(&self) -> u8 {
self.g
}
#[inline]
pub fn blue(&self) -> u8 {
self.b
}
#[inline]
pub fn alpha(&self) -> u8 {
self.a
}
pub fn demultiply(&self) -> ColorU8 {
if self.a == 0 {
ColorU8 { r: 0, g: 0, b: 0, a: 0 }
} else {
let a = self.a as u16;
let d = |c: u8| (((c as u16) * 255 + a / 2) / a).min(255) as u8;
ColorU8 { r: d(self.r), g: d(self.g), b: d(self.b), a: self.a }
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn premultiply_roundtrip() {
let c = ColorU8::from_rgba(200, 100, 50, 128);
let p = c.premultiply();
let back = p.demultiply();
assert!((back.red() as i32 - 200).abs() <= 2);
assert!((back.green() as i32 - 100).abs() <= 2);
assert_eq!(back.alpha(), 128);
}
#[test]
fn opaque_premultiply_identity() {
let c = Color::from_rgba8(10, 20, 30, 255).premultiply().to_color_u8();
assert_eq!(c, PremultipliedColorU8::from_rgba_unchecked(10, 20, 30, 255));
}
}