diff --git a/_colors/colors_extended.txt b/_colors/colors_extended.txt new file mode 100644 index 0000000..65cd288 --- /dev/null +++ b/_colors/colors_extended.txt @@ -0,0 +1,147 @@ +aliceblue #f0f8ff 240,248,255 +antiquewhite #faebd7 250,235,215 +aqua #00ffff 0,255,255 +aquamarine #7fffd4 127,255,212 +azure #f0ffff 240,255,255 +beige #f5f5dc 245,245,220 +bisque #ffe4c4 255,228,196 +black #000000 0,0,0 +blanchedalmond #ffebcd 255,235,205 +blue #0000ff 0,0,255 +blueviolet #8a2be2 138,43,226 +brown #a52a2a 165,42,42 +burlywood #deb887 222,184,135 +cadetblue #5f9ea0 95,158,160 +chartreuse #7fff00 127,255,0 +chocolate #d2691e 210,105,30 +coral #ff7f50 255,127,80 +cornflowerblue #6495ed 100,149,237 +cornsilk #fff8dc 255,248,220 +crimson #dc143c 220,20,60 +cyan #00ffff 0,255,255 +darkblue #00008b 0,0,139 +darkcyan #008b8b 0,139,139 +darkgoldenrod #b8860b 184,134,11 +darkgray #a9a9a9 169,169,169 +darkgreen #006400 0,100,0 +darkgrey #a9a9a9 169,169,169 +darkkhaki #bdb76b 189,183,107 +darkmagenta #8b008b 139,0,139 +darkolivegreen #556b2f 85,107,47 +darkorange #ff8c00 255,140,0 +darkorchid #9932cc 153,50,204 +darkred #8b0000 139,0,0 +darksalmon #e9967a 233,150,122 +darkseagreen #8fbc8f 143,188,143 +darkslateblue #483d8b 72,61,139 +darkslategray #2f4f4f 47,79,79 +darkslategrey #2f4f4f 47,79,79 +darkturquoise #00ced1 0,206,209 +darkviolet #9400d3 148,0,211 +deeppink #ff1493 255,20,147 +deepskyblue #00bfff 0,191,255 +dimgray #696969 105,105,105 +dimgrey #696969 105,105,105 +dodgerblue #1e90ff 30,144,255 +firebrick #b22222 178,34,34 +floralwhite #fffaf0 255,250,240 +forestgreen #228b22 34,139,34 +fuchsia #ff00ff 255,0,255 +gainsboro #dcdcdc 220,220,220 +ghostwhite #f8f8ff 248,248,255 +gold #ffd700 255,215,0 +goldenrod #daa520 218,165,32 +gray #808080 128,128,128 +green #008000 0,128,0 +greenyellow #adff2f 173,255,47 +grey #808080 128,128,128 +honeydew #f0fff0 240,255,240 +hotpink #ff69b4 255,105,180 +indianred #cd5c5c 205,92,92 +indigo #4b0082 75,0,130 +ivory #fffff0 255,255,240 +khaki #f0e68c 240,230,140 +lavender #e6e6fa 230,230,250 +lavenderblush #fff0f5 255,240,245 +lawngreen #7cfc00 124,252,0 +lemonchiffon #fffacd 255,250,205 +lightblue #add8e6 173,216,230 +lightcoral #f08080 240,128,128 +lightcyan #e0ffff 224,255,255 +lightgoldenrodyellow #fafad2 250,250,210 +lightgray #d3d3d3 211,211,211 +lightgreen #90ee90 144,238,144 +lightgrey #d3d3d3 211,211,211 +lightpink #ffb6c1 255,182,193 +lightsalmon #ffa07a 255,160,122 +lightseagreen #20b2aa 32,178,170 +lightskyblue #87cefa 135,206,250 +lightslategray #778899 119,136,153 +lightslategrey #778899 119,136,153 +lightsteelblue #b0c4de 176,196,222 +lightyellow #ffffe0 255,255,224 +lime #00ff00 0,255,0 +limegreen #32cd32 50,205,50 +linen #faf0e6 250,240,230 +magenta #ff00ff 255,0,255 +maroon #800000 128,0,0 +mediumaquamarine #66cdaa 102,205,170 +mediumblue #0000cd 0,0,205 +mediumorchid #ba55d3 186,85,211 +mediumpurple #9370db 147,112,219 +mediumseagreen #3cb371 60,179,113 +mediumslateblue #7b68ee 123,104,238 +mediumspringgreen #00fa9a 0,250,154 +mediumturquoise #48d1cc 72,209,204 +mediumvioletred #c71585 199,21,133 +midnightblue #191970 25,25,112 +mintcream #f5fffa 245,255,250 +mistyrose #ffe4e1 255,228,225 +moccasin #ffe4b5 255,228,181 +navajowhite #ffdead 255,222,173 +navy #000080 0,0,128 +oldlace #fdf5e6 253,245,230 +olive #808000 128,128,0 +olivedrab #6b8e23 107,142,35 +orange #ffa500 255,165,0 +orangered #ff4500 255,69,0 +orchid #da70d6 218,112,214 +palegoldenrod #eee8aa 238,232,170 +palegreen #98fb98 152,251,152 +paleturquoise #afeeee 175,238,238 +palevioletred #db7093 219,112,147 +papayawhip #ffefd5 255,239,213 +peachpuff #ffdab9 255,218,185 +peru #cd853f 205,133,63 +pink #ffc0cb 255,192,203 +plum #dda0dd 221,160,221 +powderblue #b0e0e6 176,224,230 +purple #800080 128,0,128 +red #ff0000 255,0,0 +rosybrown #bc8f8f 188,143,143 +royalblue #4169e1 65,105,225 +saddlebrown #8b4513 139,69,19 +salmon #fa8072 250,128,114 +sandybrown #f4a460 244,164,96 +seagreen #2e8b57 46,139,87 +seashell #fff5ee 255,245,238 +sienna #a0522d 160,82,45 +silver #c0c0c0 192,192,192 +skyblue #87ceeb 135,206,235 +slateblue #6a5acd 106,90,205 +slategray #708090 112,128,144 +slategrey #708090 112,128,144 +snow #fffafa 255,250,250 +springgreen #00ff7f 0,255,127 +steelblue #4682b4 70,130,180 +tan #d2b48c 210,180,140 +teal #008080 0,128,128 +thistle #d8bfd8 216,191,216 +tomato #ff6347 255,99,71 +turquoise #40e0d0 64,224,208 +violet #ee82ee 238,130,238 +wheat #f5deb3 245,222,179 +white #ffffff 255,255,255 +whitesmoke #f5f5f5 245,245,245 +yellow #ffff00 255,255,0 +yellowgreen #9acd32 154,205,50 \ No newline at end of file diff --git a/drawing/color.go b/drawing/color.go index 5459ec1..09f808b 100644 --- a/drawing/color.go +++ b/drawing/color.go @@ -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.+),(?P.+),(?P.+),(?P.+)\)`) + +// 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.+),(?P.+),(?P.+)\)`) + +// 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 diff --git a/drawing/color_test.go b/drawing/color_test.go index c749649..a3abd19 100644 --- a/drawing/color_test.go +++ b/drawing/color_test.go @@ -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)) + } +} diff --git a/testutil/helpers.go b/testutil/helpers.go index a5774a6..899d4e5 100644 --- a/testutil/helpers.go +++ b/testutil/helpers.go @@ -9,6 +9,7 @@ import ( // AssertNil asserts an argument is nil. func AssertNil(t *testing.T, actual interface{}) { + t.Helper() if !isNil(actual) { t.Errorf("assertion failed; expected actual to be nil") t.FailNow() @@ -17,6 +18,7 @@ func AssertNil(t *testing.T, actual interface{}) { // AssertNotNil asserts an argument is not nil. func AssertNotNil(t *testing.T, actual interface{}, message ...interface{}) { + t.Helper() if isNil(actual) { t.Error("assertion failed; expected actual to not be nil") if len(message) > 0 { @@ -28,6 +30,7 @@ func AssertNotNil(t *testing.T, actual interface{}, message ...interface{}) { // AssertEqual asserts two arguments are equal. func AssertEqual(t *testing.T, expected, actual interface{}, message ...interface{}) { + t.Helper() if !equal(expected, actual) { t.Errorf("assertion failed; expected %v to equal %v", actual, expected) if len(message) > 0 { @@ -39,6 +42,7 @@ func AssertEqual(t *testing.T, expected, actual interface{}, message ...interfac // AssertNotEqual asserts two arguments are not equal. func AssertNotEqual(t *testing.T, expected, actual interface{}, message ...interface{}) { + t.Helper() if equal(expected, actual) { t.Errorf("assertion failed; expected %v to not equal %v", actual, expected) if len(message) > 0 { @@ -50,16 +54,19 @@ func AssertNotEqual(t *testing.T, expected, actual interface{}, message ...inter // AssertZero asserts an argument is zero. func AssertZero(t *testing.T, actual interface{}, message ...interface{}) { + t.Helper() AssertEqual(t, 0, actual) } // AssertNotZero asserts an argument is not zero. func AssertNotZero(t *testing.T, actual interface{}, message ...interface{}) { + t.Helper() AssertNotEqual(t, 0, actual) } // AssertTrue asserts an argument is true. func AssertTrue(t *testing.T, arg bool, message ...interface{}) { + t.Helper() if !arg { t.Errorf("assertion failed; expected actual to be true") if len(message) > 0 { @@ -71,6 +78,7 @@ func AssertTrue(t *testing.T, arg bool, message ...interface{}) { // AssertFalse asserts an argument is false. func AssertFalse(t *testing.T, arg bool, message ...interface{}) { + t.Helper() if arg { t.Errorf("assertion failed; expected actual to be false") if len(message) > 0 { @@ -84,6 +92,7 @@ func AssertFalse(t *testing.T, arg bool, message ...interface{}) { // // This delta will be determined absolute, and the delta should always be positive. func AssertInDelta(t *testing.T, from, to, delta float64, message ...interface{}) { + t.Helper() if diff := math.Abs(from - to); diff > delta { t.Errorf("assertion failed; expected absolute difference of %f and %f to be %f", from, to, delta) if len(message) > 0 { @@ -95,6 +104,7 @@ func AssertInDelta(t *testing.T, from, to, delta float64, message ...interface{} // AssertEmpty asserts an argument is empty. func AssertEmpty(t *testing.T, arg interface{}, message ...interface{}) { + t.Helper() if getLength(arg) != 0 { t.Errorf("assertion failed; expected actual to be empty") if len(message) > 0 { @@ -106,6 +116,7 @@ func AssertEmpty(t *testing.T, arg interface{}, message ...interface{}) { // AssertNotEmpty asserts an argument is not empty. func AssertNotEmpty(t *testing.T, arg interface{}, message ...interface{}) { + t.Helper() if getLength(arg) == 0 { t.Errorf("assertion failed; expected actual to not be empty") if len(message) > 0 { @@ -117,6 +128,7 @@ func AssertNotEmpty(t *testing.T, arg interface{}, message ...interface{}) { // AssertLen asserts an argument has a given length. func AssertLen(t *testing.T, arg interface{}, length int, message ...interface{}) { + t.Helper() if getLength(arg) != length { t.Errorf("assertion failed; expected actual to have length %d", length) if len(message) > 0 { @@ -128,6 +140,7 @@ func AssertLen(t *testing.T, arg interface{}, length int, message ...interface{} // AssertContains asserts an argument contains a given substring. func AssertContains(t *testing.T, s, substr string, message ...interface{}) { + t.Helper() if !strings.Contains(s, substr) { t.Errorf("assertion failed; expected actual to contain %q", substr) if len(message) > 0 { @@ -139,6 +152,7 @@ func AssertContains(t *testing.T, s, substr string, message ...interface{}) { // AssertNotContains asserts an argument does not contain a given substring. func AssertNotContains(t *testing.T, s, substr string, message ...interface{}) { + t.Helper() if strings.Contains(s, substr) { t.Errorf("assertion failed; expected actual to not contain %q", substr) if len(message) > 0 {