ravenrs_animation/
oklab.rs1use ravenrs_core::Color;
2
3pub fn to_oklab(color: Color) -> [f32; 4] {
6 let [r, g, b, alpha] = color.to_linear();
7 let l = (0.412_221_46 * r + 0.536_332_55 * g + 0.051_445_995 * b).cbrt();
8 let m = (0.211_903_5 * r + 0.680_699_5 * g + 0.107_396_96 * b).cbrt();
9 let s = (0.088_302_46 * r + 0.281_718_84 * g + 0.629_978_7 * b).cbrt();
10 [
11 0.210_454_26 * l + 0.793_617_8 * m - 0.004_072_047 * s,
12 1.977_998_5 * l - 2.428_592_2 * m + 0.450_593_7 * s,
13 0.025_904_037 * l + 0.782_771_77 * m - 0.808_675_77 * s,
14 alpha,
15 ]
16}
17
18pub fn from_oklab(lab: [f32; 4]) -> Color {
20 let [l, a, b, alpha] = lab;
21 let lp = l + 0.396_337_78 * a + 0.215_803_76 * b;
22 let mp = l - 0.105_561_346 * a - 0.063_854_17 * b;
23 let sp = l - 0.089_484_18 * a - 1.291_485_5 * b;
24 let (lc, mc, sc) = (lp * lp * lp, mp * mp * mp, sp * sp * sp);
25 let channel = |c: f32| {
26 let c = c.clamp(0.0, 1.0);
27 if c <= 0.003_130_8 { c * 12.92 } else { 1.055 * c.powf(1.0 / 2.4) - 0.055 }
28 };
29 Color::rgba(
30 channel(4.076_741_7 * lc - 3.307_711_6 * mc + 0.230_969_94 * sc),
31 channel(-1.268_438 * lc + 2.609_757_4 * mc - 0.341_319_38 * sc),
32 channel(-0.004_196_086_3 * lc - 0.703_418_6 * mc + 1.707_614_7 * sc),
33 alpha.clamp(0.0, 1.0),
34 )
35}
36
37pub fn mix_oklab(from: Color, to: Color, t: f32) -> Color {
40 let (a, b) = (premultiplied(from), premultiplied(to));
41 unpremultiplied(std::array::from_fn(|i| a[i] + (b[i] - a[i]) * t))
42}
43
44pub(crate) fn premultiplied(color: Color) -> [f32; 4] {
45 let [l, a, b, alpha] = to_oklab(color);
46 [l * alpha, a * alpha, b * alpha, alpha]
47}
48
49pub(crate) fn unpremultiplied(v: [f32; 4]) -> Color {
50 let alpha = v[3];
51 if alpha <= 1e-4 {
52 return Color::TRANSPARENT;
53 }
54 from_oklab([v[0] / alpha, v[1] / alpha, v[2] / alpha, alpha])
55}