Skip to content

Commit

Permalink
vector: add DrawFilledPath, StrokePath, and FillRule
Browse files Browse the repository at this point in the history
Updates #3150
  • Loading branch information
hajimehoshi committed Nov 9, 2024
1 parent b6d539d commit 1a42372
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 89 deletions.
26 changes: 2 additions & 24 deletions examples/lines/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,34 +125,12 @@ func (g *Game) drawLine(screen *ebiten.Image, region image.Rectangle, cap vector
op.LineJoin = join
op.MiterLimit = miterLimit
op.Width = float32(r / 2)
vs, is := path.AppendVerticesAndIndicesForStroke(g.vertices[:0], g.indices[:0], op)
for i := range vs {
vs[i].SrcX = 1
vs[i].SrcY = 1
vs[i].ColorR = 1
vs[i].ColorG = 1
vs[i].ColorB = 1
vs[i].ColorA = 1
}
screen.DrawTriangles(vs, is, whiteSubImage, &ebiten.DrawTrianglesOptions{
AntiAlias: g.aa,
})
vector.StrokePath(screen, &path, color.White, g.aa, op)

// Draw the center line in red.
if g.showCenter {
op.Width = 1
vs, is := path.AppendVerticesAndIndicesForStroke(g.vertices[:0], g.indices[:0], op)
for i := range vs {
vs[i].SrcX = 1
vs[i].SrcY = 1
vs[i].ColorR = 1
vs[i].ColorG = 0
vs[i].ColorB = 0
vs[i].ColorA = 1
}
screen.DrawTriangles(vs, is, whiteSubImage, &ebiten.DrawTrianglesOptions{
AntiAlias: g.aa,
})
vector.StrokePath(screen, &path, color.RGBA{0xff, 0, 0, 0xff}, g.aa, op)
}
}

Expand Down
67 changes: 8 additions & 59 deletions examples/vector/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,35 +130,10 @@ func (g *Game) drawEbitenText(screen *ebiten.Image, x, y int, aa bool, line bool
op := &vector.StrokeOptions{}
op.Width = 5
op.LineJoin = vector.LineJoinRound
g.vertices, g.indices = path.AppendVerticesAndIndicesForStroke(g.vertices[:0], g.indices[:0], op)
vector.StrokePath(screen, &path, color.RGBA{0xdb, 0x56, 0x20, 0xff}, aa, op)
} else {
g.vertices, g.indices = path.AppendVerticesAndIndicesForFilling(g.vertices[:0], g.indices[:0])
vector.DrawFilledPath(screen, &path, color.RGBA{0xdb, 0x56, 0x20, 0xff}, aa, vector.FillRuleNonZero)
}

for i := range g.vertices {
g.vertices[i].DstX = (g.vertices[i].DstX + float32(x))
g.vertices[i].DstY = (g.vertices[i].DstY + float32(y))
g.vertices[i].SrcX = 1
g.vertices[i].SrcY = 1
g.vertices[i].ColorR = 0xdb / float32(0xff)
g.vertices[i].ColorG = 0x56 / float32(0xff)
g.vertices[i].ColorB = 0x20 / float32(0xff)
g.vertices[i].ColorA = 1
}

op := &ebiten.DrawTrianglesOptions{}
op.AntiAlias = aa

// For strokes (AppendVerticesAndIndicesForStroke), FillRuleFillAll and FillRuleNonZero work.
//
// For filling (AppendVerticesAndIndicesForFilling), FillRuleNonZero and FillRuleEvenOdd work.
// FillRuleNonZero and FillRuleEvenOdd differ when rendering a complex polygons with self-intersections and/or holes.
// See https://en.wikipedia.org/wiki/Nonzero-rule and https://en.wikipedia.org/wiki/Even%E2%80%93odd_rule .
//
// For simplicity, this example always uses FillRuleNonZero, whichever strokes or filling is done.
op.FillRule = ebiten.FillRuleNonZero

screen.DrawTriangles(g.vertices, g.indices, whiteSubImage, op)
}

