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#[doc = include_str!("../docs/conversion.md")]
11#[doc = include_str!("../docs/diagrams/model_graph.svg")]
13#[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 pub lightness: f32,
28 pub a: f32,
30 pub b: f32,
32 pub alpha: f32,
34}
35
36impl StandardColor for Laba {}
37
38impl_componentwise_vector_space!(Laba, [lightness, a, b, alpha]);
39
40impl Laba {
41 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 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 pub const fn with_lightness(self, lightness: f32) -> Self {
76 Self { lightness, ..self }
77 }
78
79 pub const CIE_EPSILON: f32 = 216.0 / 24389.0;
83
84 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 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 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
291impl 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}