1use colorutils_rs::{Oklch, Rgb, TransferFunction};
7use sha1::{Digest, Sha1};
8
9#[expect(clippy::arithmetic_side_effects)]
11fn str_to_angle(s: &str) -> f32 {
12 let bytes = s.as_bytes();
13 let result = Sha1::digest(bytes);
14 let checksum: u16 = result.first().map_or(0, |&x| u16::from(x))
15 + 256 * result.get(1).map_or(0, |&x| u16::from(x));
16 f32::from(checksum) / 65536.0 * 360.0
17}
18
19#[expect(clippy::arithmetic_side_effects)]
24fn rgb_to_u32(rgb: Rgb<u8>) -> u32 {
25 65536 * u32::from(rgb.r) + 256 * u32::from(rgb.g) + u32::from(rgb.b)
26}
27
28pub fn str_to_color(s: &str) -> u32 {
32 let lightness = 0.5;
33 let chroma = 0.23;
34 let angle = str_to_angle(s);
35 let oklch = Oklch::new(lightness, chroma, angle);
36 let rgb = oklch.to_rgb(TransferFunction::Srgb);
37
38 rgb_to_u32(rgb)
39}
40
41pub fn color_int_to_hex_string(color: u32) -> String {
43 format!("{color:#08x}").replace("0x", "#")
44}
45
46#[cfg(test)]
47mod tests {
48 use super::*;
49
50 #[test]
51 #[allow(clippy::excessive_precision)]
52 fn test_str_to_angle() {
53 assert!((str_to_angle("Romeo") - 327.255249).abs() < 1e-6);
56 assert!((str_to_angle("juliet@capulet.lit") - 209.410400).abs() < 1e-6);
57 assert!((str_to_angle("😺") - 331.199341).abs() < 1e-6);
58 assert!((str_to_angle("council") - 359.994507).abs() < 1e-6);
59 assert!((str_to_angle("Board") - 171.430664).abs() < 1e-6);
60 }
61
62 #[test]
63 fn test_rgb_to_u32() {
64 assert_eq!(rgb_to_u32(Rgb::new(0, 0, 0)), 0);
65 assert_eq!(rgb_to_u32(Rgb::new(0xff, 0xff, 0xff)), 0xffffff);
66 assert_eq!(rgb_to_u32(Rgb::new(0, 0, 0xff)), 0x0000ff);
67 assert_eq!(rgb_to_u32(Rgb::new(0, 0xff, 0)), 0x00ff00);
68 assert_eq!(rgb_to_u32(Rgb::new(0xff, 0, 0)), 0xff0000);
69 assert_eq!(rgb_to_u32(Rgb::new(0xff, 0x80, 0)), 0xff8000);
70 }
71}