Implement Edge/Flag antialias filler

This commit is contained in:
Laurent Le Goff 2011-05-27 16:04:24 +02:00
parent c763e1614f
commit 25f7be3323
11 changed files with 1709 additions and 994 deletions

View File

@ -2,7 +2,7 @@ package main
import "draw2d.googlecode.com/hg/draw2d/curve" import "draw2d.googlecode.com/hg/draw2d/curve"
import "testing" import "testing"
import __os__ "os" import __os__ "os"
import __regexp__ "regexp" import __regexp__ "regexp"
var tests = []testing.InternalTest{ var tests = []testing.InternalTest{

View File

@ -1,68 +1,67 @@
// Copyright 2010 The draw2d Authors. All rights reserved. // Copyright 2010 The draw2d Authors. All rights reserved.
// created: 17/05/2011 by Laurent Le Goff // created: 17/05/2011 by Laurent Le Goff
package curve package curve
import ( import (
"math" "math"
) )
const ( const (
CurveRecursionLimit = 32 CurveRecursionLimit = 32
) )
type CubicCurveFloat64 struct { // X1, Y1, X2, Y2, X3, Y3, X4, Y4 float64
X1, Y1, X2, Y2, X3, Y3, X4, Y4 float64 type CubicCurveFloat64 [8]float64
}
type LineTracer interface {
type LineTracer interface { LineTo(x, y float64)
LineTo(x, y float64) }
}
func (c *CubicCurveFloat64) Subdivide(c1, c2 *CubicCurveFloat64) (x23, y23 float64) {
func (c *CubicCurveFloat64) Subdivide(c1, c2 *CubicCurveFloat64) (x23, y23 float64) {
// Calculate all the mid-points of the line segments // Calculate all the mid-points of the line segments
//---------------------- //----------------------
c1.X1, c1.Y1 = c.X1, c.Y1 c1[0], c1[1] = c[0], c[1]
c2.X4, c2.Y4 = c.X4, c.Y4 c2[6], c2[7] = c[6], c[7]
c1.X2 = (c.X1 + c.X2) / 2 c1[2] = (c[0] + c[2]) / 2
c1.Y2 = (c.Y1 + c.Y2) / 2 c1[3] = (c[1] + c[3]) / 2
x23 = (c.X2 + c.X3) / 2 x23 = (c[2] + c[4]) / 2
y23 = (c.Y2 + c.Y3) / 2 y23 = (c[3] + c[5]) / 2
c2.X3 = (c.X3 + c.X4) / 2 c2[4] = (c[4] + c[6]) / 2
c2.Y3 = (c.Y3 + c.Y4) / 2 c2[5] = (c[5] + c[7]) / 2
c1.X3 = (c1.X2 + x23) / 2 c1[4] = (c1[2] + x23) / 2
c1.Y3 = (c1.Y2 + y23) / 2 c1[5] = (c1[3] + y23) / 2
c2.X2 = (x23 + c2.X3) / 2 c2[2] = (x23 + c2[4]) / 2
c2.Y2 = (y23 + c2.Y3) / 2 c2[3] = (y23 + c2[5]) / 2
c1.X4 = (c1.X3 + c2.X2) / 2 c1[6] = (c1[4] + c2[2]) / 2
c1.Y4 = (c1.Y3 + c2.Y2) / 2 c1[7] = (c1[5] + c2[3]) / 2
c2.X1, c2.Y1 = c1.X4, c1.Y4 c2[0], c2[1] = c1[6], c1[7]
return return
} }
func (curve *CubicCurveFloat64) Segment(t LineTracer, flattening_threshold float64) { func (curve *CubicCurveFloat64) Segment(t LineTracer, flattening_threshold float64) {
var curves [CurveRecursionLimit]CubicCurveFloat64 var curves [CurveRecursionLimit]CubicCurveFloat64
curves[0] = *curve curves[0] = *curve
i := 0 i := 0
// current curve // current curve
var c *CubicCurveFloat64 var c *CubicCurveFloat64
var dx, dy, d2, d3 float64 var dx, dy, d2, d3 float64
for i >= 0 { for i >= 0 {
c = &curves[i] c = &curves[i]
dx = c.X4 - c.X1 dx = c[6] - c[0]
dy = c.Y4 - c.Y1 dy = c[7] - c[1]
d2 = math.Fabs(((c.X2-c.X4)*dy - (c.Y2-c.Y4)*dx)) d2 = math.Fabs(((c[2]-c[6])*dy - (c[3]-c[7])*dx))
d3 = math.Fabs(((c.X3-c.X4)*dy - (c.Y3-c.Y4)*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 { if (d2+d3)*(d2+d3) < flattening_threshold*(dx*dx+dy*dy) || i == len(curves)-1 {
t.LineTo(c.X4, c.Y4) t.LineTo(c[6], c[7])
i-- i--
} else { } else {
// second half of bezier go lower onto the stack // second half of bezier go lower onto the stack
c.Subdivide(&curves[i+1], &curves[i]) c.Subdivide(&curves[i+1], &curves[i])
i++ i++
} }
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,94 +1,94 @@
package curve package curve
import ( import (
"testing" "testing"
"log" "log"
"fmt" "fmt"
"os" "os"
"bufio" "bufio"
"image" "image"
"image/png" "image/png"
"exp/draw" "exp/draw"
"draw2d.googlecode.com/hg/draw2d/raster" "draw2d.googlecode.com/hg/draw2d/raster"
) )
var ( var (
flattening_threshold float64 = 0.5 flattening_threshold float64 = 0.5
testsCubicFloat64 = []CubicCurveFloat64{ testsCubicFloat64 = []CubicCurveFloat64{
CubicCurveFloat64{100, 100, 200, 100, 100, 200, 200, 200}, CubicCurveFloat64{100, 100, 200, 100, 100, 200, 200, 200},
CubicCurveFloat64{100, 100, 300, 200, 200, 200, 300, 100}, CubicCurveFloat64{100, 100, 300, 200, 200, 200, 300, 100},
CubicCurveFloat64{100, 100, 0, 300, 200, 0, 300, 300}, CubicCurveFloat64{100, 100, 0, 300, 200, 0, 300, 300},
CubicCurveFloat64{150, 290, 10, 10, 290, 10, 150, 290}, CubicCurveFloat64{150, 290, 10, 10, 290, 10, 150, 290},
CubicCurveFloat64{10, 290, 10, 10, 290, 10, 290, 290}, CubicCurveFloat64{10, 290, 10, 10, 290, 10, 290, 290},
CubicCurveFloat64{100, 290, 290, 10, 10, 10, 200, 290}, CubicCurveFloat64{100, 290, 290, 10, 10, 10, 200, 290},
} }
testsQuadFloat64 = []QuadCurveFloat64{ testsQuadFloat64 = []QuadCurveFloat64{
QuadCurveFloat64{100, 100, 200, 100, 200, 200}, QuadCurveFloat64{100, 100, 200, 100, 200, 200},
QuadCurveFloat64{100, 100, 290, 200, 290, 100}, QuadCurveFloat64{100, 100, 290, 200, 290, 100},
QuadCurveFloat64{100, 100, 0, 290, 200, 290}, QuadCurveFloat64{100, 100, 0, 290, 200, 290},
QuadCurveFloat64{150, 290, 10, 10, 290, 290}, QuadCurveFloat64{150, 290, 10, 10, 290, 290},
QuadCurveFloat64{10, 290, 10, 10, 290, 290}, QuadCurveFloat64{10, 290, 10, 10, 290, 290},
QuadCurveFloat64{100, 290, 290, 10, 120, 290}, QuadCurveFloat64{100, 290, 290, 10, 120, 290},
} }
) )
type Path struct { type Path struct {
points []float64 points []float64
} }
func (p *Path) LineTo(x, y float64) { func (p *Path) LineTo(x, y float64) {
if len(p.points)+2 > cap(p.points) { if len(p.points)+2 > cap(p.points) {
points := make([]float64, len(p.points)+2, len(p.points)+32) points := make([]float64, len(p.points)+2, len(p.points)+32)
copy(points, p.points) copy(points, p.points)
p.points = points p.points = points
} else { } else {
p.points = p.points[0 : len(p.points)+2] p.points = p.points[0 : len(p.points)+2]
} }
p.points[len(p.points)-2] = x p.points[len(p.points)-2] = x
p.points[len(p.points)-1] = y p.points[len(p.points)-1] = y
} }
func init() { func init() {
f, err := os.Create("_test.html") f, err := os.Create("_test.html")
if err != nil { if err != nil {
log.Println(err) log.Println(err)
os.Exit(1) os.Exit(1)
} }
defer f.Close() defer f.Close()
log.Printf("Create html viewer") log.Printf("Create html viewer")
f.Write([]byte("<html><body>")) f.Write([]byte("<html><body>"))
for i := 0; i < len(testsCubicFloat64); i++ { for i := 0; i < len(testsCubicFloat64); i++ {
f.Write([]byte(fmt.Sprintf("<div><img src='_testRec%d.png'/>\n<img src='_test%d.png'/>\n<img src='_testAdaptiveRec%d.png'/>\n<img src='_testAdaptive%d.png'/>\n<img src='_testParabolic%d.png'/>\n</div>\n", i, i, i, i, i))) f.Write([]byte(fmt.Sprintf("<div><img src='_testRec%d.png'/>\n<img src='_test%d.png'/>\n<img src='_testAdaptiveRec%d.png'/>\n<img src='_testAdaptive%d.png'/>\n<img src='_testParabolic%d.png'/>\n</div>\n", i, i, i, i, i)))
} }
for i := 0; i < len(testsQuadFloat64); i++ { for i := 0; i < len(testsQuadFloat64); i++ {
f.Write([]byte(fmt.Sprintf("<div><img src='_testQuad%d.png'/>\n</div>\n", i))) f.Write([]byte(fmt.Sprintf("<div><img src='_testQuad%d.png'/>\n</div>\n", i)))
} }
f.Write([]byte("</body></html>")) f.Write([]byte("</body></html>"))
} }
func savepng(filePath string, m image.Image) { func savepng(filePath string, m image.Image) {
f, err := os.Create(filePath) f, err := os.Create(filePath)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
os.Exit(1) os.Exit(1)
} }
defer f.Close() defer f.Close()
b := bufio.NewWriter(f) b := bufio.NewWriter(f)
err = png.Encode(b, m) err = png.Encode(b, m)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
os.Exit(1) os.Exit(1)
} }
err = b.Flush() err = b.Flush()
if err != nil { if err != nil {
log.Println(err) log.Println(err)
os.Exit(1) os.Exit(1)
} }
} }
func drawPoints(img draw.Image, c image.Color, s ...float64) image.Image { func drawPoints(img draw.Image, c image.Color, s ...float64) image.Image {
/*for i := 0; i < len(s); i += 2 { /*for i := 0; i < len(s); i += 2 {
x, y := int(s[i]+0.5), int(s[i+1]+0.5) x, y := int(s[i]+0.5), int(s[i+1]+0.5)
img.Set(x, y, c) 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, c)
img.Set(x-1, y+1, c) img.Set(x-1, y+1, c)
img.Set(x-1, y-1, c) img.Set(x-1, y-1, c)
}*/ }*/
return img return img
} }
func TestCubicCurveRec(t *testing.T) { func TestCubicCurveRec(t *testing.T) {
for i, curve := range testsCubicFloat64 { for i, curve := range testsCubicFloat64 {
var p Path var p Path
p.LineTo(curve.X1, curve.Y1) p.LineTo(curve[0], curve[1])
curve.SegmentRec(&p, flattening_threshold) curve.SegmentRec(&p, flattening_threshold)
img := image.NewNRGBA(300, 300) 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.NRGBAColor{0xff, 0, 0, 0xff}, curve[:]...)
raster.PolylineBresenham(img, image.Black, p.points...) 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}, curve[:]...)
drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, p.points...) drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, p.points...)
savepng(fmt.Sprintf("_testRec%d.png", i), img) savepng(fmt.Sprintf("_testRec%d.png", i), img)
log.Printf("Num of points: %d\n", len(p.points)) log.Printf("Num of points: %d\n", len(p.points))
} }
fmt.Println() fmt.Println()
} }
func TestCubicCurve(t *testing.T) { func TestCubicCurve(t *testing.T) {
for i, curve := range testsCubicFloat64 { for i, curve := range testsCubicFloat64 {
var p Path var p Path
p.LineTo(curve.X1, curve.Y1) p.LineTo(curve[0], curve[1])
curve.Segment(&p, flattening_threshold) curve.Segment(&p, flattening_threshold)
img := image.NewNRGBA(300, 300) 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.NRGBAColor{0xff, 0, 0, 0xff}, curve[:]...)
raster.PolylineBresenham(img, image.Black, p.points...) 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}, curve[:]...)
drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, p.points...) drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, p.points...)
savepng(fmt.Sprintf("_test%d.png", i), img) savepng(fmt.Sprintf("_test%d.png", i), img)
log.Printf("Num of points: %d\n", len(p.points)) log.Printf("Num of points: %d\n", len(p.points))
} }
fmt.Println() fmt.Println()
} }
func TestCubicCurveAdaptiveRec(t *testing.T) { func TestCubicCurveAdaptiveRec(t *testing.T) {
for i, curve := range testsCubicFloat64 { for i, curve := range testsCubicFloat64 {
var p Path var p Path
p.LineTo(curve.X1, curve.Y1) p.LineTo(curve[0], curve[1])
curve.AdaptiveSegmentRec(&p, 1, 0, 0) curve.AdaptiveSegmentRec(&p, 1, 0, 0)
img := image.NewNRGBA(300, 300) 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.NRGBAColor{0xff, 0, 0, 0xff}, curve[:]...)
raster.PolylineBresenham(img, image.Black, p.points...) 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}, curve[:]...)
drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, p.points...) drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, p.points...)
savepng(fmt.Sprintf("_testAdaptiveRec%d.png", i), img) savepng(fmt.Sprintf("_testAdaptiveRec%d.png", i), img)
log.Printf("Num of points: %d\n", len(p.points)) log.Printf("Num of points: %d\n", len(p.points))
} }
fmt.Println() fmt.Println()
} }
func TestCubicCurveAdaptive(t *testing.T) { func TestCubicCurveAdaptive(t *testing.T) {
for i, curve := range testsCubicFloat64 { for i, curve := range testsCubicFloat64 {
var p Path var p Path
p.LineTo(curve.X1, curve.Y1) p.LineTo(curve[0], curve[1])
curve.AdaptiveSegment(&p, 1, 0, 0) curve.AdaptiveSegment(&p, 1, 0, 0)
img := image.NewNRGBA(300, 300) 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.NRGBAColor{0xff, 0, 0, 0xff}, curve[:]...)
raster.PolylineBresenham(img, image.Black, p.points...) 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}, curve[:]...)
drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, p.points...) drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, p.points...)
savepng(fmt.Sprintf("_testAdaptive%d.png", i), img) savepng(fmt.Sprintf("_testAdaptive%d.png", i), img)
log.Printf("Num of points: %d\n", len(p.points)) log.Printf("Num of points: %d\n", len(p.points))
} }
fmt.Println() fmt.Println()
} }
func TestCubicCurveParabolic(t *testing.T) { func TestCubicCurveParabolic(t *testing.T) {
for i, curve := range testsCubicFloat64 { for i, curve := range testsCubicFloat64 {
var p Path var p Path
p.LineTo(curve.X1, curve.Y1) p.LineTo(curve[0], curve[1])
curve.ParabolicSegment(&p, flattening_threshold) curve.ParabolicSegment(&p, flattening_threshold)
img := image.NewNRGBA(300, 300) 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.NRGBAColor{0xff, 0, 0, 0xff}, curve[:]...)
raster.PolylineBresenham(img, image.Black, p.points...) 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}, curve[:]...)
drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, p.points...) drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, p.points...)
savepng(fmt.Sprintf("_testParabolic%d.png", i), img) savepng(fmt.Sprintf("_testParabolic%d.png", i), img)
log.Printf("Num of points: %d\n", len(p.points)) log.Printf("Num of points: %d\n", len(p.points))
} }
fmt.Println() fmt.Println()
} }
func TestQuadCurve(t *testing.T) { func TestQuadCurve(t *testing.T) {
for i, curve := range testsQuadFloat64 { for i, curve := range testsQuadFloat64 {
var p Path var p Path
p.LineTo(curve.X1, curve.Y1) p.LineTo(curve[0], curve[1])
curve.Segment(&p, flattening_threshold) curve.Segment(&p, flattening_threshold)
img := image.NewNRGBA(300, 300) 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.NRGBAColor{0xff, 0, 0, 0xff}, curve[:]...)
raster.PolylineBresenham(img, image.Black, p.points...) raster.PolylineBresenham(img, image.Black, p.points...)
drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, p.points...) //drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, curve[:]...)
savepng(fmt.Sprintf("_testQuad%d.png", i), img) drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, p.points...)
log.Printf("Num of points: %d\n", len(p.points)) savepng(fmt.Sprintf("_testQuad%d.png", i), img)
} log.Printf("Num of points: %d\n", len(p.points))
fmt.Println() }
} fmt.Println()
}
func BenchmarkCubicCurveRec(b *testing.B) {
for i := 0; i < b.N; i++ { func BenchmarkCubicCurveRec(b *testing.B) {
for _, curve := range testsCubicFloat64 { for i := 0; i < b.N; i++ {
p := Path{make([]float64, 0, 32)} for _, curve := range testsCubicFloat64 {
p.LineTo(curve.X1, curve.Y1) p := Path{make([]float64, 0, 32)}
curve.SegmentRec(&p, flattening_threshold) p.LineTo(curve[0], curve[1])
} curve.SegmentRec(&p, flattening_threshold)
} }
} }
}
func BenchmarkCubicCurve(b *testing.B) {
for i := 0; i < b.N; i++ { func BenchmarkCubicCurve(b *testing.B) {
for _, curve := range testsCubicFloat64 { for i := 0; i < b.N; i++ {
p := Path{make([]float64, 0, 32)} for _, curve := range testsCubicFloat64 {
p.LineTo(curve.X1, curve.Y1) p := Path{make([]float64, 0, 32)}
curve.Segment(&p, flattening_threshold) p.LineTo(curve[0], curve[1])
} curve.Segment(&p, flattening_threshold)
} }
} }
}
func BenchmarkCubicCurveAdaptiveRec(b *testing.B) {
for i := 0; i < b.N; i++ { func BenchmarkCubicCurveAdaptiveRec(b *testing.B) {
for _, curve := range testsCubicFloat64 { for i := 0; i < b.N; i++ {
p := Path{make([]float64, 0, 32)} for _, curve := range testsCubicFloat64 {
p.LineTo(curve.X1, curve.Y1) p := Path{make([]float64, 0, 32)}
curve.AdaptiveSegmentRec(&p, 1, 0, 0) p.LineTo(curve[0], curve[1])
} curve.AdaptiveSegmentRec(&p, 1, 0, 0)
} }
} }
}
func BenchmarkCubicCurveAdaptive(b *testing.B) {
for i := 0; i < b.N; i++ { func BenchmarkCubicCurveAdaptive(b *testing.B) {
for _, curve := range testsCubicFloat64 { for i := 0; i < b.N; i++ {
p := Path{make([]float64, 0, 32)} for _, curve := range testsCubicFloat64 {
p.LineTo(curve.X1, curve.Y1) p := Path{make([]float64, 0, 32)}
curve.AdaptiveSegment(&p, 1, 0, 0) p.LineTo(curve[0], curve[1])
} curve.AdaptiveSegment(&p, 1, 0, 0)
} }
} }
}
func BenchmarkCubicCurveParabolic(b *testing.B) {
for i := 0; i < b.N; i++ { func BenchmarkCubicCurveParabolic(b *testing.B) {
for _, curve := range testsCubicFloat64 { for i := 0; i < b.N; i++ {
p := Path{make([]float64, 0, 32)} for _, curve := range testsCubicFloat64 {
p.LineTo(curve.X1, curve.Y1) p := Path{make([]float64, 0, 32)}
curve.ParabolicSegment(&p, flattening_threshold) p.LineTo(curve[0], curve[1])
} curve.ParabolicSegment(&p, flattening_threshold)
} }
} }
}
func BenchmarkQuadCurve(b *testing.B) {
for i := 0; i < b.N; i++ { func BenchmarkQuadCurve(b *testing.B) {
for _, curve := range testsQuadFloat64 { for i := 0; i < b.N; i++ {
p := Path{make([]float64, 0, 32)} for _, curve := range testsQuadFloat64 {
p.LineTo(curve.X1, curve.Y1) p := Path{make([]float64, 0, 32)}
curve.Segment(&p, flattening_threshold) p.LineTo(curve[0], curve[1])
} curve.Segment(&p, flattening_threshold)
} }
} }
}

View File

@ -1,54 +1,53 @@
// Copyright 2010 The draw2d Authors. All rights reserved. // Copyright 2010 The draw2d Authors. All rights reserved.
// created: 17/05/2011 by Laurent Le Goff // created: 17/05/2011 by Laurent Le Goff
package curve package curve
import ( import (
"math" "math"
) )
//X1, Y1, X2, Y2, X3, Y3 float64
type QuadCurveFloat64 struct { type QuadCurveFloat64 [6]float64
X1, Y1, X2, Y2, X3, Y3 float64
}
func (c *QuadCurveFloat64) Subdivide(c1, c2 *QuadCurveFloat64) {
func (c *QuadCurveFloat64) Subdivide(c1, c2 *QuadCurveFloat64) {
// Calculate all the mid-points of the line segments // Calculate all the mid-points of the line segments
//---------------------- //----------------------
c1.X1, c1.Y1 = c.X1, c.Y1 c1[0], c1[1] = c[0], c[1]
c2.X3, c2.Y3 = c.X3, c.Y3 c2[4], c2[5] = c[4], c[5]
c1.X2 = (c.X1 + c.X2) / 2 c1[2] = (c[0] + c[2]) / 2
c1.Y2 = (c.Y1 + c.Y2) / 2 c1[3] = (c[1] + c[3]) / 2
c2.X2 = (c.X2 + c.X3) / 2 c2[2] = (c[2] + c[4]) / 2
c2.Y2 = (c.Y2 + c.Y3) / 2 c2[3] = (c[3] + c[5]) / 2
c1.X3 = (c1.X2 + c2.X2) / 2 c1[4] = (c1[2] + c2[2]) / 2
c1.Y3 = (c1.Y2 + c2.Y2) / 2 c1[5] = (c1[3] + c2[3]) / 2
c2.X1, c2.Y1 = c1.X3, c1.Y3 c2[0], c2[1] = c1[4], c1[5]
return return
} }
func (curve *QuadCurveFloat64) Segment(t LineTracer, flattening_threshold float64) { func (curve *QuadCurveFloat64) Segment(t LineTracer, flattening_threshold float64) {
var curves [CurveRecursionLimit]QuadCurveFloat64 var curves [CurveRecursionLimit]QuadCurveFloat64
curves[0] = *curve curves[0] = *curve
i := 0 i := 0
// current curve // current curve
var c *QuadCurveFloat64 var c *QuadCurveFloat64
var dx, dy, d float64 var dx, dy, d float64
for i >= 0 { for i >= 0 {
c = &curves[i] c = &curves[i]
dx = c.X3 - c.X1 dx = c[4] - c[0]
dy = c.Y3 - c.Y1 dy = c[5] - c[1]
d = math.Fabs(((c.X2-c.X3)*dy - (c.Y2-c.Y3)*dx)) 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 { if (d*d) < flattening_threshold*(dx*dx+dy*dy) || i == len(curves)-1 {
t.LineTo(c.X3, c.Y3) t.LineTo(c[4], c[5])
i-- i--
} else { } else {
// second half of bezier go lower onto the stack // second half of bezier go lower onto the stack
c.Subdivide(&curves[i+1], &curves[i]) c.Subdivide(&curves[i+1], &curves[i])
i++ i++
} }
} }
} }

