implement clipping
This commit is contained in:
parent
cc92081ed6
commit
64d88fe46a
14
README.md
14
README.md
@ -147,6 +147,19 @@ Push()
|
|||||||
Pop()
|
Pop()
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Clipping Functions
|
||||||
|
|
||||||
|
Use clipping regions to restrict drawing operations to an area that you
|
||||||
|
defined using paths.
|
||||||
|
|
||||||
|
```go
|
||||||
|
Clip()
|
||||||
|
ClipPreserve()
|
||||||
|
ResetClip()
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: As currently implemented, clipping isn't very fast, but it works.
|
||||||
|
|
||||||
## Helper Functions
|
## Helper Functions
|
||||||
|
|
||||||
Sometimes you just don't want to write these yourself.
|
Sometimes you just don't want to write these yourself.
|
||||||
@ -165,7 +178,6 @@ SavePNG(path string, im image.Image) error
|
|||||||
If you need any of the features below, I recommend using `cairo` instead. Or
|
If you need any of the features below, I recommend using `cairo` instead. Or
|
||||||
even better, implement it and submit a pull request!
|
even better, implement it and submit a pull request!
|
||||||
|
|
||||||
- Clipping Regions
|
|
||||||
- Gradients / Patterns
|
- Gradients / Patterns
|
||||||
|
|
||||||
## How Do it Do?
|
## How Do it Do?
|
||||||
|
115
context.go
115
context.go
@ -48,6 +48,7 @@ type Context struct {
|
|||||||
width int
|
width int
|
||||||
height int
|
height int
|
||||||
im *image.RGBA
|
im *image.RGBA
|
||||||
|
mask *image.Alpha
|
||||||
color color.Color
|
color color.Color
|
||||||
strokePath raster.Path
|
strokePath raster.Path
|
||||||
fillPath raster.Path
|
fillPath raster.Path
|
||||||
@ -338,10 +339,7 @@ func (dc *Context) joiner() raster.Joiner {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// StrokePreserve strokes the current path with the current color, line width,
|
func (dc *Context) stroke(painter raster.Painter) {
|
||||||
// line cap, line join and dash settings. The path is preserved after this
|
|
||||||
// operation.
|
|
||||||
func (dc *Context) StrokePreserve() {
|
|
||||||
path := dc.strokePath
|
path := dc.strokePath
|
||||||
if len(dc.dashes) > 0 {
|
if len(dc.dashes) > 0 {
|
||||||
path = dashed(path, dc.dashes)
|
path = dashed(path, dc.dashes)
|
||||||
@ -350,14 +348,42 @@ func (dc *Context) StrokePreserve() {
|
|||||||
// that result in rendering issues
|
// that result in rendering issues
|
||||||
path = rasterPath(flattenPath(path))
|
path = rasterPath(flattenPath(path))
|
||||||
}
|
}
|
||||||
painter := raster.NewRGBAPainter(dc.im)
|
|
||||||
painter.SetColor(dc.color)
|
|
||||||
r := raster.NewRasterizer(dc.width, dc.height)
|
r := raster.NewRasterizer(dc.width, dc.height)
|
||||||
r.UseNonZeroWinding = true
|
r.UseNonZeroWinding = true
|
||||||
r.AddStroke(path, fix(dc.lineWidth), dc.capper(), dc.joiner())
|
r.AddStroke(path, fix(dc.lineWidth), dc.capper(), dc.joiner())
|
||||||
r.Rasterize(painter)
|
r.Rasterize(painter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (dc *Context) fill(painter raster.Painter) {
|
||||||
|
path := dc.fillPath
|
||||||
|
if dc.hasCurrent {
|
||||||
|
path = make(raster.Path, len(dc.fillPath))
|
||||||
|
copy(path, dc.fillPath)
|
||||||
|
path.Add1(dc.start.Fixed())
|
||||||
|
}
|
||||||
|
r := raster.NewRasterizer(dc.width, dc.height)
|
||||||
|
r.UseNonZeroWinding = dc.fillRule == FillRuleWinding
|
||||||
|
r.AddPath(path)
|
||||||
|
r.Rasterize(painter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StrokePreserve strokes the current path with the current color, line width,
|
||||||
|
// line cap, line join and dash settings. The path is preserved after this
|
||||||
|
// operation.
|
||||||
|
func (dc *Context) StrokePreserve() {
|
||||||
|
if dc.mask == nil {
|
||||||
|
painter := raster.NewRGBAPainter(dc.im)
|
||||||
|
painter.SetColor(dc.color)
|
||||||
|
dc.stroke(painter)
|
||||||
|
} else {
|
||||||
|
im := image.NewRGBA(image.Rect(0, 0, dc.width, dc.height))
|
||||||
|
painter := raster.NewRGBAPainter(im)
|
||||||
|
painter.SetColor(dc.color)
|
||||||
|
dc.stroke(painter)
|
||||||
|
draw.DrawMask(dc.im, dc.im.Bounds(), im, image.ZP, dc.mask, image.ZP, draw.Over)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Stroke strokes the current path with the current color, line width,
|
// Stroke strokes the current path with the current color, line width,
|
||||||
// line cap, line join and dash settings. The path is cleared after this
|
// line cap, line join and dash settings. The path is cleared after this
|
||||||
// operation.
|
// operation.
|
||||||
@ -369,18 +395,17 @@ func (dc *Context) Stroke() {
|
|||||||
// FillPreserve fills the current path with the current color. Open subpaths
|
// FillPreserve fills the current path with the current color. Open subpaths
|
||||||
// are implicity closed. The path is preserved after this operation.
|
// are implicity closed. The path is preserved after this operation.
|
||||||
func (dc *Context) FillPreserve() {
|
func (dc *Context) FillPreserve() {
|
||||||
path := dc.fillPath
|
if dc.mask == nil {
|
||||||
if dc.hasCurrent {
|
|
||||||
path = make(raster.Path, len(dc.fillPath))
|
|
||||||
copy(path, dc.fillPath)
|
|
||||||
path.Add1(dc.start.Fixed())
|
|
||||||
}
|
|
||||||
painter := raster.NewRGBAPainter(dc.im)
|
painter := raster.NewRGBAPainter(dc.im)
|
||||||
painter.SetColor(dc.color)
|
painter.SetColor(dc.color)
|
||||||
r := raster.NewRasterizer(dc.width, dc.height)
|
dc.fill(painter)
|
||||||
r.UseNonZeroWinding = dc.fillRule == FillRuleWinding
|
} else {
|
||||||
r.AddPath(path)
|
im := image.NewRGBA(image.Rect(0, 0, dc.width, dc.height))
|
||||||
r.Rasterize(painter)
|
painter := raster.NewRGBAPainter(im)
|
||||||
|
painter.SetColor(dc.color)
|
||||||
|
dc.fill(painter)
|
||||||
|
draw.DrawMask(dc.im, dc.im.Bounds(), im, image.ZP, dc.mask, image.ZP, draw.Over)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fill fills the current path with the current color. Open subpaths
|
// Fill fills the current path with the current color. Open subpaths
|
||||||
@ -390,6 +415,37 @@ func (dc *Context) Fill() {
|
|||||||
dc.ClearPath()
|
dc.ClearPath()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ClipPreserve updates the clipping region by intersecting the current
|
||||||
|
// clipping region with the current path as it would be filled by dc.Fill().
|
||||||
|
// The path is preserved after this operation.
|
||||||
|
func (dc *Context) ClipPreserve() {
|
||||||
|
clip := image.NewAlpha(image.Rect(0, 0, dc.width, dc.height))
|
||||||
|
painter := raster.NewAlphaOverPainter(clip)
|
||||||
|
dc.fill(painter)
|
||||||
|
if dc.mask == nil {
|
||||||
|
dc.mask = clip
|
||||||
|
} else {
|
||||||
|
r := image.Rect(0, 0, dc.width, dc.height)
|
||||||
|
mask := image.NewAlpha(r)
|
||||||
|
draw.DrawMask(mask, r, clip, image.ZP, dc.mask, image.ZP, draw.Over)
|
||||||
|
draw.DrawMask(mask, r, dc.mask, image.ZP, clip, image.ZP, draw.Over)
|
||||||
|
dc.mask = mask
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clip updates the clipping region by intersecting the current
|
||||||
|
// clipping region with the current path as it would be filled by dc.Fill().
|
||||||
|
// The path is cleared after this operation.
|
||||||
|
func (dc *Context) Clip() {
|
||||||
|
dc.ClipPreserve()
|
||||||
|
dc.ClearPath()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetClip clears the clipping region.
|
||||||
|
func (dc *Context) ResetClip() {
|
||||||
|
dc.mask = nil
|
||||||
|
}
|
||||||
|
|
||||||
// Convenient Drawing Functions
|
// Convenient Drawing Functions
|
||||||
|
|
||||||
// Clear fills the entire image with the current color.
|
// Clear fills the entire image with the current color.
|
||||||
@ -488,7 +544,11 @@ func (dc *Context) DrawImageAnchored(im image.Image, x, y int, ax, ay float64) {
|
|||||||
y -= int(ay * float64(s.Y))
|
y -= int(ay * float64(s.Y))
|
||||||
p := image.Pt(x, y)
|
p := image.Pt(x, y)
|
||||||
r := image.Rectangle{p, p.Add(s)}
|
r := image.Rectangle{p, p.Add(s)}
|
||||||
|
if dc.mask == nil {
|
||||||
draw.Draw(dc.im, r, im, image.ZP, draw.Over)
|
draw.Draw(dc.im, r, im, image.ZP, draw.Over)
|
||||||
|
} else {
|
||||||
|
draw.DrawMask(dc.im, r, im, image.ZP, dc.mask, p, draw.Over)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Text Functions
|
// Text Functions
|
||||||
@ -507,6 +567,16 @@ func (dc *Context) LoadFontFace(path string, points float64) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (dc *Context) drawString(im *image.RGBA, s string, x, y float64) {
|
||||||
|
d := &font.Drawer{
|
||||||
|
Dst: im,
|
||||||
|
Src: image.NewUniform(dc.color),
|
||||||
|
Face: dc.fontFace,
|
||||||
|
Dot: fixp(x, y),
|
||||||
|
}
|
||||||
|
d.DrawString(s)
|
||||||
|
}
|
||||||
|
|
||||||
// DrawString draws the specified text at the specified point.
|
// DrawString draws the specified text at the specified point.
|
||||||
// Currently, rotation and scaling transforms are not supported.
|
// Currently, rotation and scaling transforms are not supported.
|
||||||
func (dc *Context) DrawString(s string, x, y float64) {
|
func (dc *Context) DrawString(s string, x, y float64) {
|
||||||
@ -521,13 +591,13 @@ func (dc *Context) DrawStringAnchored(s string, x, y, ax, ay float64) {
|
|||||||
x -= ax * w
|
x -= ax * w
|
||||||
y += ay * h
|
y += ay * h
|
||||||
x, y = dc.TransformPoint(x, y)
|
x, y = dc.TransformPoint(x, y)
|
||||||
d := &font.Drawer{
|
if dc.mask == nil {
|
||||||
Dst: dc.im,
|
dc.drawString(dc.im, s, x, y)
|
||||||
Src: image.NewUniform(dc.color),
|
} else {
|
||||||
Face: dc.fontFace,
|
im := image.NewRGBA(image.Rect(0, 0, dc.width, dc.height))
|
||||||
Dot: fixp(x, y),
|
dc.drawString(im, s, x, y)
|
||||||
|
draw.DrawMask(dc.im, dc.im.Bounds(), im, image.ZP, dc.mask, image.ZP, draw.Over)
|
||||||
}
|
}
|
||||||
d.DrawString(s)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DrawStringWrapped word-wraps the specified string to the given max width
|
// DrawStringWrapped word-wraps the specified string to the given max width
|
||||||
@ -655,6 +725,7 @@ func (dc *Context) Pop() {
|
|||||||
s := dc.stack
|
s := dc.stack
|
||||||
x, s := s[len(s)-1], s[:len(s)-1]
|
x, s := s[len(s)-1], s[:len(s)-1]
|
||||||
*dc = *x
|
*dc = *x
|
||||||
|
dc.mask = before.mask
|
||||||
dc.strokePath = before.strokePath
|
dc.strokePath = before.strokePath
|
||||||
dc.fillPath = before.fillPath
|
dc.fillPath = before.fillPath
|
||||||
dc.start = before.start
|
dc.start = before.start
|
||||||
|
15
examples/clip.go
Normal file
15
examples/clip.go
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "github.com/fogleman/gg"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
dc := gg.NewContext(1000, 1000)
|
||||||
|
dc.DrawCircle(350, 500, 300)
|
||||||
|
dc.Clip()
|
||||||
|
dc.DrawCircle(650, 500, 300)
|
||||||
|
dc.Clip()
|
||||||
|
dc.DrawRectangle(0, 0, 1000, 1000)
|
||||||
|
dc.SetRGB(0, 0, 0)
|
||||||
|
dc.Fill()
|
||||||
|
dc.SavePNG("out.png")
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user