use anstyle::{Ansi256Color, AnsiColor, RgbColor};
const ANSI16: [(AnsiColor, RgbColor); 16] = [
(AnsiColor::Black, RgbColor(0, 0, 0)),
(AnsiColor::Red, RgbColor(205, 0, 0)),
(AnsiColor::Green, RgbColor(0, 205, 0)),
(AnsiColor::Yellow, RgbColor(205, 205, 0)),
(AnsiColor::Blue, RgbColor(0, 0, 238)),
(AnsiColor::Magenta, RgbColor(205, 0, 205)),
(AnsiColor::Cyan, RgbColor(0, 205, 205)),
(AnsiColor::White, RgbColor(229, 229, 229)),
(AnsiColor::BrightBlack, RgbColor(127, 127, 127)),
(AnsiColor::BrightRed, RgbColor(255, 0, 0)),
(AnsiColor::BrightGreen, RgbColor(0, 255, 0)),
(AnsiColor::BrightYellow, RgbColor(255, 255, 0)),
(AnsiColor::BrightBlue, RgbColor(92, 92, 255)),
(AnsiColor::BrightMagenta, RgbColor(255, 0, 255)),
(AnsiColor::BrightCyan, RgbColor(0, 255, 255)),
(AnsiColor::BrightWhite, RgbColor(255, 255, 255)),
];
const CUBE_LEVELS: [u8; 6] = [0, 95, 135, 175, 215, 255];
const CUBE_START: u8 = 16;
const GREY_START: u8 = 232;
const GREY_STEPS: u8 = 24;
fn distance(a: RgbColor, b: RgbColor) -> u32 {
let square = |x: u8, y: u8| u32::from(x.abs_diff(y)).pow(2);
square(a.0, b.0) + square(a.1, b.1) + square(a.2, b.2)
}
fn cube_step(component: u8) -> u8 {
(0..)
.zip(CUBE_LEVELS)
.fold((0, u32::MAX), |best, (step, level)| {
let gap = u32::from(component.abs_diff(level));
if gap < best.1 { (step, gap) } else { best }
})
.0
}
fn cube_level(step: u8) -> u8 {
CUBE_LEVELS
.get(usize::from(step))
.copied()
.unwrap_or(u8::MAX)
}
fn grey_step(brightness: u8) -> u8 {
let rounded = (u16::from(brightness).saturating_sub(8) + 5) / 10;
u8::try_from(rounded)
.unwrap_or(GREY_STEPS)
.min(GREY_STEPS - 1)
}
fn extended_rgb(index: u8) -> RgbColor {
if index >= GREY_START {
let level = 8 + 10 * (index - GREY_START);
return RgbColor(level, level, level);
}
let offset = index.saturating_sub(CUBE_START);
RgbColor(
cube_level(offset / 36),
cube_level(offset / 6 % 6),
cube_level(offset % 6),
)
}
#[must_use]
pub fn rgb_to_ansi16(color: RgbColor) -> AnsiColor {
ANSI16
.iter()
.fold((AnsiColor::Black, u32::MAX), |best, (named, rgb)| {
let gap = distance(color, *rgb);
if gap < best.1 { (*named, gap) } else { best }
})
.0
}
#[must_use]
pub fn rgb_to_ansi256(color: RgbColor) -> Ansi256Color {
let RgbColor(red, green, blue) = color;
let (red_step, green_step, blue_step) = (cube_step(red), cube_step(green), cube_step(blue));
let cube = RgbColor(
cube_level(red_step),
cube_level(green_step),
cube_level(blue_step),
);
let cube_index = CUBE_START + 36 * red_step + 6 * green_step + blue_step;
let mean = (u16::from(red) + u16::from(green) + u16::from(blue)) / 3;
let step = grey_step(u8::try_from(mean).unwrap_or(u8::MAX));
let grey_index = GREY_START + step;
let level = 8 + 10 * step;
let grey = RgbColor(level, level, level);
if distance(color, grey) < distance(color, cube) {
Ansi256Color(grey_index)
} else {
Ansi256Color(cube_index)
}
}
#[must_use]
pub fn ansi256_to_ansi16(color: Ansi256Color) -> AnsiColor {
color
.into_ansi()
.unwrap_or_else(|| rgb_to_ansi16(extended_rgb(color.index())))
}