xref: /openbsd-src/usr.bin/tmux/colour.c (revision fb8aa7497fded39583f40e800732f9c046411717)
1 /* $OpenBSD: colour.c,v 1.13 2016/06/06 10:12:58 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 colors (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);
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);
87 }
88 
89 /* Set grid cell foreground colour. */
90 void
91 colour_set_fg(struct grid_cell *gc, int c)
92 {
93 	if (c & 0x100)
94 		gc->flags |= GRID_FLAG_FG256;
95 	gc->fg = c;
96 }
97 
98 /* Set grid cell background colour. */
99 void
100 colour_set_bg(struct grid_cell *gc, int c)
101 {
102 	if (c & 0x100)
103 		gc->flags |= GRID_FLAG_BG256;
104 	gc->bg = c;
105 }
106 
107 /* Convert colour to a string. */
108 const char *
109 colour_tostring(int c)
110 {
111 	static char	s[32];
112 
113 	if (c & 0x100) {
114 		xsnprintf(s, sizeof s, "colour%d", c & ~0x100);
115 		return (s);
116 	}
117 
118 	switch (c) {
119 	case 0:
120 		return ("black");
121 	case 1:
122 		return ("red");
123 	case 2:
124 		return ("green");
125 	case 3:
126 		return ("yellow");
127 	case 4:
128 		return ("blue");
129 	case 5:
130 		return ("magenta");
131 	case 6:
132 		return ("cyan");
133 	case 7:
134 		return ("white");
135 	case 8:
136 		return ("default");
137 	case 90:
138 		return ("brightblack");
139 	case 91:
140 		return ("brightred");
141 	case 92:
142 		return ("brightgreen");
143 	case 93:
144 		return ("brightyellow");
145 	case 94:
146 		return ("brightblue");
147 	case 95:
148 		return ("brightmagenta");
149 	case 96:
150 		return ("brightcyan");
151 	case 97:
152 		return ("brightwhite");
153 	}
154 	return (NULL);
155 }
156 
157 /* Convert colour from string. */
158 int
159 colour_fromstring(const char *s)
160 {
161 	const char	*errstr;
162 	const char	*cp;
163 	int		 n;
164 	u_char		 r, g, b;
165 
166 	if (*s == '#' && strlen(s) == 7) {
167 		for (cp = s + 1; isxdigit((u_char) *cp); cp++)
168 			;
169 		if (*cp != '\0')
170 			return (-1);
171 		n = sscanf(s + 1, "%2hhx%2hhx%2hhx", &r, &g, &b);
172 		if (n != 3)
173 			return (-1);
174 		return (colour_find_rgb(r, g, b) | 0x100);
175 	}
176 
177 	if (strncasecmp(s, "colour", (sizeof "colour") - 1) == 0) {
178 		n = strtonum(s + (sizeof "colour") - 1, 0, 255, &errstr);
179 		if (errstr != NULL)
180 			return (-1);
181 		return (n | 0x100);
182 	}
183 
184 	if (strcasecmp(s, "black") == 0 || strcmp(s, "0") == 0)
185 		return (0);
186 	if (strcasecmp(s, "red") == 0 || strcmp(s, "1") == 0)
187 		return (1);
188 	if (strcasecmp(s, "green") == 0 || strcmp(s, "2") == 0)
189 		return (2);
190 	if (strcasecmp(s, "yellow") == 0 || strcmp(s, "3") == 0)
191 		return (3);
192 	if (strcasecmp(s, "blue") == 0 || strcmp(s, "4") == 0)
193 		return (4);
194 	if (strcasecmp(s, "magenta") == 0 || strcmp(s, "5") == 0)
195 		return (5);
196 	if (strcasecmp(s, "cyan") == 0 || strcmp(s, "6") == 0)
197 		return (6);
198 	if (strcasecmp(s, "white") == 0 || strcmp(s, "7") == 0)
199 		return (7);
200 	if (strcasecmp(s, "default") == 0 || strcmp(s, "8") == 0)
201 		return (8);
202 	if (strcasecmp(s, "brightblack") == 0 || strcmp(s, "90") == 0)
203 		return (90);
204 	if (strcasecmp(s, "brightred") == 0 || strcmp(s, "91") == 0)
205 		return (91);
206 	if (strcasecmp(s, "brightgreen") == 0 || strcmp(s, "92") == 0)
207 		return (92);
208 	if (strcasecmp(s, "brightyellow") == 0 || strcmp(s, "93") == 0)
209 		return (93);
210 	if (strcasecmp(s, "brightblue") == 0 || strcmp(s, "94") == 0)
211 		return (94);
212 	if (strcasecmp(s, "brightmagenta") == 0 || strcmp(s, "95") == 0)
213 		return (95);
214 	if (strcasecmp(s, "brightcyan") == 0 || strcmp(s, "96") == 0)
215 		return (96);
216 	if (strcasecmp(s, "brightwhite") == 0 || strcmp(s, "97") == 0)
217 		return (97);
218 	return (-1);
219 }
220 
221 /* Convert 256 colour palette to 16. */
222 u_char
223 colour_256to16(u_char c)
224 {
225 	static const u_char table[256] = {
226 		 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15,
227 		 0,  4,  4,  4, 12, 12,  2,  6,  4,  4, 12, 12,  2,  2,  6,  4,
228 		12, 12,  2,  2,  2,  6, 12, 12, 10, 10, 10, 10, 14, 12, 10, 10,
229 		10, 10, 10, 14,  1,  5,  4,  4, 12, 12,  3,  8,  4,  4, 12, 12,
230 		 2,  2,  6,  4, 12, 12,  2,  2,  2,  6, 12, 12, 10, 10, 10, 10,
231 		14, 12, 10, 10, 10, 10, 10, 14,  1,  1,  5,  4, 12, 12,  1,  1,
232 		 5,  4, 12, 12,  3,  3,  8,  4, 12, 12,  2,  2,  2,  6, 12, 12,
233 		10, 10, 10, 10, 14, 12, 10, 10, 10, 10, 10, 14,  1,  1,  1,  5,
234 		12, 12,  1,  1,  1,  5, 12, 12,  1,  1,  1,  5, 12, 12,  3,  3,
235 		 3,  7, 12, 12, 10, 10, 10, 10, 14, 12, 10, 10, 10, 10, 10, 14,
236 		 9,  9,  9,  9, 13, 12,  9,  9,  9,  9, 13, 12,  9,  9,  9,  9,
237 		13, 12,  9,  9,  9,  9, 13, 12, 11, 11, 11, 11,  7, 12, 10, 10,
238 		10, 10, 10, 14,  9,  9,  9,  9,  9, 13,  9,  9,  9,  9,  9, 13,
239 		 9,  9,  9,  9,  9, 13,  9,  9,  9,  9,  9, 13,  9,  9,  9,  9,
240 		 9, 13, 11, 11, 11, 11, 11, 15,  0,  0,  0,  0,  0,  0,  8,  8,
241 		 8,  8,  8,  8,  7,  7,  7,  7,  7,  7, 15, 15, 15, 15, 15, 15
242 	};
243 
244 	return (table[c]);
245 }
246