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
47pub(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#[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}