1use crate::{
2 impl_componentwise_vector_space,
3 okcolor_convert::{okhsl_to_oklab, oklab_to_okhsl},
4 Alpha, ColorToComponents, Gray, Hsla, Hsva, Hue, Hwba, Laba, Lcha, LinearRgba, Luminance, Mix,
5 Oklaba, Oklcha, Saturation, Srgba, StandardColor, Xyza,
6};
7use bevy_math::{Vec3, Vec4};
8#[cfg(feature = "bevy_reflect")]
9use bevy_reflect::prelude::*;
10
11#[doc = include_str!("../docs/conversion.md")]
17#[doc = include_str!("../docs/diagrams/model_graph.svg")]
19#[derive(Debug, Clone, Copy, PartialEq)]
21#[cfg_attr(
22 feature = "bevy_reflect",
23 derive(Reflect),
24 reflect(Clone, PartialEq, Default)
25)]
26#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
27#[cfg_attr(
28 all(feature = "serialize", feature = "bevy_reflect"),
29 reflect(Serialize, Deserialize)
30)]
31pub struct Okhsla {
32 pub hue: f32,
34 pub saturation: f32,
36 pub lightness: f32,
38 pub alpha: f32,
40}
41
42impl StandardColor for Okhsla {}
43
44impl_componentwise_vector_space!(Okhsla, [hue, saturation, lightness, alpha]);
45
46impl Okhsla {
47 pub const fn new(hue: f32, saturation: f32, lightness: f32, alpha: f32) -> Self {
56 Self {
57 hue,
58 saturation,
59 lightness,
60 alpha,
61 }
62 }
63
64 pub const fn hsl(hue: f32, saturation: f32, lightness: f32) -> Self {
72 Self::new(hue, saturation, lightness, 1.0)
73 }
74
75 pub const fn with_saturation(self, saturation: f32) -> Self {
77 Self { saturation, ..self }
78 }
79
80 pub const fn with_lightness(self, lightness: f32) -> Self {
82 Self { lightness, ..self }
83 }
84
85 pub const fn sequential_dispersed(index: u32) -> Self {
103 const FRAC_U32MAX_GOLDEN_RATIO: u32 = 2654435769; const RATIO_360: f32 = 360.0 / u32::MAX as f32;
105
106 let hue = index.wrapping_mul(FRAC_U32MAX_GOLDEN_RATIO) as f32 * RATIO_360;
111 Self::hsl(hue, 1., 0.5)
112 }
113}
114
115impl Default for Okhsla {
116 fn default() -> Self {
117 Self::new(0., 0., 1., 1.)
118 }
119}
120
121impl Mix for Okhsla {
122 #[inline]
123 fn mix(&self, other: &Self, factor: f32) -> Self {
124 let n_factor = 1.0 - factor;
125 Self {
126 hue: crate::color_ops::lerp_hue(self.hue, other.hue, factor),
127 saturation: self.saturation * n_factor + other.saturation * factor,
128 lightness: self.lightness * n_factor + other.lightness * factor,
129 alpha: self.alpha * n_factor + other.alpha * factor,
130 }
131 }
132}
133
134impl Gray for Okhsla {
135 const BLACK: Self = Self::new(0., 0., 0., 1.);
136 const WHITE: Self = Self::new(0., 0., 1., 1.);
137}
138
139impl Alpha for Okhsla {
140 #[inline]
141 fn with_alpha(&self, alpha: f32) -> Self {
142 Self { alpha, ..*self }
143 }
144
145 #[inline]
146 fn alpha(&self) -> f32 {
147 self.alpha
148 }
149
150 #[inline]
151 fn set_alpha(&mut self, alpha: f32) {
152 self.alpha = alpha;
153 }
154}
155
156impl Hue for Okhsla {
157 #[inline]
158 fn with_hue(&self, hue: f32) -> Self {
159 Self { hue, ..*self }
160 }
161
162 #[inline]
163 fn hue(&self) -> f32 {
164 self.hue
165 }
166
167 #[inline]
168 fn set_hue(&mut self, hue: f32) {
169 self.hue = hue;
170 }
171}
172
173impl Saturation for Okhsla {
174 #[inline]
175 fn with_saturation(&self, saturation: f32) -> Self {
176 Self {
177 saturation,
178 ..*self
179 }
180 }
181
182 #[inline]
183 fn saturation(&self) -> f32 {
184 self.saturation
185 }
186
187 #[inline]
188 fn set_saturation(&mut self, saturation: f32) {
189 self.saturation = saturation;
190 }
191}
192
193impl Luminance for Okhsla {
194 #[inline]
195 fn with_luminance(&self, lightness: f32) -> Self {
196 Self { lightness, ..*self }
197 }
198
199 fn luminance(&self) -> f32 {
200 self.lightness
201 }
202
203 fn darker(&self, amount: f32) -> Self {
204 Self {
205 lightness: (self.lightness - amount).clamp(0., 1.),
206 ..*self
207 }
208 }
209
210 fn lighter(&self, amount: f32) -> Self {
211 Self {
212 lightness: (self.lightness + amount).min(1.),
213 ..*self
214 }
215 }
216}
217
218impl ColorToComponents for Okhsla {
219 fn to_f32_array(self) -> [f32; 4] {
220 [self.hue, self.saturation, self.lightness, self.alpha]
221 }
222
223 fn to_f32_array_no_alpha(self) -> [f32; 3] {
224 [self.hue, self.saturation, self.lightness]
225 }
226
227 fn to_vec4(self) -> Vec4 {
228 Vec4::new(self.hue, self.saturation, self.lightness, self.alpha)
229 }
230
231 fn to_vec3(self) -> Vec3 {
232 Vec3::new(self.hue, self.saturation, self.lightness)
233 }
234
235 fn from_f32_array(color: [f32; 4]) -> Self {
236 Self {
237 hue: color[0],
238 saturation: color[1],
239 lightness: color[2],
240 alpha: color[3],
241 }
242 }
243
244 fn from_f32_array_no_alpha(color: [f32; 3]) -> Self {
245 Self {
246 hue: color[0],
247 saturation: color[1],
248 lightness: color[2],
249 alpha: 1.0,
250 }
251 }
252
253 fn from_vec4(color: Vec4) -> Self {
254 Self {
255 hue: color[0],
256 saturation: color[1],
257 lightness: color[2],
258 alpha: color[3],
259 }
260 }
261
262 fn from_vec3(color: Vec3) -> Self {
263 Self {
264 hue: color[0],
265 saturation: color[1],
266 lightness: color[2],
267 alpha: 1.0,
268 }
269 }
270}
271
272#[cfg(feature = "wgpu-types")]
273impl From<Okhsla> for wgpu_types::Color {
274 fn from(color: Okhsla) -> Self {
275 wgpu_types::Color {
276 r: color.hue as f64,
277 g: color.saturation as f64,
278 b: color.lightness as f64,
279 a: color.alpha as f64,
280 }
281 }
282}
283
284impl From<Oklaba> for Okhsla {
285 fn from(value: Oklaba) -> Self {
286 oklab_to_okhsl(value)
287 }
288}
289
290impl From<Okhsla> for Oklaba {
291 fn from(value: Okhsla) -> Self {
292 okhsl_to_oklab(value)
293 }
294}
295
296impl From<LinearRgba> for Okhsla {
299 fn from(value: LinearRgba) -> Self {
300 Oklaba::from(value).into()
301 }
302}
303
304impl From<Okhsla> for LinearRgba {
305 fn from(value: Okhsla) -> Self {
306 Oklaba::from(value).into()
307 }
308}
309
310impl From<Hsla> for Okhsla {
311 fn from(value: Hsla) -> Self {
312 Oklaba::from(value).into()
313 }
314}
315
316impl From<Okhsla> for Hsla {
317 fn from(value: Okhsla) -> Self {
318 Oklaba::from(value).into()
319 }
320}
321
322impl From<Hsva> for Okhsla {
323 fn from(value: Hsva) -> Self {
324 Oklaba::from(value).into()
325 }
326}
327
328impl From<Okhsla> for Hsva {
329 fn from(value: Okhsla) -> Self {
330 Oklaba::from(value).into()
331 }
332}
333
334impl From<Hwba> for Okhsla {
335 fn from(value: Hwba) -> Self {
336 Oklaba::from(value).into()
337 }
338}
339
340impl From<Okhsla> for Hwba {
341 fn from(value: Okhsla) -> Self {
342 Oklaba::from(value).into()
343 }
344}
345
346impl From<Lcha> for Okhsla {
347 fn from(value: Lcha) -> Self {
348 Oklaba::from(value).into()
349 }
350}
351
352impl From<Okhsla> for Lcha {
353 fn from(value: Okhsla) -> Self {
354 Oklaba::from(value).into()
355 }
356}
357
358impl From<Srgba> for Okhsla {
359 fn from(value: Srgba) -> Self {
360 Oklaba::from(value).into()
361 }
362}
363
364impl From<Okhsla> for Srgba {
365 fn from(value: Okhsla) -> Self {
366 Oklaba::from(value).into()
367 }
368}
369
370impl From<Xyza> for Okhsla {
371 fn from(value: Xyza) -> Self {
372 Oklaba::from(value).into()
373 }
374}
375
376impl From<Okhsla> for Xyza {
377 fn from(value: Okhsla) -> Self {
378 Oklaba::from(value).into()
379 }
380}
381
382impl From<Laba> for Okhsla {
383 fn from(value: Laba) -> Self {
384 Oklaba::from(value).into()
385 }
386}
387
388impl From<Okhsla> for Laba {
389 fn from(value: Okhsla) -> Self {
390 Oklaba::from(value).into()
391 }
392}
393
394impl From<Oklcha> for Okhsla {
395 fn from(value: Oklcha) -> Self {
396 Oklaba::from(value).into()
397 }
398}
399
400impl From<Okhsla> for Oklcha {
401 fn from(value: Okhsla) -> Self {
402 Oklaba::from(value).into()
403 }
404}
405
406#[cfg(test)]
407mod tests {
408 use super::*;
409 use crate::{
410 color_difference::EuclideanDistance, test_colors::TEST_COLORS, testing::assert_approx_eq,
411 };
412
413 #[test]
414 fn test_from_to_oklaba() {
415 let oklaba = Oklaba::new(0.0, 0.5, 0.5, 1.0);
417 let okhsla: Okhsla = oklaba.into();
418 let oklaba2: Oklaba = okhsla.into();
419 assert_approx_eq!(okhsla.hue, 0.0, 0.001);
420 assert_approx_eq!(okhsla.saturation, 0.0, 0.001);
421 assert_approx_eq!(okhsla.lightness, 0.0, 0.001);
422 assert_approx_eq!(okhsla.alpha, 1.0, 0.001);
423
424 assert_approx_eq!(oklaba.lightness, oklaba2.lightness, 0.001);
425 assert_approx_eq!(0.0, oklaba2.a, 0.001);
426 assert_approx_eq!(0.0, oklaba2.b, 0.001);
427 assert_approx_eq!(oklaba.alpha, oklaba2.alpha, 0.001);
428
429 let oklaba = Oklaba::new(1.0, 0.5, 0.5, 1.0);
431 let okhsla: Okhsla = oklaba.into();
432 let oklaba2: Oklaba = okhsla.into();
433 assert_approx_eq!(okhsla.hue, 0.0, 0.001);
434 assert_approx_eq!(okhsla.saturation, 0.0, 0.001);
435 assert_approx_eq!(okhsla.lightness, 1.0, 0.001);
436 assert_approx_eq!(okhsla.alpha, 1.0, 0.001);
437
438 assert_approx_eq!(oklaba.lightness, oklaba2.lightness, 0.001);
439 assert_approx_eq!(0.0, oklaba2.a, 0.001);
440 assert_approx_eq!(0.0, oklaba2.b, 0.001);
441 assert_approx_eq!(oklaba.alpha, oklaba2.alpha, 0.001);
442
443 let oklaba = Oklaba::new(0.5, 0.0, 0.0, 1.0);
445 let okhsla: Okhsla = oklaba.into();
446 let oklaba2: Oklaba = okhsla.into();
447 assert_approx_eq!(okhsla.hue, 0.0, 0.001);
448 assert_approx_eq!(okhsla.saturation, 0.0, 0.001);
449 assert_approx_eq!(okhsla.lightness, 0.42114055, 0.001);
450 assert_approx_eq!(okhsla.alpha, 1.0, 0.001);
451
452 assert_approx_eq!(oklaba.lightness, oklaba2.lightness, 0.001);
453 assert_approx_eq!(0.0, oklaba2.a, 0.001);
454 assert_approx_eq!(0.0, oklaba2.b, 0.001);
455 assert_approx_eq!(oklaba.alpha, oklaba2.alpha, 0.001);
456 }
457
458 #[test]
459 fn test_to_from_srgba() {
460 let okhsla = Okhsla::new(180.0, 0.5, 0.5, 1.0);
461 let srgba: Srgba = okhsla.into();
462 let okhsla2: Okhsla = srgba.into();
463 assert_approx_eq!(okhsla.hue, okhsla2.hue, 0.001);
464 assert_approx_eq!(okhsla.saturation, okhsla2.saturation, 0.001);
465 assert_approx_eq!(okhsla.lightness, okhsla2.lightness, 0.001);
466 assert_approx_eq!(okhsla.alpha, okhsla2.alpha, 0.001);
467 }
468
469 #[test]
470 fn test_to_from_srgba_2() {
471 for color in TEST_COLORS.iter() {
472 let rgb2: Srgba = (color.okhsl).into();
473 let okhsl: Okhsla = (color.rgb).into();
474 assert!(
475 color.rgb.distance(&rgb2) < 0.001,
476 "{}: {:?} != {:?}",
477 color.name,
478 color.rgb,
479 rgb2
480 );
481 let msg = alloc::format!(
482 "{}: expected {:?}, got {:?}",
483 color.name,
484 color.okhsl,
485 okhsl
486 );
487 assert_approx_eq!(color.okhsl.hue, okhsl.hue, 0.001, msg);
488 assert_approx_eq!(color.okhsl.saturation, okhsl.saturation, 0.001, msg);
489 assert_approx_eq!(color.okhsl.lightness, okhsl.lightness, 0.001, msg);
490 assert_approx_eq!(color.okhsl.alpha, okhsl.alpha, 0.001, msg);
491 }
492 }
493
494 #[test]
495 fn test_to_from_linear() {
496 let okhsla = Okhsla::new(180.0, 0.5, 0.5, 1.0);
497 let linear: LinearRgba = okhsla.into();
498 let okhsla2: Okhsla = linear.into();
499 assert_approx_eq!(okhsla.hue, okhsla2.hue, 0.001);
500 assert_approx_eq!(okhsla.saturation, okhsla2.saturation, 0.001);
501 assert_approx_eq!(okhsla.lightness, okhsla2.lightness, 0.001);
502 assert_approx_eq!(okhsla.alpha, okhsla2.alpha, 0.001);
503 }
504}