1use crate::{
2 Alpha, ColorToComponents, Gray, Hsva, Hue, Hwba, Lcha, LinearRgba, Luminance, Mix, Saturation,
3 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 Hsla {
27 pub hue: f32,
29 pub saturation: f32,
31 pub lightness: f32,
33 pub alpha: f32,
35}
36
37impl StandardColor for Hsla {}
38
39impl Hsla {
40 pub const fn new(hue: f32, saturation: f32, lightness: f32, alpha: f32) -> Self {
49 Self {
50 hue,
51 saturation,
52 lightness,
53 alpha,
54 }
55 }
56
57 pub const fn hsl(hue: f32, saturation: f32, lightness: f32) -> Self {
65 Self::new(hue, saturation, lightness, 1.0)
66 }
67
68 pub const fn with_saturation(self, saturation: f32) -> Self {
70 Self { saturation, ..self }
71 }
72
73 pub const fn with_lightness(self, lightness: f32) -> Self {
75 Self { lightness, ..self }
76 }
77
78 pub fn sequential_dispersed(index: u32) -> Self {
96 const FRAC_U32MAX_GOLDEN_RATIO: u32 = 2654435769; const RATIO_360: f32 = 360.0 / u32::MAX as f32;
98
99 let hue = index.wrapping_mul(FRAC_U32MAX_GOLDEN_RATIO) as f32 * RATIO_360;
104 Self::hsl(hue, 1., 0.5)
105 }
106}
107
108impl Default for Hsla {
109 fn default() -> Self {
110 Self::new(0., 0., 1., 1.)
111 }
112}
113
114impl Mix for Hsla {
115 #[inline]
116 fn mix(&self, other: &Self, factor: f32) -> Self {
117 let n_factor = 1.0 - factor;
118 Self {
119 hue: crate::color_ops::lerp_hue(self.hue, other.hue, factor),
120 saturation: self.saturation * n_factor + other.saturation * factor,
121 lightness: self.lightness * n_factor + other.lightness * factor,
122 alpha: self.alpha * n_factor + other.alpha * factor,
123 }
124 }
125}
126
127impl Gray for Hsla {
128 const BLACK: Self = Self::new(0., 0., 0., 1.);
129 const WHITE: Self = Self::new(0., 0., 1., 1.);
130}
131
132impl Alpha for Hsla {
133 #[inline]
134 fn with_alpha(&self, alpha: f32) -> Self {
135 Self { alpha, ..*self }
136 }
137
138 #[inline]
139 fn alpha(&self) -> f32 {
140 self.alpha
141 }
142
143 #[inline]
144 fn set_alpha(&mut self, alpha: f32) {
145 self.alpha = alpha;
146 }
147}
148
149impl Hue for Hsla {
150 #[inline]
151 fn with_hue(&self, hue: f32) -> Self {
152 Self { hue, ..*self }
153 }
154
155 #[inline]
156 fn hue(&self) -> f32 {
157 self.hue
158 }
159
160 #[inline]
161 fn set_hue(&mut self, hue: f32) {
162 self.hue = hue;
163 }
164}
165
166impl Saturation for Hsla {
167 #[inline]
168 fn with_saturation(&self, saturation: f32) -> Self {
169 Self {
170 saturation,
171 ..*self
172 }
173 }
174
175 #[inline]
176 fn saturation(&self) -> f32 {
177 self.saturation
178 }
179
180 #[inline]
181 fn set_saturation(&mut self, saturation: f32) {
182 self.saturation = saturation;
183 }
184}
185
186impl Luminance for Hsla {
187 #[inline]
188 fn with_luminance(&self, lightness: f32) -> Self {
189 Self { lightness, ..*self }
190 }
191
192 fn luminance(&self) -> f32 {
193 self.lightness
194 }
195
196 fn darker(&self, amount: f32) -> Self {
197 Self {
198 lightness: (self.lightness - amount).clamp(0., 1.),
199 ..*self
200 }
201 }
202
203 fn lighter(&self, amount: f32) -> Self {
204 Self {
205 lightness: (self.lightness + amount).min(1.),
206 ..*self
207 }
208 }
209}
210
211impl ColorToComponents for Hsla {
212 fn to_f32_array(self) -> [f32; 4] {
213 [self.hue, self.saturation, self.lightness, self.alpha]
214 }
215
216 fn to_f32_array_no_alpha(self) -> [f32; 3] {
217 [self.hue, self.saturation, self.lightness]
218 }
219
220 fn to_vec4(self) -> Vec4 {
221 Vec4::new(self.hue, self.saturation, self.lightness, self.alpha)
222 }
223
224 fn to_vec3(self) -> Vec3 {
225 Vec3::new(self.hue, self.saturation, self.lightness)
226 }
227
228 fn from_f32_array(color: [f32; 4]) -> Self {
229 Self {
230 hue: color[0],
231 saturation: color[1],
232 lightness: color[2],
233 alpha: color[3],
234 }
235 }
236
237 fn from_f32_array_no_alpha(color: [f32; 3]) -> Self {
238 Self {
239 hue: color[0],
240 saturation: color[1],
241 lightness: color[2],
242 alpha: 1.0,
243 }
244 }
245
246 fn from_vec4(color: Vec4) -> Self {
247 Self {
248 hue: color[0],
249 saturation: color[1],
250 lightness: color[2],
251 alpha: color[3],
252 }
253 }
254
255 fn from_vec3(color: Vec3) -> Self {
256 Self {
257 hue: color[0],
258 saturation: color[1],
259 lightness: color[2],
260 alpha: 1.0,
261 }
262 }
263}
264
265impl From<Hsla> for Hsva {
266 fn from(
267 Hsla {
268 hue,
269 saturation,
270 lightness,
271 alpha,
272 }: Hsla,
273 ) -> Self {
274 let value = lightness + saturation * lightness.min(1. - lightness);
276 let saturation = if value == 0. {
277 0.
278 } else {
279 2. * (1. - (lightness / value))
280 };
281
282 Hsva::new(hue, saturation, value, alpha)
283 }
284}
285
286impl From<Hsva> for Hsla {
287 fn from(
288 Hsva {
289 hue,
290 saturation,
291 value,
292 alpha,
293 }: Hsva,
294 ) -> Self {
295 let lightness = value * (1. - saturation / 2.);
297 let saturation = if lightness == 0. || lightness == 1. {
298 0.
299 } else {
300 (value - lightness) / lightness.min(1. - lightness)
301 };
302
303 Hsla::new(hue, saturation, lightness, alpha)
304 }
305}
306
307impl From<Hwba> for Hsla {
310 fn from(value: Hwba) -> Self {
311 Hsva::from(value).into()
312 }
313}
314
315impl From<Hsla> for Hwba {
316 fn from(value: Hsla) -> Self {
317 Hsva::from(value).into()
318 }
319}
320
321impl From<Srgba> for Hsla {
322 fn from(value: Srgba) -> Self {
323 Hsva::from(value).into()
324 }
325}
326
327impl From<Hsla> for Srgba {
328 fn from(value: Hsla) -> Self {
329 Hsva::from(value).into()
330 }
331}
332
333impl From<LinearRgba> for Hsla {
334 fn from(value: LinearRgba) -> Self {
335 Hsva::from(value).into()
336 }
337}
338
339impl From<Hsla> for LinearRgba {
340 fn from(value: Hsla) -> Self {
341 Hsva::from(value).into()
342 }
343}
344
345impl From<Lcha> for Hsla {
346 fn from(value: Lcha) -> Self {
347 Hsva::from(value).into()
348 }
349}
350
351impl From<Hsla> for Lcha {
352 fn from(value: Hsla) -> Self {
353 Hsva::from(value).into()
354 }
355}
356
357impl From<Xyza> for Hsla {
358 fn from(value: Xyza) -> Self {
359 Hsva::from(value).into()
360 }
361}
362
363impl From<Hsla> for Xyza {
364 fn from(value: Hsla) -> Self {
365 Hsva::from(value).into()
366 }
367}
368
369#[cfg(test)]
370mod tests {
371 use super::*;
372 use crate::{
373 color_difference::EuclideanDistance, test_colors::TEST_COLORS, testing::assert_approx_eq,
374 };
375
376 #[test]
377 fn test_to_from_srgba() {
378 let hsla = Hsla::new(0.5, 0.5, 0.5, 1.0);
379 let srgba: Srgba = hsla.into();
380 let hsla2: Hsla = srgba.into();
381 assert_approx_eq!(hsla.hue, hsla2.hue, 0.001);
382 assert_approx_eq!(hsla.saturation, hsla2.saturation, 0.001);
383 assert_approx_eq!(hsla.lightness, hsla2.lightness, 0.001);
384 assert_approx_eq!(hsla.alpha, hsla2.alpha, 0.001);
385 }
386
387 #[test]
388 fn test_to_from_srgba_2() {
389 for color in TEST_COLORS.iter() {
390 let rgb2: Srgba = (color.hsl).into();
391 let hsl2: Hsla = (color.rgb).into();
392 assert!(
393 color.rgb.distance(&rgb2) < 0.000001,
394 "{}: {:?} != {:?}",
395 color.name,
396 color.rgb,
397 rgb2
398 );
399 assert_approx_eq!(color.hsl.hue, hsl2.hue, 0.001);
400 assert_approx_eq!(color.hsl.saturation, hsl2.saturation, 0.001);
401 assert_approx_eq!(color.hsl.lightness, hsl2.lightness, 0.001);
402 assert_approx_eq!(color.hsl.alpha, hsl2.alpha, 0.001);
403 }
404 }
405
406 #[test]
407 fn test_to_from_linear() {
408 let hsla = Hsla::new(0.5, 0.5, 0.5, 1.0);
409 let linear: LinearRgba = hsla.into();
410 let hsla2: Hsla = linear.into();
411 assert_approx_eq!(hsla.hue, hsla2.hue, 0.001);
412 assert_approx_eq!(hsla.saturation, hsla2.saturation, 0.001);
413 assert_approx_eq!(hsla.lightness, hsla2.lightness, 0.001);
414 assert_approx_eq!(hsla.alpha, hsla2.alpha, 0.001);
415 }
416
417 #[test]
418 fn test_mix_wrap() {
419 let hsla0 = Hsla::new(10., 0.5, 0.5, 1.0);
420 let hsla1 = Hsla::new(20., 0.5, 0.5, 1.0);
421 let hsla2 = Hsla::new(350., 0.5, 0.5, 1.0);
422 assert_approx_eq!(hsla0.mix(&hsla1, 0.25).hue, 12.5, 0.001);
423 assert_approx_eq!(hsla0.mix(&hsla1, 0.5).hue, 15., 0.001);
424 assert_approx_eq!(hsla0.mix(&hsla1, 0.75).hue, 17.5, 0.001);
425
426 assert_approx_eq!(hsla1.mix(&hsla0, 0.25).hue, 17.5, 0.001);
427 assert_approx_eq!(hsla1.mix(&hsla0, 0.5).hue, 15., 0.001);
428 assert_approx_eq!(hsla1.mix(&hsla0, 0.75).hue, 12.5, 0.001);
429
430 assert_approx_eq!(hsla0.mix(&hsla2, 0.25).hue, 5., 0.001);
431 assert_approx_eq!(hsla0.mix(&hsla2, 0.5).hue, 0., 0.001);
432 assert_approx_eq!(hsla0.mix(&hsla2, 0.75).hue, 355., 0.001);
433
434 assert_approx_eq!(hsla2.mix(&hsla0, 0.25).hue, 355., 0.001);
435 assert_approx_eq!(hsla2.mix(&hsla0, 0.5).hue, 0., 0.001);
436 assert_approx_eq!(hsla2.mix(&hsla0, 0.75).hue, 5., 0.001);
437 }
438
439 #[test]
440 fn test_from_index() {
441 let references = [
442 Hsla::hsl(0.0, 1., 0.5),
443 Hsla::hsl(222.49225, 1., 0.5),
444 Hsla::hsl(84.984474, 1., 0.5),
445 Hsla::hsl(307.4767, 1., 0.5),
446 Hsla::hsl(169.96895, 1., 0.5),
447 ];
448
449 for (index, reference) in references.into_iter().enumerate() {
450 let color = Hsla::sequential_dispersed(index as u32);
451
452 assert_approx_eq!(color.hue, reference.hue, 0.001);
453 }
454 }
455}