From 45a9d54801fa30488f2a721143f8907728aee3e1 Mon Sep 17 00:00:00 2001 From: Alexandre Bury Date: Wed, 8 Jan 2020 16:49:42 -0800 Subject: [PATCH] Ncurses: fix bad grayscale projection for very dark or very bright colors --- src/backend/curses/mod.rs | 19 ++++++++++++++++--- src/theme/color.rs | 24 ++++++++++++++++++++++-- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/src/backend/curses/mod.rs b/src/backend/curses/mod.rs index 55b95b8..0845252 100644 --- a/src/backend/curses/mod.rs +++ b/src/backend/curses/mod.rs @@ -16,6 +16,7 @@ pub mod pan; // Use AHash instead of the slower SipHash type HashMap = std::collections::HashMap; +/// Split a i32 into individual bytes, little endian (least significant byte first). fn split_i32(code: i32) -> Vec { (0..4).map(|i| ((code >> (8 * i)) & 0xFF) as u8).collect() } @@ -71,6 +72,10 @@ fn find_closest_pair(pair: ColorPair, max_colors: i16) -> (i16, i16) { ) } +/// Finds the closest index in the 256-color palette. +/// +/// If `max_colors` is less than 256 (like 8 or 16), the color will be +/// downgraded to the closest one available. fn find_closest(color: Color, max_colors: i16) -> i16 { match color { Color::TerminalDefault => -1, @@ -92,11 +97,18 @@ fn find_closest(color: Color, max_colors: i16) -> i16 { Color::Light(BaseColor::White) => 15 % max_colors, Color::Rgb(r, g, b) if max_colors >= 256 => { // If r = g = b, it may be a grayscale value! - if r == g && g == b && r != 0 && r < 250 { - // Grayscale + // Grayscale colors have a bit higher resolution than the rest of + // the palette, so if we can use it we should! + // + // r=g=b < 8 should go to pure black instead. + // r=g=b >= 247 should go to pure white. + + // TODO: project almost-gray colors as well? + if r == g && g == b && r >= 8 && r < 247 { + // The grayscale palette says the colors 232+n are: // (r = g = b) = 8 + 10 * n + // With 0 <= n <= 23. This gives: // (r - 8) / 10 = n - // let n = (r - 8) / 10; i16::from(232 + n) } else { @@ -108,6 +120,7 @@ fn find_closest(color: Color, max_colors: i16) -> i16 { } } Color::Rgb(r, g, b) => { + // Have to hack it down to 8 colors. let r = if r > 127 { 1 } else { 0 }; let g = if g > 127 { 1 } else { 0 }; let b = if b > 127 { 1 } else { 0 }; diff --git a/src/theme/color.rs b/src/theme/color.rs index 9661605..bf78d3f 100644 --- a/src/theme/color.rs +++ b/src/theme/color.rs @@ -71,22 +71,30 @@ pub enum Color { /// One of the 8 base colors. /// + /// These colors should work on any terminal. + /// /// Note: the actual color used depends on the terminal configuration. Dark(BaseColor), /// Lighter version of a base color. /// + /// The native linux TTY usually doesn't support these colors, but almost + /// all terminal emulators should. + /// /// Note: the actual color used depends on the terminal configuration. Light(BaseColor), /// True-color, 24-bit. + /// + /// On terminals that don't support this, the color will be "downgraded" + /// to the closest one available. Rgb(u8, u8, u8), - /// Low-resolution + /// Low-resolution color. /// /// Each value should be `<= 5` (you'll get panics otherwise). /// - /// These 216 possible colors are part of the default color palette. + /// These 216 possible colors are part of the default color palette (256 colors). RgbLowRes(u8, u8, u8), } @@ -126,6 +134,18 @@ impl Color { } } + /// Creates a `Color::RgbLowRes` from the given values for red, green and + /// blue. + /// + /// Returns `None` if any of the values exceeds 5. + pub fn low_res(r: u8, g: u8, b: u8) -> Option { + if r <= 5 && g <= 5 && b <= 5 { + Some(Color::RgbLowRes(r, g, b)) + } else { + None + } + } + /// Parse a string into a color. /// /// Examples: