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