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
223#[cfg(feature = "wgpu-types")]
224impl From<Oklaba> for wgpu_types::Color {
225 fn from(color: Oklaba) -> Self {
226 wgpu_types::Color {
227 r: color.lightness as f64,
228 g: color.a as f64,
229 b: color.b as f64,
230 a: color.alpha as f64,
231 }
232 }
233}
234
235impl From<LinearRgba> for Oklaba {
236 fn from(value: LinearRgba) -> Self {
237 let LinearRgba {
238 red,
239 green,
240 blue,
241 alpha,
242 } = value;
243 let l = 0.41222146 * red + 0.53633255 * green + 0.051445995 * blue;
246 let m = 0.2119035 * red + 0.6806995 * green + 0.10739696 * blue;
247 let s = 0.08830246 * red + 0.28171885 * green + 0.6299787 * blue;
248 let l_ = ops::cbrt(l);
249 let m_ = ops::cbrt(m);
250 let s_ = ops::cbrt(s);
251 let l = 0.21045426 * l_ + 0.7936178 * m_ - 0.004072047 * s_;
252 let a = 1.9779985 * l_ - 2.4285922 * m_ + 0.4505937 * s_;
253 let b = 0.025904037 * l_ + 0.78277177 * m_ - 0.80867577 * s_;
254 Oklaba::new(l, a, b, alpha)
255 }
256}
257
258impl From<Oklaba> for LinearRgba {
259 fn from(value: Oklaba) -> Self {
260 let Oklaba {
261 lightness,
262 a,
263 b,
264 alpha,
265 } = value;
266
267 let l_ = lightness + 0.39633778 * a + 0.21580376 * b;
270 let m_ = lightness - 0.105561346 * a - 0.06385417 * b;
271 let s_ = lightness - 0.08948418 * a - 1.2914855 * b;
272
273 let l = l_ * l_ * l_;
274 let m = m_ * m_ * m_;
275 let s = s_ * s_ * s_;
276
277 let red = 4.0767417 * l - 3.3077116 * m + 0.23096994 * s;
278 let green = -1.268438 * l + 2.6097574 * m - 0.34131938 * s;
279 let blue = -0.0041960863 * l - 0.7034186 * m + 1.7076147 * s;
280
281 Self {
282 red,
283 green,
284 blue,
285 alpha,
286 }
287 }
288}
289
290impl From<Hsla> for Oklaba {
293 fn from(value: Hsla) -> Self {
294 LinearRgba::from(value).into()
295 }
296}
297
298impl From<Oklaba> for Hsla {
299 fn from(value: Oklaba) -> Self {
300 LinearRgba::from(value).into()
301 }
302}
303
304impl From<Hsva> for Oklaba {
305 fn from(value: Hsva) -> Self {
306 LinearRgba::from(value).into()
307 }
308}
309
310impl From<Oklaba> for Hsva {
311 fn from(value: Oklaba) -> Self {
312 LinearRgba::from(value).into()
313 }
314}
315
316impl From<Hwba> for Oklaba {
317 fn from(value: Hwba) -> Self {
318 LinearRgba::from(value).into()
319 }
320}
321
322impl From<Oklaba> for Hwba {
323 fn from(value: Oklaba) -> Self {
324 LinearRgba::from(value).into()
325 }
326}
327
328impl From<Lcha> for Oklaba {
329 fn from(value: Lcha) -> Self {
330 LinearRgba::from(value).into()
331 }
332}
333
334impl From<Oklaba> for Lcha {
335 fn from(value: Oklaba) -> Self {
336 LinearRgba::from(value).into()
337 }
338}
339
340impl From<Srgba> for Oklaba {
341 fn from(value: Srgba) -> Self {
342 LinearRgba::from(value).into()
343 }
344}
345
346impl From<Oklaba> for Srgba {
347 fn from(value: Oklaba) -> Self {
348 LinearRgba::from(value).into()
349 }
350}
351
352impl From<Xyza> for Oklaba {
353 fn from(value: Xyza) -> Self {
354 LinearRgba::from(value).into()
355 }
356}
357
358impl From<Oklaba> for Xyza {
359 fn from(value: Oklaba) -> Self {
360 LinearRgba::from(value).into()
361 }
362}
363
364#[cfg(test)]
365mod tests {
366 use super::*;
367 use crate::{test_colors::TEST_COLORS, testing::assert_approx_eq};
368
369 #[test]
370 fn test_to_from_srgba() {
371 let oklaba = Oklaba::new(0.5, 0.5, 0.5, 1.0);
372 let srgba: Srgba = oklaba.into();
373 let oklaba2: Oklaba = srgba.into();
374 assert_approx_eq!(oklaba.lightness, oklaba2.lightness, 0.001);
375 assert_approx_eq!(oklaba.a, oklaba2.a, 0.001);
376 assert_approx_eq!(oklaba.b, oklaba2.b, 0.001);
377 assert_approx_eq!(oklaba.alpha, oklaba2.alpha, 0.001);
378 }
379
380 #[test]
381 fn test_to_from_srgba_2() {
382 for color in TEST_COLORS.iter() {
383 let rgb2: Srgba = (color.oklab).into();
384 let oklab: Oklaba = (color.rgb).into();
385 assert!(
386 color.rgb.distance(&rgb2) < 0.0001,
387 "{}: {:?} != {:?}",
388 color.name,
389 color.rgb,
390 rgb2
391 );
392 assert!(
393 color.oklab.distance(&oklab) < 0.0001,
394 "{}: {:?} != {:?}",
395 color.name,
396 color.oklab,
397 oklab
398 );
399 }
400 }
401
402 #[test]
403 fn test_to_from_linear() {
404 let oklaba = Oklaba::new(0.5, 0.5, 0.5, 1.0);
405 let linear: LinearRgba = oklaba.into();
406 let oklaba2: Oklaba = linear.into();
407 assert_approx_eq!(oklaba.lightness, oklaba2.lightness, 0.001);
408 assert_approx_eq!(oklaba.a, oklaba2.a, 0.001);
409 assert_approx_eq!(oklaba.b, oklaba2.b, 0.001);
410 assert_approx_eq!(oklaba.alpha, oklaba2.alpha, 0.001);
411 }
412}