bracket_color/
hsv.rs

1use crate::prelude::{RGB, RGBA};
2use std::convert::From;
3
4#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
5#[derive(PartialEq, Copy, Clone, Default, Debug)]
6/// Represents an H/S/V triplet, in the range 0..1 (32-bit float)
7/// This can provide for a more natural color progression, and provides
8/// compatibility with HSV-based color systems.
9pub struct HSV {
10    /// Hue (range 0..1)
11    pub h: f32,
12    /// Saturation (range 0..1)
13    pub s: f32,
14    /// Value (range 0..1)
15    pub v: f32,
16}
17
18/// Support conversion from RGB
19impl From<RGB> for HSV {
20    fn from(rgb: RGB) -> Self {
21        rgb.to_hsv()
22    }
23}
24
25/// Support conversion from RGBA
26impl From<RGBA> for HSV {
27    fn from(rgba: RGBA) -> Self {
28        rgba.to_rgb().to_hsv()
29    }
30}
31
32impl HSV {
33    /// Constructs a new, zeroed (black) HSV triplet.
34    #[must_use]
35    pub fn new() -> Self {
36        Self {
37            h: 0.0,
38            s: 0.0,
39            v: 0.0,
40        }
41    }
42
43    /// Constructs a new HSV color, from 3 32-bit floats
44    /// 
45    /// # Arguments
46    /// 
47    /// * `h` - The hue (0..1) to use.
48    /// * `s` - The saturation (0..1) to use.
49    /// * `v` - The value (0..1) to use.
50    #[inline]
51    #[must_use]
52    pub const fn from_f32(h: f32, s: f32, v: f32) -> Self {
53        Self { h, s, v }
54    }
55
56    /// Converts to an RGBA value with a specified alpha level
57    #[inline]
58    #[must_use]
59    pub fn to_rgba(&self, alpha: f32) -> RGBA {
60        self.to_rgb().to_rgba(alpha)
61    }
62
63    /// Converts an HSV triple to an RGB triple
64    #[allow(clippy::many_single_char_names)] // I like my short names for this one
65    #[allow(clippy::cast_precision_loss)]
66    #[allow(clippy::cast_possible_truncation)]
67    #[inline]
68    #[must_use]
69    pub fn to_rgb(&self) -> RGB {
70        let h = self.h;
71        let s = self.s;
72        let v = self.v;
73
74        let mut r: f32 = 0.0;
75        let mut g: f32 = 0.0;
76        let mut b: f32 = 0.0;
77
78        let i = f32::floor(h * 6.0) as i32;
79        let f = h * 6.0 - i as f32;
80        let p = v * (1.0 - s);
81        let q = v * (1.0 - f * s);
82        let t = v * (1.0 - (1.0 - f) * s);
83
84        match i % 6 {
85            0 => {
86                r = v;
87                g = t;
88                b = p;
89            }
90            1 => {
91                r = q;
92                g = v;
93                b = p;
94            }
95            2 => {
96                r = p;
97                g = v;
98                b = t;
99            }
100            3 => {
101                r = p;
102                g = q;
103                b = v;
104            }
105            4 => {
106                r = t;
107                g = p;
108                b = v;
109            }
110            5 => {
111                r = v;
112                g = p;
113                b = q;
114            }
115            // Catch-all; this shouldn't happen
116            _ => {}
117        }
118
119        RGB::from_f32(r, g, b)
120    }
121
122    /// Progress smoothly between two colors, in the HSV color space.
123    /// 
124    /// # Arguments
125    /// 
126    /// * `color` - the target color.
127    /// * `percent` - the percentage (0..1) of the starting (self) and target color to use.
128    /// 
129    /// # Example
130    /// 
131    /// ```rust
132    /// use bracket_color::prelude::*;
133    /// let red = RGB::named(RED);
134    /// let blue = RGB::named(YELLOW);
135    /// let color = red.lerp(blue, 0.5);
136    /// ```
137    #[inline]
138    #[must_use]
139    pub fn lerp(&self, color: Self, percent: f32) -> Self {
140        let range = (color.h - self.h, color.s - self.s, color.v - self.v);
141        Self {
142            h: self.h + range.0 * percent,
143            s: self.s + range.1 * percent,
144            v: self.v + range.2 * percent,
145        }
146    }
147}
148
149#[cfg(test)]
150mod tests {
151    use crate::prelude::*;
152
153    #[test]
154    // Tests that we make an HSV triplet at defaults and it is black.
155    fn make_hsv_minimal() {
156        let black = HSV::new();
157        assert!(black.h < std::f32::EPSILON);
158        assert!(black.s < std::f32::EPSILON);
159        assert!(black.v < std::f32::EPSILON);
160    }
161
162    #[test]
163    // Tests that we make an HSV triplet at defaults and it is black.
164    fn convert_red_to_hsv() {
165        let red = RGB::from_f32(1.0, 0.0, 0.0);
166        let hsv = red.to_hsv();
167        assert!(hsv.h < std::f32::EPSILON);
168        assert!(f32::abs(hsv.s - 1.0) < std::f32::EPSILON);
169        assert!(f32::abs(hsv.v - 1.0) < std::f32::EPSILON);
170    }
171
172    #[test]
173    // Tests that we make an HSV triplet at defaults and it is black.
174    fn convert_green_to_hsv() {
175        let green = RGB::from_f32(0.0, 1.0, 0.0);
176        let hsv = green.to_hsv();
177        assert!(f32::abs(hsv.h - 120.0 / 360.0) < std::f32::EPSILON);
178        assert!(f32::abs(hsv.s - 1.0) < std::f32::EPSILON);
179        assert!(f32::abs(hsv.v - 1.0) < std::f32::EPSILON);
180    }
181
182    #[test]
183    // Tests that we make an HSV triplet at defaults and it is black.
184    fn convert_blue_to_hsv() {
185        let blue = RGB::from_f32(0.0, 0.0, 1.0);
186        let hsv = blue.to_hsv();
187        assert!(f32::abs(hsv.h - 240.0 / 360.0) < std::f32::EPSILON);
188        assert!(f32::abs(hsv.s - 1.0) < std::f32::EPSILON);
189        assert!(f32::abs(hsv.v - 1.0) < std::f32::EPSILON);
190    }
191
192    #[test]
193    // Tests that we make an HSV triplet at defaults and it is black.
194    fn convert_olive_to_hsv() {
195        let grey = RGB::from_u8(128, 128, 0);
196        let hsv = grey.to_hsv();
197        assert!(f32::abs(hsv.h - 60.0 / 360.0) < std::f32::EPSILON);
198        assert!(f32::abs(hsv.s - 1.0) < std::f32::EPSILON);
199        assert!(f32::abs(hsv.v - 0.5019_608) < std::f32::EPSILON);
200    }
201
202    #[test]
203    // Test the lerp function
204    fn test_lerp() {
205        let black = RGB::named(BLACK).to_hsv();
206        let white = RGB::named(WHITE).to_hsv();
207        assert!(black.lerp(white, 0.0) == black);
208        assert!(black.lerp(white, 1.0) == white);
209    }
210}