pixie_anim_lib/
color.rs

1//! Color space conversions and perceptual distance.
2
3use crate::quant::Rgb;
4
5/// Represents a color in the CIELAB color space.
6#[derive(Clone, Copy, Debug, PartialEq)]
7pub struct Lab {
8    /// Luminance (0.0 to 100.0)
9    pub l: f32,
10    /// Green-Red component (-128.0 to 127.0)
11    pub a: f32,
12    /// Blue-Yellow component (-128.0 to 127.0)
13    pub b: f32,
14}
15
16impl From<Rgb> for Lab {
17    fn from(rgb: Rgb) -> Self {
18        rgb_to_lab(rgb.r, rgb.g, rgb.b)
19    }
20}
21
22/// Converts RGB to CIELAB using D65 illuminant.
23pub fn rgb_to_lab(r: u8, g: u8, b: u8) -> Lab {
24    // 1. RGB to linear XYZ
25    let mut r = r as f32 / 255.0;
26    let mut g = g as f32 / 255.0;
27    let mut b = b as f32 / 255.0;
28
29    r = if r > 0.04045 {
30        ((r + 0.055) / 1.055).powf(2.4)
31    } else {
32        r / 12.92
33    };
34    g = if g > 0.04045 {
35        ((g + 0.055) / 1.055).powf(2.4)
36    } else {
37        g / 12.92
38    };
39    b = if b > 0.04045 {
40        ((b + 0.055) / 1.055).powf(2.4)
41    } else {
42        b / 12.92
43    };
44
45    let x = r * 0.4124 + g * 0.3576 + b * 0.1805;
46    let y = r * 0.2126 + g * 0.7152 + b * 0.0722;
47    let z = r * 0.0193 + g * 0.1192 + b * 0.9505;
48
49    // 2. XYZ to CIELAB (D65)
50    let x = x / 0.95047;
51    let y = y / 1.00000;
52    let z = z / 1.08883;
53
54    let f = |t: f32| {
55        if t > 0.008856 {
56            t.powf(1.0 / 3.0)
57        } else {
58            7.787 * t + 16.0 / 116.0
59        }
60    };
61
62    Lab {
63        l: 116.0 * f(y) - 16.0,
64        a: 500.0 * (f(x) - f(y)),
65        b: 200.0 * (f(y) - f(z)),
66    }
67}
68
69/// Calculates the squared Euclidean distance between two Lab colors.
70pub fn lab_distance_sq(c1: Lab, c2: Lab) -> f32 {
71    let dl = c1.l - c2.l;
72    let da = c1.a - c2.a;
73    let db = c1.b - c2.b;
74    dl * dl + da * da + db * db
75}