diff --git a/context.go b/context.go index efc363b..76b566c 100644 --- a/context.go +++ b/context.go @@ -42,17 +42,24 @@ type Context struct { path raster.Path start fixed.Point26_6 lineWidth float64 - capper raster.Capper - joiner raster.Joiner + lineCap LineCap + lineJoin LineJoin fillRule FillRule fontFace font.Face } func NewContext(width, height int) *Context { - im := image.NewRGBA(image.Rect(0, 0, width, height)) + return NewContextForRGBA(image.NewRGBA(image.Rect(0, 0, width, height))) +} + +func NewContextForImage(im image.Image) *Context { + return NewContextForRGBA(imageToRGBA(im)) +} + +func NewContextForRGBA(im *image.RGBA) *Context { return &Context{ - width: width, - height: height, + width: im.Bounds().Size().X, + height: im.Bounds().Size().Y, im: im, color: color.Transparent, lineWidth: 1, @@ -77,11 +84,42 @@ func (dc *Context) WriteToPNG(path string) error { return writeToPNG(path, dc.im) } -func (dc *Context) Paint() { - draw.Draw(dc.im, dc.im.Bounds(), image.NewUniform(dc.color), image.ZP, draw.Src) +func (dc *Context) SetLineWidth(lineWidth float64) { + dc.lineWidth = lineWidth } -func (dc *Context) SetSourceRGBA(r, g, b, a float64) { +func (dc *Context) SetLineCap(lineCap LineCap) { + dc.lineCap = lineCap +} + +func (dc *Context) SetLineJoin(lineJoin LineJoin) { + dc.lineJoin = lineJoin +} + +func (dc *Context) SetFillRule(fillRule FillRule) { + dc.fillRule = fillRule +} + +// Color Setters + +func (dc *Context) SetColor(c color.Color) { + dc.color = c +} + +func (dc *Context) SetHexColor(x string) { + r, g, b := parseHexColor(x) + dc.SetRGB255(r, g, b) +} + +func (dc *Context) SetRGBA255(r, g, b, a int) { + dc.color = color.NRGBA{uint8(r), uint8(g), uint8(b), uint8(a)} +} + +func (dc *Context) SetRGB255(r, g, b int) { + dc.SetRGBA255(r, g, b, 255) +} + +func (dc *Context) SetRGBA(r, g, b, a float64) { dc.color = color.NRGBA{ uint8(r * 255), uint8(g * 255), @@ -90,37 +128,11 @@ func (dc *Context) SetSourceRGBA(r, g, b, a float64) { } } -func (dc *Context) SetSourceRGB(r, g, b float64) { - dc.SetSourceRGBA(r, g, b, 1) +func (dc *Context) SetRGB(r, g, b float64) { + dc.SetRGBA(r, g, b, 1) } -func (dc *Context) SetLineWidth(lineWidth float64) { - dc.lineWidth = lineWidth -} - -func (dc *Context) SetLineCap(lineCap LineCap) { - switch lineCap { - case LineCapButt: - dc.capper = raster.ButtCapper - case LineCapRound: - dc.capper = raster.RoundCapper - case LineCapSquare: - dc.capper = raster.SquareCapper - } -} - -func (dc *Context) SetLineJoin(lineJoin LineJoin) { - switch lineJoin { - case LineJoinBevel: - dc.joiner = raster.BevelJoiner - case LineJoinRound: - dc.joiner = raster.RoundJoiner - } -} - -func (dc *Context) SetFillRule(fillRule FillRule) { - dc.fillRule = fillRule -} +// Path Manipulation func (dc *Context) MoveTo(x, y float64) { dc.start = fp(x, y) @@ -149,22 +161,40 @@ func (dc *Context) ClosePath() { } } -func (dc *Context) NewPath() { +func (dc *Context) ClearPath() { dc.path.Clear() } +// Path Drawing + func (dc *Context) StrokePreserve() { + var capper raster.Capper + switch dc.lineCap { + case LineCapButt: + capper = raster.ButtCapper + case LineCapRound: + capper = raster.RoundCapper + case LineCapSquare: + capper = raster.SquareCapper + } + var joiner raster.Joiner + switch dc.lineJoin { + case LineJoinBevel: + joiner = raster.BevelJoiner + case LineJoinRound: + joiner = raster.RoundJoiner + } painter := raster.NewRGBAPainter(dc.im) painter.SetColor(dc.color) r := raster.NewRasterizer(dc.width, dc.height) r.UseNonZeroWinding = true - r.AddStroke(dc.path, fi(dc.lineWidth), dc.capper, dc.joiner) + r.AddStroke(dc.path, fi(dc.lineWidth), capper, joiner) r.Rasterize(painter) } func (dc *Context) Stroke() { dc.StrokePreserve() - dc.NewPath() + dc.ClearPath() } func (dc *Context) FillPreserve() { @@ -182,17 +212,29 @@ func (dc *Context) FillPreserve() { func (dc *Context) Fill() { dc.FillPreserve() - dc.NewPath() + dc.ClearPath() } // Convenient Drawing Functions +func (dc *Context) Clear() { + draw.Draw(dc.im, dc.im.Bounds(), image.NewUniform(dc.color), image.ZP, draw.Src) +} + func (dc *Context) DrawLine(x1, y1, x2, y2 float64) { dc.MoveTo(x1, y1) dc.LineTo(x2, y2) } -func (dc *Context) DrawEllipseArc(x, y, rx, ry, angle1, angle2 float64) { +func (dc *Context) DrawRectangle(x, y, w, h float64) { + dc.MoveTo(x, y) + dc.LineTo(x+w, y) + dc.LineTo(x+w, y+h) + dc.LineTo(x, y+h) + dc.LineTo(x, y) +} + +func (dc *Context) DrawEllipticalArc(x, y, rx, ry, angle1, angle2 float64) { const n = 16 for i := 0; i <= n; i++ { p1 := float64(i+0) / n @@ -215,15 +257,15 @@ func (dc *Context) DrawEllipseArc(x, y, rx, ry, angle1, angle2 float64) { } func (dc *Context) DrawEllipse(x, y, rx, ry float64) { - dc.DrawEllipseArc(x, y, rx, ry, 0, 2*math.Pi) + dc.DrawEllipticalArc(x, y, rx, ry, 0, 2*math.Pi) } func (dc *Context) DrawArc(x, y, r, angle1, angle2 float64) { - dc.DrawEllipseArc(x, y, r, r, angle1, angle2) + dc.DrawEllipticalArc(x, y, r, r, angle1, angle2) } func (dc *Context) DrawCircle(x, y, r float64) { - dc.DrawEllipseArc(x, y, r, r, 0, 2*math.Pi) + dc.DrawEllipticalArc(x, y, r, r, 0, 2*math.Pi) } // Text Functions diff --git a/examples/circle.go b/examples/circle.go index d10f8b4..bd3dfa7 100644 --- a/examples/circle.go +++ b/examples/circle.go @@ -4,12 +4,12 @@ import "github.com/fogleman/gg" func main() { dc := gg.NewContext(1000, 1000) - dc.SetSourceRGB(1, 1, 1) - dc.Paint() + dc.SetRGB(1, 1, 1) + dc.Clear() dc.DrawCircle(500, 500, 400) - dc.SetSourceRGBA(0, 0, 0, 0.25) + dc.SetRGBA(0, 0, 0, 0.25) dc.FillPreserve() - dc.SetSourceRGB(0, 0, 0.5) + dc.SetRGB(0, 0, 0.5) dc.SetLineWidth(8) dc.Stroke() dc.WriteToPNG("out.png") diff --git a/examples/example0.go b/examples/example0.go index 97a4d20..80c6e3a 100644 --- a/examples/example0.go +++ b/examples/example0.go @@ -4,16 +4,16 @@ import "github.com/fogleman/gg" func main() { dc := gg.NewContext(256, 256) - dc.SetSourceRGBA(1, 0, 0, 0.3) - dc.Paint() + dc.SetRGBA(1, 0, 0, 0.3) + dc.Clear() dc.MoveTo(20, 20) dc.LineTo(236, 236) dc.LineTo(236, 128) dc.LineTo(20, 128) dc.QuadraticTo(0, 64, 120, 20) - dc.SetSourceRGBA(1, 0, 0, 0.8) + dc.SetRGBA(1, 0, 0, 0.8) dc.FillPreserve() - dc.SetSourceRGB(0, 0, 0) + dc.SetRGB(0, 0, 0) dc.SetLineWidth(8) dc.Stroke() dc.WriteToPNG("out.png") diff --git a/examples/lines.go b/examples/lines.go index 5b63d12..c49fcc6 100644 --- a/examples/lines.go +++ b/examples/lines.go @@ -10,8 +10,8 @@ func main() { const W = 1024 const H = 1024 dc := gg.NewContext(W, H) - dc.SetSourceRGB(0, 0, 0) - dc.Paint() + dc.SetRGB(0, 0, 0) + dc.Clear() for i := 0; i < 1000; i++ { x1 := rand.Float64() * W y1 := rand.Float64() * H @@ -22,10 +22,10 @@ func main() { b := rand.Float64() a := rand.Float64()*0.5 + 0.5 w := rand.Float64()*4 + 1 - dc.SetSourceRGBA(r, g, b, a) + dc.SetRGBA(r, g, b, a) dc.SetLineWidth(w) - dc.Stroke() dc.DrawLine(x1, y1, x2, y2) + dc.Stroke() } dc.WriteToPNG("out.png") } diff --git a/examples/star.go b/examples/star.go index 6591d15..6c3f448 100644 --- a/examples/star.go +++ b/examples/star.go @@ -23,17 +23,17 @@ func main() { n := 5 points := Polygon(n, 512, 512, 400) dc := gg.NewContext(1024, 1024) - dc.SetSourceRGB(1, 1, 1) - dc.Paint() + dc.SetHexColor("fff") + dc.Clear() for i := 0; i < n+1; i++ { index := (i * 2) % n p := points[index] dc.LineTo(p.X, p.Y) } - dc.SetSourceRGBA(0, 0.5, 0, 1) + dc.SetRGBA(0, 0.5, 0, 1) dc.SetFillRule(gg.FillRuleEvenOdd) dc.FillPreserve() - dc.SetSourceRGBA(0, 1, 0, 0.5) + dc.SetRGBA(0, 1, 0, 0.5) dc.SetLineWidth(16) dc.Stroke() dc.WriteToPNG("out.png") diff --git a/examples/text.go b/examples/text.go index 4bcc520..fe0fc92 100644 --- a/examples/text.go +++ b/examples/text.go @@ -4,9 +4,9 @@ import "github.com/fogleman/gg" func main() { dc := gg.NewContext(1000, 1000) - dc.SetSourceRGB(1, 1, 1) - dc.Paint() - dc.SetSourceRGB(0, 0, 0) + dc.SetRGB(1, 1, 1) + dc.Clear() + dc.SetRGB(0, 0, 0) dc.LoadFontFace("/Library/Fonts/Impact.ttf", 96) s := "Hello, world!" w := dc.MeasureString(s) diff --git a/util.go b/util.go index e38be26..1d36648 100644 --- a/util.go +++ b/util.go @@ -1,10 +1,13 @@ package gg import ( + "fmt" "image" + "image/draw" "image/png" "io/ioutil" "os" + "strings" "github.com/golang/freetype/truetype" @@ -21,6 +24,28 @@ func writeToPNG(path string, im image.Image) error { return png.Encode(file, im) } +func imageToRGBA(src image.Image) *image.RGBA { + dst := image.NewRGBA(src.Bounds()) + draw.Draw(dst, dst.Rect, src, image.ZP, draw.Src) + return dst +} + +func parseHexColor(x string) (r, g, b int) { + x = strings.TrimPrefix(x, "#") + if len(x) == 3 { + format := "%1x%1x%1x" + fmt.Sscanf(x, format, &r, &g, &b) + r |= r << 4 + g |= g << 4 + b |= b << 4 + } + if len(x) == 6 { + format := "%02x%02x%02x" + fmt.Sscanf(x, format, &r, &g, &b) + } + return +} + func fp(x, y float64) fixed.Point26_6 { return fixed.Point26_6{fixed.Int26_6(x * 64), fixed.Int26_6(y * 64)} }