Skip to main content

scala_chromatica/
color.rs

1//! RGB Color with HSV conversion and interpolation
2//!
3//! Provides a simple RGB color representation with support for:
4//! - RGB color creation
5//! - HSV to RGB conversion
6//! - Linear interpolation (lerp) between colors
7//! - Common color constants (black, white)
8
9use serde::{Deserialize, Serialize};
10
11/// RGB Color representation
12#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
13pub struct Color {
14    pub r: u8,
15    pub g: u8,
16    pub b: u8,
17}
18
19impl Color {
20    /// Create a new RGB color
21    pub fn new(r: u8, g: u8, b: u8) -> Self {
22        Self { r, g, b }
23    }
24
25    /// Create a color from HSV values
26    ///
27    /// # Arguments
28    /// * `h` - Hue (0.0 - 360.0)
29    /// * `s` - Saturation (0.0 - 1.0)
30    /// * `v` - Value/Brightness (0.0 - 1.0)
31    pub fn from_hsv(h: f64, s: f64, v: f64) -> Self {
32        let c = v * s;
33        let x = c * (1.0 - ((h / 60.0) % 2.0 - 1.0).abs());
34        let m = v - c;
35
36        let (r, g, b) = if h < 60.0 {
37            (c, x, 0.0)
38        } else if h < 120.0 {
39            (x, c, 0.0)
40        } else if h < 180.0 {
41            (0.0, c, x)
42        } else if h < 240.0 {
43            (0.0, x, c)
44        } else if h < 300.0 {
45            (x, 0.0, c)
46        } else {
47            (c, 0.0, x)
48        };
49
50        Self {
51            r: ((r + m) * 255.0) as u8,
52            g: ((g + m) * 255.0) as u8,
53            b: ((b + m) * 255.0) as u8,
54        }
55    }
56
57    /// Pure black color (0, 0, 0)
58    pub fn black() -> Self {
59        Self::new(0, 0, 0)
60    }
61
62    /// Pure white color (255, 255, 255)
63    pub fn white() -> Self {
64        Self::new(255, 255, 255)
65    }
66
67    /// Linear interpolation between two colors
68    ///
69    /// # Arguments
70    /// * `other` - The target color to interpolate towards
71    /// * `t` - Interpolation factor (0.0 = self, 1.0 = other)
72    pub fn lerp(&self, other: &Color, t: f64) -> Color {
73        let t = t.clamp(0.0, 1.0);
74        Color {
75            r: (self.r as f64 + (other.r as f64 - self.r as f64) * t) as u8,
76            g: (self.g as f64 + (other.g as f64 - self.g as f64) * t) as u8,
77            b: (self.b as f64 + (other.b as f64 - self.b as f64) * t) as u8,
78        }
79    }
80}
81
82impl std::fmt::Display for Color {
83    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84        write!(f, "RGB({},{},{})", self.r, self.g, self.b)
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91
92    #[test]
93    fn test_color_creation() {
94        let color = Color::new(255, 128, 64);
95        assert_eq!(color.r, 255);
96        assert_eq!(color.g, 128);
97        assert_eq!(color.b, 64);
98    }
99
100    #[test]
101    fn test_color_constants() {
102        let black = Color::black();
103        assert_eq!(black.r, 0);
104        assert_eq!(black.g, 0);
105        assert_eq!(black.b, 0);
106
107        let white = Color::white();
108        assert_eq!(white.r, 255);
109        assert_eq!(white.g, 255);
110        assert_eq!(white.b, 255);
111    }
112
113    #[test]
114    fn test_lerp() {
115        let red = Color::new(255, 0, 0);
116        let blue = Color::new(0, 0, 255);
117
118        let mid = red.lerp(&blue, 0.5);
119        assert_eq!(mid.r, 127);
120        assert_eq!(mid.g, 0);
121        assert_eq!(mid.b, 127);
122
123        let at_red = red.lerp(&blue, 0.0);
124        assert_eq!(at_red.r, 255);
125
126        let at_blue = red.lerp(&blue, 1.0);
127        assert_eq!(at_blue.b, 255);
128    }
129
130    #[test]
131    fn test_hsv_conversion() {
132        // Pure red (H=0)
133        let red = Color::from_hsv(0.0, 1.0, 1.0);
134        assert_eq!(red.r, 255);
135        assert_eq!(red.g, 0);
136        assert_eq!(red.b, 0);
137
138        // Pure green (H=120)
139        let green = Color::from_hsv(120.0, 1.0, 1.0);
140        assert_eq!(green.r, 0);
141        assert_eq!(green.g, 255);
142        assert_eq!(green.b, 0);
143
144        // Pure blue (H=240)
145        let blue = Color::from_hsv(240.0, 1.0, 1.0);
146        assert_eq!(blue.r, 0);
147        assert_eq!(blue.g, 0);
148        assert_eq!(blue.b, 255);
149    }
150}