diff --git a/draw2d/curve/Makefile b/draw2d/curve/Makefile index 3743daf..f7939a1 100644 --- a/draw2d/curve/Makefile +++ b/draw2d/curve/Makefile @@ -2,6 +2,8 @@ include $(GOROOT)/src/Make.inc TARG=draw2d.googlecode.com/hg/draw2d/curve GOFILES=\ - curve_float64.go\ - + cubic_float64.go\ + quad_float64.go\ + cubic_float64_others.go\ + include $(GOROOT)/src/Make.pkg diff --git a/draw2d/curve/_testmain.go b/draw2d/curve/_testmain.go new file mode 100644 index 0000000..1f48f29 --- /dev/null +++ b/draw2d/curve/_testmain.go @@ -0,0 +1,41 @@ +package main + +import "draw2d.googlecode.com/hg/draw2d/curve" +import "testing" +import __os__ "os" +import __regexp__ "regexp" + +var tests = []testing.InternalTest{ + {"curve.TestCubicCurveRec", curve.TestCubicCurveRec}, + {"curve.TestCubicCurve", curve.TestCubicCurve}, + {"curve.TestCubicCurveAdaptiveRec", curve.TestCubicCurveAdaptiveRec}, + {"curve.TestCubicCurveAdaptive", curve.TestCubicCurveAdaptive}, + {"curve.TestCubicCurveParabolic", curve.TestCubicCurveParabolic}, + {"curve.TestQuadCurve", curve.TestQuadCurve}, +} + +var benchmarks = []testing.InternalBenchmark{{"curve.BenchmarkCubicCurveRec", curve.BenchmarkCubicCurveRec}, + {"curve.BenchmarkCubicCurve", curve.BenchmarkCubicCurve}, + {"curve.BenchmarkCubicCurveAdaptiveRec", curve.BenchmarkCubicCurveAdaptiveRec}, + {"curve.BenchmarkCubicCurveAdaptive", curve.BenchmarkCubicCurveAdaptive}, + {"curve.BenchmarkCubicCurveParabolic", curve.BenchmarkCubicCurveParabolic}, + {"curve.BenchmarkQuadCurve", curve.BenchmarkQuadCurve}, +} + +var matchPat string +var matchRe *__regexp__.Regexp + +func matchString(pat, str string) (result bool, err __os__.Error) { + if matchRe == nil || matchPat != pat { + matchPat = pat + matchRe, err = __regexp__.Compile(matchPat) + if err != nil { + return + } + } + return matchRe.MatchString(str), nil +} + +func main() { + testing.Main(matchString, tests, benchmarks) +} diff --git a/draw2d/curve/cubic_float64.go b/draw2d/curve/cubic_float64.go new file mode 100644 index 0000000..f73f34e --- /dev/null +++ b/draw2d/curve/cubic_float64.go @@ -0,0 +1,69 @@ +// Copyright 2010 The draw2d Authors. All rights reserved. +// created: 17/05/2011 by Laurent Le Goff +package curve + +import ( + "math" +) + +const ( + CurveRecursionLimit = 32 +) + +type CubicCurveFloat64 struct { + X1, Y1, X2, Y2, X3, Y3, X4, Y4 float64 +} + +type LineTracer interface { + LineTo(x, y float64) +} + +func (c *CubicCurveFloat64) Subdivide(c1, c2 *CubicCurveFloat64) (x23, y23 float64) { + // Calculate all the mid-points of the line segments + //---------------------- + c1.X1, c1.Y1 = c.X1, c.Y1 + c2.X4, c2.Y4 = c.X4, c.Y4 + c1.X2 = (c.X1 + c.X2) / 2 + c1.Y2 = (c.Y1 + c.Y2) / 2 + x23 = (c.X2 + c.X3) / 2 + y23 = (c.Y2 + c.Y3) / 2 + c2.X3 = (c.X3 + c.X4) / 2 + c2.Y3 = (c.Y3 + c.Y4) / 2 + c1.X3 = (c1.X2 + x23) / 2 + c1.Y3 = (c1.Y2 + y23) / 2 + c2.X2 = (x23 + c2.X3) / 2 + c2.Y2 = (y23 + c2.Y3) / 2 + c1.X4 = (c1.X3 + c2.X2) / 2 + c1.Y4 = (c1.Y3 + c2.Y2) / 2 + c2.X1, c2.Y1 = c1.X4, c1.Y4 + return +} + +func (curve *CubicCurveFloat64) Segment(t LineTracer, flattening_threshold float64) { + // Add the first point + t.LineTo(curve.X1, curve.Y1) + + var curves [CurveRecursionLimit]CubicCurveFloat64 + curves[0] = *curve + i := 0 + // current curve + var c *CubicCurveFloat64 + var dx, dy, d2, d3 float64 + for i >= 0 { + c = &curves[i] + dx = c.X4 - c.X1 + dy = c.Y4 - c.Y1 + + d2 = math.Fabs(((c.X2-c.X4)*dy - (c.Y2-c.Y4)*dx)) + d3 = math.Fabs(((c.X3-c.X4)*dy - (c.Y3-c.Y4)*dx)) + + if (d2+d3)*(d2+d3) < flattening_threshold*(dx*dx+dy*dy) || i == len(curves)-1 { + t.LineTo(c.X4, c.Y4) + i-- + } else { + // second half of bezier go lower onto the stack + c.Subdivide(&curves[i+1], &curves[i]) + i++ + } + } +} diff --git a/draw2d/curve/curve_float64.go b/draw2d/curve/cubic_float64_others.go similarity index 93% rename from draw2d/curve/curve_float64.go rename to draw2d/curve/cubic_float64_others.go index ba7eb33..4e71fc9 100644 --- a/draw2d/curve/curve_float64.go +++ b/draw2d/curve/cubic_float64_others.go @@ -7,19 +7,10 @@ import ( ) const ( - CurveRecursionLimit = 32 CurveCollinearityEpsilon = 1e-30 CurveAngleToleranceEpsilon = 0.01 ) -type CubicCurveFloat64 struct { - X1, Y1, X2, Y2, X3, Y3, X4, Y4 float64 -} - -type LineTracer interface { - LineTo(x, y float64) -} - //mu ranges from 0 to 1, start to end of curve func (c *CubicCurveFloat64) ArbitraryPoint(mu float64) (x, y float64) { @@ -60,27 +51,6 @@ func (c *CubicCurveFloat64) SubdivideAt(c1, c2 *CubicCurveFloat64, t float64) (x return } -func (c *CubicCurveFloat64) Subdivide(c1, c2 *CubicCurveFloat64) (x23, y23 float64) { - // Calculate all the mid-points of the line segments - //---------------------- - c1.X1, c1.Y1 = c.X1, c.Y1 - c2.X4, c2.Y4 = c.X4, c.Y4 - c1.X2 = (c.X1 + c.X2) / 2 - c1.Y2 = (c.Y1 + c.Y2) / 2 - x23 = (c.X2 + c.X3) / 2 - y23 = (c.Y2 + c.Y3) / 2 - c2.X3 = (c.X3 + c.X4) / 2 - c2.Y3 = (c.Y3 + c.Y4) / 2 - c1.X3 = (c1.X2 + x23) / 2 - c1.Y3 = (c1.Y2 + y23) / 2 - c2.X2 = (x23 + c2.X3) / 2 - c2.Y2 = (y23 + c2.Y3) / 2 - c1.X4 = (c1.X3 + c2.X2) / 2 - c1.Y4 = (c1.Y3 + c2.Y2) / 2 - c2.X1, c2.Y1 = c1.X4, c1.Y4 - return -} - func (c *CubicCurveFloat64) EstimateDistance() float64 { dx1 := c.X2 - c.X1 dy1 := c.Y2 - c.Y1 @@ -121,35 +91,6 @@ func (c *CubicCurveFloat64) segmentRec(t LineTracer, flattening_threshold float6 c2.segmentRec(t, flattening_threshold) } -func (curve *CubicCurveFloat64) Segment(t LineTracer, flattening_threshold float64) { - // Add the first point - t.LineTo(curve.X1, curve.Y1) - - var curves [CurveRecursionLimit]CubicCurveFloat64 - curves[0] = *curve - i := 0 - // current curve - var c *CubicCurveFloat64 - var dx, dy, d2, d3 float64 - for i >= 0 { - c = &curves[i] - dx = c.X4 - c.X1 - dy = c.Y4 - c.Y1 - - d2 = math.Fabs(((c.X2-c.X4)*dy - (c.Y2-c.Y4)*dx)) - d3 = math.Fabs(((c.X3-c.X4)*dy - (c.Y3-c.Y4)*dx)) - - if (d2+d3)*(d2+d3) < flattening_threshold*(dx*dx+dy*dy) || i == len(curves)-1 { - t.LineTo(c.X4, c.Y4) - i-- - } else { - // second half of bezier go lower onto the stack - c.Subdivide(&curves[i+1], &curves[i]) - i++ - } - } -} - /* The function has the following parameters: approximationScale : diff --git a/draw2d/curve/curve_test.go b/draw2d/curve/curve_test.go index c43fbf9..038b25d 100644 --- a/draw2d/curve/curve_test.go +++ b/draw2d/curve/curve_test.go @@ -15,7 +15,7 @@ import ( var ( flattening_threshold float64 = 0.25 - testsFloat64 = []CubicCurveFloat64{ + testsCubicFloat64 = []CubicCurveFloat64{ CubicCurveFloat64{100, 100, 200, 100, 100, 200, 200, 200}, CubicCurveFloat64{100, 100, 300, 200, 200, 200, 300, 100}, CubicCurveFloat64{100, 100, 0, 300, 200, 0, 300, 300}, @@ -23,6 +23,14 @@ var ( CubicCurveFloat64{10, 290, 10, 10, 290, 10, 290, 290}, CubicCurveFloat64{100, 290, 290, 10, 10, 10, 200, 290}, } + testsQuadFloat64 = []QuadCurveFloat64{ + QuadCurveFloat64{100, 100, 200, 100, 200, 200}, + QuadCurveFloat64{100, 100, 290, 200, 290, 100}, + QuadCurveFloat64{100, 100, 0, 290, 200, 290}, + QuadCurveFloat64{150, 290, 10, 10, 290, 290}, + QuadCurveFloat64{10, 290, 10, 10, 290, 290}, + QuadCurveFloat64{100, 290, 290, 10, 120, 290}, + } ) type Path struct { @@ -50,9 +58,12 @@ func init() { defer f.Close() log.Printf("Create html viewer") f.Write([]byte("")) - for i := 0; i < len(testsFloat64); i++ { + for i := 0; i < len(testsCubicFloat64); i++ { f.Write([]byte(fmt.Sprintf("
\n\n\n\n\n
\n", i, i, i, i, i))) } + for i := 0; i < len(testsQuadFloat64); i++ { + f.Write([]byte(fmt.Sprintf("
\n
\n", i))) + } f.Write([]byte("")) } @@ -95,7 +106,7 @@ func drawPoints(img draw.Image, c image.Color, s ...float64) image.Image { } func TestCubicCurveRec(t *testing.T) { - for i, curve := range testsFloat64 { + for i, curve := range testsCubicFloat64 { var p Path curve.SegmentRec(&p, flattening_threshold) img := image.NewNRGBA(300, 300) @@ -110,7 +121,7 @@ func TestCubicCurveRec(t *testing.T) { } func TestCubicCurve(t *testing.T) { - for i, curve := range testsFloat64 { + for i, curve := range testsCubicFloat64 { var p Path curve.Segment(&p, flattening_threshold) img := image.NewNRGBA(300, 300) @@ -125,7 +136,7 @@ func TestCubicCurve(t *testing.T) { } func TestCubicCurveAdaptiveRec(t *testing.T) { - for i, curve := range testsFloat64 { + for i, curve := range testsCubicFloat64 { var p Path curve.AdaptiveSegmentRec(&p, 1, 0, 0) img := image.NewNRGBA(300, 300) @@ -140,7 +151,7 @@ func TestCubicCurveAdaptiveRec(t *testing.T) { } func TestCubicCurveAdaptive(t *testing.T) { - for i, curve := range testsFloat64 { + for i, curve := range testsCubicFloat64 { var p Path curve.AdaptiveSegment(&p, 1, 0, 0) img := image.NewNRGBA(300, 300) @@ -155,7 +166,7 @@ func TestCubicCurveAdaptive(t *testing.T) { } func TestCubicCurveParabolic(t *testing.T) { - for i, curve := range testsFloat64 { + for i, curve := range testsCubicFloat64 { var p Path curve.ParabolicSegment(&p, flattening_threshold) img := image.NewNRGBA(300, 300) @@ -169,9 +180,24 @@ func TestCubicCurveParabolic(t *testing.T) { fmt.Println() } + +func TestQuadCurve(t *testing.T) { + for i, curve := range testsQuadFloat64 { + var p Path + curve.Segment(&p, flattening_threshold) + img := image.NewNRGBA(300, 300) + raster.PolylineBresenham(img, image.NRGBAColor{0xff, 0, 0, 0xff}, curve.X1, curve.Y1, curve.X2, curve.Y2, curve.X3, curve.Y3) + raster.PolylineBresenham(img, image.Black, p.points...) + drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, p.points...) + savepng(fmt.Sprintf("_testQuad%d.png", i), img) + log.Printf("Num of points: %d\n", len(p.points)) + } + fmt.Println() +} + func BenchmarkCubicCurveRec(b *testing.B) { for i := 0; i < b.N; i++ { - for _, curve := range testsFloat64 { + for _, curve := range testsCubicFloat64 { p := Path{make([]float64, 0, 32)} curve.SegmentRec(&p, flattening_threshold) } @@ -180,7 +206,7 @@ func BenchmarkCubicCurveRec(b *testing.B) { func BenchmarkCubicCurve(b *testing.B) { for i := 0; i < b.N; i++ { - for _, curve := range testsFloat64 { + for _, curve := range testsCubicFloat64 { p := Path{make([]float64, 0, 32)} curve.Segment(&p, flattening_threshold) } @@ -189,7 +215,7 @@ func BenchmarkCubicCurve(b *testing.B) { func BenchmarkCubicCurveAdaptiveRec(b *testing.B) { for i := 0; i < b.N; i++ { - for _, curve := range testsFloat64 { + for _, curve := range testsCubicFloat64 { p := Path{make([]float64, 0, 32)} curve.AdaptiveSegmentRec(&p, 1, 0, 0) } @@ -198,7 +224,7 @@ func BenchmarkCubicCurveAdaptiveRec(b *testing.B) { func BenchmarkCubicCurveAdaptive(b *testing.B) { for i := 0; i < b.N; i++ { - for _, curve := range testsFloat64 { + for _, curve := range testsCubicFloat64 { p := Path{make([]float64, 0, 32)} curve.AdaptiveSegment(&p, 1, 0, 0) } @@ -207,9 +233,18 @@ func BenchmarkCubicCurveAdaptive(b *testing.B) { func BenchmarkCubicCurveParabolic(b *testing.B) { for i := 0; i < b.N; i++ { - for _, curve := range testsFloat64 { + for _, curve := range testsCubicFloat64 { p := Path{make([]float64, 0, 32)} curve.ParabolicSegment(&p, flattening_threshold) } } } + +func BenchmarkQuadCurve(b *testing.B) { + for i := 0; i < b.N; i++ { + for _, curve := range testsQuadFloat64 { + p := Path{make([]float64, 0, 32)} + curve.Segment(&p, flattening_threshold) + } + } +} diff --git a/draw2d/curve/quad_float64.go b/draw2d/curve/quad_float64.go new file mode 100644 index 0000000..ab30c70 --- /dev/null +++ b/draw2d/curve/quad_float64.go @@ -0,0 +1,56 @@ +// Copyright 2010 The draw2d Authors. All rights reserved. +// created: 17/05/2011 by Laurent Le Goff +package curve + +import ( + "math" +) + +type QuadCurveFloat64 struct { + X1, Y1, X2, Y2, X3, Y3 float64 +} + + +func (c *QuadCurveFloat64) Subdivide(c1, c2 *QuadCurveFloat64) { + // Calculate all the mid-points of the line segments + //---------------------- + c1.X1, c1.Y1 = c.X1, c.Y1 + c2.X3, c2.Y3 = c.X3, c.Y3 + c1.X2 = (c.X1 + c.X2) / 2 + c1.Y2 = (c.Y1 + c.Y2) / 2 + c2.X2 = (c.X2 + c.X3) / 2 + c2.Y2 = (c.Y2 + c.Y3) / 2 + c1.X3 = (c1.X2 + c2.X2) / 2 + c1.Y3 = (c1.Y2 + c2.Y2) / 2 + c2.X1, c2.Y1 = c1.X3, c1.Y3 + return +} + + +func (curve *QuadCurveFloat64) Segment(t LineTracer, flattening_threshold float64) { + // Add the first point + t.LineTo(curve.X1, curve.Y1) + + var curves [CurveRecursionLimit]QuadCurveFloat64 + curves[0] = *curve + i := 0 + // current curve + var c *QuadCurveFloat64 + var dx, dy, d float64 + for i >= 0 { + c = &curves[i] + dx = c.X3 - c.X1 + dy = c.Y3 - c.Y1 + + d = math.Fabs(((c.X2-c.X3)*dy - (c.Y2-c.Y3)*dx)) + + if (d*d) < flattening_threshold*(dx*dx+dy*dy) || i == len(curves)-1 { + t.LineTo(c.X3, c.Y3) + i-- + } else { + // second half of bezier go lower onto the stack + c.Subdivide(&curves[i+1], &curves[i]) + i++ + } + } +}