Merge pull request #72 from llgcode/Clean-Up

Clean Up
This commit is contained in:
Laurent Le Goff 2015-08-14 22:48:08 +02:00
commit 93c5712ecc
73 changed files with 1760 additions and 3112 deletions

View file

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>draw2d</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>com.googlecode.goclipse.goBuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>goclipse.goNature</nature>
</natures>
</projectDescription>

70
arc.go
View file

@ -1,70 +0,0 @@
// Copyright 2010 The draw2d Authors. All rights reserved.
// created: 21/11/2010 by Laurent Le Goff
package draw2d
import (
"math"
"code.google.com/p/freetype-go/freetype/raster"
)
func arc(t VertexConverter, 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.Vertex(curX, curY)
}
}
func arcAdder(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{
X: raster.Fix32(curX * 256),
Y: raster.Fix32(curY * 256)}
}
curX = x + math.Cos(angle)*rx
curY = y + math.Sin(angle)*ry
angle += da
adder.Add1(raster.Point{
X: raster.Fix32(curX * 256),
Y: raster.Fix32(curY * 256)})
}
}

View file

@ -1,36 +0,0 @@
// Copyright 2010 The draw2d Authors. All rights reserved.
// created: 21/11/2010 by Laurent Le Goff
package curve
import (
"math"
)
func SegmentArc(t LineTracer, x, y, rx, ry, start, angle, scale 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
break
}
curX = x + math.Cos(angle)*rx
curY = y + math.Sin(angle)*ry
angle += da
t.LineTo(curX, curY)
}
t.LineTo(curX, curY)
}

View file

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

View file

@ -1,696 +0,0 @@
// Copyright 2010 The draw2d Authors. All rights reserved.
// created: 17/05/2011 by Laurent Le Goff
package curve
import (
"math"
)
const (
CurveCollinearityEpsilon = 1e-30
CurveAngleToleranceEpsilon = 0.01
)
//mu ranges from 0 to 1, start to end of curve
func (c *CubicCurveFloat64) ArbitraryPoint(mu float64) (x, y float64) {
mum1 := 1 - mu
mum13 := mum1 * mum1 * mum1
mu3 := mu * mu * mu
x = mum13*c[0] + 3*mu*mum1*mum1*c[2] + 3*mu*mu*mum1*c[4] + mu3*c[6]
y = mum13*c[1] + 3*mu*mum1*mum1*c[3] + 3*mu*mu*mum1*c[5] + mu3*c[7]
return
}
func (c *CubicCurveFloat64) SubdivideAt(c1, c2 *CubicCurveFloat64, t float64) (x23, y23 float64) {
inv_t := (1 - t)
c1[0], c1[1] = c[0], c[1]
c2[6], c2[7] = c[6], c[7]
c1[2] = inv_t*c[0] + t*c[2]
c1[3] = inv_t*c[1] + t*c[3]
x23 = inv_t*c[2] + t*c[4]
y23 = inv_t*c[3] + t*c[5]
c2[4] = inv_t*c[4] + t*c[6]
c2[5] = inv_t*c[5] + t*c[7]
c1[4] = inv_t*c1[2] + t*x23
c1[5] = inv_t*c1[3] + t*y23
c2[2] = inv_t*x23 + t*c2[4]
c2[3] = inv_t*y23 + t*c2[5]
c1[6] = inv_t*c1[4] + t*c2[2]
c1[7] = inv_t*c1[5] + t*c2[3]
c2[0], c2[1] = c1[6], c1[7]
return
}
func (c *CubicCurveFloat64) EstimateDistance() float64 {
dx1 := c[2] - c[0]
dy1 := c[3] - c[1]
dx2 := c[4] - c[2]
dy2 := c[5] - c[3]
dx3 := c[6] - c[4]
dy3 := c[7] - c[5]
return math.Sqrt(dx1*dx1+dy1*dy1) + math.Sqrt(dx2*dx2+dy2*dy2) + math.Sqrt(dx3*dx3+dy3*dy3)
}
// subdivide the curve in straight lines using line approximation and Casteljau recursive subdivision
func (c *CubicCurveFloat64) SegmentRec(t LineTracer, flattening_threshold float64) {
c.segmentRec(t, flattening_threshold)
t.LineTo(c[6], c[7])
}
func (c *CubicCurveFloat64) segmentRec(t LineTracer, flattening_threshold float64) {
var c1, c2 CubicCurveFloat64
c.Subdivide(&c1, &c2)
// Try to approximate the full cubic curve by a single straight line
//------------------
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 (d2+d3)*(d2+d3) < flattening_threshold*(dx*dx+dy*dy) {
t.LineTo(c[6], c[7])
return
}
// Continue subdivision
//----------------------
c1.segmentRec(t, flattening_threshold)
c2.segmentRec(t, flattening_threshold)
}
/*
The function has the following parameters:
approximationScale :
Eventually determines the approximation accuracy. In practice we need to transform points from the World coordinate system to the Screen one.
It always has some scaling coefficient.
The curves are usually processed in the World coordinates, while the approximation accuracy should be eventually in pixels.
Usually it looks as follows:
curved.approximationScale(transform.scale());
where transform is the affine matrix that includes all the transformations, including viewport and zoom.
angleTolerance :
You set it in radians.
The less this value is the more accurate will be the approximation at sharp turns.
But 0 means that we don't consider angle conditions at all.
cuspLimit :
An angle in radians.
If 0, only the real cusps will have bevel cuts.
If more than 0, it will restrict the sharpness.
The more this value is the less sharp turns will be cut.
Typically it should not exceed 10-15 degrees.
*/
func (c *CubicCurveFloat64) AdaptiveSegmentRec(t LineTracer, approximationScale, angleTolerance, cuspLimit float64) {
cuspLimit = computeCuspLimit(cuspLimit)
distanceToleranceSquare := 0.5 / approximationScale
distanceToleranceSquare = distanceToleranceSquare * distanceToleranceSquare
c.adaptiveSegmentRec(t, 0, distanceToleranceSquare, angleTolerance, cuspLimit)
t.LineTo(c[6], c[7])
}
func computeCuspLimit(v float64) (r float64) {
if v == 0.0 {
r = 0.0
} else {
r = math.Pi - v
}
return
}
func squareDistance(x1, y1, x2, y2 float64) float64 {
dx := x2 - x1
dy := y2 - y1
return dx*dx + dy*dy
}
/**
* http://www.antigrain.com/research/adaptive_bezier/index.html
*/
func (c *CubicCurveFloat64) adaptiveSegmentRec(t LineTracer, level int, distanceToleranceSquare, angleTolerance, cuspLimit float64) {
if level > CurveRecursionLimit {
return
}
var c1, c2 CubicCurveFloat64
x23, y23 := c.Subdivide(&c1, &c2)
// Try to approximate the full cubic curve by a single straight line
//------------------
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))
switch {
case d2 <= CurveCollinearityEpsilon && d3 <= CurveCollinearityEpsilon:
// All collinear OR p1==p4
//----------------------
k := dx*dx + dy*dy
if k == 0 {
d2 = squareDistance(c[0], c[1], c[2], c[3])
d3 = squareDistance(c[6], c[7], c[4], c[5])
} else {
k = 1 / k
da1 := c[2] - c[0]
da2 := c[3] - c[1]
d2 = k * (da1*dx + da2*dy)
da1 = c[4] - c[0]
da2 = c[5] - c[1]
d3 = k * (da1*dx + da2*dy)
if d2 > 0 && d2 < 1 && d3 > 0 && d3 < 1 {
// Simple collinear case, 1---2---3---4
// We can leave just two endpoints
return
}
if d2 <= 0 {
d2 = squareDistance(c[2], c[3], c[0], c[1])
} else if d2 >= 1 {
d2 = squareDistance(c[2], c[3], c[6], c[7])
} else {
d2 = squareDistance(c[2], c[3], c[0]+d2*dx, c[1]+d2*dy)
}
if d3 <= 0 {
d3 = squareDistance(c[4], c[5], c[0], c[1])
} else if d3 >= 1 {
d3 = squareDistance(c[4], c[5], c[6], c[7])
} else {
d3 = squareDistance(c[4], c[5], c[0]+d3*dx, c[1]+d3*dy)
}
}
if d2 > d3 {
if d2 < distanceToleranceSquare {
t.LineTo(c[2], c[3])
return
}
} else {
if d3 < distanceToleranceSquare {
t.LineTo(c[4], c[5])
return
}
}
case d2 <= CurveCollinearityEpsilon && d3 > CurveCollinearityEpsilon:
// p1,p2,p4 are collinear, p3 is significant
//----------------------
if d3*d3 <= distanceToleranceSquare*(dx*dx+dy*dy) {
if angleTolerance < CurveAngleToleranceEpsilon {
t.LineTo(x23, y23)
return
}
// Angle Condition
//----------------------
da1 := math.Abs(math.Atan2(c[7]-c[5], c[6]-c[4]) - math.Atan2(c[5]-c[3], c[4]-c[2]))
if da1 >= math.Pi {
da1 = 2*math.Pi - da1
}
if da1 < angleTolerance {
t.LineTo(c[2], c[3])
t.LineTo(c[4], c[5])
return
}
if cuspLimit != 0.0 {
if da1 > cuspLimit {
t.LineTo(c[4], c[5])
return
}
}
}
case d2 > CurveCollinearityEpsilon && d3 <= CurveCollinearityEpsilon:
// p1,p3,p4 are collinear, p2 is significant
//----------------------
if d2*d2 <= distanceToleranceSquare*(dx*dx+dy*dy) {
if angleTolerance < CurveAngleToleranceEpsilon {
t.LineTo(x23, y23)
return
}
// Angle Condition
//----------------------
da1 := math.Abs(math.Atan2(c[5]-c[3], c[4]-c[2]) - math.Atan2(c[3]-c[1], c[2]-c[0]))
if da1 >= math.Pi {
da1 = 2*math.Pi - da1
}
if da1 < angleTolerance {
t.LineTo(c[2], c[3])
t.LineTo(c[4], c[5])
return
}
if cuspLimit != 0.0 {
if da1 > cuspLimit {
t.LineTo(c[2], c[3])
return
}
}
}
case d2 > CurveCollinearityEpsilon && d3 > CurveCollinearityEpsilon:
// Regular case
//-----------------
if (d2+d3)*(d2+d3) <= distanceToleranceSquare*(dx*dx+dy*dy) {
// If the curvature doesn't exceed the distanceTolerance value
// we tend to finish subdivisions.
//----------------------
if angleTolerance < CurveAngleToleranceEpsilon {
t.LineTo(x23, y23)
return
}
// Angle & Cusp Condition
//----------------------
k := math.Atan2(c[5]-c[3], c[4]-c[2])
da1 := math.Abs(k - math.Atan2(c[3]-c[1], c[2]-c[0]))
da2 := math.Abs(math.Atan2(c[7]-c[5], c[6]-c[4]) - k)
if da1 >= math.Pi {
da1 = 2*math.Pi - da1
}
if da2 >= math.Pi {
da2 = 2*math.Pi - da2
}
if da1+da2 < angleTolerance {
// Finally we can stop the recursion
//----------------------
t.LineTo(x23, y23)
return
}
if cuspLimit != 0.0 {
if da1 > cuspLimit {
t.LineTo(c[2], c[3])
return
}
if da2 > cuspLimit {
t.LineTo(c[4], c[5])
return
}
}
}
}
// Continue subdivision
//----------------------
c1.adaptiveSegmentRec(t, level+1, distanceToleranceSquare, angleTolerance, cuspLimit)
c2.adaptiveSegmentRec(t, level+1, distanceToleranceSquare, angleTolerance, cuspLimit)
}
func (curve *CubicCurveFloat64) AdaptiveSegment(t LineTracer, approximationScale, angleTolerance, cuspLimit float64) {
cuspLimit = computeCuspLimit(cuspLimit)
distanceToleranceSquare := 0.5 / approximationScale
distanceToleranceSquare = distanceToleranceSquare * distanceToleranceSquare
var curves [CurveRecursionLimit]CubicCurveFloat64
curves[0] = *curve
i := 0
// current curve
var c *CubicCurveFloat64
var c1, c2 CubicCurveFloat64
var dx, dy, d2, d3, k, x23, y23 float64
for i >= 0 {
c = &curves[i]
x23, y23 = c.Subdivide(&c1, &c2)
// Try to approximate the full cubic curve by a single straight line
//------------------
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))
switch {
case i == len(curves)-1:
t.LineTo(c[6], c[7])
i--
continue
case d2 <= CurveCollinearityEpsilon && d3 <= CurveCollinearityEpsilon:
// All collinear OR p1==p4
//----------------------
k = dx*dx + dy*dy
if k == 0 {
d2 = squareDistance(c[0], c[1], c[2], c[3])
d3 = squareDistance(c[6], c[7], c[4], c[5])
} else {
k = 1 / k
da1 := c[2] - c[0]
da2 := c[3] - c[1]
d2 = k * (da1*dx + da2*dy)
da1 = c[4] - c[0]
da2 = c[5] - c[1]
d3 = k * (da1*dx + da2*dy)
if d2 > 0 && d2 < 1 && d3 > 0 && d3 < 1 {
// Simple collinear case, 1---2---3---4
// We can leave just two endpoints
i--
continue
}
if d2 <= 0 {
d2 = squareDistance(c[2], c[3], c[0], c[1])
} else if d2 >= 1 {
d2 = squareDistance(c[2], c[3], c[6], c[7])
} else {
d2 = squareDistance(c[2], c[3], c[0]+d2*dx, c[1]+d2*dy)
}
if d3 <= 0 {
d3 = squareDistance(c[4], c[5], c[0], c[1])
} else if d3 >= 1 {
d3 = squareDistance(c[4], c[5], c[6], c[7])
} else {
d3 = squareDistance(c[4], c[5], c[0]+d3*dx, c[1]+d3*dy)
}
}
if d2 > d3 {
if d2 < distanceToleranceSquare {
t.LineTo(c[2], c[3])
i--
continue
}
} else {
if d3 < distanceToleranceSquare {
t.LineTo(c[4], c[5])
i--
continue
}
}
case d2 <= CurveCollinearityEpsilon && d3 > CurveCollinearityEpsilon:
// p1,p2,p4 are collinear, p3 is significant
//----------------------
if d3*d3 <= distanceToleranceSquare*(dx*dx+dy*dy) {
if angleTolerance < CurveAngleToleranceEpsilon {
t.LineTo(x23, y23)
i--
continue
}
// Angle Condition
//----------------------
da1 := math.Abs(math.Atan2(c[7]-c[5], c[6]-c[4]) - math.Atan2(c[5]-c[3], c[4]-c[2]))
if da1 >= math.Pi {
da1 = 2*math.Pi - da1
}
if da1 < angleTolerance {
t.LineTo(c[2], c[3])
t.LineTo(c[4], c[5])
i--
continue
}
if cuspLimit != 0.0 {
if da1 > cuspLimit {
t.LineTo(c[4], c[5])
i--
continue
}
}
}
case d2 > CurveCollinearityEpsilon && d3 <= CurveCollinearityEpsilon:
// p1,p3,p4 are collinear, p2 is significant
//----------------------
if d2*d2 <= distanceToleranceSquare*(dx*dx+dy*dy) {
if angleTolerance < CurveAngleToleranceEpsilon {
t.LineTo(x23, y23)
i--
continue
}
// Angle Condition
//----------------------
da1 := math.Abs(math.Atan2(c[5]-c[3], c[4]-c[2]) - math.Atan2(c[3]-c[1], c[2]-c[0]))
if da1 >= math.Pi {
da1 = 2*math.Pi - da1
}
if da1 < angleTolerance {
t.LineTo(c[2], c[3])
t.LineTo(c[4], c[5])
i--
continue
}
if cuspLimit != 0.0 {
if da1 > cuspLimit {
t.LineTo(c[2], c[3])
i--
continue
}
}
}
case d2 > CurveCollinearityEpsilon && d3 > CurveCollinearityEpsilon:
// Regular case
//-----------------
if (d2+d3)*(d2+d3) <= distanceToleranceSquare*(dx*dx+dy*dy) {
// If the curvature doesn't exceed the distanceTolerance value
// we tend to finish subdivisions.
//----------------------
if angleTolerance < CurveAngleToleranceEpsilon {
t.LineTo(x23, y23)
i--
continue
}
// Angle & Cusp Condition
//----------------------
k := math.Atan2(c[5]-c[3], c[4]-c[2])
da1 := math.Abs(k - math.Atan2(c[3]-c[1], c[2]-c[0]))
da2 := math.Abs(math.Atan2(c[7]-c[5], c[6]-c[4]) - k)
if da1 >= math.Pi {
da1 = 2*math.Pi - da1
}
if da2 >= math.Pi {
da2 = 2*math.Pi - da2
}
if da1+da2 < angleTolerance {
// Finally we can stop the recursion
//----------------------
t.LineTo(x23, y23)
i--
continue
}
if cuspLimit != 0.0 {
if da1 > cuspLimit {
t.LineTo(c[2], c[3])
i--
continue
}
if da2 > cuspLimit {
t.LineTo(c[4], c[5])
i--
continue
}
}
}
}
// Continue subdivision
//----------------------
curves[i+1], curves[i] = c1, c2
i++
}
t.LineTo(curve[6], curve[7])
}
/********************** Ahmad thesis *******************/
/**************************************************************************************
* This code is the implementation of the Parabolic Approximation (PA). Although *
* it uses recursive subdivision as a safe net for the failing cases, this is an *
* iterative routine and reduces considerably the number of vertices (point) *
* generation. *
**************************************************************************************/
func (c *CubicCurveFloat64) ParabolicSegment(t LineTracer, flattening_threshold float64) {
estimatedIFP := c.numberOfInflectionPoints()
if estimatedIFP == 0 {
// If no inflection points then apply PA on the full Bezier segment.
c.doParabolicApproximation(t, flattening_threshold)
return
}
// If one or more inflection point then we will have to subdivide the curve
numOfIfP, t1, t2 := c.findInflectionPoints()
if numOfIfP == 2 {
// Case when 2 inflection points then divide at the smallest one first
var sub1, tmp1, sub2, sub3 CubicCurveFloat64
c.SubdivideAt(&sub1, &tmp1, t1)
// Now find the second inflection point in the second curve an subdivide
numOfIfP, t1, t2 = tmp1.findInflectionPoints()
if numOfIfP == 2 {
tmp1.SubdivideAt(&sub2, &sub3, t2)
} else if numOfIfP == 1 {
tmp1.SubdivideAt(&sub2, &sub3, t1)
} else {
return
}
// Use PA for first subsegment
sub1.doParabolicApproximation(t, flattening_threshold)
// Use RS for the second (middle) subsegment
sub2.Segment(t, flattening_threshold)
// Drop the last point in the array will be added by the PA in third subsegment
//noOfPoints--;
// Use PA for the third curve
sub3.doParabolicApproximation(t, flattening_threshold)
} else if numOfIfP == 1 {
// Case where there is one inflection point, subdivide once and use PA on
// both subsegments
var sub1, sub2 CubicCurveFloat64
c.SubdivideAt(&sub1, &sub2, t1)
sub1.doParabolicApproximation(t, flattening_threshold)
//noOfPoints--;
sub2.doParabolicApproximation(t, flattening_threshold)
} else {
// Case where there is no inflection USA PA directly
c.doParabolicApproximation(t, flattening_threshold)
}
}
// Find the third control point deviation form the axis
func (c *CubicCurveFloat64) thirdControlPointDeviation() float64 {
dx := c[2] - c[0]
dy := c[3] - c[1]
l2 := dx*dx + dy*dy
if l2 == 0 {
return 0
}
l := math.Sqrt(l2)
r := (c[3] - c[1]) / l
s := (c[0] - c[2]) / l
u := (c[2]*c[1] - c[0]*c[3]) / l
return math.Abs(r*c[4] + s*c[5] + u)
}
// Find the number of inflection point
func (c *CubicCurveFloat64) numberOfInflectionPoints() int {
dx21 := (c[2] - c[0])
dy21 := (c[3] - c[1])
dx32 := (c[4] - c[2])
dy32 := (c[5] - c[3])
dx43 := (c[6] - c[4])
dy43 := (c[7] - c[5])
if ((dx21*dy32 - dy21*dx32) * (dx32*dy43 - dy32*dx43)) < 0 {
return 1 // One inflection point
} else if ((dx21*dy32 - dy21*dx32) * (dx21*dy43 - dy21*dx43)) > 0 {
return 0 // No inflection point
} else {
// Most cases no inflection point
b1 := (dx21*dx32 + dy21*dy32) > 0
b2 := (dx32*dx43 + dy32*dy43) > 0
if b1 || b2 && !(b1 && b2) { // xor!!
return 0
}
}
return -1 // cases where there in zero or two inflection points
}
// This is the main function where all the work is done
func (curve *CubicCurveFloat64) doParabolicApproximation(tracer LineTracer, flattening_threshold float64) {
var c *CubicCurveFloat64
c = curve
var d, t, dx, dy, d2, d3 float64
for {
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 (d2+d3)*(d2+d3) < flattening_threshold*(dx*dx+dy*dy) {
// If the subsegment deviation satisfy the flatness then store the last
// point and stop
tracer.LineTo(c[6], c[7])
break
}
// Find the third control point deviation and the t values for subdivision
d = c.thirdControlPointDeviation()
t = 2 * math.Sqrt(flattening_threshold/d/3)
if t > 1 {
// Case where the t value calculated is invalid so using RS
c.Segment(tracer, flattening_threshold)
break
}
// Valid t value to subdivide at that calculated value
var b1, b2 CubicCurveFloat64
c.SubdivideAt(&b1, &b2, t)
// First subsegment should have its deviation equal to flatness
dx = b1[6] - b1[0]
dy = b1[7] - b1[1]
d2 = math.Abs(((b1[2]-b1[6])*dy - (b1[3]-b1[7])*dx))
d3 = math.Abs(((b1[4]-b1[6])*dy - (b1[5]-b1[7])*dx))
if (d2+d3)*(d2+d3) > flattening_threshold*(dx*dx+dy*dy) {
// if not then use RS to handle any mathematical errors
b1.Segment(tracer, flattening_threshold)
} else {
tracer.LineTo(b1[6], b1[7])
}
// repeat the process for the left over subsegment.
c = &b2
}
}
// Find the actual inflection points and return the number of inflection points found
// if 2 inflection points found, the first one returned will be with smaller t value.
func (curve *CubicCurveFloat64) findInflectionPoints() (int, firstIfp, secondIfp float64) {
// For Cubic Bezier curve with equation P=a*t^3 + b*t^2 + c*t + d
// slope of the curve dP/dt = 3*a*t^2 + 2*b*t + c
// a = (float)(-bez.p1 + 3*bez.p2 - 3*bez.p3 + bez.p4);
// b = (float)(3*bez.p1 - 6*bez.p2 + 3*bez.p3);
// c = (float)(-3*bez.p1 + 3*bez.p2);
ax := (-curve[0] + 3*curve[2] - 3*curve[4] + curve[6])
bx := (3*curve[0] - 6*curve[2] + 3*curve[4])
cx := (-3*curve[0] + 3*curve[2])
ay := (-curve[1] + 3*curve[3] - 3*curve[5] + curve[7])
by := (3*curve[1] - 6*curve[3] + 3*curve[5])
cy := (-3*curve[1] + 3*curve[3])
a := (3 * (ay*bx - ax*by))
b := (3 * (ay*cx - ax*cy))
c := (by*cx - bx*cy)
r2 := (b*b - 4*a*c)
firstIfp = 0.0
secondIfp = 0.0
if r2 >= 0.0 && a != 0.0 {
r := math.Sqrt(r2)
firstIfp = ((-b + r) / (2 * a))
secondIfp = ((-b - r) / (2 * a))
if (firstIfp > 0.0 && firstIfp < 1.0) && (secondIfp > 0.0 && secondIfp < 1.0) {
if firstIfp > secondIfp {
tmp := firstIfp
firstIfp = secondIfp
secondIfp = tmp
}
if secondIfp-firstIfp > 0.00001 {
return 2, firstIfp, secondIfp
} else {
return 1, firstIfp, secondIfp
}
} else if firstIfp > 0.0 && firstIfp < 1.0 {
return 1, firstIfp, secondIfp
} else if secondIfp > 0.0 && secondIfp < 1.0 {
firstIfp = secondIfp
return 1, firstIfp, secondIfp
}
return 0, firstIfp, secondIfp
}
return 0, firstIfp, secondIfp
}

