diff --git a/draw2d/curve/_testmain.go b/draw2d/curve/_testmain.go
index 3341fc4..b411f51 100644
--- a/draw2d/curve/_testmain.go
+++ b/draw2d/curve/_testmain.go
@@ -2,7 +2,7 @@ package main
import "draw2d.googlecode.com/hg/draw2d/curve"
import "testing"
-import __os__ "os"
+import __os__ "os"
import __regexp__ "regexp"
var tests = []testing.InternalTest{
diff --git a/draw2d/curve/cubic_float64.go b/draw2d/curve/cubic_float64.go
index b1d379a..d33c582 100644
--- a/draw2d/curve/cubic_float64.go
+++ b/draw2d/curve/cubic_float64.go
@@ -1,68 +1,67 @@
// 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) {
+package curve
+
+import (
+ "math"
+)
+
+const (
+ CurveRecursionLimit = 32
+)
+
+// X1, Y1, X2, Y2, X3, Y3, X4, Y4 float64
+type CubicCurveFloat64 [8]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) {
- var curves [CurveRecursionLimit]CubicCurveFloat64
- curves[0] = *curve
- i := 0
+ c1[0], c1[1] = c[0], c[1]
+ c2[6], c2[7] = c[6], c[7]
+ c1[2] = (c[0] + c[2]) / 2
+ c1[3] = (c[1] + c[3]) / 2
+ x23 = (c[2] + c[4]) / 2
+ y23 = (c[3] + c[5]) / 2
+ c2[4] = (c[4] + c[6]) / 2
+ c2[5] = (c[5] + c[7]) / 2
+ c1[4] = (c1[2] + x23) / 2
+ c1[5] = (c1[3] + y23) / 2
+ c2[2] = (x23 + c2[4]) / 2
+ c2[3] = (y23 + c2[5]) / 2
+ c1[6] = (c1[4] + c2[2]) / 2
+ c1[7] = (c1[5] + c2[3]) / 2
+ c2[0], c2[1] = c1[6], c1[7]
+ return
+}
+
+func (curve *CubicCurveFloat64) Segment(t LineTracer, flattening_threshold float64) {
+ 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 {
+ var c *CubicCurveFloat64
+
+ var dx, dy, d2, d3 float64
+
+ for i >= 0 {
+ c = &curves[i]
+ dx = c[6] - c[0]
+ dy = c[7] - c[1]
+
+ d2 = math.Fabs(((c[2]-c[6])*dy - (c[3]-c[7])*dx))
+ d3 = math.Fabs(((c[4]-c[6])*dy - (c[5]-c[7])*dx))
+
+ if (d2+d3)*(d2+d3) < flattening_threshold*(dx*dx+dy*dy) || i == len(curves)-1 {
+ t.LineTo(c[6], c[7])
+ i--
+ } else {
// second half of bezier go lower onto the stack
- c.Subdivide(&curves[i+1], &curves[i])
- i++
- }
- }
-}
+ c.Subdivide(&curves[i+1], &curves[i])
+ i++
+ }
+ }
+}
diff --git a/draw2d/curve/cubic_float64_others.go b/draw2d/curve/cubic_float64_others.go
index d703187..4b915e8 100644
--- a/draw2d/curve/cubic_float64_others.go
+++ b/draw2d/curve/cubic_float64_others.go
@@ -1,94 +1,94 @@
// Copyright 2010 The draw2d Authors. All rights reserved.
// created: 17/05/2011 by Laurent Le Goff
-package curve
-
-import (
- "math"
-)
-
-const (
- CurveCollinearityEpsilon = 1e-30
- CurveAngleToleranceEpsilon = 0.01
-)
-
-
+package curve
+
+import (
+ "math"
+)
+
+const (
+ CurveCollinearityEpsilon = 1e-30
+ CurveAngleToleranceEpsilon = 0.01
+)
+
+
//mu ranges from 0 to 1, start to end of curve
-func (c *CubicCurveFloat64) ArbitraryPoint(mu float64) (x, y float64) {
-
- mum1 := 1 - mu
- mum13 := mum1 * mum1 * mum1
- mu3 := mu * mu * mu
-
- x = mum13*c.X1 + 3*mu*mum1*mum1*c.X2 + 3*mu*mu*mum1*c.X3 + mu3*c.X4
- y = mum13*c.Y1 + 3*mu*mum1*mum1*c.Y2 + 3*mu*mu*mum1*c.Y3 + mu3*c.Y4
- return
-}
-
-func (c *CubicCurveFloat64) SubdivideAt(c1, c2 *CubicCurveFloat64, t float64) (x23, y23 float64) {
- inv_t := (1 - t)
- c1.X1, c1.Y1 = c.X1, c.Y1
- c2.X4, c2.Y4 = c.X4, c.Y4
-
- c1.X2 = inv_t*c.X1 + t*c.X2
- c1.Y2 = inv_t*c.Y1 + t*c.Y2
-
- x23 = inv_t*c.X2 + t*c.X3
- y23 = inv_t*c.Y2 + t*c.Y3
-
- c2.X3 = inv_t*c.X3 + t*c.X4
- c2.Y3 = inv_t*c.Y3 + t*c.Y4
-
- c1.X3 = inv_t*c1.X2 + t*x23
- c1.Y3 = inv_t*c1.Y2 + t*y23
-
- c2.X2 = inv_t*x23 + t*c2.X3
- c2.Y2 = inv_t*y23 + t*c2.Y3
-
- c1.X4 = inv_t*c1.X3 + t*c2.X2
- c1.Y4 = inv_t*c1.Y3 + t*c2.Y2
-
- c2.X1, c2.Y1 = c1.X4, c1.Y4
- return
-}
-
-func (c *CubicCurveFloat64) EstimateDistance() float64 {
- dx1 := c.X2 - c.X1
- dy1 := c.Y2 - c.Y1
- dx2 := c.X3 - c.X2
- dy2 := c.Y3 - c.Y2
- dx3 := c.X4 - c.X3
- dy3 := c.Y4 - c.Y3
- return math.Sqrt(dx1*dx1+dy1*dy1) + math.Sqrt(dx2*dx2+dy2*dy2) + math.Sqrt(dx3*dx3+dy3*dy3)
-}
-
+func (c *CubicCurveFloat64) ArbitraryPoint(mu float64) (x, y float64) {
+
+ mum1 := 1 - mu
+ mum13 := mum1 * mum1 * mum1
+ mu3 := mu * mu * mu
+
+ x = mum13*c[0] + 3*mu*mum1*mum1*c[2] + 3*mu*mu*mum1*c[4] + mu3*c[6]
+ y = mum13*c[1] + 3*mu*mum1*mum1*c[3] + 3*mu*mu*mum1*c[5] + mu3*c[7]
+ return
+}
+
+func (c *CubicCurveFloat64) SubdivideAt(c1, c2 *CubicCurveFloat64, t float64) (x23, y23 float64) {
+ inv_t := (1 - t)
+ c1[0], c1[1] = c[0], c[1]
+ c2[6], c2[7] = c[6], c[7]
+
+ c1[2] = inv_t*c[0] + t*c[2]
+ c1[3] = inv_t*c[1] + t*c[3]
+
+ x23 = inv_t*c[2] + t*c[4]
+ y23 = inv_t*c[3] + t*c[5]
+
+ c2[4] = inv_t*c[4] + t*c[6]
+ c2[5] = inv_t*c[5] + t*c[7]
+
+ c1[4] = inv_t*c1[2] + t*x23
+ c1[5] = inv_t*c1[3] + t*y23
+
+ c2[2] = inv_t*x23 + t*c2[4]
+ c2[3] = inv_t*y23 + t*c2[5]
+
+ c1[6] = inv_t*c1[4] + t*c2[2]
+ c1[7] = inv_t*c1[5] + t*c2[3]
+
+ c2[0], c2[1] = c1[6], c1[7]
+ return
+}
+
+func (c *CubicCurveFloat64) EstimateDistance() float64 {
+ dx1 := c[2] - c[0]
+ dy1 := c[3] - c[1]
+ dx2 := c[4] - c[2]
+ dy2 := c[5] - c[3]
+ dx3 := c[6] - c[4]
+ dy3 := c[7] - c[5]
+ return math.Sqrt(dx1*dx1+dy1*dy1) + math.Sqrt(dx2*dx2+dy2*dy2) + math.Sqrt(dx3*dx3+dy3*dy3)
+}
+
// subdivide the curve in straight lines using line approximation and Casteljau recursive subdivision
-func (c *CubicCurveFloat64) SegmentRec(t LineTracer, flattening_threshold float64) {
- c.segmentRec(t, flattening_threshold)
- t.LineTo(c.X4, c.Y4)
-}
-
-func (c *CubicCurveFloat64) segmentRec(t LineTracer, flattening_threshold float64) {
- var c1, c2 CubicCurveFloat64
- c.Subdivide(&c1, &c2)
-
+func (c *CubicCurveFloat64) SegmentRec(t LineTracer, flattening_threshold float64) {
+ c.segmentRec(t, flattening_threshold)
+ t.LineTo(c[6], c[7])
+}
+
+func (c *CubicCurveFloat64) segmentRec(t LineTracer, flattening_threshold float64) {
+ var c1, c2 CubicCurveFloat64
+ c.Subdivide(&c1, &c2)
+
// Try to approximate the full cubic curve by a single straight line
//------------------
- 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) {
- t.LineTo(c.X4, c.Y4)
- return
- }
+ dx := c[6] - c[0]
+ dy := c[7] - c[1]
+
+ d2 := math.Fabs(((c[2]-c[6])*dy - (c[3]-c[7])*dx))
+ d3 := math.Fabs(((c[4]-c[6])*dy - (c[5]-c[7])*dx))
+
+ if (d2+d3)*(d2+d3) < flattening_threshold*(dx*dx+dy*dy) {
+ t.LineTo(c[6], c[7])
+ return
+ }
// Continue subdivision
//----------------------
- c1.segmentRec(t, flattening_threshold)
- c2.segmentRec(t, flattening_threshold)
-}
-
+ c1.segmentRec(t, flattening_threshold)
+ c2.segmentRec(t, flattening_threshold)
+}
+
/*
The function has the following parameters:
approximationScale :
@@ -108,593 +108,593 @@ func (c *CubicCurveFloat64) segmentRec(t LineTracer, flattening_threshold float6
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 (c *CubicCurveFloat64) AdaptiveSegmentRec(t LineTracer, approximationScale, angleTolerance, cuspLimit float64) {
- cuspLimit = computeCuspLimit(cuspLimit)
- distanceToleranceSquare := 0.5 / approximationScale
- distanceToleranceSquare = distanceToleranceSquare * distanceToleranceSquare
- c.adaptiveSegmentRec(t, 0, distanceToleranceSquare, angleTolerance, cuspLimit)
- t.LineTo(c.X4, c.Y4)
-}
-
-func computeCuspLimit(v float64) (r float64) {
- if v == 0.0 {
- r = 0.0
- } else {
- r = math.Pi - v
- }
- return
-}
-
-func squareDistance(x1, y1, x2, y2 float64) float64 {
- dx := x2 - x1
- dy := y2 - y1
- return dx*dx + dy*dy
-}
-
+*/
+func (c *CubicCurveFloat64) AdaptiveSegmentRec(t LineTracer, approximationScale, angleTolerance, cuspLimit float64) {
+ cuspLimit = computeCuspLimit(cuspLimit)
+ distanceToleranceSquare := 0.5 / approximationScale
+ distanceToleranceSquare = distanceToleranceSquare * distanceToleranceSquare
+ c.adaptiveSegmentRec(t, 0, distanceToleranceSquare, angleTolerance, cuspLimit)
+ t.LineTo(c[6], c[7])
+}
+
+func computeCuspLimit(v float64) (r float64) {
+ if v == 0.0 {
+ r = 0.0
+ } else {
+ r = math.Pi - v
+ }
+ return
+}
+
+func squareDistance(x1, y1, x2, y2 float64) float64 {
+ dx := x2 - x1
+ dy := y2 - y1
+ return dx*dx + dy*dy
+}
+
/**
* http://www.antigrain.com/research/adaptive_bezier/index.html
- */
-func (c *CubicCurveFloat64) adaptiveSegmentRec(t LineTracer, level int, distanceToleranceSquare, angleTolerance, cuspLimit float64) {
- if level > CurveRecursionLimit {
- return
- }
- var c1, c2 CubicCurveFloat64
- x23, y23 := c.Subdivide(&c1, &c2)
-
+ */
+func (c *CubicCurveFloat64) adaptiveSegmentRec(t LineTracer, level int, distanceToleranceSquare, angleTolerance, cuspLimit float64) {
+ if level > CurveRecursionLimit {
+ return
+ }
+ var c1, c2 CubicCurveFloat64
+ x23, y23 := c.Subdivide(&c1, &c2)
+
// Try to approximate the full cubic curve by a single straight line
//------------------
- 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))
- switch {
- case d2 <= CurveCollinearityEpsilon && d3 <= CurveCollinearityEpsilon:
+ dx := c[6] - c[0]
+ dy := c[7] - c[1]
+
+ d2 := math.Fabs(((c[2]-c[6])*dy - (c[3]-c[7])*dx))
+ d3 := math.Fabs(((c[4]-c[6])*dy - (c[5]-c[7])*dx))
+ switch {
+ case d2 <= CurveCollinearityEpsilon && d3 <= CurveCollinearityEpsilon:
// All collinear OR p1==p4
//----------------------
- k := dx*dx + dy*dy
- if k == 0 {
- d2 = squareDistance(c.X1, c.Y1, c.X2, c.Y2)
- d3 = squareDistance(c.X4, c.Y4, c.X3, c.Y3)
- } else {
- k = 1 / k
- da1 := c.X2 - c.X1
- da2 := c.Y2 - c.Y1
- d2 = k * (da1*dx + da2*dy)
- da1 = c.X3 - c.X1
- da2 = c.Y3 - c.Y1
- d3 = k * (da1*dx + da2*dy)
- if d2 > 0 && d2 < 1 && d3 > 0 && d3 < 1 {
+ k := dx*dx + dy*dy
+ if k == 0 {
+ d2 = squareDistance(c[0], c[1], c[2], c[3])
+ d3 = squareDistance(c[6], c[7], c[4], c[5])
+ } else {
+ k = 1 / k
+ da1 := c[2] - c[0]
+ da2 := c[3] - c[1]
+ d2 = k * (da1*dx + da2*dy)
+ da1 = c[4] - c[0]
+ da2 = c[5] - c[1]
+ 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(c.X2, c.Y2, c.X1, c.Y1)
- } else if d2 >= 1 {
- d2 = squareDistance(c.X2, c.Y2, c.X4, c.Y4)
- } else {
- d2 = squareDistance(c.X2, c.Y2, c.X1+d2*dx, c.Y1+d2*dy)
- }
-
- if d3 <= 0 {
- d3 = squareDistance(c.X3, c.Y3, c.X1, c.Y1)
- } else if d3 >= 1 {
- d3 = squareDistance(c.X3, c.Y3, c.X4, c.Y4)
- } else {
- d3 = squareDistance(c.X3, c.Y3, c.X1+d3*dx, c.Y1+d3*dy)
- }
- }
- if d2 > d3 {
- if d2 < distanceToleranceSquare {
- t.LineTo(c.X2, c.Y2)
- return
- }
- } else {
- if d3 < distanceToleranceSquare {
- t.LineTo(c.X3, c.Y3)
- return
- }
- }
-
- case d2 <= CurveCollinearityEpsilon && d3 > CurveCollinearityEpsilon:
+ return
+ }
+ if d2 <= 0 {
+ d2 = squareDistance(c[2], c[3], c[0], c[1])
+ } else if d2 >= 1 {
+ d2 = squareDistance(c[2], c[3], c[6], c[7])
+ } else {
+ d2 = squareDistance(c[2], c[3], c[0]+d2*dx, c[1]+d2*dy)
+ }
+
+ if d3 <= 0 {
+ d3 = squareDistance(c[4], c[5], c[0], c[1])
+ } else if d3 >= 1 {
+ d3 = squareDistance(c[4], c[5], c[6], c[7])
+ } else {
+ d3 = squareDistance(c[4], c[5], c[0]+d3*dx, c[1]+d3*dy)
+ }
+ }
+ if d2 > d3 {
+ if d2 < distanceToleranceSquare {
+ t.LineTo(c[2], c[3])
+ return
+ }
+ } else {
+ if d3 < distanceToleranceSquare {
+ t.LineTo(c[4], c[5])
+ return
+ }
+ }
+
+ case d2 <= CurveCollinearityEpsilon && d3 > CurveCollinearityEpsilon:
// p1,p2,p4 are collinear, p3 is significant
//----------------------
- if d3*d3 <= distanceToleranceSquare*(dx*dx+dy*dy) {
- if angleTolerance < CurveAngleToleranceEpsilon {
- t.LineTo(x23, y23)
- return
- }
-
+ if d3*d3 <= distanceToleranceSquare*(dx*dx+dy*dy) {
+ if angleTolerance < CurveAngleToleranceEpsilon {
+ t.LineTo(x23, y23)
+ return
+ }
+
// Angle Condition
//----------------------
- da1 := math.Fabs(math.Atan2(c.Y4-c.Y3, c.X4-c.X3) - math.Atan2(c.Y3-c.Y2, c.X3-c.X2))
- if da1 >= math.Pi {
- da1 = 2*math.Pi - da1
- }
-
- if da1 < angleTolerance {
- t.LineTo(c.X2, c.Y2)
- t.LineTo(c.X3, c.Y3)
- return
- }
-
- if cuspLimit != 0.0 {
- if da1 > cuspLimit {
- t.LineTo(c.X3, c.Y3)
- return
- }
- }
- }
-
- case d2 > CurveCollinearityEpsilon && d3 <= CurveCollinearityEpsilon:
+ da1 := math.Fabs(math.Atan2(c[7]-c[5], c[6]-c[4]) - math.Atan2(c[5]-c[3], c[4]-c[2]))
+ if da1 >= math.Pi {
+ da1 = 2*math.Pi - da1
+ }
+
+ if da1 < angleTolerance {
+ t.LineTo(c[2], c[3])
+ t.LineTo(c[4], c[5])
+ return
+ }
+
+ if cuspLimit != 0.0 {
+ if da1 > cuspLimit {
+ t.LineTo(c[4], c[5])
+ return
+ }
+ }
+ }
+
+ case d2 > CurveCollinearityEpsilon && d3 <= CurveCollinearityEpsilon:
// p1,p3,p4 are collinear, p2 is significant
//----------------------
- if d2*d2 <= distanceToleranceSquare*(dx*dx+dy*dy) {
- if angleTolerance < CurveAngleToleranceEpsilon {
- t.LineTo(x23, y23)
- return
- }
-
+ if d2*d2 <= distanceToleranceSquare*(dx*dx+dy*dy) {
+ if angleTolerance < CurveAngleToleranceEpsilon {
+ t.LineTo(x23, y23)
+ return
+ }
+
// Angle Condition
//----------------------
- da1 := math.Fabs(math.Atan2(c.Y3-c.Y2, c.X3-c.X2) - math.Atan2(c.Y2-c.Y1, c.X2-c.X1))
- if da1 >= math.Pi {
- da1 = 2*math.Pi - da1
- }
-
- if da1 < angleTolerance {
- t.LineTo(c.X2, c.Y2)
- t.LineTo(c.X3, c.Y3)
- return
- }
-
- if cuspLimit != 0.0 {
- if da1 > cuspLimit {
- t.LineTo(c.X2, c.Y2)
- return
- }
- }
- }
-
- case d2 > CurveCollinearityEpsilon && d3 > CurveCollinearityEpsilon:
+ da1 := math.Fabs(math.Atan2(c[5]-c[3], c[4]-c[2]) - math.Atan2(c[3]-c[1], c[2]-c[0]))
+ if da1 >= math.Pi {
+ da1 = 2*math.Pi - da1
+ }
+
+ if da1 < angleTolerance {
+ t.LineTo(c[2], c[3])
+ t.LineTo(c[4], c[5])
+ return
+ }
+
+ if cuspLimit != 0.0 {
+ if da1 > cuspLimit {
+ t.LineTo(c[2], c[3])
+ return
+ }
+ }
+ }
+
+ case d2 > CurveCollinearityEpsilon && d3 > CurveCollinearityEpsilon:
// Regular case
//-----------------
- if (d2+d3)*(d2+d3) <= distanceToleranceSquare*(dx*dx+dy*dy) {
+ 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 {
- t.LineTo(x23, y23)
- return
- }
-
+ if angleTolerance < CurveAngleToleranceEpsilon {
+ t.LineTo(x23, y23)
+ return
+ }
+
// Angle & Cusp Condition
//----------------------
- k := math.Atan2(c.Y3-c.Y2, c.X3-c.X2)
- da1 := math.Fabs(k - math.Atan2(c.Y2-c.Y1, c.X2-c.X1))
- da2 := math.Fabs(math.Atan2(c.Y4-c.Y3, c.X4-c.X3) - k)
- if da1 >= math.Pi {
- da1 = 2*math.Pi - da1
- }
- if da2 >= math.Pi {
- da2 = 2*math.Pi - da2
- }
-
- if da1+da2 < angleTolerance {
+ k := math.Atan2(c[5]-c[3], c[4]-c[2])
+ da1 := math.Fabs(k - math.Atan2(c[3]-c[1], c[2]-c[0]))
+ da2 := math.Fabs(math.Atan2(c[7]-c[5], c[6]-c[4]) - 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
//----------------------
- t.LineTo(x23, y23)
- return
- }
-
- if cuspLimit != 0.0 {
- if da1 > cuspLimit {
- t.LineTo(c.X2, c.Y2)
- return
- }
-
- if da2 > cuspLimit {
- t.LineTo(c.X3, c.Y3)
- return
- }
- }
- }
- }
-
+ t.LineTo(x23, y23)
+ return
+ }
+
+ if cuspLimit != 0.0 {
+ if da1 > cuspLimit {
+ t.LineTo(c[2], c[3])
+ return
+ }
+
+ if da2 > cuspLimit {
+ t.LineTo(c[4], c[5])
+ return
+ }
+ }
+ }
+ }
+
// Continue subdivision
//----------------------
- c1.adaptiveSegmentRec(t, level+1, distanceToleranceSquare, angleTolerance, cuspLimit)
- c2.adaptiveSegmentRec(t, level+1, distanceToleranceSquare, angleTolerance, cuspLimit)
-
-}
-
-func (curve *CubicCurveFloat64) AdaptiveSegment(t LineTracer, approximationScale, angleTolerance, cuspLimit float64) {
- cuspLimit = computeCuspLimit(cuspLimit)
- distanceToleranceSquare := 0.5 / approximationScale
- distanceToleranceSquare = distanceToleranceSquare * distanceToleranceSquare
-
- var curves [CurveRecursionLimit]CubicCurveFloat64
- curves[0] = *curve
- i := 0
+ c1.adaptiveSegmentRec(t, level+1, distanceToleranceSquare, angleTolerance, cuspLimit)
+ c2.adaptiveSegmentRec(t, level+1, distanceToleranceSquare, angleTolerance, cuspLimit)
+
+}
+
+func (curve *CubicCurveFloat64) AdaptiveSegment(t LineTracer, approximationScale, angleTolerance, cuspLimit float64) {
+ cuspLimit = computeCuspLimit(cuspLimit)
+ distanceToleranceSquare := 0.5 / approximationScale
+ distanceToleranceSquare = distanceToleranceSquare * distanceToleranceSquare
+
+ var curves [CurveRecursionLimit]CubicCurveFloat64
+ curves[0] = *curve
+ i := 0
// current curve
- var c *CubicCurveFloat64
- var c1, c2 CubicCurveFloat64
- var dx, dy, d2, d3, k, x23, y23 float64
- for i >= 0 {
- c = &curves[i]
- x23, y23 = c.Subdivide(&c1, &c2)
-
+ var c *CubicCurveFloat64
+ var c1, c2 CubicCurveFloat64
+ var dx, dy, d2, d3, k, x23, y23 float64
+ for i >= 0 {
+ c = &curves[i]
+ x23, y23 = c.Subdivide(&c1, &c2)
+
// Try to approximate the full cubic curve by a single straight line
//------------------
- 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))
- switch {
- case i == len(curves)-1:
- t.LineTo(c.X4, c.Y4)
- i--
- continue
- case d2 <= CurveCollinearityEpsilon && d3 <= CurveCollinearityEpsilon:
+ dx = c[6] - c[0]
+ dy = c[7] - c[1]
+
+ d2 = math.Fabs(((c[2]-c[6])*dy - (c[3]-c[7])*dx))
+ d3 = math.Fabs(((c[4]-c[6])*dy - (c[5]-c[7])*dx))
+ switch {
+ case i == len(curves)-1:
+ t.LineTo(c[6], c[7])
+ i--
+ continue
+ case d2 <= CurveCollinearityEpsilon && d3 <= CurveCollinearityEpsilon:
// All collinear OR p1==p4
//----------------------
- k = dx*dx + dy*dy
- if k == 0 {
- d2 = squareDistance(c.X1, c.Y1, c.X2, c.Y2)
- d3 = squareDistance(c.X4, c.Y4, c.X3, c.Y3)
- } else {
- k = 1 / k
- da1 := c.X2 - c.X1
- da2 := c.Y2 - c.Y1
- d2 = k * (da1*dx + da2*dy)
- da1 = c.X3 - c.X1
- da2 = c.Y3 - c.Y1
- d3 = k * (da1*dx + da2*dy)
- if d2 > 0 && d2 < 1 && d3 > 0 && d3 < 1 {
+ k = dx*dx + dy*dy
+ if k == 0 {
+ d2 = squareDistance(c[0], c[1], c[2], c[3])
+ d3 = squareDistance(c[6], c[7], c[4], c[5])
+ } else {
+ k = 1 / k
+ da1 := c[2] - c[0]
+ da2 := c[3] - c[1]
+ d2 = k * (da1*dx + da2*dy)
+ da1 = c[4] - c[0]
+ da2 = c[5] - c[1]
+ 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
- i--
- continue
- }
- if d2 <= 0 {
- d2 = squareDistance(c.X2, c.Y2, c.X1, c.Y1)
- } else if d2 >= 1 {
- d2 = squareDistance(c.X2, c.Y2, c.X4, c.Y4)
- } else {
- d2 = squareDistance(c.X2, c.Y2, c.X1+d2*dx, c.Y1+d2*dy)
- }
-
- if d3 <= 0 {
- d3 = squareDistance(c.X3, c.Y3, c.X1, c.Y1)
- } else if d3 >= 1 {
- d3 = squareDistance(c.X3, c.Y3, c.X4, c.Y4)
- } else {
- d3 = squareDistance(c.X3, c.Y3, c.X1+d3*dx, c.Y1+d3*dy)
- }
- }
- if d2 > d3 {
- if d2 < distanceToleranceSquare {
- t.LineTo(c.X2, c.Y2)
- i--
- continue
- }
- } else {
- if d3 < distanceToleranceSquare {
- t.LineTo(c.X3, c.Y3)
- i--
- continue
- }
- }
-
- case d2 <= CurveCollinearityEpsilon && d3 > CurveCollinearityEpsilon:
+ i--
+ continue
+ }
+ if d2 <= 0 {
+ d2 = squareDistance(c[2], c[3], c[0], c[1])
+ } else if d2 >= 1 {
+ d2 = squareDistance(c[2], c[3], c[6], c[7])
+ } else {
+ d2 = squareDistance(c[2], c[3], c[0]+d2*dx, c[1]+d2*dy)
+ }
+
+ if d3 <= 0 {
+ d3 = squareDistance(c[4], c[5], c[0], c[1])
+ } else if d3 >= 1 {
+ d3 = squareDistance(c[4], c[5], c[6], c[7])
+ } else {
+ d3 = squareDistance(c[4], c[5], c[0]+d3*dx, c[1]+d3*dy)
+ }
+ }
+ if d2 > d3 {
+ if d2 < distanceToleranceSquare {
+ t.LineTo(c[2], c[3])
+ i--
+ continue
+ }
+ } else {
+ if d3 < distanceToleranceSquare {
+ t.LineTo(c[4], c[5])
+ i--
+ continue
+ }
+ }
+
+ case d2 <= CurveCollinearityEpsilon && d3 > CurveCollinearityEpsilon:
// p1,p2,p4 are collinear, p3 is significant
//----------------------
- if d3*d3 <= distanceToleranceSquare*(dx*dx+dy*dy) {
- if angleTolerance < CurveAngleToleranceEpsilon {
- t.LineTo(x23, y23)
- i--
- continue
- }
-
+ if d3*d3 <= distanceToleranceSquare*(dx*dx+dy*dy) {
+ if angleTolerance < CurveAngleToleranceEpsilon {
+ t.LineTo(x23, y23)
+ i--
+ continue
+ }
+
// Angle Condition
//----------------------
- da1 := math.Fabs(math.Atan2(c.Y4-c.Y3, c.X4-c.X3) - math.Atan2(c.Y3-c.Y2, c.X3-c.X2))
- if da1 >= math.Pi {
- da1 = 2*math.Pi - da1
- }
-
- if da1 < angleTolerance {
- t.LineTo(c.X2, c.Y2)
- t.LineTo(c.X3, c.Y3)
- i--
- continue
- }
-
- if cuspLimit != 0.0 {
- if da1 > cuspLimit {
- t.LineTo(c.X3, c.Y3)
- i--
- continue
- }
- }
- }
-
- case d2 > CurveCollinearityEpsilon && d3 <= CurveCollinearityEpsilon:
+ da1 := math.Fabs(math.Atan2(c[7]-c[5], c[6]-c[4]) - math.Atan2(c[5]-c[3], c[4]-c[2]))
+ if da1 >= math.Pi {
+ da1 = 2*math.Pi - da1
+ }
+
+ if da1 < angleTolerance {
+ t.LineTo(c[2], c[3])
+ t.LineTo(c[4], c[5])
+ i--
+ continue
+ }
+
+ if cuspLimit != 0.0 {
+ if da1 > cuspLimit {
+ t.LineTo(c[4], c[5])
+ i--
+ continue
+ }
+ }
+ }
+
+ case d2 > CurveCollinearityEpsilon && d3 <= CurveCollinearityEpsilon:
// p1,p3,p4 are collinear, p2 is significant
//----------------------
- if d2*d2 <= distanceToleranceSquare*(dx*dx+dy*dy) {
- if angleTolerance < CurveAngleToleranceEpsilon {
- t.LineTo(x23, y23)
- i--
- continue
- }
-
+ if d2*d2 <= distanceToleranceSquare*(dx*dx+dy*dy) {
+ if angleTolerance < CurveAngleToleranceEpsilon {
+ t.LineTo(x23, y23)
+ i--
+ continue
+ }
+
// Angle Condition
//----------------------
- da1 := math.Fabs(math.Atan2(c.Y3-c.Y2, c.X3-c.X2) - math.Atan2(c.Y2-c.Y1, c.X2-c.X1))
- if da1 >= math.Pi {
- da1 = 2*math.Pi - da1
- }
-
- if da1 < angleTolerance {
- t.LineTo(c.X2, c.Y2)
- t.LineTo(c.X3, c.Y3)
- i--
- continue
- }
-
- if cuspLimit != 0.0 {
- if da1 > cuspLimit {
- t.LineTo(c.X2, c.Y2)
- i--
- continue
- }
- }
- }
-
- case d2 > CurveCollinearityEpsilon && d3 > CurveCollinearityEpsilon:
+ da1 := math.Fabs(math.Atan2(c[5]-c[3], c[4]-c[2]) - math.Atan2(c[3]-c[1], c[2]-c[0]))
+ if da1 >= math.Pi {
+ da1 = 2*math.Pi - da1
+ }
+
+ if da1 < angleTolerance {
+ t.LineTo(c[2], c[3])
+ t.LineTo(c[4], c[5])
+ i--
+ continue
+ }
+
+ if cuspLimit != 0.0 {
+ if da1 > cuspLimit {
+ t.LineTo(c[2], c[3])
+ i--
+ continue
+ }
+ }
+ }
+
+ case d2 > CurveCollinearityEpsilon && d3 > CurveCollinearityEpsilon:
// Regular case
//-----------------
- if (d2+d3)*(d2+d3) <= distanceToleranceSquare*(dx*dx+dy*dy) {
+ 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 {
- t.LineTo(x23, y23)
- i--
- continue
- }
-
+ if angleTolerance < CurveAngleToleranceEpsilon {
+ t.LineTo(x23, y23)
+ i--
+ continue
+ }
+
// Angle & Cusp Condition
//----------------------
- k := math.Atan2(c.Y3-c.Y2, c.X3-c.X2)
- da1 := math.Fabs(k - math.Atan2(c.Y2-c.Y1, c.X2-c.X1))
- da2 := math.Fabs(math.Atan2(c.Y4-c.Y3, c.X4-c.X3) - k)
- if da1 >= math.Pi {
- da1 = 2*math.Pi - da1
- }
- if da2 >= math.Pi {
- da2 = 2*math.Pi - da2
- }
-
- if da1+da2 < angleTolerance {
+ k := math.Atan2(c[5]-c[3], c[4]-c[2])
+ da1 := math.Fabs(k - math.Atan2(c[3]-c[1], c[2]-c[0]))
+ da2 := math.Fabs(math.Atan2(c[7]-c[5], c[6]-c[4]) - 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
//----------------------
- t.LineTo(x23, y23)
- i--
- continue
- }
-
- if cuspLimit != 0.0 {
- if da1 > cuspLimit {
- t.LineTo(c.X2, c.Y2)
- i--
- continue
- }
-
- if da2 > cuspLimit {
- t.LineTo(c.X3, c.Y3)
- i--
- continue
- }
- }
- }
- }
-
+ t.LineTo(x23, y23)
+ i--
+ continue
+ }
+
+ if cuspLimit != 0.0 {
+ if da1 > cuspLimit {
+ t.LineTo(c[2], c[3])
+ i--
+ continue
+ }
+
+ if da2 > cuspLimit {
+ t.LineTo(c[4], c[5])
+ i--
+ continue
+ }
+ }
+ }
+ }
+
// Continue subdivision
//----------------------
- curves[i+1], curves[i] = c1, c2
- i++
- }
- t.LineTo(curve.X4, curve.Y4)
-}
-
-
-/********************** Ahmad thesis *******************/
-
+ curves[i+1], curves[i] = c1, c2
+ i++
+ }
+ t.LineTo(curve[6], curve[7])
+}
+
+
+/********************** Ahmad thesis *******************/
+
/**************************************************************************************
* This code is the implementation of the Parabolic Approximation (PA). Although *
* it uses recursive subdivision as a safe net for the failing cases, this is an *
* iterative routine and reduces considerably the number of vertices (point) *
* generation. *
-**************************************************************************************/
-
-
-func (c *CubicCurveFloat64) ParabolicSegment(t LineTracer, flattening_threshold float64) {
- estimatedIFP := c.numberOfInflectionPoints()
- if estimatedIFP == 0 {
+**************************************************************************************/
+
+
+func (c *CubicCurveFloat64) ParabolicSegment(t LineTracer, flattening_threshold float64) {
+ estimatedIFP := c.numberOfInflectionPoints()
+ if estimatedIFP == 0 {
// If no inflection points then apply PA on the full Bezier segment.
- c.doParabolicApproximation(t, flattening_threshold)
- return
- }
+ c.doParabolicApproximation(t, flattening_threshold)
+ return
+ }
// If one or more inflection point then we will have to subdivide the curve
- numOfIfP, t1, t2 := c.findInflectionPoints()
- if numOfIfP == 2 {
+ numOfIfP, t1, t2 := c.findInflectionPoints()
+ if numOfIfP == 2 {
// Case when 2 inflection points then divide at the smallest one first
- var sub1, tmp1, sub2, sub3 CubicCurveFloat64
- c.SubdivideAt(&sub1, &tmp1, t1)
+ var sub1, tmp1, sub2, sub3 CubicCurveFloat64
+ c.SubdivideAt(&sub1, &tmp1, t1)
// Now find the second inflection point in the second curve an subdivide
- numOfIfP, t1, t2 = tmp1.findInflectionPoints()
- if numOfIfP == 2 {
- tmp1.SubdivideAt(&sub2, &sub3, t2)
- } else if numOfIfP == 1 {
- tmp1.SubdivideAt(&sub2, &sub3, t1)
- } else {
- return
- }
+ numOfIfP, t1, t2 = tmp1.findInflectionPoints()
+ if numOfIfP == 2 {
+ tmp1.SubdivideAt(&sub2, &sub3, t2)
+ } else if numOfIfP == 1 {
+ tmp1.SubdivideAt(&sub2, &sub3, t1)
+ } else {
+ return
+ }
// Use PA for first subsegment
- sub1.doParabolicApproximation(t, flattening_threshold)
+ sub1.doParabolicApproximation(t, flattening_threshold)
// Use RS for the second (middle) subsegment
- sub2.Segment(t, flattening_threshold)
+ sub2.Segment(t, flattening_threshold)
// Drop the last point in the array will be added by the PA in third subsegment
//noOfPoints--;
// Use PA for the third curve
- sub3.doParabolicApproximation(t, flattening_threshold)
- } else if numOfIfP == 1 {
+ sub3.doParabolicApproximation(t, flattening_threshold)
+ } else if numOfIfP == 1 {
// Case where there is one inflection point, subdivide once and use PA on
// both subsegments
- var sub1, sub2 CubicCurveFloat64
- c.SubdivideAt(&sub1, &sub2, t1)
- sub1.doParabolicApproximation(t, flattening_threshold)
+ var sub1, sub2 CubicCurveFloat64
+ c.SubdivideAt(&sub1, &sub2, t1)
+ sub1.doParabolicApproximation(t, flattening_threshold)
//noOfPoints--;
- sub2.doParabolicApproximation(t, flattening_threshold)
- } else {
+ sub2.doParabolicApproximation(t, flattening_threshold)
+ } else {
// Case where there is no inflection USA PA directly
- c.doParabolicApproximation(t, flattening_threshold)
- }
-}
-
+ c.doParabolicApproximation(t, flattening_threshold)
+ }
+}
+
// Find the third control point deviation form the axis
-func (c *CubicCurveFloat64) thirdControlPointDeviation() float64 {
- dx := c.X2 - c.X1
- dy := c.Y2 - c.Y1
- l2 := dx*dx + dy*dy
- if l2 == 0 {
- return 0
- }
- l := math.Sqrt(l2)
- r := (c.Y2 - c.Y1) / l
- s := (c.X1 - c.X2) / l
- u := (c.X2*c.Y1 - c.X1*c.Y2) / l
- return math.Fabs(r*c.X3 + s*c.Y3 + u)
-}
-
+func (c *CubicCurveFloat64) thirdControlPointDeviation() float64 {
+ dx := c[2] - c[0]
+ dy := c[3] - c[1]
+ l2 := dx*dx + dy*dy
+ if l2 == 0 {
+ return 0
+ }
+ l := math.Sqrt(l2)
+ r := (c[3] - c[1]) / l
+ s := (c[0] - c[2]) / l
+ u := (c[2]*c[1] - c[0]*c[3]) / l
+ return math.Fabs(r*c[4] + s*c[5] + u)
+}
+
// Find the number of inflection point
-func (c *CubicCurveFloat64) numberOfInflectionPoints() int {
- dx21 := (c.X2 - c.X1)
- dy21 := (c.Y2 - c.Y1)
- dx32 := (c.X3 - c.X2)
- dy32 := (c.Y3 - c.Y2)
- dx43 := (c.X4 - c.X3)
- dy43 := (c.Y4 - c.Y3)
- if ((dx21*dy32 - dy21*dx32) * (dx32*dy43 - dy32*dx43)) < 0 {
+func (c *CubicCurveFloat64) numberOfInflectionPoints() int {
+ dx21 := (c[2] - c[0])
+ dy21 := (c[3] - c[1])
+ dx32 := (c[4] - c[2])
+ dy32 := (c[5] - c[3])
+ dx43 := (c[6] - c[4])
+ dy43 := (c[7] - c[5])
+ if ((dx21*dy32 - dy21*dx32) * (dx32*dy43 - dy32*dx43)) < 0 {
return 1 // One inflection point
- } else if ((dx21*dy32 - dy21*dx32) * (dx21*dy43 - dy21*dx43)) > 0 {
+ } else if ((dx21*dy32 - dy21*dx32) * (dx21*dy43 - dy21*dx43)) > 0 {
return 0 // No inflection point
- } else {
+ } else {
// Most cases no inflection point
- b1 := (dx21*dx32 + dy21*dy32) > 0
- b2 := (dx32*dx43 + dy32*dy43) > 0
+ b1 := (dx21*dx32 + dy21*dy32) > 0
+ b2 := (dx32*dx43 + dy32*dy43) > 0
if b1 || b2 && !(b1 && b2) { // xor!!
- return 0
- }
- }
+ return 0
+ }
+ }
return -1 // cases where there in zero or two inflection points
-}
-
-
+}
+
+
// This is the main function where all the work is done
-func (curve *CubicCurveFloat64) doParabolicApproximation(tracer LineTracer, flattening_threshold float64) {
- var c *CubicCurveFloat64
- c = curve
- var d, t, dx, dy, d2, d3 float64
- for {
- 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) {
+func (curve *CubicCurveFloat64) doParabolicApproximation(tracer LineTracer, flattening_threshold float64) {
+ var c *CubicCurveFloat64
+ c = curve
+ var d, t, dx, dy, d2, d3 float64
+ for {
+ dx = c[6] - c[0]
+ dy = c[7] - c[1]
+
+ d2 = math.Fabs(((c[2]-c[6])*dy - (c[3]-c[7])*dx))
+ d3 = math.Fabs(((c[4]-c[6])*dy - (c[5]-c[7])*dx))
+
+ if (d2+d3)*(d2+d3) < flattening_threshold*(dx*dx+dy*dy) {
// If the subsegment deviation satisfy the flatness then store the last
// point and stop
- tracer.LineTo(c.X4, c.Y4)
- break
- }
+ tracer.LineTo(c[6], c[7])
+ break
+ }
// Find the third control point deviation and the t values for subdivision
- d = c.thirdControlPointDeviation()
- t = 2 * math.Sqrt(flattening_threshold/d/3)
- if t > 1 {
+ d = c.thirdControlPointDeviation()
+ t = 2 * math.Sqrt(flattening_threshold/d/3)
+ if t > 1 {
// Case where the t value calculated is invalid so using RS
- c.Segment(tracer, flattening_threshold)
- break
- }
+ c.Segment(tracer, flattening_threshold)
+ break
+ }
// Valid t value to subdivide at that calculated value
- var b1, b2 CubicCurveFloat64
- c.SubdivideAt(&b1, &b2, t)
+ var b1, b2 CubicCurveFloat64
+ c.SubdivideAt(&b1, &b2, t)
// First subsegment should have its deviation equal to flatness
- dx = b1.X4 - b1.X1
- dy = b1.Y4 - b1.Y1
-
- d2 = math.Fabs(((b1.X2-b1.X4)*dy - (b1.Y2-b1.Y4)*dx))
- d3 = math.Fabs(((b1.X3-b1.X4)*dy - (b1.Y3-b1.Y4)*dx))
-
- if (d2+d3)*(d2+d3) > flattening_threshold*(dx*dx+dy*dy) {
+ dx = b1[6] - b1[0]
+ dy = b1[7] - b1[1]
+
+ d2 = math.Fabs(((b1[2]-b1[6])*dy - (b1[3]-b1[7])*dx))
+ d3 = math.Fabs(((b1[4]-b1[6])*dy - (b1[5]-b1[7])*dx))
+
+ if (d2+d3)*(d2+d3) > flattening_threshold*(dx*dx+dy*dy) {
// if not then use RS to handle any mathematical errors
- b1.Segment(tracer, flattening_threshold)
- } else {
- tracer.LineTo(b1.X4, b1.Y4)
- }
+ b1.Segment(tracer, flattening_threshold)
+ } else {
+ tracer.LineTo(b1[6], b1[7])
+ }
// repeat the process for the left over subsegment.
- c = &b2
- }
-}
-
+ c = &b2
+ }
+}
+
// Find the actual inflection points and return the number of inflection points found
// if 2 inflection points found, the first one returned will be with smaller t value.
-func (curve *CubicCurveFloat64) findInflectionPoints() (int, firstIfp, secondIfp float64) {
+func (curve *CubicCurveFloat64) findInflectionPoints() (int, firstIfp, secondIfp float64) {
// For Cubic Bezier curve with equation P=a*t^3 + b*t^2 + c*t + d
// slope of the curve dP/dt = 3*a*t^2 + 2*b*t + c
// a = (float)(-bez.p1 + 3*bez.p2 - 3*bez.p3 + bez.p4);
// b = (float)(3*bez.p1 - 6*bez.p2 + 3*bez.p3);
// c = (float)(-3*bez.p1 + 3*bez.p2);
- ax := (-curve.X1 + 3*curve.X2 - 3*curve.X3 + curve.X4)
- bx := (3*curve.X1 - 6*curve.X2 + 3*curve.X3)
- cx := (-3*curve.X1 + 3*curve.X2)
- ay := (-curve.Y1 + 3*curve.Y2 - 3*curve.Y3 + curve.Y4)
- by := (3*curve.Y1 - 6*curve.Y2 + 3*curve.Y3)
- cy := (-3*curve.Y1 + 3*curve.Y2)
- a := (3 * (ay*bx - ax*by))
- b := (3 * (ay*cx - ax*cy))
- c := (by*cx - bx*cy)
- r2 := (b*b - 4*a*c)
- firstIfp = 0.0
- secondIfp = 0.0
- if r2 >= 0.0 && a != 0.0 {
- r := math.Sqrt(r2)
- firstIfp = ((-b + r) / (2 * a))
- secondIfp = ((-b - r) / (2 * a))
- if (firstIfp > 0.0 && firstIfp < 1.0) && (secondIfp > 0.0 && secondIfp < 1.0) {
- if firstIfp > secondIfp {
- tmp := firstIfp
- firstIfp = secondIfp
- secondIfp = tmp
- }
- if secondIfp-firstIfp > 0.00001 {
- return 2, firstIfp, secondIfp
- } else {
- return 1, firstIfp, secondIfp
- }
- } else if firstIfp > 0.0 && firstIfp < 1.0 {
- return 1, firstIfp, secondIfp
- } else if secondIfp > 0.0 && secondIfp < 1.0 {
- firstIfp = secondIfp
- return 1, firstIfp, secondIfp
- }
- return 0, firstIfp, secondIfp
- }
- return 0, firstIfp, secondIfp
-}
+ ax := (-curve[0] + 3*curve[2] - 3*curve[4] + curve[6])
+ bx := (3*curve[0] - 6*curve[2] + 3*curve[4])
+ cx := (-3*curve[0] + 3*curve[2])
+ ay := (-curve[1] + 3*curve[3] - 3*curve[5] + curve[7])
+ by := (3*curve[1] - 6*curve[3] + 3*curve[5])
+ cy := (-3*curve[1] + 3*curve[3])
+ a := (3 * (ay*bx - ax*by))
+ b := (3 * (ay*cx - ax*cy))
+ c := (by*cx - bx*cy)
+ r2 := (b*b - 4*a*c)
+ firstIfp = 0.0
+ secondIfp = 0.0
+ if r2 >= 0.0 && a != 0.0 {
+ r := math.Sqrt(r2)
+ firstIfp = ((-b + r) / (2 * a))
+ secondIfp = ((-b - r) / (2 * a))
+ if (firstIfp > 0.0 && firstIfp < 1.0) && (secondIfp > 0.0 && secondIfp < 1.0) {
+ if firstIfp > secondIfp {
+ tmp := firstIfp
+ firstIfp = secondIfp
+ secondIfp = tmp
+ }
+ if secondIfp-firstIfp > 0.00001 {
+ return 2, firstIfp, secondIfp
+ } else {
+ return 1, firstIfp, secondIfp
+ }
+ } else if firstIfp > 0.0 && firstIfp < 1.0 {
+ return 1, firstIfp, secondIfp
+ } else if secondIfp > 0.0 && secondIfp < 1.0 {
+ firstIfp = secondIfp
+ return 1, firstIfp, secondIfp
+ }
+ return 0, firstIfp, secondIfp
+ }
+ return 0, firstIfp, secondIfp
+}
diff --git a/draw2d/curve/curve_test.go b/draw2d/curve/curve_test.go
index 0adb613..b3ae18a 100644
--- a/draw2d/curve/curve_test.go
+++ b/draw2d/curve/curve_test.go
@@ -1,94 +1,94 @@
-package curve
-
-import (
- "testing"
- "log"
- "fmt"
- "os"
- "bufio"
- "image"
- "image/png"
- "exp/draw"
- "draw2d.googlecode.com/hg/draw2d/raster"
-)
-
-
-var (
- flattening_threshold float64 = 0.5
- 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},
- CubicCurveFloat64{150, 290, 10, 10, 290, 10, 150, 290},
- 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 {
- points []float64
-}
-
-func (p *Path) LineTo(x, y float64) {
- if len(p.points)+2 > cap(p.points) {
- points := make([]float64, len(p.points)+2, len(p.points)+32)
- copy(points, p.points)
- p.points = points
- } else {
- p.points = p.points[0 : len(p.points)+2]
- }
- p.points[len(p.points)-2] = x
- p.points[len(p.points)-1] = y
-}
-
-func init() {
- f, err := os.Create("_test.html")
- if err != nil {
- log.Println(err)
- os.Exit(1)
- }
- defer f.Close()
- log.Printf("Create html viewer")
- f.Write([]byte("
"))
- for i := 0; i < len(testsCubicFloat64); i++ {
- f.Write([]byte(fmt.Sprintf("\n", i, i, i, i, i)))
- }
- for i := 0; i < len(testsQuadFloat64); i++ {
- f.Write([]byte(fmt.Sprintf("\n
\n", i)))
- }
- f.Write([]byte(""))
-
-}
-
-func savepng(filePath string, m image.Image) {
- f, err := os.Create(filePath)
- 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)
- }
-}
-
-func drawPoints(img draw.Image, c image.Color, s ...float64) image.Image {
+package curve
+
+import (
+ "testing"
+ "log"
+ "fmt"
+ "os"
+ "bufio"
+ "image"
+ "image/png"
+ "exp/draw"
+ "draw2d.googlecode.com/hg/draw2d/raster"
+)
+
+
+var (
+ flattening_threshold float64 = 0.5
+ 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},
+ CubicCurveFloat64{150, 290, 10, 10, 290, 10, 150, 290},
+ 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 {
+ points []float64
+}
+
+func (p *Path) LineTo(x, y float64) {
+ if len(p.points)+2 > cap(p.points) {
+ points := make([]float64, len(p.points)+2, len(p.points)+32)
+ copy(points, p.points)
+ p.points = points
+ } else {
+ p.points = p.points[0 : len(p.points)+2]
+ }
+ p.points[len(p.points)-2] = x
+ p.points[len(p.points)-1] = y
+}
+
+func init() {
+ f, err := os.Create("_test.html")
+ if err != nil {
+ log.Println(err)
+ os.Exit(1)
+ }
+ defer f.Close()
+ log.Printf("Create html viewer")
+ f.Write([]byte(""))
+ for i := 0; i < len(testsCubicFloat64); i++ {
+ f.Write([]byte(fmt.Sprintf("\n", i, i, i, i, i)))
+ }
+ for i := 0; i < len(testsQuadFloat64); i++ {
+ f.Write([]byte(fmt.Sprintf("\n
\n", i)))
+ }
+ f.Write([]byte(""))
+
+}
+
+func savepng(filePath string, m image.Image) {
+ f, err := os.Create(filePath)
+ 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)
+ }
+}
+
+func drawPoints(img draw.Image, c image.Color, s ...float64) image.Image {
/*for i := 0; i < len(s); i += 2 {
x, y := int(s[i]+0.5), int(s[i+1]+0.5)
img.Set(x, y, c)
@@ -100,163 +100,164 @@ func drawPoints(img draw.Image, c image.Color, s ...float64) image.Image {
img.Set(x-1, y, c)
img.Set(x-1, y+1, c)
img.Set(x-1, y-1, c)
-
- }*/
- return img
-}
-
-func TestCubicCurveRec(t *testing.T) {
- for i, curve := range testsCubicFloat64 {
- var p Path
- p.LineTo(curve.X1, curve.Y1)
- curve.SegmentRec(&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, curve.X4, curve.Y4)
- raster.PolylineBresenham(img, image.Black, p.points...)
- //drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, curve.X1, curve.Y1, curve.X2, curve.Y2, curve.X3, curve.Y3, curve.X4, curve.Y4)
- drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, p.points...)
- savepng(fmt.Sprintf("_testRec%d.png", i), img)
- log.Printf("Num of points: %d\n", len(p.points))
- }
- fmt.Println()
-}
-
-func TestCubicCurve(t *testing.T) {
- for i, curve := range testsCubicFloat64 {
- var p Path
- p.LineTo(curve.X1, curve.Y1)
- 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, curve.X4, curve.Y4)
- raster.PolylineBresenham(img, image.Black, p.points...)
- //drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, curve.X1, curve.Y1, curve.X2, curve.Y2, curve.X3, curve.Y3, curve.X4, curve.Y4)
- drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, p.points...)
- savepng(fmt.Sprintf("_test%d.png", i), img)
- log.Printf("Num of points: %d\n", len(p.points))
- }
- fmt.Println()
-}
-
-func TestCubicCurveAdaptiveRec(t *testing.T) {
- for i, curve := range testsCubicFloat64 {
- var p Path
- p.LineTo(curve.X1, curve.Y1)
- curve.AdaptiveSegmentRec(&p, 1, 0, 0)
- 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, curve.X4, curve.Y4)
- raster.PolylineBresenham(img, image.Black, p.points...)
- //drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, curve.X1, curve.Y1, curve.X2, curve.Y2, curve.X3, curve.Y3, curve.X4, curve.Y4)
- drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, p.points...)
- savepng(fmt.Sprintf("_testAdaptiveRec%d.png", i), img)
- log.Printf("Num of points: %d\n", len(p.points))
- }
- fmt.Println()
-}
-
-func TestCubicCurveAdaptive(t *testing.T) {
- for i, curve := range testsCubicFloat64 {
- var p Path
- p.LineTo(curve.X1, curve.Y1)
- curve.AdaptiveSegment(&p, 1, 0, 0)
- 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, curve.X4, curve.Y4)
- raster.PolylineBresenham(img, image.Black, p.points...)
- //drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, curve.X1, curve.Y1, curve.X2, curve.Y2, curve.X3, curve.Y3, curve.X4, curve.Y4)
- drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, p.points...)
- savepng(fmt.Sprintf("_testAdaptive%d.png", i), img)
- log.Printf("Num of points: %d\n", len(p.points))
- }
- fmt.Println()
-}
-
-func TestCubicCurveParabolic(t *testing.T) {
- for i, curve := range testsCubicFloat64 {
- var p Path
- p.LineTo(curve.X1, curve.Y1)
- curve.ParabolicSegment(&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, curve.X4, curve.Y4)
- raster.PolylineBresenham(img, image.Black, p.points...)
- //drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, curve.X1, curve.Y1, curve.X2, curve.Y2, curve.X3, curve.Y3, curve.X4, curve.Y4)
- drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, p.points...)
- savepng(fmt.Sprintf("_testParabolic%d.png", i), img)
- log.Printf("Num of points: %d\n", len(p.points))
- }
- fmt.Println()
-}
-
-
-func TestQuadCurve(t *testing.T) {
- for i, curve := range testsQuadFloat64 {
- var p Path
- p.LineTo(curve.X1, curve.Y1)
- 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 testsCubicFloat64 {
- p := Path{make([]float64, 0, 32)}
- p.LineTo(curve.X1, curve.Y1)
- curve.SegmentRec(&p, flattening_threshold)
- }
- }
-}
-
-func BenchmarkCubicCurve(b *testing.B) {
- for i := 0; i < b.N; i++ {
- for _, curve := range testsCubicFloat64 {
- p := Path{make([]float64, 0, 32)}
- p.LineTo(curve.X1, curve.Y1)
- curve.Segment(&p, flattening_threshold)
- }
- }
-}
-
-func BenchmarkCubicCurveAdaptiveRec(b *testing.B) {
- for i := 0; i < b.N; i++ {
- for _, curve := range testsCubicFloat64 {
- p := Path{make([]float64, 0, 32)}
- p.LineTo(curve.X1, curve.Y1)
- curve.AdaptiveSegmentRec(&p, 1, 0, 0)
- }
- }
-}
-
-func BenchmarkCubicCurveAdaptive(b *testing.B) {
- for i := 0; i < b.N; i++ {
- for _, curve := range testsCubicFloat64 {
- p := Path{make([]float64, 0, 32)}
- p.LineTo(curve.X1, curve.Y1)
- curve.AdaptiveSegment(&p, 1, 0, 0)
- }
- }
-}
-
-func BenchmarkCubicCurveParabolic(b *testing.B) {
- for i := 0; i < b.N; i++ {
- for _, curve := range testsCubicFloat64 {
- p := Path{make([]float64, 0, 32)}
- p.LineTo(curve.X1, curve.Y1)
- 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)}
- p.LineTo(curve.X1, curve.Y1)
- curve.Segment(&p, flattening_threshold)
- }
- }
-}
+
+ }*/
+ return img
+}
+
+func TestCubicCurveRec(t *testing.T) {
+ for i, curve := range testsCubicFloat64 {
+ var p Path
+ p.LineTo(curve[0], curve[1])
+ curve.SegmentRec(&p, flattening_threshold)
+ img := image.NewNRGBA(300, 300)
+ raster.PolylineBresenham(img, image.NRGBAColor{0xff, 0, 0, 0xff}, curve[:]...)
+ raster.PolylineBresenham(img, image.Black, p.points...)
+ //drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, curve[:]...)
+ drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, p.points...)
+ savepng(fmt.Sprintf("_testRec%d.png", i), img)
+ log.Printf("Num of points: %d\n", len(p.points))
+ }
+ fmt.Println()
+}
+
+func TestCubicCurve(t *testing.T) {
+ for i, curve := range testsCubicFloat64 {
+ var p Path
+ p.LineTo(curve[0], curve[1])
+ curve.Segment(&p, flattening_threshold)
+ img := image.NewNRGBA(300, 300)
+ raster.PolylineBresenham(img, image.NRGBAColor{0xff, 0, 0, 0xff}, curve[:]...)
+ raster.PolylineBresenham(img, image.Black, p.points...)
+ //drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, curve[:]...)
+ drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, p.points...)
+ savepng(fmt.Sprintf("_test%d.png", i), img)
+ log.Printf("Num of points: %d\n", len(p.points))
+ }
+ fmt.Println()
+}
+
+func TestCubicCurveAdaptiveRec(t *testing.T) {
+ for i, curve := range testsCubicFloat64 {
+ var p Path
+ p.LineTo(curve[0], curve[1])
+ curve.AdaptiveSegmentRec(&p, 1, 0, 0)
+ img := image.NewNRGBA(300, 300)
+ raster.PolylineBresenham(img, image.NRGBAColor{0xff, 0, 0, 0xff}, curve[:]...)
+ raster.PolylineBresenham(img, image.Black, p.points...)
+ //drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, curve[:]...)
+ drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, p.points...)
+ savepng(fmt.Sprintf("_testAdaptiveRec%d.png", i), img)
+ log.Printf("Num of points: %d\n", len(p.points))
+ }
+ fmt.Println()
+}
+
+func TestCubicCurveAdaptive(t *testing.T) {
+ for i, curve := range testsCubicFloat64 {
+ var p Path
+ p.LineTo(curve[0], curve[1])
+ curve.AdaptiveSegment(&p, 1, 0, 0)
+ img := image.NewNRGBA(300, 300)
+ raster.PolylineBresenham(img, image.NRGBAColor{0xff, 0, 0, 0xff}, curve[:]...)
+ raster.PolylineBresenham(img, image.Black, p.points...)
+ //drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, curve[:]...)
+ drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, p.points...)
+ savepng(fmt.Sprintf("_testAdaptive%d.png", i), img)
+ log.Printf("Num of points: %d\n", len(p.points))
+ }
+ fmt.Println()
+}
+
+func TestCubicCurveParabolic(t *testing.T) {
+ for i, curve := range testsCubicFloat64 {
+ var p Path
+ p.LineTo(curve[0], curve[1])
+ curve.ParabolicSegment(&p, flattening_threshold)
+ img := image.NewNRGBA(300, 300)
+ raster.PolylineBresenham(img, image.NRGBAColor{0xff, 0, 0, 0xff}, curve[:]...)
+ raster.PolylineBresenham(img, image.Black, p.points...)
+ //drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, curve[:]...)
+ drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, p.points...)
+ savepng(fmt.Sprintf("_testParabolic%d.png", i), img)
+ log.Printf("Num of points: %d\n", len(p.points))
+ }
+ fmt.Println()
+}
+
+
+func TestQuadCurve(t *testing.T) {
+ for i, curve := range testsQuadFloat64 {
+ var p Path
+ p.LineTo(curve[0], curve[1])
+ curve.Segment(&p, flattening_threshold)
+ img := image.NewNRGBA(300, 300)
+ raster.PolylineBresenham(img, image.NRGBAColor{0xff, 0, 0, 0xff}, curve[:]...)
+ raster.PolylineBresenham(img, image.Black, p.points...)
+ //drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, curve[:]...)
+ 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 testsCubicFloat64 {
+ p := Path{make([]float64, 0, 32)}
+ p.LineTo(curve[0], curve[1])
+ curve.SegmentRec(&p, flattening_threshold)
+ }
+ }
+}
+
+func BenchmarkCubicCurve(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ for _, curve := range testsCubicFloat64 {
+ p := Path{make([]float64, 0, 32)}
+ p.LineTo(curve[0], curve[1])
+ curve.Segment(&p, flattening_threshold)
+ }
+ }
+}
+
+func BenchmarkCubicCurveAdaptiveRec(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ for _, curve := range testsCubicFloat64 {
+ p := Path{make([]float64, 0, 32)}
+ p.LineTo(curve[0], curve[1])
+ curve.AdaptiveSegmentRec(&p, 1, 0, 0)
+ }
+ }
+}
+
+func BenchmarkCubicCurveAdaptive(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ for _, curve := range testsCubicFloat64 {
+ p := Path{make([]float64, 0, 32)}
+ p.LineTo(curve[0], curve[1])
+ curve.AdaptiveSegment(&p, 1, 0, 0)
+ }
+ }
+}
+
+func BenchmarkCubicCurveParabolic(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ for _, curve := range testsCubicFloat64 {
+ p := Path{make([]float64, 0, 32)}
+ p.LineTo(curve[0], curve[1])
+ 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)}
+ p.LineTo(curve[0], curve[1])
+ curve.Segment(&p, flattening_threshold)
+ }
+ }
+}
diff --git a/draw2d/curve/quad_float64.go b/draw2d/curve/quad_float64.go
index 64e67bd..c1e5989 100644
--- a/draw2d/curve/quad_float64.go
+++ b/draw2d/curve/quad_float64.go
@@ -1,54 +1,53 @@
// 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) {
+package curve
+
+import (
+ "math"
+)
+//X1, Y1, X2, Y2, X3, Y3 float64
+type QuadCurveFloat64 [6]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) {
- var curves [CurveRecursionLimit]QuadCurveFloat64
- curves[0] = *curve
- i := 0
+ c1[0], c1[1] = c[0], c[1]
+ c2[4], c2[5] = c[4], c[5]
+ c1[2] = (c[0] + c[2]) / 2
+ c1[3] = (c[1] + c[3]) / 2
+ c2[2] = (c[2] + c[4]) / 2
+ c2[3] = (c[3] + c[5]) / 2
+ c1[4] = (c1[2] + c2[2]) / 2
+ c1[5] = (c1[3] + c2[3]) / 2
+ c2[0], c2[1] = c1[4], c1[5]
+ return
+}
+
+
+func (curve *QuadCurveFloat64) Segment(t LineTracer, flattening_threshold float64) {
+ 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 {
+ var c *QuadCurveFloat64
+ var dx, dy, d float64
+
+ for i >= 0 {
+ c = &curves[i]
+ dx = c[4] - c[0]
+ dy = c[5] - c[1]
+
+ d = math.Fabs(((c[2]-c[4])*dy - (c[3]-c[5])*dx))
+
+ if (d*d) < flattening_threshold*(dx*dx+dy*dy) || i == len(curves)-1 {
+ t.LineTo(c[4], c[5])
+ i--
+ } else {
// second half of bezier go lower onto the stack
- c.Subdivide(&curves[i+1], &curves[i])
- i++
- }
- }
-}
+ c.Subdivide(&curves[i+1], &curves[i])
+ i++
+ }
+ }
+}
diff --git a/draw2d/raster/Makefile b/draw2d/raster/Makefile
index 52c5288..c907c3f 100644
--- a/draw2d/raster/Makefile
+++ b/draw2d/raster/Makefile
@@ -3,5 +3,9 @@ include $(GOROOT)/src/Make.inc
TARG=draw2d.googlecode.com/hg/draw2d/raster
GOFILES=\
line.go\
+ polygon.go\
+ coverage_table.go\
+ fillerAA.go\
+
include $(GOROOT)/src/Make.pkg
diff --git a/draw2d/raster/coverage_table.go b/draw2d/raster/coverage_table.go
new file mode 100644
index 0000000..49bdf51
--- /dev/null
+++ b/draw2d/raster/coverage_table.go
@@ -0,0 +1,135 @@
+// Copyright 2011 The draw2d Authors. All rights reserved.
+// created: 27/05/2011 by Laurent Le Goff
+package raster
+
+var SUBPIXEL_OFFSETS_SAMPLE_8 = [8]float64{
+ 5 / 8, 0 / 8,
+ 3 / 8, 6 / 8,
+ 1 / 8, 4 / 8,
+ 7 / 8, 2 / 8,
+}
+
+var SUBPIXEL_OFFSETS_SAMPLE_16 = [16]float64{
+ (1 / 16),
+ (8 / 16),
+ (4 / 16),
+ (15 / 16),
+ (11 / 16),
+ (2 / 16),
+ (6 / 16),
+ (14 / 16),
+ (10 / 16),
+ (3 / 16),
+ (7 / 16),
+ (12 / 16),
+ (0 / 16),
+ (9 / 16),
+ (5 / 16),
+ (13 / 16),
+}
+
+var SUBPIXEL_OFFSETS_SAMPLE_32 = [32]float64{
+ 28 / 32,
+ 13 / 32,
+ 6 / 32,
+ 23 / 32,
+ 0 / 32,
+ 17 / 32,
+ 10 / 32,
+ 27 / 32,
+ 4 / 32,
+ 21 / 32,
+ 14 / 32,
+ 31 / 32,
+ 8 / 32,
+ 25 / 32,
+ 18 / 32,
+ 3 / 32,
+ 12 / 32,
+ 29 / 32,
+ 22 / 32,
+ 7 / 32,
+ 16 / 32,
+ 1 / 32,
+ 26 / 32,
+ 11 / 32,
+ 20 / 32,
+ 5 / 32,
+ 30 / 32,
+ 15 / 32,
+ 24 / 32,
+ 9 / 32,
+ 2 / 32,
+ 19 / 32,
+}
+
+var coverageTable = [256]uint8{
+ pixelCoverage(0x00), pixelCoverage(0x01), pixelCoverage(0x02), pixelCoverage(0x03),
+ pixelCoverage(0x04), pixelCoverage(0x05), pixelCoverage(0x06), pixelCoverage(0x07),
+ pixelCoverage(0x08), pixelCoverage(0x09), pixelCoverage(0x0a), pixelCoverage(0x0b),
+ pixelCoverage(0x0c), pixelCoverage(0x0d), pixelCoverage(0x0e), pixelCoverage(0x0f),
+ pixelCoverage(0x10), pixelCoverage(0x11), pixelCoverage(0x12), pixelCoverage(0x13),
+ pixelCoverage(0x14), pixelCoverage(0x15), pixelCoverage(0x16), pixelCoverage(0x17),
+ pixelCoverage(0x18), pixelCoverage(0x19), pixelCoverage(0x1a), pixelCoverage(0x1b),
+ pixelCoverage(0x1c), pixelCoverage(0x1d), pixelCoverage(0x1e), pixelCoverage(0x1f),
+ pixelCoverage(0x20), pixelCoverage(0x21), pixelCoverage(0x22), pixelCoverage(0x23),
+ pixelCoverage(0x24), pixelCoverage(0x25), pixelCoverage(0x26), pixelCoverage(0x27),
+ pixelCoverage(0x28), pixelCoverage(0x29), pixelCoverage(0x2a), pixelCoverage(0x2b),
+ pixelCoverage(0x2c), pixelCoverage(0x2d), pixelCoverage(0x2e), pixelCoverage(0x2f),
+ pixelCoverage(0x30), pixelCoverage(0x31), pixelCoverage(0x32), pixelCoverage(0x33),
+ pixelCoverage(0x34), pixelCoverage(0x35), pixelCoverage(0x36), pixelCoverage(0x37),
+ pixelCoverage(0x38), pixelCoverage(0x39), pixelCoverage(0x3a), pixelCoverage(0x3b),
+ pixelCoverage(0x3c), pixelCoverage(0x3d), pixelCoverage(0x3e), pixelCoverage(0x3f),
+ pixelCoverage(0x40), pixelCoverage(0x41), pixelCoverage(0x42), pixelCoverage(0x43),
+ pixelCoverage(0x44), pixelCoverage(0x45), pixelCoverage(0x46), pixelCoverage(0x47),
+ pixelCoverage(0x48), pixelCoverage(0x49), pixelCoverage(0x4a), pixelCoverage(0x4b),
+ pixelCoverage(0x4c), pixelCoverage(0x4d), pixelCoverage(0x4e), pixelCoverage(0x4f),
+ pixelCoverage(0x50), pixelCoverage(0x51), pixelCoverage(0x52), pixelCoverage(0x53),
+ pixelCoverage(0x54), pixelCoverage(0x55), pixelCoverage(0x56), pixelCoverage(0x57),
+ pixelCoverage(0x58), pixelCoverage(0x59), pixelCoverage(0x5a), pixelCoverage(0x5b),
+ pixelCoverage(0x5c), pixelCoverage(0x5d), pixelCoverage(0x5e), pixelCoverage(0x5f),
+ pixelCoverage(0x60), pixelCoverage(0x61), pixelCoverage(0x62), pixelCoverage(0x63),
+ pixelCoverage(0x64), pixelCoverage(0x65), pixelCoverage(0x66), pixelCoverage(0x67),
+ pixelCoverage(0x68), pixelCoverage(0x69), pixelCoverage(0x6a), pixelCoverage(0x6b),
+ pixelCoverage(0x6c), pixelCoverage(0x6d), pixelCoverage(0x6e), pixelCoverage(0x6f),
+ pixelCoverage(0x70), pixelCoverage(0x71), pixelCoverage(0x72), pixelCoverage(0x73),
+ pixelCoverage(0x74), pixelCoverage(0x75), pixelCoverage(0x76), pixelCoverage(0x77),
+ pixelCoverage(0x78), pixelCoverage(0x79), pixelCoverage(0x7a), pixelCoverage(0x7b),
+ pixelCoverage(0x7c), pixelCoverage(0x7d), pixelCoverage(0x7e), pixelCoverage(0x7f),
+ pixelCoverage(0x80), pixelCoverage(0x81), pixelCoverage(0x82), pixelCoverage(0x83),
+ pixelCoverage(0x84), pixelCoverage(0x85), pixelCoverage(0x86), pixelCoverage(0x87),
+ pixelCoverage(0x88), pixelCoverage(0x89), pixelCoverage(0x8a), pixelCoverage(0x8b),
+ pixelCoverage(0x8c), pixelCoverage(0x8d), pixelCoverage(0x8e), pixelCoverage(0x8f),
+ pixelCoverage(0x90), pixelCoverage(0x91), pixelCoverage(0x92), pixelCoverage(0x93),
+ pixelCoverage(0x94), pixelCoverage(0x95), pixelCoverage(0x96), pixelCoverage(0x97),
+ pixelCoverage(0x98), pixelCoverage(0x99), pixelCoverage(0x9a), pixelCoverage(0x9b),
+ pixelCoverage(0x9c), pixelCoverage(0x9d), pixelCoverage(0x9e), pixelCoverage(0x9f),
+ pixelCoverage(0xa0), pixelCoverage(0xa1), pixelCoverage(0xa2), pixelCoverage(0xa3),
+ pixelCoverage(0xa4), pixelCoverage(0xa5), pixelCoverage(0xa6), pixelCoverage(0xa7),
+ pixelCoverage(0xa8), pixelCoverage(0xa9), pixelCoverage(0xaa), pixelCoverage(0xab),
+ pixelCoverage(0xac), pixelCoverage(0xad), pixelCoverage(0xae), pixelCoverage(0xaf),
+ pixelCoverage(0xb0), pixelCoverage(0xb1), pixelCoverage(0xb2), pixelCoverage(0xb3),
+ pixelCoverage(0xb4), pixelCoverage(0xb5), pixelCoverage(0xb6), pixelCoverage(0xb7),
+ pixelCoverage(0xb8), pixelCoverage(0xb9), pixelCoverage(0xba), pixelCoverage(0xbb),
+ pixelCoverage(0xbc), pixelCoverage(0xbd), pixelCoverage(0xbe), pixelCoverage(0xbf),
+ pixelCoverage(0xc0), pixelCoverage(0xc1), pixelCoverage(0xc2), pixelCoverage(0xc3),
+ pixelCoverage(0xc4), pixelCoverage(0xc5), pixelCoverage(0xc6), pixelCoverage(0xc7),
+ pixelCoverage(0xc8), pixelCoverage(0xc9), pixelCoverage(0xca), pixelCoverage(0xcb),
+ pixelCoverage(0xcc), pixelCoverage(0xcd), pixelCoverage(0xce), pixelCoverage(0xcf),
+ pixelCoverage(0xd0), pixelCoverage(0xd1), pixelCoverage(0xd2), pixelCoverage(0xd3),
+ pixelCoverage(0xd4), pixelCoverage(0xd5), pixelCoverage(0xd6), pixelCoverage(0xd7),
+ pixelCoverage(0xd8), pixelCoverage(0xd9), pixelCoverage(0xda), pixelCoverage(0xdb),
+ pixelCoverage(0xdc), pixelCoverage(0xdd), pixelCoverage(0xde), pixelCoverage(0xdf),
+ pixelCoverage(0xe0), pixelCoverage(0xe1), pixelCoverage(0xe2), pixelCoverage(0xe3),
+ pixelCoverage(0xe4), pixelCoverage(0xe5), pixelCoverage(0xe6), pixelCoverage(0xe7),
+ pixelCoverage(0xe8), pixelCoverage(0xe9), pixelCoverage(0xea), pixelCoverage(0xeb),
+ pixelCoverage(0xec), pixelCoverage(0xed), pixelCoverage(0xee), pixelCoverage(0xef),
+ pixelCoverage(0xf0), pixelCoverage(0xf1), pixelCoverage(0xf2), pixelCoverage(0xf3),
+ pixelCoverage(0xf4), pixelCoverage(0xf5), pixelCoverage(0xf6), pixelCoverage(0xf7),
+ pixelCoverage(0xf8), pixelCoverage(0xf9), pixelCoverage(0xfa), pixelCoverage(0xfb),
+ pixelCoverage(0xfc), pixelCoverage(0xfd), pixelCoverage(0xfe), pixelCoverage(0xff),
+}
+
+func pixelCoverage(a uint8) uint8 {
+ return (((a) & 1) + (((a) >> 1) & 1) + (((a) >> 2) & 1) + (((a) >> 3) & 1) + (((a) >> 4) & 1) + (((a) >> 5) & 1) + (((a) >> 6) & 1) + (((a) >> 7) & 1))
+}
diff --git a/draw2d/raster/fillerAA.go b/draw2d/raster/fillerAA.go
new file mode 100644
index 0000000..bdbf125
--- /dev/null
+++ b/draw2d/raster/fillerAA.go
@@ -0,0 +1,178 @@
+// Copyright 2011 The draw2d Authors. All rights reserved.
+// created: 27/05/2011 by Laurent Le Goff
+package raster
+
+import (
+ "image"
+ "unsafe"
+)
+
+const (
+ SUBPIXEL_SHIFT = 3
+ SUBPIXEL_COUNT = 1 << SUBPIXEL_SHIFT
+)
+
+var SUBPIXEL_OFFSETS = SUBPIXEL_OFFSETS_SAMPLE_8
+
+type SUBPIXEL_DATA uint16
+type NON_ZERO_MASK_DATA_UNIT uint8
+
+type Rasterizer8BitsSample struct {
+ MaskBuffer []SUBPIXEL_DATA
+ WindingBuffer []NON_ZERO_MASK_DATA_UNIT
+
+ Width int
+ BufferWidth int
+ Height int
+ ClipBound [4]float64
+ RemappingMatrix [6]float64
+}
+
+/* width and height define the maximum output size for the filler.
+ * The filler will output to larger bitmaps as well, but the output will
+ * be cropped.
+ */
+func NewRasterizer8BitsSample(width, height int) *Rasterizer8BitsSample {
+ var r Rasterizer8BitsSample
+ // Scale the coordinates by SUBPIXEL_COUNT in vertical direction
+ // The sampling point for the sub-pixel is at the top right corner. This
+ // adjustment moves it to the pixel center.
+ r.RemappingMatrix = [6]float64{1, 0, 0, SUBPIXEL_COUNT, 0.5 / SUBPIXEL_COUNT, -0.5 * SUBPIXEL_COUNT}
+ r.Width = width
+ r.Height = height
+ // The buffer used for filling needs to be one pixel wider than the bitmap.
+ // This is because the end flag that turns the fill of is the first pixel
+ // after the actually drawn edge.
+ r.BufferWidth = width + 1
+
+ r.MaskBuffer = make([]SUBPIXEL_DATA, r.BufferWidth*height)
+ r.WindingBuffer = make([]NON_ZERO_MASK_DATA_UNIT, r.BufferWidth*height*SUBPIXEL_COUNT)
+ r.ClipBound = clip(0, 0, width, height, SUBPIXEL_COUNT)
+ return &r
+}
+
+func clip(x, y, width, height, scale int) [4]float64 {
+ var clipBound [4]float64
+
+ offset := 0.99 / float64(scale)
+
+ clipBound[0] = float64(x) + offset
+ clipBound[2] = float64(x+width) - offset
+
+ clipBound[1] = float64(y * scale)
+ clipBound[3] = float64((y + height) * scale)
+ return clipBound
+}
+
+func intersect(r1, r2 [4]float64) [4]float64 {
+ if r1[0] < r2[0] {
+ r1[0] = r2[0]
+ }
+ if r1[2] > r2[2] {
+ r1[2] = r2[2]
+ }
+ if r1[0] > r1[2] {
+ r1[0] = r1[2]
+ }
+
+ if r1[1] < r2[1] {
+ r1[1] = r2[1]
+ }
+ if r1[3] > r2[3] {
+ r1[3] = r2[3]
+ }
+ if r1[1] > r1[3] {
+ r1[1] = r1[3]
+ }
+ return r1
+}
+
+func (r *Rasterizer8BitsSample) RenderEvenOdd(img *image.RGBA, color *image.RGBAColor, polygon *Polygon, tr [6]float64) {
+ // memset 0 the mask buffer
+ r.MaskBuffer = make([]SUBPIXEL_DATA, r.BufferWidth*r.Height)
+
+ // inline matrix multiplication
+ transform := [6]float64{
+ tr[0]*r.RemappingMatrix[0] + tr[1]*r.RemappingMatrix[2],
+ tr[1]*r.RemappingMatrix[3] + tr[0]*r.RemappingMatrix[1],
+ tr[2]*r.RemappingMatrix[0] + tr[3]*r.RemappingMatrix[2],
+ tr[3]*r.RemappingMatrix[3] + tr[2]*r.RemappingMatrix[1],
+ tr[4]*r.RemappingMatrix[0] + tr[5]*r.RemappingMatrix[2] + r.RemappingMatrix[4],
+ tr[5]*r.RemappingMatrix[3] + tr[4]*r.RemappingMatrix[1] + r.RemappingMatrix[5],
+ }
+
+ clipRect := clip(img.Bounds().Min.X, img.Bounds().Min.Y, img.Bounds().Dx(), img.Bounds().Dy(), SUBPIXEL_COUNT)
+ clipRect = intersect(clipRect, r.ClipBound)
+ p := 0
+ l := len(*polygon) / 2
+ for p < l {
+ var edges [20]PolygonEdge
+ edgeCount := polygon.getEdges(p, 10, edges[:], transform, clipRect)
+ for k := 0; k < edgeCount; k++ {
+ r.addEvenOddEdge(&(edges[k]))
+ }
+ p += 10
+ }
+
+ r.fillEvenOdd(img, color, clipRect)
+}
+
+//! Adds an edge to be used with even-odd fill.
+func (r *Rasterizer8BitsSample) addEvenOddEdge(edge *PolygonEdge) {
+ x := edge.X
+ slope := edge.Slope
+ for y := edge.FirstLine; y <= edge.LastLine; y++ {
+ ySub := SUBPIXEL_DATA(y & (SUBPIXEL_COUNT - 1))
+ xp := (int)(x + SUBPIXEL_OFFSETS[ySub])
+ mask := SUBPIXEL_DATA(1 << ySub)
+ yLine := y >> SUBPIXEL_SHIFT
+ r.MaskBuffer[yLine*r.BufferWidth+xp] ^= mask
+ x += slope
+ }
+}
+
+// Renders the mask to the canvas with even-odd fill.
+func (r *Rasterizer8BitsSample) fillEvenOdd(img *image.RGBA, color *image.RGBAColor, clipBound [4]float64) {
+ var x, y uint32
+
+ minX := uint32(clipBound[0])
+ maxX := uint32(clipBound[2])
+
+ minY := uint32(clipBound[1]) >> SUBPIXEL_SHIFT
+ maxY := uint32(clipBound[3]) >> SUBPIXEL_SHIFT
+
+ //pixColor := (uint32(color.R) << 24) | (uint32(color.G) << 16) | (uint32(color.B) << 8) | uint32(color.A)
+ pixColor := (*uint32)(unsafe.Pointer(color))
+ cs1 := *pixColor & 0xff00ff
+ cs2 := (*pixColor >> 8) & 0xff00ff
+
+ stride := uint32(img.Stride)
+ var mask SUBPIXEL_DATA
+
+ for y = minY; y < maxY; y++ {
+ tp := img.Pix[y*stride:]
+
+ mask = 0
+ for x = minX; x <= maxX; x++ {
+ p := (*uint32)(unsafe.Pointer(&tp[x]))
+ mask ^= r.MaskBuffer[y*uint32(r.BufferWidth)+x]
+ // 8bits
+ alpha := uint32(coverageTable[mask])
+ // 16bits
+ //alpha := uint32(coverageTable[mask & 0xff] + coverageTable[(mask >> 8) & 0xff])
+ // 32bits
+ //alpha := uint32(coverageTable[mask & 0xff] + coverageTable[(mask >> 8) & 0xff] + coverageTable[(mask >> 16) & 0xff] + coverageTable[(mask >> 24) & 0xff])
+
+ // alpha is in range of 0 to SUBPIXEL_COUNT
+ invAlpha := uint32(SUBPIXEL_COUNT) - alpha
+
+ ct1 := (*p & 0xff00ff) * invAlpha
+ ct2 := ((*p >> 8) & 0xff00ff) * invAlpha
+
+ ct1 = ((ct1 + cs1*alpha) >> SUBPIXEL_SHIFT) & 0xff00ff
+ ct2 = ((ct2 + cs2*alpha) << (8 - SUBPIXEL_SHIFT)) & 0xff00ff00
+
+ *p = ct1 + ct2
+ }
+ }
+}
diff --git a/draw2d/raster/line.go b/draw2d/raster/line.go
index 2a8c0a8..235ea01 100644
--- a/draw2d/raster/line.go
+++ b/draw2d/raster/line.go
@@ -1,53 +1,55 @@
-package raster
-
-import (
- "exp/draw"
- "image"
-)
-
-func abs(i int) int {
- if i < 0 {
- return -i
- }
- return i
-}
-
-func PolylineBresenham(img draw.Image, c image.Color, s ...float64) {
- for i := 2; i < len(s); i += 2 {
- Bresenham(img, c, int(s[i-2]+0.5), int(s[i-1]+0.5), int(s[i]+0.5), int(s[i+1]+0.5))
- }
-}
-
-func Bresenham(img draw.Image, color image.Color, x0, y0, x1, y1 int) {
- dx := abs(x1 - x0)
- dy := abs(y1 - y0)
- var sx, sy int
- if x0 < x1 {
- sx = 1
- } else {
- sx = -1
- }
- if y0 < y1 {
- sy = 1
- } else {
- sy = -1
- }
- err := dx - dy
-
- var e2 int
- for {
- img.Set(x0, y0, color)
- if x0 == x1 && y0 == y1 {
- return
- }
- e2 = 2 * err
- if e2 > -dy {
- err = err - dy
- x0 = x0 + sx
- }
- if e2 < dx {
- err = err + dx
- y0 = y0 + sy
- }
- }
-}
+// Copyright 2011 The draw2d Authors. All rights reserved.
+// created: 27/05/2011 by Laurent Le Goff
+package raster
+
+import (
+ "exp/draw"
+ "image"
+)
+
+func abs(i int) int {
+ if i < 0 {
+ return -i
+ }
+ return i
+}
+
+func PolylineBresenham(img draw.Image, c image.Color, s ...float64) {
+ for i := 2; i < len(s); i += 2 {
+ Bresenham(img, c, int(s[i-2]+0.5), int(s[i-1]+0.5), int(s[i]+0.5), int(s[i+1]+0.5))
+ }
+}
+
+func Bresenham(img draw.Image, color image.Color, x0, y0, x1, y1 int) {
+ dx := abs(x1 - x0)
+ dy := abs(y1 - y0)
+ var sx, sy int
+ if x0 < x1 {
+ sx = 1
+ } else {
+ sx = -1
+ }
+ if y0 < y1 {
+ sy = 1
+ } else {
+ sy = -1
+ }
+ err := dx - dy
+
+ var e2 int
+ for {
+ img.Set(x0, y0, color)
+ if x0 == x1 && y0 == y1 {
+ return
+ }
+ e2 = 2 * err
+ if e2 > -dy {
+ err = err - dy
+ x0 = x0 + sx
+ }
+ if e2 < dx {
+ err = err + dx
+ y0 = y0 + sy
+ }
+ }
+}
diff --git a/draw2d/raster/polygon.go b/draw2d/raster/polygon.go
new file mode 100644
index 0000000..750dee7
--- /dev/null
+++ b/draw2d/raster/polygon.go
@@ -0,0 +1,273 @@
+// Copyright 2011 The draw2d Authors. All rights reserved.
+// created: 27/05/2011 by Laurent Le Goff
+package raster
+
+import (
+ "math"
+)
+
+const (
+ POLYGON_CLIP_NONE = iota
+ POLYGON_CLIP_LEFT
+ POLYGON_CLIP_RIGHT
+ POLYGON_CLIP_TOP
+ POLYGON_CLIP_BOTTOM
+)
+
+type Polygon []float64
+
+
+type PolygonEdge struct {
+ X, Slope float64
+ FirstLine, LastLine int
+ Winding int16
+}
+
+
+//! Calculates the edges of the polygon with transformation and clipping to edges array.
+/*! \param startIndex the index for the first vertex.
+ * \param vertexCount the amount of vertices to convert.
+ * \param edges the array for result edges. This should be able to contain 2*aVertexCount edges.
+ * \param tr the transformation matrix for the polygon.
+ * \param aClipRectangle the clip rectangle.
+ * \return the amount of edges in the result.
+ */
+func (p Polygon) getEdges(startIndex, vertexCount int, edges []PolygonEdge, tr [6]float64, clipBound [4]float64) int {
+ startIndex = startIndex * 2
+ endIndex := startIndex + (vertexCount * 2)
+ if endIndex > len(p) {
+ endIndex = len(p)
+ }
+
+ x := p[startIndex]
+ y := p[startIndex+1]
+ // inline transformation
+ prevX := x*tr[0] + y*tr[2] + tr[4]
+ prevY := x*tr[1] + y*tr[3] + tr[5]
+
+ //! Calculates the clip flags for a point.
+ prevClipFlags := POLYGON_CLIP_NONE
+ if prevX < clipBound[0] {
+ prevClipFlags |= POLYGON_CLIP_LEFT
+ } else if prevX >= clipBound[2] {
+ prevClipFlags |= POLYGON_CLIP_RIGHT
+ }
+
+ if prevY < clipBound[1] {
+ prevClipFlags |= POLYGON_CLIP_TOP
+ } else if prevY >= clipBound[3] {
+ prevClipFlags |= POLYGON_CLIP_BOTTOM
+ }
+
+ edgeCount := 0
+ var k, clipFlags, clipSum, clipUnion int
+ var xleft, yleft, xright, yright, oldY, maxX, minX float64
+ var swapWinding int16
+ for n := startIndex; n < endIndex; n = n + 2 {
+ k = (n + 2) % len(p)
+ x = p[k]*tr[0] + p[k+1]*tr[2] + tr[4]
+ y = p[k]*tr[1] + p[k+1]*tr[3] + tr[5]
+
+ //! Calculates the clip flags for a point.
+ clipFlags = POLYGON_CLIP_NONE
+ if prevX < clipBound[0] {
+ clipFlags |= POLYGON_CLIP_LEFT
+ } else if prevX >= clipBound[2] {
+ clipFlags |= POLYGON_CLIP_RIGHT
+ }
+ if prevY < clipBound[1] {
+ clipFlags |= POLYGON_CLIP_TOP
+ } else if prevY >= clipBound[3] {
+ clipFlags |= POLYGON_CLIP_BOTTOM
+ }
+
+ clipSum = prevClipFlags | clipFlags
+ clipUnion = prevClipFlags & clipFlags
+
+ // Skip all edges that are either completely outside at the top or at the bottom.
+ if (clipUnion & (POLYGON_CLIP_TOP | POLYGON_CLIP_BOTTOM)) == 0 {
+ if (clipUnion & POLYGON_CLIP_RIGHT) != 0 {
+ // Both clip to right, edge is a vertical line on the right side
+ if getVerticalEdge(prevY, y, clipBound[2], &(edges[edgeCount]), clipBound) {
+ edgeCount++
+ }
+ } else if (clipUnion & POLYGON_CLIP_LEFT) != 0 {
+ // Both clip to left, edge is a vertical line on the left side
+ if getVerticalEdge(prevY, y, clipBound[0], &(edges[edgeCount]), clipBound) {
+ edgeCount++
+ }
+ } else if (clipSum & (POLYGON_CLIP_RIGHT | POLYGON_CLIP_LEFT)) == 0 {
+ // No clipping in the horizontal direction
+ if getEdge(prevX, prevY, x, y, &(edges[edgeCount]), clipBound) {
+ edgeCount++
+ }
+ } else {
+ // Clips to left or right or both.
+
+ if x < prevX {
+ xleft, yleft = x, y
+ xright, yright = prevX, prevY
+ swapWinding = -1
+ } else {
+ xleft, yleft = prevX, prevY
+ xright, yright = x, y
+ swapWinding = 1
+ }
+
+ slope := (yright - yleft) / (xright - xleft)
+
+ if (clipSum & POLYGON_CLIP_RIGHT) != 0 {
+ // calculate new position for the right vertex
+ oldY = yright
+ maxX = clipBound[2]
+
+ yright = yleft + (maxX-xleft)*slope
+ xright = maxX
+
+ // add vertical edge for the overflowing part
+ if getVerticalEdge(yright, oldY, maxX, &(edges[edgeCount]), clipBound) {
+ edges[edgeCount].Winding *= swapWinding
+ edgeCount++
+ }
+ }
+
+ if (clipSum & POLYGON_CLIP_LEFT) != 0 {
+ // calculate new position for the left vertex
+ oldY = yleft
+ minX = clipBound[0]
+
+ yleft = yleft + (minX-xleft)*slope
+ xleft = minX
+
+ // add vertical edge for the overflowing part
+ if getVerticalEdge(oldY, yleft, minX, &(edges[edgeCount]), clipBound) {
+ edges[edgeCount].Winding *= swapWinding
+ edgeCount++
+ }
+ }
+
+ if getEdge(xleft, yleft, xright, yright, &(edges[edgeCount]), clipBound) {
+ edges[edgeCount].Winding *= swapWinding
+ edgeCount++
+ }
+ }
+ }
+
+ prevClipFlags = clipFlags
+ prevX = x
+ prevY = y
+ }
+
+ return edgeCount
+}
+
+
+//! Creates a polygon edge between two vectors.
+/*! Clips the edge vertically to the clip rectangle. Returns true for edges that
+ * should be rendered, false for others.
+ */
+func getEdge(x0, y0, x1, y1 float64, edge *PolygonEdge, clipBound [4]float64) bool {
+ var startX, startY, endX, endY float64
+ var winding int16
+
+ if y0 <= y1 {
+ startX = x0
+ startY = y0
+ endX = x1
+ endY = y1
+ winding = 1
+ } else {
+ startX = x1
+ startY = y1
+ endX = x0
+ endY = y0
+ winding = -1
+ }
+
+ // Essentially, firstLine is floor(startY + 1) and lastLine is floor(endY).
+ // These are refactored to integer casts in order to avoid function
+ // calls. The difference with integer cast is that numbers are always
+ // rounded towards zero. Since values smaller than zero get clipped away,
+ // only coordinates between 0 and -1 require greater attention as they
+ // also round to zero. The problems in this range can be avoided by
+ // adding one to the values before conversion and subtracting after it.
+
+ firstLine := int(math.Floor(startY)) + 1
+ lastLine := int(math.Floor(endY))
+
+ minClip := int(clipBound[1])
+ maxClip := int(clipBound[3])
+
+ // If start and end are on the same line, the edge doesn't cross
+ // any lines and thus can be ignored.
+ // If the end is smaller than the first line, edge is out.
+ // If the start is larger than the last line, edge is out.
+ if firstLine > lastLine || lastLine < minClip || firstLine >= maxClip {
+ return false
+ }
+
+ // Adjust the start based on the target.
+ if firstLine < minClip {
+ firstLine = minClip
+ }
+
+ if lastLine >= maxClip {
+ lastLine = maxClip - 1
+ }
+ edge.Slope = (endX - startX) / (endY - startY)
+ edge.X = startX + (float64(firstLine)-startY)*edge.Slope
+ edge.Winding = winding
+ edge.FirstLine = firstLine
+ edge.LastLine = lastLine
+
+ return true
+}
+
+
+//! Creates a vertical polygon edge between two y values.
+/*! Clips the edge vertically to the clip rectangle. Returns true for edges that
+ * should be rendered, false for others.
+ */
+func getVerticalEdge(startY, endY, x float64, edge *PolygonEdge, clipBound [4]float64) bool {
+ var start, end float64
+ var winding int16
+ if startY < endY {
+ start = startY
+ end = endY
+ winding = 1
+ } else {
+ start = endY
+ end = startY
+ winding = -1
+ }
+
+ firstLine := int(math.Floor(start)) + 1
+ lastLine := int(math.Floor(end))
+
+ minClip := int(clipBound[1])
+ maxClip := int(clipBound[3])
+
+ // If start and end are on the same line, the edge doesn't cross
+ // any lines and thus can be ignored.
+ // If the end is smaller than the first line, edge is out.
+ // If the start is larger than the last line, edge is out.
+ if firstLine > lastLine || lastLine < minClip || firstLine >= maxClip {
+ return false
+ }
+
+ // Adjust the start based on the clip rect.
+ if firstLine < minClip {
+ firstLine = minClip
+ }
+ if lastLine >= maxClip {
+ lastLine = maxClip - 1
+ }
+
+ edge.Slope = 0
+ edge.X = x
+ edge.Winding = winding
+ edge.FirstLine = firstLine
+ edge.LastLine = lastLine
+
+ return true
+}
diff --git a/draw2d/raster/raster_test.go b/draw2d/raster/raster_test.go
new file mode 100644
index 0000000..716088d
--- /dev/null
+++ b/draw2d/raster/raster_test.go
@@ -0,0 +1,124 @@
+package raster
+
+import (
+ "testing"
+ "log"
+ "image"
+ "os"
+ "bufio"
+ "image/png"
+ "draw2d.googlecode.com/hg/draw2d/curve"
+ "freetype-go.googlecode.com/hg/freetype/raster"
+)
+
+var flattening_threshold float64 = 0.5
+
+func savepng(filePath string, m image.Image) {
+ f, err := os.Create(filePath)
+ 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)
+ }
+}
+
+type Path struct {
+ points []float64
+}
+
+func (p *Path) LineTo(x, y float64) {
+ if len(p.points)+2 > cap(p.points) {
+ points := make([]float64, len(p.points)+2, len(p.points)+32)
+ copy(points, p.points)
+ p.points = points
+ } else {
+ p.points = p.points[0 : len(p.points)+2]
+ }
+ p.points[len(p.points)-2] = x
+ p.points[len(p.points)-1] = y
+}
+
+func TestRasterizer8BitsSample(t *testing.T) {
+ img := image.NewRGBA(200, 200)
+ var p Path
+ p.LineTo(10, 190)
+ c := curve.CubicCurveFloat64{10, 190, 10, 10, 190, 10, 190, 190}
+ c.Segment(&p, flattening_threshold)
+ poly := Polygon(p.points)
+ color := image.RGBAColor{0, 0, 0, 0xff}
+ tr := [6]float64{1, 0, 0, 1, 0, 0}
+ r := NewRasterizer8BitsSample(200, 200)
+ //PolylineBresenham(img, image.Black, poly...)
+
+
+ r.RenderEvenOdd(img, &color, &poly, tr)
+ savepng("_testRasterizer8BitsSample.png", img)
+}
+
+func TestFreetype(t *testing.T) {
+ var p Path
+ c := curve.CubicCurveFloat64{10, 190, 10, 10, 190, 10, 190, 190}
+ c.Segment(&p, flattening_threshold)
+ poly := Polygon(p.points)
+ color := image.RGBAColor{0, 0, 0, 0xff}
+
+ img := image.NewRGBA(200, 200)
+ rasterizer := raster.NewRasterizer(200, 200)
+ rasterizer.UseNonZeroWinding = false
+ rasterizer.Start(raster.Point{raster.Fix32(10 * 256), raster.Fix32(190 * 256)})
+ for j := 0; j < len(poly); j = j + 2 {
+ rasterizer.Add1(raster.Point{raster.Fix32(poly[j] * 256), raster.Fix32(poly[j+1] * 256)})
+ }
+ painter := raster.NewRGBAPainter(img)
+ painter.SetColor(color)
+ rasterizer.Rasterize(painter)
+
+ savepng("_testFreetype.png", img)
+}
+
+func BenchmarkRasterizer8BitsSample(b *testing.B) {
+ var p Path
+ p.LineTo(10, 190)
+ c := curve.CubicCurveFloat64{10, 190, 10, 10, 190, 10, 190, 190}
+ c.Segment(&p, flattening_threshold)
+ poly := Polygon(p.points)
+ color := image.RGBAColor{0, 0, 0, 0xff}
+ tr := [6]float64{1, 0, 0, 1, 0, 0}
+ for i := 0; i < b.N; i++ {
+ img := image.NewRGBA(200, 200)
+ rasterizer := NewRasterizer8BitsSample(200, 200)
+ rasterizer.RenderEvenOdd(img, &color, &poly, tr)
+ }
+}
+
+func BenchmarkFreetype(b *testing.B) {
+ var p Path
+ c := curve.CubicCurveFloat64{10, 190, 10, 10, 190, 10, 190, 190}
+ c.Segment(&p, flattening_threshold)
+ poly := Polygon(p.points)
+ color := image.RGBAColor{0, 0, 0, 0xff}
+
+ for i := 0; i < b.N; i++ {
+ img := image.NewRGBA(200, 200)
+ rasterizer := raster.NewRasterizer(200, 200)
+ rasterizer.UseNonZeroWinding = false
+ rasterizer.Start(raster.Point{raster.Fix32(10 * 256), raster.Fix32(190 * 256)})
+ for j := 0; j < len(poly); j = j + 2 {
+ rasterizer.Add1(raster.Point{raster.Fix32(poly[j] * 256), raster.Fix32(poly[j+1] * 256)})
+ }
+ painter := raster.NewRGBAPainter(img)
+ painter.SetColor(color)
+ rasterizer.Rasterize(painter)
+ }
+}