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