Only apply linear2srgb to actual text pixels

Better fix for #6209 as it does not make background_opacity non-linear.
Instead the linear2srgb() function is applied only to pixels that
actually have text content.
This commit is contained in:
Kovid Goyal
2023-06-20 13:45:34 +05:30
parent 85f066f312
commit e1ce6d08e0
4 changed files with 37 additions and 39 deletions

View File

@@ -1,5 +1,3 @@
#pragma kitty_include_shader <linear2srgb.glsl>
uniform sampler2D image;
in vec2 texcoord;
@@ -8,5 +6,4 @@ out vec4 color;
void main() {
color = texture(image, texcoord);
color.a = linear2srgb(color.a);
}

View File

@@ -1,8 +1,6 @@
#pragma kitty_include_shader <linear2srgb.glsl>
uniform uvec2 viewport;
uniform uint colors[9];
uniform float background_opacity, do_srgb_correction;
uniform float background_opacity;
uniform float tint_opacity, tint_premult;
uniform float gamma_lut[256];
in vec4 rect; // left, top, right, bottom
@@ -46,6 +44,5 @@ void main() {
color3 = is_window_bg * window_bg + (1. - is_window_bg) * color3;
float final_opacity = is_default_bg * tint_opacity + (1. - is_default_bg) * background_opacity;
float final_premult_opacity = is_default_bg * tint_premult + (1. - is_default_bg) * background_opacity;
final_opacity = do_srgb_correction * linear2srgb(final_opacity) + (1. - do_srgb_correction) * final_opacity;
color = vec4(color3 * final_premult_opacity, final_opacity);
}

View File

