diff --git a/gen-srgb-lut.py b/gen-srgb-lut.py new file mode 100755 index 000000000..7d4a328a8 --- /dev/null +++ b/gen-srgb-lut.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +# vim:fileencoding=utf-8 + +import os +from typing import List + + +def to_linear(a: float) -> float: + if a <= 0.04045: + return a / 12.92 + else: + return float(pow((a + 0.055) / 1.055, 2.4)) + + +def generate_srgb_lut(line_prefix: str = '') -> List[str]: + values: List[str] = [] + lines: List[str] = [] + + for i in range(256): + values.append('{:1.5f}f'.format(to_linear(i / 255.0))) + + for i in range(16): + lines.append(line_prefix + ', '.join(values[i * 16:(i + 1) * 16]) + ',') + + return lines + + +def generate_srgb_gamma_c() -> str: + lines: List[str] = [] + + lines.append('// Generated by gen-srgb-lut.py DO NOT edit') + lines.append('#include "srgb_gamma.h"') + lines.append('') + lines.append('const GLfloat srgb_lut[256] = {') + lines += generate_srgb_lut(' ') + lines.append('};') + + return "\n".join(lines) + + +def main() -> None: + c = generate_srgb_gamma_c() + with open(os.path.join('kitty', 'srgb_gamma.c'), 'w') as f: + f.write(f'{c}\n') + + +if __name__ == '__main__': + main() diff --git a/kitty/border_vertex.glsl b/kitty/border_vertex.glsl index 69762df95..972bd2b8d 100644 --- a/kitty/border_vertex.glsl +++ b/kitty/border_vertex.glsl @@ -3,6 +3,7 @@ uniform uvec2 viewport; uniform uint colors[9]; uniform float background_opacity; uniform float tint_opacity, tint_premult; +uniform float gamma_lut[256]; in vec4 rect; // left, top, right, bottom in uint rect_color; out vec4 color; @@ -22,7 +23,7 @@ const uvec2 pos_map[] = uvec2[4]( ); float to_color(uint c) { - return float(c & FF) / 255.0; + return gamma_lut[c & FF]; } float is_integer_value(uint c, float x) { diff --git a/kitty/cell_fragment.glsl b/kitty/cell_fragment.glsl index 28077d326..599bb0c90 100644 --- a/kitty/cell_fragment.glsl +++ b/kitty/cell_fragment.glsl @@ -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; @@ -34,32 +37,36 @@ in float colored_sprite; out vec4 final_color; // Util functions {{{ -vec4 alpha_blend(vec3 over, float over_alpha, vec3 under, float under_alpha) { +vec4 alpha_blend(vec4 over, vec4 under) { // Alpha blend two colors returning the resulting color pre-multiplied by its alpha // and its alpha. // See https://en.wikipedia.org/wiki/Alpha_compositing - float alpha = mix(under_alpha, 1.0f, over_alpha); - vec3 combined_color = mix(under * under_alpha, over, over_alpha); + float alpha = mix(under.a, 1.0f, over.a); + vec3 combined_color = mix(under.rgb * under.a, over.rgb, over.a); return vec4(combined_color, alpha); } -vec3 premul_blend(vec3 over, float over_alpha, vec3 under) { - return over + (1.0f - over_alpha) * under; -} - -vec4 alpha_blend_premul(vec3 over, float over_alpha, vec3 under, float under_alpha) { +vec4 alpha_blend_premul(vec4 over, vec4 under) { // Same as alpha_blend() except that it assumes over and under are both premultiplied. - float alpha = mix(under_alpha, 1.0f, over_alpha); - return vec4(premul_blend(over, over_alpha, under), alpha); + float inv_over_alpha = 1.0f - over.a; + float alpha = over.a + under.a * inv_over_alpha; + return vec4(over.rgb + under.rgb * inv_over_alpha, alpha); } -vec4 blend_onto_opaque_premul(vec3 over, float over_alpha, vec3 under) { +vec4 alpha_blend_premul(vec4 over, vec3 under) { // same as alpha_blend_premul with under_alpha = 1 outputs a blended color // with alpha 1 which is effectively pre-multiplied since alpha is 1 - return vec4(premul_blend(over, over_alpha, under), 1.0); + float inv_over_alpha = 1.0f - over.a; + return vec4(over.rgb + under.rgb * inv_over_alpha, 1.0); } +vec4 vec4_premul(vec3 rgb, float a) { + return vec4(rgb * a, a); +} +vec4 vec4_premul(vec4 rgba) { + return vec4(rgba.rgb * rgba.a, rgba.a); +} // }}} @@ -94,45 +101,101 @@ vec4 blend_onto_opaque_premul(vec3 over, float over_alpha, vec3 under) { * to the appropriate rendering pass from above. */ #ifdef NEEDS_FOREGROUND -vec4 calculate_foreground() { - // returns the effective foreground color in pre-multiplied form +// 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 + return pow(x, 1.0 / 2.2); +} + +float srgb2linear(float x) { + // Approximation of sRGB-to-linear conversion + return pow(x, 2.2); +} + +float clamp(float x) { + // Clamp value to suitable output range + 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); + float overL = dot(over.rgb, Y); + // This is the original gamma-incorrect rendering, it is the solution of the following equation: + // + // linear2srgb(over * overA2 + under * (1 - overA2)) = linear2srgb(over) * over.a + linear2srgb(under) * (1 - over.a) + // ^ gamma correct blending with new alpha ^ gamma incorrect blending with old alpha + over.a = clamp((srgb2linear(linear2srgb(overL) * over.a + linear2srgb(underL) * (1.0f - over.a)) - underL) / (overL - underL)); + return over; +} + +vec4 foreground_color() { vec4 text_fg = texture(sprites, sprite_pos); - vec3 fg = mix(foreground, text_fg.rgb, colored_sprite); - float text_alpha = text_fg.a; + return vec4(mix(foreground, text_fg.rgb, colored_sprite), text_fg.a); +} + +vec4 foreground_with_decorations(vec4 text_fg) { float underline_alpha = texture(sprites, underline_pos).a; float strike_alpha = texture(sprites, strike_pos).a; float cursor_alpha = texture(sprites, cursor_pos).a; // Since strike and text are the same color, we simply add the alpha values - float combined_alpha = min(text_alpha + strike_alpha, 1.0f); + float combined_alpha = min(text_fg.a + strike_alpha, 1.0f); // Underline color might be different, so alpha blend - vec4 ans = alpha_blend(fg, combined_alpha * effective_text_alpha, decoration_fg, underline_alpha * effective_text_alpha); + vec4 ans = alpha_blend(vec4(text_fg.rgb, combined_alpha * effective_text_alpha), vec4(decoration_fg, underline_alpha * effective_text_alpha)); 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) { + // 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 = mix(foreground_contrast(text_fg, bg), foreground_contrast_incorrect(text_fg, bg), text_old_gamma); + return foreground_with_decorations(text_fg); +} + #endif void main() { #ifdef SIMPLE - vec4 fg = calculate_foreground(); + vec4 fg = calculate_foreground(background); #ifdef TRANSPARENT - final_color = alpha_blend_premul(fg.rgb, fg.a, background.rgb * bg_alpha, bg_alpha); + final_color = alpha_blend_premul(fg, vec4_premul(background, bg_alpha)); #else - final_color = blend_onto_opaque_premul(fg.rgb, fg.a, background.rgb); + final_color = alpha_blend_premul(fg, background); #endif #endif #ifdef SPECIAL #ifdef TRANSPARENT - final_color = vec4(background.rgb * bg_alpha, bg_alpha); + final_color = vec4_premul(background, bg_alpha); #else - final_color = vec4(background.rgb, bg_alpha); + final_color = vec4(background, bg_alpha); #endif #endif #ifdef BACKGROUND #if defined(TRANSPARENT) - final_color = vec4(background.rgb * bg_alpha, bg_alpha); + final_color = vec4_premul(background, bg_alpha); #else - final_color = vec4(background.rgb, draw_bg); + final_color = vec4(background, draw_bg); #endif #endif diff --git a/kitty/cell_vertex.glsl b/kitty/cell_vertex.glsl index 799e1edcd..7cd1d30d8 100644 --- a/kitty/cell_vertex.glsl +++ b/kitty/cell_vertex.glsl @@ -29,6 +29,7 @@ uniform uint draw_bg_bitfield; layout(location=0) in uvec3 colors; layout(location=1) in uvec4 sprite_coords; layout(location=2) in uint is_selected; +uniform float gamma_lut[256]; const int fg_index_map[] = int[3](0, 1, 0); @@ -87,7 +88,7 @@ vec3 color_to_vec(uint c) { r = (c >> 16) & BYTE_MASK; g = (c >> 8) & BYTE_MASK; b = c & BYTE_MASK; - return vec3(float(r) / 255.0, float(g) / 255.0, float(b) / 255.0); + return vec3(gamma_lut[r], gamma_lut[g], gamma_lut[b]); } uint resolve_color(uint c, uint defval) { diff --git a/kitty/gl.c b/kitty/gl.c index 21435bd09..2776c121c 100644 --- a/kitty/gl.c +++ b/kitty/gl.c @@ -71,7 +71,7 @@ update_surface_size(int w, int h, GLuint offscreen_texture_id) { glViewport(0, 0, w, h); if (offscreen_texture_id) { glBindTexture(GL_TEXTURE_2D, offscreen_texture_id); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); + glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB_ALPHA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); } } diff --git a/kitty/glfw.c b/kitty/glfw.c index 3c490c2b1..5cd6f85b9 100644 --- a/kitty/glfw.c +++ b/kitty/glfw.c @@ -11,6 +11,7 @@ #include "charsets.h" #include #include "glfw-wrapper.h" +#include "gl.h" #ifndef __APPLE__ #include "freetype_render_ui_text.h" #endif @@ -870,6 +871,8 @@ create_os_window(PyObject UNUSED *self, PyObject *args, PyObject *kw) { if (is_first_window) { gl_init(); } + // Will make the GPU automatically apply SRGB gamma curve on the resulting framebuffer + glEnable(GL_FRAMEBUFFER_SRGB); bool is_semi_transparent = glfwGetWindowAttrib(glfw_window, GLFW_TRANSPARENT_FRAMEBUFFER); // blank the window once so that there is no initial flash of color // changing, in case the background color is not black diff --git a/kitty/options/definition.py b/kitty/options/definition.py index fb9a24b6d..7a92ef0b1 100644 --- a/kitty/options/definition.py +++ b/kitty/options/definition.py @@ -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() # }}} diff --git a/kitty/options/parse.py b/kitty/options/parse.py index bb3180f19..237c19fae 100644 --- a/kitty/options/parse.py +++ b/kitty/options/parse.py @@ -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) diff --git a/kitty/options/to-c-generated.h b/kitty/options/to-c-generated.h index 665a31d61..633f9ab31 100644 --- a/kitty/options/to-c-generated.h +++ b/kitty/options/to-c-generated.h @@ -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); diff --git a/kitty/options/types.py b/kitty/options/types.py index b37808287..6e380be1b 100644 --- a/kitty/options/types.py +++ b/kitty/options/types.py @@ -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 diff --git a/kitty/shaders.c b/kitty/shaders.c index 21a391bb1..3af305e7d 100644 --- a/kitty/shaders.c +++ b/kitty/shaders.c @@ -10,6 +10,7 @@ #include "colors.h" #include #include "window_logo.h" +#include "srgb_gamma.h" #define BLEND_ONTO_OPAQUE glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // blending onto opaque colors #define BLEND_ONTO_OPAQUE_WITH_OPAQUE_OUTPUT glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE); // blending onto opaque colors with final color having alpha 1 @@ -29,6 +30,16 @@ typedef struct { static const SpriteMap NEW_SPRITE_MAP = { .xnum = 1, .ynum = 1, .last_num_of_layers = 1, .last_ynum = -1 }; static GLint max_texture_size = 0, max_array_texture_layers = 0; +GLfloat +srgb_color(uint8_t color) { + return srgb_lut[color]; +} + +void +color_vec3(GLint location, color_type color) { + glUniform3f(location, srgb_lut[(color >> 16) & 0xFF], srgb_lut[(color >> 8) & 0xFF], srgb_lut[color & 0xFF]); +} + SPRITE_MAP_HANDLE alloc_sprite_map(unsigned int cell_width, unsigned int cell_height) { if (!max_texture_size) { @@ -102,7 +113,7 @@ realloc_sprite_texture(FONTS_DATA_HANDLE fg) { znum = z + 1; SpriteMap *sprite_map = (SpriteMap*)fg->sprite_map; width = xnum * sprite_map->cell_width; height = ynum * sprite_map->cell_height; - glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RGBA8, width, height, znum); + glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_SRGB8_ALPHA8, width, height, znum); if (sprite_map->texture_id) { // need to re-alloc src_ynum = MAX(1, sprite_map->last_ynum); @@ -159,7 +170,7 @@ send_image_to_gpu(GLuint *tex_id, const void* data, GLsizei width, GLsizei heigh } glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, r); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, r); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, is_opaque ? GL_RGB : GL_RGBA, GL_UNSIGNED_BYTE, data); + glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB_ALPHA, width, height, 0, is_opaque ? GL_RGB : GL_RGBA, GL_UNSIGNED_BYTE, data); } // }}} @@ -498,9 +509,7 @@ draw_centered_alpha_mask(OSWindow *os_window, size_t screen_width, size_t screen gpu_data_for_centered_image(data, screen_width, screen_height, width, height); bind_program(GRAPHICS_ALPHA_MASK_PROGRAM); glUniform1i(cell_uniform_data.amask_image_loc, GRAPHICS_UNIT); -#define CV3(x) (((float)((x >> 16) & 0xff))/255.f), (((float)((x >> 8) & 0xff))/255.f), (((float)(x & 0xff))/255.f) - glUniform3f(cell_uniform_data.amask_fg_loc, CV3(OPT(foreground))); -#undef CV3 + color_vec3(cell_uniform_data.amask_fg_loc, OPT(foreground)); glUniform1f(cell_uniform_data.amask_premult_loc, os_window->is_semi_transparent ? 1.f : 0.f); send_graphics_data_to_gpu(1, os_window->gvao_idx, data); glEnable(GL_BLEND); @@ -536,7 +545,7 @@ draw_tint(bool premult, Screen *screen, const CellRenderData *crd) { if (premult) { BLEND_PREMULT } else { BLEND_ONTO_OPAQUE_WITH_OPAQUE_OUTPUT } bind_program(TINT_PROGRAM); color_type window_bg = colorprofile_to_color(screen->color_profile, screen->color_profile->overridden.default_bg, screen->color_profile->configured.default_bg).rgb; -#define C(shift) ((((GLfloat)((window_bg >> shift) & 0xFF)) / 255.0f)) * premult_factor +#define C(shift) srgb_color((window_bg >> shift) & 0xFF) * premult_factor GLfloat premult_factor = premult ? OPT(background_tint) : 1.0f; glUniform4f(tint_program_layout.tint_color_location, C(16), C(8), C(0), OPT(background_tint)); #undef C @@ -560,7 +569,17 @@ 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); + SV(CELL_BG_PROGRAM, gamma_lut, 256, srgb_lut, 1fv); SV(CELL_SPECIAL_PROGRAM, gamma_lut, 256, srgb_lut, 1fv); +#undef SV cell_uniform_data.constants_set = true; } if (current_inactive_text_alpha != cell_uniform_data.prev_inactive_text_alpha || force) { @@ -612,7 +631,7 @@ render_a_bar(OSWindow *os_window, Screen *screen, const CellRenderData *crd, Win glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bar_width, bar_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, bar->buf); + glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB_ALPHA, bar_width, bar_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, bar->buf); set_cell_uniforms(1.f, false); bind_program(GRAPHICS_PROGRAM); send_graphics_data_to_gpu(1, os_window->gvao_idx, &data); @@ -707,9 +726,7 @@ draw_window_number(OSWindow *os_window, Screen *screen, const CellRenderData *cr BLEND_PREMULT; glUniform1i(cell_uniform_data.amask_image_loc, GRAPHICS_UNIT); color_type digit_color = colorprofile_to_color_with_fallback(screen->color_profile, screen->color_profile->overridden.highlight_bg, screen->color_profile->configured.highlight_bg, screen->color_profile->overridden.default_fg, screen->color_profile->configured.default_fg); -#define CV3(x) (((float)((x >> 16) & 0xff))/255.f), (((float)((x >> 8) & 0xff))/255.f), (((float)(x & 0xff))/255.f) - glUniform3f(cell_uniform_data.amask_fg_loc, CV3(digit_color)); -#undef CV3 + color_vec3(cell_uniform_data.amask_fg_loc, digit_color); glUniform1f(cell_uniform_data.amask_premult_loc, 1.f); send_graphics_data_to_gpu(1, os_window->gvao_idx, ird); draw_graphics(GRAPHICS_ALPHA_MASK_PROGRAM, 0, os_window->gvao_idx, ird, 0, 1); @@ -726,7 +743,7 @@ draw_visual_bell_flash(GLfloat intensity, const CellRenderData *crd, Screen *scr #define COLOR(name, fallback) colorprofile_to_color_with_fallback(screen->color_profile, screen->color_profile->overridden.name, screen->color_profile->configured.name, screen->color_profile->overridden.fallback, screen->color_profile->configured.fallback) const color_type flash = !IS_SPECIAL_COLOR(highlight_bg) ? COLOR(visual_bell_color, highlight_bg) : COLOR(visual_bell_color, default_fg); #undef COLOR -#define C(shift) ((((GLfloat)((flash >> shift) & 0xFF)) / 255.0f) ) +#define C(shift) srgb_color((flash >> shift) & 0xFF) const GLfloat r = C(16), g = C(8), b = C(0); const GLfloat max_channel = r > g ? (r > b ? r : b) : (g > b ? g : b); #undef C @@ -793,7 +810,7 @@ draw_cells_interleaved_premult(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen glGenFramebuffers(1, &os_window->offscreen_framebuffer); glGenTextures(1, &os_window->offscreen_texture_id); glBindTexture(GL_TEXTURE_2D, os_window->offscreen_texture_id); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, os_window->viewport_width, os_window->viewport_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); + glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB_ALPHA, os_window->viewport_width, os_window->viewport_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); @@ -862,7 +879,7 @@ draw_cells_interleaved_premult(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen void blank_canvas(float background_opacity, color_type color) { // See https://github.com/glfw/glfw/issues/1538 for why we use pre-multiplied alpha -#define C(shift) ((((GLfloat)((color >> shift) & 0xFF)) / 255.0f) * background_opacity) +#define C(shift) srgb_color((color >> shift) & 0xFF) glClearColor(C(16), C(8), C(0), background_opacity); #undef C glClear(GL_COLOR_BUFFER_BIT); @@ -966,7 +983,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, 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 @@ -977,6 +994,7 @@ init_borders_program(void) { SET_LOC(tint_opacity) SET_LOC(tint_premult) SET_LOC(colors) + SET_LOC(gamma_lut) #undef SET_LOC } @@ -1028,6 +1046,7 @@ draw_borders(ssize_t vao_idx, unsigned int num_border_rects, BorderRect *rect_bu glUniform1f(border_uniform_locations[BORDER_tint_opacity], tint_opacity); glUniform1f(border_uniform_locations[BORDER_tint_premult], tint_premult); glUniform2ui(border_uniform_locations[BORDER_viewport], viewport_width, viewport_height); + glUniform1fv(border_uniform_locations[BORDER_gamma_lut], 256, srgb_lut); if (has_bgimage(w)) { if (w->is_semi_transparent) { BLEND_PREMULT; } else { BLEND_ONTO_OPAQUE_WITH_OPAQUE_OUTPUT; } diff --git a/kitty/srgb_gamma.c b/kitty/srgb_gamma.c new file mode 100644 index 000000000..f34cfd482 --- /dev/null +++ b/kitty/srgb_gamma.c @@ -0,0 +1,21 @@ +// Generated by gen-srgb-lut.py DO NOT edit +#include "srgb_gamma.h" + +const GLfloat srgb_lut[256] = { + 0.00000f, 0.00030f, 0.00061f, 0.00091f, 0.00121f, 0.00152f, 0.00182f, 0.00212f, 0.00243f, 0.00273f, 0.00304f, 0.00335f, 0.00368f, 0.00402f, 0.00439f, 0.00478f, + 0.00518f, 0.00561f, 0.00605f, 0.00651f, 0.00700f, 0.00750f, 0.00802f, 0.00857f, 0.00913f, 0.00972f, 0.01033f, 0.01096f, 0.01161f, 0.01229f, 0.01298f, 0.01370f, + 0.01444f, 0.01521f, 0.01600f, 0.01681f, 0.01764f, 0.01850f, 0.01938f, 0.02029f, 0.02122f, 0.02217f, 0.02315f, 0.02416f, 0.02519f, 0.02624f, 0.02732f, 0.02843f, + 0.02956f, 0.03071f, 0.03190f, 0.03310f, 0.03434f, 0.03560f, 0.03689f, 0.03820f, 0.03955f, 0.04092f, 0.04231f, 0.04374f, 0.04519f, 0.04667f, 0.04817f, 0.04971f, + 0.05127f, 0.05286f, 0.05448f, 0.05613f, 0.05781f, 0.05951f, 0.06125f, 0.06301f, 0.06480f, 0.06663f, 0.06848f, 0.07036f, 0.07227f, 0.07421f, 0.07619f, 0.07819f, + 0.08022f, 0.08228f, 0.08438f, 0.08650f, 0.08866f, 0.09084f, 0.09306f, 0.09531f, 0.09759f, 0.09990f, 0.10224f, 0.10462f, 0.10702f, 0.10946f, 0.11193f, 0.11444f, + 0.11697f, 0.11954f, 0.12214f, 0.12477f, 0.12744f, 0.13014f, 0.13287f, 0.13563f, 0.13843f, 0.14126f, 0.14413f, 0.14703f, 0.14996f, 0.15293f, 0.15593f, 0.15896f, + 0.16203f, 0.16513f, 0.16827f, 0.17144f, 0.17465f, 0.17789f, 0.18116f, 0.18447f, 0.18782f, 0.19120f, 0.19462f, 0.19807f, 0.20156f, 0.20508f, 0.20864f, 0.21223f, + 0.21586f, 0.21953f, 0.22323f, 0.22697f, 0.23074f, 0.23455f, 0.23840f, 0.24228f, 0.24620f, 0.25016f, 0.25415f, 0.25818f, 0.26225f, 0.26636f, 0.27050f, 0.27468f, + 0.27889f, 0.28315f, 0.28744f, 0.29177f, 0.29614f, 0.30054f, 0.30499f, 0.30947f, 0.31399f, 0.31855f, 0.32314f, 0.32778f, 0.33245f, 0.33716f, 0.34191f, 0.34670f, + 0.35153f, 0.35640f, 0.36131f, 0.36625f, 0.37124f, 0.37626f, 0.38133f, 0.38643f, 0.39157f, 0.39676f, 0.40198f, 0.40724f, 0.41254f, 0.41789f, 0.42327f, 0.42869f, + 0.43415f, 0.43966f, 0.44520f, 0.45079f, 0.45641f, 0.46208f, 0.46778f, 0.47353f, 0.47932f, 0.48515f, 0.49102f, 0.49693f, 0.50289f, 0.50888f, 0.51492f, 0.52100f, + 0.52712f, 0.53328f, 0.53948f, 0.54572f, 0.55201f, 0.55834f, 0.56471f, 0.57112f, 0.57758f, 0.58408f, 0.59062f, 0.59720f, 0.60383f, 0.61050f, 0.61721f, 0.62396f, + 0.63076f, 0.63760f, 0.64448f, 0.65141f, 0.65837f, 0.66539f, 0.67244f, 0.67954f, 0.68669f, 0.69387f, 0.70110f, 0.70838f, 0.71569f, 0.72306f, 0.73046f, 0.73791f, + 0.74540f, 0.75294f, 0.76052f, 0.76815f, 0.77582f, 0.78354f, 0.79130f, 0.79910f, 0.80695f, 0.81485f, 0.82279f, 0.83077f, 0.83880f, 0.84687f, 0.85499f, 0.86316f, + 0.87137f, 0.87962f, 0.88792f, 0.89627f, 0.90466f, 0.91310f, 0.92158f, 0.93011f, 0.93869f, 0.94731f, 0.95597f, 0.96469f, 0.97345f, 0.98225f, 0.99110f, 1.00000f, +}; diff --git a/kitty/srgb_gamma.h b/kitty/srgb_gamma.h new file mode 100644 index 000000000..752a84c35 --- /dev/null +++ b/kitty/srgb_gamma.h @@ -0,0 +1,4 @@ +#pragma once +#include "gl.h" + +extern const GLfloat srgb_lut[256]; diff --git a/kitty/state.h b/kitty/state.h index 49ee3b51e..0b49b3d5f 100644 --- a/kitty/state.h +++ b/kitty/state.h @@ -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;