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(
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 Oklaba {
26 pub lightness: f32,
28 pub a: f32,
30 pub b: f32,
32 pub alpha: f32,
34}
35
36impl StandardColor for Oklaba {}
37
38impl_componentwise_vector_space!(Oklaba, [lightness, a, b, alpha]);
39
40impl Oklaba {
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 fn with_a(self, a: f32) -> Self {
81 Self { a, ..self }
82 }
83
84 pub const fn with_b(self, b: f32) -> Self {
86 Self { b, ..self }
87 }
88}
89
90impl Default for Oklaba {
91 fn default() -> Self {
92 Self::new(1., 0., 0., 1.)
93 }
94}
95
96impl Mix for Oklaba {
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 Oklaba {
110 const BLACK: Self = Self::new(0., 0., 0., 1.);
111 const WHITE: Self = Self::new(1.0, 0.0, 0.000000059604645, 1.0);
112}
113
114impl Alpha for Oklaba {
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 Oklaba {
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 EuclideanDistance for Oklaba {
161 #[inline]
162 fn distance_squared(&self, other: &Self) -> f32 {
163 (self.lightness - other.lightness).squared()
164 + (self.a - other.a).squared()
165 + (self.b - other.b).squared()
166 }
167}
168
169impl ColorToComponents for Oklaba {
170 fn to_f32_array(self) -> [f32; 4] {
171 [self.lightness, self.a, self.b, self.alpha]
172 }
173
174 fn to_f32_array_no_alpha(self) -> [f32; 3] {
175 [self.lightness, self.a, self.b]
176 }
177
178 fn to_vec4(self) -> Vec4 {
179 Vec4::new(self.lightness, self.a, self.b, self.alpha)
180 }
181
182 fn to_vec3(self) -> Vec3 {
183 Vec3::new(self.lightness, self.a, self.b)
184 }
185
186 fn from_f32_array(color: [f32; 4]) -> Self {
187 Self {
188 lightness: color[0],
189 a: color[1],
190 b: color[2],
191 alpha: color[3],
192 }
193 }
194
195 fn from_f32_array_no_alpha(color: [f32; 3]) -> Self {
196 Self {
197 lightness: color[0],
198 a: color[1],
199 b: color[2],
200 alpha: 1.0,
201 }
202 }
203
204 fn from_vec4(color: Vec4) -> Self {
205 Self {
206 lightness: color[0],
207 a: color[1],
208 b: color[2],
209 alpha: color[3],
210 }
211 }
212
213 fn from_vec3(color: Vec3) -> Self {
214 Self {
215 lightness: color[0],
216 a: color[1],
217 b: color[2],
218 alpha: 1.0,
219 }
220 }
221}
222
223impl From<LinearRgba> for Oklaba {
224 fn from(value: LinearRgba) -> Self {
225 let LinearRgba {
226 red,
227 green,
228 blue,
229 alpha,
230 } = value;
231 let l = 0.41222146 * red + 0.53633255 * green + 0.051445995 * blue;
234 let m = 0.2119035 * red + 0.6806995 * green + 0.10739696 * blue;
235 let s = 0.08830246 * red + 0.28171885 * green + 0.6299787 * blue;
236 let l_ = ops::cbrt(l);
237 let m_ = ops::cbrt(m);
238 let s_ = ops::cbrt(s);
239 let l = 0.21045426 * l_ + 0.7936178 * m_ - 0.004072047 * s_;
240 let a = 1.9779985 * l_ - 2.4285922 * m_ + 0.4505937 * s_;
241 let b = 0.025904037 * l_ + 0.78277177 * m_ - 0.80867577 * s_;
242 Oklaba::new(l, a, b, alpha)
243 }
244}
245
246impl From<Oklaba> for LinearRgba {
247 fn from(value: Oklaba) -> Self {
248 let Oklaba {
249 lightness,
250 a,
251 b,
252 alpha,
253 } = value;
254
255 let l_ = lightness + 0.39633778 * a + 0.21580376 * b;
258 let m_ = lightness - 0.105561346 * a - 0.06385417 * b;
259 let s_ = lightness - 0.08948418 * a - 1.2914855 * b;
260
261 let l = l_ * l_ * l_;
262 let m = m_ * m_ * m_;
263 let s = s_ * s_ * s_;
264
265 let red = 4.0767417 * l - 3.3077116 * m + 0.23096994 * s;
266 let green = -1.268438 * l + 2.6097574 * m - 0.34131938 * s;
267 let blue = -0.0041960863 * l - 0.7034186 * m + 1.7076147 * s;
268
269 Self {
270 red,
271 green,
272 blue,
273 alpha,
274 }
275 }
276}
277
278impl From<Hsla> for Oklaba {
281 fn from(value: Hsla) -> Self {
282 LinearRgba::from(value).into()
283 }
284}
285
286impl From<Oklaba> for Hsla {
287 fn from(value: Oklaba) -> Self {
288 LinearRgba::from(value).into()
289 }
290}
291
292impl From<Hsva> for Oklaba {
293 fn from(value: Hsva) -> Self {
294 LinearRgba::from(value).into()
295 }
296}
297
298impl From<Oklaba> for Hsva {
299 fn from(value: Oklaba) -> Self {
300 LinearRgba::from(value).into()
301 }
302}
303
304impl From<Hwba> for Oklaba {
305 fn from(value: Hwba) -> Self {
306 LinearRgba::from(value).into()
307 }
308}
309
310impl From<Oklaba> for Hwba {
311 fn from(value: Oklaba) -> Self {
312 LinearRgba::from(value).into()
313 }
314}
315
316impl From<Lcha> for Oklaba {
317 fn from(value: Lcha) -> Self {
318 LinearRgba::from(value).into()
319 }
320}
321
322impl From<Oklaba> for Lcha {
323 fn from(value: Oklaba) -> Self {
324 LinearRgba::from(value).into()
325 }
326}
327
328impl From<Srgba> for Oklaba {
329 fn from(value: Srgba) -> Self {
330 LinearRgba::from(value).into()
331 }
332}
333
334impl From<Oklaba> for Srgba {
335 fn from(value: Oklaba) -> Self {
336 LinearRgba::from(value).into()
337 }
338}
339
340impl From<Xyza> for Oklaba {
341 fn from(value: Xyza) -> Self {
342 LinearRgba::from(value).into()
343 }
344}
345
346impl From<Oklaba> for Xyza {
347 fn from(value: Oklaba) -> Self {
348 LinearRgba::from(value).into()
349 }
350}
351
352#[cfg(test)]
353mod tests {
354 use super::*;
355 use crate::{test_colors::TEST_COLORS, testing::assert_approx_eq};
356
357 #[test]
358 fn test_to_from_srgba() {
359 let oklaba = Oklaba::new(0.5, 0.5, 0.5, 1.0);
360 let srgba: Srgba = oklaba.into();
361 let oklaba2: Oklaba = srgba.into();
362 assert_approx_eq!(oklaba.lightness, oklaba2.lightness, 0.001);
363 assert_approx_eq!(oklaba.a, oklaba2.a, 0.001);
364 assert_approx_eq!(oklaba.b, oklaba2.b, 0.001);
365 assert_approx_eq!(oklaba.alpha, oklaba2.alpha, 0.001);
366 }
367
368 #[test]
369 fn test_to_from_srgba_2() {
370 for color in TEST_COLORS.iter() {
371 let rgb2: Srgba = (color.oklab).into();
372 let oklab: Oklaba = (color.rgb).into();
373 assert!(
374 color.rgb.distance(&rgb2) < 0.0001,
375 "{}: {:?} != {:?}",
376 color.name,
377 color.rgb,
378 rgb2
379 );
380 assert!(
381 color.oklab.distance(&oklab) < 0.0001,
382 "{}: {:?} != {:?}",
383 color.name,
384 color.oklab,
385 oklab
386 );
387 }
388 }
389
390 #[test]
391 fn test_to_from_linear() {
392 let oklaba = Oklaba::new(0.5, 0.5, 0.5, 1.0);
393 let linear: LinearRgba = oklaba.into();
394 let oklaba2: Oklaba = linear.into();
395 assert_approx_eq!(oklaba.lightness, oklaba2.lightness, 0.001);
396 assert_approx_eq!(oklaba.a, oklaba2.a, 0.001);
397 assert_approx_eq!(oklaba.b, oklaba2.b, 0.001);
398 assert_approx_eq!(oklaba.alpha, oklaba2.alpha, 0.001);
399 }
400}