Ncurses: better color approximation

On terminals with only 8 colors (like with `TERM=screen`)
This commit is contained in:
Alexandre Bury 2018-04-11 22:08:21 -07:00
parent ff9f669d73
commit 3731b7375d
3 changed files with 63 additions and 34 deletions

View File

@ -5,7 +5,7 @@
use event::{Event, Key}; use event::{Event, Key};
use std::collections::HashMap; use std::collections::HashMap;
use theme::{BaseColor, Color}; use theme::{BaseColor, Color, ColorPair};
#[cfg(feature = "ncurses")] #[cfg(feature = "ncurses")]
pub mod n; pub mod n;
@ -61,7 +61,14 @@ where
} }
} }
fn find_closest(color: &Color) -> i16 { fn find_closest_pair(pair: &ColorPair, max_colors: i16) -> (i16, i16) {
(
find_closest(&pair.front,max_colors),
find_closest(&pair.back, max_colors),
)
}
fn find_closest(color: &Color, max_colors: i16) -> i16 {
match *color { match *color {
Color::TerminalDefault => -1, Color::TerminalDefault => -1,
Color::Dark(BaseColor::Black) => 0, Color::Dark(BaseColor::Black) => 0,
@ -72,29 +79,45 @@ fn find_closest(color: &Color) -> i16 {
Color::Dark(BaseColor::Magenta) => 5, Color::Dark(BaseColor::Magenta) => 5,
Color::Dark(BaseColor::Cyan) => 6, Color::Dark(BaseColor::Cyan) => 6,
Color::Dark(BaseColor::White) => 7, Color::Dark(BaseColor::White) => 7,
Color::Light(BaseColor::Black) => 8, Color::Light(BaseColor::Black) => 8 % max_colors,
Color::Light(BaseColor::Red) => 9, Color::Light(BaseColor::Red) => 9 % max_colors,
Color::Light(BaseColor::Green) => 10, Color::Light(BaseColor::Green) => 10 % max_colors,
Color::Light(BaseColor::Yellow) => 11, Color::Light(BaseColor::Yellow) => 11 % max_colors,
Color::Light(BaseColor::Blue) => 12, Color::Light(BaseColor::Blue) => 12 % max_colors,
Color::Light(BaseColor::Magenta) => 13, Color::Light(BaseColor::Magenta) => 13 % max_colors,
Color::Light(BaseColor::Cyan) => 14, Color::Light(BaseColor::Cyan) => 14 % max_colors,
Color::Light(BaseColor::White) => 15, Color::Light(BaseColor::White) => 15 % max_colors,
Color::Rgb(r, g, b) => { Color::Rgb(r, g, b) if max_colors >= 256 => {
// If r = g = b, it may be a grayscale value! // If r = g = b, it may be a grayscale value!
if r == g && g == b && r != 0 && r < 250 { if r == g && g == b && r != 0 && r < 250 {
// Grayscale
// (r = g = b) = 8 + 10 * n // (r = g = b) = 8 + 10 * n
// (r - 8) / 10 = n // (r - 8) / 10 = n
// //
let n = (r - 8) / 10; let n = (r - 8) / 10;
(232 + n) as i16 (232 + n) as i16
} else { } else {
// Generic RGB
let r = 6 * u16::from(r) / 256; let r = 6 * u16::from(r) / 256;
let g = 6 * u16::from(g) / 256; let g = 6 * u16::from(g) / 256;
let b = 6 * u16::from(b) / 256; let b = 6 * u16::from(b) / 256;
(16 + 36 * r + 6 * g + b) as i16 (16 + 36 * r + 6 * g + b) as i16
} }
} }
Color::RgbLowRes(r, g, b) => i16::from(16 + 36 * r + 6 * g + b), Color::Rgb(r, g, b) => {
let r = if r > 127 { 1 } else { 0 };
let g = if g > 127 { 1 } else { 0 };
let b = if b > 127 { 1 } else { 0 };
(r + 2 * g + 4 * b) as i16
}
Color::RgbLowRes(r, g, b) if max_colors >= 256 => {
i16::from(16 + 36 * r + 6 * g + b)
}
Color::RgbLowRes(r, g, b) => {
let r = if r > 2 { 1 } else { 0 };
let g = if g > 2 { 1 } else { 0 };
let b = if b > 2 { 1 } else { 0 };
(r + 2 * g + 4 * b) as i16
}
} }
} }

View File

@ -1,7 +1,7 @@
extern crate ncurses; extern crate ncurses;
use self::ncurses::mmask_t; use self::ncurses::mmask_t;
use self::super::{find_closest, split_i32}; use self::super::split_i32;
use backend; use backend;
use event::{Event, Key, MouseButton, MouseEvent}; use event::{Event, Key, MouseButton, MouseEvent};
use libc; use libc;
@ -17,7 +17,9 @@ use vec::Vec2;
pub struct Backend { pub struct Backend {
current_style: Cell<ColorPair>, current_style: Cell<ColorPair>,
pairs: RefCell<HashMap<ColorPair, i16>>,
// Maps (front, back) ncurses colors to ncurses pairs
pairs: RefCell<HashMap<(i16, i16), i16>>,
key_codes: HashMap<i32, Event>, key_codes: HashMap<i32, Event>,
@ -25,6 +27,10 @@ pub struct Backend {
event_queue: Vec<Event>, event_queue: Vec<Event>,
} }
fn find_closest_pair(pair: &ColorPair) -> (i16, i16) {
super::find_closest_pair(pair, ncurses::COLORS() as i16)
}
/// Writes some bytes directly to `/dev/tty` /// Writes some bytes directly to `/dev/tty`
/// ///
/// Since this is not going to be used often, we can afford to re-open the /// Since this is not going to be used often, we can afford to re-open the
@ -89,11 +95,13 @@ impl Backend {
Box::new(c) Box::new(c)
} }
/// Save a new color pair. /// Save a new color pair.
fn insert_color( fn insert_color(
&self, pairs: &mut HashMap<ColorPair, i16>, pair: ColorPair &self, pairs: &mut HashMap<(i16, i16), i16>, (front, back): (i16, i16)
) -> i16 { ) -> i16 {
let n = 1 + pairs.len() as i16; let n = 1 + pairs.len() as i16;
let target = if ncurses::COLOR_PAIRS() > i32::from(n) { let target = if ncurses::COLOR_PAIRS() > i32::from(n) {
// We still have plenty of space for everyone. // We still have plenty of space for everyone.
n n
@ -104,12 +112,8 @@ impl Backend {
pairs.retain(|_, &mut v| v != target); pairs.retain(|_, &mut v| v != target);
target target
}; };
pairs.insert(pair, target); pairs.insert((front, back), target);
ncurses::init_pair( ncurses::init_pair(target, front, back);
target,
find_closest(&pair.front),
find_closest(&pair.back),
);
target target
} }
@ -118,11 +122,12 @@ impl Backend {
let mut pairs = self.pairs.borrow_mut(); let mut pairs = self.pairs.borrow_mut();
// Find if we have this color in stock // Find if we have this color in stock
if pairs.contains_key(&pair) { let (front, back) = find_closest_pair(&pair);
if pairs.contains_key(&(front, back)) {
// We got it! // We got it!
pairs[&pair] pairs[&(front, back)]
} else { } else {
self.insert_color(&mut *pairs, pair) self.insert_color(&mut *pairs, (front, back))
} }
} }

View File

@ -1,7 +1,7 @@
extern crate pancurses; extern crate pancurses;
use self::pancurses::mmask_t; use self::pancurses::mmask_t;
use self::super::{find_closest, split_i32}; use self::super::split_i32;
use backend; use backend;
use event::{Event, Key, MouseButton, MouseEvent}; use event::{Event, Key, MouseButton, MouseEvent};
use std::cell::{Cell, RefCell}; use std::cell::{Cell, RefCell};
@ -13,7 +13,7 @@ use vec::Vec2;
pub struct Backend { pub struct Backend {
// Used // Used
current_style: Cell<ColorPair>, current_style: Cell<ColorPair>,
pairs: RefCell<HashMap<ColorPair, i32>>, pairs: RefCell<HashMap<(i16, i16), i32>>,
key_codes: HashMap<i32, Event>, key_codes: HashMap<i32, Event>,
@ -24,6 +24,10 @@ pub struct Backend {
window: pancurses::Window, window: pancurses::Window,
} }
fn find_closest_pair(pair: &ColorPair) -> (i16, i16) {
super::find_closest_pair(pair, pancurses::COLORS() as i16)
}
impl Backend { impl Backend {
pub fn init() -> Box<Self> { pub fn init() -> Box<Self> {
::std::env::set_var("ESCDELAY", "25"); ::std::env::set_var("ESCDELAY", "25");
@ -62,8 +66,8 @@ impl Backend {
/// Save a new color pair. /// Save a new color pair.
fn insert_color( fn insert_color(
&self, &self,
pairs: &mut HashMap<ColorPair, i32>, pairs: &mut HashMap<(i16,i16), i32>,
pair: ColorPair, (front, back): (i16, i16),
) -> i32 { ) -> i32 {
let n = 1 + pairs.len() as i32; let n = 1 + pairs.len() as i32;
@ -78,18 +82,15 @@ impl Backend {
pairs.retain(|_, &mut v| v != target); pairs.retain(|_, &mut v| v != target);
target target
}; };
pairs.insert(pair, target); pairs.insert((front, back), target);
pancurses::init_pair( pancurses::init_pair(target as i16, front, back);
target as i16,
find_closest(&pair.front),
find_closest(&pair.back),
);
target target
} }
/// Checks the pair in the cache, or re-define a color if needed. /// Checks the pair in the cache, or re-define a color if needed.
fn get_or_create(&self, pair: ColorPair) -> i32 { fn get_or_create(&self, pair: ColorPair) -> i32 {
let mut pairs = self.pairs.borrow_mut(); let mut pairs = self.pairs.borrow_mut();
let pair = find_closest_pair(&pair);
// Find if we have this color in stock // Find if we have this color in stock
if pairs.contains_key(&pair) { if pairs.contains_key(&pair) {