ini_material_color_utilities_rs/util/
color.rs

1use crate::util::math;
2
3pub const SRGB_TO_XYZ: [[f64; 3]; 3] = [
4    [0.41233895, 0.35762064, 0.18051042],
5    [0.2126, 0.7152, 0.0722],
6    [0.01932141, 0.11916382, 0.95034478],
7];
8
9pub const XYZ_TO_SRGB: [[f64; 3]; 3] = [
10    [
11        3.2413774792388685,
12        -1.5376652402851851,
13        -0.49885366846268053,
14    ],
15    [-0.9691452513005321, 1.8758853451067872, 0.04156585616912061],
16    [
17        0.05562093689691305,
18        -0.20395524564742123,
19        1.0571799111220335,
20    ],
21];
22
23pub const WHITE_POINT_D65: [f64; 3] = [95.047, 100.0, 108.883];
24
25pub type RGB = [u8; 3];
26pub type ARGB = [u8; 4];
27pub type LinearRGB = [f64; 3];
28pub type XYZ = [f64; 3];
29pub type LAB = [f64; 3];
30
31/// Converts a color from RGB components to ARGB format.
32pub fn argb_from_rgb([r, g, b]: RGB) -> ARGB {
33    [255, r, g, b]
34}
35
36/// Formats a color as ARGB to #RRGGBB format
37pub fn format_argb_as_rgb(argb: [u8; 4]) -> String {
38    format!("#{:02x}{:02x}{:02x}", argb[1], argb[2], argb[3])
39}
40
41/// Converts a color from linear RGB components to ARGB format.
42pub fn argb_from_linrgb([r, g, b]: LinearRGB) -> ARGB {
43    let r = delinearized(r);
44    let g = delinearized(g);
45    let b = delinearized(b);
46
47    argb_from_rgb([r, g, b])
48}
49
50/// Converts a color from ARGB to XYZ.
51pub fn argb_from_xyz(xyz: XYZ) -> ARGB {
52    let [r, g, b] = math::matrix_multiply(xyz, XYZ_TO_SRGB);
53    let r = delinearized(r);
54    let g = delinearized(g);
55    let b = delinearized(b);
56
57    argb_from_rgb([r, g, b])
58}
59
60/// Converts a color from XYZ to ARGB.
61pub fn xyz_from_argb([_, r, g, b]: ARGB) -> XYZ {
62    let r = linearized(r);
63    let g = linearized(g);
64    let b = linearized(b);
65
66    math::matrix_multiply([r, g, b], SRGB_TO_XYZ)
67}
68
69/// Converts a color represented in Lab color space into an ARGB integer.
70pub fn argb_from_lab(l: f64, a: f64, b: f64) -> ARGB {
71    let fy = (l + 16.0) / 116.0;
72    let fx = a / 500.0 + fy;
73    let fz = fy - b / 200.0;
74    let x = lab_invf(fx) * WHITE_POINT_D65[0];
75    let y = lab_invf(fy) * WHITE_POINT_D65[1];
76    let z = lab_invf(fz) * WHITE_POINT_D65[2];
77
78    argb_from_xyz([x, y, z])
79}
80
81/// Converts a color from ARGB representation to L*a*b* representation.
82///
83/// # Arguments
84///
85/// * `argb`: the ARGB representation of a color
86///
87/// returns: a Lab object representing the color
88pub fn lab_from_argb(argb: ARGB) -> LAB {
89    let [x, y, z] = xyz_from_argb(argb);
90    let fx = lab_f(x / WHITE_POINT_D65[0]);
91    let fy = lab_f(y / WHITE_POINT_D65[1]);
92    let fz = lab_f(z / WHITE_POINT_D65[2]);
93    let l = 116.0 * fy - 16.0;
94    let a = 500.0 * (fx - fy);
95    let b = 200.0 * (fy - fz);
96
97    [l, a, b]
98}
99
100/// Converts an L* value to an ARGB representation.
101///
102/// # Arguments
103///
104/// * `lstar`: L* in L*a*b*
105///
106/// returns: ARGB representation of grayscale color with lightness matching L*
107pub fn argb_from_lstar(lstar: f64) -> ARGB {
108    let y = y_from_lstar(lstar);
109    let w = delinearized(y);
110
111    argb_from_rgb([w, w, w])
112}
113
114/// Computes the L* value of a color in ARGB representation.
115///
116/// # Arguments
117///
118/// * `argb`: ARGB representation of a color
119///
120/// returns: L*, from L*a*b*, coordinate of the color
121pub fn lstar_from_argb(argb: ARGB) -> f64 {
122    let y = xyz_from_argb(argb)[1];
123
124    116.0 * lab_f(y / 100.0) - 16.0
125}
126
127/// Converts an L* value to a Y value.
128/// <p>L* in L*a*b* and Y in XYZ measure the same quantity, luminance.
129/// <p>L* measures perceptual luminance, a linear scale. Y in XYZ measures relative luminance, a
130/// logarithmic scale.
131///
132/// # Arguments
133///
134/// * `lstar`: L* in L*a*b*
135///
136/// returns: Y in XYZ
137pub fn y_from_lstar(lstar: f64) -> f64 {
138    100.0 * lab_invf((lstar + 16.0) / 116.0)
139}
140
141/// Linearizes an RGB component.
142///
143/// # Arguments
144///
145/// * `rgb_comp`: 0 <= rgb_component <= 255, represents R/G/B channel
146///
147/// returns: 0.0 <= output <= 100.0, color channel converted to linear RGB space
148pub fn linearized(rgb_comp: u8) -> f64 {
149    let normalized = rgb_comp as f64 / 255.0;
150    if normalized <= 0.040449936 {
151        normalized / 12.92 * 100.0
152    } else {
153        ((normalized + 0.055) / 1.055).powf(2.4) * 100.0
154    }
155}
156
157/// Delinearizes an RGB component.
158///
159/// # Arguments
160///
161/// * `rgb_comp`: 0.0 <= rgb_component <= 100.0, represents linear R/G/B channel
162///
163/// returns: 0 <= output <= 255, color channel converted to regular RGB space
164pub fn delinearized(rgb_comp: f64) -> u8 {
165    let normalized = rgb_comp / 100.0;
166    let delinearized = if normalized <= 0.0031308 {
167        normalized * 12.92
168    } else {
169        1.055 * normalized.powf(1.0 / 2.4) - 0.055
170    };
171    (delinearized * 255.0).round().clamp(0.0, 255.0) as u8
172}
173
174fn lab_f(t: f64) -> f64 {
175    let e = 216.0 / 24389.0;
176    let kappa = 24389.0 / 27.0;
177    if t > e {
178        t.powf(1.0 / 3.0)
179    } else {
180        (kappa * t + 16.0) / 116.0
181    }
182}
183
184fn lab_invf(ft: f64) -> f64 {
185    let e = 216.0 / 24389.0;
186    let kappa = 24389.0 / 27.0;
187    let ft3 = ft * ft * ft;
188    if ft3 > e {
189        ft3
190    } else {
191        (116.0 * ft - 16.0) / kappa
192    }
193}