Share project "draw2d" into "https://draw2d.googlecode.com/svn"
This commit is contained in:
parent
5b82e83e21
commit
acce541041
8 changed files with 1416 additions and 0 deletions
17
draw2d/.project
Normal file
17
draw2d/.project
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<projectDescription>
|
||||||
|
<name>draw2d</name>
|
||||||
|
<comment></comment>
|
||||||
|
<projects>
|
||||||
|
</projects>
|
||||||
|
<buildSpec>
|
||||||
|
<buildCommand>
|
||||||
|
<name>com.googlecode.goclipse.goBuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
</buildSpec>
|
||||||
|
<natures>
|
||||||
|
<nature>goclipse.goNature</nature>
|
||||||
|
</natures>
|
||||||
|
</projectDescription>
|
412
draw2d/src/cmd/testdraw2d.go
Normal file
412
draw2d/src/cmd/testdraw2d.go
Normal file
|
@ -0,0 +1,412 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"bufio"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"math"
|
||||||
|
"image"
|
||||||
|
"image/png"
|
||||||
|
"draw2d"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
w, h = 256, 256
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
lastTime int64
|
||||||
|
folder = "../../test_results/"
|
||||||
|
)
|
||||||
|
|
||||||
|
func initGc(w, h int) (image.Image, *draw2d.GraphicContext) {
|
||||||
|
i := image.NewRGBA(w, h)
|
||||||
|
gc := draw2d.NewGraphicContext(i)
|
||||||
|
lastTime = time.Nanoseconds()
|
||||||
|
|
||||||
|
gc.SetStrokeColor(image.Black)
|
||||||
|
gc.SetFillColor(image.White)
|
||||||
|
// fill the background
|
||||||
|
//gc.Clear()
|
||||||
|
|
||||||
|
return i, gc
|
||||||
|
}
|
||||||
|
|
||||||
|
func saveToPngFile(TestName string, m image.Image) {
|
||||||
|
dt := time.Nanoseconds() - lastTime
|
||||||
|
fmt.Printf("%s during: %f ms\n", TestName, float(dt)*10e-6)
|
||||||
|
filePath := folder + TestName + ".png"
|
||||||
|
f, err := os.Open(filePath, os.O_CREAT|os.O_WRONLY, 0600)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
b := bufio.NewWriter(f)
|
||||||
|
err = png.Encode(b, m)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
err = b.Flush()
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
fmt.Printf("Wrote %s OK.\n", filePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
<img src="../test_results/TestPath.png"/>
|
||||||
|
*/
|
||||||
|
func TestPath() {
|
||||||
|
i, gc := initGc(w, h)
|
||||||
|
gc.MoveTo(10.0, 10.0)
|
||||||
|
gc.LineTo(100.0, 10.0)
|
||||||
|
gc.LineTo(100.0, 100.0)
|
||||||
|
gc.LineTo(10.0, 100.0)
|
||||||
|
gc.LineTo(10.0, 10.0)
|
||||||
|
gc.FillStroke()
|
||||||
|
saveToPngFile("TestPath", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func cos(f float) float {
|
||||||
|
return float(math.Cos(float64(f)))
|
||||||
|
}
|
||||||
|
func sin(f float) float {
|
||||||
|
return float(math.Sin(float64(f)))
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
<img src="../test_results/TestDrawArc.png"/>
|
||||||
|
*/
|
||||||
|
func TestDrawArc() {
|
||||||
|
i, gc := initGc(w, h)
|
||||||
|
// draw an arc
|
||||||
|
xc, yc := 128.0, 128.0
|
||||||
|
radiusX, radiusY := 100.0, 100.0
|
||||||
|
startAngle := 45.0 * (math.Pi / 180.0) /* angles are specified */
|
||||||
|
angle := 135 * (math.Pi / 180.0) /* in radians */
|
||||||
|
gc.SetLineWidth(10)
|
||||||
|
gc.SetLineCap(draw2d.ButtCap)
|
||||||
|
gc.SetStrokeColor(image.Black)
|
||||||
|
gc.ArcTo(xc, yc, radiusX, radiusY, startAngle, angle)
|
||||||
|
gc.Stroke()
|
||||||
|
// fill a circle
|
||||||
|
gc.SetStrokeColor(image.RGBAColor{255, 0x33, 0x33, 0x80})
|
||||||
|
gc.SetFillColor(image.RGBAColor{255, 0x33, 0x33, 0x80})
|
||||||
|
gc.SetLineWidth(6)
|
||||||
|
|
||||||
|
gc.MoveTo(xc, yc)
|
||||||
|
gc.LineTo(xc+cos(startAngle)*radiusX, yc+sin(startAngle)*radiusY)
|
||||||
|
gc.MoveTo(xc, yc)
|
||||||
|
gc.LineTo(xc-radiusX, yc)
|
||||||
|
gc.Stroke()
|
||||||
|
|
||||||
|
gc.ArcTo(xc, yc, 10.0, 10.0, 0, 2*math.Pi)
|
||||||
|
gc.Fill()
|
||||||
|
saveToPngFile("TestDrawArc", i)
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
<img src="../test_results/TestDrawArc.png"/>
|
||||||
|
*/
|
||||||
|
func TestDrawArcNegative() {
|
||||||
|
i, gc := initGc(w, h)
|
||||||
|
// draw an arc
|
||||||
|
xc, yc := 128.0, 128.0
|
||||||
|
radiusX, radiusY := 100.0, 100.0
|
||||||
|
startAngle := 45.0 * (math.Pi / 180.0) /* angles are specified */
|
||||||
|
angle := -225 * (math.Pi / 180.0) /* in radians */
|
||||||
|
gc.SetLineWidth(10)
|
||||||
|
gc.SetLineCap(draw2d.ButtCap)
|
||||||
|
gc.SetStrokeColor(image.Black)
|
||||||
|
gc.ArcTo(xc, yc, radiusX, radiusY, startAngle, angle)
|
||||||
|
gc.Stroke()
|
||||||
|
// fill a circle
|
||||||
|
gc.SetStrokeColor(image.RGBAColor{255, 0x33, 0x33, 0x80})
|
||||||
|
gc.SetFillColor(image.RGBAColor{255, 0x33, 0x33, 0x80})
|
||||||
|
gc.SetLineWidth(6)
|
||||||
|
|
||||||
|
gc.MoveTo(xc, yc)
|
||||||
|
gc.LineTo(xc+cos(startAngle)*radiusX, yc+sin(startAngle)*radiusY)
|
||||||
|
gc.MoveTo(xc, yc)
|
||||||
|
gc.LineTo(xc-radiusX, yc)
|
||||||
|
gc.Stroke()
|
||||||
|
|
||||||
|
gc.ArcTo(xc, yc, 10.0, 10.0, 0, 2*math.Pi)
|
||||||
|
gc.Fill()
|
||||||
|
saveToPngFile("TestDrawArcNegative", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCurveRectangle() {
|
||||||
|
i, gc := initGc(w, h)
|
||||||
|
|
||||||
|
/* a custom shape that could be wrapped in a function */
|
||||||
|
x0, y0 := 25.6, 25.6 /* parameters like cairo_rectangle */
|
||||||
|
rect_width, rect_height := 204.8, 204.8
|
||||||
|
radius := 102.4 /* and an approximate curvature radius */
|
||||||
|
|
||||||
|
x1 := x0 + rect_width
|
||||||
|
y1 := y0 + rect_height
|
||||||
|
if rect_width/2 < radius {
|
||||||
|
if rect_height/2 < radius {
|
||||||
|
gc.MoveTo(x0, (y0+y1)/2)
|
||||||
|
gc.CubicCurveTo(x0, y0, x0, y0, (x0+x1)/2, y0)
|
||||||
|
gc.CubicCurveTo(x1, y0, x1, y0, x1, (y0+y1)/2)
|
||||||
|
gc.CubicCurveTo(x1, y1, x1, y1, (x1+x0)/2, y1)
|
||||||
|
gc.CubicCurveTo(x0, y1, x0, y1, x0, (y0+y1)/2)
|
||||||
|
} else {
|
||||||
|
gc.MoveTo(x0, y0+radius)
|
||||||
|
gc.CubicCurveTo(x0, y0, x0, y0, (x0+x1)/2, y0)
|
||||||
|
gc.CubicCurveTo(x1, y0, x1, y0, x1, y0+radius)
|
||||||
|
gc.LineTo(x1, y1-radius)
|
||||||
|
gc.CubicCurveTo(x1, y1, x1, y1, (x1+x0)/2, y1)
|
||||||
|
gc.CubicCurveTo(x0, y1, x0, y1, x0, y1-radius)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if rect_height/2 < radius {
|
||||||
|
gc.MoveTo(x0, (y0+y1)/2)
|
||||||
|
gc.CubicCurveTo(x0, y0, x0, y0, x0+radius, y0)
|
||||||
|
gc.LineTo(x1-radius, y0)
|
||||||
|
gc.CubicCurveTo(x1, y0, x1, y0, x1, (y0+y1)/2)
|
||||||
|
gc.CubicCurveTo(x1, y1, x1, y1, x1-radius, y1)
|
||||||
|
gc.LineTo(x0+radius, y1)
|
||||||
|
gc.CubicCurveTo(x0, y1, x0, y1, x0, (y0+y1)/2)
|
||||||
|
} else {
|
||||||
|
gc.MoveTo(x0, y0+radius)
|
||||||
|
gc.CubicCurveTo(x0, y0, x0, y0, x0+radius, y0)
|
||||||
|
gc.LineTo(x1-radius, y0)
|
||||||
|
gc.CubicCurveTo(x1, y0, x1, y0, x1, y0+radius)
|
||||||
|
gc.LineTo(x1, y1-radius)
|
||||||
|
gc.CubicCurveTo(x1, y1, x1, y1, x1-radius, y1)
|
||||||
|
gc.LineTo(x0+radius, y1)
|
||||||
|
gc.CubicCurveTo(x0, y1, x0, y1, x0, y1-radius)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
gc.Close()
|
||||||
|
|
||||||
|
gc.SetFillColor(image.RGBAColor{0x80, 0x80, 0xFF, 0xFF})
|
||||||
|
gc.SetStrokeColor(image.RGBAColor{0x80, 0, 0, 0x80})
|
||||||
|
gc.SetLineWidth(10.0)
|
||||||
|
gc.FillStroke()
|
||||||
|
|
||||||
|
saveToPngFile("TestCurveRectangle", i)
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
<img src="../test_results/TestDrawCubicCurve.png"/>
|
||||||
|
*/
|
||||||
|
func TestDrawCubicCurve() {
|
||||||
|
i, gc := initGc(w, h)
|
||||||
|
// draw a cubic curve
|
||||||
|
x, y := 25.6, 128.0
|
||||||
|
x1, y1 := 102.4, 230.4
|
||||||
|
x2, y2 := 153.6, 25.6
|
||||||
|
x3, y3 := 230.4, 128.0
|
||||||
|
|
||||||
|
gc.SetFillColor(image.RGBAColor{0xAA, 0xAA, 0xAA, 0xFF})
|
||||||
|
gc.SetLineWidth(10)
|
||||||
|
gc.MoveTo(x, y)
|
||||||
|
gc.CubicCurveTo(x1, y1, x2, y2, x3, y3)
|
||||||
|
gc.Stroke()
|
||||||
|
|
||||||
|
gc.SetStrokeColor(image.RGBAColor{0xFF, 0x33, 0x33, 0x88})
|
||||||
|
|
||||||
|
gc.SetLineWidth(6)
|
||||||
|
// draw segment of curve
|
||||||
|
gc.MoveTo(x, y)
|
||||||
|
gc.LineTo(x1, y1)
|
||||||
|
gc.LineTo(x2, y2)
|
||||||
|
gc.LineTo(x3, y3)
|
||||||
|
gc.Stroke()
|
||||||
|
saveToPngFile("TestDrawCubicCurve", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
<img src="../test_results/TestDash.png"/>
|
||||||
|
*/
|
||||||
|
func TestDash() {
|
||||||
|
i, gc := initGc(w, h)
|
||||||
|
gc.SetLineDash([]float{50, 10, 10, 10}, -50.0)
|
||||||
|
gc.SetLineCap(draw2d.ButtCap)
|
||||||
|
gc.SetLineJoin(draw2d.BevelJoin)
|
||||||
|
gc.SetLineWidth(10)
|
||||||
|
|
||||||
|
gc.MoveTo(128.0, 25.6)
|
||||||
|
gc.LineTo(128.0, 25.6)
|
||||||
|
gc.LineTo(230.4, 230.4)
|
||||||
|
gc.RLineTo(-102.4, 0.0)
|
||||||
|
gc.CubicCurveTo(51.2, 230.4, 51.2, 128.0, 128.0, 128.0)
|
||||||
|
gc.Stroke()
|
||||||
|
gc.SetLineDash(nil, 0.0)
|
||||||
|
saveToPngFile("TestDash", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
<img src="../test_results/TestFillStroke.png"/>
|
||||||
|
*/
|
||||||
|
func TestFillStroke() {
|
||||||
|
i, gc := initGc(w, h)
|
||||||
|
gc.MoveTo(128.0, 25.6)
|
||||||
|
gc.LineTo(230.4, 230.4)
|
||||||
|
gc.RLineTo(-102.4, 0.0)
|
||||||
|
gc.CubicCurveTo(51.2, 230.4, 51.2, 128.0, 128.0, 128.0)
|
||||||
|
gc.Close()
|
||||||
|
|
||||||
|
gc.MoveTo(64.0, 25.6)
|
||||||
|
gc.RLineTo(51.2, 51.2)
|
||||||
|
gc.RLineTo(-51.2, 51.2)
|
||||||
|
gc.RLineTo(-51.2, -51.2)
|
||||||
|
gc.Close()
|
||||||
|
|
||||||
|
gc.SetLineWidth(10.0)
|
||||||
|
gc.SetFillColor(image.RGBAColor{0, 0, 0xFF, 0xFF})
|
||||||
|
gc.SetStrokeColor(image.Black)
|
||||||
|
gc.FillStroke()
|
||||||
|
saveToPngFile("TestFillStroke", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
<img src="../test_results/TestFillStyle.png"/>
|
||||||
|
*/
|
||||||
|
func TestFillStyle() {
|
||||||
|
i, gc := initGc(w, h)
|
||||||
|
gc.SetLineWidth(6)
|
||||||
|
|
||||||
|
gc.Rect(12, 12, 244, 70)
|
||||||
|
|
||||||
|
wheel1 := new(draw2d.Path)
|
||||||
|
wheel1.ArcTo(64, 64, 40, 40, 0, 2*math.Pi)
|
||||||
|
wheel2 := new(draw2d.Path)
|
||||||
|
wheel2.ArcTo(192, 64, 40, 40, 0, 2*math.Pi)
|
||||||
|
|
||||||
|
gc.SetFillRule(draw2d.FillRuleEvenOdd)
|
||||||
|
gc.SetFillColor(image.RGBAColor{0, 0xB2, 0, 0xFF})
|
||||||
|
|
||||||
|
gc.SetStrokeColor(image.Black)
|
||||||
|
gc.FillStroke(wheel1, wheel2)
|
||||||
|
|
||||||
|
gc.Rect(12, 140, 244, 198)
|
||||||
|
wheel1 = new(draw2d.Path)
|
||||||
|
wheel1.ArcTo(64, 192, 40, 40, 0, 2*math.Pi)
|
||||||
|
wheel2 = new(draw2d.Path)
|
||||||
|
wheel2.ArcTo(192, 192, 40, 40, 0, -2*math.Pi)
|
||||||
|
|
||||||
|
gc.SetFillRule(draw2d.FillRuleWinding)
|
||||||
|
gc.SetFillColor(image.RGBAColor{0, 0, 0xE5, 0xFF})
|
||||||
|
gc.FillStroke(wheel1, wheel2)
|
||||||
|
saveToPngFile("TestFillStyle", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMultiSegmentCaps() {
|
||||||
|
i, gc := initGc(w, h)
|
||||||
|
gc.MoveTo(50.0, 75.0)
|
||||||
|
gc.LineTo(200.0, 75.0)
|
||||||
|
|
||||||
|
gc.MoveTo(50.0, 125.0)
|
||||||
|
gc.LineTo(200.0, 125.0)
|
||||||
|
|
||||||
|
gc.MoveTo(50.0, 175.0)
|
||||||
|
gc.LineTo(200.0, 175.0)
|
||||||
|
|
||||||
|
gc.SetLineWidth(30.0)
|
||||||
|
gc.SetLineCap(draw2d.RoundCap)
|
||||||
|
gc.Stroke()
|
||||||
|
saveToPngFile("TestMultiSegmentCaps", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func TestRoundRectangle() {
|
||||||
|
i, gc := initGc(w, h)
|
||||||
|
/* a custom shape that could be wrapped in a function */
|
||||||
|
x, y := 25.6, 25.6
|
||||||
|
width, height := 204.8, 204.8
|
||||||
|
aspect := 1.0 /* aspect ratio */
|
||||||
|
corner_radius := height / 10.0 /* and corner curvature radius */
|
||||||
|
|
||||||
|
radius := corner_radius / aspect
|
||||||
|
degrees := math.Pi / 180.0
|
||||||
|
|
||||||
|
gc.ArcTo(x+width-radius, y+radius, radius, radius, -90*degrees, 90*degrees)
|
||||||
|
gc.ArcTo(x+width-radius, y+height-radius, radius, radius, 0*degrees, 90*degrees)
|
||||||
|
gc.ArcTo(x+radius, y+height-radius, radius, radius, 90*degrees, 90*degrees)
|
||||||
|
gc.ArcTo(x+radius, y+radius, radius, radius, 180*degrees, 90*degrees)
|
||||||
|
gc.Close()
|
||||||
|
|
||||||
|
gc.SetFillColor(image.RGBAColor{0x80, 0x80, 0xFF, 0xFF})
|
||||||
|
gc.SetStrokeColor(image.RGBAColor{0x80, 0, 0, 0x80})
|
||||||
|
gc.SetLineWidth(10.0)
|
||||||
|
gc.FillStroke()
|
||||||
|
|
||||||
|
saveToPngFile("TestRoundRectangle", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLineCap() {
|
||||||
|
i, gc := initGc(w, h)
|
||||||
|
gc.SetLineWidth(30.0)
|
||||||
|
gc.SetLineCap(draw2d.ButtCap)
|
||||||
|
gc.MoveTo(64.0, 50.0)
|
||||||
|
gc.LineTo(64.0, 200.0)
|
||||||
|
gc.Stroke()
|
||||||
|
gc.SetLineCap(draw2d.RoundCap)
|
||||||
|
gc.MoveTo(128.0, 50.0)
|
||||||
|
gc.LineTo(128.0, 200.0)
|
||||||
|
gc.Stroke()
|
||||||
|
gc.SetLineCap(draw2d.SquareCap)
|
||||||
|
gc.MoveTo(192.0, 50.0)
|
||||||
|
gc.LineTo(192.0, 200.0)
|
||||||
|
gc.Stroke()
|
||||||
|
|
||||||
|
/* draw helping lines */
|
||||||
|
gc.SetStrokeColor(image.RGBAColor{0xFF, 0x33, 0x33, 0xFF})
|
||||||
|
gc.SetLineWidth(2.56)
|
||||||
|
gc.MoveTo(64.0, 50.0)
|
||||||
|
gc.LineTo(64.0, 200.0)
|
||||||
|
gc.MoveTo(128.0, 50.0)
|
||||||
|
gc.LineTo(128.0, 200.0)
|
||||||
|
gc.MoveTo(192.0, 50.0)
|
||||||
|
gc.LineTo(192.0, 200.0)
|
||||||
|
gc.Stroke()
|
||||||
|
saveToPngFile("TestLineCap", i)
|
||||||
|
}
|
||||||
|
func TestLineJoin() {
|
||||||
|
i, gc := initGc(w, h)
|
||||||
|
gc.SetLineWidth(40.96)
|
||||||
|
gc.MoveTo(76.8, 84.48)
|
||||||
|
gc.RLineTo(51.2, -51.2)
|
||||||
|
gc.RLineTo(51.2, 51.2)
|
||||||
|
gc.SetLineJoin(draw2d.MiterJoin) /* default */
|
||||||
|
gc.Stroke()
|
||||||
|
|
||||||
|
gc.MoveTo(76.8, 161.28)
|
||||||
|
gc.RLineTo(51.2, -51.2)
|
||||||
|
gc.RLineTo(51.2, 51.2)
|
||||||
|
gc.SetLineJoin(draw2d.BevelJoin)
|
||||||
|
gc.Stroke()
|
||||||
|
|
||||||
|
gc.MoveTo(76.8, 238.08)
|
||||||
|
gc.RLineTo(51.2, -51.2)
|
||||||
|
gc.RLineTo(51.2, 51.2)
|
||||||
|
gc.SetLineJoin(draw2d.RoundJoin)
|
||||||
|
gc.Stroke()
|
||||||
|
saveToPngFile("TestLineJoin", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
TestPath()
|
||||||
|
TestDrawArc()
|
||||||
|
TestDrawArcNegative()
|
||||||
|
TestDrawCubicCurve()
|
||||||
|
TestCurveRectangle()
|
||||||
|
TestDash()
|
||||||
|
TestFillStroke()
|
||||||
|
TestFillStyle()
|
||||||
|
TestMultiSegmentCaps()
|
||||||
|
TestRoundRectangle()
|
||||||
|
TestLineCap()
|
||||||
|
TestLineJoin()
|
||||||
|
}
|
17
draw2d/src/cmd/testpath.go
Normal file
17
draw2d/src/cmd/testpath.go
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"draw2d"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
path := new(draw2d.Path)
|
||||||
|
path.MoveTo(2.0, 3.0)
|
||||||
|
path.LineTo(2.0, 3.0)
|
||||||
|
path.QuadCurveTo(2.0, 3.0, 10, 20)
|
||||||
|
path.CubicCurveTo(2.0, 3.0, 10, 20, 13, 23)
|
||||||
|
path.Rect(2.0, 3.0, 100, 200)
|
||||||
|
path.ArcTo(2.0, 3.0, 100, 200, 200, 300)
|
||||||
|
fmt.Printf("%v\n", path)
|
||||||
|
}
|
31
draw2d/src/pkg/draw2d/arc.go
Normal file
31
draw2d/src/pkg/draw2d/arc.go
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
package draw2d
|
||||||
|
|
||||||
|
|
||||||
|
func arc(t LineTracer, x, y, rx, ry, start, angle, scale float) {
|
||||||
|
end := start + angle
|
||||||
|
clockWise := true
|
||||||
|
if angle < 0 {
|
||||||
|
clockWise = false
|
||||||
|
}
|
||||||
|
ra := (fabs(rx) + fabs(ry)) / 2
|
||||||
|
da := acos(ra/(ra+0.125/scale)) * 2
|
||||||
|
//normalize
|
||||||
|
if !clockWise {
|
||||||
|
da = -da
|
||||||
|
}
|
||||||
|
angle = start + da
|
||||||
|
var curX, curY float
|
||||||
|
for {
|
||||||
|
if (angle < end-da/4) != clockWise {
|
||||||
|
curX = x + cos(end)*rx
|
||||||
|
curY = y + sin(end)*ry
|
||||||
|
t.LineTo(curX, curY)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
curX = x + cos(angle)*rx
|
||||||
|
curY = y + sin(angle)*ry
|
||||||
|
|
||||||
|
angle += da
|
||||||
|
t.LineTo(curX, curY)
|
||||||
|
}
|
||||||
|
}
|
339
draw2d/src/pkg/draw2d/curves.go
Normal file
339
draw2d/src/pkg/draw2d/curves.go
Normal file
|
@ -0,0 +1,339 @@
|
||||||
|
package draw2d
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
CurveRecursionLimit = 32
|
||||||
|
CurveCollinearityEpsilon = 1e-30
|
||||||
|
CurveAngleToleranceEpsilon = 0.01
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
The function has the following parameters:
|
||||||
|
approximationScale :
|
||||||
|
Eventually determines the approximation accuracy. In practice we need to transform points from the World coordinate system to the Screen one.
|
||||||
|
It always has some scaling coefficient.
|
||||||
|
The curves are usually processed in the World coordinates, while the approximation accuracy should be eventually in pixels.
|
||||||
|
Usually it looks as follows:
|
||||||
|
curved.approximationScale(transform.scale());
|
||||||
|
where transform is the affine matrix that includes all the transformations, including viewport and zoom.
|
||||||
|
angleTolerance :
|
||||||
|
You set it in radians.
|
||||||
|
The less this value is the more accurate will be the approximation at sharp turns.
|
||||||
|
But 0 means that we don't consider angle conditions at all.
|
||||||
|
cuspLimit :
|
||||||
|
An angle in radians.
|
||||||
|
If 0, only the real cusps will have bevel cuts.
|
||||||
|
If more than 0, it will restrict the sharpness.
|
||||||
|
The more this value is the less sharp turns will be cut.
|
||||||
|
Typically it should not exceed 10-15 degrees.
|
||||||
|
*/
|
||||||
|
|
||||||
|
func cubicBezier(v LineTracer, x1, y1, x2, y2, x3, y3, x4, y4, approximationScale, angleTolerance, cuspLimit float) {
|
||||||
|
cuspLimit = computeCuspLimit(cuspLimit)
|
||||||
|
distanceToleranceSquare := 0.5 / approximationScale
|
||||||
|
distanceToleranceSquare = distanceToleranceSquare * distanceToleranceSquare
|
||||||
|
recursiveCubicBezier(v, x1, y1, x2, y2, x3, y3, x4, y4, 0, distanceToleranceSquare, angleTolerance, cuspLimit)
|
||||||
|
v.LineTo(x4, y4)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* see cubicBezier comments for approximationScale and angleTolerance definition
|
||||||
|
*/
|
||||||
|
func quadraticBezier(v LineTracer, x1, y1, x2, y2, x3, y3, approximationScale, angleTolerance float) {
|
||||||
|
distanceToleranceSquare := 0.5 / approximationScale
|
||||||
|
distanceToleranceSquare = distanceToleranceSquare * distanceToleranceSquare
|
||||||
|
|
||||||
|
recursiveQuadraticBezierBezier(v, x1, y1, x2, y2, x3, y3, 0, distanceToleranceSquare, angleTolerance)
|
||||||
|
v.LineTo(x3, y3)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func computeCuspLimit(v float) (r float) {
|
||||||
|
if v == 0.0 {
|
||||||
|
r = 0.0
|
||||||
|
} else {
|
||||||
|
r = math.Pi - v
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* http://www.antigrain.com/research/adaptive_bezier/index.html
|
||||||
|
*/
|
||||||
|
func recursiveQuadraticBezierBezier(v LineTracer, x1, y1, x2, y2, x3, y3 float, level int, distanceToleranceSquare, angleTolerance float) {
|
||||||
|
if level > CurveRecursionLimit {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate all the mid-points of the line segments
|
||||||
|
//----------------------
|
||||||
|
x12 := (x1 + x2) / 2
|
||||||
|
y12 := (y1 + y2) / 2
|
||||||
|
x23 := (x2 + x3) / 2
|
||||||
|
y23 := (y2 + y3) / 2
|
||||||
|
x123 := (x12 + x23) / 2
|
||||||
|
y123 := (y12 + y23) / 2
|
||||||
|
|
||||||
|
dx := x3 - x1
|
||||||
|
dy := y3 - y1
|
||||||
|
d := fabs(((x2-x3)*dy - (y2-y3)*dx))
|
||||||
|
|
||||||
|
if d > CurveCollinearityEpsilon {
|
||||||
|
// Regular case
|
||||||
|
//-----------------
|
||||||
|
if d*d <= distanceToleranceSquare*(dx*dx+dy*dy) {
|
||||||
|
// If the curvature doesn't exceed the distanceTolerance value
|
||||||
|
// we tend to finish subdivisions.
|
||||||
|
//----------------------
|
||||||
|
if angleTolerance < CurveAngleToleranceEpsilon {
|
||||||
|
v.LineTo(x123, y123)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Angle & Cusp Condition
|
||||||
|
//----------------------
|
||||||
|
da := fabs(atan2(y3-y2, x3-x2) - atan2(y2-y1, x2-x1))
|
||||||
|
if da >= math.Pi {
|
||||||
|
da = 2*math.Pi - da
|
||||||
|
}
|
||||||
|
|
||||||
|
if da < angleTolerance {
|
||||||
|
// Finally we can stop the recursion
|
||||||
|
//----------------------
|
||||||
|
v.LineTo(x123, y123)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Collinear case
|
||||||
|
//------------------
|
||||||
|
da := dx*dx + dy*dy
|
||||||
|
if da == 0 {
|
||||||
|
d = squareDistance(x1, y1, x2, y2)
|
||||||
|
} else {
|
||||||
|
d = ((x2-x1)*dx + (y2-y1)*dy) / da
|
||||||
|
if d > 0 && d < 1 {
|
||||||
|
// Simple collinear case, 1---2---3
|
||||||
|
// We can leave just two endpoints
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if d <= 0 {
|
||||||
|
d = squareDistance(x2, y2, x1, y1)
|
||||||
|
} else if d >= 1 {
|
||||||
|
d = squareDistance(x2, y2, x3, y3)
|
||||||
|
} else {
|
||||||
|
d = squareDistance(x2, y2, x1+d*dx, y1+d*dy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if d < distanceToleranceSquare {
|
||||||
|
v.LineTo(x2, y2)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Continue subdivision
|
||||||
|
//----------------------
|
||||||
|
recursiveQuadraticBezierBezier(v, x1, y1, x12, y12, x123, y123, level+1, distanceToleranceSquare, angleTolerance)
|
||||||
|
recursiveQuadraticBezierBezier(v, x123, y123, x23, y23, x3, y3, level+1, distanceToleranceSquare, angleTolerance)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* http://www.antigrain.com/research/adaptive_bezier/index.html
|
||||||
|
*/
|
||||||
|
func recursiveCubicBezier(v LineTracer, x1, y1, x2, y2, x3, y3, x4, y4 float, level int, distanceToleranceSquare, angleTolerance, cuspLimit float) {
|
||||||
|
if level > CurveRecursionLimit {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate all the mid-points of the line segments
|
||||||
|
//----------------------
|
||||||
|
x12 := (x1 + x2) / 2
|
||||||
|
y12 := (y1 + y2) / 2
|
||||||
|
x23 := (x2 + x3) / 2
|
||||||
|
y23 := (y2 + y3) / 2
|
||||||
|
x34 := (x3 + x4) / 2
|
||||||
|
y34 := (y3 + y4) / 2
|
||||||
|
x123 := (x12 + x23) / 2
|
||||||
|
y123 := (y12 + y23) / 2
|
||||||
|
x234 := (x23 + x34) / 2
|
||||||
|
y234 := (y23 + y34) / 2
|
||||||
|
x1234 := (x123 + x234) / 2
|
||||||
|
y1234 := (y123 + y234) / 2
|
||||||
|
|
||||||
|
// Try to approximate the full cubic curve by a single straight line
|
||||||
|
//------------------
|
||||||
|
dx := x4 - x1
|
||||||
|
dy := y4 - y1
|
||||||
|
|
||||||
|
d2 := fabs(((x2-x4)*dy - (y2-y4)*dx))
|
||||||
|
d3 := fabs(((x3-x4)*dy - (y3-y4)*dx))
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case d2 <= CurveCollinearityEpsilon && d3 <= CurveCollinearityEpsilon:
|
||||||
|
// All collinear OR p1==p4
|
||||||
|
//----------------------
|
||||||
|
k := dx*dx + dy*dy
|
||||||
|
if k == 0 {
|
||||||
|
d2 = squareDistance(x1, y1, x2, y2)
|
||||||
|
d3 = squareDistance(x4, y4, x3, y3)
|
||||||
|
} else {
|
||||||
|
k = 1 / k
|
||||||
|
da1 := x2 - x1
|
||||||
|
da2 := y2 - y1
|
||||||
|
d2 = k * (da1*dx + da2*dy)
|
||||||
|
da1 = x3 - x1
|
||||||
|
da2 = y3 - y1
|
||||||
|
d3 = k * (da1*dx + da2*dy)
|
||||||
|
if d2 > 0 && d2 < 1 && d3 > 0 && d3 < 1 {
|
||||||
|
// Simple collinear case, 1---2---3---4
|
||||||
|
// We can leave just two endpoints
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if d2 <= 0 {
|
||||||
|
d2 = squareDistance(x2, y2, x1, y1)
|
||||||
|
} else if d2 >= 1 {
|
||||||
|
d2 = squareDistance(x2, y2, x4, y4)
|
||||||
|
} else {
|
||||||
|
d2 = squareDistance(x2, y2, x1+d2*dx, y1+d2*dy)
|
||||||
|
}
|
||||||
|
|
||||||
|
if d3 <= 0 {
|
||||||
|
d3 = squareDistance(x3, y3, x1, y1)
|
||||||
|
} else if d3 >= 1 {
|
||||||
|
d3 = squareDistance(x3, y3, x4, y4)
|
||||||
|
} else {
|
||||||
|
d3 = squareDistance(x3, y3, x1+d3*dx, y1+d3*dy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if d2 > d3 {
|
||||||
|
if d2 < distanceToleranceSquare {
|
||||||
|
v.LineTo(x2, y2)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if d3 < distanceToleranceSquare {
|
||||||
|
v.LineTo(x3, y3)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
|
||||||
|
case d2 <= CurveCollinearityEpsilon && d3 > CurveCollinearityEpsilon:
|
||||||
|
// p1,p2,p4 are collinear, p3 is significant
|
||||||
|
//----------------------
|
||||||
|
if d3*d3 <= distanceToleranceSquare*(dx*dx+dy*dy) {
|
||||||
|
if angleTolerance < CurveAngleToleranceEpsilon {
|
||||||
|
v.LineTo(x23, y23)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Angle Condition
|
||||||
|
//----------------------
|
||||||
|
da1 := fabs(atan2(y4-y3, x4-x3) - atan2(y3-y2, x3-x2))
|
||||||
|
if da1 >= math.Pi {
|
||||||
|
da1 = 2*math.Pi - da1
|
||||||
|
}
|
||||||
|
|
||||||
|
if da1 < angleTolerance {
|
||||||
|
v.LineTo(x2, y2)
|
||||||
|
v.LineTo(x3, y3)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if cuspLimit != 0.0 {
|
||||||
|
if da1 > cuspLimit {
|
||||||
|
v.LineTo(x3, y3)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
|
||||||
|
case d2 > CurveCollinearityEpsilon && d3 <= CurveCollinearityEpsilon:
|
||||||
|
// p1,p3,p4 are collinear, p2 is significant
|
||||||
|
//----------------------
|
||||||
|
if d2*d2 <= distanceToleranceSquare*(dx*dx+dy*dy) {
|
||||||
|
if angleTolerance < CurveAngleToleranceEpsilon {
|
||||||
|
v.LineTo(x23, y23)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Angle Condition
|
||||||
|
//----------------------
|
||||||
|
da1 := fabs(atan2(y3-y2, x3-x2) - atan2(y2-y1, x2-x1))
|
||||||
|
if da1 >= math.Pi {
|
||||||
|
da1 = 2*math.Pi - da1
|
||||||
|
}
|
||||||
|
|
||||||
|
if da1 < angleTolerance {
|
||||||
|
v.LineTo(x2, y2)
|
||||||
|
v.LineTo(x3, y3)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if cuspLimit != 0.0 {
|
||||||
|
if da1 > cuspLimit {
|
||||||
|
v.LineTo(x2, y2)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
|
||||||
|
case d2 > CurveCollinearityEpsilon && d3 > CurveCollinearityEpsilon:
|
||||||
|
// Regular case
|
||||||
|
//-----------------
|
||||||
|
if (d2+d3)*(d2+d3) <= distanceToleranceSquare*(dx*dx+dy*dy) {
|
||||||
|
// If the curvature doesn't exceed the distanceTolerance value
|
||||||
|
// we tend to finish subdivisions.
|
||||||
|
//----------------------
|
||||||
|
if angleTolerance < CurveAngleToleranceEpsilon {
|
||||||
|
v.LineTo(x23, y23)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Angle & Cusp Condition
|
||||||
|
//----------------------
|
||||||
|
k := atan2(y3-y2, x3-x2)
|
||||||
|
da1 := fabs(k - atan2(y2-y1, x2-x1))
|
||||||
|
da2 := fabs(atan2(y4-y3, x4-x3) - k)
|
||||||
|
if da1 >= math.Pi {
|
||||||
|
da1 = 2*math.Pi - da1
|
||||||
|
}
|
||||||
|
if da2 >= math.Pi {
|
||||||
|
da2 = 2*math.Pi - da2
|
||||||
|
}
|
||||||
|
|
||||||
|
if da1+da2 < angleTolerance {
|
||||||
|
// Finally we can stop the recursion
|
||||||
|
//----------------------
|
||||||
|
v.LineTo(x23, y23)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if cuspLimit != 0.0 {
|
||||||
|
if da1 > cuspLimit {
|
||||||
|
v.LineTo(x2, y2)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if da2 > cuspLimit {
|
||||||
|
v.LineTo(x3, y3)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Continue subdivision
|
||||||
|
//----------------------
|
||||||
|
recursiveCubicBezier(v, x1, y1, x12, y12, x123, y123, x1234, y1234, level+1, distanceToleranceSquare, angleTolerance, cuspLimit)
|
||||||
|
recursiveCubicBezier(v, x1234, y1234, x234, y234, x34, y34, x4, y4, level+1, distanceToleranceSquare, angleTolerance, cuspLimit)
|
||||||
|
|
||||||
|
}
|
337
draw2d/src/pkg/draw2d/draw2d.go
Normal file
337
draw2d/src/pkg/draw2d/draw2d.go
Normal file
|
@ -0,0 +1,337 @@
|
||||||
|
package draw2d
|
||||||
|
|
||||||
|
import (
|
||||||
|
"exp/draw"
|
||||||
|
"image"
|
||||||
|
"freetype-go.googlecode.com/hg/freetype/raster"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FillRule int
|
||||||
|
|
||||||
|
const (
|
||||||
|
FillRuleEvenOdd FillRule = iota
|
||||||
|
FillRuleWinding
|
||||||
|
)
|
||||||
|
|
||||||
|
type Cap int
|
||||||
|
|
||||||
|
const (
|
||||||
|
RoundCap Cap = iota
|
||||||
|
ButtCap
|
||||||
|
SquareCap
|
||||||
|
)
|
||||||
|
|
||||||
|
type Join int
|
||||||
|
|
||||||
|
const (
|
||||||
|
BevelJoin Join = iota
|
||||||
|
RoundJoin
|
||||||
|
MiterJoin
|
||||||
|
)
|
||||||
|
|
||||||
|
type GraphicContext struct {
|
||||||
|
PaintedImage *image.RGBA
|
||||||
|
rasterizer *raster.Rasterizer
|
||||||
|
current *contextStack
|
||||||
|
}
|
||||||
|
|
||||||
|
type contextStack struct {
|
||||||
|
path *Path
|
||||||
|
lineWidth float
|
||||||
|
dash []float
|
||||||
|
dashOffset float
|
||||||
|
strokeColor image.Color
|
||||||
|
fillColor image.Color
|
||||||
|
fillRule FillRule
|
||||||
|
cap Cap
|
||||||
|
join Join
|
||||||
|
|
||||||
|
previous *contextStack
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new Graphic context from an image
|
||||||
|
*/
|
||||||
|
func NewGraphicContext(pi *image.RGBA) *GraphicContext {
|
||||||
|
gc := new(GraphicContext)
|
||||||
|
gc.PaintedImage = pi
|
||||||
|
width, height := gc.PaintedImage.Bounds().Dx(), gc.PaintedImage.Bounds().Dy()
|
||||||
|
gc.rasterizer = raster.NewRasterizer(width, height)
|
||||||
|
|
||||||
|
gc.current = new(contextStack)
|
||||||
|
gc.current.lineWidth = 1.0
|
||||||
|
gc.current.strokeColor = image.Black
|
||||||
|
gc.current.fillColor = image.White
|
||||||
|
gc.current.cap = RoundCap
|
||||||
|
gc.current.fillRule = FillRuleEvenOdd
|
||||||
|
gc.current.join = RoundJoin
|
||||||
|
gc.current.path = new(Path)
|
||||||
|
return gc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gc *GraphicContext) Clear() {
|
||||||
|
width, height := gc.PaintedImage.Bounds().Dx(), gc.PaintedImage.Bounds().Dy()
|
||||||
|
gc.ClearRect(0, 0, width, height)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gc *GraphicContext) ClearRect(x1, y1, x2, y2 int) {
|
||||||
|
imageColor := image.NewColorImage(gc.current.fillColor)
|
||||||
|
draw.Draw(gc.PaintedImage, gc.PaintedImage.Bounds(), imageColor, image.ZP)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gc *GraphicContext) SetStrokeColor(c image.Color) {
|
||||||
|
gc.current.strokeColor = c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gc *GraphicContext) SetFillColor(c image.Color) {
|
||||||
|
gc.current.fillColor = c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gc *GraphicContext) SetFillRule(f FillRule) {
|
||||||
|
gc.current.fillRule = f
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gc *GraphicContext) SetLineWidth(lineWidth float) {
|
||||||
|
gc.current.lineWidth = lineWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gc *GraphicContext) SetLineCap(cap Cap) {
|
||||||
|
gc.current.cap = cap
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gc *GraphicContext) SetLineJoin(join Join) {
|
||||||
|
gc.current.join = join
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gc *GraphicContext) SetLineDash(dash []float, dashOffset float) {
|
||||||
|
gc.current.dash = dash
|
||||||
|
gc.current.dashOffset = dashOffset
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gc *GraphicContext) Save() {
|
||||||
|
context := new(contextStack)
|
||||||
|
context.lineWidth = gc.current.lineWidth
|
||||||
|
context.strokeColor = gc.current.strokeColor
|
||||||
|
context.fillColor = gc.current.fillColor
|
||||||
|
context.fillRule = gc.current.fillRule
|
||||||
|
context.dash = gc.current.dash
|
||||||
|
context.dashOffset = gc.current.dashOffset
|
||||||
|
context.cap = gc.current.cap
|
||||||
|
context.join = gc.current.join
|
||||||
|
context.path = gc.current.path.Copy()
|
||||||
|
context.previous = gc.current
|
||||||
|
gc.current = context
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gc *GraphicContext) Restore() {
|
||||||
|
if gc.current.previous != nil {
|
||||||
|
oldContext := gc.current
|
||||||
|
gc.current = gc.current.previous
|
||||||
|
oldContext.previous = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gc *GraphicContext) BeginPath() {
|
||||||
|
gc.current.path = new(Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gc *GraphicContext) MoveTo(x, y float) {
|
||||||
|
gc.current.path.MoveTo(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gc *GraphicContext) RMoveTo(dx, dy float) {
|
||||||
|
gc.current.path.RMoveTo(dx, dy)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gc *GraphicContext) LineTo(x, y float) {
|
||||||
|
gc.current.path.LineTo(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gc *GraphicContext) RLineTo(dx, dy float) {
|
||||||
|
gc.current.path.RLineTo(dx, dy)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gc *GraphicContext) Rect(x1, y1, x2, y2 float) {
|
||||||
|
gc.current.path.Rect(x1, y1, x2, y2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gc *GraphicContext) RRect(dx1, dy1, dx2, dy2 float) {
|
||||||
|
gc.current.path.RRect(dx1, dy1, dx2, dy2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gc *GraphicContext) QuadCurveTo(cx, cy, x, y float) {
|
||||||
|
gc.current.path.QuadCurveTo(cx, cy, x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gc *GraphicContext) RQuadCurveTo(dcx, dcy, dx, dy float) {
|
||||||
|
gc.current.path.RQuadCurveTo(dcx, dcy, dx, dy)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gc *GraphicContext) CubicCurveTo(cx1, cy1, cx2, cy2, x, y float) {
|
||||||
|
gc.current.path.CubicCurveTo(cx1, cy1, cx2, cy2, x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gc *GraphicContext) RCubicCurveTo(dcx1, dcy1, dcx2, dcy2, dx, dy float) {
|
||||||
|
gc.current.path.RCubicCurveTo(dcx1, dcy1, dcx2, dcy2, dx, dy)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gc *GraphicContext) ArcTo(cx, cy, rx, ry, startAngle, angle float) {
|
||||||
|
gc.current.path.ArcTo(cx, cy, rx, ry, startAngle, angle)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gc *GraphicContext) RArcTo(dcx, dcy, rx, ry, startAngle, angle float) {
|
||||||
|
gc.current.path.RArcTo(dcx, dcy, rx, ry, startAngle, angle)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gc *GraphicContext) Close() {
|
||||||
|
gc.current.path.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gc *GraphicContext) paint(color image.Color) {
|
||||||
|
painter := raster.NewRGBAPainter(gc.PaintedImage)
|
||||||
|
painter.SetColor(color)
|
||||||
|
gc.rasterizer.Rasterize(painter)
|
||||||
|
gc.rasterizer.Clear()
|
||||||
|
gc.current.path = new(Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gc *GraphicContext) Stroke(paths ...*Path) {
|
||||||
|
paths = append(paths, gc.current.path)
|
||||||
|
rasterPath := tracePath(gc.current.dash, gc.current.dashOffset, paths...)
|
||||||
|
gc.rasterizer.UseNonZeroWinding = true
|
||||||
|
gc.rasterizer.AddStroke(*rasterPath, raster.Fix32(gc.current.lineWidth*256), gc.current.cap.capper(), gc.current.join.joiner())
|
||||||
|
gc.paint(gc.current.strokeColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gc *GraphicContext) Fill(paths ...*Path) {
|
||||||
|
paths = append(paths, gc.current.path)
|
||||||
|
rasterPath := tracePath(nil, 0, paths...)
|
||||||
|
|
||||||
|
gc.rasterizer.UseNonZeroWinding = gc.current.fillRule.fillRule()
|
||||||
|
gc.rasterizer.AddPath(*rasterPath)
|
||||||
|
gc.paint(gc.current.fillColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gc *GraphicContext) FillStroke(paths ...*Path) {
|
||||||
|
paths = append(paths, gc.current.path)
|
||||||
|
rasterPath := tracePath(nil, 0, paths...)
|
||||||
|
|
||||||
|
gc.rasterizer.UseNonZeroWinding = gc.current.fillRule.fillRule()
|
||||||
|
gc.rasterizer.AddPath(*rasterPath)
|
||||||
|
gc.paint(gc.current.fillColor)
|
||||||
|
if gc.current.dash != nil {
|
||||||
|
rasterPath = tracePath(gc.current.dash, gc.current.dashOffset, paths...)
|
||||||
|
}
|
||||||
|
gc.rasterizer.UseNonZeroWinding = true
|
||||||
|
gc.rasterizer.AddStroke(*rasterPath, raster.Fix32(gc.current.lineWidth*256), gc.current.cap.capper(), gc.current.join.joiner())
|
||||||
|
gc.paint(gc.current.strokeColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f FillRule) fillRule() bool {
|
||||||
|
switch f {
|
||||||
|
case FillRuleEvenOdd:
|
||||||
|
return false
|
||||||
|
case FillRuleWinding:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Cap) capper() raster.Capper {
|
||||||
|
switch c {
|
||||||
|
case RoundCap:
|
||||||
|
return raster.RoundCapper
|
||||||
|
case ButtCap:
|
||||||
|
return raster.ButtCapper
|
||||||
|
case SquareCap:
|
||||||
|
return raster.SquareCapper
|
||||||
|
}
|
||||||
|
return raster.RoundCapper
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j Join) joiner() raster.Joiner {
|
||||||
|
switch j {
|
||||||
|
case RoundJoin:
|
||||||
|
return raster.RoundJoiner
|
||||||
|
case BevelJoin:
|
||||||
|
return raster.BevelJoiner
|
||||||
|
}
|
||||||
|
return raster.RoundJoiner
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type PathAdapter struct {
|
||||||
|
path *raster.Path
|
||||||
|
x, y, distance float
|
||||||
|
dash []float
|
||||||
|
currentDash int
|
||||||
|
dashOffset float
|
||||||
|
}
|
||||||
|
|
||||||
|
func tracePath(dash []float, dashOffset float, paths ...*Path) *raster.Path {
|
||||||
|
var adapter PathAdapter
|
||||||
|
if dash != nil && len(dash) > 0 {
|
||||||
|
adapter.dash = dash
|
||||||
|
} else {
|
||||||
|
adapter.dash = nil
|
||||||
|
}
|
||||||
|
adapter.currentDash = 0
|
||||||
|
adapter.dashOffset = dashOffset
|
||||||
|
adapter.path = new(raster.Path)
|
||||||
|
for _, path := range paths {
|
||||||
|
path.TraceLine(&adapter)
|
||||||
|
}
|
||||||
|
return adapter.path
|
||||||
|
}
|
||||||
|
|
||||||
|
func floatToPoint(x, y float) raster.Point {
|
||||||
|
return raster.Point{raster.Fix32(x * 256), raster.Fix32(y * 256)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PathAdapter) MoveTo(x, y float) {
|
||||||
|
p.path.Start(floatToPoint(x, y))
|
||||||
|
p.x, p.y = x, y
|
||||||
|
p.distance = p.dashOffset
|
||||||
|
p.currentDash = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PathAdapter) LineTo(x, y float) {
|
||||||
|
if p.dash != nil {
|
||||||
|
rest := p.dash[p.currentDash] - p.distance
|
||||||
|
for rest < 0 {
|
||||||
|
p.distance = p.distance - p.dash[p.currentDash]
|
||||||
|
p.currentDash = (p.currentDash + 1) % len(p.dash)
|
||||||
|
rest = p.dash[p.currentDash] - p.distance
|
||||||
|
}
|
||||||
|
d := distance(p.x, p.y, x, y)
|
||||||
|
for d >= rest {
|
||||||
|
k := rest / d
|
||||||
|
lx := p.x + k*(x-p.x)
|
||||||
|
ly := p.y + k*(y-p.y)
|
||||||
|
if p.currentDash%2 == 0 {
|
||||||
|
// line
|
||||||
|
p.path.Add1(floatToPoint(lx, ly))
|
||||||
|
} else {
|
||||||
|
// gap
|
||||||
|
p.path.Start(floatToPoint(lx, ly))
|
||||||
|
}
|
||||||
|
d = d - rest
|
||||||
|
p.x, p.y = lx, ly
|
||||||
|
p.currentDash = (p.currentDash + 1) % len(p.dash)
|
||||||
|
rest = p.dash[p.currentDash]
|
||||||
|
}
|
||||||
|
p.distance = d
|
||||||
|
if p.currentDash%2 == 0 {
|
||||||
|
p.path.Add1(floatToPoint(x, y))
|
||||||
|
} else {
|
||||||
|
p.path.Start(floatToPoint(x, y))
|
||||||
|
}
|
||||||
|
if p.distance >= p.dash[p.currentDash] {
|
||||||
|
p.distance = p.distance - p.dash[p.currentDash]
|
||||||
|
p.currentDash = (p.currentDash + 1) % len(p.dash)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
p.path.Add1(floatToPoint(x, y))
|
||||||
|
}
|
||||||
|
p.x, p.y = x, y
|
||||||
|
}
|
41
draw2d/src/pkg/draw2d/math.go
Normal file
41
draw2d/src/pkg/draw2d/math.go
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
package draw2d
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
func fabs(x float) float {
|
||||||
|
switch {
|
||||||
|
case x < 0:
|
||||||
|
return -x
|
||||||
|
case x == 0:
|
||||||
|
return 0 // return correctly fabs(-0)
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
func cos(f float) float {
|
||||||
|
return float(math.Cos(float64(f)))
|
||||||
|
}
|
||||||
|
func sin(f float) float {
|
||||||
|
return float(math.Sin(float64(f)))
|
||||||
|
}
|
||||||
|
func acos(f float) float {
|
||||||
|
return float(math.Acos(float64(f)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func atan2(x, y float) float {
|
||||||
|
return float(math.Atan2(float64(x), float64(y)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func distance(x1, y1, x2, y2 float) float {
|
||||||
|
dx := x2 - x1
|
||||||
|
dy := y2 - y1
|
||||||
|
return float(math.Sqrt(float64(dx*dx + dy*dy)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func squareDistance(x1, y1, x2, y2 float) float {
|
||||||
|
dx := x2 - x1
|
||||||
|
dy := y2 - y1
|
||||||
|
return dx*dx + dy*dy
|
||||||
|
}
|
222
draw2d/src/pkg/draw2d/path.go
Normal file
222
draw2d/src/pkg/draw2d/path.go
Normal file
|
@ -0,0 +1,222 @@
|
||||||
|
package draw2d
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PathCmd int
|
||||||
|
|
||||||
|
const (
|
||||||
|
MoveTo PathCmd = iota
|
||||||
|
LineTo
|
||||||
|
QuadCurveTo
|
||||||
|
CubicCurveTo
|
||||||
|
ArcTo
|
||||||
|
Close
|
||||||
|
)
|
||||||
|
|
||||||
|
type Path struct {
|
||||||
|
commands []PathCmd
|
||||||
|
vertices []float
|
||||||
|
x, y float
|
||||||
|
}
|
||||||
|
|
||||||
|
type LineTracer interface {
|
||||||
|
MoveTo(x, y float)
|
||||||
|
LineTo(x, y float)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Path) appendToPath(cmd PathCmd, vertices ...float) {
|
||||||
|
p.commands = append(p.commands, cmd)
|
||||||
|
p.vertices = append(p.vertices, vertices...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (src *Path) Copy() (dest *Path) {
|
||||||
|
dest = new(Path)
|
||||||
|
dest.commands = make([]PathCmd, len(src.commands))
|
||||||
|
copy(dest.commands, src.commands)
|
||||||
|
dest.vertices = make([]float, len(src.vertices))
|
||||||
|
copy(dest.vertices, src.vertices)
|
||||||
|
return dest
|
||||||
|
}
|
||||||
|
func (p *Path) LastPoint() (x, y float) {
|
||||||
|
return p.x, p.y
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Path) Close() *Path {
|
||||||
|
p.appendToPath(Close)
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Path) MoveTo(x, y float) *Path {
|
||||||
|
p.appendToPath(MoveTo, x, y)
|
||||||
|
p.x = x
|
||||||
|
p.y = y
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Path) RMoveTo(dx, dy float) *Path {
|
||||||
|
x, y := p.LastPoint()
|
||||||
|
p.MoveTo(x+dx, y+dy)
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Path) LineTo(x, y float) *Path {
|
||||||
|
p.appendToPath(LineTo, x, y)
|
||||||
|
p.x = x
|
||||||
|
p.y = y
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Path) RLineTo(dx, dy float) *Path {
|
||||||
|
x, y := p.LastPoint()
|
||||||
|
p.LineTo(x+dx, y+dy)
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Path) Rect(x1, y1, x2, y2 float) *Path {
|
||||||
|
w, h := x2-x1, y2-y1
|
||||||
|
if len(p.commands) > 0 {
|
||||||
|
p.LineTo(x1, y1)
|
||||||
|
} else {
|
||||||
|
p.MoveTo(x1, y1)
|
||||||
|
}
|
||||||
|
p.LineTo(x1+w, y1)
|
||||||
|
p.LineTo(x1+w, y1+h)
|
||||||
|
p.LineTo(x1, y1+h)
|
||||||
|
p.LineTo(x1, y1)
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Path) RRect(dx1, dy1, dx2, dy2 float) *Path {
|
||||||
|
x, y := p.LastPoint()
|
||||||
|
p.Rect(x+dx1, y+dy1, x+dx2, y+dy2)
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Path) QuadCurveTo(cx, cy, x, y float) *Path {
|
||||||
|
p.appendToPath(QuadCurveTo, cx, cy, x, y)
|
||||||
|
p.x = x
|
||||||
|
p.y = y
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Path) RQuadCurveTo(dcx, dcy, dx, dy float) *Path {
|
||||||
|
x, y := p.LastPoint()
|
||||||
|
p.RQuadCurveTo(x+dcx, y+dcy, x+dx, y+dy)
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Path) CubicCurveTo(cx1, cy1, cx2, cy2, x, y float) *Path {
|
||||||
|
p.appendToPath(CubicCurveTo, cx1, cy1, cx2, cy2, x, y)
|
||||||
|
p.x = x
|
||||||
|
p.y = y
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Path) RCubicCurveTo(dcx1, dcy1, dcx2, dcy2, dx, dy float) *Path {
|
||||||
|
x, y := p.LastPoint()
|
||||||
|
p.RCubicCurveTo(x+dcx1, y+dcy1, x+dcx2, y+dcy2, x+dx, y+dy)
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Path) ArcTo(cx, cy, rx, ry, startAngle, angle float) *Path {
|
||||||
|
endAngle := startAngle + angle
|
||||||
|
clockWise := true
|
||||||
|
if angle < 0 {
|
||||||
|
clockWise = false
|
||||||
|
}
|
||||||
|
// normalize
|
||||||
|
if clockWise {
|
||||||
|
for endAngle < startAngle {
|
||||||
|
endAngle += math.Pi * 2.0
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for startAngle < endAngle {
|
||||||
|
startAngle += math.Pi * 2.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
startX := cx + cos(startAngle)*rx
|
||||||
|
startY := cy + sin(startAngle)*ry
|
||||||
|
if len(p.commands) > 0 {
|
||||||
|
p.LineTo(startX, startY)
|
||||||
|
} else {
|
||||||
|
p.MoveTo(startX, startY)
|
||||||
|
}
|
||||||
|
p.appendToPath(ArcTo, cx, cy, rx, ry, startAngle, angle)
|
||||||
|
p.x = cx + cos(endAngle)*rx
|
||||||
|
p.y = cy + sin(endAngle)*ry
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Path) RArcTo(dcx, dcy, rx, ry, startAngle, angle float) *Path {
|
||||||
|
x, y := p.LastPoint()
|
||||||
|
p.RArcTo(x+dcx, y+dcy, rx, ry, startAngle, angle)
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Path) TraceLine(tracer LineTracer) {
|
||||||
|
j := 0
|
||||||
|
x, y := 0.0, 0.0
|
||||||
|
firstX, firstY := x, y
|
||||||
|
if len(p.commands) > 0 {
|
||||||
|
if p.commands[0] == MoveTo {
|
||||||
|
firstX, firstY = p.vertices[0], p.vertices[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, cmd := range p.commands {
|
||||||
|
switch cmd {
|
||||||
|
case MoveTo:
|
||||||
|
tracer.MoveTo(p.vertices[j], p.vertices[j+1])
|
||||||
|
x, y = p.vertices[j], p.vertices[j+1]
|
||||||
|
firstX, firstY = x, y
|
||||||
|
j = j + 2
|
||||||
|
case LineTo:
|
||||||
|
tracer.LineTo(p.vertices[j], p.vertices[j+1])
|
||||||
|
x, y = p.vertices[j], p.vertices[j+1]
|
||||||
|
j = j + 2
|
||||||
|
case QuadCurveTo:
|
||||||
|
quadraticBezier(tracer, x, y, p.vertices[j], p.vertices[j+1], p.vertices[j+2], p.vertices[j+3], 1.0, 0.0)
|
||||||
|
x, y = p.vertices[j+2], p.vertices[j+3]
|
||||||
|
j = j + 4
|
||||||
|
case CubicCurveTo:
|
||||||
|
cubicBezier(tracer, x, y, p.vertices[j], p.vertices[j+1], p.vertices[j+2], p.vertices[j+3], p.vertices[j+4], p.vertices[j+5], 1.0, 0.0, 0.0)
|
||||||
|
x, y = p.vertices[j+4], p.vertices[j+5]
|
||||||
|
j = j + 6
|
||||||
|
case ArcTo:
|
||||||
|
arc(tracer, p.vertices[j], p.vertices[j+1], p.vertices[j+2], p.vertices[j+3], p.vertices[j+4], p.vertices[j+5], 1)
|
||||||
|
j = j + 6
|
||||||
|
case Close:
|
||||||
|
tracer.LineTo(firstX, firstY)
|
||||||
|
x, y = firstX, firstY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Path) String() string {
|
||||||
|
s := ""
|
||||||
|
j := 0
|
||||||
|
for _, cmd := range p.commands {
|
||||||
|
switch cmd {
|
||||||
|
case MoveTo:
|
||||||
|
s += fmt.Sprintf("MoveTo: %f, %f\n", p.vertices[j], p.vertices[j+1])
|
||||||
|
j = j + 2
|
||||||
|
case LineTo:
|
||||||
|
s += fmt.Sprintf("LineTo: %f, %f\n", p.vertices[j], p.vertices[j+1])
|
||||||
|
j = j + 2
|
||||||
|
case QuadCurveTo:
|
||||||
|
s += fmt.Sprintf("QuadCurveTo: %f, %f, %f, %f\n", p.vertices[j], p.vertices[j+1], p.vertices[j+2], p.vertices[j+3])
|
||||||
|
j = j + 4
|
||||||
|
case CubicCurveTo:
|
||||||
|
s += fmt.Sprintf("CubicCurveTo: %f, %f, %f, %f, %f, %f\n", p.vertices[j], p.vertices[j+1], p.vertices[j+2], p.vertices[j+3], p.vertices[j+4], p.vertices[j+5])
|
||||||
|
j = j + 6
|
||||||
|
case ArcTo:
|
||||||
|
s += fmt.Sprintf("ArcTo: %f, %f, %f, %f, %f, %f\n", p.vertices[j], p.vertices[j+1], p.vertices[j+2], p.vertices[j+3], p.vertices[j+4], p.vertices[j+5])
|
||||||
|
j = j + 6
|
||||||
|
case Close:
|
||||||
|
s += "Close\n"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
Loading…
Reference in a new issue