use crate::{simd::f32x4, Paint, Point, Scalar, Transform, Units};
use std::{
fmt,
ops::{Add, Mul},
str::FromStr,
};
pub trait Color: Copy {
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] {
let [r, g, b, _] = self.to_rgba();
[r, g, b]
}
fn luma(self) -> f32 {
let [r, g, b] = self.to_rgb();
0.2126 * (r as f32 / 255.0) + 0.7152 * (g as f32 / 255.0) + 0.0722 * (b as f32 / 255.0)
}
fn best_contrast(self, c0: Self, c1: Self) -> Self {
let luma = self.luma();
if (luma - c0.luma()).abs() < (luma - c1.luma()).abs() {
c1
} else {
c0
}
}
fn lerp(self, other: Self, t: f32) -> Self;
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
pub struct RGBA([u8; 4]);
impl RGBA {
pub const fn new(r: u8, g: u8, b: u8, a: u8) -> Self {
Self([r, g, b, a])
}
pub const fn red(self) -> u8 {
self.0[0]
}
pub const fn green(self) -> u8 {
self.0[1]
}
pub const fn blue(self) -> u8 {
self.0[2]
}
pub const fn alpha(self) -> u8 {
self.0[3]
}
}
impl Color for RGBA {
fn to_rgba(self) -> [u8; 4] {
self.0
}
fn blend_over(self, other: Self) -> Self {
LinColor::from(self)
.blend_over(LinColor::from(other))
.into()
}
fn with_alpha(self, alpha: Scalar) -> Self {
LinColor::from(self).with_alpha(alpha).into()
}
fn lerp(self, other: Self, t: f32) -> Self {
LinColor::from(self).lerp(LinColor::from(other), t).into()
}
}
impl From<LinColor> for RGBA {
#[inline(always)]
fn from(lin: LinColor) -> Self {
let [r, g, b, _]: [f32; 4] = crate::simd::l2s(lin.unmultiply()).into();
RGBA::new(
(r * 255.0 + 0.5) as u8,
(g * 255.0 + 0.5) as u8,
(b * 255.0 + 0.5) as u8,
(lin.alpha() * 255.0 + 0.5) as u8,
)
}
}
impl From<[u8; 4]> for RGBA {
#[inline]
fn from(rgba: [u8; 4]) -> Self {
RGBA(rgba)
}
}
impl From<[u8; 3]> for RGBA {
#[inline]
fn from([r, g, b]: [u8; 3]) -> Self {
RGBA::new(r, g, b, 255)
}
}
impl fmt::Debug for RGBA {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
let [bg_r, bg_g, bg_b] = self.to_rgb();
let [fg_r, fg_g, fg_b] = self
.best_contrast(RGBA::new(255, 255, 255, 255), RGBA::new(0, 0, 0, 255))
.to_rgb();
write!(
fmt,
"\x1b[38;2;{};{};{};48;2;{};{};{}m",
fg_r, fg_g, fg_b, bg_r, bg_g, bg_b
)?;
write!(fmt, "{}", self)?;
write!(fmt, "\x1b[m")
}
}
impl fmt::Display for RGBA {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let [r, g, b, a] = self.to_rgba();
write!(f, "#{:02x}{:02x}{:02x}", r, g, b)?;
if a != 255 {
write!(f, "{:02x}", a)?;
}
Ok(())
}
}
impl FromStr for RGBA {
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(RGBA::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(crate::simd::f32x4);
impl LinColor {
#[inline(always)]
pub fn new(r: f32, g: f32, b: f32, a: f32) -> Self {
LinColor(f32x4::new(r, g, b, a))
}
#[inline(always)]
pub fn red(self) -> f32 {
self.0.x0()
}
#[inline(always)]
pub fn green(self) -> f32 {
self.0.x1()
}
#[inline(always)]
pub fn blue(self) -> f32 {
self.0.x2()
}
#[inline(always)]
pub fn alpha(self) -> f32 {
self.0.x3()
}
#[inline(always)]
pub fn distance(self, other: Self) -> f32 {
let diff = self.unmultiply() - other.unmultiply();
diff.dot(diff).sqrt()
}
#[inline(always)]
pub fn unmultiply(self) -> f32x4 {
let alpha = self.alpha();
if alpha <= 1e-6 {
f32x4::zero()
} else {
self.0 / f32x4::splat(alpha)
}
}
#[inline(always)]
pub(crate) fn into_srgb(self) -> Self {
Self(crate::simd::l2s(self.unmultiply()) * f32x4::splat(self.alpha()))
}
#[inline(always)]
pub(crate) fn into_linear(self) -> Self {
Self(crate::simd::s2l(self.unmultiply()) * f32x4::splat(self.alpha()))
}
}
impl Color for LinColor {
#[inline(always)]
fn to_rgba(self) -> [u8; 4] {
RGBA::from(self).to_rgba()
}
#[inline(always)]
fn blend_over(self, other: Self) -> Self {
other + self * (1.0 - other.alpha())
}
#[inline(always)]
fn with_alpha(self, alpha: Scalar) -> Self {
self * (alpha as f32)
}
#[inline(always)]
fn lerp(self, other: Self, t: f32) -> Self {
other * t + self * (1.0 - t)
}
}
impl Paint for LinColor {
fn at(&self, _: Point) -> LinColor {
*self
}
fn units(&self) -> Option<Units> {
None
}
fn transform(&self) -> Transform {
Transform::identity()
}
#[cfg(feature = "serde")]
fn to_json(&self) -> Result<serde_json::Value, crate::SvgParserError> {
Ok(serde_json::Value::String(self.to_string()))
}
}
impl Add<Self> for LinColor {
type Output = Self;
#[inline(always)]
fn add(self, other: Self) -> Self::Output {
Self(self.0 + other.0)
}
}
impl Mul<f32> for LinColor {
type Output = Self;
#[inline(always)]
fn mul(self, scale: f32) -> Self::Output {
Self(self.0 * scale)
}
}
impl From<RGBA> for LinColor {
fn from(color: RGBA) -> 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 From<LinColor> for [f32; 4] {
fn from(color: LinColor) -> Self {
color.0.into()
}
}
impl FromStr for LinColor {
type Err = ColorError;
fn from_str(color: &str) -> Result<Self, Self::Err> {
Ok(RGBA::from_str(color)?.into())
}
}
impl fmt::Display for LinColor {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
RGBA::from(*self).fmt(f)
}
}
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
}
fn lerp(self, other: Self, t: f32) -> Self {
let t = t as Scalar;
self * (1.0 - t) + other * t
}
}
#[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::*;
use crate::assert_approx_eq;
#[test]
fn test_color_u8() {
let c = RGBA::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!(RGBA::new(1, 2, 3, 4), "#01020304".parse::<RGBA>()?);
assert_eq!(RGBA::new(170, 187, 204, 255), "#aabbcc".parse::<RGBA>()?);
assert_eq!(RGBA::new(0, 0, 0, 255), "#000000".parse::<RGBA>()?);
Ok(())
}
#[test]
fn test_conversion() -> Result<(), ColorError> {
let c: RGBA = "#ff804010".parse()?;
let l: LinColor = c.into();
let r: RGBA = l.into();
assert_eq!(c, r);
Ok(())
}
#[test]
fn test_lin_and_srgb() {
for i in 0..255 {
let v = i as f32 / 255.0;
assert_approx_eq!(v, linear_to_srgb(srgb_to_linear(v)), 1e-4);
assert_approx_eq!(v, srgb_to_linear(linear_to_srgb(v)), 1e-4);
}
}
#[test]
fn test_display_parse() -> Result<(), ColorError> {
let c: RGBA = "#01020304".parse()?;
assert_eq!(c, RGBA::new(1, 2, 3, 4));
assert_eq!(c.to_string(), "#01020304");
let c: RGBA = "#010203".parse()?;
assert_eq!(c, RGBA::new(1, 2, 3, 255));
assert_eq!(c.to_string(), "#010203");
Ok(())
}
}