1use crate::{
6 Alpha, ColorToComponents, Gray, Hue, Lcha, LinearRgba, Mix, Srgba, StandardColor, Xyza,
7};
8use bevy_math::{Vec3, Vec4};
9#[cfg(feature = "bevy_reflect")]
10use bevy_reflect::prelude::*;
11
12#[doc = include_str!("../docs/conversion.md")]
15#[doc = include_str!("../docs/diagrams/model_graph.svg")]
17#[derive(Debug, Clone, Copy, PartialEq)]
19#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(PartialEq, Default))]
20#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
21#[cfg_attr(
22 all(feature = "serialize", feature = "bevy_reflect"),
23 reflect(Serialize, Deserialize)
24)]
25pub struct Hwba {
26 pub hue: f32,
28 pub whiteness: f32,
30 pub blackness: f32,
32 pub alpha: f32,
34}
35
36impl StandardColor for Hwba {}
37
38impl Hwba {
39 pub const fn new(hue: f32, whiteness: f32, blackness: f32, alpha: f32) -> Self {
48 Self {
49 hue,
50 whiteness,
51 blackness,
52 alpha,
53 }
54 }
55
56 pub const fn hwb(hue: f32, whiteness: f32, blackness: f32) -> Self {
64 Self::new(hue, whiteness, blackness, 1.0)
65 }
66
67 pub const fn with_whiteness(self, whiteness: f32) -> Self {
69 Self { whiteness, ..self }
70 }
71
72 pub const fn with_blackness(self, blackness: f32) -> Self {
74 Self { blackness, ..self }
75 }
76}
77
78impl Default for Hwba {
79 fn default() -> Self {
80 Self::new(0., 0., 1., 1.)
81 }
82}
83
84impl Mix for Hwba {
85 #[inline]
86 fn mix(&self, other: &Self, factor: f32) -> Self {
87 let n_factor = 1.0 - factor;
88 Self {
89 hue: crate::color_ops::lerp_hue(self.hue, other.hue, factor),
90 whiteness: self.whiteness * n_factor + other.whiteness * factor,
91 blackness: self.blackness * n_factor + other.blackness * factor,
92 alpha: self.alpha * n_factor + other.alpha * factor,
93 }
94 }
95}
96
97impl Gray for Hwba {
98 const BLACK: Self = Self::new(0., 0., 1., 1.);
99 const WHITE: Self = Self::new(0., 1., 0., 1.);
100}
101
102impl Alpha for Hwba {
103 #[inline]
104 fn with_alpha(&self, alpha: f32) -> Self {
105 Self { alpha, ..*self }
106 }
107
108 #[inline]
109 fn alpha(&self) -> f32 {
110 self.alpha
111 }
112
113 #[inline]
114 fn set_alpha(&mut self, alpha: f32) {
115 self.alpha = alpha;
116 }
117}
118
119impl Hue for Hwba {
120 #[inline]
121 fn with_hue(&self, hue: f32) -> Self {
122 Self { hue, ..*self }
123 }
124
125 #[inline]
126 fn hue(&self) -> f32 {
127 self.hue
128 }
129
130 #[inline]
131 fn set_hue(&mut self, hue: f32) {
132 self.hue = hue;
133 }
134}
135
136impl ColorToComponents for Hwba {
137 fn to_f32_array(self) -> [f32; 4] {
138 [self.hue, self.whiteness, self.blackness, self.alpha]
139 }
140
141 fn to_f32_array_no_alpha(self) -> [f32; 3] {
142 [self.hue, self.whiteness, self.blackness]
143 }
144
145 fn to_vec4(self) -> Vec4 {
146 Vec4::new(self.hue, self.whiteness, self.blackness, self.alpha)
147 }
148
149 fn to_vec3(self) -> Vec3 {
150 Vec3::new(self.hue, self.whiteness, self.blackness)
151 }
152
153 fn from_f32_array(color: [f32; 4]) -> Self {
154 Self {
155 hue: color[0],
156 whiteness: color[1],
157 blackness: color[2],
158 alpha: color[3],
159 }
160 }
161
162 fn from_f32_array_no_alpha(color: [f32; 3]) -> Self {
163 Self {
164 hue: color[0],
165 whiteness: color[1],
166 blackness: color[2],
167 alpha: 1.0,
168 }
169 }
170
171 fn from_vec4(color: Vec4) -> Self {
172 Self {
173 hue: color[0],
174 whiteness: color[1],
175 blackness: color[2],
176 alpha: color[3],
177 }
178 }
179
180 fn from_vec3(color: Vec3) -> Self {
181 Self {
182 hue: color[0],
183 whiteness: color[1],
184 blackness: color[2],
185 alpha: 1.0,
186 }
187 }
188}
189
190impl From<Srgba> for Hwba {
191 fn from(
192 Srgba {
193 red,
194 green,
195 blue,
196 alpha,
197 }: Srgba,
198 ) -> Self {
199 let x_max = 0f32.max(red).max(green).max(blue);
201 let x_min = 1f32.min(red).min(green).min(blue);
202
203 let chroma = x_max - x_min;
204
205 let hue = if chroma == 0.0 {
206 0.0
207 } else if red == x_max {
208 60.0 * (green - blue) / chroma
209 } else if green == x_max {
210 60.0 * (2.0 + (blue - red) / chroma)
211 } else {
212 60.0 * (4.0 + (red - green) / chroma)
213 };
214 let hue = if hue < 0.0 { 360.0 + hue } else { hue };
215
216 let whiteness = x_min;
217 let blackness = 1.0 - x_max;
218
219 Hwba {
220 hue,
221 whiteness,
222 blackness,
223 alpha,
224 }
225 }
226}
227
228impl From<Hwba> for Srgba {
229 fn from(
230 Hwba {
231 hue,
232 whiteness,
233 blackness,
234 alpha,
235 }: Hwba,
236 ) -> Self {
237 let w = whiteness;
239 let v = 1. - blackness;
240
241 let h = (hue % 360.) / 60.;
242 let i = h.floor();
243 let f = h - i;
244
245 let i = i as u8;
246
247 let f = if i % 2 == 0 { f } else { 1. - f };
248
249 let n = w + f * (v - w);
250
251 let (red, green, blue) = match i {
252 0 => (v, n, w),
253 1 => (n, v, w),
254 2 => (w, v, n),
255 3 => (w, n, v),
256 4 => (n, w, v),
257 5 => (v, w, n),
258 _ => unreachable!("i is bounded in [0, 6)"),
259 };
260
261 Srgba::new(red, green, blue, alpha)
262 }
263}
264
265impl From<LinearRgba> for Hwba {
268 fn from(value: LinearRgba) -> Self {
269 Srgba::from(value).into()
270 }
271}
272
273impl From<Hwba> for LinearRgba {
274 fn from(value: Hwba) -> Self {
275 Srgba::from(value).into()
276 }
277}
278
279impl From<Lcha> for Hwba {
280 fn from(value: Lcha) -> Self {
281 Srgba::from(value).into()
282 }
283}
284
285impl From<Hwba> for Lcha {
286 fn from(value: Hwba) -> Self {
287 Srgba::from(value).into()
288 }
289}
290
291impl From<Xyza> for Hwba {
292 fn from(value: Xyza) -> Self {
293 Srgba::from(value).into()
294 }
295}
296
297impl From<Hwba> for Xyza {
298 fn from(value: Hwba) -> Self {
299 Srgba::from(value).into()
300 }
301}
302
303#[cfg(test)]
304mod tests {
305 use super::*;
306 use crate::{
307 color_difference::EuclideanDistance, test_colors::TEST_COLORS, testing::assert_approx_eq,
308 };
309
310 #[test]
311 fn test_to_from_srgba() {
312 let hwba = Hwba::new(0.0, 0.5, 0.5, 1.0);
313 let srgba: Srgba = hwba.into();
314 let hwba2: Hwba = srgba.into();
315 assert_approx_eq!(hwba.hue, hwba2.hue, 0.001);
316 assert_approx_eq!(hwba.whiteness, hwba2.whiteness, 0.001);
317 assert_approx_eq!(hwba.blackness, hwba2.blackness, 0.001);
318 assert_approx_eq!(hwba.alpha, hwba2.alpha, 0.001);
319 }
320
321 #[test]
322 fn test_to_from_srgba_2() {
323 for color in TEST_COLORS.iter() {
324 let rgb2: Srgba = (color.hwb).into();
325 let hwb2: Hwba = (color.rgb).into();
326 assert!(
327 color.rgb.distance(&rgb2) < 0.00001,
328 "{}: {:?} != {:?}",
329 color.name,
330 color.rgb,
331 rgb2
332 );
333 assert_approx_eq!(color.hwb.hue, hwb2.hue, 0.001);
334 assert_approx_eq!(color.hwb.whiteness, hwb2.whiteness, 0.001);
335 assert_approx_eq!(color.hwb.blackness, hwb2.blackness, 0.001);
336 assert_approx_eq!(color.hwb.alpha, hwb2.alpha, 0.001);
337 }
338 }
339}