use palette::convert::FromColorUnclamped;
use palette::{Oklch, SetHue, Srgb};
use std::borrow::Borrow;
use std::fmt;
use crate::base16::Base16;
use crate::color::Gray;
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Ansi256Color {
pub index: u8,
pub srgb: Srgb<u8>,
pub oklch: Oklch,
}
impl Ansi256Color {
pub fn new(index: u8, r: u8, g: u8, b: u8) -> Self {
let srgb = Srgb::new(r, g, b);
let srgb_linear = srgb.into_linear::<f32>();
let mut oklch = Oklch::from_color_unclamped(srgb_linear);
if srgb.is_gray() {
oklch.set_hue(0.0);
}
Self { index, srgb, oklch }
}
}
impl fmt::Display for Ansi256Color {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"index: {}, srgb({},{},{}), Oklch: l:{} chroma:{} hue: {}",
self.index,
self.srgb.red,
self.srgb.green,
self.srgb.blue,
self.oklch.l,
self.oklch.chroma,
self.oklch.hue.into_inner(),
)
}
}
impl Gray for Ansi256Color {
fn is_gray(&self) -> bool {
self.srgb.is_gray()
}
}
#[derive(Debug)]
pub struct Ansi256Colors {
colors: Vec<Ansi256Color>,
}
impl Ansi256Colors {
pub fn new<T: Borrow<Base16>>(base16: T) -> Self {
const CUBE: [u8; 6] = [0, 95, 135, 175, 215, 255];
const CUBE666_START: u8 = 16;
const GRAYSCALE_START: u8 = 232;
let &Base16(base16) = base16.borrow();
let rgb_channels = base16
.into_iter()
.chain(((16u8 - CUBE666_START)..=(231u8 - CUBE666_START)).map(|x| {
[
CUBE[(x / 36) as usize],
CUBE[(x / 6 % 6) as usize],
CUBE[(x % 6) as usize],
]
}))
.chain(
((232u8 - GRAYSCALE_START)..=(255u8 - GRAYSCALE_START)).map(|x| [x * 10 + 8; 3]),
);
Self {
colors: rgb_channels
.enumerate()
.map(|(index, channels)| {
Ansi256Color::new(index as u8, channels[0], channels[1], channels[2])
})
.collect::<Vec<_>>(),
}
}
pub fn oklch_from_index(&self, index: u8) -> Oklch {
self.colors[index as usize].oklch
}
pub fn srgb_from_index(&self, index: u8) -> Srgb<u8> {
self.colors[index as usize].srgb
}
pub fn as_slice(&self) -> &[Ansi256Color] {
&self.colors
}
}
impl AsRef<Ansi256Colors> for Ansi256Colors {
fn as_ref(&self) -> &Ansi256Colors {
self
}
}
impl Default for Ansi256Colors {
fn default() -> Self {
Self::new(Base16::default())
}
}
pub const GRAY_INDEXES: [u8; 26] = [
16_u8, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250,
251, 252, 253, 254, 255, 231, ];
#[cfg(test)]
mod tests {
use crate::base16::TERMINAL_APP;
use super::*;
#[test]
fn test_ansi256_colors() {
let colors = Ansi256Colors::new(TERMINAL_APP);
insta::assert_yaml_snapshot!(colors.srgb_from_index(2));
insta::assert_yaml_snapshot!(colors.srgb_from_index(30));
insta::assert_yaml_snapshot!(colors.srgb_from_index(253));
}
#[test]
fn test_ansi256_grayscale() {
let colors = Ansi256Colors::default();
let black = colors.srgb_from_index(GRAY_INDEXES[0]);
assert_eq!(black, Srgb::new(0, 0, 0));
let white = colors.srgb_from_index(GRAY_INDEXES[25]);
assert_eq!(white, Srgb::new(255, 255, 255));
for &gi in GRAY_INDEXES.iter().take(25).skip(1) {
let c = colors.srgb_from_index(gi);
assert_eq!(c.red, c.green);
assert_eq!(c.green, c.blue);
}
}
}