1use sha1::{Digest, Sha1};
29
30use crate::jid::{BareJid, ResourceRef};
31
32use core::{cmp::Ordering, convert::Infallible, f64::consts::PI, str::FromStr};
33
34const EPSILON: f64 = 0.0088564516;
35const KAPPA: f64 = 903.2962962;
36const M: [[f64; 3]; 3] = [
37 [3.240969941904521, -1.537383177570093, -0.498610760293],
38 [-0.96924363628087, 1.87596750150772, 0.041555057407175],
39 [0.055630079696993, -0.20397695888897, 1.056971514242878],
40];
41
42const REF_Y: f64 = 1.0;
43const REF_U: f64 = 0.19783000664283;
44const REF_V: f64 = 0.46831999493879;
45
46pub struct Rgb {
48 pub red: u8,
50 pub green: u8,
52 pub blue: u8,
54}
55
56impl Rgb {
57 fn new(red: u8, green: u8, blue: u8) -> Self {
58 Self { red, green, blue }
59 }
60
61 pub fn from_resource(nick: &ResourceRef) -> Self {
63 nick.as_str().parse().unwrap()
65 }
66
67 pub fn from_barejid(jid: &BareJid) -> Self {
69 jid.as_str().parse().unwrap()
71 }
72
73 pub fn to_hex(&self) -> String {
75 format!("#{:02x}{:02x}{:02x}", self.red, self.green, self.blue)
76 }
77}
78
79impl FromStr for Rgb {
80 type Err = Infallible;
81
82 fn from_str(input: &str) -> Result<Self, Self::Err> {
83 let hash = Sha1::digest(input.as_bytes());
84
85 let mut hash_bytes = [0u8; 2];
87 hash_bytes.copy_from_slice(&hash[..2]);
88
89 let hash_value = u16::from_le_bytes(hash_bytes);
91 let hue_angle = hash_value as f64 / 65536.0 * 360.0;
92
93 let (r, g, b) = hsluv_to_rgb((hue_angle, 100.0, 50.0));
94 Ok(Self::new(r, g, b))
95 }
96}
97
98fn hsluv_to_rgb(hsl: (f64, f64, f64)) -> (u8, u8, u8) {
100 let rgb = xyz_to_rgb(luv_to_xyz(lch_to_luv(hsluv_to_lch(hsl))));
102
103 (clamp(rgb.0), clamp(rgb.1), clamp(rgb.2))
105}
106
107fn clamp(v: f64) -> u8 {
108 let mut rounded = (v * 1000.0).round() / 1000.0;
109 if rounded < 0.0 {
110 rounded = 0.0;
111 }
112 if rounded > 1.0 {
113 rounded = 1.0;
114 }
115 (rounded * 255.0).round() as u8
116}
117
118fn hsluv_to_lch(hsl: (f64, f64, f64)) -> (f64, f64, f64) {
119 let (h, s, l) = hsl;
120 match l {
121 l if l > 99.9999999 => (100.0, 0.0, h),
122 l if l < 0.00000001 => (0.0, 0.0, h),
123 _ => {
124 let mx = max_chroma_for(l, h);
125 let c = mx / 100.0 * s;
126 (l, c, h)
127 }
128 }
129}
130
131fn max_chroma_for(l: f64, h: f64) -> f64 {
132 let hrad = h / 360.0 * PI * 2.0;
133
134 let mut lengths: Vec<f64> = get_bounds(l)
135 .iter()
136 .map(|line| length_of_ray_until_intersect(hrad, line))
137 .filter(|length| length > &0.0)
138 .collect();
139
140 lengths.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
141 lengths[0]
142}
143
144fn length_of_ray_until_intersect(theta: f64, line: &(f64, f64)) -> f64 {
145 let (m1, b1) = *line;
146 let length = b1 / (theta.sin() - m1 * theta.cos());
147 if length < 0.0 {
148 -0.0001
149 } else {
150 length
151 }
152}
153
154fn luv_to_xyz(luv: (f64, f64, f64)) -> (f64, f64, f64) {
155 let (l, u, v) = luv;
156
157 if l == 0.0 {
158 return (0.0, 0.0, 0.0);
159 }
160
161 let var_y = f_inv(l);
162 let var_u = u / (13.0 * l) + REF_U;
163 let var_v = v / (13.0 * l) + REF_V;
164
165 let y = var_y * REF_Y;
166 let x = 0.0 - (9.0 * y * var_u) / ((var_u - 4.0) * var_v - var_u * var_v);
167 let z = (9.0 * y - (15.0 * var_v * y) - (var_v * x)) / (3.0 * var_v);
168
169 (x, y, z)
170}
171
172fn lch_to_luv(lch: (f64, f64, f64)) -> (f64, f64, f64) {
173 let (l, c, h) = lch;
174 let hrad = degrees_to_radians(h);
175 let u = hrad.cos() * c;
176 let v = hrad.sin() * c;
177
178 (l, u, v)
179}
180
181fn xyz_to_rgb(xyz: (f64, f64, f64)) -> (f64, f64, f64) {
182 let xyz_vec = vec![xyz.0, xyz.1, xyz.2];
183 let abc: Vec<f64> = M
184 .iter()
185 .map(|i| from_linear(dot_product(&i.to_vec(), &xyz_vec)))
186 .collect();
187 (abc[0], abc[1], abc[2])
188}
189
190fn dot_product(a: &[f64], b: &[f64]) -> f64 {
191 a.iter().zip(b.iter()).map(|(i, j)| i * j).sum()
192}
193
194fn get_bounds(l: f64) -> Vec<(f64, f64)> {
195 let sub1 = ((l + 16.0).powi(3)) / 1560896.0;
196 let sub2 = match sub1 {
197 s if s > EPSILON => s,
198 _ => l / KAPPA,
199 };
200
201 let mut bounds = Vec::new();
202
203 for ms in &M {
204 let (m1, m2, m3) = (ms[0], ms[1], ms[2]);
205 for t in 0..2 {
206 let top1 = (284517.0 * m1 - 94839.0 * m3) * sub2;
207 let top2 = (838422.0 * m3 + 769860.0 * m2 + 731718.0 * m1) * l * sub2
208 - 769860.0 * f64::from(t) * l;
209 let bottom = (632260.0 * m3 - 126452.0 * m2) * sub2 + 126452.0 * f64::from(t);
210
211 bounds.push((top1 / bottom, top2 / bottom));
212 }
213 }
214 bounds
215}
216
217fn f_inv(t: f64) -> f64 {
218 if t > 8.0 {
219 REF_Y * ((t + 16.0) / 116.0).powf(3.0)
220 } else {
221 REF_Y * t / KAPPA
222 }
223}
224
225fn degrees_to_radians(deg: f64) -> f64 {
226 deg * PI / 180.0
227}
228
229fn from_linear(c: f64) -> f64 {
230 if c <= 0.0031308 {
231 12.92 * c
232 } else {
233 1.055 * (c.powf(1.0 / 2.4)) - 0.055
234 }
235}
236
237#[cfg(test)]
238mod tests {
239 use super::{clamp, Rgb};
240
241 #[test]
242 fn test_13_1() {
243 struct TestVector<'a> {
244 pub text: &'a str,
245 #[allow(dead_code)]
246 pub hextext: &'a str,
247 #[allow(dead_code)]
248 pub angle: f64,
249 #[allow(dead_code)]
250 pub hue: f64,
251 pub r: f64,
252 pub g: f64,
253 pub b: f64,
254 }
255
256 impl<'a> TestVector<'a> {
257 const fn new(
258 text: &'a str,
259 hextext: &'a str,
260 angle: f64,
261 hue: f64,
262 r: f64,
263 g: f64,
264 b: f64,
265 ) -> TestVector<'a> {
266 TestVector {
267 text,
268 hextext,
269 angle,
270 hue,
271 r,
272 g,
273 b,
274 }
275 }
276 }
277
278 const VECTORS: &'static [&'static TestVector<'static>] = &[
279 &TestVector::new(
280 "Romeo",
281 "526f6d656f",
282 327.255249,
283 327.255249,
284 0.865,
285 0.000,
286 0.686,
287 ),
288 &TestVector::new(
289 "juliet@capulet.lit",
290 "6a756c69657440636170756c65742e6c6974",
291 209.410400,
292 209.410400,
293 0.000,
294 0.515,
295 0.573,
296 ),
297 &TestVector::new(
298 "😺", "f09f98ba", 331.199341, 331.199341, 0.872, 0.000, 0.659,
299 ),
300 &TestVector::new(
301 "council",
302 "636f756e63696c",
303 359.994507,
304 359.994507,
305 0.918,
306 0.000,
307 0.394,
308 ),
309 &TestVector::new(
310 "Board",
311 "426f617264",
312 171.430664,
313 171.430664,
314 0.000,
315 0.527,
316 0.457,
317 ),
318 ];
319
320 for vector in VECTORS {
321 let rgb: Rgb = vector.text.parse().unwrap();
322 assert_eq!(rgb.red, clamp(vector.r));
323 assert_eq!(rgb.green, clamp(vector.g));
324 assert_eq!(rgb.blue, clamp(vector.b));
325 }
326 }
327}