View File

@ -3,5 +3,9 @@ include $(GOROOT)/src/Make.inc
TARG=draw2d.googlecode.com/hg/draw2d/raster TARG=draw2d.googlecode.com/hg/draw2d/raster
GOFILES=\ GOFILES=\
line.go\ line.go\
polygon.go\
coverage_table.go\
fillerAA.go\
include $(GOROOT)/src/Make.pkg include $(GOROOT)/src/Make.pkg

View File

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

178
draw2d/raster/fillerAA.go Normal file
View File

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

View File

@ -1,53 +1,55 @@
package raster // Copyright 2011 The draw2d Authors. All rights reserved.
// created: 27/05/2011 by Laurent Le Goff
import ( package raster
"exp/draw"
"image" import (
) "exp/draw"
"image"
func abs(i int) int { )
if i < 0 {
return -i func abs(i int) int {
} if i < 0 {
return i 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 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) func Bresenham(img draw.Image, color image.Color, x0, y0, x1, y1 int) {
var sx, sy int dx := abs(x1 - x0)
if x0 < x1 { dy := abs(y1 - y0)
sx = 1 var sx, sy int
} else { if x0 < x1 {
sx = -1 sx = 1
} } else {
if y0 < y1 { sx = -1
sy = 1 }
} else { if y0 < y1 {
sy = -1 sy = 1
} } else {
err := dx - dy sy = -1
}
var e2 int err := dx - dy
for {
img.Set(x0, y0, color) var e2 int
if x0 == x1 && y0 == y1 { for {
return img.Set(x0, y0, color)
} if x0 == x1 && y0 == y1 {
e2 = 2 * err return
if e2 > -dy { }
err = err - dy e2 = 2 * err
x0 = x0 + sx if e2 > -dy {
} err = err - dy
if e2 < dx { x0 = x0 + sx
err = err + dx }
y0 = y0 + sy if e2 < dx {
} err = err + dx
} y0 = y0 + sy
} }
}
}

273
draw2d/raster/polygon.go Normal file
View File

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

View File

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