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