1use crate::{
2 color_difference::EuclideanDistance, impl_componentwise_vector_space, Alpha, ColorToComponents,
3 Gray, Hsla, Hsva, Hwba, Lcha, LinearRgba, Luminance, Mix, Srgba, StandardColor, Xyza,
4};
5use bevy_math::{ops, FloatPow, 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 Oklaba {
22 pub lightness: f32,
24 pub a: f32,
26 pub b: f32,
28 pub alpha: f32,
30}
31
32impl StandardColor for Oklaba {}
33
34impl_componentwise_vector_space!(Oklaba, [lightness, a, b, alpha]);
35
36impl Oklaba {
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 fn with_a(self, a: f32) -> Self {
77 Self { a, ..self }
78 }
79
80 pub const fn with_b(self, b: f32) -> Self {
82 Self { b, ..self }
83 }
84}
85
86impl Default for Oklaba {
87 fn default() -> Self {
88 Self::new(1., 0., 0., 1.)
89 }
90}
91
92impl Mix for Oklaba {
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 Oklaba {
106 const BLACK: Self = Self::new(0., 0., 0., 1.);
107 const WHITE: Self = Self::new(1.0, 0.0, 0.000000059604645, 1.0);
108}
109
110impl Alpha for Oklaba {
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 Oklaba {
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 EuclideanDistance for Oklaba {
157 #[inline]
158 fn distance_squared(&self, other: &Self) -> f32 {
159 (self.lightness - other.lightness).squared()
160 + (self.a - other.a).squared()
161 + (self.b - other.b).squared()
162 }
163}
164
165impl ColorToComponents for Oklaba {
166 fn to_f32_array(self) -> [f32; 4] {
167 [self.lightness, self.a, self.b, self.alpha]
168 }
169
170 fn to_f32_array_no_alpha(self) -> [f32; 3] {
171 [self.lightness, self.a, self.b]
172 }
173
174 fn to_vec4(self) -> Vec4 {
175 Vec4::new(self.lightness, self.a, self.b, self.alpha)
176 }
177
178 fn to_vec3(self) -> Vec3 {
179 Vec3::new(self.lightness, self.a, self.b)
180 }
181
182 fn from_f32_array(color: [f32; 4]) -> Self {
183 Self {
184 lightness: color[0],
185 a: color[1],
186 b: color[2],
187 alpha: color[3],
188 }
189 }
190
191 fn from_f32_array_no_alpha(color: [f32; 3]) -> Self {
192 Self {
193 lightness: color[0],
194 a: color[1],
195 b: color[2],
196 alpha: 1.0,
197 }
198 }
199
200 fn from_vec4(color: Vec4) -> Self {
201 Self {
202 lightness: color[0],
203 a: color[1],
204 b: color[2],
205 alpha: color[3],
206 }
207 }
208
209 fn from_vec3(color: Vec3) -> Self {
210 Self {
211 lightness: color[0],
212 a: color[1],
213 b: color[2],
214 alpha: 1.0,
215 }
216 }
217}
218
219#[allow(clippy::excessive_precision)]
220impl From<LinearRgba> for Oklaba {
221 fn from(value: LinearRgba) -> Self {
222 let LinearRgba {
223 red,
224 green,
225 blue,
226 alpha,
227 } = value;
228 let l = 0.4122214708 * red + 0.5363325363 * green + 0.0514459929 * blue;
230 let m = 0.2119034982 * red + 0.6806995451 * green + 0.1073969566 * blue;
231 let s = 0.0883024619 * red + 0.2817188376 * green + 0.6299787005 * blue;
232 let l_ = ops::cbrt(l);
233 let m_ = ops::cbrt(m);
234 let s_ = ops::cbrt(s);
235 let l = 0.2104542553 * l_ + 0.7936177850 * m_ - 0.0040720468 * s_;
236 let a = 1.9779984951 * l_ - 2.4285922050 * m_ + 0.4505937099 * s_;
237 let b = 0.0259040371 * l_ + 0.7827717662 * m_ - 0.8086757660 * s_;
238 Oklaba::new(l, a, b, alpha)
239 }
240}
241
242#[allow(clippy::excessive_precision)]
243impl From<Oklaba> for LinearRgba {
244 fn from(value: Oklaba) -> Self {
245 let Oklaba {
246 lightness,
247 a,
248 b,
249 alpha,
250 } = value;
251
252 let l_ = lightness + 0.3963377774 * a + 0.2158037573 * b;
254 let m_ = lightness - 0.1055613458 * a - 0.0638541728 * b;
255 let s_ = lightness - 0.0894841775 * a - 1.2914855480 * b;
256
257 let l = l_ * l_ * l_;
258 let m = m_ * m_ * m_;
259 let s = s_ * s_ * s_;
260
261 let red = 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s;
262 let green = -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s;
263 let blue = -0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s;
264
265 Self {
266 red,
267 green,
268 blue,
269 alpha,
270 }
271 }
272}
273
274impl From<Hsla> for Oklaba {
277 fn from(value: Hsla) -> Self {
278 LinearRgba::from(value).into()
279 }
280}
281
282impl From<Oklaba> for Hsla {
283 fn from(value: Oklaba) -> Self {
284 LinearRgba::from(value).into()
285 }
286}
287
288impl From<Hsva> for Oklaba {
289 fn from(value: Hsva) -> Self {
290 LinearRgba::from(value).into()
291 }
292}
293
294impl From<Oklaba> for Hsva {
295 fn from(value: Oklaba) -> Self {
296 LinearRgba::from(value).into()
297 }
298}
299
300impl From<Hwba> for Oklaba {
301 fn from(value: Hwba) -> Self {
302 LinearRgba::from(value).into()
303 }
304}
305
306impl From<Oklaba> for Hwba {
307 fn from(value: Oklaba) -> Self {
308 LinearRgba::from(value).into()
309 }
310}
311
312impl From<Lcha> for Oklaba {
313 fn from(value: Lcha) -> Self {
314 LinearRgba::from(value).into()
315 }
316}
317
318impl From<Oklaba> for Lcha {
319 fn from(value: Oklaba) -> Self {
320 LinearRgba::from(value).into()
321 }
322}
323
324impl From<Srgba> for Oklaba {
325 fn from(value: Srgba) -> Self {
326 LinearRgba::from(value).into()
327 }
328}
329
330impl From<Oklaba> for Srgba {
331 fn from(value: Oklaba) -> Self {
332 LinearRgba::from(value).into()
333 }
334}
335
336impl From<Xyza> for Oklaba {
337 fn from(value: Xyza) -> Self {
338 LinearRgba::from(value).into()
339 }
340}
341
342impl From<Oklaba> for Xyza {
343 fn from(value: Oklaba) -> Self {
344 LinearRgba::from(value).into()
345 }
346}
347
348#[cfg(test)]
349mod tests {
350 use super::*;
351 use crate::{test_colors::TEST_COLORS, testing::assert_approx_eq};
352
353 #[test]
354 fn test_to_from_srgba() {
355 let oklaba = Oklaba::new(0.5, 0.5, 0.5, 1.0);
356 let srgba: Srgba = oklaba.into();
357 let oklaba2: Oklaba = srgba.into();
358 assert_approx_eq!(oklaba.lightness, oklaba2.lightness, 0.001);
359 assert_approx_eq!(oklaba.a, oklaba2.a, 0.001);
360 assert_approx_eq!(oklaba.b, oklaba2.b, 0.001);
361 assert_approx_eq!(oklaba.alpha, oklaba2.alpha, 0.001);
362 }
363
364 #[test]
365 fn test_to_from_srgba_2() {
366 for color in TEST_COLORS.iter() {
367 let rgb2: Srgba = (color.oklab).into();
368 let oklab: Oklaba = (color.rgb).into();
369 assert!(
370 color.rgb.distance(&rgb2) < 0.0001,
371 "{}: {:?} != {:?}",
372 color.name,
373 color.rgb,
374 rgb2
375 );
376 assert!(
377 color.oklab.distance(&oklab) < 0.0001,
378 "{}: {:?} != {:?}",
379 color.name,
380 color.oklab,
381 oklab
382 );
383 }
384 }
385
386 #[test]
387 fn test_to_from_linear() {
388 let oklaba = Oklaba::new(0.5, 0.5, 0.5, 1.0);
389 let linear: LinearRgba = oklaba.into();
390 let oklaba2: Oklaba = linear.into();
391 assert_approx_eq!(oklaba.lightness, oklaba2.lightness, 0.001);
392 assert_approx_eq!(oklaba.a, oklaba2.a, 0.001);
393 assert_approx_eq!(oklaba.b, oklaba2.b, 0.001);
394 assert_approx_eq!(oklaba.alpha, oklaba2.alpha, 0.001);
395 }
396}