xref: /openbsd-src/usr.bin/tmux/colour.c (revision ae3cb403620ab940fbaabb3055fac045a63d56b7)
1 /* $OpenBSD: colour.c,v 1.15 2017/03/24 07:14:27 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 90:
145 		return ("brightblack");
146 	case 91:
147 		return ("brightred");
148 	case 92:
149 		return ("brightgreen");
150 	case 93:
151 		return ("brightyellow");
152 	case 94:
153 		return ("brightblue");
154 	case 95:
155 		return ("brightmagenta");
156 	case 96:
157 		return ("brightcyan");
158 	case 97:
159 		return ("brightwhite");
160 	}
161 	return (NULL);
162 }
163 
164 /* Convert colour from string. */
165 int
166 colour_fromstring(const char *s)
167 {
168 	const char	*errstr;
169 	const char	*cp;
170 	int		 n;
171 	u_char		 r, g, b;
172 
173 	if (*s == '#' && strlen(s) == 7) {
174 		for (cp = s + 1; isxdigit((u_char) *cp); cp++)
175 			;
176 		if (*cp != '\0')
177 			return (-1);
178 		n = sscanf(s + 1, "%2hhx%2hhx%2hhx", &r, &g, &b);
179 		if (n != 3)
180 			return (-1);
181 		return (colour_join_rgb(r, g, b));
182 	}
183 
184 	if (strncasecmp(s, "colour", (sizeof "colour") - 1) == 0) {
185 		n = strtonum(s + (sizeof "colour") - 1, 0, 255, &errstr);
186 		if (errstr != NULL)
187 			return (-1);
188 		return (n | COLOUR_FLAG_256);
189 	}
190 
191 	if (strcasecmp(s, "black") == 0 || strcmp(s, "0") == 0)
192 		return (0);
193 	if (strcasecmp(s, "red") == 0 || strcmp(s, "1") == 0)
194 		return (1);
195 	if (strcasecmp(s, "green") == 0 || strcmp(s, "2") == 0)
196 		return (2);
197 	if (strcasecmp(s, "yellow") == 0 || strcmp(s, "3") == 0)
198 		return (3);
199 	if (strcasecmp(s, "blue") == 0 || strcmp(s, "4") == 0)
200 		return (4);
201 	if (strcasecmp(s, "magenta") == 0 || strcmp(s, "5") == 0)
202 		return (5);
203 	if (strcasecmp(s, "cyan") == 0 || strcmp(s, "6") == 0)
204 		return (6);
205 	if (strcasecmp(s, "white") == 0 || strcmp(s, "7") == 0)
206 		return (7);
207 	if (strcasecmp(s, "default") == 0 || strcmp(s, "8") == 0)
208 		return (8);
209 	if (strcasecmp(s, "brightblack") == 0 || strcmp(s, "90") == 0)
210 		return (90);
211 	if (strcasecmp(s, "brightred") == 0 || strcmp(s, "91") == 0)
212 		return (91);
213 	if (strcasecmp(s, "brightgreen") == 0 || strcmp(s, "92") == 0)
214 		return (92);
215 	if (strcasecmp(s, "brightyellow") == 0 || strcmp(s, "93") == 0)
216 		return (93);
217 	if (strcasecmp(s, "brightblue") == 0 || strcmp(s, "94") == 0)
218 		return (94);
219 	if (strcasecmp(s, "brightmagenta") == 0 || strcmp(s, "95") == 0)
220 		return (95);
221 	if (strcasecmp(s, "brightcyan") == 0 || strcmp(s, "96") == 0)
222 		return (96);
223 	if (strcasecmp(s, "brightwhite") == 0 || strcmp(s, "97") == 0)
224 		return (97);
225 	return (-1);
226 }
227 
228 /* Convert 256 colour palette to 16. */
229 u_char
230 colour_256to16(u_char c)
231 {
232 	static const u_char table[256] = {
233 		 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15,
234 		 0,  4,  4,  4, 12, 12,  2,  6,  4,  4, 12, 12,  2,  2,  6,  4,
235 		12, 12,  2,  2,  2,  6, 12, 12, 10, 10, 10, 10, 14, 12, 10, 10,
236 		10, 10, 10, 14,  1,  5,  4,  4, 12, 12,  3,  8,  4,  4, 12, 12,
237 		 2,  2,  6,  4, 12, 12,  2,  2,  2,  6, 12, 12, 10, 10, 10, 10,
238 		14, 12, 10, 10, 10, 10, 10, 14,  1,  1,  5,  4, 12, 12,  1,  1,
239 		 5,  4, 12, 12,  3,  3,  8,  4, 12, 12,  2,  2,  2,  6, 12, 12,
240 		10, 10, 10, 10, 14, 12, 10, 10, 10, 10, 10, 14,  1,  1,  1,  5,
241 		12, 12,  1,  1,  1,  5, 12, 12,  1,  1,  1,  5, 12, 12,  3,  3,
242 		 3,  7, 12, 12, 10, 10, 10, 10, 14, 12, 10, 10, 10, 10, 10, 14,
243 		 9,  9,  9,  9, 13, 12,  9,  9,  9,  9, 13, 12,  9,  9,  9,  9,
244 		13, 12,  9,  9,  9,  9, 13, 12, 11, 11, 11, 11,  7, 12, 10, 10,
245 		10, 10, 10, 14,  9,  9,  9,  9,  9, 13,  9,  9,  9,  9,  9, 13,
246 		 9,  9,  9,  9,  9, 13,  9,  9,  9,  9,  9, 13,  9,  9,  9,  9,
247 		 9, 13, 11, 11, 11, 11, 11, 15,  0,  0,  0,  0,  0,  0,  8,  8,
248 		 8,  8,  8,  8,  7,  7,  7,  7,  7,  7, 15, 15, 15, 15, 15, 15
249 	};
250 
251 	return (table[c]);
252 }
253