bevy_color/
hsva.rs

1use crate::{
2    Alpha, ColorToComponents, Gray, Hue, Hwba, Lcha, LinearRgba, Mix, Saturation, Srgba,
3    StandardColor, Xyza,
4};
5use bevy_math::{Vec3, Vec4};
6#[cfg(feature = "bevy_reflect")]
7use bevy_reflect::prelude::*;
8
9/// Color in Hue-Saturation-Value (HSV) color space with alpha.
10/// Further information on this color model can be found on [Wikipedia](https://en.wikipedia.org/wiki/HSL_and_HSV).
11#[doc = include_str!("../docs/conversion.md")]
12/// <div>
13#[doc = include_str!("../docs/diagrams/model_graph.svg")]
14/// </div>
15#[derive(Debug, Clone, Copy, PartialEq)]
16#[cfg_attr(
17    feature = "bevy_reflect",
18    derive(Reflect),
19    reflect(Clone, PartialEq, Default)
20)]
21#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
22#[cfg_attr(
23    all(feature = "serialize", feature = "bevy_reflect"),
24    reflect(Serialize, Deserialize)
25)]
26pub struct Hsva {
27    /// The hue channel. [0.0, 360.0]
28    pub hue: f32,
29    /// The saturation channel. [0.0, 1.0]
30    pub saturation: f32,
31    /// The value channel. [0.0, 1.0]
32    pub value: f32,
33    /// The alpha channel. [0.0, 1.0]
34    pub alpha: f32,
35}
36
37impl StandardColor for Hsva {}
38
39impl Hsva {
40    /// Construct a new [`Hsva`] color from components.
41    ///
42    /// # Arguments
43    ///
44    /// * `hue` - Hue channel. [0.0, 360.0]
45    /// * `saturation` - Saturation channel. [0.0, 1.0]
46    /// * `value` - Value channel. [0.0, 1.0]
47    /// * `alpha` - Alpha channel. [0.0, 1.0]
48    pub const fn new(hue: f32, saturation: f32, value: f32, alpha: f32) -> Self {
49        Self {
50            hue,
51            saturation,
52            value,
53            alpha,
54        }
55    }
56
57    /// Construct a new [`Hsva`] color from (h, s, v) components, with the default alpha (1.0).
58    ///
59    /// # Arguments
60    ///
61    /// * `hue` - Hue channel. [0.0, 360.0]
62    /// * `saturation` - Saturation channel. [0.0, 1.0]
63    /// * `value` - Value channel. [0.0, 1.0]
64    pub const fn hsv(hue: f32, saturation: f32, value: f32) -> Self {
65        Self::new(hue, saturation, value, 1.0)
66    }
67
68    /// Return a copy of this color with the saturation channel set to the given value.
69    pub const fn with_saturation(self, saturation: f32) -> Self {
70        Self { saturation, ..self }
71    }
72
73    /// Return a copy of this color with the value channel set to the given value.
74    pub const fn with_value(self, value: f32) -> Self {
75        Self { value, ..self }
76    }
77}
78
79impl Default for Hsva {
80    fn default() -> Self {
81        Self::new(0., 0., 1., 1.)
82    }
83}
84
85impl Mix for Hsva {
86    #[inline]
87    fn mix(&self, other: &Self, factor: f32) -> Self {
88        let n_factor = 1.0 - factor;
89        Self {
90            hue: crate::color_ops::lerp_hue(self.hue, other.hue, factor),
91            saturation: self.saturation * n_factor + other.saturation * factor,
92            value: self.value * n_factor + other.value * factor,
93            alpha: self.alpha * n_factor + other.alpha * factor,
94        }
95    }
96}
97
98impl Gray for Hsva {
99    const BLACK: Self = Self::new(0., 0., 0., 1.);
100    const WHITE: Self = Self::new(0., 0., 1., 1.);
101}
102
103impl Alpha for Hsva {
104    #[inline]
105    fn with_alpha(&self, alpha: f32) -> Self {
106        Self { alpha, ..*self }
107    }
108
109    #[inline]
110    fn alpha(&self) -> f32 {
111        self.alpha
112    }
113
114    #[inline]
115    fn set_alpha(&mut self, alpha: f32) {
116        self.alpha = alpha;
117    }
118}
119
120impl Hue for Hsva {
121    #[inline]
122    fn with_hue(&self, hue: f32) -> Self {
123        Self { hue, ..*self }
124    }
125
126    #[inline]
127    fn hue(&self) -> f32 {
128        self.hue
129    }
130
131    #[inline]
132    fn set_hue(&mut self, hue: f32) {
133        self.hue = hue;
134    }
135}
136
137impl Saturation for Hsva {
138    #[inline]
139    fn with_saturation(&self, saturation: f32) -> Self {
140        Self {
141            saturation,
142            ..*self
143        }
144    }
145
146    #[inline]
147    fn saturation(&self) -> f32 {
148        self.saturation
149    }
150
151    #[inline]
152    fn set_saturation(&mut self, saturation: f32) {
153        self.saturation = saturation;
154    }
155}
156
157impl From<Hsva> for Hwba {
158    fn from(
159        Hsva {
160            hue,
161            saturation,
162            value,
163            alpha,
164        }: Hsva,
165    ) -> Self {
166        // Based on https://en.wikipedia.org/wiki/HWB_color_model#Conversion
167        let whiteness = (1. - saturation) * value;
168        let blackness = 1. - value;
169
170        Hwba::new(hue, whiteness, blackness, alpha)
171    }
172}
173
174impl From<Hwba> for Hsva {
175    fn from(
176        Hwba {
177            hue,
178            whiteness,
179            blackness,
180            alpha,
181        }: Hwba,
182    ) -> Self {
183        // Based on https://en.wikipedia.org/wiki/HWB_color_model#Conversion
184        let value = 1. - blackness;
185        let saturation = if value != 0. {
186            1. - (whiteness / value)
187        } else {
188            0.
189        };
190
191        Hsva::new(hue, saturation, value, alpha)
192    }
193}
194
195impl ColorToComponents for Hsva {
196    fn to_f32_array(self) -> [f32; 4] {
197        [self.hue, self.saturation, self.value, self.alpha]
198    }
199
200    fn to_f32_array_no_alpha(self) -> [f32; 3] {
201        [self.hue, self.saturation, self.value]
202    }
203
204    fn to_vec4(self) -> Vec4 {
205        Vec4::new(self.hue, self.saturation, self.value, self.alpha)
206    }
207
208    fn to_vec3(self) -> Vec3 {
209        Vec3::new(self.hue, self.saturation, self.value)
210    }
211
212    fn from_f32_array(color: [f32; 4]) -> Self {
213        Self {
214            hue: color[0],
215            saturation: color[1],
216            value: color[2],
217            alpha: color[3],
218        }
219    }
220
221    fn from_f32_array_no_alpha(color: [f32; 3]) -> Self {
222        Self {
223            hue: color[0],
224            saturation: color[1],
225            value: color[2],
226            alpha: 1.0,
227        }
228    }
229
230    fn from_vec4(color: Vec4) -> Self {
231        Self {
232            hue: color[0],
233            saturation: color[1],
234            value: color[2],
235            alpha: color[3],
236        }
237    }
238
239    fn from_vec3(color: Vec3) -> Self {
240        Self {
241            hue: color[0],
242            saturation: color[1],
243            value: color[2],
244            alpha: 1.0,
245        }
246    }
247}
248
249// Derived Conversions
250
251impl From<Srgba> for Hsva {
252    fn from(value: Srgba) -> Self {
253        Hwba::from(value).into()
254    }
255}
256
257impl From<Hsva> for Srgba {
258    fn from(value: Hsva) -> Self {
259        Hwba::from(value).into()
260    }
261}
262
263impl From<LinearRgba> for Hsva {
264    fn from(value: LinearRgba) -> Self {
265        Hwba::from(value).into()
266    }
267}
268
269impl From<Hsva> for LinearRgba {
270    fn from(value: Hsva) -> Self {
271        Hwba::from(value).into()
272    }
273}
274
275impl From<Lcha> for Hsva {
276    fn from(value: Lcha) -> Self {
277        Hwba::from(value).into()
278    }
279}
280
281impl From<Hsva> for Lcha {
282    fn from(value: Hsva) -> Self {
283        Hwba::from(value).into()
284    }
285}
286
287impl From<Xyza> for Hsva {
288    fn from(value: Xyza) -> Self {
289        Hwba::from(value).into()
290    }
291}
292
293impl From<Hsva> for Xyza {
294    fn from(value: Hsva) -> Self {
295        Hwba::from(value).into()
296    }
297}
298
299#[cfg(test)]
300mod tests {
301    use super::*;
302    use crate::{
303        color_difference::EuclideanDistance, test_colors::TEST_COLORS, testing::assert_approx_eq,
304    };
305
306    #[test]
307    fn test_to_from_srgba() {
308        let hsva = Hsva::new(180., 0.5, 0.5, 1.0);
309        let srgba: Srgba = hsva.into();
310        let hsva2: Hsva = srgba.into();
311        assert_approx_eq!(hsva.hue, hsva2.hue, 0.001);
312        assert_approx_eq!(hsva.saturation, hsva2.saturation, 0.001);
313        assert_approx_eq!(hsva.value, hsva2.value, 0.001);
314        assert_approx_eq!(hsva.alpha, hsva2.alpha, 0.001);
315    }
316
317    #[test]
318    fn test_to_from_srgba_2() {
319        for color in TEST_COLORS.iter() {
320            let rgb2: Srgba = (color.hsv).into();
321            let hsv2: Hsva = (color.rgb).into();
322            assert!(
323                color.rgb.distance(&rgb2) < 0.00001,
324                "{}: {:?} != {:?}",
325                color.name,
326                color.rgb,
327                rgb2
328            );
329            assert_approx_eq!(color.hsv.hue, hsv2.hue, 0.001);
330            assert_approx_eq!(color.hsv.saturation, hsv2.saturation, 0.001);
331            assert_approx_eq!(color.hsv.value, hsv2.value, 0.001);
332            assert_approx_eq!(color.hsv.alpha, hsv2.alpha, 0.001);
333        }
334    }
335}