Add simple line rasteriser
This commit is contained in:
parent
9606b186e0
commit
713b1fa1f6
5 changed files with 235 additions and 140 deletions
|
@ -15,7 +15,7 @@ core
|
|||
_obj
|
||||
_test
|
||||
out.png
|
||||
_testmain.go
|
||||
_test*
|
||||
|
||||
syntax: regexp
|
||||
\.dll$
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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 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("<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 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 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))
|
||||
}
|
||||
return img
|
||||
}
|
||||
|
||||
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 BenchmarkCubicCurveCasteljauRecTest1(b *testing.B) {
|
||||
var s []float64
|
||||
d := cf64Test1.EstimateDistance()
|
||||
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++ {
|
||||
s = make([]float64, 0, numSegments)
|
||||
s = cf64Test1.SegmentCasteljauRec(s)
|
||||
for _, curve := range testsFloat64 {
|
||||
d := curve.EstimateDistance()
|
||||
numSegments := int(d * 0.25)
|
||||
s := make([]float64, 0, numSegments)
|
||||
curve.SegmentRec(s)
|
||||
}
|
||||
}
|
||||
log.Printf("Num of points: %d\n", len(s))
|
||||
}
|
||||
|
||||
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)
|
||||
func BenchmarkCubicCurveCasteljau(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
s = make([]float64, 0, numSegments)
|
||||
s = cf64Test2.SegmentCasteljauRec(s)
|
||||
}
|
||||
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)
|
||||
for _, curve := range testsFloat64 {
|
||||
d := curve.EstimateDistance()
|
||||
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)
|
||||
s := make([]float64, 0, numSegments)
|
||||
curve.Segment(s)
|
||||
}
|
||||
}
|
||||
log.Printf("Num of points: %d\n", len(s))
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
log.Printf("Num of points: %d\n", len(s))
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
7
draw2d/raster/Makefile
Normal file
7
draw2d/raster/Makefile
Normal 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
47
draw2d/raster/line.go
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue