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
|
||||
uniform sampler2DArray sprites;
|
||||
uniform int text_old_gamma;
|
||||
uniform float text_contrast;
|
||||
uniform float text_gamma_adjustment;
|
||||
in float effective_text_alpha;
|
||||
in vec3 sprite_pos;
|
||||
in vec3 underline_pos;
|
||||
@ -100,6 +103,8 @@ vec4 vec4_premul(vec4 rgba) {
|
||||
#ifdef NEEDS_FOREGROUND
|
||||
// sRGB luminance values
|
||||
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) {
|
||||
// Approximation of linear-to-sRGB conversion
|
||||
@ -116,6 +121,15 @@ float clamp(float x) {
|
||||
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) {
|
||||
// Simulation of gamma-incorrect blending
|
||||
float underL = dot(under, Y);
|
||||
@ -150,8 +164,10 @@ vec4 calculate_foreground() {
|
||||
return foreground_with_decorations(text_fg);
|
||||
}
|
||||
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();
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
undercurl. Sparse and dense control how often the curl oscillates. With sparse
|
||||
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() # }}}
|
||||
|
||||
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:
|
||||
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:
|
||||
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);
|
||||
}
|
||||
|
||||
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
|
||||
convert_from_python_cursor_shape(PyObject *val, Options *opts) {
|
||||
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;
|
||||
convert_from_opts_modify_font(py_opts, opts);
|
||||
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);
|
||||
if (PyErr_Occurred()) return false;
|
||||
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_template',
|
||||
'term',
|
||||
'text_contrast',
|
||||
'text_gamma_adjustment',
|
||||
'text_old_gamma',
|
||||
'touch_scroll_multiplier',
|
||||
'undercurl_style',
|
||||
'update_check_interval',
|
||||
@ -594,6 +597,9 @@ class Options:
|
||||
tab_title_max_length: int = 0
|
||||
tab_title_template: str = '{fmt.fg.red}{bell_symbol}{activity_symbol}{fmt.fg.tab}{title}'
|
||||
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
|
||||
undercurl_style: choices_for_undercurl_style = 'thin-sparse'
|
||||
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, dim_opacity, OPT(dim_opacity), 1f); S(CELL_FG_PROGRAM, dim_opacity, OPT(dim_opacity), 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
|
||||
#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);
|
||||
|
||||
@ -47,6 +47,8 @@ typedef struct {
|
||||
WindowTitleIn macos_show_window_title_in;
|
||||
char *bell_path;
|
||||
float background_opacity, dim_opacity;
|
||||
float text_contrast, text_gamma_adjustment;
|
||||
bool text_old_gamma;
|
||||
|
||||
char *background_image, *default_window_logo;
|
||||
BackgroundImageLayout background_image_layout;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user