From 17ecae7ccc5f8ebdbc4e21983d832d619028cc1a Mon Sep 17 00:00:00 2001 From: HesterG Date: Fri, 5 May 2023 12:11:08 +0800 Subject: [PATCH 01/32] change lightness calculation function --- models/issues/label.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/models/issues/label.go b/models/issues/label.go index 35c649e8f24d9..ca1cc4ba045ff 100644 --- a/models/issues/label.go +++ b/models/issues/label.go @@ -176,10 +176,11 @@ func (l *Label) ColorRGB() (float64, float64, float64, error) { func (l *Label) UseLightTextColor() bool { if strings.HasPrefix(l.Color, "#") { if r, g, b, err := l.ColorRGB(); err == nil { - // Perceived brightness from: https://www.w3.org/TR/AERT/#color-contrast + // Reference from: https://firsching.ch/github_labels.html // In the future WCAG 3 APCA may be a better solution - brightness := (0.299*r + 0.587*g + 0.114*b) / 255 - return brightness < 0.35 + brightness := (0.2126*r + 0.7152*g + 0.0722*b) / 255 + lightnessThreshold := 0.453 + return brightness < lightnessThreshold } } From ad2b5e727ad9f2cfca040a06edf43218ebbec1b3 Mon Sep 17 00:00:00 2001 From: HesterG Date: Fri, 5 May 2023 15:56:56 +0800 Subject: [PATCH 02/32] save changes --- models/issues/label.go | 20 ++++++++++++++++---- modules/templates/util_render.go | 2 +- web_src/js/utils.js | 14 +++++++++++--- 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/models/issues/label.go b/models/issues/label.go index ca1cc4ba045ff..9c428ca08af12 100644 --- a/models/issues/label.go +++ b/models/issues/label.go @@ -7,6 +7,7 @@ package issues import ( "context" "fmt" + "math" "strconv" "strings" @@ -159,6 +160,14 @@ func (l *Label) BelongsToRepo() bool { return l.RepoID > 0 } +func getColorFromChannel(rgbChannel float64) float64 { + result := rgbChannel / 255 + if result <= 0.03928 { + return result / 12.92 * 255 + } + return math.Pow((result+0.055)/1.055, 2.4) * 255 +} + // Get color as RGB values in 0..255 range func (l *Label) ColorRGB() (float64, float64, float64, error) { color, err := strconv.ParseUint(l.Color[1:], 16, 64) @@ -166,9 +175,12 @@ func (l *Label) ColorRGB() (float64, float64, float64, error) { return 0, 0, 0, err } - r := float64(uint8(0xFF & (uint32(color) >> 16))) - g := float64(uint8(0xFF & (uint32(color) >> 8))) - b := float64(uint8(0xFF & uint32(color))) + R := float64(uint8(0xFF & (uint32(color) >> 16))) + G := float64(uint8(0xFF & (uint32(color) >> 8))) + B := float64(uint8(0xFF & uint32(color))) + r := getColorFromChannel(R) + g := getColorFromChannel(G) + b := getColorFromChannel(B) return r, g, b, nil } @@ -176,7 +188,7 @@ func (l *Label) ColorRGB() (float64, float64, float64, error) { func (l *Label) UseLightTextColor() bool { if strings.HasPrefix(l.Color, "#") { if r, g, b, err := l.ColorRGB(); err == nil { - // Reference from: https://firsching.ch/github_labels.html + // Reference from: https://firsching.ch/github_labels.html and https://www.w3.org/WAI/GL/wiki/Relative_luminance // In the future WCAG 3 APCA may be a better solution brightness := (0.2126*r + 0.7152*g + 0.0722*b) / 255 lightnessThreshold := 0.453 diff --git a/modules/templates/util_render.go b/modules/templates/util_render.go index a59ddd3f175dc..5ef85d8485e4a 100644 --- a/modules/templates/util_render.go +++ b/modules/templates/util_render.go @@ -155,7 +155,7 @@ func RenderLabel(ctx context.Context, label *issues_model.Label) template.HTML { if r, g, b, err := label.ColorRGB(); err == nil { // Make scope and item background colors slightly darker and lighter respectively. // More contrast needed with higher luminance, empirically tweaked. - luminance := (0.299*r + 0.587*g + 0.114*b) / 255 + luminance := (0.2126*r + 0.7152*g + 0.0722*b) / 255 contrast := 0.01 + luminance*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) diff --git a/web_src/js/utils.js b/web_src/js/utils.js index 25094deea2269..68fe098d05291 100644 --- a/web_src/js/utils.js +++ b/web_src/js/utils.js @@ -141,11 +141,19 @@ export function useLightTextOnBackground(backgroundColor) { if (backgroundColor[0] === '#') { backgroundColor = backgroundColor.substring(1); } - // Perceived brightness from: https://www.w3.org/TR/AERT/#color-contrast + // Reference from: https://firsching.ch/github_labels.html and https://www.w3.org/WAI/GL/wiki/Relative_luminance // In the future WCAG 3 APCA may be a better solution. const r = parseInt(backgroundColor.substring(0, 2), 16); const g = parseInt(backgroundColor.substring(2, 4), 16); const b = parseInt(backgroundColor.substring(4, 6), 16); - const brightness = (0.299 * r + 0.587 * g + 0.114 * b) / 255; - return brightness < 0.35; + const RsRGB = r / 255; + const GsRGB = g / 255; + const BsRGB = b / 255; + + const R = (RsRGB <= 0.03928) ? RsRGB / 12.92 : ((RsRGB + 0.055) / 1.055) ** 2.4; + const G = (GsRGB <= 0.03928) ? GsRGB / 12.92 : ((GsRGB + 0.055) / 1.055) ** 2.4; + const B = (BsRGB <= 0.03928) ? BsRGB / 12.92 : ((BsRGB + 0.055) / 1.055) ** 2.4; + + const brightness = 0.2126 * R + 0.7152 * G + 0.0722 * B; + return brightness < 0.453; } From d2ffc63042144af0c5ef6a27e27fa51b499efd7a Mon Sep 17 00:00:00 2001 From: HesterG Date: Fri, 5 May 2023 16:50:32 +0800 Subject: [PATCH 03/32] add some tests --- models/fixtures/label.yml | 80 +++++++++++++++++++++++++++++++++++++ models/issues/label.go | 4 +- models/issues/label_test.go | 24 +++++++++++ 3 files changed, 106 insertions(+), 2 deletions(-) diff --git a/models/fixtures/label.yml b/models/fixtures/label.yml index ab4d5ef944045..d6cdbe218ccfb 100644 --- a/models/fixtures/label.yml +++ b/models/fixtures/label.yml @@ -87,3 +87,83 @@ exclusive: true num_issues: 0 num_closed_issues: 0 + +- + id: 10 + repo_id: 55 + org_id: 0 + name: #2b8685 + color: '#2b8685' + exclusive: false + num_issues: 0 + num_closed_issues: 0 + +- + id: 11 + repo_id: 55 + org_id: 0 + name: #2b8786 + color: '#2b8786' + exclusive: false + num_issues: 0 + num_closed_issues: 0 + +- + id: 12 + repo_id: 55 + org_id: 0 + name: #2c8786 + color: '#2c8786' + exclusive: false + num_issues: 0 + num_closed_issues: 0 + +- + id: 13 + repo_id: 55 + org_id: 0 + name: #3bb6b3 + color: '#3bb6b3' + exclusive: false + num_issues: 0 + num_closed_issues: 0 + +- + id: 14 + repo_id: 55 + org_id: 0 + name: #7c7268 + color: '#7c7268' + exclusive: false + num_issues: 0 + num_closed_issues: 0 + +- + id: 15 + repo_id: 55 + org_id: 0 + name: #7e716c + color: '#7e716c' + exclusive: false + num_issues: 0 + num_closed_issues: 0 + +- + id: 16 + repo_id: 55 + org_id: 0 + name: #81706d + color: '#81706d' + exclusive: false + num_issues: 0 + num_closed_issues: 0 + +- + id: 17 + repo_id: 55 + org_id: 0 + name: #807070 + color: '#807070' + exclusive: false + num_issues: 0 + num_closed_issues: 0 diff --git a/models/issues/label.go b/models/issues/label.go index 9c428ca08af12..0f9701f449c42 100644 --- a/models/issues/label.go +++ b/models/issues/label.go @@ -190,9 +190,9 @@ func (l *Label) UseLightTextColor() bool { if r, g, b, err := l.ColorRGB(); err == nil { // Reference from: https://firsching.ch/github_labels.html and https://www.w3.org/WAI/GL/wiki/Relative_luminance // In the future WCAG 3 APCA may be a better solution - brightness := (0.2126*r + 0.7152*g + 0.0722*b) / 255 + luminance := (0.2126*r + 0.7152*g + 0.0722*b) / 255 lightnessThreshold := 0.453 - return brightness < lightnessThreshold + return luminance < lightnessThreshold } } diff --git a/models/issues/label_test.go b/models/issues/label_test.go index 1f6ce4f42ee78..f5e3ceefdb387 100644 --- a/models/issues/label_test.go +++ b/models/issues/label_test.go @@ -29,6 +29,30 @@ func TestLabel_TextColor(t *testing.T) { label = unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 2}) assert.True(t, label.UseLightTextColor()) + + label = unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 10}) + assert.True(t, label.UseLightTextColor()) + + label = unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 11}) + assert.True(t, label.UseLightTextColor()) + + label = unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 12}) + assert.False(t, label.UseLightTextColor()) + + label = unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 13}) + assert.False(t, label.UseLightTextColor()) + + label = unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 14}) + assert.True(t, label.UseLightTextColor()) + + label = unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 15}) + assert.True(t, label.UseLightTextColor()) + + label = unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 16}) + assert.True(t, label.UseLightTextColor()) + + label = unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 17}) + assert.True(t, label.UseLightTextColor()) } func TestLabel_ExclusiveScope(t *testing.T) { From 29c5a8d2dacb74fd81434dda84013bc5f39469d9 Mon Sep 17 00:00:00 2001 From: HesterG Date: Fri, 5 May 2023 17:46:32 +0800 Subject: [PATCH 04/32] add more tests --- models/fixtures/label.yml | 40 +++++++++++++++++++++++++++++++++++++ models/issues/label_test.go | 16 +++++++++++++-- 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/models/fixtures/label.yml b/models/fixtures/label.yml index d6cdbe218ccfb..e584f496b33b1 100644 --- a/models/fixtures/label.yml +++ b/models/fixtures/label.yml @@ -167,3 +167,43 @@ exclusive: false num_issues: 0 num_closed_issues: 0 + +- + id: 18 + repo_id: 55 + org_id: 0 + name: bug + color: '#D73A4A' + exclusive: false + num_issues: 0 + num_closed_issues: 0 + +- + id: 19 + repo_id: 55 + org_id: 0 + name: documentation + color: '#0075CA' + exclusive: false + num_issues: 0 + num_closed_issues: 0 + +- + id: 20 + repo_id: 55 + org_id: 0 + name: duplicate + color: '#CFD3D7' + exclusive: false + num_issues: 0 + num_closed_issues: 0 + +- + id: 21 + repo_id: 55 + org_id: 0 + name: enhancement + color: '#A2EEEF' + exclusive: false + num_issues: 0 + num_closed_issues: 0 diff --git a/models/issues/label_test.go b/models/issues/label_test.go index f5e3ceefdb387..c9750266670cd 100644 --- a/models/issues/label_test.go +++ b/models/issues/label_test.go @@ -37,10 +37,10 @@ func TestLabel_TextColor(t *testing.T) { assert.True(t, label.UseLightTextColor()) label = unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 12}) - assert.False(t, label.UseLightTextColor()) + assert.True(t, label.UseLightTextColor()) label = unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 13}) - assert.False(t, label.UseLightTextColor()) + assert.True(t, label.UseLightTextColor()) label = unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 14}) assert.True(t, label.UseLightTextColor()) @@ -53,6 +53,18 @@ func TestLabel_TextColor(t *testing.T) { label = unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 17}) assert.True(t, label.UseLightTextColor()) + + label = unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 18}) + assert.True(t, label.UseLightTextColor()) + + label = unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 19}) + assert.True(t, label.UseLightTextColor()) + + label = unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 20}) + assert.False(t, label.UseLightTextColor()) + + label = unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 21}) + assert.False(t, label.UseLightTextColor()) } func TestLabel_ExclusiveScope(t *testing.T) { From 4d3ed677d6c9682b28fb42c5d884cbf80de7bb1c Mon Sep 17 00:00:00 2001 From: HesterG Date: Fri, 5 May 2023 17:56:36 +0800 Subject: [PATCH 05/32] use same logic as github and modify tests --- models/issues/label.go | 9 +++------ models/issues/label_test.go | 11 +++-------- web_src/js/utils.js | 9 +-------- 3 files changed, 7 insertions(+), 22 deletions(-) diff --git a/models/issues/label.go b/models/issues/label.go index 0f9701f449c42..afe3b13a6d9aa 100644 --- a/models/issues/label.go +++ b/models/issues/label.go @@ -175,12 +175,9 @@ func (l *Label) ColorRGB() (float64, float64, float64, error) { return 0, 0, 0, err } - R := float64(uint8(0xFF & (uint32(color) >> 16))) - G := float64(uint8(0xFF & (uint32(color) >> 8))) - B := float64(uint8(0xFF & uint32(color))) - r := getColorFromChannel(R) - g := getColorFromChannel(G) - b := getColorFromChannel(B) + r := float64(uint8(0xFF & (uint32(color) >> 16))) + g := float64(uint8(0xFF & (uint32(color) >> 8))) + b := float64(uint8(0xFF & uint32(color))) return r, g, b, nil } diff --git a/models/issues/label_test.go b/models/issues/label_test.go index c9750266670cd..75da0b86b3367 100644 --- a/models/issues/label_test.go +++ b/models/issues/label_test.go @@ -24,23 +24,18 @@ func TestLabel_CalOpenIssues(t *testing.T) { func TestLabel_TextColor(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 1}) - assert.False(t, label.UseLightTextColor()) - - label = unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 2}) - assert.True(t, label.UseLightTextColor()) - label = unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 10}) + label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 10}) assert.True(t, label.UseLightTextColor()) label = unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 11}) assert.True(t, label.UseLightTextColor()) label = unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 12}) - assert.True(t, label.UseLightTextColor()) + assert.False(t, label.UseLightTextColor()) label = unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 13}) - assert.True(t, label.UseLightTextColor()) + assert.False(t, label.UseLightTextColor()) label = unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 14}) assert.True(t, label.UseLightTextColor()) diff --git a/web_src/js/utils.js b/web_src/js/utils.js index 68fe098d05291..6421e61bbe62b 100644 --- a/web_src/js/utils.js +++ b/web_src/js/utils.js @@ -146,14 +146,7 @@ export function useLightTextOnBackground(backgroundColor) { const r = parseInt(backgroundColor.substring(0, 2), 16); const g = parseInt(backgroundColor.substring(2, 4), 16); const b = parseInt(backgroundColor.substring(4, 6), 16); - const RsRGB = r / 255; - const GsRGB = g / 255; - const BsRGB = b / 255; - const R = (RsRGB <= 0.03928) ? RsRGB / 12.92 : ((RsRGB + 0.055) / 1.055) ** 2.4; - const G = (GsRGB <= 0.03928) ? GsRGB / 12.92 : ((GsRGB + 0.055) / 1.055) ** 2.4; - const B = (BsRGB <= 0.03928) ? BsRGB / 12.92 : ((BsRGB + 0.055) / 1.055) ** 2.4; - - const brightness = 0.2126 * R + 0.7152 * G + 0.0722 * B; + const brightness = 0.2126 * r + 0.7152 * g + 0.0722 * b; return brightness < 0.453; } From 75f1f662dcdf81706d20f1f69cd96ac978d11ee8 Mon Sep 17 00:00:00 2001 From: HesterG Date: Fri, 5 May 2023 17:57:44 +0800 Subject: [PATCH 06/32] remove unused --- models/issues/label.go | 9 --------- 1 file changed, 9 deletions(-) diff --git a/models/issues/label.go b/models/issues/label.go index afe3b13a6d9aa..50c8409aecf47 100644 --- a/models/issues/label.go +++ b/models/issues/label.go @@ -7,7 +7,6 @@ package issues import ( "context" "fmt" - "math" "strconv" "strings" @@ -160,14 +159,6 @@ func (l *Label) BelongsToRepo() bool { return l.RepoID > 0 } -func getColorFromChannel(rgbChannel float64) float64 { - result := rgbChannel / 255 - if result <= 0.03928 { - return result / 12.92 * 255 - } - return math.Pow((result+0.055)/1.055, 2.4) * 255 -} - // Get color as RGB values in 0..255 range func (l *Label) ColorRGB() (float64, float64, float64, error) { color, err := strconv.ParseUint(l.Color[1:], 16, 64) From 5f1aa4dd2427e64e5c4f2f38d3001e7bbf72e33c Mon Sep 17 00:00:00 2001 From: HesterG Date: Fri, 5 May 2023 18:02:12 +0800 Subject: [PATCH 07/32] fix --- web_src/js/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web_src/js/utils.js b/web_src/js/utils.js index 6421e61bbe62b..75f9f22f8b25c 100644 --- a/web_src/js/utils.js +++ b/web_src/js/utils.js @@ -147,6 +147,6 @@ export function useLightTextOnBackground(backgroundColor) { const g = parseInt(backgroundColor.substring(2, 4), 16); const b = parseInt(backgroundColor.substring(4, 6), 16); - const brightness = 0.2126 * r + 0.7152 * g + 0.0722 * b; + const brightness = (0.2126 * r + 0.7152 * g + 0.0722 * b) / 255; return brightness < 0.453; } From c1fd625339c981083d914fc288dfa09c1065d456 Mon Sep 17 00:00:00 2001 From: HesterG Date: Sat, 6 May 2023 10:15:30 +0800 Subject: [PATCH 08/32] modify tests --- models/fixtures/label.yml | 120 ------------------------------- models/issues/label.go | 4 +- models/issues/label_test.go | 33 +-------- modules/templates/util_render.go | 3 +- modules/util/color.go | 18 +++++ modules/util/color_test.go | 40 +++++++++++ 6 files changed, 63 insertions(+), 155 deletions(-) create mode 100644 modules/util/color.go create mode 100644 modules/util/color_test.go diff --git a/models/fixtures/label.yml b/models/fixtures/label.yml index e584f496b33b1..ab4d5ef944045 100644 --- a/models/fixtures/label.yml +++ b/models/fixtures/label.yml @@ -87,123 +87,3 @@ exclusive: true num_issues: 0 num_closed_issues: 0 - -- - id: 10 - repo_id: 55 - org_id: 0 - name: #2b8685 - color: '#2b8685' - exclusive: false - num_issues: 0 - num_closed_issues: 0 - -- - id: 11 - repo_id: 55 - org_id: 0 - name: #2b8786 - color: '#2b8786' - exclusive: false - num_issues: 0 - num_closed_issues: 0 - -- - id: 12 - repo_id: 55 - org_id: 0 - name: #2c8786 - color: '#2c8786' - exclusive: false - num_issues: 0 - num_closed_issues: 0 - -- - id: 13 - repo_id: 55 - org_id: 0 - name: #3bb6b3 - color: '#3bb6b3' - exclusive: false - num_issues: 0 - num_closed_issues: 0 - -- - id: 14 - repo_id: 55 - org_id: 0 - name: #7c7268 - color: '#7c7268' - exclusive: false - num_issues: 0 - num_closed_issues: 0 - -- - id: 15 - repo_id: 55 - org_id: 0 - name: #7e716c - color: '#7e716c' - exclusive: false - num_issues: 0 - num_closed_issues: 0 - -- - id: 16 - repo_id: 55 - org_id: 0 - name: #81706d - color: '#81706d' - exclusive: false - num_issues: 0 - num_closed_issues: 0 - -- - id: 17 - repo_id: 55 - org_id: 0 - name: #807070 - color: '#807070' - exclusive: false - num_issues: 0 - num_closed_issues: 0 - -- - id: 18 - repo_id: 55 - org_id: 0 - name: bug - color: '#D73A4A' - exclusive: false - num_issues: 0 - num_closed_issues: 0 - -- - id: 19 - repo_id: 55 - org_id: 0 - name: documentation - color: '#0075CA' - exclusive: false - num_issues: 0 - num_closed_issues: 0 - -- - id: 20 - repo_id: 55 - org_id: 0 - name: duplicate - color: '#CFD3D7' - exclusive: false - num_issues: 0 - num_closed_issues: 0 - -- - id: 21 - repo_id: 55 - org_id: 0 - name: enhancement - color: '#A2EEEF' - exclusive: false - num_issues: 0 - num_closed_issues: 0 diff --git a/models/issues/label.go b/models/issues/label.go index 50c8409aecf47..1b13c460000c8 100644 --- a/models/issues/label.go +++ b/models/issues/label.go @@ -178,9 +178,7 @@ func (l *Label) UseLightTextColor() bool { if r, g, b, err := l.ColorRGB(); err == nil { // Reference from: https://firsching.ch/github_labels.html and https://www.w3.org/WAI/GL/wiki/Relative_luminance // In the future WCAG 3 APCA may be a better solution - luminance := (0.2126*r + 0.7152*g + 0.0722*b) / 255 - lightnessThreshold := 0.453 - return luminance < lightnessThreshold + return util.IsUseLightColor(r, g, b) } } diff --git a/models/issues/label_test.go b/models/issues/label_test.go index 75da0b86b3367..f931e7b63e0a9 100644 --- a/models/issues/label_test.go +++ b/models/issues/label_test.go @@ -24,42 +24,13 @@ func TestLabel_CalOpenIssues(t *testing.T) { func TestLabel_TextColor(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) + label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 1}) - label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 10}) - assert.True(t, label.UseLightTextColor()) - - label = unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 11}) - assert.True(t, label.UseLightTextColor()) - - label = unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 12}) - assert.False(t, label.UseLightTextColor()) - - label = unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 13}) assert.False(t, label.UseLightTextColor()) - label = unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 14}) - assert.True(t, label.UseLightTextColor()) - - label = unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 15}) - assert.True(t, label.UseLightTextColor()) - - label = unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 16}) - assert.True(t, label.UseLightTextColor()) - - label = unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 17}) - assert.True(t, label.UseLightTextColor()) - - label = unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 18}) - assert.True(t, label.UseLightTextColor()) + label = unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 2}) - label = unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 19}) assert.True(t, label.UseLightTextColor()) - - label = unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 20}) - assert.False(t, label.UseLightTextColor()) - - label = unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 21}) - assert.False(t, label.UseLightTextColor()) } func TestLabel_ExclusiveScope(t *testing.T) { diff --git a/modules/templates/util_render.go b/modules/templates/util_render.go index 5ef85d8485e4a..966a1b187ec02 100644 --- a/modules/templates/util_render.go +++ b/modules/templates/util_render.go @@ -20,6 +20,7 @@ import ( "code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" ) // RenderCommitMessage renders commit message with XSS-safe and special links. @@ -155,7 +156,7 @@ func RenderLabel(ctx context.Context, label *issues_model.Label) template.HTML { if r, g, b, err := label.ColorRGB(); err == nil { // Make scope and item background colors slightly darker and lighter respectively. // More contrast needed with higher luminance, empirically tweaked. - luminance := (0.2126*r + 0.7152*g + 0.0722*b) / 255 + luminance := util.GetLuminance(r, g, b) contrast := 0.01 + luminance*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) diff --git a/modules/util/color.go b/modules/util/color.go new file mode 100644 index 0000000000000..fb26363886d17 --- /dev/null +++ b/modules/util/color.go @@ -0,0 +1,18 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package util + +// return luminance given RGB channels +func GetLuminance(r float64, g float64, b float64) float64 { + // Reference from: https://firsching.ch/github_labels.html and https://www.w3.org/WAI/GL/wiki/Relative_luminance + // In the future WCAG 3 APCA may be a better solution + luminance := (0.2126*r + 0.7152*g + 0.0722*b) / 255 + return luminance +} + +func IsUseLightColor(r float64, g float64, b float64) bool { + luminance := GetLuminance(r, g, b) + lightnessThreshold := 0.453 + return luminance < lightnessThreshold +} diff --git a/modules/util/color_test.go b/modules/util/color_test.go new file mode 100644 index 0000000000000..cf8482823e431 --- /dev/null +++ b/modules/util/color_test.go @@ -0,0 +1,40 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT +package util + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_IsUseLightColor(t *testing.T) { + cases := []struct { + r float64 + g float64 + b float64 + expected bool + }{ + {215, 58, 74, true}, + {0, 117, 202, true}, + {207, 211, 215, false}, + {162, 238, 239, false}, + {112, 87, 255, true}, + {0, 134, 114, true}, + {228, 230, 105, false}, + {216, 118, 227, false}, + {255, 255, 255, false}, + {43, 134, 133, true}, + {43, 135, 134, true}, + {44, 135, 134, false}, + {59, 182, 179, false}, + {124, 114, 104, true}, + {126, 113, 108, true}, + {129, 112, 109, true}, + {128, 112, 112, true}, + } + for n, c := range cases { + result := IsUseLightColor(c.r, c.g, c.b) + assert.Equal(t, c.expected, result, "case %d: error should match", n) + } +} From b6aab0c7bc4772cda41708dfb32644d028dda6d2 Mon Sep 17 00:00:00 2001 From: HesterG Date: Sat, 6 May 2023 12:07:35 +0800 Subject: [PATCH 09/32] save changes and add tests --- models/issues/label.go | 2 -- models/issues/label_test.go | 2 -- modules/util/color.go | 25 ++++++++++++++++++------- web_src/js/utils.js | 5 +++-- web_src/js/utils/color.js | 16 ++++++++++++++++ web_src/js/utils/color.test.js | 22 ++++++++++++++++++++++ 6 files changed, 59 insertions(+), 13 deletions(-) create mode 100644 web_src/js/utils/color.js create mode 100644 web_src/js/utils/color.test.js diff --git a/models/issues/label.go b/models/issues/label.go index 1b13c460000c8..c760c71292de1 100644 --- a/models/issues/label.go +++ b/models/issues/label.go @@ -176,8 +176,6 @@ func (l *Label) ColorRGB() (float64, float64, float64, error) { func (l *Label) UseLightTextColor() bool { if strings.HasPrefix(l.Color, "#") { if r, g, b, err := l.ColorRGB(); err == nil { - // Reference from: https://firsching.ch/github_labels.html and https://www.w3.org/WAI/GL/wiki/Relative_luminance - // In the future WCAG 3 APCA may be a better solution return util.IsUseLightColor(r, g, b) } } diff --git a/models/issues/label_test.go b/models/issues/label_test.go index f931e7b63e0a9..1f6ce4f42ee78 100644 --- a/models/issues/label_test.go +++ b/models/issues/label_test.go @@ -25,11 +25,9 @@ func TestLabel_CalOpenIssues(t *testing.T) { func TestLabel_TextColor(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 1}) - assert.False(t, label.UseLightTextColor()) label = unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 2}) - assert.True(t, label.UseLightTextColor()) } diff --git a/modules/util/color.go b/modules/util/color.go index fb26363886d17..b56d276bcf432 100644 --- a/modules/util/color.go +++ b/modules/util/color.go @@ -1,18 +1,29 @@ // Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT - package util +import "math" + +func getRGB(channel float64) float64 { + result := channel / 255 + if result <= 0.03928 { + return result / 12.92 * 255 + } + return math.Pow((result+0.055)/1.055, 2.4) * 255 +} + // return luminance given RGB channels -func GetLuminance(r float64, g float64, b float64) float64 { +func GetLuminance(r, g, b float64) float64 { // Reference from: https://firsching.ch/github_labels.html and https://www.w3.org/WAI/GL/wiki/Relative_luminance // In the future WCAG 3 APCA may be a better solution - luminance := (0.2126*r + 0.7152*g + 0.0722*b) / 255 + R := getRGB(r) + G := getRGB(g) + B := getRGB(b) + luminance := (0.2126*R + 0.7152*G + 0.0722*B) / 255 + // luminance := (0.2126*r + 0.7152*g + 0.0722*b) / 255 return luminance } -func IsUseLightColor(r float64, g float64, b float64) bool { - luminance := GetLuminance(r, g, b) - lightnessThreshold := 0.453 - return luminance < lightnessThreshold +func IsUseLightColor(r, g, b float64) bool { + return GetLuminance(r, g, b) < 0.453 } diff --git a/web_src/js/utils.js b/web_src/js/utils.js index 75f9f22f8b25c..b93cd02e8aef8 100644 --- a/web_src/js/utils.js +++ b/web_src/js/utils.js @@ -1,3 +1,5 @@ +import {isUseLightColor} from './utils/color.js'; + // transform /path/to/file.ext to file.ext export function basename(path = '') { return path ? path.replace(/^.*\//, '') : ''; @@ -147,6 +149,5 @@ export function useLightTextOnBackground(backgroundColor) { const g = parseInt(backgroundColor.substring(2, 4), 16); const b = parseInt(backgroundColor.substring(4, 6), 16); - const brightness = (0.2126 * r + 0.7152 * g + 0.0722 * b) / 255; - return brightness < 0.453; + return isUseLightColor(r, g, b); } diff --git a/web_src/js/utils/color.js b/web_src/js/utils/color.js new file mode 100644 index 0000000000000..1475570ae5dc4 --- /dev/null +++ b/web_src/js/utils/color.js @@ -0,0 +1,16 @@ +function getLuminance(r, g, b) { + // Reference from: https://firsching.ch/github_labels.html and https://www.w3.org/WAI/GL/wiki/Relative_luminance + // In the future WCAG 3 APCA may be a better solution. + const luminance = (0.2126 * r + 0.7152 * g + 0.0722 * b) / 255; + return luminance; +} + +function getRGB(channel){ + const sRGB = channel / 255; + const res = (sRGB <= 0.03928) ? sRGB / 12.92 : ((sRGB + 0.055) / 1.055) ** 2.4; + return res; +} + +export function isUseLightColor(r, g, b) { + return getLuminance(r, g, b) < 0.453; +} diff --git a/web_src/js/utils/color.test.js b/web_src/js/utils/color.test.js new file mode 100644 index 0000000000000..e3b44602d6c7e --- /dev/null +++ b/web_src/js/utils/color.test.js @@ -0,0 +1,22 @@ +import {test, expect} from 'vitest'; +import {isUseLightColor} from './color.js'; + +test('isUseLightColor', () => { + expect(isUseLightColor(215, 58, 74)).toBe(true); + expect(isUseLightColor(0, 117, 202)).toBe(true); + expect(isUseLightColor(207, 211, 215)).toBe(false); + expect(isUseLightColor(162, 238, 239)).toBe(false); + expect(isUseLightColor(112, 87, 255)).toBe(true); + expect(isUseLightColor(0, 134, 114)).toBe(true); + expect(isUseLightColor(228, 230, 105)).toBe(false); + expect(isUseLightColor(216, 118, 227)).toBe(false); + expect(isUseLightColor(255, 255, 255)).toBe(false); + expect(isUseLightColor(43, 134, 133)).toBe(true); + expect(isUseLightColor(43, 135, 134)).toBe(true); + expect(isUseLightColor(44, 135, 134)).toBe(false); + expect(isUseLightColor(59, 182, 179)).toBe(false); + expect(isUseLightColor(124, 114, 104)).toBe(true); + expect(isUseLightColor(126, 113, 108)).toBe(true); + expect(isUseLightColor(129, 112, 109)).toBe(true); + expect(isUseLightColor(128, 112, 112)).toBe(true); +}); From d43728613d617987b2c7494f4817af2bdd6dd028 Mon Sep 17 00:00:00 2001 From: HesterG Date: Sat, 6 May 2023 15:48:21 +0800 Subject: [PATCH 10/32] save changes --- models/issues/label.go | 5 +---- modules/util/color.go | 20 ++++++++++++++------ web_src/js/utils.js | 14 +++++--------- web_src/js/utils/color.js | 14 +++++++++++--- 4 files changed, 31 insertions(+), 22 deletions(-) diff --git a/models/issues/label.go b/models/issues/label.go index c760c71292de1..a5db825f06886 100644 --- a/models/issues/label.go +++ b/models/issues/label.go @@ -166,10 +166,7 @@ func (l *Label) ColorRGB() (float64, float64, float64, error) { return 0, 0, 0, err } - r := float64(uint8(0xFF & (uint32(color) >> 16))) - g := float64(uint8(0xFF & (uint32(color) >> 8))) - b := float64(uint8(0xFF & uint32(color))) - return r, g, b, nil + return util.GetRBG(color) } // Determine if label text should be light or dark to be readable on background color diff --git a/modules/util/color.go b/modules/util/color.go index b56d276bcf432..72361502e5079 100644 --- a/modules/util/color.go +++ b/modules/util/color.go @@ -4,7 +4,7 @@ package util import "math" -func getRGB(channel float64) float64 { +func getLuminanceRGB(channel float64) float64 { result := channel / 255 if result <= 0.03928 { return result / 12.92 * 255 @@ -12,15 +12,23 @@ func getRGB(channel float64) float64 { return math.Pow((result+0.055)/1.055, 2.4) * 255 } +// Get rgb channel floats from the whole color int +func GetRBG(color uint64) (float64, float64, float64, error) { + r := float64(uint8(0xFF & (uint32(color) >> 16))) + g := float64(uint8(0xFF & (uint32(color) >> 8))) + b := float64(uint8(0xFF & uint32(color))) + return r, g, b, nil +} + // return luminance given RGB channels func GetLuminance(r, g, b float64) float64 { // Reference from: https://firsching.ch/github_labels.html and https://www.w3.org/WAI/GL/wiki/Relative_luminance // In the future WCAG 3 APCA may be a better solution - R := getRGB(r) - G := getRGB(g) - B := getRGB(b) - luminance := (0.2126*R + 0.7152*G + 0.0722*B) / 255 - // luminance := (0.2126*r + 0.7152*g + 0.0722*b) / 255 + // R := getLuminanceRGB(r) + // G := getLuminanceRGB(g) + // B := getLuminanceRGB(b) + // luminance := (0.2126*R + 0.7152*G + 0.0722*B) / 255 + luminance := (0.2126*r + 0.7152*g + 0.0722*b) / 255 return luminance } diff --git a/web_src/js/utils.js b/web_src/js/utils.js index b93cd02e8aef8..bb44919cc1f55 100644 --- a/web_src/js/utils.js +++ b/web_src/js/utils.js @@ -1,4 +1,4 @@ -import {isUseLightColor} from './utils/color.js'; +import {getRGB, isUseLightColor} from './utils/color.js'; // transform /path/to/file.ext to file.ext export function basename(path = '') { @@ -140,14 +140,10 @@ export function toAbsoluteUrl(url) { // determine if light or dark text color should be used on a given background color // NOTE: see models/issue_label.go for similar implementation export function useLightTextOnBackground(backgroundColor) { - if (backgroundColor[0] === '#') { - backgroundColor = backgroundColor.substring(1); + if (backgroundColor[0] !== '#') { + return false; } - // Reference from: https://firsching.ch/github_labels.html and https://www.w3.org/WAI/GL/wiki/Relative_luminance - // In the future WCAG 3 APCA may be a better solution. - const r = parseInt(backgroundColor.substring(0, 2), 16); - const g = parseInt(backgroundColor.substring(2, 4), 16); - const b = parseInt(backgroundColor.substring(4, 6), 16); - + backgroundColor = backgroundColor.substring(1); + const [r, g, b] = getRGB(backgroundColor); return isUseLightColor(r, g, b); } diff --git a/web_src/js/utils/color.js b/web_src/js/utils/color.js index 1475570ae5dc4..da7f676dd5675 100644 --- a/web_src/js/utils/color.js +++ b/web_src/js/utils/color.js @@ -1,16 +1,24 @@ +// Reference from: https://www.w3.org/WAI/GL/wiki/Relative_luminance and https://firsching.ch/github_labels.html +// In the future WCAG 3 APCA may be a better solution. function getLuminance(r, g, b) { - // Reference from: https://firsching.ch/github_labels.html and https://www.w3.org/WAI/GL/wiki/Relative_luminance - // In the future WCAG 3 APCA may be a better solution. const luminance = (0.2126 * r + 0.7152 * g + 0.0722 * b) / 255; return luminance; } -function getRGB(channel){ +function _getLuminanceRGB(channel) { const sRGB = channel / 255; const res = (sRGB <= 0.03928) ? sRGB / 12.92 : ((sRGB + 0.055) / 1.055) ** 2.4; return res; } +// Get rgb channel integers from color string +export function getRGB(backgroundColor) { + const r = parseInt(backgroundColor.substring(0, 2), 16); + const g = parseInt(backgroundColor.substring(2, 4), 16); + const b = parseInt(backgroundColor.substring(4, 6), 16); + return [r, g, b]; +} + export function isUseLightColor(r, g, b) { return getLuminance(r, g, b) < 0.453; } From 21fa83b395e22b51f14dbb44f2baab6328f0a8ba Mon Sep 17 00:00:00 2001 From: HesterG Date: Mon, 8 May 2023 11:13:24 +0800 Subject: [PATCH 11/32] modify tests --- models/issues/label.go | 12 +---------- modules/templates/util_render.go | 2 +- modules/util/color.go | 37 ++++++++++++++++++++------------ modules/util/color_test.go | 32 ++++++++++++++++++++++++--- web_src/js/utils.js | 8 ++----- web_src/js/utils/color.js | 30 +++++++++++++++++--------- web_src/js/utils/color.test.js | 21 ++++++++++++++---- 7 files changed, 93 insertions(+), 49 deletions(-) diff --git a/models/issues/label.go b/models/issues/label.go index a5db825f06886..43bac0d6b5dc5 100644 --- a/models/issues/label.go +++ b/models/issues/label.go @@ -159,20 +159,10 @@ func (l *Label) BelongsToRepo() bool { return l.RepoID > 0 } -// Get color as RGB values in 0..255 range -func (l *Label) ColorRGB() (float64, float64, float64, error) { - color, err := strconv.ParseUint(l.Color[1:], 16, 64) - if err != nil { - return 0, 0, 0, err - } - - return util.GetRBG(color) -} - // Determine if label text should be light or dark to be readable on background color func (l *Label) UseLightTextColor() bool { if strings.HasPrefix(l.Color, "#") { - if r, g, b, err := l.ColorRGB(); err == nil { + if r, g, b, err := util.GetRBGColor(l.Color); err == nil { return util.IsUseLightColor(r, g, b) } } diff --git a/modules/templates/util_render.go b/modules/templates/util_render.go index 966a1b187ec02..40c48378caf04 100644 --- a/modules/templates/util_render.go +++ b/modules/templates/util_render.go @@ -153,7 +153,7 @@ func RenderLabel(ctx context.Context, label *issues_model.Label) template.HTML { itemColor := label.Color scopeColor := label.Color - if r, g, b, err := label.ColorRGB(); err == nil { + if r, g, b, err := util.GetRBGColor(label.Color); err == nil { // Make scope and item background colors slightly darker and lighter respectively. // More contrast needed with higher luminance, empirically tweaked. luminance := util.GetLuminance(r, g, b) diff --git a/modules/util/color.go b/modules/util/color.go index 72361502e5079..650d50ad5d775 100644 --- a/modules/util/color.go +++ b/modules/util/color.go @@ -2,18 +2,26 @@ // SPDX-License-Identifier: MIT package util -import "math" +import ( + "math" + "strconv" +) +// Return R, G, B values defined in reletive luminance func getLuminanceRGB(channel float64) float64 { - result := channel / 255 - if result <= 0.03928 { - return result / 12.92 * 255 + sRGB := channel / 255 + if sRGB <= 0.03928 { + return sRGB / 12.92 } - return math.Pow((result+0.055)/1.055, 2.4) * 255 + return math.Pow((sRGB+0.055)/1.055, 2.4) } -// Get rgb channel floats from the whole color int -func GetRBG(color uint64) (float64, float64, float64, error) { +// Get color as RGB values in 0..255 range from the hex color string +func GetRBGColor(colorString string) (float64, float64, float64, error) { + color, err := strconv.ParseUint(colorString[1:], 16, 64) + if err != nil { + return 0, 0, 0, err + } r := float64(uint8(0xFF & (uint32(color) >> 16))) g := float64(uint8(0xFF & (uint32(color) >> 8))) b := float64(uint8(0xFF & uint32(color))) @@ -21,17 +29,18 @@ func GetRBG(color uint64) (float64, float64, float64, error) { } // return luminance given RGB channels +// Reference from: https://www.w3.org/WAI/GL/wiki/Relative_luminance func GetLuminance(r, g, b float64) float64 { - // Reference from: https://firsching.ch/github_labels.html and https://www.w3.org/WAI/GL/wiki/Relative_luminance - // In the future WCAG 3 APCA may be a better solution - // R := getLuminanceRGB(r) - // G := getLuminanceRGB(g) - // B := getLuminanceRGB(b) - // luminance := (0.2126*R + 0.7152*G + 0.0722*B) / 255 - luminance := (0.2126*r + 0.7152*g + 0.0722*b) / 255 + R := getLuminanceRGB(r) + G := getLuminanceRGB(g) + B := getLuminanceRGB(b) + luminance := 0.2126*R + 0.7152*G + 0.0722*B return luminance } +// Reference from: https://firsching.ch/github_labels.html +// In the future WCAG 3 APCA may be a better solution. +// Check if text should use light color based on RGB of background func IsUseLightColor(r, g, b float64) bool { return GetLuminance(r, g, b) < 0.453 } diff --git a/modules/util/color_test.go b/modules/util/color_test.go index cf8482823e431..49fd135eb5259 100644 --- a/modules/util/color_test.go +++ b/modules/util/color_test.go @@ -8,6 +8,32 @@ import ( "github.com/stretchr/testify/assert" ) +func Test_GetRBGColor(t *testing.T) { + cases := []struct { + colorString string + expectedR float64 + expectedG float64 + expectedB float64 + }{ + {"#2b8685", 43, 134, 133}, + {"#2b8786", 43, 135, 134}, + {"#2c8786", 44, 135, 134}, + {"#3bb6b3", 59, 182, 179}, + {"#7c7268", 124, 114, 104}, + {"#7e716c", 126, 113, 108}, + {"#807070", 128, 112, 112}, + {"#81706d", 129, 112, 109}, + {"#d73a4a", 215, 58, 74}, + {"#0075ca", 0, 117, 202}, + } + for n, c := range cases { + r, g, b, _ := GetRBGColor(c.colorString) + assert.Equal(t, c.expectedR, r, "case %d: error R should match: expected %f, but get %f", n, c.expectedR, r) + assert.Equal(t, c.expectedG, g, "case %d: error G should match: expected %f, but get %f", n, c.expectedG, g) + assert.Equal(t, c.expectedB, b, "case %d: error B should match: expected %f, but get %f", n, c.expectedB, b) + } +} + func Test_IsUseLightColor(t *testing.T) { cases := []struct { r float64 @@ -22,12 +48,12 @@ func Test_IsUseLightColor(t *testing.T) { {112, 87, 255, true}, {0, 134, 114, true}, {228, 230, 105, false}, - {216, 118, 227, false}, + {216, 118, 227, true}, {255, 255, 255, false}, {43, 134, 133, true}, {43, 135, 134, true}, - {44, 135, 134, false}, - {59, 182, 179, false}, + {44, 135, 134, true}, + {59, 182, 179, true}, {124, 114, 104, true}, {126, 113, 108, true}, {129, 112, 109, true}, diff --git a/web_src/js/utils.js b/web_src/js/utils.js index bb44919cc1f55..6e7cab56499ac 100644 --- a/web_src/js/utils.js +++ b/web_src/js/utils.js @@ -1,4 +1,4 @@ -import {getRGB, isUseLightColor} from './utils/color.js'; +import {getRGBColor, isUseLightColor} from './utils/color.js'; // transform /path/to/file.ext to file.ext export function basename(path = '') { @@ -140,10 +140,6 @@ export function toAbsoluteUrl(url) { // determine if light or dark text color should be used on a given background color // NOTE: see models/issue_label.go for similar implementation export function useLightTextOnBackground(backgroundColor) { - if (backgroundColor[0] !== '#') { - return false; - } - backgroundColor = backgroundColor.substring(1); - const [r, g, b] = getRGB(backgroundColor); + const [r, g, b] = getRGBColor(backgroundColor); return isUseLightColor(r, g, b); } diff --git a/web_src/js/utils/color.js b/web_src/js/utils/color.js index da7f676dd5675..70715a4d51b77 100644 --- a/web_src/js/utils/color.js +++ b/web_src/js/utils/color.js @@ -1,24 +1,34 @@ -// Reference from: https://www.w3.org/WAI/GL/wiki/Relative_luminance and https://firsching.ch/github_labels.html -// In the future WCAG 3 APCA may be a better solution. -function getLuminance(r, g, b) { - const luminance = (0.2126 * r + 0.7152 * g + 0.0722 * b) / 255; - return luminance; -} - -function _getLuminanceRGB(channel) { +// Return R, G, B values defined in reletive luminance +function getLuminanceRGB(channel) { const sRGB = channel / 255; const res = (sRGB <= 0.03928) ? sRGB / 12.92 : ((sRGB + 0.055) / 1.055) ** 2.4; return res; } -// Get rgb channel integers from color string -export function getRGB(backgroundColor) { +// Reference from: https://www.w3.org/WAI/GL/wiki/Relative_luminance +function getLuminance(r, g, b) { + const R = getLuminanceRGB(r); + const G = getLuminanceRGB(g); + const B = getLuminanceRGB(b); + const luminance = 0.2126 * R + 0.7152 * G + 0.0722 * B; + return luminance; +} + +// Get color as RGB values in 0..255 range from the hex color string (with or without #) +export function getRGBColor(backgroundColorStr) { + let backgroundColor = backgroundColorStr; + if (backgroundColorStr[0] === '#') { + backgroundColor = backgroundColorStr.substring(1); + } const r = parseInt(backgroundColor.substring(0, 2), 16); const g = parseInt(backgroundColor.substring(2, 4), 16); const b = parseInt(backgroundColor.substring(4, 6), 16); return [r, g, b]; } +// Reference from: https://firsching.ch/github_labels.html +// In the future WCAG 3 APCA may be a better solution. +// Check if text should use light color based on RGB of background export function isUseLightColor(r, g, b) { return getLuminance(r, g, b) < 0.453; } diff --git a/web_src/js/utils/color.test.js b/web_src/js/utils/color.test.js index e3b44602d6c7e..d6759177d0586 100644 --- a/web_src/js/utils/color.test.js +++ b/web_src/js/utils/color.test.js @@ -1,5 +1,18 @@ import {test, expect} from 'vitest'; -import {isUseLightColor} from './color.js'; +import {getRGBColor, isUseLightColor} from './color.js'; + +test('getRGBColor', () => { + expect(getRGBColor('2b8685')).toEqual([43, 134, 133]); + expect(getRGBColor('2b8786')).toEqual([43, 135, 134]); + expect(getRGBColor('2c8786')).toEqual([44, 135, 134]); + expect(getRGBColor('3bb6b3')).toEqual([59, 182, 179]); + expect(getRGBColor('7c7268')).toEqual([124, 114, 104]); + expect(getRGBColor('#7e716c')).toEqual([126, 113, 108]); + expect(getRGBColor('#807070')).toEqual([128, 112, 112]); + expect(getRGBColor('#81706d')).toEqual([129, 112, 109]); + expect(getRGBColor('#d73a4a')).toEqual([215, 58, 74]); + expect(getRGBColor('#0075ca')).toEqual([0, 117, 202]); +}); test('isUseLightColor', () => { expect(isUseLightColor(215, 58, 74)).toBe(true); @@ -9,12 +22,12 @@ test('isUseLightColor', () => { expect(isUseLightColor(112, 87, 255)).toBe(true); expect(isUseLightColor(0, 134, 114)).toBe(true); expect(isUseLightColor(228, 230, 105)).toBe(false); - expect(isUseLightColor(216, 118, 227)).toBe(false); + expect(isUseLightColor(216, 118, 227)).toBe(true); expect(isUseLightColor(255, 255, 255)).toBe(false); expect(isUseLightColor(43, 134, 133)).toBe(true); expect(isUseLightColor(43, 135, 134)).toBe(true); - expect(isUseLightColor(44, 135, 134)).toBe(false); - expect(isUseLightColor(59, 182, 179)).toBe(false); + expect(isUseLightColor(44, 135, 134)).toBe(true); + expect(isUseLightColor(59, 182, 179)).toBe(true); expect(isUseLightColor(124, 114, 104)).toBe(true); expect(isUseLightColor(126, 113, 108)).toBe(true); expect(isUseLightColor(129, 112, 109)).toBe(true); From 61e7d42f24fad3af91c2a3d5712b4d50d79f3a8d Mon Sep 17 00:00:00 2001 From: HesterG Date: Mon, 8 May 2023 11:45:07 +0800 Subject: [PATCH 12/32] rename function --- web_src/js/utils.js | 4 ++-- web_src/js/utils/color.js | 2 +- web_src/js/utils/color.test.js | 24 ++++++++++++------------ 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/web_src/js/utils.js b/web_src/js/utils.js index 6e7cab56499ac..a0cadd491582c 100644 --- a/web_src/js/utils.js +++ b/web_src/js/utils.js @@ -1,4 +1,4 @@ -import {getRGBColor, isUseLightColor} from './utils/color.js'; +import {getRGBColorFromHex, isUseLightColor} from './utils/color.js'; // transform /path/to/file.ext to file.ext export function basename(path = '') { @@ -140,6 +140,6 @@ export function toAbsoluteUrl(url) { // determine if light or dark text color should be used on a given background color // NOTE: see models/issue_label.go for similar implementation export function useLightTextOnBackground(backgroundColor) { - const [r, g, b] = getRGBColor(backgroundColor); + const [r, g, b] = getRGBColorFromHex(backgroundColor); return isUseLightColor(r, g, b); } diff --git a/web_src/js/utils/color.js b/web_src/js/utils/color.js index 70715a4d51b77..2005e8a5df75a 100644 --- a/web_src/js/utils/color.js +++ b/web_src/js/utils/color.js @@ -15,7 +15,7 @@ function getLuminance(r, g, b) { } // Get color as RGB values in 0..255 range from the hex color string (with or without #) -export function getRGBColor(backgroundColorStr) { +export function getRGBColorFromHex(backgroundColorStr) { let backgroundColor = backgroundColorStr; if (backgroundColorStr[0] === '#') { backgroundColor = backgroundColorStr.substring(1); diff --git a/web_src/js/utils/color.test.js b/web_src/js/utils/color.test.js index d6759177d0586..6dd2061a7f51c 100644 --- a/web_src/js/utils/color.test.js +++ b/web_src/js/utils/color.test.js @@ -1,17 +1,17 @@ import {test, expect} from 'vitest'; -import {getRGBColor, isUseLightColor} from './color.js'; +import {getRGBColorFromHex, isUseLightColor} from './color.js'; -test('getRGBColor', () => { - expect(getRGBColor('2b8685')).toEqual([43, 134, 133]); - expect(getRGBColor('2b8786')).toEqual([43, 135, 134]); - expect(getRGBColor('2c8786')).toEqual([44, 135, 134]); - expect(getRGBColor('3bb6b3')).toEqual([59, 182, 179]); - expect(getRGBColor('7c7268')).toEqual([124, 114, 104]); - expect(getRGBColor('#7e716c')).toEqual([126, 113, 108]); - expect(getRGBColor('#807070')).toEqual([128, 112, 112]); - expect(getRGBColor('#81706d')).toEqual([129, 112, 109]); - expect(getRGBColor('#d73a4a')).toEqual([215, 58, 74]); - expect(getRGBColor('#0075ca')).toEqual([0, 117, 202]); +test('getRGBColorFromHex', () => { + expect(getRGBColorFromHex('2b8685')).toEqual([43, 134, 133]); + expect(getRGBColorFromHex('2b8786')).toEqual([43, 135, 134]); + expect(getRGBColorFromHex('2c8786')).toEqual([44, 135, 134]); + expect(getRGBColorFromHex('3bb6b3')).toEqual([59, 182, 179]); + expect(getRGBColorFromHex('7c7268')).toEqual([124, 114, 104]); + expect(getRGBColorFromHex('#7e716c')).toEqual([126, 113, 108]); + expect(getRGBColorFromHex('#807070')).toEqual([128, 112, 112]); + expect(getRGBColorFromHex('#81706d')).toEqual([129, 112, 109]); + expect(getRGBColorFromHex('#d73a4a')).toEqual([215, 58, 74]); + expect(getRGBColorFromHex('#0075ca')).toEqual([0, 117, 202]); }); test('isUseLightColor', () => { From 51f541979abf87d45e44bb93a2d3dd3c3b2f394d Mon Sep 17 00:00:00 2001 From: HesterG Date: Mon, 8 May 2023 12:02:15 +0800 Subject: [PATCH 13/32] adjust functions --- web_src/js/components/ContextPopup.vue | 5 ++-- web_src/js/features/repo-projects.js | 5 ++-- web_src/js/utils.js | 8 ------ web_src/js/utils/color.js | 2 +- web_src/js/utils/color.test.js | 38 +++++++++++++------------- 5 files changed, 26 insertions(+), 32 deletions(-) diff --git a/web_src/js/components/ContextPopup.vue b/web_src/js/components/ContextPopup.vue index 98f9db51f91dc..1bdbee5c9be12 100644 --- a/web_src/js/components/ContextPopup.vue +++ b/web_src/js/components/ContextPopup.vue @@ -26,7 +26,7 @@