17
.project
|
@ -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
|
@ -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)})
|
||||
}
|
||||
}
|
36
curve/arc.go
|
@ -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)
|
||||
}
|
|
@ -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++
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
@ -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)
|
||||
|
||||
}
|
|
@ -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
|
@ -83,3 +83,162 @@
|
|||
//
|
||||
// - https://github.com/vdobler/chart: basic charts in Go
|
||||
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
|
@ -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
|
@ -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
|
||||
}
|
|
@ -1,51 +1,48 @@
|
|||
// Copyright 2010 The draw2d Authors. All rights reserved.
|
||||
// created: 13/12/2010 by Laurent Le Goff
|
||||
|
||||
package draw2d
|
||||
package draw2dbase
|
||||
|
||||
type DashVertexConverter struct {
|
||||
command VertexCommand
|
||||
next VertexConverter
|
||||
next Flattener
|
||||
x, y, distance float64
|
||||
dash []float64
|
||||
currentDash int
|
||||
dashOffset float64
|
||||
}
|
||||
|
||||
func NewDashConverter(dash []float64, dashOffset float64, converter VertexConverter) *DashVertexConverter {
|
||||
func NewDashConverter(dash []float64, dashOffset float64, flattener Flattener) *DashVertexConverter {
|
||||
var dasher DashVertexConverter
|
||||
dasher.dash = dash
|
||||
dasher.currentDash = 0
|
||||
dasher.dashOffset = dashOffset
|
||||
dasher.next = converter
|
||||
dasher.next = flattener
|
||||
return &dasher
|
||||
}
|
||||
|
||||
func (dasher *DashVertexConverter) NextCommand(cmd VertexCommand) {
|
||||
dasher.command = cmd
|
||||
if dasher.command == VertexStopCommand {
|
||||
dasher.next.NextCommand(VertexStopCommand)
|
||||
}
|
||||
func (dasher *DashVertexConverter) LineTo(x, y float64) {
|
||||
dasher.lineTo(x, y)
|
||||
}
|
||||
|
||||
func (dasher *DashVertexConverter) Vertex(x, y float64) {
|
||||
switch dasher.command {
|
||||
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)
|
||||
func (dasher *DashVertexConverter) MoveTo(x, y float64) {
|
||||
dasher.next.MoveTo(x, y)
|
||||
dasher.x, dasher.y = x, y
|
||||
dasher.distance = dasher.dashOffset
|
||||
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) {
|
||||
rest := dasher.dash[dasher.currentDash] - dasher.distance
|
||||
for rest < 0 {
|
||||
|
@ -60,12 +57,11 @@ func (dasher *DashVertexConverter) lineTo(x, y float64) {
|
|||
ly := dasher.y + k*(y-dasher.y)
|
||||
if dasher.currentDash%2 == 0 {
|
||||
// line
|
||||
dasher.next.Vertex(lx, ly)
|
||||
dasher.next.LineTo(lx, ly)
|
||||
} else {
|
||||
// gap
|
||||
dasher.next.NextCommand(VertexStopCommand)
|
||||
dasher.next.NextCommand(VertexStartCommand)
|
||||
dasher.next.Vertex(lx, ly)
|
||||
dasher.next.End()
|
||||
dasher.next.MoveTo(lx, ly)
|
||||
}
|
||||
d = d - rest
|
||||
dasher.x, dasher.y = lx, ly
|
||||
|
@ -75,12 +71,11 @@ func (dasher *DashVertexConverter) lineTo(x, y float64) {
|
|||
dasher.distance = d
|
||||
if dasher.currentDash%2 == 0 {
|
||||
// line
|
||||
dasher.next.Vertex(x, y)
|
||||
dasher.next.LineTo(x, y)
|
||||
} else {
|
||||
// gap
|
||||
dasher.next.NextCommand(VertexStopCommand)
|
||||
dasher.next.NextCommand(VertexStartCommand)
|
||||
dasher.next.Vertex(x, y)
|
||||
dasher.next.End()
|
||||
dasher.next.MoveTo(x, y)
|
||||
}
|
||||
if 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
|
||||
}
|
||||
|
||||
func distance(x1, y1, x2, y2 float64) float64 {
|
||||
return vectorDistance(x2-x1, y2-y1)
|
||||
}
|
35
draw2dbase/demux_flattener.go
Normal 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
|
@ -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
|
||||
}
|
|
@ -1,39 +1,43 @@
|
|||
// Copyright 2010 The draw2d Authors. All rights reserved.
|
||||
// created: 21/11/2010 by Laurent Le Goff
|
||||
|
||||
package draw2d
|
||||
package draw2dbase
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
|
||||
"github.com/llgcode/draw2d"
|
||||
|
||||
"code.google.com/p/freetype-go/freetype/truetype"
|
||||
)
|
||||
|
||||
var DefaultFontData = draw2d.FontData{Name: "luxi", Family: draw2d.FontFamilySans, Style: draw2d.FontStyleNormal}
|
||||
|
||||
type StackGraphicContext struct {
|
||||
Current *ContextStack
|
||||
}
|
||||
|
||||
type ContextStack struct {
|
||||
Tr MatrixTransform
|
||||
Path *PathStorage
|
||||
Tr draw2d.Matrix
|
||||
Path *draw2d.Path
|
||||
LineWidth float64
|
||||
Dash []float64
|
||||
DashOffset float64
|
||||
StrokeColor color.Color
|
||||
FillColor color.Color
|
||||
FillRule FillRule
|
||||
Cap Cap
|
||||
Join Join
|
||||
FillRule draw2d.FillRule
|
||||
Cap draw2d.LineCap
|
||||
Join draw2d.LineJoin
|
||||
FontSize float64
|
||||
FontData FontData
|
||||
FontData draw2d.FontData
|
||||
|
||||
Font *truetype.Font
|
||||
// fontSize and dpi are used to calculate scale. scale is the number of
|
||||
// 26.6 fixed point units in 1 em.
|
||||
Scale float64
|
||||
|
||||
previous *ContextStack
|
||||
Previous *ContextStack
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -42,41 +46,41 @@ type ContextStack struct {
|
|||
func NewStackGraphicContext() *StackGraphicContext {
|
||||
gc := &StackGraphicContext{}
|
||||
gc.Current = new(ContextStack)
|
||||
gc.Current.Tr = NewIdentityMatrix()
|
||||
gc.Current.Path = NewPathStorage()
|
||||
gc.Current.Tr = draw2d.NewIdentityMatrix()
|
||||
gc.Current.Path = new(draw2d.Path)
|
||||
gc.Current.LineWidth = 1.0
|
||||
gc.Current.StrokeColor = image.Black
|
||||
gc.Current.FillColor = image.White
|
||||
gc.Current.Cap = RoundCap
|
||||
gc.Current.FillRule = FillRuleEvenOdd
|
||||
gc.Current.Join = RoundJoin
|
||||
gc.Current.Cap = draw2d.RoundCap
|
||||
gc.Current.FillRule = draw2d.FillRuleEvenOdd
|
||||
gc.Current.Join = draw2d.RoundJoin
|
||||
gc.Current.FontSize = 10
|
||||
gc.Current.FontData = defaultFontData
|
||||
gc.Current.FontData = DefaultFontData
|
||||
return gc
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) GetMatrixTransform() MatrixTransform {
|
||||
func (gc *StackGraphicContext) GetMatrixTransform() draw2d.Matrix {
|
||||
return gc.Current.Tr
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) SetMatrixTransform(Tr MatrixTransform) {
|
||||
func (gc *StackGraphicContext) SetMatrixTransform(Tr draw2d.Matrix) {
|
||||
gc.Current.Tr = Tr
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) ComposeMatrixTransform(Tr MatrixTransform) {
|
||||
gc.Current.Tr = Tr.Multiply(gc.Current.Tr)
|
||||
func (gc *StackGraphicContext) ComposeMatrixTransform(Tr draw2d.Matrix) {
|
||||
gc.Current.Tr.Compose(Tr)
|
||||
}
|
||||
|
||||
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) {
|
||||
gc.Current.Tr = NewTranslationMatrix(tx, ty).Multiply(gc.Current.Tr)
|
||||
gc.Current.Tr.Translate(tx, ty)
|
||||
}
|
||||
|
||||
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) {
|
||||
|
@ -87,45 +91,45 @@ func (gc *StackGraphicContext) SetFillColor(c color.Color) {
|
|||
gc.Current.FillColor = c
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) SetFillRule(f FillRule) {
|
||||
func (gc *StackGraphicContext) SetFillRule(f draw2d.FillRule) {
|
||||
gc.Current.FillRule = f
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) SetLineWidth(LineWidth float64) {
|
||||
gc.Current.LineWidth = LineWidth
|
||||
func (gc *StackGraphicContext) SetLineWidth(lineWidth float64) {
|
||||
gc.Current.LineWidth = lineWidth
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) SetLineCap(Cap Cap) {
|
||||
gc.Current.Cap = Cap
|
||||
func (gc *StackGraphicContext) SetLineCap(cap draw2d.LineCap) {
|
||||
gc.Current.Cap = cap
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) SetLineJoin(Join Join) {
|
||||
gc.Current.Join = Join
|
||||
func (gc *StackGraphicContext) SetLineJoin(join draw2d.LineJoin) {
|
||||
gc.Current.Join = join
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) SetLineDash(Dash []float64, DashOffset float64) {
|
||||
gc.Current.Dash = Dash
|
||||
gc.Current.DashOffset = DashOffset
|
||||
func (gc *StackGraphicContext) SetLineDash(dash []float64, dashOffset float64) {
|
||||
gc.Current.Dash = dash
|
||||
gc.Current.DashOffset = dashOffset
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) SetFontSize(FontSize float64) {
|
||||
gc.Current.FontSize = FontSize
|
||||
func (gc *StackGraphicContext) SetFontSize(fontSize float64) {
|
||||
gc.Current.FontSize = fontSize
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) GetFontSize() float64 {
|
||||
return gc.Current.FontSize
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) SetFontData(FontData FontData) {
|
||||
gc.Current.FontData = FontData
|
||||
func (gc *StackGraphicContext) SetFontData(fontData draw2d.FontData) {
|
||||
gc.Current.FontData = fontData
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) GetFontData() FontData {
|
||||
func (gc *StackGraphicContext) GetFontData() draw2d.FontData {
|
||||
return gc.Current.FontData
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) BeginPath() {
|
||||
gc.Current.Path = NewPathStorage()
|
||||
gc.Current.Path.Clear()
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) IsEmpty() bool {
|
||||
|
@ -140,42 +144,22 @@ func (gc *StackGraphicContext) MoveTo(x, y float64) {
|
|||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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() {
|
||||
gc.Current.Path.Close()
|
||||
}
|
||||
|
@ -196,14 +180,14 @@ func (gc *StackGraphicContext) Save() {
|
|||
context.Font = gc.Current.Font
|
||||
context.Scale = gc.Current.Scale
|
||||
copy(context.Tr[:], gc.Current.Tr[:])
|
||||
context.previous = gc.Current
|
||||
context.Previous = gc.Current
|
||||
gc.Current = context
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) Restore() {
|
||||
if gc.Current.previous != nil {
|
||||
if gc.Current.Previous != nil {
|
||||
oldContext := gc.Current
|
||||
gc.Current = gc.Current.previous
|
||||
oldContext.previous = nil
|
||||
gc.Current = gc.Current.Previous
|
||||
oldContext.Previous = nil
|
||||
}
|
||||
}
|
90
draw2dbase/stroker.go
Normal 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))
|
||||
}
|
|
@ -9,6 +9,8 @@ import (
|
|||
"code.google.com/p/freetype-go/freetype/raster"
|
||||
"github.com/go-gl/gl/v2.1/gl"
|
||||
"github.com/llgcode/draw2d"
|
||||
"github.com/llgcode/draw2d/draw2dbase"
|
||||
"github.com/llgcode/draw2d/draw2dimg"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@ -112,7 +114,7 @@ func NewPainter() *Painter {
|
|||
}
|
||||
|
||||
type GraphicContext struct {
|
||||
*draw2d.StackGraphicContext
|
||||
*draw2dbase.StackGraphicContext
|
||||
painter *Painter
|
||||
fillRasterizer *raster.Rasterizer
|
||||
strokeRasterizer *raster.Rasterizer
|
||||
|
@ -121,7 +123,7 @@ type GraphicContext struct {
|
|||
// NewGraphicContext creates a new Graphic context from an image.
|
||||
func NewGraphicContext(width, height int) *GraphicContext {
|
||||
gc := &GraphicContext{
|
||||
draw2d.NewStackGraphicContext(),
|
||||
draw2dbase.NewStackGraphicContext(),
|
||||
NewPainter(),
|
||||
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.Clear()
|
||||
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)
|
||||
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
|
||||
var pathConverter *draw2d.PathConverter
|
||||
|
||||
var liner draw2dbase.Flattener
|
||||
if gc.Current.Dash != nil && len(gc.Current.Dash) > 0 {
|
||||
dasher := draw2d.NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker)
|
||||
pathConverter = draw2d.NewPathConverter(dasher)
|
||||
liner = draw2dbase.NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker)
|
||||
} 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.Current.Path = draw2d.NewPathStorage()
|
||||
}
|
||||
|
||||
func (gc *GraphicContext) Fill(paths ...*draw2d.PathStorage) {
|
||||
func (gc *GraphicContext) Fill(paths ...*draw2d.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)))
|
||||
pathConverter.ApproximationScale = gc.Current.Tr.GetScale() // From agg code
|
||||
pathConverter.Convert(paths...)
|
||||
/**** first method ****/
|
||||
flattener := draw2dbase.Transformer{Tr: gc.Current.Tr, Flattener: draw2dimg.FtLineBuilder{Adder: gc.fillRasterizer}}
|
||||
for _, p := range paths {
|
||||
draw2dbase.Flatten(p, flattener, gc.Current.Tr.GetScale())
|
||||
}
|
||||
|
||||
gc.paint(gc.fillRasterizer, gc.Current.FillColor)
|
||||
gc.Current.Path = draw2d.NewPathStorage()
|
||||
}
|
||||
|
||||
func (gc *GraphicContext) FillStroke(paths ...*draw2d.PathStorage) {
|
||||
gc.fillRasterizer.UseNonZeroWinding = gc.Current.FillRule.UseNonZeroWinding()
|
||||
func (gc *GraphicContext) FillStroke(paths ...*draw2d.Path) {
|
||||
paths = append(paths, gc.Current.Path)
|
||||
gc.fillRasterizer.UseNonZeroWinding = useNonZeroWinding(gc.Current.FillRule)
|
||||
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
|
||||
|
||||
demux := draw2d.NewDemuxConverter(filler, stroker)
|
||||
paths = append(paths, gc.Current.Path)
|
||||
pathConverter := draw2d.NewPathConverter(demux)
|
||||
pathConverter.ApproximationScale = gc.Current.Tr.GetScale() // From agg code
|
||||
pathConverter.Convert(paths...)
|
||||
var liner draw2dbase.Flattener
|
||||
if gc.Current.Dash != nil && len(gc.Current.Dash) > 0 {
|
||||
liner = draw2dbase.NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker)
|
||||
} else {
|
||||
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)
|
||||
// Stroke
|
||||
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
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package draw2d
|
||||
package draw2dimg
|
||||
|
||||
import (
|
||||
"bufio"
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright 2010 The draw2d Authors. All rights reserved.
|
||||
// created: 21/11/2010 by Laurent Le Goff
|
||||
|
||||
package draw2d
|
||||
package draw2dimg
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
@ -11,21 +11,22 @@ import (
|
|||
"log"
|
||||
"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/truetype"
|
||||
)
|
||||
|
||||
// Painter implements the freetype raster.Painter and has a SetColor method like the RGBAPainter
|
||||
type Painter interface {
|
||||
raster.Painter
|
||||
SetColor(color color.Color)
|
||||
}
|
||||
|
||||
var (
|
||||
defaultFontData = FontData{"luxi", FontFamilySans, FontStyleNormal}
|
||||
)
|
||||
|
||||
type ImageGraphicContext struct {
|
||||
*StackGraphicContext
|
||||
// GraphicContext is the implementation of draw2d.GraphicContext for a raster image
|
||||
type GraphicContext struct {
|
||||
*draw2dbase.StackGraphicContext
|
||||
img draw.Image
|
||||
painter Painter
|
||||
fillRasterizer *raster.Rasterizer
|
||||
|
@ -35,7 +36,8 @@ type ImageGraphicContext struct {
|
|||
}
|
||||
|
||||
// NewGraphicContext creates a new Graphic context from an image.
|
||||
func NewGraphicContext(img draw.Image) *ImageGraphicContext {
|
||||
func NewGraphicContext(img draw.Image) *GraphicContext {
|
||||
|
||||
var painter Painter
|
||||
switch selectImage := img.(type) {
|
||||
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)
|
||||
func NewGraphicContextWithPainter(img draw.Image, painter Painter) *ImageGraphicContext {
|
||||
func NewGraphicContextWithPainter(img draw.Image, painter Painter) *GraphicContext {
|
||||
width, height := img.Bounds().Dx(), img.Bounds().Dy()
|
||||
dpi := 92
|
||||
gc := &ImageGraphicContext{
|
||||
NewStackGraphicContext(),
|
||||
gc := &GraphicContext{
|
||||
draw2dbase.NewStackGraphicContext(),
|
||||
img,
|
||||
painter,
|
||||
raster.NewRasterizer(width, height),
|
||||
|
@ -62,48 +64,56 @@ func NewGraphicContextWithPainter(img draw.Image, painter Painter) *ImageGraphic
|
|||
return gc
|
||||
}
|
||||
|
||||
func (gc *ImageGraphicContext) GetDPI() int {
|
||||
// GetDPI returns the resolution of the Image GraphicContext
|
||||
func (gc *GraphicContext) GetDPI() int {
|
||||
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()
|
||||
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)
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
gc.Fill()
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
gc.Stroke()
|
||||
return width
|
||||
}
|
||||
|
||||
func (gc *ImageGraphicContext) loadCurrentFont() (*truetype.Font, error) {
|
||||
font := GetFont(gc.Current.FontData)
|
||||
func (gc *GraphicContext) loadCurrentFont() (*truetype.Font, error) {
|
||||
font := draw2d.GetFont(gc.Current.FontData)
|
||||
if font == nil {
|
||||
font = GetFont(defaultFontData)
|
||||
font = draw2d.GetFont(draw2dbase.DefaultFontData)
|
||||
}
|
||||
if font == nil {
|
||||
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
|
||||
}
|
||||
|
||||
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.
|
||||
// The returned value is the same thing measured in floating point and positive Y
|
||||
// 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 *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 {
|
||||
func (gc *GraphicContext) 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 {
|
||||
return err
|
||||
}
|
||||
e0 := 0
|
||||
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
|
||||
}
|
||||
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.
|
||||
// For example, drawing a string that starts with a 'J' in an italic font may
|
||||
// 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()
|
||||
if err != nil {
|
||||
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.
|
||||
// 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.
|
||||
// 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()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
|
@ -219,6 +185,7 @@ func (gc *ImageGraphicContext) GetStringBounds(s string) (left, top, right, bott
|
|||
for _, rune := range s {
|
||||
index := font.Index(rune)
|
||||
if hasPrev {
|
||||
|
||||
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 {
|
||||
|
@ -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
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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.recalc()
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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.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)
|
||||
rasterizer.Rasterize(gc.painter)
|
||||
rasterizer.Clear()
|
||||
gc.Current.Path = NewPathStorage()
|
||||
gc.Current.Path.Clear()
|
||||
}
|
||||
|
||||
// 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)
|
||||
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
|
||||
var pathConverter *PathConverter
|
||||
|
||||
var liner draw2dbase.Flattener
|
||||
if gc.Current.Dash != nil && len(gc.Current.Dash) > 0 {
|
||||
dasher := NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker)
|
||||
pathConverter = NewPathConverter(dasher)
|
||||
liner = draw2dbase.NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker)
|
||||
} 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)
|
||||
}
|
||||
|
||||
// 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)
|
||||
gc.fillRasterizer.UseNonZeroWinding = gc.Current.FillRule.UseNonZeroWinding()
|
||||
gc.fillRasterizer.UseNonZeroWinding = gc.Current.FillRule == draw2d.FillRuleWinding
|
||||
|
||||
/**** first method ****/
|
||||
pathConverter := NewPathConverter(NewVertexMatrixTransform(gc.Current.Tr, NewVertexAdder(gc.fillRasterizer)))
|
||||
pathConverter.ApproximationScale = gc.Current.Tr.GetScale()
|
||||
pathConverter.Convert(paths...)
|
||||
flattener := draw2dbase.Transformer{Tr: gc.Current.Tr, Flattener: FtLineBuilder{Adder: gc.fillRasterizer}}
|
||||
for _, p := range paths {
|
||||
draw2dbase.Flatten(p, flattener, gc.Current.Tr.GetScale())
|
||||
}
|
||||
|
||||
gc.paint(gc.fillRasterizer, gc.Current.FillColor)
|
||||
}
|
||||
|
||||
// FillStroke first fills the paths and than strokes them
|
||||
func (gc *ImageGraphicContext) FillStroke(paths ...*PathStorage) {
|
||||
gc.fillRasterizer.UseNonZeroWinding = gc.Current.FillRule.UseNonZeroWinding()
|
||||
func (gc *GraphicContext) FillStroke(paths ...*draw2d.Path) {
|
||||
paths = append(paths, gc.Current.Path)
|
||||
gc.fillRasterizer.UseNonZeroWinding = gc.Current.FillRule == draw2d.FillRuleWinding
|
||||
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
|
||||
|
||||
demux := NewDemuxConverter(filler, stroker)
|
||||
paths = append(paths, gc.Current.Path)
|
||||
pathConverter := NewPathConverter(demux)
|
||||
pathConverter.ApproximationScale = gc.Current.Tr.GetScale()
|
||||
pathConverter.Convert(paths...)
|
||||
var liner draw2dbase.Flattener
|
||||
if gc.Current.Dash != nil && len(gc.Current.Dash) > 0 {
|
||||
liner = draw2dbase.NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker)
|
||||
} else {
|
||||
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)
|
||||
// Stroke
|
||||
gc.paint(gc.strokeRasterizer, gc.Current.StrokeColor)
|
||||
}
|
||||
|
||||
func (f FillRule) UseNonZeroWinding() bool {
|
||||
switch f {
|
||||
case FillRuleEvenOdd:
|
||||
return false
|
||||
case FillRuleWinding:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c Cap) Convert() raster.Capper {
|
||||
func toFtCap(c draw2d.LineCap) raster.Capper {
|
||||
switch c {
|
||||
case RoundCap:
|
||||
case draw2d.RoundCap:
|
||||
return raster.RoundCapper
|
||||
case ButtCap:
|
||||
case draw2d.ButtCap:
|
||||
return raster.ButtCapper
|
||||
case SquareCap:
|
||||
case draw2d.SquareCap:
|
||||
return raster.SquareCapper
|
||||
}
|
||||
return raster.RoundCapper
|
||||
}
|
||||
|
||||
func (j Join) Convert() raster.Joiner {
|
||||
func toFtJoin(j draw2d.LineJoin) raster.Joiner {
|
||||
switch j {
|
||||
case RoundJoin:
|
||||
case draw2d.RoundJoin:
|
||||
return raster.RoundJoiner
|
||||
case BevelJoin:
|
||||
case draw2d.BevelJoin:
|
||||
return raster.BevelJoiner
|
||||
}
|
||||
return raster.RoundJoiner
|
29
draw2dimg/ftpath.go
Normal 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() {
|
||||
}
|
|
@ -2,25 +2,29 @@
|
|||
// created: 21/11/2010 by Laurent Le Goff
|
||||
// see http://pippin.gimp.org/image_processing/chap_resampling.html
|
||||
|
||||
package draw2d
|
||||
package draw2dimg
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
"image/draw"
|
||||
"math"
|
||||
|
||||
"github.com/llgcode/draw2d"
|
||||
)
|
||||
|
||||
// ImageFilter defines sampling filter (linear, bilinear or bicubic)
|
||||
// ImageFilter defines the type of filter to use
|
||||
type ImageFilter int
|
||||
|
||||
const (
|
||||
// LinearFilter uses linear interpolation
|
||||
// LinearFilter defines a linear filter
|
||||
LinearFilter ImageFilter = iota
|
||||
// BilinearFilter uses bilinear interpolation
|
||||
// BilinearFilter defines a bilinear filter
|
||||
BilinearFilter
|
||||
// BicubicFilter uses bicubic interpolation
|
||||
// BicubicFilter defines a bicubic filter
|
||||
BicubicFilter
|
||||
// M is the maximum value for a rgb component
|
||||
M = 1<<16 - 1
|
||||
)
|
||||
|
||||
//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)}
|
||||
}
|
||||
|
||||
/**
|
||||
-- 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)
|
||||
*/
|
||||
// lerp is a linear interpolation bertween 2 points
|
||||
func lerp(v1, v2, ratio float64) float64 {
|
||||
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)
|
||||
}
|
||||
|
||||
// DrawImage draws a source image on an destination image.
|
||||
func DrawImage(src image.Image, dest draw.Image, tr MatrixTransform, op draw.Op, filter ImageFilter) {
|
||||
// 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 draw2d.Matrix, op draw.Op, filter ImageFilter) {
|
||||
bounds := src.Bounds()
|
||||
x0, y0, x1, y1 := float64(bounds.Min.X), float64(bounds.Min.Y), float64(bounds.Max.X), float64(bounds.Max.Y)
|
||||
tr.TransformRectangle(&x0, &y0, &x1, &y1)
|
||||
x0, y0, x1, y1 := tr.TransformRectangle(float64(bounds.Min.X), float64(bounds.Min.Y), float64(bounds.Max.X), float64(bounds.Max.Y))
|
||||
var x, y, u, v float64
|
||||
var c1, c2, cr color.Color
|
||||
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++ {
|
||||
u = x
|
||||
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) {
|
||||
c1 = dest.At(int(x), int(y))
|
||||
switch filter {
|
80
draw2dimg/text.go
Normal 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,
|
||||
}
|
||||
}
|
|
@ -1,16 +1,17 @@
|
|||
// Copyright 2010 The draw2d Authors. All rights reserved.
|
||||
// created: 13/12/2010 by Laurent Le Goff
|
||||
|
||||
//high level path creation
|
||||
|
||||
package draw2d
|
||||
// Package draw2dkit provides helpers to draw common figures using a Path
|
||||
package draw2dkit
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/llgcode/draw2d"
|
||||
)
|
||||
|
||||
// Rect draws a rectangle between (x1,y1) and (x2,y2)
|
||||
func Rect(path Path, x1, y1, x2, y2 float64) {
|
||||
// Rectangle draws a rectangle using a path between (x1,y1) and (x2,y2)
|
||||
func Rectangle(path draw2d.PathBuilder, x1, y1, x2, y2 float64) {
|
||||
path.MoveTo(x1, y1)
|
||||
path.LineTo(x2, y1)
|
||||
path.LineTo(x2, y2)
|
||||
|
@ -18,8 +19,8 @@ func Rect(path Path, x1, y1, x2, y2 float64) {
|
|||
path.Close()
|
||||
}
|
||||
|
||||
// RoundRect draws a rectangle between (x1,y1) and (x2,y2) with rounded corners
|
||||
func RoundRect(path Path, x1, y1, x2, y2, arcWidth, arcHeight float64) {
|
||||
// RoundedRectangle draws a rectangle using a path between (x1,y1) and (x2,y2)
|
||||
func RoundedRectangle(path draw2d.PathBuilder, x1, y1, x2, y2, arcWidth, arcHeight float64) {
|
||||
arcWidth = arcWidth / 2
|
||||
arcHeight = arcHeight / 2
|
||||
path.MoveTo(x1, y1+arcHeight)
|
||||
|
@ -33,16 +34,14 @@ func RoundRect(path Path, x1, y1, x2, y2, arcWidth, arcHeight float64) {
|
|||
path.Close()
|
||||
}
|
||||
|
||||
// Ellipse is drawn with center (cx,cy) and radius (rx,ry)
|
||||
func Ellipse(path Path, cx, cy, rx, ry float64) {
|
||||
path.MoveTo(cx-rx, cy)
|
||||
// Ellipse draws an ellipse using a path with center (cx,cy) and radius (rx,ry)
|
||||
func Ellipse(path draw2d.PathBuilder, cx, cy, rx, ry float64) {
|
||||
path.ArcTo(cx, cy, rx, ry, 0, -math.Pi*2)
|
||||
path.Close()
|
||||
}
|
||||
|
||||
// Circle is drawn with center (cx,cy) and radius
|
||||
func Circle(path Path, cx, cy, radius float64) {
|
||||
path.MoveTo(cx-radius, cy)
|
||||
// Circle draws a circle using a path with center (cx,cy) and radius
|
||||
func Circle(path draw2d.PathBuilder, cx, cy, radius float64) {
|
||||
path.ArcTo(cx, cy, radius, radius, 0, -math.Pi*2)
|
||||
path.Close()
|
||||
}
|
74
draw2dkit/droid.go
Normal 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
|
@ -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)
|
||||
}
|
|
@ -18,6 +18,8 @@ import (
|
|||
|
||||
"github.com/jung-kurt/gofpdf"
|
||||
"github.com/llgcode/draw2d"
|
||||
"github.com/llgcode/draw2d/draw2dbase"
|
||||
"github.com/llgcode/draw2d/draw2dkit"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -27,11 +29,11 @@ const (
|
|||
)
|
||||
|
||||
var (
|
||||
caps = map[draw2d.Cap]string{
|
||||
caps = map[draw2d.LineCap]string{
|
||||
draw2d.RoundCap: "round",
|
||||
draw2d.ButtCap: "butt",
|
||||
draw2d.SquareCap: "square"}
|
||||
joins = map[draw2d.Join]string{
|
||||
joins = map[draw2d.LineJoin]string{
|
||||
draw2d.RoundJoin: "round",
|
||||
draw2d.BevelJoin: "bevel",
|
||||
draw2d.MiterJoin: "miter",
|
||||
|
@ -68,7 +70,7 @@ func clearRect(gc *GraphicContext, x1, y1, x2, y2 float64) {
|
|||
x, y := gc.pdf.GetXY()
|
||||
// cover page with white rectangle
|
||||
gc.SetFillColor(white)
|
||||
draw2d.Rect(gc, x1, y1, x2, y2)
|
||||
draw2dkit.Rectangle(gc, x1, y1, x2, y2)
|
||||
gc.Fill()
|
||||
// restore state
|
||||
gc.SetFillColor(f)
|
||||
|
@ -78,14 +80,14 @@ func clearRect(gc *GraphicContext, x1, y1, x2, y2 float64) {
|
|||
// GraphicContext implements the draw2d.GraphicContext interface
|
||||
// It provides draw2d with a pdf backend (based on gofpdf)
|
||||
type GraphicContext struct {
|
||||
*draw2d.StackGraphicContext
|
||||
*draw2dbase.StackGraphicContext
|
||||
pdf *gofpdf.Fpdf
|
||||
DPI int
|
||||
}
|
||||
|
||||
// NewGraphicContext creates a new pdf GraphicContext
|
||||
func NewGraphicContext(pdf *gofpdf.Fpdf) *GraphicContext {
|
||||
gc := &GraphicContext{draw2d.NewStackGraphicContext(), pdf, DPI}
|
||||
gc := &GraphicContext{draw2dbase.NewStackGraphicContext(), pdf, DPI}
|
||||
gc.SetDPI(DPI)
|
||||
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
|
||||
func (gc *GraphicContext) Stroke(paths ...*draw2d.PathStorage) {
|
||||
func (gc *GraphicContext) Stroke(paths ...*draw2d.Path) {
|
||||
_, _, _, alphaS := gc.Current.StrokeColor.RGBA()
|
||||
gc.draw("D", alphaS, paths...)
|
||||
gc.Current.Path = draw2d.NewPathStorage()
|
||||
gc.Current.Path.Clear()
|
||||
}
|
||||
|
||||
// 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"
|
||||
if !gc.Current.FillRule.UseNonZeroWinding() {
|
||||
if gc.Current.FillRule != draw2d.FillRuleWinding {
|
||||
style += "*"
|
||||
}
|
||||
_, _, _, alphaF := gc.Current.FillColor.RGBA()
|
||||
gc.draw(style, alphaF, paths...)
|
||||
gc.Current.Path = draw2d.NewPathStorage()
|
||||
gc.Current.Path.Clear()
|
||||
}
|
||||
|
||||
// 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
|
||||
if !gc.Current.FillRule.UseNonZeroWinding() {
|
||||
if gc.Current.FillRule != draw2d.FillRuleWinding {
|
||||
rule = "*"
|
||||
}
|
||||
_, _, _, alphaS := gc.Current.StrokeColor.RGBA()
|
||||
|
@ -220,7 +222,7 @@ func (gc *GraphicContext) FillStroke(paths ...*draw2d.PathStorage) {
|
|||
gc.draw("F"+rule, alphaF, paths...)
|
||||
gc.draw("S", alphaS, paths...)
|
||||
}
|
||||
gc.Current.Path = draw2d.NewPathStorage()
|
||||
gc.Current.Path.Clear()
|
||||
}
|
||||
|
||||
var logger = log.New(os.Stdout, "", log.Lshortfile)
|
||||
|
@ -228,10 +230,11 @@ var logger = log.New(os.Stdout, "", log.Lshortfile)
|
|||
const alphaMax = float64(0xFFFF)
|
||||
|
||||
// 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)
|
||||
pathConverter := NewPathConverter(gc.pdf)
|
||||
pathConverter.Convert(paths...)
|
||||
for _, p := range paths {
|
||||
ConvertPath(p, gc.pdf)
|
||||
}
|
||||
a := float64(alpha) / alphaMax
|
||||
current, blendMode := gc.pdf.GetAlpha()
|
||||
if a != current {
|
||||
|
@ -308,13 +311,13 @@ func (gc *GraphicContext) SetLineWidth(LineWidth float64) {
|
|||
}
|
||||
|
||||
// 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.pdf.SetLineCapStyle(caps[Cap])
|
||||
}
|
||||
|
||||
// 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.pdf.SetLineJoinStyle(joins[Join])
|
||||
}
|
||||
|
|
|
@ -11,50 +11,34 @@ import (
|
|||
|
||||
const deg = 180 / math.Pi
|
||||
|
||||
// PathConverter converts the paths to the pdf api
|
||||
type PathConverter struct {
|
||||
pdf Vectorizer
|
||||
}
|
||||
|
||||
// NewPathConverter constructs a PathConverter from a pdf vectorizer
|
||||
func NewPathConverter(pdf Vectorizer) *PathConverter {
|
||||
return &PathConverter{pdf: pdf}
|
||||
}
|
||||
|
||||
// Convert converts the paths to the pdf api
|
||||
func (c *PathConverter) Convert(paths ...*draw2d.PathStorage) {
|
||||
for _, path := range paths {
|
||||
j := 0
|
||||
for _, cmd := range path.Commands {
|
||||
j = j + c.ConvertCommand(cmd, path.Vertices[j:]...)
|
||||
// ConvertPath converts a paths to the pdf api
|
||||
func ConvertPath(path *draw2d.Path, pdf Vectorizer) {
|
||||
var startX, startY float64 = 0, 0
|
||||
i := 0
|
||||
for _, cmp := range path.Components {
|
||||
switch cmp {
|
||||
case draw2d.MoveToCmp:
|
||||
startX, startY = path.Points[i], path.Points[i+1]
|
||||
pdf.MoveTo(startX, startY)
|
||||
i += 2
|
||||
case draw2d.LineToCmp:
|
||||
pdf.LineTo(path.Points[i], path.Points[i+1])
|
||||
i += 2
|
||||
case draw2d.QuadCurveToCmp:
|
||||
pdf.CurveTo(path.Points[i], path.Points[i+1], path.Points[i+2], path.Points[i+3])
|
||||
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
|
@ -8,34 +8,38 @@ import (
|
|||
"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, ...)
|
||||
type GraphicContext interface {
|
||||
Path
|
||||
// Create a new path
|
||||
PathBuilder
|
||||
// BeginPath creates a new path
|
||||
BeginPath()
|
||||
GetMatrixTransform() MatrixTransform
|
||||
SetMatrixTransform(tr MatrixTransform)
|
||||
ComposeMatrixTransform(tr MatrixTransform)
|
||||
// GetMatrixTransform returns the current transformation matrix
|
||||
GetMatrixTransform() Matrix
|
||||
// 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)
|
||||
// Translate applies a translation to the current transformation matrix.
|
||||
Translate(tx, ty float64)
|
||||
// Scale applies a scale to the current transformation matrix.
|
||||
Scale(sx, sy float64)
|
||||
// SetStrokeColor sets the current stroke color
|
||||
SetStrokeColor(c color.Color)
|
||||
// SetStrokeColor sets the current fill color
|
||||
SetFillColor(c color.Color)
|
||||
// SetFillRule sets the current fill rule
|
||||
SetFillRule(f FillRule)
|
||||
// SetLineWidth sets the current line width
|
||||
SetLineWidth(lineWidth float64)
|
||||
SetLineCap(cap Cap)
|
||||
SetLineJoin(join Join)
|
||||
// SetLineCap sets the current line cap
|
||||
SetLineCap(cap LineCap)
|
||||
// SetLineJoin sets the current line join
|
||||
SetLineJoin(join LineJoin)
|
||||
// SetLineJoin sets the current dash
|
||||
SetLineDash(dash []float64, dashOffset float64)
|
||||
// SetFontSize
|
||||
SetFontSize(fontSize float64)
|
||||
GetFontSize() float64
|
||||
SetFontData(fontData FontData)
|
||||
|
@ -53,7 +57,7 @@ type GraphicContext interface {
|
|||
FillStringAt(text string, x, y float64) (cursor float64)
|
||||
StrokeString(text string) (cursor float64)
|
||||
StrokeStringAt(text string, x, y float64) (cursor float64)
|
||||
Stroke(paths ...*PathStorage)
|
||||
Fill(paths ...*PathStorage)
|
||||
FillStroke(paths ...*PathStorage)
|
||||
Stroke(paths ...*Path)
|
||||
Fill(paths ...*Path)
|
||||
FillStroke(paths ...*Path)
|
||||
}
|
||||
|
|
52
math.go
|
@ -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
|
@ -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
|
||||
}
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 41 KiB |
92
paint.go
|
@ -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
|
@ -3,37 +3,187 @@
|
|||
|
||||
package draw2d
|
||||
|
||||
// Path describes the interface for path drawing.
|
||||
type Path interface {
|
||||
// LastPoint returns the current point of the path
|
||||
import (
|
||||
"fmt"
|
||||
"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)
|
||||
// MoveTo creates a new subpath that start at the specified point
|
||||
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(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(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(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(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
|
||||
// point (if not the same) and mark the path as closed so the
|
||||
// first and last lines join nicely.
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
184
path_storage.go
|
@ -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
|
||||
}
|
|
@ -1,40 +1,17 @@
|
|||
package raster
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/png"
|
||||
"log"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"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
|
||||
|
||||
func savepng(filePath string, m image.Image) {
|
||||
f, err := os.Create(filePath)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer f.Close()
|
||||
b := bufio.NewWriter(f)
|
||||
err = png.Encode(b, m)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
err = b.Flush()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
type Path struct {
|
||||
points []float64
|
||||
}
|
||||
|
@ -54,8 +31,7 @@ func (p *Path) LineTo(x, y float64) {
|
|||
func TestFreetype(t *testing.T) {
|
||||
var p Path
|
||||
p.LineTo(10, 190)
|
||||
c := curve.CubicCurveFloat64{10, 190, 10, 10, 190, 10, 190, 190}
|
||||
c.Segment(&p, flatteningThreshold)
|
||||
draw2dbase.TraceCubic(&p, []float64{10, 190, 10, 10, 190, 10, 190, 190}, 0.5)
|
||||
poly := Polygon(p.points)
|
||||
color := color.RGBA{0, 0, 0, 0xff}
|
||||
|
||||
|
@ -74,14 +50,13 @@ func TestFreetype(t *testing.T) {
|
|||
painter.SetColor(color)
|
||||
rasterizer.Rasterize(painter)
|
||||
|
||||
savepng("../output/raster/TestFreetype.png", img)
|
||||
draw2dimg.SaveToPngFile("../output/raster/TestFreetype.png", img)
|
||||
}
|
||||
|
||||
func TestFreetypeNonZeroWinding(t *testing.T) {
|
||||
var p Path
|
||||
p.LineTo(10, 190)
|
||||
c := curve.CubicCurveFloat64{10, 190, 10, 10, 190, 10, 190, 190}
|
||||
c.Segment(&p, flatteningThreshold)
|
||||
draw2dbase.TraceCubic(&p, []float64{10, 190, 10, 10, 190, 10, 190, 190}, 0.5)
|
||||
poly := Polygon(p.points)
|
||||
color := color.RGBA{0, 0, 0, 0xff}
|
||||
|
||||
|
@ -100,15 +75,15 @@ func TestFreetypeNonZeroWinding(t *testing.T) {
|
|||
painter.SetColor(color)
|
||||
rasterizer.Rasterize(painter)
|
||||
|
||||
savepng("../output/raster/TestFreetypeNonZeroWinding.png", img)
|
||||
draw2dimg.SaveToPngFile("../output/raster/TestFreetypeNonZeroWinding.png", img)
|
||||
}
|
||||
|
||||
func TestRasterizer(t *testing.T) {
|
||||
img := image.NewRGBA(image.Rect(0, 0, 200, 200))
|
||||
var p Path
|
||||
p.LineTo(10, 190)
|
||||
c := curve.CubicCurveFloat64{10, 190, 10, 10, 190, 10, 190, 190}
|
||||
c.Segment(&p, flatteningThreshold)
|
||||
draw2dbase.TraceCubic(&p, []float64{10, 190, 10, 10, 190, 10, 190, 190}, 0.5)
|
||||
|
||||
poly := Polygon(p.points)
|
||||
color := color.RGBA{0, 0, 0, 0xff}
|
||||
tr := [6]float64{1, 0, 0, 1, 0, 0}
|
||||
|
@ -116,15 +91,15 @@ func TestRasterizer(t *testing.T) {
|
|||
//PolylineBresenham(img, image.Black, poly...)
|
||||
|
||||
r.RenderEvenOdd(img, &color, &poly, tr)
|
||||
savepng("../output/raster/TestRasterizer.png", img)
|
||||
draw2dimg.SaveToPngFile("../output/raster/TestRasterizer.png", img)
|
||||
}
|
||||
|
||||
func TestRasterizerNonZeroWinding(t *testing.T) {
|
||||
img := image.NewRGBA(image.Rect(0, 0, 200, 200))
|
||||
var p Path
|
||||
p.LineTo(10, 190)
|
||||
c := curve.CubicCurveFloat64{10, 190, 10, 10, 190, 10, 190, 190}
|
||||
c.Segment(&p, flatteningThreshold)
|
||||
draw2dbase.TraceCubic(&p, []float64{10, 190, 10, 10, 190, 10, 190, 190}, 0.5)
|
||||
|
||||
poly := Polygon(p.points)
|
||||
color := color.RGBA{0, 0, 0, 0xff}
|
||||
tr := [6]float64{1, 0, 0, 1, 0, 0}
|
||||
|
@ -132,14 +107,14 @@ func TestRasterizerNonZeroWinding(t *testing.T) {
|
|||
//PolylineBresenham(img, image.Black, poly...)
|
||||
|
||||
r.RenderNonZeroWinding(img, &color, &poly, tr)
|
||||
savepng("../output/raster/TestRasterizerNonZeroWinding.png", img)
|
||||
draw2dimg.SaveToPngFile("../output/raster/TestRasterizerNonZeroWinding.png", img)
|
||||
}
|
||||
|
||||
func BenchmarkFreetype(b *testing.B) {
|
||||
var p Path
|
||||
p.LineTo(10, 190)
|
||||
c := curve.CubicCurveFloat64{10, 190, 10, 10, 190, 10, 190, 190}
|
||||
c.Segment(&p, flatteningThreshold)
|
||||
draw2dbase.TraceCubic(&p, []float64{10, 190, 10, 10, 190, 10, 190, 190}, 0.5)
|
||||
|
||||
poly := Polygon(p.points)
|
||||
color := color.RGBA{0, 0, 0, 0xff}
|
||||
|
||||
|
@ -164,8 +139,8 @@ func BenchmarkFreetype(b *testing.B) {
|
|||
func BenchmarkFreetypeNonZeroWinding(b *testing.B) {
|
||||
var p Path
|
||||
p.LineTo(10, 190)
|
||||
c := curve.CubicCurveFloat64{10, 190, 10, 10, 190, 10, 190, 190}
|
||||
c.Segment(&p, flatteningThreshold)
|
||||
draw2dbase.TraceCubic(&p, []float64{10, 190, 10, 10, 190, 10, 190, 190}, 0.5)
|
||||
|
||||
poly := Polygon(p.points)
|
||||
color := color.RGBA{0, 0, 0, 0xff}
|
||||
|
||||
|
@ -190,8 +165,8 @@ func BenchmarkFreetypeNonZeroWinding(b *testing.B) {
|
|||
func BenchmarkRasterizerNonZeroWinding(b *testing.B) {
|
||||
var p Path
|
||||
p.LineTo(10, 190)
|
||||
c := curve.CubicCurveFloat64{10, 190, 10, 10, 190, 10, 190, 190}
|
||||
c.Segment(&p, flatteningThreshold)
|
||||
draw2dbase.TraceCubic(&p, []float64{10, 190, 10, 10, 190, 10, 190, 190}, 0.5)
|
||||
|
||||
poly := Polygon(p.points)
|
||||
color := color.RGBA{0, 0, 0, 0xff}
|
||||
tr := [6]float64{1, 0, 0, 1, 0, 0}
|
||||
|
@ -205,8 +180,8 @@ func BenchmarkRasterizerNonZeroWinding(b *testing.B) {
|
|||
func BenchmarkRasterizer(b *testing.B) {
|
||||
var p Path
|
||||
p.LineTo(10, 190)
|
||||
c := curve.CubicCurveFloat64{10, 190, 10, 10, 190, 10, 190, 190}
|
||||
c.Segment(&p, flatteningThreshold)
|
||||
draw2dbase.TraceCubic(&p, []float64{10, 190, 10, 10, 190, 10, 190, 190}, 0.5)
|
||||
|
||||
poly := Polygon(p.points)
|
||||
color := color.RGBA{0, 0, 0, 0xff}
|
||||
tr := [6]float64{1, 0, 0, 1, 0, 0}
|
||||
|
|
BIN
resource/result/TestAndroid.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
resource/result/TestBigPicture.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
resource/result/TestBubble.png
Normal file
After Width: | Height: | Size: 4.2 KiB |
BIN
resource/result/TestCurveRectangle.png
Normal file
After Width: | Height: | Size: 9.4 KiB |
BIN
resource/result/TestDash.png
Normal file
After Width: | Height: | Size: 4.7 KiB |
BIN
resource/result/TestDrawArc.png
Normal file
After Width: | Height: | Size: 5.2 KiB |
BIN
resource/result/TestDrawArcNegative.png
Normal file
After Width: | Height: | Size: 6.4 KiB |
BIN
resource/result/TestDrawCubicCurve.png
Normal file
After Width: | Height: | Size: 8.1 KiB |
BIN
resource/result/TestDrawImage.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
resource/result/TestFillString.png
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
resource/result/TestFillStroke.png
Normal file
After Width: | Height: | Size: 5.7 KiB |
BIN
resource/result/TestFillStyle.png
Normal file
After Width: | Height: | Size: 9.8 KiB |
BIN
resource/result/TestGopher.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
resource/result/TestLineCap.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
resource/result/TestLineJoin.png
Normal file
After Width: | Height: | Size: 3.6 KiB |
BIN
resource/result/TestMultiSegmentCaps.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
resource/result/TestPath.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
resource/result/TestPathTransform.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
resource/result/TestRoundRectangle.png
Normal file
After Width: | Height: | Size: 5 KiB |
BIN
resource/result/TestStar.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
resource/result/TestTransform.png
Normal file
After Width: | Height: | Size: 6.1 KiB |
|
@ -9,6 +9,7 @@ import (
|
|||
"math"
|
||||
|
||||
"github.com/llgcode/draw2d"
|
||||
"github.com/llgcode/draw2d/draw2dkit"
|
||||
"github.com/llgcode/draw2d/samples"
|
||||
)
|
||||
|
||||
|
@ -44,32 +45,32 @@ func Draw(gc draw2d.GraphicContext, x, y float64) {
|
|||
gc.Stroke()
|
||||
|
||||
// left eye
|
||||
draw2d.Circle(gc, x+60, y+45, 5)
|
||||
draw2dkit.Circle(gc, x+60, y+45, 5)
|
||||
gc.FillStroke()
|
||||
|
||||
// right eye
|
||||
draw2d.Circle(gc, x+100, y+45, 5)
|
||||
draw2dkit.Circle(gc, x+100, y+45, 5)
|
||||
gc.FillStroke()
|
||||
|
||||
// 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()
|
||||
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()
|
||||
|
||||
// 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()
|
||||
|
||||
// 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()
|
||||
|
||||
// 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()
|
||||
|
||||
// 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()
|
||||
}
|
||||
|
|
|
@ -8,6 +8,8 @@ import (
|
|||
"math"
|
||||
|
||||
"github.com/llgcode/draw2d"
|
||||
"github.com/llgcode/draw2d/draw2dimg"
|
||||
"github.com/llgcode/draw2d/draw2dkit"
|
||||
"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,
|
||||
dw, dh, margin, lineWidth float64) error {
|
||||
// Draw frame
|
||||
draw2d.RoundRect(gc, lineWidth, lineWidth,
|
||||
dw-lineWidth, dh-lineWidth, 100, 100)
|
||||
draw2dkit.RoundedRectangle(gc, lineWidth, lineWidth, dw-lineWidth, dh-lineWidth, 100, 100)
|
||||
gc.SetLineWidth(lineWidth)
|
||||
gc.FillStroke()
|
||||
|
||||
// load the source image
|
||||
source, err := draw2d.LoadFromPngFile(png)
|
||||
source, err := draw2dimg.LoadFromPngFile(png)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"math"
|
||||
|
||||
"github.com/llgcode/draw2d"
|
||||
"github.com/llgcode/draw2d/draw2dkit"
|
||||
"github.com/llgcode/draw2d/samples"
|
||||
"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.LineTo(x+sx*60.0, y)
|
||||
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.Stroke()
|
||||
gc.SetLineDash(nil, 0.0)
|
||||
|
@ -193,7 +194,7 @@ func FillString(gc draw2d.GraphicContext, x, y, width, height float64) {
|
|||
gc.Save()
|
||||
gc.SetStrokeColor(image.Black)
|
||||
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.SetFillColor(image.Black)
|
||||
gc.SetFontSize(height / 6)
|
||||
|
@ -206,7 +207,7 @@ func FillString(gc draw2d.GraphicContext, x, y, width, height float64) {
|
|||
gc.Translate(w+sx, 0)
|
||||
left, top, right, bottom := gc.GetStringBounds("cou")
|
||||
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.Stroke()
|
||||
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
|
||||
gc.MoveTo(x+sx*113.0, y)
|
||||
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.Close()
|
||||
|
||||
gc.MoveTo(x+sx*50.0, y)
|
||||
gc.RLineTo(sx*51.2, sy*51.2)
|
||||
gc.RLineTo(sx*-51.2, sy*51.2)
|
||||
gc.RLineTo(sx*-51.2, sy*-51.2)
|
||||
rLineTo(gc, sx*51.2, sy*51.2)
|
||||
rLineTo(gc, sx*-51.2, sy*51.2)
|
||||
rLineTo(gc, sx*-51.2, sy*-51.2)
|
||||
gc.Close()
|
||||
|
||||
gc.SetLineWidth(width / 20.0)
|
||||
|
@ -237,33 +238,37 @@ func FillStroke(gc draw2d.GraphicContext, x, y, width, height float64) {
|
|||
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.
|
||||
func FillStyle(gc draw2d.GraphicContext, x, y, width, height float64) {
|
||||
sx, sy := width/232, height/220
|
||||
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)
|
||||
wheel2 := new(draw2d.PathStorage)
|
||||
wheel2.ArcTo(x+sx*180, y+sy*70, sx*40, sy*40, 0, -2*math.Pi)
|
||||
|
||||
gc.SetFillRule(draw2d.FillRuleEvenOdd)
|
||||
gc.SetFillColor(color.NRGBA{0, 0xB2, 0, 0xFF})
|
||||
|
||||
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)
|
||||
wheel1 = new(draw2d.PathStorage)
|
||||
draw2dkit.Rectangle(gc, x, y+sy*140, x+sx*232, y+sy*198)
|
||||
wheel1.Clear()
|
||||
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)
|
||||
|
||||
gc.SetFillRule(draw2d.FillRuleWinding)
|
||||
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.
|
||||
|
|
|
@ -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"/>
|
||||
gc.SetFillColor(color.RGBA{0xF6, 0xD2, 0xA2, 0xff})
|
||||
gc.MoveTo(10.634, 300.493)
|
||||
gc.RCubicCurveTo(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)
|
||||
gc.RCubicCurveTo(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, 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)
|
||||
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()
|
||||
|
||||
// Left Ear
|
||||
|
@ -61,3 +61,13 @@ func Draw(gc draw2d.GraphicContext) {
|
|||
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)
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"math"
|
||||
|
||||
"github.com/llgcode/draw2d"
|
||||
"github.com/llgcode/draw2d/draw2dkit"
|
||||
"github.com/llgcode/draw2d/samples"
|
||||
)
|
||||
|
||||
|
@ -46,61 +47,76 @@ func Draw(gc draw2d.GraphicContext, x, y, w, h float64) {
|
|||
gc.Close()
|
||||
gc.SetFillColor(brb)
|
||||
gc.Fill()
|
||||
|
||||
// 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()
|
||||
|
||||
// left ear outside
|
||||
draw2d.Circle(gc, x, y+h, w/12)
|
||||
draw2dkit.Circle(gc, x, y+h, w/12)
|
||||
gc.SetFillColor(brf)
|
||||
gc.Fill()
|
||||
|
||||
// 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.Fill()
|
||||
|
||||
// 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.Fill()
|
||||
|
||||
// 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.Fill()
|
||||
|
||||
// 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.Fill()
|
||||
|
||||
// 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.Fill()
|
||||
|
||||
// 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.Fill()
|
||||
|
||||
// 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()
|
||||
|
||||
// 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.Fill()
|
||||
|
||||
// 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.Fill()
|
||||
|
||||
// left tooth
|
||||
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()
|
||||
|
||||
// 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()
|
||||
|
||||
// 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.Fill()
|
||||
|
||||
// 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.Fill()
|
||||
}
|
||||
|
|
|
@ -8,10 +8,9 @@ package helloworld
|
|||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"math"
|
||||
|
||||
"github.com/llgcode/draw2d"
|
||||
"github.com/llgcode/draw2d/draw2dkit"
|
||||
"github.com/llgcode/draw2d/samples"
|
||||
)
|
||||
|
||||
|
@ -28,35 +27,14 @@ func Main(gc draw2d.GraphicContext, ext string) (string, error) {
|
|||
// Draw "Hello World"
|
||||
func Draw(gc draw2d.GraphicContext, text string) {
|
||||
// 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()
|
||||
|
||||
// Set the font luximbi.ttf
|
||||
gc.SetFontData(draw2d.FontData{
|
||||
Name: "luxi",
|
||||
Family: draw2d.FontFamilyMono,
|
||||
Style: draw2d.FontStyleBold | draw2d.FontStyleItalic})
|
||||
gc.SetFontData(draw2d.FontData{Name: "luxi", Family: draw2d.FontFamilyMono, Style: draw2d.FontStyleBold | draw2d.FontStyleItalic})
|
||||
// Set the fill text color to black
|
||||
gc.SetFillColor(image.Black)
|
||||
gc.SetDPI(72)
|
||||
gc.SetFontSize(14)
|
||||
// Display Hello World
|
||||
gc.SetStrokeColor(color.NRGBA{0x33, 0xFF, 0x33, 0xFF})
|
||||
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()
|
||||
gc.FillStringAt("Hello World", 8, 52)
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"github.com/go-gl/glfw/v3.1/glfw"
|
||||
"github.com/llgcode/draw2d"
|
||||
"github.com/llgcode/draw2d/draw2dgl"
|
||||
"github.com/llgcode/draw2d/draw2dkit"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -57,7 +58,7 @@ func display() {
|
|||
Style: draw2d.FontStyleBold | draw2d.FontStyleItalic})
|
||||
|
||||
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.Fill()
|
||||
|
@ -104,9 +105,11 @@ func main() {
|
|||
// time.Sleep(2 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
func onChar(w *glfw.Window, char rune) {
|
||||
log.Println(char)
|
||||
}
|
||||
|
||||
func onKey(w *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) {
|
||||
switch {
|
||||
case key == glfw.KeyEscape && action == glfw.Press,
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"image/color"
|
||||
|
||||
"github.com/llgcode/draw2d"
|
||||
"github.com/llgcode/draw2d/draw2dkit"
|
||||
"github.com/llgcode/draw2d/samples"
|
||||
)
|
||||
|
||||
|
@ -21,7 +22,7 @@ func Main(gc draw2d.GraphicContext, ext string) (string, error) {
|
|||
Draw(gc, x, 0, x, 210)
|
||||
}
|
||||
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.FillStroke()
|
||||
|
||||
|
|
|
@ -17,8 +17,8 @@ func Main(gc draw2d.GraphicContext, ext string) (string, error) {
|
|||
// Draw the line
|
||||
const offset = 75.0
|
||||
x := 35.0
|
||||
caps := []draw2d.Cap{draw2d.ButtCap, draw2d.SquareCap, draw2d.RoundCap}
|
||||
joins := []draw2d.Join{draw2d.BevelJoin, draw2d.MiterJoin, draw2d.RoundJoin}
|
||||
caps := []draw2d.LineCap{draw2d.ButtCap, draw2d.SquareCap, draw2d.RoundCap}
|
||||
joins := []draw2d.LineJoin{draw2d.BevelJoin, draw2d.MiterJoin, draw2d.RoundJoin}
|
||||
for i := range caps {
|
||||
Draw(gc, caps[i], joins[i], x, 50, x, 160, 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
|
||||
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) {
|
||||
gc.SetLineCap(cap)
|
||||
gc.SetLineJoin(join)
|
||||
|
|
135
stroker.go
|
@ -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
|
||||
}
|
||||
}
|
|
@ -7,6 +7,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/llgcode/draw2d"
|
||||
"github.com/llgcode/draw2d/draw2dimg"
|
||||
)
|
||||
|
||||
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) {
|
||||
// Initialize the graphic context on an RGBA image
|
||||
dest := image.NewRGBA(image.Rect(0, 0, 297, 210.0))
|
||||
gc := draw2d.NewGraphicContext(dest)
|
||||
gc := draw2dimg.NewGraphicContext(dest)
|
||||
// Draw Android logo
|
||||
output, err := draw(gc, "png")
|
||||
if err != nil {
|
||||
|
@ -22,7 +23,7 @@ func test(t *testing.T, draw sample) {
|
|||
return
|
||||
}
|
||||
// Save to png
|
||||
err = draw2d.SaveToPngFile(output, dest)
|
||||
err = draw2dimg.SaveToPngFile(output, dest)
|
||||
if err != nil {
|
||||
t.Errorf("Saving %q failed: %v", output, err)
|
||||
}
|
||||
|
|
307
transform.go
|
@ -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)
|
||||
}
|
26
vertex2d.go
|
@ -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)
|
||||
}
|