use std::error::Error;
use std::fmt::{self, Formatter};
use std::num::ParseIntError;
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub struct Rgb {
pub r: u8,
pub g: u8,
pub b: u8
}
impl Rgb {
pub const WHITE: Rgb = Rgb::new(255, 255, 255);
pub const BLACK: Rgb = Rgb::new(0, 0, 0);
pub const RED: Rgb = Rgb::new(255, 0, 0);
pub const GREEN: Rgb = Rgb::new(0, 255, 0);
pub const BLUE: Rgb = Rgb::new(0, 0, 255);
pub const YELLOW: Rgb = Rgb::new(255, 255, 0);
pub const CYAN: Rgb = Rgb::new(0, 255, 255);
pub const MAGENTA: Rgb = Rgb::new(255, 0, 255);
#[must_use]
#[inline]
pub const fn new(r: u8, g: u8, b: u8) -> Rgb {
Rgb { r, g, b }
}
#[must_use]
#[inline]
pub const fn gray(rgb: u8) -> Rgb {
Rgb { r: rgb, g: rgb, b: rgb }
}
#[expect(clippy::missing_errors_doc, clippy::indexing_slicing)]
pub fn from_hex(s: &str) -> Result<Rgb, ParseHexError> {
if s.len() != 7 {
Err(ParseHexError::BadLen)
}
else if s.as_bytes()[0] != b'#' {
Err(ParseHexError::MissingHash)
}
else {
Ok((u32::from_str_radix(&s[1..7], 16)? << 8).into())
}
}
#[must_use]
#[inline]
pub const fn checked_add_signed(self, r: i8, g: i8, b: i8) -> Option<Rgb> {
macro_rules! checked_add_signed {
($lhs:expr, $rhs:expr) => {
match $lhs.checked_add_signed($rhs) {
Some(v) => v,
None => return None
}
};
}
Some(Rgb {
r: checked_add_signed!(self.r, r),
g: checked_add_signed!(self.g, g),
b: checked_add_signed!(self.b, b),
})
}
#[must_use]
#[inline]
pub const fn distance(self, other: Rgb) -> u32 {
let dx = (self.r as i32) - (other.r as i32);
let dy = (self.g as i32) - (other.g as i32);
let dz = (self.b as i32) - (other.b as i32);
(dx * dx) as u32 + (dy * dy) as u32 + (dz * dz) as u32
}
#[must_use]
#[inline]
#[allow(clippy::cast_possible_truncation, clippy::suboptimal_flops)]
pub fn luma(self) -> u8 {
(0.299 * (self.r as f32) + 0.587 * (self.g as f32) + 0.114 * (self.b as f32)) as u8
}
#[must_use]
#[inline]
pub fn grayscale(self) -> Rgb {
Rgb::gray(self.luma())
}
}
impl From<u32> for Rgb {
#[inline]
fn from(value: u32) -> Rgb {
Rgb {
r: ((value >> 24) & 0xFF) as u8,
g: ((value >> 16) & 0xFF) as u8,
b: ((value >> 8) & 0xFF) as u8
}
}
}
impl From<Rgb> for u32 {
#[inline]
fn from(value: Rgb) -> u32 {
((value.r as u32) << 24) | ((value.g as u32) << 16) | ((value.b as u32) << 8) | 0xFF
}
}
impl Default for Rgb {
#[inline]
fn default() -> Rgb {
Rgb::new(91, 206, 250)
}
}
impl fmt::Debug for Rgb {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_tuple("Rgb")
.field(&self.r)
.field(&self.g)
.field(&self.b)
.finish()
}
}
impl fmt::LowerHex for Rgb {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "#{:02x}{:02x}{:02x}", self.r, self.g, self.b)
}
}
impl fmt::UpperHex for Rgb {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "#{:02X}{:02X}{:02X}", self.r, self.g, self.b)
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum ParseHexError {
BadLen,
MissingHash,
BadInt(ParseIntError)
}
impl fmt::Display for ParseHexError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
ParseHexError::BadLen => write!(f, "bad length"),
ParseHexError::MissingHash => write!(f, "missing `#` prefix"),
ParseHexError::BadInt(e) => fmt::Display::fmt(e, f)
}
}
}
impl From<ParseIntError> for ParseHexError {
fn from(e: ParseIntError) -> ParseHexError {
ParseHexError::BadInt(e)
}
}
impl Error for ParseHexError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
ParseHexError::BadInt(e) => Some(e),
_ => None
}
}
}