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