feat: additional contrast on text as a function of luminance difference
This commit is contained in:
parent
e64affe3f7
commit
3676e6651d
@ -20,6 +20,9 @@ in float bg_alpha;
|
|||||||
|
|
||||||
#ifdef NEEDS_FOREGROUND
|
#ifdef NEEDS_FOREGROUND
|
||||||
uniform sampler2DArray sprites;
|
uniform sampler2DArray sprites;
|
||||||
|
uniform int text_old_gamma;
|
||||||
|
uniform float text_contrast;
|
||||||
|
uniform float text_gamma_adjustment;
|
||||||
in float effective_text_alpha;
|
in float effective_text_alpha;
|
||||||
in vec3 sprite_pos;
|
in vec3 sprite_pos;
|
||||||
in vec3 underline_pos;
|
in vec3 underline_pos;
|
||||||
@ -100,6 +103,8 @@ vec4 vec4_premul(vec4 rgba) {
|
|||||||
#ifdef NEEDS_FOREGROUND
|
#ifdef NEEDS_FOREGROUND
|
||||||
// sRGB luminance values
|
// sRGB luminance values
|
||||||
const vec3 Y = vec3(0.2126, 0.7152, 0.0722);
|
const vec3 Y = vec3(0.2126, 0.7152, 0.0722);
|
||||||
|
// Scaling factor for the extra text-alpha adjustment for luminance-difference.
|
||||||
|
const float text_gamma_scaling = 0.5;
|
||||||
|
|
||||||
float linear2srgb(float x) {
|
float linear2srgb(float x) {
|
||||||
// Approximation of linear-to-sRGB conversion
|
// Approximation of linear-to-sRGB conversion
|
||||||
@ -116,6 +121,15 @@ float clamp(float x) {
|
|||||||
return max(min(x, 1.0f), 0.0f);
|
return max(min(x, 1.0f), 0.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vec4 foreground_contrast(vec4 over, vec3 under) {
|
||||||
|
float underL = dot(under, Y);
|
||||||
|
float overL = dot(over.rgb, Y);
|
||||||
|
// 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 sasturation.
|
||||||
|
over.a = clamp(mix(over.a, pow(over.a, text_gamma_adjustment), (1 - overL + underL) * text_gamma_scaling) * text_contrast);
|
||||||
|
return over;
|
||||||
|
}
|
||||||
|
|
||||||
vec4 foreground_contrast_incorrect(vec4 over, vec3 under) {
|
vec4 foreground_contrast_incorrect(vec4 over, vec3 under) {
|
||||||
// Simulation of gamma-incorrect blending
|
// Simulation of gamma-incorrect blending
|
||||||
float underL = dot(under, Y);
|
float underL = dot(under, Y);
|
||||||
@ -150,8 +164,10 @@ vec4 calculate_foreground() {
|
|||||||
return foreground_with_decorations(text_fg);
|
return foreground_with_decorations(text_fg);
|
||||||
}
|
}
|
||||||
vec4 calculate_foreground(vec3 bg) {
|
vec4 calculate_foreground(vec3 bg) {
|
||||||
|
// When rendering on a background we can adjust the alpha channel contrast
|
||||||
|
// to improve legibility based on the source and destination colors
|
||||||
vec4 text_fg = foreground_color();
|
vec4 text_fg = foreground_color();
|
||||||
text_fg = foreground_contrast_incorrect(text_fg, bg);
|
text_fg = mix(foreground_contrast(text_fg, bg), foreground_contrast_incorrect(text_fg, bg), text_old_gamma);
|
||||||
return foreground_with_decorations(text_fg);
|
return foreground_with_decorations(text_fg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -230,6 +230,41 @@ The style with which undercurls are rendered. This option takes the form
|
|||||||
:code:`(thin|thick)-(sparse|dense)`. Thin and thick control the thickness of the
|
:code:`(thin|thick)-(sparse|dense)`. Thin and thick control the thickness of the
|
||||||
undercurl. Sparse and dense control how often the curl oscillates. With sparse
|
undercurl. Sparse and dense control how often the curl oscillates. With sparse
|
||||||
the curl will peak once per character, with dense twice.
|
the curl will peak once per character, with dense twice.
|
||||||
|
'''
|
||||||
|
)
|
||||||
|
|
||||||
|
opt('text_old_gamma', 'no',
|
||||||
|
option_type='to_bool', ctype='bool',
|
||||||
|
long_text='''
|
||||||
|
If to simulate the old gamma-incorrect blending for the text alpha-channel, this
|
||||||
|
will make some text appear like the strokes are uneven. Dark text on bright backgrounds
|
||||||
|
will also look thicker while lighter text on darker backgrounds will look thinner.
|
||||||
|
'''
|
||||||
|
)
|
||||||
|
|
||||||
|
opt('text_gamma_adjustment', '1.0',
|
||||||
|
option_type='positive_float', ctype='float',
|
||||||
|
long_text='''
|
||||||
|
This setting adjusts the thickness of darker text on lighter backgrounds. Increasing the value
|
||||||
|
setting will make the text appear thicker while decreasing the value will make it thinner. It
|
||||||
|
can compensate for some fonts looking too-thin when using the gamma-correct alpha blending.
|
||||||
|
|
||||||
|
The result is scaled based on the luminance difference between the background and the foreground.
|
||||||
|
Dark text on light backgrounds receive the full impact of the curve while light text on dark
|
||||||
|
backgrounds are affected very little.
|
||||||
|
|
||||||
|
Range: >=0.01
|
||||||
|
MacOS: 1.7
|
||||||
|
'''
|
||||||
|
)
|
||||||
|
|
||||||
|
opt('text_contrast', '0',
|
||||||
|
option_type='positive_float', ctype='float',
|
||||||
|
long_text='''
|
||||||
|
Additional multiplicative text contrast as a percentage, will saturate and cause jagged edges if set too high.
|
||||||
|
|
||||||
|
Range: >=0.0
|
||||||
|
MacOS: 30
|
||||||
'''
|
'''
|
||||||
)
|
)
|
||||||
egr() # }}}
|
egr() # }}}
|
||||||
|
|||||||
9
kitty/options/parse.py
generated
9
kitty/options/parse.py
generated
@ -1278,6 +1278,15 @@ class Parser:
|
|||||||
def term(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
|
def term(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
|
||||||
ans['term'] = str(val)
|
ans['term'] = str(val)
|
||||||
|
|
||||||
|
def text_contrast(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
|
||||||
|
ans['text_contrast'] = positive_float(val)
|
||||||
|
|
||||||
|
def text_gamma_adjustment(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
|
||||||
|
ans['text_gamma_adjustment'] = positive_float(val)
|
||||||
|
|
||||||
|
def text_old_gamma(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
|
||||||
|
ans['text_old_gamma'] = to_bool(val)
|
||||||
|
|
||||||
def touch_scroll_multiplier(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
|
def touch_scroll_multiplier(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
|
||||||
ans['touch_scroll_multiplier'] = float(val)
|
ans['touch_scroll_multiplier'] = float(val)
|
||||||
|
|
||||||
|
|||||||
45
kitty/options/to-c-generated.h
generated
45
kitty/options/to-c-generated.h
generated
@ -57,6 +57,45 @@ convert_from_opts_modify_font(PyObject *py_opts, Options *opts) {
|
|||||||
Py_DECREF(ret);
|
Py_DECREF(ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
convert_from_python_text_old_gamma(PyObject *val, Options *opts) {
|
||||||
|
opts->text_old_gamma = PyObject_IsTrue(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
convert_from_opts_text_old_gamma(PyObject *py_opts, Options *opts) {
|
||||||
|
PyObject *ret = PyObject_GetAttrString(py_opts, "text_old_gamma");
|
||||||
|
if (ret == NULL) return;
|
||||||
|
convert_from_python_text_old_gamma(ret, opts);
|
||||||
|
Py_DECREF(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
convert_from_python_text_gamma_adjustment(PyObject *val, Options *opts) {
|
||||||
|
opts->text_gamma_adjustment = PyFloat_AsFloat(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
convert_from_opts_text_gamma_adjustment(PyObject *py_opts, Options *opts) {
|
||||||
|
PyObject *ret = PyObject_GetAttrString(py_opts, "text_gamma_adjustment");
|
||||||
|
if (ret == NULL) return;
|
||||||
|
convert_from_python_text_gamma_adjustment(ret, opts);
|
||||||
|
Py_DECREF(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
convert_from_python_text_contrast(PyObject *val, Options *opts) {
|
||||||
|
opts->text_contrast = PyFloat_AsFloat(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
convert_from_opts_text_contrast(PyObject *py_opts, Options *opts) {
|
||||||
|
PyObject *ret = PyObject_GetAttrString(py_opts, "text_contrast");
|
||||||
|
if (ret == NULL) return;
|
||||||
|
convert_from_python_text_contrast(ret, opts);
|
||||||
|
Py_DECREF(ret);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
convert_from_python_cursor_shape(PyObject *val, Options *opts) {
|
convert_from_python_cursor_shape(PyObject *val, Options *opts) {
|
||||||
opts->cursor_shape = PyLong_AsLong(val);
|
opts->cursor_shape = PyLong_AsLong(val);
|
||||||
@ -1042,6 +1081,12 @@ convert_opts_from_python_opts(PyObject *py_opts, Options *opts) {
|
|||||||
if (PyErr_Occurred()) return false;
|
if (PyErr_Occurred()) return false;
|
||||||
convert_from_opts_modify_font(py_opts, opts);
|
convert_from_opts_modify_font(py_opts, opts);
|
||||||
if (PyErr_Occurred()) return false;
|
if (PyErr_Occurred()) return false;
|
||||||
|
convert_from_opts_text_old_gamma(py_opts, opts);
|
||||||
|
if (PyErr_Occurred()) return false;
|
||||||
|
convert_from_opts_text_gamma_adjustment(py_opts, opts);
|
||||||
|
if (PyErr_Occurred()) return false;
|
||||||
|
convert_from_opts_text_contrast(py_opts, opts);
|
||||||
|
if (PyErr_Occurred()) return false;
|
||||||
convert_from_opts_cursor_shape(py_opts, opts);
|
convert_from_opts_cursor_shape(py_opts, opts);
|
||||||
if (PyErr_Occurred()) return false;
|
if (PyErr_Occurred()) return false;
|
||||||
convert_from_opts_cursor_beam_thickness(py_opts, opts);
|
convert_from_opts_cursor_beam_thickness(py_opts, opts);
|
||||||
|
|||||||
6
kitty/options/types.py
generated
6
kitty/options/types.py
generated
@ -443,6 +443,9 @@ option_names = ( # {{{
|
|||||||
'tab_title_max_length',
|
'tab_title_max_length',
|
||||||
'tab_title_template',
|
'tab_title_template',
|
||||||
'term',
|
'term',
|
||||||
|
'text_contrast',
|
||||||
|
'text_gamma_adjustment',
|
||||||
|
'text_old_gamma',
|
||||||
'touch_scroll_multiplier',
|
'touch_scroll_multiplier',
|
||||||
'undercurl_style',
|
'undercurl_style',
|
||||||
'update_check_interval',
|
'update_check_interval',
|
||||||
@ -594,6 +597,9 @@ class Options:
|
|||||||
tab_title_max_length: int = 0
|
tab_title_max_length: int = 0
|
||||||
tab_title_template: str = '{fmt.fg.red}{bell_symbol}{activity_symbol}{fmt.fg.tab}{title}'
|
tab_title_template: str = '{fmt.fg.red}{bell_symbol}{activity_symbol}{fmt.fg.tab}{title}'
|
||||||
term: str = 'xterm-kitty'
|
term: str = 'xterm-kitty'
|
||||||
|
text_contrast: float = 0
|
||||||
|
text_gamma_adjustment: float = 1.0
|
||||||
|
text_old_gamma: bool = False
|
||||||
touch_scroll_multiplier: float = 1.0
|
touch_scroll_multiplier: float = 1.0
|
||||||
undercurl_style: choices_for_undercurl_style = 'thin-sparse'
|
undercurl_style: choices_for_undercurl_style = 'thin-sparse'
|
||||||
update_check_interval: float = 24.0
|
update_check_interval: float = 24.0
|
||||||
|
|||||||
@ -569,6 +569,12 @@ set_cell_uniforms(float current_inactive_text_alpha, bool force) {
|
|||||||
S(CELL_PROGRAM, sprites, SPRITE_MAP_UNIT, 1i); S(CELL_FG_PROGRAM, sprites, SPRITE_MAP_UNIT, 1i);
|
S(CELL_PROGRAM, sprites, SPRITE_MAP_UNIT, 1i); S(CELL_FG_PROGRAM, sprites, SPRITE_MAP_UNIT, 1i);
|
||||||
S(CELL_PROGRAM, dim_opacity, OPT(dim_opacity), 1f); S(CELL_FG_PROGRAM, dim_opacity, OPT(dim_opacity), 1f);
|
S(CELL_PROGRAM, dim_opacity, OPT(dim_opacity), 1f); S(CELL_FG_PROGRAM, dim_opacity, OPT(dim_opacity), 1f);
|
||||||
S(CELL_BG_PROGRAM, defaultbg, OPT(background), 1f);
|
S(CELL_BG_PROGRAM, defaultbg, OPT(background), 1f);
|
||||||
|
int text_old_gamma = OPT(text_old_gamma) ? 1 : 0;
|
||||||
|
S(CELL_PROGRAM, text_old_gamma, text_old_gamma, 1i); S(CELL_FG_PROGRAM, text_old_gamma, text_old_gamma, 1i);
|
||||||
|
float text_contrast = 1.0f + OPT(text_contrast) * 0.01f;
|
||||||
|
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);
|
||||||
#undef S
|
#undef S
|
||||||
#define SV(prog, name, num, val, type) { bind_program(prog); glUniform##type(glGetUniformLocation(program_id(prog), #name), num, val); }
|
#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);
|
SV(CELL_PROGRAM, gamma_lut, 256, srgb_lut, 1fv); SV(CELL_FG_PROGRAM, gamma_lut, 256, srgb_lut, 1fv);
|
||||||
|
|||||||
@ -47,6 +47,8 @@ typedef struct {
|
|||||||
WindowTitleIn macos_show_window_title_in;
|
WindowTitleIn macos_show_window_title_in;
|
||||||
char *bell_path;
|
char *bell_path;
|
||||||
float background_opacity, dim_opacity;
|
float background_opacity, dim_opacity;
|
||||||
|
float text_contrast, text_gamma_adjustment;
|
||||||
|
bool text_old_gamma;
|
||||||
|
|
||||||
char *background_image, *default_window_logo;
|
char *background_image, *default_window_logo;
|
||||||
BackgroundImageLayout background_image_layout;
|
BackgroundImageLayout background_image_layout;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user