bigcolor/
matrix_utils.rs

1// Matrix utilities for color space conversions
2
3pub type Matrix3x3 = [[f32; 3]; 3];
4pub type Vector3 = [f32; 3];
5
6/// Multiply a 3D vector by a 3x3 matrix
7pub fn multiply_v3_m3x3(v: Vector3, m: Matrix3x3) -> Vector3 {
8    [
9        v[0] * m[0][0] + v[1] * m[0][1] + v[2] * m[0][2],
10        v[0] * m[1][0] + v[1] * m[1][1] + v[2] * m[1][2],
11        v[0] * m[2][0] + v[1] * m[2][1] + v[2] * m[2][2],
12    ]
13}
14
15// Recalculated for consistent reference white
16// see https://github.com/w3c/csswg-drafts/issues/6642#issuecomment-943521484
17pub const XYZ_TO_LMS_M: Matrix3x3 = [
18    [0.8190224379967030, 0.3619062600528904, -0.1288737815209879],
19    [0.0329836539323885, 0.9292868615863434, 0.0361446663506424],
20    [0.0481771893596242, 0.2642395317527308, 0.6335478284694309],
21];
22
23// inverse of XYZ_TO_LMS_M
24pub const LMS_TO_XYZ_M: Matrix3x3 = [
25    [1.2268798758459243, -0.5578149944602171, 0.2813910456659647],
26    [-0.0405757452148008, 1.1122868032803170, -0.0717110580655164],
27    [-0.0763729366746601, -0.4214933324022432, 1.5869240198367816],
28];
29
30pub const LMS_TO_LAB_M: Matrix3x3 = [
31    [0.2104542683093140, 0.7936177747023054, -0.0040720430116193],
32    [1.9779985324311684, -2.4285922420485799, 0.4505937096174110],
33    [0.0259040424655478, 0.7827717124575296, -0.8086757549230774],
34];
35
36pub const LAB_TO_LMS_M: Matrix3x3 = [
37    [1.0000000000000000, 0.3963377773761749, 0.2158037573099136],
38    [1.0000000000000000, -0.1055613458156586, -0.0638541728258133],
39    [1.0000000000000000, -0.0894841775298119, -1.2914855480194092],
40];
41
42// Bradford CAT matrices for D65 to D50 and vice versa
43pub const D65_TO_D50_M: Matrix3x3 = [
44    [1.0479297925449969, 0.022946870601609652, -0.05019226628920524],
45    [0.02962780877005599, 0.9904344267538799, -0.017073799063418826],
46    [-0.009243040646204504, 0.015055191490298152, 0.7518742814281371],
47];
48
49pub const D50_TO_D65_M: Matrix3x3 = [
50    [0.955473421488075, -0.02309845494876471, 0.06325924320057072],
51    [-0.0283697093338637, 1.0099953980813041, 0.021041441191917323],
52    [0.012314014864481998, -0.020507649298898964, 1.330365926242124],
53];
54
55// White points (standard illuminants)
56pub const WHITE_D65: Vector3 = [0.95047, 1.0, 1.08883]; // Standard D65 white point
57pub const WHITE_D50: Vector3 = [0.96422, 1.0, 0.82521]; // Standard D50 white point
58
59/// Adapt XYZ from one white point to another using Bradford transformation
60pub fn adapt_xyz(xyz: Vector3, from_white: Vector3, to_white: Vector3) -> Vector3 {
61    if from_white == to_white {
62        return xyz;
63    }
64    
65    if from_white == WHITE_D65 && to_white == WHITE_D50 {
66        multiply_v3_m3x3(xyz, D65_TO_D50_M)
67    } else if from_white == WHITE_D50 && to_white == WHITE_D65 {
68        multiply_v3_m3x3(xyz, D50_TO_D65_M)
69    } else {
70        // For other white points, we would need a more general implementation
71        // This is just a simplified version supporting D65<->D50
72        xyz
73    }
74}
75
76/// Constrain an angle to [0, 360) degrees
77pub fn constrain_angle(angle: f32) -> f32 {
78    let mut a = angle % 360.0;
79    if a < 0.0 {
80        a += 360.0;
81    }
82    a
83}