bevy_color/
laba.rs

1use crate::{
2    impl_componentwise_vector_space, Alpha, ColorToComponents, Gray, Hsla, Hsva, Hwba, LinearRgba,
3    Luminance, Mix, Oklaba, Srgba, StandardColor, Xyza,
4};
5use bevy_math::{ops, Vec3, Vec4};
6#[cfg(feature = "bevy_reflect")]
7use bevy_reflect::prelude::*;
8
9/// Color in LAB color space, with alpha
10#[doc = include_str!("../docs/conversion.md")]
11/// <div>
12#[doc = include_str!("../docs/diagrams/model_graph.svg")]
13/// </div>
14#[derive(Debug, Clone, Copy, PartialEq)]
15#[cfg_attr(
16    feature = "bevy_reflect",
17    derive(Reflect),
18    reflect(Clone, PartialEq, Default)
19)]
20#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
21#[cfg_attr(
22    all(feature = "serialize", feature = "bevy_reflect"),
23    reflect(Serialize, Deserialize)
24)]
25pub struct Laba {
26    /// The lightness channel. [0.0, 1.5]
27    pub lightness: f32,
28    /// The a axis. [-1.5, 1.5]
29    pub a: f32,
30    /// The b axis. [-1.5, 1.5]
31    pub b: f32,
32    /// The alpha channel. [0.0, 1.0]
33    pub alpha: f32,
34}
35
36impl StandardColor for Laba {}
37
38impl_componentwise_vector_space!(Laba, [lightness, a, b, alpha]);
39
40impl Laba {
41    /// Construct a new [`Laba`] color from components.
42    ///
43    /// # Arguments
44    ///
45    /// * `lightness` - Lightness channel. [0.0, 1.5]
46    /// * `a` - a axis. [-1.5, 1.5]
47    /// * `b` - b axis. [-1.5, 1.5]
48    /// * `alpha` - Alpha channel. [0.0, 1.0]
49    pub const fn new(lightness: f32, a: f32, b: f32, alpha: f32) -> Self {
50        Self {
51            lightness,
52            a,
53            b,
54            alpha,
55        }
56    }
57
58    /// Construct a new [`Laba`] color from (l, a, b) components, with the default alpha (1.0).
59    ///
60    /// # Arguments
61    ///
62    /// * `lightness` - Lightness channel. [0.0, 1.5]
63    /// * `a` - a axis. [-1.5, 1.5]
64    /// * `b` - b axis. [-1.5, 1.5]
65    pub const fn lab(lightness: f32, a: f32, b: f32) -> Self {
66        Self {
67            lightness,
68            a,
69            b,
70            alpha: 1.0,
71        }
72    }
73
74    /// Return a copy of this color with the lightness channel set to the given value.
75    pub const fn with_lightness(self, lightness: f32) -> Self {
76        Self { lightness, ..self }
77    }
78
79    /// CIE Epsilon Constant
80    ///
81    /// See [Continuity (16) (17)](http://brucelindbloom.com/index.html?LContinuity.html)
82    pub const CIE_EPSILON: f32 = 216.0 / 24389.0;
83
84    /// CIE Kappa Constant
85    ///
86    /// See [Continuity (16) (17)](http://brucelindbloom.com/index.html?LContinuity.html)
87    pub const CIE_KAPPA: f32 = 24389.0 / 27.0;
88}
89
90impl Default for Laba {
91    fn default() -> Self {
92        Self::new(1., 0., 0., 1.)
93    }
94}
95
96impl Mix for Laba {
97    #[inline]
98    fn mix(&self, other: &Self, factor: f32) -> Self {
99        let n_factor = 1.0 - factor;
100        Self {
101            lightness: self.lightness * n_factor + other.lightness * factor,
102            a: self.a * n_factor + other.a * factor,
103            b: self.b * n_factor + other.b * factor,
104            alpha: self.alpha * n_factor + other.alpha * factor,
105        }
106    }
107}
108
109impl Gray for Laba {
110    const BLACK: Self = Self::new(0., 0., 0., 1.);
111    const WHITE: Self = Self::new(1., 0., 0., 1.);
112}
113
114impl Alpha for Laba {
115    #[inline]
116    fn with_alpha(&self, alpha: f32) -> Self {
117        Self { alpha, ..*self }
118    }
119
120    #[inline]
121    fn alpha(&self) -> f32 {
122        self.alpha
123    }
124
125    #[inline]
126    fn set_alpha(&mut self, alpha: f32) {
127        self.alpha = alpha;
128    }
129}
130
131impl Luminance for Laba {
132    #[inline]
133    fn with_luminance(&self, lightness: f32) -> Self {
134        Self { lightness, ..*self }
135    }
136
137    fn luminance(&self) -> f32 {
138        self.lightness
139    }
140
141    fn darker(&self, amount: f32) -> Self {
142        Self::new(
143            (self.lightness - amount).max(0.),
144            self.a,
145            self.b,
146            self.alpha,
147        )
148    }
149
150    fn lighter(&self, amount: f32) -> Self {
151        Self::new(
152            (self.lightness + amount).min(1.),
153            self.a,
154            self.b,
155            self.alpha,
156        )
157    }
158}
159
160impl ColorToComponents for Laba {
161    fn to_f32_array(self) -> [f32; 4] {
162        [self.lightness, self.a, self.b, self.alpha]
163    }
164
165    fn to_f32_array_no_alpha(self) -> [f32; 3] {
166        [self.lightness, self.a, self.b]
167    }
168
169    fn to_vec4(self) -> Vec4 {
170        Vec4::new(self.lightness, self.a, self.b, self.alpha)
171    }
172
173    fn to_vec3(self) -> Vec3 {
174        Vec3::new(self.lightness, self.a, self.b)
175    }
176
177    fn from_f32_array(color: [f32; 4]) -> Self {
178        Self {
179            lightness: color[0],
180            a: color[1],
181            b: color[2],
182            alpha: color[3],
183        }
184    }
185
186    fn from_f32_array_no_alpha(color: [f32; 3]) -> Self {
187        Self {
188            lightness: color[0],
189            a: color[1],
190            b: color[2],
191            alpha: 1.0,
192        }
193    }
194
195    fn from_vec4(color: Vec4) -> Self {
196        Self {
197            lightness: color[0],
198            a: color[1],
199            b: color[2],
200            alpha: color[3],
201        }
202    }
203
204    fn from_vec3(color: Vec3) -> Self {
205        Self {
206            lightness: color[0],
207            a: color[1],
208            b: color[2],
209            alpha: 1.0,
210        }
211    }
212}
213
214impl From<Laba> for Xyza {
215    fn from(
216        Laba {
217            lightness,
218            a,
219            b,
220            alpha,
221        }: Laba,
222    ) -> Self {
223        // Based on http://www.brucelindbloom.com/index.html?Eqn_Lab_to_XYZ.html
224        let l = 100. * lightness;
225        let a = 100. * a;
226        let b = 100. * b;
227
228        let fy = (l + 16.0) / 116.0;
229        let fx = a / 500.0 + fy;
230        let fz = fy - b / 200.0;
231        let xr = {
232            let fx3 = ops::powf(fx, 3.0);
233
234            if fx3 > Laba::CIE_EPSILON {
235                fx3
236            } else {
237                (116.0 * fx - 16.0) / Laba::CIE_KAPPA
238            }
239        };
240        let yr = if l > Laba::CIE_EPSILON * Laba::CIE_KAPPA {
241            ops::powf((l + 16.0) / 116.0, 3.0)
242        } else {
243            l / Laba::CIE_KAPPA
244        };
245        let zr = {
246            let fz3 = ops::powf(fz, 3.0);
247
248            if fz3 > Laba::CIE_EPSILON {
249                fz3
250            } else {
251                (116.0 * fz - 16.0) / Laba::CIE_KAPPA
252            }
253        };
254        let x = xr * Xyza::D65_WHITE.x;
255        let y = yr * Xyza::D65_WHITE.y;
256        let z = zr * Xyza::D65_WHITE.z;
257
258        Xyza::new(x, y, z, alpha)
259    }
260}
261
262impl From<Xyza> for Laba {
263    fn from(Xyza { x, y, z, alpha }: Xyza) -> Self {
264        // Based on http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_Lab.html
265        let xr = x / Xyza::D65_WHITE.x;
266        let yr = y / Xyza::D65_WHITE.y;
267        let zr = z / Xyza::D65_WHITE.z;
268        let fx = if xr > Laba::CIE_EPSILON {
269            ops::cbrt(xr)
270        } else {
271            (Laba::CIE_KAPPA * xr + 16.0) / 116.0
272        };
273        let fy = if yr > Laba::CIE_EPSILON {
274            ops::cbrt(yr)
275        } else {
276            (Laba::CIE_KAPPA * yr + 16.0) / 116.0
277        };
278        let fz = if yr > Laba::CIE_EPSILON {
279            ops::cbrt(zr)
280        } else {
281            (Laba::CIE_KAPPA * zr + 16.0) / 116.0
282        };
283        let l = 1.16 * fy - 0.16;
284        let a = 5.00 * (fx - fy);
285        let b = 2.00 * (fy - fz);
286
287        Laba::new(l, a, b, alpha)
288    }
289}
290
291// Derived Conversions
292
293impl From<Srgba> for Laba {
294    fn from(value: Srgba) -> Self {
295        Xyza::from(value).into()
296    }
297}
298
299impl From<Laba> for Srgba {
300    fn from(value: Laba) -> Self {
301        Xyza::from(value).into()
302    }
303}
304
305impl From<LinearRgba> for Laba {
306    fn from(value: LinearRgba) -> Self {
307        Xyza::from(value).into()
308    }
309}
310
311impl From<Laba> for LinearRgba {
312    fn from(value: Laba) -> Self {
313        Xyza::from(value).into()
314    }
315}
316
317impl From<Hsla> for Laba {
318    fn from(value: Hsla) -> Self {
319        Xyza::from(value).into()
320    }
321}
322
323impl From<Laba> for Hsla {
324    fn from(value: Laba) -> Self {
325        Xyza::from(value).into()
326    }
327}
328
329impl From<Hsva> for Laba {
330    fn from(value: Hsva) -> Self {
331        Xyza::from(value).into()
332    }
333}
334
335impl From<Laba> for Hsva {
336    fn from(value: Laba) -> Self {
337        Xyza::from(value).into()
338    }
339}
340
341impl From<Hwba> for Laba {
342    fn from(value: Hwba) -> Self {
343        Xyza::from(value).into()
344    }
345}
346
347impl From<Laba> for Hwba {
348    fn from(value: Laba) -> Self {
349        Xyza::from(value).into()
350    }
351}
352
353impl From<Oklaba> for Laba {
354    fn from(value: Oklaba) -> Self {
355        Xyza::from(value).into()
356    }
357}
358
359impl From<Laba> for Oklaba {
360    fn from(value: Laba) -> Self {
361        Xyza::from(value).into()
362    }
363}
364
365#[cfg(test)]
366mod tests {
367    use super::*;
368    use crate::{
369        color_difference::EuclideanDistance, test_colors::TEST_COLORS, testing::assert_approx_eq,
370    };
371
372    #[test]
373    fn test_to_from_srgba() {
374        for color in TEST_COLORS.iter() {
375            let rgb2: Srgba = (color.lab).into();
376            let laba: Laba = (color.rgb).into();
377            assert!(
378                color.rgb.distance(&rgb2) < 0.0001,
379                "{}: {:?} != {:?}",
380                color.name,
381                color.rgb,
382                rgb2
383            );
384            assert_approx_eq!(color.lab.lightness, laba.lightness, 0.001);
385            if laba.lightness > 0.01 {
386                assert_approx_eq!(color.lab.a, laba.a, 0.1);
387            }
388            if laba.lightness > 0.01 && laba.a > 0.01 {
389                assert!(
390                    (color.lab.b - laba.b).abs() < 1.7,
391                    "{:?} != {:?}",
392                    color.lab,
393                    laba
394                );
395            }
396            assert_approx_eq!(color.lab.alpha, laba.alpha, 0.001);
397        }
398    }
399
400    #[test]
401    fn test_to_from_linear() {
402        for color in TEST_COLORS.iter() {
403            let rgb2: LinearRgba = (color.lab).into();
404            let laba: Laba = (color.linear_rgb).into();
405            assert!(
406                color.linear_rgb.distance(&rgb2) < 0.0001,
407                "{}: {:?} != {:?}",
408                color.name,
409                color.linear_rgb,
410                rgb2
411            );
412            assert_approx_eq!(color.lab.lightness, laba.lightness, 0.001);
413            if laba.lightness > 0.01 {
414                assert_approx_eq!(color.lab.a, laba.a, 0.1);
415            }
416            if laba.lightness > 0.01 && laba.a > 0.01 {
417                assert!(
418                    (color.lab.b - laba.b).abs() < 1.7,
419                    "{:?} != {:?}",
420                    color.lab,
421                    laba
422                );
423            }
424            assert_approx_eq!(color.lab.alpha, laba.alpha, 0.001);
425        }
426    }
427}