#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(from = "String", into = "String"))]
pub struct Color {
r: u8,
g: u8,
b: u8,
a: u8,
}
impl From<String> for Color {
fn from(value: String) -> Self {
Self::try_from_hex(&value).unwrap_or(Color::rgba(0, 0, 0, 255))
}
}
impl From<Color> for String {
fn from(val: Color) -> Self {
val.to_hex()
}
}
impl Color {
pub const TRANSPARENT: Color = Color::rgba(0, 0, 0, 0);
pub const RED: Color = Color::rgba(255, 0, 0, 255);
pub const GREEN: Color = Color::rgba(0, 255, 0, 255);
pub const BLUE: Color = Color::rgba(0, 0, 255, 255);
pub const WHITE: Color = Color::rgba(255, 255, 255, 255);
pub const BLACK: Color = Color::rgba(0, 0, 0, 255);
pub const GRAY: Color = Color::rgba(170, 170, 170, 255);
pub const PURPLE: Color = Color::rgba(128, 0, 128, 255);
pub const fn rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
Self { r, g, b, a }
}
pub fn to_f32_array(&self) -> [f32; 4] {
[
self.r as f32 / 255.0,
self.g as f32 / 255.0,
self.b as f32 / 255.0,
self.a as f32 / 255.0,
]
}
pub fn to_u8_array(&self) -> [u8; 4] {
[self.r, self.g, self.b, self.a]
}
pub fn to_hex(&self) -> String {
format!("#{:02X}{:02X}{:02X}{:02X}", self.r, self.g, self.b, self.a)
}
pub fn try_from_hex(hex_string: &str) -> Option<Self> {
if hex_string.len() != 7 && hex_string.len() != 9 || hex_string.chars().next()? != '#' {
return None;
}
let r = u8::from_str_radix(&hex_string[1..3], 16).ok()?;
let g = u8::from_str_radix(&hex_string[3..5], 16).ok()?;
let b = u8::from_str_radix(&hex_string[5..7], 16).ok()?;
let a = if hex_string.len() == 9 {
u8::from_str_radix(&hex_string[7..9], 16).ok()?
} else {
255
};
Some(Self { r, g, b, a })
}
pub const fn from_hex(hex_string: &'static str) -> Self {
let bytes = hex_string.as_bytes();
if bytes.len() != 7 && bytes.len() != 9 || bytes[0] != b'#' {
panic!("Invalid color hex string");
}
let r = decode_byte(&[bytes[1], bytes[2]]);
let g = decode_byte(&[bytes[3], bytes[4]]);
let b = decode_byte(&[bytes[5], bytes[6]]);
let a = if hex_string.len() == 9 {
decode_byte(&[bytes[7], bytes[8]])
} else {
255
};
Self { r, g, b, a }
}
pub fn with_alpha(&self, a: u8) -> Self {
Self { a, ..*self }
}
pub fn is_transparent(&self) -> bool {
self.a == 0
}
pub fn r(&self) -> u8 {
self.r
}
pub fn g(&self) -> u8 {
self.g
}
pub fn b(&self) -> u8 {
self.b
}
pub fn a(&self) -> u8 {
self.a
}
pub fn blend(&self, fore: Color) -> Color {
let back_r = self.r as f32 / 255.0;
let back_g = self.g as f32 / 255.0;
let back_b = self.b as f32 / 255.0;
let fore_r = fore.r as f32 / 255.0;
let fore_g = fore.g as f32 / 255.0;
let fore_b = fore.b as f32 / 255.0;
let a = fore.a as f32 / 255.0;
Color {
r: ((back_r * (1.0 - a) + fore_r * a) * 255.0) as u8,
g: ((back_g * (1.0 - a) + fore_g * a) * 255.0) as u8,
b: ((back_b * (1.0 - a) + fore_b * a) * 255.0) as u8,
a: self.a,
}
}
}
const fn decode_byte(chars: &[u8]) -> u8 {
debug_assert!(chars.len() == 2);
let first = decode_char(chars[0]);
let second = decode_char(chars[1]);
first * 16 + second
}
const fn decode_char(byte: u8) -> u8 {
match byte {
b'0'..=b'9' => byte - b'0',
b'a'..=b'f' => byte - b'a' + 10,
b'A'..=b'F' => byte - b'A' + 10,
_ => panic!("Invalid hex character"),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn color_serialization() {
let hex = "#FF1000AA";
let color = Color::try_from_hex(hex).unwrap();
assert_eq!(&color.to_hex(), hex);
assert_eq!(Color::from_hex(hex), color);
}
}