View file

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

View file

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

336
curves.go
View file

@ -1,336 +0,0 @@
// Copyright 2010 The draw2d Authors. All rights reserved.
// created: 21/11/2010 by Laurent Le Goff
package draw2d
import (
"math"
)
var (
CurveRecursionLimit = 32
CurveCollinearityEpsilon = 1e-30
CurveAngleToleranceEpsilon = 0.01
)
/*
The function has the following parameters:
approximationScale :
Eventually determines the approximation accuracy. In practice we need to transform points from the World coordinate system to the Screen one.
It always has some scaling coefficient.
The curves are usually processed in the World coordinates, while the approximation accuracy should be eventually in pixels.
Usually it looks as follows:
curved.approximationScale(transform.scale());
where transform is the affine matrix that includes all the transformations, including viewport and zoom.
angleTolerance :
You set it in radians.
The less this value is the more accurate will be the approximation at sharp turns.
But 0 means that we don't consider angle conditions at all.
cuspLimit :
An angle in radians.
If 0, only the real cusps will have bevel cuts.
If more than 0, it will restrict the sharpness.
The more this value is the less sharp turns will be cut.
Typically it should not exceed 10-15 degrees.
*/
func cubicBezier(v VertexConverter, x1, y1, x2, y2, x3, y3, x4, y4, approximationScale, angleTolerance, cuspLimit float64) {
cuspLimit = computeCuspLimit(cuspLimit)
distanceToleranceSquare := 0.5 / approximationScale
distanceToleranceSquare = distanceToleranceSquare * distanceToleranceSquare
recursiveCubicBezier(v, x1, y1, x2, y2, x3, y3, x4, y4, 0, distanceToleranceSquare, angleTolerance, cuspLimit)
}
/*
* see cubicBezier comments for approximationScale and angleTolerance definition
*/
func quadraticBezier(v VertexConverter, x1, y1, x2, y2, x3, y3, approximationScale, angleTolerance float64) {
distanceToleranceSquare := 0.5 / approximationScale
distanceToleranceSquare = distanceToleranceSquare * distanceToleranceSquare
recursiveQuadraticBezierBezier(v, x1, y1, x2, y2, x3, y3, 0, distanceToleranceSquare, angleTolerance)
}
func computeCuspLimit(v float64) (r float64) {
if v == 0.0 {
r = 0.0
} else {
r = math.Pi - v
}
return
}
/**
* http://www.antigrain.com/research/adaptive_bezier/index.html
*/
func recursiveQuadraticBezierBezier(v VertexConverter, x1, y1, x2, y2, x3, y3 float64, level int, distanceToleranceSquare, angleTolerance float64) {
if level > CurveRecursionLimit {
return
}
// Calculate all the mid-points of the line segments
//----------------------
x12 := (x1 + x2) / 2
y12 := (y1 + y2) / 2
x23 := (x2 + x3) / 2
y23 := (y2 + y3) / 2
x123 := (x12 + x23) / 2
y123 := (y12 + y23) / 2
dx := x3 - x1
dy := y3 - y1
d := math.Abs(((x2-x3)*dy - (y2-y3)*dx))
if d > CurveCollinearityEpsilon {
// Regular case
//-----------------
if d*d <= distanceToleranceSquare*(dx*dx+dy*dy) {
// If the curvature doesn't exceed the distanceTolerance value
// we tend to finish subdivisions.
//----------------------
if angleTolerance < CurveAngleToleranceEpsilon {
v.Vertex(x123, y123)
return
}
// Angle & Cusp Condition
//----------------------
da := math.Abs(math.Atan2(y3-y2, x3-x2) - math.Atan2(y2-y1, x2-x1))
if da >= math.Pi {
da = 2*math.Pi - da
}
if da < angleTolerance {
// Finally we can stop the recursion
//----------------------
v.Vertex(x123, y123)
return
}
}
} else {
// Collinear case
//------------------
da := dx*dx + dy*dy
if da == 0 {
d = squareDistance(x1, y1, x2, y2)
} else {
d = ((x2-x1)*dx + (y2-y1)*dy) / da
if d > 0 && d < 1 {
// Simple collinear case, 1---2---3
// We can leave just two endpoints
return
}
if d <= 0 {
d = squareDistance(x2, y2, x1, y1)
} else if d >= 1 {
d = squareDistance(x2, y2, x3, y3)
} else {
d = squareDistance(x2, y2, x1+d*dx, y1+d*dy)
}
}
if d < distanceToleranceSquare {
v.Vertex(x2, y2)
return
}
}
// Continue subdivision
//----------------------
recursiveQuadraticBezierBezier(v, x1, y1, x12, y12, x123, y123, level+1, distanceToleranceSquare, angleTolerance)
recursiveQuadraticBezierBezier(v, x123, y123, x23, y23, x3, y3, level+1, distanceToleranceSquare, angleTolerance)
}
/**
* http://www.antigrain.com/research/adaptive_bezier/index.html
*/
func recursiveCubicBezier(v VertexConverter, x1, y1, x2, y2, x3, y3, x4, y4 float64, level int, distanceToleranceSquare, angleTolerance, cuspLimit float64) {
if level > CurveRecursionLimit {
return
}
// Calculate all the mid-points of the line segments
//----------------------
x12 := (x1 + x2) / 2
y12 := (y1 + y2) / 2
x23 := (x2 + x3) / 2
y23 := (y2 + y3) / 2
x34 := (x3 + x4) / 2
y34 := (y3 + y4) / 2
x123 := (x12 + x23) / 2
y123 := (y12 + y23) / 2
x234 := (x23 + x34) / 2
y234 := (y23 + y34) / 2
x1234 := (x123 + x234) / 2
y1234 := (y123 + y234) / 2
// Try to approximate the full cubic curve by a single straight line
//------------------
dx := x4 - x1
dy := y4 - y1
d2 := math.Abs(((x2-x4)*dy - (y2-y4)*dx))
d3 := math.Abs(((x3-x4)*dy - (y3-y4)*dx))
switch {
case d2 <= CurveCollinearityEpsilon && d3 <= CurveCollinearityEpsilon:
// All collinear OR p1==p4
//----------------------
k := dx*dx + dy*dy
if k == 0 {
d2 = squareDistance(x1, y1, x2, y2)
d3 = squareDistance(x4, y4, x3, y3)
} else {
k = 1 / k
da1 := x2 - x1
da2 := y2 - y1
d2 = k * (da1*dx + da2*dy)
da1 = x3 - x1
da2 = y3 - y1
d3 = k * (da1*dx + da2*dy)
if d2 > 0 && d2 < 1 && d3 > 0 && d3 < 1 {
// Simple collinear case, 1---2---3---4
// We can leave just two endpoints
return
}
if d2 <= 0 {
d2 = squareDistance(x2, y2, x1, y1)
} else if d2 >= 1 {
d2 = squareDistance(x2, y2, x4, y4)
} else {
d2 = squareDistance(x2, y2, x1+d2*dx, y1+d2*dy)
}
if d3 <= 0 {
d3 = squareDistance(x3, y3, x1, y1)
} else if d3 >= 1 {
d3 = squareDistance(x3, y3, x4, y4)
} else {
d3 = squareDistance(x3, y3, x1+d3*dx, y1+d3*dy)
}
}
if d2 > d3 {
if d2 < distanceToleranceSquare {
v.Vertex(x2, y2)
return
}
} else {
if d3 < distanceToleranceSquare {
v.Vertex(x3, y3)
return
}
}
break
case d2 <= CurveCollinearityEpsilon && d3 > CurveCollinearityEpsilon:
// p1,p2,p4 are collinear, p3 is significant
//----------------------
if d3*d3 <= distanceToleranceSquare*(dx*dx+dy*dy) {
if angleTolerance < CurveAngleToleranceEpsilon {
v.Vertex(x23, y23)
return
}
// Angle Condition
//----------------------
da1 := math.Abs(math.Atan2(y4-y3, x4-x3) - math.Atan2(y3-y2, x3-x2))
if da1 >= math.Pi {
da1 = 2*math.Pi - da1
}
if da1 < angleTolerance {
v.Vertex(x2, y2)
v.Vertex(x3, y3)
return
}
if cuspLimit != 0.0 {
if da1 > cuspLimit {
v.Vertex(x3, y3)
return
}
}
}
break
case d2 > CurveCollinearityEpsilon && d3 <= CurveCollinearityEpsilon:
// p1,p3,p4 are collinear, p2 is significant
//----------------------
if d2*d2 <= distanceToleranceSquare*(dx*dx+dy*dy) {
if angleTolerance < CurveAngleToleranceEpsilon {
v.Vertex(x23, y23)
return
}
// Angle Condition
//----------------------
da1 := math.Abs(math.Atan2(y3-y2, x3-x2) - math.Atan2(y2-y1, x2-x1))
if da1 >= math.Pi {
da1 = 2*math.Pi - da1
}
if da1 < angleTolerance {
v.Vertex(x2, y2)
v.Vertex(x3, y3)
return
}
if cuspLimit != 0.0 {
if da1 > cuspLimit {
v.Vertex(x2, y2)
return
}
}
}
break
case d2 > CurveCollinearityEpsilon && d3 > CurveCollinearityEpsilon:
// Regular case
//-----------------
if (d2+d3)*(d2+d3) <= distanceToleranceSquare*(dx*dx+dy*dy) {
// If the curvature doesn't exceed the distanceTolerance value
// we tend to finish subdivisions.
//----------------------
if angleTolerance < CurveAngleToleranceEpsilon {
v.Vertex(x23, y23)
return
}
// Angle & Cusp Condition
//----------------------
k := math.Atan2(y3-y2, x3-x2)
da1 := math.Abs(k - math.Atan2(y2-y1, x2-x1))
da2 := math.Abs(math.Atan2(y4-y3, x4-x3) - k)
if da1 >= math.Pi {
da1 = 2*math.Pi - da1
}
if da2 >= math.Pi {
da2 = 2*math.Pi - da2
}
if da1+da2 < angleTolerance {
// Finally we can stop the recursion
//----------------------
v.Vertex(x23, y23)
return
}
if cuspLimit != 0.0 {
if da1 > cuspLimit {
v.Vertex(x2, y2)
return
}
if da2 > cuspLimit {
v.Vertex(x3, y3)
return
}
}
}
break
}
// Continue subdivision
//----------------------
recursiveCubicBezier(v, x1, y1, x12, y12, x123, y123, x1234, y1234, level+1, distanceToleranceSquare, angleTolerance, cuspLimit)
recursiveCubicBezier(v, x1234, y1234, x234, y234, x34, y34, x4, y4, level+1, distanceToleranceSquare, angleTolerance, cuspLimit)
}

View file

@ -1,23 +0,0 @@
// Copyright 2010 The draw2d Authors. All rights reserved.
// created: 13/12/2010 by Laurent Le Goff
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 float64) {
for _, converter := range dc.converters {
converter.Vertex(x, y)
}
}

159
draw2d.go
View file

@ -83,3 +83,162 @@
// //
// - https://github.com/vdobler/chart: basic charts in Go // - https://github.com/vdobler/chart: basic charts in Go
package draw2d package draw2d
import (
"image"
"image/color"
)
// FillRule defines the type for fill rules
type FillRule int
const (
// FillRuleEvenOdd determines the "insideness" of a point in the shape
// by drawing a ray from that point to infinity in any direction
// and counting the number of path segments from the given shape that the ray crosses.
// If this number is odd, the point is inside; if even, the point is outside.
FillRuleEvenOdd FillRule = iota
// FillRuleWinding determines the "insideness" of a point in the shape
// by drawing a ray from that point to infinity in any direction
// and then examining the places where a segment of the shape crosses the ray.
// Starting with a count of zero, add one each time a path segment crosses
// the ray from left to right and subtract one each time
// a path segment crosses the ray from right to left. After counting the crossings,
// if the result is zero then the point is outside the path. Otherwise, it is inside.
FillRuleWinding
)
// LineCap is the style of line extremities
type LineCap int
const (
// RoundCap defines a rounded shape at the end of the line
RoundCap LineCap = iota
// ButtCap defines a squared shape exactly at the end of the line
ButtCap
// SquareCap defines a squared shape at the end of the line
SquareCap
)
// LineJoin is the style of segments joint
type LineJoin int
const (
// BevelJoin represents cut segments joint
BevelJoin LineJoin = iota
// RoundJoin represents rounded segments joint
RoundJoin
// MiterJoin represents peaker segments joint
MiterJoin
)
// StrokeStyle keeps stroke style attributes
// that is used by the Stroke method of a Drawer
type StrokeStyle struct {
// Color defines the color of stroke
Color color.Color
// Line width
Width float64
// Line cap style rounded, butt or square
LineCap LineCap
// Line join style bevel, round or miter
LineJoin LineJoin
// offset of the first dash
DashOffset float64
// array represented dash length pair values are plain dash and impair are space between dash
// if empty display plain line
Dash []float64
}
// FillStyle is an empty interface you may want to use SolidFillStyle
type FillStyle interface {
}
// SolidFillStyle define style attributes for a solid fill style
type SolidFillStyle struct {
// Color defines the line color
Color color.Color
// FillRule defines the file rule to used
FillRule FillRule
}
// Valign Vertical Alignment of the text
type Valign int
const (
// ValignTop top align text
ValignTop Valign = iota
// ValignCenter centered text
ValignCenter
// ValignBottom bottom aligned text
ValignBottom
// ValignBaseline align text with the baseline of the font
ValignBaseline
)
// Halign Horizontal Alignment of the text
type Halign int
const (
// HalignLeft Horizontally align to left
HalignLeft = iota
// HalignCenter Horizontally align to center
HalignCenter
// HalignRight Horizontally align to right
HalignRight
)
// TextStyle describe text property
type TextStyle struct {
// Color defines the color of text
Color color.Color
// Size font size
Size float64
// The font to use
Font FontData
// Horizontal Alignment of the text
Halign Halign
// Vertical Alignment of the text
Valign Valign
}
// ScalingPolicy is a constant to define how to scale an image
type ScalingPolicy int
const (
// ScalingNone no scaling applied
ScalingNone ScalingPolicy = iota
// ScalingStretch the image is stretched so that its width and height are exactly the given width and height
ScalingStretch
// ScalingWidth the image is scaled so that its width is exactly the given width
ScalingWidth
// ScalingHeight the image is scaled so that its height is exactly the given height
ScalingHeight
// ScalingFit the image is scaled to the largest scale that allow the image to fit within a rectangle width x height
ScalingFit
// ScalingSameArea the image is scaled so that its area is exactly the area of the given rectangle width x height
ScalingSameArea
// ScalingFill the image is scaled to the smallest scale that allow the image to fully cover a rectangle width x height
ScalingFill
)
// ImageScaling style attributes used to display the image
type ImageScaling struct {
// Horizontal Alignment of the image
Halign Halign
// Vertical Alignment of the image
Valign Valign
// Width Height used by scaling policy
Width, Height float64
// ScalingPolicy defines the scaling policy to applied to the image
ScalingPolicy ScalingPolicy
}
// Drawer can fill and stroke a path
type Drawer interface {
Matrix() *Matrix
Fill(path *Path, style FillStyle)
Stroke(path *Path, style StrokeStyle)
Text(text string, x, y float64, style TextStyle)
Image(image image.Image, x, y float64, scaling ImageScaling)
}

161
draw2dbase/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 draw2dbase
import (
"math"
)
const (
// CurveRecursionLimit represents the maximum recursion that is really necessary to subsivide a curve into straight lines
CurveRecursionLimit = 32
)
// Cubic
// x1, y1, cpx1, cpy1, cpx2, cpy2, x2, y2 float64
// SubdivideCubic 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 Liner
// flattening_threshold helps determines the flattening expectation of the curve
func TraceCubic(t Liner, cubic []float64, flatteningThreshold 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) < flatteningThreshold*(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
// SubdivideQuad 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
}
// TraceQuad generate lines subdividing the curve using a Liner
// flattening_threshold helps determines the flattening expectation of the curve
func TraceQuad(t Liner, quad []float64, flatteningThreshold 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) < flatteningThreshold*(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 Liner
func TraceArc(t Liner, 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)
}
}

136
draw2dbase/curve_test.go Normal file
View file

@ -0,0 +1,136 @@
package draw2dbase
import (
"bufio"
"fmt"
"image"
"image/color"
"image/draw"
"image/png"
"log"
"os"
"testing"
"github.com/llgcode/draw2d/raster"
)
var (
flatteningThreshold = 0.5
testsCubicFloat64 = []float64{
100, 100, 200, 100, 100, 200, 200, 200,
100, 100, 300, 200, 200, 200, 300, 100,
100, 100, 0, 300, 200, 0, 300, 300,
150, 290, 10, 10, 290, 10, 150, 290,
10, 290, 10, 10, 290, 10, 290, 290,
100, 290, 290, 10, 10, 10, 200, 290,
}
testsQuadFloat64 = []float64{
100, 100, 200, 100, 200, 200,
100, 100, 290, 200, 290, 100,
100, 100, 0, 290, 200, 290,
150, 290, 10, 10, 290, 290,
10, 290, 10, 10, 290, 290,
100, 290, 290, 10, 120, 290,
}
)
func init() {
os.Mkdir("test_results", 0666)
f, err := os.Create("../output/curve/_test.html")
if err != nil {
log.Println(err)
os.Exit(1)
}
defer f.Close()
log.Printf("Create html viewer")
f.Write([]byte("<html><body>"))
for i := 0; i < len(testsCubicFloat64)/8; i++ {
f.Write([]byte(fmt.Sprintf("<div><img src='_test%d.png'/></div>\n", i)))
}
for i := 0; i < len(testsQuadFloat64); i++ {
f.Write([]byte(fmt.Sprintf("<div><img src='_testQuad%d.png'/>\n</div>\n", i)))
}
f.Write([]byte("</body></html>"))
}
func drawPoints(img draw.Image, c color.Color, s ...float64) image.Image {
for i := 0; i < len(s); i += 2 {
x, y := int(s[i]+0.5), int(s[i+1]+0.5)
img.Set(x, y, c)
img.Set(x, y+1, c)
img.Set(x, y-1, c)
img.Set(x+1, y, c)
img.Set(x+1, y+1, c)
img.Set(x+1, y-1, c)
img.Set(x-1, y, c)
img.Set(x-1, y+1, c)
img.Set(x-1, y-1, c)
}
return img
}
func TestCubicCurve(t *testing.T) {
for i := 0; i < len(testsCubicFloat64); i += 8 {
var p SegmentedPath
p.MoveTo(testsCubicFloat64[i], testsCubicFloat64[i+1])
TraceCubic(&p, testsCubicFloat64[i:], flatteningThreshold)
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, image.Black, p.Points...)
//drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, curve[:]...)
drawPoints(img, color.NRGBA{0, 0, 0, 0xff}, p.Points...)
SaveToPngFile(fmt.Sprintf("../output/curve/_test%d.png", i/8), img)
log.Printf("Num of points: %d\n", len(p.Points))
}
fmt.Println()
}
func TestQuadCurve(t *testing.T) {
for i := 0; i < len(testsQuadFloat64); i += 6 {
var p SegmentedPath
p.MoveTo(testsQuadFloat64[i], testsQuadFloat64[i+1])
TraceQuad(&p, testsQuadFloat64[i:], flatteningThreshold)
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, image.Black, p.Points...)
//drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, curve[:]...)
drawPoints(img, color.NRGBA{0, 0, 0, 0xff}, p.Points...)
SaveToPngFile(fmt.Sprintf("../output/curve/_testQuad%d.png", i), img)
log.Printf("Num of points: %d\n", len(p.Points))
}
fmt.Println()
}
func BenchmarkCubicCurve(b *testing.B) {
for i := 0; i < b.N; i++ {
for i := 0; i < len(testsCubicFloat64); i += 8 {
var p SegmentedPath
p.MoveTo(testsCubicFloat64[i], testsCubicFloat64[i+1])
TraceCubic(&p, testsCubicFloat64[i:], flatteningThreshold)
}
}
}
// SaveToPngFile create and save an image to a file using PNG format
func SaveToPngFile(filePath string, m image.Image) error {
// Create the file
f, err := os.Create(filePath)
if err != nil {
return err
}
defer f.Close()
// Create Writer from file
b := bufio.NewWriter(f)
// Write the image into the buffer
err = png.Encode(b, m)
if err != nil {
return err
}
err = b.Flush()
if err != nil {
return err
}
return nil
}

View file

