use vertex converter as antigrain project

add a Stroke2 and Fill2 method to GraphicContext (comparison purpose: different algorithm)
This commit is contained in:
legoff.laurent 2010-12-06 13:46:08 +00:00
parent 0f2cebb82d
commit d946bca7aa
13 changed files with 566 additions and 207 deletions

View File

@ -1,6 +1,6 @@
package draw2d
import(
import (
"math"
)
@ -15,25 +15,25 @@ func Rect(path Path, x1, y1, x2, y2 float) {
}
func RoundRect(path Path, x1, y1, x2, y2, arcWidth, arcHeight float) {
arcWidth = arcWidth/2;
arcHeight = arcHeight/2;
path.MoveTo(x1, y1+ arcHeight);
path.QuadCurveTo(x1, y1, x1 + arcWidth, y1);
path.LineTo(x2-arcWidth, y1);
path.QuadCurveTo(x2, y1, x2, y1 + arcHeight);
path.LineTo(x2, y2-arcHeight);
path.QuadCurveTo(x2, y2, x2 - arcWidth, y2);
path.LineTo(x1 + arcWidth, y2);
path.QuadCurveTo(x1, y2, x1, y2 - arcHeight);
arcWidth = arcWidth / 2
arcHeight = arcHeight / 2
path.MoveTo(x1, y1+arcHeight)
path.QuadCurveTo(x1, y1, x1+arcWidth, y1)
path.LineTo(x2-arcWidth, y1)
path.QuadCurveTo(x2, y1, x2, y1+arcHeight)
path.LineTo(x2, y2-arcHeight)
path.QuadCurveTo(x2, y2, x2-arcWidth, y2)
path.LineTo(x1+arcWidth, y2)
path.QuadCurveTo(x1, y2, x1, y2-arcHeight)
path.Close()
}
func Ellipse(path Path, cx, cy, rx, ry float) {
path.ArcTo(cx, cy, rx, ry, 0, -math.Pi * 2)
path.ArcTo(cx, cy, rx, ry, 0, -math.Pi*2)
path.Close()
}
func Circle(path Path, cx, cy, radius float) {
path.ArcTo(cx, cy, radius, radius, 0, -math.Pi * 2)
path.ArcTo(cx, cy, radius, radius, 0, -math.Pi*2)
path.Close()
}
}

View File

