use rand::Rng; #[derive(Debug, Clone, Copy, PartialEq)] pub struct HsvColor { h: f32, s: f32, v: f32, } impl HsvColor { pub fn new(h: f32, s: f32, v: f32) -> Self { Self { h, s, v } } /// Converts the color to its RGB equivalent. /// /// # Panics /// /// Panics if the color has a Hue value outside of the range `0..=360`. pub fn to_rgb(self) -> RgbColor { // See https://en.wikipedia.org/wiki/HSL_and_HSV#HSV_to_RGB let chroma = self.s * self.v; let h_prime = self.h / 60.0; // second largest component let second = chroma * (1.0 - ((h_prime % 2.0) - 1.0).abs()); let (r, g, b) = match h_prime { h if (0.0..1.0).contains(&h) => (chroma, second, 0.0), h if (1.0..2.0).contains(&h) => (second, chroma, 0.0), h if (2.0..3.0).contains(&h) => (0.0, chroma, second), h if (3.0..4.0).contains(&h) => (0.0, second, chroma), h if (4.0..5.0).contains(&h) => (second, 0.0, chroma), h if (5.0..=6.0).contains(&h) => (chroma, 0.0, second), _ => panic!( "Unexpected Hue value during HSV to RGB conversion: {}", self.h ), }; let m = self.v - chroma; let (r, g, b) = (r + m, g + m, b + m); let (r, g, b) = (r * 255.0, g * 255.0, b * 255.0); let (r, g, b) = (r.round(), g.round(), b.round()); debug_assert!((0.0..=255.0).contains(&r)); debug_assert!((0.0..=255.0).contains(&g)); debug_assert!((0.0..=255.0).contains(&b)); let (r, g, b) = (r as u8, g as u8, b as u8); RgbColor { r, g, b } } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct RgbColor { r: u8, g: u8, b: u8, } impl RgbColor { pub fn new(r: u8, g: u8, b: u8) -> Self { Self { r, g, b } } pub fn to_u32(self) -> u32 { (self.r as u32) << 16 | (self.g as u32) << 8 | (self.b as u32) } } pub fn pastel_color() -> RgbColor { HsvColor::new(rand::thread_rng().gen_range(0.0..360.0), 0.5, 0.9).to_rgb() } pub fn ugly_color() -> RgbColor { HsvColor::new(rand::thread_rng().gen_range(0.0..360.0), 1.0, 0.55).to_rgb() } #[cfg(test)] mod tests { use super::*; #[test] fn rgb_to_u32() { assert_eq!(RgbColor::new(255, 255, 255).to_u32(), 0xFFFFFF); assert_eq!(RgbColor::new(0, 0, 0).to_u32(), 0x000000); assert_eq!(RgbColor::new(255, 0, 0).to_u32(), 0xFF0000); assert_eq!(RgbColor::new(0, 0, 255).to_u32(), 0x0000FF); assert_eq!(RgbColor::new(0, 255, 0).to_u32(), 0x00FF00); assert_eq!(RgbColor::new(82, 252, 3).to_u32(), 0x52FC03); assert_eq!(RgbColor::new(3, 32, 252).to_u32(), 0x0320FC); assert_eq!(RgbColor::new(86, 3, 252).to_u32(), 0x5603FC); assert_eq!(RgbColor::new(252, 136, 3).to_u32(), 0xFC8803); assert_eq!(RgbColor::new(252, 40, 3).to_u32(), 0xFC2803); assert_eq!(RgbColor::new(3, 252, 211).to_u32(), 0x03FCD3); } #[test] fn hsv_to_rgb() { assert_eq!( HsvColor::new(217.0, 0.73, 0.96).to_rgb(), RgbColor::new(66, 135, 245) ); assert_eq!( HsvColor::new(122.0, 0.51, 0.61).to_rgb(), RgbColor::new(76, 156, 79) ); assert_eq!( HsvColor::new(54.0, 0.91, 0.85).to_rgb(), RgbColor::new(217, 197, 20) ); assert_eq!( HsvColor::new(0.0, 0.73, 0.96).to_rgb(), RgbColor::new(245, 66, 66) ); assert_eq!( HsvColor::new(360.0, 0.73, 0.96).to_rgb(), RgbColor::new(245, 66, 66) ); assert_eq!( HsvColor::new(0.0, 0.0, 0.0).to_rgb(), RgbColor::new(0, 0, 0) ); assert_eq!( HsvColor::new(0.0, 0.0, 1.0).to_rgb(), RgbColor::new(255, 255, 255) ); assert_eq!( HsvColor::new(0.0, 1.0, 1.0).to_rgb(), RgbColor::new(255, 0, 0) ); assert_eq!( HsvColor::new(120.0, 1.0, 1.0).to_rgb(), RgbColor::new(0, 255, 0) ); assert_eq!( HsvColor::new(240.0, 1.0, 1.0).to_rgb(), RgbColor::new(0, 0, 255) ); } }