1use crate::{
2 okhsva::Okhsva, Alpha, ColorToComponents, Gray, Hsla, Hsva, Hue, Hwba, Laba, Lcha, LinearRgba,
3 Mix, Okhsla, Oklaba, Oklcha, Srgba, StandardColor, Xyza,
4};
5use bevy_math::{Vec3, Vec4};
6#[cfg(feature = "bevy_reflect")]
7use bevy_reflect::prelude::*;
8
9#[doc = include_str!("../docs/conversion.md")]
12#[doc = include_str!("../docs/diagrams/model_graph.svg")]
14#[derive(Debug, Clone, Copy, PartialEq)]
16#[cfg_attr(
17 feature = "bevy_reflect",
18 derive(Reflect),
19 reflect(Clone, PartialEq, Default)
20)]
21#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
22#[cfg_attr(
23 all(feature = "serialize", feature = "bevy_reflect"),
24 reflect(Serialize, Deserialize)
25)]
26pub struct Okhwba {
27 pub hue: f32,
29 pub whiteness: f32,
31 pub blackness: f32,
33 pub alpha: f32,
35}
36
37impl StandardColor for Okhwba {}
38
39impl Okhwba {
40 pub const fn new(hue: f32, whiteness: f32, blackness: f32, alpha: f32) -> Self {
49 Self {
50 hue,
51 whiteness,
52 blackness,
53 alpha,
54 }
55 }
56
57 pub const fn hwb(hue: f32, whiteness: f32, blackness: f32) -> Self {
65 Self::new(hue, whiteness, blackness, 1.0)
66 }
67
68 pub const fn with_whiteness(self, whiteness: f32) -> Self {
70 Self { whiteness, ..self }
71 }
72
73 pub const fn with_blackness(self, blackness: f32) -> Self {
75 Self { blackness, ..self }
76 }
77}
78
79impl Default for Okhwba {
80 fn default() -> Self {
81 Self::new(0., 0., 1., 1.)
82 }
83}
84
85impl Mix for Okhwba {
86 #[inline]
87 fn mix(&self, other: &Self, factor: f32) -> Self {
88 let n_factor = 1.0 - factor;
89 Self {
90 hue: crate::color_ops::lerp_hue(self.hue, other.hue, factor),
91 whiteness: self.whiteness * n_factor + other.whiteness * factor,
92 blackness: self.blackness * n_factor + other.blackness * factor,
93 alpha: self.alpha * n_factor + other.alpha * factor,
94 }
95 }
96}
97
98impl Gray for Okhwba {
99 const BLACK: Self = Self::new(0., 0., 1., 1.);
100 const WHITE: Self = Self::new(0., 1., 0., 1.);
101}
102
103impl Alpha for Okhwba {
104 #[inline]
105 fn with_alpha(&self, alpha: f32) -> Self {
106 Self { alpha, ..*self }
107 }
108
109 #[inline]
110 fn alpha(&self) -> f32 {
111 self.alpha
112 }
113
114 #[inline]
115 fn set_alpha(&mut self, alpha: f32) {
116 self.alpha = alpha;
117 }
118}
119
120impl Hue for Okhwba {
121 #[inline]
122 fn with_hue(&self, hue: f32) -> Self {
123 Self { hue, ..*self }
124 }
125
126 #[inline]
127 fn hue(&self) -> f32 {
128 self.hue
129 }
130
131 #[inline]
132 fn set_hue(&mut self, hue: f32) {
133 self.hue = hue;
134 }
135}
136
137impl ColorToComponents for Okhwba {
138 fn to_f32_array(self) -> [f32; 4] {
139 [self.hue, self.whiteness, self.blackness, self.alpha]
140 }
141
142 fn to_f32_array_no_alpha(self) -> [f32; 3] {
143 [self.hue, self.whiteness, self.blackness]
144 }
145
146 fn to_vec4(self) -> Vec4 {
147 Vec4::new(self.hue, self.whiteness, self.blackness, self.alpha)
148 }
149
150 fn to_vec3(self) -> Vec3 {
151 Vec3::new(self.hue, self.whiteness, self.blackness)
152 }
153
154 fn from_f32_array(color: [f32; 4]) -> Self {
155 Self {
156 hue: color[0],
157 whiteness: color[1],
158 blackness: color[2],
159 alpha: color[3],
160 }
161 }
162
163 fn from_f32_array_no_alpha(color: [f32; 3]) -> Self {
164 Self {
165 hue: color[0],
166 whiteness: color[1],
167 blackness: color[2],
168 alpha: 1.0,
169 }
170 }
171
172 fn from_vec4(color: Vec4) -> Self {
173 Self {
174 hue: color[0],
175 whiteness: color[1],
176 blackness: color[2],
177 alpha: color[3],
178 }
179 }
180
181 fn from_vec3(color: Vec3) -> Self {
182 Self {
183 hue: color[0],
184 whiteness: color[1],
185 blackness: color[2],
186 alpha: 1.0,
187 }
188 }
189}
190
191impl From<Okhsva> for Okhwba {
192 fn from(
193 Okhsva {
194 hue,
195 saturation,
196 value,
197 alpha,
198 }: Okhsva,
199 ) -> Self {
200 let whiteness = (1. - saturation) * value;
202 let blackness = 1. - value;
203
204 Okhwba::new(hue, whiteness, blackness, alpha)
205 }
206}
207
208impl From<Okhwba> for Okhsva {
209 fn from(
210 Okhwba {
211 hue,
212 whiteness,
213 blackness,
214 alpha,
215 }: Okhwba,
216 ) -> Self {
217 let value = 1. - blackness;
219 let saturation = if value != 0. {
220 1. - (whiteness / value)
221 } else {
222 0.
223 };
224
225 Okhsva::new(hue, saturation, value, alpha)
226 }
227}
228
229impl From<LinearRgba> for Okhwba {
232 fn from(value: LinearRgba) -> Self {
233 Okhsva::from(value).into()
234 }
235}
236
237impl From<Okhwba> for LinearRgba {
238 fn from(value: Okhwba) -> Self {
239 Okhsva::from(value).into()
240 }
241}
242
243impl From<Srgba> for Okhwba {
244 fn from(value: Srgba) -> Self {
245 Okhsva::from(value).into()
246 }
247}
248
249impl From<Okhwba> for Srgba {
250 fn from(value: Okhwba) -> Self {
251 Okhsva::from(value).into()
252 }
253}
254
255impl From<Hwba> for Okhwba {
256 fn from(value: Hwba) -> Self {
257 Okhsva::from(value).into()
258 }
259}
260
261impl From<Okhwba> for Hwba {
262 fn from(value: Okhwba) -> Self {
263 Okhsva::from(value).into()
264 }
265}
266
267impl From<Lcha> for Okhwba {
268 fn from(value: Lcha) -> Self {
269 Okhsva::from(value).into()
270 }
271}
272
273impl From<Okhwba> for Lcha {
274 fn from(value: Okhwba) -> Self {
275 Okhsva::from(value).into()
276 }
277}
278
279impl From<Xyza> for Okhwba {
280 fn from(value: Xyza) -> Self {
281 Okhsva::from(value).into()
282 }
283}
284
285impl From<Okhwba> for Xyza {
286 fn from(value: Okhwba) -> Self {
287 Okhsva::from(value).into()
288 }
289}
290
291impl From<Okhsla> for Okhwba {
292 fn from(value: Okhsla) -> Self {
293 Okhsva::from(value).into()
294 }
295}
296
297impl From<Okhwba> for Okhsla {
298 fn from(value: Okhwba) -> Self {
299 Okhsva::from(value).into()
300 }
301}
302
303impl From<Hsla> for Okhwba {
304 fn from(value: Hsla) -> Self {
305 Okhsva::from(value).into()
306 }
307}
308
309impl From<Okhwba> for Hsla {
310 fn from(value: Okhwba) -> Self {
311 Okhsva::from(value).into()
312 }
313}
314
315impl From<Hsva> for Okhwba {
316 fn from(value: Hsva) -> Self {
317 Okhsva::from(value).into()
318 }
319}
320
321impl From<Okhwba> for Hsva {
322 fn from(value: Okhwba) -> Self {
323 Okhsva::from(value).into()
324 }
325}
326
327impl From<Laba> for Okhwba {
328 fn from(value: Laba) -> Self {
329 Okhsva::from(value).into()
330 }
331}
332
333impl From<Okhwba> for Laba {
334 fn from(value: Okhwba) -> Self {
335 Okhsva::from(value).into()
336 }
337}
338
339impl From<Oklaba> for Okhwba {
340 fn from(value: Oklaba) -> Self {
341 Okhsva::from(value).into()
342 }
343}
344
345impl From<Okhwba> for Oklaba {
346 fn from(value: Okhwba) -> Self {
347 Okhsva::from(value).into()
348 }
349}
350
351impl From<Oklcha> for Okhwba {
352 fn from(value: Oklcha) -> Self {
353 Okhsva::from(value).into()
354 }
355}
356
357impl From<Okhwba> for Oklcha {
358 fn from(value: Okhwba) -> Self {
359 Okhsva::from(value).into()
360 }
361}
362
363#[cfg(test)]
364mod tests {
365 use super::*;
366 use crate::{
367 color_difference::EuclideanDistance, test_colors::TEST_COLORS, testing::assert_approx_eq,
368 };
369
370 #[test]
371 fn test_from_okhsva() {
372 let okhsva = Okhsva::new(90.0, 0.0, 0.4, 1.0);
374 let okhwba: Okhwba = okhsva.into();
375 let okhsva2: Okhsva = okhwba.into();
376 assert_approx_eq!(okhwba.hue, 90.0, 0.001);
377 assert_approx_eq!(okhwba.whiteness, 0.4, 0.001);
378 assert_approx_eq!(okhwba.blackness, 0.6, 0.001);
379 assert_approx_eq!(okhwba.alpha, 1.0, 0.001);
380
381 assert_approx_eq!(okhsva.hue, okhsva2.hue, 0.001);
382 assert_approx_eq!(okhsva.saturation, okhsva2.saturation, 0.001);
383 assert_approx_eq!(okhsva.value, okhsva2.value, 0.001);
384 assert_approx_eq!(okhsva.alpha, okhsva2.alpha, 0.001);
385
386 let okhsva = Okhsva::new(270.0, 1.0, 1.0, 1.0);
388 let okhwba: Okhwba = okhsva.into();
389 let okhsva2: Okhsva = okhwba.into();
390 assert_approx_eq!(okhwba.hue, 270.0, 0.001);
391 assert_approx_eq!(okhwba.whiteness, 0.0, 0.001);
392 assert_approx_eq!(okhwba.blackness, 0.0, 0.001);
393 assert_approx_eq!(okhwba.alpha, 1.0, 0.001);
394
395 assert_approx_eq!(okhsva.hue, okhsva2.hue, 0.001);
396 assert_approx_eq!(okhsva.saturation, okhsva2.saturation, 0.001);
397 assert_approx_eq!(okhsva.value, okhsva2.value, 0.001);
398 assert_approx_eq!(okhsva.alpha, okhsva2.alpha, 0.001);
399
400 let okhsva = Okhsva::new(0.0, 0.0, 1.0, 1.0);
402 let okhwba: Okhwba = okhsva.into();
403 let okhsva2: Okhsva = okhwba.into();
404 assert_approx_eq!(okhwba.hue, 0.0, 0.001);
405 assert_approx_eq!(okhwba.whiteness, 1.0, 0.001);
406 assert_approx_eq!(okhwba.blackness, 0.0, 0.001);
407 assert_approx_eq!(okhwba.alpha, 1.0, 0.001);
408
409 assert_approx_eq!(okhsva.hue, okhsva2.hue, 0.001);
410 assert_approx_eq!(okhsva.saturation, okhsva2.saturation, 0.001);
411 assert_approx_eq!(okhsva.value, okhsva2.value, 0.001);
412 assert_approx_eq!(okhsva.alpha, okhsva2.alpha, 0.001);
413
414 let okhsva = Okhsva::new(0.0, 0.8, 0.0, 1.0);
416 let okhwba: Okhwba = okhsva.into();
417 let okhsva2: Okhsva = okhwba.into();
418 assert_approx_eq!(okhwba.hue, 0.0, 0.001);
419 assert_approx_eq!(okhwba.whiteness, 0.0, 0.001);
420 assert_approx_eq!(okhwba.blackness, 1.0, 0.001);
421 assert_approx_eq!(okhwba.alpha, 1.0, 0.001);
422
423 assert_approx_eq!(okhsva.hue, okhsva2.hue, 0.001);
424 assert_approx_eq!(0.0, okhsva2.saturation, 0.001);
425 assert_approx_eq!(okhsva.value, okhsva2.value, 0.001);
426 assert_approx_eq!(okhsva.alpha, okhsva2.alpha, 0.001);
427 }
428
429 #[test]
430 fn test_from_oklaba() {
431 let oklaba = Oklaba::new(0.0, 0.5, 0.5, 1.0);
433 let okhwba: Okhwba = oklaba.into();
434 let oklaba2: Oklaba = okhwba.into();
435 assert_approx_eq!(okhwba.hue, 0.0, 0.001);
436 assert_approx_eq!(okhwba.whiteness, 0.0, 0.001);
437 assert_approx_eq!(okhwba.blackness, 1.0, 0.001);
438 assert_approx_eq!(okhwba.alpha, 1.0, 0.001);
439
440 assert_approx_eq!(oklaba.lightness, oklaba2.lightness, 0.001);
441 assert_approx_eq!(0.0, oklaba2.a, 0.001);
442 assert_approx_eq!(0.0, oklaba2.b, 0.001);
443 assert_approx_eq!(oklaba.alpha, oklaba2.alpha, 0.001);
444
445 let oklaba = Oklaba::new(1.0, 0.5, 0.5, 1.0);
447 let okhwba: Okhwba = oklaba.into();
448 let oklaba2: Oklaba = okhwba.into();
449 assert_approx_eq!(okhwba.hue, 0.0, 0.001);
450 assert_approx_eq!(okhwba.whiteness, 1.0, 0.001);
451 assert_approx_eq!(okhwba.blackness, 0.0, 0.001);
452 assert_approx_eq!(okhwba.alpha, 1.0, 0.001);
453
454 assert_approx_eq!(oklaba.lightness, oklaba2.lightness, 0.001);
455 assert_approx_eq!(0.0, oklaba2.a, 0.001);
456 assert_approx_eq!(0.0, oklaba2.b, 0.001);
457 assert_approx_eq!(oklaba.alpha, oklaba2.alpha, 0.001);
458
459 let oklaba = Oklaba::new(0.5, 0.0, 0.0, 1.0);
461 let okhwba: Okhwba = oklaba.into();
462 let oklaba2: Oklaba = okhwba.into();
463 assert_approx_eq!(okhwba.hue, 0.0, 0.001);
464 assert_approx_eq!(okhwba.whiteness, 0.42114055, 0.001);
465 assert_approx_eq!(okhwba.blackness, 0.57885945, 0.001);
466 assert_approx_eq!(okhwba.alpha, 1.0, 0.001);
467
468 assert_approx_eq!(oklaba.lightness, oklaba2.lightness, 0.001);
469 assert_approx_eq!(0.0, oklaba2.a, 0.001);
470 assert_approx_eq!(0.0, oklaba2.b, 0.001);
471 assert_approx_eq!(oklaba.alpha, oklaba2.alpha, 0.001);
472 }
473
474 #[test]
475 fn test_to_from_srgba() {
476 let okhwba = Okhwba::new(180.0, 0.5, 0.5, 1.0);
477 let srgba: Srgba = okhwba.into();
478 let okhwba2: Okhwba = srgba.into();
479 assert_approx_eq!(okhwba.hue, okhwba2.hue, 0.001);
480 assert_approx_eq!(okhwba.whiteness, okhwba2.whiteness, 0.001);
481 assert_approx_eq!(okhwba.blackness, okhwba2.blackness, 0.001);
482 assert_approx_eq!(okhwba.alpha, okhwba2.alpha, 0.001);
483 }
484
485 #[test]
486 fn test_to_from_linear() {
487 let okhwba = Okhwba::new(180.0, 0.5, 0.5, 1.0);
488 let linear: LinearRgba = okhwba.into();
489 let okhwba2: Okhwba = linear.into();
490 assert_approx_eq!(okhwba.hue, okhwba2.hue, 0.001);
491 assert_approx_eq!(okhwba.whiteness, okhwba2.whiteness, 0.001);
492 assert_approx_eq!(okhwba.blackness, okhwba2.blackness, 0.001);
493 assert_approx_eq!(okhwba.alpha, okhwba2.alpha, 0.001);
494 }
495
496 #[test]
497 fn test_to_from_srgba_2() {
498 for color in TEST_COLORS.iter() {
499 let rgb2: Srgba = (color.okhwb).into();
500 let okhwb: Okhwba = (color.rgb).into();
501 assert!(
502 color.rgb.distance(&rgb2) < 0.001,
503 "{}: {:?} != {:?}",
504 color.name,
505 color.rgb,
506 rgb2,
507 );
508 let msg = alloc::format!(
509 "{}: expected {:?}, got {:?}",
510 color.name,
511 color.okhwb,
512 okhwb
513 );
514 assert_approx_eq!(color.okhwb.hue, okhwb.hue, 0.001, msg);
515 assert_approx_eq!(color.okhwb.whiteness, okhwb.whiteness, 0.001, msg);
516 assert_approx_eq!(color.okhwb.blackness, okhwb.blackness, 0.001, msg);
517 assert_approx_eq!(color.okhwb.alpha, okhwb.alpha, 0.001, msg);
518 }
519 }
520}