@ -1,51 +1,48 @@
// 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 draw2d package draw2dbase
type DashVertexConverter struct { type DashVertexConverter struct {
command VertexCommand next Flattener
next VertexConverter
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 VertexConverter) *DashVertexConverter { func NewDashConverter(dash []float64, dashOffset float64, flattener Flattener) *DashVertexConverter {
var dasher DashVertexConverter var dasher DashVertexConverter
dasher.dash = dash dasher.dash = dash
dasher.currentDash = 0 dasher.currentDash = 0
dasher.dashOffset = dashOffset dasher.dashOffset = dashOffset
dasher.next = converter dasher.next = flattener
return &dasher return &dasher
} }
func (dasher *DashVertexConverter) NextCommand(cmd VertexCommand) { func (dasher *DashVertexConverter) LineTo(x, y float64) {
dasher.command = cmd dasher.lineTo(x, y)
if dasher.command == VertexStopCommand {
dasher.next.NextCommand(VertexStopCommand)
}
} }
func (dasher *DashVertexConverter) Vertex(x, y float64) { func (dasher *DashVertexConverter) MoveTo(x, y float64) {
switch dasher.command { dasher.next.MoveTo(x, y)
case VertexStartCommand:
dasher.start(x, y)
default:
dasher.lineTo(x, y)
}
dasher.command = VertexNoCommand
}
func (dasher *DashVertexConverter) start(x, y float64) {
dasher.next.NextCommand(VertexStartCommand)
dasher.next.Vertex(x, y)
dasher.x, dasher.y = x, y dasher.x, dasher.y = x, y
dasher.distance = dasher.dashOffset dasher.distance = dasher.dashOffset
dasher.currentDash = 0 dasher.currentDash = 0
} }
func (dasher *DashVertexConverter) LineJoin() {
dasher.next.LineJoin()
}
func (dasher *DashVertexConverter) Close() {
dasher.next.Close()
}
func (dasher *DashVertexConverter) End() {
dasher.next.End()
}
func (dasher *DashVertexConverter) lineTo(x, y float64) { func (dasher *DashVertexConverter) lineTo(x, y float64) {
rest := dasher.dash[dasher.currentDash] - dasher.distance rest := dasher.dash[dasher.currentDash] - dasher.distance
for rest < 0 { for rest < 0 {
@ -60,12 +57,11 @@ func (dasher *DashVertexConverter) lineTo(x, y float64) {
ly := dasher.y + k*(y-dasher.y) ly := dasher.y + k*(y-dasher.y)
if dasher.currentDash%2 == 0 { if dasher.currentDash%2 == 0 {
// line // line
dasher.next.Vertex(lx, ly) dasher.next.LineTo(lx, ly)
} else { } else {
// gap // gap
dasher.next.NextCommand(VertexStopCommand) dasher.next.End()
dasher.next.NextCommand(VertexStartCommand) dasher.next.MoveTo(lx, ly)
dasher.next.Vertex(lx, ly)
} }
d = d - rest d = d - rest
dasher.x, dasher.y = lx, ly dasher.x, dasher.y = lx, ly
@ -75,12 +71,11 @@ func (dasher *DashVertexConverter) lineTo(x, y float64) {
dasher.distance = d dasher.distance = d
if dasher.currentDash%2 == 0 { if dasher.currentDash%2 == 0 {
// line // line
dasher.next.Vertex(x, y) dasher.next.LineTo(x, y)
} else { } else {
// gap // gap
dasher.next.NextCommand(VertexStopCommand) dasher.next.End()
dasher.next.NextCommand(VertexStartCommand) dasher.next.MoveTo(x, y)
dasher.next.Vertex(x, y)
} }
if dasher.distance >= dasher.dash[dasher.currentDash] { if dasher.distance >= dasher.dash[dasher.currentDash] {
dasher.distance = dasher.distance - dasher.dash[dasher.currentDash] dasher.distance = dasher.distance - dasher.dash[dasher.currentDash]
@ -88,3 +83,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,35 @@
package draw2dbase
type DemuxFlattener struct {
Flatteners []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()
}
}

127
draw2dbase/flattener.go Normal file
View file

@ -0,0 +1,127 @@
// Copyright 2010 The draw2d Authors. All rights reserved.
// created: 06/12/2010 by Laurent Le Goff
package draw2dbase
import (
"github.com/llgcode/draw2d"
)
// Liner receive segment definition
type Liner interface {
// LineTo Draw a line from the current position to the point (x, y)
LineTo(x, y float64)
}
// 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()
}
// Flatten convert curves into straight segments keeping join segments info
func Flatten(path *draw2d.Path, flattener Flattener, scale float64) {
// First Point
var startX, startY float64 = 0, 0
// Current Point
var x, y float64 = 0, 0
i := 0
for _, cmp := range path.Components {
switch cmp {
case draw2d.MoveToCmp:
x, y = path.Points[i], path.Points[i+1]
startX, startY = x, y
if i != 0 {
flattener.End()
}
flattener.MoveTo(x, y)
i += 2
case draw2d.LineToCmp:
x, y = path.Points[i], path.Points[i+1]
flattener.LineTo(x, y)
flattener.LineJoin()
i += 2
case draw2d.QuadCurveToCmp:
TraceQuad(flattener, path.Points[i-2:], 0.5)
x, y = path.Points[i+2], path.Points[i+3]
flattener.LineTo(x, y)
i += 4
case draw2d.CubicCurveToCmp:
TraceCubic(flattener, path.Points[i-2:], 0.5)
x, y = path.Points[i+4], path.Points[i+5]
flattener.LineTo(x, y)
i += 6
case draw2d.ArcToCmp:
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)
flattener.LineTo(x, y)
i += 6
case draw2d.CloseCmp:
flattener.LineTo(startX, startY)
flattener.Close()
}
}
flattener.End()
}
// Transformer apply the Matrix transformation tr
type Transformer struct {
Tr draw2d.Matrix
Flattener Flattener
}
func (t Transformer) MoveTo(x, y float64) {
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 (t Transformer) LineTo(x, y float64) {
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.LineTo(u, v)
}
func (t Transformer) LineJoin() {
t.Flattener.LineJoin()
}
func (t Transformer) Close() {
t.Flattener.Close()
}
func (t Transformer) End() {
t.Flattener.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
}

View file

@ -1,39 +1,43 @@
// 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 (
"image" "image"
"image/color" "image/color"
"github.com/llgcode/draw2d"
"code.google.com/p/freetype-go/freetype/truetype" "code.google.com/p/freetype-go/freetype/truetype"
) )
var DefaultFontData = draw2d.FontData{Name: "luxi", Family: draw2d.FontFamilySans, Style: draw2d.FontStyleNormal}
type StackGraphicContext struct { type StackGraphicContext struct {
Current *ContextStack Current *ContextStack
} }
type ContextStack struct { type ContextStack struct {
Tr MatrixTransform Tr draw2d.Matrix
Path *PathStorage 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 Cap Cap draw2d.LineCap
Join 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 float64 Scale float64
previous *ContextStack Previous *ContextStack
} }
/** /**
@ -42,41 +46,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 = NewPathStorage() 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 = RoundCap gc.Current.Cap = draw2d.RoundCap
gc.Current.FillRule = FillRuleEvenOdd gc.Current.FillRule = draw2d.FillRuleEvenOdd
gc.Current.Join = 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.Matrix {
return gc.Current.Tr return gc.Current.Tr
} }
func (gc *StackGraphicContext) SetMatrixTransform(Tr MatrixTransform) { func (gc *StackGraphicContext) SetMatrixTransform(Tr draw2d.Matrix) {
gc.Current.Tr = Tr gc.Current.Tr = Tr
} }
func (gc *StackGraphicContext) ComposeMatrixTransform(Tr MatrixTransform) { func (gc *StackGraphicContext) ComposeMatrixTransform(Tr draw2d.Matrix) {
gc.Current.Tr = Tr.Multiply(gc.Current.Tr) gc.Current.Tr.Compose(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.Rotate(angle)
} }
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.Translate(tx, ty)
} }
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.Scale(sx, sy)
} }
func (gc *StackGraphicContext) SetStrokeColor(c color.Color) { func (gc *StackGraphicContext) SetStrokeColor(c color.Color) {
@ -87,45 +91,45 @@ 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 Cap) { func (gc *StackGraphicContext) SetLineCap(cap draw2d.LineCap) {
gc.Current.Cap = Cap gc.Current.Cap = cap
} }
func (gc *StackGraphicContext) SetLineJoin(Join 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
} }
func (gc *StackGraphicContext) BeginPath() { func (gc *StackGraphicContext) BeginPath() {
gc.Current.Path = NewPathStorage() gc.Current.Path.Clear()
} }
func (gc *StackGraphicContext) IsEmpty() bool { func (gc *StackGraphicContext) IsEmpty() bool {
@ -140,42 +144,22 @@ func (gc *StackGraphicContext) MoveTo(x, y float64) {
gc.Current.Path.MoveTo(x, y) gc.Current.Path.MoveTo(x, y)
} }
func (gc *StackGraphicContext) RMoveTo(dx, dy float64) {
gc.Current.Path.RMoveTo(dx, dy)
}
func (gc *StackGraphicContext) LineTo(x, y float64) { func (gc *StackGraphicContext) LineTo(x, y float64) {
gc.Current.Path.LineTo(x, y) gc.Current.Path.LineTo(x, y)
} }
func (gc *StackGraphicContext) RLineTo(dx, dy float64) {
gc.Current.Path.RLineTo(dx, dy)
}
func (gc *StackGraphicContext) QuadCurveTo(cx, cy, x, y float64) { func (gc *StackGraphicContext) QuadCurveTo(cx, cy, x, y float64) {
gc.Current.Path.QuadCurveTo(cx, cy, x, y) gc.Current.Path.QuadCurveTo(cx, cy, x, y)
} }
func (gc *StackGraphicContext) RQuadCurveTo(dcx, dcy, dx, dy float64) {
gc.Current.Path.RQuadCurveTo(dcx, dcy, dx, dy)
}
func (gc *StackGraphicContext) CubicCurveTo(cx1, cy1, cx2, cy2, x, y float64) { func (gc *StackGraphicContext) CubicCurveTo(cx1, cy1, cx2, cy2, x, y float64) {
gc.Current.Path.CubicCurveTo(cx1, cy1, cx2, cy2, x, y) gc.Current.Path.CubicCurveTo(cx1, cy1, cx2, cy2, x, y)
} }
func (gc *StackGraphicContext) RCubicCurveTo(dcx1, dcy1, dcx2, dcy2, dx, dy float64) {
gc.Current.Path.RCubicCurveTo(dcx1, dcy1, dcx2, dcy2, dx, dy)
}
func (gc *StackGraphicContext) ArcTo(cx, cy, rx, ry, startAngle, angle float64) { func (gc *StackGraphicContext) ArcTo(cx, cy, rx, ry, startAngle, angle float64) {
gc.Current.Path.ArcTo(cx, cy, rx, ry, startAngle, angle) gc.Current.Path.ArcTo(cx, cy, rx, ry, startAngle, angle)
} }
func (gc *StackGraphicContext) RArcTo(dcx, dcy, rx, ry, startAngle, angle float64) {
gc.Current.Path.RArcTo(dcx, dcy, rx, ry, startAngle, angle)
}
func (gc *StackGraphicContext) Close() { func (gc *StackGraphicContext) Close() {
gc.Current.Path.Close() gc.Current.Path.Close()
} }
@ -196,14 +180,14 @@ func (gc *StackGraphicContext) Save() {
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
} }
} }

90
draw2dbase/stroker.go Normal file
View file

@ -0,0 +1,90 @@
// Copyright 2010 The draw2d Authors. All rights reserved.
// created: 13/12/2010 by Laurent Le Goff
package draw2dbase
import (
"math"
"github.com/llgcode/draw2d"
)
type LineStroker struct {
Flattener Flattener
HalfLineWidth float64
Cap draw2d.LineCap
Join draw2d.LineJoin
vertices []float64
rewind []float64
x, y, nx, ny float64
}
func NewLineStroker(c draw2d.LineCap, j draw2d.LineJoin, flattener Flattener) *LineStroker {
l := new(LineStroker)
l.Flattener = flattener
l.HalfLineWidth = 0.5
l.Cap = c
l.Join = j
return l
}
func (l *LineStroker) MoveTo(x, y float64) {
l.x, l.y = x, y
}
func (l *LineStroker) LineTo(x, y float64) {
l.line(l.x, l.y, x, y)
}
func (l *LineStroker) LineJoin() {
}
func (l *LineStroker) line(x1, y1, x2, y2 float64) {
dx := (x2 - x1)
dy := (y2 - y1)
d := vectorDistance(dx, dy)
if d != 0 {
nx := dy * l.HalfLineWidth / d
ny := -(dx * l.HalfLineWidth / d)
l.appendVertex(x1+nx, y1+ny, x2+nx, y2+ny, x1-nx, y1-ny, x2-nx, y2-ny)
l.x, l.y, l.nx, l.ny = x2, y2, nx, ny
}
}
func (l *LineStroker) Close() {
if len(l.vertices) > 1 {
l.appendVertex(l.vertices[0], l.vertices[1], l.rewind[0], l.rewind[1])
}
}
func (l *LineStroker) End() {
if len(l.vertices) > 1 {
l.Flattener.MoveTo(l.vertices[0], l.vertices[1])
for i, j := 2, 3; j < len(l.vertices); i, j = i+2, j+2 {
l.Flattener.LineTo(l.vertices[i], l.vertices[j])
}
}
for i, j := len(l.rewind)-2, len(l.rewind)-1; j > 0; i, j = i-2, j-2 {
l.Flattener.LineTo(l.rewind[i], l.rewind[j])
}
if len(l.vertices) > 1 {
l.Flattener.LineTo(l.vertices[0], l.vertices[1])
}
l.Flattener.End()
// reinit vertices
l.vertices = l.vertices[0:0]
l.rewind = l.rewind[0:0]
l.x, l.y, l.nx, l.ny = 0, 0, 0, 0
}
func (l *LineStroker) appendVertex(vertices ...float64) {
s := len(vertices) / 2
l.vertices = append(l.vertices, 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,6 +9,8 @@ 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/draw2dbase"
"github.com/llgcode/draw2d/draw2dimg"
) )
func init() { func init() {
@ -112,7 +114,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
@ -121,7 +123,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),
@ -180,56 +182,76 @@ func (gc *GraphicContext) paint(rasterizer *raster.Rasterizer, color color.Color
rasterizer.Rasterize(gc.painter) rasterizer.Rasterize(gc.painter)
rasterizer.Clear() rasterizer.Clear()
gc.painter.Flush() gc.painter.Flush()
gc.Current.Path.Clear()
} }
func (gc *GraphicContext) Stroke(paths ...*draw2d.PathStorage) { 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 := draw2d.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2d.NewVertexMatrixTransform(gc.Current.Tr, draw2d.NewVertexAdder(gc.strokeRasterizer))) stroker := draw2dbase.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2dbase.Transformer{Tr: gc.Current.Tr, Flattener: draw2dimg.FtLineBuilder{Adder: gc.strokeRasterizer}})
stroker.HalfLineWidth = gc.Current.LineWidth / 2 stroker.HalfLineWidth = gc.Current.LineWidth / 2
var pathConverter *draw2d.PathConverter
var liner draw2dbase.Flattener
if gc.Current.Dash != nil && len(gc.Current.Dash) > 0 { if gc.Current.Dash != nil && len(gc.Current.Dash) > 0 {
dasher := draw2d.NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker) liner = draw2dbase.NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker)
pathConverter = draw2d.NewPathConverter(dasher)
} else { } else {
pathConverter = draw2d.NewPathConverter(stroker) liner = stroker
}
for _, p := range paths {
draw2dbase.Flatten(p, liner, gc.Current.Tr.GetScale())
} }
pathConverter.ApproximationScale = gc.Current.Tr.GetScale() // From agg code
pathConverter.Convert(paths...)
gc.paint(gc.strokeRasterizer, gc.Current.StrokeColor) gc.paint(gc.strokeRasterizer, gc.Current.StrokeColor)
gc.Current.Path = draw2d.NewPathStorage()
} }
func (gc *GraphicContext) Fill(paths ...*draw2d.PathStorage) { 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)
pathConverter := draw2d.NewPathConverter(draw2d.NewVertexMatrixTransform(gc.Current.Tr, draw2d.NewVertexAdder(gc.fillRasterizer))) /**** first method ****/
pathConverter.ApproximationScale = gc.Current.Tr.GetScale() // From agg code flattener := draw2dbase.Transformer{Tr: gc.Current.Tr, Flattener: draw2dimg.FtLineBuilder{Adder: gc.fillRasterizer}}
pathConverter.Convert(paths...) for _, p := range paths {
draw2dbase.Flatten(p, flattener, gc.Current.Tr.GetScale())
}
gc.paint(gc.fillRasterizer, gc.Current.FillColor) gc.paint(gc.fillRasterizer, gc.Current.FillColor)
gc.Current.Path = draw2d.NewPathStorage()
} }
func (gc *GraphicContext) FillStroke(paths ...*draw2d.PathStorage) { func (gc *GraphicContext) FillStroke(paths ...*draw2d.Path) {
gc.fillRasterizer.UseNonZeroWinding = gc.Current.FillRule.UseNonZeroWinding() paths = append(paths, gc.Current.Path)
gc.fillRasterizer.UseNonZeroWinding = useNonZeroWinding(gc.Current.FillRule)
gc.strokeRasterizer.UseNonZeroWinding = true gc.strokeRasterizer.UseNonZeroWinding = true
filler := draw2d.NewVertexMatrixTransform(gc.Current.Tr, draw2d.NewVertexAdder(gc.fillRasterizer)) flattener := draw2dbase.Transformer{Tr: gc.Current.Tr, Flattener: draw2dimg.FtLineBuilder{Adder: gc.fillRasterizer}}
stroker := draw2d.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2d.NewVertexMatrixTransform(gc.Current.Tr, draw2d.NewVertexAdder(gc.strokeRasterizer))) stroker := draw2dbase.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2dbase.Transformer{Tr: gc.Current.Tr, Flattener: draw2dimg.FtLineBuilder{Adder: gc.strokeRasterizer}})
stroker.HalfLineWidth = gc.Current.LineWidth / 2 stroker.HalfLineWidth = gc.Current.LineWidth / 2
demux := draw2d.NewDemuxConverter(filler, stroker) var liner draw2dbase.Flattener
paths = append(paths, gc.Current.Path) if gc.Current.Dash != nil && len(gc.Current.Dash) > 0 {
pathConverter := draw2d.NewPathConverter(demux) liner = draw2dbase.NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker)
pathConverter.ApproximationScale = gc.Current.Tr.GetScale() // From agg code } else {
pathConverter.Convert(paths...) liner = stroker
}
demux := draw2dbase.DemuxFlattener{Flatteners: []draw2dbase.Flattener{flattener, liner}}
for _, p := range paths {
draw2dbase.Flatten(p, demux, gc.Current.Tr.GetScale())
}
// Fill
gc.paint(gc.fillRasterizer, gc.Current.FillColor) gc.paint(gc.fillRasterizer, gc.Current.FillColor)
// Stroke
gc.paint(gc.strokeRasterizer, gc.Current.StrokeColor) gc.paint(gc.strokeRasterizer, gc.Current.StrokeColor)
gc.Current.Path = draw2d.NewPathStorage() }
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,7 +1,7 @@
// 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"
@ -11,21 +11,22 @@ import (
"log" "log"
"math" "math"
"github.com/llgcode/draw2d"
"github.com/llgcode/draw2d/draw2dbase"
"code.google.com/p/freetype-go/freetype/raster" "code.google.com/p/freetype-go/freetype/raster"
"code.google.com/p/freetype-go/freetype/truetype" "code.google.com/p/freetype-go/freetype/truetype"
) )
// Painter implements the freetype raster.Painter and has a SetColor method like the RGBAPainter
type Painter interface { type Painter interface {
raster.Painter raster.Painter
SetColor(color color.Color) SetColor(color color.Color)
} }
var ( // GraphicContext is the implementation of draw2d.GraphicContext for a raster image
defaultFontData = FontData{"luxi", FontFamilySans, FontStyleNormal} type GraphicContext struct {
) *draw2dbase.StackGraphicContext
type ImageGraphicContext struct {
*StackGraphicContext
img draw.Image img draw.Image
painter Painter painter Painter
fillRasterizer *raster.Rasterizer fillRasterizer *raster.Rasterizer
@ -35,7 +36,8 @@ type ImageGraphicContext struct {
} }
// NewGraphicContext creates a new Graphic context from an image. // NewGraphicContext creates 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:
@ -47,11 +49,11 @@ func NewGraphicContext(img draw.Image) *ImageGraphicContext {
} }
// NewGraphicContextWithPainter creates a new Graphic context from an image and a Painter (see Freetype-go) // NewGraphicContextWithPainter creates 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),
@ -62,48 +64,56 @@ func NewGraphicContextWithPainter(img draw.Image, painter Painter) *ImageGraphic
return gc return gc
} }
func (gc *ImageGraphicContext) GetDPI() int { // GetDPI returns the resolution of the Image GraphicContext
func (gc *GraphicContext) GetDPI() int {
return gc.DPI return gc.DPI
} }
func (gc *ImageGraphicContext) Clear() { // Clear fills the current canvas with a default transparent color
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) { // ClearRect fills the current canvas with a default transparent color at the specified rectangle
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) { // DrawImage draws the raster image in the current canvas
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) { // FillString draws the text at point (0, 0)
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) { // FillStringAt draws the text at the specified point (x, y)
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) { // StrokeString draws the contour of the text at point (0, 0)
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) { // StrokeStringAt draws the contour of the text at point (x, y)
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.")
@ -113,61 +123,17 @@ func (gc *ImageGraphicContext) loadCurrentFont() (*truetype.Font, error) {
return font, nil return font, nil
} }
func fUnitsToFloat64(x int32) float64 {
scaled := x << 2
return float64(scaled/256) + float64(scaled%256)/256.0
}
// p is a truetype.Point measured in FUnits and positive Y going upwards. // p is a truetype.Point measured in FUnits and positive Y going upwards.
// The returned value is the same thing measured in floating point and positive Y // The returned value is the same thing measured in floating point and positive Y
// going downwards. // going downwards.
func pointToF64Point(p truetype.Point) (x, y float64) {
return fUnitsToFloat64(p.X), -fUnitsToFloat64(p.Y)
}
// drawContour draws the given closed contour at the given sub-pixel offset. func (gc *GraphicContext) drawGlyph(glyph truetype.Index, dx, dy float64) error {
func (gc *ImageGraphicContext) drawContour(ps []truetype.Point, dx, dy float64) {
if len(ps) == 0 {
return
}
startX, startY := pointToF64Point(ps[0])
gc.MoveTo(startX+dx, startY+dy)
q0X, q0Y, on0 := startX, startY, true
for _, p := range ps[1:] {
qX, qY := pointToF64Point(p)
on := p.Flags&0x01 != 0
if on {
if on0 {
gc.LineTo(qX+dx, qY+dy)
} else {
gc.QuadCurveTo(q0X+dx, q0Y+dy, qX+dx, qY+dy)
}
} else {
if on0 {
// No-op.
} else {
midX := (q0X + qX) / 2
midY := (q0Y + qY) / 2
gc.QuadCurveTo(q0X+dx, q0Y+dy, midX+dx, midY+dy)
}
}
q0X, q0Y, on0 = qX, qY, on
}
// Close the curve.
if on0 {
gc.LineTo(startX+dx, startY+dy)
} else {
gc.QuadCurveTo(q0X+dx, q0Y+dy, startX+dx, startY+dy)
}
}
func (gc *ImageGraphicContext) drawGlyph(glyph truetype.Index, dx, dy float64) error {
if err := gc.glyphBuf.Load(gc.Current.Font, int32(gc.Current.Scale), glyph, truetype.NoHinting); err != nil { if err := gc.glyphBuf.Load(gc.Current.Font, int32(gc.Current.Scale), glyph, truetype.NoHinting); err != nil {
return err return err
} }
e0 := 0 e0 := 0
for _, e1 := range gc.glyphBuf.End { for _, e1 := range gc.glyphBuf.End {
gc.drawContour(gc.glyphBuf.Point[e0:e1], dx, dy) DrawContour(gc, gc.glyphBuf.Point[e0:e1], dx, dy)
e0 = e1 e0 = e1
} }
return nil return nil
@ -179,7 +145,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)
@ -204,10 +170,10 @@ func (gc *ImageGraphicContext) CreateStringPath(s string, x, y float64) float64
} }
// GetStringBounds returns the approximate pixel bounds of the string s at x, y. // GetStringBounds returns the approximate pixel bounds of the string s at x, y.
// 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)
@ -219,6 +185,7 @@ 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(int32(gc.Current.Scale), prev, index)) cursor += fUnitsToFloat64(font.Kerning(int32(gc.Current.Scale), prev, index))
} }
if err := gc.glyphBuf.Load(gc.Current.Font, int32(gc.Current.Scale), index, truetype.NoHinting); err != nil { if err := gc.glyphBuf.Load(gc.Current.Font, int32(gc.Current.Scale), index, truetype.NoHinting); err != nil {
@ -244,114 +211,115 @@ 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 = gc.Current.FontSize * float64(gc.DPI) * (64.0 / 72.0) gc.Current.Scale = 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 = NewPathStorage() gc.Current.Path.Clear()
} }
// Stroke strokes the paths with the color specified by SetStrokeColor // Stroke strokes the paths with the color specified by SetStrokeColor
func (gc *ImageGraphicContext) Stroke(paths ...*PathStorage) { 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 := NewLineStroker(gc.Current.Cap, gc.Current.Join, NewVertexMatrixTransform(gc.Current.Tr, NewVertexAdder(gc.strokeRasterizer))) stroker := draw2dbase.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2dbase.Transformer{Tr: gc.Current.Tr, Flattener: FtLineBuilder{Adder: gc.strokeRasterizer}})
stroker.HalfLineWidth = gc.Current.LineWidth / 2 stroker.HalfLineWidth = gc.Current.LineWidth / 2
var pathConverter *PathConverter
var liner draw2dbase.Flattener
if gc.Current.Dash != nil && len(gc.Current.Dash) > 0 { if gc.Current.Dash != nil && len(gc.Current.Dash) > 0 {
dasher := NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker) liner = draw2dbase.NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker)
pathConverter = NewPathConverter(dasher)
} else { } else {
pathConverter = NewPathConverter(stroker) liner = stroker
}
for _, p := range paths {
draw2dbase.Flatten(p, liner, gc.Current.Tr.GetScale())
} }
pathConverter.ApproximationScale = gc.Current.Tr.GetScale()
pathConverter.Convert(paths...)
gc.paint(gc.strokeRasterizer, gc.Current.StrokeColor) gc.paint(gc.strokeRasterizer, gc.Current.StrokeColor)
} }
// Fill fills the paths with the color specified by SetFillColor // Fill fills the paths with the color specified by SetFillColor
func (gc *ImageGraphicContext) Fill(paths ...*PathStorage) { 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 = gc.Current.FillRule == draw2d.FillRuleWinding
/**** first method ****/ /**** first method ****/
pathConverter := NewPathConverter(NewVertexMatrixTransform(gc.Current.Tr, NewVertexAdder(gc.fillRasterizer))) flattener := draw2dbase.Transformer{Tr: gc.Current.Tr, Flattener: FtLineBuilder{Adder: gc.fillRasterizer}}
pathConverter.ApproximationScale = gc.Current.Tr.GetScale() for _, p := range paths {
pathConverter.Convert(paths...) draw2dbase.Flatten(p, flattener, gc.Current.Tr.GetScale())
}
gc.paint(gc.fillRasterizer, gc.Current.FillColor) gc.paint(gc.fillRasterizer, gc.Current.FillColor)
} }
// FillStroke first fills the paths and than strokes them // FillStroke first fills the paths and than strokes them
func (gc *ImageGraphicContext) FillStroke(paths ...*PathStorage) { func (gc *GraphicContext) FillStroke(paths ...*draw2d.Path) {
gc.fillRasterizer.UseNonZeroWinding = gc.Current.FillRule.UseNonZeroWinding() paths = append(paths, gc.Current.Path)
gc.fillRasterizer.UseNonZeroWinding = gc.Current.FillRule == draw2d.FillRuleWinding
gc.strokeRasterizer.UseNonZeroWinding = true gc.strokeRasterizer.UseNonZeroWinding = true
filler := NewVertexMatrixTransform(gc.Current.Tr, NewVertexAdder(gc.fillRasterizer)) flattener := draw2dbase.Transformer{Tr: gc.Current.Tr, Flattener: FtLineBuilder{Adder: gc.fillRasterizer}}
stroker := NewLineStroker(gc.Current.Cap, gc.Current.Join, NewVertexMatrixTransform(gc.Current.Tr, NewVertexAdder(gc.strokeRasterizer))) stroker := draw2dbase.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2dbase.Transformer{Tr: gc.Current.Tr, Flattener: FtLineBuilder{Adder: gc.strokeRasterizer}})
stroker.HalfLineWidth = gc.Current.LineWidth / 2 stroker.HalfLineWidth = gc.Current.LineWidth / 2
demux := NewDemuxConverter(filler, stroker) var liner draw2dbase.Flattener
paths = append(paths, gc.Current.Path) if gc.Current.Dash != nil && len(gc.Current.Dash) > 0 {
pathConverter := NewPathConverter(demux) liner = draw2dbase.NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker)
pathConverter.ApproximationScale = gc.Current.Tr.GetScale() } else {
pathConverter.Convert(paths...) liner = stroker
}
demux := draw2dbase.DemuxFlattener{Flatteners: []draw2dbase.Flattener{flattener, liner}}
for _, p := range paths {
draw2dbase.Flatten(p, demux, gc.Current.Tr.GetScale())
}
// Fill
gc.paint(gc.fillRasterizer, gc.Current.FillColor) gc.paint(gc.fillRasterizer, gc.Current.FillColor)
// Stroke
gc.paint(gc.strokeRasterizer, gc.Current.StrokeColor) gc.paint(gc.strokeRasterizer, gc.Current.StrokeColor)
} }
func (f FillRule) UseNonZeroWinding() bool { func toFtCap(c draw2d.LineCap) raster.Capper {
switch f {
case FillRuleEvenOdd:
return false
case FillRuleWinding:
return true
}
return false
}
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
} }
func (j Join) Convert() raster.Joiner { func toFtJoin(j draw2d.LineJoin) 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

29
draw2dimg/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 draw2dimg
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{X: raster.Fix32(x * 256), Y: raster.Fix32(y * 256)})
}
func (liner FtLineBuilder) LineTo(x, y float64) {
liner.Adder.Add1(raster.Point{X: raster.Fix32(x * 256), Y: raster.Fix32(y * 256)})
}
func (liner FtLineBuilder) LineJoin() {
}
func (liner FtLineBuilder) Close() {
}
func (liner FtLineBuilder) End() {
}

