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