csscolorparser/
utils.rs

1#[cfg(feature = "lab")]
2use std::f32::consts::{PI, TAU};
3
4#[cfg(feature = "lab")]
5const PI_3: f32 = PI * 3.0;
6
7#[allow(clippy::excessive_precision)]
8pub(crate) fn oklab_to_linear_rgb(l: f32, a: f32, b: f32) -> [f32; 3] {
9    let l_ = (l + 0.3963377774 * a + 0.2158037573 * b).powi(3);
10    let m_ = (l - 0.1055613458 * a - 0.0638541728 * b).powi(3);
11    let s_ = (l - 0.0894841775 * a - 1.2914855480 * b).powi(3);
12    let r = 4.0767416621 * l_ - 3.3077115913 * m_ + 0.2309699292 * s_;
13    let g = -1.2684380046 * l_ + 2.6097574011 * m_ - 0.3413193965 * s_;
14    let b = -0.0041960863 * l_ - 0.7034186147 * m_ + 1.7076147010 * s_;
15    [r, g, b]
16}
17
18#[allow(clippy::excessive_precision)]
19pub(crate) fn linear_rgb_to_oklab(r: f32, g: f32, b: f32) -> [f32; 3] {
20    let l_ = (0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b).cbrt();
21    let m_ = (0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b).cbrt();
22    let s_ = (0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b).cbrt();
23    let l = 0.2104542553 * l_ + 0.7936177850 * m_ - 0.0040720468 * s_;
24    let a = 1.9779984951 * l_ - 2.4285922050 * m_ + 0.4505937099 * s_;
25    let b = 0.0259040371 * l_ + 0.7827717662 * m_ - 0.8086757660 * s_;
26    [l, a, b]
27}
28
29pub(crate) fn hue_to_rgb(n1: f32, n2: f32, h: f32) -> f32 {
30    let h = modulo(h, 6.0);
31
32    if h < 1.0 {
33        return n1 + ((n2 - n1) * h);
34    }
35
36    if h < 3.0 {
37        return n2;
38    }
39
40    if h < 4.0 {
41        return n1 + ((n2 - n1) * (4.0 - h));
42    }
43
44    n1
45}
46
47// h = 0..360
48// s, l = 0..1
49// r, g, b = 0..1
50pub(crate) fn hsl_to_rgb(h: f32, s: f32, l: f32) -> [f32; 3] {
51    if s == 0.0 {
52        return [l, l, l];
53    }
54
55    let n2 = if l < 0.5 {
56        l * (1.0 + s)
57    } else {
58        l + s - (l * s)
59    };
60
61    let n1 = 2.0 * l - n2;
62    let h = h / 60.0;
63    let r = hue_to_rgb(n1, n2, h + 2.0);
64    let g = hue_to_rgb(n1, n2, h);
65    let b = hue_to_rgb(n1, n2, h - 2.0);
66    [r, g, b]
67}
68
69pub(crate) fn hwb_to_rgb(hue: f32, white: f32, black: f32) -> [f32; 3] {
70    if white + black >= 1.0 {
71        let l = white / (white + black);
72        return [l, l, l];
73    }
74
75    let [r, g, b] = hsl_to_rgb(hue, 1.0, 0.5);
76    let r = r * (1.0 - white - black) + white;
77    let g = g * (1.0 - white - black) + white;
78    let b = b * (1.0 - white - black) + white;
79    [r, g, b]
80}
81
82#[allow(clippy::float_cmp)]
83pub(crate) fn hsv_to_hsl(h: f32, s: f32, v: f32) -> [f32; 3] {
84    let l = (2.0 - s) * v / 2.0;
85
86    let s = if l != 0.0 {
87        if l == 1.0 {
88            0.0
89        } else if l < 0.5 {
90            s * v / (l * 2.0)
91        } else {
92            s * v / (2.0 - l * 2.0)
93        }
94    } else {
95        s
96    };
97
98    [h, s, l]
99}
100
101pub(crate) fn hsv_to_rgb(h: f32, s: f32, v: f32) -> [f32; 3] {
102    let [h, s, l] = hsv_to_hsl(h, s, v);
103    hsl_to_rgb(h, s, l)
104}
105
106#[allow(clippy::float_cmp)]
107pub(crate) fn rgb_to_hsv(r: f32, g: f32, b: f32) -> [f32; 3] {
108    let v = r.max(g.max(b));
109    let d = v - r.min(g.min(b));
110
111    if d == 0.0 {
112        return [0.0, 0.0, v];
113    }
114
115    let s = d / v;
116    let dr = (v - r) / d;
117    let dg = (v - g) / d;
118    let db = (v - b) / d;
119
120    let h = if r == v {
121        db - dg
122    } else if g == v {
123        2.0 + dr - db
124    } else {
125        4.0 + dg - dr
126    };
127
128    let h = (h * 60.0) % 360.0;
129    [normalize_angle(h), s, v]
130}
131
132#[allow(clippy::float_cmp)]
133pub(crate) fn rgb_to_hsl(r: f32, g: f32, b: f32) -> [f32; 3] {
134    let min = r.min(g.min(b));
135    let max = r.max(g.max(b));
136    let l = (max + min) / 2.0;
137
138    if min == max {
139        return [0.0, 0.0, l];
140    }
141
142    let d = max - min;
143
144    let s = if l < 0.5 {
145        d / (max + min)
146    } else {
147        d / (2.0 - max - min)
148    };
149
150    let dr = (max - r) / d;
151    let dg = (max - g) / d;
152    let db = (max - b) / d;
153
154    let h = if r == max {
155        db - dg
156    } else if g == max {
157        2.0 + dr - db
158    } else {
159        4.0 + dg - dr
160    };
161
162    let h = (h * 60.0) % 360.0;
163    [normalize_angle(h), s, l]
164}
165
166pub(crate) fn rgb_to_hwb(r: f32, g: f32, b: f32) -> [f32; 3] {
167    let [hue, _, _] = rgb_to_hsl(r, g, b);
168    let white = r.min(g.min(b));
169    let black = 1.0 - r.max(g.max(b));
170    [hue, white, black]
171}
172
173#[inline]
174pub(crate) fn normalize_angle(t: f32) -> f32 {
175    ((t % 360.0) + 360.0) % 360.0
176}
177
178#[inline]
179pub(crate) fn interp_angle(a0: f32, a1: f32, t: f32) -> f32 {
180    let delta = (((a1 - a0) % 360.0) + 540.0) % 360.0 - 180.0;
181    (a0 + t * delta + 360.0) % 360.0
182}
183
184#[cfg(feature = "lab")]
185#[inline]
186pub(crate) fn interp_angle_rad(a0: f32, a1: f32, t: f32) -> f32 {
187    let delta = (((a1 - a0) % TAU) + PI_3) % TAU - PI;
188    (a0 + t * delta + TAU) % TAU
189}
190
191#[inline]
192pub(crate) fn modulo(x: f32, n: f32) -> f32 {
193    (x % n + n) % n
194}
195
196// Map t from range [a, b] to range [c, d]
197#[inline]
198pub(crate) fn remap(t: f32, a: f32, b: f32, c: f32, d: f32) -> f32 {
199    (t - a) * ((d - c) / (b - a)) + c
200}
201
202#[cfg(test)]
203mod tests {
204    use super::*;
205
206    #[test]
207    fn test_normalize_angle() {
208        let data = vec![
209            (0.0, 0.0),
210            (360.0, 0.0),
211            (720.0, 0.0),
212            (400.0, 40.0),
213            (1155.0, 75.0),
214            (-360.0, 0.0),
215            (-90.0, 270.0),
216            (-765.0, 315.0),
217        ];
218        for (x, expected) in data {
219            let c = normalize_angle(x);
220            assert_eq!(expected, c);
221        }
222    }
223
224    #[test]
225    fn test_interp_angle() {
226        let data = vec![
227            ((0.0, 360.0, 0.5), 0.0),
228            ((360.0, 90.0, 0.0), 0.0),
229            ((360.0, 90.0, 0.5), 45.0),
230            ((360.0, 90.0, 1.0), 90.0),
231        ];
232        for ((a, b, t), expected) in data {
233            let v = interp_angle(a, b, t);
234            assert_eq!(expected, v);
235        }
236    }
237}