termsnap_lib/
colors.rs

1use alacritty_terminal::{
2    term::color::Colors as AlacrittyColors,
3    vte::ansi::{Color, NamedColor, Rgb as AlacrittyRgb},
4};
5
6use std::collections::HashMap;
7
8use crate::{Rgb, Screen};
9
10pub(crate) struct Colors {
11    colors: AlacrittyColors,
12}
13
14impl Colors {
15    pub fn to_rgb(&self, color: Color) -> Rgb {
16        let AlacrittyRgb { r, g, b } = match color {
17            Color::Named(named_color) => {
18                self.colors[named_color as usize].expect("all colors should be defined")
19            }
20            Color::Indexed(idx) => {
21                self.colors[usize::from(idx)].expect("all colors should be defined")
22            }
23            Color::Spec(rgb) => rgb,
24        };
25
26        Rgb { r, g, b }
27    }
28}
29
30impl Default for Colors {
31    /// Generate a terminal color table
32    fn default() -> Colors {
33        let mut colors = AlacrittyColors::default();
34
35        fill_named(&mut colors);
36        fill_cube(&mut colors);
37        fill_gray_ramp(&mut colors);
38
39        Colors { colors }
40    }
41}
42
43/// Fill named terminal colors with the solarized dark theme
44fn fill_named(colors: &mut AlacrittyColors) {
45    colors[NamedColor::Black as usize] = Some("#073642".parse().unwrap());
46    colors[NamedColor::Black] = Some("#073642".parse().unwrap());
47    colors[NamedColor::Red] = Some("#dc322f".parse().unwrap());
48    colors[NamedColor::Green] = Some("#859900".parse().unwrap());
49    colors[NamedColor::Yellow] = Some("#b58900".parse().unwrap());
50    colors[NamedColor::Blue] = Some("#268bd2".parse().unwrap());
51    colors[NamedColor::Magenta] = Some("#d33682".parse().unwrap());
52    colors[NamedColor::Cyan] = Some("#2aa198".parse().unwrap());
53    colors[NamedColor::White] = Some("#eee8d5".parse().unwrap());
54    colors[NamedColor::BrightBlack] = Some("#002b36".parse().unwrap());
55    colors[NamedColor::BrightRed] = Some("#cb4b16".parse().unwrap());
56    colors[NamedColor::BrightGreen] = Some("#586e75".parse().unwrap());
57    colors[NamedColor::BrightYellow] = Some("#657b83".parse().unwrap());
58    colors[NamedColor::BrightBlue] = Some("#839496".parse().unwrap());
59    colors[NamedColor::BrightMagenta] = Some("#6c71c4".parse().unwrap());
60    colors[NamedColor::BrightCyan] = Some("#93a1a1".parse().unwrap());
61    colors[NamedColor::BrightWhite] = Some("#fdf6e3".parse().unwrap());
62    colors[NamedColor::Foreground] = Some("#839496".parse().unwrap());
63    colors[NamedColor::Background] = Some("#002b36".parse().unwrap());
64    colors[NamedColor::Cursor] = Some("#839496".parse().unwrap());
65    colors[NamedColor::DimBlack] = Some("#073642".parse().unwrap());
66    colors[NamedColor::DimRed] = Some("#dc322f".parse().unwrap());
67    colors[NamedColor::DimGreen] = Some("#859900".parse().unwrap());
68    colors[NamedColor::DimYellow] = Some("#b58900".parse().unwrap());
69    colors[NamedColor::DimBlue] = Some("#268bd2".parse().unwrap());
70    colors[NamedColor::DimMagenta] = Some("#d33682".parse().unwrap());
71    colors[NamedColor::DimCyan] = Some("#2aa198".parse().unwrap());
72    colors[NamedColor::DimWhite] = Some("#eee8d5".parse().unwrap());
73    colors[NamedColor::DimForeground] = Some("#839496".parse().unwrap());
74    colors[NamedColor::BrightForeground] = Some("#839496".parse().unwrap());
75}
76
77fn fill_cube(colors: &mut AlacrittyColors) {
78    // adapted from: https://github.com/alacritty/alacritty/blob/da554e41f3a91ed6cc5db66b23bf65c58529db83/alacritty/src/display/color.rs#L91-L115
79    let mut index = 16usize;
80
81    // Build colors.
82    for r in 0..6 {
83        for g in 0..6 {
84            for b in 0..6 {
85                // Override colors 16..232 with the config (if present).
86                colors[index] = Some(AlacrittyRgb {
87                    r: if r == 0 { 0 } else { r * 40 + 55 },
88                    g: if g == 0 { 0 } else { g * 40 + 55 },
89                    b: if b == 0 { 0 } else { b * 40 + 55 },
90                });
91                index += 1;
92            }
93        }
94    }
95
96    debug_assert!(index == 232);
97}
98
99fn fill_gray_ramp(colors: &mut AlacrittyColors) {
100    // adapted from: https://github.com/alacritty/alacritty/blob/da554e41f3a91ed6cc5db66b23bf65c58529db83/alacritty/src/display/color.rs#L118-L139
101    let mut index: usize = 232;
102
103    // Build colors.
104    for i in 0..24 {
105        let value = i * 10 + 8;
106        colors[index] = Some(AlacrittyRgb {
107            r: value,
108            g: value,
109            b: value,
110        });
111        index += 1;
112    }
113
114    debug_assert!(index == 256);
115}
116
117pub(crate) fn most_common_color(screen: &Screen) -> Rgb {
118    use std::hash::{Hash, Hasher};
119
120    #[derive(PartialEq, Eq, Copy, Clone)]
121    struct Rgb_(Rgb);
122
123    impl Hash for Rgb_ {
124        fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
125            state.write_u32(
126                (u32::from(self.0.r) << 16) + (u32::from(self.0.g) << 8) + u32::from(self.0.b),
127            );
128        }
129    }
130
131    #[derive(Default)]
132    struct NoHashHasher(u64);
133
134    impl Hasher for NoHashHasher {
135        fn finish(&self) -> u64 {
136            self.0
137        }
138
139        fn write(&mut self, bytes: &[u8]) {
140            for byte in bytes {
141                self.0 <<= 8;
142                self.0 += u64::from(*byte);
143            }
144        }
145    }
146
147    let mut counts = HashMap::<Rgb_, u32, _>::with_capacity_and_hasher(
148        16,
149        std::hash::BuildHasherDefault::<NoHashHasher>::default(),
150    );
151
152    for idx in 0..screen.lines() * screen.columns() {
153        let cell = &screen.cells[usize::from(idx)];
154        let bg = &cell.bg;
155
156        *counts.entry(Rgb_(*bg)).or_insert(0) += 1;
157    }
158
159    counts
160        .iter()
161        .max_by_key(|(_, count)| *count)
162        .map(|(k, _)| k.0)
163        // counts can be empty for 0x0 screens
164        .unwrap_or(Rgb { r: 0, g: 0, b: 0 })
165}