use crate::color::Color;
use std::env;
use std::fmt;
use std::io::{self, Read, Write};
use std::time::{Duration, Instant};
use termion;
use termion::raw::CONTROL_SEQUENCE_TIMEOUT;
pub fn termion_color(
color: Option<Color>,
foreground: bool,
support: ColorSupport,
) -> TermionColor {
TermionColor {
foreground,
color,
support,
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum ColorSupport {
None,
Mono,
Ansi3bit,
Ansi4bit,
Ansi8bit,
RGB24bit,
}
impl Default for ColorSupport {
fn default() -> ColorSupport {
ColorSupport::None
}
}
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct TermionColor {
pub color: Option<Color>,
pub support: ColorSupport,
pub foreground: bool,
}
impl fmt::Display for TermionColor {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
use self::ColorSupport::*;
if let Some(Color {
red: ref r,
green: ref g,
blue: ref b,
}) = self.color
{
match (self.foreground, self.support) {
(_, None) => {
Ok(())
}
(true, Mono) => {
if rgb_to_1bit(r, g, b) {
termion::color::Fg(termion::color::White).fmt(f)
} else {
termion::color::Fg(termion::color::Black).fmt(f)
}
}
(false, Mono) => {
if rgb_to_1bit(r, g, b) {
termion::color::Bg(termion::color::White).fmt(f)
} else {
termion::color::Bg(termion::color::Black).fmt(f)
}
}
(true, Ansi3bit) => {
termion::color::Fg(termion::color::AnsiValue(rgb_to_3bit(r, g, b))).fmt(f)
}
(false, Ansi3bit) => {
termion::color::Bg(termion::color::AnsiValue(rgb_to_3bit(r, g, b))).fmt(f)
}
(true, Ansi4bit) => {
termion::color::Fg(termion::color::AnsiValue(rgb_to_4bit(r, g, b))).fmt(f)
}
(false, Ansi4bit) => {
termion::color::Bg(termion::color::AnsiValue(rgb_to_4bit(r, g, b))).fmt(f)
}
(true, Ansi8bit) => {
termion::color::Fg(termion::color::AnsiValue(rgb_to_ansi(r, g, b))).fmt(f)
}
(false, Ansi8bit) => {
termion::color::Bg(termion::color::AnsiValue(rgb_to_ansi(r, g, b))).fmt(f)
}
(true, RGB24bit) => termion::color::Fg(termion::color::Rgb(*r, *g, *b)).fmt(f),
(false, RGB24bit) => termion::color::Bg(termion::color::Rgb(*r, *g, *b)).fmt(f),
}
} else {
match (self.support, self.foreground) {
(ColorSupport::None, _) => Ok(()),
(_, true) => termion::color::Fg(termion::color::Reset).fmt(f),
(_, false) => termion::color::Bg(termion::color::Reset).fmt(f),
}
}
}
}
pub fn get_color_support(input: &mut Read, output: &mut Write) -> io::Result<ColorSupport> {
if let Ok(colorterm) = env::var("COLORTERM") {
if colorterm.eq("truecolor") || colorterm.eq("24bit") {
return Ok(ColorSupport::RGB24bit);
} else if colorterm.eq("yes") {
return Ok(ColorSupport::Ansi8bit);
}
}
if let Ok(term) = env::var("TERM") {
if term.contains("256color") {
return Ok(ColorSupport::Ansi8bit);
}
}
if detect_color(input, output, 0)? {
let mut min = 0u8;
let mut max = 0xFFu8;
let mut i = max;
while max - min > 1 {
trace!("color min: {}, max: {}, i: {}", min, max, i);
if detect_color(input, output, i)? {
min = i
} else {
max = i
}
i = min + (max - min) / 2;
}
trace!("colors: {}", max);
Ok(match max {
0 => ColorSupport::None,
1...6 => ColorSupport::Mono,
7...14 => ColorSupport::Ansi3bit,
15...0xFE => ColorSupport::Ansi4bit,
0xFF => ColorSupport::Ansi8bit,
_ => unreachable!(),
})
} else {
Ok(ColorSupport::None)
}
}
fn detect_color(input: &mut Read, output: &mut Write, color: u8) -> io::Result<bool> {
write!(output, "\x1B]4;{};?\x07", color)?;
output.flush()?;
let mut buf: [u8; 1] = [0];
let mut total_read = 0;
let timeout = Duration::from_millis(CONTROL_SEQUENCE_TIMEOUT);
let now = Instant::now();
let bell = 7u8;
while buf[0] != bell && now.elapsed() < timeout {
total_read += input.read(&mut buf)?;
}
Ok(total_read > 0)
}
#[inline]
fn bit(byte: &u8, bit: u8) -> u8 {
1u8 & (byte >> bit)
}
fn rgb_to_1bit(r: &u8, g: &u8, b: &u8) -> bool {
(r | g | b) & 8 == 8
}
fn rgb_from_1bit(white: &bool) -> (u8, u8, u8) {
if *white {
(128, 128, 128)
} else {
(0, 0, 0)
}
}
fn rgb_to_3bit(r: &u8, g: &u8, b: &u8) -> u8 {
let (r, g, b, r_, g_, b_) = (
bit(r, 6),
bit(g, 6),
bit(b, 6),
bit(r, 7),
bit(g, 7),
bit(b, 7),
);
let v: u8 = r | r_ | (g << 1) | (g_ << 1) | (b << 2) | (b_ << 2);
v
}
fn rgb_from_3bit(c: &u8) -> (u8, u8, u8) {
match c & 0b111 {
0 => (0, 0, 0),
1 => (128, 0, 0),
2 => (0, 128, 0),
3 => (128, 128, 0),
4 => (0, 0, 128),
5 => (128, 0, 128),
6 => (0, 128, 128),
7 => (192, 192, 192),
_ => unreachable!(),
}
}
fn rgb_to_4bit(r: &u8, g: &u8, b: &u8) -> u8 {
if r & g & b == 0xFF {
return 15;
}
let (r, g, b, r_, g_, b_) = (
bit(r, 6),
bit(g, 6),
bit(b, 6),
bit(r, 7),
bit(g, 7),
bit(b, 7),
);
let v: u8 = r | r_ | (g << 1) | (g_ << 1) | (b << 2) | (b_ << 2) | ((r_ | g_ | b_) << 3);
match v {
0b1000 => unreachable!(),
0b0111 => 8, 0b1111 => 7, v => v % 16, }
}
fn rgb_from_4bit(c: &u8) -> (u8, u8, u8) {
match c & 0b1111 {
0 => (0, 0, 0),
1 => (128, 0, 0),
2 => (0, 128, 0),
3 => (128, 128, 0),
4 => (0, 0, 128),
5 => (128, 0, 128),
6 => (0, 128, 128),
7 => (192, 192, 192),
8 => (128, 128, 128),
9 => (255, 0, 0),
10 => (0, 255, 0),
11 => (255, 255, 0),
12 => (0, 0, 255),
13 => (255, 0, 255),
14 => (0, 255, 255),
15 => (255, 255, 255),
_ => unreachable!(),
}
}
fn c256_to_gray(c: &u8) -> u8 {
232u8.checked_add(c / 10).unwrap_or(255)
}
fn gray_to_c256(c: &u8) -> u8 {
8 + (c % 232) * 10
}
fn rgb_to_5bit(r: &u8, g: &u8, b: &u8) -> u8 {
16 + 36 * (r / 51) + 6 * (g / 51) + (b / 51)
}
fn rgb_from_5bit(c: &u8) -> (u8, u8, u8) {
((c / 36) * 0xF, ((c / 6) % 6) * 0xF, (c / 4 & 0x1) * 0xF)
}
fn rgb_to_ansi(r: &u8, g: &u8, b: &u8) -> u8 {
let c = rgb_to_4bit(r, g, b);
let gray = c256_to_gray(&r);
if (*r, *g, *b) == rgb_from_4bit(&c) {
c
} else if r == g && g == b {
gray
} else {
rgb_to_5bit(r, g, b)
}
}