mirror of
https://github.com/chatmail/core.git
synced 2026-04-17 13:36:30 +03:00
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
This commit is contained in:
12
Cargo.lock
generated
12
Cargo.lock
generated
@@ -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",
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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,
|
||||
|
||||
46
src/color.rs
Normal file
46
src/color.rs
Normal file
@@ -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<str>) -> 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<str>) -> 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);
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -48,31 +48,6 @@ pub(crate) fn dc_truncate(buf: &str, approx_chars: usize) -> Cow<str> {
|
||||
}
|
||||
}
|
||||
|
||||
/// 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<str>) -> 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
|
||||
******************************************************************************/
|
||||
|
||||
@@ -77,6 +77,7 @@ pub mod stock_str;
|
||||
mod token;
|
||||
#[macro_use]
|
||||
mod dehtml;
|
||||
mod color;
|
||||
pub mod html;
|
||||
pub mod plaintext;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user