@@ -117,6 +117,7 @@ vec4 foreground_contrast(vec4 over, vec3 under) {
return over;
}
#ifdef TEXT_OLD_GAMMA
vec4 foreground_contrast_incorrect(vec4 over, vec3 under) {
// Simulation of gamma-incorrect blending
float under_luminance = dot(under, Y);
@@ -131,13 +132,17 @@ vec4 foreground_contrast_incorrect(vec4 over, vec3 under) {
over.a = clamp_to_unit_float((srgb2linear(linear2srgb(over_lumininace) * over.a + linear2srgb(under_luminance) * (1.0f - over.a)) - under_luminance) / (over_lumininace - under_luminance));
return over;
}
#endif
vec4 foreground_color() {
vec4 load_text_foreground_color() {
// For colored sprites use the color from the sprite rather than the text foreground
// Return non-premultiplied foreground color
vec4 text_fg = texture(sprites, sprite_pos);
return vec4(mix(foreground, text_fg.rgb, colored_sprite), text_fg.a);
}
vec4 foreground_with_decorations(vec4 text_fg) {
vec4 calculate_premul_foreground_from_sprites(vec4 text_fg) {
// Return premul foreground color from decorations (cursor, underline, strikethrough)
float underline_alpha = texture(sprites, underline_pos).a;
float strike_alpha = texture(sprites, strike_pos).a;
float cursor_alpha = texture(sprites, cursor_pos).a;
@@ -148,39 +153,41 @@ vec4 foreground_with_decorations(vec4 text_fg) {
return mix(ans, cursor_color_vec, cursor_alpha);
}
vec4 calculate_foreground() {
// returns the effective foreground color in pre-multiplied form
vec4 text_fg = foreground_color();
return foreground_with_decorations(text_fg);
}
vec4 calculate_foreground(vec3 bg) {
vec4 adjust_foreground_contrast_with_background(vec4 text_fg, 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();
#ifdef TEXT_OLD_GAMMA
text_fg = foreground_contrast_incorrect(text_fg, bg);
return foreground_contrast_incorrect(text_fg, bg);
#else
text_fg = foreground_contrast(text_fg, bg);
return foreground_contrast(text_fg, bg);
#endif
return foreground_with_decorations(text_fg);
}
#endif
float adjust_alpha_for_incorrect_blending_by_compositor(float text_fg_alpha, float final_alpha) {
// Adjust the transparent alpha-channel to account for incorrect
// gamma-blending performed by the compositor (true for at least wlroots, picom)
// We have a linear alpha channel apply the sRGB curve to it once again to compensate
// for the incorrect blending in the compositor.
// We apply the correction only if there was actual text at this pixel, so as to not make
// background_opacity non-linear
// See https://github.com/kovidgoyal/kitty/issues/6209 for discussion.
const float threshold = 0.000001;
// linear2srgb(final_alpha) if text_fg_alpha >= threshold else final_alpha
return mix(final_alpha, linear2srgb(final_alpha), step(threshold, text_fg_alpha));
}
void main() {
#ifdef SIMPLE
vec4 fg = calculate_foreground(background);
vec4 text_fg = load_text_foreground_color();
text_fg = adjust_foreground_contrast_with_background(text_fg, background);
vec4 text_fg_premul = calculate_premul_foreground_from_sprites(text_fg);
#ifdef TRANSPARENT
final_color = alpha_blend_premul(fg, vec4_premul(background, bg_alpha));
// Adjust the transparent alpha-channel to account for incorrect
// gamma-blending performed by the compositor (true for at least wlroots,
// picom, GNOME, MacOS).
// This is the last pass:
final_color.a = linear2srgb(final_color.a);
final_color = alpha_blend_premul(text_fg_premul, vec4_premul(background, bg_alpha));
final_color.a = adjust_alpha_for_incorrect_blending_by_compositor(text_fg_premul.a, final_color.a);
#else
final_color = alpha_blend_premul(fg, background);
final_color = alpha_blend_premul(text_fg_premul, background);
#endif
#endif
@@ -201,11 +208,12 @@ void main() {
#endif
#ifdef FOREGROUND
final_color = calculate_foreground(); // pre-multiplied foreground
// This is the last pass, called both with transparency and without but not in draw_cells_simple().
// When transparent it is drawn into a framebuffer and linear2srgb() will be done
// when blitting that framebuffer. When not transparent there is not need to do linear2srgb() anyway.
vec4 text_fg = load_text_foreground_color();
vec4 text_fg_premul = calculate_premul_foreground_from_sprites(text_fg);
final_color = text_fg_premul;
#ifdef TRANSPARENT
final_color.a = adjust_alpha_for_incorrect_blending_by_compositor(text_fg_premul.a, final_color.a);
#endif
#endif
}

View File

@@ -988,7 +988,7 @@ draw_cells(ssize_t vao_idx, ssize_t gvao_idx, const ScreenRenderData *srd, float
// }}}
// Borders {{{
enum BorderUniforms { BORDER_viewport, BORDER_background_opacity, BORDER_tint_opacity, BORDER_tint_premult, BORDER_colors, BORDER_gamma_lut, BORDER_do_srgb_correction, NUM_BORDER_UNIFORMS };
enum BorderUniforms { BORDER_viewport, BORDER_background_opacity, BORDER_tint_opacity, BORDER_tint_premult, BORDER_colors, BORDER_gamma_lut, NUM_BORDER_UNIFORMS };
static GLint border_uniform_locations[NUM_BORDER_UNIFORMS] = {0};
static void
@@ -998,7 +998,6 @@ init_borders_program(void) {
SET_LOC(background_opacity)
SET_LOC(tint_opacity)
SET_LOC(tint_premult)
SET_LOC(do_srgb_correction)
SET_LOC(colors)
SET_LOC(gamma_lut)
#undef SET_LOC
@@ -1022,7 +1021,6 @@ draw_borders(ssize_t vao_idx, unsigned int num_border_rects, BorderRect *rect_bu
float background_opacity = w->is_semi_transparent ? w->background_opacity: 1.0f;
float tint_opacity = background_opacity;
float tint_premult = background_opacity;
float do_srgb_correction = 1.0f;
if (has_bgimage(w)) {
glEnable(GL_BLEND);
BLEND_ONTO_OPAQUE;
@@ -1031,7 +1029,6 @@ draw_borders(ssize_t vao_idx, unsigned int num_border_rects, BorderRect *rect_bu
background_opacity = 1.0f;
tint_opacity = OPT(background_tint) * OPT(background_tint_gaps);
tint_premult = w->is_semi_transparent ? OPT(background_tint) : 1.0f;
do_srgb_correction = 0.0f;
}
if (num_border_rects) {
@@ -1053,7 +1050,6 @@ draw_borders(ssize_t vao_idx, unsigned int num_border_rects, BorderRect *rect_bu
glUniform1f(border_uniform_locations[BORDER_background_opacity], background_opacity);
glUniform1f(border_uniform_locations[BORDER_tint_opacity], tint_opacity);
glUniform1f(border_uniform_locations[BORDER_tint_premult], tint_premult);
glUniform1f(border_uniform_locations[BORDER_do_srgb_correction], do_srgb_correction);
glUniform2ui(border_uniform_locations[BORDER_viewport], viewport_width, viewport_height);
glUniform1fv(border_uniform_locations[BORDER_gamma_lut], 256, srgb_lut);
if (has_bgimage(w)) {