func (g *Game) drawEbitenLogo(screen *ebiten.Image, x, y int, aa bool, line bool) {
Expand Down Expand Up @@ -191,8 +166,10 @@ func (g *Game) drawEbitenLogo(screen *ebiten.Image, x, y int, aa bool, line bool
op := &vector.StrokeOptions{}
op.Width = 5
op.LineJoin = vector.LineJoinRound
// TODO: Use vector.StrokePath, but this requries to 'shift' the path by (x, y).
g.vertices, g.indices = path.AppendVerticesAndIndicesForStroke(g.vertices[:0], g.indices[:0], op)
} else {
// TODO: Use vector.DrawFilledPath, but this requries to 'shift' the path by (x, y).
g.vertices, g.indices = path.AppendVerticesAndIndicesForFilling(g.vertices[:0], g.indices[:0])
}

Expand Down Expand Up @@ -233,24 +210,10 @@ func (g *Game) drawArc(screen *ebiten.Image, count int, aa bool, line bool) {
op := &vector.StrokeOptions{}
op.Width = 5
op.LineJoin = vector.LineJoinRound
g.vertices, g.indices = path.AppendVerticesAndIndicesForStroke(g.vertices[:0], g.indices[:0], op)
vector.StrokePath(screen, &path, color.RGBA{0x33, 0xcc, 0x66, 0xff}, aa, op)
} else {
g.vertices, g.indices = path.AppendVerticesAndIndicesForFilling(g.vertices[:0], g.indices[:0])
}

for i := range g.vertices {
g.vertices[i].SrcX = 1
g.vertices[i].SrcY = 1
g.vertices[i].ColorR = 0x33 / float32(0xff)
g.vertices[i].ColorG = 0xcc / float32(0xff)
g.vertices[i].ColorB = 0x66 / float32(0xff)
g.vertices[i].ColorA = 1
vector.DrawFilledPath(screen, &path, color.RGBA{0x33, 0xcc, 0x66, 0xff}, aa, vector.FillRuleNonZero)
}

op := &ebiten.DrawTrianglesOptions{}
op.AntiAlias = aa
op.FillRule = ebiten.FillRuleNonZero
screen.DrawTriangles(g.vertices, g.indices, whiteSubImage, op)
}

func maxCounter(index int) int {
Expand Down Expand Up @@ -286,24 +249,10 @@ func (g *Game) drawWave(screen *ebiten.Image, counter int, aa bool, line bool) {
op := &vector.StrokeOptions{}
op.Width = 5
op.LineJoin = vector.LineJoinRound
g.vertices, g.indices = path.AppendVerticesAndIndicesForStroke(g.vertices[:0], g.indices[:0], op)
vector.StrokePath(screen, &path, color.RGBA{0x33, 0x66, 0xff, 0xff}, aa, op)
} else {
g.vertices, g.indices = path.AppendVerticesAndIndicesForFilling(g.vertices[:0], g.indices[:0])
vector.DrawFilledPath(screen, &path, color.RGBA{0x33, 0x66, 0xff, 0xff}, aa, vector.FillRuleNonZero)
}

for i := range g.vertices {
g.vertices[i].SrcX = 1
g.vertices[i].SrcY = 1
g.vertices[i].ColorR = 0x33 / float32(0xff)
g.vertices[i].ColorG = 0x66 / float32(0xff)
g.vertices[i].ColorB = 0xff / float32(0xff)
g.vertices[i].ColorA = 1
}

op := &ebiten.DrawTrianglesOptions{}
op.AntiAlias = aa
op.FillRule = ebiten.FillRuleNonZero
screen.DrawTriangles(g.vertices, g.indices, whiteSubImage, op)
}

