Remove glsl if-else, add macro, separate new opt

This adds a config option called text_fg_override_threshold that
specifies a percentage difference luminance below which the
foreground color will be overridden. The foreground color is set
to white if the background is dark or black if the background is
light. The default is 0, and the computations will only be
performed if the option is set.

Many programs output colors that look good with the author's
terminal's color scheme but which are completely illegible with
other color schemes. This allows the user ensure that there is
always sufficient contrast to read the text on the screen.

I originally implemented this is as a parameter on
text_composition_strategy. For that to work, the option needed to
take _up to_ rather than _exactly_ the number of available
parameters. While it now has nothing to do with the new feature,
it seems like that change should be made anyway, so I'm leaving
it in for now.
This commit is contained in:
Luna McNulty 2023-05-20 09:54:43 -04:00
parent 05d94e8256
commit 9bd97b090d
8 changed files with 59 additions and 21 deletions

View file

@ -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.

View file

@ -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 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 @@ 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
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() # }}}

View file

@ -1284,6 +1284,9 @@ class Parser:
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)

View file

@ -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);

View file

@ -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; }

View file

@ -445,6 +445,7 @@ option_names = ( # {{{
'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

View file

@ -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;

View file

@ -397,6 +397,8 @@ class LoadShaderPrograms:
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')