mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 15:56:13 +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.
|
||||
@"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
|
||||
/// 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
|
||||
@ -1569,6 +1575,9 @@ pub fn finalize(self: *Config) !void {
|
||||
// Clamp our 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
|
||||
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");
|
||||
|
@ -152,6 +152,7 @@ pub const DerivedConfig = struct {
|
||||
selection_background: ?terminal.color.RGB,
|
||||
selection_foreground: ?terminal.color.RGB,
|
||||
invert_selection_fg_bg: bool,
|
||||
min_contrast: f32,
|
||||
custom_shaders: std.ArrayListUnmanaged([]const u8),
|
||||
custom_shader_animation: bool,
|
||||
links: link.Set,
|
||||
@ -203,6 +204,7 @@ pub const DerivedConfig = struct {
|
||||
.background = config.background.toTerminalRGB(),
|
||||
.foreground = config.foreground.toTerminalRGB(),
|
||||
.invert_selection_fg_bg = config.@"selection-invert-fg-bg",
|
||||
.min_contrast = @floatCast(config.@"minimum-contrast"),
|
||||
|
||||
.selection_background = if (config.@"selection-background") |bg|
|
||||
bg.toTerminalRGB()
|
||||
@ -374,6 +376,7 @@ pub fn init(alloc: Allocator, options: renderer.Options) !Metal {
|
||||
.cell_size = undefined,
|
||||
.strikethrough_position = @floatFromInt(metrics.strikethrough_position),
|
||||
.strikethrough_thickness = @floatFromInt(metrics.strikethrough_thickness),
|
||||
.min_contrast = options.config.min_contrast,
|
||||
},
|
||||
|
||||
// Fonts
|
||||
@ -531,6 +534,7 @@ pub fn setFontSize(self: *Metal, size: font.face.DesiredSize) !void {
|
||||
},
|
||||
.strikethrough_position = @floatFromInt(metrics.strikethrough_position),
|
||||
.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
|
||||
@ -1257,6 +1261,9 @@ pub fn changeConfig(self: *Metal, config: *DerivedConfig) !void {
|
||||
self.font_shaper = font_shaper;
|
||||
}
|
||||
|
||||
// Set our new minimum contrast
|
||||
self.uniforms.min_contrast = config.min_contrast;
|
||||
|
||||
self.config.deinit();
|
||||
self.config = config.*;
|
||||
}
|
||||
@ -1305,6 +1312,7 @@ pub fn setScreenSize(
|
||||
},
|
||||
.strikethrough_position = old.strikethrough_position,
|
||||
.strikethrough_thickness = old.strikethrough_thickness,
|
||||
.min_contrast = old.min_contrast,
|
||||
};
|
||||
|
||||
// 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 };
|
||||
} 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 (cell.char > 0) {
|
||||
|
@ -126,6 +126,10 @@ pub const Uniforms = extern struct {
|
||||
/// Metrics for underline/strikethrough
|
||||
strikethrough_position: 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.
|
||||
|
@ -13,6 +13,7 @@ struct Uniforms {
|
||||
float2 cell_size;
|
||||
float strikethrough_position;
|
||||
float strikethrough_thickness;
|
||||
float min_contrast;
|
||||
};
|
||||
|
||||
struct VertexIn {
|
||||
@ -29,7 +30,11 @@ struct VertexIn {
|
||||
// the text color. For styles, this is the color of the style.
|
||||
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) ]];
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
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 bg_premult = bg.rgb * bg.a;
|
||||
float ratio = contrast_ratio(fg_premult, bg_premult);
|
||||
if (ratio <= 3.0f) {
|
||||
float ratio = contrast_ratio(float3(1.0f), bg_premult);
|
||||
if (ratio > 3.0f) {
|
||||
if (ratio < min) {
|
||||
float white_ratio = contrast_ratio(float3(1.0f), bg_premult);
|
||||
float black_ratio = contrast_ratio(float3(0.0f), bg_premult);
|
||||
if (white_ratio > black_ratio) {
|
||||
return float4(1.0f);
|
||||
} else {
|
||||
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.
|
||||
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;
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user