From 6c859cca821d33f39351d1d2de06344b82421f43 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 1 Dec 2023 20:51:16 -0800 Subject: [PATCH] renderer/metal: minimum contrast experiment This uses WCAG2 algorithms with a minimum ratio hardcoded of 3:1. This is not shippable in its current state because we want the ratio to be configurable and I'm not happy with the way data is being sent to the shader. --- src/renderer/Metal.zig | 13 +++++++-- src/renderer/metal/shaders.zig | 12 +++++++++ src/renderer/shaders/cell.metal | 48 +++++++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+), 2 deletions(-) diff --git a/src/renderer/Metal.zig b/src/renderer/Metal.zig index 205f7eafc..df494ef41 100644 --- a/src/renderer/Metal.zig +++ b/src/renderer/Metal.zig @@ -1667,7 +1667,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 +1701,11 @@ 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 .{ 0, 0, 0, 0 }; // If the cell has a character, draw it if (cell.char > 0) { @@ -1729,6 +1732,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 +1763,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 +1776,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 +1840,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 +1893,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 +1902,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 }, diff --git a/src/renderer/metal/shaders.zig b/src/renderer/metal/shaders.zig index 030ae2b6c..6980a309e 100644 --- a/src/renderer/metal/shaders.zig +++ b/src/renderer/metal/shaders.zig @@ -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) { @@ -401,6 +402,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, diff --git a/src/renderer/shaders/cell.metal b/src/renderer/shaders/cell.metal index e6ba3f7ac..b8116e1c0 100644 --- a/src/renderer/shaders/cell.metal +++ b/src/renderer/shaders/cell.metal @@ -30,6 +30,7 @@ struct VertexIn { uchar4 color [[ attribute(5) ]]; // The fields below are present only when rendering text. + uchar4 bg_color [[ attribute(7) ]]; // The position of the glyph in the texture (x,y) uint2 glyph_pos [[ attribute(2) ]]; @@ -49,6 +50,51 @@ 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); +} + +float4 contrasted_color(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) { + return float4(1.0f); + } else { + return float4(0.0f, 0.0f, 0.0f, 1.0f); + } + } + + return fg; +} + //------------------------------------------------------------------- // Terminal Grid Cell Shader //------------------------------------------------------------------- @@ -112,6 +158,8 @@ 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; + + out.color = contrasted_color(out.color, float4(input.bg_color) / 255.0f); break; }