feat: ensure min contrast ratio as in xterm.js

This commit is contained in:
arne314
2025-03-09 16:57:30 +01:00
parent a5d4ca3735
commit 59a4cc7b3a
3 changed files with 64 additions and 2 deletions

View File

@@ -7,6 +7,8 @@
#define HAS_TRANSPARENCY {TRANSPARENT}
#define FG_OVERRIDE {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 TEXT_NEW_GAMMA {TEXT_NEW_GAMMA}
#define DECORATION_SHIFT {DECORATION_SHIFT}

View File

@@ -87,6 +87,53 @@ vec3 fg_override(float under_luminance, float over_lumininace, vec3 over) {
return original_level * over + override_level * vec3(step(under_luminance, 0.5f));
}
#endif
#if (APPLY_MIN_CONTRAST_RATIO == 1)
float contrast_ratio(float under_luminance, float over_luminance) {
return (max(under_luminance, over_luminance) + 0.05f) / (min(under_luminance, over_luminance) + 0.05f);
}
vec3 increase_luminance(vec3 over, float ratio, float target_ratio, float under_luminance) {
vec3 one = vec3(1.f);
while (ratio < target_ratio && any(lessThan(over, one))) {
over = clamp(over + (one - over) * 0.1f + 0.02f, 0.f, 1.f); // add 0.02 to converge faster towards 1
ratio = contrast_ratio(under_luminance, dot(over, Y));
}
return over;
}
vec3 decrease_luminance(vec3 over, float ratio, float target_ratio, float under_luminance) {
vec3 zero = vec3(0.f);
while (ratio < target_ratio && any(greaterThan(over, zero))) {
over = clamp(over * 0.9f - 0.02f, 0.f, 1.f); // subtract 0.02 to converge faster towards 0
ratio = contrast_ratio(under_luminance, dot(over, Y));
}
return over;
}
vec3 ensure_min_contrast_ratio(float under_luminance, float over_luminance, vec3 under, vec3 over) {
// as in https://github.com/xtermjs/xterm.js/blob/2042bb85023714e55c0c2e986b5000e33b17c414/src/common/Color.ts#L288
float ratio = contrast_ratio(under_luminance, over_luminance);
if (ratio < MIN_CONTRAST_RATIO) {
if (over_luminance < under_luminance) {
vec3 result_a = decrease_luminance(over, ratio, MIN_CONTRAST_RATIO, under_luminance);
float result_a_ratio = contrast_ratio(under_luminance, dot(result_a, Y));
if (result_a_ratio < ratio) {
vec3 result_b = increase_luminance(over, ratio, MIN_CONTRAST_RATIO, under_luminance);
float result_b_ratio = contrast_ratio(under_luminance, dot(result_b, Y));
return result_a_ratio > result_b_ratio ? result_a : result_b;
}
return result_a;
} else {
vec3 result_a = increase_luminance(over, ratio, MIN_CONTRAST_RATIO, under_luminance);
float result_a_ratio = contrast_ratio(under_luminance, dot(result_a, Y));
if (result_a_ratio < ratio) {
vec3 result_b = decrease_luminance(over, ratio, MIN_CONTRAST_RATIO, under_luminance);
float result_b_ratio = contrast_ratio(under_luminance, dot(result_b, Y));
return result_a_ratio > result_b_ratio ? result_a : result_b;
}
return result_a;
}
}
return over;
}
#endif
vec4 foreground_contrast(vec4 over, vec3 under) {
float under_luminance = dot(under, Y);
@@ -96,6 +143,10 @@ vec4 foreground_contrast(vec4 over, vec3 under) {
over.rgb = fg_override(under_luminance, over_lumininace, over.rgb);
over_lumininace = dot(over.rgb, Y);
#endif
#if (APPLY_MIN_CONTRAST_RATIO == 1)
over.rgb = ensure_min_contrast_ratio(under_luminance, over_lumininace, under, over.rgb);
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.
@@ -111,6 +162,10 @@ vec4 foreground_contrast_incorrect(vec4 over, vec3 under) {
#if (FG_OVERRIDE == 1)
over.rgb = fg_override(under_luminance, over_lumininace, over.rgb);
over_lumininace = dot(over.rgb, Y);
#endif
#if (APPLY_MIN_CONTRAST_RATIO == 1)
over.rgb = ensure_min_contrast_ratio(under_luminance, over_lumininace, under, over.rgb);
over_lumininace = dot(over.rgb, Y);
#endif
// This is the original gamma-incorrect rendering, it is the solution of the following equation:
//

View File

@@ -150,7 +150,10 @@ class LoadShaderPrograms:
self.semi_transparent = semi_transparent
opts = get_options()
self.text_old_gamma = opts.text_composition_strategy == 'legacy'
self.text_fg_override_threshold = max(0, min(opts.text_fg_override_threshold, 100)) * 0.01
if opts.text_fg_override_threshold < 0:
self.text_fg_override_threshold = opts.text_fg_override_threshold
else:
self.text_fg_override_threshold = max(0, min(opts.text_fg_override_threshold, 100)) * 0.01
cell = program_for('cell')
if self.cell_program_replacer is null_replacer:
self.cell_program_replacer = MultiReplacer(
@@ -168,7 +171,9 @@ class LoadShaderPrograms:
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'] = '1' if self.text_fg_override_threshold != 0. else '0'
r['FG_OVERRIDE'] = '1' if self.text_fg_override_threshold > 0 else '0'
r['MIN_CONTRAST_RATIO'] = str(-self.text_fg_override_threshold)
r['APPLY_MIN_CONTRAST_RATIO'] = '1' if self.text_fg_override_threshold < 0 else '0'
r['TEXT_NEW_GAMMA'] = '0' if self.text_old_gamma else '1'
return self.cell_program_replacer(src)