mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-24 12:46:10 +03:00
Merge pull request #981 from mitchellh/min-contrast
Minimum Contrast Configuration
This commit is contained in:
@ -195,6 +195,19 @@ 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.
|
||||
///
|
||||
/// If you want to avoid invisible text (same color as background),
|
||||
/// a value of 1.1 is a good value. If you want to avoid text that is
|
||||
/// difficult to read, a value of 3 or higher is a good value. The higher
|
||||
/// the value, the more likely that text will become black or white.
|
||||
///
|
||||
/// This value does not apply to Emoji or images.
|
||||
@"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 +1582,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.
|
||||
@ -1667,7 +1675,7 @@ pub fn updateCell(
|
||||
const alpha: u8 = if (cell.attrs.faint) 175 else 255;
|
||||
|
||||
// If the cell has a background, we always draw it.
|
||||
if (colors.bg) |rgb| {
|
||||
const bg: [4]u8 = if (colors.bg) |rgb| bg: {
|
||||
// Determine our background alpha. If we have transparency configured
|
||||
// then this is dynamic depending on some situations. This is all
|
||||
// in an attempt to make transparency look the best for various
|
||||
@ -1701,8 +1709,16 @@ pub fn updateCell(
|
||||
.grid_pos = .{ @as(f32, @floatFromInt(x)), @as(f32, @floatFromInt(y)) },
|
||||
.cell_width = cell.widthLegacy(),
|
||||
.color = .{ rgb.r, rgb.g, rgb.b, bg_alpha },
|
||||
.bg_color = .{ 0, 0, 0, 0 },
|
||||
});
|
||||
}
|
||||
|
||||
break :bg .{ rgb.r, rgb.g, rgb.b, bg_alpha };
|
||||
} 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) {
|
||||
@ -1729,6 +1745,7 @@ pub fn updateCell(
|
||||
.grid_pos = .{ @as(f32, @floatFromInt(x)), @as(f32, @floatFromInt(y)) },
|
||||
.cell_width = cell.widthLegacy(),
|
||||
.color = .{ colors.fg.r, colors.fg.g, colors.fg.b, alpha },
|
||||
.bg_color = bg,
|
||||
.glyph_pos = .{ glyph.atlas_x, glyph.atlas_y },
|
||||
.glyph_size = .{ glyph.width, glyph.height },
|
||||
.glyph_offset = .{ glyph.offset_x, glyph.offset_y },
|
||||
@ -1759,6 +1776,7 @@ pub fn updateCell(
|
||||
.grid_pos = .{ @as(f32, @floatFromInt(x)), @as(f32, @floatFromInt(y)) },
|
||||
.cell_width = cell.widthLegacy(),
|
||||
.color = .{ color.r, color.g, color.b, alpha },
|
||||
.bg_color = bg,
|
||||
.glyph_pos = .{ glyph.atlas_x, glyph.atlas_y },
|
||||
.glyph_size = .{ glyph.width, glyph.height },
|
||||
.glyph_offset = .{ glyph.offset_x, glyph.offset_y },
|
||||
@ -1771,6 +1789,7 @@ pub fn updateCell(
|
||||
.grid_pos = .{ @as(f32, @floatFromInt(x)), @as(f32, @floatFromInt(y)) },
|
||||
.cell_width = cell.widthLegacy(),
|
||||
.color = .{ colors.fg.r, colors.fg.g, colors.fg.b, alpha },
|
||||
.bg_color = bg,
|
||||
});
|
||||
}
|
||||
|
||||
@ -1834,6 +1853,7 @@ fn addCursor(
|
||||
},
|
||||
.cell_width = if (wide) 2 else 1,
|
||||
.color = .{ color.r, color.g, color.b, alpha },
|
||||
.bg_color = .{ 0, 0, 0, 0 },
|
||||
.glyph_pos = .{ glyph.atlas_x, glyph.atlas_y },
|
||||
.glyph_size = .{ glyph.width, glyph.height },
|
||||
.glyph_offset = .{ glyph.offset_x, glyph.offset_y },
|
||||
@ -1886,6 +1906,7 @@ fn addPreeditCell(
|
||||
.grid_pos = .{ @as(f32, @floatFromInt(x)), @as(f32, @floatFromInt(y)) },
|
||||
.cell_width = if (cp.wide) 2 else 1,
|
||||
.color = .{ bg.r, bg.g, bg.b, 255 },
|
||||
.bg_color = .{ bg.r, bg.g, bg.b, 255 },
|
||||
});
|
||||
|
||||
// Add our text
|
||||
@ -1894,6 +1915,7 @@ fn addPreeditCell(
|
||||
.grid_pos = .{ @as(f32, @floatFromInt(x)), @as(f32, @floatFromInt(y)) },
|
||||
.cell_width = if (cp.wide) 2 else 1,
|
||||
.color = .{ fg.r, fg.g, fg.b, 255 },
|
||||
.bg_color = .{ bg.r, bg.g, bg.b, 255 },
|
||||
.glyph_pos = .{ glyph.atlas_x, glyph.atlas_y },
|
||||
.glyph_size = .{ glyph.width, glyph.height },
|
||||
.glyph_offset = .{ glyph.offset_x, glyph.offset_y },
|
||||
|
@ -101,6 +101,7 @@ surface_mailbox: apprt.surface.Mailbox,
|
||||
/// simple we apply all OpenGL context changes in the render() call.
|
||||
deferred_screen_size: ?SetScreenSize = null,
|
||||
deferred_font_size: ?SetFontSize = null,
|
||||
deferred_config: ?SetConfig = null,
|
||||
|
||||
/// If we're drawing with single threaded operations
|
||||
draw_mutex: DrawMutex = drawMutexZero,
|
||||
@ -206,6 +207,20 @@ const SetFontSize = struct {
|
||||
}
|
||||
};
|
||||
|
||||
const SetConfig = struct {
|
||||
fn apply(self: SetConfig, r: *const OpenGL) !void {
|
||||
_ = self;
|
||||
const gl_state = r.gl_state orelse return error.OpenGLUninitialized;
|
||||
|
||||
const bind = try gl_state.cell_program.program.use();
|
||||
defer bind.unbind();
|
||||
try gl_state.cell_program.program.setUniform(
|
||||
"min_contrast",
|
||||
r.config.min_contrast,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/// The configuration for this renderer that is derived from the main
|
||||
/// configuration. This must be exported so that we don't need to
|
||||
/// pass around Config pointers which makes memory management a pain.
|
||||
@ -224,6 +239,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,
|
||||
@ -275,6 +291,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()
|
||||
@ -336,6 +353,7 @@ pub fn init(alloc: Allocator, options: renderer.Options) !OpenGL {
|
||||
.padding = options.padding,
|
||||
.surface_mailbox = options.surface_mailbox,
|
||||
.deferred_font_size = .{ .metrics = metrics },
|
||||
.deferred_config = .{},
|
||||
};
|
||||
}
|
||||
|
||||
@ -462,6 +480,7 @@ pub fn displayRealize(self: *OpenGL) !void {
|
||||
self.deferred_screen_size = .{ .size = size };
|
||||
}
|
||||
self.deferred_font_size = .{ .metrics = metrics };
|
||||
self.deferred_config = .{};
|
||||
}
|
||||
|
||||
/// Callback called by renderer.Thread when it begins.
|
||||
@ -1148,6 +1167,10 @@ fn addPreeditCell(
|
||||
.g = bg.g,
|
||||
.b = bg.b,
|
||||
.a = 255,
|
||||
.bg_r = 0,
|
||||
.bg_g = 0,
|
||||
.bg_b = 0,
|
||||
.bg_a = 0,
|
||||
});
|
||||
|
||||
// Add our text
|
||||
@ -1166,6 +1189,10 @@ fn addPreeditCell(
|
||||
.g = fg.g,
|
||||
.b = fg.b,
|
||||
.a = 255,
|
||||
.bg_r = bg.r,
|
||||
.bg_g = bg.g,
|
||||
.bg_b = bg.b,
|
||||
.bg_a = 255,
|
||||
});
|
||||
}
|
||||
|
||||
@ -1227,6 +1254,10 @@ fn addCursor(
|
||||
.g = color.g,
|
||||
.b = color.b,
|
||||
.a = alpha,
|
||||
.bg_r = 0,
|
||||
.bg_g = 0,
|
||||
.bg_b = 0,
|
||||
.bg_a = 0,
|
||||
.glyph_x = glyph.atlas_x,
|
||||
.glyph_y = glyph.atlas_y,
|
||||
.glyph_width = glyph.width,
|
||||
@ -1334,7 +1365,7 @@ pub fn updateCell(
|
||||
const alpha: u8 = if (cell.attrs.faint) 175 else 255;
|
||||
|
||||
// If the cell has a background, we always draw it.
|
||||
if (colors.bg) |rgb| {
|
||||
const bg: [4]u8 = if (colors.bg) |rgb| bg: {
|
||||
// Determine our background alpha. If we have transparency configured
|
||||
// then this is dynamic depending on some situations. This is all
|
||||
// in an attempt to make transparency look the best for various
|
||||
@ -1378,8 +1409,19 @@ pub fn updateCell(
|
||||
.g = rgb.g,
|
||||
.b = rgb.b,
|
||||
.a = bg_alpha,
|
||||
.bg_r = 0,
|
||||
.bg_g = 0,
|
||||
.bg_b = 0,
|
||||
.bg_a = 0,
|
||||
});
|
||||
}
|
||||
|
||||
break :bg .{ rgb.r, rgb.g, rgb.b, bg_alpha };
|
||||
} else .{
|
||||
self.draw_background.r,
|
||||
self.draw_background.g,
|
||||
self.draw_background.b,
|
||||
@intFromFloat(@max(0, @min(255, @round(self.config.background_opacity * 255)))),
|
||||
};
|
||||
|
||||
// If the cell has a character, draw it
|
||||
if (cell.char > 0) {
|
||||
@ -1416,6 +1458,10 @@ pub fn updateCell(
|
||||
.g = colors.fg.g,
|
||||
.b = colors.fg.b,
|
||||
.a = alpha,
|
||||
.bg_r = bg[0],
|
||||
.bg_g = bg[1],
|
||||
.bg_b = bg[2],
|
||||
.bg_a = bg[3],
|
||||
});
|
||||
}
|
||||
|
||||
@ -1453,6 +1499,10 @@ pub fn updateCell(
|
||||
.g = color.g,
|
||||
.b = color.b,
|
||||
.a = alpha,
|
||||
.bg_r = bg[0],
|
||||
.bg_g = bg[1],
|
||||
.bg_b = bg[2],
|
||||
.bg_a = bg[3],
|
||||
});
|
||||
}
|
||||
|
||||
@ -1472,6 +1522,10 @@ pub fn updateCell(
|
||||
.g = colors.fg.g,
|
||||
.b = colors.fg.b,
|
||||
.a = alpha,
|
||||
.bg_r = bg[0],
|
||||
.bg_g = bg[1],
|
||||
.bg_b = bg[2],
|
||||
.bg_a = bg[3],
|
||||
});
|
||||
}
|
||||
|
||||
@ -1511,6 +1565,9 @@ pub fn changeConfig(self: *OpenGL, config: *DerivedConfig) !void {
|
||||
self.font_shaper = font_shaper;
|
||||
}
|
||||
|
||||
// Update our uniforms
|
||||
self.deferred_config = .{};
|
||||
|
||||
self.config.deinit();
|
||||
self.config = config.*;
|
||||
}
|
||||
@ -1728,6 +1785,10 @@ fn drawCellProgram(
|
||||
try v.apply(self);
|
||||
self.deferred_font_size = null;
|
||||
}
|
||||
if (self.deferred_config) |v| {
|
||||
try v.apply(self);
|
||||
self.deferred_config = null;
|
||||
}
|
||||
|
||||
// Draw background images first
|
||||
try self.drawImages(
|
||||
|
@ -95,6 +95,7 @@ pub const Cell = extern struct {
|
||||
glyph_size: [2]u32 = .{ 0, 0 },
|
||||
glyph_offset: [2]i32 = .{ 0, 0 },
|
||||
color: [4]u8,
|
||||
bg_color: [4]u8,
|
||||
cell_width: u8,
|
||||
|
||||
pub const Mode = enum(u8) {
|
||||
@ -125,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.
|
||||
@ -401,6 +406,17 @@ fn initCellPipeline(device: objc.Object, library: objc.Object) !objc.Object {
|
||||
attr.setProperty("offset", @as(c_ulong, @offsetOf(Cell, "color")));
|
||||
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
||||
}
|
||||
{
|
||||
const attr = attrs.msgSend(
|
||||
objc.Object,
|
||||
objc.sel("objectAtIndexedSubscript:"),
|
||||
.{@as(c_ulong, 7)},
|
||||
);
|
||||
|
||||
attr.setProperty("format", @intFromEnum(mtl.MTLVertexFormat.uchar4));
|
||||
attr.setProperty("offset", @as(c_ulong, @offsetOf(Cell, "bg_color")));
|
||||
attr.setProperty("bufferIndex", @as(c_ulong, 0));
|
||||
}
|
||||
{
|
||||
const attr = attrs.msgSend(
|
||||
objc.Object,
|
||||
|
@ -29,12 +29,18 @@ pub const Cell = extern struct {
|
||||
glyph_offset_x: i32 = 0,
|
||||
glyph_offset_y: i32 = 0,
|
||||
|
||||
/// vec4 fg_color_in
|
||||
/// vec4 color_in
|
||||
r: u8,
|
||||
g: u8,
|
||||
b: u8,
|
||||
a: u8,
|
||||
|
||||
/// vec4 bg_color_in
|
||||
bg_r: u8,
|
||||
bg_g: u8,
|
||||
bg_b: u8,
|
||||
bg_a: u8,
|
||||
|
||||
/// uint mode
|
||||
mode: CellMode,
|
||||
|
||||
@ -105,9 +111,11 @@ pub fn init() !CellProgram {
|
||||
offset += 2 * @sizeOf(i32);
|
||||
try vbobind.attributeAdvanced(4, 4, gl.c.GL_UNSIGNED_BYTE, false, @sizeOf(Cell), offset);
|
||||
offset += 4 * @sizeOf(u8);
|
||||
try vbobind.attributeIAdvanced(5, 1, gl.c.GL_UNSIGNED_BYTE, @sizeOf(Cell), offset);
|
||||
offset += 1 * @sizeOf(u8);
|
||||
try vbobind.attributeAdvanced(5, 4, gl.c.GL_UNSIGNED_BYTE, false, @sizeOf(Cell), offset);
|
||||
offset += 4 * @sizeOf(u8);
|
||||
try vbobind.attributeIAdvanced(6, 1, gl.c.GL_UNSIGNED_BYTE, @sizeOf(Cell), offset);
|
||||
offset += 1 * @sizeOf(u8);
|
||||
try vbobind.attributeIAdvanced(7, 1, gl.c.GL_UNSIGNED_BYTE, @sizeOf(Cell), offset);
|
||||
try vbobind.enableAttribArray(0);
|
||||
try vbobind.enableAttribArray(1);
|
||||
try vbobind.enableAttribArray(2);
|
||||
@ -115,6 +123,7 @@ pub fn init() !CellProgram {
|
||||
try vbobind.enableAttribArray(4);
|
||||
try vbobind.enableAttribArray(5);
|
||||
try vbobind.enableAttribArray(6);
|
||||
try vbobind.enableAttribArray(7);
|
||||
try vbobind.attributeDivisor(0, 1);
|
||||
try vbobind.attributeDivisor(1, 1);
|
||||
try vbobind.attributeDivisor(2, 1);
|
||||
@ -122,6 +131,7 @@ pub fn init() !CellProgram {
|
||||
try vbobind.attributeDivisor(4, 1);
|
||||
try vbobind.attributeDivisor(5, 1);
|
||||
try vbobind.attributeDivisor(6, 1);
|
||||
try vbobind.attributeDivisor(7, 1);
|
||||
|
||||
return .{
|
||||
.program = program,
|
||||
|
@ -13,6 +13,7 @@ struct Uniforms {
|
||||
float2 cell_size;
|
||||
float strikethrough_position;
|
||||
float strikethrough_thickness;
|
||||
float min_contrast;
|
||||
};
|
||||
|
||||
struct VertexIn {
|
||||
@ -29,7 +30,12 @@ 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)
|
||||
uint2 glyph_pos [[ attribute(2) ]];
|
||||
@ -49,6 +55,55 @@ struct VertexOut {
|
||||
float2 tex_coord;
|
||||
};
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Color Functions
|
||||
//-------------------------------------------------------------------
|
||||
#pragma mark - Colors
|
||||
|
||||
// https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
|
||||
float luminance_component(float c) {
|
||||
if (c <= 0.03928f) {
|
||||
return c / 12.92f;
|
||||
} else {
|
||||
return pow((c + 0.055f) / 1.055f, 2.4f);
|
||||
}
|
||||
}
|
||||
|
||||
float relative_luminance(float3 color) {
|
||||
color.r = luminance_component(color.r);
|
||||
color.g = luminance_component(color.g);
|
||||
color.b = luminance_component(color.b);
|
||||
float3 weights = float3(0.2126f, 0.7152f, 0.0722f);
|
||||
return dot(color, weights);
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef
|
||||
float contrast_ratio(float3 color1, float3 color2) {
|
||||
float l1 = relative_luminance(color1);
|
||||
float l2 = relative_luminance(color2);
|
||||
return (max(l1, l2) + 0.05f) / (min(l1, l2) + 0.05f);
|
||||
}
|
||||
|
||||
// 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 < 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);
|
||||
}
|
||||
}
|
||||
|
||||
return fg;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Terminal Grid Cell Shader
|
||||
//-------------------------------------------------------------------
|
||||
@ -112,6 +167,15 @@ vertex VertexOut uber_vertex(
|
||||
// Calculate the texture coordinate in pixels. This is NOT normalized
|
||||
// (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;
|
||||
|
||||
// If we have a minimum contrast, we need to check if we need to
|
||||
// change the color of the text to ensure it has enough contrast
|
||||
// with the background.
|
||||
if (uniforms.min_contrast > 1.0f && input.mode == MODE_FG) {
|
||||
float4 bg_color = float4(input.bg_color) / 255.0f;
|
||||
out.color = contrasted_color(uniforms.min_contrast, out.color, bg_color);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -25,14 +25,18 @@ layout (location = 3) in vec2 glyph_offset;
|
||||
// depends on mode.
|
||||
layout (location = 4) in vec4 color_in;
|
||||
|
||||
// Only set for MODE_FG, this is the background color of the FG text.
|
||||
// This is used to detect minimal contrast for the text.
|
||||
layout (location = 5) in vec4 bg_color_in;
|
||||
|
||||
// The mode of this shader. The mode determines what fields are used,
|
||||
// what the output will be, etc. This shader is capable of executing in
|
||||
// multiple "modes" so that we can share some logic and so that we can draw
|
||||
// the entire terminal grid in a single GPU pass.
|
||||
layout (location = 5) in uint mode_in;
|
||||
layout (location = 6) in uint mode_in;
|
||||
|
||||
// The width in cells of this item.
|
||||
layout (location = 6) in uint grid_width;
|
||||
layout (location = 7) in uint grid_width;
|
||||
|
||||
// The background or foreground color for the fragment, depending on
|
||||
// whether this is a background or foreground pass.
|
||||
@ -54,6 +58,7 @@ uniform vec2 cell_size;
|
||||
uniform mat4 projection;
|
||||
uniform float strikethrough_position;
|
||||
uniform float strikethrough_thickness;
|
||||
uniform float min_contrast;
|
||||
|
||||
/********************************************************************
|
||||
* Modes
|
||||
@ -75,6 +80,61 @@ uniform float strikethrough_thickness;
|
||||
*
|
||||
*/
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Color Functions
|
||||
//-------------------------------------------------------------------
|
||||
|
||||
// https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
|
||||
float luminance_component(float c) {
|
||||
if (c <= 0.03928) {
|
||||
return c / 12.92;
|
||||
} else {
|
||||
return pow((c + 0.055) / 1.055, 2.4);
|
||||
}
|
||||
}
|
||||
|
||||
float relative_luminance(vec3 color) {
|
||||
vec3 color_adjusted = vec3(
|
||||
luminance_component(color.r),
|
||||
luminance_component(color.g),
|
||||
luminance_component(color.b)
|
||||
);
|
||||
|
||||
vec3 weights = vec3(0.2126, 0.7152, 0.0722);
|
||||
return dot(color_adjusted, weights);
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef
|
||||
float contrast_ratio(vec3 color1, vec3 color2) {
|
||||
float luminance1 = relative_luminance(color1) + 0.05;
|
||||
float luminance2 = relative_luminance(color2) + 0.05;
|
||||
return max(luminance1, luminance2) / min(luminance1, luminance2);
|
||||
}
|
||||
|
||||
// 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.
|
||||
vec4 contrasted_color(float min_ratio, vec4 fg, vec4 bg) {
|
||||
vec3 fg_premult = fg.rgb * fg.a;
|
||||
vec3 bg_premult = bg.rgb * bg.a;
|
||||
float ratio = contrast_ratio(fg_premult, bg_premult);
|
||||
if (ratio < min_ratio) {
|
||||
float white_ratio = contrast_ratio(vec3(1.0, 1.0, 1.0), bg_premult);
|
||||
float black_ratio = contrast_ratio(vec3(0.0, 0.0, 0.0), bg_premult);
|
||||
if (white_ratio > black_ratio) {
|
||||
return vec4(1.0, 1.0, 1.0, fg.a);
|
||||
} else {
|
||||
return vec4(0.0, 0.0, 0.0, fg.a);
|
||||
}
|
||||
}
|
||||
|
||||
return fg;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Main
|
||||
//-------------------------------------------------------------------
|
||||
|
||||
void main() {
|
||||
// We always forward our mode unmasked because the fragment
|
||||
// shader doesn't use any of the masks.
|
||||
@ -147,8 +207,15 @@ void main() {
|
||||
vec2 glyph_tex_size = glyph_size / text_size;
|
||||
glyph_tex_coords = glyph_tex_pos + glyph_tex_size * position;
|
||||
|
||||
// Set our foreground color output
|
||||
color = color_in / 255.;
|
||||
// If we have a minimum contrast, we need to check if we need to
|
||||
// change the color of the text to ensure it has enough contrast
|
||||
// with the background.
|
||||
vec4 color_final = color_in / 255.0;
|
||||
if (min_contrast > 1.0 && mode == MODE_FG) {
|
||||
vec4 bg_color = bg_color_in / 255.0;
|
||||
color_final = contrasted_color(min_contrast, color_final, bg_color);
|
||||
}
|
||||
color = color_final;
|
||||
break;
|
||||
|
||||
case MODE_STRIKETHROUGH:
|
||||
|
Reference in New Issue
Block a user