diff --git a/kitty/cell_defines.glsl b/kitty/cell_defines.glsl index c6467c795..37c770e85 100644 --- a/kitty/cell_defines.glsl +++ b/kitty/cell_defines.glsl @@ -5,10 +5,9 @@ #define PHASE {WHICH_PHASE} #define HAS_TRANSPARENCY {TRANSPARENT} -#define FG_OVERRIDE {FG_OVERRIDE} +#define DO_FG_OVERRIDE {DO_FG_OVERRIDE} #define FG_OVERRIDE_THRESHOLD {FG_OVERRIDE_THRESHOLD} -#define APPLY_MIN_CONTRAST_RATIO {APPLY_MIN_CONTRAST_RATIO} -#define MIN_CONTRAST_RATIO {MIN_CONTRAST_RATIO} +#define FG_OVERRIDE_ALGO {FG_OVERRIDE_ALGO} #define TEXT_NEW_GAMMA {TEXT_NEW_GAMMA} #define DECORATION_SHIFT {DECORATION_SHIFT} diff --git a/kitty/cell_fragment.glsl b/kitty/cell_fragment.glsl index df908a008..74cac0be6 100644 --- a/kitty/cell_fragment.glsl +++ b/kitty/cell_fragment.glsl @@ -78,7 +78,7 @@ float clamp_to_unit_float(float x) { return clamp(x, 0.0f, 1.0f); } -#if (FG_OVERRIDE == 1) +#if (FG_OVERRIDE_ALGO == 1) vec3 fg_override(float under_luminance, float over_lumininace, vec3 over) { // If the difference in luminance is too small, // force the foreground color to be black or white. @@ -87,8 +87,7 @@ vec3 fg_override(float under_luminance, float over_lumininace, vec3 over) { float original_level = 1.f - override_level; return original_level * over + override_level * vec3(step(under_luminance, 0.5f)); } -#endif -#if (APPLY_MIN_CONTRAST_RATIO == 1) +#else float contrast_ratio(float under_luminance, float over_luminance) { return clamp((max(under_luminance, over_luminance) + 0.05f) / (min(under_luminance, over_luminance) + 0.05f), 1.f, 21.f); } @@ -96,30 +95,29 @@ vec3 apply_min_contrast_ratio(float under_luminance, float over_luminance, vec3 float ratio = contrast_ratio(under_luminance, over_luminance); vec3 diff = abs(under - over); vec3 over_hsluv = rgbToHsluv(over); - float target_lum_a = clamp((under_luminance + 0.05f) * MIN_CONTRAST_RATIO - 0.05f, 0.f, 1.f); - float target_lum_b = clamp((under_luminance + 0.05f) / MIN_CONTRAST_RATIO - 0.05f, 0.f, 1.f); + const float min_contrast_ratio = FG_OVERRIDE_THRESHOLD; + float target_lum_a = clamp((under_luminance + 0.05f) * min_contrast_ratio - 0.05f, 0.f, 1.f); + float target_lum_b = clamp((under_luminance + 0.05f) / min_contrast_ratio - 0.05f, 0.f, 1.f); vec3 result_a = clamp(hsluvToRgb(vec3(over_hsluv.x, over_hsluv.y, target_lum_a * 100.f)), 0.f, 1.f); vec3 result_b = clamp(hsluvToRgb(vec3(over_hsluv.x, over_hsluv.y, target_lum_b * 100.f)), 0.f, 1.f); float result_a_ratio = contrast_ratio(under_luminance, dot(result_a, Y)); float result_b_ratio = contrast_ratio(under_luminance, dot(result_b, Y)); vec3 result = mix(result_a, result_b, step(result_a_ratio, result_b_ratio)); - return mix(result, over, max(step(diff.r + diff.g + diff.g, 0.001f), step(MIN_CONTRAST_RATIO, ratio))); + return mix(result, over, max(step(diff.r + diff.g + diff.g, 0.001f), step(min_contrast_ratio, ratio))); } #endif vec4 foreground_contrast(vec4 over, vec3 under) { float under_luminance = dot(under, Y); float over_lumininace = dot(over.rgb, Y); - -#if (FG_OVERRIDE == 1) +#if DO_FG_OVERRIDE == 1 +#if FG_OVERRIDE_ALGO == 1 over.rgb = fg_override(under_luminance, over_lumininace, over.rgb); - over_lumininace = dot(over.rgb, Y); -#endif -#if (APPLY_MIN_CONTRAST_RATIO == 1) +#else over.rgb = apply_min_contrast_ratio(under_luminance, over_lumininace, under, over.rgb); +#endif over_lumininace = dot(over.rgb, Y); #endif - // 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 saturation. over.a = clamp_to_unit_float(mix(over.a, pow(over.a, text_gamma_adjustment), (1 - over_lumininace + under_luminance) * text_gamma_scaling) * text_contrast); @@ -131,12 +129,12 @@ vec4 foreground_contrast_incorrect(vec4 over, vec3 under) { // Simulation of gamma-incorrect blending float under_luminance = dot(under, Y); float over_lumininace = dot(over.rgb, Y); -#if (FG_OVERRIDE == 1) +#if DO_FG_OVERRIDE == 1 +#if FG_OVERRIDE_ALGO == 1 over.rgb = fg_override(under_luminance, over_lumininace, over.rgb); - over_lumininace = dot(over.rgb, Y); -#endif -#if (APPLY_MIN_CONTRAST_RATIO == 1) +#else over.rgb = apply_min_contrast_ratio(under_luminance, over_lumininace, under, over.rgb); +#endif over_lumininace = dot(over.rgb, Y); #endif // This is the original gamma-incorrect rendering, it is the solution of the following equation: diff --git a/kitty/shaders.py b/kitty/shaders.py index 92b1cebff..84e30c23d 100644 --- a/kitty/shaders.py +++ b/kitty/shaders.py @@ -5,7 +5,7 @@ from collections.abc import Callable, Iterator from functools import lru_cache, partial from itertools import count -from typing import Any, Optional +from typing import Any, Literal, NamedTuple, Optional from .constants import read_kitty_resource from .fast_data_types import ( @@ -130,9 +130,14 @@ def __call__(self, src: str) -> str: null_replacer = MultiReplacer() +class TextFgOverrideThreshold(NamedTuple): + value: float = 0 + unit: Literal['%', 'ratio'] = '%' + scaled_value: float = 0 + + class LoadShaderPrograms: - text_fg_override_threshold: float = 0 - text_fg_override_threshold_unit: str = '%' + text_fg_override_threshold: TextFgOverrideThreshold = TextFgOverrideThreshold() text_old_gamma: bool = False semi_transparent: bool = False cell_program_replacer: MultiReplacer = null_replacer @@ -141,7 +146,7 @@ class LoadShaderPrograms: def needs_recompile(self) -> bool: opts = get_options() return ( - opts.text_fg_override_threshold != (self.text_fg_override_threshold, self.text_fg_override_threshold_unit) + opts.text_fg_override_threshold != (self.text_fg_override_threshold.value, self.text_fg_override_threshold.unit) or (opts.text_composition_strategy == 'legacy') != self.text_old_gamma ) @@ -154,12 +159,14 @@ def __call__(self, semi_transparent: bool = False, allow_recompile: bool = False opts = get_options() self.text_old_gamma = opts.text_composition_strategy == 'legacy' - self.text_fg_override_threshold, self.text_fg_override_threshold_unit = opts.text_fg_override_threshold - match self.text_fg_override_threshold_unit: + text_fg_override_threshold: float = opts.text_fg_override_threshold[0] + match opts.text_fg_override_threshold[1]: case '%': - self.text_fg_override_threshold = max(0, min(self.text_fg_override_threshold, 100.0)) * 0.01 + text_fg_override_threshold = max(0, min(text_fg_override_threshold, 100.0)) * 0.01 case 'ratio': - self.text_fg_override_threshold = max(0, min(self.text_fg_override_threshold, 21.0)) + text_fg_override_threshold = max(0, min(text_fg_override_threshold, 21.0)) + self.text_fg_override_threshold = TextFgOverrideThreshold( + opts.text_fg_override_threshold[0], opts.text_fg_override_threshold[1], text_fg_override_threshold) cell = program_for('cell') if self.cell_program_replacer is null_replacer: @@ -177,10 +184,9 @@ def resolve_cell_defines(which: str, src: str) -> str: r = self.cell_program_replacer.replacements r['WHICH_PHASE'] = f'PHASE_{which}' r['TRANSPARENT'] = '1' if semi_transparent else '0' - r['FG_OVERRIDE_THRESHOLD'] = str(self.text_fg_override_threshold) - r['FG_OVERRIDE'] = str(int(bool(self.text_fg_override_threshold_unit == '%'))) - r['MIN_CONTRAST_RATIO'] = str(self.text_fg_override_threshold) - r['APPLY_MIN_CONTRAST_RATIO'] = str(int(bool(self.text_fg_override_threshold_unit == 'ratio'))) + r['DO_FG_OVERRIDE'] = '1' if self.text_fg_override_threshold.scaled_value else '0' + r['FG_OVERRIDE_ALGO'] = '1' if self.text_fg_override_threshold.unit == '%' else '2' + r['FG_OVERRIDE_THRESHOLD'] = str(self.text_fg_override_threshold.scaled_value) r['TEXT_NEW_GAMMA'] = '0' if self.text_old_gamma else '1' return self.cell_program_replacer(src)