From 1357950324b126635c13f8d954b90e67dc93ec91 Mon Sep 17 00:00:00 2001 From: Will Charczuk Date: Sat, 9 Jul 2016 11:23:35 -0700 Subject: [PATCH] a color type that actually works. --- chart.go | 2 +- defaults.go | 30 +++++++-------- drawing/color.go | 85 +++++++++++++++++++++++++++++++++++++++++++ drawing/color_test.go | 41 +++++++++++++++++++++ raster_renderer.go | 8 ++-- renderer.go | 8 ++-- style.go | 39 ++++++++++---------- testserver/main.go | 4 +- util.go | 12 ------ vector_renderer.go | 21 +++++------ 10 files changed, 181 insertions(+), 69 deletions(-) create mode 100644 drawing/color.go create mode 100644 drawing/color_test.go diff --git a/chart.go b/chart.go index 9dd5465..d8a5a19 100644 --- a/chart.go +++ b/chart.go @@ -350,7 +350,7 @@ func (c Chart) drawSeries(r Renderer, canvasBox Box, index int, s Series, xrange var x, y int fill := s.GetStyle().GetFillColor() - if !ColorIsZero(fill) { + if !fill.IsZero() { r.SetFillColor(fill) r.MoveTo(x0+cx, y0+cy) for i := 1; i < s.Len(); i++ { diff --git a/defaults.go b/defaults.go index 03eefdd..0910ddf 100644 --- a/defaults.go +++ b/defaults.go @@ -1,10 +1,10 @@ package chart import ( - "image/color" "sync" "github.com/golang/freetype/truetype" + "github.com/wcharczuk/go-chart/drawing" ) const ( @@ -47,44 +47,44 @@ const ( var ( // DefaultBackgroundColor is the default chart background color. // It is equivalent to css color:white. - DefaultBackgroundColor = color.RGBA{R: 255, G: 255, B: 255, A: 255} + DefaultBackgroundColor = drawing.Color{R: 255, G: 255, B: 255, A: 255} // DefaultBackgroundStrokeColor is the default chart border color. // It is equivalent to color:white. - DefaultBackgroundStrokeColor = color.RGBA{R: 255, G: 255, B: 255, A: 255} + DefaultBackgroundStrokeColor = drawing.Color{R: 255, G: 255, B: 255, A: 255} // DefaultCanvasColor is the default chart canvas color. // It is equivalent to css color:white. - DefaultCanvasColor = color.RGBA{R: 255, G: 255, B: 255, A: 255} + DefaultCanvasColor = drawing.Color{R: 255, G: 255, B: 255, A: 255} // DefaultCanvasStrokColor is the default chart canvas stroke color. // It is equivalent to css color:white. - DefaultCanvasStrokColor = color.RGBA{R: 255, G: 255, B: 255, A: 255} + DefaultCanvasStrokColor = drawing.Color{R: 255, G: 255, B: 255, A: 255} // DefaultTextColor is the default chart text color. // It is equivalent to #333333. - DefaultTextColor = color.RGBA{R: 51, G: 51, B: 51, A: 255} + DefaultTextColor = drawing.Color{R: 51, G: 51, B: 51, A: 255} // DefaultAxisColor is the default chart axis line color. // It is equivalent to #333333. - DefaultAxisColor = color.RGBA{R: 51, G: 51, B: 51, A: 255} + DefaultAxisColor = drawing.Color{R: 51, G: 51, B: 51, A: 255} // DefaultStrokeColor is the default chart border color. // It is equivalent to #efefef. - DefaultStrokeColor = color.RGBA{R: 239, G: 239, B: 239, A: 255} + DefaultStrokeColor = drawing.Color{R: 239, G: 239, B: 239, A: 255} // DefaultFillColor is the default fill color. // It is equivalent to #0074d9. - DefaultFillColor = color.RGBA{R: 0, G: 217, B: 116, A: 255} + DefaultFillColor = drawing.Color{R: 0, G: 217, B: 116, A: 255} // DefaultFinalLabelBackgroundColor is the default final label background color. - DefaultFinalLabelBackgroundColor = color.RGBA{R: 255, G: 255, B: 255, A: 255} + DefaultFinalLabelBackgroundColor = drawing.Color{R: 255, G: 255, B: 255, A: 255} ) var ( // DefaultSeriesStrokeColors are a couple default series colors. - DefaultSeriesStrokeColors = []color.RGBA{ - color.RGBA{R: 0, G: 116, B: 217, A: 255}, - color.RGBA{R: 0, G: 217, B: 116, A: 255}, - color.RGBA{R: 217, G: 0, B: 116, A: 255}, + DefaultSeriesStrokeColors = []drawing.Color{ + drawing.Color{R: 0, G: 116, B: 217, A: 255}, + drawing.Color{R: 0, G: 217, B: 116, A: 255}, + drawing.Color{R: 217, G: 0, B: 116, A: 255}, } ) // GetDefaultSeriesStrokeColor returns a color from the default list by index. // NOTE: the index will wrap around (using a modulo).g -func GetDefaultSeriesStrokeColor(index int) color.RGBA { +func GetDefaultSeriesStrokeColor(index int) drawing.Color { finalIndex := index % len(DefaultSeriesStrokeColors) return DefaultSeriesStrokeColors[finalIndex] } diff --git a/drawing/color.go b/drawing/color.go new file mode 100644 index 0000000..d01bb98 --- /dev/null +++ b/drawing/color.go @@ -0,0 +1,85 @@ +package drawing + +import ( + "fmt" + "strconv" +) + +var ( + // ColorTransparent is a fully transparent color. + ColorTransparent = Color{} + + // 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} + + // ColorBlue is blue. + ColorBlue = Color{R: 0, G: 0, B: 255, A: 255} +) + +func parseHex(hex string) uint8 { + v, _ := strconv.ParseInt(hex, 16, 16) + return uint8(v) +} + +// ColorFromHex returns a color from a css hex code. +func ColorFromHex(hex string) Color { + var c Color + if len(hex) == 3 { + c.R = parseHex(string(hex[0])) * 0x11 + c.G = parseHex(string(hex[1])) * 0x11 + c.B = parseHex(string(hex[2])) * 0x11 + } else { + c.R = parseHex(string(hex[0:2])) + c.G = parseHex(string(hex[2:4])) + c.B = parseHex(string(hex[4:6])) + } + c.A = 255 + return c +} + +// Color is our internal color type because color.Color is bullshit. +type Color struct { + R uint8 + G uint8 + B uint8 + A uint8 +} + +// RGBA returns the color as a pre-alpha mixed color set. +func (c Color) RGBA() (r, g, b, a uint32) { + fa := float64(c.A) / 255.0 + r = uint32(float64(uint32(c.R)) * fa) + r |= r << 8 + g = uint32(float64(uint32(c.G)) * fa) + g |= g << 8 + b = uint32(float64(uint32(c.B)) * fa) + b |= b << 8 + a = uint32(c.A) + a |= a << 8 + return +} + +// IsZero returns if the color has been set or not. +func (c Color) IsZero() bool { + return c.R == 0 && c.G == 0 && c.B == 0 && c.A == 0 +} + +// IsTransparent returns if the colors alpha channel is zero. +func (c Color) IsTransparent() bool { + return c.A == 0 +} + +// String returns a css string representation of the color. +func (c Color) String() string { + fa := float64(c.A) / float64(255) + return fmt.Sprintf("rgba(%v,%v,%v,%.1f)", c.R, c.G, c.B, fa) +} diff --git a/drawing/color_test.go b/drawing/color_test.go new file mode 100644 index 0000000..d0616e2 --- /dev/null +++ b/drawing/color_test.go @@ -0,0 +1,41 @@ +package drawing + +import ( + "testing" + + "github.com/blendlabs/go-assert" +) + +func TestColorFromHex(t *testing.T) { + assert := assert.New(t) + + white := ColorFromHex("FFFFFF") + assert.Equal(ColorWhite, white) + + shortWhite := ColorFromHex("FFF") + assert.Equal(ColorWhite, shortWhite) + + black := ColorFromHex("000000") + assert.Equal(ColorBlack, black) + + shortBlack := ColorFromHex("000") + assert.Equal(ColorBlack, shortBlack) + + red := ColorFromHex("FF0000") + assert.Equal(ColorRed, red) + + shortRed := ColorFromHex("F00") + assert.Equal(ColorRed, shortRed) + + green := ColorFromHex("00FF00") + assert.Equal(ColorGreen, green) + + shortGreen := ColorFromHex("0F0") + assert.Equal(ColorGreen, shortGreen) + + blue := ColorFromHex("0000FF") + assert.Equal(ColorBlue, blue) + + shortBlue := ColorFromHex("00F") + assert.Equal(ColorBlue, shortBlue) +} diff --git a/raster_renderer.go b/raster_renderer.go index 8114fac..88b29ef 100644 --- a/raster_renderer.go +++ b/raster_renderer.go @@ -29,7 +29,7 @@ type rasterRenderer struct { gc *drawing.RasterGraphicContext fontSize float64 - fontColor color.RGBA + fontColor color.Color f *truetype.Font } @@ -39,12 +39,12 @@ func (rr *rasterRenderer) SetDPI(dpi float64) { } // SetStrokeColor implements the interface method. -func (rr *rasterRenderer) SetStrokeColor(c color.RGBA) { +func (rr *rasterRenderer) SetStrokeColor(c drawing.Color) { rr.gc.SetStrokeColor(c) } // SetFillColor implements the interface method. -func (rr *rasterRenderer) SetFillColor(c color.RGBA) { +func (rr *rasterRenderer) SetFillColor(c drawing.Color) { rr.gc.SetFillColor(c) } @@ -109,7 +109,7 @@ func (rr *rasterRenderer) SetFontSize(size float64) { } // SetFontColor implements the interface method. -func (rr *rasterRenderer) SetFontColor(c color.RGBA) { +func (rr *rasterRenderer) SetFontColor(c drawing.Color) { rr.fontColor = c rr.gc.SetFillColor(c) rr.gc.SetStrokeColor(c) diff --git a/renderer.go b/renderer.go index 9ad8a17..5a7c367 100644 --- a/renderer.go +++ b/renderer.go @@ -1,10 +1,10 @@ package chart import ( - "image/color" "io" "github.com/golang/freetype/truetype" + "github.com/wcharczuk/go-chart/drawing" ) // RendererProvider is a function that returns a renderer. @@ -16,10 +16,10 @@ type Renderer interface { SetDPI(dpi float64) // SetStrokeColor sets the current stroke color. - SetStrokeColor(color.RGBA) + SetStrokeColor(drawing.Color) // SetFillColor sets the current fill color. - SetFillColor(color.RGBA) + SetFillColor(drawing.Color) // SetStrokeWidth sets the stroke width. SetStrokeWidth(width float64) @@ -50,7 +50,7 @@ type Renderer interface { SetFont(*truetype.Font) // SetFontColor sets a font's color - SetFontColor(color.RGBA) + SetFontColor(drawing.Color) // SetFontSize sets the font size for a text field. SetFontSize(size float64) diff --git a/style.go b/style.go index e0f5232..9619aba 100644 --- a/style.go +++ b/style.go @@ -2,7 +2,6 @@ package chart import ( "fmt" - "image/color" "strings" "github.com/wcharczuk/go-chart/drawing" @@ -11,37 +10,37 @@ import ( // Style is a simple style set. type Style struct { Show bool - StrokeColor color.RGBA - FillColor color.RGBA + StrokeColor drawing.Color + FillColor drawing.Color StrokeWidth float64 FontSize float64 - FontColor color.RGBA + FontColor drawing.Color Padding Box } // IsZero returns if the object is set or not. func (s Style) IsZero() bool { - return ColorIsZero(s.StrokeColor) && ColorIsZero(s.FillColor) && s.StrokeWidth == 0 && s.FontSize == 0 + return s.StrokeColor.IsZero() && s.FillColor.IsZero() && s.StrokeWidth == 0 && s.FontSize == 0 } // GetStrokeColor returns the stroke color. -func (s Style) GetStrokeColor(defaults ...color.RGBA) color.RGBA { - if ColorIsZero(s.StrokeColor) { +func (s Style) GetStrokeColor(defaults ...drawing.Color) drawing.Color { + if s.StrokeColor.IsZero() { if len(defaults) > 0 { return defaults[0] } - return color.RGBA{} + return drawing.ColorTransparent } return s.StrokeColor } // GetFillColor returns the fill color. -func (s Style) GetFillColor(defaults ...color.RGBA) color.RGBA { - if ColorIsZero(s.FillColor) { +func (s Style) GetFillColor(defaults ...drawing.Color) drawing.Color { + if s.FillColor.IsZero() { if len(defaults) > 0 { return defaults[0] } - return color.RGBA{} + return drawing.ColorTransparent } return s.FillColor } @@ -69,12 +68,12 @@ func (s Style) GetFontSize(defaults ...float64) float64 { } // GetFontColor gets the font size. -func (s Style) GetFontColor(defaults ...color.RGBA) color.RGBA { - if ColorIsZero(s.FontColor) { +func (s Style) GetFontColor(defaults ...drawing.Color) drawing.Color { + if s.FontColor.IsZero() { if len(defaults) > 0 { return defaults[0] } - return color.RGBA{} + return drawing.ColorTransparent } return s.FontColor } @@ -93,13 +92,13 @@ func (s Style) SVG(dpi float64) string { } strokeText := "stroke:none" - if !ColorIsZero(sc) { - strokeText = "stroke:" + ColorAsString(sc) + if !sc.IsZero() { + strokeText = "stroke:" + sc.String() } fillText := "fill:none" - if !ColorIsZero(fc) { - fillText = "fill:" + ColorAsString(fc) + if !fc.IsZero() { + fillText = "fill:" + fc.String() } fontSizeText := "" @@ -107,8 +106,8 @@ func (s Style) SVG(dpi float64) string { fontSizeText = "font-size:" + fmt.Sprintf("%.1fpx", drawing.PointsToPixels(dpi, fs)) } - if !ColorIsZero(fnc) { - fillText = "fill:" + ColorAsString(fnc) + if !fnc.IsZero() { + fillText = "fill:" + fnc.String() } return strings.Join([]string{strokeWidthText, strokeText, fillText, fontSizeText}, ";") } diff --git a/testserver/main.go b/testserver/main.go index b4d01ad..4e6db6a 100644 --- a/testserver/main.go +++ b/testserver/main.go @@ -1,11 +1,11 @@ package main import ( - "image/color" "log" "net/http" "github.com/wcharczuk/go-chart" + "github.com/wcharczuk/go-chart/drawing" "github.com/wcharczuk/go-web" ) @@ -46,7 +46,7 @@ func chartHandler(rc *web.RequestContext) web.ControllerResult { XValues: []float64{1.0, 2.0, 3.0, 4.0}, YValues: []float64{2.5, 5.0, 2.0, 3.3}, Style: chart.Style{ - FillColor: color.RGBA{R: 0, G: 116, B: 217, A: 255}, + FillColor: drawing.Color{R: 0, G: 116, B: 217, A: 128}, }, }, chart.ContinuousSeries{ diff --git a/util.go b/util.go index 4aab867..85205dc 100644 --- a/util.go +++ b/util.go @@ -2,21 +2,9 @@ package chart import ( "fmt" - "image/color" "time" ) -// ColorIsZero returns if a color.RGBA is unset or not. -func ColorIsZero(c color.RGBA) bool { - return c.R == 0 && c.G == 0 && c.B == 0 && c.A == 0 -} - -// ColorAsString returns if a color.RGBA is unset or not. -func ColorAsString(c color.RGBA) string { - a := float64(c.A) / float64(255) - return fmt.Sprintf("rgba(%v,%v,%v,%.1f)", c.R, c.G, c.B, a) -} - // MinAndMax returns both the min and max in one pass. func MinAndMax(values ...float64) (min float64, max float64) { if len(values) == 0 { diff --git a/vector_renderer.go b/vector_renderer.go index 66d23bf..6e00796 100644 --- a/vector_renderer.go +++ b/vector_renderer.go @@ -3,7 +3,6 @@ package chart import ( "bytes" "fmt" - "image/color" "io" "strings" @@ -43,12 +42,12 @@ func (vr *vectorRenderer) SetDPI(dpi float64) { } // SetStrokeColor implements the interface method. -func (vr *vectorRenderer) SetStrokeColor(c color.RGBA) { +func (vr *vectorRenderer) SetStrokeColor(c drawing.Color) { vr.s.StrokeColor = c } // SetFillColor implements the interface method. -func (vr *vectorRenderer) SetFillColor(c color.RGBA) { +func (vr *vectorRenderer) SetFillColor(c drawing.Color) { vr.s.FillColor = c } @@ -73,22 +72,22 @@ func (vr *vectorRenderer) Close() { // Stroke draws the path with no fill. func (vr *vectorRenderer) Stroke() { - vr.s.FillColor = color.RGBA{} - vr.s.FontColor = color.RGBA{} + vr.s.FillColor = drawing.ColorTransparent + vr.s.FontColor = drawing.ColorTransparent vr.drawPath() } // Fill draws the path with no stroke. func (vr *vectorRenderer) Fill() { - vr.s.StrokeColor = color.RGBA{} + vr.s.StrokeColor = drawing.ColorTransparent vr.s.StrokeWidth = 0 - vr.s.FontColor = color.RGBA{} + vr.s.FontColor = drawing.ColorTransparent vr.drawPath() } // FillStroke draws the path with both fill and stroke. func (vr *vectorRenderer) FillStroke() { - vr.s.FontColor = color.RGBA{} + vr.s.FontColor = drawing.ColorTransparent vr.drawPath() } @@ -108,7 +107,7 @@ func (vr *vectorRenderer) SetFont(f *truetype.Font) { } // SetFontColor implements the interface method. -func (vr *vectorRenderer) SetFontColor(c color.RGBA) { +func (vr *vectorRenderer) SetFontColor(c drawing.Color) { vr.s.FontColor = c } @@ -130,8 +129,8 @@ func (vr *vectorRenderer) svgFontFace() string { // Text draws a text blob. func (vr *vectorRenderer) Text(body string, x, y int) { - vr.s.FillColor = color.RGBA{} - vr.s.StrokeColor = color.RGBA{} + vr.s.FillColor = drawing.ColorTransparent + vr.s.StrokeColor = drawing.ColorTransparent vr.s.StrokeWidth = 0 vr.c.Text(x, y, body, vr.s.SVG(vr.dpi)+";"+vr.svgFontFace()) }