Compare commits
146 commits
Author | SHA1 | Date | |
---|---|---|---|
675e82cb64 | |||
ff82ab9451 | |||
a7539fd29b | |||
41b8d7304e | |||
9dbc14edd6 | |||
9941d77460 | |||
e3566f7fc4 | |||
219501b99b | |||
|
f52c8a71af | ||
|
bdf3a69827 | ||
|
587a55234c | ||
|
94de6e33b6 | ||
|
cd0433711b | ||
|
274031cf2a | ||
|
bc151d5e2c | ||
|
72e6a3c750 | ||
|
0b72959009 | ||
|
7419075cb6 | ||
|
1588b49f0d | ||
|
50aafedab4 | ||
|
99cc16d0ac | ||
|
1b49270d08 | ||
|
c1e5edea41 | ||
|
6f03f106f6 | ||
|
3af25f5588 | ||
|
90f962641f | ||
|
215a761ccb | ||
|
6d31bfac59 | ||
|
d297a025cd | ||
|
484fe1caef | ||
|
0b3b26d85f | ||
|
41d8a21ba2 | ||
|
6c0a15c624 | ||
|
ca83e24222 | ||
|
bd7567e331 | ||
|
cdf301b7be | ||
|
295a8365b3 | ||
|
96883adea4 | ||
|
c41aa97d30 | ||
|
647da9ceaa | ||
|
f3e35015aa | ||
|
b81f74eb39 | ||
|
a5f7ac8ebe | ||
|
8167230c09 | ||
|
3e4c36c4c9 | ||
|
4cdcb11e52 | ||
|
e4816c5375 | ||
|
dd69e0c822 | ||
|
dcbfbe505d | ||
|
1f71aa3f15 | ||
|
0cf6b8d61f | ||
|
7c57ea38bb | ||
|
eca7b76ebc | ||
|
dfbef878aa | ||
|
1286d3b203 | ||
|
c12070824c | ||
|
0d961cd299 | ||
|
7cc6abeee3 | ||
|
401ee667f2 | ||
|
c2920005d6 | ||
|
c2851a6eb6 | ||
|
3a5a1d8830 | ||
|
b9005c988d | ||
|
4a3322e29e | ||
|
8380dd9458 | ||
|
51ba099819 | ||
|
a6ceba03c8 | ||
|
475a830567 | ||
|
105a963210 | ||
|
13548be874 | ||
|
e0e534f3a5 | ||
|
155ff5c755 | ||
|
3f01cfe277 | ||
|
5e675a3055 | ||
|
3bb234e85b | ||
|
0545b30698 | ||
|
f444aacdd7 | ||
|
11b6fa221b | ||
|
001a24bc17 | ||
|
6c047429f6 | ||
|
598513aa60 | ||
|
9ffe0e7eb5 | ||
|
56180d8101 | ||
|
d6d74f19f9 | ||
|
7b20985151 | ||
|
835d17ca7c | ||
|
caad194462 | ||
|
96d42f14c0 | ||
|
35dcbff3f7 | ||
|
37f345f4d3 | ||
|
c378327bfa | ||
|
7510d72d52 | ||
|
48a313740b | ||
|
82a7e1e58e | ||
|
c8d67448a9 | ||
|
094e39780f | ||
|
ad6b615bc4 | ||
|
0e0aa125a3 | ||
|
bf42fff416 | ||
|
ed44998c46 | ||
|
1a2db78d7b | ||
|
755362132b | ||
|
a238a47879 | ||
|
b07a8ba2e0 | ||
|
93c5712ecc | ||
|
7e94968f5e | ||
|
1e0467b8fc | ||
|
3b19ab855e | ||
|
9b55e34990 | ||
|
7ef94ce784 | ||
|
04427cabf5 | ||
|
b14683a552 | ||
|
f6e1ada0f2 | ||
|
f2563306e4 | ||
|
72643a28b2 | ||
|
383fef0d7d | ||
|
0345095002 | ||
|
c7ef18681a | ||
|
c686a7fcf6 | ||
|
cf01bf3026 | ||
|
94ef483cbd | ||
|
9012e5e580 | ||
|
47f90d3414 | ||
|
74e6b9b1ec | ||
|
82ef300f1d | ||
|
ee83fedb10 | ||
|
409365e40f | ||
|
511954196b | ||
|
966a9b73f7 | ||
|
24d62b9aa7 | ||
|
ce6fbe94f3 | ||
|
79f25c1ea2 | ||
|
0b7a049f3e | ||
|
1d191b3eaf | ||
|
61a6e03fdb | ||
|
d6812fd8e6 | ||
|
06178b5d2d | ||
|
fef7265145 | ||
|
41809b9132 | ||
|
565dfa9eb9 | ||
|
5df1705bb4 | ||
|
42d0eb260f | ||
|
4fa829a373 | ||
|
ceb331894d | ||
|
4b3ba53f4c | ||
|
216d3f60dd |
2
.gitignore
vendored
|
@ -21,3 +21,5 @@ _test*
|
||||||
**/core*[0-9]
|
**/core*[0-9]
|
||||||
.private
|
.private
|
||||||
|
|
||||||
|
go.sum
|
||||||
|
|
||||||
|
|
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>
|
|
2
AUTHORS
|
@ -1,2 +1,4 @@
|
||||||
Laurent Le Goff
|
Laurent Le Goff
|
||||||
Stani Michiels, gmail:stani.be
|
Stani Michiels, gmail:stani.be
|
||||||
|
Drahoslav Bednář
|
||||||
|
Sebastien Binet
|
||||||
|
|
38
README.md
|
@ -1,5 +1,7 @@
|
||||||
draw2d
|
draw2d
|
||||||
======
|
======
|
||||||
|
[![Coverage](http://gocover.io/_badge/github.com/llgcode/draw2d?0)](http://gocover.io/github.com/llgcode/draw2d)
|
||||||
|
[![GoDoc](https://godoc.org/github.com/llgcode/draw2d?status.svg)](https://godoc.org/github.com/llgcode/draw2d)
|
||||||
|
|
||||||
Package draw2d is a pure [go](http://golang.org) 2D vector graphics library with support for multiple output devices such as [images](http://golang.org/pkg/image) (draw2d), pdf documents (draw2dpdf) and opengl (draw2dgl), which can also be used on the google app engine. It can be used as a pure go [Cairo](http://www.cairographics.org/) alternative. draw2d is released under the BSD license. See the [documentation](http://godoc.org/github.com/llgcode/draw2d) for more details.
|
Package draw2d is a pure [go](http://golang.org) 2D vector graphics library with support for multiple output devices such as [images](http://golang.org/pkg/image) (draw2d), pdf documents (draw2dpdf) and opengl (draw2dgl), which can also be used on the google app engine. It can be used as a pure go [Cairo](http://www.cairographics.org/) alternative. draw2d is released under the BSD license. See the [documentation](http://godoc.org/github.com/llgcode/draw2d) for more details.
|
||||||
|
|
||||||
|
@ -19,19 +21,35 @@ Installation
|
||||||
|
|
||||||
Install [golang](http://golang.org/doc/install). To install or update the package draw2d on your system, run:
|
Install [golang](http://golang.org/doc/install). To install or update the package draw2d on your system, run:
|
||||||
|
|
||||||
|
Stable release
|
||||||
|
```
|
||||||
|
go get -u gopkg.in/llgcode/draw2d.v1
|
||||||
|
```
|
||||||
|
|
||||||
|
or Current release
|
||||||
```
|
```
|
||||||
go get -u github.com/llgcode/draw2d
|
go get -u github.com/llgcode/draw2d
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
Quick Start
|
Quick Start
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
The following Go code generates a simple drawing and saves it to an image file with package draw2d:
|
The following Go code generates a simple drawing and saves it to an image file with package draw2d:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/llgcode/draw2d/draw2dimg"
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
// Initialize the graphic context on an RGBA image
|
// Initialize the graphic context on an RGBA image
|
||||||
dest := image.NewRGBA(image.Rect(0, 0, 297, 210.0))
|
dest := image.NewRGBA(image.Rect(0, 0, 297, 210.0))
|
||||||
gc := draw2d.NewGraphicContext(dest)
|
gc := draw2dimg.NewGraphicContext(dest)
|
||||||
|
|
||||||
// Set some properties
|
// Set some properties
|
||||||
gc.SetFillColor(color.RGBA{0x44, 0xff, 0x44, 0xff})
|
gc.SetFillColor(color.RGBA{0x44, 0xff, 0x44, 0xff})
|
||||||
|
@ -39,22 +57,32 @@ gc.SetStrokeColor(color.RGBA{0x44, 0x44, 0x44, 0xff})
|
||||||
gc.SetLineWidth(5)
|
gc.SetLineWidth(5)
|
||||||
|
|
||||||
// Draw a closed shape
|
// Draw a closed shape
|
||||||
gc.MoveTo(10, 10) // should always be called first for a new path
|
gc.BeginPath() // Initialize a new path
|
||||||
|
gc.MoveTo(10, 10) // Move to a position to start the new path
|
||||||
gc.LineTo(100, 50)
|
gc.LineTo(100, 50)
|
||||||
gc.QuadCurveTo(100, 10, 10, 10)
|
gc.QuadCurveTo(100, 10, 10, 10)
|
||||||
gc.Close()
|
gc.Close()
|
||||||
gc.FillStroke()
|
gc.FillStroke()
|
||||||
|
|
||||||
// Save to file
|
// Save to file
|
||||||
draw2d.SaveToPngFile("hello.png", dest)
|
draw2dimg.SaveToPngFile("hello.png", dest)
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
The same Go code can also generate a pdf document with package draw2dpdf:
|
The same Go code can also generate a pdf document with package draw2dpdf:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/llgcode/draw2d/draw2dpdf"
|
||||||
|
"image/color"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
// Initialize the graphic context on an RGBA image
|
// Initialize the graphic context on an RGBA image
|
||||||
dest := draw2dpdf.NewPdf("L", "mm", "A4")
|
dest := draw2dpdf.NewPdf("L", "mm", "A4")
|
||||||
gc := draw2d.NewGraphicContext(dest)
|
gc := draw2dpdf.NewGraphicContext(dest)
|
||||||
|
|
||||||
// Set some properties
|
// Set some properties
|
||||||
gc.SetFillColor(color.RGBA{0x44, 0xff, 0x44, 0xff})
|
gc.SetFillColor(color.RGBA{0x44, 0xff, 0x44, 0xff})
|
||||||
|
@ -70,6 +98,7 @@ gc.FillStroke()
|
||||||
|
|
||||||
// Save to file
|
// Save to file
|
||||||
draw2dpdf.SaveToPdfFile("hello.pdf", dest)
|
draw2dpdf.SaveToPdfFile("hello.pdf", dest)
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
There are more examples here: https://github.com/llgcode/draw2d/tree/master/samples
|
There are more examples here: https://github.com/llgcode/draw2d/tree/master/samples
|
||||||
|
@ -105,6 +134,7 @@ Packages using draw2d
|
||||||
- [smartcrop](https://github.com/muesli/smartcrop): content aware image cropping
|
- [smartcrop](https://github.com/muesli/smartcrop): content aware image cropping
|
||||||
- [karta](https://github.com/peterhellberg/karta): drawing Voronoi diagrams
|
- [karta](https://github.com/peterhellberg/karta): drawing Voronoi diagrams
|
||||||
- [chart](https://github.com/vdobler/chart): basic charts in Go
|
- [chart](https://github.com/vdobler/chart): basic charts in Go
|
||||||
|
- [hilbert](https://github.com/google/hilbert): package for drawing Hilbert curves
|
||||||
|
|
||||||
References
|
References
|
||||||
---------
|
---------
|
||||||
|
|
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"
|
|
||||||
|
|
||||||
"gopkg.in/llgcode/draw2d.v1/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)
|
|
||||||
}
|
|
||||||
}
|
|
178
draw2d.go
|
@ -19,16 +19,25 @@
|
||||||
// Installation
|
// Installation
|
||||||
//
|
//
|
||||||
// To install or update the package draw2d on your system, run:
|
// To install or update the package draw2d on your system, run:
|
||||||
// go get -u gopkg.in/llgcode/draw2d.v1
|
// go get -u github.com/llgcode/draw2d
|
||||||
//
|
//
|
||||||
// Quick Start
|
// Quick Start
|
||||||
//
|
//
|
||||||
// Package draw2d itself provides a graphic context that can draw vector
|
// Package draw2d itself provides a graphic context that can draw vector
|
||||||
// graphics and text on an image canvas. The following Go code
|
// graphics and text on an image canvas. The following Go code
|
||||||
// generates a simple drawing and saves it to an image file:
|
// generates a simple drawing and saves it to an image file:
|
||||||
|
// package main
|
||||||
|
//
|
||||||
|
// import (
|
||||||
|
// "github.com/llgcode/draw2d/draw2dimg"
|
||||||
|
// "image"
|
||||||
|
// "image/color"
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// func main() {
|
||||||
// // Initialize the graphic context on an RGBA image
|
// // Initialize the graphic context on an RGBA image
|
||||||
// dest := image.NewRGBA(image.Rect(0, 0, 297, 210.0))
|
// dest := image.NewRGBA(image.Rect(0, 0, 297, 210.0))
|
||||||
// gc := draw2d.NewGraphicContext(dest)
|
// gc := draw2dimg.NewGraphicContext(dest)
|
||||||
//
|
//
|
||||||
// // Set some properties
|
// // Set some properties
|
||||||
// gc.SetFillColor(color.RGBA{0x44, 0xff, 0x44, 0xff})
|
// gc.SetFillColor(color.RGBA{0x44, 0xff, 0x44, 0xff})
|
||||||
|
@ -43,10 +52,12 @@
|
||||||
// gc.FillStroke()
|
// gc.FillStroke()
|
||||||
//
|
//
|
||||||
// // Save to file
|
// // Save to file
|
||||||
// draw2d.SaveToPngFile("hello.png", dest)
|
// draw2dimg.SaveToPngFile("hello.png", dest)
|
||||||
|
// }
|
||||||
|
//
|
||||||
//
|
//
|
||||||
// There are more examples here:
|
// There are more examples here:
|
||||||
// https://gopkg.in/llgcode/draw2d.v1/tree/master/samples
|
// https://github.com/llgcode/draw2d/tree/master/samples
|
||||||
//
|
//
|
||||||
// Drawing on pdf documents is provided by the draw2dpdf package.
|
// Drawing on pdf documents is provided by the draw2dpdf package.
|
||||||
// Drawing on opengl is provided by the draw2dgl package.
|
// Drawing on opengl is provided by the draw2dgl package.
|
||||||
|
@ -83,3 +94,162 @@
|
||||||
//
|
//
|
||||||
// - https://github.com/vdobler/chart: basic charts in Go
|
// - https://github.com/vdobler/chart: basic charts in Go
|
||||||
package draw2d
|
package draw2d
|
||||||
|
|
||||||
|
import "image/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
|
||||||
|
)
|
||||||
|
|
||||||
|
func (cap LineCap) String() string {
|
||||||
|
return map[LineCap]string{
|
||||||
|
RoundCap: "round",
|
||||||
|
ButtCap: "cap",
|
||||||
|
SquareCap: "square",
|
||||||
|
}[cap]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
)
|
||||||
|
|
||||||
|
func (join LineJoin) String() string {
|
||||||
|
return map[LineJoin]string{
|
||||||
|
RoundJoin: "round",
|
||||||
|
BevelJoin: "bevel",
|
||||||
|
MiterJoin: "miter",
|
||||||
|
}[join]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
7
draw2dbase/README.md
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
draw2d/draw2dbase
|
||||||
|
=================
|
||||||
|
|
||||||
|
[![Coverage](http://gocover.io/_badge/github.com/llgcode/draw2d/draw2dbase?0)](http://gocover.io/github.com/llgcode/draw2d/draw2dbase)
|
||||||
|
[![GoDoc](https://godoc.org/github.com/llgcode/draw2d/draw2dbase?status.svg)](https://godoc.org/github.com/llgcode/draw2d/draw2dbase)
|
||||||
|
|
||||||
|
Base package implementation that is used by pdf, svg, img, gl implementations.
|
172
draw2dbase/curve.go
Normal file
|
@ -0,0 +1,172 @@
|
||||||
|
// Copyright 2010 The draw2d Authors. All rights reserved.
|
||||||
|
// created: 17/05/2011 by Laurent Le Goff
|
||||||
|
|
||||||
|
package draw2dbase
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"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
|
||||||
|
// length of c, c1 and c2 must be 8 otherwise it panics.
|
||||||
|
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) error {
|
||||||
|
if len(cubic) < 8 {
|
||||||
|
return errors.New("cubic length must be >= 8")
|
||||||
|
}
|
||||||
|
// 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:]
|
||||||
|
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)-8 {
|
||||||
|
t.LineTo(c[6], c[7])
|
||||||
|
i -= 8
|
||||||
|
} else {
|
||||||
|
// second half of bezier go lower onto the stack
|
||||||
|
SubdivideCubic(c, curves[i+8:], curves[i:])
|
||||||
|
i += 8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// length of c, c1 and c2 must be 6 otherwise it panics.
|
||||||
|
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) error {
|
||||||
|
if len(quad) < 6 {
|
||||||
|
return errors.New("quad length must be >= 6")
|
||||||
|
}
|
||||||
|
// 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:]
|
||||||
|
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)-6 {
|
||||||
|
t.LineTo(c[4], c[5])
|
||||||
|
i -= 6
|
||||||
|
} else {
|
||||||
|
// second half of bezier go lower onto the stack
|
||||||
|
SubdivideQuad(c, curves[i+6:], curves[i:])
|
||||||
|
i += 6
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
176
draw2dbase/curve_test.go
Normal file
|
@ -0,0 +1,176 @@
|
||||||
|
package draw2dbase
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
"image/draw"
|
||||||
|
"image/png"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
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))
|
||||||
|
PolylineBresenham(img, color.NRGBA{0xff, 0, 0, 0xff}, testsCubicFloat64[i:i+8]...)
|
||||||
|
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))
|
||||||
|
PolylineBresenham(img, color.NRGBA{0xff, 0, 0, 0xff}, testsQuadFloat64[i:i+6]...)
|
||||||
|
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 TestQuadCurveCombinedPoint(t *testing.T) {
|
||||||
|
var p1 SegmentedPath
|
||||||
|
TraceQuad(&p1, []float64{0, 0, 0, 0, 0, 0}, flatteningThreshold)
|
||||||
|
if len(p1.Points) != 2 {
|
||||||
|
t.Error("It must have one point for this curve", len(p1.Points))
|
||||||
|
}
|
||||||
|
var p2 SegmentedPath
|
||||||
|
TraceQuad(&p2, []float64{0, 0, 100, 100, 0, 0}, flatteningThreshold)
|
||||||
|
if len(p2.Points) != 2 {
|
||||||
|
t.Error("It must have one point for this curve", len(p2.Points))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCubicCurveCombinedPoint(t *testing.T) {
|
||||||
|
var p1 SegmentedPath
|
||||||
|
TraceCubic(&p1, []float64{0, 0, 0, 0, 0, 0, 0, 0}, flatteningThreshold)
|
||||||
|
if len(p1.Points) != 2 {
|
||||||
|
t.Error("It must have one point for this curve", len(p1.Points))
|
||||||
|
}
|
||||||
|
var p2 SegmentedPath
|
||||||
|
TraceCubic(&p2, []float64{0, 0, 100, 100, 200, 200, 0, 0}, flatteningThreshold)
|
||||||
|
if len(p2.Points) != 2 {
|
||||||
|
t.Error("It must have one point for this curve", len(p2.Points))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOutOfRangeTraceCurve(t *testing.T) {
|
||||||
|
c := []float64{
|
||||||
|
100, 100, 200, 100, 100, 200,
|
||||||
|
}
|
||||||
|
var p SegmentedPath
|
||||||
|
TraceCubic(&p, c, flatteningThreshold)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOutOfRangeTraceQuad(t *testing.T) {
|
||||||
|
c := []float64{
|
||||||
|
100, 100, 200, 100,
|
||||||
|
}
|
||||||
|
var p SegmentedPath
|
||||||
|
TraceQuad(&p, c, flatteningThreshold)
|
||||||
|
}
|
|
@ -1,51 +1,48 @@
|
||||||
// Copyright 2010 The draw2d Authors. All rights reserved.
|
// Copyright 2010 The draw2d Authors. All rights reserved.
|
||||||
// created: 13/12/2010 by Laurent Le Goff
|
// created: 13/12/2010 by Laurent Le Goff
|
||||||
|
|
||||||
package draw2d
|
package draw2dbase
|
||||||
|
|
||||||
type DashVertexConverter struct {
|
type DashVertexConverter struct {
|
||||||
command VertexCommand
|
next Flattener
|
||||||
next VertexConverter
|
|
||||||
x, y, distance float64
|
x, y, distance float64
|
||||||
dash []float64
|
dash []float64
|
||||||
currentDash int
|
currentDash int
|
||||||
dashOffset float64
|
dashOffset float64
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDashConverter(dash []float64, dashOffset float64, converter VertexConverter) *DashVertexConverter {
|
func NewDashConverter(dash []float64, dashOffset float64, flattener Flattener) *DashVertexConverter {
|
||||||
var dasher DashVertexConverter
|
var dasher DashVertexConverter
|
||||||
dasher.dash = dash
|
dasher.dash = dash
|
||||||
dasher.currentDash = 0
|
dasher.currentDash = 0
|
||||||
dasher.dashOffset = dashOffset
|
dasher.dashOffset = dashOffset
|
||||||
dasher.next = converter
|
dasher.next = flattener
|
||||||
return &dasher
|
return &dasher
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dasher *DashVertexConverter) NextCommand(cmd VertexCommand) {
|
func (dasher *DashVertexConverter) LineTo(x, y float64) {
|
||||||
dasher.command = cmd
|
|
||||||
if dasher.command == VertexStopCommand {
|
|
||||||
dasher.next.NextCommand(VertexStopCommand)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dasher *DashVertexConverter) Vertex(x, y float64) {
|
|
||||||
switch dasher.command {
|
|
||||||
case VertexStartCommand:
|
|
||||||
dasher.start(x, y)
|
|
||||||
default:
|
|
||||||
dasher.lineTo(x, y)
|
dasher.lineTo(x, y)
|
||||||
}
|
}
|
||||||
dasher.command = VertexNoCommand
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dasher *DashVertexConverter) start(x, y float64) {
|
func (dasher *DashVertexConverter) MoveTo(x, y float64) {
|
||||||
dasher.next.NextCommand(VertexStartCommand)
|
dasher.next.MoveTo(x, y)
|
||||||
dasher.next.Vertex(x, y)
|
|
||||||
dasher.x, dasher.y = x, y
|
dasher.x, dasher.y = x, y
|
||||||
dasher.distance = dasher.dashOffset
|
dasher.distance = dasher.dashOffset
|
||||||
dasher.currentDash = 0
|
dasher.currentDash = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (dasher *DashVertexConverter) LineJoin() {
|
||||||
|
dasher.next.LineJoin()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dasher *DashVertexConverter) Close() {
|
||||||
|
dasher.next.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dasher *DashVertexConverter) End() {
|
||||||
|
dasher.next.End()
|
||||||
|
}
|
||||||
|
|
||||||
func (dasher *DashVertexConverter) lineTo(x, y float64) {
|
func (dasher *DashVertexConverter) lineTo(x, y float64) {
|
||||||
rest := dasher.dash[dasher.currentDash] - dasher.distance
|
rest := dasher.dash[dasher.currentDash] - dasher.distance
|
||||||
for rest < 0 {
|
for rest < 0 {
|
||||||
|
@ -60,12 +57,11 @@ func (dasher *DashVertexConverter) lineTo(x, y float64) {
|
||||||
ly := dasher.y + k*(y-dasher.y)
|
ly := dasher.y + k*(y-dasher.y)
|
||||||
if dasher.currentDash%2 == 0 {
|
if dasher.currentDash%2 == 0 {
|
||||||
// line
|
// line
|
||||||
dasher.next.Vertex(lx, ly)
|
dasher.next.LineTo(lx, ly)
|
||||||
} else {
|
} else {
|
||||||
// gap
|
// gap
|
||||||
dasher.next.NextCommand(VertexStopCommand)
|
dasher.next.End()
|
||||||
dasher.next.NextCommand(VertexStartCommand)
|
dasher.next.MoveTo(lx, ly)
|
||||||
dasher.next.Vertex(lx, ly)
|
|
||||||
}
|
}
|
||||||
d = d - rest
|
d = d - rest
|
||||||
dasher.x, dasher.y = lx, ly
|
dasher.x, dasher.y = lx, ly
|
||||||
|
@ -75,12 +71,11 @@ func (dasher *DashVertexConverter) lineTo(x, y float64) {
|
||||||
dasher.distance = d
|
dasher.distance = d
|
||||||
if dasher.currentDash%2 == 0 {
|
if dasher.currentDash%2 == 0 {
|
||||||
// line
|
// line
|
||||||
dasher.next.Vertex(x, y)
|
dasher.next.LineTo(x, y)
|
||||||
} else {
|
} else {
|
||||||
// gap
|
// gap
|
||||||
dasher.next.NextCommand(VertexStopCommand)
|
dasher.next.End()
|
||||||
dasher.next.NextCommand(VertexStartCommand)
|
dasher.next.MoveTo(x, y)
|
||||||
dasher.next.Vertex(x, y)
|
|
||||||
}
|
}
|
||||||
if dasher.distance >= dasher.dash[dasher.currentDash] {
|
if dasher.distance >= dasher.dash[dasher.currentDash] {
|
||||||
dasher.distance = dasher.distance - dasher.dash[dasher.currentDash]
|
dasher.distance = dasher.distance - dasher.dash[dasher.currentDash]
|
||||||
|
@ -88,3 +83,7 @@ func (dasher *DashVertexConverter) lineTo(x, y float64) {
|
||||||
}
|
}
|
||||||
dasher.x, dasher.y = x, y
|
dasher.x, dasher.y = x, y
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func distance(x1, y1, x2, y2 float64) float64 {
|
||||||
|
return vectorDistance(x2-x1, y2-y1)
|
||||||
|
}
|
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 (
|
||||||
|
"git.fromouter.space/crunchy-rocks/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 use Round, Bevel or miter to join points
|
||||||
|
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,6 +1,7 @@
|
||||||
// Copyright 2011 The draw2d Authors. All rights reserved.
|
// Copyright 2011 The draw2d Authors. All rights reserved.
|
||||||
// created: 27/05/2011 by Laurent Le Goff
|
// created: 27/05/2011 by Laurent Le Goff
|
||||||
package raster
|
|
||||||
|
package draw2dbase
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"image/color"
|
"image/color"
|
||||||
|
@ -14,12 +15,14 @@ func abs(i int) int {
|
||||||
return i
|
return i
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PolylineBresenham draws a polyline to an image
|
||||||
func PolylineBresenham(img draw.Image, c color.Color, s ...float64) {
|
func PolylineBresenham(img draw.Image, c color.Color, s ...float64) {
|
||||||
for i := 2; i < len(s); i += 2 {
|
for i := 2; i < len(s); i += 2 {
|
||||||
Bresenham(img, c, int(s[i-2]+0.5), int(s[i-1]+0.5), int(s[i]+0.5), int(s[i+1]+0.5))
|
Bresenham(img, c, int(s[i-2]+0.5), int(s[i-1]+0.5), int(s[i]+0.5), int(s[i+1]+0.5))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Bresenham draws a line between (x0, y0) and (x1, y1)
|
||||||
func Bresenham(img draw.Image, color color.Color, x0, y0, x1, y1 int) {
|
func Bresenham(img draw.Image, color color.Color, x0, y0, x1, y1 int) {
|
||||||
dx := abs(x1 - x0)
|
dx := abs(x1 - x0)
|
||||||
dy := abs(y1 - y0)
|
dy := abs(y1 - y0)
|
|
@ -1,39 +1,50 @@
|
||||||
// Copyright 2010 The draw2d Authors. All rights reserved.
|
// Copyright 2010 The draw2d Authors. All rights reserved.
|
||||||
// created: 21/11/2010 by Laurent Le Goff
|
// created: 21/11/2010 by Laurent Le Goff
|
||||||
|
|
||||||
package draw2d
|
package draw2dbase
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
|
|
||||||
"code.google.com/p/freetype-go/freetype/truetype"
|
"git.fromouter.space/crunchy-rocks/draw2d"
|
||||||
|
|
||||||
|
"git.fromouter.space/crunchy-rocks/freetype/truetype"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var DefaultFontData = draw2d.FontData{Name: "luxi", Family: draw2d.FontFamilySans, Style: draw2d.FontStyleNormal}
|
||||||
|
|
||||||
type StackGraphicContext struct {
|
type StackGraphicContext struct {
|
||||||
Current *ContextStack
|
Current *ContextStack
|
||||||
}
|
}
|
||||||
|
|
||||||
type ContextStack struct {
|
type ContextStack struct {
|
||||||
Tr MatrixTransform
|
Tr draw2d.Matrix
|
||||||
Path *PathStorage
|
Path *draw2d.Path
|
||||||
LineWidth float64
|
LineWidth float64
|
||||||
Dash []float64
|
Dash []float64
|
||||||
DashOffset float64
|
DashOffset float64
|
||||||
StrokeColor color.Color
|
StrokeColor color.Color
|
||||||
FillColor color.Color
|
FillColor color.Color
|
||||||
FillRule FillRule
|
FillRule draw2d.FillRule
|
||||||
Cap Cap
|
Cap draw2d.LineCap
|
||||||
Join Join
|
Join draw2d.LineJoin
|
||||||
FontSize float64
|
FontSize float64
|
||||||
FontData FontData
|
FontData draw2d.FontData
|
||||||
|
|
||||||
Font *truetype.Font
|
Font *truetype.Font
|
||||||
// fontSize and dpi are used to calculate scale. scale is the number of
|
// fontSize and dpi are used to calculate scale. scale is the number of
|
||||||
// 26.6 fixed point units in 1 em.
|
// 26.6 fixed point units in 1 em.
|
||||||
Scale float64
|
Scale float64
|
||||||
|
|
||||||
previous *ContextStack
|
Previous *ContextStack
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFontName gets the current FontData with fontSize as a string
|
||||||
|
func (cs *ContextStack) GetFontName() string {
|
||||||
|
fontData := cs.FontData
|
||||||
|
return fmt.Sprintf("%s:%d:%d:%9.2f", fontData.Name, fontData.Family, fontData.Style, cs.FontSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -42,41 +53,41 @@ type ContextStack struct {
|
||||||
func NewStackGraphicContext() *StackGraphicContext {
|
func NewStackGraphicContext() *StackGraphicContext {
|
||||||
gc := &StackGraphicContext{}
|
gc := &StackGraphicContext{}
|
||||||
gc.Current = new(ContextStack)
|
gc.Current = new(ContextStack)
|
||||||
gc.Current.Tr = NewIdentityMatrix()
|
gc.Current.Tr = draw2d.NewIdentityMatrix()
|
||||||
gc.Current.Path = NewPathStorage()
|
gc.Current.Path = new(draw2d.Path)
|
||||||
gc.Current.LineWidth = 1.0
|
gc.Current.LineWidth = 1.0
|
||||||
gc.Current.StrokeColor = image.Black
|
gc.Current.StrokeColor = image.Black
|
||||||
gc.Current.FillColor = image.White
|
gc.Current.FillColor = image.White
|
||||||
gc.Current.Cap = RoundCap
|
gc.Current.Cap = draw2d.RoundCap
|
||||||
gc.Current.FillRule = FillRuleEvenOdd
|
gc.Current.FillRule = draw2d.FillRuleEvenOdd
|
||||||
gc.Current.Join = RoundJoin
|
gc.Current.Join = draw2d.RoundJoin
|
||||||
gc.Current.FontSize = 10
|
gc.Current.FontSize = 10
|
||||||
gc.Current.FontData = defaultFontData
|
gc.Current.FontData = DefaultFontData
|
||||||
return gc
|
return gc
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gc *StackGraphicContext) GetMatrixTransform() MatrixTransform {
|
func (gc *StackGraphicContext) GetMatrixTransform() draw2d.Matrix {
|
||||||
return gc.Current.Tr
|
return gc.Current.Tr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gc *StackGraphicContext) SetMatrixTransform(Tr MatrixTransform) {
|
func (gc *StackGraphicContext) SetMatrixTransform(Tr draw2d.Matrix) {
|
||||||
gc.Current.Tr = Tr
|
gc.Current.Tr = Tr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gc *StackGraphicContext) ComposeMatrixTransform(Tr MatrixTransform) {
|
func (gc *StackGraphicContext) ComposeMatrixTransform(Tr draw2d.Matrix) {
|
||||||
gc.Current.Tr = Tr.Multiply(gc.Current.Tr)
|
gc.Current.Tr.Compose(Tr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gc *StackGraphicContext) Rotate(angle float64) {
|
func (gc *StackGraphicContext) Rotate(angle float64) {
|
||||||
gc.Current.Tr = NewRotationMatrix(angle).Multiply(gc.Current.Tr)
|
gc.Current.Tr.Rotate(angle)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gc *StackGraphicContext) Translate(tx, ty float64) {
|
func (gc *StackGraphicContext) Translate(tx, ty float64) {
|
||||||
gc.Current.Tr = NewTranslationMatrix(tx, ty).Multiply(gc.Current.Tr)
|
gc.Current.Tr.Translate(tx, ty)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gc *StackGraphicContext) Scale(sx, sy float64) {
|
func (gc *StackGraphicContext) Scale(sx, sy float64) {
|
||||||
gc.Current.Tr = NewScaleMatrix(sx, sy).Multiply(gc.Current.Tr)
|
gc.Current.Tr.Scale(sx, sy)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gc *StackGraphicContext) SetStrokeColor(c color.Color) {
|
func (gc *StackGraphicContext) SetStrokeColor(c color.Color) {
|
||||||
|
@ -87,45 +98,49 @@ func (gc *StackGraphicContext) SetFillColor(c color.Color) {
|
||||||
gc.Current.FillColor = c
|
gc.Current.FillColor = c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gc *StackGraphicContext) SetFillRule(f FillRule) {
|
func (gc *StackGraphicContext) SetFillRule(f draw2d.FillRule) {
|
||||||
gc.Current.FillRule = f
|
gc.Current.FillRule = f
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gc *StackGraphicContext) SetLineWidth(LineWidth float64) {
|
func (gc *StackGraphicContext) SetLineWidth(lineWidth float64) {
|
||||||
gc.Current.LineWidth = LineWidth
|
gc.Current.LineWidth = lineWidth
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gc *StackGraphicContext) SetLineCap(Cap Cap) {
|
func (gc *StackGraphicContext) SetLineCap(cap draw2d.LineCap) {
|
||||||
gc.Current.Cap = Cap
|
gc.Current.Cap = cap
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gc *StackGraphicContext) SetLineJoin(Join Join) {
|
func (gc *StackGraphicContext) SetLineJoin(join draw2d.LineJoin) {
|
||||||
gc.Current.Join = Join
|
gc.Current.Join = join
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gc *StackGraphicContext) SetLineDash(Dash []float64, DashOffset float64) {
|
func (gc *StackGraphicContext) SetLineDash(dash []float64, dashOffset float64) {
|
||||||
gc.Current.Dash = Dash
|
gc.Current.Dash = dash
|
||||||
gc.Current.DashOffset = DashOffset
|
gc.Current.DashOffset = dashOffset
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gc *StackGraphicContext) SetFontSize(FontSize float64) {
|
func (gc *StackGraphicContext) SetFontSize(fontSize float64) {
|
||||||
gc.Current.FontSize = FontSize
|
gc.Current.FontSize = fontSize
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gc *StackGraphicContext) GetFontSize() float64 {
|
func (gc *StackGraphicContext) GetFontSize() float64 {
|
||||||
return gc.Current.FontSize
|
return gc.Current.FontSize
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gc *StackGraphicContext) SetFontData(FontData FontData) {
|
func (gc *StackGraphicContext) SetFontData(fontData draw2d.FontData) {
|
||||||
gc.Current.FontData = FontData
|
gc.Current.FontData = fontData
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gc *StackGraphicContext) GetFontData() FontData {
|
func (gc *StackGraphicContext) GetFontData() draw2d.FontData {
|
||||||
return gc.Current.FontData
|
return gc.Current.FontData
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gc *StackGraphicContext) BeginPath() {
|
func (gc *StackGraphicContext) BeginPath() {
|
||||||
gc.Current.Path = NewPathStorage()
|
gc.Current.Path.Clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gc *StackGraphicContext) GetPath() draw2d.Path {
|
||||||
|
return *gc.Current.Path.Copy()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gc *StackGraphicContext) IsEmpty() bool {
|
func (gc *StackGraphicContext) IsEmpty() bool {
|
||||||
|
@ -140,42 +155,22 @@ func (gc *StackGraphicContext) MoveTo(x, y float64) {
|
||||||
gc.Current.Path.MoveTo(x, y)
|
gc.Current.Path.MoveTo(x, y)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gc *StackGraphicContext) RMoveTo(dx, dy float64) {
|
|
||||||
gc.Current.Path.RMoveTo(dx, dy)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gc *StackGraphicContext) LineTo(x, y float64) {
|
func (gc *StackGraphicContext) LineTo(x, y float64) {
|
||||||
gc.Current.Path.LineTo(x, y)
|
gc.Current.Path.LineTo(x, y)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gc *StackGraphicContext) RLineTo(dx, dy float64) {
|
|
||||||
gc.Current.Path.RLineTo(dx, dy)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gc *StackGraphicContext) QuadCurveTo(cx, cy, x, y float64) {
|
func (gc *StackGraphicContext) QuadCurveTo(cx, cy, x, y float64) {
|
||||||
gc.Current.Path.QuadCurveTo(cx, cy, x, y)
|
gc.Current.Path.QuadCurveTo(cx, cy, x, y)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gc *StackGraphicContext) RQuadCurveTo(dcx, dcy, dx, dy float64) {
|
|
||||||
gc.Current.Path.RQuadCurveTo(dcx, dcy, dx, dy)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gc *StackGraphicContext) CubicCurveTo(cx1, cy1, cx2, cy2, x, y float64) {
|
func (gc *StackGraphicContext) CubicCurveTo(cx1, cy1, cx2, cy2, x, y float64) {
|
||||||
gc.Current.Path.CubicCurveTo(cx1, cy1, cx2, cy2, x, y)
|
gc.Current.Path.CubicCurveTo(cx1, cy1, cx2, cy2, x, y)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gc *StackGraphicContext) RCubicCurveTo(dcx1, dcy1, dcx2, dcy2, dx, dy float64) {
|
|
||||||
gc.Current.Path.RCubicCurveTo(dcx1, dcy1, dcx2, dcy2, dx, dy)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gc *StackGraphicContext) ArcTo(cx, cy, rx, ry, startAngle, angle float64) {
|
func (gc *StackGraphicContext) ArcTo(cx, cy, rx, ry, startAngle, angle float64) {
|
||||||
gc.Current.Path.ArcTo(cx, cy, rx, ry, startAngle, angle)
|
gc.Current.Path.ArcTo(cx, cy, rx, ry, startAngle, angle)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gc *StackGraphicContext) RArcTo(dcx, dcy, rx, ry, startAngle, angle float64) {
|
|
||||||
gc.Current.Path.RArcTo(dcx, dcy, rx, ry, startAngle, angle)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gc *StackGraphicContext) Close() {
|
func (gc *StackGraphicContext) Close() {
|
||||||
gc.Current.Path.Close()
|
gc.Current.Path.Close()
|
||||||
}
|
}
|
||||||
|
@ -196,14 +191,18 @@ func (gc *StackGraphicContext) Save() {
|
||||||
context.Font = gc.Current.Font
|
context.Font = gc.Current.Font
|
||||||
context.Scale = gc.Current.Scale
|
context.Scale = gc.Current.Scale
|
||||||
copy(context.Tr[:], gc.Current.Tr[:])
|
copy(context.Tr[:], gc.Current.Tr[:])
|
||||||
context.previous = gc.Current
|
context.Previous = gc.Current
|
||||||
gc.Current = context
|
gc.Current = context
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gc *StackGraphicContext) Restore() {
|
func (gc *StackGraphicContext) Restore() {
|
||||||
if gc.Current.previous != nil {
|
if gc.Current.Previous != nil {
|
||||||
oldContext := gc.Current
|
oldContext := gc.Current
|
||||||
gc.Current = gc.Current.previous
|
gc.Current = gc.Current.Previous
|
||||||
oldContext.previous = nil
|
oldContext.Previous = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (gc *StackGraphicContext) GetFontName() string {
|
||||||
|
return gc.Current.GetFontName()
|
||||||
|
}
|
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"
|
||||||
|
|
||||||
|
"git.fromouter.space/crunchy-rocks/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))
|
||||||
|
}
|
82
draw2dbase/text.go
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
package draw2dbase
|
||||||
|
|
||||||
|
import "git.fromouter.space/crunchy-rocks/draw2d"
|
||||||
|
|
||||||
|
// GlyphCache manage a cache of glyphs
|
||||||
|
type GlyphCache interface {
|
||||||
|
// Fetch fetches a glyph from the cache, storing with Render first if it doesn't already exist
|
||||||
|
Fetch(gc draw2d.GraphicContext, fontName string, chr rune) *Glyph
|
||||||
|
}
|
||||||
|
|
||||||
|
// GlyphCacheImp manage a map of glyphs without sync mecanism, not thread safe
|
||||||
|
type GlyphCacheImp struct {
|
||||||
|
glyphs map[string]map[rune]*Glyph
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGlyphCache initializes a GlyphCache
|
||||||
|
func NewGlyphCache() *GlyphCacheImp {
|
||||||
|
glyphs := make(map[string]map[rune]*Glyph)
|
||||||
|
return &GlyphCacheImp{
|
||||||
|
glyphs: glyphs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch fetches a glyph from the cache, calling renderGlyph first if it doesn't already exist
|
||||||
|
func (glyphCache *GlyphCacheImp) Fetch(gc draw2d.GraphicContext, fontName string, chr rune) *Glyph {
|
||||||
|
if glyphCache.glyphs[fontName] == nil {
|
||||||
|
glyphCache.glyphs[fontName] = make(map[rune]*Glyph, 60)
|
||||||
|
}
|
||||||
|
if glyphCache.glyphs[fontName][chr] == nil {
|
||||||
|
glyphCache.glyphs[fontName][chr] = renderGlyph(gc, fontName, chr)
|
||||||
|
}
|
||||||
|
return glyphCache.glyphs[fontName][chr].Copy()
|
||||||
|
}
|
||||||
|
|
||||||
|
// renderGlyph renders a glyph then caches and returns it
|
||||||
|
func renderGlyph(gc draw2d.GraphicContext, fontName string, chr rune) *Glyph {
|
||||||
|
gc.Save()
|
||||||
|
defer gc.Restore()
|
||||||
|
gc.BeginPath()
|
||||||
|
width := gc.CreateStringPath(string(chr), 0, 0)
|
||||||
|
path := gc.GetPath()
|
||||||
|
return &Glyph{
|
||||||
|
Path: &path,
|
||||||
|
Width: width,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Glyph represents a rune which has been converted to a Path and width
|
||||||
|
type Glyph struct {
|
||||||
|
// path represents a glyph, it is always at (0, 0)
|
||||||
|
Path *draw2d.Path
|
||||||
|
// Width of the glyph
|
||||||
|
Width float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy Returns a copy of a Glyph
|
||||||
|
func (g *Glyph) Copy() *Glyph {
|
||||||
|
return &Glyph{
|
||||||
|
Path: g.Path.Copy(),
|
||||||
|
Width: g.Width,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill copies a glyph from the cache, and fills it
|
||||||
|
func (g *Glyph) Fill(gc draw2d.GraphicContext, x, y float64) float64 {
|
||||||
|
gc.Save()
|
||||||
|
gc.BeginPath()
|
||||||
|
gc.Translate(x, y)
|
||||||
|
gc.Fill(g.Path)
|
||||||
|
gc.Restore()
|
||||||
|
return g.Width
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stroke fetches a glyph from the cache, and strokes it
|
||||||
|
func (g *Glyph) Stroke(gc draw2d.GraphicContext, x, y float64) float64 {
|
||||||
|
gc.Save()
|
||||||
|
gc.BeginPath()
|
||||||
|
gc.Translate(x, y)
|
||||||
|
gc.Stroke(g.Path)
|
||||||
|
gc.Restore()
|
||||||
|
return g.Width
|
||||||
|
}
|
280
draw2dgl/gc.go
|
@ -4,11 +4,19 @@ import (
|
||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
"image/draw"
|
"image/draw"
|
||||||
|
"log"
|
||||||
|
"math"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
"code.google.com/p/freetype-go/freetype/raster"
|
"git.fromouter.space/crunchy-rocks/draw2d"
|
||||||
|
"git.fromouter.space/crunchy-rocks/draw2d/draw2dbase"
|
||||||
|
"git.fromouter.space/crunchy-rocks/draw2d/draw2dimg"
|
||||||
"github.com/go-gl/gl/v2.1/gl"
|
"github.com/go-gl/gl/v2.1/gl"
|
||||||
"gopkg.in/llgcode/draw2d.v1"
|
"github.com/golang/freetype/raster"
|
||||||
|
"github.com/golang/freetype/truetype"
|
||||||
|
|
||||||
|
"golang.org/x/image/font"
|
||||||
|
"golang.org/x/image/math/fixed"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -50,8 +58,8 @@ func (p *Painter) Paint(ss []raster.Span, done bool) {
|
||||||
vertices []int32
|
vertices []int32
|
||||||
)
|
)
|
||||||
for _, s := range ss {
|
for _, s := range ss {
|
||||||
ma := s.A >> 16
|
a := uint8((s.Alpha * p.ca / M16) >> 8)
|
||||||
a := uint8((ma * p.ca / M16) >> 8)
|
|
||||||
colors = p.colors[ci:]
|
colors = p.colors[ci:]
|
||||||
colors[0] = p.cr
|
colors[0] = p.cr
|
||||||
colors[1] = p.cg
|
colors[1] = p.cg
|
||||||
|
@ -112,67 +120,217 @@ func NewPainter() *Painter {
|
||||||
}
|
}
|
||||||
|
|
||||||
type GraphicContext struct {
|
type GraphicContext struct {
|
||||||
*draw2d.StackGraphicContext
|
*draw2dbase.StackGraphicContext
|
||||||
painter *Painter
|
painter *Painter
|
||||||
fillRasterizer *raster.Rasterizer
|
fillRasterizer *raster.Rasterizer
|
||||||
strokeRasterizer *raster.Rasterizer
|
strokeRasterizer *raster.Rasterizer
|
||||||
|
FontCache draw2d.FontCache
|
||||||
|
glyphCache draw2dbase.GlyphCache
|
||||||
|
glyphBuf *truetype.GlyphBuf
|
||||||
|
DPI int
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGraphicContext creates a new Graphic context from an image.
|
// NewGraphicContext creates a new Graphic context from an image.
|
||||||
func NewGraphicContext(width, height int) *GraphicContext {
|
func NewGraphicContext(width, height int) *GraphicContext {
|
||||||
gc := &GraphicContext{
|
gc := &GraphicContext{
|
||||||
draw2d.NewStackGraphicContext(),
|
draw2dbase.NewStackGraphicContext(),
|
||||||
NewPainter(),
|
NewPainter(),
|
||||||
raster.NewRasterizer(width, height),
|
raster.NewRasterizer(width, height),
|
||||||
raster.NewRasterizer(width, height),
|
raster.NewRasterizer(width, height),
|
||||||
|
draw2d.GetGlobalFontCache(),
|
||||||
|
draw2dbase.NewGlyphCache(),
|
||||||
|
&truetype.GlyphBuf{},
|
||||||
|
92,
|
||||||
}
|
}
|
||||||
return gc
|
return gc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (gc *GraphicContext) loadCurrentFont() (*truetype.Font, error) {
|
||||||
|
font, err := gc.FontCache.Load(gc.Current.FontData)
|
||||||
|
if err != nil {
|
||||||
|
font, err = gc.FontCache.Load(draw2dbase.DefaultFontData)
|
||||||
|
}
|
||||||
|
if font != nil {
|
||||||
|
gc.SetFont(font)
|
||||||
|
gc.SetFontSize(gc.Current.FontSize)
|
||||||
|
}
|
||||||
|
return font, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gc *GraphicContext) drawGlyph(glyph truetype.Index, dx, dy float64) error {
|
||||||
|
if err := gc.glyphBuf.Load(gc.Current.Font, fixed.Int26_6(gc.Current.Scale), glyph, font.HintingNone); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
e0 := 0
|
||||||
|
for _, e1 := range gc.glyphBuf.Ends {
|
||||||
|
DrawContour(gc, gc.glyphBuf.Points[e0:e1], dx, dy)
|
||||||
|
e0 = e1
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateStringPath creates a path from the string s at x, y, and returns the string width.
|
||||||
|
// The text is placed so that the left edge of the em square of the first character of s
|
||||||
|
// and the baseline intersect at x, y. The majority of the affected pixels will be
|
||||||
|
// 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 *GraphicContext) CreateStringPath(s string, x, y float64) float64 {
|
func (gc *GraphicContext) CreateStringPath(s string, x, y float64) float64 {
|
||||||
panic("not implemented")
|
f, err := gc.loadCurrentFont()
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return 0.0
|
||||||
|
}
|
||||||
|
startx := x
|
||||||
|
prev, hasPrev := truetype.Index(0), false
|
||||||
|
for _, rune := range s {
|
||||||
|
index := f.Index(rune)
|
||||||
|
if hasPrev {
|
||||||
|
x += fUnitsToFloat64(f.Kern(fixed.Int26_6(gc.Current.Scale), prev, index))
|
||||||
|
}
|
||||||
|
err := gc.drawGlyph(index, x, y)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return startx - x
|
||||||
|
}
|
||||||
|
x += fUnitsToFloat64(f.HMetric(fixed.Int26_6(gc.Current.Scale), index).AdvanceWidth)
|
||||||
|
prev, hasPrev = index, true
|
||||||
|
}
|
||||||
|
return x - startx
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gc *GraphicContext) FillStringAt(text string, x, y float64) (cursor float64) {
|
// FillString draws the text at point (0, 0)
|
||||||
panic("not implemented")
|
func (gc *GraphicContext) FillString(text string) (width float64) {
|
||||||
|
return gc.FillStringAt(text, 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FillStringAt draws the text at the specified point (x, y)
|
||||||
|
func (gc *GraphicContext) FillStringAt(text string, x, y float64) (width float64) {
|
||||||
|
f, err := gc.loadCurrentFont()
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return 0.0
|
||||||
|
}
|
||||||
|
startx := x
|
||||||
|
prev, hasPrev := truetype.Index(0), false
|
||||||
|
fontName := gc.GetFontName()
|
||||||
|
for _, r := range text {
|
||||||
|
index := f.Index(r)
|
||||||
|
if hasPrev {
|
||||||
|
x += fUnitsToFloat64(f.Kern(fixed.Int26_6(gc.Current.Scale), prev, index))
|
||||||
|
}
|
||||||
|
glyph := gc.glyphCache.Fetch(gc, fontName, r)
|
||||||
|
x += glyph.Fill(gc, x, y)
|
||||||
|
prev, hasPrev = index, true
|
||||||
|
}
|
||||||
|
return x - startx
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetStringBounds returns the approximate pixel bounds of the string s at x, y.
|
||||||
|
// 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 *GraphicContext) GetStringBounds(s string) (left, top, right, bottom float64) {
|
func (gc *GraphicContext) GetStringBounds(s string) (left, top, right, bottom float64) {
|
||||||
panic("not implemented")
|
f, err := gc.loadCurrentFont()
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return 0, 0, 0, 0
|
||||||
|
}
|
||||||
|
top, left, bottom, right = 10e6, 10e6, -10e6, -10e6
|
||||||
|
cursor := 0.0
|
||||||
|
prev, hasPrev := truetype.Index(0), false
|
||||||
|
for _, rune := range s {
|
||||||
|
index := f.Index(rune)
|
||||||
|
if hasPrev {
|
||||||
|
cursor += fUnitsToFloat64(f.Kern(fixed.Int26_6(gc.Current.Scale), prev, index))
|
||||||
|
}
|
||||||
|
if err := gc.glyphBuf.Load(gc.Current.Font, fixed.Int26_6(gc.Current.Scale), index, font.HintingNone); err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return 0, 0, 0, 0
|
||||||
|
}
|
||||||
|
e0 := 0
|
||||||
|
for _, e1 := range gc.glyphBuf.Ends {
|
||||||
|
ps := gc.glyphBuf.Points[e0:e1]
|
||||||
|
for _, p := range ps {
|
||||||
|
x, y := pointToF64Point(p)
|
||||||
|
top = math.Min(top, y)
|
||||||
|
bottom = math.Max(bottom, y)
|
||||||
|
left = math.Min(left, x+cursor)
|
||||||
|
right = math.Max(right, x+cursor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cursor += fUnitsToFloat64(f.HMetric(fixed.Int26_6(gc.Current.Scale), index).AdvanceWidth)
|
||||||
|
prev, hasPrev = index, true
|
||||||
|
}
|
||||||
|
return left, top, right, bottom
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gc *GraphicContext) StrokeString(text string) (cursor float64) {
|
// StrokeString draws the contour of the text at point (0, 0)
|
||||||
|
func (gc *GraphicContext) StrokeString(text string) (width float64) {
|
||||||
return gc.StrokeStringAt(text, 0, 0)
|
return gc.StrokeStringAt(text, 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gc *GraphicContext) StrokeStringAt(text string, x, y float64) (cursor float64) {
|
// StrokeStringAt draws the contour of the text at point (x, y)
|
||||||
width := gc.CreateStringPath(text, x, y)
|
func (gc *GraphicContext) StrokeStringAt(text string, x, y float64) (width float64) {
|
||||||
gc.Stroke()
|
f, err := gc.loadCurrentFont()
|
||||||
return width
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return 0.0
|
||||||
|
}
|
||||||
|
startx := x
|
||||||
|
prev, hasPrev := truetype.Index(0), false
|
||||||
|
fontName := gc.GetFontName()
|
||||||
|
for _, r := range text {
|
||||||
|
index := f.Index(r)
|
||||||
|
if hasPrev {
|
||||||
|
x += fUnitsToFloat64(f.Kern(fixed.Int26_6(gc.Current.Scale), prev, index))
|
||||||
|
}
|
||||||
|
glyph := gc.glyphCache.Fetch(gc, fontName, r)
|
||||||
|
x += glyph.Stroke(gc, x, y)
|
||||||
|
prev, hasPrev = index, true
|
||||||
|
}
|
||||||
|
return x - startx
|
||||||
|
}
|
||||||
|
|
||||||
|
// recalc recalculates scale and bounds values from the font size, screen
|
||||||
|
// resolution and font metrics, and invalidates the glyph cache.
|
||||||
|
func (gc *GraphicContext) recalc() {
|
||||||
|
gc.Current.Scale = gc.Current.FontSize * float64(gc.DPI) * (64.0 / 72.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gc *GraphicContext) SetDPI(dpi int) {
|
func (gc *GraphicContext) SetDPI(dpi int) {
|
||||||
|
gc.DPI = dpi
|
||||||
|
gc.recalc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetFont sets the font used to draw text.
|
||||||
|
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 *GraphicContext) SetFontSize(fontSize float64) {
|
||||||
|
gc.Current.FontSize = fontSize
|
||||||
|
gc.recalc()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gc *GraphicContext) GetDPI() int {
|
func (gc *GraphicContext) GetDPI() int {
|
||||||
return -1
|
return gc.DPI
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO
|
||||||
func (gc *GraphicContext) Clear() {
|
func (gc *GraphicContext) Clear() {
|
||||||
|
panic("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO
|
||||||
func (gc *GraphicContext) ClearRect(x1, y1, x2, y2 int) {
|
func (gc *GraphicContext) ClearRect(x1, y1, x2, y2 int) {
|
||||||
|
panic("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO
|
||||||
func (gc *GraphicContext) DrawImage(img image.Image) {
|
func (gc *GraphicContext) DrawImage(img image.Image) {
|
||||||
|
panic("not implemented")
|
||||||
}
|
|
||||||
|
|
||||||
func (gc *GraphicContext) FillString(text string) (cursor float64) {
|
|
||||||
return 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gc *GraphicContext) paint(rasterizer *raster.Rasterizer, color color.Color) {
|
func (gc *GraphicContext) paint(rasterizer *raster.Rasterizer, color color.Color) {
|
||||||
|
@ -180,56 +338,76 @@ func (gc *GraphicContext) paint(rasterizer *raster.Rasterizer, color color.Color
|
||||||
rasterizer.Rasterize(gc.painter)
|
rasterizer.Rasterize(gc.painter)
|
||||||
rasterizer.Clear()
|
rasterizer.Clear()
|
||||||
gc.painter.Flush()
|
gc.painter.Flush()
|
||||||
|
gc.Current.Path.Clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gc *GraphicContext) Stroke(paths ...*draw2d.PathStorage) {
|
func (gc *GraphicContext) Stroke(paths ...*draw2d.Path) {
|
||||||
paths = append(paths, gc.Current.Path)
|
paths = append(paths, gc.Current.Path)
|
||||||
gc.strokeRasterizer.UseNonZeroWinding = true
|
gc.strokeRasterizer.UseNonZeroWinding = true
|
||||||
|
|
||||||
stroker := draw2d.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2d.NewVertexMatrixTransform(gc.Current.Tr, draw2d.NewVertexAdder(gc.strokeRasterizer)))
|
stroker := draw2dbase.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2dbase.Transformer{Tr: gc.Current.Tr, Flattener: draw2dimg.FtLineBuilder{Adder: gc.strokeRasterizer}})
|
||||||
stroker.HalfLineWidth = gc.Current.LineWidth / 2
|
stroker.HalfLineWidth = gc.Current.LineWidth / 2
|
||||||
var pathConverter *draw2d.PathConverter
|
|
||||||
|
var liner draw2dbase.Flattener
|
||||||
if gc.Current.Dash != nil && len(gc.Current.Dash) > 0 {
|
if gc.Current.Dash != nil && len(gc.Current.Dash) > 0 {
|
||||||
dasher := draw2d.NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker)
|
liner = draw2dbase.NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker)
|
||||||
pathConverter = draw2d.NewPathConverter(dasher)
|
|
||||||
} else {
|
} else {
|
||||||
pathConverter = draw2d.NewPathConverter(stroker)
|
liner = stroker
|
||||||
|
}
|
||||||
|
for _, p := range paths {
|
||||||
|
draw2dbase.Flatten(p, liner, gc.Current.Tr.GetScale())
|
||||||
}
|
}
|
||||||
pathConverter.ApproximationScale = gc.Current.Tr.GetScale() // From agg code
|
|
||||||
pathConverter.Convert(paths...)
|
|
||||||
|
|
||||||
gc.paint(gc.strokeRasterizer, gc.Current.StrokeColor)
|
gc.paint(gc.strokeRasterizer, gc.Current.StrokeColor)
|
||||||
gc.Current.Path = draw2d.NewPathStorage()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gc *GraphicContext) Fill(paths ...*draw2d.PathStorage) {
|
func (gc *GraphicContext) Fill(paths ...*draw2d.Path) {
|
||||||
paths = append(paths, gc.Current.Path)
|
paths = append(paths, gc.Current.Path)
|
||||||
gc.fillRasterizer.UseNonZeroWinding = gc.Current.FillRule.UseNonZeroWinding()
|
gc.fillRasterizer.UseNonZeroWinding = useNonZeroWinding(gc.Current.FillRule)
|
||||||
|
|
||||||
pathConverter := draw2d.NewPathConverter(draw2d.NewVertexMatrixTransform(gc.Current.Tr, draw2d.NewVertexAdder(gc.fillRasterizer)))
|
/**** first method ****/
|
||||||
pathConverter.ApproximationScale = gc.Current.Tr.GetScale() // From agg code
|
flattener := draw2dbase.Transformer{Tr: gc.Current.Tr, Flattener: draw2dimg.FtLineBuilder{Adder: gc.fillRasterizer}}
|
||||||
pathConverter.Convert(paths...)
|
for _, p := range paths {
|
||||||
|
draw2dbase.Flatten(p, flattener, gc.Current.Tr.GetScale())
|
||||||
|
}
|
||||||
|
|
||||||
gc.paint(gc.fillRasterizer, gc.Current.FillColor)
|
gc.paint(gc.fillRasterizer, gc.Current.FillColor)
|
||||||
gc.Current.Path = draw2d.NewPathStorage()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gc *GraphicContext) FillStroke(paths ...*draw2d.PathStorage) {
|
func (gc *GraphicContext) FillStroke(paths ...*draw2d.Path) {
|
||||||
gc.fillRasterizer.UseNonZeroWinding = gc.Current.FillRule.UseNonZeroWinding()
|
paths = append(paths, gc.Current.Path)
|
||||||
|
gc.fillRasterizer.UseNonZeroWinding = useNonZeroWinding(gc.Current.FillRule)
|
||||||
gc.strokeRasterizer.UseNonZeroWinding = true
|
gc.strokeRasterizer.UseNonZeroWinding = true
|
||||||
|
|
||||||
filler := draw2d.NewVertexMatrixTransform(gc.Current.Tr, draw2d.NewVertexAdder(gc.fillRasterizer))
|
flattener := draw2dbase.Transformer{Tr: gc.Current.Tr, Flattener: draw2dimg.FtLineBuilder{Adder: gc.fillRasterizer}}
|
||||||
|
|
||||||
stroker := draw2d.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2d.NewVertexMatrixTransform(gc.Current.Tr, draw2d.NewVertexAdder(gc.strokeRasterizer)))
|
stroker := draw2dbase.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2dbase.Transformer{Tr: gc.Current.Tr, Flattener: draw2dimg.FtLineBuilder{Adder: gc.strokeRasterizer}})
|
||||||
stroker.HalfLineWidth = gc.Current.LineWidth / 2
|
stroker.HalfLineWidth = gc.Current.LineWidth / 2
|
||||||
|
|
||||||
demux := draw2d.NewDemuxConverter(filler, stroker)
|
var liner draw2dbase.Flattener
|
||||||
paths = append(paths, gc.Current.Path)
|
if gc.Current.Dash != nil && len(gc.Current.Dash) > 0 {
|
||||||
pathConverter := draw2d.NewPathConverter(demux)
|
liner = draw2dbase.NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker)
|
||||||
pathConverter.ApproximationScale = gc.Current.Tr.GetScale() // From agg code
|
} else {
|
||||||
pathConverter.Convert(paths...)
|
liner = stroker
|
||||||
|
}
|
||||||
gc.paint(gc.fillRasterizer, gc.Current.FillColor)
|
|
||||||
gc.paint(gc.strokeRasterizer, gc.Current.StrokeColor)
|
demux := draw2dbase.DemuxFlattener{Flatteners: []draw2dbase.Flattener{flattener, liner}}
|
||||||
gc.Current.Path = draw2d.NewPathStorage()
|
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 useNonZeroWinding(f draw2d.FillRule) bool {
|
||||||
|
switch f {
|
||||||
|
case draw2d.FillRuleEvenOdd:
|
||||||
|
return false
|
||||||
|
case draw2d.FillRuleWinding:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
82
draw2dgl/text.go
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
package draw2dgl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.fromouter.space/crunchy-rocks/draw2d"
|
||||||
|
"git.fromouter.space/crunchy-rocks/freetype/truetype"
|
||||||
|
|
||||||
|
"golang.org/x/image/math/fixed"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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 fixed.Int26_6) 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(fixed.Int26_6(font.FUnitsPerEm()))
|
||||||
|
scale := size / float64(font.FUnitsPerEm())
|
||||||
|
return FontExtents{
|
||||||
|
Ascent: float64(bounds.Max.Y) * scale,
|
||||||
|
Descent: float64(bounds.Min.Y) * scale,
|
||||||
|
Height: float64(bounds.Max.Y-bounds.Min.Y) * scale,
|
||||||
|
}
|
||||||
|
}
|
8
draw2dimg/README.md
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
draw2d/draw2dimg
|
||||||
|
=================
|
||||||
|
|
||||||
|
[![Coverage](http://gocover.io/_badge/github.com/llgcode/draw2d/draw2dimg?0)](http://gocover.io/github.com/llgcode/draw2d/draw2dimg)
|
||||||
|
[![GoDoc](https://godoc.org/github.com/llgcode/draw2d/draw2dimg?status.svg)](https://godoc.org/github.com/llgcode/draw2d/draw2dimg)
|
||||||
|
|
||||||
|
|
||||||
|
draw2d implementation that generates raster images using https://github.com/golang/freetype package.
|
190
draw2dimg/curve_limit_test.go
Normal file
|
@ -0,0 +1,190 @@
|
||||||
|
package draw2dimg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.fromouter.space/crunchy-rocks/draw2d"
|
||||||
|
"git.fromouter.space/crunchy-rocks/draw2d/draw2dkit"
|
||||||
|
"git.fromouter.space/crunchy-rocks/freetype/truetype"
|
||||||
|
"golang.org/x/image/font/gofont/goregular"
|
||||||
|
)
|
||||||
|
|
||||||
|
// font generated from icomoon.io and converted to go byte slice
|
||||||
|
// contains only two glyphs
|
||||||
|
// \u2716 - which should look like a cross
|
||||||
|
// \u25cb - which should look like an empty circle
|
||||||
|
var icoTTF = []byte{
|
||||||
|
0, 1, 0, 0, 0, 12, 0, 128, 0, 3, 0, 64, 71, 83, 85, 66, 219, 7, 221, 185,
|
||||||
|
0, 0, 0, 204, 0, 0, 0, 188, 79, 83, 47, 50, 175, 17, 51, 150, 0, 0, 1, 136,
|
||||||
|
0, 0, 0, 96, 99, 109, 97, 112, 37, 204, 43, 67, 0, 0, 1, 232, 0, 0, 0, 148,
|
||||||
|
103, 97, 115, 112, 0, 0, 0, 16, 0, 0, 2, 124, 0, 0, 0, 8, 103, 108, 121, 102,
|
||||||
|
163, 112, 233, 32, 0, 0, 2, 132, 0, 0, 3, 64, 104, 101, 97, 100, 15, 49, 194, 135,
|
||||||
|
0, 0, 5, 196, 0, 0, 0, 54, 104, 104, 101, 97, 7, 194, 3, 217, 0, 0, 5, 252,
|
||||||
|
0, 0, 0, 36, 104, 109, 116, 120, 14, 0, 0, 2, 0, 0, 6, 32, 0, 0, 0, 96,
|
||||||
|
108, 111, 99, 97, 6, 168, 5, 226, 0, 0, 6, 128, 0, 0, 0, 50, 109, 97, 120, 112,
|
||||||
|
0, 27, 0, 86, 0, 0, 6, 180, 0, 0, 0, 32, 110, 97, 109, 101, 108, 36, 213, 69,
|
||||||
|
0, 0, 6, 212, 0, 0, 1, 170, 112, 111, 115, 116, 0, 3, 0, 0, 0, 0, 8, 128,
|
||||||
|
0, 0, 0, 32, 0, 1, 0, 0, 0, 10, 0, 30, 0, 44, 0, 1, 108, 97, 116, 110,
|
||||||
|
0, 8, 0, 4, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 108, 105, 103, 97,
|
||||||
|
0, 8, 0, 0, 0, 1, 0, 0, 0, 1, 0, 4, 0, 4, 0, 0, 0, 1, 0, 10,
|
||||||
|
0, 0, 0, 1, 0, 12, 0, 3, 0, 22, 0, 54, 0, 120, 0, 1, 0, 3, 0, 8,
|
||||||
|
0, 17, 0, 23, 0, 2, 0, 6, 0, 18, 0, 22, 0, 5, 0, 17, 0, 16, 0, 18,
|
||||||
|
0, 18, 0, 22, 0, 6, 0, 6, 0, 15, 0, 8, 0, 10, 0, 14, 0, 2, 0, 6,
|
||||||
|
0, 38, 0, 21, 0, 15, 0, 6, 0, 9, 0, 12, 0, 16, 0, 4, 0, 20, 0, 15,
|
||||||
|
0, 8, 0, 11, 0, 10, 0, 8, 0, 13, 0, 10, 0, 9, 0, 21, 0, 13, 0, 6,
|
||||||
|
0, 9, 0, 12, 0, 16, 0, 4, 0, 7, 0, 20, 0, 19, 0, 19, 0, 16, 0, 15,
|
||||||
|
0, 5, 0, 1, 0, 4, 0, 22, 0, 2, 0, 23, 0, 3, 3, 85, 1, 144, 0, 5,
|
||||||
|
0, 0, 2, 153, 2, 204, 0, 0, 0, 143, 2, 153, 2, 204, 0, 0, 1, 235, 0, 51,
|
||||||
|
1, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
|
||||||
|
160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 39, 23,
|
||||||
|
3, 192, 255, 192, 0, 64, 3, 192, 0, 64, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 3, 0, 0, 0, 3, 0, 0, 0, 28,
|
||||||
|
0, 1, 0, 3, 0, 0, 0, 28, 0, 3, 0, 1, 0, 0, 0, 28, 0, 4, 0, 120,
|
||||||
|
0, 0, 0, 26, 0, 16, 0, 3, 0, 10, 0, 1, 0, 32, 0, 45, 0, 51, 0, 101,
|
||||||
|
0, 105, 0, 108, 0, 111, 0, 117, 37, 203, 39, 23, 255, 253, 255, 255, 0, 0, 0, 0,
|
||||||
|
0, 32, 0, 45, 0, 51, 0, 97, 0, 104, 0, 107, 0, 110, 0, 114, 37, 203, 39, 22,
|
||||||
|
255, 253, 255, 255, 0, 1, 255, 227, 255, 215, 255, 210, 255, 165, 255, 163, 255, 162, 255, 161,
|
||||||
|
255, 159, 218, 74, 217, 0, 0, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1,
|
||||||
|
255, 255, 0, 15, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
|
||||||
|
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
|
||||||
|
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
|
||||||
|
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
|
||||||
|
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
|
||||||
|
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
|
||||||
|
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
|
||||||
|
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
|
||||||
|
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
|
||||||
|
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
|
||||||
|
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
|
||||||
|
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
|
||||||
|
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
|
||||||
|
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
|
||||||
|
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
|
||||||
|
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
|
||||||
|
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
|
||||||
|
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
|
||||||
|
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
|
||||||
|
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
|
||||||
|
1, 0, 0, 0, 0, 2, 0, 0, 255, 192, 4, 0, 3, 192, 0, 27, 0, 55, 0, 0,
|
||||||
|
1, 34, 7, 14, 1, 7, 6, 21, 20, 23, 30, 1, 23, 22, 51, 50, 55, 62, 1, 55,
|
||||||
|
54, 53, 52, 39, 46, 1, 39, 38, 3, 34, 39, 46, 1, 39, 38, 53, 52, 55, 62, 1,
|
||||||
|
55, 54, 51, 50, 23, 30, 1, 23, 22, 21, 20, 7, 14, 1, 7, 6, 2, 0, 106, 93,
|
||||||
|
94, 139, 40, 40, 40, 40, 139, 94, 93, 106, 106, 93, 94, 139, 40, 40, 40, 40, 139, 94,
|
||||||
|
93, 106, 80, 69, 70, 105, 30, 30, 30, 30, 105, 70, 69, 80, 80, 69, 70, 105, 30, 30,
|
||||||
|
30, 30, 105, 70, 69, 3, 192, 40, 40, 139, 94, 93, 106, 106, 93, 94, 139, 40, 40, 40,
|
||||||
|
40, 139, 94, 93, 106, 106, 93, 94, 139, 40, 40, 252, 128, 30, 30, 105, 70, 69, 80, 80,
|
||||||
|
69, 70, 105, 30, 30, 30, 30, 105, 70, 69, 80, 80, 69, 70, 105, 30, 30, 0, 0, 0,
|
||||||
|
0, 1, 0, 2, 255, 194, 3, 254, 3, 190, 0, 83, 0, 0, 37, 56, 1, 49, 9, 1,
|
||||||
|
56, 1, 49, 62, 1, 55, 54, 38, 47, 1, 46, 1, 7, 14, 1, 7, 56, 1, 49, 9,
|
||||||
|
1, 56, 1, 49, 46, 1, 39, 38, 6, 15, 1, 14, 1, 23, 30, 1, 23, 56, 1, 49,
|
||||||
|
9, 1, 56, 1, 49, 14, 1, 7, 6, 22, 31, 1, 30, 1, 55, 62, 1, 55, 56, 1,
|
||||||
|
49, 9, 1, 56, 1, 49, 30, 1, 23, 22, 54, 63, 1, 62, 1, 39, 46, 1, 3, 247,
|
||||||
|
254, 201, 1, 55, 2, 4, 1, 3, 3, 7, 147, 7, 18, 9, 3, 6, 2, 254, 201, 254,
|
||||||
|
201, 2, 6, 3, 9, 18, 7, 147, 7, 3, 3, 1, 4, 2, 1, 55, 254, 201, 2, 4,
|
||||||
|
1, 3, 3, 7, 147, 7, 18, 9, 3, 6, 2, 1, 55, 1, 55, 2, 6, 3, 9, 18,
|
||||||
|
7, 147, 7, 3, 3, 1, 4, 137, 1, 55, 1, 55, 2, 6, 3, 9, 18, 7, 147, 7,
|
||||||
|
3, 3, 1, 4, 2, 254, 201, 1, 55, 2, 4, 1, 3, 3, 7, 147, 7, 18, 9, 3,
|
||||||
|
6, 2, 254, 201, 254, 201, 2, 6, 3, 9, 18, 7, 147, 7, 3, 3, 1, 4, 2, 1,
|
||||||
|
55, 254, 201, 2, 4, 1, 3, 3, 7, 147, 7, 18, 9, 3, 6, 0, 0, 1, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57, 1, 0, 0, 0, 0, 1, 0, 0,
|
||||||
|
0, 1, 0, 0, 32, 120, 21, 165, 95, 15, 60, 245, 0, 11, 4, 0, 0, 0, 0, 0,
|
||||||
|
214, 9, 63, 5, 0, 0, 0, 0, 214, 9, 63, 5, 0, 0, 255, 192, 4, 0, 3, 192,
|
||||||
|
0, 0, 0, 8, 0, 2, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 3, 192, 255, 192,
|
||||||
|
0, 0, 4, 0, 0, 0, 0, 0, 4, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 24, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 2,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 20, 0, 30, 0, 40, 0, 50, 0, 60,
|
||||||
|
0, 70, 0, 80, 0, 90, 0, 100, 0, 110, 0, 120, 0, 130, 0, 140, 0, 150, 0, 160,
|
||||||
|
0, 170, 0, 180, 0, 190, 0, 200, 1, 32, 1, 150, 1, 160, 0, 0, 0, 1, 0, 0,
|
||||||
|
0, 24, 0, 84, 0, 2, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 0, 174, 0, 1, 0, 0, 0, 0,
|
||||||
|
0, 1, 0, 10, 0, 0, 0, 1, 0, 0, 0, 0, 0, 2, 0, 7, 0, 123, 0, 1,
|
||||||
|
0, 0, 0, 0, 0, 3, 0, 10, 0, 63, 0, 1, 0, 0, 0, 0, 0, 4, 0, 10,
|
||||||
|
0, 144, 0, 1, 0, 0, 0, 0, 0, 5, 0, 11, 0, 30, 0, 1, 0, 0, 0, 0,
|
||||||
|
0, 6, 0, 10, 0, 93, 0, 1, 0, 0, 0, 0, 0, 10, 0, 26, 0, 174, 0, 3,
|
||||||
|
0, 1, 4, 9, 0, 1, 0, 20, 0, 10, 0, 3, 0, 1, 4, 9, 0, 2, 0, 14,
|
||||||
|
0, 130, 0, 3, 0, 1, 4, 9, 0, 3, 0, 20, 0, 73, 0, 3, 0, 1, 4, 9,
|
||||||
|
0, 4, 0, 20, 0, 154, 0, 3, 0, 1, 4, 9, 0, 5, 0, 22, 0, 41, 0, 3,
|
||||||
|
0, 1, 4, 9, 0, 6, 0, 20, 0, 103, 0, 3, 0, 1, 4, 9, 0, 10, 0, 52,
|
||||||
|
0, 200, 105, 99, 111, 45, 112, 101, 110, 101, 103, 111, 0, 105, 0, 99, 0, 111, 0, 45,
|
||||||
|
0, 112, 0, 101, 0, 110, 0, 101, 0, 103, 0, 111, 86, 101, 114, 115, 105, 111, 110, 32,
|
||||||
|
49, 46, 48, 0, 86, 0, 101, 0, 114, 0, 115, 0, 105, 0, 111, 0, 110, 0, 32, 0,
|
||||||
|
49, 0, 46, 0, 48, 105, 99, 111, 45, 112, 101, 110, 101, 103, 111, 0, 105, 0, 99, 0,
|
||||||
|
111, 0, 45, 0, 112, 0, 101, 0, 110, 0, 101, 0, 103, 0, 111, 105, 99, 111, 45, 112,
|
||||||
|
101, 110, 101, 103, 111, 0, 105, 0, 99, 0, 111, 0, 45, 0, 112, 0, 101, 0, 110, 0,
|
||||||
|
101, 0, 103, 0, 111, 82, 101, 103, 117, 108, 97, 114, 0, 82, 0, 101, 0, 103, 0, 117,
|
||||||
|
0, 108, 0, 97, 0, 114, 105, 99, 111, 45, 112, 101, 110, 101, 103, 111, 0, 105, 0, 99,
|
||||||
|
0, 111, 0, 45, 0, 112, 0, 101, 0, 110, 0, 101, 0, 103, 0, 111, 70, 111, 110, 116,
|
||||||
|
32, 103, 101, 110, 101, 114, 97, 116, 101, 100, 32, 98, 121, 32, 73, 99, 111, 77, 111, 111,
|
||||||
|
110, 46, 0, 70, 0, 111, 0, 110, 0, 116, 0, 32, 0, 103, 0, 101, 0, 110, 0, 101,
|
||||||
|
0, 114, 0, 97, 0, 116, 0, 101, 0, 100, 0, 32, 0, 98, 0, 121, 0, 32, 0, 73,
|
||||||
|
0, 99, 0, 111, 0, 77, 0, 111, 0, 111, 0, 110, 0, 46, 0, 0, 0, 3, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
type customFontCache map[string]*truetype.Font
|
||||||
|
|
||||||
|
func (fc customFontCache) Store(fd draw2d.FontData, font *truetype.Font) {
|
||||||
|
fc[fd.Name] = font
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fc customFontCache) Load(fd draw2d.FontData) (*truetype.Font, error) {
|
||||||
|
font, stored := fc[fd.Name]
|
||||||
|
if !stored {
|
||||||
|
return nil, fmt.Errorf("font %s is not stored in font cache", fd.Name)
|
||||||
|
}
|
||||||
|
return font, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func initFontCache() { // init font cache
|
||||||
|
fontCache := customFontCache{}
|
||||||
|
// add gofont to cache
|
||||||
|
gofont, err := truetype.Parse(goregular.TTF)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
fontCache.Store(draw2d.FontData{Name: "goregular"}, gofont)
|
||||||
|
// add icofont to cache
|
||||||
|
icofont, err := truetype.Parse(icoTTF)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
fontCache.Store(draw2d.FontData{Name: "ico"}, icofont)
|
||||||
|
|
||||||
|
draw2d.SetFontCache(fontCache)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCurveIndexOutOfRange(t *testing.T) {
|
||||||
|
|
||||||
|
initFontCache()
|
||||||
|
|
||||||
|
// Initialize the graphic context on an RGBA image
|
||||||
|
dest := image.NewRGBA(image.Rect(0, 0, 512, 512))
|
||||||
|
gc := NewGraphicContext(dest)
|
||||||
|
|
||||||
|
// background
|
||||||
|
gc.SetFillColor(color.RGBA{0xef, 0xef, 0xef, 0xff})
|
||||||
|
draw2dkit.Rectangle(gc, 0, 0, 512, 512)
|
||||||
|
gc.Fill()
|
||||||
|
|
||||||
|
// text
|
||||||
|
gc.SetFontSize(20)
|
||||||
|
gc.SetFillColor(color.RGBA{0x10, 0x10, 0x10, 0xff})
|
||||||
|
gc.SetFontData(draw2d.FontData{Name: "goregular"})
|
||||||
|
|
||||||
|
// gc.FillStringAt("Hello", 128, 120) // this works well
|
||||||
|
|
||||||
|
gc.SetFontData(draw2d.FontData{Name: "ico"})
|
||||||
|
gc.FillStringAt("\u25cb", 128, 150) // this also works
|
||||||
|
gc.FillStringAt("\u2716", 128, 170) // Works now
|
||||||
|
|
||||||
|
SaveToPngFile("_test_hello.png", dest)
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package draw2d
|
package draw2dimg
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
449
draw2dimg/ftgc.go
Normal file
|
@ -0,0 +1,449 @@
|
||||||
|
// Copyright 2010 The draw2d Authors. All rights reserved.
|
||||||
|
// created: 21/11/2010 by Laurent Le Goff
|
||||||
|
|
||||||
|
package draw2dimg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
"log"
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"git.fromouter.space/crunchy-rocks/draw2d"
|
||||||
|
"git.fromouter.space/crunchy-rocks/draw2d/draw2dbase"
|
||||||
|
"git.fromouter.space/crunchy-rocks/emoji"
|
||||||
|
|
||||||
|
"git.fromouter.space/crunchy-rocks/freetype/raster"
|
||||||
|
"git.fromouter.space/crunchy-rocks/freetype/truetype"
|
||||||
|
|
||||||
|
"golang.org/x/image/draw"
|
||||||
|
"golang.org/x/image/font"
|
||||||
|
"golang.org/x/image/math/f64"
|
||||||
|
"golang.org/x/image/math/fixed"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Painter implements the freetype raster.Painter and has a SetColor method like the RGBAPainter
|
||||||
|
type Painter interface {
|
||||||
|
raster.Painter
|
||||||
|
SetColor(color color.Color)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphicContext is the implementation of draw2d.GraphicContext for a raster image
|
||||||
|
type GraphicContext struct {
|
||||||
|
*draw2dbase.StackGraphicContext
|
||||||
|
img draw.Image
|
||||||
|
painter Painter
|
||||||
|
fillRasterizer *raster.Rasterizer
|
||||||
|
strokeRasterizer *raster.Rasterizer
|
||||||
|
FontCache draw2d.FontCache
|
||||||
|
glyphCache draw2dbase.GlyphCache
|
||||||
|
glyphBuf *truetype.GlyphBuf
|
||||||
|
DPI int
|
||||||
|
Emojis emoji.Table
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImageFilter defines the type of filter to use
|
||||||
|
type ImageFilter int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// LinearFilter defines a linear filter
|
||||||
|
LinearFilter ImageFilter = iota
|
||||||
|
// BilinearFilter defines a bilinear filter
|
||||||
|
BilinearFilter
|
||||||
|
// BicubicFilter defines a bicubic filter
|
||||||
|
BicubicFilter
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewGraphicContext creates a new Graphic context from an image.
|
||||||
|
func NewGraphicContext(img draw.Image) *GraphicContext {
|
||||||
|
var painter Painter
|
||||||
|
switch selectImage := img.(type) {
|
||||||
|
case *image.RGBA:
|
||||||
|
painter = raster.NewRGBAPainter(selectImage)
|
||||||
|
default:
|
||||||
|
panic("Image type not supported")
|
||||||
|
}
|
||||||
|
return NewGraphicContextWithPainter(img, painter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGraphicContextWithPainter creates a new Graphic context from an image and a Painter (see Freetype-go)
|
||||||
|
func NewGraphicContextWithPainter(img draw.Image, painter Painter) *GraphicContext {
|
||||||
|
width, height := img.Bounds().Dx(), img.Bounds().Dy()
|
||||||
|
dpi := 92
|
||||||
|
gc := &GraphicContext{
|
||||||
|
draw2dbase.NewStackGraphicContext(),
|
||||||
|
img,
|
||||||
|
painter,
|
||||||
|
raster.NewRasterizer(width, height),
|
||||||
|
raster.NewRasterizer(width, height),
|
||||||
|
draw2d.GetGlobalFontCache(),
|
||||||
|
draw2dbase.NewGlyphCache(),
|
||||||
|
&truetype.GlyphBuf{},
|
||||||
|
dpi,
|
||||||
|
make(emoji.Table),
|
||||||
|
}
|
||||||
|
return gc
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDPI returns the resolution of the Image GraphicContext
|
||||||
|
func (gc *GraphicContext) GetDPI() int {
|
||||||
|
return gc.DPI
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
var transformer draw.Transformer
|
||||||
|
switch filter {
|
||||||
|
case LinearFilter:
|
||||||
|
transformer = draw.NearestNeighbor
|
||||||
|
case BilinearFilter:
|
||||||
|
transformer = draw.BiLinear
|
||||||
|
case BicubicFilter:
|
||||||
|
transformer = draw.CatmullRom
|
||||||
|
}
|
||||||
|
transformer.Transform(dest, f64.Aff3{tr[0], tr[1], tr[4], tr[2], tr[3], tr[5]}, src, src.Bounds(), op, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FillString draws the text at point (0, 0)
|
||||||
|
func (gc *GraphicContext) FillString(text string) (width float64) {
|
||||||
|
return gc.FillStringAt(text, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
const emojiSpacing = 10
|
||||||
|
const emojiScale = 110
|
||||||
|
|
||||||
|
// FillStringAt draws the text at the specified point (x, y)
|
||||||
|
func (gc *GraphicContext) FillStringAt(text string, x, y float64) (width float64) {
|
||||||
|
f, err := gc.loadCurrentFont()
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return 0.0
|
||||||
|
}
|
||||||
|
startx := x
|
||||||
|
prev, hasPrev := truetype.Index(0), false
|
||||||
|
fontName := gc.GetFontName()
|
||||||
|
for fragment := range gc.Emojis.Iterate(text) {
|
||||||
|
if fragment.IsEmoji {
|
||||||
|
img, err := LoadFromPngFile(fragment.Emoji.Path)
|
||||||
|
if err == nil {
|
||||||
|
gc.Save()
|
||||||
|
scale := gc.GetFontSize() / 100
|
||||||
|
gc.Translate(x+scale*emojiSpacing, y-scale*emojiScale)
|
||||||
|
gc.Scale(scale, scale)
|
||||||
|
gc.DrawImage(img)
|
||||||
|
gc.Restore()
|
||||||
|
x += scale*float64(img.Bounds().Size().X) + scale*emojiSpacing*2
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
index := f.Index(fragment.Rune)
|
||||||
|
if hasPrev {
|
||||||
|
x += fUnitsToFloat64(f.Kern(fixed.Int26_6(gc.Current.Scale), prev, index))
|
||||||
|
}
|
||||||
|
glyph := gc.glyphCache.Fetch(gc, fontName, fragment.Rune)
|
||||||
|
x += glyph.Fill(gc, x, y)
|
||||||
|
prev, hasPrev = index, true
|
||||||
|
}
|
||||||
|
return x - startx
|
||||||
|
}
|
||||||
|
|
||||||
|
// StrokeString draws the contour of the text at point (0, 0)
|
||||||
|
func (gc *GraphicContext) StrokeString(text string) (width float64) {
|
||||||
|
return gc.StrokeStringAt(text, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StrokeStringAt draws the contour of the text at point (x, y)
|
||||||
|
func (gc *GraphicContext) StrokeStringAt(text string, x, y float64) (width float64) {
|
||||||
|
f, err := gc.loadCurrentFont()
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return 0.0
|
||||||
|
}
|
||||||
|
startx := x
|
||||||
|
prev, hasPrev := truetype.Index(0), false
|
||||||
|
fontName := gc.GetFontName()
|
||||||
|
for fragment := range gc.Emojis.Iterate(text) {
|
||||||
|
if fragment.IsEmoji {
|
||||||
|
img, err := LoadFromPngFile(fragment.Emoji.Path)
|
||||||
|
if err == nil {
|
||||||
|
gc.Save()
|
||||||
|
scale := gc.GetFontSize() / 100
|
||||||
|
gc.Translate(x+scale*emojiSpacing, y-scale*emojiScale)
|
||||||
|
gc.Scale(scale, scale)
|
||||||
|
gc.DrawImage(img)
|
||||||
|
gc.Restore()
|
||||||
|
x += scale*float64(img.Bounds().Size().X) + scale*emojiSpacing*2
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
index := f.Index(fragment.Rune)
|
||||||
|
if hasPrev {
|
||||||
|
x += fUnitsToFloat64(f.Kern(fixed.Int26_6(gc.Current.Scale), prev, index))
|
||||||
|
}
|
||||||
|
glyph := gc.glyphCache.Fetch(gc, fontName, fragment.Rune)
|
||||||
|
x += glyph.Stroke(gc, x, y)
|
||||||
|
prev, hasPrev = index, true
|
||||||
|
}
|
||||||
|
return x - startx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gc *GraphicContext) loadCurrentFont() (*truetype.Font, error) {
|
||||||
|
font, err := gc.FontCache.Load(gc.Current.FontData)
|
||||||
|
if err != nil {
|
||||||
|
font, err = gc.FontCache.Load(draw2dbase.DefaultFontData)
|
||||||
|
}
|
||||||
|
if font != nil {
|
||||||
|
gc.SetFont(font)
|
||||||
|
gc.SetFontSize(gc.Current.FontSize)
|
||||||
|
}
|
||||||
|
return font, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 (gc *GraphicContext) drawGlyph(glyph truetype.Index, dx, dy float64) error {
|
||||||
|
if err := gc.glyphBuf.Load(gc.Current.Font, fixed.Int26_6(gc.Current.Scale), glyph, font.HintingNone); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
e0 := 0
|
||||||
|
for _, e1 := range gc.glyphBuf.Ends {
|
||||||
|
DrawContour(gc, gc.glyphBuf.Points[e0:e1], dx, dy)
|
||||||
|
e0 = e1
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateStringPath creates a path from the string s at x, y, and returns the string width.
|
||||||
|
// The text is placed so that the left edge of the em square of the first character of s
|
||||||
|
// and the baseline intersect at x, y. The majority of the affected pixels will be
|
||||||
|
// 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 *GraphicContext) CreateStringPath(s string, x, y float64) float64 {
|
||||||
|
f, err := gc.loadCurrentFont()
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return 0.0
|
||||||
|
}
|
||||||
|
startx := x
|
||||||
|
prev, hasPrev := truetype.Index(0), false
|
||||||
|
for _, rune := range s {
|
||||||
|
index := f.Index(rune)
|
||||||
|
if hasPrev {
|
||||||
|
x += fUnitsToFloat64(f.Kern(fixed.Int26_6(gc.Current.Scale), prev, index))
|
||||||
|
}
|
||||||
|
err := gc.drawGlyph(index, x, y)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return startx - x
|
||||||
|
}
|
||||||
|
x += fUnitsToFloat64(f.HMetric(fixed.Int26_6(gc.Current.Scale), index).AdvanceWidth)
|
||||||
|
prev, hasPrev = index, true
|
||||||
|
}
|
||||||
|
return x - startx
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetStringBounds returns the approximate pixel bounds of the string s at x, y.
|
||||||
|
// 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 *GraphicContext) GetStringBounds(s string) (left, top, right, bottom float64) {
|
||||||
|
f, err := gc.loadCurrentFont()
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return 0, 0, 0, 0
|
||||||
|
}
|
||||||
|
top, left, bottom, right = 10e6, 10e6, -10e6, -10e6
|
||||||
|
cursor := 0.0
|
||||||
|
prev, hasPrev := truetype.Index(0), false
|
||||||
|
// Get sample letter for approximated emoji calculations
|
||||||
|
const letter = 'M'
|
||||||
|
mindex := f.Index(letter)
|
||||||
|
mtop, mleft, mheight, mwidth := 10e6, 10e6, -10e6, -10e6
|
||||||
|
if err := gc.glyphBuf.Load(gc.Current.Font, fixed.Int26_6(gc.Current.Scale), mindex, font.HintingNone); err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return 0, 0, 0, 0
|
||||||
|
}
|
||||||
|
e0 := 0
|
||||||
|
for _, e1 := range gc.glyphBuf.Ends {
|
||||||
|
ps := gc.glyphBuf.Points[e0:e1]
|
||||||
|
for _, p := range ps {
|
||||||
|
x, y := pointToF64Point(p)
|
||||||
|
mtop = math.Min(mtop, y)
|
||||||
|
mheight = math.Max(mheight, y)
|
||||||
|
mleft = math.Min(mleft, x)
|
||||||
|
mwidth = math.Max(mwidth, x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mtop *= 1.2
|
||||||
|
mwidth *= 1.55
|
||||||
|
mheight += math.Abs(mtop-mheight) * 0.2
|
||||||
|
// Actually iterate through the string
|
||||||
|
for fragment := range gc.Emojis.Iterate(s) {
|
||||||
|
if fragment.IsEmoji {
|
||||||
|
cursor += mwidth
|
||||||
|
top = math.Min(top, mtop)
|
||||||
|
bottom = math.Max(bottom, mheight)
|
||||||
|
left = math.Min(left, mleft)
|
||||||
|
right = math.Max(right, cursor)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
index := f.Index(fragment.Rune)
|
||||||
|
if hasPrev {
|
||||||
|
cursor += fUnitsToFloat64(f.Kern(fixed.Int26_6(gc.Current.Scale), prev, index))
|
||||||
|
}
|
||||||
|
if err := gc.glyphBuf.Load(gc.Current.Font, fixed.Int26_6(gc.Current.Scale), index, font.HintingNone); err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return 0, 0, 0, 0
|
||||||
|
}
|
||||||
|
e0 := 0
|
||||||
|
for _, e1 := range gc.glyphBuf.Ends {
|
||||||
|
ps := gc.glyphBuf.Points[e0:e1]
|
||||||
|
for _, p := range ps {
|
||||||
|
x, y := pointToF64Point(p)
|
||||||
|
top = math.Min(top, y)
|
||||||
|
bottom = math.Max(bottom, y)
|
||||||
|
left = math.Min(left, x+cursor)
|
||||||
|
right = math.Max(right, x+cursor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cursor += fUnitsToFloat64(f.HMetric(fixed.Int26_6(gc.Current.Scale), index).AdvanceWidth)
|
||||||
|
prev, hasPrev = index, true
|
||||||
|
}
|
||||||
|
return left, top, right, bottom
|
||||||
|
}
|
||||||
|
|
||||||
|
// recalc recalculates scale and bounds values from the font size, screen
|
||||||
|
// resolution and font metrics, and invalidates the glyph cache.
|
||||||
|
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 *GraphicContext) SetDPI(dpi int) {
|
||||||
|
gc.DPI = dpi
|
||||||
|
gc.recalc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetFont sets the font used to draw text.
|
||||||
|
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 *GraphicContext) SetFontSize(fontSize float64) {
|
||||||
|
gc.Current.FontSize = fontSize
|
||||||
|
gc.recalc()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gc *GraphicContext) paint(rasterizer *raster.Rasterizer, color color.Color) {
|
||||||
|
gc.painter.SetColor(color)
|
||||||
|
rasterizer.Rasterize(gc.painter)
|
||||||
|
rasterizer.Clear()
|
||||||
|
gc.Current.Path.Clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stroke strokes the paths with the color specified by SetStrokeColor
|
||||||
|
func (gc *GraphicContext) Stroke(paths ...*draw2d.Path) {
|
||||||
|
paths = append(paths, gc.Current.Path)
|
||||||
|
gc.strokeRasterizer.UseNonZeroWinding = true
|
||||||
|
|
||||||
|
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 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
|
||||||
|
}
|
||||||
|
for _, p := range paths {
|
||||||
|
draw2dbase.Flatten(p, liner, gc.Current.Tr.GetScale())
|
||||||
|
}
|
||||||
|
|
||||||
|
gc.paint(gc.strokeRasterizer, gc.Current.StrokeColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill fills the paths with the color specified by SetFillColor
|
||||||
|
func (gc *GraphicContext) Fill(paths ...*draw2d.Path) {
|
||||||
|
paths = append(paths, gc.Current.Path)
|
||||||
|
gc.fillRasterizer.UseNonZeroWinding = gc.Current.FillRule == draw2d.FillRuleWinding
|
||||||
|
|
||||||
|
/**** first method ****/
|
||||||
|
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 *GraphicContext) FillStroke(paths ...*draw2d.Path) {
|
||||||
|
paths = append(paths, gc.Current.Path)
|
||||||
|
gc.fillRasterizer.UseNonZeroWinding = gc.Current.FillRule == draw2d.FillRuleWinding
|
||||||
|
gc.strokeRasterizer.UseNonZeroWinding = true
|
||||||
|
|
||||||
|
flattener := draw2dbase.Transformer{Tr: gc.Current.Tr, Flattener: FtLineBuilder{Adder: gc.fillRasterizer}}
|
||||||
|
|
||||||
|
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 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 toFtCap(c draw2d.LineCap) raster.Capper {
|
||||||
|
switch c {
|
||||||
|
case draw2d.RoundCap:
|
||||||
|
return raster.RoundCapper
|
||||||
|
case draw2d.ButtCap:
|
||||||
|
return raster.ButtCapper
|
||||||
|
case draw2d.SquareCap:
|
||||||
|
return raster.SquareCapper
|
||||||
|
}
|
||||||
|
return raster.RoundCapper
|
||||||
|
}
|
||||||
|
|
||||||
|
func toFtJoin(j draw2d.LineJoin) raster.Joiner {
|
||||||
|
switch j {
|
||||||
|
case draw2d.RoundJoin:
|
||||||
|
return raster.RoundJoiner
|
||||||
|
case draw2d.BevelJoin:
|
||||||
|
return raster.BevelJoiner
|
||||||
|
}
|
||||||
|
return raster.RoundJoiner
|
||||||
|
}
|
30
draw2dimg/ftpath.go
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
// Copyright 2010 The draw2d Authors. All rights reserved.
|
||||||
|
// created: 13/12/2010 by Laurent Le Goff
|
||||||
|
|
||||||
|
package draw2dimg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.fromouter.space/crunchy-rocks/freetype/raster"
|
||||||
|
"golang.org/x/image/math/fixed"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FtLineBuilder struct {
|
||||||
|
Adder raster.Adder
|
||||||
|
}
|
||||||
|
|
||||||
|
func (liner FtLineBuilder) MoveTo(x, y float64) {
|
||||||
|
liner.Adder.Start(fixed.Point26_6{X: fixed.Int26_6(x * 64), Y: fixed.Int26_6(y * 64)})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (liner FtLineBuilder) LineTo(x, y float64) {
|
||||||
|
liner.Adder.Add1(fixed.Point26_6{X: fixed.Int26_6(x * 64), Y: fixed.Int26_6(y * 64)})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (liner FtLineBuilder) LineJoin() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (liner FtLineBuilder) Close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (liner FtLineBuilder) End() {
|
||||||
|
}
|
82
draw2dimg/text.go
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
package draw2dimg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.fromouter.space/crunchy-rocks/draw2d"
|
||||||
|
"git.fromouter.space/crunchy-rocks/freetype/truetype"
|
||||||
|
|
||||||
|
"golang.org/x/image/math/fixed"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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 fixed.Int26_6) 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(fixed.Int26_6(font.FUnitsPerEm()))
|
||||||
|
scale := size / float64(font.FUnitsPerEm())
|
||||||
|
return FontExtents{
|
||||||
|
Ascent: float64(bounds.Max.Y) * scale,
|
||||||
|
Descent: float64(bounds.Min.Y) * scale,
|
||||||
|
Height: float64(bounds.Max.Y-bounds.Min.Y) * scale,
|
||||||
|
}
|
||||||
|
}
|
7
draw2dkit/README.md
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
draw2d/draw2dkit
|
||||||
|
=================
|
||||||
|
|
||||||
|
[![Coverage](http://gocover.io/_badge/github.com/llgcode/draw2d/draw2dkit?0)](http://gocover.io/github.com/llgcode/draw2d/draw2dkit)
|
||||||
|
[![GoDoc](https://godoc.org/github.com/llgcode/draw2d/draw2dkit?status.svg)](https://godoc.org/github.com/llgcode/draw2d/draw2dkit)
|
||||||
|
|
||||||
|
Util package that help drawing common vectorial draw.
|
|
@ -1,16 +1,17 @@
|
||||||
// Copyright 2010 The draw2d Authors. All rights reserved.
|
// Copyright 2010 The draw2d Authors. All rights reserved.
|
||||||
// created: 13/12/2010 by Laurent Le Goff
|
// created: 13/12/2010 by Laurent Le Goff
|
||||||
|
|
||||||
//high level path creation
|
// Package draw2dkit provides helpers to draw common figures using a Path
|
||||||
|
package draw2dkit
|
||||||
package draw2d
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
|
"github.com/llgcode/draw2d"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Rect draws a rectangle between (x1,y1) and (x2,y2)
|
// Rectangle draws a rectangle using a path between (x1,y1) and (x2,y2)
|
||||||
func Rect(path Path, x1, y1, x2, y2 float64) {
|
func Rectangle(path draw2d.PathBuilder, x1, y1, x2, y2 float64) {
|
||||||
path.MoveTo(x1, y1)
|
path.MoveTo(x1, y1)
|
||||||
path.LineTo(x2, y1)
|
path.LineTo(x2, y1)
|
||||||
path.LineTo(x2, y2)
|
path.LineTo(x2, y2)
|
||||||
|
@ -18,8 +19,8 @@ func Rect(path Path, x1, y1, x2, y2 float64) {
|
||||||
path.Close()
|
path.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// RoundRect draws a rectangle between (x1,y1) and (x2,y2) with rounded corners
|
// RoundedRectangle draws a rectangle using a path between (x1,y1) and (x2,y2)
|
||||||
func RoundRect(path Path, x1, y1, x2, y2, arcWidth, arcHeight float64) {
|
func RoundedRectangle(path draw2d.PathBuilder, x1, y1, x2, y2, arcWidth, arcHeight float64) {
|
||||||
arcWidth = arcWidth / 2
|
arcWidth = arcWidth / 2
|
||||||
arcHeight = arcHeight / 2
|
arcHeight = arcHeight / 2
|
||||||
path.MoveTo(x1, y1+arcHeight)
|
path.MoveTo(x1, y1+arcHeight)
|
||||||
|
@ -33,16 +34,14 @@ func RoundRect(path Path, x1, y1, x2, y2, arcWidth, arcHeight float64) {
|
||||||
path.Close()
|
path.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ellipse is drawn with center (cx,cy) and radius (rx,ry)
|
// Ellipse draws an ellipse using a path with center (cx,cy) and radius (rx,ry)
|
||||||
func Ellipse(path Path, cx, cy, rx, ry float64) {
|
func Ellipse(path draw2d.PathBuilder, cx, cy, rx, ry float64) {
|
||||||
path.MoveTo(cx-rx, cy)
|
|
||||||
path.ArcTo(cx, cy, rx, ry, 0, -math.Pi*2)
|
path.ArcTo(cx, cy, rx, ry, 0, -math.Pi*2)
|
||||||
path.Close()
|
path.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Circle is drawn with center (cx,cy) and radius
|
// Circle draws a circle using a path with center (cx,cy) and radius
|
||||||
func Circle(path Path, cx, cy, radius float64) {
|
func Circle(path draw2d.PathBuilder, cx, cy, radius float64) {
|
||||||
path.MoveTo(cx-radius, cy)
|
|
||||||
path.ArcTo(cx, cy, radius, radius, 0, -math.Pi*2)
|
path.ArcTo(cx, cy, radius, radius, 0, -math.Pi*2)
|
||||||
path.Close()
|
path.Close()
|
||||||
}
|
}
|
29
draw2dkit/draw2dkit_test.go
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
package draw2dkit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/llgcode/draw2d/draw2dimg"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCircle(t *testing.T) {
|
||||||
|
width := 200
|
||||||
|
height := 200
|
||||||
|
img := image.NewRGBA(image.Rect(0, 0, width, height))
|
||||||
|
gc := draw2dimg.NewGraphicContext(img)
|
||||||
|
|
||||||
|
gc.SetStrokeColor(color.NRGBA{255, 255, 255, 255})
|
||||||
|
gc.SetFillColor(color.NRGBA{255, 255, 255, 255})
|
||||||
|
gc.Clear()
|
||||||
|
|
||||||
|
gc.SetStrokeColor(color.NRGBA{255, 0, 0, 255})
|
||||||
|
gc.SetLineWidth(1)
|
||||||
|
|
||||||
|
// Draw a circle
|
||||||
|
Circle(gc, 100, 100, 50)
|
||||||
|
gc.Stroke()
|
||||||
|
|
||||||
|
draw2dimg.SaveToPngFile("../output/draw2dkit/TestCircle.png", img)
|
||||||
|
}
|
|
@ -10,7 +10,7 @@ The following Go code generates a simple drawing and saves it to a pdf document:
|
||||||
```go
|
```go
|
||||||
// Initialize the graphic context on an RGBA image
|
// Initialize the graphic context on an RGBA image
|
||||||
dest := draw2dpdf.NewPdf("L", "mm", "A4")
|
dest := draw2dpdf.NewPdf("L", "mm", "A4")
|
||||||
gc := draw2d.NewGraphicContext(dest)
|
gc := draw2dpdf.NewGraphicContext(dest)
|
||||||
|
|
||||||
// Set some properties
|
// Set some properties
|
||||||
gc.SetFillColor(color.RGBA{0x44, 0xff, 0x44, 0xff})
|
gc.SetFillColor(color.RGBA{0x44, 0xff, 0x44, 0xff})
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
// pdf document:
|
// pdf document:
|
||||||
// // Initialize the graphic context on an RGBA image
|
// // Initialize the graphic context on an RGBA image
|
||||||
// dest := draw2dpdf.NewPdf("L", "mm", "A4")
|
// dest := draw2dpdf.NewPdf("L", "mm", "A4")
|
||||||
// gc := draw2d.NewGraphicContext(dest)
|
// gc := draw2dpdf.NewGraphicContext(dest)
|
||||||
//
|
//
|
||||||
// // Set some properties
|
// // Set some properties
|
||||||
// gc.SetFillColor(color.RGBA{0x44, 0xff, 0x44, 0xff})
|
// gc.SetFillColor(color.RGBA{0x44, 0xff, 0x44, 0xff})
|
||||||
|
@ -28,7 +28,7 @@
|
||||||
// draw2dpdf.SaveToPdfFile("hello.pdf", dest)
|
// draw2dpdf.SaveToPdfFile("hello.pdf", dest)
|
||||||
//
|
//
|
||||||
// There are more examples here:
|
// There are more examples here:
|
||||||
// https://gopkg.in/llgcode/draw2d.v1/tree/master/samples
|
// https://github.com/llgcode/draw2d/tree/master/samples
|
||||||
//
|
//
|
||||||
// Alternative backends
|
// Alternative backends
|
||||||
//
|
//
|
||||||
|
|
|
@ -14,10 +14,12 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"code.google.com/p/freetype-go/freetype/truetype"
|
"git.fromouter.space/crunchy-rocks/freetype/truetype"
|
||||||
|
|
||||||
"github.com/jung-kurt/gofpdf"
|
"github.com/jung-kurt/gofpdf"
|
||||||
"gopkg.in/llgcode/draw2d.v1"
|
"github.com/llgcode/draw2d"
|
||||||
|
"github.com/llgcode/draw2d/draw2dbase"
|
||||||
|
"github.com/llgcode/draw2d/draw2dkit"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -27,11 +29,11 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
caps = map[draw2d.Cap]string{
|
caps = map[draw2d.LineCap]string{
|
||||||
draw2d.RoundCap: "round",
|
draw2d.RoundCap: "round",
|
||||||
draw2d.ButtCap: "butt",
|
draw2d.ButtCap: "butt",
|
||||||
draw2d.SquareCap: "square"}
|
draw2d.SquareCap: "square"}
|
||||||
joins = map[draw2d.Join]string{
|
joins = map[draw2d.LineJoin]string{
|
||||||
draw2d.RoundJoin: "round",
|
draw2d.RoundJoin: "round",
|
||||||
draw2d.BevelJoin: "bevel",
|
draw2d.BevelJoin: "bevel",
|
||||||
draw2d.MiterJoin: "miter",
|
draw2d.MiterJoin: "miter",
|
||||||
|
@ -68,7 +70,7 @@ func clearRect(gc *GraphicContext, x1, y1, x2, y2 float64) {
|
||||||
x, y := gc.pdf.GetXY()
|
x, y := gc.pdf.GetXY()
|
||||||
// cover page with white rectangle
|
// cover page with white rectangle
|
||||||
gc.SetFillColor(white)
|
gc.SetFillColor(white)
|
||||||
draw2d.Rect(gc, x1, y1, x2, y2)
|
draw2dkit.Rectangle(gc, x1, y1, x2, y2)
|
||||||
gc.Fill()
|
gc.Fill()
|
||||||
// restore state
|
// restore state
|
||||||
gc.SetFillColor(f)
|
gc.SetFillColor(f)
|
||||||
|
@ -78,14 +80,14 @@ func clearRect(gc *GraphicContext, x1, y1, x2, y2 float64) {
|
||||||
// GraphicContext implements the draw2d.GraphicContext interface
|
// GraphicContext implements the draw2d.GraphicContext interface
|
||||||
// It provides draw2d with a pdf backend (based on gofpdf)
|
// It provides draw2d with a pdf backend (based on gofpdf)
|
||||||
type GraphicContext struct {
|
type GraphicContext struct {
|
||||||
*draw2d.StackGraphicContext
|
*draw2dbase.StackGraphicContext
|
||||||
pdf *gofpdf.Fpdf
|
pdf *gofpdf.Fpdf
|
||||||
DPI int
|
DPI int
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGraphicContext creates a new pdf GraphicContext
|
// NewGraphicContext creates a new pdf GraphicContext
|
||||||
func NewGraphicContext(pdf *gofpdf.Fpdf) *GraphicContext {
|
func NewGraphicContext(pdf *gofpdf.Fpdf) *GraphicContext {
|
||||||
gc := &GraphicContext{draw2d.NewStackGraphicContext(), pdf, DPI}
|
gc := &GraphicContext{draw2dbase.NewStackGraphicContext(), pdf, DPI}
|
||||||
gc.SetDPI(DPI)
|
gc.SetDPI(DPI)
|
||||||
return gc
|
return gc
|
||||||
}
|
}
|
||||||
|
@ -189,27 +191,27 @@ func (gc *GraphicContext) StrokeStringAt(text string, x, y float64) (cursor floa
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stroke strokes the paths with the color specified by SetStrokeColor
|
// Stroke strokes the paths with the color specified by SetStrokeColor
|
||||||
func (gc *GraphicContext) Stroke(paths ...*draw2d.PathStorage) {
|
func (gc *GraphicContext) Stroke(paths ...*draw2d.Path) {
|
||||||
_, _, _, alphaS := gc.Current.StrokeColor.RGBA()
|
_, _, _, alphaS := gc.Current.StrokeColor.RGBA()
|
||||||
gc.draw("D", alphaS, paths...)
|
gc.draw("D", alphaS, paths...)
|
||||||
gc.Current.Path = draw2d.NewPathStorage()
|
gc.Current.Path.Clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fill fills the paths with the color specified by SetFillColor
|
// Fill fills the paths with the color specified by SetFillColor
|
||||||
func (gc *GraphicContext) Fill(paths ...*draw2d.PathStorage) {
|
func (gc *GraphicContext) Fill(paths ...*draw2d.Path) {
|
||||||
style := "F"
|
style := "F"
|
||||||
if !gc.Current.FillRule.UseNonZeroWinding() {
|
if gc.Current.FillRule != draw2d.FillRuleWinding {
|
||||||
style += "*"
|
style += "*"
|
||||||
}
|
}
|
||||||
_, _, _, alphaF := gc.Current.FillColor.RGBA()
|
_, _, _, alphaF := gc.Current.FillColor.RGBA()
|
||||||
gc.draw(style, alphaF, paths...)
|
gc.draw(style, alphaF, paths...)
|
||||||
gc.Current.Path = draw2d.NewPathStorage()
|
gc.Current.Path.Clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
// FillStroke first fills the paths and than strokes them
|
// FillStroke first fills the paths and than strokes them
|
||||||
func (gc *GraphicContext) FillStroke(paths ...*draw2d.PathStorage) {
|
func (gc *GraphicContext) FillStroke(paths ...*draw2d.Path) {
|
||||||
var rule string
|
var rule string
|
||||||
if !gc.Current.FillRule.UseNonZeroWinding() {
|
if gc.Current.FillRule != draw2d.FillRuleWinding {
|
||||||
rule = "*"
|
rule = "*"
|
||||||
}
|
}
|
||||||
_, _, _, alphaS := gc.Current.StrokeColor.RGBA()
|
_, _, _, alphaS := gc.Current.StrokeColor.RGBA()
|
||||||
|
@ -220,7 +222,7 @@ func (gc *GraphicContext) FillStroke(paths ...*draw2d.PathStorage) {
|
||||||
gc.draw("F"+rule, alphaF, paths...)
|
gc.draw("F"+rule, alphaF, paths...)
|
||||||
gc.draw("S", alphaS, paths...)
|
gc.draw("S", alphaS, paths...)
|
||||||
}
|
}
|
||||||
gc.Current.Path = draw2d.NewPathStorage()
|
gc.Current.Path.Clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
var logger = log.New(os.Stdout, "", log.Lshortfile)
|
var logger = log.New(os.Stdout, "", log.Lshortfile)
|
||||||
|
@ -228,10 +230,11 @@ var logger = log.New(os.Stdout, "", log.Lshortfile)
|
||||||
const alphaMax = float64(0xFFFF)
|
const alphaMax = float64(0xFFFF)
|
||||||
|
|
||||||
// draw fills and/or strokes paths
|
// draw fills and/or strokes paths
|
||||||
func (gc *GraphicContext) draw(style string, alpha uint32, paths ...*draw2d.PathStorage) {
|
func (gc *GraphicContext) draw(style string, alpha uint32, paths ...*draw2d.Path) {
|
||||||
paths = append(paths, gc.Current.Path)
|
paths = append(paths, gc.Current.Path)
|
||||||
pathConverter := NewPathConverter(gc.pdf)
|
for _, p := range paths {
|
||||||
pathConverter.Convert(paths...)
|
ConvertPath(p, gc.pdf)
|
||||||
|
}
|
||||||
a := float64(alpha) / alphaMax
|
a := float64(alpha) / alphaMax
|
||||||
current, blendMode := gc.pdf.GetAlpha()
|
current, blendMode := gc.pdf.GetAlpha()
|
||||||
if a != current {
|
if a != current {
|
||||||
|
@ -308,13 +311,13 @@ func (gc *GraphicContext) SetLineWidth(LineWidth float64) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetLineCap sets the line cap (round, but or square)
|
// SetLineCap sets the line cap (round, but or square)
|
||||||
func (gc *GraphicContext) SetLineCap(Cap draw2d.Cap) {
|
func (gc *GraphicContext) SetLineCap(Cap draw2d.LineCap) {
|
||||||
gc.StackGraphicContext.SetLineCap(Cap)
|
gc.StackGraphicContext.SetLineCap(Cap)
|
||||||
gc.pdf.SetLineCapStyle(caps[Cap])
|
gc.pdf.SetLineCapStyle(caps[Cap])
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetLineJoin sets the line cap (round, bevel or miter)
|
// SetLineJoin sets the line cap (round, bevel or miter)
|
||||||
func (gc *GraphicContext) SetLineJoin(Join draw2d.Join) {
|
func (gc *GraphicContext) SetLineJoin(Join draw2d.LineJoin) {
|
||||||
gc.StackGraphicContext.SetLineJoin(Join)
|
gc.StackGraphicContext.SetLineJoin(Join)
|
||||||
gc.pdf.SetLineJoinStyle(joins[Join])
|
gc.pdf.SetLineJoinStyle(joins[Join])
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,55 +6,39 @@ package draw2dpdf
|
||||||
import (
|
import (
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
"gopkg.in/llgcode/draw2d.v1"
|
"github.com/llgcode/draw2d"
|
||||||
)
|
)
|
||||||
|
|
||||||
const deg = 180 / math.Pi
|
const deg = 180 / math.Pi
|
||||||
|
|
||||||
// PathConverter converts the paths to the pdf api
|
// ConvertPath converts a paths to the pdf api
|
||||||
type PathConverter struct {
|
func ConvertPath(path *draw2d.Path, pdf Vectorizer) {
|
||||||
pdf Vectorizer
|
var startX, startY float64 = 0, 0
|
||||||
}
|
i := 0
|
||||||
|
for _, cmp := range path.Components {
|
||||||
// NewPathConverter constructs a PathConverter from a pdf vectorizer
|
switch cmp {
|
||||||
func NewPathConverter(pdf Vectorizer) *PathConverter {
|
case draw2d.MoveToCmp:
|
||||||
return &PathConverter{pdf: pdf}
|
startX, startY = path.Points[i], path.Points[i+1]
|
||||||
}
|
pdf.MoveTo(startX, startY)
|
||||||
|
i += 2
|
||||||
// Convert converts the paths to the pdf api
|
case draw2d.LineToCmp:
|
||||||
func (c *PathConverter) Convert(paths ...*draw2d.PathStorage) {
|
pdf.LineTo(path.Points[i], path.Points[i+1])
|
||||||
for _, path := range paths {
|
i += 2
|
||||||
j := 0
|
case draw2d.QuadCurveToCmp:
|
||||||
for _, cmd := range path.Commands {
|
pdf.CurveTo(path.Points[i], path.Points[i+1], path.Points[i+2], path.Points[i+3])
|
||||||
j = j + c.ConvertCommand(cmd, path.Vertices[j:]...)
|
i += 4
|
||||||
}
|
case draw2d.CubicCurveToCmp:
|
||||||
}
|
pdf.CurveBezierCubicTo(path.Points[i], path.Points[i+1], path.Points[i+2], path.Points[i+3], path.Points[i+4], path.Points[i+5])
|
||||||
}
|
i += 6
|
||||||
|
case draw2d.ArcToCmp:
|
||||||
// ConvertCommand converts a single path segment to the pdf api
|
pdf.ArcTo(path.Points[i], path.Points[i+1], path.Points[i+2], path.Points[i+3],
|
||||||
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
|
0, // degRotate
|
||||||
-vertices[4]*deg, // degStart = -startAngle
|
path.Points[i+4]*deg, // degStart = startAngle
|
||||||
(-vertices[4]-vertices[5])*deg) // degEnd = -startAngle-angle
|
(path.Points[i+4]-path.Points[i+5])*deg) // degEnd = startAngle-angle
|
||||||
return 6
|
i += 6
|
||||||
default: // case draw2d.Close:
|
case draw2d.CloseCmp:
|
||||||
c.pdf.ClosePath()
|
pdf.LineTo(startX, startY)
|
||||||
return 0
|
pdf.ClosePath()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,16 +7,16 @@ package draw2dpdf_test
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"gopkg.in/llgcode/draw2d.v1"
|
"github.com/llgcode/draw2d"
|
||||||
"gopkg.in/llgcode/draw2d.v1/samples/android"
|
"github.com/llgcode/draw2d/samples/android"
|
||||||
"gopkg.in/llgcode/draw2d.v1/samples/frameimage"
|
"github.com/llgcode/draw2d/samples/frameimage"
|
||||||
"gopkg.in/llgcode/draw2d.v1/samples/geometry"
|
"github.com/llgcode/draw2d/samples/geometry"
|
||||||
"gopkg.in/llgcode/draw2d.v1/samples/gopher"
|
"github.com/llgcode/draw2d/samples/gopher"
|
||||||
"gopkg.in/llgcode/draw2d.v1/samples/gopher2"
|
"github.com/llgcode/draw2d/samples/gopher2"
|
||||||
"gopkg.in/llgcode/draw2d.v1/samples/helloworld"
|
"github.com/llgcode/draw2d/samples/helloworld"
|
||||||
"gopkg.in/llgcode/draw2d.v1/samples/line"
|
"github.com/llgcode/draw2d/samples/line"
|
||||||
"gopkg.in/llgcode/draw2d.v1/samples/linecapjoin"
|
"github.com/llgcode/draw2d/samples/linecapjoin"
|
||||||
"gopkg.in/llgcode/draw2d.v1/samples/postscript"
|
"github.com/llgcode/draw2d/samples/postscript"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSampleAndroid(t *testing.T) {
|
func TestSampleAndroid(t *testing.T) {
|
||||||
|
|
|
@ -9,8 +9,8 @@ package draw2dpdf_test
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"gopkg.in/llgcode/draw2d.v1"
|
"github.com/llgcode/draw2d"
|
||||||
"gopkg.in/llgcode/draw2d.v1/draw2dpdf"
|
"github.com/llgcode/draw2d/draw2dpdf"
|
||||||
)
|
)
|
||||||
|
|
||||||
type sample func(gc draw2d.GraphicContext, ext string) (string, error)
|
type sample func(gc draw2d.GraphicContext, ext string) (string, error)
|
||||||
|
|
176
draw2dsvg/converters.go
Normal file
|
@ -0,0 +1,176 @@
|
||||||
|
// Copyright 2015 The draw2d Authors. All rights reserved.
|
||||||
|
// created: 16/12/2017 by Drahoslav Bednářpackage draw2dsvg
|
||||||
|
|
||||||
|
package draw2dsvg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
"image/png"
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.fromouter.space/crunchy-rocks/draw2d"
|
||||||
|
)
|
||||||
|
|
||||||
|
func toSvgRGBA(c color.Color) string {
|
||||||
|
r, g, b, a := c.RGBA()
|
||||||
|
r, g, b, a = r>>8, g>>8, b>>8, a>>8
|
||||||
|
if a == 255 {
|
||||||
|
return optiSprintf("#%02X%02X%02X", r, g, b)
|
||||||
|
}
|
||||||
|
return optiSprintf("rgba(%v,%v,%v,%f)", r, g, b, float64(a)/255)
|
||||||
|
}
|
||||||
|
|
||||||
|
func toSvgLength(l float64) string {
|
||||||
|
if math.IsInf(l, 1) {
|
||||||
|
return "100%"
|
||||||
|
}
|
||||||
|
return optiSprintf("%f", l)
|
||||||
|
}
|
||||||
|
|
||||||
|
func toSvgArray(nums []float64) string {
|
||||||
|
arr := make([]string, len(nums))
|
||||||
|
for i, num := range nums {
|
||||||
|
arr[i] = optiSprintf("%f", num)
|
||||||
|
}
|
||||||
|
return strings.Join(arr, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
func toSvgFillRule(rule draw2d.FillRule) string {
|
||||||
|
return map[draw2d.FillRule]string{
|
||||||
|
draw2d.FillRuleEvenOdd: "evenodd",
|
||||||
|
draw2d.FillRuleWinding: "nonzero",
|
||||||
|
}[rule]
|
||||||
|
}
|
||||||
|
|
||||||
|
func toSvgPathDesc(p *draw2d.Path) string {
|
||||||
|
parts := make([]string, len(p.Components))
|
||||||
|
ps := p.Points
|
||||||
|
for i, cmp := range p.Components {
|
||||||
|
switch cmp {
|
||||||
|
case draw2d.MoveToCmp:
|
||||||
|
parts[i] = optiSprintf("M %f,%f", ps[0], ps[1])
|
||||||
|
ps = ps[2:]
|
||||||
|
case draw2d.LineToCmp:
|
||||||
|
parts[i] = optiSprintf("L %f,%f", ps[0], ps[1])
|
||||||
|
ps = ps[2:]
|
||||||
|
case draw2d.QuadCurveToCmp:
|
||||||
|
parts[i] = optiSprintf("Q %f,%f %f,%f", ps[0], ps[1], ps[2], ps[3])
|
||||||
|
ps = ps[4:]
|
||||||
|
case draw2d.CubicCurveToCmp:
|
||||||
|
parts[i] = optiSprintf("C %f,%f %f,%f %f,%f", ps[0], ps[1], ps[2], ps[3], ps[4], ps[5])
|
||||||
|
ps = ps[6:]
|
||||||
|
case draw2d.ArcToCmp:
|
||||||
|
cx, cy := ps[0], ps[1] // center
|
||||||
|
rx, ry := ps[2], ps[3] // radii
|
||||||
|
fi := ps[4] + ps[5] // startAngle + angle
|
||||||
|
|
||||||
|
// compute endpoint
|
||||||
|
sinfi, cosfi := math.Sincos(fi)
|
||||||
|
nom := math.Hypot(ry*cosfi, rx*sinfi)
|
||||||
|
x := cx + (rx*ry*cosfi)/nom
|
||||||
|
y := cy + (rx*ry*sinfi)/nom
|
||||||
|
|
||||||
|
// compute large and sweep flags
|
||||||
|
large := 0
|
||||||
|
sweep := 0
|
||||||
|
if math.Abs(ps[5]) > math.Pi {
|
||||||
|
large = 1
|
||||||
|
}
|
||||||
|
if !math.Signbit(ps[5]) {
|
||||||
|
sweep = 1
|
||||||
|
}
|
||||||
|
// dirty hack to ensure whole arc is drawn
|
||||||
|
// if start point equals end point
|
||||||
|
if sweep == 1 {
|
||||||
|
x += 0.01 * sinfi
|
||||||
|
y += 0.01 * -cosfi
|
||||||
|
} else {
|
||||||
|
x += 0.01 * sinfi
|
||||||
|
y += 0.01 * cosfi
|
||||||
|
}
|
||||||
|
|
||||||
|
// rx ry x-axis-rotation large-arc-flag sweep-flag x y
|
||||||
|
parts[i] = optiSprintf("A %f %f %v %v %v %F %F",
|
||||||
|
rx, ry, 0, large, sweep, x, y,
|
||||||
|
)
|
||||||
|
ps = ps[6:]
|
||||||
|
case draw2d.CloseCmp:
|
||||||
|
parts[i] = "Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strings.Join(parts, " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func toSvgTransform(mat draw2d.Matrix) string {
|
||||||
|
if mat.IsIdentity() {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if mat.IsTranslation() {
|
||||||
|
x, y := mat.GetTranslation()
|
||||||
|
return optiSprintf("translate(%f,%f)", x, y)
|
||||||
|
}
|
||||||
|
return optiSprintf("matrix(%f,%f,%f,%f,%f,%f)",
|
||||||
|
mat[0], mat[1], mat[2], mat[3], mat[4], mat[5],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func imageToSvgHref(image image.Image) string {
|
||||||
|
out := "data:image/png;base64,"
|
||||||
|
pngBuf := &bytes.Buffer{}
|
||||||
|
png.Encode(pngBuf, image)
|
||||||
|
out += base64.RawStdEncoding.EncodeToString(pngBuf.Bytes())
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do the same thing as fmt.Sprintf
|
||||||
|
// except it uses the optimal precition for floats: (0-3) for f and (0-6) for F
|
||||||
|
// eg.:
|
||||||
|
// optiSprintf("%f", 3.0) => fmt.Sprintf("%.0f", 3.0)
|
||||||
|
// optiSprintf("%f", 3.33) => fmt.Sprintf("%.2f", 3.33)
|
||||||
|
// optiSprintf("%f", 3.3001) => fmt.Sprintf("%.1f", 3.3001)
|
||||||
|
// optiSprintf("%f", 3.333333333333333) => fmt.Sprintf("%.3f", 3.333333333333333)
|
||||||
|
// optiSprintf("%F", 3.333333333333333) => fmt.Sprintf("%.6f", 3.333333333333333)
|
||||||
|
func optiSprintf(format string, a ...interface{}) string {
|
||||||
|
chunks := strings.Split(format, "%")
|
||||||
|
newChunks := make([]string, len(chunks))
|
||||||
|
for i, chunk := range chunks {
|
||||||
|
if i != 0 {
|
||||||
|
verb := chunk[0]
|
||||||
|
if verb == 'f' || verb == 'F' {
|
||||||
|
num := a[i-1].(float64)
|
||||||
|
p := strconv.Itoa(getPrec(num, verb == 'F'))
|
||||||
|
chunk = strings.Replace(chunk, string(verb), "."+p+"f", 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newChunks[i] = chunk
|
||||||
|
}
|
||||||
|
format = strings.Join(newChunks, "%")
|
||||||
|
return fmt.Sprintf(format, a...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO needs test, since it is not quiet right
|
||||||
|
func getPrec(num float64, better bool) int {
|
||||||
|
max := 3
|
||||||
|
eps := 0.0005
|
||||||
|
if better {
|
||||||
|
max = 6
|
||||||
|
eps = 0.0000005
|
||||||
|
}
|
||||||
|
prec := 0
|
||||||
|
for math.Mod(num, 1) > eps {
|
||||||
|
num *= 10
|
||||||
|
eps *= 10
|
||||||
|
prec++
|
||||||
|
}
|
||||||
|
|
||||||
|
if max < prec {
|
||||||
|
return max
|
||||||
|
}
|
||||||
|
return prec
|
||||||
|
}
|
11
draw2dsvg/doc.go
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
// Copyright 2015 The draw2d Authors. All rights reserved.
|
||||||
|
// created: 16/12/2017 by Drahoslav Bednář
|
||||||
|
|
||||||
|
// Package draw2svg provides a graphic context that can draw
|
||||||
|
// vector graphics and text on svg file.
|
||||||
|
//
|
||||||
|
// Quick Start
|
||||||
|
// The following Go code geneartes a simple drawing and saves it
|
||||||
|
// to a svg document:
|
||||||
|
// TODO
|
||||||
|
package draw2dsvg
|
22
draw2dsvg/fileutil.go
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
package draw2dsvg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
_ "errors"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SaveToSvgFile(filePath string, svg *Svg) error {
|
||||||
|
f, err := os.Create(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
f.Write([]byte(xml.Header))
|
||||||
|
encoder := xml.NewEncoder(f)
|
||||||
|
encoder.Indent("", "\t")
|
||||||
|
err = encoder.Encode(svg)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
403
draw2dsvg/gc.go
Normal file
|
@ -0,0 +1,403 @@
|
||||||
|
// Copyright 2015 The draw2d Authors. All rights reserved.
|
||||||
|
// created: 16/12/2017 by Drahoslav Bednář
|
||||||
|
|
||||||
|
package draw2dsvg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
"log"
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.fromouter.space/crunchy-rocks/draw2d"
|
||||||
|
"git.fromouter.space/crunchy-rocks/draw2d/draw2dbase"
|
||||||
|
"git.fromouter.space/crunchy-rocks/freetype/truetype"
|
||||||
|
"golang.org/x/image/font"
|
||||||
|
"golang.org/x/image/math/fixed"
|
||||||
|
)
|
||||||
|
|
||||||
|
type drawType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
filled drawType = 1 << iota
|
||||||
|
stroked
|
||||||
|
)
|
||||||
|
|
||||||
|
// GraphicContext implements the draw2d.GraphicContext interface
|
||||||
|
// It provides draw2d with a svg backend
|
||||||
|
type GraphicContext struct {
|
||||||
|
*draw2dbase.StackGraphicContext
|
||||||
|
FontCache draw2d.FontCache
|
||||||
|
glyphCache draw2dbase.GlyphCache
|
||||||
|
glyphBuf *truetype.GlyphBuf
|
||||||
|
svg *Svg
|
||||||
|
DPI int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGraphicContext(svg *Svg) *GraphicContext {
|
||||||
|
gc := &GraphicContext{
|
||||||
|
draw2dbase.NewStackGraphicContext(),
|
||||||
|
draw2d.GetGlobalFontCache(),
|
||||||
|
draw2dbase.NewGlyphCache(),
|
||||||
|
&truetype.GlyphBuf{},
|
||||||
|
svg,
|
||||||
|
92,
|
||||||
|
}
|
||||||
|
return gc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear fills the current canvas with a default transparent color
|
||||||
|
func (gc *GraphicContext) Clear() {
|
||||||
|
gc.svg.Groups = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stroke strokes the paths with the color specified by SetStrokeColor
|
||||||
|
func (gc *GraphicContext) Stroke(paths ...*draw2d.Path) {
|
||||||
|
gc.drawPaths(stroked, paths...)
|
||||||
|
gc.Current.Path.Clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill fills the paths with the color specified by SetFillColor
|
||||||
|
func (gc *GraphicContext) Fill(paths ...*draw2d.Path) {
|
||||||
|
gc.drawPaths(filled, paths...)
|
||||||
|
gc.Current.Path.Clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
// FillStroke first fills the paths and than strokes them
|
||||||
|
func (gc *GraphicContext) FillStroke(paths ...*draw2d.Path) {
|
||||||
|
gc.drawPaths(filled|stroked, paths...)
|
||||||
|
gc.Current.Path.Clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
// FillString draws the text at point (0, 0)
|
||||||
|
func (gc *GraphicContext) FillString(text string) (cursor float64) {
|
||||||
|
return gc.FillStringAt(text, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FillStringAt draws the text at the specified point (x, y)
|
||||||
|
func (gc *GraphicContext) FillStringAt(text string, x, y float64) (cursor float64) {
|
||||||
|
return gc.drawString(text, filled, x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StrokeStringAt draws the contour of the text at point (x, y)
|
||||||
|
func (gc *GraphicContext) StrokeStringAt(text string, x, y float64) (cursor float64) {
|
||||||
|
return gc.drawString(text, stroked, x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the context and push it to the context stack
|
||||||
|
func (gc *GraphicContext) Save() {
|
||||||
|
gc.StackGraphicContext.Save()
|
||||||
|
// TODO use common transformation group for multiple elements
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore remove the current context and restore the last one
|
||||||
|
func (gc *GraphicContext) Restore() {
|
||||||
|
gc.StackGraphicContext.Restore()
|
||||||
|
// TODO use common transformation group for multiple elements
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gc *GraphicContext) SetDPI(dpi int) {
|
||||||
|
gc.DPI = dpi
|
||||||
|
gc.recalc()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gc *GraphicContext) GetDPI() int {
|
||||||
|
return gc.DPI
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetFont sets the font used to draw text.
|
||||||
|
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 *GraphicContext) SetFontSize(fontSize float64) {
|
||||||
|
gc.Current.FontSize = fontSize
|
||||||
|
gc.recalc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// DrawImage draws the raster image in the current canvas
|
||||||
|
func (gc *GraphicContext) DrawImage(image image.Image) {
|
||||||
|
bounds := image.Bounds()
|
||||||
|
|
||||||
|
svgImage := &Image{Href: imageToSvgHref(image)}
|
||||||
|
svgImage.X = float64(bounds.Min.X)
|
||||||
|
svgImage.Y = float64(bounds.Min.Y)
|
||||||
|
svgImage.Width = toSvgLength(float64(bounds.Max.X - bounds.Min.X))
|
||||||
|
svgImage.Height = toSvgLength(float64(bounds.Max.Y - bounds.Min.Y))
|
||||||
|
gc.newGroup(0).Image = svgImage
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearRect fills the specified rectangle with a default transparent color
|
||||||
|
func (gc *GraphicContext) ClearRect(x1, y1, x2, y2 int) {
|
||||||
|
mask := gc.newMask(x1, y1, x2-x1, y2-y1)
|
||||||
|
|
||||||
|
newGroup := &Group{
|
||||||
|
Groups: gc.svg.Groups,
|
||||||
|
Mask: "url(#" + mask.Id + ")",
|
||||||
|
}
|
||||||
|
|
||||||
|
// replace groups with new masked group
|
||||||
|
gc.svg.Groups = []*Group{newGroup}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE following two functions and soe other further below copied from dwra2d{img|gl}
|
||||||
|
// TODO move them all to common draw2dbase?
|
||||||
|
|
||||||
|
// CreateStringPath creates a path from the string s at x, y, and returns the string width.
|
||||||
|
// The text is placed so that the left edge of the em square of the first character of s
|
||||||
|
// and the baseline intersect at x, y. The majority of the affected pixels will be
|
||||||
|
// 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 *GraphicContext) CreateStringPath(s string, x, y float64) (cursor float64) {
|
||||||
|
f, err := gc.loadCurrentFont()
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return 0.0
|
||||||
|
}
|
||||||
|
startx := x
|
||||||
|
prev, hasPrev := truetype.Index(0), false
|
||||||
|
for _, rune := range s {
|
||||||
|
index := f.Index(rune)
|
||||||
|
if hasPrev {
|
||||||
|
x += fUnitsToFloat64(f.Kern(fixed.Int26_6(gc.Current.Scale), prev, index))
|
||||||
|
}
|
||||||
|
err := gc.drawGlyph(index, x, y)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return startx - x
|
||||||
|
}
|
||||||
|
x += fUnitsToFloat64(f.HMetric(fixed.Int26_6(gc.Current.Scale), index).AdvanceWidth)
|
||||||
|
prev, hasPrev = index, true
|
||||||
|
}
|
||||||
|
|
||||||
|
return x - startx
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetStringBounds returns the approximate pixel bounds of the string s at x, y.
|
||||||
|
// 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 *GraphicContext) GetStringBounds(s string) (left, top, right, bottom float64) {
|
||||||
|
f, err := gc.loadCurrentFont()
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return 0, 0, 0, 0
|
||||||
|
}
|
||||||
|
if gc.Current.Scale == 0 {
|
||||||
|
panic("zero scale")
|
||||||
|
}
|
||||||
|
top, left, bottom, right = 10e6, 10e6, -10e6, -10e6
|
||||||
|
cursor := 0.0
|
||||||
|
prev, hasPrev := truetype.Index(0), false
|
||||||
|
for _, rune := range s {
|
||||||
|
index := f.Index(rune)
|
||||||
|
if hasPrev {
|
||||||
|
cursor += fUnitsToFloat64(f.Kern(fixed.Int26_6(gc.Current.Scale), prev, index))
|
||||||
|
}
|
||||||
|
if err := gc.glyphBuf.Load(gc.Current.Font, fixed.Int26_6(gc.Current.Scale), index, font.HintingNone); err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return 0, 0, 0, 0
|
||||||
|
}
|
||||||
|
e0 := 0
|
||||||
|
for _, e1 := range gc.glyphBuf.Ends {
|
||||||
|
ps := gc.glyphBuf.Points[e0:e1]
|
||||||
|
for _, p := range ps {
|
||||||
|
x, y := pointToF64Point(p)
|
||||||
|
top = math.Min(top, y)
|
||||||
|
bottom = math.Max(bottom, y)
|
||||||
|
left = math.Min(left, x+cursor)
|
||||||
|
right = math.Max(right, x+cursor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cursor += fUnitsToFloat64(f.HMetric(fixed.Int26_6(gc.Current.Scale), index).AdvanceWidth)
|
||||||
|
prev, hasPrev = index, true
|
||||||
|
}
|
||||||
|
return left, top, right, bottom
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////
|
||||||
|
// private funcitons
|
||||||
|
|
||||||
|
func (gc *GraphicContext) drawPaths(drawType drawType, paths ...*draw2d.Path) {
|
||||||
|
// create elements
|
||||||
|
svgPath := Path{}
|
||||||
|
group := gc.newGroup(drawType)
|
||||||
|
|
||||||
|
// set attrs to path element
|
||||||
|
paths = append(paths, gc.Current.Path)
|
||||||
|
svgPathsDesc := make([]string, len(paths))
|
||||||
|
// multiple pathes has to be joined to single svg path description
|
||||||
|
// because fill-rule wont work for whole group as excepted
|
||||||
|
for i, path := range paths {
|
||||||
|
svgPathsDesc[i] = toSvgPathDesc(path)
|
||||||
|
}
|
||||||
|
svgPath.Desc = strings.Join(svgPathsDesc, " ")
|
||||||
|
|
||||||
|
// attach to group
|
||||||
|
group.Paths = []*Path{&svgPath}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add text element to svg and returns its expected width
|
||||||
|
func (gc *GraphicContext) drawString(text string, drawType drawType, x, y float64) float64 {
|
||||||
|
switch gc.svg.FontMode {
|
||||||
|
case PathFontMode:
|
||||||
|
w := gc.CreateStringPath(text, x, y)
|
||||||
|
gc.drawPaths(drawType)
|
||||||
|
gc.Current.Path.Clear()
|
||||||
|
return w
|
||||||
|
case SvgFontMode:
|
||||||
|
gc.embedSvgFont(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
// create elements
|
||||||
|
svgText := Text{}
|
||||||
|
group := gc.newGroup(drawType)
|
||||||
|
|
||||||
|
// set attrs to text element
|
||||||
|
svgText.Text = text
|
||||||
|
svgText.FontSize = gc.Current.FontSize
|
||||||
|
svgText.X = x
|
||||||
|
svgText.Y = y
|
||||||
|
svgText.FontFamily = gc.Current.FontData.Name
|
||||||
|
|
||||||
|
// attach to group
|
||||||
|
group.Texts = []*Text{&svgText}
|
||||||
|
left, _, right, _ := gc.GetStringBounds(text)
|
||||||
|
return right - left
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates new group from current context
|
||||||
|
// attach it to svg and return
|
||||||
|
func (gc *GraphicContext) newGroup(drawType drawType) *Group {
|
||||||
|
group := Group{}
|
||||||
|
// set attrs to group
|
||||||
|
if drawType&stroked == stroked {
|
||||||
|
group.Stroke = toSvgRGBA(gc.Current.StrokeColor)
|
||||||
|
group.StrokeWidth = toSvgLength(gc.Current.LineWidth)
|
||||||
|
group.StrokeLinecap = gc.Current.Cap.String()
|
||||||
|
group.StrokeLinejoin = gc.Current.Join.String()
|
||||||
|
if len(gc.Current.Dash) > 0 {
|
||||||
|
group.StrokeDasharray = toSvgArray(gc.Current.Dash)
|
||||||
|
group.StrokeDashoffset = toSvgLength(gc.Current.DashOffset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if drawType&filled == filled {
|
||||||
|
group.Fill = toSvgRGBA(gc.Current.FillColor)
|
||||||
|
group.FillRule = toSvgFillRule(gc.Current.FillRule)
|
||||||
|
}
|
||||||
|
|
||||||
|
group.Transform = toSvgTransform(gc.Current.Tr)
|
||||||
|
|
||||||
|
// attach
|
||||||
|
gc.svg.Groups = append(gc.svg.Groups, &group)
|
||||||
|
|
||||||
|
return &group
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates new mask attached to svg
|
||||||
|
func (gc *GraphicContext) newMask(x, y, width, height int) *Mask {
|
||||||
|
mask := &Mask{}
|
||||||
|
mask.X = float64(x)
|
||||||
|
mask.Y = float64(y)
|
||||||
|
mask.Width = toSvgLength(float64(width))
|
||||||
|
mask.Height = toSvgLength(float64(height))
|
||||||
|
|
||||||
|
// attach mask
|
||||||
|
gc.svg.Masks = append(gc.svg.Masks, mask)
|
||||||
|
mask.Id = "mask-" + strconv.Itoa(len(gc.svg.Masks))
|
||||||
|
return mask
|
||||||
|
}
|
||||||
|
|
||||||
|
// Embed svg font definition to svg tree itself
|
||||||
|
// Or update existing if already exists for curent font data
|
||||||
|
func (gc *GraphicContext) embedSvgFont(text string) *Font {
|
||||||
|
fontName := gc.Current.FontData.Name
|
||||||
|
gc.loadCurrentFont()
|
||||||
|
|
||||||
|
// find or create font Element
|
||||||
|
svgFont := (*Font)(nil)
|
||||||
|
for _, font := range gc.svg.Fonts {
|
||||||
|
if font.Name == fontName {
|
||||||
|
svgFont = font
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if svgFont == nil {
|
||||||
|
// create new
|
||||||
|
svgFont = &Font{}
|
||||||
|
// and attach
|
||||||
|
gc.svg.Fonts = append(gc.svg.Fonts, svgFont)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fill with glyphs
|
||||||
|
|
||||||
|
gc.Save()
|
||||||
|
defer gc.Restore()
|
||||||
|
gc.SetFontSize(2048)
|
||||||
|
defer gc.SetDPI(gc.GetDPI())
|
||||||
|
gc.SetDPI(92)
|
||||||
|
filling:
|
||||||
|
for _, rune := range text {
|
||||||
|
for _, g := range svgFont.Glyphs {
|
||||||
|
if g.Rune == Rune(rune) {
|
||||||
|
continue filling
|
||||||
|
}
|
||||||
|
}
|
||||||
|
glyph := gc.glyphCache.Fetch(gc, gc.GetFontName(), rune)
|
||||||
|
// glyphCache.Load indirectly calls CreateStringPath for single rune string
|
||||||
|
|
||||||
|
glypPath := glyph.Path.VerticalFlip() // svg font glyphs have oposite y axe
|
||||||
|
svgFont.Glyphs = append(svgFont.Glyphs, &Glyph{
|
||||||
|
Rune: Rune(rune),
|
||||||
|
Desc: toSvgPathDesc(glypPath),
|
||||||
|
HorizAdvX: glyph.Width,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// set attrs
|
||||||
|
svgFont.Id = "font-" + strconv.Itoa(len(gc.svg.Fonts))
|
||||||
|
svgFont.Name = fontName
|
||||||
|
|
||||||
|
// TODO use css @font-face with id instead of this
|
||||||
|
svgFont.Face = &Face{Family: fontName, Units: 2048, HorizAdvX: 2048}
|
||||||
|
return svgFont
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gc *GraphicContext) loadCurrentFont() (*truetype.Font, error) {
|
||||||
|
font, err := gc.FontCache.Load(gc.Current.FontData)
|
||||||
|
if err != nil {
|
||||||
|
font, err = gc.FontCache.Load(draw2dbase.DefaultFontData)
|
||||||
|
}
|
||||||
|
if font != nil {
|
||||||
|
gc.SetFont(font)
|
||||||
|
gc.SetFontSize(gc.Current.FontSize)
|
||||||
|
}
|
||||||
|
return font, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gc *GraphicContext) drawGlyph(glyph truetype.Index, dx, dy float64) error {
|
||||||
|
if err := gc.glyphBuf.Load(gc.Current.Font, fixed.Int26_6(gc.Current.Scale), glyph, font.HintingNone); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
e0 := 0
|
||||||
|
for _, e1 := range gc.glyphBuf.Ends {
|
||||||
|
DrawContour(gc, gc.glyphBuf.Points[e0:e1], dx, dy)
|
||||||
|
e0 = e1
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// recalc recalculates scale and bounds values from the font size, screen
|
||||||
|
// resolution and font metrics, and invalidates the glyph cache.
|
||||||
|
func (gc *GraphicContext) recalc() {
|
||||||
|
gc.Current.Scale = gc.Current.FontSize * float64(gc.DPI) * (64.0 / 72.0)
|
||||||
|
}
|
65
draw2dsvg/samples_test.go
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
// Copyright 2015 The draw2d Authors. All rights reserved.
|
||||||
|
// created: 26/06/2015 by Stani Michiels
|
||||||
|
// See also test_test.go
|
||||||
|
|
||||||
|
package draw2dsvg_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.fromouter.space/crunchy-rocks/draw2d"
|
||||||
|
"git.fromouter.space/crunchy-rocks/draw2d/samples/android"
|
||||||
|
"git.fromouter.space/crunchy-rocks/draw2d/samples/frameimage"
|
||||||
|
"git.fromouter.space/crunchy-rocks/draw2d/samples/geometry"
|
||||||
|
"git.fromouter.space/crunchy-rocks/draw2d/samples/gopher"
|
||||||
|
"git.fromouter.space/crunchy-rocks/draw2d/samples/gopher2"
|
||||||
|
"git.fromouter.space/crunchy-rocks/draw2d/samples/helloworld"
|
||||||
|
"git.fromouter.space/crunchy-rocks/draw2d/samples/line"
|
||||||
|
"git.fromouter.space/crunchy-rocks/draw2d/samples/linecapjoin"
|
||||||
|
"git.fromouter.space/crunchy-rocks/draw2d/samples/postscript"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSampleAndroid(t *testing.T) {
|
||||||
|
test(t, android.Main)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: FillString: w (width) is incorrect
|
||||||
|
func TestSampleGeometry(t *testing.T) {
|
||||||
|
// Set the global folder for searching fonts
|
||||||
|
// The pdf backend needs for every ttf file its corresponding
|
||||||
|
// json/.z file which is generated by gofpdf/makefont.
|
||||||
|
draw2d.SetFontFolder("../resource/font")
|
||||||
|
test(t, geometry.Main)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSampleGopher(t *testing.T) {
|
||||||
|
test(t, gopher.Main)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSampleGopher2(t *testing.T) {
|
||||||
|
test(t, gopher2.Main)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSampleHelloWorld(t *testing.T) {
|
||||||
|
// Set the global folder for searching fonts
|
||||||
|
// The pdf backend needs for every ttf file its corresponding
|
||||||
|
// json/.z file which is generated by gofpdf/makefont.
|
||||||
|
draw2d.SetFontFolder("../resource/font")
|
||||||
|
test(t, helloworld.Main)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSampleFrameImage(t *testing.T) {
|
||||||
|
test(t, frameimage.Main)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSampleLine(t *testing.T) {
|
||||||
|
test(t, line.Main)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSampleLineCap(t *testing.T) {
|
||||||
|
test(t, linecapjoin.Main)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSamplePostscript(t *testing.T) {
|
||||||
|
test(t, postscript.Main)
|
||||||
|
}
|
172
draw2dsvg/svg.go
Normal file
|
@ -0,0 +1,172 @@
|
||||||
|
// Copyright 2015 The draw2d Authors. All rights reserved.
|
||||||
|
// created: 16/12/2017 by Drahoslav Bednář
|
||||||
|
|
||||||
|
package draw2dsvg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
)
|
||||||
|
|
||||||
|
/* svg elements */
|
||||||
|
|
||||||
|
type FontMode int
|
||||||
|
|
||||||
|
// Modes of font handling in svg
|
||||||
|
const (
|
||||||
|
// Does nothing special
|
||||||
|
// Makes sense only for common system fonts
|
||||||
|
SysFontMode FontMode = 1 << iota
|
||||||
|
|
||||||
|
// Links font files in css def
|
||||||
|
// Requires distribution of font files with outputed svg
|
||||||
|
LinkFontMode // TODO implement
|
||||||
|
|
||||||
|
// Embeds glyphs definition in svg file itself in svg font format
|
||||||
|
// Has poor browser support
|
||||||
|
SvgFontMode
|
||||||
|
|
||||||
|
// Embeds font definiton in svg file itself in woff format as part of css def
|
||||||
|
CssFontMode // TODO implement
|
||||||
|
|
||||||
|
// Converts texts to paths
|
||||||
|
PathFontMode
|
||||||
|
)
|
||||||
|
|
||||||
|
type Svg struct {
|
||||||
|
XMLName xml.Name `xml:"svg"`
|
||||||
|
Xmlns string `xml:"xmlns,attr"`
|
||||||
|
Fonts []*Font `xml:"defs>font"`
|
||||||
|
Masks []*Mask `xml:"defs>mask"`
|
||||||
|
Groups []*Group `xml:"g"`
|
||||||
|
FontMode FontMode `xml:"-"`
|
||||||
|
FillStroke
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSvg() *Svg {
|
||||||
|
return &Svg{
|
||||||
|
Xmlns: "http://www.w3.org/2000/svg",
|
||||||
|
FillStroke: FillStroke{Fill: "none", Stroke: "none"},
|
||||||
|
FontMode: PathFontMode,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Group struct {
|
||||||
|
FillStroke
|
||||||
|
Transform string `xml:"transform,attr,omitempty"`
|
||||||
|
Groups []*Group `xml:"g"`
|
||||||
|
Paths []*Path `xml:"path"`
|
||||||
|
Texts []*Text `xml:"text"`
|
||||||
|
Image *Image `xml:"image"`
|
||||||
|
Mask string `xml:"mask,attr,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Path struct {
|
||||||
|
FillStroke
|
||||||
|
Desc string `xml:"d,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Text struct {
|
||||||
|
FillStroke
|
||||||
|
Position
|
||||||
|
FontSize float64 `xml:"font-size,attr,omitempty"`
|
||||||
|
FontFamily string `xml:"font-family,attr,omitempty"`
|
||||||
|
Text string `xml:",innerxml"`
|
||||||
|
Style string `xml:"style,attr,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Image struct {
|
||||||
|
Position
|
||||||
|
Dimension
|
||||||
|
Href string `xml:"href,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Mask struct {
|
||||||
|
Identity
|
||||||
|
Position
|
||||||
|
Dimension
|
||||||
|
}
|
||||||
|
|
||||||
|
type Rect struct {
|
||||||
|
Position
|
||||||
|
Dimension
|
||||||
|
FillStroke
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m Mask) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
||||||
|
bigRect := Rect{}
|
||||||
|
bigRect.X, bigRect.Y = 0, 0
|
||||||
|
bigRect.Width, bigRect.Height = "100%", "100%"
|
||||||
|
bigRect.Fill = "#fff"
|
||||||
|
rect := Rect{}
|
||||||
|
rect.X, rect.Y = m.X, m.Y
|
||||||
|
rect.Width, rect.Height = m.Width, m.Height
|
||||||
|
rect.Fill = "#000"
|
||||||
|
|
||||||
|
return e.EncodeElement(struct {
|
||||||
|
XMLName xml.Name `xml:"mask"`
|
||||||
|
Rects [2]Rect `xml:"rect"`
|
||||||
|
Id string `xml:"id,attr"`
|
||||||
|
}{
|
||||||
|
Rects: [2]Rect{bigRect, rect},
|
||||||
|
Id: m.Id,
|
||||||
|
}, start)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* font related elements */
|
||||||
|
|
||||||
|
type Font struct {
|
||||||
|
Identity
|
||||||
|
Face *Face `xml:"font-face"`
|
||||||
|
Glyphs []*Glyph `xml:"glyph"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Face struct {
|
||||||
|
Family string `xml:"font-family,attr"`
|
||||||
|
Units int `xml:"units-per-em,attr"`
|
||||||
|
HorizAdvX float64 `xml:"horiz-adv-x,attr"`
|
||||||
|
// TODO add other attrs, like style, variant, weight...
|
||||||
|
}
|
||||||
|
|
||||||
|
type Glyph struct {
|
||||||
|
Rune Rune `xml:"unicode,attr"`
|
||||||
|
Desc string `xml:"d,attr"`
|
||||||
|
HorizAdvX float64 `xml:"horiz-adv-x,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Rune rune
|
||||||
|
|
||||||
|
func (r Rune) MarshalXMLAttr(name xml.Name) (xml.Attr, error) {
|
||||||
|
return xml.Attr{
|
||||||
|
Name: name,
|
||||||
|
Value: string(rune(r)),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/* shared attrs */
|
||||||
|
|
||||||
|
type Identity struct {
|
||||||
|
Id string `xml:"id,attr"`
|
||||||
|
Name string `xml:"name,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Position struct {
|
||||||
|
X float64 `xml:"x,attr,omitempty"`
|
||||||
|
Y float64 `xml:"y,attr,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Dimension struct {
|
||||||
|
Width string `xml:"width,attr"`
|
||||||
|
Height string `xml:"height,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FillStroke struct {
|
||||||
|
Fill string `xml:"fill,attr,omitempty"`
|
||||||
|
FillRule string `xml:"fill-rule,attr,omitempty"`
|
||||||
|
|
||||||
|
Stroke string `xml:"stroke,attr,omitempty"`
|
||||||
|
StrokeWidth string `xml:"stroke-width,attr,omitempty"`
|
||||||
|
StrokeLinecap string `xml:"stroke-linecap,attr,omitempty"`
|
||||||
|
StrokeLinejoin string `xml:"stroke-linejoin,attr,omitempty"`
|
||||||
|
StrokeDasharray string `xml:"stroke-dasharray,attr,omitempty"`
|
||||||
|
StrokeDashoffset string `xml:"stroke-dashoffset,attr,omitempty"`
|
||||||
|
}
|
32
draw2dsvg/test_test.go
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
// Copyright 2015 The draw2d Authors. All rights reserved.
|
||||||
|
// created: 16/12/2017 by Drahoslav Bednář
|
||||||
|
|
||||||
|
// Package draw2dsvg_test gives test coverage with the command:
|
||||||
|
// go test -cover ./... | grep -v "no test"
|
||||||
|
// (It should be run from its parent draw2d directory.)
|
||||||
|
package draw2dsvg_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.fromouter.space/crunchy-rocks/draw2d"
|
||||||
|
"git.fromouter.space/crunchy-rocks/draw2d/draw2dsvg"
|
||||||
|
)
|
||||||
|
|
||||||
|
type sample func(gc draw2d.GraphicContext, ext string) (string, error)
|
||||||
|
|
||||||
|
func test(t *testing.T, draw sample) {
|
||||||
|
// Initialize the graphic context on an pdf document
|
||||||
|
dest := draw2dsvg.NewSvg()
|
||||||
|
gc := draw2dsvg.NewGraphicContext(dest)
|
||||||
|
// Draw sample
|
||||||
|
output, err := draw(gc, "svg")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Drawing %q failed: %v", output, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = draw2dsvg.SaveToSvgFile(output, dest)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Saving %q failed: %v", output, err)
|
||||||
|
}
|
||||||
|
}
|
83
draw2dsvg/text.go
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
// NOTE that this is identical copy of draw2dgl/text.go and draw2dimg/text.go
|
||||||
|
package draw2dsvg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.fromouter.space/crunchy-rocks/draw2d"
|
||||||
|
"git.fromouter.space/crunchy-rocks/freetype/truetype"
|
||||||
|
"golang.org/x/image/math/fixed"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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 fixed.Int26_6) 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(fixed.Int26_6(font.FUnitsPerEm()))
|
||||||
|
scale := size / float64(font.FUnitsPerEm())
|
||||||
|
return FontExtents{
|
||||||
|
Ascent: float64(bounds.Max.Y) * scale,
|
||||||
|
Descent: float64(bounds.Min.Y) * scale,
|
||||||
|
Height: float64(bounds.Max.Y-bounds.Min.Y) * scale,
|
||||||
|
}
|
||||||
|
}
|
58
draw2dsvg/xml_test.go
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
// Copyright 2015 The draw2d Authors. All rights reserved.
|
||||||
|
// created: 16/12/2017 by Drahoslav Bednář
|
||||||
|
|
||||||
|
// Package draw2dsvg_test gives test coverage with the command:
|
||||||
|
// go test -cover ./... | grep -v "no test"
|
||||||
|
// (It should be run from its parent draw2d directory.)
|
||||||
|
package draw2dsvg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Test basic encoding of svg/xml elements
|
||||||
|
func TestXml(t *testing.T) {
|
||||||
|
|
||||||
|
svg := NewSvg()
|
||||||
|
svg.Groups = []*Group{&Group{
|
||||||
|
Groups: []*Group{
|
||||||
|
&Group{}, // nested groups
|
||||||
|
&Group{},
|
||||||
|
},
|
||||||
|
Texts: []*Text{
|
||||||
|
&Text{Text: "Hello"}, // text
|
||||||
|
&Text{Text: "world", Style: "opacity: 0.5"}, // text with style
|
||||||
|
},
|
||||||
|
Paths: []*Path{
|
||||||
|
&Path{Desc: "M100,200 C100,100 250,100 250,200 S400,300 400,200"}, // simple path
|
||||||
|
&Path{}, // empty path
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
|
||||||
|
expectedOut := `<svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke="none">
|
||||||
|
<defs></defs>
|
||||||
|
<g>
|
||||||
|
<g></g>
|
||||||
|
<g></g>
|
||||||
|
<path d="M100,200 C100,100 250,100 250,200 S400,300 400,200"></path>
|
||||||
|
<path d=""></path>
|
||||||
|
<text>Hello</text>
|
||||||
|
<text style="opacity: 0.5">world</text>
|
||||||
|
</g>
|
||||||
|
</svg>`
|
||||||
|
|
||||||
|
out, err := xml.MarshalIndent(svg, "", " ")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if string(out) != expectedOut {
|
||||||
|
t.Errorf("svg output is not as expected\n"+
|
||||||
|
"got:\n%s\n\n"+
|
||||||
|
"want:\n%s\n",
|
||||||
|
string(out),
|
||||||
|
expectedOut,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
182
font.go
|
@ -6,18 +6,16 @@ package draw2d
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"path"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"code.google.com/p/freetype-go/freetype/truetype"
|
"sync"
|
||||||
)
|
|
||||||
|
"git.fromouter.space/crunchy-rocks/freetype/truetype"
|
||||||
var (
|
|
||||||
fontFolder = "../resource/font/"
|
|
||||||
fonts = make(map[string]*truetype.Font)
|
|
||||||
fontNamer FontFileNamer = FontFileName
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// FontStyle defines bold and italic styles for the font
|
||||||
|
// It is possible to combine values for mixed styles, eg.
|
||||||
|
// FontData.Style = FontStyleBold | FontStyleItalic
|
||||||
type FontStyle byte
|
type FontStyle byte
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -66,41 +64,167 @@ func FontFileName(fontData FontData) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func RegisterFont(fontData FontData, font *truetype.Font) {
|
func RegisterFont(fontData FontData, font *truetype.Font) {
|
||||||
fonts[fontNamer(fontData)] = font
|
fontCache.Store(fontData, font)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetFont(fontData FontData) *truetype.Font {
|
func GetFont(fontData FontData) (font *truetype.Font) {
|
||||||
fontFileName := fontNamer(fontData)
|
var err error
|
||||||
font := fonts[fontFileName]
|
|
||||||
if font != nil {
|
if font, err = fontCache.Load(fontData); err != nil {
|
||||||
return font
|
log.Println(err)
|
||||||
}
|
}
|
||||||
fonts[fontFileName] = loadFont(fontFileName)
|
|
||||||
return fonts[fontFileName]
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetFontFolder() string {
|
func GetFontFolder() string {
|
||||||
return fontFolder
|
return defaultFonts.folder
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetFontFolder(folder string) {
|
func SetFontFolder(folder string) {
|
||||||
fontFolder = filepath.Clean(folder)
|
defaultFonts.setFolder(filepath.Clean(folder))
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetGlobalFontCache() FontCache {
|
||||||
|
return fontCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetFontNamer(fn FontFileNamer) {
|
func SetFontNamer(fn FontFileNamer) {
|
||||||
fontNamer = fn
|
defaultFonts.setNamer(fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadFont(fontFileName string) *truetype.Font {
|
// Types implementing this interface can be passed to SetFontCache to change the
|
||||||
fontBytes, err := ioutil.ReadFile(path.Join(fontFolder, fontFileName))
|
// way fonts are being stored and retrieved.
|
||||||
if err != nil {
|
type FontCache interface {
|
||||||
log.Println(err)
|
// Loads a truetype font represented by the FontData object passed as
|
||||||
return nil
|
// argument.
|
||||||
|
// The method returns an error if the font could not be loaded, either
|
||||||
|
// because it didn't exist or the resource it was loaded from was corrupted.
|
||||||
|
Load(FontData) (*truetype.Font, error)
|
||||||
|
|
||||||
|
// Sets the truetype font that will be returned by Load when given the font
|
||||||
|
// data passed as first argument.
|
||||||
|
Store(FontData, *truetype.Font)
|
||||||
}
|
}
|
||||||
font, err := truetype.Parse(fontBytes)
|
|
||||||
if err != nil {
|
// Changes the font cache backend used by the package. After calling this
|
||||||
log.Println(err)
|
// functionSetFontFolder and SetFontNamer will not affect anymore how fonts are
|
||||||
return nil
|
// loaded.
|
||||||
|
// To restore the default font cache, call this function passing nil as argument.
|
||||||
|
func SetFontCache(cache FontCache) {
|
||||||
|
if cache == nil {
|
||||||
|
fontCache = defaultFonts
|
||||||
|
} else {
|
||||||
|
fontCache = cache
|
||||||
}
|
}
|
||||||
return font
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FolderFontCache can Load font from folder
|
||||||
|
type FolderFontCache struct {
|
||||||
|
fonts map[string]*truetype.Font
|
||||||
|
folder string
|
||||||
|
namer FontFileNamer
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFolderFontCache creates FolderFontCache
|
||||||
|
func NewFolderFontCache(folder string) *FolderFontCache {
|
||||||
|
return &FolderFontCache{
|
||||||
|
fonts: make(map[string]*truetype.Font),
|
||||||
|
folder: folder,
|
||||||
|
namer: FontFileName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load a font from cache if exists otherwise it will load the font from file
|
||||||
|
func (cache *FolderFontCache) Load(fontData FontData) (font *truetype.Font, err error) {
|
||||||
|
if font = cache.fonts[cache.namer(fontData)]; font != nil {
|
||||||
|
return font, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var data []byte
|
||||||
|
var file = cache.namer(fontData)
|
||||||
|
|
||||||
|
if data, err = ioutil.ReadFile(filepath.Join(cache.folder, file)); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if font, err = truetype.Parse(data); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cache.fonts[file] = font
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store a font to this cache
|
||||||
|
func (cache *FolderFontCache) Store(fontData FontData, font *truetype.Font) {
|
||||||
|
cache.fonts[cache.namer(fontData)] = font
|
||||||
|
}
|
||||||
|
|
||||||
|
// SyncFolderFontCache can Load font from folder
|
||||||
|
type SyncFolderFontCache struct {
|
||||||
|
sync.RWMutex
|
||||||
|
fonts map[string]*truetype.Font
|
||||||
|
folder string
|
||||||
|
namer FontFileNamer
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSyncFolderFontCache creates SyncFolderFontCache
|
||||||
|
func NewSyncFolderFontCache(folder string) *SyncFolderFontCache {
|
||||||
|
return &SyncFolderFontCache{
|
||||||
|
fonts: make(map[string]*truetype.Font),
|
||||||
|
folder: folder,
|
||||||
|
namer: FontFileName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cache *SyncFolderFontCache) setFolder(folder string) {
|
||||||
|
cache.Lock()
|
||||||
|
cache.folder = folder
|
||||||
|
cache.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cache *SyncFolderFontCache) setNamer(namer FontFileNamer) {
|
||||||
|
cache.Lock()
|
||||||
|
cache.namer = namer
|
||||||
|
cache.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load a font from cache if exists otherwise it will load the font from file
|
||||||
|
func (cache *SyncFolderFontCache) Load(fontData FontData) (font *truetype.Font, err error) {
|
||||||
|
cache.RLock()
|
||||||
|
font = cache.fonts[cache.namer(fontData)]
|
||||||
|
cache.RUnlock()
|
||||||
|
|
||||||
|
if font != nil {
|
||||||
|
return font, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var data []byte
|
||||||
|
var file = cache.namer(fontData)
|
||||||
|
|
||||||
|
if data, err = ioutil.ReadFile(filepath.Join(cache.folder, file)); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if font, err = truetype.Parse(data); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cache.Lock()
|
||||||
|
cache.fonts[file] = font
|
||||||
|
cache.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store a font to this cache
|
||||||
|
func (cache *SyncFolderFontCache) Store(fontData FontData, font *truetype.Font) {
|
||||||
|
cache.Lock()
|
||||||
|
cache.fonts[cache.namer(fontData)] = font
|
||||||
|
cache.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
defaultFonts = NewSyncFolderFontCache("../resource/font")
|
||||||
|
|
||||||
|
fontCache FontCache = defaultFonts
|
||||||
|
)
|
||||||
|
|
68
gc.go
|
@ -8,52 +8,80 @@ import (
|
||||||
"image/color"
|
"image/color"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FillRule defines the type for fill rules
|
|
||||||
type FillRule int
|
|
||||||
|
|
||||||
const (
|
|
||||||
// FillRuleEvenOdd defines the even odd filling rule
|
|
||||||
FillRuleEvenOdd FillRule = iota
|
|
||||||
// FillRuleWinding defines the non zero winding rule
|
|
||||||
FillRuleWinding
|
|
||||||
)
|
|
||||||
|
|
||||||
// GraphicContext describes the interface for the various backends (images, pdf, opengl, ...)
|
// GraphicContext describes the interface for the various backends (images, pdf, opengl, ...)
|
||||||
type GraphicContext interface {
|
type GraphicContext interface {
|
||||||
Path
|
// PathBuilder describes the interface for path drawing
|
||||||
// Create a new path
|
PathBuilder
|
||||||
|
// BeginPath creates a new path
|
||||||
BeginPath()
|
BeginPath()
|
||||||
GetMatrixTransform() MatrixTransform
|
// GetPath copies the current path, then returns it
|
||||||
SetMatrixTransform(tr MatrixTransform)
|
GetPath() Path
|
||||||
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)
|
Rotate(angle float64)
|
||||||
|
// Translate applies a translation to the current transformation matrix.
|
||||||
Translate(tx, ty float64)
|
Translate(tx, ty float64)
|
||||||
|
// Scale applies a scale to the current transformation matrix.
|
||||||
Scale(sx, sy float64)
|
Scale(sx, sy float64)
|
||||||
|
// SetStrokeColor sets the current stroke color
|
||||||
SetStrokeColor(c color.Color)
|
SetStrokeColor(c color.Color)
|
||||||
|
// SetFillColor sets the current fill color
|
||||||
SetFillColor(c color.Color)
|
SetFillColor(c color.Color)
|
||||||
|
// SetFillRule sets the current fill rule
|
||||||
SetFillRule(f FillRule)
|
SetFillRule(f FillRule)
|
||||||
|
// SetLineWidth sets the current line width
|
||||||
SetLineWidth(lineWidth float64)
|
SetLineWidth(lineWidth float64)
|
||||||
SetLineCap(cap Cap)
|
// SetLineCap sets the current line cap
|
||||||
SetLineJoin(join Join)
|
SetLineCap(cap LineCap)
|
||||||
|
// SetLineJoin sets the current line join
|
||||||
|
SetLineJoin(join LineJoin)
|
||||||
|
// SetLineDash sets the current dash
|
||||||
SetLineDash(dash []float64, dashOffset float64)
|
SetLineDash(dash []float64, dashOffset float64)
|
||||||
|
// SetFontSize sets the current font size
|
||||||
SetFontSize(fontSize float64)
|
SetFontSize(fontSize float64)
|
||||||
|
// GetFontSize gets the current font size
|
||||||
GetFontSize() float64
|
GetFontSize() float64
|
||||||
|
// SetFontData sets the current FontData
|
||||||
SetFontData(fontData FontData)
|
SetFontData(fontData FontData)
|
||||||
|
// GetFontData gets the current FontData
|
||||||
GetFontData() FontData
|
GetFontData() FontData
|
||||||
|
// GetFontName gets the current FontData as a string
|
||||||
|
GetFontName() string
|
||||||
|
// DrawImage draws the raster image in the current canvas
|
||||||
DrawImage(image image.Image)
|
DrawImage(image image.Image)
|
||||||
|
// Save the context and push it to the context stack
|
||||||
Save()
|
Save()
|
||||||
|
// Restore remove the current context and restore the last one
|
||||||
Restore()
|
Restore()
|
||||||
|
// Clear fills the current canvas with a default transparent color
|
||||||
Clear()
|
Clear()
|
||||||
|
// ClearRect fills the specified rectangle with a default transparent color
|
||||||
ClearRect(x1, y1, x2, y2 int)
|
ClearRect(x1, y1, x2, y2 int)
|
||||||
|
// SetDPI sets the current DPI
|
||||||
SetDPI(dpi int)
|
SetDPI(dpi int)
|
||||||
|
// GetDPI gets the current DPI
|
||||||
GetDPI() int
|
GetDPI() int
|
||||||
|
// GetStringBounds gets pixel bounds(dimensions) of given string
|
||||||
GetStringBounds(s string) (left, top, right, bottom float64)
|
GetStringBounds(s string) (left, top, right, bottom float64)
|
||||||
|
// CreateStringPath creates a path from the string s at x, y
|
||||||
CreateStringPath(text string, x, y float64) (cursor float64)
|
CreateStringPath(text string, x, y float64) (cursor float64)
|
||||||
|
// FillString draws the text at point (0, 0)
|
||||||
FillString(text string) (cursor float64)
|
FillString(text string) (cursor float64)
|
||||||
|
// FillStringAt draws the text at the specified point (x, y)
|
||||||
FillStringAt(text string, x, y float64) (cursor float64)
|
FillStringAt(text string, x, y float64) (cursor float64)
|
||||||
|
// StrokeString draws the contour of the text at point (0, 0)
|
||||||
StrokeString(text string) (cursor float64)
|
StrokeString(text string) (cursor float64)
|
||||||
|
// StrokeStringAt draws the contour of the text at point (x, y)
|
||||||
StrokeStringAt(text string, x, y float64) (cursor float64)
|
StrokeStringAt(text string, x, y float64) (cursor float64)
|
||||||
Stroke(paths ...*PathStorage)
|
// Stroke strokes the paths with the color specified by SetStrokeColor
|
||||||
Fill(paths ...*PathStorage)
|
Stroke(paths ...*Path)
|
||||||
FillStroke(paths ...*PathStorage)
|
// Fill fills the paths with the color specified by SetFillColor
|
||||||
|
Fill(paths ...*Path)
|
||||||
|
// FillStroke first fills the paths and than strokes them
|
||||||
|
FillStroke(paths ...*Path)
|
||||||
}
|
}
|
||||||
|
|
13
go.mod
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
module git.fromouter.space/crunchy-rocks/draw2d
|
||||||
|
|
||||||
|
require (
|
||||||
|
git.fromouter.space/crunchy-rocks/emoji v0.0.0-20181116142102-2188aadaf093
|
||||||
|
git.fromouter.space/crunchy-rocks/freetype v0.0.0-20181116104610-3115318f2577
|
||||||
|
github.com/go-gl/gl v0.0.0-20180407155706-68e253793080
|
||||||
|
github.com/go-gl/glfw v0.0.0-20180426074136-46a8d530c326
|
||||||
|
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
|
||||||
|
github.com/jung-kurt/gofpdf v1.0.0
|
||||||
|
github.com/llgcode/draw2d v0.0.0-20180825133448-f52c8a71aff0
|
||||||
|
github.com/llgcode/ps v0.0.0-20150911083025-f1443b32eedb
|
||||||
|
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81
|
||||||
|
)
|
358
image.go
|
@ -1,358 +0,0 @@
|
||||||
// Copyright 2010 The draw2d Authors. All rights reserved.
|
|
||||||
// created: 21/11/2010 by Laurent Le Goff
|
|
||||||
|
|
||||||
package draw2d
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"image"
|
|
||||||
"image/color"
|
|
||||||
"image/draw"
|
|
||||||
"log"
|
|
||||||
"math"
|
|
||||||
|
|
||||||
"code.google.com/p/freetype-go/freetype/raster"
|
|
||||||
"code.google.com/p/freetype-go/freetype/truetype"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Painter interface {
|
|
||||||
raster.Painter
|
|
||||||
SetColor(color color.Color)
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
defaultFontData = FontData{"luxi", FontFamilySans, FontStyleNormal}
|
|
||||||
)
|
|
||||||
|
|
||||||
type ImageGraphicContext struct {
|
|
||||||
*StackGraphicContext
|
|
||||||
img draw.Image
|
|
||||||
painter Painter
|
|
||||||
fillRasterizer *raster.Rasterizer
|
|
||||||
strokeRasterizer *raster.Rasterizer
|
|
||||||
glyphBuf *truetype.GlyphBuf
|
|
||||||
DPI int
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewGraphicContext creates a new Graphic context from an image.
|
|
||||||
func NewGraphicContext(img draw.Image) *ImageGraphicContext {
|
|
||||||
var painter Painter
|
|
||||||
switch selectImage := img.(type) {
|
|
||||||
case *image.RGBA:
|
|
||||||
painter = raster.NewRGBAPainter(selectImage)
|
|
||||||
default:
|
|
||||||
panic("Image type not supported")
|
|
||||||
}
|
|
||||||
return NewGraphicContextWithPainter(img, painter)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewGraphicContextWithPainter creates a new Graphic context from an image and a Painter (see Freetype-go)
|
|
||||||
func NewGraphicContextWithPainter(img draw.Image, painter Painter) *ImageGraphicContext {
|
|
||||||
width, height := img.Bounds().Dx(), img.Bounds().Dy()
|
|
||||||
dpi := 92
|
|
||||||
gc := &ImageGraphicContext{
|
|
||||||
NewStackGraphicContext(),
|
|
||||||
img,
|
|
||||||
painter,
|
|
||||||
raster.NewRasterizer(width, height),
|
|
||||||
raster.NewRasterizer(width, height),
|
|
||||||
truetype.NewGlyphBuf(),
|
|
||||||
dpi,
|
|
||||||
}
|
|
||||||
return gc
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gc *ImageGraphicContext) GetDPI() int {
|
|
||||||
return gc.DPI
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gc *ImageGraphicContext) 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) {
|
|
||||||
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(img, gc.img, gc.Current.Tr, draw.Over, BilinearFilter)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gc *ImageGraphicContext) FillString(text string) (cursor float64) {
|
|
||||||
return gc.FillStringAt(text, 0, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gc *ImageGraphicContext) 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) {
|
|
||||||
return gc.StrokeStringAt(text, 0, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gc *ImageGraphicContext) 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)
|
|
||||||
if font == nil {
|
|
||||||
font = GetFont(defaultFontData)
|
|
||||||
}
|
|
||||||
if font == nil {
|
|
||||||
return nil, errors.New("No font set, and no default font available.")
|
|
||||||
}
|
|
||||||
gc.SetFont(font)
|
|
||||||
gc.SetFontSize(gc.Current.FontSize)
|
|
||||||
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 {
|
|
||||||
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)
|
|
||||||
e0 = e1
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateStringPath creates a path from the string s at x, y, and returns the string width.
|
|
||||||
// The text is placed so that the left edge of the em square of the first character of s
|
|
||||||
// and the baseline intersect at x, y. The majority of the affected pixels will be
|
|
||||||
// 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 {
|
|
||||||
font, err := gc.loadCurrentFont()
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return 0.0
|
|
||||||
}
|
|
||||||
startx := x
|
|
||||||
prev, hasPrev := truetype.Index(0), false
|
|
||||||
for _, rune := range s {
|
|
||||||
index := font.Index(rune)
|
|
||||||
if hasPrev {
|
|
||||||
x += fUnitsToFloat64(font.Kerning(int32(gc.Current.Scale), prev, index))
|
|
||||||
}
|
|
||||||
err := gc.drawGlyph(index, x, y)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return startx - x
|
|
||||||
}
|
|
||||||
x += fUnitsToFloat64(font.HMetric(int32(gc.Current.Scale), index).AdvanceWidth)
|
|
||||||
prev, hasPrev = index, true
|
|
||||||
}
|
|
||||||
return x - startx
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
// 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) {
|
|
||||||
font, err := gc.loadCurrentFont()
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return 0, 0, 0, 0
|
|
||||||
}
|
|
||||||
top, left, bottom, right = 10e6, 10e6, -10e6, -10e6
|
|
||||||
cursor := 0.0
|
|
||||||
prev, hasPrev := truetype.Index(0), false
|
|
||||||
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 {
|
|
||||||
log.Println(err)
|
|
||||||
return 0, 0, 0, 0
|
|
||||||
}
|
|
||||||
e0 := 0
|
|
||||||
for _, e1 := range gc.glyphBuf.End {
|
|
||||||
ps := gc.glyphBuf.Point[e0:e1]
|
|
||||||
for _, p := range ps {
|
|
||||||
x, y := pointToF64Point(p)
|
|
||||||
top = math.Min(top, y)
|
|
||||||
bottom = math.Max(bottom, y)
|
|
||||||
left = math.Min(left, x+cursor)
|
|
||||||
right = math.Max(right, x+cursor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cursor += fUnitsToFloat64(font.HMetric(int32(gc.Current.Scale), index).AdvanceWidth)
|
|
||||||
prev, hasPrev = index, true
|
|
||||||
}
|
|
||||||
return left, top, right, bottom
|
|
||||||
}
|
|
||||||
|
|
||||||
// recalc recalculates scale and bounds values from the font size, screen
|
|
||||||
// resolution and font metrics, and invalidates the glyph cache.
|
|
||||||
func (gc *ImageGraphicContext) 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) {
|
|
||||||
gc.DPI = dpi
|
|
||||||
gc.recalc()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetFont sets the font used to draw text.
|
|
||||||
func (gc *ImageGraphicContext) 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) {
|
|
||||||
gc.Current.FontSize = fontSize
|
|
||||||
gc.recalc()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gc *ImageGraphicContext) paint(rasterizer *raster.Rasterizer, color color.Color) {
|
|
||||||
gc.painter.SetColor(color)
|
|
||||||
rasterizer.Rasterize(gc.painter)
|
|
||||||
rasterizer.Clear()
|
|
||||||
gc.Current.Path = NewPathStorage()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stroke strokes the paths with the color specified by SetStrokeColor
|
|
||||||
func (gc *ImageGraphicContext) Stroke(paths ...*PathStorage) {
|
|
||||||
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.HalfLineWidth = gc.Current.LineWidth / 2
|
|
||||||
var pathConverter *PathConverter
|
|
||||||
if gc.Current.Dash != nil && len(gc.Current.Dash) > 0 {
|
|
||||||
dasher := NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker)
|
|
||||||
pathConverter = NewPathConverter(dasher)
|
|
||||||
} else {
|
|
||||||
pathConverter = NewPathConverter(stroker)
|
|
||||||
}
|
|
||||||
pathConverter.ApproximationScale = gc.Current.Tr.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) {
|
|
||||||
paths = append(paths, gc.Current.Path)
|
|
||||||
gc.fillRasterizer.UseNonZeroWinding = gc.Current.FillRule.UseNonZeroWinding()
|
|
||||||
|
|
||||||
/**** first method ****/
|
|
||||||
pathConverter := NewPathConverter(NewVertexMatrixTransform(gc.Current.Tr, NewVertexAdder(gc.fillRasterizer)))
|
|
||||||
pathConverter.ApproximationScale = gc.Current.Tr.GetScale()
|
|
||||||
pathConverter.Convert(paths...)
|
|
||||||
|
|
||||||
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()
|
|
||||||
gc.strokeRasterizer.UseNonZeroWinding = true
|
|
||||||
|
|
||||||
filler := NewVertexMatrixTransform(gc.Current.Tr, NewVertexAdder(gc.fillRasterizer))
|
|
||||||
|
|
||||||
stroker := NewLineStroker(gc.Current.Cap, gc.Current.Join, NewVertexMatrixTransform(gc.Current.Tr, NewVertexAdder(gc.strokeRasterizer)))
|
|
||||||
stroker.HalfLineWidth = gc.Current.LineWidth / 2
|
|
||||||
|
|
||||||
demux := NewDemuxConverter(filler, stroker)
|
|
||||||
paths = append(paths, gc.Current.Path)
|
|
||||||
pathConverter := NewPathConverter(demux)
|
|
||||||
pathConverter.ApproximationScale = gc.Current.Tr.GetScale()
|
|
||||||
pathConverter.Convert(paths...)
|
|
||||||
|
|
||||||
gc.paint(gc.fillRasterizer, gc.Current.FillColor)
|
|
||||||
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 {
|
|
||||||
switch c {
|
|
||||||
case RoundCap:
|
|
||||||
return raster.RoundCapper
|
|
||||||
case ButtCap:
|
|
||||||
return raster.ButtCapper
|
|
||||||
case SquareCap:
|
|
||||||
return raster.SquareCapper
|
|
||||||
}
|
|
||||||
return raster.RoundCapper
|
|
||||||
}
|
|
||||||
|
|
||||||
func (j Join) Convert() raster.Joiner {
|
|
||||||
switch j {
|
|
||||||
case RoundJoin:
|
|
||||||
return raster.RoundJoiner
|
|
||||||
case BevelJoin:
|
|
||||||
return raster.BevelJoiner
|
|
||||||
}
|
|
||||||
return raster.RoundJoiner
|
|
||||||
}
|
|
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
|
||||||
|
}
|
4
output/draw2dkit/.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
# Ignore everything in this directory
|
||||||
|
*
|
||||||
|
# Except this file
|
||||||
|
!.gitignore
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 42 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}
|
|
||||||
}
|
|
||||||
*/
|
|
220
path.go
|
@ -3,37 +3,221 @@
|
||||||
|
|
||||||
package draw2d
|
package draw2d
|
||||||
|
|
||||||
// Path describes the interface for path drawing.
|
import (
|
||||||
type Path interface {
|
"fmt"
|
||||||
// LastPoint returns the current point of the path
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PathBuilder describes the interface for path drawing.
|
||||||
|
type PathBuilder interface {
|
||||||
|
// LastPoint returns the current point of the current sub path
|
||||||
LastPoint() (x, y float64)
|
LastPoint() (x, y float64)
|
||||||
// MoveTo creates a new subpath that start at the specified point
|
// MoveTo creates a new subpath that start at the specified point
|
||||||
MoveTo(x, y float64)
|
MoveTo(x, y float64)
|
||||||
// RMoveTo creates a new subpath that start at the specified point
|
|
||||||
// relative to the current point
|
|
||||||
RMoveTo(dx, dy float64)
|
|
||||||
// LineTo adds a line to the current subpath
|
// LineTo adds a line to the current subpath
|
||||||
LineTo(x, y float64)
|
LineTo(x, y float64)
|
||||||
// RLineTo adds a line to the current subpath
|
|
||||||
// relative to the current point
|
|
||||||
RLineTo(dx, dy float64)
|
|
||||||
// QuadCurveTo adds a quadratic Bézier curve to the current subpath
|
// QuadCurveTo adds a quadratic Bézier curve to the current subpath
|
||||||
QuadCurveTo(cx, cy, x, y float64)
|
QuadCurveTo(cx, cy, x, y float64)
|
||||||
// QuadCurveTo adds a quadratic Bézier curve to the current subpath
|
|
||||||
// relative to the current point
|
|
||||||
RQuadCurveTo(dcx, dcy, dx, dy float64)
|
|
||||||
// CubicCurveTo adds a cubic Bézier curve to the current subpath
|
// CubicCurveTo adds a cubic Bézier curve to the current subpath
|
||||||
CubicCurveTo(cx1, cy1, cx2, cy2, x, y float64)
|
CubicCurveTo(cx1, cy1, cx2, cy2, x, y float64)
|
||||||
// RCubicCurveTo adds a cubic Bézier curve to the current subpath
|
|
||||||
// relative to the current point
|
|
||||||
RCubicCurveTo(dcx1, dcy1, dcx2, dcy2, dx, dy float64)
|
|
||||||
// ArcTo adds an arc to the current subpath
|
// ArcTo adds an arc to the current subpath
|
||||||
ArcTo(cx, cy, rx, ry, startAngle, angle float64)
|
ArcTo(cx, cy, rx, ry, startAngle, angle float64)
|
||||||
// RArcTo adds an arc to the current subpath
|
|
||||||
// relative to the current point
|
|
||||||
RArcTo(dcx, dcy, rx, ry, startAngle, angle float64)
|
|
||||||
// Close creates a line from the current point to the last MoveTo
|
// Close creates a line from the current point to the last MoveTo
|
||||||
// point (if not the same) and mark the path as closed so the
|
// point (if not the same) and mark the path as closed so the
|
||||||
// first and last lines join nicely.
|
// first and last lines join nicely.
|
||||||
Close()
|
Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PathCmp represents component of a path
|
||||||
|
type PathCmp int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// MoveToCmp is a MoveTo component in a Path
|
||||||
|
MoveToCmp PathCmp = iota
|
||||||
|
// LineToCmp is a LineTo component in a Path
|
||||||
|
LineToCmp
|
||||||
|
// QuadCurveToCmp is a QuadCurveTo component in a Path
|
||||||
|
QuadCurveToCmp
|
||||||
|
// CubicCurveToCmp is a CubicCurveTo component in a Path
|
||||||
|
CubicCurveToCmp
|
||||||
|
// ArcToCmp is a ArcTo component in a Path
|
||||||
|
ArcToCmp
|
||||||
|
// CloseCmp is a ArcTo component in a Path
|
||||||
|
CloseCmp
|
||||||
|
)
|
||||||
|
|
||||||
|
// Path stores points
|
||||||
|
type Path struct {
|
||||||
|
// Components is a slice of PathCmp in a Path and mark the role of each points in the Path
|
||||||
|
Components []PathCmp
|
||||||
|
// Points are combined with Components to have a specific role in the path
|
||||||
|
Points []float64
|
||||||
|
// Last Point of the Path
|
||||||
|
x, y float64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Path) appendToPath(cmd PathCmp, points ...float64) {
|
||||||
|
p.Components = append(p.Components, cmd)
|
||||||
|
p.Points = append(p.Points, points...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LastPoint returns the current point of the current path
|
||||||
|
func (p *Path) LastPoint() (x, y float64) {
|
||||||
|
return p.x, p.y
|
||||||
|
}
|
||||||
|
|
||||||
|
// MoveTo starts a new path at (x, y) position
|
||||||
|
func (p *Path) MoveTo(x, y float64) {
|
||||||
|
p.appendToPath(MoveToCmp, x, y)
|
||||||
|
p.x = x
|
||||||
|
p.y = y
|
||||||
|
}
|
||||||
|
|
||||||
|
// LineTo adds a line to the current path
|
||||||
|
func (p *Path) LineTo(x, y float64) {
|
||||||
|
if len(p.Components) == 0 { //special case when no move has been done
|
||||||
|
p.MoveTo(x, y)
|
||||||
|
} else {
|
||||||
|
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(x, y)
|
||||||
|
} else {
|
||||||
|
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(x, y)
|
||||||
|
} else {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns new Path with flipped y axes
|
||||||
|
func (path *Path) VerticalFlip() *Path {
|
||||||
|
p := path.Copy()
|
||||||
|
j := 0
|
||||||
|
for _, cmd := range p.Components {
|
||||||
|
switch cmd {
|
||||||
|
case MoveToCmp, LineToCmp:
|
||||||
|
p.Points[j+1] = -p.Points[j+1]
|
||||||
|
j = j + 2
|
||||||
|
case QuadCurveToCmp:
|
||||||
|
p.Points[j+1] = -p.Points[j+1]
|
||||||
|
p.Points[j+3] = -p.Points[j+3]
|
||||||
|
j = j + 4
|
||||||
|
case CubicCurveToCmp:
|
||||||
|
p.Points[j+1] = -p.Points[j+1]
|
||||||
|
p.Points[j+3] = -p.Points[j+3]
|
||||||
|
p.Points[j+5] = -p.Points[j+5]
|
||||||
|
j = j + 6
|
||||||
|
case ArcToCmp:
|
||||||
|
p.Points[j+1] = -p.Points[j+1]
|
||||||
|
p.Points[j+3] = -p.Points[j+3]
|
||||||
|
p.Points[j+4] = -p.Points[j+4] // start angle
|
||||||
|
p.Points[j+5] = -p.Points[j+5] // angle
|
||||||
|
j = j + 6
|
||||||
|
case CloseCmp:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.y = -p.y
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
|
@ -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,203 +0,0 @@
|
||||||
// Copyright 2011 The draw2d Authors. All rights reserved.
|
|
||||||
// created: 27/05/2011 by Laurent Le Goff
|
|
||||||
package raster
|
|
||||||
|
|
||||||
var SUBPIXEL_OFFSETS_SAMPLE_8 = [8]float64{
|
|
||||||
5.0 / 8,
|
|
||||||
0.0 / 8,
|
|
||||||
3.0 / 8,
|
|
||||||
6.0 / 8,
|
|
||||||
1.0 / 8,
|
|
||||||
4.0 / 8,
|
|
||||||
7.0 / 8,
|
|
||||||
2.0 / 8,
|
|
||||||
}
|
|
||||||
|
|
||||||
var SUBPIXEL_OFFSETS_SAMPLE_8_FIXED = [8]Fix{
|
|
||||||
Fix(SUBPIXEL_OFFSETS_SAMPLE_8[0] * FIXED_FLOAT_COEF),
|
|
||||||
Fix(SUBPIXEL_OFFSETS_SAMPLE_8[1] * FIXED_FLOAT_COEF),
|
|
||||||
Fix(SUBPIXEL_OFFSETS_SAMPLE_8[2] * FIXED_FLOAT_COEF),
|
|
||||||
Fix(SUBPIXEL_OFFSETS_SAMPLE_8[3] * FIXED_FLOAT_COEF),
|
|
||||||
Fix(SUBPIXEL_OFFSETS_SAMPLE_8[4] * FIXED_FLOAT_COEF),
|
|
||||||
Fix(SUBPIXEL_OFFSETS_SAMPLE_8[5] * FIXED_FLOAT_COEF),
|
|
||||||
Fix(SUBPIXEL_OFFSETS_SAMPLE_8[6] * FIXED_FLOAT_COEF),
|
|
||||||
Fix(SUBPIXEL_OFFSETS_SAMPLE_8[7] * FIXED_FLOAT_COEF),
|
|
||||||
}
|
|
||||||
|
|
||||||
var SUBPIXEL_OFFSETS_SAMPLE_16 = [16]float64{
|
|
||||||
1.0 / 16,
|
|
||||||
8.0 / 16,
|
|
||||||
4.0 / 16,
|
|
||||||
15.0 / 16,
|
|
||||||
11.0 / 16,
|
|
||||||
2.0 / 16,
|
|
||||||
6.0 / 16,
|
|
||||||
14.0 / 16,
|
|
||||||
10.0 / 16,
|
|
||||||
3.0 / 16,
|
|
||||||
7.0 / 16,
|
|
||||||
12.0 / 16,
|
|
||||||
0.0 / 16,
|
|
||||||
9.0 / 16,
|
|
||||||
5.0 / 16,
|
|
||||||
13.0 / 16,
|
|
||||||
}
|
|
||||||
|
|
||||||
var SUBPIXEL_OFFSETS_SAMPLE_16_FIXED = [16]Fix{
|
|
||||||
Fix(SUBPIXEL_OFFSETS_SAMPLE_16[0] * FIXED_FLOAT_COEF),
|
|
||||||
Fix(SUBPIXEL_OFFSETS_SAMPLE_16[1] * FIXED_FLOAT_COEF),
|
|
||||||
Fix(SUBPIXEL_OFFSETS_SAMPLE_16[2] * FIXED_FLOAT_COEF),
|
|
||||||
Fix(SUBPIXEL_OFFSETS_SAMPLE_16[3] * FIXED_FLOAT_COEF),
|
|
||||||
Fix(SUBPIXEL_OFFSETS_SAMPLE_16[4] * FIXED_FLOAT_COEF),
|
|
||||||
Fix(SUBPIXEL_OFFSETS_SAMPLE_16[5] * FIXED_FLOAT_COEF),
|
|
||||||
Fix(SUBPIXEL_OFFSETS_SAMPLE_16[6] * FIXED_FLOAT_COEF),
|
|
||||||
Fix(SUBPIXEL_OFFSETS_SAMPLE_16[7] * FIXED_FLOAT_COEF),
|
|
||||||
Fix(SUBPIXEL_OFFSETS_SAMPLE_16[8] * FIXED_FLOAT_COEF),
|
|
||||||
Fix(SUBPIXEL_OFFSETS_SAMPLE_16[9] * FIXED_FLOAT_COEF),
|
|
||||||
Fix(SUBPIXEL_OFFSETS_SAMPLE_16[10] * FIXED_FLOAT_COEF),
|
|
||||||
Fix(SUBPIXEL_OFFSETS_SAMPLE_16[11] * FIXED_FLOAT_COEF),
|
|
||||||
Fix(SUBPIXEL_OFFSETS_SAMPLE_16[12] * FIXED_FLOAT_COEF),
|
|
||||||
Fix(SUBPIXEL_OFFSETS_SAMPLE_16[13] * FIXED_FLOAT_COEF),
|
|
||||||
Fix(SUBPIXEL_OFFSETS_SAMPLE_16[14] * FIXED_FLOAT_COEF),
|
|
||||||
Fix(SUBPIXEL_OFFSETS_SAMPLE_16[15] * FIXED_FLOAT_COEF),
|
|
||||||
}
|
|
||||||
|
|
||||||
var SUBPIXEL_OFFSETS_SAMPLE_32 = [32]float64{
|
|
||||||
28.0 / 32,
|
|
||||||
13.0 / 32,
|
|
||||||
6.0 / 32,
|
|
||||||
23.0 / 32,
|
|
||||||
0.0 / 32,
|
|
||||||
17.0 / 32,
|
|
||||||
10.0 / 32,
|
|
||||||
27.0 / 32,
|
|
||||||
4.0 / 32,
|
|
||||||
21.0 / 32,
|
|
||||||
14.0 / 32,
|
|
||||||
31.0 / 32,
|
|
||||||
8.0 / 32,
|
|
||||||
25.0 / 32,
|
|
||||||
18.0 / 32,
|
|
||||||
3.0 / 32,
|
|
||||||
12.0 / 32,
|
|
||||||
29.0 / 32,
|
|
||||||
22.0 / 32,
|
|
||||||
7.0 / 32,
|
|
||||||
16.0 / 32,
|
|
||||||
1.0 / 32,
|
|
||||||
26.0 / 32,
|
|
||||||
11.0 / 32,
|
|
||||||
20.0 / 32,
|
|
||||||
5.0 / 32,
|
|
||||||
30.0 / 32,
|
|
||||||
15.0 / 32,
|
|
||||||
24.0 / 32,
|
|
||||||
9.0 / 32,
|
|
||||||
2.0 / 32,
|
|
||||||
19.0 / 32,
|
|
||||||
}
|
|
||||||
var SUBPIXEL_OFFSETS_SAMPLE_32_FIXED = [32]Fix{
|
|
||||||
Fix(SUBPIXEL_OFFSETS_SAMPLE_32[0] * FIXED_FLOAT_COEF),
|
|
||||||
Fix(SUBPIXEL_OFFSETS_SAMPLE_32[1] * FIXED_FLOAT_COEF),
|
|
||||||
Fix(SUBPIXEL_OFFSETS_SAMPLE_32[2] * FIXED_FLOAT_COEF),
|
|
||||||
Fix(SUBPIXEL_OFFSETS_SAMPLE_32[3] * FIXED_FLOAT_COEF),
|
|
||||||
Fix(SUBPIXEL_OFFSETS_SAMPLE_32[4] * FIXED_FLOAT_COEF),
|
|
||||||
Fix(SUBPIXEL_OFFSETS_SAMPLE_32[5] * FIXED_FLOAT_COEF),
|
|
||||||
Fix(SUBPIXEL_OFFSETS_SAMPLE_32[6] * FIXED_FLOAT_COEF),
|
|
||||||
Fix(SUBPIXEL_OFFSETS_SAMPLE_32[7] * FIXED_FLOAT_COEF),
|
|
||||||
Fix(SUBPIXEL_OFFSETS_SAMPLE_32[8] * FIXED_FLOAT_COEF),
|
|
||||||
Fix(SUBPIXEL_OFFSETS_SAMPLE_32[9] * FIXED_FLOAT_COEF),
|
|
||||||
Fix(SUBPIXEL_OFFSETS_SAMPLE_32[10] * FIXED_FLOAT_COEF),
|
|
||||||
Fix(SUBPIXEL_OFFSETS_SAMPLE_32[11] * FIXED_FLOAT_COEF),
|
|
||||||
Fix(SUBPIXEL_OFFSETS_SAMPLE_32[12] * FIXED_FLOAT_COEF),
|
|
||||||
Fix(SUBPIXEL_OFFSETS_SAMPLE_32[13] * FIXED_FLOAT_COEF),
|
|
||||||
Fix(SUBPIXEL_OFFSETS_SAMPLE_32[14] * FIXED_FLOAT_COEF),
|
|
||||||
Fix(SUBPIXEL_OFFSETS_SAMPLE_32[15] * FIXED_FLOAT_COEF),
|
|
||||||
Fix(SUBPIXEL_OFFSETS_SAMPLE_32[16] * FIXED_FLOAT_COEF),
|
|
||||||
Fix(SUBPIXEL_OFFSETS_SAMPLE_32[17] * FIXED_FLOAT_COEF),
|
|
||||||
Fix(SUBPIXEL_OFFSETS_SAMPLE_32[18] * FIXED_FLOAT_COEF),
|
|
||||||
Fix(SUBPIXEL_OFFSETS_SAMPLE_32[19] * FIXED_FLOAT_COEF),
|
|
||||||
Fix(SUBPIXEL_OFFSETS_SAMPLE_32[20] * FIXED_FLOAT_COEF),
|
|
||||||
Fix(SUBPIXEL_OFFSETS_SAMPLE_32[21] * FIXED_FLOAT_COEF),
|
|
||||||
Fix(SUBPIXEL_OFFSETS_SAMPLE_32[22] * FIXED_FLOAT_COEF),
|
|
||||||
Fix(SUBPIXEL_OFFSETS_SAMPLE_32[23] * FIXED_FLOAT_COEF),
|
|
||||||
Fix(SUBPIXEL_OFFSETS_SAMPLE_32[24] * FIXED_FLOAT_COEF),
|
|
||||||
Fix(SUBPIXEL_OFFSETS_SAMPLE_32[25] * FIXED_FLOAT_COEF),
|
|
||||||
Fix(SUBPIXEL_OFFSETS_SAMPLE_32[26] * FIXED_FLOAT_COEF),
|
|
||||||
Fix(SUBPIXEL_OFFSETS_SAMPLE_32[27] * FIXED_FLOAT_COEF),
|
|
||||||
Fix(SUBPIXEL_OFFSETS_SAMPLE_32[28] * FIXED_FLOAT_COEF),
|
|
||||||
Fix(SUBPIXEL_OFFSETS_SAMPLE_32[29] * FIXED_FLOAT_COEF),
|
|
||||||
Fix(SUBPIXEL_OFFSETS_SAMPLE_32[30] * FIXED_FLOAT_COEF),
|
|
||||||
Fix(SUBPIXEL_OFFSETS_SAMPLE_32[31] * FIXED_FLOAT_COEF),
|
|
||||||
}
|
|
||||||
|
|
||||||
var coverageTable = [256]uint8{
|
|
||||||
pixelCoverage(0x00), pixelCoverage(0x01), pixelCoverage(0x02), pixelCoverage(0x03),
|
|
||||||
pixelCoverage(0x04), pixelCoverage(0x05), pixelCoverage(0x06), pixelCoverage(0x07),
|
|
||||||
pixelCoverage(0x08), pixelCoverage(0x09), pixelCoverage(0x0a), pixelCoverage(0x0b),
|
|
||||||
pixelCoverage(0x0c), pixelCoverage(0x0d), pixelCoverage(0x0e), pixelCoverage(0x0f),
|
|
||||||
pixelCoverage(0x10), pixelCoverage(0x11), pixelCoverage(0x12), pixelCoverage(0x13),
|
|
||||||
pixelCoverage(0x14), pixelCoverage(0x15), pixelCoverage(0x16), pixelCoverage(0x17),
|
|
||||||
pixelCoverage(0x18), pixelCoverage(0x19), pixelCoverage(0x1a), pixelCoverage(0x1b),
|
|
||||||
pixelCoverage(0x1c), pixelCoverage(0x1d), pixelCoverage(0x1e), pixelCoverage(0x1f),
|
|
||||||
pixelCoverage(0x20), pixelCoverage(0x21), pixelCoverage(0x22), pixelCoverage(0x23),
|
|
||||||
pixelCoverage(0x24), pixelCoverage(0x25), pixelCoverage(0x26), pixelCoverage(0x27),
|
|
||||||
pixelCoverage(0x28), pixelCoverage(0x29), pixelCoverage(0x2a), pixelCoverage(0x2b),
|
|
||||||
pixelCoverage(0x2c), pixelCoverage(0x2d), pixelCoverage(0x2e), pixelCoverage(0x2f),
|
|
||||||
pixelCoverage(0x30), pixelCoverage(0x31), pixelCoverage(0x32), pixelCoverage(0x33),
|
|
||||||
pixelCoverage(0x34), pixelCoverage(0x35), pixelCoverage(0x36), pixelCoverage(0x37),
|
|
||||||
pixelCoverage(0x38), pixelCoverage(0x39), pixelCoverage(0x3a), pixelCoverage(0x3b),
|
|
||||||
pixelCoverage(0x3c), pixelCoverage(0x3d), pixelCoverage(0x3e), pixelCoverage(0x3f),
|
|
||||||
pixelCoverage(0x40), pixelCoverage(0x41), pixelCoverage(0x42), pixelCoverage(0x43),
|
|
||||||
pixelCoverage(0x44), pixelCoverage(0x45), pixelCoverage(0x46), pixelCoverage(0x47),
|
|
||||||
pixelCoverage(0x48), pixelCoverage(0x49), pixelCoverage(0x4a), pixelCoverage(0x4b),
|
|
||||||
pixelCoverage(0x4c), pixelCoverage(0x4d), pixelCoverage(0x4e), pixelCoverage(0x4f),
|
|
||||||
pixelCoverage(0x50), pixelCoverage(0x51), pixelCoverage(0x52), pixelCoverage(0x53),
|
|
||||||
pixelCoverage(0x54), pixelCoverage(0x55), pixelCoverage(0x56), pixelCoverage(0x57),
|
|
||||||
pixelCoverage(0x58), pixelCoverage(0x59), pixelCoverage(0x5a), pixelCoverage(0x5b),
|
|
||||||
pixelCoverage(0x5c), pixelCoverage(0x5d), pixelCoverage(0x5e), pixelCoverage(0x5f),
|
|
||||||
pixelCoverage(0x60), pixelCoverage(0x61), pixelCoverage(0x62), pixelCoverage(0x63),
|
|
||||||
pixelCoverage(0x64), pixelCoverage(0x65), pixelCoverage(0x66), pixelCoverage(0x67),
|
|
||||||
pixelCoverage(0x68), pixelCoverage(0x69), pixelCoverage(0x6a), pixelCoverage(0x6b),
|
|
||||||
pixelCoverage(0x6c), pixelCoverage(0x6d), pixelCoverage(0x6e), pixelCoverage(0x6f),
|
|
||||||
pixelCoverage(0x70), pixelCoverage(0x71), pixelCoverage(0x72), pixelCoverage(0x73),
|
|
||||||
pixelCoverage(0x74), pixelCoverage(0x75), pixelCoverage(0x76), pixelCoverage(0x77),
|
|
||||||
pixelCoverage(0x78), pixelCoverage(0x79), pixelCoverage(0x7a), pixelCoverage(0x7b),
|
|
||||||
pixelCoverage(0x7c), pixelCoverage(0x7d), pixelCoverage(0x7e), pixelCoverage(0x7f),
|
|
||||||
pixelCoverage(0x80), pixelCoverage(0x81), pixelCoverage(0x82), pixelCoverage(0x83),
|
|
||||||
pixelCoverage(0x84), pixelCoverage(0x85), pixelCoverage(0x86), pixelCoverage(0x87),
|
|
||||||
pixelCoverage(0x88), pixelCoverage(0x89), pixelCoverage(0x8a), pixelCoverage(0x8b),
|
|
||||||
pixelCoverage(0x8c), pixelCoverage(0x8d), pixelCoverage(0x8e), pixelCoverage(0x8f),
|
|
||||||
pixelCoverage(0x90), pixelCoverage(0x91), pixelCoverage(0x92), pixelCoverage(0x93),
|
|
||||||
pixelCoverage(0x94), pixelCoverage(0x95), pixelCoverage(0x96), pixelCoverage(0x97),
|
|
||||||
pixelCoverage(0x98), pixelCoverage(0x99), pixelCoverage(0x9a), pixelCoverage(0x9b),
|
|
||||||
pixelCoverage(0x9c), pixelCoverage(0x9d), pixelCoverage(0x9e), pixelCoverage(0x9f),
|
|
||||||
pixelCoverage(0xa0), pixelCoverage(0xa1), pixelCoverage(0xa2), pixelCoverage(0xa3),
|
|
||||||
pixelCoverage(0xa4), pixelCoverage(0xa5), pixelCoverage(0xa6), pixelCoverage(0xa7),
|
|
||||||
pixelCoverage(0xa8), pixelCoverage(0xa9), pixelCoverage(0xaa), pixelCoverage(0xab),
|
|
||||||
pixelCoverage(0xac), pixelCoverage(0xad), pixelCoverage(0xae), pixelCoverage(0xaf),
|
|
||||||
pixelCoverage(0xb0), pixelCoverage(0xb1), pixelCoverage(0xb2), pixelCoverage(0xb3),
|
|
||||||
pixelCoverage(0xb4), pixelCoverage(0xb5), pixelCoverage(0xb6), pixelCoverage(0xb7),
|
|
||||||
pixelCoverage(0xb8), pixelCoverage(0xb9), pixelCoverage(0xba), pixelCoverage(0xbb),
|
|
||||||
pixelCoverage(0xbc), pixelCoverage(0xbd), pixelCoverage(0xbe), pixelCoverage(0xbf),
|
|
||||||
pixelCoverage(0xc0), pixelCoverage(0xc1), pixelCoverage(0xc2), pixelCoverage(0xc3),
|
|
||||||
pixelCoverage(0xc4), pixelCoverage(0xc5), pixelCoverage(0xc6), pixelCoverage(0xc7),
|
|
||||||
pixelCoverage(0xc8), pixelCoverage(0xc9), pixelCoverage(0xca), pixelCoverage(0xcb),
|
|
||||||
pixelCoverage(0xcc), pixelCoverage(0xcd), pixelCoverage(0xce), pixelCoverage(0xcf),
|
|
||||||
pixelCoverage(0xd0), pixelCoverage(0xd1), pixelCoverage(0xd2), pixelCoverage(0xd3),
|
|
||||||
pixelCoverage(0xd4), pixelCoverage(0xd5), pixelCoverage(0xd6), pixelCoverage(0xd7),
|
|
||||||
pixelCoverage(0xd8), pixelCoverage(0xd9), pixelCoverage(0xda), pixelCoverage(0xdb),
|
|
||||||
pixelCoverage(0xdc), pixelCoverage(0xdd), pixelCoverage(0xde), pixelCoverage(0xdf),
|
|
||||||
pixelCoverage(0xe0), pixelCoverage(0xe1), pixelCoverage(0xe2), pixelCoverage(0xe3),
|
|
||||||
pixelCoverage(0xe4), pixelCoverage(0xe5), pixelCoverage(0xe6), pixelCoverage(0xe7),
|
|
||||||
pixelCoverage(0xe8), pixelCoverage(0xe9), pixelCoverage(0xea), pixelCoverage(0xeb),
|
|
||||||
pixelCoverage(0xec), pixelCoverage(0xed), pixelCoverage(0xee), pixelCoverage(0xef),
|
|
||||||
pixelCoverage(0xf0), pixelCoverage(0xf1), pixelCoverage(0xf2), pixelCoverage(0xf3),
|
|
||||||
pixelCoverage(0xf4), pixelCoverage(0xf5), pixelCoverage(0xf6), pixelCoverage(0xf7),
|
|
||||||
pixelCoverage(0xf8), pixelCoverage(0xf9), pixelCoverage(0xfa), pixelCoverage(0xfb),
|
|
||||||
pixelCoverage(0xfc), pixelCoverage(0xfd), pixelCoverage(0xfe), pixelCoverage(0xff),
|
|
||||||
}
|
|
||||||
|
|
||||||
func pixelCoverage(a uint8) uint8 {
|
|
||||||
return a&1 + a>>1&1 + a>>2&1 + a>>3&1 + a>>4&1 + a>>5&1 + a>>6&1 + a>>7&1
|
|
||||||
}
|
|
|
@ -1,320 +0,0 @@
|
||||||
// Copyright 2011 The draw2d Authors. All rights reserved.
|
|
||||||
// created: 27/05/2011 by Laurent Le Goff
|
|
||||||
package raster
|
|
||||||
|
|
||||||
import (
|
|
||||||
"image"
|
|
||||||
"image/color"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
SUBPIXEL_SHIFT = 3
|
|
||||||
SUBPIXEL_COUNT = 1 << SUBPIXEL_SHIFT
|
|
||||||
)
|
|
||||||
|
|
||||||
var SUBPIXEL_OFFSETS = SUBPIXEL_OFFSETS_SAMPLE_8_FIXED
|
|
||||||
|
|
||||||
type SUBPIXEL_DATA uint8
|
|
||||||
type NON_ZERO_MASK_DATA_UNIT uint8
|
|
||||||
|
|
||||||
type Rasterizer8BitsSample struct {
|
|
||||||
MaskBuffer []SUBPIXEL_DATA
|
|
||||||
WindingBuffer []NON_ZERO_MASK_DATA_UNIT
|
|
||||||
|
|
||||||
Width int
|
|
||||||
BufferWidth int
|
|
||||||
Height int
|
|
||||||
ClipBound [4]float64
|
|
||||||
RemappingMatrix [6]float64
|
|
||||||
}
|
|
||||||
|
|
||||||
/* width and height define the maximum output size for the filler.
|
|
||||||
* The filler will output to larger bitmaps as well, but the output will
|
|
||||||
* be cropped.
|
|
||||||
*/
|
|
||||||
func NewRasterizer8BitsSample(width, height int) *Rasterizer8BitsSample {
|
|
||||||
var r Rasterizer8BitsSample
|
|
||||||
// Scale the coordinates by SUBPIXEL_COUNT in vertical direction
|
|
||||||
// The sampling point for the sub-pixel is at the top right corner. This
|
|
||||||
// adjustment moves it to the pixel center.
|
|
||||||
r.RemappingMatrix = [6]float64{1, 0, 0, SUBPIXEL_COUNT, 0.5 / SUBPIXEL_COUNT, -0.5 * SUBPIXEL_COUNT}
|
|
||||||
r.Width = width
|
|
||||||
r.Height = height
|
|
||||||
// The buffer used for filling needs to be one pixel wider than the bitmap.
|
|
||||||
// This is because the end flag that turns the fill of is the first pixel
|
|
||||||
// after the actually drawn edge.
|
|
||||||
r.BufferWidth = width + 1
|
|
||||||
|
|
||||||
r.MaskBuffer = make([]SUBPIXEL_DATA, r.BufferWidth*height)
|
|
||||||
r.WindingBuffer = make([]NON_ZERO_MASK_DATA_UNIT, r.BufferWidth*height*SUBPIXEL_COUNT)
|
|
||||||
r.ClipBound = clip(0, 0, width, height, SUBPIXEL_COUNT)
|
|
||||||
return &r
|
|
||||||
}
|
|
||||||
|
|
||||||
func clip(x, y, width, height, scale int) [4]float64 {
|
|
||||||
var clipBound [4]float64
|
|
||||||
|
|
||||||
offset := 0.99 / float64(scale)
|
|
||||||
|
|
||||||
clipBound[0] = float64(x) + offset
|
|
||||||
clipBound[2] = float64(x+width) - offset
|
|
||||||
|
|
||||||
clipBound[1] = float64(y * scale)
|
|
||||||
clipBound[3] = float64((y + height) * scale)
|
|
||||||
return clipBound
|
|
||||||
}
|
|
||||||
|
|
||||||
func intersect(r1, r2 [4]float64) [4]float64 {
|
|
||||||
if r1[0] < r2[0] {
|
|
||||||
r1[0] = r2[0]
|
|
||||||
}
|
|
||||||
if r1[2] > r2[2] {
|
|
||||||
r1[2] = r2[2]
|
|
||||||
}
|
|
||||||
if r1[0] > r1[2] {
|
|
||||||
r1[0] = r1[2]
|
|
||||||
}
|
|
||||||
|
|
||||||
if r1[1] < r2[1] {
|
|
||||||
r1[1] = r2[1]
|
|
||||||
}
|
|
||||||
if r1[3] > r2[3] {
|
|
||||||
r1[3] = r2[3]
|
|
||||||
}
|
|
||||||
if r1[1] > r1[3] {
|
|
||||||
r1[1] = r1[3]
|
|
||||||
}
|
|
||||||
return r1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Rasterizer8BitsSample) RenderEvenOdd(img *image.RGBA, color *color.RGBA, polygon *Polygon, tr [6]float64) {
|
|
||||||
// memset 0 the mask buffer
|
|
||||||
r.MaskBuffer = make([]SUBPIXEL_DATA, r.BufferWidth*r.Height)
|
|
||||||
|
|
||||||
// inline matrix multiplication
|
|
||||||
transform := [6]float64{
|
|
||||||
tr[0]*r.RemappingMatrix[0] + tr[1]*r.RemappingMatrix[2],
|
|
||||||
tr[1]*r.RemappingMatrix[3] + tr[0]*r.RemappingMatrix[1],
|
|
||||||
tr[2]*r.RemappingMatrix[0] + tr[3]*r.RemappingMatrix[2],
|
|
||||||
tr[3]*r.RemappingMatrix[3] + tr[2]*r.RemappingMatrix[1],
|
|
||||||
tr[4]*r.RemappingMatrix[0] + tr[5]*r.RemappingMatrix[2] + r.RemappingMatrix[4],
|
|
||||||
tr[5]*r.RemappingMatrix[3] + tr[4]*r.RemappingMatrix[1] + r.RemappingMatrix[5],
|
|
||||||
}
|
|
||||||
|
|
||||||
clipRect := clip(img.Bounds().Min.X, img.Bounds().Min.Y, img.Bounds().Dx(), img.Bounds().Dy(), SUBPIXEL_COUNT)
|
|
||||||
clipRect = intersect(clipRect, r.ClipBound)
|
|
||||||
p := 0
|
|
||||||
l := len(*polygon) / 2
|
|
||||||
var edges [32]PolygonEdge
|
|
||||||
for p < l {
|
|
||||||
edgeCount := polygon.getEdges(p, 16, edges[:], transform, clipRect)
|
|
||||||
for k := 0; k < edgeCount; k++ {
|
|
||||||
r.addEvenOddEdge(&edges[k])
|
|
||||||
}
|
|
||||||
p += 16
|
|
||||||
}
|
|
||||||
|
|
||||||
r.fillEvenOdd(img, color, clipRect)
|
|
||||||
}
|
|
||||||
|
|
||||||
//! Adds an edge to be used with even-odd fill.
|
|
||||||
func (r *Rasterizer8BitsSample) addEvenOddEdge(edge *PolygonEdge) {
|
|
||||||
x := Fix(edge.X * FIXED_FLOAT_COEF)
|
|
||||||
slope := Fix(edge.Slope * FIXED_FLOAT_COEF)
|
|
||||||
slopeFix := Fix(0)
|
|
||||||
if edge.LastLine-edge.FirstLine >= SLOPE_FIX_STEP {
|
|
||||||
slopeFix = Fix(edge.Slope*SLOPE_FIX_STEP*FIXED_FLOAT_COEF) - slope<<SLOPE_FIX_SHIFT
|
|
||||||
}
|
|
||||||
|
|
||||||
var mask SUBPIXEL_DATA
|
|
||||||
var ySub uint32
|
|
||||||
var xp, yLine int
|
|
||||||
for y := edge.FirstLine; y <= edge.LastLine; y++ {
|
|
||||||
ySub = uint32(y & (SUBPIXEL_COUNT - 1))
|
|
||||||
xp = int((x + SUBPIXEL_OFFSETS[ySub]) >> FIXED_SHIFT)
|
|
||||||
mask = SUBPIXEL_DATA(1 << ySub)
|
|
||||||
yLine = y >> SUBPIXEL_SHIFT
|
|
||||||
r.MaskBuffer[yLine*r.BufferWidth+xp] ^= mask
|
|
||||||
x += slope
|
|
||||||
if y&SLOPE_FIX_MASK == 0 {
|
|
||||||
x += slopeFix
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//! Adds an edge to be used with non-zero winding fill.
|
|
||||||
func (r *Rasterizer8BitsSample) addNonZeroEdge(edge *PolygonEdge) {
|
|
||||||
x := Fix(edge.X * FIXED_FLOAT_COEF)
|
|
||||||
slope := Fix(edge.Slope * FIXED_FLOAT_COEF)
|
|
||||||
slopeFix := Fix(0)
|
|
||||||
if edge.LastLine-edge.FirstLine >= SLOPE_FIX_STEP {
|
|
||||||
slopeFix = Fix(edge.Slope*SLOPE_FIX_STEP*FIXED_FLOAT_COEF) - slope<<SLOPE_FIX_SHIFT
|
|
||||||
}
|
|
||||||
var mask SUBPIXEL_DATA
|
|
||||||
var ySub uint32
|
|
||||||
var xp, yLine int
|
|
||||||
winding := NON_ZERO_MASK_DATA_UNIT(edge.Winding)
|
|
||||||
for y := edge.FirstLine; y <= edge.LastLine; y++ {
|
|
||||||
ySub = uint32(y & (SUBPIXEL_COUNT - 1))
|
|
||||||
xp = int((x + SUBPIXEL_OFFSETS[ySub]) >> FIXED_SHIFT)
|
|
||||||
mask = SUBPIXEL_DATA(1 << ySub)
|
|
||||||
yLine = y >> SUBPIXEL_SHIFT
|
|
||||||
r.MaskBuffer[yLine*r.BufferWidth+xp] |= mask
|
|
||||||
r.WindingBuffer[(yLine*r.BufferWidth+xp)*SUBPIXEL_COUNT+int(ySub)] += winding
|
|
||||||
x += slope
|
|
||||||
if y&SLOPE_FIX_MASK == 0 {
|
|
||||||
x += slopeFix
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Renders the mask to the canvas with even-odd fill.
|
|
||||||
func (r *Rasterizer8BitsSample) fillEvenOdd(img *image.RGBA, color *color.RGBA, clipBound [4]float64) {
|
|
||||||
var x, y uint32
|
|
||||||
|
|
||||||
minX := uint32(clipBound[0])
|
|
||||||
maxX := uint32(clipBound[2])
|
|
||||||
|
|
||||||
minY := uint32(clipBound[1]) >> SUBPIXEL_SHIFT
|
|
||||||
maxY := uint32(clipBound[3]) >> SUBPIXEL_SHIFT
|
|
||||||
|
|
||||||
//pixColor := (uint32(color.R) << 24) | (uint32(color.G) << 16) | (uint32(color.B) << 8) | uint32(color.A)
|
|
||||||
pixColor := (*uint32)(unsafe.Pointer(color))
|
|
||||||
cs1 := *pixColor & 0xff00ff
|
|
||||||
cs2 := *pixColor >> 8 & 0xff00ff
|
|
||||||
|
|
||||||
stride := uint32(img.Stride)
|
|
||||||
var mask SUBPIXEL_DATA
|
|
||||||
|
|
||||||
for y = minY; y < maxY; y++ {
|
|
||||||
tp := img.Pix[y*stride:]
|
|
||||||
|
|
||||||
mask = 0
|
|
||||||
for x = minX; x <= maxX; x++ {
|
|
||||||
p := (*uint32)(unsafe.Pointer(&tp[x]))
|
|
||||||
mask ^= r.MaskBuffer[y*uint32(r.BufferWidth)+x]
|
|
||||||
// 8bits
|
|
||||||
alpha := uint32(coverageTable[mask])
|
|
||||||
// 16bits
|
|
||||||
//alpha := uint32(coverageTable[mask & 0xff] + coverageTable[(mask >> 8) & 0xff])
|
|
||||||
// 32bits
|
|
||||||
//alpha := uint32(coverageTable[mask & 0xff] + coverageTable[(mask >> 8) & 0xff] + coverageTable[(mask >> 16) & 0xff] + coverageTable[(mask >> 24) & 0xff])
|
|
||||||
|
|
||||||
// alpha is in range of 0 to SUBPIXEL_COUNT
|
|
||||||
invAlpha := SUBPIXEL_COUNT - alpha
|
|
||||||
|
|
||||||
ct1 := *p & 0xff00ff * invAlpha
|
|
||||||
ct2 := *p >> 8 & 0xff00ff * invAlpha
|
|
||||||
|
|
||||||
ct1 = (ct1 + cs1*alpha) >> SUBPIXEL_SHIFT & 0xff00ff
|
|
||||||
ct2 = (ct2 + cs2*alpha) << (8 - SUBPIXEL_SHIFT) & 0xff00ff00
|
|
||||||
|
|
||||||
*p = ct1 + ct2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Renders the polygon with non-zero winding fill.
|
|
||||||
* param aTarget the target bitmap.
|
|
||||||
* param aPolygon the polygon to render.
|
|
||||||
* param aColor the color to be used for rendering.
|
|
||||||
* param aTransformation the transformation matrix.
|
|
||||||
*/
|
|
||||||
func (r *Rasterizer8BitsSample) RenderNonZeroWinding(img *image.RGBA, color *color.RGBA, polygon *Polygon, tr [6]float64) {
|
|
||||||
|
|
||||||
r.MaskBuffer = make([]SUBPIXEL_DATA, r.BufferWidth*r.Height)
|
|
||||||
r.WindingBuffer = make([]NON_ZERO_MASK_DATA_UNIT, r.BufferWidth*r.Height*SUBPIXEL_COUNT)
|
|
||||||
|
|
||||||
// inline matrix multiplication
|
|
||||||
transform := [6]float64{
|
|
||||||
tr[0]*r.RemappingMatrix[0] + tr[1]*r.RemappingMatrix[2],
|
|
||||||
tr[1]*r.RemappingMatrix[3] + tr[0]*r.RemappingMatrix[1],
|
|
||||||
tr[2]*r.RemappingMatrix[0] + tr[3]*r.RemappingMatrix[2],
|
|
||||||
tr[3]*r.RemappingMatrix[3] + tr[2]*r.RemappingMatrix[1],
|
|
||||||
tr[4]*r.RemappingMatrix[0] + tr[5]*r.RemappingMatrix[2] + r.RemappingMatrix[4],
|
|
||||||
tr[5]*r.RemappingMatrix[3] + tr[4]*r.RemappingMatrix[1] + r.RemappingMatrix[5],
|
|
||||||
}
|
|
||||||
|
|
||||||
clipRect := clip(img.Bounds().Min.X, img.Bounds().Min.Y, img.Bounds().Dx(), img.Bounds().Dy(), SUBPIXEL_COUNT)
|
|
||||||
clipRect = intersect(clipRect, r.ClipBound)
|
|
||||||
|
|
||||||
p := 0
|
|
||||||
l := len(*polygon) / 2
|
|
||||||
var edges [32]PolygonEdge
|
|
||||||
for p < l {
|
|
||||||
edgeCount := polygon.getEdges(p, 16, edges[:], transform, clipRect)
|
|
||||||
for k := 0; k < edgeCount; k++ {
|
|
||||||
r.addNonZeroEdge(&edges[k])
|
|
||||||
}
|
|
||||||
p += 16
|
|
||||||
}
|
|
||||||
|
|
||||||
r.fillNonZero(img, color, clipRect)
|
|
||||||
}
|
|
||||||
|
|
||||||
//! Renders the mask to the canvas with non-zero winding fill.
|
|
||||||
func (r *Rasterizer8BitsSample) fillNonZero(img *image.RGBA, color *color.RGBA, clipBound [4]float64) {
|
|
||||||
var x, y uint32
|
|
||||||
|
|
||||||
minX := uint32(clipBound[0])
|
|
||||||
maxX := uint32(clipBound[2])
|
|
||||||
|
|
||||||
minY := uint32(clipBound[1]) >> SUBPIXEL_SHIFT
|
|
||||||
maxY := uint32(clipBound[3]) >> SUBPIXEL_SHIFT
|
|
||||||
|
|
||||||
//pixColor := (uint32(color.R) << 24) | (uint32(color.G) << 16) | (uint32(color.B) << 8) | uint32(color.A)
|
|
||||||
pixColor := (*uint32)(unsafe.Pointer(color))
|
|
||||||
cs1 := *pixColor & 0xff00ff
|
|
||||||
cs2 := *pixColor >> 8 & 0xff00ff
|
|
||||||
|
|
||||||
stride := uint32(img.Stride)
|
|
||||||
var mask SUBPIXEL_DATA
|
|
||||||
var n uint32
|
|
||||||
var values [SUBPIXEL_COUNT]NON_ZERO_MASK_DATA_UNIT
|
|
||||||
for n = 0; n < SUBPIXEL_COUNT; n++ {
|
|
||||||
values[n] = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
for y = minY; y < maxY; y++ {
|
|
||||||
tp := img.Pix[y*stride:]
|
|
||||||
|
|
||||||
mask = 0
|
|
||||||
for x = minX; x <= maxX; x++ {
|
|
||||||
p := (*uint32)(unsafe.Pointer(&tp[x]))
|
|
||||||
temp := r.MaskBuffer[y*uint32(r.BufferWidth)+x]
|
|
||||||
if temp != 0 {
|
|
||||||
var bit SUBPIXEL_DATA = 1
|
|
||||||
for n = 0; n < SUBPIXEL_COUNT; n++ {
|
|
||||||
if temp&bit != 0 {
|
|
||||||
t := values[n]
|
|
||||||
values[n] += r.WindingBuffer[(y*uint32(r.BufferWidth)+x)*SUBPIXEL_COUNT+n]
|
|
||||||
if (t == 0 || values[n] == 0) && t != values[n] {
|
|
||||||
mask ^= bit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bit <<= 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 8bits
|
|
||||||
alpha := uint32(coverageTable[mask])
|
|
||||||
// 16bits
|
|
||||||
//alpha := uint32(coverageTable[mask & 0xff] + coverageTable[(mask >> 8) & 0xff])
|
|
||||||
// 32bits
|
|
||||||
//alpha := uint32(coverageTable[mask & 0xff] + coverageTable[(mask >> 8) & 0xff] + coverageTable[(mask >> 16) & 0xff] + coverageTable[(mask >> 24) & 0xff])
|
|
||||||
|
|
||||||
// alpha is in range of 0 to SUBPIXEL_COUNT
|
|
||||||
invAlpha := uint32(SUBPIXEL_COUNT) - alpha
|
|
||||||
|
|
||||||
ct1 := *p & 0xff00ff * invAlpha
|
|
||||||
ct2 := *p >> 8 & 0xff00ff * invAlpha
|
|
||||||
|
|
||||||
ct1 = (ct1 + cs1*alpha) >> SUBPIXEL_SHIFT & 0xff00ff
|
|
||||||
ct2 = (ct2 + cs2*alpha) << (8 - SUBPIXEL_SHIFT) & 0xff00ff00
|
|
||||||
|
|
||||||
*p = ct1 + ct2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,306 +0,0 @@
|
||||||
// Copyright 2011 The draw2d Authors. All rights reserved.
|
|
||||||
// created: 27/05/2011 by Laurent Le Goff
|
|
||||||
|
|
||||||
// +build ignore
|
|
||||||
|
|
||||||
package raster
|
|
||||||
|
|
||||||
import (
|
|
||||||
"image"
|
|
||||||
"image/color"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
SUBPIXEL_SHIFT = 3
|
|
||||||
SUBPIXEL_COUNT = 1 << SUBPIXEL_SHIFT
|
|
||||||
)
|
|
||||||
|
|
||||||
var SUBPIXEL_OFFSETS = SUBPIXEL_OFFSETS_SAMPLE_8
|
|
||||||
|
|
||||||
type SUBPIXEL_DATA uint16
|
|
||||||
type NON_ZERO_MASK_DATA_UNIT uint8
|
|
||||||
|
|
||||||
type Rasterizer8BitsSample struct {
|
|
||||||
MaskBuffer []SUBPIXEL_DATA
|
|
||||||
WindingBuffer []NON_ZERO_MASK_DATA_UNIT
|
|
||||||
|
|
||||||
Width int
|
|
||||||
BufferWidth int
|
|
||||||
Height int
|
|
||||||
ClipBound [4]float64
|
|
||||||
RemappingMatrix [6]float64
|
|
||||||
}
|
|
||||||
|
|
||||||
/* width and height define the maximum output size for the filler.
|
|
||||||
* The filler will output to larger bitmaps as well, but the output will
|
|
||||||
* be cropped.
|
|
||||||
*/
|
|
||||||
func NewRasterizer8BitsSample(width, height int) *Rasterizer8BitsSample {
|
|
||||||
var r Rasterizer8BitsSample
|
|
||||||
// Scale the coordinates by SUBPIXEL_COUNT in vertical direction
|
|
||||||
// The sampling point for the sub-pixel is at the top right corner. This
|
|
||||||
// adjustment moves it to the pixel center.
|
|
||||||
r.RemappingMatrix = [6]float64{1, 0, 0, SUBPIXEL_COUNT, 0.5 / SUBPIXEL_COUNT, -0.5 * SUBPIXEL_COUNT}
|
|
||||||
r.Width = width
|
|
||||||
r.Height = height
|
|
||||||
// The buffer used for filling needs to be one pixel wider than the bitmap.
|
|
||||||
// This is because the end flag that turns the fill of is the first pixel
|
|
||||||
// after the actually drawn edge.
|
|
||||||
r.BufferWidth = width + 1
|
|
||||||
|
|
||||||
r.MaskBuffer = make([]SUBPIXEL_DATA, r.BufferWidth*height)
|
|
||||||
r.WindingBuffer = make([]NON_ZERO_MASK_DATA_UNIT, r.BufferWidth*height*SUBPIXEL_COUNT)
|
|
||||||
r.ClipBound = clip(0, 0, width, height, SUBPIXEL_COUNT)
|
|
||||||
return &r
|
|
||||||
}
|
|
||||||
|
|
||||||
func clip(x, y, width, height, scale int) [4]float64 {
|
|
||||||
var clipBound [4]float64
|
|
||||||
|
|
||||||
offset := 0.99 / float64(scale)
|
|
||||||
|
|
||||||
clipBound[0] = float64(x) + offset
|
|
||||||
clipBound[2] = float64(x+width) - offset
|
|
||||||
|
|
||||||
clipBound[1] = float64(y * scale)
|
|
||||||
clipBound[3] = float64((y + height) * scale)
|
|
||||||
return clipBound
|
|
||||||
}
|
|
||||||
|
|
||||||
func intersect(r1, r2 [4]float64) [4]float64 {
|
|
||||||
if r1[0] < r2[0] {
|
|
||||||
r1[0] = r2[0]
|
|
||||||
}
|
|
||||||
if r1[2] > r2[2] {
|
|
||||||
r1[2] = r2[2]
|
|
||||||
}
|
|
||||||
if r1[0] > r1[2] {
|
|
||||||
r1[0] = r1[2]
|
|
||||||
}
|
|
||||||
|
|
||||||
if r1[1] < r2[1] {
|
|
||||||
r1[1] = r2[1]
|
|
||||||
}
|
|
||||||
if r1[3] > r2[3] {
|
|
||||||
r1[3] = r2[3]
|
|
||||||
}
|
|
||||||
if r1[1] > r1[3] {
|
|
||||||
r1[1] = r1[3]
|
|
||||||
}
|
|
||||||
return r1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Rasterizer8BitsSample) RenderEvenOdd(img *image.RGBA, color *color.RGBA, polygon *Polygon, tr [6]float64) {
|
|
||||||
// memset 0 the mask buffer
|
|
||||||
r.MaskBuffer = make([]SUBPIXEL_DATA, r.BufferWidth*r.Height)
|
|
||||||
|
|
||||||
// inline matrix multiplication
|
|
||||||
transform := [6]float64{
|
|
||||||
tr[0]*r.RemappingMatrix[0] + tr[1]*r.RemappingMatrix[2],
|
|
||||||
tr[1]*r.RemappingMatrix[3] + tr[0]*r.RemappingMatrix[1],
|
|
||||||
tr[2]*r.RemappingMatrix[0] + tr[3]*r.RemappingMatrix[2],
|
|
||||||
tr[3]*r.RemappingMatrix[3] + tr[2]*r.RemappingMatrix[1],
|
|
||||||
tr[4]*r.RemappingMatrix[0] + tr[5]*r.RemappingMatrix[2] + r.RemappingMatrix[4],
|
|
||||||
tr[5]*r.RemappingMatrix[3] + tr[4]*r.RemappingMatrix[1] + r.RemappingMatrix[5],
|
|
||||||
}
|
|
||||||
|
|
||||||
clipRect := clip(img.Bounds().Min.X, img.Bounds().Min.Y, img.Bounds().Dx(), img.Bounds().Dy(), SUBPIXEL_COUNT)
|
|
||||||
clipRect = intersect(clipRect, r.ClipBound)
|
|
||||||
p := 0
|
|
||||||
l := len(*polygon) / 2
|
|
||||||
var edges [32]PolygonEdge
|
|
||||||
for p < l {
|
|
||||||
edgeCount := polygon.getEdges(p, 16, edges[:], transform, clipRect)
|
|
||||||
for k := 0; k < edgeCount; k++ {
|
|
||||||
r.addEvenOddEdge(&edges[k])
|
|
||||||
}
|
|
||||||
p += 16
|
|
||||||
}
|
|
||||||
|
|
||||||
r.fillEvenOdd(img, color, clipRect)
|
|
||||||
}
|
|
||||||
|
|
||||||
//! Adds an edge to be used with even-odd fill.
|
|
||||||
func (r *Rasterizer8BitsSample) addEvenOddEdge(edge *PolygonEdge) {
|
|
||||||
x := edge.X
|
|
||||||
slope := edge.Slope
|
|
||||||
var ySub, mask SUBPIXEL_DATA
|
|
||||||
var xp, yLine int
|
|
||||||
for y := edge.FirstLine; y <= edge.LastLine; y++ {
|
|
||||||
ySub = SUBPIXEL_DATA(y & (SUBPIXEL_COUNT - 1))
|
|
||||||
xp = int(x + SUBPIXEL_OFFSETS[ySub])
|
|
||||||
mask = SUBPIXEL_DATA(1 << ySub)
|
|
||||||
yLine = y >> SUBPIXEL_SHIFT
|
|
||||||
r.MaskBuffer[yLine*r.BufferWidth+xp] ^= mask
|
|
||||||
x += slope
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Renders the mask to the canvas with even-odd fill.
|
|
||||||
func (r *Rasterizer8BitsSample) fillEvenOdd(img *image.RGBA, color *color.RGBA, clipBound [4]float64) {
|
|
||||||
var x, y uint32
|
|
||||||
|
|
||||||
minX := uint32(clipBound[0])
|
|
||||||
maxX := uint32(clipBound[2])
|
|
||||||
|
|
||||||
minY := uint32(clipBound[1]) >> SUBPIXEL_SHIFT
|
|
||||||
maxY := uint32(clipBound[3]) >> SUBPIXEL_SHIFT
|
|
||||||
|
|
||||||
//pixColor := (uint32(color.R) << 24) | (uint32(color.G) << 16) | (uint32(color.B) << 8) | uint32(color.A)
|
|
||||||
pixColor := (*uint32)(unsafe.Pointer(color))
|
|
||||||
cs1 := *pixColor & 0xff00ff
|
|
||||||
cs2 := *pixColor >> 8 & 0xff00ff
|
|
||||||
|
|
||||||
stride := uint32(img.Stride)
|
|
||||||
var mask SUBPIXEL_DATA
|
|
||||||
|
|
||||||
for y = minY; y < maxY; y++ {
|
|
||||||
tp := img.Pix[y*stride:]
|
|
||||||
|
|
||||||
mask = 0
|
|
||||||
for x = minX; x <= maxX; x++ {
|
|
||||||
p := (*uint32)(unsafe.Pointer(&tp[x]))
|
|
||||||
mask ^= r.MaskBuffer[y*uint32(r.BufferWidth)+x]
|
|
||||||
// 8bits
|
|
||||||
alpha := uint32(coverageTable[mask])
|
|
||||||
// 16bits
|
|
||||||
//alpha := uint32(coverageTable[mask & 0xff] + coverageTable[(mask >> 8) & 0xff])
|
|
||||||
// 32bits
|
|
||||||
//alpha := uint32(coverageTable[mask & 0xff] + coverageTable[(mask >> 8) & 0xff] + coverageTable[(mask >> 16) & 0xff] + coverageTable[(mask >> 24) & 0xff])
|
|
||||||
|
|
||||||
// alpha is in range of 0 to SUBPIXEL_COUNT
|
|
||||||
invAlpha := uint32(SUBPIXEL_COUNT) - alpha
|
|
||||||
|
|
||||||
ct1 := *p & 0xff00ff * invAlpha
|
|
||||||
ct2 := *p >> 8 & 0xff00ff * invAlpha
|
|
||||||
|
|
||||||
ct1 = (ct1 + cs1*alpha) >> SUBPIXEL_SHIFT & 0xff00ff
|
|
||||||
ct2 = (ct2 + cs2*alpha) << (8 - SUBPIXEL_SHIFT) & 0xff00ff00
|
|
||||||
|
|
||||||
*p = ct1 + ct2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Renders the polygon with non-zero winding fill.
|
|
||||||
* param aTarget the target bitmap.
|
|
||||||
* param aPolygon the polygon to render.
|
|
||||||
* param aColor the color to be used for rendering.
|
|
||||||
* param aTransformation the transformation matrix.
|
|
||||||
*/
|
|
||||||
func (r *Rasterizer8BitsSample) RenderNonZeroWinding(img *image.RGBA, color *color.RGBA, polygon *Polygon, tr [6]float64) {
|
|
||||||
|
|
||||||
r.MaskBuffer = make([]SUBPIXEL_DATA, r.BufferWidth*r.Height)
|
|
||||||
r.WindingBuffer = make([]NON_ZERO_MASK_DATA_UNIT, r.BufferWidth*r.Height*SUBPIXEL_COUNT)
|
|
||||||
|
|
||||||
// inline matrix multiplication
|
|
||||||
transform := [6]float64{
|
|
||||||
tr[0]*r.RemappingMatrix[0] + tr[1]*r.RemappingMatrix[2],
|
|
||||||
tr[1]*r.RemappingMatrix[3] + tr[0]*r.RemappingMatrix[1],
|
|
||||||
tr[2]*r.RemappingMatrix[0] + tr[3]*r.RemappingMatrix[2],
|
|
||||||
tr[3]*r.RemappingMatrix[3] + tr[2]*r.RemappingMatrix[1],
|
|
||||||
tr[4]*r.RemappingMatrix[0] + tr[5]*r.RemappingMatrix[2] + r.RemappingMatrix[4],
|
|
||||||
tr[5]*r.RemappingMatrix[3] + tr[4]*r.RemappingMatrix[1] + r.RemappingMatrix[5],
|
|
||||||
}
|
|
||||||
|
|
||||||
clipRect := clip(img.Bounds().Min.X, img.Bounds().Min.Y, img.Bounds().Dx(), img.Bounds().Dy(), SUBPIXEL_COUNT)
|
|
||||||
clipRect = intersect(clipRect, r.ClipBound)
|
|
||||||
|
|
||||||
p := 0
|
|
||||||
l := len(*polygon) / 2
|
|
||||||
var edges [32]PolygonEdge
|
|
||||||
for p < l {
|
|
||||||
edgeCount := polygon.getEdges(p, 16, edges[:], transform, clipRect)
|
|
||||||
for k := 0; k < edgeCount; k++ {
|
|
||||||
r.addNonZeroEdge(&edges[k])
|
|
||||||
}
|
|
||||||
p += 16
|
|
||||||
}
|
|
||||||
|
|
||||||
r.fillNonZero(img, color, clipRect)
|
|
||||||
}
|
|
||||||
|
|
||||||
//! Adds an edge to be used with non-zero winding fill.
|
|
||||||
func (r *Rasterizer8BitsSample) addNonZeroEdge(edge *PolygonEdge) {
|
|
||||||
x := edge.X
|
|
||||||
slope := edge.Slope
|
|
||||||
var ySub, mask SUBPIXEL_DATA
|
|
||||||
var xp, yLine int
|
|
||||||
winding := NON_ZERO_MASK_DATA_UNIT(edge.Winding)
|
|
||||||
for y := edge.FirstLine; y <= edge.LastLine; y++ {
|
|
||||||
ySub = SUBPIXEL_DATA(y & (SUBPIXEL_COUNT - 1))
|
|
||||||
xp = int(x + SUBPIXEL_OFFSETS[ySub])
|
|
||||||
mask = SUBPIXEL_DATA(1 << ySub)
|
|
||||||
yLine = y >> SUBPIXEL_SHIFT
|
|
||||||
r.MaskBuffer[yLine*r.BufferWidth+xp] |= mask
|
|
||||||
r.WindingBuffer[(yLine*r.BufferWidth+xp)*SUBPIXEL_COUNT+int(ySub)] += winding
|
|
||||||
x += slope
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//! Renders the mask to the canvas with non-zero winding fill.
|
|
||||||
func (r *Rasterizer8BitsSample) fillNonZero(img *image.RGBA, color *color.RGBA, clipBound [4]float64) {
|
|
||||||
var x, y uint32
|
|
||||||
|
|
||||||
minX := uint32(clipBound[0])
|
|
||||||
maxX := uint32(clipBound[2])
|
|
||||||
|
|
||||||
minY := uint32(clipBound[1]) >> SUBPIXEL_SHIFT
|
|
||||||
maxY := uint32(clipBound[3]) >> SUBPIXEL_SHIFT
|
|
||||||
|
|
||||||
//pixColor := (uint32(color.R) << 24) | (uint32(color.G) << 16) | (uint32(color.B) << 8) | uint32(color.A)
|
|
||||||
pixColor := (*uint32)(unsafe.Pointer(color))
|
|
||||||
cs1 := *pixColor & 0xff00ff
|
|
||||||
cs2 := *pixColor >> 8 & 0xff00ff
|
|
||||||
|
|
||||||
stride := uint32(img.Stride)
|
|
||||||
var mask SUBPIXEL_DATA
|
|
||||||
var n uint32
|
|
||||||
var values [SUBPIXEL_COUNT]NON_ZERO_MASK_DATA_UNIT
|
|
||||||
for n = 0; n < SUBPIXEL_COUNT; n++ {
|
|
||||||
values[n] = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
for y = minY; y < maxY; y++ {
|
|
||||||
tp := img.Pix[y*stride:]
|
|
||||||
|
|
||||||
mask = 0
|
|
||||||
for x = minX; x <= maxX; x++ {
|
|
||||||
p := (*uint32)(unsafe.Pointer(&tp[x]))
|
|
||||||
temp := r.MaskBuffer[y*uint32(r.BufferWidth)+x]
|
|
||||||
if temp != 0 {
|
|
||||||
var bit SUBPIXEL_DATA = 1
|
|
||||||
for n = 0; n < SUBPIXEL_COUNT; n++ {
|
|
||||||
if temp&bit != 0 {
|
|
||||||
t := values[n]
|
|
||||||
values[n] += r.WindingBuffer[(y*uint32(r.BufferWidth)+x)*SUBPIXEL_COUNT+n]
|
|
||||||
if (t == 0 || values[n] == 0) && t != values[n] {
|
|
||||||
mask ^= bit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bit <<= 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 8bits
|
|
||||||
alpha := uint32(coverageTable[mask])
|
|
||||||
// 16bits
|
|
||||||
//alpha := uint32(coverageTable[mask & 0xff] + coverageTable[(mask >> 8) & 0xff])
|
|
||||||
// 32bits
|
|
||||||
//alpha := uint32(coverageTable[mask & 0xff] + coverageTable[(mask >> 8) & 0xff] + coverageTable[(mask >> 16) & 0xff] + coverageTable[(mask >> 24) & 0xff])
|
|
||||||
|
|
||||||
// alpha is in range of 0 to SUBPIXEL_COUNT
|
|
||||||
invAlpha := uint32(SUBPIXEL_COUNT) - alpha
|
|
||||||
|
|
||||||
ct1 := *p & 0xff00ff * invAlpha
|
|
||||||
ct2 := *p >> 8 & 0xff00ff * invAlpha
|
|
||||||
|
|
||||||
ct1 = (ct1 + cs1*alpha) >> SUBPIXEL_SHIFT & 0xff00ff
|
|
||||||
ct2 = (ct2 + cs2*alpha) << (8 - SUBPIXEL_SHIFT) & 0xff00ff00
|
|
||||||
|
|
||||||
*p = ct1 + ct2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,323 +0,0 @@
|
||||||
// Copyright 2011 The draw2d Authors. All rights reserved.
|
|
||||||
// created: 27/05/2011 by Laurent Le Goff
|
|
||||||
|
|
||||||
// +build ignore
|
|
||||||
|
|
||||||
package raster
|
|
||||||
|
|
||||||
import (
|
|
||||||
"image"
|
|
||||||
"image/color"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
SUBPIXEL_SHIFT = 5
|
|
||||||
SUBPIXEL_COUNT = 1 << SUBPIXEL_SHIFT
|
|
||||||
)
|
|
||||||
|
|
||||||
var SUBPIXEL_OFFSETS = SUBPIXEL_OFFSETS_SAMPLE_32_FIXED
|
|
||||||
|
|
||||||
type SUBPIXEL_DATA uint32
|
|
||||||
type NON_ZERO_MASK_DATA_UNIT uint8
|
|
||||||
|
|
||||||
type Rasterizer8BitsSample struct {
|
|
||||||
MaskBuffer []SUBPIXEL_DATA
|
|
||||||
WindingBuffer []NON_ZERO_MASK_DATA_UNIT
|
|
||||||
|
|
||||||
Width int
|
|
||||||
BufferWidth int
|
|
||||||
Height int
|
|
||||||
ClipBound [4]float64
|
|
||||||
RemappingMatrix [6]float64
|
|
||||||
}
|
|
||||||
|
|
||||||
/* width and height define the maximum output size for the filler.
|
|
||||||
* The filler will output to larger bitmaps as well, but the output will
|
|
||||||
* be cropped.
|
|
||||||
*/
|
|
||||||
func NewRasterizer8BitsSample(width, height int) *Rasterizer8BitsSample {
|
|
||||||
var r Rasterizer8BitsSample
|
|
||||||
// Scale the coordinates by SUBPIXEL_COUNT in vertical direction
|
|
||||||
// The sampling point for the sub-pixel is at the top right corner. This
|
|
||||||
// adjustment moves it to the pixel center.
|
|
||||||
r.RemappingMatrix = [6]float64{1, 0, 0, SUBPIXEL_COUNT, 0.5 / SUBPIXEL_COUNT, -0.5 * SUBPIXEL_COUNT}
|
|
||||||
r.Width = width
|
|
||||||
r.Height = height
|
|
||||||
// The buffer used for filling needs to be one pixel wider than the bitmap.
|
|
||||||
// This is because the end flag that turns the fill of is the first pixel
|
|
||||||
// after the actually drawn edge.
|
|
||||||
r.BufferWidth = width + 1
|
|
||||||
|
|
||||||
r.MaskBuffer = make([]SUBPIXEL_DATA, r.BufferWidth*height)
|
|
||||||
r.WindingBuffer = make([]NON_ZERO_MASK_DATA_UNIT, r.BufferWidth*height*SUBPIXEL_COUNT)
|
|
||||||
r.ClipBound = clip(0, 0, width, height, SUBPIXEL_COUNT)
|
|
||||||
return &r
|
|
||||||
}
|
|
||||||
|
|
||||||
func clip(x, y, width, height, scale int) [4]float64 {
|
|
||||||
var clipBound [4]float64
|
|
||||||
|
|
||||||
offset := 0.99 / float64(scale)
|
|
||||||
|
|
||||||
clipBound[0] = float64(x) + offset
|
|
||||||
clipBound[2] = float64(x+width) - offset
|
|
||||||
|
|
||||||
clipBound[1] = float64(y * scale)
|
|
||||||
clipBound[3] = float64((y + height) * scale)
|
|
||||||
return clipBound
|
|
||||||
}
|
|
||||||
|
|
||||||
func intersect(r1, r2 [4]float64) [4]float64 {
|
|
||||||
if r1[0] < r2[0] {
|
|
||||||
r1[0] = r2[0]
|
|
||||||
}
|
|
||||||
if r1[2] > r2[2] {
|
|
||||||
r1[2] = r2[2]
|
|
||||||
}
|
|
||||||
if r1[0] > r1[2] {
|
|
||||||
r1[0] = r1[2]
|
|
||||||
}
|
|
||||||
|
|
||||||
if r1[1] < r2[1] {
|
|
||||||
r1[1] = r2[1]
|
|
||||||
}
|
|
||||||
if r1[3] > r2[3] {
|
|
||||||
r1[3] = r2[3]
|
|
||||||
}
|
|
||||||
if r1[1] > r1[3] {
|
|
||||||
r1[1] = r1[3]
|
|
||||||
}
|
|
||||||
return r1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Rasterizer8BitsSample) RenderEvenOdd(img *image.RGBA, color *color.RGBA, polygon *Polygon, tr [6]float64) {
|
|
||||||
// memset 0 the mask buffer
|
|
||||||
r.MaskBuffer = make([]SUBPIXEL_DATA, r.BufferWidth*r.Height)
|
|
||||||
|
|
||||||
// inline matrix multiplication
|
|
||||||
transform := [6]float64{
|
|
||||||
tr[0]*r.RemappingMatrix[0] + tr[1]*r.RemappingMatrix[2],
|
|
||||||
tr[1]*r.RemappingMatrix[3] + tr[0]*r.RemappingMatrix[1],
|
|
||||||
tr[2]*r.RemappingMatrix[0] + tr[3]*r.RemappingMatrix[2],
|
|
||||||
tr[3]*r.RemappingMatrix[3] + tr[2]*r.RemappingMatrix[1],
|
|
||||||
tr[4]*r.RemappingMatrix[0] + tr[5]*r.RemappingMatrix[2] + r.RemappingMatrix[4],
|
|
||||||
tr[5]*r.RemappingMatrix[3] + tr[4]*r.RemappingMatrix[1] + r.RemappingMatrix[5],
|
|
||||||
}
|
|
||||||
|
|
||||||
clipRect := clip(img.Bounds().Min.X, img.Bounds().Min.Y, img.Bounds().Dx(), img.Bounds().Dy(), SUBPIXEL_COUNT)
|
|
||||||
clipRect = intersect(clipRect, r.ClipBound)
|
|
||||||
p := 0
|
|
||||||
l := len(*polygon) / 2
|
|
||||||
var edges [32]PolygonEdge
|
|
||||||
for p < l {
|
|
||||||
edgeCount := polygon.getEdges(p, 16, edges[:], transform, clipRect)
|
|
||||||
for k := 0; k < edgeCount; k++ {
|
|
||||||
r.addEvenOddEdge(&edges[k])
|
|
||||||
}
|
|
||||||
p += 16
|
|
||||||
}
|
|
||||||
|
|
||||||
r.fillEvenOdd(img, color, clipRect)
|
|
||||||
}
|
|
||||||
|
|
||||||
//! Adds an edge to be used with even-odd fill.
|
|
||||||
func (r *Rasterizer8BitsSample) addEvenOddEdge(edge *PolygonEdge) {
|
|
||||||
x := Fix(edge.X * FIXED_FLOAT_COEF)
|
|
||||||
slope := Fix(edge.Slope * FIXED_FLOAT_COEF)
|
|
||||||
slopeFix := Fix(0)
|
|
||||||
if edge.LastLine-edge.FirstLine >= SLOPE_FIX_STEP {
|
|
||||||
slopeFix = Fix(edge.Slope*SLOPE_FIX_STEP*FIXED_FLOAT_COEF) - slope<<SLOPE_FIX_SHIFT
|
|
||||||
}
|
|
||||||
|
|
||||||
var mask SUBPIXEL_DATA
|
|
||||||
var ySub uint32
|
|
||||||
var xp, yLine int
|
|
||||||
for y := edge.FirstLine; y <= edge.LastLine; y++ {
|
|
||||||
ySub = uint32(y & (SUBPIXEL_COUNT - 1))
|
|
||||||
xp = int((x + SUBPIXEL_OFFSETS[ySub]) >> FIXED_SHIFT)
|
|
||||||
mask = SUBPIXEL_DATA(1 << ySub)
|
|
||||||
yLine = y >> SUBPIXEL_SHIFT
|
|
||||||
r.MaskBuffer[yLine*r.BufferWidth+xp] ^= mask
|
|
||||||
x += slope
|
|
||||||
if y&SLOPE_FIX_MASK == 0 {
|
|
||||||
x += slopeFix
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//! Adds an edge to be used with non-zero winding fill.
|
|
||||||
func (r *Rasterizer8BitsSample) addNonZeroEdge(edge *PolygonEdge) {
|
|
||||||
x := Fix(edge.X * FIXED_FLOAT_COEF)
|
|
||||||
slope := Fix(edge.Slope * FIXED_FLOAT_COEF)
|
|
||||||
slopeFix := Fix(0)
|
|
||||||
if edge.LastLine-edge.FirstLine >= SLOPE_FIX_STEP {
|
|
||||||
slopeFix = Fix(edge.Slope*SLOPE_FIX_STEP*FIXED_FLOAT_COEF) - slope<<SLOPE_FIX_SHIFT
|
|
||||||
}
|
|
||||||
var mask SUBPIXEL_DATA
|
|
||||||
var ySub uint32
|
|
||||||
var xp, yLine int
|
|
||||||
winding := NON_ZERO_MASK_DATA_UNIT(edge.Winding)
|
|
||||||
for y := edge.FirstLine; y <= edge.LastLine; y++ {
|
|
||||||
ySub = uint32(y & (SUBPIXEL_COUNT - 1))
|
|
||||||
xp = int((x + SUBPIXEL_OFFSETS[ySub]) >> FIXED_SHIFT)
|
|
||||||
mask = SUBPIXEL_DATA(1 << ySub)
|
|
||||||
yLine = y >> SUBPIXEL_SHIFT
|
|
||||||
r.MaskBuffer[yLine*r.BufferWidth+xp] |= mask
|
|
||||||
r.WindingBuffer[(yLine*r.BufferWidth+xp)*SUBPIXEL_COUNT+int(ySub)] += winding
|
|
||||||
x += slope
|
|
||||||
if y&SLOPE_FIX_MASK == 0 {
|
|
||||||
x += slopeFix
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Renders the mask to the canvas with even-odd fill.
|
|
||||||
func (r *Rasterizer8BitsSample) fillEvenOdd(img *image.RGBA, color *color.RGBA, clipBound [4]float64) {
|
|
||||||
var x, y uint32
|
|
||||||
|
|
||||||
minX := uint32(clipBound[0])
|
|
||||||
maxX := uint32(clipBound[2])
|
|
||||||
|
|
||||||
minY := uint32(clipBound[1]) >> SUBPIXEL_SHIFT
|
|
||||||
maxY := uint32(clipBound[3]) >> SUBPIXEL_SHIFT
|
|
||||||
|
|
||||||
//pixColor := (uint32(color.R) << 24) | (uint32(color.G) << 16) | (uint32(color.B) << 8) | uint32(color.A)
|
|
||||||
pixColor := (*uint32)(unsafe.Pointer(color))
|
|
||||||
cs1 := *pixColor & 0xff00ff
|
|
||||||
cs2 := *pixColor >> 8 & 0xff00ff
|
|
||||||
|
|
||||||
stride := uint32(img.Stride)
|
|
||||||
var mask SUBPIXEL_DATA
|
|
||||||
|
|
||||||
for y = minY; y < maxY; y++ {
|
|
||||||
tp := img.Pix[y*stride:]
|
|
||||||
|
|
||||||
mask = 0
|
|
||||||
for x = minX; x <= maxX; x++ {
|
|
||||||
p := (*uint32)(unsafe.Pointer(&tp[x]))
|
|
||||||
mask ^= r.MaskBuffer[y*uint32(r.BufferWidth)+x]
|
|
||||||
// 8bits
|
|
||||||
//alpha := uint32(coverageTable[mask])
|
|
||||||
// 16bits
|
|
||||||
//alpha := uint32(coverageTable[mask & 0xff] + coverageTable[(mask >> 8) & 0xff])
|
|
||||||
// 32bits
|
|
||||||
alpha := uint32(coverageTable[mask&0xff] + coverageTable[mask>>8&0xff] + coverageTable[mask>>16&0xff] + coverageTable[mask>>24&0xff])
|
|
||||||
|
|
||||||
// alpha is in range of 0 to SUBPIXEL_COUNT
|
|
||||||
invAlpha := uint32(SUBPIXEL_COUNT) - alpha
|
|
||||||
|
|
||||||
ct1 := *p & 0xff00ff * invAlpha
|
|
||||||
ct2 := *p >> 8 & 0xff00ff * invAlpha
|
|
||||||
|
|
||||||
ct1 = (ct1 + cs1*alpha) >> SUBPIXEL_SHIFT & 0xff00ff
|
|
||||||
ct2 = (ct2 + cs2*alpha) << (8 - SUBPIXEL_SHIFT) & 0xff00ff00
|
|
||||||
|
|
||||||
*p = ct1 + ct2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Renders the polygon with non-zero winding fill.
|
|
||||||
* param aTarget the target bitmap.
|
|
||||||
* param aPolygon the polygon to render.
|
|
||||||
* param aColor the color to be used for rendering.
|
|
||||||
* param aTransformation the transformation matrix.
|
|
||||||
*/
|
|
||||||
func (r *Rasterizer8BitsSample) RenderNonZeroWinding(img *image.RGBA, color *color.RGBA, polygon *Polygon, tr [6]float64) {
|
|
||||||
|
|
||||||
r.MaskBuffer = make([]SUBPIXEL_DATA, r.BufferWidth*r.Height)
|
|
||||||
r.WindingBuffer = make([]NON_ZERO_MASK_DATA_UNIT, r.BufferWidth*r.Height*SUBPIXEL_COUNT)
|
|
||||||
|
|
||||||
// inline matrix multiplication
|
|
||||||
transform := [6]float64{
|
|
||||||
tr[0]*r.RemappingMatrix[0] + tr[1]*r.RemappingMatrix[2],
|
|
||||||
tr[1]*r.RemappingMatrix[3] + tr[0]*r.RemappingMatrix[1],
|
|
||||||
tr[2]*r.RemappingMatrix[0] + tr[3]*r.RemappingMatrix[2],
|
|
||||||
tr[3]*r.RemappingMatrix[3] + tr[2]*r.RemappingMatrix[1],
|
|
||||||
tr[4]*r.RemappingMatrix[0] + tr[5]*r.RemappingMatrix[2] + r.RemappingMatrix[4],
|
|
||||||
tr[5]*r.RemappingMatrix[3] + tr[4]*r.RemappingMatrix[1] + r.RemappingMatrix[5],
|
|
||||||
}
|
|
||||||
|
|
||||||
clipRect := clip(img.Bounds().Min.X, img.Bounds().Min.Y, img.Bounds().Dx(), img.Bounds().Dy(), SUBPIXEL_COUNT)
|
|
||||||
clipRect = intersect(clipRect, r.ClipBound)
|
|
||||||
|
|
||||||
p := 0
|
|
||||||
l := len(*polygon) / 2
|
|
||||||
var edges [32]PolygonEdge
|
|
||||||
for p < l {
|
|
||||||
edgeCount := polygon.getEdges(p, 16, edges[:], transform, clipRect)
|
|
||||||
for k := 0; k < edgeCount; k++ {
|
|
||||||
r.addNonZeroEdge(&edges[k])
|
|
||||||
}
|
|
||||||
p += 16
|
|
||||||
}
|
|
||||||
|
|
||||||
r.fillNonZero(img, color, clipRect)
|
|
||||||
}
|
|
||||||
|
|
||||||
//! Renders the mask to the canvas with non-zero winding fill.
|
|
||||||
func (r *Rasterizer8BitsSample) fillNonZero(img *image.RGBA, color *color.RGBA, clipBound [4]float64) {
|
|
||||||
var x, y uint32
|
|
||||||
|
|
||||||
minX := uint32(clipBound[0])
|
|
||||||
maxX := uint32(clipBound[2])
|
|
||||||
|
|
||||||
minY := uint32(clipBound[1]) >> SUBPIXEL_SHIFT
|
|
||||||
maxY := uint32(clipBound[3]) >> SUBPIXEL_SHIFT
|
|
||||||
|
|
||||||
//pixColor := (uint32(color.R) << 24) | (uint32(color.G) << 16) | (uint32(color.B) << 8) | uint32(color.A)
|
|
||||||
pixColor := (*uint32)(unsafe.Pointer(color))
|
|
||||||
cs1 := *pixColor & 0xff00ff
|
|
||||||
cs2 := *pixColor >> 8 & 0xff00ff
|
|
||||||
|
|
||||||
stride := uint32(img.Stride)
|
|
||||||
var mask SUBPIXEL_DATA
|
|
||||||
var n uint32
|
|
||||||
var values [SUBPIXEL_COUNT]NON_ZERO_MASK_DATA_UNIT
|
|
||||||
for n = 0; n < SUBPIXEL_COUNT; n++ {
|
|
||||||
values[n] = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
for y = minY; y < maxY; y++ {
|
|
||||||
tp := img.Pix[y*stride:]
|
|
||||||
|
|
||||||
mask = 0
|
|
||||||
for x = minX; x <= maxX; x++ {
|
|
||||||
p := (*uint32)(unsafe.Pointer(&tp[x]))
|
|
||||||
temp := r.MaskBuffer[y*uint32(r.BufferWidth)+x]
|
|
||||||
if temp != 0 {
|
|
||||||
var bit SUBPIXEL_DATA = 1
|
|
||||||
for n = 0; n < SUBPIXEL_COUNT; n++ {
|
|
||||||
if temp&bit != 0 {
|
|
||||||
t := values[n]
|
|
||||||
values[n] += r.WindingBuffer[(y*uint32(r.BufferWidth)+x)*SUBPIXEL_COUNT+n]
|
|
||||||
if (t == 0 || values[n] == 0) && t != values[n] {
|
|
||||||
mask ^= bit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bit <<= 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 8bits
|
|
||||||
//alpha := uint32(coverageTable[mask])
|
|
||||||
// 16bits
|
|
||||||
//alpha := uint32(coverageTable[mask & 0xff] + coverageTable[(mask >> 8) & 0xff])
|
|
||||||
// 32bits
|
|
||||||
alpha := uint32(coverageTable[mask&0xff] + coverageTable[mask>>8&0xff] + coverageTable[mask>>16&0xff] + coverageTable[mask>>24&0xff])
|
|
||||||
|
|
||||||
// alpha is in range of 0 to SUBPIXEL_COUNT
|
|
||||||
invAlpha := uint32(SUBPIXEL_COUNT) - alpha
|
|
||||||
|
|
||||||
ct1 := *p & 0xff00ff * invAlpha
|
|
||||||
ct2 := *p >> 8 & 0xff00ff * invAlpha
|
|
||||||
|
|
||||||
ct1 = (ct1 + cs1*alpha) >> SUBPIXEL_SHIFT & 0xff00ff
|
|
||||||
ct2 = (ct2 + cs2*alpha) << (8 - SUBPIXEL_SHIFT) & 0xff00ff00
|
|
||||||
|
|
||||||
*p = ct1 + ct2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
package raster
|
|
||||||
|
|
||||||
type Fix int32
|
|
||||||
|
|
||||||
const (
|
|
||||||
FIXED_SHIFT = 16
|
|
||||||
FIXED_FLOAT_COEF = 1 << FIXED_SHIFT
|
|
||||||
)
|
|
||||||
|
|
||||||
/*! Fixed point math inevitably introduces rounding error to the DDA. The error is
|
|
||||||
* fixed every now and then by a separate fix value. The defines below set these.
|
|
||||||
*/
|
|
||||||
const (
|
|
||||||
SLOPE_FIX_SHIFT = 8
|
|
||||||
SLOPE_FIX_STEP = 1 << SLOPE_FIX_SHIFT
|
|
||||||
SLOPE_FIX_MASK = SLOPE_FIX_STEP - 1
|
|
||||||
)
|
|
|
@ -1,581 +0,0 @@
|
||||||
// Copyright 2011 The draw2d Authors. All rights reserved.
|
|
||||||
// created: 27/05/2011 by Laurent Le Goff
|
|
||||||
package raster
|
|
||||||
|
|
||||||
const (
|
|
||||||
POLYGON_CLIP_NONE = iota
|
|
||||||
POLYGON_CLIP_LEFT
|
|
||||||
POLYGON_CLIP_RIGHT
|
|
||||||
POLYGON_CLIP_TOP
|
|
||||||
POLYGON_CLIP_BOTTOM
|
|
||||||
)
|
|
||||||
|
|
||||||
type Polygon []float64
|
|
||||||
|
|
||||||
type PolygonEdge struct {
|
|
||||||
X, Slope float64
|
|
||||||
FirstLine, LastLine int
|
|
||||||
Winding int16
|
|
||||||
}
|
|
||||||
|
|
||||||
//! A more optimized representation of a polygon edge.
|
|
||||||
type PolygonScanEdge struct {
|
|
||||||
FirstLine, LastLine int
|
|
||||||
Winding int16
|
|
||||||
X Fix
|
|
||||||
Slope Fix
|
|
||||||
SlopeFix Fix
|
|
||||||
NextEdge *PolygonScanEdge
|
|
||||||
}
|
|
||||||
|
|
||||||
//! Calculates the edges of the polygon with transformation and clipping to edges array.
|
|
||||||
/*! \param startIndex the index for the first vertex.
|
|
||||||
* \param vertexCount the amount of vertices to convert.
|
|
||||||
* \param edges the array for result edges. This should be able to contain 2*aVertexCount edges.
|
|
||||||
* \param tr the transformation matrix for the polygon.
|
|
||||||
* \param aClipRectangle the clip rectangle.
|
|
||||||
* \return the amount of edges in the result.
|
|
||||||
*/
|
|
||||||
func (p Polygon) getEdges(startIndex, vertexCount int, edges []PolygonEdge, tr [6]float64, clipBound [4]float64) int {
|
|
||||||
startIndex = startIndex * 2
|
|
||||||
endIndex := startIndex + vertexCount*2
|
|
||||||
if endIndex > len(p) {
|
|
||||||
endIndex = len(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
x := p[startIndex]
|
|
||||||
y := p[startIndex+1]
|
|
||||||
// inline transformation
|
|
||||||
prevX := x*tr[0] + y*tr[2] + tr[4]
|
|
||||||
prevY := x*tr[1] + y*tr[3] + tr[5]
|
|
||||||
|
|
||||||
//! Calculates the clip flags for a point.
|
|
||||||
prevClipFlags := POLYGON_CLIP_NONE
|
|
||||||
if prevX < clipBound[0] {
|
|
||||||
prevClipFlags |= POLYGON_CLIP_LEFT
|
|
||||||
} else if prevX >= clipBound[2] {
|
|
||||||
prevClipFlags |= POLYGON_CLIP_RIGHT
|
|
||||||
}
|
|
||||||
|
|
||||||
if prevY < clipBound[1] {
|
|
||||||
prevClipFlags |= POLYGON_CLIP_TOP
|
|
||||||
} else if prevY >= clipBound[3] {
|
|
||||||
prevClipFlags |= POLYGON_CLIP_BOTTOM
|
|
||||||
}
|
|
||||||
|
|
||||||
edgeCount := 0
|
|
||||||
var k, clipFlags, clipSum, clipUnion int
|
|
||||||
var xleft, yleft, xright, yright, oldY, maxX, minX float64
|
|
||||||
var swapWinding int16
|
|
||||||
for n := startIndex; n < endIndex; n = n + 2 {
|
|
||||||
k = (n + 2) % len(p)
|
|
||||||
x = p[k]*tr[0] + p[k+1]*tr[2] + tr[4]
|
|
||||||
y = p[k]*tr[1] + p[k+1]*tr[3] + tr[5]
|
|
||||||
|
|
||||||
//! Calculates the clip flags for a point.
|
|
||||||
clipFlags = POLYGON_CLIP_NONE
|
|
||||||
if prevX < clipBound[0] {
|
|
||||||
clipFlags |= POLYGON_CLIP_LEFT
|
|
||||||
} else if prevX >= clipBound[2] {
|
|
||||||
clipFlags |= POLYGON_CLIP_RIGHT
|
|
||||||
}
|
|
||||||
if prevY < clipBound[1] {
|
|
||||||
clipFlags |= POLYGON_CLIP_TOP
|
|
||||||
} else if prevY >= clipBound[3] {
|
|
||||||
clipFlags |= POLYGON_CLIP_BOTTOM
|
|
||||||
}
|
|
||||||
|
|
||||||
clipSum = prevClipFlags | clipFlags
|
|
||||||
clipUnion = prevClipFlags & clipFlags
|
|
||||||
|
|
||||||
// Skip all edges that are either completely outside at the top or at the bottom.
|
|
||||||
if clipUnion&(POLYGON_CLIP_TOP|POLYGON_CLIP_BOTTOM) == 0 {
|
|
||||||
if clipUnion&POLYGON_CLIP_RIGHT != 0 {
|
|
||||||
// Both clip to right, edge is a vertical line on the right side
|
|
||||||
if getVerticalEdge(prevY, y, clipBound[2], &edges[edgeCount], clipBound) {
|
|
||||||
edgeCount++
|
|
||||||
}
|
|
||||||
} else if clipUnion&POLYGON_CLIP_LEFT != 0 {
|
|
||||||
// Both clip to left, edge is a vertical line on the left side
|
|
||||||
if getVerticalEdge(prevY, y, clipBound[0], &edges[edgeCount], clipBound) {
|
|
||||||
edgeCount++
|
|
||||||
}
|
|
||||||
} else if clipSum&(POLYGON_CLIP_RIGHT|POLYGON_CLIP_LEFT) == 0 {
|
|
||||||
// No clipping in the horizontal direction
|
|
||||||
if getEdge(prevX, prevY, x, y, &edges[edgeCount], clipBound) {
|
|
||||||
edgeCount++
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Clips to left or right or both.
|
|
||||||
|
|
||||||
if x < prevX {
|
|
||||||
xleft, yleft = x, y
|
|
||||||
xright, yright = prevX, prevY
|
|
||||||
swapWinding = -1
|
|
||||||
} else {
|
|
||||||
xleft, yleft = prevX, prevY
|
|
||||||
xright, yright = x, y
|
|
||||||
swapWinding = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
slope := (yright - yleft) / (xright - xleft)
|
|
||||||
|
|
||||||
if clipSum&POLYGON_CLIP_RIGHT != 0 {
|
|
||||||
// calculate new position for the right vertex
|
|
||||||
oldY = yright
|
|
||||||
maxX = clipBound[2]
|
|
||||||
|
|
||||||
yright = yleft + (maxX-xleft)*slope
|
|
||||||
xright = maxX
|
|
||||||
|
|
||||||
// add vertical edge for the overflowing part
|
|
||||||
if getVerticalEdge(yright, oldY, maxX, &edges[edgeCount], clipBound) {
|
|
||||||
edges[edgeCount].Winding *= swapWinding
|
|
||||||
edgeCount++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if clipSum&POLYGON_CLIP_LEFT != 0 {
|
|
||||||
// calculate new position for the left vertex
|
|
||||||
oldY = yleft
|
|
||||||
minX = clipBound[0]
|
|
||||||
|
|
||||||
yleft = yleft + (minX-xleft)*slope
|
|
||||||
xleft = minX
|
|
||||||
|
|
||||||
// add vertical edge for the overflowing part
|
|
||||||
if getVerticalEdge(oldY, yleft, minX, &edges[edgeCount], clipBound) {
|
|
||||||
edges[edgeCount].Winding *= swapWinding
|
|
||||||
edgeCount++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if getEdge(xleft, yleft, xright, yright, &edges[edgeCount], clipBound) {
|
|
||||||
edges[edgeCount].Winding *= swapWinding
|
|
||||||
edgeCount++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
prevClipFlags = clipFlags
|
|
||||||
prevX = x
|
|
||||||
prevY = y
|
|
||||||
}
|
|
||||||
|
|
||||||
return edgeCount
|
|
||||||
}
|
|
||||||
|
|
||||||
//! Creates a polygon edge between two vectors.
|
|
||||||
/*! Clips the edge vertically to the clip rectangle. Returns true for edges that
|
|
||||||
* should be rendered, false for others.
|
|
||||||
*/
|
|
||||||
func getEdge(x0, y0, x1, y1 float64, edge *PolygonEdge, clipBound [4]float64) bool {
|
|
||||||
var startX, startY, endX, endY float64
|
|
||||||
var winding int16
|
|
||||||
|
|
||||||
if y0 <= y1 {
|
|
||||||
startX = x0
|
|
||||||
startY = y0
|
|
||||||
endX = x1
|
|
||||||
endY = y1
|
|
||||||
winding = 1
|
|
||||||
} else {
|
|
||||||
startX = x1
|
|
||||||
startY = y1
|
|
||||||
endX = x0
|
|
||||||
endY = y0
|
|
||||||
winding = -1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Essentially, firstLine is floor(startY + 1) and lastLine is floor(endY).
|
|
||||||
// These are refactored to integer casts in order to avoid function
|
|
||||||
// calls. The difference with integer cast is that numbers are always
|
|
||||||
// rounded towards zero. Since values smaller than zero get clipped away,
|
|
||||||
// only coordinates between 0 and -1 require greater attention as they
|
|
||||||
// also round to zero. The problems in this range can be avoided by
|
|
||||||
// adding one to the values before conversion and subtracting after it.
|
|
||||||
|
|
||||||
firstLine := int(startY + 1)
|
|
||||||
lastLine := int(endY+1) - 1
|
|
||||||
|
|
||||||
minClip := int(clipBound[1])
|
|
||||||
maxClip := int(clipBound[3])
|
|
||||||
|
|
||||||
// If start and end are on the same line, the edge doesn't cross
|
|
||||||
// any lines and thus can be ignored.
|
|
||||||
// If the end is smaller than the first line, edge is out.
|
|
||||||
// If the start is larger than the last line, edge is out.
|
|
||||||
if firstLine > lastLine || lastLine < minClip || firstLine >= maxClip {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Adjust the start based on the target.
|
|
||||||
if firstLine < minClip {
|
|
||||||
firstLine = minClip
|
|
||||||
}
|
|
||||||
|
|
||||||
if lastLine >= maxClip {
|
|
||||||
lastLine = maxClip - 1
|
|
||||||
}
|
|
||||||
edge.Slope = (endX - startX) / (endY - startY)
|
|
||||||
edge.X = startX + (float64(firstLine)-startY)*edge.Slope
|
|
||||||
edge.Winding = winding
|
|
||||||
edge.FirstLine = firstLine
|
|
||||||
edge.LastLine = lastLine
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
//! Creates a vertical polygon edge between two y values.
|
|
||||||
/*! Clips the edge vertically to the clip rectangle. Returns true for edges that
|
|
||||||
* should be rendered, false for others.
|
|
||||||
*/
|
|
||||||
func getVerticalEdge(startY, endY, x float64, edge *PolygonEdge, clipBound [4]float64) bool {
|
|
||||||
var start, end float64
|
|
||||||
var winding int16
|
|
||||||
if startY < endY {
|
|
||||||
start = startY
|
|
||||||
end = endY
|
|
||||||
winding = 1
|
|
||||||
} else {
|
|
||||||
start = endY
|
|
||||||
end = startY
|
|
||||||
winding = -1
|
|
||||||
}
|
|
||||||
|
|
||||||
firstLine := int(start + 1)
|
|
||||||
lastLine := int(end+1) - 1
|
|
||||||
|
|
||||||
minClip := int(clipBound[1])
|
|
||||||
maxClip := int(clipBound[3])
|
|
||||||
|
|
||||||
// If start and end are on the same line, the edge doesn't cross
|
|
||||||
// any lines and thus can be ignored.
|
|
||||||
// If the end is smaller than the first line, edge is out.
|
|
||||||
// If the start is larger than the last line, edge is out.
|
|
||||||
if firstLine > lastLine || lastLine < minClip || firstLine >= maxClip {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Adjust the start based on the clip rect.
|
|
||||||
if firstLine < minClip {
|
|
||||||
firstLine = minClip
|
|
||||||
}
|
|
||||||
if lastLine >= maxClip {
|
|
||||||
lastLine = maxClip - 1
|
|
||||||
}
|
|
||||||
|
|
||||||
edge.Slope = 0
|
|
||||||
edge.X = x
|
|
||||||
edge.Winding = winding
|
|
||||||
edge.FirstLine = firstLine
|
|
||||||
edge.LastLine = lastLine
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
type VertexData struct {
|
|
||||||
X, Y float64
|
|
||||||
ClipFlags int
|
|
||||||
Line int
|
|
||||||
}
|
|
||||||
|
|
||||||
//! Calculates the edges of the polygon with transformation and clipping to edges array.
|
|
||||||
/*! Note that this may return upto three times the amount of edges that the polygon has vertices,
|
|
||||||
* in the unlucky case where both left and right side get clipped for all edges.
|
|
||||||
* \param edges the array for result edges. This should be able to contain 2*aVertexCount edges.
|
|
||||||
* \param aTransformation the transformation matrix for the polygon.
|
|
||||||
* \param aClipRectangle the clip rectangle.
|
|
||||||
* \return the amount of edges in the result.
|
|
||||||
*/
|
|
||||||
func (p Polygon) getScanEdges(edges []PolygonScanEdge, tr [6]float64, clipBound [4]float64) int {
|
|
||||||
var n int
|
|
||||||
vertexData := make([]VertexData, len(p)/2+1)
|
|
||||||
for n = 0; n < len(vertexData)-1; n = n + 1 {
|
|
||||||
k := n * 2
|
|
||||||
vertexData[n].X = p[k]*tr[0] + p[k+1]*tr[2] + tr[4]
|
|
||||||
vertexData[n].Y = p[k]*tr[1] + p[k+1]*tr[3] + tr[5]
|
|
||||||
// Calculate clip flags for all vertices.
|
|
||||||
vertexData[n].ClipFlags = POLYGON_CLIP_NONE
|
|
||||||
if vertexData[n].X < clipBound[0] {
|
|
||||||
vertexData[n].ClipFlags |= POLYGON_CLIP_LEFT
|
|
||||||
} else if vertexData[n].X >= clipBound[2] {
|
|
||||||
vertexData[n].ClipFlags |= POLYGON_CLIP_RIGHT
|
|
||||||
}
|
|
||||||
if vertexData[n].Y < clipBound[1] {
|
|
||||||
vertexData[n].ClipFlags |= POLYGON_CLIP_TOP
|
|
||||||
} else if vertexData[n].Y >= clipBound[3] {
|
|
||||||
vertexData[n].ClipFlags |= POLYGON_CLIP_BOTTOM
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate line of the vertex. If the vertex is clipped by top or bottom, the line
|
|
||||||
// is determined by the clip rectangle.
|
|
||||||
if vertexData[n].ClipFlags&POLYGON_CLIP_TOP != 0 {
|
|
||||||
vertexData[n].Line = int(clipBound[1])
|
|
||||||
} else if vertexData[n].ClipFlags&POLYGON_CLIP_BOTTOM != 0 {
|
|
||||||
vertexData[n].Line = int(clipBound[3] - 1)
|
|
||||||
} else {
|
|
||||||
vertexData[n].Line = int(vertexData[n].Y+1) - 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy the data from 0 to the last entry to make the data to loop.
|
|
||||||
vertexData[len(vertexData)-1] = vertexData[0]
|
|
||||||
|
|
||||||
// Transform the first vertex; store.
|
|
||||||
// Process mVertexCount - 1 times, next is n+1
|
|
||||||
// copy the first vertex to
|
|
||||||
// Process 1 time, next is n
|
|
||||||
|
|
||||||
edgeCount := 0
|
|
||||||
for n = 0; n < len(vertexData)-1; n++ {
|
|
||||||
clipSum := vertexData[n].ClipFlags | vertexData[n+1].ClipFlags
|
|
||||||
clipUnion := vertexData[n].ClipFlags & vertexData[n+1].ClipFlags
|
|
||||||
|
|
||||||
if clipUnion&(POLYGON_CLIP_TOP|POLYGON_CLIP_BOTTOM) == 0 &&
|
|
||||||
vertexData[n].Line != vertexData[n+1].Line {
|
|
||||||
var startIndex, endIndex int
|
|
||||||
var winding int16
|
|
||||||
if vertexData[n].Y < vertexData[n+1].Y {
|
|
||||||
startIndex = n
|
|
||||||
endIndex = n + 1
|
|
||||||
winding = 1
|
|
||||||
} else {
|
|
||||||
startIndex = n + 1
|
|
||||||
endIndex = n
|
|
||||||
winding = -1
|
|
||||||
}
|
|
||||||
|
|
||||||
firstLine := vertexData[startIndex].Line + 1
|
|
||||||
lastLine := vertexData[endIndex].Line
|
|
||||||
|
|
||||||
if clipUnion&POLYGON_CLIP_RIGHT != 0 {
|
|
||||||
// Both clip to right, edge is a vertical line on the right side
|
|
||||||
edges[edgeCount].FirstLine = firstLine
|
|
||||||
edges[edgeCount].LastLine = lastLine
|
|
||||||
edges[edgeCount].Winding = winding
|
|
||||||
edges[edgeCount].X = Fix(clipBound[2] * FIXED_FLOAT_COEF)
|
|
||||||
edges[edgeCount].Slope = 0
|
|
||||||
edges[edgeCount].SlopeFix = 0
|
|
||||||
|
|
||||||
edgeCount++
|
|
||||||
} else if clipUnion&POLYGON_CLIP_LEFT != 0 {
|
|
||||||
// Both clip to left, edge is a vertical line on the left side
|
|
||||||
edges[edgeCount].FirstLine = firstLine
|
|
||||||
edges[edgeCount].LastLine = lastLine
|
|
||||||
edges[edgeCount].Winding = winding
|
|
||||||
edges[edgeCount].X = Fix(clipBound[0] * FIXED_FLOAT_COEF)
|
|
||||||
edges[edgeCount].Slope = 0
|
|
||||||
edges[edgeCount].SlopeFix = 0
|
|
||||||
|
|
||||||
edgeCount++
|
|
||||||
} else if clipSum&(POLYGON_CLIP_RIGHT|POLYGON_CLIP_LEFT) == 0 {
|
|
||||||
// No clipping in the horizontal direction
|
|
||||||
slope := (vertexData[endIndex].X -
|
|
||||||
vertexData[startIndex].X) /
|
|
||||||
(vertexData[endIndex].Y -
|
|
||||||
vertexData[startIndex].Y)
|
|
||||||
|
|
||||||
// If there is vertical clip (for the top) it will be processed here. The calculation
|
|
||||||
// should be done for all non-clipping edges as well to determine the accurate position
|
|
||||||
// where the edge crosses the first scanline.
|
|
||||||
startx := vertexData[startIndex].X +
|
|
||||||
(float64(firstLine)-vertexData[startIndex].Y)*slope
|
|
||||||
|
|
||||||
edges[edgeCount].FirstLine = firstLine
|
|
||||||
edges[edgeCount].LastLine = lastLine
|
|
||||||
edges[edgeCount].Winding = winding
|
|
||||||
edges[edgeCount].X = Fix(startx * FIXED_FLOAT_COEF)
|
|
||||||
edges[edgeCount].Slope = Fix(slope * FIXED_FLOAT_COEF)
|
|
||||||
|
|
||||||
if lastLine-firstLine >= SLOPE_FIX_STEP {
|
|
||||||
edges[edgeCount].SlopeFix = Fix(slope*SLOPE_FIX_STEP*FIXED_FLOAT_COEF) -
|
|
||||||
edges[edgeCount].Slope<<SLOPE_FIX_SHIFT
|
|
||||||
} else {
|
|
||||||
edges[edgeCount].SlopeFix = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
edgeCount++
|
|
||||||
} else {
|
|
||||||
// Clips to left or right or both.
|
|
||||||
slope := (vertexData[endIndex].X -
|
|
||||||
vertexData[startIndex].X) /
|
|
||||||
(vertexData[endIndex].Y -
|
|
||||||
vertexData[startIndex].Y)
|
|
||||||
|
|
||||||
// The edge may clip to both left and right.
|
|
||||||
// The clip results in one or two new vertices, and one to three segments.
|
|
||||||
// The rounding for scanlines may produce a result where any of the segments is
|
|
||||||
// ignored.
|
|
||||||
|
|
||||||
// The start is always above the end. Calculate the clip positions to clipVertices.
|
|
||||||
// It is possible that only one of the vertices exist. This will be detected from the
|
|
||||||
// clip flags of the vertex later, so they are initialized here.
|
|
||||||
var clipVertices [2]VertexData
|
|
||||||
|
|
||||||
if vertexData[startIndex].X <
|
|
||||||
vertexData[endIndex].X {
|
|
||||||
clipVertices[0].X = clipBound[0]
|
|
||||||
clipVertices[1].X = clipBound[2]
|
|
||||||
clipVertices[0].ClipFlags = POLYGON_CLIP_LEFT
|
|
||||||
clipVertices[1].ClipFlags = POLYGON_CLIP_RIGHT
|
|
||||||
} else {
|
|
||||||
clipVertices[0].X = clipBound[2]
|
|
||||||
clipVertices[1].X = clipBound[0]
|
|
||||||
clipVertices[0].ClipFlags = POLYGON_CLIP_RIGHT
|
|
||||||
clipVertices[1].ClipFlags = POLYGON_CLIP_LEFT
|
|
||||||
}
|
|
||||||
|
|
||||||
var p int
|
|
||||||
for p = 0; p < 2; p++ {
|
|
||||||
// Check if either of the vertices crosses the edge marked for the clip vertex
|
|
||||||
if clipSum&clipVertices[p].ClipFlags != 0 {
|
|
||||||
// The the vertex is required, calculate it.
|
|
||||||
clipVertices[p].Y = vertexData[startIndex].Y +
|
|
||||||
(clipVertices[p].X-
|
|
||||||
vertexData[startIndex].X)/slope
|
|
||||||
|
|
||||||
// If there is clipping in the vertical direction, the new vertex may be clipped.
|
|
||||||
if clipSum&(POLYGON_CLIP_TOP|POLYGON_CLIP_BOTTOM) != 0 {
|
|
||||||
if clipVertices[p].Y < clipBound[1] {
|
|
||||||
clipVertices[p].ClipFlags = POLYGON_CLIP_TOP
|
|
||||||
clipVertices[p].Line = int(clipBound[1])
|
|
||||||
} else if clipVertices[p].Y > clipBound[3] {
|
|
||||||
clipVertices[p].ClipFlags = POLYGON_CLIP_BOTTOM
|
|
||||||
clipVertices[p].Line = int(clipBound[3] - 1)
|
|
||||||
} else {
|
|
||||||
clipVertices[p].ClipFlags = 0
|
|
||||||
clipVertices[p].Line = int(clipVertices[p].Y+1) - 1
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
clipVertices[p].ClipFlags = 0
|
|
||||||
clipVertices[p].Line = int(clipVertices[p].Y+1) - 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now there are three or four vertices, in the top-to-bottom order of start, clip0, clip1,
|
|
||||||
// end. What kind of edges are required for connecting these can be determined from the
|
|
||||||
// clip flags.
|
|
||||||
// -if clip vertex has horizontal clip flags, it doesn't exist. No edge is generated.
|
|
||||||
// -if start vertex or end vertex has horizontal clip flag, the edge to/from the clip vertex is vertical
|
|
||||||
// -if the line of two vertices is the same, the edge is not generated, since the edge doesn't
|
|
||||||
// cross any scanlines.
|
|
||||||
|
|
||||||
// The alternative patterns are:
|
|
||||||
// start - clip0 - clip1 - end
|
|
||||||
// start - clip0 - end
|
|
||||||
// start - clip1 - end
|
|
||||||
|
|
||||||
var topClipIndex, bottomClipIndex int
|
|
||||||
if (clipVertices[0].ClipFlags|clipVertices[1].ClipFlags)&
|
|
||||||
(POLYGON_CLIP_LEFT|POLYGON_CLIP_RIGHT) == 0 {
|
|
||||||
// Both sides are clipped, the order is start-clip0-clip1-end
|
|
||||||
topClipIndex = 0
|
|
||||||
bottomClipIndex = 1
|
|
||||||
|
|
||||||
// Add the edge from clip0 to clip1
|
|
||||||
// Check that the line is different for the vertices.
|
|
||||||
if clipVertices[0].Line != clipVertices[1].Line {
|
|
||||||
firstClipLine := clipVertices[0].Line + 1
|
|
||||||
|
|
||||||
startx := vertexData[startIndex].X +
|
|
||||||
(float64(firstClipLine)-vertexData[startIndex].Y)*slope
|
|
||||||
|
|
||||||
edges[edgeCount].X = Fix(startx * FIXED_FLOAT_COEF)
|
|
||||||
edges[edgeCount].Slope = Fix(slope * FIXED_FLOAT_COEF)
|
|
||||||
edges[edgeCount].FirstLine = firstClipLine
|
|
||||||
edges[edgeCount].LastLine = clipVertices[1].Line
|
|
||||||
edges[edgeCount].Winding = winding
|
|
||||||
|
|
||||||
if edges[edgeCount].LastLine-edges[edgeCount].FirstLine >= SLOPE_FIX_STEP {
|
|
||||||
edges[edgeCount].SlopeFix = Fix(slope*SLOPE_FIX_STEP*FIXED_FLOAT_COEF) -
|
|
||||||
edges[edgeCount].Slope<<SLOPE_FIX_SHIFT
|
|
||||||
} else {
|
|
||||||
edges[edgeCount].SlopeFix = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
edgeCount++
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Clip at either side, check which side. The clip flag is on for the vertex
|
|
||||||
// that doesn't exist, i.e. has not been clipped to be inside the rect.
|
|
||||||
if clipVertices[0].ClipFlags&(POLYGON_CLIP_LEFT|POLYGON_CLIP_RIGHT) != 0 {
|
|
||||||
topClipIndex = 1
|
|
||||||
bottomClipIndex = 1
|
|
||||||
} else {
|
|
||||||
topClipIndex = 0
|
|
||||||
bottomClipIndex = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate the edges from start - clip top and clip bottom - end
|
|
||||||
// Clip top and clip bottom may be the same vertex if there is only one
|
|
||||||
// clipped vertex.
|
|
||||||
|
|
||||||
// Check that the line is different for the vertices.
|
|
||||||
if vertexData[startIndex].Line != clipVertices[topClipIndex].Line {
|
|
||||||
edges[edgeCount].FirstLine = firstLine
|
|
||||||
edges[edgeCount].LastLine = clipVertices[topClipIndex].Line
|
|
||||||
edges[edgeCount].Winding = winding
|
|
||||||
|
|
||||||
// If startIndex is clipped, the edge is a vertical one.
|
|
||||||
if vertexData[startIndex].ClipFlags&(POLYGON_CLIP_LEFT|POLYGON_CLIP_RIGHT) != 0 {
|
|
||||||
edges[edgeCount].X = Fix(clipVertices[topClipIndex].X * FIXED_FLOAT_COEF)
|
|
||||||
edges[edgeCount].Slope = 0
|
|
||||||
edges[edgeCount].SlopeFix = 0
|
|
||||||
} else {
|
|
||||||
startx := vertexData[startIndex].X +
|
|
||||||
(float64(firstLine)-vertexData[startIndex].Y)*slope
|
|
||||||
|
|
||||||
edges[edgeCount].X = Fix(startx * FIXED_FLOAT_COEF)
|
|
||||||
edges[edgeCount].Slope = Fix(slope * FIXED_FLOAT_COEF)
|
|
||||||
|
|
||||||
if edges[edgeCount].LastLine-edges[edgeCount].FirstLine >= SLOPE_FIX_STEP {
|
|
||||||
edges[edgeCount].SlopeFix = Fix(slope*SLOPE_FIX_STEP*FIXED_FLOAT_COEF) -
|
|
||||||
edges[edgeCount].Slope<<SLOPE_FIX_SHIFT
|
|
||||||
} else {
|
|
||||||
edges[edgeCount].SlopeFix = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
edgeCount++
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check that the line is different for the vertices.
|
|
||||||
if clipVertices[bottomClipIndex].Line != vertexData[endIndex].Line {
|
|
||||||
firstClipLine := clipVertices[bottomClipIndex].Line + 1
|
|
||||||
|
|
||||||
edges[edgeCount].FirstLine = firstClipLine
|
|
||||||
edges[edgeCount].LastLine = lastLine
|
|
||||||
edges[edgeCount].Winding = winding
|
|
||||||
|
|
||||||
// If endIndex is clipped, the edge is a vertical one.
|
|
||||||
if vertexData[endIndex].ClipFlags&(POLYGON_CLIP_LEFT|POLYGON_CLIP_RIGHT) != 0 {
|
|
||||||
edges[edgeCount].X = Fix(clipVertices[bottomClipIndex].X * FIXED_FLOAT_COEF)
|
|
||||||
edges[edgeCount].Slope = 0
|
|
||||||
edges[edgeCount].SlopeFix = 0
|
|
||||||
} else {
|
|
||||||
startx := vertexData[startIndex].X +
|
|
||||||
(float64(firstClipLine)-vertexData[startIndex].Y)*slope
|
|
||||||
|
|
||||||
edges[edgeCount].X = Fix(startx * FIXED_FLOAT_COEF)
|
|
||||||
edges[edgeCount].Slope = Fix(slope * FIXED_FLOAT_COEF)
|
|
||||||
|
|
||||||
if edges[edgeCount].LastLine-edges[edgeCount].FirstLine >= SLOPE_FIX_STEP {
|
|
||||||
edges[edgeCount].SlopeFix = Fix(slope*SLOPE_FIX_STEP*FIXED_FLOAT_COEF) -
|
|
||||||
edges[edgeCount].Slope<<SLOPE_FIX_SHIFT
|
|
||||||
} else {
|
|
||||||
edges[edgeCount].SlopeFix = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
edgeCount++
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return edgeCount
|
|
||||||
}
|
|
|
@ -1,218 +0,0 @@
|
||||||
package raster
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"image"
|
|
||||||
"image/color"
|
|
||||||
"image/png"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"code.google.com/p/freetype-go/freetype/raster"
|
|
||||||
"gopkg.in/llgcode/draw2d.v1/curve"
|
|
||||||
)
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
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 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)
|
|
||||||
poly := Polygon(p.points)
|
|
||||||
color := color.RGBA{0, 0, 0, 0xff}
|
|
||||||
|
|
||||||
img := image.NewRGBA(image.Rect(0, 0, 200, 200))
|
|
||||||
rasterizer := raster.NewRasterizer(200, 200)
|
|
||||||
rasterizer.UseNonZeroWinding = false
|
|
||||||
rasterizer.Start(raster.Point{
|
|
||||||
X: raster.Fix32(10 * 256),
|
|
||||||
Y: raster.Fix32(190 * 256)})
|
|
||||||
for j := 0; j < len(poly); j = j + 2 {
|
|
||||||
rasterizer.Add1(raster.Point{
|
|
||||||
X: raster.Fix32(poly[j] * 256),
|
|
||||||
Y: raster.Fix32(poly[j+1] * 256)})
|
|
||||||
}
|
|
||||||
painter := raster.NewRGBAPainter(img)
|
|
||||||
painter.SetColor(color)
|
|
||||||
rasterizer.Rasterize(painter)
|
|
||||||
|
|
||||||
savepng("../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)
|
|
||||||
poly := Polygon(p.points)
|
|
||||||
color := color.RGBA{0, 0, 0, 0xff}
|
|
||||||
|
|
||||||
img := image.NewRGBA(image.Rect(0, 0, 200, 200))
|
|
||||||
rasterizer := raster.NewRasterizer(200, 200)
|
|
||||||
rasterizer.UseNonZeroWinding = true
|
|
||||||
rasterizer.Start(raster.Point{
|
|
||||||
X: raster.Fix32(10 * 256),
|
|
||||||
Y: raster.Fix32(190 * 256)})
|
|
||||||
for j := 0; j < len(poly); j = j + 2 {
|
|
||||||
rasterizer.Add1(raster.Point{
|
|
||||||
X: raster.Fix32(poly[j] * 256),
|
|
||||||
Y: raster.Fix32(poly[j+1] * 256)})
|
|
||||||
}
|
|
||||||
painter := raster.NewRGBAPainter(img)
|
|
||||||
painter.SetColor(color)
|
|
||||||
rasterizer.Rasterize(painter)
|
|
||||||
|
|
||||||
savepng("../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)
|
|
||||||
poly := Polygon(p.points)
|
|
||||||
color := color.RGBA{0, 0, 0, 0xff}
|
|
||||||
tr := [6]float64{1, 0, 0, 1, 0, 0}
|
|
||||||
r := NewRasterizer8BitsSample(200, 200)
|
|
||||||
//PolylineBresenham(img, image.Black, poly...)
|
|
||||||
|
|
||||||
r.RenderEvenOdd(img, &color, &poly, tr)
|
|
||||||
savepng("../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)
|
|
||||||
poly := Polygon(p.points)
|
|
||||||
color := color.RGBA{0, 0, 0, 0xff}
|
|
||||||
tr := [6]float64{1, 0, 0, 1, 0, 0}
|
|
||||||
r := NewRasterizer8BitsSample(200, 200)
|
|
||||||
//PolylineBresenham(img, image.Black, poly...)
|
|
||||||
|
|
||||||
r.RenderNonZeroWinding(img, &color, &poly, tr)
|
|
||||||
savepng("../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)
|
|
||||||
poly := Polygon(p.points)
|
|
||||||
color := color.RGBA{0, 0, 0, 0xff}
|
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
img := image.NewRGBA(image.Rect(0, 0, 200, 200))
|
|
||||||
rasterizer := raster.NewRasterizer(200, 200)
|
|
||||||
rasterizer.UseNonZeroWinding = false
|
|
||||||
rasterizer.Start(raster.Point{
|
|
||||||
X: raster.Fix32(10 * 256),
|
|
||||||
Y: raster.Fix32(190 * 256)})
|
|
||||||
for j := 0; j < len(poly); j = j + 2 {
|
|
||||||
rasterizer.Add1(raster.Point{
|
|
||||||
X: raster.Fix32(poly[j] * 256),
|
|
||||||
Y: raster.Fix32(poly[j+1] * 256)})
|
|
||||||
}
|
|
||||||
painter := raster.NewRGBAPainter(img)
|
|
||||||
painter.SetColor(color)
|
|
||||||
rasterizer.Rasterize(painter)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
poly := Polygon(p.points)
|
|
||||||
color := color.RGBA{0, 0, 0, 0xff}
|
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
img := image.NewRGBA(image.Rect(0, 0, 200, 200))
|
|
||||||
rasterizer := raster.NewRasterizer(200, 200)
|
|
||||||
rasterizer.UseNonZeroWinding = true
|
|
||||||
rasterizer.Start(raster.Point{
|
|
||||||
X: raster.Fix32(10 * 256),
|
|
||||||
Y: raster.Fix32(190 * 256)})
|
|
||||||
for j := 0; j < len(poly); j = j + 2 {
|
|
||||||
rasterizer.Add1(raster.Point{
|
|
||||||
X: raster.Fix32(poly[j] * 256),
|
|
||||||
Y: raster.Fix32(poly[j+1] * 256)})
|
|
||||||
}
|
|
||||||
painter := raster.NewRGBAPainter(img)
|
|
||||||
painter.SetColor(color)
|
|
||||||
rasterizer.Rasterize(painter)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
poly := Polygon(p.points)
|
|
||||||
color := color.RGBA{0, 0, 0, 0xff}
|
|
||||||
tr := [6]float64{1, 0, 0, 1, 0, 0}
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
img := image.NewRGBA(image.Rect(0, 0, 200, 200))
|
|
||||||
rasterizer := NewRasterizer8BitsSample(200, 200)
|
|
||||||
rasterizer.RenderNonZeroWinding(img, &color, &poly, tr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
poly := Polygon(p.points)
|
|
||||||
color := color.RGBA{0, 0, 0, 0xff}
|
|
||||||
tr := [6]float64{1, 0, 0, 1, 0, 0}
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
img := image.NewRGBA(image.Rect(0, 0, 200, 200))
|
|
||||||
rasterizer := NewRasterizer8BitsSample(200, 200)
|
|
||||||
rasterizer.RenderEvenOdd(img, &color, &poly, tr)
|
|
||||||
}
|
|
||||||
}
|
|
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 |
|
@ -1,155 +0,0 @@
|
||||||
// Copyright 2010 The draw2d Authors. All rights reserved.
|
|
||||||
// created: 21/11/2010 by Laurent Le Goff
|
|
||||||
// see http://pippin.gimp.org/image_processing/chap_resampling.html
|
|
||||||
|
|
||||||
package draw2d
|
|
||||||
|
|
||||||
import (
|
|
||||||
"image"
|
|
||||||
"image/color"
|
|
||||||
"image/draw"
|
|
||||||
"math"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ImageFilter defines sampling filter (linear, bilinear or bicubic)
|
|
||||||
type ImageFilter int
|
|
||||||
|
|
||||||
const (
|
|
||||||
// LinearFilter uses linear interpolation
|
|
||||||
LinearFilter ImageFilter = iota
|
|
||||||
// BilinearFilter uses bilinear interpolation
|
|
||||||
BilinearFilter
|
|
||||||
// BicubicFilter uses bicubic interpolation
|
|
||||||
BicubicFilter
|
|
||||||
)
|
|
||||||
|
|
||||||
//see http://pippin.gimp.org/image_processing/chap_resampling.html
|
|
||||||
func getColorLinear(img image.Image, x, y float64) color.Color {
|
|
||||||
return img.At(int(x), int(y))
|
|
||||||
}
|
|
||||||
|
|
||||||
func getColorBilinear(img image.Image, x, y float64) color.Color {
|
|
||||||
x0 := math.Floor(x)
|
|
||||||
y0 := math.Floor(y)
|
|
||||||
dx := x - x0
|
|
||||||
dy := y - y0
|
|
||||||
|
|
||||||
rt, gt, bt, at := img.At(int(x0), int(y0)).RGBA()
|
|
||||||
r0, g0, b0, a0 := float64(rt), float64(gt), float64(bt), float64(at)
|
|
||||||
rt, gt, bt, at = img.At(int(x0+1), int(y0)).RGBA()
|
|
||||||
r1, g1, b1, a1 := float64(rt), float64(gt), float64(bt), float64(at)
|
|
||||||
rt, gt, bt, at = img.At(int(x0+1), int(y0+1)).RGBA()
|
|
||||||
r2, g2, b2, a2 := float64(rt), float64(gt), float64(bt), float64(at)
|
|
||||||
rt, gt, bt, at = img.At(int(x0), int(y0+1)).RGBA()
|
|
||||||
r3, g3, b3, a3 := float64(rt), float64(gt), float64(bt), float64(at)
|
|
||||||
|
|
||||||
r := int(lerp(lerp(r0, r1, dx), lerp(r3, r2, dx), dy))
|
|
||||||
g := int(lerp(lerp(g0, g1, dx), lerp(g3, g2, dx), dy))
|
|
||||||
b := int(lerp(lerp(b0, b1, dx), lerp(b3, b2, dx), dy))
|
|
||||||
a := int(lerp(lerp(a0, a1, dx), lerp(a3, a2, dx), dy))
|
|
||||||
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)
|
|
||||||
*/
|
|
||||||
func lerp(v1, v2, ratio float64) float64 {
|
|
||||||
return v1*(1-ratio) + v2*ratio
|
|
||||||
}
|
|
||||||
|
|
||||||
func getColorCubicRow(img image.Image, x, y, offset float64) color.Color {
|
|
||||||
c0 := img.At(int(x), int(y))
|
|
||||||
c1 := img.At(int(x+1), int(y))
|
|
||||||
c2 := img.At(int(x+2), int(y))
|
|
||||||
c3 := img.At(int(x+3), int(y))
|
|
||||||
rt, gt, bt, at := c0.RGBA()
|
|
||||||
r0, g0, b0, a0 := float64(rt), float64(gt), float64(bt), float64(at)
|
|
||||||
rt, gt, bt, at = c1.RGBA()
|
|
||||||
r1, g1, b1, a1 := float64(rt), float64(gt), float64(bt), float64(at)
|
|
||||||
rt, gt, bt, at = c2.RGBA()
|
|
||||||
r2, g2, b2, a2 := float64(rt), float64(gt), float64(bt), float64(at)
|
|
||||||
rt, gt, bt, at = c3.RGBA()
|
|
||||||
r3, g3, b3, a3 := float64(rt), float64(gt), float64(bt), float64(at)
|
|
||||||
r, g, b, a := cubic(offset, r0, r1, r2, r3), cubic(offset, g0, g1, g2, g3), cubic(offset, b0, b1, b2, b3), cubic(offset, a0, a1, a2, a3)
|
|
||||||
return color.RGBA{uint8(r >> 8), uint8(g >> 8), uint8(b >> 8), uint8(a >> 8)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getColorBicubic(img image.Image, x, y float64) color.Color {
|
|
||||||
x0 := math.Floor(x)
|
|
||||||
y0 := math.Floor(y)
|
|
||||||
dx := x - x0
|
|
||||||
dy := y - y0
|
|
||||||
c0 := getColorCubicRow(img, x0-1, y0-1, dx)
|
|
||||||
c1 := getColorCubicRow(img, x0-1, y0, dx)
|
|
||||||
c2 := getColorCubicRow(img, x0-1, y0+1, dx)
|
|
||||||
c3 := getColorCubicRow(img, x0-1, y0+2, dx)
|
|
||||||
rt, gt, bt, at := c0.RGBA()
|
|
||||||
r0, g0, b0, a0 := float64(rt), float64(gt), float64(bt), float64(at)
|
|
||||||
rt, gt, bt, at = c1.RGBA()
|
|
||||||
r1, g1, b1, a1 := float64(rt), float64(gt), float64(bt), float64(at)
|
|
||||||
rt, gt, bt, at = c2.RGBA()
|
|
||||||
r2, g2, b2, a2 := float64(rt), float64(gt), float64(bt), float64(at)
|
|
||||||
rt, gt, bt, at = c3.RGBA()
|
|
||||||
r3, g3, b3, a3 := float64(rt), float64(gt), float64(bt), float64(at)
|
|
||||||
r, g, b, a := cubic(dy, r0, r1, r2, r3), cubic(dy, g0, g1, g2, g3), cubic(dy, b0, b1, b2, b3), cubic(dy, a0, a1, a2, a3)
|
|
||||||
return color.RGBA{uint8(r >> 8), uint8(g >> 8), uint8(b >> 8), uint8(a >> 8)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func cubic(offset, v0, v1, v2, v3 float64) uint32 {
|
|
||||||
// offset is the offset of the sampled value between v1 and v2
|
|
||||||
return uint32(((((-7*v0+21*v1-21*v2+7*v3)*offset+
|
|
||||||
(15*v0-36*v1+27*v2-6*v3))*offset+
|
|
||||||
(-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) {
|
|
||||||
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)
|
|
||||||
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
|
|
||||||
var color color.RGBA
|
|
||||||
for x = x0; x < x1; x++ {
|
|
||||||
for y = y0; y < y1; y++ {
|
|
||||||
u = x
|
|
||||||
v = y
|
|
||||||
tr.InverseTransform(&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 {
|
|
||||||
case LinearFilter:
|
|
||||||
c2 = src.At(int(u), int(v))
|
|
||||||
case BilinearFilter:
|
|
||||||
c2 = getColorBilinear(src, u, v)
|
|
||||||
case BicubicFilter:
|
|
||||||
c2 = getColorBicubic(src, u, v)
|
|
||||||
}
|
|
||||||
switch op {
|
|
||||||
case draw.Over:
|
|
||||||
r1, g1, b1, a1 = c1.RGBA()
|
|
||||||
r2, g2, b2, a2 = c2.RGBA()
|
|
||||||
ia = M - a2
|
|
||||||
r = ((r1 * ia) / M) + r2
|
|
||||||
g = ((g1 * ia) / M) + g2
|
|
||||||
b = ((b1 * ia) / M) + b2
|
|
||||||
a = ((a1 * ia) / M) + a2
|
|
||||||
color.R = uint8(r >> 8)
|
|
||||||
color.G = uint8(g >> 8)
|
|
||||||
color.B = uint8(b >> 8)
|
|
||||||
color.A = uint8(a >> 8)
|
|
||||||
cr = color
|
|
||||||
default:
|
|
||||||
cr = c2
|
|
||||||
}
|
|
||||||
dest.Set(int(x), int(y), cr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -19,7 +19,7 @@ import (
|
||||||
function main(){}
|
function main(){}
|
||||||
// Initialize the graphic context on an RGBA image
|
// Initialize the graphic context on an RGBA image
|
||||||
dest := image.NewRGBA(image.Rect(0, 0, 297, 210.0))
|
dest := image.NewRGBA(image.Rect(0, 0, 297, 210.0))
|
||||||
gc := draw2d.NewGraphicContext(dest)
|
gc := draw2dimg.NewGraphicContext(dest)
|
||||||
// Draw Android logo
|
// Draw Android logo
|
||||||
fn, err := android.Main(gc, "png")
|
fn, err := android.Main(gc, "png")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -8,8 +8,9 @@ import (
|
||||||
"image/color"
|
"image/color"
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
"gopkg.in/llgcode/draw2d.v1"
|
"git.fromouter.space/crunchy-rocks/draw2d"
|
||||||
"gopkg.in/llgcode/draw2d.v1/samples"
|
"git.fromouter.space/crunchy-rocks/draw2d/draw2dkit"
|
||||||
|
"git.fromouter.space/crunchy-rocks/draw2d/samples"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Main draws a droid and returns the filename. This should only be
|
// Main draws a droid and returns the filename. This should only be
|
||||||
|
@ -44,32 +45,32 @@ func Draw(gc draw2d.GraphicContext, x, y float64) {
|
||||||
gc.Stroke()
|
gc.Stroke()
|
||||||
|
|
||||||
// left eye
|
// left eye
|
||||||
draw2d.Circle(gc, x+60, y+45, 5)
|
draw2dkit.Circle(gc, x+60, y+45, 5)
|
||||||
gc.FillStroke()
|
gc.FillStroke()
|
||||||
|
|
||||||
// right eye
|
// right eye
|
||||||
draw2d.Circle(gc, x+100, y+45, 5)
|
draw2dkit.Circle(gc, x+100, y+45, 5)
|
||||||
gc.FillStroke()
|
gc.FillStroke()
|
||||||
|
|
||||||
// body
|
// body
|
||||||
draw2d.RoundRect(gc, x+30, y+75, x+30+100, y+75+90, 10, 10)
|
draw2dkit.RoundedRectangle(gc, x+30, y+75, x+30+100, y+75+90, 10, 10)
|
||||||
gc.FillStroke()
|
gc.FillStroke()
|
||||||
draw2d.Rect(gc, x+30, y+75, x+30+100, y+75+80)
|
draw2dkit.Rectangle(gc, x+30, y+75, x+30+100, y+75+80)
|
||||||
gc.FillStroke()
|
gc.FillStroke()
|
||||||
|
|
||||||
// left arm
|
// left arm
|
||||||
draw2d.RoundRect(gc, x+5, y+80, x+5+20, y+80+70, 10, 10)
|
draw2dkit.RoundedRectangle(gc, x+5, y+80, x+5+20, y+80+70, 10, 10)
|
||||||
gc.FillStroke()
|
gc.FillStroke()
|
||||||
|
|
||||||
// right arm
|
// right arm
|
||||||
draw2d.RoundRect(gc, x+135, y+80, x+135+20, y+80+70, 10, 10)
|
draw2dkit.RoundedRectangle(gc, x+135, y+80, x+135+20, y+80+70, 10, 10)
|
||||||
gc.FillStroke()
|
gc.FillStroke()
|
||||||
|
|
||||||
// left leg
|
// left leg
|
||||||
draw2d.RoundRect(gc, x+50, y+150, x+50+20, y+150+50, 10, 10)
|
draw2dkit.RoundedRectangle(gc, x+50, y+150, x+50+20, y+150+50, 10, 10)
|
||||||
gc.FillStroke()
|
gc.FillStroke()
|
||||||
|
|
||||||
// right leg
|
// right leg
|
||||||
draw2d.RoundRect(gc, x+90, y+150, x+90+20, y+150+50, 10, 10)
|
draw2dkit.RoundedRectangle(gc, x+90, y+150, x+90+20, y+150+50, 10, 10)
|
||||||
gc.FillStroke()
|
gc.FillStroke()
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,9 +9,9 @@ import (
|
||||||
"image/png"
|
"image/png"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"gopkg.in/llgcode/draw2d.v1"
|
"git.fromouter.space/crunchy-rocks/draw2d/draw2dimg"
|
||||||
"gopkg.in/llgcode/draw2d.v1/draw2dpdf"
|
"git.fromouter.space/crunchy-rocks/draw2d/draw2dpdf"
|
||||||
"gopkg.in/llgcode/draw2d.v1/samples/android"
|
"git.fromouter.space/crunchy-rocks/draw2d/samples/android"
|
||||||
|
|
||||||
"appengine"
|
"appengine"
|
||||||
)
|
)
|
||||||
|
@ -59,7 +59,7 @@ func imgPng(w http.ResponseWriter, r *http.Request) *appError {
|
||||||
|
|
||||||
// Initialize the graphic context on an RGBA image
|
// Initialize the graphic context on an RGBA image
|
||||||
dest := image.NewRGBA(image.Rect(0, 0, 297, 210.0))
|
dest := image.NewRGBA(image.Rect(0, 0, 297, 210.0))
|
||||||
gc := draw2d.NewGraphicContext(dest)
|
gc := draw2dimg.NewGraphicContext(dest)
|
||||||
|
|
||||||
// Draw sample
|
// Draw sample
|
||||||
android.Draw(gc, 65, 0)
|
android.Draw(gc, 65, 0)
|
||||||
|
|
|
@ -7,8 +7,10 @@ package frameimage
|
||||||
import (
|
import (
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
"gopkg.in/llgcode/draw2d.v1"
|
"git.fromouter.space/crunchy-rocks/draw2d"
|
||||||
"gopkg.in/llgcode/draw2d.v1/samples"
|
"git.fromouter.space/crunchy-rocks/draw2d/draw2dimg"
|
||||||
|
"git.fromouter.space/crunchy-rocks/draw2d/draw2dkit"
|
||||||
|
"git.fromouter.space/crunchy-rocks/draw2d/samples"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Main draws the image frame and returns the filename.
|
// Main draws the image frame and returns the filename.
|
||||||
|
@ -33,13 +35,12 @@ func Main(gc draw2d.GraphicContext, ext string) (string, error) {
|
||||||
func Draw(gc draw2d.GraphicContext, png string,
|
func Draw(gc draw2d.GraphicContext, png string,
|
||||||
dw, dh, margin, lineWidth float64) error {
|
dw, dh, margin, lineWidth float64) error {
|
||||||
// Draw frame
|
// Draw frame
|
||||||
draw2d.RoundRect(gc, lineWidth, lineWidth,
|
draw2dkit.RoundedRectangle(gc, lineWidth, lineWidth, dw-lineWidth, dh-lineWidth, 100, 100)
|
||||||
dw-lineWidth, dh-lineWidth, 100, 100)
|
|
||||||
gc.SetLineWidth(lineWidth)
|
gc.SetLineWidth(lineWidth)
|
||||||
gc.FillStroke()
|
gc.FillStroke()
|
||||||
|
|
||||||
// load the source image
|
// load the source image
|
||||||
source, err := draw2d.LoadFromPngFile(png)
|
source, err := draw2dimg.LoadFromPngFile(png)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,9 +9,10 @@ import (
|
||||||
"image/color"
|
"image/color"
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
"gopkg.in/llgcode/draw2d.v1"
|
"git.fromouter.space/crunchy-rocks/draw2d"
|
||||||
"gopkg.in/llgcode/draw2d.v1/samples"
|
"git.fromouter.space/crunchy-rocks/draw2d/draw2dkit"
|
||||||
"gopkg.in/llgcode/draw2d.v1/samples/gopher2"
|
"git.fromouter.space/crunchy-rocks/draw2d/samples"
|
||||||
|
"git.fromouter.space/crunchy-rocks/draw2d/samples/gopher2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Main draws geometry and returns the filename. This should only be
|
// Main draws geometry and returns the filename. This should only be
|
||||||
|
@ -98,7 +99,7 @@ func Dash(gc draw2d.GraphicContext, x, y, width, height float64) {
|
||||||
gc.MoveTo(x+sx*60.0, y)
|
gc.MoveTo(x+sx*60.0, y)
|
||||||
gc.LineTo(x+sx*60.0, y)
|
gc.LineTo(x+sx*60.0, y)
|
||||||
gc.LineTo(x+sx*162, y+sy*205)
|
gc.LineTo(x+sx*162, y+sy*205)
|
||||||
gc.RLineTo(sx*-102.4, 0.0)
|
rLineTo(gc, sx*-102.4, 0)
|
||||||
gc.CubicCurveTo(x+sx*-17, y+sy*205, x+sx*-17, y+sy*103, x+sx*60.0, y+sy*103.0)
|
gc.CubicCurveTo(x+sx*-17, y+sy*205, x+sx*-17, y+sy*103, x+sx*60.0, y+sy*103.0)
|
||||||
gc.Stroke()
|
gc.Stroke()
|
||||||
gc.SetLineDash(nil, 0.0)
|
gc.SetLineDash(nil, 0.0)
|
||||||
|
@ -188,12 +189,13 @@ func CubicCurve(gc draw2d.GraphicContext, x, y, width, height float64) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// FillString draws a filled and stroked string.
|
// FillString draws a filled and stroked string.
|
||||||
|
// And filles/stroked path created from string. Which may have different - unselectable - output in non raster gc implementations.
|
||||||
func FillString(gc draw2d.GraphicContext, x, y, width, height float64) {
|
func FillString(gc draw2d.GraphicContext, x, y, width, height float64) {
|
||||||
sx, sy := width/100, height/100
|
sx, sy := width/100, height/100
|
||||||
gc.Save()
|
gc.Save()
|
||||||
gc.SetStrokeColor(image.Black)
|
gc.SetStrokeColor(image.Black)
|
||||||
gc.SetLineWidth(1)
|
gc.SetLineWidth(1)
|
||||||
draw2d.RoundRect(gc, x+sx*5, y+sy*5, x+sx*95, y+sy*95, sx*10, sy*10)
|
draw2dkit.RoundedRectangle(gc, x+sx*5, y+sy*5, x+sx*95, y+sy*95, sx*10, sy*10)
|
||||||
gc.FillStroke()
|
gc.FillStroke()
|
||||||
gc.SetFillColor(image.Black)
|
gc.SetFillColor(image.Black)
|
||||||
gc.SetFontSize(height / 6)
|
gc.SetFontSize(height / 6)
|
||||||
|
@ -201,18 +203,27 @@ func FillString(gc draw2d.GraphicContext, x, y, width, height float64) {
|
||||||
gc.SetFontData(draw2d.FontData{
|
gc.SetFontData(draw2d.FontData{
|
||||||
Name: "luxi",
|
Name: "luxi",
|
||||||
Family: draw2d.FontFamilyMono,
|
Family: draw2d.FontFamilyMono,
|
||||||
Style: draw2d.FontStyleBold | draw2d.FontStyleItalic})
|
Style: draw2d.FontStyleBold | draw2d.FontStyleItalic,
|
||||||
|
})
|
||||||
w := gc.FillString("Hug")
|
w := gc.FillString("Hug")
|
||||||
gc.Translate(w+sx, 0)
|
gc.Translate(w+sx, 0)
|
||||||
left, top, right, bottom := gc.GetStringBounds("cou")
|
left, top, right, bottom := gc.GetStringBounds("cou")
|
||||||
gc.SetStrokeColor(color.NRGBA{255, 0x33, 0x33, 0x80})
|
gc.SetStrokeColor(color.NRGBA{255, 0x33, 0x33, 0x80})
|
||||||
draw2d.Rect(gc, left, top, right, bottom)
|
draw2dkit.Rectangle(gc, left, top, right, bottom)
|
||||||
gc.SetLineWidth(height / 50)
|
gc.SetLineWidth(height / 50)
|
||||||
gc.Stroke()
|
gc.Stroke()
|
||||||
gc.SetFillColor(color.NRGBA{0x33, 0x33, 0xff, 0xff})
|
gc.SetFillColor(color.NRGBA{0x33, 0x33, 0xff, 0xff})
|
||||||
gc.SetStrokeColor(color.NRGBA{0x33, 0x33, 0xff, 0xff})
|
gc.SetStrokeColor(color.NRGBA{0x33, 0x33, 0xff, 0xff})
|
||||||
gc.SetLineWidth(height / 100)
|
gc.SetLineWidth(height / 100)
|
||||||
gc.StrokeString("Hug")
|
gc.StrokeString("Hug")
|
||||||
|
|
||||||
|
gc.Translate(-(w + sx), sy*24)
|
||||||
|
w = gc.CreateStringPath("Hug", 0, 0)
|
||||||
|
gc.Fill()
|
||||||
|
gc.Translate(w+sx, 0)
|
||||||
|
gc.CreateStringPath("Hug", 0, 0)
|
||||||
|
path := gc.GetPath()
|
||||||
|
gc.Stroke((&path).VerticalFlip())
|
||||||
gc.Restore()
|
gc.Restore()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -221,14 +232,14 @@ func FillStroke(gc draw2d.GraphicContext, x, y, width, height float64) {
|
||||||
sx, sy := width/210, height/215
|
sx, sy := width/210, height/215
|
||||||
gc.MoveTo(x+sx*113.0, y)
|
gc.MoveTo(x+sx*113.0, y)
|
||||||
gc.LineTo(x+sx*215.0, y+sy*215)
|
gc.LineTo(x+sx*215.0, y+sy*215)
|
||||||
gc.RLineTo(sx*-100, 0)
|
rLineTo(gc, sx*-100, 0)
|
||||||
gc.CubicCurveTo(x+sx*35, y+sy*215, x+sx*35, y+sy*113, x+sx*113.0, y+sy*113)
|
gc.CubicCurveTo(x+sx*35, y+sy*215, x+sx*35, y+sy*113, x+sx*113.0, y+sy*113)
|
||||||
gc.Close()
|
gc.Close()
|
||||||
|
|
||||||
gc.MoveTo(x+sx*50.0, y)
|
gc.MoveTo(x+sx*50.0, y)
|
||||||
gc.RLineTo(sx*51.2, sy*51.2)
|
rLineTo(gc, sx*51.2, sy*51.2)
|
||||||
gc.RLineTo(sx*-51.2, sy*51.2)
|
rLineTo(gc, sx*-51.2, sy*51.2)
|
||||||
gc.RLineTo(sx*-51.2, sy*-51.2)
|
rLineTo(gc, sx*-51.2, sy*-51.2)
|
||||||
gc.Close()
|
gc.Close()
|
||||||
|
|
||||||
gc.SetLineWidth(width / 20.0)
|
gc.SetLineWidth(width / 20.0)
|
||||||
|
@ -237,33 +248,37 @@ func FillStroke(gc draw2d.GraphicContext, x, y, width, height float64) {
|
||||||
gc.FillStroke()
|
gc.FillStroke()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func rLineTo(path draw2d.PathBuilder, x, y float64) {
|
||||||
|
x0, y0 := path.LastPoint()
|
||||||
|
path.LineTo(x0+x, y0+y)
|
||||||
|
}
|
||||||
|
|
||||||
// FillStyle demonstrates the difference between even odd and non zero winding rule.
|
// FillStyle demonstrates the difference between even odd and non zero winding rule.
|
||||||
func FillStyle(gc draw2d.GraphicContext, x, y, width, height float64) {
|
func FillStyle(gc draw2d.GraphicContext, x, y, width, height float64) {
|
||||||
sx, sy := width/232, height/220
|
sx, sy := width/232, height/220
|
||||||
gc.SetLineWidth(width / 40)
|
gc.SetLineWidth(width / 40)
|
||||||
|
|
||||||
draw2d.Rect(gc, x+sx*0, y+sy*12, x+sx*232, y+sy*70)
|
draw2dkit.Rectangle(gc, x+sx*0, y+sy*12, x+sx*232, y+sy*70)
|
||||||
|
|
||||||
wheel1 := new(draw2d.PathStorage)
|
var wheel1, wheel2 draw2d.Path
|
||||||
wheel1.ArcTo(x+sx*52, y+sy*70, sx*40, sy*40, 0, 2*math.Pi)
|
wheel1.ArcTo(x+sx*52, y+sy*70, sx*40, sy*40, 0, 2*math.Pi)
|
||||||
wheel2 := new(draw2d.PathStorage)
|
|
||||||
wheel2.ArcTo(x+sx*180, y+sy*70, sx*40, sy*40, 0, -2*math.Pi)
|
wheel2.ArcTo(x+sx*180, y+sy*70, sx*40, sy*40, 0, -2*math.Pi)
|
||||||
|
|
||||||
gc.SetFillRule(draw2d.FillRuleEvenOdd)
|
gc.SetFillRule(draw2d.FillRuleEvenOdd)
|
||||||
gc.SetFillColor(color.NRGBA{0, 0xB2, 0, 0xFF})
|
gc.SetFillColor(color.NRGBA{0, 0xB2, 0, 0xFF})
|
||||||
|
|
||||||
gc.SetStrokeColor(image.Black)
|
gc.SetStrokeColor(image.Black)
|
||||||
gc.FillStroke(wheel1, wheel2)
|
gc.FillStroke(&wheel1, &wheel2)
|
||||||
|
|
||||||
draw2d.Rect(gc, x, y+sy*140, x+sx*232, y+sy*198)
|
draw2dkit.Rectangle(gc, x, y+sy*140, x+sx*232, y+sy*198)
|
||||||
wheel1 = new(draw2d.PathStorage)
|
wheel1.Clear()
|
||||||
wheel1.ArcTo(x+sx*52, y+sy*198, sx*40, sy*40, 0, 2*math.Pi)
|
wheel1.ArcTo(x+sx*52, y+sy*198, sx*40, sy*40, 0, 2*math.Pi)
|
||||||
wheel2 = new(draw2d.PathStorage)
|
wheel2.Clear()
|
||||||
wheel2.ArcTo(x+sx*180, y+sy*198, sx*40, sy*40, 0, -2*math.Pi)
|
wheel2.ArcTo(x+sx*180, y+sy*198, sx*40, sy*40, 0, -2*math.Pi)
|
||||||
|
|
||||||
gc.SetFillRule(draw2d.FillRuleWinding)
|
gc.SetFillRule(draw2d.FillRuleWinding)
|
||||||
gc.SetFillColor(color.NRGBA{0, 0, 0xE5, 0xFF})
|
gc.SetFillColor(color.NRGBA{0, 0, 0xE5, 0xFF})
|
||||||
gc.FillStroke(wheel1, wheel2)
|
gc.FillStroke(&wheel1, &wheel2)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PathTransform scales a path differently in horizontal and vertical direction.
|
// PathTransform scales a path differently in horizontal and vertical direction.
|
||||||
|
|
|
@ -8,8 +8,8 @@ package gopher
|
||||||
import (
|
import (
|
||||||
"image/color"
|
"image/color"
|
||||||
|
|
||||||
"gopkg.in/llgcode/draw2d.v1"
|
"git.fromouter.space/crunchy-rocks/draw2d"
|
||||||
"gopkg.in/llgcode/draw2d.v1/samples"
|
"git.fromouter.space/crunchy-rocks/draw2d/samples"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Main draws a left hand and ear of a gopher. Afterwards it returns
|
// Main draws a left hand and ear of a gopher. Afterwards it returns
|
||||||
|
@ -39,17 +39,17 @@ func Draw(gc draw2d.GraphicContext) {
|
||||||
// c0.389-6.064,1.088-12.128,0.744-18.216c-10.23-0.927-21.357,1.509-29.744,7.602C10.277,286.542,2.177,296.561,10.634,300.493"/>
|
// c0.389-6.064,1.088-12.128,0.744-18.216c-10.23-0.927-21.357,1.509-29.744,7.602C10.277,286.542,2.177,296.561,10.634,300.493"/>
|
||||||
gc.SetFillColor(color.RGBA{0xF6, 0xD2, 0xA2, 0xff})
|
gc.SetFillColor(color.RGBA{0xF6, 0xD2, 0xA2, 0xff})
|
||||||
gc.MoveTo(10.634, 300.493)
|
gc.MoveTo(10.634, 300.493)
|
||||||
gc.RCubicCurveTo(0.764, 15.751, 16.499, 8.463, 23.626, 3.539)
|
rCubicCurveTo(gc, 0.764, 15.751, 16.499, 8.463, 23.626, 3.539)
|
||||||
gc.RCubicCurveTo(6.765, -4.675, 8.743, -0.789, 9.337, -10.015)
|
rCubicCurveTo(gc, 6.765, -4.675, 8.743, -0.789, 9.337, -10.015)
|
||||||
gc.RCubicCurveTo(0.389, -6.064, 1.088, -12.128, 0.744, -18.216)
|
rCubicCurveTo(gc, 0.389, -6.064, 1.088, -12.128, 0.744, -18.216)
|
||||||
gc.RCubicCurveTo(-10.23, -0.927, -21.357, 1.509, -29.744, 7.602)
|
rCubicCurveTo(gc, -10.23, -0.927, -21.357, 1.509, -29.744, 7.602)
|
||||||
gc.CubicCurveTo(10.277, 286.542, 2.177, 296.561, 10.634, 300.493)
|
gc.CubicCurveTo(10.277, 286.542, 2.177, 296.561, 10.634, 300.493)
|
||||||
gc.FillStroke()
|
gc.FillStroke()
|
||||||
|
|
||||||
// <path fill-rule="evenodd" clip-rule="evenodd" fill="#C6B198" stroke="#000000" stroke-width="3" stroke-linecap="round" d="
|
// <path fill-rule="evenodd" clip-rule="evenodd" fill="#C6B198" stroke="#000000" stroke-width="3" stroke-linecap="round" d="
|
||||||
// M10.634,300.493c2.29-0.852,4.717-1.457,6.271-3.528"/>
|
// M10.634,300.493c2.29-0.852,4.717-1.457,6.271-3.528"/>
|
||||||
gc.MoveTo(10.634, 300.493)
|
gc.MoveTo(10.634, 300.493)
|
||||||
gc.RCubicCurveTo(2.29, -0.852, 4.717, -1.457, 6.271, -3.528)
|
rCubicCurveTo(gc, 2.29, -0.852, 4.717, -1.457, 6.271, -3.528)
|
||||||
gc.Stroke()
|
gc.Stroke()
|
||||||
|
|
||||||
// Left Ear
|
// Left Ear
|
||||||
|
@ -61,3 +61,13 @@ func Draw(gc draw2d.GraphicContext) {
|
||||||
gc.Close()
|
gc.Close()
|
||||||
gc.Stroke()
|
gc.Stroke()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func rQuadCurveTo(path draw2d.PathBuilder, dcx, dcy, dx, dy float64) {
|
||||||
|
x, y := path.LastPoint()
|
||||||
|
path.QuadCurveTo(x+dcx, y+dcy, x+dx, y+dy)
|
||||||
|
}
|
||||||
|
|
||||||
|
func rCubicCurveTo(path draw2d.PathBuilder, dcx1, dcy1, dcx2, dcy2, dx, dy float64) {
|
||||||
|
x, y := path.LastPoint()
|
||||||
|
path.CubicCurveTo(x+dcx1, y+dcy1, x+dcx2, y+dcy2, x+dx, y+dy)
|
||||||
|
}
|
||||||
|
|
|
@ -10,8 +10,9 @@ import (
|
||||||
"image/color"
|
"image/color"
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
"gopkg.in/llgcode/draw2d.v1"
|
"git.fromouter.space/crunchy-rocks/draw2d"
|
||||||
"gopkg.in/llgcode/draw2d.v1/samples"
|
"git.fromouter.space/crunchy-rocks/draw2d/draw2dkit"
|
||||||
|
"git.fromouter.space/crunchy-rocks/draw2d/samples"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Main draws a rotated face of the gopher. Afterwards it returns
|
// Main draws a rotated face of the gopher. Afterwards it returns
|
||||||
|
@ -46,61 +47,76 @@ func Draw(gc draw2d.GraphicContext, x, y, w, h float64) {
|
||||||
gc.Close()
|
gc.Close()
|
||||||
gc.SetFillColor(brb)
|
gc.SetFillColor(brb)
|
||||||
gc.Fill()
|
gc.Fill()
|
||||||
|
|
||||||
// rectangle head bottom
|
// rectangle head bottom
|
||||||
draw2d.RoundRect(gc, x, y+h, x+w, y+h+h, h/5, h/5)
|
draw2dkit.RoundedRectangle(gc, x, y+h, x+w, y+h+h, h/5, h/5)
|
||||||
gc.Fill()
|
gc.Fill()
|
||||||
|
|
||||||
// left ear outside
|
// left ear outside
|
||||||
draw2d.Circle(gc, x, y+h, w/12)
|
draw2dkit.Circle(gc, x, y+h, w/12)
|
||||||
gc.SetFillColor(brf)
|
gc.SetFillColor(brf)
|
||||||
gc.Fill()
|
gc.Fill()
|
||||||
|
|
||||||
// left ear inside
|
// left ear inside
|
||||||
draw2d.Circle(gc, x, y+h, 0.5*w/12)
|
draw2dkit.Circle(gc, x, y+h, 0.5*w/12)
|
||||||
gc.SetFillColor(nf)
|
gc.SetFillColor(nf)
|
||||||
gc.Fill()
|
gc.Fill()
|
||||||
|
|
||||||
// right ear outside
|
// right ear outside
|
||||||
draw2d.Circle(gc, x+w, y+h, w/12)
|
draw2dkit.Circle(gc, x+w, y+h, w/12)
|
||||||
gc.SetFillColor(brf)
|
gc.SetFillColor(brf)
|
||||||
gc.Fill()
|
gc.Fill()
|
||||||
|
|
||||||
// right ear inside
|
// right ear inside
|
||||||
draw2d.Circle(gc, x+w, y+h, 0.5*w/12)
|
draw2dkit.Circle(gc, x+w, y+h, 0.5*w/12)
|
||||||
gc.SetFillColor(nf)
|
gc.SetFillColor(nf)
|
||||||
gc.Fill()
|
gc.Fill()
|
||||||
|
|
||||||
// left eye outside white
|
// left eye outside white
|
||||||
draw2d.Circle(gc, x+w/3, y+h23, w/9)
|
draw2dkit.Circle(gc, x+w/3, y+h23, w/9)
|
||||||
gc.SetFillColor(wf)
|
gc.SetFillColor(wf)
|
||||||
gc.Fill()
|
gc.Fill()
|
||||||
|
|
||||||
// left eye black
|
// left eye black
|
||||||
draw2d.Circle(gc, x+w/3+w/24, y+h23, 0.5*w/9)
|
draw2dkit.Circle(gc, x+w/3+w/24, y+h23, 0.5*w/9)
|
||||||
gc.SetFillColor(blf)
|
gc.SetFillColor(blf)
|
||||||
gc.Fill()
|
gc.Fill()
|
||||||
|
|
||||||
// left eye inside white
|
// left eye inside white
|
||||||
draw2d.Circle(gc, x+w/3+w/24+w/48, y+h23, 0.2*w/9)
|
draw2dkit.Circle(gc, x+w/3+w/24+w/48, y+h23, 0.2*w/9)
|
||||||
gc.SetFillColor(wf)
|
gc.SetFillColor(wf)
|
||||||
gc.Fill()
|
gc.Fill()
|
||||||
|
|
||||||
// right eye outside white
|
// right eye outside white
|
||||||
draw2d.Circle(gc, x+w-w/3, y+h23, w/9)
|
draw2dkit.Circle(gc, x+w-w/3, y+h23, w/9)
|
||||||
gc.Fill()
|
gc.Fill()
|
||||||
|
|
||||||
// right eye black
|
// right eye black
|
||||||
draw2d.Circle(gc, x+w-w/3+w/24, y+h23, 0.5*w/9)
|
draw2dkit.Circle(gc, x+w-w/3+w/24, y+h23, 0.5*w/9)
|
||||||
gc.SetFillColor(blf)
|
gc.SetFillColor(blf)
|
||||||
gc.Fill()
|
gc.Fill()
|
||||||
|
|
||||||
// right eye inside white
|
// right eye inside white
|
||||||
draw2d.Circle(gc, x+w-(w/3)+w/24+w/48, y+h23, 0.2*w/9)
|
draw2dkit.Circle(gc, x+w-(w/3)+w/24+w/48, y+h23, 0.2*w/9)
|
||||||
gc.SetFillColor(wf)
|
gc.SetFillColor(wf)
|
||||||
gc.Fill()
|
gc.Fill()
|
||||||
|
|
||||||
// left tooth
|
// left tooth
|
||||||
gc.SetFillColor(wf)
|
gc.SetFillColor(wf)
|
||||||
draw2d.RoundRect(gc, x+w/2-w/8, y+h+h/2.5, x+w/2-w/8+w/8, y+h+h/2.5+w/6, w/10, w/10)
|
draw2dkit.RoundedRectangle(gc, x+w/2-w/8, y+h+h/2.5, x+w/2-w/8+w/8, y+h+h/2.5+w/6, w/10, w/10)
|
||||||
gc.Fill()
|
gc.Fill()
|
||||||
|
|
||||||
// right tooth
|
// right tooth
|
||||||
draw2d.RoundRect(gc, x+w/2, y+h+h/2.5, x+w/2+w/8, y+h+h/2.5+w/6, w/10, w/10)
|
draw2dkit.RoundedRectangle(gc, x+w/2, y+h+h/2.5, x+w/2+w/8, y+h+h/2.5+w/6, w/10, w/10)
|
||||||
gc.Fill()
|
gc.Fill()
|
||||||
|
|
||||||
// snout
|
// snout
|
||||||
draw2d.Ellipse(gc, x+(w/2), y+h+h/2.5, w/6, w/12)
|
draw2dkit.Ellipse(gc, x+(w/2), y+h+h/2.5, w/6, w/12)
|
||||||
gc.SetFillColor(nf)
|
gc.SetFillColor(nf)
|
||||||
gc.Fill()
|
gc.Fill()
|
||||||
|
|
||||||
// nose
|
// nose
|
||||||
draw2d.Ellipse(gc, x+(w/2), y+h+h/7, w/10, w/12)
|
draw2dkit.Ellipse(gc, x+(w/2), y+h+h/7, w/10, w/12)
|
||||||
gc.SetFillColor(blf)
|
gc.SetFillColor(blf)
|
||||||
gc.Fill()
|
gc.Fill()
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,11 +8,10 @@ package helloworld
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
"image/color"
|
|
||||||
"math"
|
|
||||||
|
|
||||||
"gopkg.in/llgcode/draw2d.v1"
|
"git.fromouter.space/crunchy-rocks/draw2d"
|
||||||
"gopkg.in/llgcode/draw2d.v1/samples"
|
"git.fromouter.space/crunchy-rocks/draw2d/draw2dkit"
|
||||||
|
"git.fromouter.space/crunchy-rocks/draw2d/samples"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Main draws "Hello World" and returns the filename. This should only be
|
// Main draws "Hello World" and returns the filename. This should only be
|
||||||
|
@ -28,35 +27,14 @@ func Main(gc draw2d.GraphicContext, ext string) (string, error) {
|
||||||
// Draw "Hello World"
|
// Draw "Hello World"
|
||||||
func Draw(gc draw2d.GraphicContext, text string) {
|
func Draw(gc draw2d.GraphicContext, text string) {
|
||||||
// Draw a rounded rectangle using default colors
|
// Draw a rounded rectangle using default colors
|
||||||
draw2d.RoundRect(gc, 5, 5, 292, 205, 10, 10)
|
draw2dkit.RoundedRectangle(gc, 5, 5, 135, 95, 10, 10)
|
||||||
gc.FillStroke()
|
gc.FillStroke()
|
||||||
|
|
||||||
// Set the font luximbi.ttf
|
// Set the font luximbi.ttf
|
||||||
gc.SetFontData(draw2d.FontData{
|
gc.SetFontData(draw2d.FontData{Name: "luxi", Family: draw2d.FontFamilyMono, Style: draw2d.FontStyleBold | draw2d.FontStyleItalic})
|
||||||
Name: "luxi",
|
|
||||||
Family: draw2d.FontFamilyMono,
|
|
||||||
Style: draw2d.FontStyleBold | draw2d.FontStyleItalic})
|
|
||||||
// Set the fill text color to black
|
// Set the fill text color to black
|
||||||
gc.SetFillColor(image.Black)
|
gc.SetFillColor(image.Black)
|
||||||
gc.SetDPI(72)
|
|
||||||
gc.SetFontSize(14)
|
gc.SetFontSize(14)
|
||||||
// Display Hello World
|
// Display Hello World
|
||||||
gc.SetStrokeColor(color.NRGBA{0x33, 0xFF, 0x33, 0xFF})
|
gc.FillStringAt("Hello World", 8, 52)
|
||||||
gc.MoveTo(8, 0)
|
|
||||||
gc.LineTo(8, 52)
|
|
||||||
gc.LineTo(297, 52)
|
|
||||||
gc.Stroke()
|
|
||||||
gc.FillString(text)
|
|
||||||
gc.FillStringAt(text, 8, 52)
|
|
||||||
|
|
||||||
gc.Save()
|
|
||||||
gc.SetFillColor(color.NRGBA{0xFF, 0x33, 0x33, 0xFF})
|
|
||||||
gc.SetStrokeColor(color.NRGBA{0xFF, 0x33, 0x33, 0xFF})
|
|
||||||
gc.Translate(145, 85)
|
|
||||||
gc.StrokeStringAt(text, -50, 0)
|
|
||||||
gc.Rotate(math.Pi / 4)
|
|
||||||
gc.SetFillColor(color.NRGBA{0x33, 0x33, 0xFF, 0xFF})
|
|
||||||
gc.SetStrokeColor(color.NRGBA{0x33, 0x33, 0xFF, 0xFF})
|
|
||||||
gc.StrokeString(text)
|
|
||||||
gc.Restore()
|
|
||||||
}
|
}
|
||||||
|
|