1use crate::{
2 color_difference::EuclideanDistance, impl_componentwise_vector_space,
3 okcolor_convert::libm_cbrtf, Alpha, ColorToComponents, Gray, Hsla, Hsva, Hwba, Lcha,
4 LinearRgba, Luminance, Mix, Srgba, StandardColor, Xyza,
5};
6use bevy_math::{FloatPow, Vec3, Vec4};
7#[cfg(feature = "bevy_reflect")]
8use bevy_reflect::prelude::*;
9
10#[doc = include_str!("../docs/conversion.md")]
12#[doc = include_str!("../docs/diagrams/model_graph.svg")]
14#[derive(Debug, Clone, Copy, PartialEq)]
16#[cfg_attr(
17 feature = "bevy_reflect",
18 derive(Reflect),
19 reflect(Clone, PartialEq, Default)
20)]
21#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
22#[cfg_attr(
23 all(feature = "serialize", feature = "bevy_reflect"),
24 reflect(Serialize, Deserialize)
25)]
26pub struct Oklaba {
27 pub lightness: f32,
29 pub a: f32,
31 pub b: f32,
33 pub alpha: f32,
35}
36
37impl StandardColor for Oklaba {}
38
39impl_componentwise_vector_space!(Oklaba, [lightness, a, b, alpha]);
40
41impl Oklaba {
42 pub const fn new(lightness: f32, a: f32, b: f32, alpha: f32) -> Self {
51 Self {
52 lightness,
53 a,
54 b,
55 alpha,
56 }
57 }
58
59 pub const fn lab(lightness: f32, a: f32, b: f32) -> Self {
67 Self {
68 lightness,
69 a,
70 b,
71 alpha: 1.0,
72 }
73 }
74
75 pub const fn with_lightness(self, lightness: f32) -> Self {
77 Self { lightness, ..self }
78 }
79
80 pub const fn with_a(self, a: f32) -> Self {
82 Self { a, ..self }
83 }
84
85 pub const fn with_b(self, b: f32) -> Self {
87 Self { b, ..self }
88 }
89}
90
91impl Default for Oklaba {
92 fn default() -> Self {
93 Self::new(1., 0., 0., 1.)
94 }
95}
96
97impl Mix for Oklaba {
98 #[inline]
99 fn mix(&self, other: &Self, factor: f32) -> Self {
100 let n_factor = 1.0 - factor;
101 Self {
102 lightness: self.lightness * n_factor + other.lightness * factor,
103 a: self.a * n_factor + other.a * factor,
104 b: self.b * n_factor + other.b * factor,
105 alpha: self.alpha * n_factor + other.alpha * factor,
106 }
107 }
108}
109
110impl Gray for Oklaba {
111 const BLACK: Self = Self::new(0., 0., 0., 1.);
112 const WHITE: Self = Self::new(1.0, 0.0, 0.000000059604645, 1.0);
113}
114
115impl Alpha for Oklaba {
116 #[inline]
117 fn with_alpha(&self, alpha: f32) -> Self {
118 Self { alpha, ..*self }
119 }
120
121 #[inline]
122 fn alpha(&self) -> f32 {
123 self.alpha
124 }
125
126 #[inline]
127 fn set_alpha(&mut self, alpha: f32) {
128 self.alpha = alpha;
129 }
130}
131
132impl Luminance for Oklaba {
133 #[inline]
134 fn with_luminance(&self, lightness: f32) -> Self {
135 Self { lightness, ..*self }
136 }
137
138 fn luminance(&self) -> f32 {
139 self.lightness
140 }
141
142 fn darker(&self, amount: f32) -> Self {
143 Self::new(
144 (self.lightness - amount).max(0.),
145 self.a,
146 self.b,
147 self.alpha,
148 )
149 }
150
151 fn lighter(&self, amount: f32) -> Self {
152 Self::new(
153 (self.lightness + amount).min(1.),
154 self.a,
155 self.b,
156 self.alpha,
157 )
158 }
159}
160
161impl EuclideanDistance for Oklaba {
162 #[inline]
163 fn distance_squared(&self, other: &Self) -> f32 {
164 (self.lightness - other.lightness).squared()
165 + (self.a - other.a).squared()
166 + (self.b - other.b).squared()
167 }
168}
169
170impl ColorToComponents for Oklaba {
171 fn to_f32_array(self) -> [f32; 4] {
172 [self.lightness, self.a, self.b, self.alpha]
173 }
174
175 fn to_f32_array_no_alpha(self) -> [f32; 3] {
176 [self.lightness, self.a, self.b]
177 }
178
179 fn to_vec4(self) -> Vec4 {
180 Vec4::new(self.lightness, self.a, self.b, self.alpha)
181 }
182
183 fn to_vec3(self) -> Vec3 {
184 Vec3::new(self.lightness, self.a, self.b)
185 }
186
187 fn from_f32_array(color: [f32; 4]) -> Self {
188 Self {
189 lightness: color[0],
190 a: color[1],
191 b: color[2],
192 alpha: color[3],
193 }
194 }
195
196 fn from_f32_array_no_alpha(color: [f32; 3]) -> Self {
197 Self {
198 lightness: color[0],
199 a: color[1],
200 b: color[2],
201 alpha: 1.0,
202 }
203 }
204
205 fn from_vec4(color: Vec4) -> Self {
206 Self {
207 lightness: color[0],
208 a: color[1],
209 b: color[2],
210 alpha: color[3],
211 }
212 }
213
214 fn from_vec3(color: Vec3) -> Self {
215 Self {
216 lightness: color[0],
217 a: color[1],
218 b: color[2],
219 alpha: 1.0,
220 }
221 }
222}
223
224#[cfg(feature = "wgpu-types")]
225impl From<Oklaba> for wgpu_types::Color {
226 fn from(color: Oklaba) -> Self {
227 wgpu_types::Color {
228 r: color.lightness as f64,
229 g: color.a as f64,
230 b: color.b as f64,
231 a: color.alpha as f64,
232 }
233 }
234}
235
236impl From<LinearRgba> for Oklaba {
237 fn from(value: LinearRgba) -> Self {
238 let LinearRgba {
239 red,
240 green,
241 blue,
242 alpha,
243 } = value;
244 let l = 0.41222146 * red + 0.53633255 * green + 0.051445995 * blue;
247 let m = 0.2119035 * red + 0.6806995 * green + 0.10739696 * blue;
248 let s = 0.08830246 * red + 0.28171885 * green + 0.6299787 * blue;
249 let l_ = libm_cbrtf(l);
250 let m_ = libm_cbrtf(m);
251 let s_ = libm_cbrtf(s);
252 let l = 0.21045426 * l_ + 0.7936178 * m_ - 0.004072047 * s_;
253 let a = 1.9779985 * l_ - 2.4285922 * m_ + 0.4505937 * s_;
254 let b = 0.025904037 * l_ + 0.78277177 * m_ - 0.80867577 * s_;
255 Oklaba::new(l, a, b, alpha)
256 }
257}
258
259impl From<Oklaba> for LinearRgba {
260 fn from(value: Oklaba) -> Self {
261 let Oklaba {
262 lightness,
263 a,
264 b,
265 alpha,
266 } = value;
267
268 let l_ = lightness + 0.39633778 * a + 0.21580376 * b;
271 let m_ = lightness - 0.105561346 * a - 0.06385417 * b;
272 let s_ = lightness - 0.08948418 * a - 1.2914855 * b;
273
274 let l = l_ * l_ * l_;
275 let m = m_ * m_ * m_;
276 let s = s_ * s_ * s_;
277
278 let red = 4.0767417 * l - 3.3077116 * m + 0.23096994 * s;
279 let green = -1.268438 * l + 2.6097574 * m - 0.34131938 * s;
280 let blue = -0.0041960863 * l - 0.7034186 * m + 1.7076147 * s;
281
282 Self {
283 red,
284 green,
285 blue,
286 alpha,
287 }
288 }
289}
290
291impl From<Hsla> for Oklaba {
294 fn from(value: Hsla) -> Self {
295 LinearRgba::from(value).into()
296 }
297}
298
299impl From<Oklaba> for Hsla {
300 fn from(value: Oklaba) -> Self {
301 LinearRgba::from(value).into()
302 }
303}
304
305impl From<Hsva> for Oklaba {
306 fn from(value: Hsva) -> Self {
307 LinearRgba::from(value).into()
308 }
309}
310
311impl From<Oklaba> for Hsva {
312 fn from(value: Oklaba) -> Self {
313 LinearRgba::from(value).into()
314 }
315}
316
317impl From<Hwba> for Oklaba {
318 fn from(value: Hwba) -> Self {
319 LinearRgba::from(value).into()
320 }
321}
322
323impl From<Oklaba> for Hwba {
324 fn from(value: Oklaba) -> Self {
325 LinearRgba::from(value).into()
326 }
327}
328
329impl From<Lcha> for Oklaba {
330 fn from(value: Lcha) -> Self {
331 LinearRgba::from(value).into()
332 }
333}
334
335impl From<Oklaba> for Lcha {
336 fn from(value: Oklaba) -> Self {
337 LinearRgba::from(value).into()
338 }
339}
340
341impl From<Srgba> for Oklaba {
342 fn from(value: Srgba) -> Self {
343 LinearRgba::from(value).into()
344 }
345}
346
347impl From<Oklaba> for Srgba {
348 fn from(value: Oklaba) -> Self {
349 LinearRgba::from(value).into()
350 }
351}
352
353impl From<Xyza> for Oklaba {
354 fn from(value: Xyza) -> Self {
355 LinearRgba::from(value).into()
356 }
357}
358
359impl From<Oklaba> for Xyza {
360 fn from(value: Oklaba) -> Self {
361 LinearRgba::from(value).into()
362 }
363}
364
365#[cfg(test)]
366mod tests {
367 use super::*;
368 use crate::{test_colors::TEST_COLORS, testing::assert_approx_eq};
369
370 #[test]
371 fn test_to_from_srgba() {
372 let oklaba = Oklaba::new(0.5, 0.5, 0.5, 1.0);
373 let srgba: Srgba = oklaba.into();
374 let oklaba2: Oklaba = srgba.into();
375 assert_approx_eq!(oklaba.lightness, oklaba2.lightness, 0.001);
376 assert_approx_eq!(oklaba.a, oklaba2.a, 0.001);
377 assert_approx_eq!(oklaba.b, oklaba2.b, 0.001);
378 assert_approx_eq!(oklaba.alpha, oklaba2.alpha, 0.001);
379 }
380
381 #[test]
382 fn test_to_from_srgba_2() {
383 for color in TEST_COLORS.iter() {
384 let rgb2: Srgba = (color.oklab).into();
385 let oklab: Oklaba = (color.rgb).into();
386 assert!(
387 color.rgb.distance(&rgb2) < 0.0001,
388 "{}: {:?} != {:?}",
389 color.name,
390 color.rgb,
391 rgb2
392 );
393 assert!(
394 color.oklab.distance(&oklab) < 0.0001,
395 "{}: {:?} != {:?}",
396 color.name,
397 color.oklab,
398 oklab
399 );
400 }
401 }
402
403 #[test]
404 fn test_to_from_linear() {
405 let oklaba = Oklaba::new(0.5, 0.5, 0.5, 1.0);
406 let linear: LinearRgba = oklaba.into();
407 let oklaba2: Oklaba = linear.into();
408 assert_approx_eq!(oklaba.lightness, oklaba2.lightness, 0.001);
409 assert_approx_eq!(oklaba.a, oklaba2.a, 0.001);
410 assert_approx_eq!(oklaba.b, oklaba2.b, 0.001);
411 assert_approx_eq!(oklaba.alpha, oklaba2.alpha, 0.001);
412 }
413}