Add simple line rasteriser

This commit is contained in:
Laurent Le Goff 2011-05-18 22:59:30 +02:00
parent 9606b186e0
commit 713b1fa1f6
5 changed files with 235 additions and 140 deletions

View file

@ -15,7 +15,7 @@ core
_obj
_test
out.png
_testmain.go
_test*
syntax: regexp
\.dll$

View file

@ -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
}

View file

@ -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("<html><body>"))
for i := 0; i < len(testsFloat64); i++ {
f.Write([]byte(fmt.Sprintf("<div><img src='_testRec%d.png'/><img src='_test%d.png'/></div>", i, i)))
}
f.Write([]byte("</body></html>"))
}
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()
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 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)
for i := 0; i < b.N; i++ {
s = make([]float64, 0, numSegments)
s = cf64Test2.SegmentCasteljau(s)
}
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)
}
}
}

7
draw2d/raster/Makefile Normal file
View file

@ -0,0 +1,7 @@
include $(GOROOT)/src/Make.inc
TARG=draw2d.googlecode.com/hg/draw2d/raster
GOFILES=\
line.go\
include $(GOROOT)/src/Make.pkg

47
draw2d/raster/line.go Normal file
View file

@ -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
}
}
}