mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 00:06:09 +03:00
renderer/metal: minimum contrast ratio is configurable
This commit is contained in:
@ -195,6 +195,12 @@ foreground: Color = .{ .r = 0xFF, .g = 0xFF, .b = 0xFF },
|
|||||||
/// the selection color will vary across the selection.
|
/// the selection color will vary across the selection.
|
||||||
@"selection-invert-fg-bg": bool = false,
|
@"selection-invert-fg-bg": bool = false,
|
||||||
|
|
||||||
|
/// The minimum contrast ratio between the foreground and background
|
||||||
|
/// colors. The contrast ratio is a value between 1 and 21. A value of
|
||||||
|
/// 1 allows for no contrast (i.e. black on black). This value is
|
||||||
|
/// the contrast ratio as defined by the WCAG 2.0 specification.
|
||||||
|
@"minimum-contrast": f64 = 1,
|
||||||
|
|
||||||
/// Color palette for the 256 color form that many terminal applications
|
/// Color palette for the 256 color form that many terminal applications
|
||||||
/// use. The syntax of this configuration is "N=HEXCODE" where "n"
|
/// use. The syntax of this configuration is "N=HEXCODE" where "n"
|
||||||
/// is 0 to 255 (for the 256 colors) and HEXCODE is a typical RGB
|
/// is 0 to 255 (for the 256 colors) and HEXCODE is a typical RGB
|
||||||
@ -1569,6 +1575,9 @@ pub fn finalize(self: *Config) !void {
|
|||||||
// Clamp our split opacity
|
// Clamp our split opacity
|
||||||
self.@"unfocused-split-opacity" = @min(1.0, @max(0.15, self.@"unfocused-split-opacity"));
|
self.@"unfocused-split-opacity" = @min(1.0, @max(0.15, self.@"unfocused-split-opacity"));
|
||||||
|
|
||||||
|
// Clamp our contrast
|
||||||
|
self.@"minimum-contrast" = @min(21, @max(1, self.@"minimum-contrast"));
|
||||||
|
|
||||||
// Minimmum window size
|
// Minimmum window size
|
||||||
if (self.@"window-width" > 0) self.@"window-width" = @max(10, self.@"window-width");
|
if (self.@"window-width" > 0) self.@"window-width" = @max(10, self.@"window-width");
|
||||||
if (self.@"window-height" > 0) self.@"window-height" = @max(4, self.@"window-height");
|
if (self.@"window-height" > 0) self.@"window-height" = @max(4, self.@"window-height");
|
||||||
|
@ -152,6 +152,7 @@ pub const DerivedConfig = struct {
|
|||||||
selection_background: ?terminal.color.RGB,
|
selection_background: ?terminal.color.RGB,
|
||||||
selection_foreground: ?terminal.color.RGB,
|
selection_foreground: ?terminal.color.RGB,
|
||||||
invert_selection_fg_bg: bool,
|
invert_selection_fg_bg: bool,
|
||||||
|
min_contrast: f32,
|
||||||
custom_shaders: std.ArrayListUnmanaged([]const u8),
|
custom_shaders: std.ArrayListUnmanaged([]const u8),
|
||||||
custom_shader_animation: bool,
|
custom_shader_animation: bool,
|
||||||
links: link.Set,
|
links: link.Set,
|
||||||
@ -203,6 +204,7 @@ pub const DerivedConfig = struct {
|
|||||||
.background = config.background.toTerminalRGB(),
|
.background = config.background.toTerminalRGB(),
|
||||||
.foreground = config.foreground.toTerminalRGB(),
|
.foreground = config.foreground.toTerminalRGB(),
|
||||||
.invert_selection_fg_bg = config.@"selection-invert-fg-bg",
|
.invert_selection_fg_bg = config.@"selection-invert-fg-bg",
|
||||||
|
.min_contrast = @floatCast(config.@"minimum-contrast"),
|
||||||
|
|
||||||
.selection_background = if (config.@"selection-background") |bg|
|
.selection_background = if (config.@"selection-background") |bg|
|
||||||
bg.toTerminalRGB()
|
bg.toTerminalRGB()
|
||||||
@ -374,6 +376,7 @@ pub fn init(alloc: Allocator, options: renderer.Options) !Metal {
|
|||||||
.cell_size = undefined,
|
.cell_size = undefined,
|
||||||
.strikethrough_position = @floatFromInt(metrics.strikethrough_position),
|
.strikethrough_position = @floatFromInt(metrics.strikethrough_position),
|
||||||
.strikethrough_thickness = @floatFromInt(metrics.strikethrough_thickness),
|
.strikethrough_thickness = @floatFromInt(metrics.strikethrough_thickness),
|
||||||
|
.min_contrast = options.config.min_contrast,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Fonts
|
// Fonts
|
||||||
@ -531,6 +534,7 @@ pub fn setFontSize(self: *Metal, size: font.face.DesiredSize) !void {
|
|||||||
},
|
},
|
||||||
.strikethrough_position = @floatFromInt(metrics.strikethrough_position),
|
.strikethrough_position = @floatFromInt(metrics.strikethrough_position),
|
||||||
.strikethrough_thickness = @floatFromInt(metrics.strikethrough_thickness),
|
.strikethrough_thickness = @floatFromInt(metrics.strikethrough_thickness),
|
||||||
|
.min_contrast = self.uniforms.min_contrast,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Recalculate our cell size. If it is the same as before, then we do
|
// Recalculate our cell size. If it is the same as before, then we do
|
||||||
@ -1257,6 +1261,9 @@ pub fn changeConfig(self: *Metal, config: *DerivedConfig) !void {
|
|||||||
self.font_shaper = font_shaper;
|
self.font_shaper = font_shaper;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set our new minimum contrast
|
||||||
|
self.uniforms.min_contrast = config.min_contrast;
|
||||||
|
|
||||||
self.config.deinit();
|
self.config.deinit();
|
||||||
self.config = config.*;
|
self.config = config.*;
|
||||||
}
|
}
|
||||||
@ -1305,6 +1312,7 @@ pub fn setScreenSize(
|
|||||||
},
|
},
|
||||||
.strikethrough_position = old.strikethrough_position,
|
.strikethrough_position = old.strikethrough_position,
|
||||||
.strikethrough_thickness = old.strikethrough_thickness,
|
.strikethrough_thickness = old.strikethrough_thickness,
|
||||||
|
.min_contrast = old.min_contrast,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Reset our buffer sizes so that we free memory when the screen shrinks.
|
// Reset our buffer sizes so that we free memory when the screen shrinks.
|
||||||
@ -1705,7 +1713,12 @@ pub fn updateCell(
|
|||||||
});
|
});
|
||||||
|
|
||||||
break :bg .{ rgb.r, rgb.g, rgb.b, bg_alpha };
|
break :bg .{ rgb.r, rgb.g, rgb.b, bg_alpha };
|
||||||
} else .{ 0, 0, 0, 0 };
|
} else .{
|
||||||
|
self.current_background_color.r,
|
||||||
|
self.current_background_color.g,
|
||||||
|
self.current_background_color.b,
|
||||||
|
@intFromFloat(@max(0, @min(255, @round(self.config.background_opacity * 255)))),
|
||||||
|
};
|
||||||
|
|
||||||
// If the cell has a character, draw it
|
// If the cell has a character, draw it
|
||||||
if (cell.char > 0) {
|
if (cell.char > 0) {
|
||||||
|
@ -126,6 +126,10 @@ pub const Uniforms = extern struct {
|
|||||||
/// Metrics for underline/strikethrough
|
/// Metrics for underline/strikethrough
|
||||||
strikethrough_position: f32,
|
strikethrough_position: f32,
|
||||||
strikethrough_thickness: f32,
|
strikethrough_thickness: f32,
|
||||||
|
|
||||||
|
/// The minimum contrast ratio for text. The contrast ratio is calculated
|
||||||
|
/// according to the WCAG 2.0 spec.
|
||||||
|
min_contrast: f32,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The uniforms used for custom postprocess shaders.
|
/// The uniforms used for custom postprocess shaders.
|
||||||
|
@ -13,6 +13,7 @@ struct Uniforms {
|
|||||||
float2 cell_size;
|
float2 cell_size;
|
||||||
float strikethrough_position;
|
float strikethrough_position;
|
||||||
float strikethrough_thickness;
|
float strikethrough_thickness;
|
||||||
|
float min_contrast;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct VertexIn {
|
struct VertexIn {
|
||||||
@ -29,7 +30,11 @@ struct VertexIn {
|
|||||||
// the text color. For styles, this is the color of the style.
|
// the text color. For styles, this is the color of the style.
|
||||||
uchar4 color [[ attribute(5) ]];
|
uchar4 color [[ attribute(5) ]];
|
||||||
|
|
||||||
// The fields below are present only when rendering text.
|
// The fields below are present only when rendering text (fg mode)
|
||||||
|
|
||||||
|
// The background color of the cell. This is used to determine if
|
||||||
|
// we need to render the text with a different color to ensure
|
||||||
|
// contrast.
|
||||||
uchar4 bg_color [[ attribute(7) ]];
|
uchar4 bg_color [[ attribute(7) ]];
|
||||||
|
|
||||||
// The position of the glyph in the texture (x,y)
|
// The position of the glyph in the texture (x,y)
|
||||||
@ -79,13 +84,17 @@ float contrast_ratio(float3 color1, float3 color2) {
|
|||||||
return (max(l1, l2) + 0.05f) / (min(l1, l2) + 0.05f);
|
return (max(l1, l2) + 0.05f) / (min(l1, l2) + 0.05f);
|
||||||
}
|
}
|
||||||
|
|
||||||
float4 contrasted_color(float4 fg, float4 bg) {
|
// Return the fg if the contrast ratio is greater than min, otherwise
|
||||||
|
// return a color that satisfies the contrast ratio. Currently, the color
|
||||||
|
// is always white or black, whichever has the highest contrast ratio.
|
||||||
|
float4 contrasted_color(float min, float4 fg, float4 bg) {
|
||||||
float3 fg_premult = fg.rgb * fg.a;
|
float3 fg_premult = fg.rgb * fg.a;
|
||||||
float3 bg_premult = bg.rgb * bg.a;
|
float3 bg_premult = bg.rgb * bg.a;
|
||||||
float ratio = contrast_ratio(fg_premult, bg_premult);
|
float ratio = contrast_ratio(fg_premult, bg_premult);
|
||||||
if (ratio <= 3.0f) {
|
if (ratio < min) {
|
||||||
float ratio = contrast_ratio(float3(1.0f), bg_premult);
|
float white_ratio = contrast_ratio(float3(1.0f), bg_premult);
|
||||||
if (ratio > 3.0f) {
|
float black_ratio = contrast_ratio(float3(0.0f), bg_premult);
|
||||||
|
if (white_ratio > black_ratio) {
|
||||||
return float4(1.0f);
|
return float4(1.0f);
|
||||||
} else {
|
} else {
|
||||||
return float4(0.0f, 0.0f, 0.0f, 1.0f);
|
return float4(0.0f, 0.0f, 0.0f, 1.0f);
|
||||||
@ -159,7 +168,7 @@ vertex VertexOut uber_vertex(
|
|||||||
// (between 0.0 and 1.0) and must be done in the fragment shader.
|
// (between 0.0 and 1.0) and must be done in the fragment shader.
|
||||||
out.tex_coord = float2(input.glyph_pos) + float2(input.glyph_size) * position;
|
out.tex_coord = float2(input.glyph_pos) + float2(input.glyph_size) * position;
|
||||||
|
|
||||||
out.color = contrasted_color(out.color, float4(input.bg_color) / 255.0f);
|
out.color = contrasted_color(uniforms.min_contrast, out.color, float4(input.bg_color) / 255.0f);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user