Add support for dashed strokes
This commit is contained in:
parent
68f7fe39e5
commit
5af67d1f6f
@ -98,6 +98,7 @@ SetHexColor(x string)
|
|||||||
SetLineWidth(lineWidth float64)
|
SetLineWidth(lineWidth float64)
|
||||||
SetLineCap(lineCap LineCap)
|
SetLineCap(lineCap LineCap)
|
||||||
SetLineJoin(lineJoin LineJoin)
|
SetLineJoin(lineJoin LineJoin)
|
||||||
|
SetDash(dashes ...float64)
|
||||||
SetFillRule(fillRule FillRule)
|
SetFillRule(fillRule FillRule)
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -149,9 +150,6 @@ even better, implement it and submit a pull request!
|
|||||||
|
|
||||||
- Clipping Regions
|
- Clipping Regions
|
||||||
- Gradients / Patterns
|
- Gradients / Patterns
|
||||||
- Dashed Lines\*
|
|
||||||
|
|
||||||
\* *might be implemented soon*
|
|
||||||
|
|
||||||
## How Do it Do?
|
## How Do it Do?
|
||||||
|
|
||||||
|
11
context.go
11
context.go
@ -43,6 +43,7 @@ type Context struct {
|
|||||||
start Point
|
start Point
|
||||||
current Point
|
current Point
|
||||||
hasCurrent bool
|
hasCurrent bool
|
||||||
|
dashes []float64
|
||||||
lineWidth float64
|
lineWidth float64
|
||||||
lineCap LineCap
|
lineCap LineCap
|
||||||
lineJoin LineJoin
|
lineJoin LineJoin
|
||||||
@ -91,6 +92,10 @@ func (dc *Context) SavePNG(path string) error {
|
|||||||
return SavePNG(path, dc.im)
|
return SavePNG(path, dc.im)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (dc *Context) SetDash(dashes ...float64) {
|
||||||
|
dc.dashes = dashes
|
||||||
|
}
|
||||||
|
|
||||||
func (dc *Context) SetLineWidth(lineWidth float64) {
|
func (dc *Context) SetLineWidth(lineWidth float64) {
|
||||||
dc.lineWidth = lineWidth
|
dc.lineWidth = lineWidth
|
||||||
}
|
}
|
||||||
@ -276,11 +281,15 @@ func (dc *Context) joiner() raster.Joiner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (dc *Context) StrokePreserve() {
|
func (dc *Context) StrokePreserve() {
|
||||||
|
path := dc.strokePath
|
||||||
|
if len(dc.dashes) > 0 {
|
||||||
|
path = dashed(path, dc.dashes)
|
||||||
|
}
|
||||||
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)
|
r := raster.NewRasterizer(dc.width, dc.height)
|
||||||
r.UseNonZeroWinding = true
|
r.UseNonZeroWinding = true
|
||||||
r.AddStroke(dc.strokePath, fi(dc.lineWidth), dc.capper(), dc.joiner())
|
r.AddStroke(path, fi(dc.lineWidth), dc.capper(), dc.joiner())
|
||||||
r.Rasterize(painter)
|
r.Rasterize(painter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,9 +19,10 @@ func main() {
|
|||||||
dc.MoveTo(x0, y0)
|
dc.MoveTo(x0, y0)
|
||||||
dc.CubicTo(x1, y1, x2, y2, x3, y3)
|
dc.CubicTo(x1, y1, x2, y2, x3, y3)
|
||||||
dc.SetRGBA(0, 0, 0, 0.2)
|
dc.SetRGBA(0, 0, 0, 0.2)
|
||||||
dc.SetLineWidth(10)
|
dc.SetLineWidth(8)
|
||||||
dc.FillPreserve()
|
dc.FillPreserve()
|
||||||
dc.SetRGB(0, 0, 0)
|
dc.SetRGB(0, 0, 0)
|
||||||
|
dc.SetDash(16, 24)
|
||||||
dc.Stroke()
|
dc.Stroke()
|
||||||
|
|
||||||
dc.MoveTo(x0, y0)
|
dc.MoveTo(x0, y0)
|
||||||
@ -30,6 +31,7 @@ func main() {
|
|||||||
dc.LineTo(x3, y3)
|
dc.LineTo(x3, y3)
|
||||||
dc.SetRGBA(1, 0, 0, 0.4)
|
dc.SetRGBA(1, 0, 0, 0.4)
|
||||||
dc.SetLineWidth(2)
|
dc.SetLineWidth(2)
|
||||||
|
dc.SetDash(4, 8, 1, 8)
|
||||||
dc.Stroke()
|
dc.Stroke()
|
||||||
|
|
||||||
dc.SavePNG("out.png")
|
dc.SavePNG("out.png")
|
||||||
|
20
point.go
20
point.go
@ -1,11 +1,25 @@
|
|||||||
package gg
|
package gg
|
||||||
|
|
||||||
import "golang.org/x/image/math/fixed"
|
import (
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"golang.org/x/image/math/fixed"
|
||||||
|
)
|
||||||
|
|
||||||
type Point struct {
|
type Point struct {
|
||||||
X, Y float64
|
X, Y float64
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p Point) Fixed() fixed.Point26_6 {
|
func (a Point) Fixed() fixed.Point26_6 {
|
||||||
return fp(p.X, p.Y)
|
return fp(a.X, a.Y)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Point) Distance(b Point) float64 {
|
||||||
|
return math.Hypot(a.X-b.X, a.Y-b.Y)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Point) Interpolate(b Point, t float64) Point {
|
||||||
|
x := a.X + (b.X-a.X)*t
|
||||||
|
y := a.Y + (b.Y-a.Y)*t
|
||||||
|
return Point{x, y}
|
||||||
}
|
}
|
||||||
|
86
util.go
86
util.go
@ -107,14 +107,14 @@ func loadFontFace(path string, points float64) font.Face {
|
|||||||
|
|
||||||
func flattenPath(p raster.Path) [][]Point {
|
func flattenPath(p raster.Path) [][]Point {
|
||||||
var result [][]Point
|
var result [][]Point
|
||||||
path := make([]Point, 0, 16)
|
var path []Point
|
||||||
var cx, cy float64
|
var cx, cy float64
|
||||||
for i := 0; i < len(p); {
|
for i := 0; i < len(p); {
|
||||||
switch p[i] {
|
switch p[i] {
|
||||||
case 0:
|
case 0:
|
||||||
if len(path) > 0 {
|
if len(path) > 0 {
|
||||||
result = append(result, path)
|
result = append(result, path)
|
||||||
path = make([]Point, 0, 16)
|
path = nil
|
||||||
}
|
}
|
||||||
x := unfix(p[i+1])
|
x := unfix(p[i+1])
|
||||||
y := unfix(p[i+2])
|
y := unfix(p[i+2])
|
||||||
@ -156,3 +156,85 @@ func flattenPath(p raster.Path) [][]Point {
|
|||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func dashPath(paths [][]Point, dashes []float64) [][]Point {
|
||||||
|
var result [][]Point
|
||||||
|
if len(dashes) == 0 {
|
||||||
|
return paths
|
||||||
|
}
|
||||||
|
if len(dashes) == 1 {
|
||||||
|
dashes = append(dashes, dashes[0])
|
||||||
|
}
|
||||||
|
for _, path := range paths {
|
||||||
|
if len(path) < 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
previous := path[0]
|
||||||
|
pathIndex := 1
|
||||||
|
dashIndex := 0
|
||||||
|
segmentLength := 0.0
|
||||||
|
var segment []Point
|
||||||
|
segment = append(segment, previous)
|
||||||
|
for pathIndex < len(path) {
|
||||||
|
dashLength := dashes[dashIndex]
|
||||||
|
point := path[pathIndex]
|
||||||
|
d := previous.Distance(point)
|
||||||
|
maxd := dashLength - segmentLength
|
||||||
|
if d > maxd {
|
||||||
|
t := maxd / d
|
||||||
|
p := previous.Interpolate(point, t)
|
||||||
|
segment = append(segment, p)
|
||||||
|
if dashIndex%2 == 0 && len(segment) > 1 {
|
||||||
|
result = append(result, segment)
|
||||||
|
}
|
||||||
|
segment = nil
|
||||||
|
segment = append(segment, p)
|
||||||
|
segmentLength = 0
|
||||||
|
previous = p
|
||||||
|
dashIndex = (dashIndex + 1) % len(dashes)
|
||||||
|
} else {
|
||||||
|
segment = append(segment, point)
|
||||||
|
previous = point
|
||||||
|
segmentLength += d
|
||||||
|
pathIndex++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if dashIndex%2 == 0 && len(segment) > 1 {
|
||||||
|
result = append(result, segment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func rasterPath(paths [][]Point) raster.Path {
|
||||||
|
var result raster.Path
|
||||||
|
for _, path := range paths {
|
||||||
|
var previous fixed.Point26_6
|
||||||
|
for i, point := range path {
|
||||||
|
f := point.Fixed()
|
||||||
|
if i == 0 {
|
||||||
|
result.Start(f)
|
||||||
|
} else {
|
||||||
|
dx := f.X - previous.X
|
||||||
|
dy := f.Y - previous.Y
|
||||||
|
if dx < 0 {
|
||||||
|
dx = -dx
|
||||||
|
}
|
||||||
|
if dy < 0 {
|
||||||
|
dy = -dy
|
||||||
|
}
|
||||||
|
if dx+dy > 4 {
|
||||||
|
// TODO: this is a hack for cases where two points are
|
||||||
|
// too close - causes rendering issues with joins / caps
|
||||||
|
result.Add1(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
previous = f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func dashed(path raster.Path, dashes []float64) raster.Path {
|
||||||
|
return rasterPath(dashPath(flattenPath(path), dashes))
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user