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