Skip to content

Commit b13b62e

Browse files
committed
Actually compute proper foreground color for labels
1 parent 4aa3cac commit b13b62e

File tree

2 files changed

+53
-12
lines changed

2 files changed

+53
-12
lines changed

models/issue_label.go

+31-5
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ package models
88
import (
99
"fmt"
1010
"html/template"
11+
"math"
1112
"regexp"
1213
"strconv"
1314
"strings"
@@ -138,19 +139,44 @@ func (label *Label) BelongsToRepo() bool {
138139
return label.RepoID > 0
139140
}
140141

142+
// SrgbToLinear converts a component of an sRGB color to its linear intensity
143+
// See: https://en.wikipedia.org/wiki/SRGB#The_reverse_transformation_(sRGB_to_CIE_XYZ)
144+
func SrgbToLinear(color uint8) float64 {
145+
flt := float64(color) / 255
146+
if flt <= 0.04045 {
147+
return flt / 12.92
148+
}
149+
return math.Pow((flt+0.055)/1.055, 2.4)
150+
}
151+
152+
// Luminance returns the luminance of an sRGB color
153+
func Luminance(color uint32) float64 {
154+
r := SrgbToLinear(uint8(0xFF & (color >> 16)))
155+
g := SrgbToLinear(uint8(0xFF & (color >> 8)))
156+
b := SrgbToLinear(uint8(0xFF & color))
157+
158+
// luminance ratios for sRGB
159+
return 0.2126*r + 0.7152*g + 0.0722*b
160+
}
161+
162+
// LuminanceThreshold is the luminance at which white and black appear to have the same contrast
163+
// i.e. x such that 1.05 / (x + 0.05) = (x + 0.05) / 0.05
164+
// i.e. math.Sqrt(1.05*0.05) - 0.05
165+
const LuminanceThreshold float64 = 0.179
166+
141167
// ForegroundColor calculates the text color for labels based
142168
// on their background color.
143169
func (label *Label) ForegroundColor() template.CSS {
144170
if strings.HasPrefix(label.Color, "#") {
145171
if color, err := strconv.ParseUint(label.Color[1:], 16, 64); err == nil {
146-
r := float32(0xFF & (color >> 16))
147-
g := float32(0xFF & (color >> 8))
148-
b := float32(0xFF & color)
149-
luminance := (0.2126*r + 0.7152*g + 0.0722*b) / 255
172+
// NOTE: see web_src/js/components/ContextPopup.vue for similar implementation
173+
luminance := Luminance(uint32(color))
150174

151-
if luminance < 0.66 {
175+
// prefer white or black based upon contrast
176+
if luminance < LuminanceThreshold {
152177
return template.CSS("#fff")
153178
}
179+
return template.CSS("#000")
154180
}
155181
}
156182

web_src/js/components/ContextPopup.vue

+22-7
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,22 @@ import {SvgIcon} from '../svg.js';
2424
2525
const {AppSubUrl} = window.config;
2626
27+
// NOTE: see models/issue_label.go for similar implementation
28+
const srgbToLinear = (color) => {
29+
color /= 255;
30+
if (color <= 0.04045) {
31+
return color / 12.92;
32+
}
33+
return ((color + 0.055) / 1.055) ** 2.4;
34+
};
35+
const luminance = (colorString) => {
36+
const r = srgbToLinear(parseInt(colorString.substring(0, 2), 16));
37+
const g = srgbToLinear(parseInt(colorString.substring(2, 4), 16));
38+
const b = srgbToLinear(parseInt(colorString.substring(4, 6), 16));
39+
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
40+
};
41+
const luminanceThreshold = 0.179;
42+
2743
export default {
2844
name: 'ContextPopup',
2945
@@ -74,14 +90,13 @@ export default {
7490
7591
labels() {
7692
return this.issue.labels.map((label) => {
77-
const red = parseInt(label.color.substring(0, 2), 16);
78-
const green = parseInt(label.color.substring(2, 4), 16);
79-
const blue = parseInt(label.color.substring(4, 6), 16);
80-
let color = '#ffffff';
81-
if ((red * 0.299 + green * 0.587 + blue * 0.114) > 125) {
82-
color = '#000000';
93+
let textColor;
94+
if (luminance(label.color) < luminanceThreshold) {
95+
textColor = '#ffffff';
96+
} else {
97+
textColor = '#000000';
8398
}
84-
return {name: label.name, color: `#${label.color}`, textColor: color};
99+
return {name: label.name, color: `#${label.color}`, textColor};
85100
});
86101
}
87102
},

0 commit comments

Comments
 (0)