diff --git a/colorizer/src/color/hsv.rs b/colorizer/src/color/hsv.rs new file mode 100644 index 0000000..93fb669 --- /dev/null +++ b/colorizer/src/color/hsv.rs @@ -0,0 +1,69 @@ +use std::fmt::Display; + +use crate::color::{ColorHue, HSV, Percentage, RGB}; + +impl HSV { + pub fn new(h: u16, s: u8, v: u8) -> Self { + Self( + ColorHue::new(h as i16), + Percentage::new(s as i16), + Percentage::new(v as i16), + ) + } +} + +impl Display for HSV { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "hsv({}, {}%, {}%)", self.0, self.1, self.2) + } +} + +impl PartialEq for HSV { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 && self.1 == other.1 && self.2 == other.2 + } + + fn ne(&self, other: &Self) -> bool { + self.0 != other.0 || self.1 != other.1 || self.2 != other.2 + } +} + +impl From for HSV { + fn from(color: RGB) -> Self { + let r = color.0.to_f32() / 255.0; + let g = color.1.to_f32() / 255.0; + let b = color.2.to_f32() / 255.0; + + let min: f32 = r.min(g.min(b)); + let max: f32 = r.max(g.max(b)); + + let v = max; + + if min == max { + return Self::new(0, 0, v as u8); + } + + let s = (max - min) / max; + let dif = max - min; + let rc = (max - r) / dif; + let gc = (max - g) / dif; + let bc = (max - b) / dif; + + let mut h: f32; + if r == max { + h = bc - gc; + } else if g == max { + h = 2.0 + rc - bc; + } else { + h = 4.0 + gc - rc; + } + + h = (h / 6.0).rem_euclid(1.0); + + Self::new( + (h * 360.0).round() as u16, + (s * 100.0).round() as u8, + (v * 100.0).round() as u8, + ) + } +} diff --git a/colorizer/src/color/mod.rs b/colorizer/src/color/mod.rs index e30ad25..48fb400 100644 --- a/colorizer/src/color/mod.rs +++ b/colorizer/src/color/mod.rs @@ -3,9 +3,10 @@ mod test; pub mod hsl; +pub mod hsv; pub mod rgb; -use std::fmt::{UpperHex, write}; +use std::fmt::UpperHex; use crate::core::ranged::RangedInt; @@ -16,15 +17,12 @@ pub type Percentage = RangedInt<0, 100>; pub struct RGB(ColorIntensity, ColorIntensity, ColorIntensity); #[derive(Debug)] pub struct HSL(ColorHue, Percentage, Percentage); -// pub struct HSV(ColorHue, Percentage, Percentage); +#[derive(Debug)] +pub struct HSV(ColorHue, Percentage, Percentage); #[derive(Debug)] pub struct Color(RGB); -impl Color { - pub fn format(&self) -> String { - format!("{:?}", self.0) - } -} +impl Color {} impl UpperHex for Color { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -53,3 +51,9 @@ impl From for Color { Color(RGB::from(color)) } } + +impl From for Color { + fn from(color: HSV) -> Self { + Color(RGB::from(color)) + } +} diff --git a/colorizer/src/color/rgb.rs b/colorizer/src/color/rgb.rs index 36cf818..0336f6b 100644 --- a/colorizer/src/color/rgb.rs +++ b/colorizer/src/color/rgb.rs @@ -1,7 +1,7 @@ -use std::fmt::UpperHex; +use std::fmt::{Display, UpperHex}; use crate::{ - color::{ColorIntensity, HSL, RGB}, + color::{ColorIntensity, HSL, HSV, RGB}, core::ranged::BaseNumber, }; @@ -25,6 +25,12 @@ impl PartialEq for RGB { } } +impl Display for RGB { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "rgb({}, {}, {})", self.0, self.1, self.2) + } +} + impl UpperHex for RGB { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{:0>2X}{:0>2X}{:0>2X}", self.0, self.1, self.2) @@ -86,3 +92,66 @@ impl From for RGB { ) } } + +impl From for RGB { + fn from(color: HSV) -> Self { + let h = color.0.to_f32() / 360.0; + let s = color.1.to_f32() / 100.0; + let v = color.2.to_f32() / 100.0; + + if s == 0.0 { + let grey = (v * 255.0) as u8; + return Self::new(grey, grey, grey); + } + + let i = (h * 6.0) as u8; + + let f = (h * 6.0) - i as f32; + let p = v * (1.0 - s); + let q = v * (1.0 - s * f); + let t = v * (1.0 - s * (1.0 - f)); + let i = i % 6; + + if i == 0 { + return Self::new( + (v * 255.0).round() as u8, + (t * 255.0).round() as u8, + (p * 255.0).round() as u8, + ); + } + if i == 1 { + return Self::new( + (q * 255.0).round() as u8, + (v * 255.0).round() as u8, + (p * 255.0).round() as u8, + ); + } + if i == 2 { + return Self::new( + (p * 255.0).round() as u8, + (v * 255.0).round() as u8, + (t * 255.0).round() as u8, + ); + } + if i == 3 { + return Self::new( + (p * 255.0).round() as u8, + (q * 255.0).round() as u8, + (v * 255.0).round() as u8, + ); + } + if i == 4 { + return Self::new( + (t * 255.0).round() as u8, + (p * 255.0).round() as u8, + (v * 255.0).round() as u8, + ); + } + + return Self::new( + (v * 255.0).round() as u8, + (p * 255.0).round() as u8, + (q * 255.0).round() as u8, + ); + } +} diff --git a/colorizer/src/color/test/colors.test.rs b/colorizer/src/color/test/colors.test.rs index b8a4b44..0f1cf97 100644 --- a/colorizer/src/color/test/colors.test.rs +++ b/colorizer/src/color/test/colors.test.rs @@ -1,8 +1,21 @@ #[cfg(test)] pub mod tests { - use std::fmt::format; + use crate::color::{Color, HSL, HSV, RGB}; - use crate::color::{Color, HSL, RGB}; + #[test] + fn test_color_initialization() { + let red_hsl = Color::from(HSL::new(0, 100, 50)); + let red_rgb = Color::from(RGB::new(255, 0, 0)); + assert_eq!(red_hsl, red_rgb); + + let green_hsl = Color::from(HSL::new(120, 100, 50)); + let green_rgb = Color::from(RGB::new(0, 255, 0)); + assert_eq!(green_hsl, green_rgb); + + let blue_hsl = Color::from(HSL::new(240, 100, 50)); + let blue_rgb = Color::from(RGB::new(0, 0, 255)); + assert_eq!(blue_hsl, blue_rgb); + } #[test] fn test_conversion() { @@ -117,17 +130,36 @@ pub mod tests { } #[test] - fn test_color_initialization() { - let red_hsl = Color::from(HSL::new(0, 100, 50)); - let red_rgb = Color::from(RGB::new(255, 0, 0)); - assert_eq!(red_hsl, red_rgb); + fn test_hsv_from_rgb() { + // Base colors + let color = RGB::new(255, 0, 0); + assert_eq!(HSV::from(color), HSV::new(0, 100, 100)); + let color = RGB::new(0, 255, 0); + assert_eq!(HSV::from(color), HSV::new(120, 100, 100)); + let color = RGB::new(0, 0, 255); + assert_eq!(HSV::from(color), HSV::new(240, 100, 100)); - let green_hsl = Color::from(HSL::new(120, 100, 50)); - let green_rgb = Color::from(RGB::new(0, 255, 0)); - assert_eq!(green_hsl, green_rgb); + // Complex colors + let color = RGB::new(20, 240, 100); + assert_eq!(HSV::from(color), HSV::new(142, 92, 94)); + let color = RGB::new(220, 10, 50); + assert_eq!(HSV::from(color), HSV::new(349, 95, 86)); + } - let blue_hsl = Color::from(HSL::new(240, 100, 50)); - let blue_rgb = Color::from(RGB::new(0, 0, 255)); - assert_eq!(blue_hsl, blue_rgb); + #[test] + fn test_rgb_from_hsv() { + // Base colors + let color = HSV::new(0, 100, 100); + assert_eq!(RGB::from(color), RGB::new(255, 0, 0)); + let color = HSV::new(120, 100, 100); + assert_eq!(RGB::from(color), RGB::new(0, 255, 0)); + let color = HSV::new(240, 100, 100); + assert_eq!(RGB::from(color), RGB::new(0, 0, 255)); + + // Complex colors + let color = HSV::new(349, 95, 86); + assert_eq!(RGB::from(color), RGB::new(219, 11, 49)); + let color = HSV::new(142, 92, 94); + assert_eq!(RGB::from(color), RGB::new(19, 240, 100)); } } diff --git a/colorizer/src/main.rs b/colorizer/src/main.rs index 1b2912f..3139fdb 100644 --- a/colorizer/src/main.rs +++ b/colorizer/src/main.rs @@ -1,8 +1,7 @@ use clipboard::ClipboardContext; use clipboard::ClipboardProvider; -use crate::color::Color; -use crate::color::HSL; +use crate::color::HSV; use crate::color::RGB; mod color; @@ -18,8 +17,7 @@ fn main() { println!("Hello, world!"); example(); - let hsl_color = Color::from(HSL::new(0, 100, 50)); - // let rgb_color = Color::from(HSL::new(193, 67, 28)); - println!("RGB Color: {:X}", hsl_color); - // println!("RGB Color: {}", rgb_color.format()); + let color = RGB::new(220, 10, 50); + let hsv_color = HSV::from(RGB::new(220, 10, 50)); + println!("RGB color: {}, HSV Color: {}", color, hsv_color); }