use hex::FromHexError;
use std::{fmt::Display, io::Error};
#[derive(Clone)]
pub enum Color {
RGB(u8, u8, u8),
RGBA(u8, u8, u8, u8),
PALETTE(u8),
}
impl Default for Color {
fn default() -> Self {
Color::rgb(0, 0, 0)
}
}
impl Color {
pub fn rgb(r: u8, g: u8, b: u8) -> Self {
Color::RGB(r, g, b)
}
pub fn rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
Color::RGBA(r, g, b, a)
}
pub fn from_color_string(string: &str) -> Result<Self, Error> {
let parsed: String = string.chars().filter(|c| !c.is_whitespace()).collect();
match parsed.to_lowercase().as_str() {
"black" => Ok(Color::PALETTE(0)),
"red" => Ok(Color::PALETTE(1)),
"green" => Ok(Color::PALETTE(2)),
"yellow" => Ok(Color::PALETTE(3)),
"blue" => Ok(Color::PALETTE(4)),
"magenta" => Ok(Color::PALETTE(5)),
"cyan" => Ok(Color::PALETTE(6)),
"white" => Ok(Color::PALETTE(7)),
"brightblack" => Ok(Color::PALETTE(8)),
"brightred" => Ok(Color::PALETTE(9)),
"brightgreen" => Ok(Color::PALETTE(10)),
"brightyellow" => Ok(Color::PALETTE(11)),
"brightblue" => Ok(Color::PALETTE(12)),
"brightmagenta" => Ok(Color::PALETTE(13)),
"brightcyan" => Ok(Color::PALETTE(14)),
"brightwhite" => Ok(Color::PALETTE(15)),
_ => Err(Error::new(
std::io::ErrorKind::InvalidInput,
format!("'{string}' is not a recognized color"),
)),
}
}
pub fn from_rgb_string(string: &str) -> Result<Self, Error> {
let mut decode: Vec<u8> = Vec::new();
for color in string
.split(|c: char| !c.is_numeric())
.filter(|s| !s.is_empty())
{
let mut num: u16 = 0;
for character in color.chars() {
num *= 10;
num += character as u16 - '0' as u16;
if num > 255 {
return Err(Error::new(
std::io::ErrorKind::InvalidInput,
format!("Value over 255 in rgb : {num} at index {}", decode.len()),
));
}
}
decode.push(num as u8);
}
if decode.len() < 3 {
return Err(Error::new(
std::io::ErrorKind::InvalidInput,
"less than 3 arguments for color definition",
));
}
if decode.len() == 3 {
Ok(Color::rgb(decode[0], decode[1], decode[2]))
} else {
Ok(Color::rgba(decode[0], decode[1], decode[2], decode[3]))
}
}
pub fn from_hexadecimal(hexadecimal_color: &str) -> Result<Self, FromHexError> {
let mut color: String = hexadecimal_color.to_string();
if hexadecimal_color.starts_with("#") {
color = hexadecimal_color.chars().filter(|c| *c != '#').collect();
}
let decode = hex::decode(&color)?;
if decode.len() <= 2 || decode.len() > 4 {
Err(FromHexError::InvalidStringLength)
} else if decode.len() == 3 {
Ok(Color::rgb(decode[0], decode[1], decode[2]))
} else {
Ok(Color::rgba(decode[0], decode[1], decode[2], decode[3]))
}
}
pub fn red(&self) -> u8 {
*match self {
Color::RGB(r, _, _) => r,
Color::RGBA(r, _, _, _) => r,
Color::PALETTE(p) => p,
}
}
pub fn green(&self) -> u8 {
*match self {
Color::RGB(_, g, _) => g,
Color::RGBA(_, g, _, _) => g,
Color::PALETTE(p) => p,
}
}
pub fn blue(&self) -> u8 {
*match self {
Color::RGB(_, _, b) => b,
Color::RGBA(_, _, b, _) => b,
Color::PALETTE(p) => p,
}
}
pub fn alpha(&self) -> u8 {
match self {
Color::RGB(_, _, _) => 255,
Color::RGBA(_, _, _, a) => *a,
Color::PALETTE(_) => 255,
}
}
pub fn hexadecimal_value(&self) -> String {
match self {
Color::RGB(r, g, b) => hex::encode(vec![*r, *g, *b]),
Color::RGBA(r, g, b, a) => hex::encode(vec![*r, *g, *b, *a]),
Color::PALETTE(p) => hex::encode(vec![*p]),
}
}
pub fn inverted(&self) -> Self {
Color::rgb(255 - self.red(), 255 - self.green(), 255 - self.blue())
}
}
impl Display for Color {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Color::RGB(r, g, b) => write!(
f,
"r: {r}, g: {g}, b: {b}, hex: {}",
self.hexadecimal_value()
),
Color::RGBA(r, g, b, a) => write!(
f,
"r: {r}, g: {g}, b: {b}, a: {a}, hex: {}",
self.hexadecimal_value()
),
Color::PALETTE(p) => write!(f, "palette index : {p}"),
}
}
}