bevy_color/
color_ops.rs

1use bevy_math::{ops, Vec3, Vec4};
2
3/// Methods for changing the luminance of a color. Note that these methods are not
4/// guaranteed to produce consistent results across color spaces,
5/// but will be within a given space.
6pub trait Luminance: Sized {
7    /// Return the luminance of this color (0.0 - 1.0).
8    fn luminance(&self) -> f32;
9
10    /// Return a new version of this color with the given luminance. The resulting color will
11    /// be clamped to the valid range for the color space; for some color spaces, clamping
12    /// may cause the hue or chroma to change.
13    fn with_luminance(&self, value: f32) -> Self;
14
15    /// Return a darker version of this color. The `amount` should be between 0.0 and 1.0.
16    /// The amount represents an absolute decrease in luminance, and is distributive:
17    /// `color.darker(a).darker(b) == color.darker(a + b)`. Colors are clamped to black
18    /// if the amount would cause them to go below black.
19    ///
20    /// For a relative decrease in luminance, you can simply `mix()` with black.
21    fn darker(&self, amount: f32) -> Self;
22
23    /// Return a lighter version of this color. The `amount` should be between 0.0 and 1.0.
24    /// The amount represents an absolute increase in luminance, and is distributive:
25    /// `color.lighter(a).lighter(b) == color.lighter(a + b)`. Colors are clamped to white
26    /// if the amount would cause them to go above white.
27    ///
28    /// For a relative increase in luminance, you can simply `mix()` with white.
29    fn lighter(&self, amount: f32) -> Self;
30}
31
32/// Linear interpolation of two colors within a given color space.
33pub trait Mix: Sized {
34    /// Linearly interpolate between this and another color, by factor.
35    /// Factor should be between 0.0 and 1.0.
36    fn mix(&self, other: &Self, factor: f32) -> Self;
37
38    /// Linearly interpolate between this and another color, by factor, storing the result
39    /// in this color. Factor should be between 0.0 and 1.0.
40    fn mix_assign(&mut self, other: Self, factor: f32) {
41        *self = self.mix(&other, factor);
42    }
43}
44
45/// Trait for returning a grayscale color of a provided lightness.
46pub trait Gray: Mix + Sized {
47    /// A pure black color.
48    const BLACK: Self;
49    /// A pure white color.
50    const WHITE: Self;
51
52    /// Returns a grey color with the provided lightness from (0.0 - 1.0). 0 is black, 1 is white.
53    fn gray(lightness: f32) -> Self {
54        Self::BLACK.mix(&Self::WHITE, lightness)
55    }
56}
57
58/// Methods for manipulating alpha values.
59pub trait Alpha: Sized {
60    /// Return a new version of this color with the given alpha value.
61    fn with_alpha(&self, alpha: f32) -> Self;
62
63    /// Return a the alpha component of this color.
64    fn alpha(&self) -> f32;
65
66    /// Sets the alpha component of this color.
67    fn set_alpha(&mut self, alpha: f32);
68
69    /// Is the alpha component of this color less than or equal to 0.0?
70    fn is_fully_transparent(&self) -> bool {
71        self.alpha() <= 0.0
72    }
73
74    /// Is the alpha component of this color greater than or equal to 1.0?
75    fn is_fully_opaque(&self) -> bool {
76        self.alpha() >= 1.0
77    }
78}
79
80/// Trait for manipulating the hue of a color.
81pub trait Hue: Sized {
82    /// Return a new version of this color with the hue channel set to the given value.
83    fn with_hue(&self, hue: f32) -> Self;
84
85    /// Return the hue of this color [0.0, 360.0].
86    fn hue(&self) -> f32;
87
88    /// Sets the hue of this color.
89    fn set_hue(&mut self, hue: f32);
90
91    /// Return a new version of this color with the hue channel rotated by the given degrees.
92    fn rotate_hue(&self, degrees: f32) -> Self {
93        let rotated_hue = ops::rem_euclid(self.hue() + degrees, 360.);
94        self.with_hue(rotated_hue)
95    }
96}
97
98/// Trait for manipulating the saturation of a color.
99///
100/// When working with color spaces that do not have native saturation components
101/// the operations are performed in [`crate::Hsla`].
102pub trait Saturation: Sized {
103    /// Return a new version of this color with the saturation channel set to the given value.
104    fn with_saturation(&self, saturation: f32) -> Self;
105
106    /// Return the saturation of this color [0.0, 1.0].
107    fn saturation(&self) -> f32;
108
109    /// Sets the saturation of this color.
110    fn set_saturation(&mut self, saturation: f32);
111}
112
113/// Trait with methods for converting colors to non-color types
114pub trait ColorToComponents {
115    /// Convert to an f32 array
116    fn to_f32_array(self) -> [f32; 4];
117    /// Convert to an f32 array without the alpha value
118    fn to_f32_array_no_alpha(self) -> [f32; 3];
119    /// Convert to a Vec4
120    fn to_vec4(self) -> Vec4;
121    /// Convert to a Vec3
122    fn to_vec3(self) -> Vec3;
123    /// Convert from an f32 array
124    fn from_f32_array(color: [f32; 4]) -> Self;
125    /// Convert from an f32 array without the alpha value
126    fn from_f32_array_no_alpha(color: [f32; 3]) -> Self;
127    /// Convert from a Vec4
128    fn from_vec4(color: Vec4) -> Self;
129    /// Convert from a Vec3
130    fn from_vec3(color: Vec3) -> Self;
131}
132
133/// Trait with methods for converting colors to packed non-color types
134pub trait ColorToPacked {
135    /// Convert to [u8; 4] where that makes sense (Srgba is most relevant)
136    fn to_u8_array(self) -> [u8; 4];
137    /// Convert to [u8; 3] where that makes sense (Srgba is most relevant)
138    fn to_u8_array_no_alpha(self) -> [u8; 3];
139    /// Convert from [u8; 4] where that makes sense (Srgba is most relevant)
140    fn from_u8_array(color: [u8; 4]) -> Self;
141    /// Convert to [u8; 3] where that makes sense (Srgba is most relevant)
142    fn from_u8_array_no_alpha(color: [u8; 3]) -> Self;
143}
144
145/// Utility function for interpolating hue values. This ensures that the interpolation
146/// takes the shortest path around the color wheel, and that the result is always between
147/// 0 and 360.
148pub(crate) fn lerp_hue(a: f32, b: f32, t: f32) -> f32 {
149    let diff = ops::rem_euclid(b - a + 180.0, 360.) - 180.;
150    ops::rem_euclid(a + diff * t, 360.)
151}
152
153#[cfg(test)]
154mod tests {
155    use core::fmt::Debug;
156
157    use super::*;
158    use crate::{testing::assert_approx_eq, Hsla};
159
160    #[test]
161    fn test_rotate_hue() {
162        let hsla = Hsla::hsl(180.0, 1.0, 0.5);
163        assert_eq!(hsla.rotate_hue(90.0), Hsla::hsl(270.0, 1.0, 0.5));
164        assert_eq!(hsla.rotate_hue(-90.0), Hsla::hsl(90.0, 1.0, 0.5));
165        assert_eq!(hsla.rotate_hue(180.0), Hsla::hsl(0.0, 1.0, 0.5));
166        assert_eq!(hsla.rotate_hue(-180.0), Hsla::hsl(0.0, 1.0, 0.5));
167        assert_eq!(hsla.rotate_hue(0.0), hsla);
168        assert_eq!(hsla.rotate_hue(360.0), hsla);
169        assert_eq!(hsla.rotate_hue(-360.0), hsla);
170    }
171
172    #[test]
173    fn test_hue_wrap() {
174        assert_approx_eq!(lerp_hue(10., 20., 0.25), 12.5, 0.001);
175        assert_approx_eq!(lerp_hue(10., 20., 0.5), 15., 0.001);
176        assert_approx_eq!(lerp_hue(10., 20., 0.75), 17.5, 0.001);
177
178        assert_approx_eq!(lerp_hue(20., 10., 0.25), 17.5, 0.001);
179        assert_approx_eq!(lerp_hue(20., 10., 0.5), 15., 0.001);
180        assert_approx_eq!(lerp_hue(20., 10., 0.75), 12.5, 0.001);
181
182        assert_approx_eq!(lerp_hue(10., 350., 0.25), 5., 0.001);
183        assert_approx_eq!(lerp_hue(10., 350., 0.5), 0., 0.001);
184        assert_approx_eq!(lerp_hue(10., 350., 0.75), 355., 0.001);
185
186        assert_approx_eq!(lerp_hue(350., 10., 0.25), 355., 0.001);
187        assert_approx_eq!(lerp_hue(350., 10., 0.5), 0., 0.001);
188        assert_approx_eq!(lerp_hue(350., 10., 0.75), 5., 0.001);
189    }
190
191    fn verify_gray<Col>()
192    where
193        Col: Gray + Debug + PartialEq,
194    {
195        assert_eq!(Col::gray(0.), Col::BLACK);
196        assert_eq!(Col::gray(1.), Col::WHITE);
197    }
198
199    #[test]
200    fn test_gray() {
201        verify_gray::<Hsla>();
202        verify_gray::<crate::Hsva>();
203        verify_gray::<crate::Hwba>();
204        verify_gray::<crate::Laba>();
205        verify_gray::<crate::Lcha>();
206        verify_gray::<crate::LinearRgba>();
207        verify_gray::<crate::Oklaba>();
208        verify_gray::<crate::Oklcha>();
209        verify_gray::<crate::Xyza>();
210    }
211}