aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--models/issue_label.go36
-rw-r--r--web_src/js/components/ContextPopup.vue29
2 files changed, 53 insertions, 12 deletions
diff --git a/models/issue_label.go b/models/issue_label.go
index 5d50203b5a..936dd71e38 100644
--- a/models/issue_label.go
+++ b/models/issue_label.go
@@ -8,6 +8,7 @@ package models
import (
"fmt"
"html/template"
+ "math"
"regexp"
"strconv"
"strings"
@@ -138,19 +139,44 @@ func (label *Label) BelongsToRepo() bool {
return label.RepoID > 0
}
+// SrgbToLinear converts a component of an sRGB color to its linear intensity
+// See: https://en.wikipedia.org/wiki/SRGB#The_reverse_transformation_(sRGB_to_CIE_XYZ)
+func SrgbToLinear(color uint8) float64 {
+ flt := float64(color) / 255
+ if flt <= 0.04045 {
+ return flt / 12.92
+ }
+ return math.Pow((flt+0.055)/1.055, 2.4)
+}
+
+// Luminance returns the luminance of an sRGB color
+func Luminance(color uint32) float64 {
+ r := SrgbToLinear(uint8(0xFF & (color >> 16)))
+ g := SrgbToLinear(uint8(0xFF & (color >> 8)))
+ b := SrgbToLinear(uint8(0xFF & color))
+
+ // luminance ratios for sRGB
+ return 0.2126*r + 0.7152*g + 0.0722*b
+}
+
+// LuminanceThreshold is the luminance at which white and black appear to have the same contrast
+// i.e. x such that 1.05 / (x + 0.05) = (x + 0.05) / 0.05
+// i.e. math.Sqrt(1.05*0.05) - 0.05
+const LuminanceThreshold float64 = 0.179
+
// ForegroundColor calculates the text color for labels based
// on their background color.
func (label *Label) ForegroundColor() template.CSS {
if strings.HasPrefix(label.Color, "#") {
if color, err := strconv.ParseUint(label.Color[1:], 16, 64); err == nil {
- r := float32(0xFF & (color >> 16))
- g := float32(0xFF & (color >> 8))
- b := float32(0xFF & color)
- luminance := (0.2126*r + 0.7152*g + 0.0722*b) / 255
+ // NOTE: see web_src/js/components/ContextPopup.vue for similar implementation
+ luminance := Luminance(uint32(color))
- if luminance < 0.66 {
+ // prefer white or black based upon contrast
+ if luminance < LuminanceThreshold {
return template.CSS("#fff")
}
+ return template.CSS("#000")
}
}
diff --git a/web_src/js/components/ContextPopup.vue b/web_src/js/components/ContextPopup.vue
index df04f0d8d8..be428b4776 100644
--- a/web_src/js/components/ContextPopup.vue
+++ b/web_src/js/components/ContextPopup.vue
@@ -24,6 +24,22 @@ import {SvgIcon} from '../svg.js';
const {AppSubUrl} = window.config;
+// NOTE: see models/issue_label.go for similar implementation
+const srgbToLinear = (color) => {
+ color /= 255;
+ if (color <= 0.04045) {
+ return color / 12.92;
+ }
+ return ((color + 0.055) / 1.055) ** 2.4;
+};
+const luminance = (colorString) => {
+ const r = srgbToLinear(parseInt(colorString.substring(0, 2), 16));
+ const g = srgbToLinear(parseInt(colorString.substring(2, 4), 16));
+ const b = srgbToLinear(parseInt(colorString.substring(4, 6), 16));
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b;
+};
+const luminanceThreshold = 0.179;
+
export default {
name: 'ContextPopup',
@@ -74,14 +90,13 @@ export default {
labels() {
return this.issue.labels.map((label) => {
- const red = parseInt(label.color.substring(0, 2), 16);
- const green = parseInt(label.color.substring(2, 4), 16);
- const blue = parseInt(label.color.substring(4, 6), 16);
- let color = '#ffffff';
- if ((red * 0.299 + green * 0.587 + blue * 0.114) > 125) {
- color = '#000000';
+ let textColor;
+ if (luminance(label.color) < luminanceThreshold) {
+ textColor = '#ffffff';
+ } else {
+ textColor = '#000000';
}
- return {name: label.name, color: `#${label.color}`, textColor: color};
+ return {name: label.name, color: `#${label.color}`, textColor};
});
}
},