From 25f7be3323535b68c4d3e798bb90f6704a62cc09 Mon Sep 17 00:00:00 2001 From: Laurent Le Goff Date: Fri, 27 May 2011 16:04:24 +0200 Subject: [PATCH] Implement Edge/Flag antialias filler --- draw2d/curve/_testmain.go | 2 +- draw2d/curve/cubic_float64.go | 123 ++- draw2d/curve/cubic_float64_others.go | 1158 +++++++++++++------------- draw2d/curve/curve_test.go | 503 +++++------ draw2d/curve/quad_float64.go | 95 ++- draw2d/raster/Makefile | 4 + draw2d/raster/coverage_table.go | 135 +++ draw2d/raster/fillerAA.go | 178 ++++ draw2d/raster/line.go | 108 +-- draw2d/raster/polygon.go | 273 ++++++ draw2d/raster/raster_test.go | 124 +++ 11 files changed, 1709 insertions(+), 994 deletions(-) create mode 100644 draw2d/raster/coverage_table.go create mode 100644 draw2d/raster/fillerAA.go create mode 100644 draw2d/raster/polygon.go create mode 100644 draw2d/raster/raster_test.go 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\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("")) - -} - -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\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("")) + +} + +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) + } +}