bevy_color/color_ops.rs
1use bevy_math::{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 = (self.hue() + degrees).rem_euclid(360.);
94 self.with_hue(rotated_hue)
95 }
96}
97
98/// Trait with methods for converting colors to non-color types
99pub trait ColorToComponents {
100 /// Convert to an f32 array
101 fn to_f32_array(self) -> [f32; 4];
102 /// Convert to an f32 array without the alpha value
103 fn to_f32_array_no_alpha(self) -> [f32; 3];
104 /// Convert to a Vec4
105 fn to_vec4(self) -> Vec4;
106 /// Convert to a Vec3
107 fn to_vec3(self) -> Vec3;
108 /// Convert from an f32 array
109 fn from_f32_array(color: [f32; 4]) -> Self;
110 /// Convert from an f32 array without the alpha value
111 fn from_f32_array_no_alpha(color: [f32; 3]) -> Self;
112 /// Convert from a Vec4
113 fn from_vec4(color: Vec4) -> Self;
114 /// Convert from a Vec3
115 fn from_vec3(color: Vec3) -> Self;
116}
117
118/// Trait with methods for converting colors to packed non-color types
119pub trait ColorToPacked {
120 /// Convert to [u8; 4] where that makes sense (Srgba is most relevant)
121 fn to_u8_array(self) -> [u8; 4];
122 /// Convert to [u8; 3] where that makes sense (Srgba is most relevant)
123 fn to_u8_array_no_alpha(self) -> [u8; 3];
124 /// Convert from [u8; 4] where that makes sense (Srgba is most relevant)
125 fn from_u8_array(color: [u8; 4]) -> Self;
126 /// Convert to [u8; 3] where that makes sense (Srgba is most relevant)
127 fn from_u8_array_no_alpha(color: [u8; 3]) -> Self;
128}
129
130/// Utility function for interpolating hue values. This ensures that the interpolation
131/// takes the shortest path around the color wheel, and that the result is always between
132/// 0 and 360.
133pub(crate) fn lerp_hue(a: f32, b: f32, t: f32) -> f32 {
134 let diff = (b - a + 180.0).rem_euclid(360.) - 180.;
135 (a + diff * t).rem_euclid(360.0)
136}
137
138#[cfg(test)]
139mod tests {
140 use core::fmt::Debug;
141
142 use super::*;
143 use crate::{testing::assert_approx_eq, Hsla};
144
145 #[test]
146 fn test_rotate_hue() {
147 let hsla = Hsla::hsl(180.0, 1.0, 0.5);
148 assert_eq!(hsla.rotate_hue(90.0), Hsla::hsl(270.0, 1.0, 0.5));
149 assert_eq!(hsla.rotate_hue(-90.0), Hsla::hsl(90.0, 1.0, 0.5));
150 assert_eq!(hsla.rotate_hue(180.0), Hsla::hsl(0.0, 1.0, 0.5));
151 assert_eq!(hsla.rotate_hue(-180.0), Hsla::hsl(0.0, 1.0, 0.5));
152 assert_eq!(hsla.rotate_hue(0.0), hsla);
153 assert_eq!(hsla.rotate_hue(360.0), hsla);
154 assert_eq!(hsla.rotate_hue(-360.0), hsla);
155 }
156
157 #[test]
158 fn test_hue_wrap() {
159 assert_approx_eq!(lerp_hue(10., 20., 0.25), 12.5, 0.001);
160 assert_approx_eq!(lerp_hue(10., 20., 0.5), 15., 0.001);
161 assert_approx_eq!(lerp_hue(10., 20., 0.75), 17.5, 0.001);
162
163 assert_approx_eq!(lerp_hue(20., 10., 0.25), 17.5, 0.001);
164 assert_approx_eq!(lerp_hue(20., 10., 0.5), 15., 0.001);
165 assert_approx_eq!(lerp_hue(20., 10., 0.75), 12.5, 0.001);
166
167 assert_approx_eq!(lerp_hue(10., 350., 0.25), 5., 0.001);
168 assert_approx_eq!(lerp_hue(10., 350., 0.5), 0., 0.001);
169 assert_approx_eq!(lerp_hue(10., 350., 0.75), 355., 0.001);
170
171 assert_approx_eq!(lerp_hue(350., 10., 0.25), 355., 0.001);
172 assert_approx_eq!(lerp_hue(350., 10., 0.5), 0., 0.001);
173 assert_approx_eq!(lerp_hue(350., 10., 0.75), 5., 0.001);
174 }
175
176 fn verify_gray<Col>()
177 where
178 Col: Gray + Debug + PartialEq,
179 {
180 assert_eq!(Col::gray(0.), Col::BLACK);
181 assert_eq!(Col::gray(1.), Col::WHITE);
182 }
183
184 #[test]
185 fn test_gray() {
186 verify_gray::<Hsla>();
187 verify_gray::<crate::Hsva>();
188 verify_gray::<crate::Hwba>();
189 verify_gray::<crate::Laba>();
190 verify_gray::<crate::Lcha>();
191 verify_gray::<crate::LinearRgba>();
192 verify_gray::<crate::Oklaba>();
193 verify_gray::<crate::Oklcha>();
194 verify_gray::<crate::Xyza>();
195 }
196}