diff --git a/kitty/cell_fragment.glsl b/kitty/cell_fragment.glsl index 113b3c8c2..88080f359 100644 --- a/kitty/cell_fragment.glsl +++ b/kitty/cell_fragment.glsl @@ -23,6 +23,7 @@ uniform sampler2DArray sprites; uniform int text_old_gamma; uniform float text_contrast; uniform float text_gamma_adjustment; +uniform float text_fg_override_threshold; in float effective_text_alpha; in vec3 sprite_pos; in vec3 underline_pos; @@ -130,6 +131,16 @@ float clamp_to_unit_float(float x) { vec4 foreground_contrast(vec4 over, vec3 under) { float underL = dot(under, Y); float overL = dot(over.rgb, Y); + + // 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); + } + // 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. over.a = clamp_to_unit_float(mix(over.a, pow(over.a, text_gamma_adjustment), (1 - overL + underL) * text_gamma_scaling) * text_contrast); diff --git a/kitty/options/definition.py b/kitty/options/definition.py index 435e1c7cc..5288309f9 100644 --- a/kitty/options/definition.py +++ b/kitty/options/definition.py @@ -249,7 +249,7 @@ light text on dark backgrounds thinner. It might also make some text appear like the strokes are uneven. You can fine tune the actual contrast curve used for glyph composition by -specifying two space separated numbers for this setting. +specifying up to three 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,6 +263,11 @@ The second number is an additional multiplicative contrast. It is percentage 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 diff --git a/kitty/options/to-c.h b/kitty/options/to-c.h index fce4c8820..bbdd31285 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_gamma_adjustment = 1.0f; opts->text_contrast = 0.f; opts->text_fg_override_threshold = 0.f; if (PyUnicode_CompareWithASCIIString(val, "platform") == 0) { #ifdef __APPLE__ opts->text_gamma_adjustment = 1.7f; opts->text_contrast = 30.f; @@ -192,15 +192,29 @@ text_composition_strategy(PyObject *val, Options *opts) { else if (PyUnicode_CompareWithASCIIString(val, "legacy") == 0) { opts->text_old_gamma = true; } else { - DECREF_AFTER_FUNCTION PyObject *parts = PyUnicode_Split(val, NULL, 1); - if (PyList_GET_SIZE(parts) != 2) { PyErr_SetString(PyExc_ValueError, "text_rendering_strategy must be of the form number:number"); return; } - DECREF_AFTER_FUNCTION PyObject *ga = PyFloat_FromString(PyList_GET_ITEM(parts, 0)); - if (PyErr_Occurred()) return; - opts->text_gamma_adjustment = MAX(0.01f, PyFloat_AsFloat(ga)); - DECREF_AFTER_FUNCTION PyObject *contrast = PyFloat_FromString(PyList_GET_ITEM(parts, 1)); - if (PyErr_Occurred()) return; - opts->text_contrast = MAX(0.0f, PyFloat_AsFloat(contrast)); - opts->text_contrast = MIN(100.0f, opts->text_contrast); + 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 > 0) { + DECREF_AFTER_FUNCTION PyObject *ga = PyFloat_FromString(PyList_GET_ITEM(parts, 0)); + if (PyErr_Occurred()) return; + opts->text_gamma_adjustment = MAX(0.01f, PyFloat_AsFloat(ga)); + } + + if (size > 1) { + DECREF_AFTER_FUNCTION PyObject *contrast = PyFloat_FromString(PyList_GET_ITEM(parts, 1)); + if (PyErr_Occurred()) return; + 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)); + } } } diff --git a/kitty/shaders.c b/kitty/shaders.c index d9b8005f2..5d77d6bb4 100644 --- a/kitty/shaders.c +++ b/kitty/shaders.c @@ -576,6 +576,9 @@ set_cell_uniforms(float current_inactive_text_alpha, bool force) { S(CELL_PROGRAM, text_contrast, text_contrast, 1f); S(CELL_FG_PROGRAM, text_contrast, text_contrast, 1f); float text_gamma_adjustment = OPT(text_gamma_adjustment) < 0.01f ? 1.0f : 1.0f / OPT(text_gamma_adjustment); S(CELL_PROGRAM, text_gamma_adjustment, text_gamma_adjustment, 1f); S(CELL_FG_PROGRAM, text_gamma_adjustment, text_gamma_adjustment, 1f); + + float text_fg_override_threshold = OPT(text_fg_override_threshold) * 0.01f; + S(CELL_PROGRAM, text_fg_override_threshold, text_fg_override_threshold, 1f); S(CELL_FG_PROGRAM, text_fg_override_threshold, text_fg_override_threshold, 1f); #undef S #define SV(prog, name, num, val, type) { bind_program(prog); glUniform##type(glGetUniformLocation(program_id(prog), #name), num, val); } SV(CELL_PROGRAM, gamma_lut, 256, srgb_lut, 1fv); SV(CELL_FG_PROGRAM, gamma_lut, 256, srgb_lut, 1fv); diff --git a/kitty/state.h b/kitty/state.h index 454aa90d0..08a4557c7 100644 --- a/kitty/state.h +++ b/kitty/state.h @@ -48,7 +48,7 @@ typedef struct { WindowTitleIn macos_show_window_title_in; char *bell_path, *bell_theme; float background_opacity, dim_opacity; - float text_contrast, text_gamma_adjustment; + float text_contrast, text_gamma_adjustment, text_fg_override_threshold; bool text_old_gamma; char *background_image, *default_window_logo;