Skip to main content

colours/
hsv.rs

1use super::{IsColorChannel, IsColor, HasAlpha, HasntAlpha, Hsva, Hsl, Rgb};
2
3/// Generic HSV color
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub struct Hsv<T> {
6    pub hue: T,
7    pub saturation: T,
8    pub value: T,
9}
10
11impl<T: IsColorChannel> IsColor for Hsv<T> {
12    type Channel = T;
13}
14
15impl<T: IsColorChannel> HasntAlpha for Hsv<T> {
16    type Alphaful = Hsva<T>;
17
18    fn with_alpha(self, alpha: Self::Channel) -> Self::Alphaful {
19        let Hsv { hue, saturation, value } = self;
20        Hsva { hue, saturation, value, alpha }
21    }
22}
23
24impl<T> Hsv<T> {
25    pub fn new(hue: T, saturation: T, value: T) -> Self {
26        Self { hue, saturation, value }
27    }
28}
29
30impl<T: IsColorChannel> Default for Hsv<T> {
31    fn default() -> Self {
32        Self::new(T::MIN, T::MIN, T::MIN)
33    }
34}
35
36impl<T: IsColorChannel> From<Hsva<T>> for Hsv<T> {
37    fn from(hsva: Hsva<T>) -> Self {
38        hsva.without_alpha()
39    }
40}
41
42impl From<Hsv<u8>> for Hsv<f32> {
43    fn from(Hsv { hue, saturation, value }: Hsv<u8>) -> Self {
44        Self::new(
45            hue as f32 / 255.0,
46            saturation as f32 / 255.0,
47            value as f32 / 255.0,
48        )
49    }
50}
51
52impl From<Hsv<f32>> for Hsv<u8> {
53    fn from(Hsv { hue, saturation, value }: Hsv<f32>) -> Self {
54        Self::new(
55            (hue.clamp_channel() * 255.0).round() as u8,
56            (saturation.clamp_channel() * 255.0).round() as u8,
57            (value.clamp_channel() * 255.0).round() as u8,
58        )
59    }
60}
61
62impl From<Hsl<f32>> for Hsv<f32> {
63    fn from(Hsl { hue, saturation, lightness }: Hsl<f32>) -> Self {
64        let lightness = lightness * 2.0;
65        let saturation = saturation * if lightness > 1.0 { 2.0 - lightness } else { lightness };
66
67        let lightness_plus_saturation = lightness + saturation;
68
69        let value = lightness_plus_saturation * 0.5;
70        let saturation = 2.0 * saturation / lightness_plus_saturation;
71
72        Self { hue, saturation, value }
73    }
74}
75
76impl From<Rgb<f32>> for Hsv<f32> {
77    fn from(Rgb { red, green, blue }: Rgb<f32>) -> Self {
78        let max = 0.0f32.max(red).max(green).max(blue);
79        let min = 1.0f32.min(red).min(green).min(blue);
80
81        if max > 0.0 {
82            let value = max;
83            let delta = max - min;
84            let saturation = delta / max;
85
86            let hue = if delta != 0.0 {
87                (
88                    if red == max {
89                        (green - blue) / delta
90                    } else if green == max {
91                        2.0 + (blue - red) / delta
92                    } else {
93                        4.0 + (red - green) / delta
94                    }
95                    * 1.0 / 6.0
96                ).unwind_channel()
97            } else {
98                0.0
99            };
100
101            Self { hue, saturation, value }
102        } else {
103            Self { hue: 0.0, saturation: 0.0, value: 0.0 }
104        }
105    }
106}
107
108#[cfg(test)]
109mod test {
110    use super::*;
111
112    #[test]
113    fn from_rgb() {
114        assert_eq!(Hsv::<u8>::from(Hsv::from(Rgb::<f32>::from(Rgb::new(84u8, 37, 181)))), Hsv::new(184u8, 203, 181));
115    }
116
117    #[test]
118    fn from_hsl() {
119        assert_eq!(Hsv::<u8>::from(Hsv::from(Hsl::<f32>::from(Hsl::new(184u8, 169, 109)))), Hsv::new(184u8, 203, 181));
120    }
121}