dte test coverage


Directory: ./
File: src/terminal/color.c
Date: 2025-05-08 15:05:54
Exec Total Coverage
Lines: 77 77 100.0%
Functions: 8 8 100.0%
Branches: 36 36 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 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