1use std::fmt;
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
40pub struct ColorTriplet {
41 pub red: u8,
42 pub green: u8,
43 pub blue: u8,
44}
45
46impl ColorTriplet {
47 pub const fn new(red: u8, green: u8, blue: u8) -> Self {
49 Self { red, green, blue }
50 }
51}
52
53impl fmt::Display for ColorTriplet {
54 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55 write!(f, "#{:02x}{:02x}{:02x}", self.red, self.green, self.blue)
56 }
57}
58use std::hash::Hash;
59
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
67pub enum ColorSystem {
68 Standard = 1,
70 EightBit = 2,
72 TrueColor = 3,
74}
75
76impl fmt::Display for ColorSystem {
77 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78 match self {
79 Self::Standard => write!(f, "standard"),
80 Self::EightBit => write!(f, "256"),
81 Self::TrueColor => write!(f, "truecolor"),
82 }
83 }
84}
85
86#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
92pub enum ColorType {
93 Default,
95 Standard,
97 EightBit,
99 TrueColor,
101}
102
103#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
118pub struct Color {
119 pub(crate) color_type: ColorType,
120 pub(crate) number: Option<u8>,
123 pub(crate) triplet: Option<(u8, u8, u8)>,
125 pub(crate) name: Option<&'static str>,
127}
128
129impl Color {
130 pub const fn default() -> Self {
134 Self {
135 color_type: ColorType::Default,
136 number: None,
137 triplet: None,
138 name: None,
139 }
140 }
141
142 pub fn from_ansi_name(name: &str) -> Option<Self> {
144 let n = ANSI_NAME_MAP.get(name).copied()?;
145 Some(Self {
146 color_type: if n < 16 {
147 ColorType::Standard
148 } else {
149 ColorType::EightBit
150 },
151 number: Some(n),
152 triplet: None,
153 name: None,
154 })
155 }
156
157 pub fn from_8bit(n: u8) -> Self {
159 Self {
160 color_type: ColorType::EightBit,
161 number: Some(n),
162 triplet: None,
163 name: None,
164 }
165 }
166
167 pub fn from_rgb(r: u8, g: u8, b: u8) -> Self {
169 Self {
170 color_type: ColorType::TrueColor,
171 number: None,
172 triplet: Some((r, g, b)),
173 name: None,
174 }
175 }
176
177 pub fn from_hex(hex: &str) -> Result<Self, ColorParseError> {
179 let hex = hex.trim_start_matches('#');
180 if hex.len() != 6 {
181 return Err(ColorParseError::InvalidHex(hex.to_string()));
182 }
183 let r = u8::from_str_radix(&hex[0..2], 16)
184 .map_err(|_| ColorParseError::InvalidHex(hex.to_string()))?;
185 let g = u8::from_str_radix(&hex[2..4], 16)
186 .map_err(|_| ColorParseError::InvalidHex(hex.to_string()))?;
187 let b = u8::from_str_radix(&hex[4..6], 16)
188 .map_err(|_| ColorParseError::InvalidHex(hex.to_string()))?;
189 Ok(Self::from_rgb(r, g, b))
190 }
191
192 pub fn parse(s: &str) -> Result<Self, ColorParseError> {
194 let lower = s.to_lowercase();
195 if lower == "default" || lower.is_empty() {
196 return Ok(Self::default());
197 }
198 if let Some(c) = Self::from_ansi_name(&lower) {
199 return Ok(c);
200 }
201 if lower.starts_with('#') || lower.len() == 6 {
202 return Self::from_hex(&lower);
203 }
204 if lower.starts_with("color") {
206 if let Ok(n) = lower[5..].parse::<u8>() {
207 return Ok(Self::from_8bit(n));
208 }
209 }
210 Err(ColorParseError::UnknownName(lower))
211 }
212
213 pub fn is_default(&self) -> bool {
217 matches!(self.color_type, ColorType::Default)
218 }
219
220 pub fn get_truecolor(&self, theme: &TerminalTheme) -> (u8, u8, u8) {
223 match self.color_type {
224 ColorType::TrueColor => self.triplet.unwrap(),
225 ColorType::Default => theme.foreground_color,
226 _ => {
227 if let Some(n) = self.number {
228 if let Some(&[r, g, b]) = EIGHT_BIT_PALETTE.get(n as usize) {
229 return (r, g, b);
230 }
231 }
232 theme.foreground_color
233 }
234 }
235 }
236
237 pub fn downgrade(&self, system: ColorSystem) -> Self {
239 match system {
240 ColorSystem::TrueColor => *self,
241 ColorSystem::EightBit => {
242 if matches!(self.color_type, ColorType::TrueColor) {
243 let (r, g, b) = self.triplet.unwrap();
244 let idx = rgb_to_8bit(r, g, b);
245 Self::from_8bit(idx)
246 } else {
247 *self
248 }
249 }
250 ColorSystem::Standard => {
251 if matches!(self.color_type, ColorType::TrueColor) {
252 let (r, g, b) = self.triplet.unwrap();
253 let idx = rgb_to_standard(r, g, b);
254 Self {
255 color_type: ColorType::Standard,
256 number: Some(idx),
257 triplet: None,
258 name: None,
259 }
260 } else if let Some(n) = self.number {
261 if n >= 16 {
262 let idx = n % 16;
263 Self {
264 color_type: ColorType::Standard,
265 number: Some(idx),
266 triplet: None,
267 name: None,
268 }
269 } else {
270 *self
271 }
272 } else {
273 *self
274 }
275 }
276 }
277 }
278}
279
280impl fmt::Display for Color {
281 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
282 match self.color_type {
283 ColorType::Default => write!(f, "default"),
284 ColorType::Standard => write!(f, "{}", STANDARD_COLOR_NAMES[self.number.unwrap() as usize]),
285 ColorType::EightBit => write!(f, "color({})", self.number.unwrap()),
286 ColorType::TrueColor => {
287 let (r, g, b) = self.triplet.unwrap();
288 write!(f, "#{:02x}{:02x}{:02x}", r, g, b)
289 }
290 }
291 }
292}
293
294#[derive(Debug, Clone)]
300pub enum ColorParseError {
301 UnknownName(String),
303 InvalidHex(String),
305}
306
307impl fmt::Display for ColorParseError {
308 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
309 match self {
310 Self::UnknownName(n) => write!(f, "unknown color name: {n}"),
311 Self::InvalidHex(h) => write!(f, "invalid hex color: {h}"),
312 }
313 }
314}
315
316impl std::error::Error for ColorParseError {}
317
318#[derive(Debug, Clone, Copy)]
324pub struct TerminalTheme {
325 pub foreground_color: (u8, u8, u8),
326 pub background_color: (u8, u8, u8),
327}
328
329impl Default for TerminalTheme {
330 fn default() -> Self {
331 Self {
332 foreground_color: (255, 255, 255),
333 background_color: (0, 0, 0),
334 }
335 }
336}
337
338pub static STANDARD_PALETTE: &[(u8, u8, u8)] = &[
344 (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), ];
361
362pub static STANDARD_COLOR_NAMES: &[&str] = &[
364 "black", "red", "green", "yellow", "blue", "magenta", "cyan", "white",
365 "bright_black", "bright_red", "bright_green", "bright_yellow",
366 "bright_blue", "bright_magenta", "bright_cyan", "bright_white",
367];
368
369pub static EIGHT_BIT_PALETTE: Lazy<[[u8; 3]; 256]> = Lazy::new(|| {
371 let mut table = [[0u8; 3]; 256];
372 let std: [[u8; 3]; 16] = [
373 [0, 0, 0],
374 [128, 0, 0],
375 [0, 128, 0],
376 [128, 128, 0],
377 [0, 0, 128],
378 [128, 0, 128],
379 [0, 128, 128],
380 [192, 192, 192],
381 [128, 128, 128],
382 [255, 0, 0],
383 [0, 255, 0],
384 [255, 255, 0],
385 [0, 0, 255],
386 [255, 0, 255],
387 [0, 255, 255],
388 [255, 255, 255],
389 ];
390 for i in 0..16 {
391 table[i] = std[i];
392 }
393 let levels = [0u8, 95, 135, 175, 215, 255];
394 for r in 0..6 {
395 for g in 0..6 {
396 for b in 0..6 {
397 let idx = 16 + 36 * r + 6 * g + b;
398 table[idx] = [levels[r], levels[g], levels[b]];
399 }
400 }
401 }
402 for gr in 0..24 {
403 let val = (gr * 10 + 8) as u8;
404 table[232 + gr] = [val, val, val];
405 }
406 table
407});
408
409pub fn rgb_to_8bit(r: u8, g: u8, b: u8) -> u8 {
415 let grey = ((r as u32 + g as u32 + b as u32) / 3) as u8;
417 if r == g && g == b {
418 if grey < 8 {
419 return 16; }
421 if grey > 248 {
422 return 231; }
424 return 232 + ((grey - 8) / 10) as u8;
425 }
426
427 let r6 = ((r as f64 / 255.0) * 5.0).round() as u8;
429 let g6 = ((g as f64 / 255.0) * 5.0).round() as u8;
430 let b6 = ((b as f64 / 255.0) * 5.0).round() as u8;
431 16 + 36 * r6 + 6 * g6 + b6
432}
433
434pub fn rgb_to_standard(_r: u8, _g: u8, _b: u8) -> u8 {
436 let mut best_idx = 0u8;
438 let mut best_dist = u32::MAX;
439 for (i, &(pr, pg, pb)) in STANDARD_PALETTE.iter().enumerate() {
440 let dr = _r as i32 - pr as i32;
441 let dg = _g as i32 - pg as i32;
442 let db = _b as i32 - pb as i32;
443 let dist = (dr * dr + dg * dg + db * db) as u32;
444 if dist < best_dist {
445 best_dist = dist;
446 best_idx = i as u8;
447 }
448 }
449 best_idx
450}
451
452pub fn blend_rgb(
454 color1: (u8, u8, u8),
455 color2: (u8, u8, u8),
456 cross_fade: f64,
457) -> (u8, u8, u8) {
458 let r = (color1.0 as f64 + (color2.0 as f64 - color1.0 as f64) * cross_fade) as u8;
459 let g = (color1.1 as f64 + (color2.1 as f64 - color1.1 as f64) * cross_fade) as u8;
460 let b = (color1.2 as f64 + (color2.2 as f64 - color1.2 as f64) * cross_fade) as u8;
461 (r, g, b)
462}
463
464pub fn blend_colors(
466 color1: &Color,
467 color2: &Color,
468 cross_fade: f64,
469 theme: &TerminalTheme,
470) -> Color {
471 let rgb1 = color1.get_truecolor(theme);
472 let rgb2 = color2.get_truecolor(theme);
473 let blended = blend_rgb(rgb1, rgb2, cross_fade);
474 Color::from_rgb(blended.0, blended.1, blended.2)
475}
476
477use once_cell::sync::Lazy;
485use std::collections::HashMap;
486
487static ANSI_NAME_MAP: Lazy<HashMap<&'static str, u8>> = Lazy::new(|| {
488 let mut m = HashMap::new();
489 m.insert("black", 0u8);
491 m.insert("red", 1u8);
492 m.insert("green", 2u8);
493 m.insert("yellow", 3u8);
494 m.insert("blue", 4u8);
495 m.insert("magenta", 5u8);
496 m.insert("cyan", 6u8);
497 m.insert("white", 7u8);
498 m.insert("bright_black", 8u8);
499 m.insert("grey", 8u8);
500 m.insert("gray", 8u8);
501 m.insert("bright_red", 9u8);
502 m.insert("bright_green", 10u8);
503 m.insert("bright_yellow", 11u8);
504 m.insert("bright_blue", 12u8);
505 m.insert("bright_magenta", 13u8);
506 m.insert("bright_cyan", 14u8);
507 m.insert("bright_white", 15u8);
508 m.insert("grey0", 16u8);
510 m.insert("gray0", 16u8);
511 m.insert("navy_blue", 17u8);
512 m.insert("dark_blue", 18u8);
513 m.insert("blue3", 20u8);
514 m.insert("blue1", 21u8);
515 m.insert("dark_green", 22u8);
516 m.insert("deep_sky_blue4", 25u8);
517 m.insert("dodger_blue3", 26u8);
518 m.insert("dodger_blue2", 27u8);
519 m.insert("green4", 28u8);
520 m.insert("spring_green4", 29u8);
521 m.insert("turquoise4", 30u8);
522 m.insert("deep_sky_blue3", 32u8);
523 m.insert("dodger_blue1", 33u8);
524 m.insert("dark_cyan", 36u8);
525 m.insert("light_sea_green", 37u8);
526 m.insert("deep_sky_blue2", 38u8);
527 m.insert("deep_sky_blue1", 39u8);
528 m.insert("green3", 40u8);
529 m.insert("spring_green3", 41u8);
530 m.insert("cyan3", 43u8);
531 m.insert("dark_turquoise", 44u8);
532 m.insert("turquoise2", 45u8);
533 m.insert("green1", 46u8);
534 m.insert("spring_green2", 47u8);
535 m.insert("spring_green1", 48u8);
536 m.insert("medium_spring_green", 49u8);
537 m.insert("cyan2", 50u8);
538 m.insert("cyan1", 51u8);
539 m.insert("purple4", 55u8);
540 m.insert("purple3", 56u8);
541 m.insert("blue_violet", 57u8);
542 m.insert("grey37", 59u8);
543 m.insert("gray37", 59u8);
544 m.insert("medium_purple4", 60u8);
545 m.insert("slate_blue3", 62u8);
546 m.insert("royal_blue1", 63u8);
547 m.insert("chartreuse4", 64u8);
548 m.insert("pale_turquoise4", 66u8);
549 m.insert("steel_blue", 67u8);
550 m.insert("steel_blue3", 68u8);
551 m.insert("cornflower_blue", 69u8);
552 m.insert("dark_sea_green4", 71u8);
553 m.insert("dark_sea_green", 71u8);
554 m.insert("cadet_blue", 73u8);
555 m.insert("sky_blue3", 74u8);
556 m.insert("chartreuse3", 76u8);
557 m.insert("sea_green3", 78u8);
558 m.insert("aquamarine3", 79u8);
559 m.insert("medium_turquoise", 80u8);
560 m.insert("steel_blue1", 81u8);
561 m.insert("sea_green2", 83u8);
562 m.insert("sea_green1", 85u8);
563 m.insert("dark_slate_gray2", 87u8);
564 m.insert("dark_red", 88u8);
565 m.insert("dark_magenta", 91u8);
566 m.insert("orange4", 94u8);
567 m.insert("light_pink4", 95u8);
568 m.insert("plum4", 96u8);
569 m.insert("medium_purple3", 98u8);
570 m.insert("slate_blue1", 99u8);
571 m.insert("wheat4", 101u8);
572 m.insert("grey53", 102u8);
573 m.insert("gray53", 102u8);
574 m.insert("light_slate_grey", 103u8);
575 m.insert("light_slate_gray", 103u8);
576 m.insert("medium_purple", 104u8);
577 m.insert("light_slate_blue", 105u8);
578 m.insert("yellow4", 106u8);
579 m.insert("dark_olive_green3", 110u8); m.insert("light_sky_blue3", 110u8);
581 m.insert("sky_blue2", 111u8);
582 m.insert("chartreuse2", 112u8);
583 m.insert("pale_green3", 114u8);
584 m.insert("dark_slate_gray3", 116u8);
585 m.insert("sky_blue1", 117u8);
586 m.insert("chartreuse1", 118u8);
587 m.insert("light_green", 120u8);
588 m.insert("aquamarine1", 122u8);
589 m.insert("dark_slate_gray1", 123u8);
590 m.insert("deep_pink4", 125u8);
591 m.insert("medium_violet_red", 126u8);
592 m.insert("dark_violet", 128u8);
593 m.insert("purple", 129u8);
594 m.insert("medium_orchid3", 133u8);
595 m.insert("medium_orchid", 134u8);
596 m.insert("dark_goldenrod", 136u8);
597 m.insert("rosy_brown", 138u8);
598 m.insert("grey63", 139u8);
599 m.insert("gray63", 139u8);
600 m.insert("medium_purple2", 140u8);
601 m.insert("medium_purple1", 141u8);
602 m.insert("dark_khaki", 143u8);
603 m.insert("navajo_white3", 144u8);
604 m.insert("grey69", 145u8);
605 m.insert("gray69", 145u8);
606 m.insert("light_steel_blue3", 146u8);
607 m.insert("light_steel_blue", 147u8);
608 m.insert("dark_olive_green2", 155u8);
609 m.insert("pale_green1", 156u8);
610 m.insert("dark_sea_green2", 157u8);
611 m.insert("pale_turquoise1", 159u8);
612 m.insert("red3", 160u8);
613 m.insert("deep_pink3", 162u8);
614 m.insert("magenta3", 164u8);
615 m.insert("dark_orange3", 166u8);
616 m.insert("indian_red", 167u8);
617 m.insert("hot_pink3", 168u8);
618 m.insert("hot_pink2", 169u8);
619 m.insert("orchid", 170u8);
620 m.insert("orange3", 172u8);
621 m.insert("light_salmon3", 173u8);
622 m.insert("light_pink3", 174u8);
623 m.insert("pink3", 175u8);
624 m.insert("plum3", 176u8);
625 m.insert("violet", 177u8);
626 m.insert("gold3", 178u8);
627 m.insert("light_goldenrod3", 179u8);
628 m.insert("tan", 180u8);
629 m.insert("misty_rose3", 181u8);
630 m.insert("thistle3", 182u8);
631 m.insert("plum2", 183u8);
632 m.insert("yellow3", 184u8);
633 m.insert("khaki3", 185u8);
634 m.insert("light_yellow3", 187u8);
635 m.insert("grey84", 188u8);
636 m.insert("gray84", 188u8);
637 m.insert("light_steel_blue1", 189u8);
638 m.insert("yellow2", 190u8);
639 m.insert("dark_olive_green1", 192u8);
640 m.insert("dark_sea_green1", 193u8);
641 m.insert("honeydew2", 194u8);
642 m.insert("light_cyan1", 195u8);
643 m.insert("red1", 196u8);
644 m.insert("deep_pink2", 197u8);
645 m.insert("deep_pink1", 199u8);
646 m.insert("magenta2", 200u8);
647 m.insert("magenta1", 201u8);
648 m.insert("orange_red1", 202u8);
649 m.insert("indian_red1", 204u8);
650 m.insert("hot_pink", 206u8);
651 m.insert("medium_orchid1", 207u8);
652 m.insert("dark_orange", 208u8);
653 m.insert("salmon1", 209u8);
654 m.insert("light_coral", 210u8);
655 m.insert("pale_violet_red1", 211u8);
656 m.insert("orchid2", 212u8);
657 m.insert("orchid1", 213u8);
658 m.insert("orange1", 214u8);
659 m.insert("sandy_brown", 215u8);
660 m.insert("light_salmon1", 216u8);
661 m.insert("light_pink1", 217u8);
662 m.insert("pink1", 218u8);
663 m.insert("plum1", 219u8);
664 m.insert("gold1", 220u8);
665 m.insert("light_goldenrod2", 222u8);
666 m.insert("navajo_white1", 223u8);
667 m.insert("misty_rose1", 224u8);
668 m.insert("thistle1", 225u8);
669 m.insert("yellow1", 226u8);
670 m.insert("light_goldenrod1", 227u8);
671 m.insert("khaki1", 228u8);
672 m.insert("wheat1", 229u8);
673 m.insert("cornsilk1", 230u8);
674 m.insert("grey100", 231u8);
675 m.insert("gray100", 231u8);
676 m.insert("grey3", 232u8);
678 m.insert("gray3", 232u8);
679 m.insert("grey7", 233u8);
680 m.insert("gray7", 233u8);
681 m.insert("grey11", 234u8);
682 m.insert("gray11", 234u8);
683 m.insert("grey15", 235u8);
684 m.insert("gray15", 235u8);
685 m.insert("grey19", 236u8);
686 m.insert("gray19", 236u8);
687 m.insert("grey23", 237u8);
688 m.insert("gray23", 237u8);
689 m.insert("grey27", 238u8);
690 m.insert("gray27", 238u8);
691 m.insert("grey30", 239u8);
692 m.insert("gray30", 239u8);
693 m.insert("grey35", 240u8);
694 m.insert("gray35", 240u8);
695 m.insert("grey39", 241u8);
696 m.insert("gray39", 241u8);
697 m.insert("grey42", 242u8);
698 m.insert("gray42", 242u8);
699 m.insert("grey46", 243u8);
700 m.insert("gray46", 243u8);
701 m.insert("grey50", 244u8);
702 m.insert("gray50", 244u8);
703 m.insert("grey54", 245u8);
704 m.insert("gray54", 245u8);
705 m.insert("grey58", 246u8);
706 m.insert("gray58", 246u8);
707 m.insert("grey62", 247u8);
708 m.insert("gray62", 247u8);
709 m.insert("grey66", 248u8);
710 m.insert("gray66", 248u8);
711 m.insert("dark_grey", 248u8);
712 m.insert("dark_gray", 248u8);
713 m.insert("grey70", 249u8);
714 m.insert("gray70", 249u8);
715 m.insert("grey74", 250u8);
716 m.insert("gray74", 250u8);
717 m.insert("light_grey", 250u8);
718 m.insert("light_gray", 250u8);
719 m.insert("grey78", 251u8);
720 m.insert("gray78", 251u8);
721 m.insert("grey82", 252u8);
722 m.insert("gray82", 252u8);
723 m.insert("grey85", 253u8);
724 m.insert("gray85", 253u8);
725 m.insert("grey89", 254u8);
726 m.insert("gray89", 254u8);
727 m.insert("grey93", 255u8);
728 m.insert("gray93", 255u8);
729 m
730});
731
732impl Color {
733 pub fn name_to_index(name: &str) -> Option<u8> {
735 ANSI_NAME_MAP.get(name).copied()
736 }
737}
738
739#[cfg(test)]
740mod tests {
741 use super::*;
742
743 #[test]
744 fn test_default_color() {
745 let c = Color::default();
746 assert!(c.is_default());
747 }
748
749 #[test]
750 fn test_parse_red() {
751 let c = Color::parse("red").unwrap();
752 assert_eq!(c.number, Some(1));
753 }
754
755 #[test]
756 fn test_parse_hex() {
757 let c = Color::parse("#ff0000").unwrap();
758 assert_eq!(c.triplet, Some((255, 0, 0)));
759 }
760
761 #[test]
762 fn test_rgb_to_8bit_black() {
763 assert_eq!(rgb_to_8bit(0, 0, 0), 16);
764 }
765}