]> source.dussan.org Git - gitea.git/commitdiff
Actually compute proper foreground color for labels (#16729)
authorClar Fon <usr@ltdk.xyz>
Wed, 25 Aug 2021 17:55:47 +0000 (13:55 -0400)
committerGitHub <noreply@github.com>
Wed, 25 Aug 2021 17:55:47 +0000 (12:55 -0500)
models/issue_label.go
web_src/js/components/ContextPopup.vue

index 5d50203b5a5baf99f0bb9d9fb9c5073617f3f308..936dd71e381de2a6022f14a8f9fa448a0c5569b4 100644 (file)
@@ -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")
                }
        }
 
index df04f0d8d8de3b2eae0a2e56b0d19c6324d0118a..be428b477650dd184b1d5f53172ae9328c1f0867 100644 (file)
@@ -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};
       });
     }
   },