Merge branch 'linear-gamma-blending' of https://github.com/m4rw3r/kitty

This commit is contained in:
Kovid Goyal 2023-02-08 11:22:35 +05:30
commit b5b070aade
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
14 changed files with 299 additions and 42 deletions

48
gen-srgb-lut.py Executable file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -11,6 +11,7 @@
#include "charsets.h"
#include <structmember.h>
#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

View File

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

View File

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

View File

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

View File

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

View File

@ -10,6 +10,7 @@
#include "colors.h"
#include <stddef.h>
#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; }

21
kitty/srgb_gamma.c Normal file
View File

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

4
kitty/srgb_gamma.h Normal file
View File

@ -0,0 +1,4 @@
#pragma once
#include "gl.h"
extern const GLfloat srgb_lut[256];

View File

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