From 713b1fa1f63affe048c6ba595d7a4d67b2dd0a86 Mon Sep 17 00:00:00 2001 From: Laurent Le Goff Date: Wed, 18 May 2011 22:59:30 +0200 Subject: [PATCH] Add simple line rasteriser --- .hgignore | 2 +- draw2d/curve/curve_float64.go | 139 +++++++++++++------------- draw2d/curve/curve_test.go | 180 +++++++++++++++++++++------------- draw2d/raster/Makefile | 7 ++ draw2d/raster/line.go | 47 +++++++++ 5 files changed, 235 insertions(+), 140 deletions(-) create mode 100644 draw2d/raster/Makefile create mode 100644 draw2d/raster/line.go diff --git a/.hgignore b/.hgignore index 5ac6a8e..22a58d5 100644 --- a/.hgignore +++ b/.hgignore @@ -15,7 +15,7 @@ core _obj _test out.png -_testmain.go +_test* syntax: regexp \.dll$ diff --git a/draw2d/curve/curve_float64.go b/draw2d/curve/curve_float64.go index f08a9f5..4d8ecfa 100644 --- a/draw2d/curve/curve_float64.go +++ b/draw2d/curve/curve_float64.go @@ -11,11 +11,7 @@ var ( ) type CubicCurveFloat64 struct { - x1, y1, x2, y2, x3, y3, x4, y4 float64 -} - -func NewCubicCurveFloat64(x1, y1, x2, y2, x3, y3, x4, y4 float64) *CubicCurveFloat64 { - return &CubicCurveFloat64{x1, y1, x2, y2, x3, y3, x4, y4} + X1, Y1, X2, Y2, X3, Y3, X4, Y4 float64 } //mu ranges from 0 to 1, start to end of curve @@ -25,107 +21,112 @@ func (c *CubicCurveFloat64) ArbitraryPoint(mu float64) (x, y float64) { 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 + 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) { inv_t := (1 - t) - c1.x1, c1.y1 = c.x1, c.y1 - c2.x4, c2.y4 = c.x4, c.y4 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + c2.X1, c2.Y1 = c1.X4, c1.Y4 } func (c *CubicCurveFloat64) Subdivide(c1, c2 *CubicCurveFloat64) { // 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 + 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 } 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 + 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) } -// subdivide the curve in straight lines using Casteljau subdivision +// subdivide the curve in straight lines using straight line approximation and Casteljau recursive subdivision // and computing minimal distance tolerance -func (c *CubicCurveFloat64) SegmentCasteljauRec(segments []float64) []float64 { +func (c *CubicCurveFloat64) SegmentRec(segments []float64) []float64 { // reinit segments segments = segments[0 : len(segments)+2] - segments[len(segments)-2] = c.x1 - segments[len(segments)-1] = c.y1 - segments = c.segmentCasteljauRec(segments) + segments[len(segments)-2] = c.X1 + segments[len(segments)-1] = c.Y1 + segments = c.segmentRec(segments) segments = segments[0 : len(segments)+2] - segments[len(segments)-2] = c.x4 - segments[len(segments)-1] = c.y4 + segments[len(segments)-2] = c.X4 + segments[len(segments)-1] = c.Y4 return segments } -func (c *CubicCurveFloat64) segmentCasteljauRec(segments []float64) []float64 { +func (c *CubicCurveFloat64) segmentRec(segments []float64) []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 + 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)) + 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) { segments = segments[0 : len(segments)+2] - segments[len(segments)-2] = c2.x1 - segments[len(segments)-1] = c2.y1 + segments[len(segments)-2] = c2.X4 + segments[len(segments)-1] = c2.Y4 return segments } // Continue subdivision //---------------------- - segments = c1.segmentCasteljauRec(segments) - segments = c2.segmentCasteljauRec(segments) + segments = c1.segmentRec(segments) + segments = c2.segmentRec(segments) return segments } -func (curve *CubicCurveFloat64) SegmentCasteljau(segments []float64) ([]float64) { +func (curve *CubicCurveFloat64) Segment(segments []float64) ([]float64) { + // Add the first point + segments = segments[0 : len(segments)+2] + segments[len(segments)-2] = curve.X1 + segments[len(segments)-1] = curve.Y1 + var curves [32]CubicCurveFloat64 curves[0] = *curve i := 0 @@ -134,16 +135,16 @@ func (curve *CubicCurveFloat64) SegmentCasteljau(segments []float64) ([]float64) var dx, dy, d2, d3 float64 for i >= 0 { c = &curves[i] - dx = c.x4 - c.x1 - dy = c.y4 - c.y1 + 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)) + 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 { segments = segments[0 : len(segments)+2] - segments[len(segments)-2] = c.x1 - segments[len(segments)-1] = c.y1 + segments[len(segments)-2] = c.X4 + segments[len(segments)-1] = c.Y4 i--; } else { // second half of bezier go lower onto the stack @@ -151,8 +152,6 @@ func (curve *CubicCurveFloat64) SegmentCasteljau(segments []float64) ([]float64) i++; } } - segments = segments[0 : len(segments)+2] - segments[len(segments)-2] = curve.x1 - segments[len(segments)-1] = curve.y1 return segments -} \ No newline at end of file +} + diff --git a/draw2d/curve/curve_test.go b/draw2d/curve/curve_test.go index cf621f5..56fbab1 100644 --- a/draw2d/curve/curve_test.go +++ b/draw2d/curve/curve_test.go @@ -3,86 +3,128 @@ package curve import ( "testing" "log" + "fmt" + "os" + "bufio" + "image" + "image/png" + "exp/draw" + "draw2d.googlecode.com/hg/draw2d/raster" ) var ( - cf64Test1 = NewCubicCurveFloat64(100, 100, 200, 100, 100, 200, 200, 200) - cf64Test2 = NewCubicCurveFloat64(100, 100, 300, 200, 200, 200, 200, 100) + testsFloat64 = []CubicCurveFloat64 { + CubicCurveFloat64{100, 100, 200, 100, 100, 200, 200, 200}, + CubicCurveFloat64{100, 100, 300, 200, 200, 200, 200, 100}, + } ) -func TestCubicCurveCasteljauRecTest1(t *testing.T) { - var s []float64 - d := cf64Test1.EstimateDistance() - log.Printf("Distance estimation: %f\n", d) - numSegments := int(d * 0.25) - log.Printf("Max segments estimation: %d\n", numSegments) - s = make([]float64, 0, numSegments) - s = cf64Test1.SegmentCasteljauRec(s) - log.Printf("points: %v\n", s) - log.Printf("Num of points: %d\n", len(s)) -} - -func TestCubicCurveCasteljauTest1(t *testing.T) { - var s []float64 - d := cf64Test1.EstimateDistance() - log.Printf("Distance estimation: %f\n", d) - numSegments := int(d * 0.25) - log.Printf("Max segments estimation: %d\n", numSegments) - s = make([]float64, 0, numSegments) - s = cf64Test1.SegmentCasteljau(s) - log.Printf("points: %v\n", s) - log.Printf("Num of points: %d\n", len(s)) -} - - -func BenchmarkCubicCurveCasteljauRecTest1(b *testing.B) { - var s []float64 - d := cf64Test1.EstimateDistance() - log.Printf("Distance estimation: %f\n", d) - numSegments := int(d * 0.25) - log.Printf("Max segments estimation: %d\n", numSegments) - for i := 0; i < b.N; i++ { - s = make([]float64, 0, numSegments) - s = cf64Test1.SegmentCasteljauRec(s) +func init() { + f, err := os.Create("_test.html") + if err != nil { + log.Println(err) + os.Exit(1) } - log.Printf("Num of points: %d\n", len(s)) + defer f.Close() + log.Printf("Create html viewer") + f.Write([]byte("")) + for i := 0; i < len(testsFloat64); i++ { + f.Write([]byte(fmt.Sprintf("
", i, i))) + } + f.Write([]byte("")) + + + } -func BenchmarkCubicCurveCasteljauRecTest2(b *testing.B) { - var s []float64 - d := cf64Test1.EstimateDistance() - log.Printf("Distance estimation: %f\n", d) - numSegments := int(d * 0.25) - log.Printf("Max segments estimation: %d\n", numSegments) - for i := 0; i < b.N; i++ { - s = make([]float64, 0, numSegments) - s = cf64Test2.SegmentCasteljauRec(s) +func rasterPolyline(img draw.Image, c image.Color, s ...float64) image.Image { + for i := 2; i < len(s); i+=2 { + raster.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)) } - log.Printf("Num of points: %d\n", len(s)) -} -func BenchmarkCubicCurveCasteljauTest1(b *testing.B) { - var s []float64 - d := cf64Test1.EstimateDistance() - log.Printf("Distance estimation: %f\n", d) - numSegments := int(d * 0.25) - log.Printf("Max segments estimation: %d\n", numSegments) - for i := 0; i < b.N; i++ { - s = make([]float64, 0, numSegments) - s = cf64Test1.SegmentCasteljau(s) - } - log.Printf("Num of points: %d\n", len(s)) + return img } -func BenchmarkCubicCurveCasteljauTest2(b *testing.B) { - var s []float64 - d := cf64Test1.EstimateDistance() - log.Printf("Distance estimation: %f\n", d) - numSegments := int(d * 0.25) - log.Printf("Max segments estimation: %d\n", numSegments) - for i := 0; i < b.N; i++ { - s = make([]float64, 0, numSegments) - s = cf64Test2.SegmentCasteljau(s) +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) } - log.Printf("Num of points: %d\n", len(s)) } + + +func TestCubicCurveCasteljauRec(t *testing.T) { + for i, curve := range testsFloat64 { + d := curve.EstimateDistance() + log.Printf("Distance estimation: %f\n", d) + numSegments := int(d * 0.25) + log.Printf("Max segments estimation: %d\n", numSegments) + s := make([]float64, 0, numSegments) + s = curve.SegmentRec(s) + img := image.NewNRGBA(300, 300) + rasterPolyline(img, image.NRGBAColor{0xff, 0, 0, 0xff}, curve.X1, curve.Y1, curve.X2, curve.Y2, curve.X3, curve.Y3, curve.X4, curve.Y4) + savepng(fmt.Sprintf("_testRec%d.png", i), rasterPolyline(img, image.Black, s...)) + log.Printf("Num of points: %d\n", len(s)) + } +} + +func TestCubicCurveCasteljau(t *testing.T) { + for i, curve := range testsFloat64 { + d := curve.EstimateDistance() + log.Printf("Distance estimation: %f\n", d) + numSegments := int(d * 0.25) + log.Printf("Max segments estimation: %d\n", numSegments) + s := make([]float64, 0, numSegments) + s = curve.Segment(s) + img := image.NewNRGBA(300, 300) + rasterPolyline(img, image.NRGBAColor{0xff, 0, 0, 0xff}, curve.X1, curve.Y1, curve.X2, curve.Y2, curve.X3, curve.Y3, curve.X4, curve.Y4) + savepng(fmt.Sprintf("_test%d.png", i), rasterPolyline(img, image.Black, s...)) + log.Printf("Num of points: %d\n", len(s)) + } +} + + +func BenchmarkCubicCurveCasteljauRec(b *testing.B) { + for i := 0; i < b.N; i++ { + for _, curve := range testsFloat64 { + d := curve.EstimateDistance() + numSegments := int(d * 0.25) + s := make([]float64, 0, numSegments) + curve.SegmentRec(s) + } + } +} + +func BenchmarkCubicCurveCasteljau(b *testing.B) { + for i := 0; i < b.N; i++ { + for _, curve := range testsFloat64 { + d := curve.EstimateDistance() + numSegments := int(d * 0.25) + s := make([]float64, 0, numSegments) + curve.Segment(s) + } + } +} + + + + + + + + + + diff --git a/draw2d/raster/Makefile b/draw2d/raster/Makefile new file mode 100644 index 0000000..52c5288 --- /dev/null +++ b/draw2d/raster/Makefile @@ -0,0 +1,7 @@ +include $(GOROOT)/src/Make.inc + +TARG=draw2d.googlecode.com/hg/draw2d/raster +GOFILES=\ + line.go\ + +include $(GOROOT)/src/Make.pkg diff --git a/draw2d/raster/line.go b/draw2d/raster/line.go new file mode 100644 index 0000000..183c395 --- /dev/null +++ b/draw2d/raster/line.go @@ -0,0 +1,47 @@ +package raster + +import ( + "exp/draw" + "image" +) + +func abs(i int) int { + if i < 0 { + return -i + } + return i +} + +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 + } + } +}