colorutils_rs/
hsv.rs

1/*
2 * // Copyright 2024 (c) the Radzivon Bartoshyk. All rights reserved.
3 * //
4 * // Use of this source code is governed by a BSD-style
5 * // license that can be found in the LICENSE file.
6 */
7use crate::rgb::Rgb;
8
9#[repr(C)]
10#[derive(Debug, Copy, Clone, PartialOrd, PartialEq)]
11/// Represents *HSV* (hue, saturation, value) colorspace, H ∈ [0, 360f32], s ∈ [0f32, 1f32], v ∈ [0f32, 1f32]
12pub struct Hsv {
13    /// Hue H ∈ [0, 360f32]
14    pub h: f32,
15    /// Saturation s ∈ [0, 1f32]
16    pub s: f32,
17    /// Value v ∈ [0, 1f32]
18    pub v: f32,
19}
20
21static HSV_U8_SCALE: f32 = 1f32 / 255f32;
22static HSV_PERCENTAGE_SCALE: f32 = 1f32 / 100f32;
23
24impl Hsv {
25    #[inline]
26    pub fn new(h: u16, s: u16, l: u16) -> Hsv {
27        Hsv {
28            h: h as f32,
29            s: s as f32 * HSV_PERCENTAGE_SCALE,
30            v: l as f32 * HSV_PERCENTAGE_SCALE,
31        }
32    }
33    #[inline]
34    pub fn from_components(h: f32, s: f32, v: f32) -> Hsv {
35        Hsv { h, s, v }
36    }
37    #[inline]
38    pub fn from(rgb: Rgb<u8>) -> Hsv {
39        let (h, s, v) = rgb_to_hsv(
40            rgb.r as f32 * HSV_U8_SCALE,
41            rgb.g as f32 * HSV_U8_SCALE,
42            rgb.b as f32 * HSV_U8_SCALE,
43        );
44        Hsv { h, s, v }
45    }
46    #[inline]
47    pub fn to_rgb8(&self) -> Rgb<u8> {
48        let (rf, gf, bf) = hsv_to_rgb(self.h, self.s, self.v);
49        Rgb {
50            r: (rf * 255f32) as u8,
51            g: (gf * 255f32) as u8,
52            b: (bf * 255f32) as u8,
53        }
54    }
55    #[inline]
56    pub fn get_hue(&self) -> f32 {
57        self.h
58    }
59    #[inline]
60    pub fn get_saturation(&self) -> f32 {
61        self.s
62    }
63    #[inline]
64    pub fn get_value(&self) -> f32 {
65        self.v
66    }
67    #[inline]
68    pub fn get_hue_p(&self) -> u16 {
69        self.h.max(0f32).min(360f32) as u16
70    }
71    #[inline]
72    pub fn get_saturation_p(&self) -> u16 {
73        (self.s * 100f32).max(0f32).min(100f32) as u16
74    }
75    #[inline]
76    pub fn get_value_p(&self) -> u16 {
77        (self.v * 100f32).max(0f32).min(100f32) as u16
78    }
79}
80
81#[inline]
82fn rgb_to_hsv(r: f32, g: f32, b: f32) -> (f32, f32, f32) {
83    let c_max = r.max(g).max(b);
84    let c_min = r.min(g).min(b);
85    let delta = c_max - c_min;
86
87    let mut h = 0f32;
88    let mut s = 0f32;
89    let v = c_max;
90
91    if delta > 0f32 {
92        if c_max == r {
93            h = 60f32 * (((g - b) / delta) % 6f32);
94        } else if c_max == g {
95            h = 60f32 * (((b - r) / delta) + 2f32);
96        } else if c_max == b {
97            h = 60f32 * (((r - g) / delta) + 4f32);
98        }
99
100        if c_max > 0f32 {
101            s = delta / c_max;
102        }
103    }
104
105    if h < 0f32 {
106        h += 360f32;
107    }
108
109    (h, s, v)
110}
111
112#[inline]
113#[allow(clippy::manual_range_contains)]
114fn hsv_to_rgb(h: f32, s: f32, v: f32) -> (f32, f32, f32) {
115    let c = v * s;
116    let h_prime = (h / 60f32) % 6f32;
117    let x = c * (1f32 - ((h_prime % 2f32) - 1f32).abs());
118    let m = v - c;
119
120    let (r, g, b) = if h_prime >= 0f32 && h_prime < 1f32 {
121        (c, x, 0f32)
122    } else if h_prime >= 1f32 && h_prime < 2f32 {
123        (x, c, 0f32)
124    } else if h_prime >= 2f32 && h_prime < 3f32 {
125        (0f32, c, x)
126    } else if h_prime >= 3f32 && h_prime < 4f32 {
127        (0f32, x, c)
128    } else if h_prime >= 4f32 && h_prime < 5f32 {
129        (x, 0f32, c)
130    } else if h_prime >= 5f32 && h_prime < 6f32 {
131        (c, 0f32, x)
132    } else {
133        (0f32, 0f32, 0f32)
134    };
135
136    (r + m, g + m, b + m)
137}