func (g *Game) Update() error {
Expand Down
46 changes: 40 additions & 6 deletions vector/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func init() {
whiteImage.WritePixels(pix)
}

func drawVerticesForUtil(dst *ebiten.Image, vs []ebiten.Vertex, is []uint16, clr color.Color, antialias bool) {
func drawVerticesForUtil(dst *ebiten.Image, vs []ebiten.Vertex, is []uint16, clr color.Color, antialias bool, fillRule ebiten.FillRule) {
r, g, b, a := clr.RGBA()
for i := range vs {
vs[i].SrcX = 1
Expand All @@ -64,6 +64,7 @@ func drawVerticesForUtil(dst *ebiten.Image, vs []ebiten.Vertex, is []uint16, clr
op := &ebiten.DrawTrianglesOptions{}
op.ColorScaleMode = ebiten.ColorScaleModePremultipliedAlpha
op.AntiAlias = antialias
op.FillRule = fillRule
dst.DrawTriangles(vs, is, whiteSubImage, op)
}

Expand All @@ -79,7 +80,7 @@ func StrokeLine(dst *ebiten.Image, x0, y0, x1, y1 float32, strokeWidth float32,

useCachedVerticesAndIndices(func(vs []ebiten.Vertex, is []uint16) ([]ebiten.Vertex, []uint16) {
vs, is = path.AppendVerticesAndIndicesForStroke(vs, is, strokeOp)
drawVerticesForUtil(dst, vs, is, clr, antialias)
drawVerticesForUtil(dst, vs, is, clr, antialias, ebiten.FillRuleFillAll)
return vs, is
})
}
Expand All @@ -94,7 +95,7 @@ func DrawFilledRect(dst *ebiten.Image, x, y, width, height float32, clr color.Co

useCachedVerticesAndIndices(func(vs []ebiten.Vertex, is []uint16) ([]ebiten.Vertex, []uint16) {
vs, is = path.AppendVerticesAndIndicesForFilling(vs, is)
drawVerticesForUtil(dst, vs, is, clr, antialias)
drawVerticesForUtil(dst, vs, is, clr, antialias, ebiten.FillRuleFillAll)
return vs, is
})
}
Expand All @@ -116,7 +117,7 @@ func StrokeRect(dst *ebiten.Image, x, y, width, height float32, strokeWidth floa

useCachedVerticesAndIndices(func(vs []ebiten.Vertex, is []uint16) ([]ebiten.Vertex, []uint16) {
vs, is = path.AppendVerticesAndIndicesForStroke(vs, is, strokeOp)
drawVerticesForUtil(dst, vs, is, clr, antialias)
drawVerticesForUtil(dst, vs, is, clr, antialias, ebiten.FillRuleFillAll)
return vs, is
})
}
Expand All @@ -128,7 +129,7 @@ func DrawFilledCircle(dst *ebiten.Image, cx, cy, r float32, clr color.Color, ant

useCachedVerticesAndIndices(func(vs []ebiten.Vertex, is []uint16) ([]ebiten.Vertex, []uint16) {
vs, is = path.AppendVerticesAndIndicesForFilling(vs, is)
drawVerticesForUtil(dst, vs, is, clr, antialias)
drawVerticesForUtil(dst, vs, is, clr, antialias, ebiten.FillRuleFillAll)
return vs, is
})
}
Expand All @@ -146,7 +147,40 @@ func StrokeCircle(dst *ebiten.Image, cx, cy, r float32, strokeWidth float32, clr

useCachedVerticesAndIndices(func(vs []ebiten.Vertex, is []uint16) ([]ebiten.Vertex, []uint16) {
vs, is = path.AppendVerticesAndIndicesForStroke(vs, is, strokeOp)
drawVerticesForUtil(dst, vs, is, clr, antialias)
drawVerticesForUtil(dst, vs, is, clr, antialias, ebiten.FillRuleFillAll)
return vs, is
})
}

// FillRule is the rule whether an overlapped region is rendered or not.
type FillRule int

const (
// FillRuleNonZero means that triangles are rendered based on the non-zero rule.
// If and only if the number of overlaps is not 0, the region is rendered.
FillRuleNonZero FillRule = FillRule(ebiten.FillRuleNonZero)

// FillRuleEvenOdd means that triangles are rendered based on the even-odd rule.
// If and only if the number of overlaps is odd, the region is rendered.
FillRuleEvenOdd FillRule = FillRule(ebiten.FillRuleEvenOdd)
)

// DrawFilledRect fills the specified path with the specified color.
func DrawFilledPath(dst *ebiten.Image, path *Path, clr color.Color, antialias bool, fillRule FillRule) {
useCachedVerticesAndIndices(func(vs []ebiten.Vertex, is []uint16) ([]ebiten.Vertex, []uint16) {
vs, is = path.AppendVerticesAndIndicesForFilling(vs, is)
drawVerticesForUtil(dst, vs, is, clr, antialias, ebiten.FillRule(fillRule))
return vs, is
})
}

// StrokeCircle strokes the specified path with the specified color and stroke options.
//
// clr has be to be a solid (non-transparent) color.
func StrokePath(dst *ebiten.Image, path *Path, clr color.Color, antialias bool, options *StrokeOptions) {
useCachedVerticesAndIndices(func(vs []ebiten.Vertex, is []uint16) ([]ebiten.Vertex, []uint16) {
vs, is = path.AppendVerticesAndIndicesForStroke(vs, is, options)
drawVerticesForUtil(dst, vs, is, clr, antialias, ebiten.FillRuleFillAll)
return vs, is
})
}

0 comments on commit 1a42372

Please sign in to comment.