deltachat/
color.rs

1//! Implementation of Consistent Color Generation.
2//!
3//! Consistent Color Generation is defined in XEP-0392.
4//!
5//! Color Vision Deficiency correction is not implemented as Delta Chat does not offer
6//! corresponding settings.
7use hsluv::hsluv_to_rgb;
8use sha1::{Digest, Sha1};
9
10/// Converts an identifier to Hue angle.
11fn str_to_angle(s: &str) -> f64 {
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    f64::from(checksum) / 65536.0 * 360.0
17}
18
19/// Converts RGB tuple to a 24-bit number.
20///
21/// Returns a 24-bit number with 8 least significant bits corresponding to the blue color and 8
22/// most significant bits corresponding to the red color.
23fn rgb_to_u32((r, g, b): (f64, f64, f64)) -> u32 {
24    let r = ((r * 256.0) as u32).min(255);
25    let g = ((g * 256.0) as u32).min(255);
26    let b = ((b * 256.0) as u32).min(255);
27    65536 * r + 256 * g + b
28}
29
30/// Converts an identifier to RGB color.
31///
32/// Saturation is set to maximum (100.0) to make colors distinguishable, and lightness is set to
33/// half (50.0) to make colors suitable both for light and dark theme.
34pub fn str_to_color(s: &str) -> u32 {
35    rgb_to_u32(hsluv_to_rgb((str_to_angle(s), 100.0, 50.0)))
36}
37
38/// Returns color as a "#RRGGBB" `String` where R, G, B are hex digits.
39pub fn color_int_to_hex_string(color: u32) -> String {
40    format!("{color:#08x}").replace("0x", "#")
41}
42
43#[cfg(test)]
44mod tests {
45    use super::*;
46
47    #[test]
48    fn test_str_to_angle() {
49        // Test against test vectors from
50        // <https://xmpp.org/extensions/xep-0392.html#testvectors-fullrange-no-cvd>
51        assert!((str_to_angle("Romeo") - 327.255249).abs() < 1e-6);
52        assert!((str_to_angle("juliet@capulet.lit") - 209.410400).abs() < 1e-6);
53        assert!((str_to_angle("😺") - 331.199341).abs() < 1e-6);
54        assert!((str_to_angle("council") - 359.994507).abs() < 1e-6);
55        assert!((str_to_angle("Board") - 171.430664).abs() < 1e-6);
56    }
57
58    #[test]
59    fn test_rgb_to_u32() {
60        assert_eq!(rgb_to_u32((0.0, 0.0, 0.0)), 0);
61        assert_eq!(rgb_to_u32((1.0, 1.0, 1.0)), 0xffffff);
62        assert_eq!(rgb_to_u32((0.0, 0.0, 1.0)), 0x0000ff);
63        assert_eq!(rgb_to_u32((0.0, 1.0, 0.0)), 0x00ff00);
64        assert_eq!(rgb_to_u32((1.0, 0.0, 0.0)), 0xff0000);
65        assert_eq!(rgb_to_u32((1.0, 0.5, 0.0)), 0xff8000);
66    }
67}