1
1
mirror of https://github.com/walles/moar.git synced 2024-11-22 11:45:50 +03:00

Test looping over all styles' background colors

This commit is contained in:
Johan Walles 2023-12-20 16:15:29 +01:00
parent d13e9d4994
commit 22885dc4b0
5 changed files with 88 additions and 27 deletions

View File

@ -55,11 +55,7 @@ func twinStyleFromChroma(chromaStyle *chroma.Style, chromaFormatter *chroma.Form
return &cells[0].Style
}
func backgroundStyleFromChroma(chromaStyle *chroma.Style) *twin.Style {
if chromaStyle == nil {
return nil
}
func backgroundStyleFromChroma(chromaStyle chroma.Style) twin.Style {
backgroundEntry := chromaStyle.Get(chroma.Background)
if !backgroundEntry.Background.IsSet() {
@ -92,7 +88,7 @@ func backgroundStyleFromChroma(chromaStyle *chroma.Style) *twin.Style {
returnMe = returnMe.WithAttr(twin.AttrUnderline)
}
return &returnMe
return returnMe
}
// consumeLessTermcapEnvs parses LESS_TERMCAP_xx environment variables and
@ -128,9 +124,8 @@ func styleUi(chromaStyle *chroma.Style, chromaFormatter *chroma.Formatter, statu
if standoutStyle != nil {
statusbarStyle = *standoutStyle
} else if statusbarOption == STATUSBAR_STYLE_INVERSE {
styleBackground := backgroundStyleFromChroma(chromaStyle)
if styleBackground != nil {
statusbarStyle = styleBackground.WithAttr(twin.AttrReverse)
if chromaStyle != nil {
statusbarStyle = backgroundStyleFromChroma(*chromaStyle).WithAttr(twin.AttrReverse)
} else {
statusbarStyle = twin.StyleDefault.WithAttr(twin.AttrReverse)
}

View File

@ -4,15 +4,30 @@ import (
"testing"
"github.com/alecthomas/chroma/v2/styles"
"github.com/walles/moar/twin"
"gotest.tools/v3/assert"
)
func TestBackgroundStyleFromChromaGithub(t *testing.T) {
style := backgroundStyleFromChroma(styles.Get("github"))
assert.Check(t, style != nil)
style := backgroundStyleFromChroma(*styles.Get("github"))
assert.Equal(t, style.String(), "Default color on #ffffff")
}
// FIXME: Add a parameterized test looping over all styles and checking that the
// contrast we get in backgroundStyleFromChroma is good enough.
// Test looping over all styles and checking that the contrast we get in
// backgroundStyleFromChroma is good enough.
func TestBackgroundStyleContrast(t *testing.T) {
for _, style := range styles.Registry {
t.Run(style.Name, func(t *testing.T) {
backgroundStyle := backgroundStyleFromChroma(*style)
assert.Check(t, backgroundStyle.Background().ColorType() == twin.ColorType24bit)
if backgroundStyle.Foreground().ColorType() == twin.ColorTypeDefault {
return
}
distance := backgroundStyle.Background().Distance(backgroundStyle.Foreground())
assert.Check(t, distance > 0.5, "distance=%f", distance)
})
}
}

View File

@ -4,6 +4,7 @@ import (
"fmt"
"math"
"github.com/alecthomas/chroma/v2"
"github.com/lucasb-eyer/go-colorful"
)
@ -14,7 +15,7 @@ type ColorType uint8
const (
// Default foreground / background color
colorTypeDefault ColorType = iota
ColorTypeDefault ColorType = iota
// https://en.wikipedia.org/wiki/ANSI_escape_code#3-bit_and_4-bit
//
@ -33,7 +34,7 @@ const (
)
// Reset to default foreground / background color
var ColorDefault = newColor(colorTypeDefault, 0)
var ColorDefault = newColor(ColorTypeDefault, 0)
// From: https://en.wikipedia.org/wiki/ANSI_escape_code#3-bit_and_4-bit
var colorNames16 = map[int]string{
@ -77,7 +78,7 @@ func NewColorHex(rgb uint32) Color {
return newColor(ColorType24bit, rgb)
}
func (color Color) colorType() ColorType {
func (color Color) ColorType() ColorType {
return ColorType(color >> 24)
}
@ -94,13 +95,13 @@ func (color Color) ansiString(foreground bool, terminalColorCount ColorType) str
fgBgMarker = "4"
}
if color.colorType() == colorTypeDefault {
if color.ColorType() == ColorTypeDefault {
return fmt.Sprint("\x1b[", fgBgMarker, "9m")
}
color = color.downsampleTo(terminalColorCount)
if color.colorType() == ColorType16 {
if color.ColorType() == ColorType16 {
value := color.colorValue()
if value < 8 {
return fmt.Sprint("\x1b[", fgBgMarker, value, "m")
@ -113,14 +114,14 @@ func (color Color) ansiString(foreground bool, terminalColorCount ColorType) str
}
}
if color.colorType() == ColorType256 {
if color.ColorType() == ColorType256 {
value := color.colorValue()
if value <= 255 {
return fmt.Sprint("\x1b[", fgBgMarker, "8;5;", value, "m")
}
}
if color.colorType() == ColorType24bit {
if color.ColorType() == ColorType24bit {
value := color.colorValue()
red := (value & 0xff0000) >> 16
green := (value & 0xff00) >> 8
@ -129,7 +130,7 @@ func (color Color) ansiString(foreground bool, terminalColorCount ColorType) str
return fmt.Sprint("\x1b[", fgBgMarker, "8;2;", red, ";", green, ";", blue, "m")
}
panic(fmt.Errorf("unhandled color type=%d %s", color.colorType(), color.String()))
panic(fmt.Errorf("unhandled color type=%d %s", color.ColorType(), color.String()))
}
func (color Color) ForegroundAnsiString(terminalColorCount ColorType) string {
@ -143,8 +144,8 @@ func (color Color) BackgroundAnsiString(terminalColorCount ColorType) string {
}
func (color Color) String() string {
switch color.colorType() {
case colorTypeDefault:
switch color.ColorType() {
case ColorTypeDefault:
return "Default color"
case ColorType16:
@ -160,15 +161,15 @@ func (color Color) String() string {
return fmt.Sprintf("#%06x", color.colorValue())
}
panic(fmt.Errorf("unhandled color type %d", color.colorType()))
panic(fmt.Errorf("unhandled color type %d", color.ColorType()))
}
func (color Color) downsampleTo(terminalColorCount ColorType) Color {
if color.colorType() == colorTypeDefault || terminalColorCount == colorTypeDefault {
if color.ColorType() == ColorTypeDefault || terminalColorCount == ColorTypeDefault {
panic(fmt.Errorf("downsampling to or from default color not supported, %s -> %#v", color.String(), terminalColorCount))
}
if color.colorType() <= terminalColorCount {
if color.ColorType() <= terminalColorCount {
// Already low enough
return color
}
@ -177,7 +178,7 @@ func (color Color) downsampleTo(terminalColorCount ColorType) Color {
var targetR float64
var targetG float64
var targetB float64
if color.colorType() == ColorType24bit {
if color.ColorType() == ColorType24bit {
targetR = float64(color.colorValue()>>16) / 255.0
targetG = float64(color.colorValue()>>8&0xff) / 255.0
targetB = float64(color.colorValue()&0xff) / 255.0
@ -227,3 +228,31 @@ func (color Color) downsampleTo(terminalColorCount ColorType) Color {
return NewColor256(uint8(bestMatch))
}
}
// Wrapper for Chroma's color distance function.
//
// That one says it uses this formula: https://www.compuphase.com/cmetric.htm
//
// The result from this function has been scaled to 0.0-1.0, where 1.0 is the
// distance between black and white.
func (c Color) Distance(other Color) float64 {
if c.ColorType() != ColorType24bit {
panic(fmt.Errorf("contrast only supported for 24 bit colors, got %s vs %s", c.String(), other.String()))
}
baseColor := chroma.NewColour(
uint8(c.colorValue()>>16&0xff),
uint8(c.colorValue()>>8&0xff),
uint8(c.colorValue()&0xff),
)
otherColor := chroma.NewColour(
uint8(other.colorValue()>>16&0xff),
uint8(other.colorValue()>>8&0xff),
uint8(other.colorValue()&0xff),
)
// Magic constant comes from testing
maxDistance := 764.8333151739665
return baseColor.Distance(otherColor) / maxDistance
}

View File

@ -40,3 +40,17 @@ func TestAnsiStringDefault(t *testing.T) {
"\x1b[39m",
)
}
func TestDistance(t *testing.T) {
// Black -> white
assert.Equal(t,
NewColor24Bit(0, 0, 0).Distance(NewColor24Bit(255, 255, 255)),
1.0,
)
// White -> black
assert.Equal(t,
NewColor24Bit(255, 255, 255).Distance(NewColor24Bit(0, 0, 0)),
1.0,
)
}

View File

@ -117,6 +117,14 @@ func (attr AttrMask) has(attrs AttrMask) bool {
return attr&attrs != 0
}
func (style Style) Background() Color {
return style.bg
}
func (style Style) Foreground() Color {
return style.fg
}
func (style Style) WithBackground(color Color) Style {
return Style{
fg: style.fg,