diff --git a/kitty/colors.c b/kitty/colors.c index 5f88febb1..f2c41eb2d 100644 --- a/kitty/colors.c +++ b/kitty/colors.c @@ -15,6 +15,7 @@ #endif static const color_type NULL_COLOR_VALUE = 0xffffffff; +static const color_type GENERATED_COLOR_MASK = 0xff000000; static uint32_t FG_BG_256[256] = { 0x000000, // 0 @@ -122,6 +123,10 @@ set_mark_colors(ColorProfile *self, PyObject *opts) { return true; } +// Generating 256 color palette {{{ +// For more information, see +// https://gist.github.com/jake-stewart/0a8ea46159a7da2c808e5be2177e1783 + static void color_type_to_lab(color_type color, float *lab) { float r = ((color >> 16) & 0xff) / 255.0f; @@ -168,7 +173,7 @@ lab_to_color_type(float *lab) { uint8_t gb = (uint8_t)(fminf(fmaxf(g, 0.0f), 1.0f) * 255.0f + 0.5f); uint8_t bb = (uint8_t)(fminf(fmaxf(b, 0.0f), 1.0f) * 255.0f + 0.5f); - return (rb << 16) | (gb << 8) | bb; + return GENERATED_COLOR_MASK | (rb << 16) | (gb << 8) | bb; } static void @@ -178,8 +183,6 @@ lerp_lab(float t, float *a, float *b, float *out) { out[2] = a[2] + t * (b[2] - a[2]); } -// For more information, see -// https://gist.github.com/jake-stewart/0a8ea46159a7da2c808e5be2177e1783 static void setup_256_palette_lab(ColorProfile *self, color_type *color_table, bool semantic, float base8_lab[8][3]) { const color_type bg = colorprofile_to_color(self, self->overridden.default_bg, self->configured.default_bg).rgb; @@ -198,6 +201,9 @@ setup_256_palette_lab(ColorProfile *self, color_type *color_table, bool semantic } } +static bool +is_generated_color(const color_type c) { return (c & GENERATED_COLOR_MASK) == GENERATED_COLOR_MASK; } + static void generate_256_palette(ColorProfile *self, color_type *color_table, bool semantic) { float base8_lab[8][3]; @@ -217,7 +223,7 @@ generate_256_palette(ColorProfile *self, color_type *color_table, bool semantic) lerp_lab(tg, c0, c1, c4); lerp_lab(tg, c2, c3, c5); for (int b = 0; b < 6; b++) { - if (color_table[idx] == NULL_COLOR_VALUE) { + if (is_generated_color(color_table[idx])) { float c6[3]; lerp_lab(b / 5.0f, c4, c5, c6); color_table[idx] = lab_to_color_type(c6); @@ -231,9 +237,7 @@ generate_256_palette(ColorProfile *self, color_type *color_table, bool semantic) float t = (i + 1) / 25.0f; float lab[3]; lerp_lab(t, base8_lab[0], base8_lab[7], lab); - if (color_table[idx] == NULL_COLOR_VALUE) { - color_table[idx] = lab_to_color_type(lab); - } + if (is_generated_color(color_table[idx])) color_table[idx] = lab_to_color_type(lab); idx++; } } @@ -267,7 +271,7 @@ static void fixed_color_palette(color_type *color_table) { init_FG_BG_table(); for (unsigned i = 16; i < arraysz(FG_BG_256); i++) { - if (color_table[i] == NULL_COLOR_VALUE) color_table[i] = FG_BG_256[i]; + if (is_generated_color(color_table[i])) color_table[i] = GENERATED_COLOR_MASK | FG_BG_256[i]; } } @@ -276,11 +280,15 @@ palette_generation_is_dynamic(PyObject *opts, bool *semantic) { bool ans = false; *semantic = false; if (opts) { RAII_PyObject(policy, PyObject_GetAttrString(opts, "palette_generate")); if (!policy) return ans; - ans = PyUnicode_CompareWithASCIIString(policy, "fixed") != 0; - *semantic = ans && PyUnicode_CompareWithASCIIString(policy, "semantic") == 0; + switch(PyUnicode_AsUTF8(policy)[0]) { + case 'f': break; + case 's': *semantic = true; /* fallthrough */ + default: ans = true; + } } return ans; } +// }}} static bool set_colortable(ColorProfile *self, PyObject *opts) { @@ -296,7 +304,7 @@ set_colortable(ColorProfile *self, PyObject *opts) { for (size_t i = 0; i < arraysz(FG_BG_256); i++) self->color_table[i] = color_table[i]; if (dynamic_palette) generate_256_palette(self, self->color_table, semantic_generation); else fixed_color_palette(self->color_table); - memcpy(self->orig_color_table, self->color_table, arraysz(self->color_table) * sizeof(self->color_table[0])); + memcpy(self->orig_color_table, self->color_table, sizeof(self->color_table)); return true; } @@ -462,7 +470,7 @@ colorprofile_to_color(const ColorProfile *self, DynamicColor entry, DynamicColor return defval; case COLOR_IS_INDEX: { DynamicColor ans; - ans.rgb = self->color_table[entry.rgb & 0xff] & 0xffffff; + ans.rgb = self->color_table[entry.rgb & 0xff] & (~GENERATED_COLOR_MASK); ans.type = COLOR_IS_RGB; return ans; } @@ -483,7 +491,7 @@ colorprofile_to_color_with_fallback(ColorProfile *self, DynamicColor entry, Dyna case COLOR_IS_RGB: return entry.rgb; case COLOR_IS_INDEX: - return self->color_table[entry.rgb & 0xff] & 0xffffff; + return self->color_table[entry.rgb & 0xff] & (~GENERATED_COLOR_MASK); } return entry.rgb; } @@ -494,7 +502,7 @@ colortable_colors_into_dict(ColorProfile *self, unsigned start, unsigned limit, static char buf[32] = {'c', 'o', 'l', 'o', 'r', 0}; for (unsigned i = start; i < limit; i++) { snprintf(buf + 5, sizeof(buf) - 6, "%u", i); - PyObject *val = PyLong_FromUnsignedLong(self->color_table[i]); + PyObject *val = PyLong_FromUnsignedLong(self->color_table[i] &(~GENERATED_COLOR_MASK)); if (!val) return false; int ret = PyDict_SetItemString(ans, buf, val); Py_DECREF(val); @@ -575,7 +583,7 @@ as_color(ColorProfile *self, PyObject *val) { switch(t) { case 1: r = (entry >> 8) & 0xff; - col = self->color_table[r]; + col = self->color_table[r] & (~GENERATED_COLOR_MASK); break; case 2: col = entry >> 8; @@ -617,7 +625,7 @@ set_color(ColorProfile *self, PyObject *args) { self->dirty = true; if (val == NULL_COLOR_VALUE && i >= 16) { bool semantic, dynamic = palette_generation_is_dynamic(global_state.options_object, &semantic); - self->color_table[i] = dynamic ? generate_256_palette_color(self, self->color_table, i, semantic) : FG_BG_256[i]; + self->color_table[i] = dynamic ? generate_256_palette_color(self, self->color_table, i, semantic) : GENERATED_COLOR_MASK | FG_BG_256[i]; } else self->color_table[i] = val; Py_RETURN_NONE; } @@ -626,7 +634,7 @@ void copy_color_table_to_buffer(ColorProfile *self, color_type *buf, int offset, size_t stride) { size_t i; stride = MAX(1u, stride); - for (i = 0, buf = buf + offset; i < arraysz(self->color_table); i++, buf += stride) *buf = self->color_table[i]; + for (i = 0, buf = buf + offset; i < arraysz(self->color_table); i++, buf += stride) *buf = self->color_table[i] & (~GENERATED_COLOR_MASK); // Copy the mark colors for (i = 0; i < arraysz(self->mark_backgrounds); i++) { *buf = self->mark_backgrounds[i]; buf += stride; @@ -778,11 +786,26 @@ static PyMemberDef cp_members[] = { }; static PyObject* -reload_from_opts(ColorProfile *self, PyObject *args UNUSED) { - PyObject *opts = global_state.options_object; - if (!PyArg_ParseTuple(args, "|O", &opts)) return NULL; +palette_color_is_generated(ColorProfile *self, PyObject *x) { + if (!PyLong_Check(x)) { PyErr_SetString(PyExc_TypeError, "idx must be an integer"); return NULL; } + size_t idx = PyLong_AsUnsignedLong(x); + if (idx >= arraysz(FG_BG_256)) { PyErr_SetString(PyExc_IndexError, "idx out of bounds"); return NULL; } + return Py_NewRef(is_generated_color(self->color_table[idx]) ? Py_True : Py_False); +} + +static PyObject* +reload_from_opts(ColorProfile *self, PyObject *args, PyObject *kw) { + PyObject *opts = NULL; + int reset_overriden_colors = 1; + static const char* kwlist[] = {"opts", "reset_overriden_colors", NULL}; + if (!PyArg_ParseTupleAndKeywords(args, kw, "|Op", (char**)kwlist, &opts, &reset_overriden_colors)) return NULL; + if (opts == NULL) opts = global_state.options_object; self->dirty = true; if (!set_configured_colors(self, opts)) return NULL; + if (reset_overriden_colors) { + zero_at_ptr(&self->overridden); + memset(self->overriden_transparent_colors, 0, sizeof(self->overriden_transparent_colors)); + } if (!set_mark_colors(self, opts)) return NULL; if (!set_colortable(self, opts)) return NULL; Py_RETURN_NONE; @@ -826,8 +849,9 @@ static PyMethodDef cp_methods[] = { METHOD(as_color, METH_O) METHOD(reset_color, METH_O) METHOD(set_color, METH_VARARGS) + METHODB(palette_color_is_generated, METH_O), METHODB(get_transparent_background_color, METH_O), - METHODB(reload_from_opts, METH_VARARGS), + {"reload_from_opts", (PyCFunction)(void (*) (void))(reload_from_opts), METH_VARARGS | METH_KEYWORDS, NULL}, {"set_transparent_background_color", (PyCFunction)(void(*)(void))set_transparent_background_color, METH_FASTCALL, ""}, {NULL} /* Sentinel */ }; diff --git a/kitty/colors.py b/kitty/colors.py index fb5872734..c10776aa0 100644 --- a/kitty/colors.py +++ b/kitty/colors.py @@ -248,7 +248,7 @@ def patch_options_with_color_spec( def patch_colors( - spec: ColorsSpec, transparent_background_colors: TransparentBackgroundColors, configured: bool = False, + spec: ColorsSpec, transparent_background_colors: TransparentBackgroundColors = (), configured: bool = False, windows: Sequence[WindowType] | None = None, notify_on_bg_change: bool = True, background_image_options: BackgroundImageOptions | None = None ) -> None: diff --git a/kitty/fast_data_types.pyi b/kitty/fast_data_types.pyi index ef17a378c..de7fdd657 100644 --- a/kitty/fast_data_types.pyi +++ b/kitty/fast_data_types.pyi @@ -870,7 +870,8 @@ class ColorProfile: def reset_color(self, num: int) -> None: pass - def reload_from_opts(self, opts: Optional[Options] = None) -> None: ... + def reload_from_opts(self, opts: Optional[Options] = None, reset_overriden_colors: bool = True) -> None: ... + def palette_color_is_generated(self, idx: int) -> None: ... def get_transparent_background_color(self, index: int) -> Color | None: ... def set_transparent_background_color(self, index: int, color: Color | None = None, opacity: float | None = None) -> None: ... diff --git a/kitty_tests/screen.py b/kitty_tests/screen.py index 06bbb82e7..d0c604862 100644 --- a/kitty_tests/screen.py +++ b/kitty_tests/screen.py @@ -1593,6 +1593,7 @@ class TestScreen(BaseTest): t('=fleur', 'move') def test_color_profile(self): + from kitty.fast_data_types import patch_color_profiles opts = self.set_options({'palette_generate': 'fixed'}) c = ColorProfile(opts) for i in range(8): @@ -1616,9 +1617,11 @@ class TestScreen(BaseTest): q({'selection_background': ''}) self.assertIsNone(s.color_profile.highlight_bg) q({'selection_background': '?'}, {'selection_background': ''}) + self.assertTrue(s.color_profile.palette_color_is_generated(213)) opts = self.set_options({'palette_generate': 'semantic'}) q({'213': ''}) q({'213': '?'}, {'213': Color(216, 125, 215)}) + self.assertTrue(s.color_profile.palette_color_is_generated(213)) s.color_profile.reload_from_opts(opts) q({'transparent_background_color9': '?'}, {'transparent_background_color9': '?'}) q({'transparent_background_color2': '?'}, {'transparent_background_color2': ''}) @@ -1626,6 +1629,17 @@ class TestScreen(BaseTest): q({'transparent_background_color2': '?'}, {'transparent_background_color2': (Color(255, 0, 0), 126)}) q({'transparent_background_color2': '#ffffff@-1'}) q({'transparent_background_color2': '?'}, {'transparent_background_color2': (Color(255, 255, 255), 255)}) + opts.color114 = Color(1, 1, 4) + s.color_profile.reload_from_opts(opts) + self.assertTrue(s.color_profile.palette_color_is_generated(213)) + self.assertFalse(s.color_profile.palette_color_is_generated(114)) + q({'213': '?'}, {'213': Color(216, 125, 215)}) + patch_color_profiles( + {'background': Color(255, 255, 255), 'foreground': Color(0, 0, 0)}, (), (s.color_profile,), True) + self.assertTrue(s.color_profile.palette_color_is_generated(213)) + self.assertFalse(s.color_profile.palette_color_is_generated(114)) + q({'213': '?'}, {'213': Color(216, 125, 215)}) + def test_multi_cursors(self): s = self.create_screen()