From 0bff8ca6ee60422002ad63362f7a2de2d6c17099 Mon Sep 17 00:00:00 2001 From: "legoff.laurent" Date: Mon, 6 Dec 2010 13:46:08 +0000 Subject: [PATCH] use vertex converter as antigrain project add a Stroke2 and Fill2 method to GraphicContext (comparison purpose: different algorithm) --- draw2d/src/pkg/draw2d/advanced_path.go | 28 ++--- draw2d/src/pkg/draw2d/arc.go | 10 +- draw2d/src/pkg/draw2d/curves.go | 45 ++++--- draw2d/src/pkg/draw2d/dasher.go | 61 +++++---- draw2d/src/pkg/draw2d/demux_converter.go | 20 +++ draw2d/src/pkg/draw2d/draw2d.go | 154 +++++++++++++++-------- draw2d/src/pkg/draw2d/math.go | 4 + draw2d/src/pkg/draw2d/path.go | 18 ++- draw2d/src/pkg/draw2d/path_adder.go | 37 +++--- draw2d/src/pkg/draw2d/path_converter.go | 153 ++++++++++++++++++++++ draw2d/src/pkg/draw2d/path_storage.go | 38 ------ draw2d/src/pkg/draw2d/stroker.go | 120 ++++++++++++++++++ draw2d/src/pkg/draw2d/transform.go | 85 ++++++++----- 13 files changed, 566 insertions(+), 207 deletions(-) create mode 100644 draw2d/src/pkg/draw2d/demux_converter.go create mode 100644 draw2d/src/pkg/draw2d/path_converter.go create mode 100644 draw2d/src/pkg/draw2d/stroker.go diff --git a/draw2d/src/pkg/draw2d/advanced_path.go b/draw2d/src/pkg/draw2d/advanced_path.go index ec88083..28e7698 100644 --- a/draw2d/src/pkg/draw2d/advanced_path.go +++ b/draw2d/src/pkg/draw2d/advanced_path.go @@ -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() -} \ No newline at end of file +} diff --git a/draw2d/src/pkg/draw2d/arc.go b/draw2d/src/pkg/draw2d/arc.go index 6ea5bd5..9c4e704 100644 --- a/draw2d/src/pkg/draw2d/arc.go +++ b/draw2d/src/pkg/draw2d/arc.go @@ -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 } diff --git a/draw2d/src/pkg/draw2d/curves.go b/draw2d/src/pkg/draw2d/curves.go index 5efb110..f00e650 100644 --- a/draw2d/src/pkg/draw2d/curves.go +++ b/draw2d/src/pkg/draw2d/curves.go @@ -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 } } diff --git a/draw2d/src/pkg/draw2d/dasher.go b/draw2d/src/pkg/draw2d/dasher.go index 7a01be5..8cef27b 100644 --- a/draw2d/src/pkg/draw2d/dasher.go +++ b/draw2d/src/pkg/draw2d/dasher.go @@ -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] diff --git a/draw2d/src/pkg/draw2d/demux_converter.go b/draw2d/src/pkg/draw2d/demux_converter.go new file mode 100644 index 0000000..872d6b2 --- /dev/null +++ b/draw2d/src/pkg/draw2d/demux_converter.go @@ -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) + } +} diff --git a/draw2d/src/pkg/draw2d/draw2d.go b/draw2d/src/pkg/draw2d/draw2d.go index 2b4abdf..5206446 100644 --- a/draw2d/src/pkg/draw2d/draw2d.go +++ b/draw2d/src/pkg/draw2d/draw2d.go @@ -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 } - diff --git a/draw2d/src/pkg/draw2d/math.go b/draw2d/src/pkg/draw2d/math.go index 25eb470..1815ac2 100644 --- a/draw2d/src/pkg/draw2d/math.go +++ b/draw2d/src/pkg/draw2d/math.go @@ -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 diff --git a/draw2d/src/pkg/draw2d/path.go b/draw2d/src/pkg/draw2d/path.go index dbe21da..601e7b4 100644 --- a/draw2d/src/pkg/draw2d/path.go +++ b/draw2d/src/pkg/draw2d/path.go @@ -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) +} diff --git a/draw2d/src/pkg/draw2d/path_adder.go b/draw2d/src/pkg/draw2d/path_adder.go index 71524d0..ee7fe6e 100644 --- a/draw2d/src/pkg/draw2d/path_adder.go +++ b/draw2d/src/pkg/draw2d/path_adder.go @@ -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 } diff --git a/draw2d/src/pkg/draw2d/path_converter.go b/draw2d/src/pkg/draw2d/path_converter.go new file mode 100644 index 0000000..0dc27ab --- /dev/null +++ b/draw2d/src/pkg/draw2d/path_converter.go @@ -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 +} diff --git a/draw2d/src/pkg/draw2d/path_storage.go b/draw2d/src/pkg/draw2d/path_storage.go index b13ed88..95cf158 100644 --- a/draw2d/src/pkg/draw2d/path_storage.go +++ b/draw2d/src/pkg/draw2d/path_storage.go @@ -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 diff --git a/draw2d/src/pkg/draw2d/stroker.go b/draw2d/src/pkg/draw2d/stroker.go new file mode 100644 index 0000000..1b213d2 --- /dev/null +++ b/draw2d/src/pkg/draw2d/stroker.go @@ -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 + } +} diff --git a/draw2d/src/pkg/draw2d/transform.go b/draw2d/src/pkg/draw2d/transform.go index 1962c64..41563d1 100644 --- a/draw2d/src/pkg/draw2d/transform.go +++ b/draw2d/src/pkg/draw2d/transform.go @@ -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) } -