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 mask = 0; | |
8 | 37 | unsigned int val = 0; | |
9 | |||
10 |
2/2✓ Branch 0 taken 13 times.
✓ Branch 1 taken 24 times.
|
37 | if (len == 6) { |
11 |
2/2✓ Branch 0 taken 78 times.
✓ Branch 1 taken 13 times.
|
91 | for (size_t i = 0; i < len; i++) { |
12 | 78 | unsigned int digit = hex_decode(str[i]); | |
13 | 78 | mask |= digit; | |
14 | 78 | val = val << 4 | digit; | |
15 | } | ||
16 |
2/2✓ Branch 0 taken 11 times.
✓ Branch 1 taken 13 times.
|
24 | } else if (len == 3) { |
17 |
2/2✓ Branch 0 taken 33 times.
✓ Branch 1 taken 11 times.
|
44 | for (size_t i = 0; i < len; i++) { |
18 | 33 | unsigned int digit = hex_decode(str[i]); | |
19 | 33 | mask |= digit; | |
20 | 33 | val = val << 8 | digit << 4 | digit; | |
21 | } | ||
22 | } else { | ||
23 | return COLOR_INVALID; | ||
24 | } | ||
25 | |||
26 |
2/2✓ Branch 0 taken 21 times.
✓ Branch 1 taken 3 times.
|
24 | return unlikely(mask & HEX_INVALID) ? COLOR_INVALID : COLOR_RGB(val); |
27 | } | ||
28 | |||
29 | // Calculate squared Euclidean distance between 2 RGB colors | ||
30 | 910 | static int color_distance ( | |
31 | uint8_t R, uint8_t G, uint8_t B, | ||
32 | uint8_t r, uint8_t g, uint8_t b | ||
33 | ) { | ||
34 | 910 | static_assert(INT_MAX >= 3 * 255 * 255); | |
35 | 910 | return (R - r) * (R - r) + (G - g) * (G - g) + (B - b) * (B - b); | |
36 | } | ||
37 | |||
38 | 18 | UNITTEST { | |
39 | // NOLINTBEGIN(bugprone-assert-side-effect) | ||
40 | 18 | BUG_ON(color_distance(1,1,1, 1,0,1) != 1); | |
41 | 18 | BUG_ON(color_distance(100,0,0, 80,0,0) != 400); | |
42 | 18 | BUG_ON(color_distance(0,5,10, 5,0,2) != 25 + 25 + 64); | |
43 | 18 | BUG_ON(color_distance(0,0,0, 255,0,0) != 255 * 255); | |
44 | 18 | BUG_ON(color_distance(255,255,255, 0,0,0) != 255 * 255 * 3); | |
45 | // NOLINTEND(bugprone-assert-side-effect) | ||
46 | 18 | } | |
47 | |||
48 | // Convert RGB color component (0-255) to nearest xterm color cube index (0-5). | ||
49 | // Color stops: 0, 95, 135, 175, 215, 255. | ||
50 | 1392 | static unsigned int nearest_cube_index(uint8_t c) | |
51 | { | ||
52 |
2/2✓ Branch 0 taken 819 times.
✓ Branch 1 taken 573 times.
|
1392 | unsigned int a = (c < 80) ? MIN(c, 7) : 35; |
53 | 1392 | return (c - a) / 40; | |
54 | } | ||
55 | |||
56 | 18 | UNITTEST { | |
57 | // NOLINTBEGIN(bugprone-assert-side-effect) | ||
58 | 18 | BUG_ON(nearest_cube_index(0) != 0); | |
59 | 18 | BUG_ON(nearest_cube_index(46) != 0); | |
60 | 18 | BUG_ON(nearest_cube_index(47) != 1); | |
61 | 18 | BUG_ON(nearest_cube_index(114) != 1); | |
62 | 18 | BUG_ON(nearest_cube_index(115) != 2); | |
63 | 18 | BUG_ON(nearest_cube_index(170) != 3); | |
64 | 18 | BUG_ON(nearest_cube_index(255) != 5); | |
65 | 18 | BUG_ON(nearest_cube_index(255 - 20) != 5); | |
66 | 18 | BUG_ON(nearest_cube_index(255 - 21) != 4); | |
67 | // NOLINTEND(bugprone-assert-side-effect) | ||
68 | 18 | } | |
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 = nearest_cube_index(r); | |
80 | 410 | uint8_t g_idx = nearest_cube_index(g); | |
81 | 410 | uint8_t b_idx = nearest_cube_index(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 | int gray_avg = (r + g + b) / 3; | |
88 |
2/2✓ Branch 0 taken 380 times.
✓ Branch 1 taken 30 times.
|
410 | int gray_idx = (gray_avg > 238) ? 23 : ((gray_avg - 3) / 10); |
89 | 410 | int gray = 8 + (10 * gray_idx); | |
90 | |||
91 | // Calculate differences | ||
92 | 410 | int rgb_distance = color_distance(r_stop, g_stop, b_stop, r, g, b); | |
93 | 410 | int gray_distance = color_distance(gray, gray, gray, r, g, b); | |
94 | |||
95 |
2/2✓ Branch 0 taken 110 times.
✓ Branch 1 taken 300 times.
|
410 | if (gray_distance < rgb_distance) { |
96 | // Gray is closest match | ||
97 | 110 | *exact = (gray_distance == 0); | |
98 | 110 | return 232 + gray_idx; | |
99 | } else { | ||
100 | // RGB cube color is closest match | ||
101 | 300 | *exact = (rgb_distance == 0); | |
102 | 300 | return 16 + (36 * r_idx) + (6 * g_idx) + b_idx; | |
103 | } | ||
104 | } | ||
105 | |||
106 | 465 | static uint8_t color_256_to_16(uint8_t color) | |
107 | { | ||
108 | 465 | enum { | |
109 | k = COLOR_BLACK, | ||
110 | r = COLOR_RED, | ||
111 | g = COLOR_GREEN, | ||
112 | y = COLOR_YELLOW, | ||
113 | b = COLOR_BLUE, | ||
114 | m = COLOR_MAGENTA, | ||
115 | c = COLOR_CYAN, | ||
116 | a = COLOR_GRAY, | ||
117 | A = COLOR_DARKGRAY, | ||
118 | R = COLOR_LIGHTRED, | ||
119 | G = COLOR_LIGHTGREEN, | ||
120 | Y = COLOR_LIGHTYELLOW, | ||
121 | B = COLOR_LIGHTBLUE, | ||
122 | M = COLOR_LIGHTMAGENTA, | ||
123 | C = COLOR_LIGHTCYAN, | ||
124 | W = COLOR_WHITE | ||
125 | }; | ||
126 | |||
127 | 465 | static const uint8_t table[256] = { | |
128 | k, r, g, y, b, m, c, a, A, R, G, Y, B, M, C, W, // 0...15 | ||
129 | k, b, b, b, B, B, g, c, b, b, B, B, g, g, c, b, // 16...31 | ||
130 | B, B, g, g, g, c, B, B, G, G, G, C, C, B, G, G, // 32...47 | ||
131 | G, G, C, C, r, m, m, m, m, B, y, A, b, b, B, B, // 48...63 | ||
132 | g, g, c, b, B, B, g, g, g, c, B, B, G, G, G, G, // 64...79 | ||
133 | C, B, G, G, G, G, G, C, r, m, m, m, m, m, y, r, // 80...95 | ||
134 | m, m, m, m, y, y, A, b, B, B, g, g, g, c, B, B, // 96..111 | ||
135 | G, G, G, G, C, B, G, G, G, G, G, C, r, r, m, m, // 112..127 | ||
136 | m, m, r, r, r, m, M, M, y, y, r, m, M, M, y, y, // 128..143 | ||
137 | y, a, B, B, G, G, G, G, C, B, G, G, G, G, G, C, // 144..159 | ||
138 | R, R, R, m, M, M, R, R, M, M, M, M, R, R, R, R, // 160..175 | ||
139 | M, M, y, y, y, M, M, M, Y, Y, Y, Y, a, B, Y, G, // 176..191 | ||
140 | G, G, G, C, R, R, R, M, M, M, R, R, R, R, R, M, // 192..207 | ||
141 | R, R, R, M, M, M, y, y, y, R, M, M, y, y, Y, Y, // 208..223 | ||
142 | R, M, Y, Y, Y, Y, Y, W, k, k, k, k, k, k, A, A, // 224..239 | ||
143 | A, A, A, A, a, a, a, a, a, a, W, W, W, W, W, W // 240..255 | ||
144 | }; | ||
145 | |||
146 | 465 | return table[color]; | |
147 | } | ||
148 | |||
149 | // Quantize a color to the nearest value supported by the terminal | ||
150 | 1614 | int32_t color_to_nearest(int32_t c, TermFeatureFlags flags, bool optimize) | |
151 | { | ||
152 | 1614 | BUG_ON(!color_is_valid(c)); | |
153 | 1614 | BUG_ON(optimize && !(flags & TFLAG_TRUE_COLOR)); | |
154 | |||
155 | // Note that higher color capabilities are taken as implying support for | ||
156 | // the lower ones (because no terminal supports e.g. true color but not | ||
157 | // palette colors) | ||
158 | 1657 | int32_t limit = COLOR_DEFAULT; | |
159 |
2/2✓ Branch 0 taken 43 times.
✓ Branch 1 taken 1528 times.
|
1571 | if (flags & TFLAG_TRUE_COLOR) { |
160 |
2/2✓ Branch 0 taken 43 times.
✓ Branch 1 taken 43 times.
|
86 | limit = optimize ? 255 : COLOR_RGB(0xFFFFFF); |
161 |
2/2✓ Branch 0 taken 1225 times.
✓ Branch 1 taken 303 times.
|
1528 | } else if (flags & TFLAG_256_COLOR) { |
162 | limit = 255; | ||
163 |
2/2✓ Branch 0 taken 1182 times.
✓ Branch 1 taken 43 times.
|
1225 | } else if (flags & TFLAG_16_COLOR) { |
164 | limit = COLOR_WHITE; | ||
165 |
2/2✓ Branch 0 taken 229 times.
✓ Branch 1 taken 953 times.
|
1182 | } else if (flags & TFLAG_8_COLOR) { |
166 | 229 | limit = COLOR_GRAY; | |
167 | } | ||
168 | |||
169 |
2/2✓ Branch 0 taken 785 times.
✓ Branch 1 taken 829 times.
|
1614 | if (likely(c <= limit)) { |
170 | // Color is already within the supported range | ||
171 | return c; | ||
172 | } | ||
173 | |||
174 | 785 | bool rgb = color_is_rgb(c); | |
175 | 785 | BUG_ON(optimize && !rgb); | |
176 | |||
177 | 785 | bool exact = true; | |
178 |
2/2✓ Branch 0 taken 410 times.
✓ Branch 1 taken 375 times.
|
785 | int32_t tmp = rgb ? color_rgb_to_256(c, &exact) : c; |
179 |
4/4✓ Branch 0 taken 30 times.
✓ Branch 1 taken 755 times.
✓ Branch 2 taken 13 times.
✓ Branch 3 taken 17 times.
|
785 | c = (!(flags & TFLAG_TRUE_COLOR) || exact) ? tmp : c; |
180 |
2/2✓ Branch 0 taken 465 times.
✓ Branch 1 taken 320 times.
|
785 | c = (limit <= COLOR_WHITE) ? color_256_to_16(c) : c; |
181 |
2/2✓ Branch 0 taken 428 times.
✓ Branch 1 taken 37 times.
|
465 | c = (limit <= COLOR_GRAY) ? (c & 7) : c; |
182 |
2/2✓ Branch 0 taken 383 times.
✓ Branch 1 taken 45 times.
|
428 | c = (limit <= COLOR_DEFAULT) ? COLOR_DEFAULT : c; |
183 | return c; | ||
184 | } | ||
185 |