diff --git a/context.go b/context.go index e186027..d62e2cb 100644 --- a/context.go +++ b/context.go @@ -45,25 +45,27 @@ const ( ) type Context struct { - width int - height int - im *image.RGBA - mask *image.Alpha - color color.Color - strokePath raster.Path - fillPath raster.Path - start Point - current Point - hasCurrent bool - dashes []float64 - lineWidth float64 - lineCap LineCap - lineJoin LineJoin - fillRule FillRule - fontFace font.Face - fontHeight float64 - matrix Matrix - stack []*Context + width int + height int + im *image.RGBA + mask *image.Alpha + color color.Color + fillPattern Pattern + strokePattern Pattern + strokePath raster.Path + fillPath raster.Path + start Point + current Point + hasCurrent bool + dashes []float64 + lineWidth float64 + lineCap LineCap + lineJoin LineJoin + fillRule FillRule + fontFace font.Face + fontHeight float64 + matrix Matrix + stack []*Context } // NewContext creates a new image.RGBA with the specified width and height @@ -172,9 +174,25 @@ func (dc *Context) SetFillRuleEvenOdd() { // Color Setters +func (dc *Context) setFillAndStrokeColor(c color.Color) { + dc.color = c + dc.fillPattern = NewSolidPattern(c) + dc.strokePattern = NewSolidPattern(c) +} + +// SetFillStyle sets current fill style +func (dc *Context) SetFillStyle(pattern Pattern) { + dc.fillPattern = pattern +} + +// SetStrokeStyle sets current stroke style +func (dc *Context) SetStrokeStyle(pattern Pattern) { + dc.strokePattern = pattern +} + // SetColor sets the current color. func (dc *Context) SetColor(c color.Color) { - dc.color = c + dc.setFillAndStrokeColor(c) } // SetHexColor sets the current color using a hex string. The leading pound @@ -189,6 +207,7 @@ func (dc *Context) SetHexColor(x string) { // 255, inclusive. func (dc *Context) SetRGBA255(r, g, b, a int) { dc.color = color.NRGBA{uint8(r), uint8(g), uint8(b), uint8(a)} + dc.setFillAndStrokeColor(dc.color) } // SetRGB255 sets the current color. r, g, b values should be between 0 and 255, @@ -206,6 +225,7 @@ func (dc *Context) SetRGBA(r, g, b, a float64) { uint8(b * 255), uint8(a * 255), } + dc.setFillAndStrokeColor(dc.color) } // SetRGB sets the current color. r, g, b values should be between 0 and 1, @@ -372,13 +392,13 @@ func (dc *Context) fill(painter raster.Painter) { // operation. func (dc *Context) StrokePreserve() { if dc.mask == nil { - painter := raster.NewRGBAPainter(dc.im) - painter.SetColor(dc.color) + painter := newPatternPainter(dc.im) + painter.setPattern(dc.strokePattern) dc.stroke(painter) } else { im := image.NewRGBA(image.Rect(0, 0, dc.width, dc.height)) - painter := raster.NewRGBAPainter(im) - painter.SetColor(dc.color) + painter := newPatternPainter(im) + painter.setPattern(dc.strokePattern) dc.stroke(painter) draw.DrawMask(dc.im, dc.im.Bounds(), im, image.ZP, dc.mask, image.ZP, draw.Over) } @@ -396,13 +416,13 @@ func (dc *Context) Stroke() { // are implicity closed. The path is preserved after this operation. func (dc *Context) FillPreserve() { if dc.mask == nil { - painter := raster.NewRGBAPainter(dc.im) - painter.SetColor(dc.color) + painter := newPatternPainter(dc.im) + painter.setPattern(dc.fillPattern) dc.fill(painter) } else { im := image.NewRGBA(image.Rect(0, 0, dc.width, dc.height)) - painter := raster.NewRGBAPainter(im) - painter.SetColor(dc.color) + painter := newPatternPainter(im) + painter.setPattern(dc.fillPattern) dc.fill(painter) draw.DrawMask(dc.im, dc.im.Bounds(), im, image.ZP, dc.mask, image.ZP, draw.Over) } diff --git a/examples/fill-pattern.go b/examples/fill-pattern.go new file mode 100644 index 0000000..4500350 --- /dev/null +++ b/examples/fill-pattern.go @@ -0,0 +1,20 @@ +package main + +import "github.com/fogleman/gg" + +func main() { + im, err := gg.LoadPNG("examples/lenna.png") + if err != nil { + panic(err) + } + pattern := gg.NewSurfacePattern(im, gg.RepeatBoth) + dc := gg.NewContext(600, 600) + dc.MoveTo(20, 20) + dc.LineTo(590, 20) + dc.LineTo(590, 590) + dc.LineTo(20, 590) + dc.ClosePath() + dc.SetFillStyle(pattern) + dc.Fill() + dc.SavePNG("out.png") +} diff --git a/pattern.go b/pattern.go new file mode 100644 index 0000000..8b20764 --- /dev/null +++ b/pattern.go @@ -0,0 +1,121 @@ +package gg + +import ( + "image" + "image/color" + + "github.com/golang/freetype/raster" +) + +type RepeatOp int + +const ( + RepeatBoth RepeatOp = iota + RepeatX + RepeatY + RepeatNone +) + +type Pattern interface { + ColorAt(x, y int) color.Color +} + +// Solid Pattern +type solidPattern struct { + color color.Color +} + +func (p *solidPattern) ColorAt(x, y int) color.Color { + return p.color +} + +func NewSolidPattern(color color.Color) Pattern { + return &solidPattern{color: color} +} + +// Surface Pattern +type surfacePattern struct { + im image.Image + op RepeatOp +} + +func (p *surfacePattern) ColorAt(x, y int) color.Color { + b := p.im.Bounds() + switch p.op { + case RepeatX: + if y >= b.Dy() { + return color.Transparent + } + case RepeatY: + if x >= b.Dx() { + return color.Transparent + } + case RepeatNone: + if x >= b.Dx() || y >= b.Dy() { + return color.Transparent + } + } + x = x%b.Dx() + b.Min.X + y = y%b.Dy() + b.Min.Y + return p.im.At(x, y) +} + +func NewSurfacePattern(im image.Image, op RepeatOp) Pattern { + return &surfacePattern{im: im, op: op} +} + +type patternPainter struct { + im *image.RGBA + p Pattern +} + +// Paint satisfies the Painter interface. +func (r *patternPainter) Paint(ss []raster.Span, done bool) { + b := r.im.Bounds() + for _, s := range ss { + if s.Y < b.Min.Y { + continue + } + if s.Y >= b.Max.Y { + return + } + if s.X0 < b.Min.X { + s.X0 = b.Min.X + } + if s.X1 > b.Max.X { + s.X1 = b.Max.X + } + if s.X0 >= s.X1 { + continue + } + ma := s.Alpha + const m = 1<<16 - 1 + y := s.Y - r.im.Rect.Min.Y + x0 := s.X0 - r.im.Rect.Min.X + // x1 := x0 + s.X1 - s.X0 + // RGBAPainter.Paint() in $GOPATH/src/github.com/golang/freetype/raster/paint.go + i0 := (s.Y-r.im.Rect.Min.Y)*r.im.Stride + (s.X0-r.im.Rect.Min.X)*4 + i1 := i0 + (s.X1-s.X0)*4 + for i, x := i0, x0; i < i1; i, x = i+4, x+1 { + c := r.p.ColorAt(x, y) + cr, cg, cb, ca := c.RGBA() + dr := uint32(r.im.Pix[i+0]) + dg := uint32(r.im.Pix[i+1]) + db := uint32(r.im.Pix[i+2]) + da := uint32(r.im.Pix[i+3]) + a := (m - (ca * ma / m)) * 0x101 + r.im.Pix[i+0] = uint8((dr*a + cr*ma) / m >> 8) + r.im.Pix[i+1] = uint8((dg*a + cg*ma) / m >> 8) + r.im.Pix[i+2] = uint8((db*a + cb*ma) / m >> 8) + r.im.Pix[i+3] = uint8((da*a + ca*ma) / m >> 8) + } + } +} + +func (r *patternPainter) setPattern(pattern Pattern) { + r.p = pattern +} + +func newPatternPainter(im *image.RGBA) *patternPainter { + return &patternPainter{im: im} +}