Invert fixed color cursor if it's close to cell bg

This should reduce the number of times people with fixed cursor colors
run into troubles when existing text is already colored.

Using just the background color as a metric instead of both background
and foreground color should ensure that the cursor still has a clear
shape, since just changing the foreground color for a cursor might be
difficult to see. Always inverting the entire cursor instead of keeping
the fixed foreground color is important to make sure the contrast isn't
messed up.

Fixes #4016.
This commit is contained in:
Christian Duerr 2020-07-26 01:04:39 +00:00 committed by GitHub
parent 9a4d847d89
commit bedf5f3004
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 83 additions and 3 deletions

View File

@ -10,6 +10,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Secondary device attributes escape (`CSI > 0 c`)
### Changed
- Cursors are now inverted when their fixed color is similar to the cell's background
## 0.5.0-dev
### Packaging

View File

@ -25,6 +25,42 @@ pub struct Rgb {
pub b: u8,
}
impl Rgb {
/// Implementation of W3C's luminance algorithm:
/// https://www.w3.org/TR/WCAG20/#relativeluminancedef
fn luminance(self) -> f64 {
let channel_luminance = |channel| {
let channel = channel as f64 / 255.;
if channel <= 0.03928 {
channel / 12.92
} else {
f64::powf((channel + 0.055) / 1.055, 2.4)
}
};
let r_luminance = channel_luminance(self.r);
let g_luminance = channel_luminance(self.g);
let b_luminance = channel_luminance(self.b);
0.2126 * r_luminance + 0.7152 * g_luminance + 0.0722 * b_luminance
}
/// Implementation of W3C's contrast algorithm:
/// https://www.w3.org/TR/WCAG20/#contrast-ratiodef
pub fn contrast(self, other: Rgb) -> f64 {
let self_luminance = self.luminance();
let other_luminance = other.luminance();
let (darker, lighter) = if self_luminance > other_luminance {
(other_luminance, self_luminance)
} else {
(self_luminance, other_luminance)
};
(lighter + 0.05) / (darker + 0.05)
}
}
// A multiply function for Rgb, as the default dim is just *2/3.
impl Mul<f32> for Rgb {
type Output = Rgb;
@ -370,3 +406,28 @@ impl IndexMut<u8> for List {
&mut self.0[idx as usize]
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::f64::EPSILON;
#[test]
fn contrast() {
let rgb1 = Rgb { r: 0xff, g: 0xff, b: 0xff };
let rgb2 = Rgb { r: 0x00, g: 0x00, b: 0x00 };
assert!((rgb1.contrast(rgb2) - 21.).abs() < EPSILON);
let rgb1 = Rgb { r: 0xff, g: 0xff, b: 0xff };
assert!((rgb1.contrast(rgb1) - 1.).abs() < EPSILON);
let rgb1 = Rgb { r: 0xff, g: 0x00, b: 0xff };
let rgb2 = Rgb { r: 0x00, g: 0xff, b: 0x00 };
assert!((rgb1.contrast(rgb2) - 2.285_543_608_124_253_3).abs() < EPSILON);
let rgb1 = Rgb { r: 0x12, g: 0x34, b: 0x56 };
let rgb2 = Rgb { r: 0xfe, g: 0xdc, b: 0xba };
assert!((rgb1.contrast(rgb2) - 9.786_558_997_257_74).abs() < EPSILON);
}
}

View File

@ -31,6 +31,9 @@ mod search;
/// Max size of the window title stack.
const TITLE_STACK_MAX_DEPTH: usize = 4096;
/// Minimum contrast between a fixed cursor color and the cell's background.
const MIN_CURSOR_CONTRAST: f64 = 1.5;
/// Maximum number of linewraps followed outside of the viewport during search highlighting.
const MAX_SEARCH_LINES: usize = 100;
@ -389,13 +392,19 @@ impl<'a, C> Iterator for RenderableCellsIter<'a, C> {
if self.cursor.point.line == self.inner.line()
&& self.cursor.point.col == self.inner.column()
{
// Handle cell below cursor.
if self.cursor.rendered {
// Handle cell below cursor.
let cell = self.inner.next()?;
let mut cell = RenderableCell::new(self, cell);
if self.cursor.key.style == CursorStyle::Block {
cell.fg = self.cursor.text_color.color(cell.fg, cell.bg);
// Invert cursor if static background is close to the cell's background.
match self.cursor.cursor_color {
CellRgb::Rgb(col) if col.contrast(cell.bg) >= MIN_CURSOR_CONTRAST => {
cell.fg = self.cursor.text_color.color(cell.fg, cell.bg);
},
_ => cell.fg = cell.bg,
}
}
return Some(cell);
@ -412,7 +421,13 @@ impl<'a, C> Iterator for RenderableCellsIter<'a, C> {
let mut cell = RenderableCell::new(self, cell);
cell.inner = RenderableCellContent::Cursor(self.cursor.key);
cell.fg = self.cursor.cursor_color.color(cell.fg, cell.bg);
// Only apply static color if it isn't close to the cell's current background.
if let CellRgb::Rgb(color) = self.cursor.cursor_color {
if color.contrast(cell.bg) >= MIN_CURSOR_CONTRAST {
cell.fg = self.cursor.cursor_color.color(cell.fg, cell.bg);
}
}
return Some(cell);
}