This adds a new dependency, `colorful`. Hope it's worth it! All our projects that use this lib need gradients as well. We could perhaps embark our own HSL-space interpolator, but… I gotta go.main v0.3.0
parent
a5e3ef1a98
commit
cb99a2290d
@ -0,0 +1,212 @@
|
|||||||
|
package judgment
|
||||||
|
|
||||||
|
// Some code below is taken directly from colorful's doc examples.
|
||||||
|
// https://github.com/lucasb-eyer/go-colorful/blob/master/doc/gradientgen/gradientgen.go
|
||||||
|
|
||||||
|
// May be useful later:
|
||||||
|
// c, err := colorful.Hex(s)
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/lucasb-eyer/go-colorful"
|
||||||
|
"image/color"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CreateDefaultPalette returns a Palette of amountOfColors colors.
|
||||||
|
// 7 colors we use, red to green:
|
||||||
|
// "#df3222", "#ed6f01", "#fab001", "#c5d300", "#7bbd3e", "#00a249", "#017a36"
|
||||||
|
// When requiring more than 7, we interpolate in HSV space.
|
||||||
|
// This tries to be fault-tolerant, and returns an empty palette upon trouble.
|
||||||
|
func CreateDefaultPalette(amountOfColors int) color.Palette {
|
||||||
|
const Color0 = 0xdf3222
|
||||||
|
const Color1 = 0xed6f01
|
||||||
|
const Color2 = 0xfab001
|
||||||
|
const Color3 = 0xc5d300
|
||||||
|
const Color4 = 0x7bbd3e
|
||||||
|
const Color5 = 0x00a249
|
||||||
|
const Color6 = 0x017a36
|
||||||
|
|
||||||
|
color0 := hexToRGB(Color0)
|
||||||
|
color1 := hexToRGB(Color1)
|
||||||
|
color2 := hexToRGB(Color2)
|
||||||
|
color3 := hexToRGB(Color3)
|
||||||
|
color4 := hexToRGB(Color4)
|
||||||
|
color5 := hexToRGB(Color5)
|
||||||
|
color6 := hexToRGB(Color6)
|
||||||
|
|
||||||
|
if amountOfColors < 0 {
|
||||||
|
amountOfColors = amountOfColors * -1
|
||||||
|
}
|
||||||
|
|
||||||
|
switch amountOfColors {
|
||||||
|
case 0:
|
||||||
|
return []color.Color{}
|
||||||
|
case 1:
|
||||||
|
return []color.Color{
|
||||||
|
color5,
|
||||||
|
}
|
||||||
|
case 2:
|
||||||
|
return []color.Color{
|
||||||
|
color0,
|
||||||
|
color5,
|
||||||
|
}
|
||||||
|
case 3:
|
||||||
|
return []color.Color{
|
||||||
|
color0,
|
||||||
|
color2,
|
||||||
|
color5,
|
||||||
|
}
|
||||||
|
case 4:
|
||||||
|
return []color.Color{
|
||||||
|
color0,
|
||||||
|
color2,
|
||||||
|
color4,
|
||||||
|
color6,
|
||||||
|
}
|
||||||
|
case 5:
|
||||||
|
return []color.Color{
|
||||||
|
color0,
|
||||||
|
color1,
|
||||||
|
color2,
|
||||||
|
color4,
|
||||||
|
color5,
|
||||||
|
}
|
||||||
|
case 6:
|
||||||
|
return []color.Color{
|
||||||
|
color0,
|
||||||
|
color1,
|
||||||
|
color2,
|
||||||
|
color4,
|
||||||
|
color5,
|
||||||
|
color6,
|
||||||
|
}
|
||||||
|
case 7:
|
||||||
|
return []color.Color{
|
||||||
|
color0,
|
||||||
|
color1,
|
||||||
|
color2,
|
||||||
|
color3,
|
||||||
|
color4,
|
||||||
|
color5,
|
||||||
|
color6,
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
palette, err := bakePalette(amountOfColors, []color.Color{
|
||||||
|
color0,
|
||||||
|
color1,
|
||||||
|
color2,
|
||||||
|
color3,
|
||||||
|
color4,
|
||||||
|
color5,
|
||||||
|
color6,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
//panic("CreateDefaultPalette: failed to bake: "+err.Error())
|
||||||
|
return []color.Color{}
|
||||||
|
}
|
||||||
|
return palette
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DumpPaletteHexString dumps the provided palette as a string
|
||||||
|
// Looks like: "#df3222", "#ed6f01", "#fab001", "#c5d300", "#7bbd3e", "#00a249", "#017a36"
|
||||||
|
func DumpPaletteHexString(palette color.Palette, separator string, quote string) string {
|
||||||
|
out := ""
|
||||||
|
|
||||||
|
for colorIndex, colorRgba := range palette {
|
||||||
|
if colorIndex > 0 {
|
||||||
|
out += separator
|
||||||
|
}
|
||||||
|
out += quote
|
||||||
|
out += DumpColorHexString(colorRgba, "#", false)
|
||||||
|
out += quote
|
||||||
|
}
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DumpColorHexString outputs strings like #ff3399 or #ff3399ff with alpha
|
||||||
|
// Be mindful that PRECISION IS LOST because hex format has less bits
|
||||||
|
func DumpColorHexString(c color.Color, prefix string, withAlpha bool) string {
|
||||||
|
out := prefix
|
||||||
|
r, g, b, a := c.RGBA()
|
||||||
|
out += fmt.Sprintf("%02x", r>>8)
|
||||||
|
out += fmt.Sprintf("%02x", g>>8)
|
||||||
|
out += fmt.Sprintf("%02x", b>>8)
|
||||||
|
if withAlpha {
|
||||||
|
out += fmt.Sprintf("%02x", a>>8)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// there probably is a colorful way to do this
|
||||||
|
func hexToRGB(hexColor int) color.Color {
|
||||||
|
rgba := color.RGBA{
|
||||||
|
R: uint8((hexColor & 0xff0000) >> 16),
|
||||||
|
G: uint8((hexColor & 0x00ff00) >> 8),
|
||||||
|
B: uint8((hexColor & 0x0000ff) >> 0),
|
||||||
|
A: 0xff,
|
||||||
|
}
|
||||||
|
c, success := colorful.MakeColor(rgba)
|
||||||
|
if !success {
|
||||||
|
panic("hexToRgb")
|
||||||
|
}
|
||||||
|
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// This table contains the "key" colors of the color gradient we want to generate.
|
||||||
|
// The position of each key has to live in the range [0,1]
|
||||||
|
type keyColor struct {
|
||||||
|
Color colorful.Color
|
||||||
|
Position float64
|
||||||
|
}
|
||||||
|
type gradientTable []keyColor
|
||||||
|
|
||||||
|
// This is the meat of the gradient computation. It returns a HCL-blend between
|
||||||
|
// the two colors around `t`.
|
||||||
|
// Note: It relies heavily on the fact that the gradient keypoints are sorted.
|
||||||
|
func (gt gradientTable) getInterpolatedColorFor(t float64) colorful.Color {
|
||||||
|
for i := 0; i < len(gt)-1; i++ {
|
||||||
|
c1 := gt[i]
|
||||||
|
c2 := gt[i+1]
|
||||||
|
if c1.Position <= t && t <= c2.Position {
|
||||||
|
// We are in between c1 and c2. Go blend them!
|
||||||
|
t := (t - c1.Position) / (c2.Position - c1.Position)
|
||||||
|
return c1.Color.BlendHcl(c2.Color, t).Clamped()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nothing found? Means we're at (or past) the last gradient keypoint.
|
||||||
|
return gt[len(gt)-1].Color
|
||||||
|
}
|
||||||
|
|
||||||
|
func bakePalette(toLength int, keyColors color.Palette) (color.Palette, error) {
|
||||||
|
if toLength < 2 {
|
||||||
|
return nil, errors.New("bakePalette: the length of the palette must be > 1")
|
||||||
|
}
|
||||||
|
|
||||||
|
keyPoints := gradientTable{}
|
||||||
|
paletteLen := len(keyColors)
|
||||||
|
|
||||||
|
for colorIndex, colorObject := range keyColors {
|
||||||
|
colorfulColor, success := colorful.MakeColor(colorObject)
|
||||||
|
if !success {
|
||||||
|
panic("Bad palette color: alpha channel is probably 0.")
|
||||||
|
}
|
||||||
|
keyPoints = append(keyPoints, keyColor{
|
||||||
|
Color: colorfulColor,
|
||||||
|
Position: float64(colorIndex) / (float64(paletteLen) - 1.0),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
outPalette := make([]color.Color, 0, 7)
|
||||||
|
|
||||||
|
for i := 0; i < toLength; i++ {
|
||||||
|
c := keyPoints.getInterpolatedColorFor(float64(i) / (float64(toLength) - 1))
|
||||||
|
outPalette = append(outPalette, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
return outPalette, nil
|
||||||
|
}
|
@ -0,0 +1,133 @@
|
|||||||
|
package judgment
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/lucasb-eyer/go-colorful"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"image/color"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func hex(s string) color.Color {
|
||||||
|
c, err := colorful.Hex(s)
|
||||||
|
if err != nil {
|
||||||
|
panic("hex: " + err.Error())
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
//func TestBakePalette(t *testing.T) {
|
||||||
|
// type args struct {
|
||||||
|
// toLength int
|
||||||
|
// keyColors color.Palette
|
||||||
|
// }
|
||||||
|
// tests := []struct {
|
||||||
|
// name string
|
||||||
|
// args args
|
||||||
|
// want color.Palette
|
||||||
|
// wantErr bool
|
||||||
|
// }{
|
||||||
|
// // TODO: Add test cases.
|
||||||
|
// }
|
||||||
|
// for _tt := range tests {
|
||||||
|
// t.Run(tt.namefunc(t *testing.T) {
|
||||||
|
// goterr := bakePalette(tt.args.toLengthtt.args.keyColors)
|
||||||
|
// if (err != nil) != tt.wantErr {
|
||||||
|
// t.Errorf("bakePalette() error = %vwantErr %v"errtt.wantErr)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// if !reflect.DeepEqual(gottt.want) {
|
||||||
|
// t.Errorf("bakePalette() got = %vwant %v"gottt.want)
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
func TestCreatePalette(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
amountOfColors int
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want color.Palette
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Palette of 2",
|
||||||
|
args: args{
|
||||||
|
amountOfColors: 2,
|
||||||
|
},
|
||||||
|
want: []color.Color{
|
||||||
|
hex("#df3222"),
|
||||||
|
hex("#00a249"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Palette of 7",
|
||||||
|
args: args{
|
||||||
|
amountOfColors: 7,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Palette of 32",
|
||||||
|
args: args{
|
||||||
|
amountOfColors: 32,
|
||||||
|
},
|
||||||
|
want: []color.Color{
|
||||||
|
hex("#df3222"),
|
||||||
|
hex("#e3401d"),
|
||||||
|
hex("#e64c18"),
|
||||||
|
hex("#e95712"),
|
||||||
|
hex("#eb630b"),
|
||||||
|
hex("#ed6d02"),
|
||||||
|
hex("#f07a00"),
|
||||||
|
hex("#f38700"),
|
||||||
|
hex("#f69300"),
|
||||||
|
hex("#f8a000"),
|
||||||
|
hex("#faac00"),
|
||||||
|
hex("#f5b500"),
|
||||||
|
hex("#ecbc00"),
|
||||||
|
hex("#e2c300"),
|
||||||
|
hex("#d7ca00"),
|
||||||
|
hex("#cbd000"),
|
||||||
|
hex("#bdd20c"),
|
||||||
|
hex("#aece1d"),
|
||||||
|
hex("#a0ca28"),
|
||||||
|
hex("#92c531"),
|
||||||
|
hex("#84c039"),
|
||||||
|
hex("#76bc3e"),
|
||||||
|
hex("#65b740"),
|
||||||
|
hex("#54b142"),
|
||||||
|
hex("#40ac44"),
|
||||||
|
hex("#28a747"),
|
||||||
|
hex("#00a148"),
|
||||||
|
hex("#009944"),
|
||||||
|
hex("#009141"),
|
||||||
|
hex("#00893d"),
|
||||||
|
hex("#008239"),
|
||||||
|
hex("#017a36"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
actual := CreateDefaultPalette(tt.args.amountOfColors)
|
||||||
|
print(DumpPaletteHexString(actual, ", ", "\"") + "\n")
|
||||||
|
if nil == tt.want {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for i, expectedColor := range tt.want {
|
||||||
|
// our test values are not as precise as colorful's colors
|
||||||
|
//assert.Equal(t, expectedColor, actual[i])
|
||||||
|
// so we use equalish comparisons
|
||||||
|
p := 300.0
|
||||||
|
er, eg, eb, ea := expectedColor.RGBA()
|
||||||
|
ar, ag, ab, aa := actual[i].RGBA()
|
||||||
|
assert.InDelta(t, er, ar, p)
|
||||||
|
assert.InDelta(t, eg, ag, p)
|
||||||
|
assert.InDelta(t, eb, ab, p)
|
||||||
|
assert.InDelta(t, ea, aa, p)
|
||||||
|
}
|
||||||
|
//assert.Equal(t, true, true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in new issue