Line | Branch | Exec | Source |
---|---|---|---|
1 | #include "color.h" | ||
2 | #include "util/debug.h" | ||
3 | #include "util/strtonum.h" | ||
4 | |||
5 | 37 | int32_t parse_rgb(const char *str, size_t len) | |
6 | { | ||
7 | 37 | unsigned int n3 = (len == 3); | |
8 |
2/2✓ Branch 0 (2→4) taken 24 times.
✓ Branch 1 (2→7) taken 13 times.
|
37 | if (unlikely(!n3 && len != 6)) { |
9 | return COLOR_INVALID; | ||
10 | } | ||
11 | |||
12 | unsigned int mask = 0; | ||
13 | unsigned int val = 0; | ||
14 | |||
15 |
2/2✓ Branch 0 (4→3) taken 144 times.
✓ Branch 1 (4→5) taken 24 times.
|
168 | for (size_t i = 0; i < 6; i++) { |
16 | // The right shift here produces `i / 2` if `len == 3` | ||
17 | 144 | unsigned int digit = hex_decode(str[i >> n3]); | |
18 | 144 | mask |= digit; | |
19 | 144 | val = val << 4 | digit; | |
20 | } | ||
21 | |||
22 |
2/2✓ Branch 0 (5→6) taken 21 times.
✓ Branch 1 (5→7) taken 3 times.
|
24 | return unlikely(mask & HEX_INVALID) ? COLOR_INVALID : COLOR_RGB(val); |
23 | } | ||
24 | |||
25 | // Calculate squared Euclidean distance between 2 RGB colors | ||
26 | 974 | static unsigned int color_distance ( | |
27 | uint8_t R, uint8_t G, uint8_t B, | ||
28 | uint8_t r, uint8_t g, uint8_t b | ||
29 | ) { | ||
30 | // The operands here are promoted to `int` and, since each distance | ||
31 | // is squared, the result is always positive | ||
32 | 974 | static_assert(INT_MAX >= 3 * 255 * 255); | |
33 | 974 | return ((R - r) * (R - r)) + ((G - g) * (G - g)) + ((B - b) * (B - b)); | |
34 | } | ||
35 | |||
36 | 22 | UNITTEST { | |
37 | // NOLINTBEGIN(bugprone-assert-side-effect) | ||
38 | 22 | BUG_ON(color_distance(0,0,0, 0,0,0) != 0); | |
39 | 22 | BUG_ON(color_distance(1,1,1, 1,0,1) != 1); | |
40 | 22 | BUG_ON(color_distance(100,0,0, 80,0,0) != 400); | |
41 | 22 | BUG_ON(color_distance(0,5,10, 5,0,2) != 25 + 25 + 64); | |
42 | 22 | BUG_ON(color_distance(0,0,0, 255,0,0) != 255 * 255); | |
43 | 22 | BUG_ON(color_distance(255,255,255, 0,0,0) != 255 * 255 * 3); | |
44 | 22 | BUG_ON(color_distance(0,0,0, 255,255,255) != 255 * 255 * 3); | |
45 | // NOLINTEND(bugprone-assert-side-effect) | ||
46 | 22 | } | |
47 | |||
48 | // Convert an RGB color component (0-255) to the index (0-5) of the | ||
49 | // nearest XTerm palette color stop (0, 95, 135, 175, 215, 255) | ||
50 | 1428 | static unsigned int quantize_rgb(uint8_t c) | |
51 | { | ||
52 |
2/2✓ Branch 0 (2→3) taken 831 times.
✓ Branch 1 (2→4) taken 597 times.
|
1428 | unsigned int a = (c < 80) ? MIN(c, 7) : 35; |
53 | 1428 | return (c - a) / 40; | |
54 | } | ||
55 | |||
56 | 22 | UNITTEST { | |
57 | // NOLINTBEGIN(bugprone-assert-side-effect) | ||
58 | 22 | BUG_ON(quantize_rgb(0) != 0); | |
59 | 22 | BUG_ON(quantize_rgb(46) != 0); | |
60 | 22 | BUG_ON(quantize_rgb(47) != 1); | |
61 | 22 | BUG_ON(quantize_rgb(114) != 1); | |
62 | 22 | BUG_ON(quantize_rgb(115) != 2); | |
63 | 22 | BUG_ON(quantize_rgb(170) != 3); | |
64 | 22 | BUG_ON(quantize_rgb(255) != 5); | |
65 | 22 | BUG_ON(quantize_rgb(255 - 20) != 5); | |
66 | 22 | BUG_ON(quantize_rgb(255 - 21) != 4); | |
67 | // NOLINTEND(bugprone-assert-side-effect) | ||
68 | 22 | } | |
69 | |||
70 | 410 | static uint8_t color_rgb_to_256(uint32_t color, bool *exact) | |
71 | { | ||
72 | 410 | BUG_ON(!color_is_rgb(color)); | |
73 | 410 | uint8_t r = color_r(color); | |
74 | 410 | uint8_t g = color_g(color); | |
75 | 410 | uint8_t b = color_b(color); | |
76 | |||
77 | // Calculate closest 6x6x6 RGB cube color | ||
78 | 410 | static const uint8_t color_stops[6] = {0, 95, 135, 175, 215, 255}; | |
79 | 410 | uint8_t r_idx = quantize_rgb(r); | |
80 | 410 | uint8_t g_idx = quantize_rgb(g); | |
81 | 410 | uint8_t b_idx = quantize_rgb(b); | |
82 | 410 | uint8_t r_stop = color_stops[r_idx]; | |
83 | 410 | uint8_t g_stop = color_stops[g_idx]; | |
84 | 410 | uint8_t b_stop = color_stops[b_idx]; | |
85 | |||
86 | // Calculate closest gray | ||
87 | 410 | uint8_t gray_avg = (r + g + b) / 3; | |
88 | 410 | uint8_t gray_idx = (MIN(gray_avg, 238) - MIN(gray_avg, 3)) / 10; | |
89 | 410 | uint8_t gray = 8 + (10 * gray_idx); | |
90 | |||
91 | // Calculate (squared) color distances | ||
92 | 410 | unsigned int rgb_distance = color_distance(r_stop, g_stop, b_stop, r, g, b); | |
93 | 410 | unsigned int gray_distance = color_distance(gray, gray, gray, r, g, b); | |
94 | |||
95 | // Return index of nearest palette color (RGB: 16-231; gray: 232-255) | ||
96 | 410 | bool use_gray = (gray_distance < rgb_distance); | |
97 |
2/2✓ Branch 0 (4→5) taken 110 times.
✓ Branch 1 (4→6) taken 300 times.
|
410 | *exact = use_gray ? !gray_distance : !rgb_distance; |
98 |
2/2✓ Branch 0 (7→8) taken 110 times.
✓ Branch 1 (7→9) taken 300 times.
|
410 | return use_gray ? 232 + gray_idx : 16 + (36 * r_idx) + (6 * g_idx) + b_idx; |
99 | } | ||
100 | |||
101 | 533 | static uint8_t color_256_to_16(uint8_t color) | |
102 | { | ||
103 | 533 | enum { | |
104 | k = COLOR_BLACK, | ||
105 | r = COLOR_RED, | ||
106 | g = COLOR_GREEN, | ||
107 | y = COLOR_YELLOW, | ||
108 | b = COLOR_BLUE, | ||
109 | m = COLOR_MAGENTA, | ||
110 | c = COLOR_CYAN, | ||
111 | a = COLOR_GRAY, | ||
112 | A = COLOR_DARKGRAY, | ||
113 | R = COLOR_LIGHTRED, | ||
114 | G = COLOR_LIGHTGREEN, | ||
115 | Y = COLOR_LIGHTYELLOW, | ||
116 | B = COLOR_LIGHTBLUE, | ||
117 | M = COLOR_LIGHTMAGENTA, | ||
118 | C = COLOR_LIGHTCYAN, | ||
119 | W = COLOR_WHITE | ||
120 | }; | ||
121 | |||
122 | 533 | static const uint8_t table[256] = { | |
123 | k, r, g, y, b, m, c, a, A, R, G, Y, B, M, C, W, // 0...15 | ||
124 | k, b, b, b, B, B, g, c, b, b, B, B, g, g, c, b, // 16...31 | ||
125 | B, B, g, g, g, c, B, B, G, G, G, C, C, B, G, G, // 32...47 | ||
126 | G, G, C, C, r, m, m, m, m, B, y, A, b, b, B, B, // 48...63 | ||
127 | g, g, c, b, B, B, g, g, g, c, B, B, G, G, G, G, // 64...79 | ||
128 | C, B, G, G, G, G, G, C, r, m, m, m, m, m, y, r, // 80...95 | ||
129 | m, m, m, m, y, y, A, b, B, B, g, g, g, c, B, B, // 96..111 | ||
130 | G, G, G, G, C, B, G, G, G, G, G, C, r, r, m, m, // 112..127 | ||
131 | m, m, r, r, r, m, M, M, y, y, r, m, M, M, y, y, // 128..143 | ||
132 | y, a, B, B, G, G, G, G, C, B, G, G, G, G, G, C, // 144..159 | ||
133 | R, R, R, m, M, M, R, R, M, M, M, M, R, R, R, R, // 160..175 | ||
134 | M, M, y, y, y, M, M, M, Y, Y, Y, Y, a, B, Y, G, // 176..191 | ||
135 | G, G, G, C, R, R, R, M, M, M, R, R, R, R, R, M, // 192..207 | ||
136 | R, R, R, M, M, M, y, y, y, R, M, M, y, y, Y, Y, // 208..223 | ||
137 | R, M, Y, Y, Y, Y, Y, W, k, k, k, k, k, k, A, A, // 224..239 | ||
138 | A, A, A, A, a, a, a, a, a, a, W, W, W, W, W, W // 240..255 | ||
139 | }; | ||
140 | |||
141 | 533 | return table[color]; | |
142 | } | ||
143 | |||
144 | // Quantize a color to the nearest value supported by the terminal | ||
145 | 1796 | int32_t color_to_nearest(int32_t c, TermFeatureFlags flags, bool optimize) | |
146 | { | ||
147 | 1796 | BUG_ON(!color_is_valid(c)); | |
148 | 1796 | BUG_ON(optimize && !(flags & TFLAG_TRUE_COLOR)); | |
149 | |||
150 | // Note that higher color capabilities are taken as implying support for | ||
151 | // the lower ones (because no terminal supports e.g. true color but not | ||
152 | // palette colors) | ||
153 | 1839 | int32_t limit = COLOR_DEFAULT; | |
154 |
2/2✓ Branch 0 (7→8) taken 43 times.
✓ Branch 1 (7→10) taken 1710 times.
|
1753 | if (flags & TFLAG_TRUE_COLOR) { |
155 |
2/2✓ Branch 0 (8→9) taken 43 times.
✓ Branch 1 (8→14) taken 43 times.
|
86 | limit = optimize ? 255 : COLOR_RGB(0xFFFFFF); |
156 |
2/2✓ Branch 0 (10→11) taken 1407 times.
✓ Branch 1 (10→14) taken 303 times.
|
1710 | } else if (flags & TFLAG_256_COLOR) { |
157 | limit = 255; | ||
158 |
2/2✓ Branch 0 (11→12) taken 1364 times.
✓ Branch 1 (11→14) taken 43 times.
|
1407 | } else if (flags & TFLAG_16_COLOR) { |
159 | limit = COLOR_WHITE; | ||
160 |
2/2✓ Branch 0 (12→13) taken 229 times.
✓ Branch 1 (12→14) taken 1135 times.
|
1364 | } else if (flags & TFLAG_8_COLOR) { |
161 | 229 | limit = COLOR_GRAY; | |
162 | } | ||
163 | |||
164 |
2/2✓ Branch 0 (14→15) taken 853 times.
✓ Branch 1 (14→27) taken 943 times.
|
1796 | if (likely(c <= limit)) { |
165 | // Color is already within the supported range | ||
166 | return c; | ||
167 | } | ||
168 | |||
169 | 853 | bool rgb = color_is_rgb(c); | |
170 | 853 | BUG_ON(optimize && !rgb); | |
171 | |||
172 | 853 | bool exact = true; | |
173 |
2/2✓ Branch 0 (17→18) taken 410 times.
✓ Branch 1 (17→20) taken 443 times.
|
853 | int32_t tmp = rgb ? color_rgb_to_256(c, &exact) : c; |
174 |
4/4✓ Branch 0 (20→21) taken 30 times.
✓ Branch 1 (20→23) taken 823 times.
✓ Branch 2 (21→22) taken 13 times.
✓ Branch 3 (21→23) taken 17 times.
|
853 | c = (!(flags & TFLAG_TRUE_COLOR) || exact) ? tmp : c; |
175 |
2/2✓ Branch 0 (23→24) taken 533 times.
✓ Branch 1 (23→27) taken 320 times.
|
853 | c = (limit <= COLOR_WHITE) ? color_256_to_16(c) : c; |
176 |
2/2✓ Branch 0 (24→25) taken 496 times.
✓ Branch 1 (24→27) taken 37 times.
|
533 | c = (limit <= COLOR_GRAY) ? (c & 7) : c; |
177 |
2/2✓ Branch 0 (25→26) taken 451 times.
✓ Branch 1 (25→27) taken 45 times.
|
496 | c = (limit <= COLOR_DEFAULT) ? COLOR_DEFAULT : c; |
178 | return c; | ||
179 | } | ||
180 |