1 /** Terminal color composing.
10 #include "terminal/color.h"
11 #include "terminal/draw.h"
12 #include "util/color.h"
13 #include "util/error.h"
16 unsigned char r
, g
, b
;
20 #include "terminal/palette.inc"
22 struct rgb_cache_entry
{
29 color_distance(const struct rgb
*c1
, const struct rgb
*c2
)
31 int r
= c1
->r
- c2
->r
;
32 int g
= c1
->g
- c2
->g
;
33 int b
= c1
->b
- c2
->b
;
35 return (3 * r
* r
) + (4 * g
* g
) + (2 * b
* b
);
38 #define RED_COLOR_MASK 0x00FF0000
39 #define GREEN_COLOR_MASK 0x0000FF00
40 #define BLUE_COLOR_MASK 0x000000FF
42 #define RED_COLOR(color) (((color) & RED_COLOR_MASK) >> 16)
43 #define GREEN_COLOR(color) (((color) & GREEN_COLOR_MASK) >> 8)
44 #define BLUE_COLOR(color) (((color) & BLUE_COLOR_MASK) >> 0)
46 #define RED(color) (RED_COLOR(color) << 3)
47 #define GREEN(color) (GREEN_COLOR(color) << 2)
48 #define BLUE(color) (BLUE_COLOR(color) << 0)
50 #define RGBCOLOR(color) (RED(color) + GREEN(color) + BLUE(color))
52 #define RGB_HASH_SIZE 4096
53 #define HASH_RGB(color, l) ((RGBCOLOR(color) + (l)) & (RGB_HASH_SIZE - 1))
55 /** Initialize a rgb struct from a color_T
57 #define INIT_RGB(color) \
58 { RED_COLOR(color), GREEN_COLOR(color), BLUE_COLOR(color) }
60 /** Locates the nearest terminal color. */
61 static inline unsigned char
62 get_color(color_T color
, const struct rgb
*palette
, int level
)
64 static struct rgb_cache_entry cache
[RGB_HASH_SIZE
];
65 struct rgb_cache_entry
*rgb_cache
= &cache
[HASH_RGB(color
, level
)];
67 /* Uninitialized cache entries have level set to zero. */
68 if (rgb_cache
->level
== 0
69 || rgb_cache
->level
!= level
70 || rgb_cache
->rgb
!= color
) {
71 struct rgb rgb
= INIT_RGB(color
);
72 unsigned char nearest_color
= 0;
73 int min_dist
= color_distance(&rgb
, &palette
[0]);
78 INTERNAL("get_color() called with @level <= 0");
81 for (i
= 1; i
< level
; i
++) {
82 int dist
= color_distance(&rgb
, &palette
[i
]);
84 if (dist
< min_dist
) {
90 rgb_cache
->color
= nearest_color
;
91 rgb_cache
->level
= level
;
92 rgb_cache
->rgb
= color
;
95 return rgb_cache
->color
;
110 #undef RED_COLOR_MASK
111 #undef GREEN_COLOR_MASK
112 #undef BLUE_COLOR_MASK
114 /** Controls what color ranges to use when setting the terminal color.
115 * @todo TODO: Part of the 256 color palette is gray scale, maybe we
116 * could experiment with a grayscale mode. ;) --jonas */
121 PALETTE_RANGES
, /* XXX: Keep last */
124 struct color_mode_info
{
125 const struct rgb
*palette
;
130 } palette_range
[PALETTE_RANGES
];
133 static const struct color_mode_info color_mode_16
= {
136 /* PALETTE_FULL */ { 8, 16 },
137 /* PALETTE_HALF */ { 8, 8 },
141 #ifdef CONFIG_88_COLORS
142 static const struct color_mode_info color_mode_88
= {
145 /* PALETTE_FULL */ { 88, 88 },
146 /* PALETTE_HALF */ { 88, 44 },
151 #ifdef CONFIG_256_COLORS
152 static const struct color_mode_info color_mode_256
= {
155 /* PALETTE_FULL */ { 256, 256 },
156 /* PALETTE_HALF */ { 256, 128 },
161 static const struct color_mode_info
*const color_modes
[] = {
162 /* COLOR_MODE_MONO */ &color_mode_16
,
163 /* COLOR_MODE_16 */ &color_mode_16
,
164 #ifdef CONFIG_88_COLORS
165 /* COLOR_MODE_88 */ &color_mode_88
,
167 /* COLOR_MODE_88 */ &color_mode_16
,
169 #ifdef CONFIG_256_COLORS
170 /* COLOR_MODE_256 */ &color_mode_256
,
172 /* COLOR_MODE_256 */ &color_mode_16
,
174 /* @set_term_color reads @color_modes[COLOR_MODE_TRUE_COLOR]
175 * only if CONFIG_TRUE_COLOR is not defined. */
176 /* COLOR_MODE_TRUE_COLOR */ &color_mode_16
,
178 /** Get a compile-time error if the ::color_modes array has the wrong
180 typedef int assert_enough_color_modes
[
181 (sizeof(color_modes
) / sizeof(color_modes
[0]) == COLOR_MODES
)
184 /* Colors values used in the foreground color table:
186 * 0 == black 8 == darkgrey (brightblack ;)
187 * 1 == red 9 == brightred
188 * 2 == green 10 == brightgreen
189 * 3 == brown 11 == brightyellow
190 * 4 == blue 12 == brightblue
191 * 5 == magenta 13 == brightmagenta
192 * 6 == cyan 14 == brightcyan
193 * 7 == white 15 == brightwhite
195 * Bright colors will be rendered bold. */
197 /** Map foreground colors to more visible ones on various backgrounds.
198 * Use like: fg = fg_color[fg][bg];
200 * This table is based mostly on wild guesses of mine. Feel free to
201 * correct it. --pasky */
202 static const unsigned char fg_color
[16][8] = {
203 /* bk r gr br bl m c w */
206 { 7, 0, 0, 0, 7, 0, 0, 0 },
208 { 1, 9, 1, 9, 9, 9, 1, 1 },
210 { 2, 2, 10, 2, 2, 2, 10, 10 },
212 { 3, 11, 3, 11, 3, 11, 3, 3 },
214 { 12, 12, 6, 4, 12, 6, 4, 4 },
216 { 5, 13, 5, 13, 13, 13, 5, 5 },
218 { 6, 6, 14, 6, 6, 6, 14, 14 },
220 { 7, 7, 0, 7, 7, 7, 0, 0 }, /* Don't s/0/8/, messy --pasky */
222 { 15, 15, 8, 15, 15, 15, 8, 8 },
224 { 9, 9, 1, 9, 9, 9, 1, 9 }, /* I insist on 7->9 --pasky */
225 /* 10 (brightgreen) */
226 { 10, 10, 10, 10, 10, 10, 10, 10 },
227 /* 11 (brightyellow) */
228 { 11, 11, 11, 11, 11, 11, 11, 11 },
229 /* 12 (brightblue) */
230 { 12, 12, 6, 4, 6, 6, 4, 12 },
231 /* 13 (brightmagenta) */
232 { 13, 13, 5, 13, 13, 13, 5, 5 },
233 /* 14 (brightcyan) */
234 { 14, 14, 14, 14, 14, 14, 14, 14 },
235 /* 15 (brightwhite) */
236 { 15, 15, 15, 15, 15, 15, 15, 15 },
239 /* When determining whether to use negative image we make the most significant
240 * be least significant. */
241 #define CMPCODE(c) (((c) << 1 | (c) >> 2) & TERM_COLOR_MASK)
242 #define use_inverse(bg, fg) CMPCODE(fg & TERM_COLOR_MASK) < CMPCODE(bg)
244 NONSTATIC_INLINE
void
245 set_term_color16(struct screen_char
*schar
, enum color_flags flags
,
246 unsigned char fg
, unsigned char bg
)
248 /* Adjusts the foreground color to be more visible. */
249 if (flags
& COLOR_INCREASE_CONTRAST
) {
250 fg
= fg_color
[fg
][bg
];
253 /* Add various color enhancement based on the attributes. */
255 if (schar
->attr
& SCREEN_ATTR_ITALIC
)
258 if (schar
->attr
& SCREEN_ATTR_BOLD
)
259 fg
|= SCREEN_ATTR_BOLD
;
261 if ((schar
->attr
& SCREEN_ATTR_UNDERLINE
)
262 && (flags
& COLOR_ENHANCE_UNDERLINE
)) {
263 fg
|= SCREEN_ATTR_BOLD
;
268 /* Adjusts the foreground color to be more visible. */
269 if ((flags
& COLOR_INCREASE_CONTRAST
)
270 || (bg
== fg
&& (flags
& COLOR_ENSURE_CONTRAST
))) {
271 if (flags
& COLOR_ENSURE_INVERTED_CONTRAST
) {
272 unsigned char contrastbg
= fg_color
[fg
][bg
];
277 fg
= fg_color
[fg
][bg
];
281 if (fg
& SCREEN_ATTR_BOLD
) {
282 schar
->attr
|= SCREEN_ATTR_BOLD
;
285 if (use_inverse(bg
, fg
)) {
286 schar
->attr
|= SCREEN_ATTR_STANDOUT
;
288 schar
->color
[0] = (bg
<< 4 | fg
);
292 set_term_color(struct screen_char
*schar
, struct color_pair
*pair
,
293 enum color_flags flags
, enum color_mode color_mode
)
295 const struct color_mode_info
*mode
;
296 enum palette_range palette_range
= PALETTE_FULL
;
297 unsigned char fg
, bg
;
299 assert(color_mode
>= COLOR_MODE_DUMP
&& color_mode
< COLOR_MODES
);
301 /* Options for the various color modes. */
302 switch (color_mode
) {
304 case COLOR_MODE_MONO
:
305 /* TODO: A better way if possible to find out whether to
306 * inverse the fore- and backgroundcolor. Else figure out what:
308 * CMPCODE(c) (((c) << 1 | (c) >> 2) & TERM_COLOR_MASK)
310 * mean. :) --jonas */
312 /* Decrease the range of the 16 palette to not include
314 if (flags
& COLOR_DECREASE_LIGHTNESS
) {
315 palette_range
= PALETTE_HALF
;
316 schar
->attr
|= SCREEN_ATTR_STANDOUT
;
321 /* If the desired color mode was not compiled in,
324 /* Decrease the range of the 16 palette to not include
326 if (flags
& COLOR_DECREASE_LIGHTNESS
)
327 palette_range
= PALETTE_HALF
;
329 #if defined(CONFIG_88_COLORS) || defined(CONFIG_256_COLORS)
330 #ifdef CONFIG_88_COLORS
333 #ifdef CONFIG_256_COLORS
336 /* TODO: Handle decrease lightness by converting to
337 * hue-lightness-saturation color model */
340 #ifdef CONFIG_TRUE_COLOR
341 case COLOR_MODE_TRUE_COLOR
:
342 /* TODO: make it better */
343 if (pair
->foreground
== pair
->background
&& (flags
& COLOR_ENSURE_CONTRAST
)) {
344 if (flags
& COLOR_ENSURE_INVERTED_CONTRAST
) {
345 pair
->background
= (pair
->foreground
== 0) ? 0xffffff : 0;
347 pair
->foreground
= (pair
->background
== 0) ? 0xffffff : 0;
350 schar
->color
[0] = (pair
->foreground
>> 16) & 255; /* r */
351 schar
->color
[1] = (pair
->foreground
>> 8) & 255; /* g */
352 schar
->color
[2] = pair
->foreground
& 255; /* b */
353 schar
->color
[3] = (pair
->background
>> 16) & 255; /* r */
354 schar
->color
[4] = (pair
->background
>> 8) & 255; /* g */
355 schar
->color
[5] = pair
->background
& 255; /* b */
358 case COLOR_MODE_DUMP
:
362 /* This is caught by the assert() above. */
368 mode
= color_modes
[color_mode
];
369 fg
= get_color(pair
->foreground
, mode
->palette
, mode
->palette_range
[palette_range
].fg
);
370 bg
= get_color(pair
->background
, mode
->palette
, mode
->palette_range
[palette_range
].bg
);
372 switch (color_mode
) {
374 case COLOR_MODE_DUMP
:
375 INTERNAL("Bad color mode, it should _never_ occur here.");
378 #if defined(CONFIG_88_COLORS) || defined(CONFIG_256_COLORS)
379 #ifdef CONFIG_88_COLORS
382 #ifdef CONFIG_256_COLORS
385 /* Adjusts the foreground color to be more visible. */
386 /* TODO: Be smarter! Here we just choose either black or white
387 * ANSI color to make sure the color is visible. Pasky
388 * mentioned maybe calculating a distance and choosing some
389 * intermediate color. --jonas */
390 /* TODO: Maybe also do something to honour the increase_contrast
392 if (bg
== fg
&& (flags
& COLOR_ENSURE_CONTRAST
)) {
393 if (flags
& COLOR_ENSURE_INVERTED_CONTRAST
) {
394 bg
= (fg
== 0) ? 15 : 0;
396 fg
= (bg
== 0) ? 15 : 0;
400 TERM_COLOR_FOREGROUND_256(schar
->color
) = fg
;
401 TERM_COLOR_BACKGROUND_256(schar
->color
) = bg
;
404 #ifdef CONFIG_TRUE_COLOR
405 case COLOR_MODE_TRUE_COLOR
:
409 /* If the desired color mode was not compiled in,
411 case COLOR_MODE_MONO
:
413 set_term_color16(schar
, flags
, fg
, bg
);