use std::{num::ParseFloatError, str::FromStr}; use tui::style::{Color, Modifier, Style}; use crate::RenderingMode; #[derive(Clone, Copy, Debug, PartialEq)] pub struct RinkColor { pub color: Color, pub alpha: u8, } impl Default for RinkColor { fn default() -> Self { Self { color: Color::Black, alpha: 0, } } } impl RinkColor { pub fn blend(self, other: Color) -> Color { if self.color == Color::Reset { Color::Reset } else if self.alpha == 0 { other } else { let [sr, sg, sb] = to_rgb(self.color).map(|e| e as u16); let [or, og, ob] = to_rgb(other).map(|e| e as u16); let sa = self.alpha as u16; let rsa = 255 - sa; Color::Rgb( ((sr * sa + or * rsa) / 255) as u8, ((sg * sa + og * rsa) / 255) as u8, ((sb * sa + ob * rsa) / 255) as u8, ) } } } fn parse_value( v: &str, current_max_output: f32, required_max_output: f32, ) -> Result { if let Some(stripped) = v.strip_suffix('%') { Ok((stripped.trim().parse::()? / 100.0) * required_max_output) } else { Ok((v.trim().parse::()? / current_max_output) * required_max_output) } } pub struct ParseColorError; fn parse_hex(color: &str) -> Result { let mut values = [0, 0, 0]; let mut color_ok = true; for i in 0..values.len() { if let Ok(v) = u8::from_str_radix(&color[(1 + 2 * i)..(1 + 2 * (i + 1))], 16) { values[i] = v; } else { color_ok = false; } } if color_ok { Ok(Color::Rgb(values[0], values[1], values[2])) } else { Err(ParseColorError) } } fn parse_rgb(color: &str) -> Result { let mut values = [0, 0, 0]; let mut color_ok = true; for (v, i) in color.split(',').zip(0..values.len()) { if let Ok(v) = parse_value(v.trim(), 255.0, 255.0) { values[i] = v as u8; } else { color_ok = false; } } if color_ok { Ok(Color::Rgb(values[0], values[1], values[2])) } else { Err(ParseColorError) } } fn parse_hsl(color: &str) -> Result { let mut values = [0.0, 0.0, 0.0]; let mut color_ok = true; for (v, i) in color.split(',').zip(0..values.len()) { if let Ok(v) = parse_value(v.trim(), if i == 0 { 360.0 } else { 100.0 }, 1.0) { values[i] = v; } else { color_ok = false; } } if color_ok { let [h, s, l] = values; let rgb = if s == 0.0 { [l as u8; 3] } else { fn hue_to_rgb(p: f32, q: f32, mut t: f32) -> f32 { if t < 0.0 { t += 1.0; } if t > 1.0 { t -= 1.0; } if t < 1.0 / 6.0 { p + (q - p) * 6.0 * t } else if t < 1.0 / 2.0 { q } else if t < 2.0 / 3.0 { p + (q - p) * (2.0 / 3.0 - t) * 6.0 } else { p } } let q = if l < 0.5 { l * (1.0 + s) } else { l + s - l * s }; let p = 2.0 * l - q; [ (hue_to_rgb(p, q, h + 1.0 / 3.0) * 255.0) as u8, (hue_to_rgb(p, q, h) * 255.0) as u8, (hue_to_rgb(p, q, h - 1.0 / 3.0) * 255.0) as u8, ] }; Ok(Color::Rgb(rgb[0], rgb[1], rgb[2])) } else { Err(ParseColorError) } } impl FromStr for RinkColor { type Err = ParseColorError; fn from_str(color: &str) -> Result { match color { "red" => Ok(RinkColor { color: Color::Red, alpha: 255, }), "black" => Ok(RinkColor { color: Color::Black, alpha: 255, }), "green" => Ok(RinkColor { color: Color::Green, alpha: 255, }), "yellow" => Ok(RinkColor { color: Color::Yellow, alpha: 255, }), "blue" => Ok(RinkColor { color: Color::Blue, alpha: 255, }), "magenta" => Ok(RinkColor { color: Color::Magenta, alpha: 255, }), "cyan" => Ok(RinkColor { color: Color::Cyan, alpha: 255, }), "gray" => Ok(RinkColor { color: Color::Gray, alpha: 255, }), "darkgray" => Ok(RinkColor { color: Color::DarkGray, alpha: 255, }), // light red does not exist "orangered" => Ok(RinkColor { color: Color::LightRed, alpha: 255, }), "lightgreen" => Ok(RinkColor { color: Color::LightGreen, alpha: 255, }), "lightyellow" => Ok(RinkColor { color: Color::LightYellow, alpha: 255, }), "lightblue" => Ok(RinkColor { color: Color::LightBlue, alpha: 255, }), // light magenta does not exist "orchid" => Ok(RinkColor { color: Color::LightMagenta, alpha: 255, }), "lightcyan" => Ok(RinkColor { color: Color::LightCyan, alpha: 255, }), "white" => Ok(RinkColor { color: Color::White, alpha: 255, }), _ => { if color.len() == 7 && color.starts_with('#') { parse_hex(color).map(|c| RinkColor { color: c, alpha: 255, }) } else if let Some(stripped) = color.strip_prefix("rgb(") { let color_values = stripped.trim_end_matches(')'); if color.matches(',').count() == 3 { let (alpha, rgb_values) = color_values.rsplit_once(',').ok_or(ParseColorError)?; if let Ok(a) = alpha.parse() { parse_rgb(rgb_values).map(|c| RinkColor { color: c, alpha: a }) } else { Err(ParseColorError) } } else { parse_rgb(color_values).map(|c| RinkColor { color: c, alpha: 255, }) } } else if let Some(stripped) = color.strip_prefix("rgba(") { let color_values = stripped.trim_end_matches(')'); if color.matches(',').count() == 3 { let (rgb_values, alpha) = color_values.rsplit_once(',').ok_or(ParseColorError)?; if let Ok(a) = parse_value(alpha, 1.0, 1.0) { parse_rgb(rgb_values).map(|c| RinkColor { color: c, alpha: (a * 255.0) as u8, }) } else { Err(ParseColorError) } } else { parse_rgb(color_values).map(|c| RinkColor { color: c, alpha: 255, }) } } else if let Some(stripped) = color.strip_prefix("hsl(") { let color_values = stripped.trim_end_matches(')'); if color.matches(',').count() == 3 { let (rgb_values, alpha) = color_values.rsplit_once(',').ok_or(ParseColorError)?; if let Ok(a) = parse_value(alpha, 1.0, 1.0) { parse_hsl(rgb_values).map(|c| RinkColor { color: c, alpha: (a * 255.0) as u8, }) } else { Err(ParseColorError) } } else { parse_hsl(color_values).map(|c| RinkColor { color: c, alpha: 255, }) } } else if let Some(stripped) = color.strip_prefix("hsla(") { let color_values = stripped.trim_end_matches(')'); if color.matches(',').count() == 3 { let (rgb_values, alpha) = color_values.rsplit_once(',').ok_or(ParseColorError)?; if let Ok(a) = parse_value(alpha, 1.0, 1.0) { parse_hsl(rgb_values).map(|c| RinkColor { color: c, alpha: (a * 255.0) as u8, }) } else { Err(ParseColorError) } } else { parse_hsl(color_values).map(|c| RinkColor { color: c, alpha: 255, }) } } else { Err(ParseColorError) } } } } } fn to_rgb(c: Color) -> [u8; 3] { match c { Color::Black => [0, 0, 0], Color::Red => [255, 0, 0], Color::Green => [0, 128, 0], Color::Yellow => [255, 255, 0], Color::Blue => [0, 0, 255], Color::Magenta => [255, 0, 255], Color::Cyan => [0, 255, 255], Color::Gray => [128, 128, 128], Color::DarkGray => [169, 169, 169], Color::LightRed => [255, 69, 0], Color::LightGreen => [144, 238, 144], Color::LightYellow => [255, 255, 224], Color::LightBlue => [173, 216, 230], Color::LightMagenta => [218, 112, 214], Color::LightCyan => [224, 255, 255], Color::White => [255, 255, 255], Color::Rgb(r, g, b) => [r, g, b], Color::Indexed(idx) => match idx { 16..=231 => { let v = idx - 16; // add 3 to round up let r = ((v as u16 / 36) * 255 + 3) / 5; let g = (((v as u16 % 36) / 6) * 255 + 3) / 5; let b = ((v as u16 % 6) * 255 + 3) / 5; [r as u8, g as u8, b as u8] } 232..=255 => { let l = (idx - 232) / 24; [l; 3] } // rink will never generate these colors, but they might be on the screen from another program _ => [0, 0, 0], }, Color::Reset => [0, 0, 0], } } pub fn convert(mode: RenderingMode, c: Color) -> Color { if let Color::Reset = c { c } else { match mode { crate::RenderingMode::BaseColors => match c { Color::Rgb(_, _, _) => panic!("cannot convert rgb color to base color"), Color::Indexed(_) => panic!("cannot convert Ansi color to base color"), _ => c, }, crate::RenderingMode::Rgb => { let rgb = to_rgb(c); Color::Rgb(rgb[0], rgb[1], rgb[2]) } crate::RenderingMode::Ansi => match c { Color::Indexed(_) => c, _ => { let rgb = to_rgb(c); // 16-231: 6 × 6 × 6 color cube // 232-255: 23 step grayscale if rgb[0] == rgb[1] && rgb[1] == rgb[2] { let idx = 232 + (rgb[0] as u16 * 23 / 255) as u8; Color::Indexed(idx) } else { let r = (rgb[0] as u16 * 5) / 255; let g = (rgb[1] as u16 * 5) / 255; let b = (rgb[2] as u16 * 5) / 255; let idx = 16 + r * 36 + g * 6 + b; Color::Indexed(idx as u8) } } }, } } } #[test] fn rgb_to_ansi() { for idx in 17..=231 { let idxed = Color::Indexed(idx); let rgb = to_rgb(idxed); // gray scale colors have two equivelent repersentations let color = Color::Rgb(rgb[0], rgb[1], rgb[2]); let converted = convert(RenderingMode::Ansi, color); if let Color::Indexed(i) = converted { if rgb[0] != rgb[1] || rgb[1] != rgb[2] { assert_eq!(idxed, converted); } else { assert!(i >= 232); } } else { panic!("color is not indexed") } } for idx in 232..=255 { let idxed = Color::Indexed(idx); let rgb = to_rgb(idxed); assert!(rgb[0] == rgb[1] && rgb[1] == rgb[2]); } } #[derive(Clone, Copy, PartialEq, Debug)] pub struct RinkStyle { pub fg: Option, pub bg: Option, pub add_modifier: Modifier, pub sub_modifier: Modifier, } impl Default for RinkStyle { fn default() -> Self { Self { fg: Some(RinkColor { color: Color::White, alpha: 255, }), bg: None, add_modifier: Modifier::empty(), sub_modifier: Modifier::empty(), } } } impl RinkStyle { pub fn add_modifier(mut self, m: Modifier) -> Self { self.sub_modifier.remove(m); self.add_modifier.insert(m); self } pub fn remove_modifier(mut self, m: Modifier) -> Self { self.add_modifier.remove(m); self.sub_modifier.insert(m); self } pub fn merge(mut self, other: RinkStyle) -> Self { self.fg = self.fg.or(other.fg); self.add_modifier(other.add_modifier) .remove_modifier(other.sub_modifier) } } impl From for Style { fn from(val: RinkStyle) -> Self { Style { fg: val.fg.map(|c| c.color), bg: val.bg.map(|c| c.color), add_modifier: val.add_modifier, sub_modifier: val.sub_modifier, } } }