View file

@ -2,25 +2,29 @@
// 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 (
"image" "image"
"image/color" "image/color"
"image/draw" "image/draw"
"math" "math"
"github.com/llgcode/draw2d"
) )
// ImageFilter defines sampling filter (linear, bilinear or bicubic) // ImageFilter defines the type of filter to use
type ImageFilter int type ImageFilter int
const ( const (
// LinearFilter uses linear interpolation // LinearFilter defines a linear filter
LinearFilter ImageFilter = iota LinearFilter ImageFilter = iota
// BilinearFilter uses bilinear interpolation // BilinearFilter defines a bilinear filter
BilinearFilter BilinearFilter
// BicubicFilter uses bicubic interpolation // BicubicFilter defines a bicubic filter
BicubicFilter BicubicFilter
// M is the maximum value for a rgb component
M = 1<<16 - 1
) )
//see http://pippin.gimp.org/image_processing/chap_resampling.html //see http://pippin.gimp.org/image_processing/chap_resampling.html
@ -50,14 +54,7 @@ func getColorBilinear(img image.Image, x, y float64) color.Color {
return color.RGBA{uint8(r >> 8), uint8(g >> 8), uint8(b >> 8), uint8(a >> 8)} return color.RGBA{uint8(r >> 8), uint8(g >> 8), uint8(b >> 8), uint8(a >> 8)}
} }
/** // lerp is a linear interpolation bertween 2 points
-- LERP
-- /lerp/, vi.,n.
--
-- Quasi-acronym for Linear Interpolation, used as a verb or noun for
-- the operation. "Bresenham's algorithm lerps incrementally between the
-- two endpoints of the line." (From Jargon File (4.4.4, 14 Aug 2003)
*/
func lerp(v1, v2, ratio float64) float64 { func lerp(v1, v2, ratio float64) float64 {
return v1*(1-ratio) + v2*ratio return v1*(1-ratio) + v2*ratio
} }
@ -107,11 +104,10 @@ 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)
} }
// DrawImage draws a source image on an destination image. // DrawImage draws an image into dest using an affine transformation matrix, an op and a filter
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.Matrix, 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 := tr.TransformRectangle(float64(bounds.Min.X), float64(bounds.Min.Y), float64(bounds.Max.X), float64(bounds.Max.Y))
tr.TransformRectangle(&x0, &y0, &x1, &y1)
var x, y, u, v float64 var x, y, u, v float64
var c1, c2, cr color.Color var c1, c2, cr color.Color
var r, g, b, a, ia, r1, g1, b1, a1, r2, g2, b2, a2 uint32 var r, g, b, a, ia, r1, g1, b1, a1, r2, g2, b2, a2 uint32
@ -120,7 +116,7 @@ func DrawImage(src image.Image, dest draw.Image, tr MatrixTransform, op draw.Op,
for y = y0; y < y1; y++ { for y = y0; y < y1; y++ {
u = x u = x
v = y v = y
tr.InverseTransform(&u, &v) u, v = tr.InverseTransformPoint(u, v)
if bounds.Min.X <= int(u) && bounds.Max.X > int(u) && bounds.Min.Y <= int(v) && bounds.Max.Y > int(v) { if bounds.Min.X <= int(u) && bounds.Max.X > int(u) && bounds.Min.Y <= int(v) && bounds.Max.Y > int(v) {
c1 = dest.At(int(x), int(y)) c1 = dest.At(int(x), int(y))
switch filter { switch filter {

80
draw2dimg/text.go Normal file
View file

@ -0,0 +1,80 @@
package draw2dimg
import (
"code.google.com/p/freetype-go/freetype/truetype"
"github.com/llgcode/draw2d"
)
// DrawContour draws the given closed contour at the given sub-pixel offset.
func DrawContour(path draw2d.PathBuilder, ps []truetype.Point, dx, dy float64) {
if len(ps) == 0 {
return
}
startX, startY := pointToF64Point(ps[0])
path.MoveTo(startX+dx, startY+dy)
q0X, q0Y, on0 := startX, startY, true
for _, p := range ps[1:] {
qX, qY := pointToF64Point(p)
on := p.Flags&0x01 != 0
if on {
if on0 {
path.LineTo(qX+dx, qY+dy)
} else {
path.QuadCurveTo(q0X+dx, q0Y+dy, qX+dx, qY+dy)
}
} else {
if on0 {
// No-op.
} else {
midX := (q0X + qX) / 2
midY := (q0Y + qY) / 2
path.QuadCurveTo(q0X+dx, q0Y+dy, midX+dx, midY+dy)
}
}
q0X, q0Y, on0 = qX, qY, on
}
// Close the curve.
if on0 {
path.LineTo(startX+dx, startY+dy)
} else {
path.QuadCurveTo(q0X+dx, q0Y+dy, startX+dx, startY+dy)
}
}
func pointToF64Point(p truetype.Point) (x, y float64) {
return fUnitsToFloat64(p.X), -fUnitsToFloat64(p.Y)
}
func fUnitsToFloat64(x int32) float64 {
scaled := x << 2
return float64(scaled/256) + float64(scaled%256)/256.0
}
// FontExtents contains font metric information.
type FontExtents struct {
// Ascent is the distance that the text
// extends above the baseline.
Ascent float64
// Descent is the distance that the text
// extends below the baseline. The descent
// is given as a negative value.
Descent float64
// Height is the distance from the lowest
// descending point to the highest ascending
// point.
Height float64
}
// Extents returns the FontExtents for a font.
// TODO needs to read this https://developer.apple.com/fonts/TrueType-Reference-Manual/RM02/Chap2.html#intro
func Extents(font *truetype.Font, size float64) FontExtents {
bounds := font.Bounds(font.FUnitsPerEm())
scale := size / float64(font.FUnitsPerEm())
return FontExtents{
Ascent: float64(bounds.YMax) * scale,
Descent: float64(bounds.YMin) * scale,
Height: float64(bounds.YMax-bounds.YMin) * scale,
}
}

View file

@ -1,16 +1,17 @@
// 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
//high level path creation // Package draw2dkit provides helpers to draw common figures using a Path
package draw2dkit
package draw2d
import ( import (
"math" "math"
"github.com/llgcode/draw2d"
) )
// Rect draws a rectangle between (x1,y1) and (x2,y2) // Rectangle draws a rectangle using a path between (x1,y1) and (x2,y2)
func Rect(path Path, x1, y1, x2, y2 float64) { func Rectangle(path draw2d.PathBuilder, x1, y1, x2, y2 float64) {
path.MoveTo(x1, y1) path.MoveTo(x1, y1)
path.LineTo(x2, y1) path.LineTo(x2, y1)
path.LineTo(x2, y2) path.LineTo(x2, y2)
@ -18,8 +19,8 @@ func Rect(path Path, x1, y1, x2, y2 float64) {
path.Close() path.Close()
} }
// RoundRect draws a rectangle between (x1,y1) and (x2,y2) with rounded corners // RoundedRectangle draws a rectangle using a path between (x1,y1) and (x2,y2)
func RoundRect(path Path, x1, y1, x2, y2, arcWidth, arcHeight float64) { func RoundedRectangle(path draw2d.PathBuilder, x1, y1, x2, y2, arcWidth, arcHeight float64) {
arcWidth = arcWidth / 2 arcWidth = arcWidth / 2
arcHeight = arcHeight / 2 arcHeight = arcHeight / 2
path.MoveTo(x1, y1+arcHeight) path.MoveTo(x1, y1+arcHeight)
@ -33,16 +34,14 @@ func RoundRect(path Path, x1, y1, x2, y2, arcWidth, arcHeight float64) {
path.Close() path.Close()
} }
// Ellipse is drawn with center (cx,cy) and radius (rx,ry) // Ellipse draws an ellipse using a path with center (cx,cy) and radius (rx,ry)
func Ellipse(path Path, cx, cy, rx, ry float64) { func Ellipse(path draw2d.PathBuilder, cx, cy, rx, ry float64) {
path.MoveTo(cx-rx, cy)
path.ArcTo(cx, cy, rx, ry, 0, -math.Pi*2) path.ArcTo(cx, cy, rx, ry, 0, -math.Pi*2)
path.Close() path.Close()
} }
// Circle is drawn with center (cx,cy) and radius // Circle draws a circle using a path with center (cx,cy) and radius
func Circle(path Path, cx, cy, radius float64) { func Circle(path draw2d.PathBuilder, cx, cy, radius float64) {
path.MoveTo(cx-radius, cy)
path.ArcTo(cx, cy, radius, radius, 0, -math.Pi*2) path.ArcTo(cx, cy, radius, radius, 0, -math.Pi*2)
path.Close() path.Close()
} }

74
draw2dkit/droid.go Normal file
View file

@ -0,0 +1,74 @@
package draw2dkit
import (
"math"
"github.com/llgcode/draw2d"
)
// Droid draws a droid at specified position
func Droid(drawer draw2d.Drawer, x, y float64, fillStyle draw2d.FillStyle, strokeStyle draw2d.StrokeStyle) {
strokeStyle.LineCap = draw2d.RoundCap
strokeStyle.Width = 5
path := &draw2d.Path{}
// head
path.ArcTo(x+80, y+70, 50, 50, 180*(math.Pi/180), 360*(math.Pi/180))
drawer.Fill(path, fillStyle)
drawer.Stroke(path, strokeStyle)
path.Clear()
path.MoveTo(x+60, y+25)
path.LineTo(x+50, y+10)
path.MoveTo(x+100, y+25)
path.LineTo(x+110, y+10)
drawer.Stroke(path, strokeStyle)
// left eye
path.Clear()
Circle(path, x+60, y+45, 5)
drawer.Fill(path, fillStyle)
drawer.Stroke(path, strokeStyle)
// right eye
path.Clear()
Circle(path, x+100, y+45, 5)
drawer.Fill(path, fillStyle)
drawer.Stroke(path, strokeStyle)
// body
path.Clear()
RoundedRectangle(path, x+30, y+75, x+30+100, y+75+90, 10, 10)
drawer.Fill(path, fillStyle)
drawer.Stroke(path, strokeStyle)
path.Clear()
Rectangle(path, x+30, y+75, x+30+100, y+75+80)
drawer.Fill(path, fillStyle)
drawer.Stroke(path, strokeStyle)
// left arm
path.Clear()
RoundedRectangle(path, x+5, y+80, x+5+20, y+80+70, 10, 10)
drawer.Fill(path, fillStyle)
drawer.Stroke(path, strokeStyle)
// right arm
path.Clear()
RoundedRectangle(path, x+135, y+80, x+135+20, y+80+70, 10, 10)
drawer.Fill(path, fillStyle)
drawer.Stroke(path, strokeStyle)
// left leg
path.Clear()
RoundedRectangle(path, x+50, y+150, x+50+20, y+150+50, 10, 10)
drawer.Fill(path, fillStyle)
drawer.Stroke(path, strokeStyle)
// right leg
path.Clear()
RoundedRectangle(path, x+90, y+150, x+90+20, y+150+50, 10, 10)
drawer.Fill(path, fillStyle)
drawer.Stroke(path, strokeStyle)
}

56
draw2dkit/gopher.go Normal file
View file

@ -0,0 +1,56 @@
// Copyright 2010 The draw2d Authors. All rights reserved.
// created: 21/11/2010 by Laurent Le Goff
package draw2dkit
import (
"image/color"
"github.com/llgcode/draw2d"
)
// Gopher draw a gopher using a GraphicContext thanks to https://github.com/golang-samples/gopher-vector/
func Gopher(gc draw2d.GraphicContext, x, y, w, h float64) {
// Initialize Stroke Attribute
gc.SetLineWidth(3)
gc.SetLineCap(draw2d.RoundCap)
gc.SetStrokeColor(color.Black)
// Left hand
// <path fill-rule="evenodd" clip-rule="evenodd" fill="#F6D2A2" stroke="#000000" stroke-width="3" stroke-linecap="round" d="
// M10.634,300.493c0.764,15.751,16.499,8.463,23.626,3.539c6.765-4.675,8.743-0.789,9.337-10.015
// c0.389-6.064,1.088-12.128,0.744-18.216c-10.23-0.927-21.357,1.509-29.744,7.602C10.277,286.542,2.177,296.561,10.634,300.493"/>
gc.SetFillColor(color.RGBA{0xF6, 0xD2, 0xA2, 0xff})
gc.MoveTo(10.634, 300.493)
rCubicCurveTo(gc, 0.764, 15.751, 16.499, 8.463, 23.626, 3.539)
rCubicCurveTo(gc, 6.765, -4.675, 8.743, -0.789, 9.337, -10.015)
rCubicCurveTo(gc, 0.389, -6.064, 1.088, -12.128, 0.744, -18.216)
rCubicCurveTo(gc, -10.23, -0.927, -21.357, 1.509, -29.744, 7.602)
gc.CubicCurveTo(10.277, 286.542, 2.177, 296.561, 10.634, 300.493)
gc.FillStroke()
// <path fill-rule="evenodd" clip-rule="evenodd" fill="#C6B198" stroke="#000000" stroke-width="3" stroke-linecap="round" d="
// M10.634,300.493c2.29-0.852,4.717-1.457,6.271-3.528"/>
gc.MoveTo(10.634, 300.493)
rCubicCurveTo(gc, 2.29, -0.852, 4.717, -1.457, 6.271, -3.528)
gc.Stroke()
// Left Ear
// <path fill-rule="evenodd" clip-rule="evenodd" fill="#6AD7E5" stroke="#000000" stroke-width="3" stroke-linecap="round" d="
// M46.997,112.853C-13.3,95.897,31.536,19.189,79.956,50.74L46.997,112.853z"/>
gc.MoveTo(46.997, 112.853)
gc.CubicCurveTo(-13.3, 95.897, 31.536, 19.189, 79.956, 50.74)
gc.LineTo(46.997, 112.853)
gc.Close()
gc.Stroke()
}
func rQuadCurveTo(path draw2d.PathBuilder, dcx, dcy, dx, dy float64) {
x, y := path.LastPoint()
path.QuadCurveTo(x+dcx, y+dcy, x+dx, y+dy)
}
func rCubicCurveTo(path draw2d.PathBuilder, dcx1, dcy1, dcx2, dcy2, dx, dy float64) {
x, y := path.LastPoint()
path.CubicCurveTo(x+dcx1, y+dcy1, x+dcx2, y+dcy2, x+dx, y+dy)
}

View file

@ -18,6 +18,8 @@ import (
"github.com/jung-kurt/gofpdf" "github.com/jung-kurt/gofpdf"
"github.com/llgcode/draw2d" "github.com/llgcode/draw2d"
"github.com/llgcode/draw2d/draw2dbase"
"github.com/llgcode/draw2d/draw2dkit"
) )
const ( const (
@ -27,11 +29,11 @@ const (
) )
var ( var (
caps = map[draw2d.Cap]string{ caps = map[draw2d.LineCap]string{
draw2d.RoundCap: "round", draw2d.RoundCap: "round",
draw2d.ButtCap: "butt", draw2d.ButtCap: "butt",
draw2d.SquareCap: "square"} draw2d.SquareCap: "square"}
joins = map[draw2d.Join]string{ joins = map[draw2d.LineJoin]string{
draw2d.RoundJoin: "round", draw2d.RoundJoin: "round",
draw2d.BevelJoin: "bevel", draw2d.BevelJoin: "bevel",
draw2d.MiterJoin: "miter", draw2d.MiterJoin: "miter",
@ -68,7 +70,7 @@ func clearRect(gc *GraphicContext, x1, y1, x2, y2 float64) {
x, y := gc.pdf.GetXY() x, y := gc.pdf.GetXY()
// cover page with white rectangle // cover page with white rectangle
gc.SetFillColor(white) gc.SetFillColor(white)
draw2d.Rect(gc, x1, y1, x2, y2) draw2dkit.Rectangle(gc, x1, y1, x2, y2)
gc.Fill() gc.Fill()
// restore state // restore state
gc.SetFillColor(f) gc.SetFillColor(f)
@ -78,14 +80,14 @@ func clearRect(gc *GraphicContext, x1, y1, x2, y2 float64) {
// GraphicContext implements the draw2d.GraphicContext interface // GraphicContext implements the draw2d.GraphicContext interface
// It provides draw2d with a pdf backend (based on gofpdf) // It provides draw2d with a pdf backend (based on gofpdf)
type GraphicContext struct { type GraphicContext struct {
*draw2d.StackGraphicContext *draw2dbase.StackGraphicContext
pdf *gofpdf.Fpdf pdf *gofpdf.Fpdf
DPI int DPI int
} }
// NewGraphicContext creates a new pdf GraphicContext // NewGraphicContext creates a new pdf GraphicContext
func NewGraphicContext(pdf *gofpdf.Fpdf) *GraphicContext { func NewGraphicContext(pdf *gofpdf.Fpdf) *GraphicContext {
gc := &GraphicContext{draw2d.NewStackGraphicContext(), pdf, DPI} gc := &GraphicContext{draw2dbase.NewStackGraphicContext(), pdf, DPI}
gc.SetDPI(DPI) gc.SetDPI(DPI)
return gc return gc
} }
@ -189,27 +191,27 @@ func (gc *GraphicContext) StrokeStringAt(text string, x, y float64) (cursor floa
} }
// Stroke strokes the paths with the color specified by SetStrokeColor // Stroke strokes the paths with the color specified by SetStrokeColor
func (gc *GraphicContext) Stroke(paths ...*draw2d.PathStorage) { func (gc *GraphicContext) Stroke(paths ...*draw2d.Path) {
_, _, _, alphaS := gc.Current.StrokeColor.RGBA() _, _, _, alphaS := gc.Current.StrokeColor.RGBA()
gc.draw("D", alphaS, paths...) gc.draw("D", alphaS, paths...)
gc.Current.Path = draw2d.NewPathStorage() gc.Current.Path.Clear()
} }
// Fill fills the paths with the color specified by SetFillColor // Fill fills the paths with the color specified by SetFillColor
func (gc *GraphicContext) Fill(paths ...*draw2d.PathStorage) { func (gc *GraphicContext) Fill(paths ...*draw2d.Path) {
style := "F" style := "F"
if !gc.Current.FillRule.UseNonZeroWinding() { if gc.Current.FillRule != draw2d.FillRuleWinding {
style += "*" style += "*"
} }
_, _, _, alphaF := gc.Current.FillColor.RGBA() _, _, _, alphaF := gc.Current.FillColor.RGBA()
gc.draw(style, alphaF, paths...) gc.draw(style, alphaF, paths...)
gc.Current.Path = draw2d.NewPathStorage() gc.Current.Path.Clear()
} }
// FillStroke first fills the paths and than strokes them // FillStroke first fills the paths and than strokes them
func (gc *GraphicContext) FillStroke(paths ...*draw2d.PathStorage) { func (gc *GraphicContext) FillStroke(paths ...*draw2d.Path) {
var rule string var rule string
if !gc.Current.FillRule.UseNonZeroWinding() { if gc.Current.FillRule != draw2d.FillRuleWinding {
rule = "*" rule = "*"
} }
_, _, _, alphaS := gc.Current.StrokeColor.RGBA() _, _, _, alphaS := gc.Current.StrokeColor.RGBA()
@ -220,7 +222,7 @@ func (gc *GraphicContext) FillStroke(paths ...*draw2d.PathStorage) {
gc.draw("F"+rule, alphaF, paths...) gc.draw("F"+rule, alphaF, paths...)
gc.draw("S", alphaS, paths...) gc.draw("S", alphaS, paths...)
} }
gc.Current.Path = draw2d.NewPathStorage() gc.Current.Path.Clear()
} }
var logger = log.New(os.Stdout, "", log.Lshortfile) var logger = log.New(os.Stdout, "", log.Lshortfile)
@ -228,10 +230,11 @@ var logger = log.New(os.Stdout, "", log.Lshortfile)
const alphaMax = float64(0xFFFF) const alphaMax = float64(0xFFFF)
// draw fills and/or strokes paths // draw fills and/or strokes paths
func (gc *GraphicContext) draw(style string, alpha uint32, paths ...*draw2d.PathStorage) { func (gc *GraphicContext) draw(style string, alpha uint32, paths ...*draw2d.Path) {
paths = append(paths, gc.Current.Path) paths = append(paths, gc.Current.Path)
pathConverter := NewPathConverter(gc.pdf) for _, p := range paths {
pathConverter.Convert(paths...) ConvertPath(p, gc.pdf)
}
a := float64(alpha) / alphaMax a := float64(alpha) / alphaMax
current, blendMode := gc.pdf.GetAlpha() current, blendMode := gc.pdf.GetAlpha()
if a != current { if a != current {
@ -308,13 +311,13 @@ func (gc *GraphicContext) SetLineWidth(LineWidth float64) {
} }
// SetLineCap sets the line cap (round, but or square) // SetLineCap sets the line cap (round, but or square)
func (gc *GraphicContext) SetLineCap(Cap draw2d.Cap) { func (gc *GraphicContext) SetLineCap(Cap draw2d.LineCap) {
gc.StackGraphicContext.SetLineCap(Cap) gc.StackGraphicContext.SetLineCap(Cap)
gc.pdf.SetLineCapStyle(caps[Cap]) gc.pdf.SetLineCapStyle(caps[Cap])
} }
// SetLineJoin sets the line cap (round, bevel or miter) // SetLineJoin sets the line cap (round, bevel or miter)
func (gc *GraphicContext) SetLineJoin(Join draw2d.Join) { func (gc *GraphicContext) SetLineJoin(Join draw2d.LineJoin) {
gc.StackGraphicContext.SetLineJoin(Join) gc.StackGraphicContext.SetLineJoin(Join)
gc.pdf.SetLineJoinStyle(joins[Join]) gc.pdf.SetLineJoinStyle(joins[Join])
} }

View file

@ -11,50 +11,34 @@ import (
const deg = 180 / math.Pi const deg = 180 / math.Pi
// PathConverter converts the paths to the pdf api // ConvertPath converts a paths to the pdf api
type PathConverter struct { func ConvertPath(path *draw2d.Path, pdf Vectorizer) {
pdf Vectorizer var startX, startY float64 = 0, 0
} i := 0
for _, cmp := range path.Components {
// NewPathConverter constructs a PathConverter from a pdf vectorizer switch cmp {
func NewPathConverter(pdf Vectorizer) *PathConverter { case draw2d.MoveToCmp:
return &PathConverter{pdf: pdf} startX, startY = path.Points[i], path.Points[i+1]
} pdf.MoveTo(startX, startY)
i += 2
// Convert converts the paths to the pdf api case draw2d.LineToCmp:
func (c *PathConverter) Convert(paths ...*draw2d.PathStorage) { pdf.LineTo(path.Points[i], path.Points[i+1])
for _, path := range paths { i += 2
j := 0 case draw2d.QuadCurveToCmp:
for _, cmd := range path.Commands { pdf.CurveTo(path.Points[i], path.Points[i+1], path.Points[i+2], path.Points[i+3])
j = j + c.ConvertCommand(cmd, path.Vertices[j:]...) i += 4
case draw2d.CubicCurveToCmp:
pdf.CurveBezierCubicTo(path.Points[i], path.Points[i+1], path.Points[i+2], path.Points[i+3], path.Points[i+4], path.Points[i+5])
i += 6
case draw2d.ArcToCmp:
pdf.ArcTo(path.Points[i], path.Points[i+1], path.Points[i+2], path.Points[i+3],
0, // degRotate
path.Points[i+4]*deg, // degStart = startAngle
(path.Points[i+4]-path.Points[i+5])*deg) // degEnd = startAngle-angle
i += 6
case draw2d.CloseCmp:
pdf.LineTo(startX, startY)
pdf.ClosePath()
} }
} }
} }
// ConvertCommand converts a single path segment to the pdf api
func (c *PathConverter) ConvertCommand(cmd draw2d.PathCmd, vertices ...float64) int {
switch cmd {
case draw2d.MoveTo:
c.pdf.MoveTo(vertices[0], vertices[1])
return 2
case draw2d.LineTo:
c.pdf.LineTo(vertices[0], vertices[1])
return 2
case draw2d.QuadCurveTo:
c.pdf.CurveTo(vertices[0], vertices[1], vertices[2], vertices[3])
return 4
case draw2d.CubicCurveTo:
c.pdf.CurveBezierCubicTo(vertices[0], vertices[1], vertices[2], vertices[3], vertices[4], vertices[5])
return 6
case draw2d.ArcTo:
// draw2d: angles clockwise, fpdf angles counter clockwise
c.pdf.ArcTo(vertices[0], vertices[1], vertices[2], vertices[3],
0, // degRotate
-vertices[4]*deg, // degStart = -startAngle
(-vertices[4]-vertices[5])*deg) // degEnd = -startAngle-angle
return 6
default: // case draw2d.Close:
c.pdf.ClosePath()
return 0
}
}

44
gc.go
View file

@ -8,34 +8,38 @@ import (
"image/color" "image/color"
) )
// FillRule defines the type for fill rules
type FillRule int
const (
// FillRuleEvenOdd defines the even odd filling rule
FillRuleEvenOdd FillRule = iota
// FillRuleWinding defines the non zero winding rule
FillRuleWinding
)
// GraphicContext describes the interface for the various backends (images, pdf, opengl, ...) // GraphicContext describes the interface for the various backends (images, pdf, opengl, ...)
type GraphicContext interface { type GraphicContext interface {
Path PathBuilder
// Create a new path // BeginPath creates a new path
BeginPath() BeginPath()
GetMatrixTransform() MatrixTransform // GetMatrixTransform returns the current transformation matrix
SetMatrixTransform(tr MatrixTransform) GetMatrixTransform() Matrix
ComposeMatrixTransform(tr MatrixTransform) // SetMatrixTransform sets the current transformation matrix
SetMatrixTransform(tr Matrix)
// ComposeMatrixTransform composes the current transformation matrix with tr
ComposeMatrixTransform(tr Matrix)
// Rotate applies a rotation to the current transformation matrix. angle is in radian.
Rotate(angle float64) Rotate(angle float64)
// Translate applies a translation to the current transformation matrix.
Translate(tx, ty float64) Translate(tx, ty float64)
// Scale applies a scale to the current transformation matrix.
Scale(sx, sy float64) Scale(sx, sy float64)
// SetStrokeColor sets the current stroke color
SetStrokeColor(c color.Color) SetStrokeColor(c color.Color)
// SetStrokeColor sets the current fill color
SetFillColor(c color.Color) SetFillColor(c color.Color)
// SetFillRule sets the current fill rule
SetFillRule(f FillRule) SetFillRule(f FillRule)
// SetLineWidth sets the current line width
SetLineWidth(lineWidth float64) SetLineWidth(lineWidth float64)
SetLineCap(cap Cap) // SetLineCap sets the current line cap
SetLineJoin(join Join) SetLineCap(cap LineCap)
// SetLineJoin sets the current line join
SetLineJoin(join LineJoin)
// SetLineJoin sets the current dash
SetLineDash(dash []float64, dashOffset float64) SetLineDash(dash []float64, dashOffset float64)
// SetFontSize
SetFontSize(fontSize float64) SetFontSize(fontSize float64)
GetFontSize() float64 GetFontSize() float64
SetFontData(fontData FontData) SetFontData(fontData FontData)
@ -53,7 +57,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 ...*PathStorage) Stroke(paths ...*Path)
Fill(paths ...*PathStorage) Fill(paths ...*Path)
FillStroke(paths ...*PathStorage) FillStroke(paths ...*Path)
} }

52
math.go
View file

@ -1,52 +0,0 @@
// Copyright 2010 The draw2d Authors. All rights reserved.
// created: 21/11/2010 by Laurent Le Goff
package draw2d
import (
"math"
)
func distance(x1, y1, x2, y2 float64) float64 {
dx := x2 - x1
dy := y2 - y1
return float64(math.Sqrt(dx*dx + dy*dy))
}
func vectorDistance(dx, dy float64) float64 {
return float64(math.Sqrt(dx*dx + dy*dy))
}
func squareDistance(x1, y1, x2, y2 float64) float64 {
dx := x2 - x1
dy := y2 - y1
return dx*dx + dy*dy
}
func min(x, y float64) float64 {
if x < y {
return x
}
return y
}
func max(x, y float64) float64 {
if x > y {
return x
}
return y
}
func minMax(x, y float64) (min, max float64) {
if x > y {
return y, x
}
return x, y
}
func minUint32(a, b uint32) uint32 {
if a < b {
return a
}
return b
}

222
matrix.go Normal file
View file

@ -0,0 +1,222 @@
// Copyright 2010 The draw2d Authors. All rights reserved.
// created: 21/11/2010 by Laurent Le Goff
package draw2d
import (
"math"
)
// Matrix represents an affine transformation
type Matrix [6]float64
const (
epsilon = 1e-6
)
// Determinant compute the determinant of the matrix
func (tr Matrix) Determinant() float64 {
return tr[0]*tr[3] - tr[1]*tr[2]
}
// Transform applies the transformation matrix to points. It modify the points passed in parameter.
func (tr Matrix) Transform(points []float64) {
for i, j := 0, 1; j < len(points); i, j = i+2, j+2 {
x := points[i]
y := points[j]
points[i] = x*tr[0] + y*tr[2] + tr[4]
points[j] = x*tr[1] + y*tr[3] + tr[5]
}
}
// TransformPoint applies the transformation matrix to point. It returns the point the transformed point.
func (tr Matrix) TransformPoint(x, y float64) (xres, yres float64) {
xres = x*tr[0] + y*tr[2] + tr[4]
yres = x*tr[1] + y*tr[3] + tr[5]
return xres, yres
}
func minMax(x, y float64) (min, max float64) {
if x > y {
return y, x
}
return x, y
}
// Transform applies the transformation matrix to the rectangle represented by the min and the max point of the rectangle
func (tr Matrix) TransformRectangle(x0, y0, x2, y2 float64) (nx0, ny0, nx2, ny2 float64) {
points := []float64{x0, y0, x2, y0, x2, y2, x0, y2}
tr.Transform(points)
points[0], points[2] = minMax(points[0], points[2])
points[4], points[6] = minMax(points[4], points[6])
points[1], points[3] = minMax(points[1], points[3])
points[5], points[7] = minMax(points[5], points[7])
nx0 = math.Min(points[0], points[4])
ny0 = math.Min(points[1], points[5])
nx2 = math.Max(points[2], points[6])
ny2 = math.Max(points[3], points[7])
return nx0, ny0, nx2, ny2
}
// InverseTransform applies the transformation inverse matrix to the rectangle represented by the min and the max point of the rectangle
func (tr Matrix) InverseTransform(points []float64) {
d := tr.Determinant() // matrix determinant
for i, j := 0, 1; j < len(points); i, j = i+2, j+2 {
x := points[i]
y := points[j]
points[i] = ((x-tr[4])*tr[3] - (y-tr[5])*tr[2]) / d
points[j] = ((y-tr[5])*tr[0] - (x-tr[4])*tr[1]) / d
}
}
// InverseTransformPoint applies the transformation inverse matrix to point. It returns the point the transformed point.
func (tr Matrix) InverseTransformPoint(x, y float64) (xres, yres float64) {
d := tr.Determinant() // matrix determinant
xres = ((x-tr[4])*tr[3] - (y-tr[5])*tr[2]) / d
yres = ((y-tr[5])*tr[0] - (x-tr[4])*tr[1]) / d
return xres, yres
}
// VectorTransform applies the transformation matrix to points without using the translation parameter of the affine matrix.
// It modify the points passed in parameter.
func (tr Matrix) VectorTransform(points []float64) {
for i, j := 0, 1; j < len(points); i, j = i+2, j+2 {
x := points[i]
y := points[j]
points[i] = x*tr[0] + y*tr[2]
points[j] = x*tr[1] + y*tr[3]
}
}
// NewIdentityMatrix creates an identity transformation matrix.
func NewIdentityMatrix() Matrix {
return Matrix{1, 0, 0, 1, 0, 0}
}
// NewTranslationMatrix creates a transformation matrix with a translation tx and ty translation parameter
func NewTranslationMatrix(tx, ty float64) Matrix {
return Matrix{1, 0, 0, 1, tx, ty}
}
// NewScaleMatrix creates a transformation matrix with a sx, sy scale factor
func NewScaleMatrix(sx, sy float64) Matrix {
return Matrix{sx, 0, 0, sy, 0, 0}
}
// NewRotationMatrix creates a rotation transformation matrix. angle is in radian
func NewRotationMatrix(angle float64) Matrix {
c := math.Cos(angle)
s := math.Sin(angle)
return Matrix{c, s, -s, c, 0, 0}
}
// NewMatrixFromRects creates a transformation matrix, combining a scale and a translation, that transform rectangle1 into rectangle2.
func NewMatrixFromRects(rectangle1, rectangle2 [4]float64) Matrix {
xScale := (rectangle2[2] - rectangle2[0]) / (rectangle1[2] - rectangle1[0])
yScale := (rectangle2[3] - rectangle2[1]) / (rectangle1[3] - rectangle1[1])
xOffset := rectangle2[0] - (rectangle1[0] * xScale)
yOffset := rectangle2[1] - (rectangle1[1] * yScale)
return Matrix{xScale, 0, 0, yScale, xOffset, yOffset}
}
// Inverse computes the inverse matrix
func (tr *Matrix) Inverse() {
d := tr.Determinant() // matrix determinant
tr0, tr1, tr2, tr3, tr4, tr5 := tr[0], tr[1], tr[2], tr[3], tr[4], tr[5]
tr[0] = tr3 / d
tr[1] = -tr1 / d
tr[2] = -tr2 / d
tr[3] = tr0 / d
tr[4] = (tr2*tr5 - tr3*tr4) / d
tr[5] = (tr1*tr4 - tr0*tr5) / d
}
func (tr Matrix) Copy() Matrix {
var result Matrix
copy(result[:], tr[:])
return result
}
// Compose multiplies trToConcat x tr
func (tr *Matrix) Compose(trToCompose Matrix) {
tr0, tr1, tr2, tr3, tr4, tr5 := tr[0], tr[1], tr[2], tr[3], tr[4], tr[5]
tr[0] = trToCompose[0]*tr0 + trToCompose[1]*tr2
tr[1] = trToCompose[1]*tr3 + trToCompose[0]*tr1
tr[2] = trToCompose[2]*tr0 + trToCompose[3]*tr2
tr[3] = trToCompose[3]*tr3 + trToCompose[2]*tr1
tr[4] = trToCompose[4]*tr0 + trToCompose[5]*tr2 + tr4
tr[5] = trToCompose[5]*tr3 + trToCompose[4]*tr1 + tr5
}
// Scale adds a scale to the matrix
func (tr *Matrix) Scale(sx, sy float64) {
tr[0] = sx * tr[0]
tr[1] = sx * tr[1]
tr[2] = sy * tr[2]
tr[3] = sy * tr[3]
}
// Translate adds a translation to the matrix
func (tr *Matrix) Translate(tx, ty float64) {
tr[4] = tx*tr[0] + ty*tr[2] + tr[4]
tr[5] = ty*tr[3] + tx*tr[1] + tr[5]
}
// Rotate adds a rotation to the matrix. angle is in radian
func (tr *Matrix) Rotate(angle float64) {
c := math.Cos(angle)
s := math.Sin(angle)
t0 := c*tr[0] + s*tr[2]
t1 := s*tr[3] + c*tr[1]
t2 := c*tr[2] - s*tr[0]
t3 := c*tr[3] - s*tr[1]
tr[0] = t0
tr[1] = t1
tr[2] = t2
tr[3] = t3
}
// GetTranslation
func (tr Matrix) GetTranslation() (x, y float64) {
return tr[4], tr[5]
}
// GetScaling
func (tr Matrix) GetScaling() (x, y float64) {
return tr[0], tr[3]
}
// GetScale computes a scale for the matrix
func (tr Matrix) GetScale() float64 {
x := 0.707106781*tr[0] + 0.707106781*tr[1]
y := 0.707106781*tr[2] + 0.707106781*tr[3]
return math.Sqrt(x*x + y*y)
}
// ******************** Testing ********************
// Equals tests if a two transformation are equal. A tolerance is applied when comparing matrix elements.
func (tr1 Matrix) Equals(tr2 Matrix) bool {
for i := 0; i < 6; i = i + 1 {
if !fequals(tr1[i], tr2[i]) {
return false
}
}
return true
}
// IsIdentity tests if a transformation is the identity transformation. A tolerance is applied when comparing matrix elements.
func (tr Matrix) IsIdentity() bool {
return fequals(tr[4], 0) && fequals(tr[5], 0) && tr.IsTranslation()
}
// IsTranslation tests if a transformation is is a pure translation. A tolerance is applied when comparing matrix elements.
func (tr Matrix) IsTranslation() bool {
return fequals(tr[0], 1) && fequals(tr[1], 0) && fequals(tr[2], 0) && fequals(tr[3], 1)
}
// fequals compares two floats. return true if the distance between the two floats is less than epsilon, false otherwise
func fequals(float1, float2 float64) bool {
return math.Abs(float1-float2) <= epsilon
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 41 KiB

View file

@ -1,92 +0,0 @@
// Copyright 2010 The draw2d Authors. All rights reserved.
// created: 21/11/2010 by Laurent Le Goff
package draw2d
/*
import (
"image/draw"
"image"
"freetype-go.googlecode.com/hg/freetype/raster"
)*/
const M = 1<<16 - 1
/*
type NRGBAPainter struct {
// The image to compose onto.
Image *image.NRGBA
// The Porter-Duff composition operator.
Op draw.Op
// The 16-bit color to paint the spans.
cr, cg, cb, ca uint32
}
// Paint satisfies the Painter interface by painting ss onto an image.RGBA.
func (r *NRGBAPainter) Paint(ss []raster.Span, done bool) {
b := r.Image.Bounds()
for _, s := range ss {
if s.Y < b.Min.Y {
continue
}
if s.Y >= b.Max.Y {
return
}
if s.X0 < b.Min.X {
s.X0 = b.Min.X
}
if s.X1 > b.Max.X {
s.X1 = b.Max.X
}
if s.X0 >= s.X1 {
continue
}
base := s.Y * r.Image.Stride
p := r.Image.Pix[base+s.X0 : base+s.X1]
// This code is duplicated from drawGlyphOver in $GOROOT/src/pkg/image/draw/draw.go.
// TODO(nigeltao): Factor out common code into a utility function, once the compiler
// can inline such function calls.
ma := s.A >> 16
if r.Op == draw.Over {
for i, nrgba := range p {
dr, dg, db, da := nrgba.
a := M - (r.ca*ma)/M
da = (da*a + r.ca*ma) / M
if da != 0 {
dr = minUint32(M, (dr*a+r.cr*ma)/da)
dg = minUint32(M, (dg*a+r.cg*ma)/da)
db = minUint32(M, (db*a+r.cb*ma)/da)
} else {
dr, dg, db = 0, 0, 0
}
p[i] = image.NRGBAColor{uint8(dr >> 8), uint8(dg >> 8), uint8(db >> 8), uint8(da >> 8)}
}
} else {
for i, nrgba := range p {
dr, dg, db, da := nrgba.RGBA()
a := M - ma
da = (da*a + r.ca*ma) / M
if da != 0 {
dr = minUint32(M, (dr*a+r.cr*ma)/da)
dg = minUint32(M, (dg*a+r.cg*ma)/da)
db = minUint32(M, (db*a+r.cb*ma)/da)
} else {
dr, dg, db = 0, 0, 0
}
p[i] = image.NRGBAColor{uint8(dr >> 8), uint8(dg >> 8), uint8(db >> 8), uint8(da >> 8)}
}
}
}
}
// SetColor sets the color to paint the spans.
func (r *NRGBAPainter) SetColor(c image.Color) {
r.cr, r.cg, r.cb, r.ca = c.RGBA()
}
// NewRGBAPainter creates a new RGBAPainter for the given image.
func NewNRGBAPainter(m *image.NRGBA) *NRGBAPainter {
return &NRGBAPainter{Image: m}
}
*/

186
path.go
View file

@ -3,37 +3,187 @@
package draw2d package draw2d
// Path describes the interface for path drawing. import (
type Path interface { "fmt"
// LastPoint returns the current point of the path "math"
)
// PathBuilder describes the interface for path drawing.
type PathBuilder interface {
// LastPoint returns the current point of the current sub path
LastPoint() (x, y float64) LastPoint() (x, y float64)
// MoveTo creates a new subpath that start at the specified point // MoveTo creates a new subpath that start at the specified point
MoveTo(x, y float64) MoveTo(x, y float64)
// RMoveTo creates a new subpath that start at the specified point
// relative to the current point
RMoveTo(dx, dy float64)
// LineTo adds a line to the current subpath // LineTo adds a line to the current subpath
LineTo(x, y float64) LineTo(x, y float64)
// RLineTo adds a line to the current subpath
// relative to the current point
RLineTo(dx, dy float64)
// QuadCurveTo adds a quadratic Bézier curve to the current subpath // QuadCurveTo adds a quadratic Bézier curve to the current subpath
QuadCurveTo(cx, cy, x, y float64) QuadCurveTo(cx, cy, x, y float64)
// QuadCurveTo adds a quadratic Bézier curve to the current subpath
// relative to the current point
RQuadCurveTo(dcx, dcy, dx, dy float64)
// CubicCurveTo adds a cubic Bézier curve to the current subpath // CubicCurveTo adds a cubic Bézier curve to the current subpath
CubicCurveTo(cx1, cy1, cx2, cy2, x, y float64) CubicCurveTo(cx1, cy1, cx2, cy2, x, y float64)
// RCubicCurveTo adds a cubic Bézier curve to the current subpath
// relative to the current point
RCubicCurveTo(dcx1, dcy1, dcx2, dcy2, dx, dy float64)
// ArcTo adds an arc to the current subpath // ArcTo adds an arc to the current subpath
ArcTo(cx, cy, rx, ry, startAngle, angle float64) ArcTo(cx, cy, rx, ry, startAngle, angle float64)
// RArcTo adds an arc to the current subpath
// relative to the current point
RArcTo(dcx, dcy, rx, ry, startAngle, angle float64)
// Close creates a line from the current point to the last MoveTo // Close creates a line from the current point to the last MoveTo
// point (if not the same) and mark the path as closed so the // point (if not the same) and mark the path as closed so the
// first and last lines join nicely. // first and last lines join nicely.
Close() Close()
} }
// PathCmp represents component of a path
type PathCmp int
const (
// MoveToCmp is a MoveTo component in a Path
MoveToCmp PathCmp = iota
// LineToCmp is a LineTo component in a Path
LineToCmp
// QuadCurveToCmp is a QuadCurveTo component in a Path
QuadCurveToCmp
// CubicCurveToCmp is a CubicCurveTo component in a Path
CubicCurveToCmp
// ArcToCmp is a ArcTo component in a Path
ArcToCmp
// CloseCmp is a ArcTo component in a Path
CloseCmp
)
// Path stores points
type Path struct {
// Components is a slice of PathCmp in a Path and mark the role of each points in the Path
Components []PathCmp
// Points are combined with Components to have a specific role in the path
Points []float64
// Last Point of the Path
x, y float64
}
func (p *Path) appendToPath(cmd PathCmp, points ...float64) {
p.Components = append(p.Components, cmd)
p.Points = append(p.Points, points...)
}
// LastPoint returns the current point of the current path
func (p *Path) LastPoint() (x, y float64) {
return p.x, p.y
}
// MoveTo starts a new path at (x, y) position
func (p *Path) MoveTo(x, y float64) {
p.appendToPath(MoveToCmp, x, y)
p.x = x
p.y = y
}
// LineTo adds a line to the current path
func (p *Path) LineTo(x, y float64) {
if len(p.Components) == 0 { //special case when no move has been done
p.MoveTo(0, 0)
}
p.appendToPath(LineToCmp, x, y)
p.x = x
p.y = y
}
// QuadCurveTo adds a quadratic bezier curve to the current path
func (p *Path) QuadCurveTo(cx, cy, x, y float64) {
if len(p.Components) == 0 { //special case when no move has been done
p.MoveTo(0, 0)
}
p.appendToPath(QuadCurveToCmp, cx, cy, x, y)
p.x = x
p.y = y
}
// CubicCurveTo adds a cubic bezier curve to the current path
func (p *Path) CubicCurveTo(cx1, cy1, cx2, cy2, x, y float64) {
if len(p.Components) == 0 { //special case when no move has been done
p.MoveTo(0, 0)
}
p.appendToPath(CubicCurveToCmp, cx1, cy1, cx2, cy2, x, y)
p.x = x
p.y = y
}
// ArcTo adds an arc to the path
func (p *Path) ArcTo(cx, cy, rx, ry, startAngle, angle float64) {
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 + math.Cos(startAngle)*rx
startY := cy + math.Sin(startAngle)*ry
if len(p.Components) > 0 {
p.LineTo(startX, startY)
} else {
p.MoveTo(startX, startY)
}
p.appendToPath(ArcToCmp, cx, cy, rx, ry, startAngle, angle)
p.x = cx + math.Cos(endAngle)*rx
p.y = cy + math.Sin(endAngle)*ry
}
// Close closes the current path
func (p *Path) Close() {
p.appendToPath(CloseCmp)
}
// Copy make a clone of the current path and return it
func (p *Path) Copy() (dest *Path) {
dest = new(Path)
dest.Components = make([]PathCmp, len(p.Components))
copy(dest.Components, p.Components)
dest.Points = make([]float64, len(p.Points))
copy(dest.Points, p.Points)
dest.x, dest.y = p.x, p.y
return dest
}
// Clear reset the path
func (p *Path) Clear() {
p.Components = p.Components[0:0]
p.Points = p.Points[0:0]
return
}
// IsEmpty returns true if the path is empty
func (p *Path) IsEmpty() bool {
return len(p.Components) == 0
}
// String returns a debug text view of the path
func (p *Path) String() string {
s := ""
j := 0
for _, cmd := range p.Components {
switch cmd {
case MoveToCmp:
s += fmt.Sprintf("MoveTo: %f, %f\n", p.Points[j], p.Points[j+1])
j = j + 2
case LineToCmp:
s += fmt.Sprintf("LineTo: %f, %f\n", p.Points[j], p.Points[j+1])
j = j + 2
case QuadCurveToCmp:
s += fmt.Sprintf("QuadCurveTo: %f, %f, %f, %f\n", p.Points[j], p.Points[j+1], p.Points[j+2], p.Points[j+3])
j = j + 4
case CubicCurveToCmp:
s += fmt.Sprintf("CubicCurveTo: %f, %f, %f, %f, %f, %f\n", p.Points[j], p.Points[j+1], p.Points[j+2], p.Points[j+3], p.Points[j+4], p.Points[j+5])
j = j + 6
case ArcToCmp:
s += fmt.Sprintf("ArcTo: %f, %f, %f, %f, %f, %f\n", p.Points[j], p.Points[j+1], p.Points[j+2], p.Points[j+3], p.Points[j+4], p.Points[j+5])
j = j + 6
case CloseCmp:
s += "Close\n"
}
}
return s
}

View file

@ -1,93 +0,0 @@
// Copyright 2010 The draw2d Authors. All rights reserved.
// created: 13/12/2010 by Laurent Le Goff
package draw2d
import (
"code.google.com/p/freetype-go/freetype/raster"
)
type VertexAdder struct {
command VertexCommand
adder raster.Adder
}
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 float64) {
switch vertexAdder.command {
case VertexStartCommand:
vertexAdder.adder.Start(raster.Point{
X: raster.Fix32(x * 256),
Y: raster.Fix32(y * 256)})
default:
vertexAdder.adder.Add1(raster.Point{
X: raster.Fix32(x * 256),
Y: raster.Fix32(y * 256)})
}
vertexAdder.command = VertexNoCommand
}
type PathAdder struct {
adder raster.Adder
firstPoint raster.Point
ApproximationScale float64
}
func NewPathAdder(adder raster.Adder) *PathAdder {
return &PathAdder{adder, raster.Point{X: 0, Y: 0}, 1}
}
func (pathAdder *PathAdder) Convert(paths ...*PathStorage) {
for _, path := range paths {
j := 0
for _, cmd := range path.Commands {
switch cmd {
case MoveTo:
pathAdder.firstPoint = raster.Point{
X: raster.Fix32(path.Vertices[j] * 256),
Y: raster.Fix32(path.Vertices[j+1] * 256)}
pathAdder.adder.Start(pathAdder.firstPoint)
j += 2
case LineTo:
pathAdder.adder.Add1(raster.Point{
X: raster.Fix32(path.Vertices[j] * 256),
Y: raster.Fix32(path.Vertices[j+1] * 256)})
j += 2
case QuadCurveTo:
pathAdder.adder.Add2(
raster.Point{
X: raster.Fix32(path.Vertices[j] * 256),
Y: raster.Fix32(path.Vertices[j+1] * 256)},
raster.Point{
X: raster.Fix32(path.Vertices[j+2] * 256),
Y: raster.Fix32(path.Vertices[j+3] * 256)})
j += 4
case CubicCurveTo:
pathAdder.adder.Add3(
raster.Point{
X: raster.Fix32(path.Vertices[j] * 256),
Y: raster.Fix32(path.Vertices[j+1] * 256)},
raster.Point{
X: raster.Fix32(path.Vertices[j+2] * 256),
Y: raster.Fix32(path.Vertices[j+3] * 256)},
raster.Point{
X: raster.Fix32(path.Vertices[j+4] * 256),
Y: raster.Fix32(path.Vertices[j+5] * 256)})
j += 6
case ArcTo:
lastPoint := arcAdder(pathAdder.adder, path.Vertices[j], path.Vertices[j+1], path.Vertices[j+2], path.Vertices[j+3], path.Vertices[j+4], path.Vertices[j+5], pathAdder.ApproximationScale)
pathAdder.adder.Add1(lastPoint)
j += 6
case Close:
pathAdder.adder.Add1(pathAdder.firstPoint)
}
}
}
}

View file

@ -1,173 +0,0 @@
// 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 float64
startX, startY, x, y float64
}
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 ...float64) int {
switch cmd {
case MoveTo:
c.x, c.y = vertices[0], vertices[1]
c.startX, c.startY = c.x, c.y
c.converter.NextCommand(VertexStopCommand)
c.converter.NextCommand(VertexStartCommand)
c.converter.Vertex(c.x, c.y)
return 2
case LineTo:
c.x, c.y = vertices[0], vertices[1]
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 2
case QuadCurveTo:
quadraticBezier(c.converter, c.x, c.y, vertices[0], vertices[1], vertices[2], vertices[3], c.ApproximationScale, c.AngleTolerance)
c.x, c.y = vertices[2], vertices[3]
if c.startX == c.x && c.startY == c.y {
c.converter.NextCommand(VertexCloseCommand)
}
c.converter.Vertex(c.x, c.y)
return 4
case CubicCurveTo:
cubicBezier(c.converter, c.x, c.y, vertices[0], vertices[1], vertices[2], vertices[3], vertices[4], vertices[5], c.ApproximationScale, c.AngleTolerance, c.CuspLimit)
c.x, c.y = vertices[4], vertices[5]
if c.startX == c.x && c.startY == c.y {
c.converter.NextCommand(VertexCloseCommand)
}
c.converter.Vertex(c.x, c.y)
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.converter.NextCommand(VertexCloseCommand)
c.converter.Vertex(c.startX, c.startY)
return 0
}
return 0
}
func (c *PathConverter) MoveTo(x, y float64) *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 float64) *PathConverter {
c.MoveTo(c.x+dx, c.y+dy)
return c
}
func (c *PathConverter) LineTo(x, y float64) *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 float64) *PathConverter {
c.LineTo(c.x+dx, c.y+dy)
return c
}
func (c *PathConverter) QuadCurveTo(cx, cy, x, y float64) *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 float64) *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 float64) *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 float64) *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 float64) *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 + math.Cos(startAngle)*rx
startY := cy + math.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 float64) *PathConverter {
c.ArcTo(c.x+dcx, c.y+dcy, rx, ry, startAngle, angle)
return c
}
func (c *PathConverter) Close() *PathConverter {
c.converter.NextCommand(VertexCloseCommand)
c.converter.Vertex(c.startX, c.startY)
return c
}

View file

@ -1,184 +0,0 @@
// Copyright 2010 The draw2d Authors. All rights reserved.
// created: 21/11/2010 by Laurent Le Goff
package draw2d
import (
"fmt"
"math"
)
type PathCmd int
const (
MoveTo PathCmd = iota
LineTo
QuadCurveTo
CubicCurveTo
ArcTo
Close
)
type PathStorage struct {
Commands []PathCmd
Vertices []float64
x, y float64
}
func NewPathStorage() (p *PathStorage) {
p = new(PathStorage)
p.Commands = make([]PathCmd, 0, 256)
p.Vertices = make([]float64, 0, 256)
return
}
func (p *PathStorage) appendToPath(cmd PathCmd, Vertices ...float64) {
if cap(p.Vertices) <= len(p.Vertices)+6 {
a := make([]PathCmd, len(p.Commands), cap(p.Commands)+256)
b := make([]float64, len(p.Vertices), cap(p.Vertices)+256)
copy(a, p.Commands)
p.Commands = a
copy(b, p.Vertices)
p.Vertices = b
}
p.Commands = p.Commands[0 : len(p.Commands)+1]
p.Commands[len(p.Commands)-1] = cmd
copy(p.Vertices[len(p.Vertices):len(p.Vertices)+len(Vertices)], Vertices)
p.Vertices = p.Vertices[0 : len(p.Vertices)+len(Vertices)]
}
func (src *PathStorage) Copy() (dest *PathStorage) {
dest = new(PathStorage)
dest.Commands = make([]PathCmd, len(src.Commands))
copy(dest.Commands, src.Commands)
dest.Vertices = make([]float64, len(src.Vertices))
copy(dest.Vertices, src.Vertices)
return dest
}
func (p *PathStorage) LastPoint() (x, y float64) {
return p.x, p.y
}
func (p *PathStorage) IsEmpty() bool {
return len(p.Commands) == 0
}
func (p *PathStorage) Close() *PathStorage {
p.appendToPath(Close)
return p
}
func (p *PathStorage) MoveTo(x, y float64) *PathStorage {
p.appendToPath(MoveTo, x, y)
p.x = x
p.y = y
return p
}
func (p *PathStorage) RMoveTo(dx, dy float64) *PathStorage {
x, y := p.LastPoint()
p.MoveTo(x+dx, y+dy)
return p
}
func (p *PathStorage) LineTo(x, y float64) *PathStorage {
p.appendToPath(LineTo, x, y)
p.x = x
p.y = y
return p
}
func (p *PathStorage) RLineTo(dx, dy float64) *PathStorage {
x, y := p.LastPoint()
p.LineTo(x+dx, y+dy)
return p
}
func (p *PathStorage) QuadCurveTo(cx, cy, x, y float64) *PathStorage {
p.appendToPath(QuadCurveTo, cx, cy, x, y)
p.x = x
p.y = y
return p
}
func (p *PathStorage) RQuadCurveTo(dcx, dcy, dx, dy float64) *PathStorage {
x, y := p.LastPoint()
p.QuadCurveTo(x+dcx, y+dcy, x+dx, y+dy)
return p
}
func (p *PathStorage) CubicCurveTo(cx1, cy1, cx2, cy2, x, y float64) *PathStorage {
p.appendToPath(CubicCurveTo, cx1, cy1, cx2, cy2, x, y)
p.x = x
p.y = y
return p
}
func (p *PathStorage) RCubicCurveTo(dcx1, dcy1, dcx2, dcy2, dx, dy float64) *PathStorage {
x, y := p.LastPoint()
p.CubicCurveTo(x+dcx1, y+dcy1, x+dcx2, y+dcy2, x+dx, y+dy)
return p
}
func (p *PathStorage) ArcTo(cx, cy, rx, ry, startAngle, angle float64) *PathStorage {
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 + math.Cos(startAngle)*rx
startY := cy + math.Sin(startAngle)*ry
if len(p.Commands) > 0 {
p.LineTo(startX, startY)
} else {
p.MoveTo(startX, startY)
}
p.appendToPath(ArcTo, cx, cy, rx, ry, startAngle, angle)
p.x = cx + math.Cos(endAngle)*rx
p.y = cy + math.Sin(endAngle)*ry
return p
}
func (p *PathStorage) RArcTo(dcx, dcy, rx, ry, startAngle, angle float64) *PathStorage {
x, y := p.LastPoint()
p.ArcTo(x+dcx, y+dcy, rx, ry, startAngle, angle)
return p
}
func (p *PathStorage) String() string {
s := ""
j := 0
for _, cmd := range p.Commands {
switch cmd {
case MoveTo:
s += fmt.Sprintf("MoveTo: %f, %f\n", p.Vertices[j], p.Vertices[j+1])
j = j + 2
case LineTo:
s += fmt.Sprintf("LineTo: %f, %f\n", p.Vertices[j], p.Vertices[j+1])
j = j + 2
case QuadCurveTo:
s += fmt.Sprintf("QuadCurveTo: %f, %f, %f, %f\n", p.Vertices[j], p.Vertices[j+1], p.Vertices[j+2], p.Vertices[j+3])
j = j + 4
case CubicCurveTo:
s += fmt.Sprintf("CubicCurveTo: %f, %f, %f, %f, %f, %f\n", p.Vertices[j], p.Vertices[j+1], p.Vertices[j+2], p.Vertices[j+3], p.Vertices[j+4], p.Vertices[j+5])
j = j + 6
case ArcTo:
s += fmt.Sprintf("ArcTo: %f, %f, %f, %f, %f, %f\n", p.Vertices[j], p.Vertices[j+1], p.Vertices[j+2], p.Vertices[j+3], p.Vertices[j+4], p.Vertices[j+5])
j = j + 6
case Close:
s += "Close\n"
}
}
return s
}

View file

@ -1,40 +1,17 @@
package raster package raster
import ( import (
"bufio"
"image" "image"
"image/color" "image/color"
"image/png"
"log"
"os"
"testing" "testing"
"code.google.com/p/freetype-go/freetype/raster" "code.google.com/p/freetype-go/freetype/raster"
"github.com/llgcode/draw2d/curve" "github.com/llgcode/draw2d/draw2dbase"
"github.com/llgcode/draw2d/draw2dimg"
) )
var flatteningThreshold = 0.5 var flatteningThreshold = 0.5
func savepng(filePath string, m image.Image) {
f, err := os.Create(filePath)
if err != nil {
log.Println(err)
os.Exit(1)
}
defer f.Close()
b := bufio.NewWriter(f)
err = png.Encode(b, m)
if err != nil {
log.Println(err)
os.Exit(1)
}
err = b.Flush()
if err != nil {
log.Println(err)
os.Exit(1)
}
}
type Path struct { type Path struct {
points []float64 points []float64
} }
@ -54,8 +31,7 @@ func (p *Path) LineTo(x, y float64) {
func TestFreetype(t *testing.T) { func TestFreetype(t *testing.T) {
var p Path var p Path
p.LineTo(10, 190) p.LineTo(10, 190)
c := curve.CubicCurveFloat64{10, 190, 10, 10, 190, 10, 190, 190} draw2dbase.TraceCubic(&p, []float64{10, 190, 10, 10, 190, 10, 190, 190}, 0.5)
c.Segment(&p, flatteningThreshold)
poly := Polygon(p.points) poly := Polygon(p.points)
color := color.RGBA{0, 0, 0, 0xff} color := color.RGBA{0, 0, 0, 0xff}
@ -74,14 +50,13 @@ func TestFreetype(t *testing.T) {
painter.SetColor(color) painter.SetColor(color)
rasterizer.Rasterize(painter) rasterizer.Rasterize(painter)
savepng("../output/raster/TestFreetype.png", img) draw2dimg.SaveToPngFile("../output/raster/TestFreetype.png", img)
} }
func TestFreetypeNonZeroWinding(t *testing.T) { func TestFreetypeNonZeroWinding(t *testing.T) {
var p Path var p Path
p.LineTo(10, 190) p.LineTo(10, 190)
c := curve.CubicCurveFloat64{10, 190, 10, 10, 190, 10, 190, 190} draw2dbase.TraceCubic(&p, []float64{10, 190, 10, 10, 190, 10, 190, 190}, 0.5)
c.Segment(&p, flatteningThreshold)
poly := Polygon(p.points) poly := Polygon(p.points)
color := color.RGBA{0, 0, 0, 0xff} color := color.RGBA{0, 0, 0, 0xff}
@ -100,15 +75,15 @@ func TestFreetypeNonZeroWinding(t *testing.T) {
painter.SetColor(color) painter.SetColor(color)
rasterizer.Rasterize(painter) rasterizer.Rasterize(painter)
savepng("../output/raster/TestFreetypeNonZeroWinding.png", img) draw2dimg.SaveToPngFile("../output/raster/TestFreetypeNonZeroWinding.png", img)
} }
func TestRasterizer(t *testing.T) { func TestRasterizer(t *testing.T) {
img := image.NewRGBA(image.Rect(0, 0, 200, 200)) img := image.NewRGBA(image.Rect(0, 0, 200, 200))
var p Path var p Path
p.LineTo(10, 190) p.LineTo(10, 190)
c := curve.CubicCurveFloat64{10, 190, 10, 10, 190, 10, 190, 190} draw2dbase.TraceCubic(&p, []float64{10, 190, 10, 10, 190, 10, 190, 190}, 0.5)
c.Segment(&p, flatteningThreshold)
poly := Polygon(p.points) poly := Polygon(p.points)
color := color.RGBA{0, 0, 0, 0xff} color := color.RGBA{0, 0, 0, 0xff}
tr := [6]float64{1, 0, 0, 1, 0, 0} tr := [6]float64{1, 0, 0, 1, 0, 0}
@ -116,15 +91,15 @@ func TestRasterizer(t *testing.T) {
//PolylineBresenham(img, image.Black, poly...) //PolylineBresenham(img, image.Black, poly...)
r.RenderEvenOdd(img, &color, &poly, tr) r.RenderEvenOdd(img, &color, &poly, tr)
savepng("../output/raster/TestRasterizer.png", img) draw2dimg.SaveToPngFile("../output/raster/TestRasterizer.png", img)
} }
func TestRasterizerNonZeroWinding(t *testing.T) { func TestRasterizerNonZeroWinding(t *testing.T) {
img := image.NewRGBA(image.Rect(0, 0, 200, 200)) img := image.NewRGBA(image.Rect(0, 0, 200, 200))
var p Path var p Path
p.LineTo(10, 190) p.LineTo(10, 190)
c := curve.CubicCurveFloat64{10, 190, 10, 10, 190, 10, 190, 190} draw2dbase.TraceCubic(&p, []float64{10, 190, 10, 10, 190, 10, 190, 190}, 0.5)
c.Segment(&p, flatteningThreshold)
poly := Polygon(p.points) poly := Polygon(p.points)
color := color.RGBA{0, 0, 0, 0xff} color := color.RGBA{0, 0, 0, 0xff}
tr := [6]float64{1, 0, 0, 1, 0, 0} tr := [6]float64{1, 0, 0, 1, 0, 0}
@ -132,14 +107,14 @@ func TestRasterizerNonZeroWinding(t *testing.T) {
//PolylineBresenham(img, image.Black, poly...) //PolylineBresenham(img, image.Black, poly...)
r.RenderNonZeroWinding(img, &color, &poly, tr) r.RenderNonZeroWinding(img, &color, &poly, tr)
savepng("../output/raster/TestRasterizerNonZeroWinding.png", img) draw2dimg.SaveToPngFile("../output/raster/TestRasterizerNonZeroWinding.png", img)
} }
func BenchmarkFreetype(b *testing.B) { func BenchmarkFreetype(b *testing.B) {
var p Path var p Path
p.LineTo(10, 190) p.LineTo(10, 190)
c := curve.CubicCurveFloat64{10, 190, 10, 10, 190, 10, 190, 190} draw2dbase.TraceCubic(&p, []float64{10, 190, 10, 10, 190, 10, 190, 190}, 0.5)
c.Segment(&p, flatteningThreshold)
poly := Polygon(p.points) poly := Polygon(p.points)
color := color.RGBA{0, 0, 0, 0xff} color := color.RGBA{0, 0, 0, 0xff}
@ -164,8 +139,8 @@ func BenchmarkFreetype(b *testing.B) {
func BenchmarkFreetypeNonZeroWinding(b *testing.B) { func BenchmarkFreetypeNonZeroWinding(b *testing.B) {
var p Path var p Path
p.LineTo(10, 190) p.LineTo(10, 190)
c := curve.CubicCurveFloat64{10, 190, 10, 10, 190, 10, 190, 190} draw2dbase.TraceCubic(&p, []float64{10, 190, 10, 10, 190, 10, 190, 190}, 0.5)
c.Segment(&p, flatteningThreshold)
poly := Polygon(p.points) poly := Polygon(p.points)
color := color.RGBA{0, 0, 0, 0xff} color := color.RGBA{0, 0, 0, 0xff}
@ -190,8 +165,8 @@ func BenchmarkFreetypeNonZeroWinding(b *testing.B) {
func BenchmarkRasterizerNonZeroWinding(b *testing.B) { func BenchmarkRasterizerNonZeroWinding(b *testing.B) {
var p Path var p Path
p.LineTo(10, 190) p.LineTo(10, 190)
c := curve.CubicCurveFloat64{10, 190, 10, 10, 190, 10, 190, 190} draw2dbase.TraceCubic(&p, []float64{10, 190, 10, 10, 190, 10, 190, 190}, 0.5)
c.Segment(&p, flatteningThreshold)
poly := Polygon(p.points) poly := Polygon(p.points)
color := color.RGBA{0, 0, 0, 0xff} color := color.RGBA{0, 0, 0, 0xff}
tr := [6]float64{1, 0, 0, 1, 0, 0} tr := [6]float64{1, 0, 0, 1, 0, 0}
@ -205,8 +180,8 @@ func BenchmarkRasterizerNonZeroWinding(b *testing.B) {
func BenchmarkRasterizer(b *testing.B) { func BenchmarkRasterizer(b *testing.B) {
var p Path var p Path
p.LineTo(10, 190) p.LineTo(10, 190)
c := curve.CubicCurveFloat64{10, 190, 10, 10, 190, 10, 190, 190} draw2dbase.TraceCubic(&p, []float64{10, 190, 10, 10, 190, 10, 190, 190}, 0.5)
c.Segment(&p, flatteningThreshold)
poly := Polygon(p.points) poly := Polygon(p.points)
color := color.RGBA{0, 0, 0, 0xff} color := color.RGBA{0, 0, 0, 0xff}
tr := [6]float64{1, 0, 0, 1, 0, 0} tr := [6]float64{1, 0, 0, 1, 0, 0}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

View file

@ -9,6 +9,7 @@ import (
"math" "math"
"github.com/llgcode/draw2d" "github.com/llgcode/draw2d"
"github.com/llgcode/draw2d/draw2dkit"
"github.com/llgcode/draw2d/samples" "github.com/llgcode/draw2d/samples"
) )
@ -44,32 +45,32 @@ func Draw(gc draw2d.GraphicContext, x, y float64) {
gc.Stroke() gc.Stroke()
// left eye // left eye
draw2d.Circle(gc, x+60, y+45, 5) draw2dkit.Circle(gc, x+60, y+45, 5)
gc.FillStroke() gc.FillStroke()
// right eye // right eye
draw2d.Circle(gc, x+100, y+45, 5) draw2dkit.Circle(gc, x+100, y+45, 5)
gc.FillStroke() gc.FillStroke()
// body // body
draw2d.RoundRect(gc, x+30, y+75, x+30+100, y+75+90, 10, 10) draw2dkit.RoundedRectangle(gc, x+30, y+75, x+30+100, y+75+90, 10, 10)
gc.FillStroke() gc.FillStroke()
draw2d.Rect(gc, x+30, y+75, x+30+100, y+75+80) draw2dkit.Rectangle(gc, x+30, y+75, x+30+100, y+75+80)
gc.FillStroke() gc.FillStroke()
// left arm // left arm
draw2d.RoundRect(gc, x+5, y+80, x+5+20, y+80+70, 10, 10) draw2dkit.RoundedRectangle(gc, x+5, y+80, x+5+20, y+80+70, 10, 10)
gc.FillStroke() gc.FillStroke()
// right arm // right arm
draw2d.RoundRect(gc, x+135, y+80, x+135+20, y+80+70, 10, 10) draw2dkit.RoundedRectangle(gc, x+135, y+80, x+135+20, y+80+70, 10, 10)
gc.FillStroke() gc.FillStroke()
// left leg // left leg
draw2d.RoundRect(gc, x+50, y+150, x+50+20, y+150+50, 10, 10) draw2dkit.RoundedRectangle(gc, x+50, y+150, x+50+20, y+150+50, 10, 10)
gc.FillStroke() gc.FillStroke()
// right leg // right leg
draw2d.RoundRect(gc, x+90, y+150, x+90+20, y+150+50, 10, 10) draw2dkit.RoundedRectangle(gc, x+90, y+150, x+90+20, y+150+50, 10, 10)
gc.FillStroke() gc.FillStroke()
} }

View file

@ -8,6 +8,8 @@ import (
"math" "math"
"github.com/llgcode/draw2d" "github.com/llgcode/draw2d"
"github.com/llgcode/draw2d/draw2dimg"
"github.com/llgcode/draw2d/draw2dkit"
"github.com/llgcode/draw2d/samples" "github.com/llgcode/draw2d/samples"
) )
@ -33,13 +35,12 @@ func Main(gc draw2d.GraphicContext, ext string) (string, error) {
func Draw(gc draw2d.GraphicContext, png string, func Draw(gc draw2d.GraphicContext, png string,
dw, dh, margin, lineWidth float64) error { dw, dh, margin, lineWidth float64) error {
// Draw frame // Draw frame
draw2d.RoundRect(gc, lineWidth, lineWidth, draw2dkit.RoundedRectangle(gc, lineWidth, lineWidth, dw-lineWidth, dh-lineWidth, 100, 100)
dw-lineWidth, dh-lineWidth, 100, 100)
gc.SetLineWidth(lineWidth) gc.SetLineWidth(lineWidth)
gc.FillStroke() gc.FillStroke()
// load the source image // load the source image
source, err := draw2d.LoadFromPngFile(png) source, err := draw2dimg.LoadFromPngFile(png)
if err != nil { if err != nil {
return err return err
} }

View file

@ -10,6 +10,7 @@ import (
"math" "math"
"github.com/llgcode/draw2d" "github.com/llgcode/draw2d"
"github.com/llgcode/draw2d/draw2dkit"
"github.com/llgcode/draw2d/samples" "github.com/llgcode/draw2d/samples"
"github.com/llgcode/draw2d/samples/gopher2" "github.com/llgcode/draw2d/samples/gopher2"
) )
@ -98,7 +99,7 @@ func Dash(gc draw2d.GraphicContext, x, y, width, height float64) {
gc.MoveTo(x+sx*60.0, y) gc.MoveTo(x+sx*60.0, y)
gc.LineTo(x+sx*60.0, y) gc.LineTo(x+sx*60.0, y)
gc.LineTo(x+sx*162, y+sy*205) gc.LineTo(x+sx*162, y+sy*205)
gc.RLineTo(sx*-102.4, 0.0) rLineTo(gc, sx*-102.4, 0)
gc.CubicCurveTo(x+sx*-17, y+sy*205, x+sx*-17, y+sy*103, x+sx*60.0, y+sy*103.0) gc.CubicCurveTo(x+sx*-17, y+sy*205, x+sx*-17, y+sy*103, x+sx*60.0, y+sy*103.0)
gc.Stroke() gc.Stroke()
gc.SetLineDash(nil, 0.0) gc.SetLineDash(nil, 0.0)
@ -193,7 +194,7 @@ func FillString(gc draw2d.GraphicContext, x, y, width, height float64) {
gc.Save() gc.Save()
gc.SetStrokeColor(image.Black) gc.SetStrokeColor(image.Black)
gc.SetLineWidth(1) gc.SetLineWidth(1)
draw2d.RoundRect(gc, x+sx*5, y+sy*5, x+sx*95, y+sy*95, sx*10, sy*10) draw2dkit.RoundedRectangle(gc, x+sx*5, y+sy*5, x+sx*95, y+sy*95, sx*10, sy*10)
gc.FillStroke() gc.FillStroke()
gc.SetFillColor(image.Black) gc.SetFillColor(image.Black)
gc.SetFontSize(height / 6) gc.SetFontSize(height / 6)
@ -206,7 +207,7 @@ func FillString(gc draw2d.GraphicContext, x, y, width, height float64) {
gc.Translate(w+sx, 0) gc.Translate(w+sx, 0)
left, top, right, bottom := gc.GetStringBounds("cou") left, top, right, bottom := gc.GetStringBounds("cou")
gc.SetStrokeColor(color.NRGBA{255, 0x33, 0x33, 0x80}) gc.SetStrokeColor(color.NRGBA{255, 0x33, 0x33, 0x80})
draw2d.Rect(gc, left, top, right, bottom) draw2dkit.Rectangle(gc, left, top, right, bottom)
gc.SetLineWidth(height / 50) gc.SetLineWidth(height / 50)
gc.Stroke() gc.Stroke()
gc.SetFillColor(color.NRGBA{0x33, 0x33, 0xff, 0xff}) gc.SetFillColor(color.NRGBA{0x33, 0x33, 0xff, 0xff})
@ -221,14 +222,14 @@ func FillStroke(gc draw2d.GraphicContext, x, y, width, height float64) {
sx, sy := width/210, height/215 sx, sy := width/210, height/215
gc.MoveTo(x+sx*113.0, y) gc.MoveTo(x+sx*113.0, y)
gc.LineTo(x+sx*215.0, y+sy*215) gc.LineTo(x+sx*215.0, y+sy*215)
gc.RLineTo(sx*-100, 0) rLineTo(gc, sx*-100, 0)
gc.CubicCurveTo(x+sx*35, y+sy*215, x+sx*35, y+sy*113, x+sx*113.0, y+sy*113) gc.CubicCurveTo(x+sx*35, y+sy*215, x+sx*35, y+sy*113, x+sx*113.0, y+sy*113)
gc.Close() gc.Close()
gc.MoveTo(x+sx*50.0, y) gc.MoveTo(x+sx*50.0, y)
gc.RLineTo(sx*51.2, sy*51.2) rLineTo(gc, sx*51.2, sy*51.2)
gc.RLineTo(sx*-51.2, sy*51.2) rLineTo(gc, sx*-51.2, sy*51.2)
gc.RLineTo(sx*-51.2, sy*-51.2) rLineTo(gc, sx*-51.2, sy*-51.2)
gc.Close() gc.Close()
gc.SetLineWidth(width / 20.0) gc.SetLineWidth(width / 20.0)
@ -237,33 +238,37 @@ func FillStroke(gc draw2d.GraphicContext, x, y, width, height float64) {
gc.FillStroke() gc.FillStroke()
} }
func rLineTo(path draw2d.PathBuilder, x, y float64) {
x0, y0 := path.LastPoint()
path.LineTo(x0+x, y0+y)
}
// FillStyle demonstrates the difference between even odd and non zero winding rule. // FillStyle demonstrates the difference between even odd and non zero winding rule.
func FillStyle(gc draw2d.GraphicContext, x, y, width, height float64) { func FillStyle(gc draw2d.GraphicContext, x, y, width, height float64) {
sx, sy := width/232, height/220 sx, sy := width/232, height/220
gc.SetLineWidth(width / 40) gc.SetLineWidth(width / 40)
draw2d.Rect(gc, x+sx*0, y+sy*12, x+sx*232, y+sy*70) draw2dkit.Rectangle(gc, x+sx*0, y+sy*12, x+sx*232, y+sy*70)
wheel1 := new(draw2d.PathStorage) var wheel1, wheel2 draw2d.Path
wheel1.ArcTo(x+sx*52, y+sy*70, sx*40, sy*40, 0, 2*math.Pi) wheel1.ArcTo(x+sx*52, y+sy*70, sx*40, sy*40, 0, 2*math.Pi)
wheel2 := new(draw2d.PathStorage)
wheel2.ArcTo(x+sx*180, y+sy*70, sx*40, sy*40, 0, -2*math.Pi) wheel2.ArcTo(x+sx*180, y+sy*70, sx*40, sy*40, 0, -2*math.Pi)
gc.SetFillRule(draw2d.FillRuleEvenOdd) gc.SetFillRule(draw2d.FillRuleEvenOdd)
gc.SetFillColor(color.NRGBA{0, 0xB2, 0, 0xFF}) gc.SetFillColor(color.NRGBA{0, 0xB2, 0, 0xFF})
gc.SetStrokeColor(image.Black) gc.SetStrokeColor(image.Black)
gc.FillStroke(wheel1, wheel2) gc.FillStroke(&wheel1, &wheel2)
draw2d.Rect(gc, x, y+sy*140, x+sx*232, y+sy*198) draw2dkit.Rectangle(gc, x, y+sy*140, x+sx*232, y+sy*198)
wheel1 = new(draw2d.PathStorage) wheel1.Clear()
wheel1.ArcTo(x+sx*52, y+sy*198, sx*40, sy*40, 0, 2*math.Pi) wheel1.ArcTo(x+sx*52, y+sy*198, sx*40, sy*40, 0, 2*math.Pi)
wheel2 = new(draw2d.PathStorage) wheel2.Clear()
wheel2.ArcTo(x+sx*180, y+sy*198, sx*40, sy*40, 0, -2*math.Pi) wheel2.ArcTo(x+sx*180, y+sy*198, sx*40, sy*40, 0, -2*math.Pi)
gc.SetFillRule(draw2d.FillRuleWinding) gc.SetFillRule(draw2d.FillRuleWinding)
gc.SetFillColor(color.NRGBA{0, 0, 0xE5, 0xFF}) gc.SetFillColor(color.NRGBA{0, 0, 0xE5, 0xFF})
gc.FillStroke(wheel1, wheel2) gc.FillStroke(&wheel1, &wheel2)
} }
// PathTransform scales a path differently in horizontal and vertical direction. // PathTransform scales a path differently in horizontal and vertical direction.

View file

@ -39,17 +39,17 @@ func Draw(gc draw2d.GraphicContext) {
// c0.389-6.064,1.088-12.128,0.744-18.216c-10.23-0.927-21.357,1.509-29.744,7.602C10.277,286.542,2.177,296.561,10.634,300.493"/> // c0.389-6.064,1.088-12.128,0.744-18.216c-10.23-0.927-21.357,1.509-29.744,7.602C10.277,286.542,2.177,296.561,10.634,300.493"/>
gc.SetFillColor(color.RGBA{0xF6, 0xD2, 0xA2, 0xff}) gc.SetFillColor(color.RGBA{0xF6, 0xD2, 0xA2, 0xff})
gc.MoveTo(10.634, 300.493) gc.MoveTo(10.634, 300.493)
gc.RCubicCurveTo(0.764, 15.751, 16.499, 8.463, 23.626, 3.539) rCubicCurveTo(gc, 0.764, 15.751, 16.499, 8.463, 23.626, 3.539)
gc.RCubicCurveTo(6.765, -4.675, 8.743, -0.789, 9.337, -10.015) rCubicCurveTo(gc, 6.765, -4.675, 8.743, -0.789, 9.337, -10.015)
gc.RCubicCurveTo(0.389, -6.064, 1.088, -12.128, 0.744, -18.216) rCubicCurveTo(gc, 0.389, -6.064, 1.088, -12.128, 0.744, -18.216)
gc.RCubicCurveTo(-10.23, -0.927, -21.357, 1.509, -29.744, 7.602) rCubicCurveTo(gc, -10.23, -0.927, -21.357, 1.509, -29.744, 7.602)
gc.CubicCurveTo(10.277, 286.542, 2.177, 296.561, 10.634, 300.493) gc.CubicCurveTo(10.277, 286.542, 2.177, 296.561, 10.634, 300.493)
gc.FillStroke() gc.FillStroke()
// <path fill-rule="evenodd" clip-rule="evenodd" fill="#C6B198" stroke="#000000" stroke-width="3" stroke-linecap="round" d=" // <path fill-rule="evenodd" clip-rule="evenodd" fill="#C6B198" stroke="#000000" stroke-width="3" stroke-linecap="round" d="
// M10.634,300.493c2.29-0.852,4.717-1.457,6.271-3.528"/> // M10.634,300.493c2.29-0.852,4.717-1.457,6.271-3.528"/>
gc.MoveTo(10.634, 300.493) gc.MoveTo(10.634, 300.493)
gc.RCubicCurveTo(2.29, -0.852, 4.717, -1.457, 6.271, -3.528) rCubicCurveTo(gc, 2.29, -0.852, 4.717, -1.457, 6.271, -3.528)
gc.Stroke() gc.Stroke()
// Left Ear // Left Ear
@ -61,3 +61,13 @@ func Draw(gc draw2d.GraphicContext) {
gc.Close() gc.Close()
gc.Stroke() gc.Stroke()
} }
func rQuadCurveTo(path draw2d.PathBuilder, dcx, dcy, dx, dy float64) {
x, y := path.LastPoint()
path.QuadCurveTo(x+dcx, y+dcy, x+dx, y+dy)
}
func rCubicCurveTo(path draw2d.PathBuilder, dcx1, dcy1, dcx2, dcy2, dx, dy float64) {
x, y := path.LastPoint()
path.CubicCurveTo(x+dcx1, y+dcy1, x+dcx2, y+dcy2, x+dx, y+dy)
}

View file

@ -11,6 +11,7 @@ import (
"math" "math"
"github.com/llgcode/draw2d" "github.com/llgcode/draw2d"
"github.com/llgcode/draw2d/draw2dkit"
"github.com/llgcode/draw2d/samples" "github.com/llgcode/draw2d/samples"
) )
@ -46,61 +47,76 @@ func Draw(gc draw2d.GraphicContext, x, y, w, h float64) {
gc.Close() gc.Close()
gc.SetFillColor(brb) gc.SetFillColor(brb)
gc.Fill() gc.Fill()
// rectangle head bottom // rectangle head bottom
draw2d.RoundRect(gc, x, y+h, x+w, y+h+h, h/5, h/5) draw2dkit.RoundedRectangle(gc, x, y+h, x+w, y+h+h, h/5, h/5)
gc.Fill() gc.Fill()
// left ear outside // left ear outside
draw2d.Circle(gc, x, y+h, w/12) draw2dkit.Circle(gc, x, y+h, w/12)
gc.SetFillColor(brf) gc.SetFillColor(brf)
gc.Fill() gc.Fill()
// left ear inside // left ear inside
draw2d.Circle(gc, x, y+h, 0.5*w/12) draw2dkit.Circle(gc, x, y+h, 0.5*w/12)
gc.SetFillColor(nf) gc.SetFillColor(nf)
gc.Fill() gc.Fill()
// right ear outside // right ear outside
draw2d.Circle(gc, x+w, y+h, w/12) draw2dkit.Circle(gc, x+w, y+h, w/12)
gc.SetFillColor(brf) gc.SetFillColor(brf)
gc.Fill() gc.Fill()
// right ear inside // right ear inside
draw2d.Circle(gc, x+w, y+h, 0.5*w/12) draw2dkit.Circle(gc, x+w, y+h, 0.5*w/12)
gc.SetFillColor(nf) gc.SetFillColor(nf)
gc.Fill() gc.Fill()
// left eye outside white // left eye outside white
draw2d.Circle(gc, x+w/3, y+h23, w/9) draw2dkit.Circle(gc, x+w/3, y+h23, w/9)
gc.SetFillColor(wf) gc.SetFillColor(wf)
gc.Fill() gc.Fill()
// left eye black // left eye black
draw2d.Circle(gc, x+w/3+w/24, y+h23, 0.5*w/9) draw2dkit.Circle(gc, x+w/3+w/24, y+h23, 0.5*w/9)
gc.SetFillColor(blf) gc.SetFillColor(blf)
gc.Fill() gc.Fill()
// left eye inside white // left eye inside white
draw2d.Circle(gc, x+w/3+w/24+w/48, y+h23, 0.2*w/9) draw2dkit.Circle(gc, x+w/3+w/24+w/48, y+h23, 0.2*w/9)
gc.SetFillColor(wf) gc.SetFillColor(wf)
gc.Fill() gc.Fill()
// right eye outside white // right eye outside white
draw2d.Circle(gc, x+w-w/3, y+h23, w/9) draw2dkit.Circle(gc, x+w-w/3, y+h23, w/9)
gc.Fill() gc.Fill()
// right eye black // right eye black
draw2d.Circle(gc, x+w-w/3+w/24, y+h23, 0.5*w/9) draw2dkit.Circle(gc, x+w-w/3+w/24, y+h23, 0.5*w/9)
gc.SetFillColor(blf) gc.SetFillColor(blf)
gc.Fill() gc.Fill()
// right eye inside white // right eye inside white
draw2d.Circle(gc, x+w-(w/3)+w/24+w/48, y+h23, 0.2*w/9) draw2dkit.Circle(gc, x+w-(w/3)+w/24+w/48, y+h23, 0.2*w/9)
gc.SetFillColor(wf) gc.SetFillColor(wf)
gc.Fill() gc.Fill()
// left tooth // left tooth
gc.SetFillColor(wf) gc.SetFillColor(wf)
draw2d.RoundRect(gc, x+w/2-w/8, y+h+h/2.5, x+w/2-w/8+w/8, y+h+h/2.5+w/6, w/10, w/10) draw2dkit.RoundedRectangle(gc, x+w/2-w/8, y+h+h/2.5, x+w/2-w/8+w/8, y+h+h/2.5+w/6, w/10, w/10)
gc.Fill() gc.Fill()
// right tooth // right tooth
draw2d.RoundRect(gc, x+w/2, y+h+h/2.5, x+w/2+w/8, y+h+h/2.5+w/6, w/10, w/10) draw2dkit.RoundedRectangle(gc, x+w/2, y+h+h/2.5, x+w/2+w/8, y+h+h/2.5+w/6, w/10, w/10)
gc.Fill() gc.Fill()
// snout // snout
draw2d.Ellipse(gc, x+(w/2), y+h+h/2.5, w/6, w/12) draw2dkit.Ellipse(gc, x+(w/2), y+h+h/2.5, w/6, w/12)
gc.SetFillColor(nf) gc.SetFillColor(nf)
gc.Fill() gc.Fill()
// nose // nose
draw2d.Ellipse(gc, x+(w/2), y+h+h/7, w/10, w/12) draw2dkit.Ellipse(gc, x+(w/2), y+h+h/7, w/10, w/12)
gc.SetFillColor(blf) gc.SetFillColor(blf)
gc.Fill() gc.Fill()
} }

View file

@ -8,10 +8,9 @@ package helloworld
import ( import (
"fmt" "fmt"
"image" "image"
"image/color"
"math"
"github.com/llgcode/draw2d" "github.com/llgcode/draw2d"
"github.com/llgcode/draw2d/draw2dkit"
"github.com/llgcode/draw2d/samples" "github.com/llgcode/draw2d/samples"
) )
@ -28,35 +27,14 @@ func Main(gc draw2d.GraphicContext, ext string) (string, error) {
// Draw "Hello World" // Draw "Hello World"
func Draw(gc draw2d.GraphicContext, text string) { func Draw(gc draw2d.GraphicContext, text string) {
// Draw a rounded rectangle using default colors // Draw a rounded rectangle using default colors
draw2d.RoundRect(gc, 5, 5, 292, 205, 10, 10) draw2dkit.RoundedRectangle(gc, 5, 5, 135, 95, 10, 10)
gc.FillStroke() gc.FillStroke()
// Set the font luximbi.ttf // Set the font luximbi.ttf
gc.SetFontData(draw2d.FontData{ gc.SetFontData(draw2d.FontData{Name: "luxi", Family: draw2d.FontFamilyMono, Style: draw2d.FontStyleBold | draw2d.FontStyleItalic})
Name: "luxi",
Family: draw2d.FontFamilyMono,
Style: draw2d.FontStyleBold | draw2d.FontStyleItalic})
// Set the fill text color to black // Set the fill text color to black
gc.SetFillColor(image.Black) gc.SetFillColor(image.Black)
gc.SetDPI(72)
gc.SetFontSize(14) gc.SetFontSize(14)
// Display Hello World // Display Hello World
gc.SetStrokeColor(color.NRGBA{0x33, 0xFF, 0x33, 0xFF}) gc.FillStringAt("Hello World", 8, 52)
gc.MoveTo(8, 0)
gc.LineTo(8, 52)
gc.LineTo(297, 52)
gc.Stroke()
gc.FillString(text)
gc.FillStringAt(text, 8, 52)
gc.Save()
gc.SetFillColor(color.NRGBA{0xFF, 0x33, 0x33, 0xFF})
gc.SetStrokeColor(color.NRGBA{0xFF, 0x33, 0x33, 0xFF})
gc.Translate(145, 85)
gc.StrokeStringAt(text, -50, 0)
gc.Rotate(math.Pi / 4)
gc.SetFillColor(color.NRGBA{0x33, 0x33, 0xFF, 0xFF})
gc.SetStrokeColor(color.NRGBA{0x33, 0x33, 0xFF, 0xFF})
gc.StrokeString(text)
gc.Restore()
} }

View file

@ -10,6 +10,7 @@ import (
"github.com/go-gl/glfw/v3.1/glfw" "github.com/go-gl/glfw/v3.1/glfw"
"github.com/llgcode/draw2d" "github.com/llgcode/draw2d"
"github.com/llgcode/draw2d/draw2dgl" "github.com/llgcode/draw2d/draw2dgl"
"github.com/llgcode/draw2d/draw2dkit"
) )
var ( var (
@ -57,7 +58,7 @@ func display() {
Style: draw2d.FontStyleBold | draw2d.FontStyleItalic}) Style: draw2d.FontStyleBold | draw2d.FontStyleItalic})
gc.BeginPath() gc.BeginPath()
draw2d.RoundRect(gc, 200, 200, 600, 600, 100, 100) draw2dkit.RoundedRectangle(gc, 200, 200, 600, 600, 100, 100)
gc.SetFillColor(color.RGBA{0, 0, 0, 0xff}) gc.SetFillColor(color.RGBA{0, 0, 0, 0xff})
gc.Fill() gc.Fill()
@ -104,9 +105,11 @@ func main() {
// time.Sleep(2 * time.Second) // time.Sleep(2 * time.Second)
} }
} }
func onChar(w *glfw.Window, char rune) { func onChar(w *glfw.Window, char rune) {
log.Println(char) log.Println(char)
} }
func onKey(w *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) { func onKey(w *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) {
switch { switch {
case key == glfw.KeyEscape && action == glfw.Press, case key == glfw.KeyEscape && action == glfw.Press,

View file

@ -8,6 +8,7 @@ import (
"image/color" "image/color"
"github.com/llgcode/draw2d" "github.com/llgcode/draw2d"
"github.com/llgcode/draw2d/draw2dkit"
"github.com/llgcode/draw2d/samples" "github.com/llgcode/draw2d/samples"
) )
@ -21,7 +22,7 @@ func Main(gc draw2d.GraphicContext, ext string) (string, error) {
Draw(gc, x, 0, x, 210) Draw(gc, x, 0, x, 210)
} }
gc.ClearRect(100, 75, 197, 135) gc.ClearRect(100, 75, 197, 135)
draw2d.Ellipse(gc, 148.5, 105, 35, 25) draw2dkit.Ellipse(gc, 148.5, 105, 35, 25)
gc.SetFillColor(color.RGBA{0xff, 0xff, 0x44, 0xff}) gc.SetFillColor(color.RGBA{0xff, 0xff, 0x44, 0xff})
gc.FillStroke() gc.FillStroke()

View file

@ -17,8 +17,8 @@ func Main(gc draw2d.GraphicContext, ext string) (string, error) {
// Draw the line // Draw the line
const offset = 75.0 const offset = 75.0
x := 35.0 x := 35.0
caps := []draw2d.Cap{draw2d.ButtCap, draw2d.SquareCap, draw2d.RoundCap} caps := []draw2d.LineCap{draw2d.ButtCap, draw2d.SquareCap, draw2d.RoundCap}
joins := []draw2d.Join{draw2d.BevelJoin, draw2d.MiterJoin, draw2d.RoundJoin} joins := []draw2d.LineJoin{draw2d.BevelJoin, draw2d.MiterJoin, draw2d.RoundJoin}
for i := range caps { for i := range caps {
Draw(gc, caps[i], joins[i], x, 50, x, 160, offset) Draw(gc, caps[i], joins[i], x, 50, x, 160, offset)
x += offset x += offset
@ -29,7 +29,7 @@ func Main(gc draw2d.GraphicContext, ext string) (string, error) {
} }
// Draw a line with an angle with specified line cap and join // Draw a line with an angle with specified line cap and join
func Draw(gc draw2d.GraphicContext, cap draw2d.Cap, join draw2d.Join, func Draw(gc draw2d.GraphicContext, cap draw2d.LineCap, join draw2d.LineJoin,
x0, y0, x1, y1, offset float64) { x0, y0, x1, y1, offset float64) {
gc.SetLineCap(cap) gc.SetLineCap(cap)
gc.SetLineJoin(join) gc.SetLineJoin(join)

View file

@ -1,135 +0,0 @@
// Copyright 2010 The draw2d Authors. All rights reserved.
// created: 13/12/2010 by Laurent Le Goff
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 float64
Cap Cap
Join Join
vertices []float64
rewind []float64
x, y, nx, ny float64
command VertexCommand
}
func NewLineStroker(c Cap, j Join, converter VertexConverter) *LineStroker {
l := new(LineStroker)
l.Next = converter
l.HalfLineWidth = 0.5
l.vertices = make([]float64, 0, 256)
l.rewind = make([]float64, 0, 256)
l.Cap = c
l.Join = j
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 = l.vertices[0:0]
l.rewind = l.rewind[0:0]
l.x, l.y, l.nx, l.ny = 0, 0, 0, 0
}
}
func (l *LineStroker) Vertex(x, y float64) {
switch l.command {
case VertexNoCommand:
l.line(l.x, l.y, x, y)
case VertexJoinCommand:
l.joinLine(l.x, l.y, l.nx, l.ny, x, y)
case VertexStartCommand:
l.x, l.y = 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) appendVertex(vertices ...float64) {
s := len(vertices) / 2
if len(l.vertices)+s >= cap(l.vertices) {
v := make([]float64, len(l.vertices), cap(l.vertices)+128)
copy(v, l.vertices)
l.vertices = v
v = make([]float64, len(l.rewind), cap(l.rewind)+128)
copy(v, l.rewind)
l.rewind = v
}
copy(l.vertices[len(l.vertices):len(l.vertices)+s], vertices[:s])
l.vertices = l.vertices[0 : len(l.vertices)+s]
copy(l.rewind[len(l.rewind):len(l.rewind)+s], vertices[s:])
l.rewind = l.rewind[0 : len(l.rewind)+s]
}
func (l *LineStroker) closePolygon() {
if len(l.vertices) > 1 {
l.appendVertex(l.vertices[0], l.vertices[1], l.rewind[0], l.rewind[1])
}
}
func (l *LineStroker) line(x1, y1, x2, y2 float64) {
dx := (x2 - x1)
dy := (y2 - y1)
d := vectorDistance(dx, dy)
if d != 0 {
nx := dy * l.HalfLineWidth / d
ny := -(dx * l.HalfLineWidth / d)
l.appendVertex(x1+nx, y1+ny, x2+nx, y2+ny, 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 float64) {
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.appendVertex(x1+nx, y1+ny, x2+nx, y2+ny, x1-nx, y1-ny, x2-nx, y2-ny)
l.x, l.y, l.nx, l.ny = x2, y2, nx, ny
}
}

View file

@ -7,6 +7,7 @@ import (
"testing" "testing"
"github.com/llgcode/draw2d" "github.com/llgcode/draw2d"
"github.com/llgcode/draw2d/draw2dimg"
) )
type sample func(gc draw2d.GraphicContext, ext string) (string, error) type sample func(gc draw2d.GraphicContext, ext string) (string, error)
@ -14,7 +15,7 @@ type sample func(gc draw2d.GraphicContext, ext string) (string, error)
func test(t *testing.T, draw sample) { func test(t *testing.T, draw sample) {
// Initialize the graphic context on an RGBA image // Initialize the graphic context on an RGBA image
dest := image.NewRGBA(image.Rect(0, 0, 297, 210.0)) dest := image.NewRGBA(image.Rect(0, 0, 297, 210.0))
gc := draw2d.NewGraphicContext(dest) gc := draw2dimg.NewGraphicContext(dest)
// Draw Android logo // Draw Android logo
output, err := draw(gc, "png") output, err := draw(gc, "png")
if err != nil { if err != nil {
@ -22,7 +23,7 @@ func test(t *testing.T, draw sample) {
return return
} }
// Save to png // Save to png
err = draw2d.SaveToPngFile(output, dest) err = draw2dimg.SaveToPngFile(output, dest)
if err != nil { if err != nil {
t.Errorf("Saving %q failed: %v", output, err) t.Errorf("Saving %q failed: %v", output, err)
} }

View file

@ -1,307 +0,0 @@
// Copyright 2010 The draw2d Authors. All rights reserved.
// created: 21/11/2010 by Laurent Le Goff
package draw2d
import (
"math"
"code.google.com/p/freetype-go/freetype/raster"
)
type MatrixTransform [6]float64
const (
epsilon = 1e-6
)
func (tr MatrixTransform) Determinant() float64 {
return tr[0]*tr[3] - tr[1]*tr[2]
}
func (tr MatrixTransform) Transform(points ...*float64) {
for i, j := 0, 1; j < len(points); i, j = i+2, j+2 {
x := *points[i]
y := *points[j]
*points[i] = x*tr[0] + y*tr[2] + tr[4]
*points[j] = x*tr[1] + y*tr[3] + tr[5]
}
}
func (tr MatrixTransform) TransformArray(points []float64) {
for i, j := 0, 1; j < len(points); i, j = i+2, j+2 {
x := points[i]
y := points[j]
points[i] = x*tr[0] + y*tr[2] + tr[4]
points[j] = x*tr[1] + y*tr[3] + tr[5]
}
}
func (tr MatrixTransform) TransformRectangle(x0, y0, x2, y2 *float64) {
x1 := *x2
y1 := *y0
x3 := *x0
y3 := *y2
tr.Transform(x0, y0, &x1, &y1, x2, y2, &x3, &y3)
*x0, x1 = minMax(*x0, x1)
*x2, x3 = minMax(*x2, x3)
*y0, y1 = minMax(*y0, y1)
*y2, y3 = minMax(*y2, y3)
*x0 = min(*x0, *x2)
*y0 = min(*y0, *y2)
*x2 = max(x1, x3)
*y2 = max(y1, y3)
}
func (tr MatrixTransform) TransformRasterPoint(points ...*raster.Point) {
for _, point := range points {
x := float64(point.X) / 256
y := float64(point.Y) / 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)
}
}
func (tr MatrixTransform) InverseTransform(points ...*float64) {
d := tr.Determinant() // matrix determinant
for i, j := 0, 1; j < len(points); i, j = i+2, j+2 {
x := *points[i]
y := *points[j]
*points[i] = ((x-tr[4])*tr[3] - (y-tr[5])*tr[2]) / d
*points[j] = ((y-tr[5])*tr[0] - (x-tr[4])*tr[1]) / d
}
}
// ******************** Vector transformations ********************
func (tr MatrixTransform) VectorTransform(points ...*float64) {
for i, j := 0, 1; j < len(points); i, j = i+2, j+2 {
x := *points[i]
y := *points[j]
*points[i] = x*tr[0] + y*tr[2]
*points[j] = x*tr[1] + y*tr[3]
}
}
// ******************** Transformations creation ********************
/** Creates an identity transformation. */
func NewIdentityMatrix() MatrixTransform {
return [6]float64{1, 0, 0, 1, 0, 0}
}
/**
* Creates a transformation with a translation, that,
* transform point1 into point2.
*/
func NewTranslationMatrix(tx, ty float64) MatrixTransform {
return [6]float64{1, 0, 0, 1, tx, ty}
}
/**
* Creates a transformation with a sx, sy scale factor
*/
func NewScaleMatrix(sx, sy float64) MatrixTransform {
return [6]float64{sx, 0, 0, sy, 0, 0}
}
/**
* Creates a rotation transformation.
*/
func NewRotationMatrix(angle float64) MatrixTransform {
c := math.Cos(angle)
s := math.Sin(angle)
return [6]float64{c, s, -s, c, 0, 0}
}
/**
* Creates a transformation, combining a scale and a translation, that transform rectangle1 into rectangle2.
*/
func NewMatrixTransform(rectangle1, rectangle2 [4]float64) MatrixTransform {
xScale := (rectangle2[2] - rectangle2[0]) / (rectangle1[2] - rectangle1[0])
yScale := (rectangle2[3] - rectangle2[1]) / (rectangle1[3] - rectangle1[1])
xOffset := rectangle2[0] - (rectangle1[0] * xScale)
yOffset := rectangle2[1] - (rectangle1[1] * yScale)
return [6]float64{xScale, 0, 0, yScale, xOffset, yOffset}
}
// ******************** Transformations operations ********************
/**
* Returns a transformation that is the inverse of the given transformation.
*/
func (tr MatrixTransform) GetInverseTransformation() MatrixTransform {
d := tr.Determinant() // matrix determinant
return [6]float64{
tr[3] / d,
-tr[1] / d,
-tr[2] / d,
tr[0] / d,
(tr[2]*tr[5] - tr[3]*tr[4]) / d,
(tr[1]*tr[4] - tr[0]*tr[5]) / d}
}
func (tr1 MatrixTransform) Multiply(tr2 MatrixTransform) MatrixTransform {
return [6]float64{
tr1[0]*tr2[0] + tr1[1]*tr2[2],
tr1[1]*tr2[3] + tr1[0]*tr2[1],
tr1[2]*tr2[0] + tr1[3]*tr2[2],
tr1[3]*tr2[3] + tr1[2]*tr2[1],
tr1[4]*tr2[0] + tr1[5]*tr2[2] + tr2[4],
tr1[5]*tr2[3] + tr1[4]*tr2[1] + tr2[5]}
}
func (tr *MatrixTransform) Scale(sx, sy float64) *MatrixTransform {
tr[0] = sx * tr[0]
tr[1] = sx * tr[1]
tr[2] = sy * tr[2]
tr[3] = sy * tr[3]
return tr
}
func (tr *MatrixTransform) Translate(tx, ty float64) *MatrixTransform {
tr[4] = tx*tr[0] + ty*tr[2] + tr[4]
tr[5] = ty*tr[3] + tx*tr[1] + tr[5]
return tr
}
func (tr *MatrixTransform) Rotate(angle float64) *MatrixTransform {
c := math.Cos(angle)
s := math.Sin(angle)
t0 := c*tr[0] + s*tr[2]
t1 := s*tr[3] + c*tr[1]
t2 := c*tr[2] - s*tr[0]
t3 := c*tr[3] - s*tr[1]
tr[0] = t0
tr[1] = t1
tr[2] = t2
tr[3] = t3
return tr
}
func (tr MatrixTransform) GetTranslation() (x, y float64) {
return tr[4], tr[5]
}
func (tr MatrixTransform) GetScaling() (x, y float64) {
return tr[0], tr[3]
}
func (tr MatrixTransform) GetScale() float64 {
x := 0.707106781*tr[0] + 0.707106781*tr[1]
y := 0.707106781*tr[2] + 0.707106781*tr[3]
return math.Sqrt(x*x + y*y)
}
func (tr MatrixTransform) GetMaxAbsScaling() (s float64) {
sx := math.Abs(tr[0])
sy := math.Abs(tr[3])
if sx > sy {
return sx
}
return sy
}
func (tr MatrixTransform) GetMinAbsScaling() (s float64) {
sx := math.Abs(tr[0])
sy := math.Abs(tr[3])
if sx > sy {
return sy
}
return sx
}
// ******************** Testing ********************
/**
* Tests if a two transformation are equal. A tolerance is applied when
* comparing matrix elements.
*/
func (tr1 MatrixTransform) Equals(tr2 MatrixTransform) bool {
for i := 0; i < 6; i = i + 1 {
if !fequals(tr1[i], tr2[i]) {
return false
}
}
return true
}
/**
* Tests if a transformation is the identity transformation. A tolerance
* is applied when comparing matrix elements.
*/
func (tr MatrixTransform) IsIdentity() bool {
return fequals(tr[4], 0) && fequals(tr[5], 0) && tr.IsTranslation()
}
/**
* Tests if a transformation is is a pure translation. A tolerance
* is applied when comparing matrix elements.
*/
func (tr MatrixTransform) IsTranslation() bool {
return fequals(tr[0], 1) && fequals(tr[1], 0) && fequals(tr[2], 0) && fequals(tr[3], 1)
}
/**
* Compares two floats.
* return true if the distance between the two floats is less than epsilon, false otherwise
*/
func fequals(float1, float2 float64) bool {
return math.Abs(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 float64) {
u := x*vmt.tr[0] + y*vmt.tr[2] + vmt.tr[4]
v := x*vmt.tr[1] + y*vmt.tr[3] + vmt.tr[5]
vmt.Next.Vertex(u, v)
}
// this adder apply a Matrix transformation to points
type MatrixTransformAdder struct {
tr MatrixTransform
next raster.Adder
}
func NewMatrixTransformAdder(tr MatrixTransform, adder raster.Adder) *MatrixTransformAdder {
return &MatrixTransformAdder{tr, adder}
}
// Start starts a new curve at the given point.
func (mta MatrixTransformAdder) Start(a raster.Point) {
mta.tr.TransformRasterPoint(&a)
mta.next.Start(a)
}
// Add1 adds a linear segment to the current curve.
func (mta MatrixTransformAdder) Add1(b raster.Point) {
mta.tr.TransformRasterPoint(&b)
mta.next.Add1(b)
}
// Add2 adds a quadratic segment to the current curve.
func (mta MatrixTransformAdder) Add2(b, c raster.Point) {
mta.tr.TransformRasterPoint(&b, &c)
mta.next.Add2(b, c)
}
// Add3 adds a cubic segment to the current curve.
func (mta MatrixTransformAdder) Add3(b, c, d raster.Point) {
mta.tr.TransformRasterPoint(&b, &c, &d)
mta.next.Add3(b, c, d)
}

View file

@ -1,26 +0,0 @@
// Copyright 2010 The draw2d Authors. All rights reserved.
// created: 21/11/2010 by Laurent Le Goff
package draw2d
// VertexCommand defines different commands to describe the vertex of a path.
type VertexCommand byte
const (
// VertexNoCommand does nothing
VertexNoCommand VertexCommand = iota
// VertexStartCommand starts a (sub)path
VertexStartCommand
// VertexJoinCommand joins the two edges at the vertex
VertexJoinCommand
// VertexCloseCommand closes the subpath
VertexCloseCommand
// VertexStopCommand is the endpoint of the path.
VertexStopCommand
)
// VertexConverter allows to convert vertices.
type VertexConverter interface {
NextCommand(cmd VertexCommand)
Vertex(x, y float64)
}