remove path package and create draw2dimg package

This commit is contained in:
Laurent Le Goff 2015-04-29 14:33:32 +02:00
parent 24d62b9aa7
commit 966a9b73f7
24 changed files with 506 additions and 598 deletions

161
curve.go Normal file
View File

@ -0,0 +1,161 @@
// Copyright 2010 The draw2d Authors. All rights reserved.
// created: 17/05/2011 by Laurent Le Goff
package draw2d
import (
"math"
)
const (
CurveRecursionLimit = 32
)
// Cubic
// x1, y1, cpx1, cpy1, cpx2, cpy2, x2, y2 float64
// Subdivide a Bezier cubic curve in 2 equivalents Bezier cubic curves.
// c1 and c2 parameters are the resulting curves
func SubdivideCubic(c, c1, c2 []float64) {
// First point of c is the first point of c1
c1[0], c1[1] = c[0], c[1]
// Last point of c is the last point of c2
c2[6], c2[7] = c[6], c[7]
// Subdivide segment using midpoints
c1[2] = (c[0] + c[2]) / 2
c1[3] = (c[1] + c[3]) / 2
midX := (c[2] + c[4]) / 2
midY := (c[3] + c[5]) / 2
c2[4] = (c[4] + c[6]) / 2
c2[5] = (c[5] + c[7]) / 2
c1[4] = (c1[2] + midX) / 2
c1[5] = (c1[3] + midY) / 2
c2[2] = (midX + c2[4]) / 2
c2[3] = (midY + c2[5]) / 2
c1[6] = (c1[4] + c2[2]) / 2
c1[7] = (c1[5] + c2[3]) / 2
// Last Point of c1 is equal to the first point of c2
c2[0], c2[1] = c1[6], c1[7]
}
// TraceCubic generate lines subdividing the cubic curve using a Flattener
// flattening_threshold helps determines the flattening expectation of the curve
func TraceCubic(t Flattener, cubic []float64, flattening_threshold float64) {
// Allocation curves
var curves [CurveRecursionLimit * 8]float64
copy(curves[0:8], cubic[0:8])
i := 0
// current curve
var c []float64
var dx, dy, d2, d3 float64
for i >= 0 {
c = curves[i*8:]
dx = c[6] - c[0]
dy = c[7] - c[1]
d2 = math.Abs((c[2]-c[6])*dy - (c[3]-c[7])*dx)
d3 = math.Abs((c[4]-c[6])*dy - (c[5]-c[7])*dx)
// if it's flat then trace a line
if (d2+d3)*(d2+d3) < flattening_threshold*(dx*dx+dy*dy) || i == len(curves)-1 {
t.LineTo(c[6], c[7])
i--
} else {
// second half of bezier go lower onto the stack
SubdivideCubic(c, curves[(i+1)*8:], curves[i*8:])
i++
}
}
}
// Quad
// x1, y1, cpx1, cpy2, x2, y2 float64
// Subdivide a Bezier quad curve in 2 equivalents Bezier quad curves.
// c1 and c2 parameters are the resulting curves
func SubdivideQuad(c, c1, c2 []float64) {
// First point of c is the first point of c1
c1[0], c1[1] = c[0], c[1]
// Last point of c is the last point of c2
c2[4], c2[5] = c[4], c[5]
// Subdivide segment using midpoints
c1[2] = (c[0] + c[2]) / 2
c1[3] = (c[1] + c[3]) / 2
c2[2] = (c[2] + c[4]) / 2
c2[3] = (c[3] + c[5]) / 2
c1[4] = (c1[2] + c2[2]) / 2
c1[5] = (c1[3] + c2[3]) / 2
c2[0], c2[1] = c1[4], c1[5]
return
}
// Trace generate lines subdividing the curve using a Flattener
// flattening_threshold helps determines the flattening expectation of the curve
func TraceQuad(t Flattener, quad []float64, flattening_threshold float64) {
// Allocates curves stack
var curves [CurveRecursionLimit * 6]float64
copy(curves[0:6], quad[0:6])
i := 0
// current curve
var c []float64
var dx, dy, d float64
for i >= 0 {
c = curves[i*6:]
dx = c[4] - c[0]
dy = c[5] - c[1]
d = math.Abs(((c[2]-c[4])*dy - (c[3]-c[5])*dx))
// if it's flat then trace a line
if (d*d) < flattening_threshold*(dx*dx+dy*dy) || i == len(curves)-1 {
t.LineTo(c[4], c[5])
i--
} else {
// second half of bezier go lower onto the stack
SubdivideQuad(c, curves[(i+1)*6:], curves[i*6:])
i++
}
}
}
// TraceArc trace an arc using a Flattener
func TraceArc(t Flattener, x, y, rx, ry, start, angle, scale float64) (lastX, lastY float64) {
end := start + angle
clockWise := true
if angle < 0 {
clockWise = false
}
ra := (math.Abs(rx) + math.Abs(ry)) / 2
da := math.Acos(ra/(ra+0.125/scale)) * 2
//normalize
if !clockWise {
da = -da
}
angle = start + da
var curX, curY float64
for {
if (angle < end-da/4) != clockWise {
curX = x + math.Cos(end)*rx
curY = y + math.Sin(end)*ry
return curX, curY
}
curX = x + math.Cos(angle)*rx
curY = y + math.Sin(angle)*ry
angle += da
t.LineTo(curX, curY)
}
return curX, curY
}

View File

@ -1,70 +0,0 @@
// Copyright 2010 The draw2d Authors. All rights reserved.
// created: 21/11/2010 by Laurent Le Goff
package curve
import (
"math"
"code.google.com/p/freetype-go/freetype/raster"
)
// TraceArc trace an arc using a LineBuilder
func TraceArc(t LineBuilder, x, y, rx, ry, start, angle, scale float64) (lastX, lastY float64) {
end := start + angle
clockWise := true
if angle < 0 {
clockWise = false
}
ra := (math.Abs(rx) + math.Abs(ry)) / 2
da := math.Acos(ra/(ra+0.125/scale)) * 2
//normalize
if !clockWise {
da = -da
}
angle = start + da
var curX, curY float64
for {
if (angle < end-da/4) != clockWise {
curX = x + math.Cos(end)*rx
curY = y + math.Sin(end)*ry
return curX, curY
}
curX = x + math.Cos(angle)*rx
curY = y + math.Sin(angle)*ry
angle += da
t.LineTo(curX, curY)
}
return curX, curY
}
// TraceArc trace an arc using a Freetype
func TraceArcFt(adder raster.Adder, x, y, rx, ry, start, angle, scale float64) raster.Point {
end := start + angle
clockWise := true
if angle < 0 {
clockWise = false
}
ra := (math.Abs(rx) + math.Abs(ry)) / 2
da := math.Acos(ra/(ra+0.125/scale)) * 2
//normalize
if !clockWise {
da = -da
}
angle = start + da
var curX, curY float64
for {
if (angle < end-da/4) != clockWise {
curX = x + math.Cos(end)*rx
curY = y + math.Sin(end)*ry
return raster.Point{raster.Fix32(curX * 256), raster.Fix32(curY * 256)}
}
curX = x + math.Cos(angle)*rx
curY = y + math.Sin(angle)*ry
angle += da
adder.Add1(raster.Point{raster.Fix32(curX * 256), raster.Fix32(curY * 256)})
}
return raster.Point{raster.Fix32(curX * 256), raster.Fix32(curY * 256)}
}

View File

@ -1,80 +0,0 @@
// Copyright 2010 The draw2d Authors. All rights reserved.
// created: 17/05/2011 by Laurent Le Goff
// Package curve implements Bezier Curve Subdivision using De Casteljau's algorithm
package curve
import (
"math"
)
const (
CurveRecursionLimit = 32
)
// x1, y1, cpx1, cpy1, cpx2, cpy2, x2, y2 float64
// type Cubic []float64
// Subdivide a Bezier cubic curve in 2 equivalents Bezier cubic curves.
// c1 and c2 parameters are the resulting curves
func SubdivideCubic(c, c1, c2 []float64) {
// First point of c is the first point of c1
c1[0], c1[1] = c[0], c[1]
// Last point of c is the last point of c2
c2[6], c2[7] = c[6], c[7]
// Subdivide segment using midpoints
c1[2] = (c[0] + c[2]) / 2
c1[3] = (c[1] + c[3]) / 2
midX := (c[2] + c[4]) / 2
midY := (c[3] + c[5]) / 2
c2[4] = (c[4] + c[6]) / 2
c2[5] = (c[5] + c[7]) / 2
c1[4] = (c1[2] + midX) / 2
c1[5] = (c1[3] + midY) / 2
c2[2] = (midX + c2[4]) / 2
c2[3] = (midY + c2[5]) / 2
c1[6] = (c1[4] + c2[2]) / 2
c1[7] = (c1[5] + c2[3]) / 2
// Last Point of c1 is equal to the first point of c2
c2[0], c2[1] = c1[6], c1[7]
}
// TraceCubic generate lines subdividing the cubic curve using a LineBuilder
// flattening_threshold helps determines the flattening expectation of the curve
func TraceCubic(t LineBuilder, cubic []float64, flattening_threshold float64) {
// Allocation curves
var curves [CurveRecursionLimit * 8]float64
copy(curves[0:8], cubic[0:8])
i := 0
// current curve
var c []float64
var dx, dy, d2, d3 float64
for i >= 0 {
c = curves[i*8:]
dx = c[6] - c[0]
dy = c[7] - c[1]
d2 = math.Abs((c[2]-c[6])*dy - (c[3]-c[7])*dx)
d3 = math.Abs((c[4]-c[6])*dy - (c[5]-c[7])*dx)
// if it's flat then trace a line
if (d2+d3)*(d2+d3) < flattening_threshold*(dx*dx+dy*dy) || i == len(curves)-1 {
t.LineTo(c[6], c[7])
i--
} else {
// second half of bezier go lower onto the stack
SubdivideCubic(c, curves[(i+1)*8:], curves[i*8:])
i++
}
}
}

