xref: /openbsd-src/usr.bin/tmux/colour.c (revision 1a8dbaac879b9f3335ad7fb25429ce63ac1d6bac)
1 /* $OpenBSD: colour.c,v 1.19 2020/08/25 11:35:32 nicm Exp $ */
2 
3 /*
4  * Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com>
5  * Copyright (c) 2016 Avi Halachmi <avihpit@yahoo.com>
6  *
7  * Permission to use, copy, modify, and distribute this software for any
8  * purpose with or without fee is hereby granted, provided that the above
9  * copyright notice and this permission notice appear in all copies.
10  *
11  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15  * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
16  * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
17  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18  */
19 
20 #include <sys/types.h>
21 
22 #include <ctype.h>
23 #include <stdlib.h>
24 #include <string.h>
25 
26 #include "tmux.h"
27 
28 static int
29 colour_dist_sq(int R, int G, int B, int r, int g, int b)
30 {
31 	return ((R - r) * (R - r) + (G - g) * (G - g) + (B - b) * (B - b));
32 }
33 
34 static int
35 colour_to_6cube(int v)
36 {
37 	if (v < 48)
38 		return (0);
39 	if (v < 114)
40 		return (1);
41 	return ((v - 35) / 40);
42 }
43 
44 /*
45  * Convert an RGB triplet to the xterm(1) 256 colour palette.
46  *
47  * xterm provides a 6x6x6 colour cube (16 - 231) and 24 greys (232 - 255). We
48  * map our RGB colour to the closest in the cube, also work out the closest
49  * grey, and use the nearest of the two.
50  *
51  * Note that the xterm has much lower resolution for darker colours (they are
52  * not evenly spread out), so our 6 levels are not evenly spread: 0x0, 0x5f
53  * (95), 0x87 (135), 0xaf (175), 0xd7 (215) and 0xff (255). Greys are more
54  * evenly spread (8, 18, 28 ... 238).
55  */
56 int
57 colour_find_rgb(u_char r, u_char g, u_char b)
58 {
59 	static const int	q2c[6] = { 0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff };
60 	int			qr, qg, qb, cr, cg, cb, d, idx;
61 	int			grey_avg, grey_idx, grey;
62 
63 	/* Map RGB to 6x6x6 cube. */
64 	qr = colour_to_6cube(r); cr = q2c[qr];
65 	qg = colour_to_6cube(g); cg = q2c[qg];
66 	qb = colour_to_6cube(b); cb = q2c[qb];
67 
68 	/* If we have hit the colour exactly, return early. */
69 	if (cr == r && cg == g && cb == b)
70 		return ((16 + (36 * qr) + (6 * qg) + qb) | COLOUR_FLAG_256);
71 
72 	/* Work out the closest grey (average of RGB). */
73 	grey_avg = (r + g + b) / 3;
74 	if (grey_avg > 238)
75 		grey_idx = 23;
76 	else
77 		grey_idx = (grey_avg - 3) / 10;
78 	grey = 8 + (10 * grey_idx);
79 
80 	/* Is grey or 6x6x6 colour closest? */
81 	d = colour_dist_sq(cr, cg, cb, r, g, b);
82 	if (colour_dist_sq(grey, grey, grey, r, g, b) < d)
83 		idx = 232 + grey_idx;
84 	else
85 		idx = 16 + (36 * qr) + (6 * qg) + qb;
86 	return (idx | COLOUR_FLAG_256);
87 }
88 
89 /* Join RGB into a colour. */
90 int
91 colour_join_rgb(u_char r, u_char g, u_char b)
92 {
93 	return ((((int)((r) & 0xff)) << 16) |
94 	    (((int)((g) & 0xff)) << 8) |
95 	    (((int)((b) & 0xff))) | COLOUR_FLAG_RGB);
96 }
97 
98 /* Split colour into RGB. */
99 void
100 colour_split_rgb(int c, u_char *r, u_char *g, u_char *b)
101 {
102 	*r = (c >> 16) & 0xff;
103 	*g = (c >> 8) & 0xff;
104 	*b = c & 0xff;
105 }
106 
107 /* Convert colour to a string. */
108 const char *
109 colour_tostring(int c)
110 {
111 	static char	s[32];
112 	u_char		r, g, b;
113 
114 	if (c & COLOUR_FLAG_RGB) {
115 		colour_split_rgb(c, &r, &g, &b);
116 		xsnprintf(s, sizeof s, "#%02x%02x%02x", r, g, b);
117 		return (s);
118 	}
119 
120 	if (c & COLOUR_FLAG_256) {
121 		xsnprintf(s, sizeof s, "colour%u", c & 0xff);
122 		return (s);
123 	}
124 
125 	switch (c) {
126 	case 0:
127 		return ("black");
128 	case 1:
129 		return ("red");
130 	case 2:
131 		return ("green");
132 	case 3:
133 		return ("yellow");
134 	case 4:
135 		return ("blue");
136 	case 5:
137 		return ("magenta");
138 	case 6:
139 		return ("cyan");
140 	case 7:
141 		return ("white");
142 	case 8:
143 		return ("default");
144 	case 9:
145 		return ("terminal");
146 	case 90:
147 		return ("brightblack");
148 	case 91:
149 		return ("brightred");
150 	case 92:
151 		return ("brightgreen");
152 	case 93:
153 		return ("brightyellow");
154 	case 94:
155 		return ("brightblue");
156 	case 95:
157 		return ("brightmagenta");
158 	case 96:
159 		return ("brightcyan");
160 	case 97:
161 		return ("brightwhite");
162 	}
163 	return ("invalid");
164 }
165 
166 /* Convert colour from string. */
167 int
168 colour_fromstring(const char *s)
169 {
170 	const char	*errstr;
171 	const char	*cp;
172 	int		 n;
173 	u_char		 r, g, b;
174 
175 	if (*s == '#' && strlen(s) == 7) {
176 		for (cp = s + 1; isxdigit((u_char) *cp); cp++)
177 			;
178 		if (*cp != '\0')
179 			return (-1);
180 		n = sscanf(s + 1, "%2hhx%2hhx%2hhx", &r, &g, &b);
181 		if (n != 3)
182 			return (-1);
183 		return (colour_join_rgb(r, g, b));
184 	}
185 
186 	if (strncasecmp(s, "colour", (sizeof "colour") - 1) == 0) {
187 		n = strtonum(s + (sizeof "colour") - 1, 0, 255, &errstr);
188 		if (errstr != NULL)
189 			return (-1);
190 		return (n | COLOUR_FLAG_256);
191 	}
192 	if (strncasecmp(s, "color", (sizeof "color") - 1) == 0) {
193 		n = strtonum(s + (sizeof "color") - 1, 0, 255, &errstr);
194 		if (errstr != NULL)
195 			return (-1);
196 		return (n | COLOUR_FLAG_256);
197 	}
198 
199 	if (strcasecmp(s, "default") == 0)
200 		return (8);
201 	if (strcasecmp(s, "terminal") == 0)
202 		return (9);
203 
204 	if (strcasecmp(s, "black") == 0 || strcmp(s, "0") == 0)
205 		return (0);
206 	if (strcasecmp(s, "red") == 0 || strcmp(s, "1") == 0)
207 		return (1);
208 	if (strcasecmp(s, "green") == 0 || strcmp(s, "2") == 0)
209 		return (2);
210 	if (strcasecmp(s, "yellow") == 0 || strcmp(s, "3") == 0)
211 		return (3);
212 	if (strcasecmp(s, "blue") == 0 || strcmp(s, "4") == 0)
213 		return (4);
214 	if (strcasecmp(s, "magenta") == 0 || strcmp(s, "5") == 0)
215 		return (5);
216 	if (strcasecmp(s, "cyan") == 0 || strcmp(s, "6") == 0)
217 		return (6);
218 	if (strcasecmp(s, "white") == 0 || strcmp(s, "7") == 0)
219 		return (7);
220 	if (strcasecmp(s, "brightblack") == 0 || strcmp(s, "90") == 0)
221 		return (90);
222 	if (strcasecmp(s, "brightred") == 0 || strcmp(s, "91") == 0)
223 		return (91);
224 	if (strcasecmp(s, "brightgreen") == 0 || strcmp(s, "92") == 0)
225 		return (92);
226 	if (strcasecmp(s, "brightyellow") == 0 || strcmp(s, "93") == 0)
227 		return (93);
228 	if (strcasecmp(s, "brightblue") == 0 || strcmp(s, "94") == 0)
229 		return (94);
230 	if (strcasecmp(s, "brightmagenta") == 0 || strcmp(s, "95") == 0)
231 		return (95);
232 	if (strcasecmp(s, "brightcyan") == 0 || strcmp(s, "96") == 0)
233 		return (96);
234 	if (strcasecmp(s, "brightwhite") == 0 || strcmp(s, "97") == 0)
235 		return (97);
236 	return (-1);
237 }
238 
239 /* Convert 256 colour to RGB colour. */
240 int
241 colour_256toRGB(int c)
242 {
243 	static const int table[256] = {
244 		0x000000, 0x800000, 0x008000, 0x808000,
245 		0x000080, 0x800080, 0x008080, 0xc0c0c0,
246 		0x808080, 0xff0000, 0x00ff00, 0xffff00,
247 		0x0000ff, 0xff00ff, 0x00ffff, 0xffffff,
248 		0x000000, 0x00005f, 0x000087, 0x0000af,
249 		0x0000d7, 0x0000ff, 0x005f00, 0x005f5f,
250 		0x005f87, 0x005faf, 0x005fd7, 0x005fff,
251 		0x008700, 0x00875f, 0x008787, 0x0087af,
252 		0x0087d7, 0x0087ff, 0x00af00, 0x00af5f,
253 		0x00af87, 0x00afaf, 0x00afd7, 0x00afff,
254 		0x00d700, 0x00d75f, 0x00d787, 0x00d7af,
255 		0x00d7d7, 0x00d7ff, 0x00ff00, 0x00ff5f,
256 		0x00ff87, 0x00ffaf, 0x00ffd7, 0x00ffff,
257 		0x5f0000, 0x5f005f, 0x5f0087, 0x5f00af,
258 		0x5f00d7, 0x5f00ff, 0x5f5f00, 0x5f5f5f,
259 		0x5f5f87, 0x5f5faf, 0x5f5fd7, 0x5f5fff,
260 		0x5f8700, 0x5f875f, 0x5f8787, 0x5f87af,
261 		0x5f87d7, 0x5f87ff, 0x5faf00, 0x5faf5f,
262 		0x5faf87, 0x5fafaf, 0x5fafd7, 0x5fafff,
263 		0x5fd700, 0x5fd75f, 0x5fd787, 0x5fd7af,
264 		0x5fd7d7, 0x5fd7ff, 0x5fff00, 0x5fff5f,
265 		0x5fff87, 0x5fffaf, 0x5fffd7, 0x5fffff,
266 		0x870000, 0x87005f, 0x870087, 0x8700af,
267 		0x8700d7, 0x8700ff, 0x875f00, 0x875f5f,
268 		0x875f87, 0x875faf, 0x875fd7, 0x875fff,
269 		0x878700, 0x87875f, 0x878787, 0x8787af,
270 		0x8787d7, 0x8787ff, 0x87af00, 0x87af5f,
271 		0x87af87, 0x87afaf, 0x87afd7, 0x87afff,
272 		0x87d700, 0x87d75f, 0x87d787, 0x87d7af,
273 		0x87d7d7, 0x87d7ff, 0x87ff00, 0x87ff5f,
274 		0x87ff87, 0x87ffaf, 0x87ffd7, 0x87ffff,
275 		0xaf0000, 0xaf005f, 0xaf0087, 0xaf00af,
276 		0xaf00d7, 0xaf00ff, 0xaf5f00, 0xaf5f5f,
277 		0xaf5f87, 0xaf5faf, 0xaf5fd7, 0xaf5fff,
278 		0xaf8700, 0xaf875f, 0xaf8787, 0xaf87af,
279 		0xaf87d7, 0xaf87ff, 0xafaf00, 0xafaf5f,
280 		0xafaf87, 0xafafaf, 0xafafd7, 0xafafff,
281 		0xafd700, 0xafd75f, 0xafd787, 0xafd7af,
282 		0xafd7d7, 0xafd7ff, 0xafff00, 0xafff5f,
283 		0xafff87, 0xafffaf, 0xafffd7, 0xafffff,
284 		0xd70000, 0xd7005f, 0xd70087, 0xd700af,
285 		0xd700d7, 0xd700ff, 0xd75f00, 0xd75f5f,
286 		0xd75f87, 0xd75faf, 0xd75fd7, 0xd75fff,
287 		0xd78700, 0xd7875f, 0xd78787, 0xd787af,
288 		0xd787d7, 0xd787ff, 0xd7af00, 0xd7af5f,
289 		0xd7af87, 0xd7afaf, 0xd7afd7, 0xd7afff,
290 		0xd7d700, 0xd7d75f, 0xd7d787, 0xd7d7af,
291 		0xd7d7d7, 0xd7d7ff, 0xd7ff00, 0xd7ff5f,
292 		0xd7ff87, 0xd7ffaf, 0xd7ffd7, 0xd7ffff,
293 		0xff0000, 0xff005f, 0xff0087, 0xff00af,
294 		0xff00d7, 0xff00ff, 0xff5f00, 0xff5f5f,
295 		0xff5f87, 0xff5faf, 0xff5fd7, 0xff5fff,
296 		0xff8700, 0xff875f, 0xff8787, 0xff87af,
297 		0xff87d7, 0xff87ff, 0xffaf00, 0xffaf5f,
298 		0xffaf87, 0xffafaf, 0xffafd7, 0xffafff,
299 		0xffd700, 0xffd75f, 0xffd787, 0xffd7af,
300 		0xffd7d7, 0xffd7ff, 0xffff00, 0xffff5f,
301 		0xffff87, 0xffffaf, 0xffffd7, 0xffffff,
302 		0x080808, 0x121212, 0x1c1c1c, 0x262626,
303 		0x303030, 0x3a3a3a, 0x444444, 0x4e4e4e,
304 		0x585858, 0x626262, 0x6c6c6c, 0x767676,
305 		0x808080, 0x8a8a8a, 0x949494, 0x9e9e9e,
306 		0xa8a8a8, 0xb2b2b2, 0xbcbcbc, 0xc6c6c6,
307 		0xd0d0d0, 0xdadada, 0xe4e4e4, 0xeeeeee
308 	};
309 
310 	return (table[c & 0xff] | COLOUR_FLAG_RGB);
311 }
312 
313 /* Convert 256 colour to 16 colour. */
314 int
315 colour_256to16(int c)
316 {
317 	static const char table[256] = {
318 		 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15,
319 		 0,  4,  4,  4, 12, 12,  2,  6,  4,  4, 12, 12,  2,  2,  6,  4,
320 		12, 12,  2,  2,  2,  6, 12, 12, 10, 10, 10, 10, 14, 12, 10, 10,
321 		10, 10, 10, 14,  1,  5,  4,  4, 12, 12,  3,  8,  4,  4, 12, 12,
322 		 2,  2,  6,  4, 12, 12,  2,  2,  2,  6, 12, 12, 10, 10, 10, 10,
323 		14, 12, 10, 10, 10, 10, 10, 14,  1,  1,  5,  4, 12, 12,  1,  1,
324 		 5,  4, 12, 12,  3,  3,  8,  4, 12, 12,  2,  2,  2,  6, 12, 12,
325 		10, 10, 10, 10, 14, 12, 10, 10, 10, 10, 10, 14,  1,  1,  1,  5,
326 		12, 12,  1,  1,  1,  5, 12, 12,  1,  1,  1,  5, 12, 12,  3,  3,
327 		 3,  7, 12, 12, 10, 10, 10, 10, 14, 12, 10, 10, 10, 10, 10, 14,
328 		 9,  9,  9,  9, 13, 12,  9,  9,  9,  9, 13, 12,  9,  9,  9,  9,
329 		13, 12,  9,  9,  9,  9, 13, 12, 11, 11, 11, 11,  7, 12, 10, 10,
330 		10, 10, 10, 14,  9,  9,  9,  9,  9, 13,  9,  9,  9,  9,  9, 13,
331 		 9,  9,  9,  9,  9, 13,  9,  9,  9,  9,  9, 13,  9,  9,  9,  9,
332 		 9, 13, 11, 11, 11, 11, 11, 15,  0,  0,  0,  0,  0,  0,  8,  8,
333 		 8,  8,  8,  8,  7,  7,  7,  7,  7,  7, 15, 15, 15, 15, 15, 15
334 	};
335 
336 	return (table[c & 0xff]);
337 }
338