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