View File

@ -1,7 +0,0 @@
package curve
// LineBuilder is an interface that help segmenting curve into small lines
type LineBuilder interface {
// LineTo a point
LineTo(x, y float64)
}

View File

@ -1,60 +0,0 @@
// Copyright 2010 The draw2d Authors. All rights reserved.
// created: 17/05/2011 by Laurent Le Goff
package curve
import (
"math"
)
// x1, y1, cpx1, cpy2, x2, y2 float64
// type Quad [6]float64
// Subdivide a Bezier quad curve in 2 equivalents Bezier quad curves.
// c1 and c2 parameters are the resulting curves
func SubdivideQuad(c, c1, c2 []float64) {
// First point of c is the first point of c1
c1[0], c1[1] = c[0], c[1]
// Last point of c is the last point of c2
c2[4], c2[5] = c[4], c[5]
// Subdivide segment using midpoints
c1[2] = (c[0] + c[2]) / 2
c1[3] = (c[1] + c[3]) / 2
c2[2] = (c[2] + c[4]) / 2
c2[3] = (c[3] + c[5]) / 2
c1[4] = (c1[2] + c2[2]) / 2
c1[5] = (c1[3] + c2[3]) / 2
c2[0], c2[1] = c1[4], c1[5]
return
}
// Trace generate lines subdividing the curve using a LineBuilder
// flattening_threshold helps determines the flattening expectation of the curve
func TraceQuad(t LineBuilder, quad []float64, flattening_threshold float64) {
// Allocates curves stack
var curves [CurveRecursionLimit * 6]float64
copy(curves[0:6], quad[0:6])
i := 0
// current curve
var c []float64
var dx, dy, d float64
for i >= 0 {
c = curves[i*6:]
dx = c[4] - c[0]
dy = c[5] - c[1]
d = math.Abs(((c[2]-c[4])*dy - (c[3]-c[5])*dx))
// if it's flat then trace a line
if (d*d) < flattening_threshold*(dx*dx+dy*dy) || i == len(curves)-1 {
t.LineTo(c[4], c[5])
i--
} else {
// second half of bezier go lower onto the stack
SubdivideQuad(c, curves[(i+1)*6:], curves[i*6:])
i++
}
}
}

View File

