use std::fmt;
use usvgr::svgtree::SvgAttributeValue;
use crate::animation;
#[derive(Copy, Default, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Color {
pub r: u8,
pub g: u8,
pub b: u8,
}
impl From<Color> for SvgAttributeValue<'static> {
fn from(color: Color) -> Self {
SvgAttributeValue::Color(usvgr::svgtree::svgrtypes::Color::new_rgb(
color.r, color.g, color.b,
))
}
}
const HASH: u8 = b'#';
pub const fn char_to_digit(char: char, radix: u32) -> u32 {
let mut digit = (char as u32).wrapping_sub('0' as u32);
if radix > 10 {
assert!(radix <= 36, "to_digit: radix is too high (maximum 36)");
if digit < 10 {
return digit;
}
digit = (char as u32 | 0b10_0000)
.wrapping_sub('a' as u32)
.saturating_add(10);
}
if digit < radix {
digit
} else {
panic!("can not parse char in a radix string");
}
}
impl Color {
pub const BLACK: Color = Color { r: 0, g: 0, b: 0 };
pub const WHITE: Color = Color {
r: 255,
g: 255,
b: 255,
};
pub const fn hex_num(num: u32) -> Self {
if num <= 0xfff {
let r = ((num >> 8) & 0xF) as u8;
let g = ((num >> 4) & 0xF) as u8;
let b = (num & 0xF) as u8;
Color {
r: (r * 16 + r),
g: (g * 16 + g),
b: (b * 16 + b),
}
} else {
Color {
r: ((num >> 16) & 0xFF) as u8,
g: ((num >> 8) & 0xFF) as u8,
b: (num & 0xFF) as u8,
}
}
}
pub const fn hex(hex_str: &str) -> Self {
let buffer = hex_str.as_bytes();
if buffer[0] != HASH {
return Color::BLACK;
}
match buffer.len() {
4 => {
let r = char_to_digit(buffer[1] as char, 16) as u8;
let g = char_to_digit(buffer[2] as char, 16) as u8;
let b = char_to_digit(buffer[3] as char, 16) as u8;
Color {
r: (r + r * 16),
g: (g + g * 16),
b: (b + b * 16),
}
}
7 => {
let r = char_to_digit(buffer[1] as char, 16) as u8;
let r1 = char_to_digit(buffer[2] as char, 16) as u8;
let g = char_to_digit(buffer[3] as char, 16) as u8;
let g1 = char_to_digit(buffer[4] as char, 16) as u8;
let b = char_to_digit(buffer[5] as char, 16) as u8;
let b1 = char_to_digit(buffer[6] as char, 16) as u8;
Color {
r: (r * 16 + r1),
g: (g * 16 + g1),
b: (b * 16 + b1),
}
}
_ => Color::BLACK,
}
}
pub const fn rgb(r: u8, g: u8, b: u8) -> Self {
Color { r, g, b }
}
}
struct ColorF32 {
r: f32,
g: f32,
b: f32,
}
impl From<Color> for ColorF32 {
fn from(Color { r, g, b }: Color) -> Self {
ColorF32 {
r: r as f32,
g: g as f32,
b: b as f32,
}
}
}
impl animation::Animatable for Color {
fn apply_progress(&self, to: &Self, progress: f32) -> Self {
let from = ColorF32::from(*self);
let to = ColorF32::from(*to);
Color {
r: (from.r + ((to.r - from.r) * progress)) as u8,
g: (from.g + ((to.g - from.g) * progress)) as u8,
b: (from.b + ((to.b - from.b) * progress)) as u8,
}
}
}
impl fmt::Display for Color {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
let Color { r, g, b } = self;
write!(formatter, "rgb({r}, {g}, {b})",)
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
pub fn char_to_digit_test() {
assert_eq!(char_to_digit('f', 16), 15);
assert_eq!(char_to_digit('F', 16), 15);
assert_eq!(char_to_digit('9', 16), 9);
}
#[test]
pub fn hex_parsing() {
assert_eq!(Color::hex("#f0f"), Color::rgb(255, 0, 255));
assert_eq!(Color::hex("#ff00ff"), Color::rgb(255, 0, 255));
assert_eq!(Color::hex("#ef4444"), Color::rgb(239, 68, 68));
assert_eq!(Color::hex("#1c1917"), Color::rgb(28, 25, 23));
}
#[test]
pub fn hex_num_parsing() {
assert_eq!(Color::hex_num(0xf0f), Color::rgb(255, 0, 255));
assert_eq!(Color::hex_num(0xff00ff), Color::rgb(255, 0, 255));
assert_eq!(Color::hex_num(0xef4444), Color::rgb(239, 68, 68));
assert_eq!(Color::hex_num(0x1c1917), Color::rgb(28, 25, 23));
}
}