@ -1,10 +1,8 @@
// Copyright 2010 The draw2d Authors. All rights reserved.
// created: 21/11/2010 by Laurent Le Goff
package draw2d
func arc(t LineTracer, x, y, rx, ry, start, angle, scale float) {
func arc(t VertexConverter, x, y, rx, ry, start, angle, scale float) (lastX, lastY float) {
end := start + angle
clockWise := true
if angle < 0 {
@ -22,13 +20,13 @@ func arc(t LineTracer, x, y, rx, ry, start, angle, scale float) {
if (angle < end-da/4) != clockWise {
curX = x + cos(end)*rx
curY = y + sin(end)*ry
t.LineTo(curX, curY)
break
return curX, curY
}
curX = x + cos(angle)*rx
curY = y + sin(angle)*ry
angle += da
t.LineTo(curX, curY)
t.Vertex(curX, curY)
}
return curX, curY
}

View File

@ -33,25 +33,22 @@ var (
The more this value is the less sharp turns will be cut.
Typically it should not exceed 10-15 degrees.
*/
func cubicBezier(v LineTracer, x1, y1, x2, y2, x3, y3, x4, y4, approximationScale, angleTolerance, cuspLimit float) {
func cubicBezier(v VertexConverter, x1, y1, x2, y2, x3, y3, x4, y4, approximationScale, angleTolerance, cuspLimit float) {
cuspLimit = computeCuspLimit(cuspLimit)
distanceToleranceSquare := 0.5 / approximationScale
distanceToleranceSquare = distanceToleranceSquare * distanceToleranceSquare
recursiveCubicBezier(v, x1, y1, x2, y2, x3, y3, x4, y4, 0, distanceToleranceSquare, angleTolerance, cuspLimit)
v.LineTo(x4, y4)
}
/*
* see cubicBezier comments for approximationScale and angleTolerance definition
*/
func quadraticBezier(v LineTracer, x1, y1, x2, y2, x3, y3, approximationScale, angleTolerance float) {
func quadraticBezier(v VertexConverter, x1, y1, x2, y2, x3, y3, approximationScale, angleTolerance float) {
distanceToleranceSquare := 0.5 / approximationScale
distanceToleranceSquare = distanceToleranceSquare * distanceToleranceSquare
recursiveQuadraticBezierBezier(v, x1, y1, x2, y2, x3, y3, 0, distanceToleranceSquare, angleTolerance)
v.LineTo(x3, y3)
}
@ -68,7 +65,7 @@ func computeCuspLimit(v float) (r float) {
/**
* http://www.antigrain.com/research/adaptive_bezier/index.html
*/
func recursiveQuadraticBezierBezier(v LineTracer, x1, y1, x2, y2, x3, y3 float, level int, distanceToleranceSquare, angleTolerance float) {
func recursiveQuadraticBezierBezier(v VertexConverter, x1, y1, x2, y2, x3, y3 float, level int, distanceToleranceSquare, angleTolerance float) {
if level > CurveRecursionLimit {
return
}
@ -94,7 +91,7 @@ func recursiveQuadraticBezierBezier(v LineTracer, x1, y1, x2, y2, x3, y3 float,
// we tend to finish subdivisions.
//----------------------
if angleTolerance < CurveAngleToleranceEpsilon {
v.LineTo(x123, y123)
v.Vertex(x123, y123)
return
}
@ -108,7 +105,7 @@ func recursiveQuadraticBezierBezier(v LineTracer, x1, y1, x2, y2, x3, y3 float,
if da < angleTolerance {
// Finally we can stop the recursion
//----------------------
v.LineTo(x123, y123)
v.Vertex(x123, y123)
return
}
}
@ -134,7 +131,7 @@ func recursiveQuadraticBezierBezier(v LineTracer, x1, y1, x2, y2, x3, y3 float,
}
}
if d < distanceToleranceSquare {
v.LineTo(x2, y2)
v.Vertex(x2, y2)
return
}
}
@ -148,7 +145,7 @@ func recursiveQuadraticBezierBezier(v LineTracer, x1, y1, x2, y2, x3, y3 float,
/**
* http://www.antigrain.com/research/adaptive_bezier/index.html
*/
func recursiveCubicBezier(v LineTracer, x1, y1, x2, y2, x3, y3, x4, y4 float, level int, distanceToleranceSquare, angleTolerance, cuspLimit float) {
func recursiveCubicBezier(v VertexConverter, x1, y1, x2, y2, x3, y3, x4, y4 float, level int, distanceToleranceSquare, angleTolerance, cuspLimit float) {
if level > CurveRecursionLimit {
return
}
@ -215,12 +212,12 @@ func recursiveCubicBezier(v LineTracer, x1, y1, x2, y2, x3, y3, x4, y4 float, le
}
if d2 > d3 {
if d2 < distanceToleranceSquare {
v.LineTo(x2, y2)
v.Vertex(x2, y2)
return
}
} else {
if d3 < distanceToleranceSquare {
v.LineTo(x3, y3)
v.Vertex(x3, y3)
return
}
}
@ -231,7 +228,7 @@ func recursiveCubicBezier(v LineTracer, x1, y1, x2, y2, x3, y3, x4, y4 float, le
//----------------------
if d3*d3 <= distanceToleranceSquare*(dx*dx+dy*dy) {
if angleTolerance < CurveAngleToleranceEpsilon {
v.LineTo(x23, y23)
v.Vertex(x23, y23)
return
}
@ -243,14 +240,14 @@ func recursiveCubicBezier(v LineTracer, x1, y1, x2, y2, x3, y3, x4, y4 float, le
}
if da1 < angleTolerance {
v.LineTo(x2, y2)
v.LineTo(x3, y3)
v.Vertex(x2, y2)
v.Vertex(x3, y3)
return
}
if cuspLimit != 0.0 {
if da1 > cuspLimit {
v.LineTo(x3, y3)
v.Vertex(x3, y3)
return
}
}
@ -262,7 +259,7 @@ func recursiveCubicBezier(v LineTracer, x1, y1, x2, y2, x3, y3, x4, y4 float, le
//----------------------
if d2*d2 <= distanceToleranceSquare*(dx*dx+dy*dy) {
if angleTolerance < CurveAngleToleranceEpsilon {
v.LineTo(x23, y23)
v.Vertex(x23, y23)
return
}
@ -274,14 +271,14 @@ func recursiveCubicBezier(v LineTracer, x1, y1, x2, y2, x3, y3, x4, y4 float, le
}
if da1 < angleTolerance {
v.LineTo(x2, y2)
v.LineTo(x3, y3)
v.Vertex(x2, y2)
v.Vertex(x3, y3)
return
}
if cuspLimit != 0.0 {
if da1 > cuspLimit {
v.LineTo(x2, y2)
v.Vertex(x2, y2)
return
}
}
@ -296,7 +293,7 @@ func recursiveCubicBezier(v LineTracer, x1, y1, x2, y2, x3, y3, x4, y4 float, le
// we tend to finish subdivisions.
//----------------------
if angleTolerance < CurveAngleToleranceEpsilon {
v.LineTo(x23, y23)
v.Vertex(x23, y23)
return
}
@ -315,18 +312,18 @@ func recursiveCubicBezier(v LineTracer, x1, y1, x2, y2, x3, y3, x4, y4 float, le
if da1+da2 < angleTolerance {
// Finally we can stop the recursion
//----------------------
v.LineTo(x23, y23)
v.Vertex(x23, y23)
return
}
if cuspLimit != 0.0 {
if da1 > cuspLimit {
v.LineTo(x2, y2)
v.Vertex(x2, y2)
return
}
if da2 > cuspLimit {
v.LineTo(x3, y3)
v.Vertex(x3, y3)
return
}
}

View File

@ -1,40 +1,49 @@
package draw2d
import(
"freetype-go.googlecode.com/hg/freetype/raster"
)
type DashAdder struct {
adder raster.Adder
type DashVertexConverter struct {
command VertexCommand
next VertexConverter
x, y, distance float
dash []float
currentDash int
dashOffset float
}
func traceDashPath(dash []float, dashOffset float, approximationScale float, adder raster.Adder, paths ...*PathStorage) {
var dasher DashAdder
if dash != nil && len(dash) > 0 {
dasher.dash = dash
} else {
dasher.dash = nil
}
func NewDashConverter(dash []float, dashOffset float, converter VertexConverter) *DashVertexConverter {
var dasher DashVertexConverter
dasher.dash = dash
dasher.currentDash = 0
dasher.dashOffset = dashOffset
dasher.adder = adder
for _, path := range paths {
path.TraceLine(&dasher, approximationScale)
dasher.next = converter
return &dasher
}
func (dasher *DashVertexConverter) NextCommand(cmd VertexCommand) {
dasher.command = cmd
if(dasher.command == VertexStopCommand) {
dasher.next.NextCommand(VertexStopCommand)
}
}
func (dasher *DashAdder) MoveTo(x, y float) {
dasher.adder.Start(floatToPoint(x, y))
func (dasher *DashVertexConverter) Vertex(x, y float) {
switch dasher.command {
case VertexStartCommand:
dasher.start(x, y)
default:
dasher.lineTo(x, y)
}
dasher.command = VertexNoCommand
}
func (dasher *DashVertexConverter) start(x, y float) {
dasher.next.NextCommand(VertexStartCommand)
dasher.next.Vertex(x, y)
dasher.x, dasher.y = x, y
dasher.distance = dasher.dashOffset
dasher.currentDash = 0
}
func (dasher *DashAdder) LineTo(x, y float) {
func (dasher *DashVertexConverter) lineTo(x, y float) {
rest := dasher.dash[dasher.currentDash] - dasher.distance
for rest < 0 {
dasher.distance = dasher.distance - dasher.dash[dasher.currentDash]
@ -48,10 +57,12 @@ func (dasher *DashAdder) LineTo(x, y float) {
ly := dasher.y + k*(y-dasher.y)
if dasher.currentDash%2 == 0 {
// line
dasher.adder.Add1(floatToPoint(lx, ly))
dasher.next.Vertex(lx, ly)
} else {
// gap
dasher.adder.Start(floatToPoint(lx, ly))
dasher.next.NextCommand(VertexStopCommand)
dasher.next.NextCommand(VertexStartCommand)
dasher.next.Vertex(lx, ly)
}
d = d - rest
dasher.x, dasher.y = lx, ly
@ -60,9 +71,13 @@ func (dasher *DashAdder) LineTo(x, y float) {
}
dasher.distance = d
if dasher.currentDash%2 == 0 {
dasher.adder.Add1(floatToPoint(x, y))
// line
dasher.next.Vertex(x, y)
} else {
dasher.adder.Start(floatToPoint(x, y))
// gap
dasher.next.NextCommand(VertexStopCommand)
dasher.next.NextCommand(VertexStartCommand)
dasher.next.Vertex(x, y)
}
if dasher.distance >= dasher.dash[dasher.currentDash] {
dasher.distance = dasher.distance - dasher.dash[dasher.currentDash]

View File

@ -0,0 +1,20 @@
package draw2d
type DemuxConverter struct {
converters []VertexConverter
}
func NewDemuxConverter(converters ...VertexConverter) *DemuxConverter {
return &DemuxConverter{converters}
}
func (dc *DemuxConverter) NextCommand(cmd VertexCommand) {
for _, converter := range dc.converters {
converter.NextCommand(cmd)
}
}
func (dc *DemuxConverter) Vertex(x, y float) {
for _, converter := range dc.converters {
converter.Vertex(x, y)
}
}

View File

@ -16,30 +16,15 @@ const (
FillRuleWinding
)
type Cap int
const (
RoundCap Cap = iota
ButtCap
SquareCap
)
type Join int
const (
BevelJoin Join = iota
RoundJoin
MiterJoin
)
type GraphicContext struct {
PaintedImage *image.RGBA
rasterizer *raster.Rasterizer
current *contextStack
PaintedImage *image.RGBA
fillRasterizer *raster.Rasterizer
strokeRasterizer *raster.Rasterizer
current *contextStack
}
type contextStack struct {
tr MatrixTransform
tr MatrixTransform
path *PathStorage
lineWidth float
dash []float
@ -49,7 +34,7 @@ type contextStack struct {
fillRule FillRule
cap Cap
join Join
previous *contextStack
previous *contextStack
}
/**
@ -59,10 +44,11 @@ func NewGraphicContext(pi *image.RGBA) *GraphicContext {
gc := new(GraphicContext)
gc.PaintedImage = pi
width, height := gc.PaintedImage.Bounds().Dx(), gc.PaintedImage.Bounds().Dy()
gc.rasterizer = raster.NewRasterizer(width, height)
gc.fillRasterizer = raster.NewRasterizer(width, height)
gc.strokeRasterizer = raster.NewRasterizer(width, height)
gc.current = new(contextStack)
gc.current.tr = NewIdentityMatrix()
gc.current.path = new(PathStorage)
gc.current.lineWidth = 1.0
@ -205,55 +191,124 @@ func (gc *GraphicContext) Close() {
gc.current.path.Close()
}
func (gc *GraphicContext) paint(color image.Color) {
func (gc *GraphicContext) paint(rasterizer *raster.Rasterizer, color image.Color) {
painter := raster.NewRGBAPainter(gc.PaintedImage)
painter.SetColor(color)
gc.rasterizer.Rasterize(painter)
gc.rasterizer.Clear()
rasterizer.Rasterize(painter)
rasterizer.Clear()
gc.current.path = new(PathStorage)
}
func (gc *GraphicContext) Stroke(paths ...*PathStorage) {
/**** first method ****/
func (gc *GraphicContext) Stroke(paths ...*PathStorage) {
paths = append(paths, gc.current.path)
gc.rasterizer.UseNonZeroWinding = true
gc.strokeRasterizer.UseNonZeroWinding = true
rasterPath := new(raster.Path)
if(gc.current.dash == nil) {
tracePath(gc.current.tr.GetMaxAbsScaling(), rasterPath, paths...)
var pathConverter *PathConverter
if gc.current.dash != nil && len(gc.current.dash) > 0 {
dasher := NewDashConverter(gc.current.dash, gc.current.dashOffset, NewVertexAdder(rasterPath))
pathConverter = NewPathConverter(dasher)
} else {
traceDashPath(gc.current.dash, gc.current.dashOffset, gc.current.tr.GetMaxAbsScaling(), rasterPath, paths...)
pathConverter = NewPathConverter(NewVertexAdder(rasterPath))
}
mta := NewMatrixTransformAdder(gc.current.tr, gc.rasterizer)
pathConverter.ApproximationScale = gc.current.tr.GetMaxAbsScaling()
pathConverter.Convert(paths...)
mta := NewMatrixTransformAdder(gc.current.tr, gc.strokeRasterizer)
raster.Stroke(mta, *rasterPath, raster.Fix32(gc.current.lineWidth*256), gc.current.cap.capper(), gc.current.join.joiner())
gc.paint(gc.current.strokeColor)
gc.paint(gc.strokeRasterizer, gc.current.strokeColor)
}
/**** second method ****/
func (gc *GraphicContext) Stroke2(paths ...*PathStorage) {
paths = append(paths, gc.current.path)
gc.strokeRasterizer.UseNonZeroWinding = true
stroker := NewLineStroker(NewVertexMatrixTransform(gc.current.tr, NewVertexAdder(gc.strokeRasterizer)))
stroker.HalfLineWidth = gc.current.lineWidth / 2
var pathConverter *PathConverter
if gc.current.dash != nil && len(gc.current.dash) > 0 {
dasher := NewDashConverter(gc.current.dash, gc.current.dashOffset, stroker)
pathConverter = NewPathConverter(dasher)
} else {
pathConverter = NewPathConverter(stroker)
}
pathConverter.ApproximationScale = gc.current.tr.GetMaxAbsScaling()
pathConverter.Convert(paths...)
gc.paint(gc.strokeRasterizer, gc.current.strokeColor)
}
/**** first method ****/
func (gc *GraphicContext) Fill(paths ...*PathStorage) {
paths = append(paths, gc.current.path)
gc.rasterizer.UseNonZeroWinding = gc.current.fillRule.fillRule()
mta := NewMatrixTransformAdder(gc.current.tr, gc.rasterizer)
tracePath(gc.current.tr.GetMaxAbsScaling(), mta, paths...)
gc.paint(gc.current.fillColor)
gc.fillRasterizer.UseNonZeroWinding = gc.current.fillRule.fillRule()
pathConverter := NewPathConverter(NewVertexAdder(NewMatrixTransformAdder(gc.current.tr, gc.fillRasterizer)))
pathConverter.ApproximationScale = gc.current.tr.GetMaxAbsScaling()
pathConverter.Convert(paths...)
gc.paint(gc.fillRasterizer, gc.current.fillColor)
}
/**** second method ****/
func (gc *GraphicContext) Fill2(paths ...*PathStorage) {
paths = append(paths, gc.current.path)
gc.fillRasterizer.UseNonZeroWinding = gc.current.fillRule.fillRule()
/**** first method ****/
pathConverter := NewPathConverter(NewVertexMatrixTransform(gc.current.tr, NewVertexAdder(gc.fillRasterizer)))
pathConverter.ApproximationScale = gc.current.tr.GetMaxAbsScaling()
pathConverter.Convert(paths...)
gc.paint(gc.fillRasterizer, gc.current.fillColor)
}
func (gc *GraphicContext) FillStroke(paths ...*PathStorage) {
paths = append(paths, gc.current.path)
mta := NewMatrixTransformAdder(gc.current.tr, gc.rasterizer)
tracePath(gc.current.tr.GetMaxAbsScaling(), mta, paths...)
gc.fillRasterizer.UseNonZeroWinding = gc.current.fillRule.fillRule()
gc.strokeRasterizer.UseNonZeroWinding = true
gc.rasterizer.UseNonZeroWinding = gc.current.fillRule.fillRule()
gc.paint(gc.current.fillColor)
gc.rasterizer.UseNonZeroWinding = true
filler := NewVertexMatrixTransform(gc.current.tr, NewVertexAdder(gc.fillRasterizer))
rasterPath := new(raster.Path)
if(gc.current.dash == nil) {
tracePath(gc.current.tr.GetMaxAbsScaling(), rasterPath, paths...)
} else {
traceDashPath(gc.current.dash, gc.current.dashOffset, gc.current.tr.GetMaxAbsScaling(), rasterPath, paths...)
}
stroker := NewVertexAdder(rasterPath)
demux := NewDemuxConverter(filler, stroker)
pathConverter := NewPathConverter(demux)
pathConverter.ApproximationScale = gc.current.tr.GetMaxAbsScaling()
pathConverter.Convert(paths...)
mta := NewMatrixTransformAdder(gc.current.tr, gc.strokeRasterizer)
raster.Stroke(mta, *rasterPath, raster.Fix32(gc.current.lineWidth*256), gc.current.cap.capper(), gc.current.join.joiner())
gc.paint(gc.current.strokeColor)
gc.paint(gc.fillRasterizer, gc.current.fillColor)
gc.paint(gc.strokeRasterizer, gc.current.strokeColor)
}
/* second method */
func (gc *GraphicContext) FillStroke2(paths ...*PathStorage) {
gc.fillRasterizer.UseNonZeroWinding = gc.current.fillRule.fillRule()
gc.strokeRasterizer.UseNonZeroWinding = true
filler := NewVertexMatrixTransform(gc.current.tr, NewVertexAdder(gc.fillRasterizer))
stroker := NewLineStroker(NewVertexMatrixTransform(gc.current.tr, NewVertexAdder(gc.strokeRasterizer)))
stroker.HalfLineWidth = gc.current.lineWidth / 2
demux := NewDemuxConverter(filler, stroker)
paths = append(paths, gc.current.path)
pathConverter := NewPathConverter(demux)
pathConverter.ApproximationScale = gc.current.tr.GetMaxAbsScaling()
pathConverter.Convert(paths...)
gc.paint(gc.fillRasterizer, gc.current.fillColor)
gc.paint(gc.strokeRasterizer, gc.current.strokeColor)
}
func (f FillRule) fillRule() bool {
switch f {
case FillRuleEvenOdd:
@ -285,4 +340,3 @@ func (j Join) joiner() raster.Joiner {
}
return raster.RoundJoiner
}

View File

@ -37,6 +37,10 @@ func distance(x1, y1, x2, y2 float) float {
return float(math.Sqrt(float64(dx*dx + dy*dy)))
}
func vectorDistance(dx, dy float) float {
return float(math.Sqrt(float64(dx*dx + dy*dy)))
}
func squareDistance(x1, y1, x2, y2 float) float {
dx := x2 - x1
dy := y2 - y1

View File

@ -17,8 +17,18 @@ type Path interface {
Close()
}
type LineTracer interface {
MoveTo(x, y float)
LineTo(x, y float)
}
type VertexCommand byte
const (
VertexNoCommand VertexCommand = iota
VertexStartCommand
VertexJoinCommand
VertexCloseCommand
VertexStopCommand
)
type VertexConverter interface {
NextCommand(cmd VertexCommand)
Vertex(x, y float)
}

View File

@ -1,13 +1,14 @@
package draw2d
import(
"freetype-go.googlecode.com/hg/freetype/raster"
import (
"freetype-go.googlecode.com/hg/freetype/raster"
)
type PathAdder struct {
adder raster.Adder
type VertexAdder struct {
command VertexCommand
adder raster.Adder
}
@ -15,17 +16,21 @@ func floatToPoint(x, y float) raster.Point {
return raster.Point{raster.Fix32(x * 256), raster.Fix32(y * 256)}
}
func tracePath(approximationScale float, adder raster.Adder, paths ...*PathStorage) {
pathAdder := &PathAdder{adder}
for _, path := range paths {
path.TraceLine(pathAdder, approximationScale)
func NewVertexAdder(adder raster.Adder) *VertexAdder {
return &VertexAdder{VertexNoCommand, adder}
}
func (vertexAdder *VertexAdder) NextCommand(cmd VertexCommand) {
vertexAdder.command = cmd
}
func (vertexAdder *VertexAdder) Vertex(x, y float) {
switch vertexAdder.command {
case VertexStartCommand:
vertexAdder.adder.Start(floatToPoint(x, y))
default:
vertexAdder.adder.Add1(floatToPoint(x, y))
}
}
func (pathAdder *PathAdder) MoveTo(x, y float) {
pathAdder.adder.Start(floatToPoint(x, y))
}
func (pathAdder *PathAdder) LineTo(x, y float) {
pathAdder.adder.Add1(floatToPoint(x, y))
vertexAdder.command = VertexNoCommand
}

View File

@ -0,0 +1,153 @@
// Copyright 2010 The draw2d Authors. All rights reserved.
// created: 06/12/2010 by Laurent Le Goff
package draw2d
import (
"math"
)
type PathConverter struct {
converter VertexConverter
ApproximationScale, AngleTolerance, CuspLimit float
startX, startY, x, y float
}
func NewPathConverter(converter VertexConverter) *PathConverter {
return &PathConverter{converter, 1, 0, 0, 0, 0, 0, 0}
}
func (c *PathConverter) Convert(paths ...*PathStorage) {
for _, path := range paths {
j := 0
for _, cmd := range path.commands {
j = j + c.ConvertCommand(cmd, path.vertices[j:]...)
}
c.converter.NextCommand(VertexStopCommand)
}
}
func (c *PathConverter) ConvertCommand(cmd PathCmd, vertices ...float) int {
switch cmd {
case MoveTo:
c.MoveTo(vertices[0], vertices[1])
return 2
case LineTo:
c.LineTo(vertices[0], vertices[1])
return 2
case QuadCurveTo:
c.QuadCurveTo(vertices[0], vertices[1], vertices[2], vertices[3])
return 4
case CubicCurveTo:
c.CubicCurveTo(vertices[0], vertices[1], vertices[2], vertices[3], vertices[4], vertices[5])
return 6
case ArcTo:
c.x, c.y = arc(c.converter, vertices[0], vertices[1], vertices[2], vertices[3], vertices[4], vertices[5], c.ApproximationScale)
if c.startX == c.x && c.startY == c.y {
c.converter.NextCommand(VertexCloseCommand)
}
c.converter.Vertex(c.x, c.y)
return 6
case Close:
c.Close()
return 0
}
return 0
}
func (c *PathConverter) MoveTo(x, y float) *PathConverter {
c.x, c.y = x, y
c.startX, c.startY = c.x, c.y
c.converter.NextCommand(VertexStopCommand)
c.converter.NextCommand(VertexStartCommand)
c.converter.Vertex(c.x, c.y)
return c
}
func (c *PathConverter) RMoveTo(dx, dy float) *PathConverter {
c.MoveTo(c.x+dx, c.y+dy)
return c
}
func (c *PathConverter) LineTo(x, y float) *PathConverter {
c.x, c.y = x, y
if c.startX == c.x && c.startY == c.y {
c.converter.NextCommand(VertexCloseCommand)
}
c.converter.Vertex(c.x, c.y)
c.converter.NextCommand(VertexJoinCommand)
return c
}
func (c *PathConverter) RLineTo(dx, dy float) *PathConverter {
c.LineTo(c.x+dx, c.y+dy)
return c
}
func (c *PathConverter) QuadCurveTo(cx, cy, x, y float) *PathConverter {
quadraticBezier(c.converter, c.x, c.y, cx, cy, x, y, c.ApproximationScale, c.AngleTolerance)
c.x, c.y = x, y
if c.startX == c.x && c.startY == c.y {
c.converter.NextCommand(VertexCloseCommand)
}
c.converter.Vertex(c.x, c.y)
return c
}
func (c *PathConverter) RQuadCurveTo(dcx, dcy, dx, dy float) *PathConverter {
c.QuadCurveTo(c.x+dcx, c.y+dcy, c.x+dx, c.y+dy)
return c
}
func (c *PathConverter) CubicCurveTo(cx1, cy1, cx2, cy2, x, y float) *PathConverter {
cubicBezier(c.converter, c.x, c.y, cx1, cy1, cx2, cy2, x, y, c.ApproximationScale, c.AngleTolerance, c.CuspLimit)
c.x, c.y = x, y
if c.startX == c.x && c.startY == c.y {
c.converter.NextCommand(VertexCloseCommand)
}
c.converter.Vertex(c.x, c.y)
return c
}
func (c *PathConverter) RCubicCurveTo(dcx1, dcy1, dcx2, dcy2, dx, dy float) *PathConverter {
c.CubicCurveTo(c.x+dcx1, c.y+dcy1, c.x+dcx2, c.y+dcy2, c.x+dx, c.y+dy)
return c
}
func (c *PathConverter) ArcTo(cx, cy, rx, ry, startAngle, angle float) *PathConverter {
endAngle := startAngle + angle
clockWise := true
if angle < 0 {
clockWise = false
}
// normalize
if clockWise {
for endAngle < startAngle {
endAngle += math.Pi * 2.0
}
} else {
for startAngle < endAngle {
startAngle += math.Pi * 2.0
}
}
startX := cx + cos(startAngle)*rx
startY := cy + sin(startAngle)*ry
c.MoveTo(startX, startY)
c.x, c.y = arc(c.converter, cx, cy, rx, ry, startAngle, angle, c.ApproximationScale)
if c.startX == c.x && c.startY == c.y {
c.converter.NextCommand(VertexCloseCommand)
}
c.converter.Vertex(c.x, c.y)
return c
}
func (c *PathConverter) RArcTo(dcx, dcy, rx, ry, startAngle, angle float) *PathConverter {
c.ArcTo(c.x+dcx, c.y+dcy, rx, ry, startAngle, angle)
return c
}
func (c *PathConverter) Close() *PathConverter {
c.converter.NextCommand(VertexCloseCommand)
c.converter.Vertex(c.startX, c.startY)
return c
}

View File

@ -137,44 +137,6 @@ func (p *PathStorage) RArcTo(dcx, dcy, rx, ry, startAngle, angle float) *PathSto
return p
}
func (p *PathStorage) TraceLine(tracer LineTracer, approximationScale float) {
j := 0
x, y := 0.0, 0.0
firstX, firstY := x, y
if len(p.commands) > 0 {
if p.commands[0] == MoveTo {
firstX, firstY = p.vertices[0], p.vertices[1]
}
}
for _, cmd := range p.commands {
switch cmd {
case MoveTo:
tracer.MoveTo(p.vertices[j], p.vertices[j+1])
x, y = p.vertices[j], p.vertices[j+1]
firstX, firstY = x, y
j = j + 2
case LineTo:
tracer.LineTo(p.vertices[j], p.vertices[j+1])
x, y = p.vertices[j], p.vertices[j+1]
j = j + 2
case QuadCurveTo:
quadraticBezier(tracer, x, y, p.vertices[j], p.vertices[j+1], p.vertices[j+2], p.vertices[j+3], approximationScale, 0)
x, y = p.vertices[j+2], p.vertices[j+3]
j = j + 4
case CubicCurveTo:
cubicBezier(tracer, x, y, p.vertices[j], p.vertices[j+1], p.vertices[j+2], p.vertices[j+3], p.vertices[j+4], p.vertices[j+5], approximationScale, 0, 0)
x, y = p.vertices[j+4], p.vertices[j+5]
j = j + 6
case ArcTo:
arc(tracer, p.vertices[j], p.vertices[j+1], p.vertices[j+2], p.vertices[j+3], p.vertices[j+4], p.vertices[j+5], approximationScale)
j = j + 6
case Close:
tracer.LineTo(firstX, firstY)
x, y = firstX, firstY
}
}
}
func (p *PathStorage) String() string {
s := ""
j := 0

View File

@ -0,0 +1,120 @@
package draw2d
type Cap int
const (
RoundCap Cap = iota
ButtCap
SquareCap
)
type Join int
const (
BevelJoin Join = iota
RoundJoin
MiterJoin
)
type LineStroker struct {
Next VertexConverter
HalfLineWidth float
Cap Cap
Join Join
vertices []float
rewind []float
x, y, nx, ny float
command VertexCommand
}
func NewLineStroker(converter VertexConverter) *LineStroker {
l := new(LineStroker)
l.Next = converter
l.HalfLineWidth = 0.5
l.vertices = make([]float, 0)
l.rewind = make([]float, 0)
l.Cap = ButtCap
l.Join = MiterJoin
l.command = VertexNoCommand
return l
}
func (l *LineStroker) NextCommand(command VertexCommand) {
l.command = command
if command == VertexStopCommand {
l.Next.NextCommand(VertexStartCommand)
for i, j := 0, 1; j < len(l.vertices); i, j = i+2, j+2 {
l.Next.Vertex(l.vertices[i], l.vertices[j])
l.Next.NextCommand(VertexNoCommand)
}
for i, j := len(l.rewind)-2, len(l.rewind)-1; j > 0; i, j = i-2, j-2 {
l.Next.NextCommand(VertexNoCommand)
l.Next.Vertex(l.rewind[i], l.rewind[j])
}
if len(l.vertices) > 1 {
l.Next.NextCommand(VertexNoCommand)
l.Next.Vertex(l.vertices[0], l.vertices[1])
}
l.Next.NextCommand(VertexStopCommand)
// reinit vertices
l.vertices = make([]float, 0)
l.rewind = make([]float, 0)
l.x, l.y, l.nx, l.ny = 0, 0, 0, 0
}
}
func (l *LineStroker) Vertex(x, y float) {
switch l.command {
case VertexNoCommand:
l.line(l.x, l.y, x, y)
case VertexStartCommand:
l.x, l.y = x, y
case VertexJoinCommand:
l.joinLine(l.x, l.y, l.nx, l.ny, x, y)
case VertexCloseCommand:
l.line(l.x, l.y, x, y)
l.joinLine(l.x, l.y, l.nx, l.ny, x, y)
l.closePolygon()
}
l.command = VertexNoCommand
}
func (l *LineStroker) closePolygon() {
if len(l.vertices) > 1 {
l.vertices = append(l.vertices, l.vertices[0], l.vertices[1])
l.rewind = append(l.rewind, l.rewind[0], l.rewind[1])
}
}
func (l *LineStroker) line(x1, y1, x2, y2 float) {
dx := (x2 - x1)
dy := (y2 - y1)
d := vectorDistance(dx, dy)
if d != 0 {
nx := dy * l.HalfLineWidth / d
ny := -(dx * l.HalfLineWidth / d)
l.vertices = append(l.vertices, x1+nx, y1+ny, x2+nx, y2+ny)
l.rewind = append(l.rewind, x1-nx, y1-ny, x2-nx, y2-ny)
l.x, l.y, l.nx, l.ny = x2, y2, nx, ny
}
}
func (l *LineStroker) joinLine(x1, y1, nx1, ny1, x2, y2 float) {
dx := (x2 - x1)
dy := (y2 - y1)
d := vectorDistance(dx, dy)
if d != 0 {
nx := dy * l.HalfLineWidth / d
ny := -(dx * l.HalfLineWidth / d)
/* l.join(x1, y1, x1 + nx, y1 - ny, nx, ny, x1 + ny2, y1 + nx2, nx2, ny2)
l.join(x1, y1, x1 - ny1, y1 - nx1, nx1, ny1, x1 - ny2, y1 - nx2, nx2, ny2)*/
l.vertices = append(l.vertices, x1+nx, y1+ny, x2+nx, y2+ny)
l.rewind = append(l.rewind, x1-nx, y1-ny, x2-nx, y2-ny)
l.x, l.y, l.nx, l.ny = x2, y2, nx, ny
}
}

View File

@ -2,6 +2,7 @@
// created: 21/11/2010 by Laurent Le Goff
package draw2d
import (
"freetype-go.googlecode.com/hg/freetype/raster"
)
@ -29,7 +30,7 @@ func (tr MatrixTransform) TransformRasterPoint(points ...*raster.Point) {
for _, point := range points {
x := float(point.X) / 256
y := float(point.Y) / 256
point.X = raster.Fix32((x*tr[0] + y*tr[2] + tr[4]) * 256)
point.X = raster.Fix32((x*tr[0] + y*tr[2] + tr[4]) * 256)
point.Y = raster.Fix32((x*tr[1] + y*tr[3] + tr[5]) * 256)
}
}
@ -51,7 +52,7 @@ func (tr MatrixTransform) VectorTransform(points ...*float) {
x := *points[i]
y := *points[j]
*points[i] = x*tr[0] + y*tr[2]
*points[j] = x*tr[1] + y*tr[3]
*points[j] = x*tr[1] + y*tr[3]
}
}
@ -125,35 +126,35 @@ func (tr1 MatrixTransform) Multiply(tr2 MatrixTransform) MatrixTransform {
}
func (tr *MatrixTransform) Scale(sx, sy float) (*MatrixTransform){
tr[0] = tr[0]*sx;
tr[1] = tr[1]*sx;
tr[4] = tr[4]*sx;
tr[2] = tr[2]*sy;
tr[3] = tr[3]*sy;
tr[5] = tr[5]*sy;
return tr;
func (tr *MatrixTransform) Scale(sx, sy float) *MatrixTransform {
tr[0] = tr[0] * sx
tr[1] = tr[1] * sx
tr[4] = tr[4] * sx
tr[2] = tr[2] * sy
tr[3] = tr[3] * sy
tr[5] = tr[5] * sy
return tr
}
func (tr *MatrixTransform) Translate(tx, ty float) (*MatrixTransform){
tr[4] = tr[4] + tx
tr[5] = tr[5] + ty
return tr;
func (tr *MatrixTransform) Translate(tx, ty float) *MatrixTransform {
tr[4] = tr[4] + tx
tr[5] = tr[5] + ty
return tr
}
func (tr *MatrixTransform) Rotate(angle float) (*MatrixTransform){
ca := cos(angle);
sa := sin(angle);
t0 := tr[0] * ca - tr[1] * sa;
t2 := tr[1] * ca - tr[3] * sa;
t4 := tr[4] * ca - tr[5] * sa;
tr[1] = tr[0] * sa + tr[1] * ca;
tr[3] = tr[2] * sa + tr[3] * ca;
tr[5] = tr[4] * sa + tr[5] * ca;
tr[0] = t0;
tr[2] = t2;
tr[4] = t4;
return tr;
func (tr *MatrixTransform) Rotate(angle float) *MatrixTransform {
ca := cos(angle)
sa := sin(angle)
t0 := tr[0]*ca - tr[1]*sa
t2 := tr[1]*ca - tr[3]*sa
t4 := tr[4]*ca - tr[5]*sa
tr[1] = tr[0]*sa + tr[1]*ca
tr[3] = tr[2]*sa + tr[3]*ca
tr[5] = tr[4]*sa + tr[5]*ca
tr[0] = t0
tr[2] = t2
tr[4] = t4
return tr
}
func (tr MatrixTransform) GetTranslation() (x, y float) {
@ -167,7 +168,7 @@ func (tr MatrixTransform) GetScaling() (x, y float) {
func (tr MatrixTransform) GetMaxAbsScaling() (s float) {
sx := fabs(tr[0])
sy := fabs(tr[3])
if(sx > sy) {
if sx > sy {
return sx
}
return sy
@ -176,7 +177,7 @@ func (tr MatrixTransform) GetMaxAbsScaling() (s float) {
func (tr MatrixTransform) GetMinAbsScaling() (s float) {
sx := fabs(tr[0])
sy := fabs(tr[3])
if(sx > sy) {
if sx > sy {
return sy
}
return sx
@ -221,13 +222,34 @@ func fequals(float1, float2 float) bool {
return fabs(float1-float2) <= epsilon
}
// this VertexConverter apply the Matrix transformation tr
type VertexMatrixTransform struct {
tr MatrixTransform
Next VertexConverter
}
func NewVertexMatrixTransform(tr MatrixTransform, converter VertexConverter) *VertexMatrixTransform {
return &VertexMatrixTransform{tr, converter}
}
// Vertex Matrix Transform
func (vmt *VertexMatrixTransform) NextCommand(command VertexCommand) {
vmt.Next.NextCommand(command)
}
func (vmt *VertexMatrixTransform) Vertex(x, y float) {
vmt.tr.Transform(&x, &y)
vmt.Next.Vertex(x, y)
}
// this adder apply a Matrix transformation to points
type MatrixTransformAdder struct {
tr MatrixTransform
tr MatrixTransform
next raster.Adder
}
func NewMatrixTransformAdder(tr MatrixTransform, adder raster.Adder) (*MatrixTransformAdder) {
func NewMatrixTransformAdder(tr MatrixTransform, adder raster.Adder) *MatrixTransformAdder {
return &MatrixTransformAdder{tr, adder}
}
@ -255,4 +277,3 @@ func (mta MatrixTransformAdder) Add3(b, c, d raster.Point) {
mta.tr.TransformRasterPoint(&b, &c, &d)
mta.next.Add3(b, c, d)
}