@ -1,4 +1,4 @@
package curve package draw2d
import ( import (
"bufio" "bufio"
@ -34,18 +34,6 @@ var (
} }
) )
type Path struct {
points []float64
}
func (p *Path) MoveTo(x, y float64) {
p.points = append(p.points, x, y)
}
func (p *Path) LineTo(x, y float64) {
p.points = append(p.points, x, y)
}
func init() { func init() {
os.Mkdir("test_results", 0666) os.Mkdir("test_results", 0666)
f, err := os.Create("test_results/_test.html") f, err := os.Create("test_results/_test.html")
@ -85,32 +73,32 @@ func drawPoints(img draw.Image, c color.Color, s ...float64) image.Image {
func TestCubicCurve(t *testing.T) { func TestCubicCurve(t *testing.T) {
for i := 0; i < len(testsCubicFloat64); i += 8 { for i := 0; i < len(testsCubicFloat64); i += 8 {
var p Path var p SegmentedPath
p.MoveTo(testsCubicFloat64[i], testsCubicFloat64[i+1]) p.MoveTo(testsCubicFloat64[i], testsCubicFloat64[i+1])
TraceCubic(&p, testsCubicFloat64[i:], flattening_threshold) TraceCubic(&p, testsCubicFloat64[i:], flattening_threshold)
img := image.NewNRGBA(image.Rect(0, 0, 300, 300)) img := image.NewNRGBA(image.Rect(0, 0, 300, 300))
raster.PolylineBresenham(img, color.NRGBA{0xff, 0, 0, 0xff}, testsCubicFloat64[i:i+8]...) raster.PolylineBresenham(img, color.NRGBA{0xff, 0, 0, 0xff}, testsCubicFloat64[i:i+8]...)
raster.PolylineBresenham(img, image.Black, p.points...) raster.PolylineBresenham(img, image.Black, p.Points...)
//drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, curve[:]...) //drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, curve[:]...)
drawPoints(img, color.NRGBA{0, 0, 0, 0xff}, p.points...) drawPoints(img, color.NRGBA{0, 0, 0, 0xff}, p.Points...)
SaveToPngFile(fmt.Sprintf("test_results/_test%d.png", i/8), img) SaveToPngFile(fmt.Sprintf("test_results/_test%d.png", i/8), 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 := 0; i < len(testsQuadFloat64); i += 6 { for i := 0; i < len(testsQuadFloat64); i += 6 {
var p Path var p SegmentedPath
p.MoveTo(testsQuadFloat64[i], testsQuadFloat64[i+1]) p.MoveTo(testsQuadFloat64[i], testsQuadFloat64[i+1])
TraceQuad(&p, testsQuadFloat64[i:], flattening_threshold) TraceQuad(&p, testsQuadFloat64[i:], flattening_threshold)
img := image.NewNRGBA(image.Rect(0, 0, 300, 300)) img := image.NewNRGBA(image.Rect(0, 0, 300, 300))
raster.PolylineBresenham(img, color.NRGBA{0xff, 0, 0, 0xff}, testsQuadFloat64[i:i+6]...) raster.PolylineBresenham(img, color.NRGBA{0xff, 0, 0, 0xff}, testsQuadFloat64[i:i+6]...)
raster.PolylineBresenham(img, image.Black, p.points...) raster.PolylineBresenham(img, image.Black, p.Points...)
//drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, curve[:]...) //drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, curve[:]...)
drawPoints(img, color.NRGBA{0, 0, 0, 0xff}, p.points...) drawPoints(img, color.NRGBA{0, 0, 0, 0xff}, p.Points...)
SaveToPngFile(fmt.Sprintf("test_results/_testQuad%d.png", i), img) SaveToPngFile(fmt.Sprintf("test_results/_testQuad%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()
} }
@ -118,7 +106,7 @@ func TestQuadCurve(t *testing.T) {
func BenchmarkCubicCurve(b *testing.B) { func BenchmarkCubicCurve(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
for i := 0; i < len(testsCubicFloat64); i += 8 { for i := 0; i < len(testsCubicFloat64); i += 8 {
var p Path var p SegmentedPath
p.MoveTo(testsCubicFloat64[i], testsCubicFloat64[i+1]) p.MoveTo(testsCubicFloat64[i], testsCubicFloat64[i+1])
TraceCubic(&p, testsCubicFloat64[i:], flattening_threshold) TraceCubic(&p, testsCubicFloat64[i:], flattening_threshold)
} }

View File

@ -1,17 +1,21 @@
// Copyright 2010 The draw2d Authors. All rights reserved. // Copyright 2010 The draw2d Authors. All rights reserved.
// created: 13/12/2010 by Laurent Le Goff // created: 13/12/2010 by Laurent Le Goff
package path package draw2dbase
import (
"github.com/llgcode/draw2d"
)
type DashVertexConverter struct { type DashVertexConverter struct {
next LineBuilder next draw2d.Flattener
x, y, distance float64 x, y, distance float64
dash []float64 dash []float64
currentDash int currentDash int
dashOffset float64 dashOffset float64
} }
func NewDashConverter(dash []float64, dashOffset float64, converter LineBuilder) *DashVertexConverter { func NewDashConverter(dash []float64, dashOffset float64, converter draw2d.Flattener) *DashVertexConverter {
var dasher DashVertexConverter var dasher DashVertexConverter
dasher.dash = dash dasher.dash = dash
dasher.currentDash = 0 dasher.currentDash = 0
@ -83,3 +87,7 @@ func (dasher *DashVertexConverter) lineTo(x, y float64) {
} }
dasher.x, dasher.y = x, y dasher.x, dasher.y = x, y
} }
func distance(x1, y1, x2, y2 float64) float64 {
return vectorDistance(x2-x1, y2-y1)
}

View File

@ -0,0 +1,39 @@
package draw2dbase
import (
"github.com/llgcode/draw2d"
)
type DemuxFlattener struct {
Flatteners []draw2d.Flattener
}
func (dc DemuxFlattener) MoveTo(x, y float64) {
for _, flattener := range dc.Flatteners {
flattener.MoveTo(x, y)
}
}
func (dc DemuxFlattener) LineTo(x, y float64) {
for _, flattener := range dc.Flatteners {
flattener.LineTo(x, y)
}
}
func (dc DemuxFlattener) LineJoin() {
for _, flattener := range dc.Flatteners {
flattener.LineJoin()
}
}
func (dc DemuxFlattener) Close() {
for _, flattener := range dc.Flatteners {
flattener.Close()
}
}
func (dc DemuxFlattener) End() {
for _, flattener := range dc.Flatteners {
flattener.End()
}
}

29
draw2dbase/ftpath.go Normal file
View File

@ -0,0 +1,29 @@
// Copyright 2010 The draw2d Authors. All rights reserved.
// created: 13/12/2010 by Laurent Le Goff
package draw2dbase
import (
"code.google.com/p/freetype-go/freetype/raster"
)
type FtLineBuilder struct {
Adder raster.Adder
}
func (liner FtLineBuilder) MoveTo(x, y float64) {
liner.Adder.Start(raster.Point{raster.Fix32(x * 256), raster.Fix32(y * 256)})
}
func (liner FtLineBuilder) LineTo(x, y float64) {
liner.Adder.Add1(raster.Point{raster.Fix32(x * 256), raster.Fix32(y * 256)})
}
func (liner FtLineBuilder) LineJoin() {
}
func (liner FtLineBuilder) Close() {
}
func (liner FtLineBuilder) End() {
}

View File

@ -1,40 +1,42 @@
// Copyright 2010 The draw2d Authors. All rights reserved. // Copyright 2010 The draw2d Authors. All rights reserved.
// created: 21/11/2010 by Laurent Le Goff // created: 21/11/2010 by Laurent Le Goff
package draw2d package draw2dbase
import ( import (
"github.com/llgcode/draw2d/path" "github.com/llgcode/draw2d"
"image" "image"
"image/color" "image/color"
"code.google.com/p/freetype-go/freetype/truetype" "code.google.com/p/freetype-go/freetype/truetype"
) )
var DefaultFontData = draw2d.FontData{"luxi", draw2d.FontFamilySans, draw2d.FontStyleNormal}
type StackGraphicContext struct { type StackGraphicContext struct {
Current *ContextStack Current *ContextStack
} }
type ContextStack struct { type ContextStack struct {
Tr MatrixTransform Tr draw2d.MatrixTransform
Path *path.Path Path *draw2d.Path
LineWidth float64 LineWidth float64
Dash []float64 Dash []float64
DashOffset float64 DashOffset float64
StrokeColor color.Color StrokeColor color.Color
FillColor color.Color FillColor color.Color
FillRule FillRule FillRule draw2d.FillRule
Cap path.Cap Cap draw2d.LineCap
Join path.Join Join draw2d.LineJoin
FontSize float64 FontSize float64
FontData FontData FontData draw2d.FontData
font *truetype.Font Font *truetype.Font
// fontSize and dpi are used to calculate scale. scale is the number of // fontSize and dpi are used to calculate scale. scale is the number of
// 26.6 fixed point units in 1 em. // 26.6 fixed point units in 1 em.
scale int32 Scale int32
previous *ContextStack Previous *ContextStack
} }
/** /**
@ -43,41 +45,41 @@ type ContextStack struct {
func NewStackGraphicContext() *StackGraphicContext { func NewStackGraphicContext() *StackGraphicContext {
gc := &StackGraphicContext{} gc := &StackGraphicContext{}
gc.Current = new(ContextStack) gc.Current = new(ContextStack)
gc.Current.Tr = NewIdentityMatrix() gc.Current.Tr = draw2d.NewIdentityMatrix()
gc.Current.Path = new(path.Path) gc.Current.Path = new(draw2d.Path)
gc.Current.LineWidth = 1.0 gc.Current.LineWidth = 1.0
gc.Current.StrokeColor = image.Black gc.Current.StrokeColor = image.Black
gc.Current.FillColor = image.White gc.Current.FillColor = image.White
gc.Current.Cap = path.RoundCap gc.Current.Cap = draw2d.RoundCap
gc.Current.FillRule = FillRuleEvenOdd gc.Current.FillRule = draw2d.FillRuleEvenOdd
gc.Current.Join = path.RoundJoin gc.Current.Join = draw2d.RoundJoin
gc.Current.FontSize = 10 gc.Current.FontSize = 10
gc.Current.FontData = defaultFontData gc.Current.FontData = DefaultFontData
return gc return gc
} }
func (gc *StackGraphicContext) GetMatrixTransform() MatrixTransform { func (gc *StackGraphicContext) GetMatrixTransform() draw2d.MatrixTransform {
return gc.Current.Tr return gc.Current.Tr
} }
func (gc *StackGraphicContext) SetMatrixTransform(Tr MatrixTransform) { func (gc *StackGraphicContext) SetMatrixTransform(Tr draw2d.MatrixTransform) {
gc.Current.Tr = Tr gc.Current.Tr = Tr
} }
func (gc *StackGraphicContext) ComposeMatrixTransform(Tr MatrixTransform) { func (gc *StackGraphicContext) ComposeMatrixTransform(Tr draw2d.MatrixTransform) {
gc.Current.Tr = Tr.Multiply(gc.Current.Tr) gc.Current.Tr = Tr.Multiply(gc.Current.Tr)
} }
func (gc *StackGraphicContext) Rotate(angle float64) { func (gc *StackGraphicContext) Rotate(angle float64) {
gc.Current.Tr = NewRotationMatrix(angle).Multiply(gc.Current.Tr) gc.Current.Tr = draw2d.NewRotationMatrix(angle).Multiply(gc.Current.Tr)
} }
func (gc *StackGraphicContext) Translate(tx, ty float64) { func (gc *StackGraphicContext) Translate(tx, ty float64) {
gc.Current.Tr = NewTranslationMatrix(tx, ty).Multiply(gc.Current.Tr) gc.Current.Tr = draw2d.NewTranslationMatrix(tx, ty).Multiply(gc.Current.Tr)
} }
func (gc *StackGraphicContext) Scale(sx, sy float64) { func (gc *StackGraphicContext) Scale(sx, sy float64) {
gc.Current.Tr = NewScaleMatrix(sx, sy).Multiply(gc.Current.Tr) gc.Current.Tr = draw2d.NewScaleMatrix(sx, sy).Multiply(gc.Current.Tr)
} }
func (gc *StackGraphicContext) SetStrokeColor(c color.Color) { func (gc *StackGraphicContext) SetStrokeColor(c color.Color) {
@ -88,40 +90,40 @@ func (gc *StackGraphicContext) SetFillColor(c color.Color) {
gc.Current.FillColor = c gc.Current.FillColor = c
} }
func (gc *StackGraphicContext) SetFillRule(f FillRule) { func (gc *StackGraphicContext) SetFillRule(f draw2d.FillRule) {
gc.Current.FillRule = f gc.Current.FillRule = f
} }
func (gc *StackGraphicContext) SetLineWidth(LineWidth float64) { func (gc *StackGraphicContext) SetLineWidth(lineWidth float64) {
gc.Current.LineWidth = LineWidth gc.Current.LineWidth = lineWidth
} }
func (gc *StackGraphicContext) SetLineCap(cap path.Cap) { func (gc *StackGraphicContext) SetLineCap(cap draw2d.LineCap) {
gc.Current.Cap = cap gc.Current.Cap = cap
} }
func (gc *StackGraphicContext) SetLineJoin(join path.Join) { func (gc *StackGraphicContext) SetLineJoin(join draw2d.LineJoin) {
gc.Current.Join = join gc.Current.Join = join
} }
func (gc *StackGraphicContext) SetLineDash(Dash []float64, DashOffset float64) { func (gc *StackGraphicContext) SetLineDash(dash []float64, dashOffset float64) {
gc.Current.Dash = Dash gc.Current.Dash = dash
gc.Current.DashOffset = DashOffset gc.Current.DashOffset = dashOffset
} }
func (gc *StackGraphicContext) SetFontSize(FontSize float64) { func (gc *StackGraphicContext) SetFontSize(fontSize float64) {
gc.Current.FontSize = FontSize gc.Current.FontSize = fontSize
} }
func (gc *StackGraphicContext) GetFontSize() float64 { func (gc *StackGraphicContext) GetFontSize() float64 {
return gc.Current.FontSize return gc.Current.FontSize
} }
func (gc *StackGraphicContext) SetFontData(FontData FontData) { func (gc *StackGraphicContext) SetFontData(fontData draw2d.FontData) {
gc.Current.FontData = FontData gc.Current.FontData = fontData
} }
func (gc *StackGraphicContext) GetFontData() FontData { func (gc *StackGraphicContext) GetFontData() draw2d.FontData {
return gc.Current.FontData return gc.Current.FontData
} }
@ -174,17 +176,17 @@ func (gc *StackGraphicContext) Save() {
context.Cap = gc.Current.Cap context.Cap = gc.Current.Cap
context.Join = gc.Current.Join context.Join = gc.Current.Join
context.Path = gc.Current.Path.Copy() context.Path = gc.Current.Path.Copy()
context.font = gc.Current.font context.Font = gc.Current.Font
context.scale = gc.Current.scale context.Scale = gc.Current.Scale
copy(context.Tr[:], gc.Current.Tr[:]) copy(context.Tr[:], gc.Current.Tr[:])
context.previous = gc.Current context.Previous = gc.Current
gc.Current = context gc.Current = context
} }
func (gc *StackGraphicContext) Restore() { func (gc *StackGraphicContext) Restore() {
if gc.Current.previous != nil { if gc.Current.Previous != nil {
oldContext := gc.Current oldContext := gc.Current
gc.Current = gc.Current.previous gc.Current = gc.Current.Previous
oldContext.previous = nil oldContext.Previous = nil
} }
} }

View File

@ -1,66 +1,50 @@
// Copyright 2010 The draw2d Authors. All rights reserved. // Copyright 2010 The draw2d Authors. All rights reserved.
// created: 13/12/2010 by Laurent Le Goff // created: 13/12/2010 by Laurent Le Goff
package path package draw2dbase
import ( import (
"code.google.com/p/freetype-go/freetype/raster" "code.google.com/p/freetype-go/freetype/raster"
"github.com/llgcode/draw2d"
"math"
) )
type Cap int func toFtCap(c draw2d.LineCap) raster.Capper {
const (
RoundCap Cap = iota
ButtCap
SquareCap
)
func (c Cap) Convert() raster.Capper {
switch c { switch c {
case RoundCap: case draw2d.RoundCap:
return raster.RoundCapper return raster.RoundCapper
case ButtCap: case draw2d.ButtCap:
return raster.ButtCapper return raster.ButtCapper
case SquareCap: case draw2d.SquareCap:
return raster.SquareCapper return raster.SquareCapper
} }
return raster.RoundCapper return raster.RoundCapper
} }
type Join int func toFtJoin(j draw2d.LineJoin) raster.Joiner {
const (
BevelJoin Join = iota
RoundJoin
MiterJoin
)
func (j Join) Convert() raster.Joiner {
switch j { switch j {
case RoundJoin: case draw2d.RoundJoin:
return raster.RoundJoiner return raster.RoundJoiner
case BevelJoin: case draw2d.BevelJoin:
return raster.BevelJoiner return raster.BevelJoiner
} }
return raster.RoundJoiner return raster.RoundJoiner
} }
type LineStroker struct { type LineStroker struct {
Next LineBuilder Next draw2d.Flattener
HalfLineWidth float64 HalfLineWidth float64
Cap Cap Cap draw2d.LineCap
Join Join Join draw2d.LineJoin
vertices []float64 vertices []float64
rewind []float64 rewind []float64
x, y, nx, ny float64 x, y, nx, ny float64
} }
func NewLineStroker(c Cap, j Join, converter LineBuilder) *LineStroker { func NewLineStroker(c draw2d.LineCap, j draw2d.LineJoin, flattener draw2d.Flattener) *LineStroker {
l := new(LineStroker) l := new(LineStroker)
l.Next = converter l.Next = flattener
l.HalfLineWidth = 0.5 l.HalfLineWidth = 0.5
l.vertices = make([]float64, 0, 256)
l.rewind = make([]float64, 0, 256)
l.Cap = c l.Cap = c
l.Join = j l.Join = j
return l return l
@ -122,3 +106,7 @@ func (l *LineStroker) appendVertex(vertices ...float64) {
l.vertices = append(l.vertices, vertices[:s]...) l.vertices = append(l.vertices, vertices[:s]...)
l.rewind = append(l.rewind, vertices[s:]...) l.rewind = append(l.rewind, vertices[s:]...)
} }
func vectorDistance(dx, dy float64) float64 {
return float64(math.Sqrt(dx*dx + dy*dy))
}

View File

@ -9,7 +9,7 @@ import (
"code.google.com/p/freetype-go/freetype/raster" "code.google.com/p/freetype-go/freetype/raster"
"github.com/go-gl/gl/v2.1/gl" "github.com/go-gl/gl/v2.1/gl"
"github.com/llgcode/draw2d" "github.com/llgcode/draw2d"
"github.com/llgcode/draw2d/path" "github.com/llgcode/draw2d/draw2dbase"
) )
func init() { func init() {
@ -113,7 +113,7 @@ func NewPainter() *Painter {
} }
type GraphicContext struct { type GraphicContext struct {
*draw2d.StackGraphicContext *draw2dbase.StackGraphicContext
painter *Painter painter *Painter
fillRasterizer *raster.Rasterizer fillRasterizer *raster.Rasterizer
strokeRasterizer *raster.Rasterizer strokeRasterizer *raster.Rasterizer
@ -122,7 +122,7 @@ type GraphicContext struct {
// NewGraphicContext creates a new Graphic context from an image. // NewGraphicContext creates a new Graphic context from an image.
func NewGraphicContext(width, height int) *GraphicContext { func NewGraphicContext(width, height int) *GraphicContext {
gc := &GraphicContext{ gc := &GraphicContext{
draw2d.NewStackGraphicContext(), draw2dbase.NewStackGraphicContext(),
NewPainter(), NewPainter(),
raster.NewRasterizer(width, height), raster.NewRasterizer(width, height),
raster.NewRasterizer(width, height), raster.NewRasterizer(width, height),
@ -184,20 +184,19 @@ func (gc *GraphicContext) paint(rasterizer *raster.Rasterizer, color color.Color
gc.Current.Path.Clear() gc.Current.Path.Clear()
} }
func (gc *GraphicContext) Stroke(paths ...*path.Path) { func (gc *GraphicContext) Stroke(paths ...*draw2d.Path) {
paths = append(paths, gc.Current.Path) paths = append(paths, gc.Current.Path)
gc.strokeRasterizer.UseNonZeroWinding = true gc.strokeRasterizer.UseNonZeroWinding = true
stroker := path.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2d.NewVertexMatrixTransform(gc.Current.Tr, path.NewFtLineBuilder(gc.strokeRasterizer))) stroker := draw2dbase.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2d.Transformer{gc.Current.Tr, draw2dbase.FtLineBuilder{gc.strokeRasterizer}})
stroker.HalfLineWidth = gc.Current.LineWidth / 2 stroker.HalfLineWidth = gc.Current.LineWidth / 2
var liner path.LineBuilder var liner draw2d.Flattener
if gc.Current.Dash != nil && len(gc.Current.Dash) > 0 { if gc.Current.Dash != nil && len(gc.Current.Dash) > 0 {
liner = path.NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker) liner = draw2dbase.NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker)
} else { } else {
liner = stroker liner = stroker
} }
for _, p := range paths { for _, p := range paths {
p.Flatten(liner, gc.Current.Tr.GetScale()) p.Flatten(liner, gc.Current.Tr.GetScale())
} }
@ -205,37 +204,37 @@ func (gc *GraphicContext) Stroke(paths ...*path.Path) {
gc.paint(gc.strokeRasterizer, gc.Current.StrokeColor) gc.paint(gc.strokeRasterizer, gc.Current.StrokeColor)
} }
func (gc *GraphicContext) Fill(paths ...*path.Path) { func (gc *GraphicContext) Fill(paths ...*draw2d.Path) {
paths = append(paths, gc.Current.Path) paths = append(paths, gc.Current.Path)
gc.fillRasterizer.UseNonZeroWinding = gc.Current.FillRule.UseNonZeroWinding() gc.fillRasterizer.UseNonZeroWinding = useNonZeroWinding(gc.Current.FillRule)
flattener := draw2d.NewVertexMatrixTransform(gc.Current.Tr, path.NewFtLineBuilder(gc.fillRasterizer)) /**** first method ****/
flattener := draw2d.Transformer{gc.Current.Tr, draw2dbase.FtLineBuilder{gc.fillRasterizer}}
for _, p := range paths { for _, p := range paths {
p.Flatten(flattener, gc.Current.Tr.GetScale()) p.Flatten(flattener, gc.Current.Tr.GetScale())
} }
gc.paint(gc.fillRasterizer, gc.Current.FillColor) gc.paint(gc.fillRasterizer, gc.Current.FillColor)
gc.Current.Path.Clear()
} }
func (gc *GraphicContext) FillStroke(paths ...*path.Path) { func (gc *GraphicContext) FillStroke(paths ...*draw2d.Path) {
paths = append(paths, gc.Current.Path) paths = append(paths, gc.Current.Path)
gc.fillRasterizer.UseNonZeroWinding = gc.Current.FillRule.UseNonZeroWinding() gc.fillRasterizer.UseNonZeroWinding = useNonZeroWinding(gc.Current.FillRule)
gc.strokeRasterizer.UseNonZeroWinding = true gc.strokeRasterizer.UseNonZeroWinding = true
flattener := draw2d.NewVertexMatrixTransform(gc.Current.Tr, path.NewFtLineBuilder(gc.fillRasterizer)) flattener := draw2d.Transformer{gc.Current.Tr, draw2dbase.FtLineBuilder{gc.fillRasterizer}}
stroker := path.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2d.NewVertexMatrixTransform(gc.Current.Tr, path.NewFtLineBuilder(gc.strokeRasterizer))) stroker := draw2dbase.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2d.Transformer{gc.Current.Tr, draw2dbase.FtLineBuilder{gc.strokeRasterizer}})
stroker.HalfLineWidth = gc.Current.LineWidth / 2 stroker.HalfLineWidth = gc.Current.LineWidth / 2
var liner path.LineBuilder var liner draw2d.Flattener
if gc.Current.Dash != nil && len(gc.Current.Dash) > 0 { if gc.Current.Dash != nil && len(gc.Current.Dash) > 0 {
liner = path.NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker) liner = draw2dbase.NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker)
} else { } else {
liner = stroker liner = stroker
} }
demux := path.NewLineBuilders(flattener, liner) demux := draw2dbase.DemuxFlattener{[]draw2d.Flattener{flattener, liner}}
for _, p := range paths { for _, p := range paths {
p.Flatten(demux, gc.Current.Tr.GetScale()) p.Flatten(demux, gc.Current.Tr.GetScale())
} }
@ -245,3 +244,13 @@ func (gc *GraphicContext) FillStroke(paths ...*path.Path) {
// Stroke // Stroke
gc.paint(gc.strokeRasterizer, gc.Current.StrokeColor) gc.paint(gc.strokeRasterizer, gc.Current.StrokeColor)
} }
func useNonZeroWinding(f draw2d.FillRule) bool {
switch f {
case draw2d.FillRuleEvenOdd:
return false
case draw2d.FillRuleWinding:
return true
}
return false
}

View File

@ -1,4 +1,4 @@
package draw2d package draw2dimg
import ( import (
"bufio" "bufio"

View File

@ -1,11 +1,12 @@
// Copyright 2010 The draw2d Authors. All rights reserved. // Copyright 2010 The draw2d Authors. All rights reserved.
// created: 21/11/2010 by Laurent Le Goff // created: 21/11/2010 by Laurent Le Goff
package draw2d package draw2dimg
import ( import (
"errors" "errors"
"github.com/llgcode/draw2d/path" "github.com/llgcode/draw2d"
"github.com/llgcode/draw2d/draw2dbase"
"image" "image"
"image/color" "image/color"
"image/draw" "image/draw"
@ -21,12 +22,8 @@ type Painter interface {
SetColor(color color.Color) SetColor(color color.Color)
} }
var ( type GraphicContext struct {
defaultFontData = FontData{"luxi", FontFamilySans, FontStyleNormal} *draw2dbase.StackGraphicContext
)
type ImageGraphicContext struct {
*StackGraphicContext
img draw.Image img draw.Image
painter Painter painter Painter
fillRasterizer *raster.Rasterizer fillRasterizer *raster.Rasterizer
@ -38,7 +35,7 @@ type ImageGraphicContext struct {
/** /**
* Create a new Graphic context from an image * Create a new Graphic context from an image
*/ */
func NewGraphicContext(img draw.Image) *ImageGraphicContext { func NewGraphicContext(img draw.Image) *GraphicContext {
var painter Painter var painter Painter
switch selectImage := img.(type) { switch selectImage := img.(type) {
case *image.RGBA: case *image.RGBA:
@ -50,11 +47,11 @@ func NewGraphicContext(img draw.Image) *ImageGraphicContext {
} }
// Create a new Graphic context from an image and a Painter (see Freetype-go) // Create a new Graphic context from an image and a Painter (see Freetype-go)
func NewGraphicContextWithPainter(img draw.Image, painter Painter) *ImageGraphicContext { func NewGraphicContextWithPainter(img draw.Image, painter Painter) *GraphicContext {
width, height := img.Bounds().Dx(), img.Bounds().Dy() width, height := img.Bounds().Dx(), img.Bounds().Dy()
dpi := 92 dpi := 92
gc := &ImageGraphicContext{ gc := &GraphicContext{
NewStackGraphicContext(), draw2dbase.NewStackGraphicContext(),
img, img,
painter, painter,
raster.NewRasterizer(width, height), raster.NewRasterizer(width, height),
@ -65,48 +62,48 @@ func NewGraphicContextWithPainter(img draw.Image, painter Painter) *ImageGraphic
return gc return gc
} }
func (gc *ImageGraphicContext) GetDPI() int { func (gc *GraphicContext) GetDPI() int {
return gc.DPI return gc.DPI
} }
func (gc *ImageGraphicContext) Clear() { func (gc *GraphicContext) Clear() {
width, height := gc.img.Bounds().Dx(), gc.img.Bounds().Dy() width, height := gc.img.Bounds().Dx(), gc.img.Bounds().Dy()
gc.ClearRect(0, 0, width, height) gc.ClearRect(0, 0, width, height)
} }
func (gc *ImageGraphicContext) ClearRect(x1, y1, x2, y2 int) { func (gc *GraphicContext) ClearRect(x1, y1, x2, y2 int) {
imageColor := image.NewUniform(gc.Current.FillColor) imageColor := image.NewUniform(gc.Current.FillColor)
draw.Draw(gc.img, image.Rect(x1, y1, x2, y2), imageColor, image.ZP, draw.Over) draw.Draw(gc.img, image.Rect(x1, y1, x2, y2), imageColor, image.ZP, draw.Over)
} }
func (gc *ImageGraphicContext) DrawImage(img image.Image) { func (gc *GraphicContext) DrawImage(img image.Image) {
DrawImage(img, gc.img, gc.Current.Tr, draw.Over, BilinearFilter) DrawImage(img, gc.img, gc.Current.Tr, draw.Over, BilinearFilter)
} }
func (gc *ImageGraphicContext) FillString(text string) (cursor float64) { func (gc *GraphicContext) FillString(text string) (cursor float64) {
return gc.FillStringAt(text, 0, 0) return gc.FillStringAt(text, 0, 0)
} }
func (gc *ImageGraphicContext) FillStringAt(text string, x, y float64) (cursor float64) { func (gc *GraphicContext) FillStringAt(text string, x, y float64) (cursor float64) {
width := gc.CreateStringPath(text, x, y) width := gc.CreateStringPath(text, x, y)
gc.Fill() gc.Fill()
return width return width
} }
func (gc *ImageGraphicContext) StrokeString(text string) (cursor float64) { func (gc *GraphicContext) StrokeString(text string) (cursor float64) {
return gc.StrokeStringAt(text, 0, 0) return gc.StrokeStringAt(text, 0, 0)
} }
func (gc *ImageGraphicContext) StrokeStringAt(text string, x, y float64) (cursor float64) { func (gc *GraphicContext) StrokeStringAt(text string, x, y float64) (cursor float64) {
width := gc.CreateStringPath(text, x, y) width := gc.CreateStringPath(text, x, y)
gc.Stroke() gc.Stroke()
return width return width
} }
func (gc *ImageGraphicContext) loadCurrentFont() (*truetype.Font, error) { func (gc *GraphicContext) loadCurrentFont() (*truetype.Font, error) {
font := GetFont(gc.Current.FontData) font := draw2d.GetFont(gc.Current.FontData)
if font == nil { if font == nil {
font = GetFont(defaultFontData) font = draw2d.GetFont(draw2dbase.DefaultFontData)
} }
if font == nil { if font == nil {
return nil, errors.New("No font set, and no default font available.") return nil, errors.New("No font set, and no default font available.")
@ -129,7 +126,7 @@ func pointToF64Point(p truetype.Point) (x, y float64) {
} }
// drawContour draws the given closed contour at the given sub-pixel offset. // drawContour draws the given closed contour at the given sub-pixel offset.
func (gc *ImageGraphicContext) drawContour(ps []truetype.Point, dx, dy float64) { func (gc *GraphicContext) drawContour(ps []truetype.Point, dx, dy float64) {
if len(ps) == 0 { if len(ps) == 0 {
return return
} }
@ -164,8 +161,8 @@ func (gc *ImageGraphicContext) drawContour(ps []truetype.Point, dx, dy float64)
} }
} }
func (gc *ImageGraphicContext) drawGlyph(glyph truetype.Index, dx, dy float64) error { func (gc *GraphicContext) drawGlyph(glyph truetype.Index, dx, dy float64) error {
if err := gc.glyphBuf.Load(gc.Current.font, gc.Current.scale, glyph, truetype.NoHinting); err != nil { if err := gc.glyphBuf.Load(gc.Current.Font, gc.Current.Scale, glyph, truetype.NoHinting); err != nil {
return err return err
} }
e0 := 0 e0 := 0
@ -182,7 +179,7 @@ func (gc *ImageGraphicContext) drawGlyph(glyph truetype.Index, dx, dy float64) e
// above and to the right of the point, but some may be below or to the left. // above and to the right of the point, but some may be below or to the left.
// For example, drawing a string that starts with a 'J' in an italic font may // For example, drawing a string that starts with a 'J' in an italic font may
// affect pixels below and left of the point. // affect pixels below and left of the point.
func (gc *ImageGraphicContext) CreateStringPath(s string, x, y float64) float64 { func (gc *GraphicContext) CreateStringPath(s string, x, y float64) float64 {
font, err := gc.loadCurrentFont() font, err := gc.loadCurrentFont()
if err != nil { if err != nil {
log.Println(err) log.Println(err)
@ -193,14 +190,14 @@ func (gc *ImageGraphicContext) CreateStringPath(s string, x, y float64) float64
for _, rune := range s { for _, rune := range s {
index := font.Index(rune) index := font.Index(rune)
if hasPrev { if hasPrev {
x += fUnitsToFloat64(font.Kerning(gc.Current.scale, prev, index)) x += fUnitsToFloat64(font.Kerning(gc.Current.Scale, prev, index))
} }
err := gc.drawGlyph(index, x, y) err := gc.drawGlyph(index, x, y)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
return startx - x return startx - x
} }
x += fUnitsToFloat64(font.HMetric(gc.Current.scale, index).AdvanceWidth) x += fUnitsToFloat64(font.HMetric(gc.Current.Scale, index).AdvanceWidth)
prev, hasPrev = index, true prev, hasPrev = index, true
} }
return x - startx return x - startx
@ -210,7 +207,7 @@ func (gc *ImageGraphicContext) CreateStringPath(s string, x, y float64) float64
// The the left edge of the em square of the first character of s // The the left edge of the em square of the first character of s
// and the baseline intersect at 0, 0 in the returned coordinates. // and the baseline intersect at 0, 0 in the returned coordinates.
// Therefore the top and left coordinates may well be negative. // Therefore the top and left coordinates may well be negative.
func (gc *ImageGraphicContext) GetStringBounds(s string) (left, top, right, bottom float64) { func (gc *GraphicContext) GetStringBounds(s string) (left, top, right, bottom float64) {
font, err := gc.loadCurrentFont() font, err := gc.loadCurrentFont()
if err != nil { if err != nil {
log.Println(err) log.Println(err)
@ -222,9 +219,9 @@ func (gc *ImageGraphicContext) GetStringBounds(s string) (left, top, right, bott
for _, rune := range s { for _, rune := range s {
index := font.Index(rune) index := font.Index(rune)
if hasPrev { if hasPrev {
cursor += fUnitsToFloat64(font.Kerning(gc.Current.scale, prev, index)) cursor += fUnitsToFloat64(font.Kerning(gc.Current.Scale, prev, index))
} }
if err := gc.glyphBuf.Load(gc.Current.font, gc.Current.scale, index, truetype.NoHinting); err != nil { if err := gc.glyphBuf.Load(gc.Current.Font, gc.Current.Scale, index, truetype.NoHinting); err != nil {
log.Println(err) log.Println(err)
return 0, 0, 0, 0 return 0, 0, 0, 0
} }
@ -239,7 +236,7 @@ func (gc *ImageGraphicContext) GetStringBounds(s string) (left, top, right, bott
right = math.Max(right, x+cursor) right = math.Max(right, x+cursor)
} }
} }
cursor += fUnitsToFloat64(font.HMetric(gc.Current.scale, index).AdvanceWidth) cursor += fUnitsToFloat64(font.HMetric(gc.Current.Scale, index).AdvanceWidth)
prev, hasPrev = index, true prev, hasPrev = index, true
} }
return left, top, right, bottom return left, top, right, bottom
@ -247,44 +244,44 @@ func (gc *ImageGraphicContext) GetStringBounds(s string) (left, top, right, bott
// recalc recalculates scale and bounds values from the font size, screen // recalc recalculates scale and bounds values from the font size, screen
// resolution and font metrics, and invalidates the glyph cache. // resolution and font metrics, and invalidates the glyph cache.
func (gc *ImageGraphicContext) recalc() { func (gc *GraphicContext) recalc() {
gc.Current.scale = int32(gc.Current.FontSize * float64(gc.DPI) * (64.0 / 72.0)) gc.Current.Scale = int32(gc.Current.FontSize * float64(gc.DPI) * (64.0 / 72.0))
} }
// SetDPI sets the screen resolution in dots per inch. // SetDPI sets the screen resolution in dots per inch.
func (gc *ImageGraphicContext) SetDPI(dpi int) { func (gc *GraphicContext) SetDPI(dpi int) {
gc.DPI = dpi gc.DPI = dpi
gc.recalc() gc.recalc()
} }
// SetFont sets the font used to draw text. // SetFont sets the font used to draw text.
func (gc *ImageGraphicContext) SetFont(font *truetype.Font) { func (gc *GraphicContext) SetFont(font *truetype.Font) {
gc.Current.font = font gc.Current.Font = font
} }
// SetFontSize sets the font size in points (as in ``a 12 point font''). // SetFontSize sets the font size in points (as in ``a 12 point font'').
func (gc *ImageGraphicContext) SetFontSize(fontSize float64) { func (gc *GraphicContext) SetFontSize(fontSize float64) {
gc.Current.FontSize = fontSize gc.Current.FontSize = fontSize
gc.recalc() gc.recalc()
} }
func (gc *ImageGraphicContext) paint(rasterizer *raster.Rasterizer, color color.Color) { func (gc *GraphicContext) paint(rasterizer *raster.Rasterizer, color color.Color) {
gc.painter.SetColor(color) gc.painter.SetColor(color)
rasterizer.Rasterize(gc.painter) rasterizer.Rasterize(gc.painter)
rasterizer.Clear() rasterizer.Clear()
gc.Current.Path.Clear() gc.Current.Path.Clear()
} }
func (gc *ImageGraphicContext) Stroke(paths ...*path.Path) { func (gc *GraphicContext) Stroke(paths ...*draw2d.Path) {
paths = append(paths, gc.Current.Path) paths = append(paths, gc.Current.Path)
gc.strokeRasterizer.UseNonZeroWinding = true gc.strokeRasterizer.UseNonZeroWinding = true
stroker := path.NewLineStroker(gc.Current.Cap, gc.Current.Join, NewVertexMatrixTransform(gc.Current.Tr, path.NewFtLineBuilder(gc.strokeRasterizer))) stroker := draw2dbase.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2d.Transformer{gc.Current.Tr, draw2dbase.FtLineBuilder{gc.strokeRasterizer}})
stroker.HalfLineWidth = gc.Current.LineWidth / 2 stroker.HalfLineWidth = gc.Current.LineWidth / 2
var liner path.LineBuilder var liner draw2d.Flattener
if gc.Current.Dash != nil && len(gc.Current.Dash) > 0 { if gc.Current.Dash != nil && len(gc.Current.Dash) > 0 {
liner = path.NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker) liner = draw2dbase.NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker)
} else { } else {
liner = stroker liner = stroker
} }
@ -295,12 +292,12 @@ func (gc *ImageGraphicContext) Stroke(paths ...*path.Path) {
gc.paint(gc.strokeRasterizer, gc.Current.StrokeColor) gc.paint(gc.strokeRasterizer, gc.Current.StrokeColor)
} }
func (gc *ImageGraphicContext) Fill(paths ...*path.Path) { func (gc *GraphicContext) Fill(paths ...*draw2d.Path) {
paths = append(paths, gc.Current.Path) paths = append(paths, gc.Current.Path)
gc.fillRasterizer.UseNonZeroWinding = gc.Current.FillRule.UseNonZeroWinding() gc.fillRasterizer.UseNonZeroWinding = useNonZeroWinding(gc.Current.FillRule)
/**** first method ****/ /**** first method ****/
flattener := NewVertexMatrixTransform(gc.Current.Tr, path.NewFtLineBuilder(gc.fillRasterizer)) flattener := draw2d.Transformer{gc.Current.Tr, draw2dbase.FtLineBuilder{gc.fillRasterizer}}
for _, p := range paths { for _, p := range paths {
p.Flatten(flattener, gc.Current.Tr.GetScale()) p.Flatten(flattener, gc.Current.Tr.GetScale())
} }
@ -308,24 +305,24 @@ func (gc *ImageGraphicContext) Fill(paths ...*path.Path) {
gc.paint(gc.fillRasterizer, gc.Current.FillColor) gc.paint(gc.fillRasterizer, gc.Current.FillColor)
} }
func (gc *ImageGraphicContext) FillStroke(paths ...*path.Path) { func (gc *GraphicContext) FillStroke(paths ...*draw2d.Path) {
paths = append(paths, gc.Current.Path) paths = append(paths, gc.Current.Path)
gc.fillRasterizer.UseNonZeroWinding = gc.Current.FillRule.UseNonZeroWinding() gc.fillRasterizer.UseNonZeroWinding = useNonZeroWinding(gc.Current.FillRule)
gc.strokeRasterizer.UseNonZeroWinding = true gc.strokeRasterizer.UseNonZeroWinding = true
flattener := NewVertexMatrixTransform(gc.Current.Tr, path.NewFtLineBuilder(gc.fillRasterizer)) flattener := draw2d.Transformer{gc.Current.Tr, draw2dbase.FtLineBuilder{gc.fillRasterizer}}
stroker := path.NewLineStroker(gc.Current.Cap, gc.Current.Join, NewVertexMatrixTransform(gc.Current.Tr, path.NewFtLineBuilder(gc.strokeRasterizer))) stroker := draw2dbase.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2d.Transformer{gc.Current.Tr, draw2dbase.FtLineBuilder{gc.strokeRasterizer}})
stroker.HalfLineWidth = gc.Current.LineWidth / 2 stroker.HalfLineWidth = gc.Current.LineWidth / 2
var liner path.LineBuilder var liner draw2d.Flattener
if gc.Current.Dash != nil && len(gc.Current.Dash) > 0 { if gc.Current.Dash != nil && len(gc.Current.Dash) > 0 {
liner = path.NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker) liner = draw2dbase.NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker)
} else { } else {
liner = stroker liner = stroker
} }
demux := path.NewLineBuilders(flattener, liner) demux := draw2dbase.DemuxFlattener{[]draw2d.Flattener{flattener, liner}}
for _, p := range paths { for _, p := range paths {
p.Flatten(demux, gc.Current.Tr.GetScale()) p.Flatten(demux, gc.Current.Tr.GetScale())
} }
@ -336,11 +333,11 @@ func (gc *ImageGraphicContext) FillStroke(paths ...*path.Path) {
gc.paint(gc.strokeRasterizer, gc.Current.StrokeColor) gc.paint(gc.strokeRasterizer, gc.Current.StrokeColor)
} }
func (f FillRule) UseNonZeroWinding() bool { func useNonZeroWinding(f draw2d.FillRule) bool {
switch f { switch f {
case FillRuleEvenOdd: case draw2d.FillRuleEvenOdd:
return false return false
case FillRuleWinding: case draw2d.FillRuleWinding:
return true return true
} }
return false return false

View File

@ -2,9 +2,10 @@
// created: 21/11/2010 by Laurent Le Goff // created: 21/11/2010 by Laurent Le Goff
// see http://pippin.gimp.org/image_processing/chap_resampling.html // see http://pippin.gimp.org/image_processing/chap_resampling.html
package draw2d package draw2dimg
import ( import (
"github.com/llgcode/draw2d"
"image" "image"
"image/color" "image/color"
"image/draw" "image/draw"
@ -17,6 +18,7 @@ const (
LinearFilter ImageFilter = iota LinearFilter ImageFilter = iota
BilinearFilter BilinearFilter
BicubicFilter BicubicFilter
M = 1<<16 - 1
) )
//see http://pippin.gimp.org/image_processing/chap_resampling.html //see http://pippin.gimp.org/image_processing/chap_resampling.html
@ -103,7 +105,7 @@ func cubic(offset, v0, v1, v2, v3 float64) uint32 {
(-9*v0+9*v2))*offset + (v0 + 16*v1 + v2)) / 18.0) (-9*v0+9*v2))*offset + (v0 + 16*v1 + v2)) / 18.0)
} }
func DrawImage(src image.Image, dest draw.Image, tr MatrixTransform, op draw.Op, filter ImageFilter) { func DrawImage(src image.Image, dest draw.Image, tr draw2d.MatrixTransform, op draw.Op, filter ImageFilter) {
bounds := src.Bounds() bounds := src.Bounds()
x0, y0, x1, y1 := float64(bounds.Min.X), float64(bounds.Min.Y), float64(bounds.Max.X), float64(bounds.Max.Y) x0, y0, x1, y1 := float64(bounds.Min.X), float64(bounds.Min.Y), float64(bounds.Max.X), float64(bounds.Max.Y)
tr.TransformRectangle(&x0, &y0, &x1, &y1) tr.TransformRectangle(&x0, &y0, &x1, &y1)

View File

@ -1,7 +1,7 @@
// Copyright 2010 The draw2d Authors. All rights reserved. // Copyright 2010 The draw2d Authors. All rights reserved.
// created: 13/12/2010 by Laurent Le Goff // created: 13/12/2010 by Laurent Le Goff
package path package draw2d
import ( import (
"math" "math"

59
flattener.go Normal file
View File

@ -0,0 +1,59 @@
// Copyright 2010 The draw2d Authors. All rights reserved.
// created: 06/12/2010 by Laurent Le Goff
package draw2d
// Flattener receive segment definition
type Flattener interface {
// MoveTo Start a New line from the point (x, y)
MoveTo(x, y float64)
// LineTo Draw a line from the current position to the point (x, y)
LineTo(x, y float64)
// LineJoin add the most recent starting point to close the path to create a polygon
LineJoin()
// Close add the most recent starting point to close the path to create a polygon
Close()
// End mark the current line as finished so we can draw caps
End()
}
type SegmentedPath struct {
Points []float64
}
func (p *SegmentedPath) MoveTo(x, y float64) {
p.Points = append(p.Points, x, y)
// TODO need to mark this point as moveto
}
func (p *SegmentedPath) LineTo(x, y float64) {
p.Points = append(p.Points, x, y)
}
func (p *SegmentedPath) LineJoin() {
// TODO need to mark the current point as linejoin
}
func (p *SegmentedPath) Close() {
// TODO Close
}
func (p *SegmentedPath) End() {
// Nothing to do
}
type LineCap int
const (
RoundCap LineCap = iota
ButtCap
SquareCap
)
type LineJoin int
const (
BevelJoin LineJoin = iota
RoundJoin
MiterJoin
)

13
gc.go
View File

@ -4,7 +4,6 @@
package draw2d package draw2d
import ( import (
"github.com/llgcode/draw2d/path"
"image" "image"
"image/color" "image/color"
) )
@ -17,7 +16,7 @@ const (
) )
type GraphicContext interface { type GraphicContext interface {
path.PathBuilder PathBuilder
// Create a new path // Create a new path
BeginPath() BeginPath()
GetMatrixTransform() MatrixTransform GetMatrixTransform() MatrixTransform
@ -30,8 +29,8 @@ type GraphicContext interface {
SetFillColor(c color.Color) SetFillColor(c color.Color)
SetFillRule(f FillRule) SetFillRule(f FillRule)
SetLineWidth(lineWidth float64) SetLineWidth(lineWidth float64)
SetLineCap(cap path.Cap) SetLineCap(cap LineCap)
SetLineJoin(join path.Join) SetLineJoin(join LineJoin)
SetLineDash(dash []float64, dashOffset float64) SetLineDash(dash []float64, dashOffset float64)
SetFontSize(fontSize float64) SetFontSize(fontSize float64)
GetFontSize() float64 GetFontSize() float64
@ -50,7 +49,7 @@ type GraphicContext interface {
FillStringAt(text string, x, y float64) (cursor float64) FillStringAt(text string, x, y float64) (cursor float64)
StrokeString(text string) (cursor float64) StrokeString(text string) (cursor float64)
StrokeStringAt(text string, x, y float64) (cursor float64) StrokeStringAt(text string, x, y float64) (cursor float64)
Stroke(paths ...*path.Path) Stroke(paths ...*Path)
Fill(paths ...*path.Path) Fill(paths ...*Path)
FillStroke(paths ...*path.Path) FillStroke(paths ...*Path)
} }

11
math.go
View File

@ -1,11 +0,0 @@
// Copyright 2010 The draw2d Authors. All rights reserved.
// created: 21/11/2010 by Laurent Le Goff
package draw2d
func minMax(x, y float64) (min, max float64) {
if x > y {
return y, x
}
return x, y
}

View File

@ -2,11 +2,10 @@
// created: 21/11/2010 by Laurent Le Goff // created: 21/11/2010 by Laurent Le Goff
// Package path implements function to build path // Package path implements function to build path
package path package draw2d
import ( import (
"fmt" "fmt"
"github.com/llgcode/draw2d/curve"
"math" "math"
) )
@ -177,7 +176,7 @@ func (p *Path) String() string {
} }
// Flatten convert curves in straight segments keeping join segements // Flatten convert curves in straight segments keeping join segements
func (path *Path) Flatten(liner LineBuilder, scale float64) { func (path *Path) Flatten(flattener Flattener, scale float64) {
// First Point // First Point
var startX, startY float64 = 0, 0 var startX, startY float64 = 0, 0
// Current Point // Current Point
@ -189,33 +188,33 @@ func (path *Path) Flatten(liner LineBuilder, scale float64) {
x, y = path.Points[i], path.Points[i+1] x, y = path.Points[i], path.Points[i+1]
startX, startY = x, y startX, startY = x, y
if i != 0 { if i != 0 {
liner.End() flattener.End()
} }
liner.MoveTo(x, y) flattener.MoveTo(x, y)
i += 2 i += 2
case LineToCmp: case LineToCmp:
x, y = path.Points[i], path.Points[i+1] x, y = path.Points[i], path.Points[i+1]
liner.LineTo(x, y) flattener.LineTo(x, y)
liner.LineJoin() flattener.LineJoin()
i += 2 i += 2
case QuadCurveToCmp: case QuadCurveToCmp:
curve.TraceQuad(liner, path.Points[i-2:], 0.5) TraceQuad(flattener, path.Points[i-2:], 0.5)
x, y = path.Points[i+2], path.Points[i+3] x, y = path.Points[i+2], path.Points[i+3]
liner.LineTo(x, y) flattener.LineTo(x, y)
i += 4 i += 4
case CubicCurveToCmp: case CubicCurveToCmp:
curve.TraceCubic(liner, path.Points[i-2:], 0.5) TraceCubic(flattener, path.Points[i-2:], 0.5)
x, y = path.Points[i+4], path.Points[i+5] x, y = path.Points[i+4], path.Points[i+5]
liner.LineTo(x, y) flattener.LineTo(x, y)
i += 6 i += 6
case ArcToCmp: case ArcToCmp:
x, y = curve.TraceArc(liner, path.Points[i], path.Points[i+1], path.Points[i+2], path.Points[i+3], path.Points[i+4], path.Points[i+5], scale) x, y = TraceArc(flattener, path.Points[i], path.Points[i+1], path.Points[i+2], path.Points[i+3], path.Points[i+4], path.Points[i+5], scale)
liner.LineTo(x, y) flattener.LineTo(x, y)
i += 6 i += 6
case CloseCmp: case CloseCmp:
liner.LineTo(startX, startY) flattener.LineTo(startX, startY)
liner.Close() flattener.Close()
} }
} }
liner.End() flattener.End()
} }

View File

@ -1,56 +0,0 @@
// Copyright 2010 The draw2d Authors. All rights reserved.
// created: 06/12/2010 by Laurent Le Goff
package path
// LineBuilder defines drawing line methods
type LineBuilder interface {
// MoveTo Start a New line from the point (x, y)
MoveTo(x, y float64)
// LineTo Draw a line from the current position to the point (x, y)
LineTo(x, y float64)
// LineJoin add the most recent starting point to close the path to create a polygon
LineJoin()
// Close add the most recent starting point to close the path to create a polygon
Close()
// End mark the current line as finished so we can draw caps
End()
}
type LineBuilders struct {
builders []LineBuilder
}
func NewLineBuilders(builders ...LineBuilder) *LineBuilders {
return &LineBuilders{builders}
}
func (dc *LineBuilders) MoveTo(x, y float64) {
for _, converter := range dc.builders {
converter.MoveTo(x, y)
}
}
func (dc *LineBuilders) LineTo(x, y float64) {
for _, converter := range dc.builders {
converter.LineTo(x, y)
}
}
func (dc *LineBuilders) LineJoin() {
for _, converter := range dc.builders {
converter.LineJoin()
}
}
func (dc *LineBuilders) Close() {
for _, converter := range dc.builders {
converter.Close()
}
}
func (dc *LineBuilders) End() {
for _, converter := range dc.builders {
converter.End()
}
}

View File

@ -1,73 +0,0 @@
// Copyright 2010 The draw2d Authors. All rights reserved.
// created: 13/12/2010 by Laurent Le Goff
package path
import (
"code.google.com/p/freetype-go/freetype/raster"
"github.com/llgcode/draw2d/curve"
)
type FtLineBuilder struct {
adder raster.Adder
}
func NewFtLineBuilder(adder raster.Adder) *FtLineBuilder {
return &FtLineBuilder{adder}
}
func (FtLineBuilder *FtLineBuilder) MoveTo(x, y float64) {
FtLineBuilder.adder.Start(raster.Point{raster.Fix32(x * 256), raster.Fix32(y * 256)})
}
func (FtLineBuilder *FtLineBuilder) LineTo(x, y float64) {
FtLineBuilder.adder.Add1(raster.Point{raster.Fix32(x * 256), raster.Fix32(y * 256)})
}
func (FtLineBuilder *FtLineBuilder) LineJoin() {
}
func (FtLineBuilder *FtLineBuilder) Close() {
}
func (FtLineBuilder *FtLineBuilder) End() {
}
type PathAdder struct {
adder raster.Adder
firstPoint raster.Point
ApproximationScale float64
}
func NewPathAdder(adder raster.Adder) *PathAdder {
return &PathAdder{adder, raster.Point{0, 0}, 1}
}
func (pathAdder *PathAdder) Convert(paths ...*Path) {
for _, apath := range paths {
j := 0
for _, cmd := range apath.Components {
switch cmd {
case MoveToCmp:
pathAdder.firstPoint = raster.Point{raster.Fix32(apath.Points[j] * 256), raster.Fix32(apath.Points[j+1] * 256)}
pathAdder.adder.Start(pathAdder.firstPoint)
j += 2
case LineToCmp:
pathAdder.adder.Add1(raster.Point{raster.Fix32(apath.Points[j] * 256), raster.Fix32(apath.Points[j+1] * 256)})
j += 2
case QuadCurveToCmp:
pathAdder.adder.Add2(raster.Point{raster.Fix32(apath.Points[j] * 256), raster.Fix32(apath.Points[j+1] * 256)}, raster.Point{raster.Fix32(apath.Points[j+2] * 256), raster.Fix32(apath.Points[j+3] * 256)})
j += 4
case CubicCurveToCmp:
pathAdder.adder.Add3(raster.Point{raster.Fix32(apath.Points[j] * 256), raster.Fix32(apath.Points[j+1] * 256)}, raster.Point{raster.Fix32(apath.Points[j+2] * 256), raster.Fix32(apath.Points[j+3] * 256)}, raster.Point{raster.Fix32(apath.Points[j+4] * 256), raster.Fix32(apath.Points[j+5] * 256)})
j += 6
case ArcToCmp:
lastPoint := curve.TraceArcFt(pathAdder.adder, apath.Points[j], apath.Points[j+1], apath.Points[j+2], apath.Points[j+3], apath.Points[j+4], apath.Points[j+5], pathAdder.ApproximationScale)
pathAdder.adder.Add1(lastPoint)
j += 6
case CloseCmp:
pathAdder.adder.Add1(pathAdder.firstPoint)
}
}
}
}

View File

@ -1,13 +0,0 @@
package path
import (
"math"
)
func distance(x1, y1, x2, y2 float64) float64 {
return vectorDistance(x2-x1, y2-y1)
}
func vectorDistance(dx, dy float64) float64 {
return float64(math.Sqrt(dx*dx + dy*dy))
}

View File

@ -7,7 +7,6 @@ import (
"math" "math"
"code.google.com/p/freetype-go/freetype/raster" "code.google.com/p/freetype-go/freetype/raster"
"github.com/llgcode/draw2d/path"
) )
type MatrixTransform [6]float64 type MatrixTransform [6]float64
@ -38,6 +37,13 @@ func (tr MatrixTransform) TransformArray(points []float64) {
} }
} }
func minMax(x, y float64) (min, max float64) {
if x > y {
return y, x
}
return x, y
}
func (tr MatrixTransform) TransformRectangle(x0, y0, x2, y2 *float64) { func (tr MatrixTransform) TransformRectangle(x0, y0, x2, y2 *float64) {
x1 := *x2 x1 := *x2
y1 := *y0 y1 := *y0
@ -252,38 +258,34 @@ func fequals(float1, float2 float64) bool {
return math.Abs(float1-float2) <= epsilon return math.Abs(float1-float2) <= epsilon
} }
// this VertexConverter apply the Matrix transformation tr // Transformer apply the Matrix transformation tr
type VertexMatrixTransform struct { type Transformer struct {
tr MatrixTransform Tr MatrixTransform
Next path.LineBuilder Flattener Flattener
} }
func NewVertexMatrixTransform(tr MatrixTransform, converter path.LineBuilder) *VertexMatrixTransform { func (t Transformer) MoveTo(x, y float64) {
return &VertexMatrixTransform{tr, converter} u := x*t.Tr[0] + y*t.Tr[2] + t.Tr[4]
v := x*t.Tr[1] + y*t.Tr[3] + t.Tr[5]
t.Flattener.MoveTo(u, v)
} }
func (vmt *VertexMatrixTransform) MoveTo(x, y float64) { func (t Transformer) LineTo(x, y float64) {
u := x*vmt.tr[0] + y*vmt.tr[2] + vmt.tr[4] u := x*t.Tr[0] + y*t.Tr[2] + t.Tr[4]
v := x*vmt.tr[1] + y*vmt.tr[3] + vmt.tr[5] v := x*t.Tr[1] + y*t.Tr[3] + t.Tr[5]
vmt.Next.MoveTo(u, v) t.Flattener.LineTo(u, v)
} }
func (vmt *VertexMatrixTransform) LineTo(x, y float64) { func (t Transformer) LineJoin() {
u := x*vmt.tr[0] + y*vmt.tr[2] + vmt.tr[4] t.Flattener.LineJoin()
v := x*vmt.tr[1] + y*vmt.tr[3] + vmt.tr[5]
vmt.Next.LineTo(u, v)
} }
func (vmt *VertexMatrixTransform) LineJoin() { func (t Transformer) Close() {
vmt.Next.LineJoin() t.Flattener.Close()
} }
func (vmt *VertexMatrixTransform) Close() { func (t Transformer) End() {
vmt.Next.Close() t.Flattener.End()
}
func (vmt *VertexMatrixTransform) End() {
vmt.Next.End()
} }
// this adder apply a Matrix transformation to points // this adder apply a Matrix transformation to points
@ -292,10 +294,6 @@ type MatrixTransformAdder struct {
next raster.Adder next raster.Adder
} }
func NewMatrixTransformAdder(tr MatrixTransform, adder raster.Adder) *MatrixTransformAdder {
return &MatrixTransformAdder{tr, adder}
}
// Start starts a new curve at the given point. // Start starts a new curve at the given point.
func (mta MatrixTransformAdder) Start(a raster.Point) { func (mta MatrixTransformAdder) Start(a raster.Point) {
mta.tr.TransformRasterPoint(&a) mta.tr.TransformRasterPoint(&a)