From 40fc19a1a21d646539e9263a577df787fc2f2679 Mon Sep 17 00:00:00 2001 From: link2xt Date: Fri, 12 Feb 2021 21:36:04 +0300 Subject: [PATCH] Implement Consistent Color Generation (XEP-0392) This should make colors used by Delta Chat for emails similar to colors used by XMPP clients implementing the same specification[1], such as Conversations and Snikket. Previously Delta Chat used only hardcoded 16 colors, so this change should also increase the number of colors and make it easier to distinguish different contacts. [1] https://xmpp.org/extensions/xep-0392.html --- Cargo.lock | 12 ++++++++++-- Cargo.toml | 2 ++ src/chat.rs | 9 +++++---- src/color.rs | 46 ++++++++++++++++++++++++++++++++++++++++++++++ src/contact.rs | 7 +++---- src/dc_tools.rs | 25 ------------------------- src/lib.rs | 1 + 7 files changed, 67 insertions(+), 35 deletions(-) create mode 100644 src/color.rs diff --git a/Cargo.lock b/Cargo.lock index 224a20b832..9272a44eb5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1112,10 +1112,12 @@ dependencies = [ "rand 0.7.3", "regex", "rusqlite", + "rust-hsluv", "rustyline", "sanitize-filename", "serde", "serde_json", + "sha-1", "sha2", "smallvec", "stop-token", @@ -3019,6 +3021,12 @@ dependencies = [ "crossbeam-utils 0.8.1", ] +[[package]] +name = "rust-hsluv" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efe2374f2385cdd8755a446f80b2a646de603c9d8539ca38734879b5c71e378b" + [[package]] name = "rustc-demangle" version = "0.1.18" @@ -3224,9 +3232,9 @@ dependencies = [ [[package]] name = "sha-1" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce3cdf1b5e620a498ee6f2a171885ac7e22f0e12089ec4b3d22b84921792507c" +checksum = "f4b312c3731e3fe78a185e6b9b911a7aa715b8e31cce117975219aab2acf285d" dependencies = [ "block-buffer", "cfg-if 1.0.0", diff --git a/Cargo.toml b/Cargo.toml index 0e0a95d488..ef7234c62e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ deltachat_derive = { path = "./deltachat_derive" } libc = "0.2.51" pgp = { version = "0.7.0", default-features = false } hex = "0.4.0" +sha-1 = "0.9.3" sha2 = "0.9.0" rand = "0.7.0" smallvec = "1.0.0" @@ -64,6 +65,7 @@ url = "2.1.1" async-std-resolver = "0.19.5" async-tar = "0.3.0" uuid = { version = "0.8", features = ["serde", "v4"] } +rust-hsluv = "0.1.4" pretty_env_logger = { version = "0.4.0", optional = true } log = {version = "0.4.8", optional = true } diff --git a/src/chat.rs b/src/chat.rs index 0dc729180f..83963aa83a 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -15,6 +15,7 @@ use serde::{Deserialize, Serialize}; use crate::aheader::EncryptPreference; use crate::blob::{BlobError, BlobObject}; use crate::chatlist::dc_get_archived_cnt; +use crate::color::str_to_color; use crate::config::Config; use crate::constants::{ Blocked, Chattype, ShowEmails, Viewtype, DC_CHAT_ID_ALLDONE_HINT, DC_CHAT_ID_ARCHIVED_LINK, @@ -26,8 +27,8 @@ use crate::contact::{addr_cmp, Contact, Origin, VerifiedStatus}; use crate::context::Context; use crate::dc_tools::{ dc_create_id, dc_create_outgoing_rfc724_mid, dc_create_smeared_timestamp, - dc_create_smeared_timestamps, dc_get_abs_path, dc_gm2local_offset, dc_str_to_color, - improve_single_line_input, time, IsNoneOrEmpty, + dc_create_smeared_timestamps, dc_get_abs_path, dc_gm2local_offset, improve_single_line_input, + time, IsNoneOrEmpty, }; use crate::ephemeral::{delete_expired_messages, schedule_ephemeral_task, Timer as EphemeralTimer}; use crate::events::EventType; @@ -891,7 +892,7 @@ impl Chat { } } } else { - color = dc_str_to_color(&self.name); + color = str_to_color(&self.name); } color @@ -3081,7 +3082,7 @@ mod tests { "param": "", "gossiped_timestamp": 0, "is_sending_locations": false, - "color": 15895624, + "color": 35391, "profile_image": "", "draft": "", "is_muted": false, diff --git a/src/color.rs b/src/color.rs new file mode 100644 index 0000000000..457b4c25d5 --- /dev/null +++ b/src/color.rs @@ -0,0 +1,46 @@ +//! Implementation of Consistent Color Generation +//! +//! Consistent Color Generation is defined in XEP-0392. +//! +//! Color Vision Deficiency correction is not implemented as Delta Chat does not offer +//! corresponding settings. +use hsluv::hsluv_to_rgb; +use sha1::{Digest, Sha1}; + +/// Converts an identifier to Hue angle. +fn str_to_angle(s: impl AsRef) -> f64 { + let bytes = s.as_ref().as_bytes(); + let result = Sha1::digest(bytes); + let checksum: u16 = result.get(0).map_or(0, |&x| u16::from(x)) + + 256 * result.get(1).map_or(0, |&x| u16::from(x)); + f64::from(checksum) / 65536.0 * 360.0 +} + +/// Converts an identifier to RGB color. +/// +/// Returns a 24-bit number with 8 least significant bits corresponding to the blue color and 8 +/// most significant bits corresponding to the red color. +/// +/// Saturation is set to maximum (100.0) to make colors distinguishable, and lightness is set to +/// half (50.0) to make colors suitable both for light and dark theme. +pub(crate) fn str_to_color(s: impl AsRef) -> u32 { + let (r, g, b) = hsluv_to_rgb((str_to_angle(s), 100.0, 50.0)); + 65536 * (r * 256.0) as u32 + 256 * (g * 256.0) as u32 + (b * 256.0) as u32 +} + +#[cfg(test)] +mod tests { + use super::*; + + #[allow(clippy::float_cmp)] + #[test] + fn test_str_to_angle() { + // Test against test vectors from + // https://xmpp.org/extensions/xep-0392.html#testvectors-fullrange-no-cvd + assert!((str_to_angle("Romeo") - 327.255249).abs() < 1e-6); + assert!((str_to_angle("juliet@capulet.lit") - 209.410400).abs() < 1e-6); + assert!((str_to_angle("😺") - 331.199341).abs() < 1e-6); + assert!((str_to_angle("council") - 359.994507).abs() < 1e-6); + assert!((str_to_angle("Board") - 171.430664).abs() < 1e-6); + } +} diff --git a/src/contact.rs b/src/contact.rs index 7646848983..91520fb8a2 100644 --- a/src/contact.rs +++ b/src/contact.rs @@ -9,15 +9,14 @@ use regex::Regex; use crate::aheader::EncryptPreference; use crate::chat::ChatId; +use crate::color::str_to_color; use crate::config::Config; use crate::constants::{ Chattype, DC_CHAT_ID_DEADDROP, DC_CONTACT_ID_DEVICE, DC_CONTACT_ID_DEVICE_ADDR, DC_CONTACT_ID_LAST_SPECIAL, DC_CONTACT_ID_SELF, DC_GCL_ADD_SELF, DC_GCL_VERIFIED_ONLY, }; use crate::context::Context; -use crate::dc_tools::{ - dc_get_abs_path, dc_str_to_color, improve_single_line_input, listflags_has, EmailAddress, -}; +use crate::dc_tools::{dc_get_abs_path, improve_single_line_input, listflags_has, EmailAddress}; use crate::events::EventType; use crate::key::{DcKey, SignedPublicKey}; use crate::login_param::LoginParam; @@ -947,7 +946,7 @@ impl Contact { /// and can be used for an fallback avatar with white initials /// as well as for headlines in bubbles of group chats. pub fn get_color(&self) -> u32 { - dc_str_to_color(&self.addr) + str_to_color(&self.addr) } /// Gets the contact's status. diff --git a/src/dc_tools.rs b/src/dc_tools.rs index ce182935ae..d1fb386709 100644 --- a/src/dc_tools.rs +++ b/src/dc_tools.rs @@ -48,31 +48,6 @@ pub(crate) fn dc_truncate(buf: &str, approx_chars: usize) -> Cow { } } -/// the colors must fulfill some criterions as: -/// - contrast to black and to white -/// - work as a text-color -/// - being noticeable on a typical map -/// - harmonize together while being different enough -/// (therefore, we cannot just use random rgb colors :) -const COLORS: [u32; 16] = [ - 0xe5_65_55, 0xf2_8c_48, 0x8e_85_ee, 0x76_c8_4d, 0x5b_b6_cc, 0x54_9c_dd, 0xd2_5c_99, 0xb3_78_00, - 0xf2_30_30, 0x39_b2_49, 0xbb_24_3b, 0x96_40_78, 0x66_87_4f, 0x30_8a_b9, 0x12_7e_d0, 0xbe_45_0c, -]; - -#[allow(clippy::indexing_slicing)] -pub(crate) fn dc_str_to_color(s: impl AsRef) -> u32 { - let str_lower = s.as_ref().to_lowercase(); - let mut checksum = 0; - let bytes = str_lower.as_bytes(); - for (i, byte) in bytes.iter().enumerate() { - checksum += (i + 1) * *byte as usize; - checksum %= 0x00ff_ffff; - } - let color_index = checksum % COLORS.len(); - - COLORS[color_index] -} - /* ****************************************************************************** * date/time tools ******************************************************************************/ diff --git a/src/lib.rs b/src/lib.rs index 132873637b..9e9793b9fe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -77,6 +77,7 @@ pub mod stock_str; mod token; #[macro_use] mod dehtml; +mod color; pub mod html; pub mod plaintext;