From bf466ab1d71afafa4ab428f83167de7d14f45a78 Mon Sep 17 00:00:00 2001 From: James Hurst Date: Sat, 18 Jan 2025 23:21:34 -0500 Subject: [PATCH] feat: enforce minimum contrast ratio for reverse video cursor --- color-types/src/lib.rs | 52 +++++++++++++++++++----- wezterm-gui/src/termwindow/render/mod.rs | 26 ++++++++---- 2 files changed, 60 insertions(+), 18 deletions(-) diff --git a/color-types/src/lib.rs b/color-types/src/lib.rs index bd12a1ae3cd..efa228e0f86 100644 --- a/color-types/src/lib.rs +++ b/color-types/src/lib.rs @@ -538,16 +538,8 @@ impl SrgbaTuple { *deltae::DeltaE::new(a, b, deltae::DEMethod::DE2000).value() } - pub fn contrast_ratio(&self, other: &Self) -> f64 { - let (_, _, l_a, _) = self.to_hsla(); - let (_, _, l_b, _) = other.to_hsla(); - let a = l_a + 0.05; - let b = l_b + 0.05; - if a > b { - a / b - } else { - b / a - } + pub fn contrast_ratio(&self, other: &Self) -> f32 { + self.to_linear().contrast_ratio(&other.to_linear()) } } @@ -891,6 +883,22 @@ impl LinearRgba { self.3, ) } + + pub fn relative_luminance(&self) -> f32 { + 0.2126 * self.0 + 0.7152 * self.1 + 0.0722 * self.2 + } + + pub fn contrast_ratio(&self, other: &Self) -> f32 { + let l_a = self.relative_luminance(); + let l_b = other.relative_luminance(); + let a = l_a + 0.05; + let b = l_b + 0.05; + if a > b { + a / b + } else { + b / a + } + } } #[cfg(test)] @@ -960,4 +968,28 @@ mod tests { let grey = SrgbaTuple::from_str("rgb:f0f0/f0f0/f0f0").unwrap(); assert_eq!(grey.to_rgb_string(), "#f0f0f0"); } + + #[test] + fn linear_rgb_contrast_ratio() { + let a = LinearRgba::with_srgba(255, 0, 0, 1); + let b = LinearRgba::with_srgba(0, 255, 0, 1); + let contrast_ratio = a.contrast_ratio(&b); + assert!( + (2.91 - contrast_ratio).abs() < 0.01, + "contrast({}) == 2.91", + contrast_ratio + ); + } + + #[test] + fn srgba_contrast_ratio() { + let a = SrgbaTuple::from_str("hsl:0 100 50").unwrap(); + let b = SrgbaTuple::from_str("hsl:120 100 50").unwrap(); + let contrast_ratio = a.contrast_ratio(&b); + assert!( + (2.91 - contrast_ratio).abs() < 0.01, + "contrast({}) == 2.91", + contrast_ratio + ); + } } diff --git a/wezterm-gui/src/termwindow/render/mod.rs b/wezterm-gui/src/termwindow/render/mod.rs index 7e164459b04..c819a8515ec 100644 --- a/wezterm-gui/src/termwindow/render/mod.rs +++ b/wezterm-gui/src/termwindow/render/mod.rs @@ -484,6 +484,8 @@ impl crate::TermWindow { } pub fn compute_cell_fg_bg(&self, params: ComputeCellFgBgParams) -> ComputeCellFgBgResult { + const CURSOR_MIN_CONTRAST: f32 = 2.5; + if params.cursor.is_some() { if let Some(bg_color_mix) = self.get_intensity_if_bell_target_ringing( params.pane.expect("cursor only set if pane present"), @@ -524,12 +526,14 @@ impl crate::TermWindow { self.dead_key_status != DeadKeyStatus::None || self.leader_is_active(); if dead_key_or_leader && params.is_active_pane { - let (fg_color, bg_color) = - if self.config.force_reverse_video_cursor && params.cursor_is_default_color { - (params.bg_color, params.fg_color) - } else { - (params.cursor_fg, params.cursor_bg) - }; + let (fg_color, bg_color) = if self.config.force_reverse_video_cursor + && params.cursor_is_default_color + && params.fg_color.contrast_ratio(¶ms.bg_color) >= CURSOR_MIN_CONTRAST + { + (params.bg_color, params.fg_color) + } else { + (params.cursor_fg, params.cursor_bg) + }; let color = params .config @@ -585,7 +589,10 @@ impl crate::TermWindow { CursorShape::BlinkingBlock | CursorShape::SteadyBlock, CursorVisibility::Visible, ) => { - if self.config.force_reverse_video_cursor && params.cursor_is_default_color { + if self.config.force_reverse_video_cursor + && params.cursor_is_default_color + && params.fg_color.contrast_ratio(¶ms.bg_color) >= CURSOR_MIN_CONTRAST + { (params.bg_color, params.fg_color, params.fg_color) } else { ( @@ -604,7 +611,10 @@ impl crate::TermWindow { | CursorShape::SteadyBar, CursorVisibility::Visible, ) => { - if self.config.force_reverse_video_cursor && params.cursor_is_default_color { + if self.config.force_reverse_video_cursor + && params.cursor_is_default_color + && params.fg_color.contrast_ratio(¶ms.bg_color) >= CURSOR_MIN_CONTRAST + { (params.fg_color, params.bg_color, params.fg_color) } else { (params.fg_color, params.bg_color, params.cursor_bg)