diff options
Diffstat (limited to 'src/palette.c')
| -rw-r--r-- | src/palette.c | 492 |
1 files changed, 492 insertions, 0 deletions
diff --git a/src/palette.c b/src/palette.c new file mode 100644 index 0000000..1b27e3a --- /dev/null +++ b/src/palette.c @@ -0,0 +1,492 @@ +#include "palette.h" +#include "internal.h" + +#include <math.h> +#include <stdbool.h> +#include <stdint.h> +#include <string.h> + +/* Linear light for each byte value of an sRGB channel. The entries are the + * transfer curve, evaluated once and stored, so every host reads the same ones. */ +static const float srgb_linear[256] = { + 0x0p+0f, 0x1.3e4568p-12f, 0x1.3e4568p-11f, 0x1.dd681cp-11f, + 0x1.3e4568p-10f, 0x1.8dd6c2p-10f, 0x1.dd681cp-10f, 0x1.167cbap-9f, + 0x1.3e4568p-9f, 0x1.660e16p-9f, 0x1.8dd6c2p-9f, 0x1.b6a31ap-9f, + 0x1.e1e31ap-9f, 0x1.07c38cp-8f, 0x1.1fcc2cp-8f, 0x1.390ffap-8f, + 0x1.53936ep-8f, 0x1.6f5adep-8f, 0x1.8c6a92p-8f, 0x1.aac6c2p-8f, + 0x1.ca7382p-8f, 0x1.eb74ep-8f, 0x1.06e76ap-7f, 0x1.18c2a4p-7f, + 0x1.2b4e06p-7f, 0x1.3e8b7cp-7f, 0x1.527cd6p-7f, 0x1.6723eep-7f, + 0x1.7c8292p-7f, 0x1.929a86p-7f, 0x1.a96d8ep-7f, 0x1.c0fd62p-7f, + 0x1.d94bbep-7f, 0x1.f25a44p-7f, 0x1.06155p-6f, 0x1.135f3ep-6f, + 0x1.210bb6p-6f, 0x1.2f1b8ap-6f, 0x1.3d8f84p-6f, 0x1.4c6866p-6f, + 0x1.5ba6fap-6f, 0x1.6b4c02p-6f, 0x1.7b584p-6f, 0x1.8bcc72p-6f, + 0x1.9ca956p-6f, 0x1.adefaap-6f, 0x1.bfa02p-6f, 0x1.d1bb72p-6f, + 0x1.e44258p-6f, 0x1.f73582p-6f, 0x1.054ad2p-5f, 0x1.0f31b8p-5f, + 0x1.194fccp-5f, 0x1.23a55ep-5f, 0x1.2e32c6p-5f, 0x1.38f85cp-5f, + 0x1.43f678p-5f, 0x1.4f2d64p-5f, 0x1.5a9d76p-5f, 0x1.6647p-5f, + 0x1.722a56p-5f, 0x1.7e47c6p-5f, 0x1.8a9fa2p-5f, 0x1.973238p-5f, + 0x1.a3ffdep-5f, 0x1.b108d4p-5f, 0x1.be4d6ep-5f, 0x1.cbcdfcp-5f, + 0x1.d98ac8p-5f, 0x1.e7841ep-5f, 0x1.f5ba52p-5f, 0x1.0216cep-4f, + 0x1.096f2ap-4f, 0x1.10e66p-4f, 0x1.187c94p-4f, 0x1.2031ecp-4f, + 0x1.28068ap-4f, 0x1.2ffa94p-4f, 0x1.380e2cp-4f, 0x1.404176p-4f, + 0x1.489496p-4f, 0x1.5107aep-4f, 0x1.599aep-4f, 0x1.624e5p-4f, + 0x1.6b2224p-4f, 0x1.741676p-4f, 0x1.7d2b6ap-4f, 0x1.866124p-4f, + 0x1.8fb7c4p-4f, 0x1.992f6cp-4f, 0x1.a2c83ep-4f, 0x1.ac8258p-4f, + 0x1.b65ddep-4f, 0x1.c05aeep-4f, 0x1.ca79aap-4f, 0x1.d4ba32p-4f, + 0x1.df1ca4p-4f, 0x1.e9a122p-4f, 0x1.f447dp-4f, 0x1.ff10c2p-4f, + 0x1.04fe0ep-3f, 0x1.0a85p-3f, 0x1.101d46p-3f, 0x1.15c6fp-3f, + 0x1.1b820ap-3f, 0x1.214ea8p-3f, 0x1.272cd6p-3f, 0x1.2d1ca4p-3f, + 0x1.331e2p-3f, 0x1.39315ap-3f, 0x1.3f566p-3f, 0x1.458d46p-3f, + 0x1.4bd612p-3f, 0x1.5230d8p-3f, 0x1.589dap-3f, 0x1.5f1c82p-3f, + 0x1.65ad88p-3f, 0x1.6c50c2p-3f, 0x1.73063cp-3f, 0x1.79ce06p-3f, + 0x1.80a82cp-3f, 0x1.8794bep-3f, 0x1.8e93c8p-3f, 0x1.95a55cp-3f, + 0x1.9cc984p-3f, 0x1.a4004ep-3f, 0x1.ab49ccp-3f, 0x1.b2a606p-3f, + 0x1.ba1516p-3f, 0x1.c196f8p-3f, 0x1.c92bcp-3f, 0x1.d0d37ep-3f, + 0x1.d88e4p-3f, 0x1.e05c18p-3f, 0x1.e83d08p-3f, 0x1.f0312p-3f, + 0x1.f8387p-3f, 0x1.002984p-2f, 0x1.044078p-2f, 0x1.08611ap-2f, + 0x1.0c8b74p-2f, 0x1.10bf8ap-2f, 0x1.14fd64p-2f, 0x1.194506p-2f, + 0x1.1d967ap-2f, 0x1.21f1c2p-2f, 0x1.2656e8p-2f, 0x1.2ac5fp-2f, + 0x1.2f3ee2p-2f, 0x1.33c1c4p-2f, 0x1.384e9cp-2f, 0x1.3ce56ep-2f, + 0x1.418644p-2f, 0x1.463124p-2f, 0x1.4ae61p-2f, 0x1.4fa512p-2f, + 0x1.546e3p-2f, 0x1.59416ep-2f, 0x1.5e1ed2p-2f, 0x1.630666p-2f, + 0x1.67f83p-2f, 0x1.6cf43p-2f, 0x1.71fa6ep-2f, 0x1.770af2p-2f, + 0x1.7c25c2p-2f, 0x1.814ae2p-2f, 0x1.867a5ap-2f, 0x1.8bb42ep-2f, + 0x1.90f866p-2f, 0x1.964706p-2f, 0x1.9ba016p-2f, 0x1.a1039ap-2f, + 0x1.a67198p-2f, 0x1.abea16p-2f, 0x1.b16d1ap-2f, 0x1.b6faa8p-2f, + 0x1.bc92cap-2f, 0x1.c23582p-2f, 0x1.c7e2d6p-2f, 0x1.cd9acep-2f, + 0x1.d35d6cp-2f, 0x1.d92abap-2f, 0x1.df02bap-2f, 0x1.e4e574p-2f, + 0x1.ead2ecp-2f, 0x1.f0cb28p-2f, 0x1.f6ce2ep-2f, 0x1.fcdc04p-2f, + 0x1.017a5ap-1f, 0x1.048c1cp-1f, 0x1.07a34ep-1f, 0x1.0abff2p-1f, + 0x1.0de20cp-1f, 0x1.11099ep-1f, 0x1.1436acp-1f, 0x1.176936p-1f, + 0x1.1aa14p-1f, 0x1.1ddecep-1f, 0x1.2121e2p-1f, 0x1.246a7cp-1f, + 0x1.27b8a2p-1f, 0x1.2b0c56p-1f, 0x1.2e659ap-1f, 0x1.31c472p-1f, + 0x1.3528dep-1f, 0x1.3892e2p-1f, 0x1.3c0282p-1f, 0x1.3f77bep-1f, + 0x1.42f29cp-1f, 0x1.46731cp-1f, 0x1.49f94p-1f, 0x1.4d850cp-1f, + 0x1.511682p-1f, 0x1.54ada6p-1f, 0x1.584a7ap-1f, 0x1.5bedp-1f, + 0x1.5f953cp-1f, 0x1.63432ep-1f, 0x1.66f6d8p-1f, 0x1.6ab04p-1f, + 0x1.6e6f66p-1f, 0x1.72344cp-1f, 0x1.75fef8p-1f, 0x1.79cf6ap-1f, + 0x1.7da5a4p-1f, 0x1.8181a8p-1f, 0x1.85637cp-1f, 0x1.894b2p-1f, + 0x1.8d3896p-1f, 0x1.912be2p-1f, 0x1.952504p-1f, 0x1.992402p-1f, + 0x1.9d28dcp-1f, 0x1.a13396p-1f, 0x1.a5443p-1f, 0x1.a95aaep-1f, + 0x1.ad7714p-1f, 0x1.b1996p-1f, 0x1.b5c19ap-1f, 0x1.b9efcp-1f, + 0x1.be23d8p-1f, 0x1.c25ddcp-1f, 0x1.c69ddep-1f, 0x1.cae3d2p-1f, + 0x1.cf2fc6p-1f, 0x1.d381acp-1f, 0x1.d7d99ap-1f, 0x1.dc377ep-1f, + 0x1.e09b7p-1f, 0x1.e5055ep-1f, 0x1.e9755cp-1f, 0x1.edeb5cp-1f, + 0x1.f26772p-1f, 0x1.f6e98cp-1f, 0x1.fb71cp-1f, 0x1p+0f, +}; + +static inline float linf_to_srgbf(float c) +{ + return c > 0.0031308f ? 1.055f * powf(c, 1.0f / 2.4f) - 0.055f + : 12.92f * c; +} + +static const struct term_theme builtin_theme = { + .fg = 0xFFFFFFFFu, + .bg = 0xFF1E1E2Eu, + .cursor = 0xFF89B4FAu, + .ansi = { + 0xFF585B70, 0xFFCC6666, 0xFFB5BD68, 0xFFF0C674, + 0xFF81A2BE, 0xFFB294BB, 0xFF8ABEB7, 0xFFC5C8C6, + 0xFF666666, 0xFFD54E53, 0xFFB9CA4A, 0xFFE7C547, + 0xFF7AA6DA, 0xFFC397D8, 0xFF70C0B1, 0xFFEAEAEA, + }, +}; + +void term_theme_default(struct term_theme *theme) +{ + *theme = builtin_theme; +} + +static const struct term_theme high_contrast_theme = { + .fg = 0xFFFFFFFFu, + .bg = 0xFF000000u, + .cursor = 0xFF00FFFFu, + .ansi = { + 0xFF000000, 0xFFFF0000, 0xFF00FF00, 0xFFFFFF00, + 0xFF0000FF, 0xFFFF00FF, 0xFF00FFFF, 0xFFFFFFFF, + 0xFF808080, 0xFFFF5555, 0xFF55FF55, 0xFFFFFF55, + 0xFF5555FF, 0xFFFF55FF, 0xFF55FFFF, 0xFFFFFFFF, + }, +}; + +void term_theme_high_contrast(struct term_theme *theme) +{ + *theme = high_contrast_theme; +} + +/* Finds the slot for key and the default it resets to. Keys are + * foreground, background, and color0 to color15. */ +static bool palette_slot(struct term_theme *p, const struct term_theme *def, + const char *key, uint32_t **slot, + const uint32_t **fallback) +{ + if (!strcmp(key, "foreground")) { + *slot = &p->fg; + *fallback = &def->fg; + return true; + } + if (!strcmp(key, "background")) { + *slot = &p->bg; + *fallback = &def->bg; + return true; + } + if (strncmp(key, "color", 5)) + return false; + const char *digits = key + 5; + if (!*digits || (*digits == '0' && digits[1])) + return false; + int i = 0; + for (const char *d = digits; *d; d++) { + if (*d < '0' || *d > '9') + return false; + i = i * 10 + (*d - '0'); + if (i > 15) + return false; + } + *slot = &p->ansi[i]; + *fallback = &def->ansi[i]; + return true; +} + +bool term_theme_set_key(struct term_theme *p, const char *key, const char *val) +{ + struct term_theme def; + term_theme_default(&def); + uint32_t *slot; + const uint32_t *fallback; + if (!palette_slot(p, &def, key, &slot, &fallback)) + return false; + if (!val[0]) { + *slot = *fallback; + return true; + } + uint32_t v; + if (!term_color_parse(val, &v)) + return false; + *slot = v; + return true; +} + +static bool parse_hex_channel(const char *s, size_t len, uint8_t *out) +{ + if (len == 0 || len > 4) + return false; + unsigned v = 0; + for (size_t i = 0; i < len; i++) { + int d = term_hex_nib(s[i]); + if (d < 0) + return false; + v = (v << 4) | (unsigned)d; + } + static const unsigned max_for_len[5] = {0, 15, 255, 4095, 65535}; + unsigned max = max_for_len[len]; + *out = (uint8_t)((unsigned long)v * 255 / max); + return true; +} + +static bool parse_fraction(const char *s, size_t len, uint8_t *out) +{ + + if (len == 0) + return false; + bool seen_digit = false; + bool seen_dot = false; + unsigned integer = 0; + unsigned frac = 0; + unsigned frac_digits = 0; + size_t i = 0; + if (s[0] == '.') { + seen_dot = true; + i = 1; + } + for (; i < len; i++) { + char c = s[i]; + if (c >= '0' && c <= '9') { + seen_digit = true; + if (!seen_dot) { + integer = integer * 10 + (unsigned)(c - '0'); + if (integer > 1) + return false; + } else { + if (frac_digits < 9) { + frac = frac * 10 + (unsigned)(c - '0'); + frac_digits++; + } + } + } else if (c == '.' && !seen_dot) { + seen_dot = true; + } else { + return false; + } + } + if (!seen_digit) + return false; + double value = (double)integer; + if (frac_digits > 0) { + double f = (double)frac; + for (unsigned k = 0; k < frac_digits; k++) + f /= 10.0; + value += f; + } + if (value > 1.0) value = 1.0; + *out = (uint8_t)(value * 255.0 + 0.5); + return true; +} + +static const char *skip_leading_ws(const char *s) +{ + while (*s == ' ' || *s == '\t') + s++; + return s; +} + +static size_t rtrimmed_len(const char *s) +{ + size_t n = strlen(s); + while (n > 0 && (s[n - 1] == ' ' || s[n - 1] == '\t')) + n--; + return n; +} + +bool term_color_parse(const char *input, uint32_t *out) +{ + if (!input || !out) + return false; + const char *s = skip_leading_ws(input); + size_t len = rtrimmed_len(s); + if (len == 0) + return false; + + if (s[0] == '#') { + const char *body = s + 1; + size_t blen = len - 1; + size_t cw = blen / 3; + if (blen != 3 && blen != 6 && blen != 9 && blen != 12) + return false; + uint8_t r, g, b; + if (!parse_hex_channel(body, cw, &r) || + !parse_hex_channel(body + cw, cw, &g) || + !parse_hex_channel(body + 2 * cw, cw, &b)) + return false; + *out = term_rgb(r, g, b); + return true; + } + + if (len >= 4 && (s[0] == 'r' || s[0] == 'R') && + (s[1] == 'g' || s[1] == 'G') && (s[2] == 'b' || s[2] == 'B')) { + size_t i = 3; + bool intensity = false; + if ((s[i] == 'i' || s[i] == 'I')) { + intensity = true; + i++; + } + if (i >= len || s[i] != ':') + return false; + i++; + + const char *channels[3]; + size_t chlens[3]; + int ci = 0; + size_t start = i; + for (;; i++) { + if (i == len || s[i] == '/') { + if (ci >= 3) + return false; + channels[ci] = s + start; + chlens[ci] = i - start; + ci++; + if (i == len) + break; + start = i + 1; + } + } + if (ci != 3) + return false; + uint8_t r, g, b; + if (intensity) { + if (!parse_fraction(channels[0], chlens[0], &r) || + !parse_fraction(channels[1], chlens[1], &g) || + !parse_fraction(channels[2], chlens[2], &b)) + return false; + } else { + if (!parse_hex_channel(channels[0], chlens[0], &r) || + !parse_hex_channel(channels[1], chlens[1], &g) || + !parse_hex_channel(channels[2], chlens[2], &b)) + return false; + } + *out = term_rgb(r, g, b); + return true; + } + + if (len == 6 || len == 3) { + uint8_t r, g, b; + size_t cw = len / 3; + if (!parse_hex_channel(s, cw, &r) || + !parse_hex_channel(s + cw, cw, &g) || + !parse_hex_channel(s + 2 * cw, cw, &b)) + return false; + *out = term_rgb(r, g, b); + return true; + } + + return false; +} + +struct lab { float l, a, b; }; + +static struct lab lab_from_rgb(uint32_t argb) +{ + float r = srgb_linear[(argb >> 16) & 0xFF]; + float g = srgb_linear[(argb >> 8) & 0xFF]; + float b = srgb_linear[argb & 0xFF]; + + float x = (r * 0.4124564f + g * 0.3575761f + b * 0.1804375f) / 0.95047f; + float y = r * 0.2126729f + g * 0.7151522f + b * 0.0721750f; + float z = (r * 0.0193339f + g * 0.1191920f + b * 0.9503041f) / 1.08883f; + + x = x > 0.008856f ? cbrtf(x) : 7.787f * x + 16.0f / 116.0f; + y = y > 0.008856f ? cbrtf(y) : 7.787f * y + 16.0f / 116.0f; + z = z > 0.008856f ? cbrtf(z) : 7.787f * z + 16.0f / 116.0f; + + struct lab lab; + lab.l = 116.0f * y - 16.0f; + lab.a = 500.0f * (x - y); + lab.b = 200.0f * (y - z); + return lab; +} + +static uint32_t lab_to_rgb(struct lab lab) +{ + float y = (lab.l + 16.0f) / 116.0f; + float x = lab.a / 500.0f + y; + float z = y - lab.b / 200.0f; + + float x3 = x * x * x; + float y3 = y * y * y; + float z3 = z * z * z; + float xf = (x3 > 0.008856f ? x3 : (x - 16.0f / 116.0f) / 7.787f) * 0.95047f; + float yf = (y3 > 0.008856f ? y3 : (y - 16.0f / 116.0f) / 7.787f); + float zf = (z3 > 0.008856f ? z3 : (z - 16.0f / 116.0f) / 7.787f) * 1.08883f; + + float r = xf * 3.2404542f - yf * 1.5371385f - zf * 0.4985314f; + float g = -xf * 0.9692660f + yf * 1.8760108f + zf * 0.0415560f; + float b = xf * 0.0556434f - yf * 0.2040259f + zf * 1.0572252f; + + r = linf_to_srgbf(r); + g = linf_to_srgbf(g); + b = linf_to_srgbf(b); + + if (r < 0.0f) r = 0.0f; + if (r > 1.0f) r = 1.0f; + if (g < 0.0f) g = 0.0f; + if (g > 1.0f) g = 1.0f; + if (b < 0.0f) b = 0.0f; + if (b > 1.0f) b = 1.0f; + + uint8_t rb = (uint8_t)(r * 255.0f + 0.5f); + uint8_t gb = (uint8_t)(g * 255.0f + 0.5f); + uint8_t bb = (uint8_t)(b * 255.0f + 0.5f); + return term_rgb(rb, gb, bb); +} + +static struct lab lab_lerp(float t, struct lab a, struct lab b) +{ + struct lab r; + r.l = a.l + t * (b.l - a.l); + r.a = a.a + t * (b.a - a.a); + r.b = a.b + t * (b.b - a.b); + return r; +} + +/* + * Derive the 256-color palette from a theme. Entries 0-15 are the theme's + * ANSI colors. The 6x6x6 cube and the gray ramp interpolate in Lab space + * between the background, the ANSI colors, and the foreground, so they match + * the theme instead of the fixed xterm values. + */ +static void derive_palette(uint32_t *p, const struct term_theme *theme) +{ + struct lab base8[8]; + base8[0] = lab_from_rgb(theme->bg); + for (int i = 1; i < 7; i++) + base8[i] = lab_from_rgb(theme->ansi[i]); + base8[7] = lab_from_rgb(theme->fg); + + for (int i = 0; i < 16; i++) + p[i] = theme->ansi[i]; + p[TERM_PALETTE_FG] = theme->fg; + p[TERM_PALETTE_BG] = theme->bg; + p[TERM_PALETTE_CURSOR] = theme->cursor; + + bool is_light_theme = base8[7].l < base8[0].l; + if (is_light_theme) { + struct lab tmp = base8[0]; + base8[0] = base8[7]; + base8[7] = tmp; + } + + int idx = 16; + for (int ri = 0; ri < 6; ri++) { + float tr = (float)ri / 5.0f; + struct lab c0 = lab_lerp(tr, base8[0], base8[1]); + struct lab c1 = lab_lerp(tr, base8[2], base8[3]); + struct lab c2 = lab_lerp(tr, base8[4], base8[5]); + struct lab c3 = lab_lerp(tr, base8[6], base8[7]); + for (int gi = 0; gi < 6; gi++) { + float tg = (float)gi / 5.0f; + struct lab c4 = lab_lerp(tg, c0, c1); + struct lab c5 = lab_lerp(tg, c2, c3); + for (int bi = 0; bi < 6; bi++) + p[idx++] = lab_to_rgb(lab_lerp((float)bi / 5.0f, c4, c5)); + } + } + + for (int i = 0; i < 24; i++) { + float t = (float)(i + 1) / 25.0f; + p[idx++] = lab_to_rgb(lab_lerp(t, base8[0], base8[7])); + } +} + +static bool theme_is_zero(const struct term_theme *theme) +{ + static const struct term_theme zero; + return !memcmp(theme, &zero, sizeof(zero)); +} + +void term_palette_init(struct palette *p, const struct term_theme *theme) +{ + p->theme = theme && !theme_is_zero(theme) ? *theme : builtin_theme; + if (!p->theme.cursor) + p->theme.cursor = builtin_theme.cursor; + derive_palette(p->base, &p->theme); + memcpy(p->colors, p->base, sizeof(p->colors)); + p->version++; +} + +static bool valid_slot(int slot) +{ + return slot >= 0 && slot < TERM_PALETTE_SIZE; +} + +void term_palette_set(struct palette *p, int slot, uint32_t argb) +{ + if (!valid_slot(slot) || p->colors[slot] == argb) + return; + p->colors[slot] = argb; + p->version++; +} + +void term_palette_reset(struct palette *p, int slot) +{ + if (valid_slot(slot)) + term_palette_set(p, slot, p->base[slot]); +} + +void term_palette_reset_all(struct palette *p) +{ + if (memcmp(p->colors, p->base, sizeof(p->colors))) { + memcpy(p->colors, p->base, sizeof(p->colors)); + p->version++; + } +} |
