use crate::{Paint, Point, Scalar, Transform, Units};
use std::{
fmt,
ops::{Add, Mul},
str::FromStr,
};
pub trait Color {
fn blend_over(&self, other: &Self) -> Self;
fn with_alpha(&self, alpha: Scalar) -> Self;
fn to_rgba(&self) -> [u8; 4];
fn to_rgb(&self) -> [u8; 3]
where
Self: Sized,
{
let [r, g, b, _] = self.to_rgba();
[r, g, b]
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct ColorU8(u32);
impl ColorU8 {
pub const fn new(r: u8, g: u8, b: u8, a: u8) -> Self {
Self(((a as u32) << 24) | ((b as u32) << 16) | ((g as u32) << 8) | (r as u32))
}
pub const fn alpha(self) -> u8 {
((self.0 >> 24) & 0xff) as u8
}
pub const fn blue(self) -> u8 {
((self.0 >> 16) & 0xff) as u8
}
pub const fn green(self) -> u8 {
((self.0 >> 8) & 0xff) as u8
}
pub const fn red(self) -> u8 {
(self.0 & 0xff) as u8
}
}
impl Color for ColorU8 {
fn to_rgba(&self) -> [u8; 4] {
self.0.to_le_bytes()
}
fn blend_over(&self, _other: &Self) -> Self {
todo!()
}
fn with_alpha(&self, _alpha: Scalar) -> Self {
todo!()
}
}
impl From<LinColor> for ColorU8 {
fn from(lin: LinColor) -> Self {
let LinColor([r, g, b, a]) = lin;
if a < std::f32::EPSILON {
return ColorU8::default();
}
let r = (linear_to_srgb(r / a) * 255.0 + 0.5) as u8;
let g = (linear_to_srgb(g / a) * 255.0 + 0.5) as u8;
let b = (linear_to_srgb(b / a) * 255.0 + 0.5) as u8;
let a = (a * 255.0 + 0.5) as u8;
ColorU8::new(r, g, b, a)
}
}
impl fmt::Debug for ColorU8 {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt.debug_struct("ColorU8")
.field("r", &self.red())
.field("g", &self.green())
.field("b", &self.blue())
.field("a", &self.alpha())
.finish()
}
}
impl FromStr for ColorU8 {
type Err = ColorError;
fn from_str(color: &str) -> Result<Self, Self::Err> {
if color.starts_with('#') && (color.len() == 7 || color.len() == 9) {
let bytes: &[u8] = color[1..].as_ref();
let digit = |byte| match byte {
b'A'..=b'F' => Ok(byte - b'A' + 10),
b'a'..=b'f' => Ok(byte - b'a' + 10),
b'0'..=b'9' => Ok(byte - b'0'),
_ => Err(ColorError::HexExpected),
};
let mut hex = bytes
.chunks(2)
.map(|pair| Ok(digit(pair[0])? << 4 | digit(pair[1])?));
Ok(ColorU8::new(
hex.next().unwrap_or(Ok(0))?,
hex.next().unwrap_or(Ok(0))?,
hex.next().unwrap_or(Ok(0))?,
hex.next().unwrap_or(Ok(255))?,
))
} else {
Err(ColorError::HexExpected)
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct LinColor([f32; 4]);
impl LinColor {
pub const fn new(r: f32, g: f32, b: f32, a: f32) -> Self {
LinColor([r, g, b, a])
}
pub const fn red(&self) -> f32 {
self.0[0]
}
pub const fn green(&self) -> f32 {
self.0[1]
}
pub const fn blue(&self) -> f32 {
self.0[2]
}
pub const fn alpha(&self) -> f32 {
self.0[3]
}
pub fn lerp(self, other: LinColor, t: Scalar) -> Self {
let t = t as f32;
other * t + self * (1.0 - t)
}
pub(crate) fn into_srgb(self) -> Self {
let Self([r, g, b, a]) = self;
Self::new(
linear_to_srgb(r * a),
linear_to_srgb(g * a),
linear_to_srgb(b * a),
a,
)
}
pub(crate) fn into_linear(self) -> Self {
let Self([r, g, b, a]) = self;
Self::new(
srgb_to_linear(r / a),
srgb_to_linear(g / a),
srgb_to_linear(b / a),
a,
)
}
}
impl Color for LinColor {
fn to_rgba(&self) -> [u8; 4] {
ColorU8::from(*self).to_rgba()
}
fn blend_over(&self, other: &Self) -> Self {
*other + *self * (1.0 - other.alpha())
}
fn with_alpha(&self, alpha: Scalar) -> Self {
*self * (alpha as f32)
}
}
impl Paint for LinColor {
fn at(&self, _: Point) -> LinColor {
*self
}
fn units(&self) -> Option<Units> {
None
}
fn transform(&self) -> Transform {
Transform::identity()
}
}
impl Add<Self> for LinColor {
type Output = Self;
#[inline]
fn add(self, other: Self) -> Self::Output {
let Self([r0, g0, b0, a0]) = self;
let Self([r1, g1, b1, a1]) = other;
Self([r0 + r1, g0 + g1, b0 + b1, a0 + a1])
}
}
impl Mul<f32> for LinColor {
type Output = Self;
#[inline]
fn mul(self, scale: f32) -> Self::Output {
let Self([r, g, b, a]) = self;
Self([scale * r, scale * g, scale * b, scale * a])
}
}
impl From<ColorU8> for LinColor {
fn from(color: ColorU8) -> Self {
let a = color.alpha() as f32 / 255.0;
let r = srgb_to_linear(color.red() as f32 / 255.0) * a;
let g = srgb_to_linear(color.green() as f32 / 255.0) * a;
let b = srgb_to_linear(color.blue() as f32 / 255.0) * a;
LinColor::new(r, g, b, a)
}
}
impl FromStr for LinColor {
type Err = ColorError;
fn from_str(color: &str) -> Result<Self, Self::Err> {
Ok(ColorU8::from_str(color)?.into())
}
}
impl Color for Scalar {
fn to_rgba(&self) -> [u8; 4] {
let color = (linear_to_srgb(1.0 - (*self as f32)) * 255.0 + 0.5) as u8;
[color, color, color, 255]
}
fn blend_over(&self, other: &Self) -> Self {
other + self * (1.0 - other)
}
fn with_alpha(&self, alpha: Scalar) -> Self {
self * alpha
}
}
#[inline]
pub fn linear_to_srgb(x0: f32) -> f32 {
if x0 <= 0.0031308 {
x0 * 12.92
} else {
let x1 = x0.sqrt();
let x2 = x1.sqrt();
let x3 = x2.sqrt();
-0.01848558 * x0 + 0.6445592 * x1 + 0.70994765 * x2 - 0.33605254 * x3
}
}
#[inline]
pub fn srgb_to_linear(value: f32) -> f32 {
if value <= 0.04045 {
value / 12.92
} else {
((value + 0.055) / 1.055).powf(2.4)
}
}
#[derive(Debug, Clone)]
pub enum ColorError {
HexExpected,
}
impl fmt::Display for ColorError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ColorError::HexExpected => {
write!(f, "Color expected to be #RRGGBB(AA) in hexidemical format")
}
}
}
}
impl std::error::Error for ColorError {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_color_u8() {
let c = ColorU8::new(1, 2, 3, 4);
assert_eq!([1, 2, 3, 4], c.to_rgba());
assert_eq!(1, c.red());
assert_eq!(2, c.green());
assert_eq!(3, c.blue());
assert_eq!(4, c.alpha());
}
#[test]
fn test_color_u8_parse() -> Result<(), ColorError> {
assert_eq!(ColorU8::new(1, 2, 3, 4), "#01020304".parse::<ColorU8>()?);
assert_eq!(
ColorU8::new(170, 187, 204, 255),
"#aabbcc".parse::<ColorU8>()?
);
assert_eq!(ColorU8::new(0, 0, 0, 255), "#000000".parse::<ColorU8>()?);
Ok(())
}
}