colette/
colette.rs

1//! # Colette
2//!
3//! Convert between truecolor, xterm(1) 256 color and 16 color.
4//! Single-header library.
5//! Arranged from [tmux].
6//!
7//! [tmux]: https://github.com/tmux/tmux
8
9/// Convert an RGB triplet to the xterm(1) 256 color palette.
10///
11/// xterm provides a 6x6x6 color cube (16 - 231) and 24 greys (232 - 255). We
12/// map our RGB color to the closest in the cube, also work out the closest
13/// grey, and use the nearest of the two.
14///
15/// Note that the xterm has much lower resolution for darker colors (they are
16/// not evenly spread out), so our 6 levels are not evenly spread: 0x0, 0x5f
17/// (95), 0x87 (135), 0xaf (175), 0xd7 (215) and 0xff (255). Greys are more
18/// evenly spread (8, 18, 28 ... 238).
19pub fn color_rgbto256(r: u8, g: u8, b: u8) -> i32 {
20    let q2c = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff];
21
22    /* Map RGB to 6x6x6 cube. */
23    let qr = color_to_6cube(r);
24    let cr = q2c[qr as usize];
25    let qg = color_to_6cube(g);
26    let cg = q2c[qg as usize];
27    let qb = color_to_6cube(b);
28    let cb = q2c[qb as usize];
29
30    /* If we have hit the color exactly, return early. */
31    if cr == r && cg == g && cb == b {
32        return (16 + 36 * qr + 6 * qg + qb) as i32;
33    }
34
35    /* Work out the closest grey (average of RGB). */
36    let grey_avg: i32 = (((r as i64) + (g as i64) + (b as i64)) / 3) as i32;
37    let grey_idx: i32;
38    let mut grey = 0;
39
40    if grey_avg > 238 {
41        grey_idx = 23;
42    } else {
43        grey_idx = (grey_avg - 3) / 10;
44        grey = 8 + (10 * grey_idx);
45    }
46
47    let idx: i32;
48
49    /* Is grey or 6x6x6 color closest? */
50    if dist_sq(grey, grey, grey, r as i32, g as i32, b as i32)
51        < dist_sq(
52            cr as i32, cg as i32, cb as i32, r as i32, g as i32, b as i32,
53        )
54    {
55        idx = 232 + grey_idx;
56    } else {
57        idx = (16 + 36 * qr + 6 * qg + qb) as i32;
58    }
59
60    idx
61}
62
63/// Join RGB into a color.
64pub fn join_rgb(r: u8, g: u8, b: u8) -> i32 {
65    (r as i32) << 16 | (g as i32) << 8 | (b as i32)
66}
67
68/// Split color into RGB.
69pub fn split_rgb(c: i32, r: &mut u8, g: &mut u8, b: &mut u8) {
70    *r = (c >> 16 & 0xff) as u8;
71    *g = (c >> 8 & 0xff) as u8;
72    *b = (c & 0xff) as u8;
73}
74
75/// Convert 256 color to RGB color.
76pub fn color_256torgb(c: u8) -> i32 {
77    let table = [
78        0x000000, 0x800000, 0x008000, 0x808000, 0x000080, 0x800080, 0x008080, 0xc0c0c0, 0x808080,
79        0xff0000, 0x00ff00, 0xffff00, 0x0000ff, 0xff00ff, 0x00ffff, 0xffffff, 0x000000, 0x00005f,
80        0x000087, 0x0000af, 0x0000d7, 0x0000ff, 0x005f00, 0x005f5f, 0x005f87, 0x005faf, 0x005fd7,
81        0x005fff, 0x008700, 0x00875f, 0x008787, 0x0087af, 0x0087d7, 0x0087ff, 0x00af00, 0x00af5f,
82        0x00af87, 0x00afaf, 0x00afd7, 0x00afff, 0x00d700, 0x00d75f, 0x00d787, 0x00d7af, 0x00d7d7,
83        0x00d7ff, 0x00ff00, 0x00ff5f, 0x00ff87, 0x00ffaf, 0x00ffd7, 0x00ffff, 0x5f0000, 0x5f005f,
84        0x5f0087, 0x5f00af, 0x5f00d7, 0x5f00ff, 0x5f5f00, 0x5f5f5f, 0x5f5f87, 0x5f5faf, 0x5f5fd7,
85        0x5f5fff, 0x5f8700, 0x5f875f, 0x5f8787, 0x5f87af, 0x5f87d7, 0x5f87ff, 0x5faf00, 0x5faf5f,
86        0x5faf87, 0x5fafaf, 0x5fafd7, 0x5fafff, 0x5fd700, 0x5fd75f, 0x5fd787, 0x5fd7af, 0x5fd7d7,
87        0x5fd7ff, 0x5fff00, 0x5fff5f, 0x5fff87, 0x5fffaf, 0x5fffd7, 0x5fffff, 0x870000, 0x87005f,
88        0x870087, 0x8700af, 0x8700d7, 0x8700ff, 0x875f00, 0x875f5f, 0x875f87, 0x875faf, 0x875fd7,
89        0x875fff, 0x878700, 0x87875f, 0x878787, 0x8787af, 0x8787d7, 0x8787ff, 0x87af00, 0x87af5f,
90        0x87af87, 0x87afaf, 0x87afd7, 0x87afff, 0x87d700, 0x87d75f, 0x87d787, 0x87d7af, 0x87d7d7,
91        0x87d7ff, 0x87ff00, 0x87ff5f, 0x87ff87, 0x87ffaf, 0x87ffd7, 0x87ffff, 0xaf0000, 0xaf005f,
92        0xaf0087, 0xaf00af, 0xaf00d7, 0xaf00ff, 0xaf5f00, 0xaf5f5f, 0xaf5f87, 0xaf5faf, 0xaf5fd7,
93        0xaf5fff, 0xaf8700, 0xaf875f, 0xaf8787, 0xaf87af, 0xaf87d7, 0xaf87ff, 0xafaf00, 0xafaf5f,
94        0xafaf87, 0xafafaf, 0xafafd7, 0xafafff, 0xafd700, 0xafd75f, 0xafd787, 0xafd7af, 0xafd7d7,
95        0xafd7ff, 0xafff00, 0xafff5f, 0xafff87, 0xafffaf, 0xafffd7, 0xafffff, 0xd70000, 0xd7005f,
96        0xd70087, 0xd700af, 0xd700d7, 0xd700ff, 0xd75f00, 0xd75f5f, 0xd75f87, 0xd75faf, 0xd75fd7,
97        0xd75fff, 0xd78700, 0xd7875f, 0xd78787, 0xd787af, 0xd787d7, 0xd787ff, 0xd7af00, 0xd7af5f,
98        0xd7af87, 0xd7afaf, 0xd7afd7, 0xd7afff, 0xd7d700, 0xd7d75f, 0xd7d787, 0xd7d7af, 0xd7d7d7,
99        0xd7d7ff, 0xd7ff00, 0xd7ff5f, 0xd7ff87, 0xd7ffaf, 0xd7ffd7, 0xd7ffff, 0xff0000, 0xff005f,
100        0xff0087, 0xff00af, 0xff00d7, 0xff00ff, 0xff5f00, 0xff5f5f, 0xff5f87, 0xff5faf, 0xff5fd7,
101        0xff5fff, 0xff8700, 0xff875f, 0xff8787, 0xff87af, 0xff87d7, 0xff87ff, 0xffaf00, 0xffaf5f,
102        0xffaf87, 0xffafaf, 0xffafd7, 0xffafff, 0xffd700, 0xffd75f, 0xffd787, 0xffd7af, 0xffd7d7,
103        0xffd7ff, 0xffff00, 0xffff5f, 0xffff87, 0xffffaf, 0xffffd7, 0xffffff, 0x080808, 0x121212,
104        0x1c1c1c, 0x262626, 0x303030, 0x3a3a3a, 0x444444, 0x4e4e4e, 0x585858, 0x626262, 0x6c6c6c,
105        0x767676, 0x808080, 0x8a8a8a, 0x949494, 0x9e9e9e, 0xa8a8a8, 0xb2b2b2, 0xbcbcbc, 0xc6c6c6,
106        0xd0d0d0, 0xdadada, 0xe4e4e4, 0xeeeeee,
107    ];
108
109    table[c as usize]
110}
111
112/// Convert 256 color to 16 color.
113pub fn color_256to16(c: u8) -> i32 {
114    let table = [
115        0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 4, 4, 4, 12, 12, 2, 6, 4, 4, 12,
116        12, 2, 2, 6, 4, 12, 12, 2, 2, 2, 6, 12, 12, 10, 10, 10, 10, 14, 12, 10, 10, 10, 10, 10, 14,
117        1, 5, 4, 4, 12, 12, 3, 8, 4, 4, 12, 12, 2, 2, 6, 4, 12, 12, 2, 2, 2, 6, 12, 12, 10, 10, 10,
118        10, 14, 12, 10, 10, 10, 10, 10, 14, 1, 1, 5, 4, 12, 12, 1, 1, 5, 4, 12, 12, 3, 3, 8, 4, 12,
119        12, 2, 2, 2, 6, 12, 12, 10, 10, 10, 10, 14, 12, 10, 10, 10, 10, 10, 14, 1, 1, 1, 5, 12, 12,
120        1, 1, 1, 5, 12, 12, 1, 1, 1, 5, 12, 12, 3, 3, 3, 7, 12, 12, 10, 10, 10, 10, 14, 12, 10, 10,
121        10, 10, 10, 14, 9, 9, 9, 9, 13, 12, 9, 9, 9, 9, 13, 12, 9, 9, 9, 9, 13, 12, 9, 9, 9, 9, 13,
122        12, 11, 11, 11, 11, 7, 12, 10, 10, 10, 10, 10, 14, 9, 9, 9, 9, 9, 13, 9, 9, 9, 9, 9, 13, 9,
123        9, 9, 9, 9, 13, 9, 9, 9, 9, 9, 13, 9, 9, 9, 9, 9, 13, 11, 11, 11, 11, 11, 15, 0, 0, 0, 0,
124        0, 0, 8, 8, 8, 8, 8, 8, 7, 7, 7, 7, 7, 7, 15, 15, 15, 15, 15, 15,
125    ];
126
127    table[c as usize]
128}
129
130#[allow(non_snake_case)]
131fn dist_sq(R: i32, G: i32, B: i32, r: i32, g: i32, b: i32) -> i32 {
132    (R - r) * (R - r) + (G - g) * (G - g) + (B - b) * (B - b)
133}
134
135fn color_to_6cube(v: u8) -> u8 {
136    if v < 48 {
137        0
138    } else {
139        if v < 114 {
140            1
141        } else {
142            (v - 35) / 40
143        }
144    }
145}
146
147#[cfg(test)]
148mod tests {
149    use crate::*;
150
151    #[test]
152    fn test_color_rgbto256() {
153        assert_eq!(16, color_rgbto256(0, 0, 0)); // Black (0 / 16)
154        assert_eq!(231, color_rgbto256(255, 255, 255)); // White (15 / 231)
155        assert_eq!(196, color_rgbto256(255, 0, 0)); // Red (196)
156        assert_eq!(46, color_rgbto256(0, 255, 0)); // Green (46)
157        assert_eq!(21, color_rgbto256(0, 0, 255)); // Blue (21)
158        assert_eq!(251, color_rgbto256(200, 200, 200)); // Grey (232 - 255)
159        assert_eq!(251, color_rgbto256(201, 201, 201)); // Fuzzy match
160        assert_eq!(188, color_rgbto256(215, 215, 215)); // Exact match
161    }
162
163    #[test]
164    fn test_join_rgb() {
165        assert_eq!(0x000000, join_rgb(0, 0, 0));
166        assert_eq!(0xffffff, join_rgb(255, 255, 255));
167        assert_eq!(0xff0000, join_rgb(255, 0, 0));
168        assert_eq!(0x00ff00, join_rgb(0, 255, 0));
169        assert_eq!(0x0000ff, join_rgb(0, 0, 255));
170        assert_eq!(0xc8c8c8, join_rgb(200, 200, 200));
171    }
172
173    #[test]
174    fn test_split_rgb() {
175        let mut r: u8 = 0;
176        let mut g: u8 = 0;
177        let mut b: u8 = 0;
178        split_rgb(0xc8c8c8, &mut r, &mut g, &mut b);
179        assert_eq!(200, r);
180        assert_eq!(200, g);
181        assert_eq!(200, b);
182    }
183
184    #[test]
185    fn test_color_256torgb() {
186        assert_eq!(join_rgb(135, 255, 135), color_256torgb(120));
187        assert_eq!(join_rgb(215, 255, 0), color_256torgb(190));
188    }
189
190    #[test]
191    fn test_color_256to16() {
192        assert_eq!(12, color_256to16(21));
193        assert_eq!(1, color_256to16(52));
194        assert_eq!(9, color_256to16(196));
195        assert_eq!(7, color_256to16(244));
196        assert_eq!(15, color_256to16(253));
197    }
198}