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