#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Rgb(pub u8, pub u8, pub u8);
impl Rgb {
pub const BLACK: Rgb = Rgb(0, 0, 0);
pub const WHITE: Rgb = Rgb(255, 255, 255);
pub const RED: Rgb = Rgb(255, 0, 0);
pub const GREEN: Rgb = Rgb(0, 255, 0);
pub const BLUE: Rgb = Rgb(0, 0, 255);
pub const YELLOW: Rgb = Rgb(255, 255, 0);
pub const CYAN: Rgb = Rgb(0, 255, 255);
pub const MAGENTA: Rgb = Rgb(255, 0, 255);
pub const ORANGE: Rgb = Rgb(255, 165, 0);
pub const PURPLE: Rgb = Rgb(128, 0, 128);
pub const PINK: Rgb = Rgb(255, 105, 180);
pub const GRAY: Rgb = Rgb(128, 128, 128);
pub const SILVER: Rgb = Rgb(192, 192, 192);
pub const GOLD: Rgb = Rgb(255, 215, 0);
pub const NAVY: Rgb = Rgb(0, 0, 128);
pub const TEAL: Rgb = Rgb(0, 128, 128);
pub const MAROON: Rgb = Rgb(128, 0, 0);
pub const OLIVE: Rgb = Rgb(128, 128, 0);
pub const LIME: Rgb = Rgb(50, 205, 50);
pub const INDIGO: Rgb = Rgb(75, 0, 130);
pub const VIOLET: Rgb = Rgb(238, 130, 238);
pub const BROWN: Rgb = Rgb(139, 69, 19);
pub const CORAL: Rgb = Rgb(255, 127, 80);
pub const SALMON: Rgb = Rgb(250, 128, 114);
pub const TURQUOISE: Rgb = Rgb(64, 224, 208);
pub const CRIMSON: Rgb = Rgb(220, 20, 60);
pub const SEPIA: Rgb = Rgb(112, 66, 20);
pub const fn new(r: u8, g: u8, b: u8) -> Self {
Self(r, g, b)
}
#[inline]
pub const fn r(self) -> u8 {
self.0
}
#[inline]
pub const fn g(self) -> u8 {
self.1
}
#[inline]
pub const fn b(self) -> u8 {
self.2
}
pub(crate) fn ansi_fg(self) -> String {
let mut buf = Vec::with_capacity(19);
crate::fast_ansi::push_truecolor_escape(&mut buf, self.0, self.1, self.2);
unsafe { String::from_utf8_unchecked(buf) }
}
pub(crate) fn scale(self, brightness: f32) -> Self {
let brightness = brightness.clamp(0.0, 1.0);
Rgb(
(f32::from(self.0) * brightness).round() as u8,
(f32::from(self.1) * brightness).round() as u8,
(f32::from(self.2) * brightness).round() as u8,
)
}
pub fn mix(self, other: Rgb, t: f32) -> Rgb {
let t = t.clamp(0.0, 1.0);
let lerp = |a: u8, b: u8| (f32::from(a) + (f32::from(b) - f32::from(a)) * t).round() as u8;
Rgb(
lerp(self.0, other.0),
lerp(self.1, other.1),
lerp(self.2, other.2),
)
}
}
pub(crate) fn hsv_to_rgb(h: f32, s: f32, v: f32) -> Rgb {
let c = v * s;
let hp = h / 60.0;
let x = c * (1.0 - (hp.rem_euclid(2.0) - 1.0).abs());
let (r1, g1, b1) = match hp as u32 {
0 => (c, x, 0.0),
1 => (x, c, 0.0),
2 => (0.0, c, x),
3 => (0.0, x, c),
4 => (x, 0.0, c),
_ => (c, 0.0, x),
};
let m = v - c;
Rgb(
((r1 + m) * 255.0).round() as u8,
((g1 + m) * 255.0).round() as u8,
((b1 + m) * 255.0).round() as u8,
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn hsv_to_rgb_primary_hues_are_exact() {
assert_eq!(hsv_to_rgb(0.0, 1.0, 1.0), Rgb::new(255, 0, 0));
assert_eq!(hsv_to_rgb(120.0, 1.0, 1.0), Rgb::new(0, 255, 0));
assert_eq!(hsv_to_rgb(240.0, 1.0, 1.0), Rgb::new(0, 0, 255));
}
}