use std::str::FromStr;
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Color {
pub r: f32,
pub g: f32,
pub b: f32,
pub a: f32,
}
impl Color {
pub fn new(r: f32, g: f32, b: f32, a: f32) -> Self {
Self { r, g, b, a }
}
pub fn rgba(r: f32, g: f32, b: f32, a: f32) -> Self {
Self::new(r, g, b, a)
}
pub fn rgb(r: f32, g: f32, b: f32) -> Self {
Self::rgba(r, g, b, 1.0_f32)
}
pub fn make_transparent(self, a: f32) -> Self {
let mut new = self;
new.a = a;
new
}
pub fn from_hex_str(hex: &str) -> Result<Self, ParseColorError> {
if !hex.starts_with("#") {
return Err(ParseColorError::ParseColorFromHex(hex.into()));
}
let hex = &hex[1..];
let hex4bit = |pos| u8::from_str_radix(&hex[pos..pos + 1], 16).map(|v| v as f32 / 15.0);
let hex8bit = |pos| u8::from_str_radix(&hex[pos..pos + 2], 16).map(|v| v as f32 / 255.0);
match hex.len() {
3 | 4 => Ok(Color::rgba(
hex4bit(0)?,
hex4bit(1)?,
hex4bit(2)?,
if hex.len() == 4 { hex4bit(3)? } else { 1.0 },
)),
6 | 8 => Ok(Color::rgba(
hex8bit(0)?,
hex8bit(2)?,
hex8bit(4)?,
if hex.len() == 8 { hex8bit(6)? } else { 1.0 },
)),
_ => Err(ParseColorError::ParseColorFromHex(hex.into())),
}
}
pub fn to_svg_color(&self) -> String {
let r = (self.r * 255.0) as u8;
let g = (self.g * 255.0) as u8;
let b = (self.b * 255.0) as u8;
let a = self.a;
if a < 1.0 {
format!("rgba({r}, {g}, {b}, {a:.2})")
} else {
format!("#{r:02X}{g:02X}{b:02X}")
}
}
}
impl Default for Color {
fn default() -> Self {
Self::rgba(0.5, 0.5, 0.5, 1.0)
}
}
impl std::fmt::Display for Color {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "rgba({}, {}, {}, {})", self.r, self.g, self.b, self.a)
}
}
impl FromStr for Color {
type Err = ParseColorError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"blue" => Ok(Self::rgb(0.0, 0.0, 1.0)),
"red" => Ok(Self::rgb(1.0, 0.0, 0.0)),
"green" => Ok(Self::rgb(0.0, 1.0, 0.0)),
"yellow" => Ok(Self::rgb(1.0, 1.0, 0.0)),
"cyan" => Ok(Self::rgb(0.0, 1.0, 1.0)),
"magenta" => Ok(Self::rgb(1.0, 0.0, 1.0)),
"black" => Ok(Self::rgb(0.0, 0.0, 0.0)),
"white" => Ok(Self::rgb(1.0, 1.0, 1.0)),
"gray" => Ok(Self::rgb(0.5, 0.5, 0.5)),
"orange" => Ok(Self::rgb(1.0, 0.5, 0.0)),
"purple" => Ok(Self::rgb(0.5, 0.0, 0.5)),
"pink" => Ok(Self::rgb(1.0, 0.75, 0.8)),
"brown" => Ok(Self::rgb(0.6, 0.3, 0.1)),
"lime" => Ok(Self::rgb(0.75, 1.0, 0.0)),
"teal" => Ok(Self::rgb(0.0, 0.5, 0.5)),
"navy" => Ok(Self::rgb(0.0, 0.0, 0.5)),
"transparent" => Ok(Self::rgba(0.0, 0.0, 0.0, 0.0)),
s => {
if s.starts_with("#") {
Self::from_hex_str(s)
} else {
Err(ParseColorError::UnknownColorName(s.to_string()))
}
}
}
}
}
use thiserror::Error;
#[derive(Error, Debug)]
pub enum ParseColorError {
#[error("Unknown color name: {0}")]
UnknownColorName(String),
#[error("Could not parse color from hex string: {0}")]
ParseColorFromHex(String),
#[error("Error parsing integer literal: {0}")]
ParseIntError(#[from] std::num::ParseIntError),
}