diff --git a/kitty/cell_fragment.glsl b/kitty/cell_fragment.glsl index 88080f359..df6cb767c 100644 --- a/kitty/cell_fragment.glsl +++ b/kitty/cell_fragment.glsl @@ -1,6 +1,7 @@ #version GLSL_VERSION #define {WHICH_PROGRAM} #define NOT_TRANSPARENT +#define NO_FG_OVERRIDE #if defined(SIMPLE) || defined(BACKGROUND) || defined(SPECIAL) #define NEEDS_BACKROUND @@ -132,14 +133,17 @@ vec4 foreground_contrast(vec4 over, vec3 under) { float underL = dot(under, Y); float overL = dot(over.rgb, Y); +#if defined(FG_OVERRIDE) // If the difference in luminance is too small, // force the foreground color to be black or white. float diffL = abs(underL - overL); - if (0.5 < underL && diffL < text_fg_override_threshold) { - over.rgb = vec3(0, 0, 0); - } else if (underL < 0.5 && diffL < text_fg_override_threshold) { - over.rgb = vec3(1, 1, 1); - } + float overrideLvl = step(diffL, text_fg_override_threshold); + float originalLvl = 1.f - overrideLvl; + over.rgb = ( + originalLvl * over.rgb + + overrideLvl * vec3(step(underL, 0.5f)) + ); +#endif // Apply additional gamma-adjustment scaled by the luminance difference, the darker the foreground the more adjustment we apply. // A multiplicative contrast is also available to increase saturation. diff --git a/kitty/options/definition.py b/kitty/options/definition.py index 5288309f9..b7c84c169 100644 --- a/kitty/options/definition.py +++ b/kitty/options/definition.py @@ -249,7 +249,7 @@ the strokes are uneven. You can fine tune the actual contrast curve used for glyph composition by -specifying up to three space-separated numbers for this setting. +specifying up to two space-separated numbers for this setting. The first number is the gamma adjustment, which controls the thickness of dark text on light backgrounds. Increasing the value will make text appear thicker. @@ -263,17 +263,23 @@ ranging from :code:`0` to :code:`100`. The default value is :code:`0` on Linux and :code:`30` on macOS. -The third number is an override threshold. It is percentage ranging from :code:`0` -to :code:`100`. If the difference in luminance of the foreground and background -is below this threshold, the foreground color will be set to white if the background -is dark or black if the background is light. The default value is :code:`0`. - If you wish to achieve similar looking thickness in light and dark themes, a good way to experiment is start by setting the value to :code:`1.0 0` and use a dark theme. Then adjust the second parameter until it looks good. Then switch to a light theme and adjust the first parameter until the perceived thickness matches the dark theme. ''') +opt('text_fg_override_threshold', 0, + ctype='!text_fg_override_threshold', + long_text=''' +The minimum accepted difference in luminance between the foreground and background +color, below which kitty will override the foreground color. It is percentage +ranging from :code:`0` to :code:`100`. If the difference in luminance of the +foreground and background is below this threshold, the foreground color will be set +to white if the background is dark or black if the background is light. The default +value is :code:`0`. +''') + egr() # }}} diff --git a/kitty/options/parse.py b/kitty/options/parse.py index d4a607f15..7bd395c28 100644 --- a/kitty/options/parse.py +++ b/kitty/options/parse.py @@ -1284,6 +1284,9 @@ def term(self, val: str, ans: typing.Dict[str, typing.Any]) -> None: def text_composition_strategy(self, val: str, ans: typing.Dict[str, typing.Any]) -> None: ans['text_composition_strategy'] = str(val) + def text_fg_override_threshold(self, val: str, ans: typing.Dict[str, typing.Any]) -> None: + ans['text_fg_override_threshold'] = str(val) + def touch_scroll_multiplier(self, val: str, ans: typing.Dict[str, typing.Any]) -> None: ans['touch_scroll_multiplier'] = float(val) diff --git a/kitty/options/to-c-generated.h b/kitty/options/to-c-generated.h index c1ea350a2..8fb15102e 100644 --- a/kitty/options/to-c-generated.h +++ b/kitty/options/to-c-generated.h @@ -70,6 +70,19 @@ convert_from_opts_text_composition_strategy(PyObject *py_opts, Options *opts) { Py_DECREF(ret); } +static void +convert_from_python_text_fg_override_threshold(PyObject *val, Options *opts) { + text_fg_override_threshold(val, opts); +} + +static void +convert_from_opts_text_fg_override_threshold(PyObject *py_opts, Options *opts) { + PyObject *ret = PyObject_GetAttrString(py_opts, "text_fg_override_threshold"); + if (ret == NULL) return; + convert_from_python_text_fg_override_threshold(ret, opts); + Py_DECREF(ret); +} + static void convert_from_python_cursor_shape(PyObject *val, Options *opts) { opts->cursor_shape = PyLong_AsLong(val); @@ -1070,6 +1083,8 @@ convert_opts_from_python_opts(PyObject *py_opts, Options *opts) { if (PyErr_Occurred()) return false; convert_from_opts_text_composition_strategy(py_opts, opts); if (PyErr_Occurred()) return false; + convert_from_opts_text_fg_override_threshold(py_opts, opts); + if (PyErr_Occurred()) return false; convert_from_opts_cursor_shape(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_cursor_beam_thickness(py_opts, opts); diff --git a/kitty/options/to-c.h b/kitty/options/to-c.h index bbdd31285..fa4661c6d 100644 --- a/kitty/options/to-c.h +++ b/kitty/options/to-c.h @@ -183,7 +183,7 @@ static void text_composition_strategy(PyObject *val, Options *opts) { if (!PyUnicode_Check(val)) { PyErr_SetString(PyExc_TypeError, "text_rendering_strategy must be a string"); return; } opts->text_old_gamma = false; - opts->text_gamma_adjustment = 1.0f; opts->text_contrast = 0.f; opts->text_fg_override_threshold = 0.f; + opts->text_gamma_adjustment = 1.0f; opts->text_contrast = 0.f; if (PyUnicode_CompareWithASCIIString(val, "platform") == 0) { #ifdef __APPLE__ opts->text_gamma_adjustment = 1.7f; opts->text_contrast = 30.f; @@ -194,7 +194,7 @@ text_composition_strategy(PyObject *val, Options *opts) { } else { DECREF_AFTER_FUNCTION PyObject *parts = PyUnicode_Split(val, NULL, 2); int size = PyList_GET_SIZE(parts); - if (size < 1 || 3 < size) { PyErr_SetString(PyExc_ValueError, "text_rendering_strategy must be of the form number:[number]:[number]"); return; } + if (size < 1 || 2 < size) { PyErr_SetString(PyExc_ValueError, "text_rendering_strategy must be of the form number:[number]"); return; } if (size > 0) { DECREF_AFTER_FUNCTION PyObject *ga = PyFloat_FromString(PyList_GET_ITEM(parts, 0)); @@ -208,16 +208,21 @@ text_composition_strategy(PyObject *val, Options *opts) { opts->text_contrast = MAX(0.0f, PyFloat_AsFloat(contrast)); opts->text_contrast = MIN(100.0f, opts->text_contrast); } - - if (size > 2) { - DECREF_AFTER_FUNCTION PyObject *text_fg_override_threshold = PyFloat_FromString(PyList_GET_ITEM(parts, 2)); - if (PyErr_Occurred()) return; - opts->text_fg_override_threshold = MAX(0.f, PyFloat_AsFloat(text_fg_override_threshold)); - opts->text_fg_override_threshold = MIN(100.f, PyFloat_AsFloat(text_fg_override_threshold)); - } } } +static void +text_fg_override_threshold(PyObject *val, Options *opts) { + if (!PyUnicode_Check(val)) { PyErr_SetString(PyExc_TypeError, "text_fg_override_threshold must be a string"); return; } + opts->text_fg_override_threshold = 0.f; + + DECREF_AFTER_FUNCTION PyObject *text_fg_override_threshold = PyFloat_FromString(val); + if (PyErr_Occurred()) return; + opts->text_fg_override_threshold = MAX(0.f, PyFloat_AsFloat(text_fg_override_threshold)); + opts->text_fg_override_threshold = MIN(100.f, PyFloat_AsFloat(text_fg_override_threshold)); + +} + static char_type* list_of_chars(PyObject *chars) { if (!PyUnicode_Check(chars)) { PyErr_SetString(PyExc_TypeError, "list_of_chars must be a string"); return NULL; } diff --git a/kitty/options/types.py b/kitty/options/types.py index dde02d82f..f4a738db3 100644 --- a/kitty/options/types.py +++ b/kitty/options/types.py @@ -445,6 +445,7 @@ 'tab_title_template', 'term', 'text_composition_strategy', + 'text_fg_override_threshold', 'touch_scroll_multiplier', 'undercurl_style', 'update_check_interval', @@ -598,6 +599,7 @@ class Options: tab_title_template: str = '{fmt.fg.red}{bell_symbol}{activity_symbol}{fmt.fg.tab}{title}' term: str = 'xterm-kitty' text_composition_strategy: str = 'platform' + text_fg_override_threshold: str = '0' touch_scroll_multiplier: float = 1.0 undercurl_style: choices_for_undercurl_style = 'thin-sparse' update_check_interval: float = 24.0 diff --git a/kitty/state.h b/kitty/state.h index 08a4557c7..7fd748d63 100644 --- a/kitty/state.h +++ b/kitty/state.h @@ -48,7 +48,8 @@ typedef struct { WindowTitleIn macos_show_window_title_in; char *bell_path, *bell_theme; float background_opacity, dim_opacity; - float text_contrast, text_gamma_adjustment, text_fg_override_threshold; + float text_contrast, text_gamma_adjustment; + float text_fg_override_threshold; bool text_old_gamma; char *background_image, *default_window_logo; diff --git a/kitty/window.py b/kitty/window.py index 732c8d051..f5e207932 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -397,6 +397,8 @@ def __call__(self, semi_transparent: bool = False) -> None: DECORATION_MASK=DECORATION_MASK, STRIKE_SPRITE_INDEX=NUM_UNDERLINE_STYLES + 1, ) + if get_options().text_fg_override_threshold != '0': + ff = ff.replace('#define NO_FG_OVERRIDE', '#define FG_OVERRIDE') if semi_transparent: vv = vv.replace('#define NOT_TRANSPARENT', '#define TRANSPARENT') ff = ff.replace('#define NOT_TRANSPARENT', '#define TRANSPARENT')