getting ready to add extended colors but stopping at basic colors for now

This commit is contained in:
Will Charczuk 2023-11-04 13:30:06 -07:00
parent dd823617d9
commit 2f3402adfb
4 changed files with 366 additions and 9 deletions

View file

@ -2,28 +2,46 @@ package drawing
import (
"fmt"
"regexp"
"strconv"
"strings"
)
// Basic Colors from:
// https://www.w3.org/wiki/CSS/Properties/color/keywords
var (
// ColorTransparent is a fully transparent color.
ColorTransparent = Color{R: 255, G: 255, B: 255, A: 0}
// ColorWhite is white.
ColorWhite = Color{R: 255, G: 255, B: 255, A: 255}
// ColorBlack is black.
ColorBlack = Color{R: 0, G: 0, B: 0, A: 255}
// ColorRed is red.
ColorRed = Color{R: 255, G: 0, B: 0, A: 255}
// ColorGreen is green.
ColorGreen = Color{R: 0, G: 255, B: 0, A: 255}
ColorGreen = Color{R: 0, G: 128, B: 0, A: 255}
// ColorBlue is blue.
ColorBlue = Color{R: 0, G: 0, B: 255, A: 255}
// ColorSilver is a known color.
ColorSilver = Color{R: 192, G: 192, B: 192, A: 255}
// ColorMaroon is a known color.
ColorMaroon = Color{R: 128, G: 0, B: 0, A: 255}
// ColorPurple is a known color.
ColorPurple = Color{R: 128, G: 0, B: 128, A: 255}
// ColorFuchsia is a known color.
ColorFuchsia = Color{R: 255, G: 0, B: 255, A: 255}
// ColorLime is a known color.
ColorLime = Color{R: 0, G: 255, B: 0, A: 255}
// ColorOlive is a known color.
ColorOlive = Color{R: 128, G: 128, B: 0, A: 255}
// ColorYellow is a known color.
ColorYellow = Color{R: 255, G: 255, B: 0, A: 255}
// ColorNavy is a known color.
ColorNavy = Color{R: 0, G: 0, B: 128, A: 255}
// ColorTeal is a known color.
ColorTeal = Color{R: 0, G: 128, B: 128, A: 255}
// ColorAqua is a known color.
ColorAqua = Color{R: 0, G: 255, B: 255, A: 255}
)
func parseHex(hex string) uint8 {
@ -31,6 +49,90 @@ func parseHex(hex string) uint8 {
return uint8(v)
}
// ParseColor parses a color from a string.
func ParseColor(rawColor string) Color {
if strings.HasPrefix(rawColor, "rgba") {
return ColorFromRGBA(rawColor)
}
if strings.HasPrefix(rawColor, "rgb") {
return ColorFromRGB(rawColor)
}
if strings.HasPrefix(rawColor, "#") {
return ColorFromHex(rawColor)
}
return ColorFromKnown(rawColor)
}
var rgbaexpr = regexp.MustCompile(`rgba\((?P<R>.+),(?P<G>.+),(?P<B>.+),(?P<A>.+)\)`)
// ColorFromRGBA returns a color from an `rgba()` css function.
func ColorFromRGBA(rgba string) (output Color) {
values := rgbaexpr.FindStringSubmatch(rgba)
for i, name := range rgbaexpr.SubexpNames() {
if i == 0 {
continue
}
if i >= len(values) {
break
}
switch name {
case "R":
value := strings.TrimSpace(values[i])
parsed, _ := strconv.ParseInt(value, 10, 16)
output.R = uint8(parsed)
case "G":
value := strings.TrimSpace(values[i])
parsed, _ := strconv.ParseInt(value, 10, 16)
output.G = uint8(parsed)
case "B":
value := strings.TrimSpace(values[i])
parsed, _ := strconv.ParseInt(value, 10, 16)
output.B = uint8(parsed)
case "A":
value := strings.TrimSpace(values[i])
parsed, _ := strconv.ParseFloat(value, 32)
if parsed > 1 {
parsed = 1
} else if parsed < 0 {
parsed = 0
}
output.A = uint8(parsed * 255)
}
}
return
}
var rgbexpr = regexp.MustCompile(`rgb\((?P<R>.+),(?P<G>.+),(?P<B>.+)\)`)
// ColorFromRGB returns a color from an `rgb()` css function.
func ColorFromRGB(rgb string) (output Color) {
output.A = 255
values := rgbexpr.FindStringSubmatch(rgb)
for i, name := range rgbaexpr.SubexpNames() {
if i == 0 {
continue
}
if i >= len(values) {
break
}
switch name {
case "R":
value := strings.TrimSpace(values[i])
parsed, _ := strconv.ParseInt(value, 10, 16)
output.R = uint8(parsed)
case "G":
value := strings.TrimSpace(values[i])
parsed, _ := strconv.ParseInt(value, 10, 16)
output.G = uint8(parsed)
case "B":
value := strings.TrimSpace(values[i])
parsed, _ := strconv.ParseInt(value, 10, 16)
output.B = uint8(parsed)
}
}
return
}
// ColorFromHex returns a color from a css hex code.
//
// NOTE: it will trim a leading '#' character if present.
@ -52,6 +154,43 @@ func ColorFromHex(hex string) Color {
return c
}
func ColorFromKnown(known string) Color {
switch strings.ToLower(known) {
case "white":
return ColorWhite
case "black":
return ColorBlack
case "red":
return ColorRed
case "blue":
return ColorBlue
case "green":
return ColorGreen
case "silver":
return ColorSilver
case "maroon":
return ColorMaroon
case "purple":
return ColorPurple
case "fuchsia":
return ColorFuchsia
case "lime":
return ColorLime
case "olive":
return ColorOlive
case "yellow":
return ColorYellow
case "navy":
return ColorNavy
case "teal":
return ColorTeal
case "aqua":
return ColorAqua
default:
return Color{}
}
}
// ColorFromAlphaMixedRGBA returns the system alpha mixed rgba values.
func ColorFromAlphaMixedRGBA(r, g, b, a uint32) Color {
fa := float64(a) / 255.0

View file

@ -1,6 +1,7 @@
package drawing
import (
"fmt"
"testing"
"image/color"
@ -27,11 +28,11 @@ func TestColorFromHex(t *testing.T) {
shortRed := ColorFromHex("F00")
testutil.AssertEqual(t, ColorRed, shortRed)
green := ColorFromHex("00FF00")
green := ColorFromHex("008000")
testutil.AssertEqual(t, ColorGreen, green)
shortGreen := ColorFromHex("0F0")
testutil.AssertEqual(t, ColorGreen, shortGreen)
// shortGreen := ColorFromHex("0F0")
// testutil.AssertEqual(t, ColorGreen, shortGreen)
blue := ColorFromHex("0000FF")
testutil.AssertEqual(t, ColorBlue, blue)
@ -55,3 +56,59 @@ func TestColorFromAlphaMixedRGBA(t *testing.T) {
white := ColorFromAlphaMixedRGBA(color.White.RGBA())
testutil.AssertTrue(t, white.Equals(ColorWhite), white.String())
}
func Test_ColorFromRGBA(t *testing.T) {
value := "rgba(192, 192, 192, 1.0)"
parsed := ColorFromRGBA(value)
testutil.AssertEqual(t, ColorSilver, parsed)
value = "rgba(192,192,192,1.0)"
parsed = ColorFromRGBA(value)
testutil.AssertEqual(t, ColorSilver, parsed)
value = "rgba(192,192,192,1.5)"
parsed = ColorFromRGBA(value)
testutil.AssertEqual(t, ColorSilver, parsed)
}
func TestParseColor(t *testing.T) {
testCases := [...]struct {
Input string
Expected Color
}{
{"", Color{}},
{"white", ColorWhite},
{"WHITE", ColorWhite}, // caps!
{"black", ColorBlack},
{"red", ColorRed},
{"green", ColorGreen},
{"blue", ColorBlue},
{"silver", ColorSilver},
{"maroon", ColorMaroon},
{"purple", ColorPurple},
{"fuchsia", ColorFuchsia},
{"lime", ColorLime},
{"olive", ColorOlive},
{"yellow", ColorYellow},
{"navy", ColorNavy},
{"teal", ColorTeal},
{"aqua", ColorAqua},
{"rgba(192, 192, 192, 1.0)", ColorSilver},
{"rgba(192,192,192,1.0)", ColorSilver},
{"rgb(192, 192, 192)", ColorSilver},
{"rgb(192,192,192)", ColorSilver},
{"#FF0000", ColorRed},
{"#008000", ColorGreen},
{"#0000FF", ColorBlue},
{"#F00", ColorRed},
{"#080", Color{0, 136, 0, 255}},
{"#00F", ColorBlue},
}
for index, tc := range testCases {
actual := ParseColor(tc.Input)
testutil.AssertEqual(t, tc.Expected, actual, fmt.Sprintf("test case: %d -> %s", index, tc.Input))
}
}