bevy_color/
hsva.rs

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