#[derive(Debug, PartialEq, Eq)]
pub struct Rgba {
pub red: u8,
pub green: u8,
pub blue: u8,
pub alpha: u8,
}
pub struct Hsl {
pub hue: f64,
pub saturation: f64,
pub lightness: f64,
}
pub struct Hsv {
pub hue: f64,
pub saturation: f64,
pub value: f64,
}
pub struct Cmyk {
pub cyan: f64,
pub magenta: f64,
pub yellow: f64,
pub black: f64,
}
#[derive(Debug)]
pub enum ColorParserError {
InvalidLength,
InvalidCharacter,
InvalidRgbValue,
}
impl std::fmt::Display for ColorParserError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
ColorParserError::InvalidLength => write!(f, "Hex color must be 6 character long"),
ColorParserError::InvalidCharacter => write!(f, "Invalid character in hex color"),
ColorParserError::InvalidRgbValue => write!(f, "RGB value must be between 0 and 255"),
}
}
}
impl std::error::Error for ColorParserError {}
pub fn parse_hex_to_rgba(hex: &str) -> Result<Rgba, ColorParserError> {
let hex = hex.trim_start_matches('#');
let expanded = match hex.len() {
8 => hex.to_string(), 6 => format!("{}FF", hex), 4 => {
let mut s = String::with_capacity(8);
for ch in hex.chars() {
s.push(ch);
s.push(ch);
}
s
}
3 => {
let mut s = String::with_capacity(8);
for ch in hex.chars() {
s.push(ch);
s.push(ch);
}
s.push_str("FF"); s
}
_ => return Err(ColorParserError::InvalidLength),
};
let red =
u8::from_str_radix(&expanded[0..2], 16).map_err(|_| ColorParserError::InvalidCharacter)?;
let green =
u8::from_str_radix(&expanded[2..4], 16).map_err(|_| ColorParserError::InvalidCharacter)?;
let blue =
u8::from_str_radix(&expanded[4..6], 16).map_err(|_| ColorParserError::InvalidCharacter)?;
let alpha =
u8::from_str_radix(&expanded[6..8], 16).map_err(|_| ColorParserError::InvalidCharacter)?;
if !(0..=255).contains(&red)
|| !(0..=255).contains(&green)
|| !(0..=255).contains(&blue)
|| !(0..=255).contains(&alpha)
{
return Err(ColorParserError::InvalidRgbValue);
}
Ok(Rgba {
red,
green,
blue,
alpha,
})
}
pub fn parse_rgb_to_hsl(color: &Rgba) -> Result<Hsl, ColorParserError> {
if !(0..=255).contains(&color.red)
|| !(0..=255).contains(&color.green)
|| !(0..=255).contains(&color.blue)
{
return Err(ColorParserError::InvalidRgbValue);
}
let r = color.red as f64 / 255.0;
let g = color.green as f64 / 255.0;
let b = color.blue as f64 / 255.0;
let max = r.max(g).max(b);
let min = r.min(g).min(b);
let delta = max - min;
let lightness = (max + min) / 2.0;
let saturation = if delta == 0.0 {
0.0
} else {
delta / (1.0 - (2.0 * lightness - 1.0).abs())
};
let hue = if delta == 0.0 {
0.0
} else if max == r {
(g - b) / delta + (if g < b { 6.0 } else { 0.0 })
} else if max == g {
(b - r) / delta + 2.0 } else {
(r - g) / delta + 4.0 };
Ok(Hsl {
hue: (hue * 60.0) % 360.0, saturation: saturation * 100.0,
lightness: lightness * 100.0,
})
}
pub fn parse_rgb_to_hsv(color: &Rgba) -> Result<Hsv, ColorParserError> {
if !(0..=255).contains(&color.red)
|| !(0..=255).contains(&color.green)
|| !(0..=255).contains(&color.blue)
{
return Err(ColorParserError::InvalidRgbValue);
}
let r = color.red as f64 / 255.0;
let g = color.green as f64 / 255.0;
let b = color.blue as f64 / 255.0;
let max = r.max(g).max(b);
let min = r.min(g).min(b);
let delta = max - min;
let value = max;
let saturation = if delta == 0.0 { 0.0 } else { delta / value };
let hue = if delta == 0.0 {
0.0
} else if max == r {
(g - b) / delta + (if g < b { 6.0 } else { 0.0 })
} else if max == g {
(b - r) / delta + 2.0
} else {
(r - g) / delta + 4.0
};
Ok(Hsv {
hue: (hue * 60.0) % 360.0,
saturation: saturation * 100.0,
value: value * 100.0,
})
}
pub fn parse_rgb_to_cmyk(color: &Rgba) -> Result<Cmyk, ColorParserError> {
if !(0..=255).contains(&color.red)
|| !(0..=255).contains(&color.green)
|| !(0..=255).contains(&color.blue)
{
return Err(ColorParserError::InvalidRgbValue);
}
let r = color.red as f64 / 255.0;
let g = color.green as f64 / 255.0;
let b = color.blue as f64 / 255.0;
let k = 1.0 - r.max(g).max(b);
if k == 1.0 {
return Ok(Cmyk {
cyan: 0.0 * 100.0,
magenta: 0.0 * 100.0,
yellow: 0.0 * 100.0,
black: 100.0,
});
}
let c = (1.0 - r - k) / (1.0 - k);
let m = (1.0 - g - k) / (1.0 - k);
let y = (1.0 - b - k) / (1.0 - k);
Ok(Cmyk {
cyan: c * 100.0,
magenta: m * 100.0,
yellow: y * 100.0,
black: k * 100.0,
})
}