dte test coverage


Directory: ./
File: src/terminal/color.c
Date: 2024-12-21 16:03:22
Exec Total Coverage
Lines: 83 83 100.0%
Functions: 8 8 100.0%
Branches: 40 40 100.0%

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