#![allow(clippy::cast_sign_loss)]
#![allow(clippy::cast_possible_truncation)]
use std::fmt;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Color {
Rgb { r: u8, g: u8, b: u8 },
Rgba { r: u8, g: u8, b: u8, a: u8 },
}
impl Default for Color {
fn default() -> Self {
Color::Rgb { r: 0, g: 0, b: 0 }
}
}
impl From<Color> for crossterm::style::Color {
fn from(color: Color) -> Self {
match color {
Color::Rgb { r, g, b } | Color::Rgba { r, g, b, a: _ } => {
crossterm::style::Color::Rgb { r, g, b }
}
}
}
}
impl fmt::Display for Color {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Color::Rgb { r, g, b } => write!(f, "#{r:02x}{g:02x}{b:02x}"),
Color::Rgba { r, g, b, a } => write!(f, "#{r:02x}{g:02x}{b:02x}{a:02x}"),
}
}
}
pub fn parse_rgb(s: &str) -> anyhow::Result<Color> {
if !s.starts_with('#') {
anyhow::bail!("Invalid hex string: {s}");
}
let hex = s.trim_start_matches('#');
let len = hex.len();
if len != 6 && len != 8 {
anyhow::bail!("Hex string must be in the format #RRGGBB or #RRGGBBAA instead of {s}");
}
let red = u8::from_str_radix(&hex[0..2], 16)?;
let green = u8::from_str_radix(&hex[2..4], 16)?;
let blue = u8::from_str_radix(&hex[4..6], 16)?;
if len == 8 {
let alpha = u8::from_str_radix(&hex[6..8], 16)?;
Ok(Color::Rgba {
r: red,
g: green,
b: blue,
a: alpha,
})
} else {
Ok(Color::Rgb {
r: red,
g: green,
b: blue,
})
}
}
#[must_use]
pub fn blend_color(foreground: Color, background: Color) -> Color {
match (foreground, background) {
(
Color::Rgba { r, g, b, a },
Color::Rgb {
r: background_r,
g: background_g,
b: background_b,
},
) => {
let alpha = f32::from(a) / 255.0;
let inv_alpha = 1.0 - alpha;
let r = (f32::from(r) * alpha + f32::from(background_r) * inv_alpha) as u8;
let g = (f32::from(g) * alpha + f32::from(background_g) * inv_alpha) as u8;
let b = (f32::from(b) * alpha + f32::from(background_b) * inv_alpha) as u8;
Color::Rgb { r, g, b }
}
_ => foreground,
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_parse_rgb_simple() {
let hex_str = "#000000";
assert_eq!(parse_rgb(hex_str).unwrap(), Color::Rgb { r: 0, g: 0, b: 0 });
}
#[test]
fn test_parse_rgb_complex() {
let hex_str = "#d35a87";
assert_eq!(
parse_rgb(hex_str).unwrap(),
Color::Rgb {
r: 0xd3,
g: 0x5a,
b: 0x87
}
);
}
}