Skip to main content

bevy_color/
okhsva.rs

1use crate::{
2    okcolor_convert::{okhsv_to_oklab, oklab_to_okhsv},
3    okhsla::Okhsla,
4    Alpha, ColorToComponents, Gray, Hsla, Hsva, Hue, Hwba, Laba, Lcha, LinearRgba, Mix, Oklaba,
5    Oklcha, Saturation, Srgba, StandardColor, Xyza,
6};
7use bevy_math::{Vec3, Vec4};
8#[cfg(feature = "bevy_reflect")]
9use bevy_reflect::prelude::*;
10
11/// Color in Okhsv color space with alpha.
12/// Further information on this color model can be found on <https://bottosson.github.io/posts/colorpicker>.
13///
14/// Okhsv is defined relative to the sRGB (Rec. 709) gamut. Converting a wide-gamut or
15/// HDR color can push saturation and value outside `[0.0, 1.0]`.
16#[doc = include_str!("../docs/conversion.md")]
17/// <div>
18#[doc = include_str!("../docs/diagrams/model_graph.svg")]
19/// </div>
20#[derive(Debug, Clone, Copy, PartialEq)]
21#[cfg_attr(
22    feature = "bevy_reflect",
23    derive(Reflect),
24    reflect(Clone, PartialEq, Default)
25)]
26#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
27#[cfg_attr(
28    all(feature = "serialize", feature = "bevy_reflect"),
29    reflect(Serialize, Deserialize)
30)]
31pub struct Okhsva {
32    /// The hue channel. [0.0, 360.0]
33    pub hue: f32,
34    /// The saturation channel. [0.0, 1.0]
35    pub saturation: f32,
36    /// The value channel. [0.0, 1.0]
37    pub value: f32,
38    /// The alpha channel. [0.0, 1.0]
39    pub alpha: f32,
40}
41
42impl StandardColor for Okhsva {}
43
44impl Okhsva {
45    /// Construct a new [`Okhsva`] color from components.
46    ///
47    /// # Arguments
48    ///
49    /// * `hue` - Hue channel. [0.0, 360.0]
50    /// * `saturation` - Saturation channel. [0.0, 1.0]
51    /// * `value` - Value channel. [0.0, 1.0]
52    /// * `alpha` - Alpha channel. [0.0, 1.0]
53    pub const fn new(hue: f32, saturation: f32, value: f32, alpha: f32) -> Self {
54        Self {
55            hue,
56            saturation,
57            value,
58            alpha,
59        }
60    }
61
62    /// Construct a new [`Okhsva`] color from (h, s, v) components, with the default alpha (1.0).
63    ///
64    /// # Arguments
65    ///
66    /// * `hue` - Hue channel. [0.0, 360.0]
67    /// * `saturation` - Saturation channel. [0.0, 1.0]
68    /// * `value` - Value channel. [0.0, 1.0]
69    pub const fn hsv(hue: f32, saturation: f32, value: f32) -> Self {
70        Self::new(hue, saturation, value, 1.0)
71    }
72
73    /// Return a copy of this color with the saturation channel set to the given value.
74    pub const fn with_saturation(self, saturation: f32) -> Self {
75        Self { saturation, ..self }
76    }
77
78    /// Return a copy of this color with the value channel set to the given value.
79    pub const fn with_value(self, value: f32) -> Self {
80        Self { value, ..self }
81    }
82}
83
84impl Default for Okhsva {
85    fn default() -> Self {
86        Self::new(0., 0., 1., 1.)
87    }
88}
89
90impl Mix for Okhsva {
91    #[inline]
92    fn mix(&self, other: &Self, factor: f32) -> Self {
93        let n_factor = 1.0 - factor;
94        Self {
95            hue: crate::color_ops::lerp_hue(self.hue, other.hue, factor),
96            saturation: self.saturation * n_factor + other.saturation * factor,
97            value: self.value * n_factor + other.value * factor,
98            alpha: self.alpha * n_factor + other.alpha * factor,
99        }
100    }
101}
102
103impl Gray for Okhsva {
104    const BLACK: Self = Self::new(0., 0., 0., 1.);
105    const WHITE: Self = Self::new(0., 0., 1., 1.);
106}
107
108impl Alpha for Okhsva {
109    #[inline]
110    fn with_alpha(&self, alpha: f32) -> Self {
111        Self { alpha, ..*self }
112    }
113
114    #[inline]
115    fn alpha(&self) -> f32 {
116        self.alpha
117    }
118
119    #[inline]
120    fn set_alpha(&mut self, alpha: f32) {
121        self.alpha = alpha;
122    }
123}
124
125impl Hue for Okhsva {
126    #[inline]
127    fn with_hue(&self, hue: f32) -> Self {
128        Self { hue, ..*self }
129    }
130
131    #[inline]
132    fn hue(&self) -> f32 {
133        self.hue
134    }
135
136    #[inline]
137    fn set_hue(&mut self, hue: f32) {
138        self.hue = hue;
139    }
140}
141
142impl Saturation for Okhsva {
143    #[inline]
144    fn with_saturation(&self, saturation: f32) -> Self {
145        Self {
146            saturation,
147            ..*self
148        }
149    }
150
151    #[inline]
152    fn saturation(&self) -> f32 {
153        self.saturation
154    }
155
156    #[inline]
157    fn set_saturation(&mut self, saturation: f32) {
158        self.saturation = saturation;
159    }
160}
161
162impl ColorToComponents for Okhsva {
163    fn to_f32_array(self) -> [f32; 4] {
164        [self.hue, self.saturation, self.value, self.alpha]
165    }
166
167    fn to_f32_array_no_alpha(self) -> [f32; 3] {
168        [self.hue, self.saturation, self.value]
169    }
170
171    fn to_vec4(self) -> Vec4 {
172        Vec4::new(self.hue, self.saturation, self.value, self.alpha)
173    }
174
175    fn to_vec3(self) -> Vec3 {
176        Vec3::new(self.hue, self.saturation, self.value)
177    }
178
179    fn from_f32_array(color: [f32; 4]) -> Self {
180        Self {
181            hue: color[0],
182            saturation: color[1],
183            value: color[2],
184            alpha: color[3],
185        }
186    }
187
188    fn from_f32_array_no_alpha(color: [f32; 3]) -> Self {
189        Self {
190            hue: color[0],
191            saturation: color[1],
192            value: color[2],
193            alpha: 1.0,
194        }
195    }
196
197    fn from_vec4(color: Vec4) -> Self {
198        Self {
199            hue: color[0],
200            saturation: color[1],
201            value: color[2],
202            alpha: color[3],
203        }
204    }
205
206    fn from_vec3(color: Vec3) -> Self {
207        Self {
208            hue: color[0],
209            saturation: color[1],
210            value: color[2],
211            alpha: 1.0,
212        }
213    }
214}
215
216impl From<Oklaba> for Okhsva {
217    fn from(value: Oklaba) -> Self {
218        oklab_to_okhsv(value)
219    }
220}
221
222impl From<Okhsva> for Oklaba {
223    fn from(value: Okhsva) -> Self {
224        okhsv_to_oklab(value)
225    }
226}
227
228// Derived Conversions
229
230impl From<LinearRgba> for Okhsva {
231    fn from(value: LinearRgba) -> Self {
232        Oklaba::from(value).into()
233    }
234}
235
236impl From<Okhsva> for LinearRgba {
237    fn from(value: Okhsva) -> Self {
238        Oklaba::from(value).into()
239    }
240}
241
242impl From<Srgba> for Okhsva {
243    fn from(value: Srgba) -> Self {
244        Oklaba::from(value).into()
245    }
246}
247
248impl From<Okhsva> for Srgba {
249    fn from(value: Okhsva) -> Self {
250        Oklaba::from(value).into()
251    }
252}
253
254impl From<Hwba> for Okhsva {
255    fn from(value: Hwba) -> Self {
256        Oklaba::from(value).into()
257    }
258}
259
260impl From<Okhsva> for Hwba {
261    fn from(value: Okhsva) -> Self {
262        Oklaba::from(value).into()
263    }
264}
265
266impl From<Lcha> for Okhsva {
267    fn from(value: Lcha) -> Self {
268        Oklaba::from(value).into()
269    }
270}
271
272impl From<Okhsva> for Lcha {
273    fn from(value: Okhsva) -> Self {
274        Oklaba::from(value).into()
275    }
276}
277
278impl From<Xyza> for Okhsva {
279    fn from(value: Xyza) -> Self {
280        Oklaba::from(value).into()
281    }
282}
283
284impl From<Okhsva> for Xyza {
285    fn from(value: Okhsva) -> Self {
286        Oklaba::from(value).into()
287    }
288}
289
290impl From<Okhsla> for Okhsva {
291    fn from(value: Okhsla) -> Self {
292        Oklaba::from(value).into()
293    }
294}
295
296impl From<Okhsva> for Okhsla {
297    fn from(value: Okhsva) -> Self {
298        Oklaba::from(value).into()
299    }
300}
301
302impl From<Hsla> for Okhsva {
303    fn from(value: Hsla) -> Self {
304        Oklaba::from(value).into()
305    }
306}
307
308impl From<Okhsva> for Hsla {
309    fn from(value: Okhsva) -> Self {
310        Oklaba::from(value).into()
311    }
312}
313
314impl From<Hsva> for Okhsva {
315    fn from(value: Hsva) -> Self {
316        Oklaba::from(value).into()
317    }
318}
319
320impl From<Okhsva> for Hsva {
321    fn from(value: Okhsva) -> Self {
322        Oklaba::from(value).into()
323    }
324}
325
326impl From<Laba> for Okhsva {
327    fn from(value: Laba) -> Self {
328        Oklaba::from(value).into()
329    }
330}
331
332impl From<Okhsva> for Laba {
333    fn from(value: Okhsva) -> Self {
334        Oklaba::from(value).into()
335    }
336}
337
338impl From<Oklcha> for Okhsva {
339    fn from(value: Oklcha) -> Self {
340        Oklaba::from(value).into()
341    }
342}
343
344impl From<Okhsva> for Oklcha {
345    fn from(value: Okhsva) -> Self {
346        Oklaba::from(value).into()
347    }
348}
349
350#[cfg(test)]
351mod tests {
352    use super::*;
353    use crate::{
354        color_difference::EuclideanDistance, test_colors::TEST_COLORS, testing::assert_approx_eq,
355    };
356
357    #[test]
358    fn test_from_oklaba() {
359        // Test `oklab_l == 0.0`
360        let oklaba = Oklaba::new(0.0, 0.5, 0.5, 1.0);
361        let okhsva: Okhsva = oklaba.into();
362        let oklaba2: Oklaba = okhsva.into();
363        assert_approx_eq!(okhsva.hue, 0.0, 0.001);
364        assert_approx_eq!(okhsva.saturation, 0.0, 0.001);
365        assert_approx_eq!(okhsva.value, 0.0, 0.001);
366        assert_approx_eq!(okhsva.alpha, 1.0, 0.001);
367
368        assert_approx_eq!(oklaba.lightness, oklaba2.lightness, 0.001);
369        assert_approx_eq!(0.0, oklaba2.a, 0.001);
370        assert_approx_eq!(0.0, oklaba2.b, 0.001);
371        assert_approx_eq!(oklaba.alpha, oklaba2.alpha, 0.001);
372
373        // Test `oklab_l == 1.0`
374        let oklaba = Oklaba::new(1.0, 0.5, 0.5, 1.0);
375        let okhsva: Okhsva = oklaba.into();
376        let oklaba2: Oklaba = okhsva.into();
377        assert_approx_eq!(okhsva.hue, 0.0, 0.001);
378        assert_approx_eq!(okhsva.saturation, 0.0, 0.001);
379        assert_approx_eq!(okhsva.value, 1.0, 0.001);
380        assert_approx_eq!(okhsva.alpha, 1.0, 0.001);
381
382        assert_approx_eq!(oklaba.lightness, oklaba2.lightness, 0.001);
383        assert_approx_eq!(0.0, oklaba2.a, 0.001);
384        assert_approx_eq!(0.0, oklaba2.b, 0.001);
385        assert_approx_eq!(oklaba.alpha, oklaba2.alpha, 0.001);
386
387        // Test `oklab_a == 0.0 && oklab_b ==0.0` (C == 0.0)
388        let oklaba = Oklaba::new(0.5, 0.0, 0.0, 1.0);
389        let okhsva: Okhsva = oklaba.into();
390        let oklaba2: Oklaba = okhsva.into();
391        assert_approx_eq!(okhsva.hue, 0.0, 0.001);
392        assert_approx_eq!(okhsva.saturation, 0.0, 0.001);
393        assert_approx_eq!(okhsva.value, 0.42114055, 0.001);
394        assert_approx_eq!(okhsva.alpha, 1.0, 0.001);
395
396        assert_approx_eq!(oklaba.lightness, oklaba2.lightness, 0.001);
397        assert_approx_eq!(0.0, oklaba2.a, 0.001);
398        assert_approx_eq!(0.0, oklaba2.b, 0.001);
399        assert_approx_eq!(oklaba.alpha, oklaba2.alpha, 0.001);
400    }
401
402    #[test]
403    fn test_to_from_srgba() {
404        let okhsva = Okhsva::new(180.0, 0.5, 0.5, 1.0);
405        let srgba: Srgba = okhsva.into();
406        let okhsva2: Okhsva = srgba.into();
407        assert_approx_eq!(okhsva.hue, okhsva2.hue, 0.001);
408        assert_approx_eq!(okhsva.saturation, okhsva2.saturation, 0.001);
409        assert_approx_eq!(okhsva.value, okhsva2.value, 0.001);
410        assert_approx_eq!(okhsva.alpha, okhsva2.alpha, 0.001);
411    }
412
413    #[test]
414    fn test_to_from_srgba_2() {
415        for color in TEST_COLORS.iter() {
416            let rgb2: Srgba = (color.okhsv).into();
417            let okhsv: Okhsva = (color.rgb).into();
418            assert!(
419                color.rgb.distance(&rgb2) < 0.001,
420                "{}: {:?} != {:?}",
421                color.name,
422                color.rgb,
423                rgb2,
424            );
425            let msg = alloc::format!(
426                "{}: expected {:?}, got {:?}",
427                color.name,
428                color.okhsv,
429                okhsv
430            );
431            assert_approx_eq!(color.okhsv.hue, okhsv.hue, 0.001, msg);
432            assert_approx_eq!(color.okhsv.saturation, okhsv.saturation, 0.001, msg);
433            assert_approx_eq!(color.okhsv.value, okhsv.value, 0.001, msg);
434            assert_approx_eq!(color.okhsv.alpha, okhsv.alpha, 0.001, msg);
435        }
436    }
437
438    #[test]
439    fn test_to_from_linear() {
440        let okhsva = Okhsva::new(0.5, 0.5, 0.5, 1.0);
441        let linear: LinearRgba = okhsva.into();
442        let okhsva2: Okhsva = linear.into();
443        assert_approx_eq!(okhsva.hue, okhsva2.hue, 0.001);
444        assert_approx_eq!(okhsva.saturation, okhsva2.saturation, 0.001);
445        assert_approx_eq!(okhsva.value, okhsva2.value, 0.001);
446        assert_approx_eq!(okhsva.alpha, okhsva2.alpha, 0.001);
447    }
448}