use iascii::render::ColorDepth;
use crate::fast_ansi;
pub(crate) type Rgb8 = (u8, u8, u8);
pub(crate) static ANSI16_PALETTE: [Rgb8; 16] = [
(0, 0, 0),
(128, 0, 0),
(0, 128, 0),
(128, 128, 0),
(0, 0, 128),
(128, 0, 128),
(0, 128, 128),
(192, 192, 192),
(128, 128, 128),
(255, 0, 0),
(0, 255, 0),
(255, 255, 0),
(0, 0, 255),
(255, 0, 255),
(0, 255, 255),
(255, 255, 255),
];
const CUBE_LEVELS: [u8; 6] = [0, 95, 135, 175, 215, 255];
pub(crate) fn nearest_ansi16(c: Rgb8) -> u8 {
fast_ansi::nearest_ansi16(c.0, c.1, c.2)
}
pub(crate) fn ansi16_rgb(idx: u8) -> Rgb8 {
ANSI16_PALETTE[(idx as usize).min(15)]
}
pub(crate) fn nearest_ansi256(c: Rgb8) -> u8 {
fast_ansi::nearest_ansi256(c.0, c.1, c.2)
}
pub(crate) fn ansi256_rgb(idx: u8) -> Rgb8 {
if idx >= 232 {
let v = 8 + (idx - 232) * 10;
(v, v, v)
} else if idx >= 16 {
let i = idx - 16;
let r = CUBE_LEVELS[(i / 36) as usize];
let g = CUBE_LEVELS[((i / 6) % 6) as usize];
let b = CUBE_LEVELS[(i % 6) as usize];
(r, g, b)
} else {
ansi16_rgb(idx)
}
}
pub(crate) fn posterize_channel(v: u8, levels: u8) -> u8 {
let levels = levels.max(2) as f32;
let step = 255.0 / (levels - 1.0);
((v as f32 / step).round() * step).clamp(0.0, 255.0) as u8
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Quantized {
TrueColor(Rgb8),
Ansi256(u8),
Ansi16(u8),
}
impl Quantized {
pub(crate) fn rgb(&self) -> Rgb8 {
match *self {
Quantized::TrueColor(c) => c,
Quantized::Ansi256(i) => ansi256_rgb(i),
Quantized::Ansi16(i) => ansi16_rgb(i),
}
}
pub(crate) fn push_escape(&self, buf: &mut Vec<u8>) {
match *self {
Quantized::TrueColor((r, g, b)) => fast_ansi::push_truecolor_escape(buf, r, g, b),
Quantized::Ansi256(i) => fast_ansi::push_ansi256_escape(buf, i),
Quantized::Ansi16(i) => fast_ansi::push_ansi16_escape(buf, i),
}
}
}
pub(crate) fn quantize_for_depth(c: Rgb8, depth: ColorDepth, levels: Option<u8>) -> Quantized {
match depth {
ColorDepth::TrueColor => match levels {
Some(l) => Quantized::TrueColor((
posterize_channel(c.0, l),
posterize_channel(c.1, l),
posterize_channel(c.2, l),
)),
None => Quantized::TrueColor(c),
},
ColorDepth::Ansi256 => Quantized::Ansi256(nearest_ansi256(c)),
ColorDepth::Ansi16 => Quantized::Ansi16(nearest_ansi16(c)),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn nearest_ansi16_matches_exact_palette_entries() {
for (i, &c) in ANSI16_PALETTE.iter().enumerate() {
assert_eq!(nearest_ansi16(c), i as u8);
}
}
#[test]
fn ansi256_roundtrips_cube_corners() {
let idx = nearest_ansi256((255, 0, 0));
let rgb = ansi256_rgb(idx);
assert!(rgb.0 > 200 && rgb.1 < 50 && rgb.2 < 50);
}
#[test]
fn posterize_channel_clamps_to_levels() {
assert_eq!(posterize_channel(0, 2), 0);
assert_eq!(posterize_channel(255, 2), 255);
assert_eq!(posterize_channel(130, 2), 255);
assert_eq!(posterize_channel(120, 2), 0);
}
#[test]
fn quantize_truecolor_without_levels_is_identity() {
let q = quantize_for_depth((10, 20, 30), ColorDepth::TrueColor, None);
assert_eq!(q.rgb(), (10, 20, 30));
}
}