enhance(ui): improve luminance calculations (#38682)

This commit is contained in:
silverwind
2026-08-06 08:41:40 +02:00
committed by GitHub
parent cc0d348d84
commit 92ac357b1a
5 changed files with 45 additions and 22 deletions
+7 -7
View File
@@ -120,15 +120,15 @@ func (ut *RenderUtils) RenderLabel(label *issues_model.Label) template.HTML {
itemHTML := ut.RenderEmoji(label.Name[len(labelScope)+1:])
// Make scope and item background colors slightly darker and lighter respectively.
// More contrast needed with higher luminance, empirically tweaked.
luminance := util.GetRelativeLuminance(label.Color)
contrast := 0.01 + luminance*0.03
// More contrast needed with higher brightness, empirically tweaked.
brightness := util.GetPerceivedBrightness(label.Color)
contrast := 0.01 + brightness*0.03
// Ensure we add the same amount of contrast also near 0 and 1.
darken := contrast + math.Max(luminance+contrast-1.0, 0.0)
lighten := contrast + math.Max(contrast-luminance, 0.0)
darken := contrast + math.Max(brightness+contrast-1.0, 0.0)
lighten := contrast + math.Max(contrast-brightness, 0.0)
// Compute the factor to keep RGB values proportional.
darkenFactor := math.Max(luminance-darken, 0.0) / math.Max(luminance, 1.0/255.0)
lightenFactor := math.Min(luminance+lighten, 1.0) / math.Max(luminance, 1.0/255.0)
darkenFactor := math.Max(brightness-darken, 0.0) / math.Max(brightness, 1.0/255.0)
lightenFactor := math.Min(brightness+lighten, 1.0) / math.Max(brightness, 1.0/255.0)
r, g, b := util.HexToRBGColor(label.Color)
scopeBytes := []byte{
+21 -6
View File
@@ -5,6 +5,7 @@ package util
import (
"fmt"
"math"
"strconv"
"strings"
)
@@ -37,20 +38,34 @@ func HexToRBGColor(colorString string) (float64, float64, float64) {
return r, g, b
}
// GetRelativeLuminance returns relative luminance for a SRGB color - https://en.wikipedia.org/wiki/Relative_luminance
// Keep this in sync with web_src/js/utils/color.js
func GetRelativeLuminance(color string) float64 {
// linearizeChannel undoes the sRGB transfer function, channel is in 0..255 range
func linearizeChannel(channel float64) float64 {
srgb := channel / 255
if srgb <= 0.04045 {
return srgb / 12.92
}
return math.Pow((srgb+0.055)/1.055, 2.4)
}
// getRelativeLuminance returns relative luminance for a SRGB color - https://www.w3.org/TR/WCAG20/#relativeluminancedef
// Keep this in sync with web_src/js/utils/color.ts
func getRelativeLuminance(color string) float64 {
r, g, b := HexToRBGColor(color)
return 0.2126*linearizeChannel(r) + 0.7152*linearizeChannel(g) + 0.0722*linearizeChannel(b)
}
// GetPerceivedBrightness weights the gamma-encoded channels, so it stays in the same space as the
// raw channels and can scale them proportionally. Not a luminance, don't use it for contrast.
func GetPerceivedBrightness(color string) float64 {
r, g, b := HexToRBGColor(color)
return (0.2126729*r + 0.7151522*g + 0.0721750*b) / 255
}
func UseLightText(backgroundColor string) bool {
return GetRelativeLuminance(backgroundColor) < 0.453
return getRelativeLuminance(backgroundColor) < 0.36 // matches APCA better than WCAG's own 0.179
}
// ContrastColor returns a black or white foreground color that the highest contrast ratio.
// In the future, the APCA contrast function, or CSS `contrast-color` will be better.
// https://github.com/color-js/color.js/blob/eb7b53f7a13bb716ec8b28c7a56f052cd599acd9/src/contrast/APCA.js#L42
func ContrastColor(backgroundColor string) string {
if UseLightText(backgroundColor) {
return "#fff"
+4 -2
View File
@@ -45,11 +45,13 @@ func Test_UseLightText(t *testing.T) {
{"#7057ff", "#fff"},
{"#008672", "#fff"},
{"#e4e669", "#000"},
{"#d876e3", "#000"},
{"#d876e3", "#fff"},
{"#ffffff", "#000"},
{"#2b8684", "#fff"},
{"#2b8786", "#fff"},
{"#2c8786", "#000"},
{"#2c8786", "#fff"},
{"#2bb3b2", "#fff"},
{"#2bb4b3", "#000"},
{"#3bb6b3", "#000"},
{"#7c7268", "#fff"},
{"#7e716c", "#fff"},