bevy_color/
hsva.rs

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