Compare commits
337 Commits
Author | SHA1 | Date |
---|---|---|
Hamcha | 675e82cb64 | |
Hamcha | ff82ab9451 | |
Hamcha | a7539fd29b | |
Hamcha | 41b8d7304e | |
Hamcha | 9dbc14edd6 | |
Hamcha | 9941d77460 | |
Hamcha | e3566f7fc4 | |
Hamcha | 219501b99b | |
llgcode | f52c8a71af | |
Piotr Kowalczuk | bdf3a69827 | |
llgcode | 587a55234c | |
Sebastien Binet | 94de6e33b6 | |
Sebastien Binet | cd0433711b | |
llgcode | 274031cf2a | |
llgcode | bc151d5e2c | |
Drahoslav Bednář | 72e6a3c750 | |
Drahoslav | 0b72959009 | |
Drahoslav | 7419075cb6 | |
Drahoslav | 1588b49f0d | |
llgcode | 50aafedab4 | |
Drahoslav | 99cc16d0ac | |
Drahoslav | 1b49270d08 | |
Drahoslav | c1e5edea41 | |
Drahoslav | 6f03f106f6 | |
Drahoslav | 3af25f5588 | |
Drahoslav | 90f962641f | |
Drahoslav | 215a761ccb | |
Drahoslav | 6d31bfac59 | |
Drahoslav | d297a025cd | |
Drahoslav | 484fe1caef | |
Drahoslav | 0b3b26d85f | |
Drahoslav | 41d8a21ba2 | |
Drahoslav | 6c0a15c624 | |
Drahoslav | ca83e24222 | |
Drahoslav | bd7567e331 | |
Drahoslav | cdf301b7be | |
Drahoslav | 295a8365b3 | |
Drahoslav | 96883adea4 | |
llgcode | c41aa97d30 | |
llgcode | 647da9ceaa | |
llgcode | f3e35015aa | |
llgcode | b81f74eb39 | |
llgcode | a5f7ac8ebe | |
llgcode | 8167230c09 | |
llgcode | 3e4c36c4c9 | |
llgcode | 4cdcb11e52 | |
llgcode | e4816c5375 | |
llgcode | dd69e0c822 | |
llgcode | dcbfbe505d | |
André Hänsel | 1f71aa3f15 | |
gerald1248 | 0cf6b8d61f | |
gerald1248 | 7c57ea38bb | |
gerald1248 | eca7b76ebc | |
gerald1248 | dfbef878aa | |
llgcode | 1286d3b203 | |
redstarcoder | c12070824c | |
llgcode | 0d961cd299 | |
redstarcoder | 7cc6abeee3 | |
llgcode | 401ee667f2 | |
redstarcoder | c2920005d6 | |
redstarcoder | c2851a6eb6 | |
redstarcoder | 3a5a1d8830 | |
redstarcoder | b9005c988d | |
redstarcoder | 4a3322e29e | |
redstarcoder | 8380dd9458 | |
llgcode | 51ba099819 | |
llgcode | a6ceba03c8 | |
redstarcoder | 475a830567 | |
redstarcoder | 105a963210 | |
llgcode | 13548be874 | |
kortschak | e0e534f3a5 | |
llgcode | 155ff5c755 | |
Laurent Le Goff | 3f01cfe277 | |
llgcode | 5e675a3055 | |
Zdenek Styblik | 3bb234e85b | |
llgcode | 0545b30698 | |
Laurent Le Goff | f444aacdd7 | |
Chris Rhodes | 11b6fa221b | |
Chris Rhodes | 001a24bc17 | |
Achille Roussel | 6c047429f6 | |
Achille Roussel | 598513aa60 | |
Laurent Le Goff | 9ffe0e7eb5 | |
llgcode | 56180d8101 | |
Laurent Le Goff | d6d74f19f9 | |
Andrew Brampton | 7b20985151 | |
Laurent Le Goff | 835d17ca7c | |
Laurent Le Goff | caad194462 | |
Laurent Le Goff | 96d42f14c0 | |
Laurent Le Goff | 35dcbff3f7 | |
Laurent Le Goff | 37f345f4d3 | |
Andrew Brampton | c378327bfa | |
Laurent Le Goff | 7510d72d52 | |
Laurent Le Goff | 48a313740b | |
Laurent Le Goff | 82a7e1e58e | |
Laurent Le Goff | c8d67448a9 | |
Laurent Le Goff | 094e39780f | |
Laurent Le Goff | ad6b615bc4 | |
Steven Edwards | 0e0aa125a3 | |
Laurent Le Goff | bf42fff416 | |
Laurent Le Goff | ed44998c46 | |
Steven Edwards | 1a2db78d7b | |
Laurent Le Goff | 755362132b | |
Steven Edwards | a238a47879 | |
Laurent Le Goff | b07a8ba2e0 | |
Laurent Le Goff | 93c5712ecc | |
Laurent Le Goff | 7e94968f5e | |
Laurent Le Goff | 1e0467b8fc | |
Laurent Le Goff | 3b19ab855e | |
Laurent Le Goff | 9b55e34990 | |
Laurent Le Goff | 7ef94ce784 | |
Laurent Le Goff | ce9c7f76f7 | |
Steven Edwards | a78b24d408 | |
Steven Edwards | be5a5617ef | |
Steven Edwards | d0cd538261 | |
Stani | 994e11cd6d | |
Stani | c9398530eb | |
Stani | 27ca8d66a7 | |
Stani | 64dc974458 | |
Stani | b1e17999b6 | |
Stani | fcd849f2e4 | |
Stani | 3450d01825 | |
Stani | 6b72c6dec7 | |
Stani | bc823442ba | |
Stani | 1f0b304e2e | |
Stani | bbb2a4b372 | |
Stani | 88ee58870c | |
Stani | 9bd0ecf14e | |
Stani | 316bf1b039 | |
Stani | 339f012445 | |
Stani | b4e9f9c266 | |
Stani | da9799659c | |
Stani | 5d7c08c52f | |
Stani | 6e58827921 | |
Stani | ce7f6b422e | |
Stani | 8d08b5e816 | |
Stani | a819f6b102 | |
Stani | 2099d15a26 | |
Stani | 790eaf6842 | |
Stani | d3a9977ae5 | |
Stani | a5918dc963 | |
Stani | 99aee45045 | |
Stani | 16a86e7588 | |
Stani | 224b85d99a | |
Stani | 99f1fece69 | |
Stani | a43544c31d | |
Stani | 6e1b43c32f | |
Stani | 743e113349 | |
Stani | 6a06be3c7c | |
Stani | 08a6c87a0b | |
Stani | 781a0defe5 | |
Stani | a826fc7216 | |
Stani | bb523db1f4 | |
Stani | f879ad32a7 | |
Stani | fbb061ceef | |
Stani | d00980730f | |
Stani | 55b5c931cf | |
Stani | fe06e5046c | |
Stani | 1da2ef6b1e | |
Stani | 572711ae7e | |
Stani | d4e1581526 | |
Stani | bb793d237f | |
Stani | fb6189e246 | |
Stani | 72c9be4ed0 | |
Stani | 089f4efae2 | |
Stani | 202ecbdf85 | |
Stani | bf2e1c0174 | |
Stani | 195892ccb9 | |
Stani | 61b036038c | |
Stani | bdd6cf67be | |
Stani | 730589cb95 | |
Stani | cec365b96a | |
Stani | a6fc7c06e1 | |
Stani | dc12ec8ca5 | |
Stani | aee8e42541 | |
Stani | f24cc0d0ec | |
Stani | 18946de153 | |
Stani | bdf30d74eb | |
Stani | 401ff36d5a | |
Stani | 14bf3f5052 | |
Stani | 0c99623341 | |
Stani | 851731f191 | |
Stani | 51d0dc23f4 | |
Stani | 7e64fd0a68 | |
Stani | b1417d86fa | |
Stani | 96c95d0346 | |
Stani | 10796a3100 | |
Stani | f5ef2c0154 | |
Stani | 903829eb54 | |
Stani | 990ae8fd84 | |
Laurent Le Goff | 04427cabf5 | |
Laurent Le Goff | ed5fa0e5a7 | |
Stani | 7749b30624 | |
Laurent Le Goff | 91a9aff94d | |
Stani | 4c03f51911 | |
Stani | 33f067e4dd | |
Stani | 437a88ccda | |
Stani | 62e23c1693 | |
Stani | dbf591ab97 | |
Stani | 8c8e0b1760 | |
Stani | d446bebfa1 | |
Stani | 67213e40fc | |
Stani | 8c807d1289 | |
Stani | cf81b0b120 | |
Stani | e94fab8197 | |
Laurent Le Goff | 7a817d6c88 | |
Stani | dbdf3e22bc | |
Stani | dbd88a6569 | |
Stani | 96e73eab52 | |
Stani | 8ca9a0bbe3 | |
Stani | 1841a3e90f | |
Stani | 4262ff5718 | |
Stani | 99dce02263 | |
Stani | a861276f40 | |
Stani | 8e69b2d8d6 | |
Stani | 19b20e1fba | |
Stani | f6e57f4712 | |
Stani | 71c1aba4fe | |
Stani | 0144ae516b | |
Stani | 225f790d40 | |
Stani | 0acdcf4fff | |
Stani | 39bb5b6cf6 | |
Stani | 5e8829cc26 | |
Stani | a3866cec52 | |
Stani | 2f2bd1937a | |
Stani | 6608ccca67 | |
Stani | b3bea4f206 | |
Stani | 458c46b28c | |
Stani | 4d393c1035 | |
Stani | 244893c6ab | |
Stani | be91a632c9 | |
Stani | 13f1147b90 | |
Stani | 2905171a22 | |
Stani | 01150ac981 | |
Stani | eee9c42bb9 | |
Stani | 5a3d7085a2 | |
Stani | 7ffd99f6bd | |
Stani | 66b0d91546 | |
Stani | 464bc2488b | |
Stani | d140c26465 | |
Stani | 5d1b0f3315 | |
Stani | 0d629a4957 | |
Stani | 74e84c4493 | |
Stani | bbcbc3df5e | |
Stani | a422b2462d | |
Stani | d47e08f7c9 | |
Laurent Le Goff | b14683a552 | |
Laurent Le Goff | f6e1ada0f2 | |
Laurent Le Goff | f2563306e4 | |
Laurent Le Goff | 72643a28b2 | |
Laurent Le Goff | 383fef0d7d | |
Laurent Le Goff | 0345095002 | |
Laurent Le Goff | c7ef18681a | |
Laurent Le Goff | c686a7fcf6 | |
Laurent Le Goff | cf01bf3026 | |
Laurent Le Goff | 94ef483cbd | |
Laurent Le Goff | 9012e5e580 | |
Laurent Le Goff | 47f90d3414 | |
Laurent Le Goff | 74e6b9b1ec | |
Laurent Le Goff | 82ef300f1d | |
Laurent Le Goff | ee83fedb10 | |
Laurent Le Goff | 409365e40f | |
Laurent Le Goff | 511954196b | |
Laurent Le Goff | 966a9b73f7 | |
Laurent Le Goff | 24d62b9aa7 | |
Laurent Le Goff | ce6fbe94f3 | |
Laurent Le Goff | 79f25c1ea2 | |
Laurent Le Goff | 0b7a049f3e | |
Laurent Le Goff | 1d191b3eaf | |
Laurent Le Goff | 61a6e03fdb | |
Laurent Le Goff | d6812fd8e6 | |
Laurent Le Goff | 06178b5d2d | |
Laurent Le Goff | fef7265145 | |
Laurent Le Goff | 41809b9132 | |
Laurent Le Goff | 565dfa9eb9 | |
Laurent Le Goff | 5df1705bb4 | |
Laurent Le Goff | 42d0eb260f | |
Laurent Le Goff | 4fa829a373 | |
Laurent Le Goff | ceb331894d | |
Laurent Le Goff | 4b3ba53f4c | |
Laurent Le Goff | 216d3f60dd | |
Laurent Le Goff | f8fb5a2052 | |
Laurent Le Goff | f43b901cb4 | |
Laurent Le Goff | d41211e1f1 | |
Laurent Le Goff | cfe27c6c0a | |
Laurent Le Goff | 26fd99f263 | |
Laurent Le Goff | 146e6d6c8e | |
Laurent Le Goff | 93abbc2231 | |
Laurent Le Goff | 78e0449cca | |
Laurent Le Goff | b7cc153384 | |
Laurent Le Goff | 5908f1da11 | |
Laurent Le Goff | 17aa105606 | |
Laurent Le Goff | a211e06ea7 | |
Laurent Le Goff | cef48beb1f | |
Laurent Le Goff | fff6b05c93 | |
Laurent Le Goff | b4f19d35fe | |
Laurent Le Goff | 870a1a827e | |
Laurent Le Goff | 690ce767e2 | |
Sebastien Binet | f1d2545bc5 | |
Sebastien Binet | 2e7baa0204 | |
Laurent Le Goff | 8b0d5d5048 | |
Laurent Le Goff | 5990ba619c | |
Laurent Le Goff | 0bbef16af2 | |
Laurent Le Goff | dba783c8af | |
Laurent Le Goff | 0abcba8669 | |
Laurent Le Goff | b1b8ea1540 | |
Laurent Le Goff | 6857de8d5b | |
Sebastien Binet | 0eeb62825f | |
Sebastien Binet | ef603db535 | |
Sebastien Binet | c1b7f443b4 | |
Sebastien Binet | a8154b175c | |
Laurent Le Goff | 3d2a09c9e2 | |
Sebastien Binet | 49e09051b1 | |
Sebastien Binet | 983b7fb4a9 | |
Sebastien Binet | f78ac58522 | |
Sebastien Binet | 7e968a713e | |
Laurent Le Goff | 91cca64ef9 | |
Laurent Le Goff | 234df7b8d1 | |
legoff laurent | 90b71903ae | |
Laurent Le Goff | 79dabfe7e3 | |
Jonathan Feinberg | 4cfd6dfa78 | |
Jonathan Feinberg | 80a6c6c182 | |
Jonathan Feinberg | 19b0ec55b5 | |
Laurent Le Goff | b864adcd21 | |
Laurent Le Goff | fb2948a31e | |
Laurent Le Goff | f7dc650faa | |
Laurent Le Goff | fda5c8e713 | |
Laurent Le Goff | 041bb7cbbb | |
Laurent Le Goff | 492dcdf86f | |
Laurent Le Goff | cf94206931 | |
Laurent Le Goff | 489c6261be | |
Laurent Le Goff | 51a750535c | |
Laurent Le Goff | 6d19118a1c | |
Laurent Le Goff | 7f2c247e7a | |
Laurent Le Goff | c595982fba | |
Laurent Le Goff | 72c07eba44 | |
legoff laurent | e174ed5ef3 | |
Laurent Le Goff | 64935ae1fc |
|
@ -0,0 +1,25 @@
|
|||
.DS_Store
|
||||
**/*.[568ao]
|
||||
**/*.ao
|
||||
**/*.so
|
||||
**/*.pyc
|
||||
**/._*
|
||||
**/.nfs.*
|
||||
**/[568a].out
|
||||
**/*.exe
|
||||
**/*~
|
||||
**/*.orig
|
||||
**/*.out
|
||||
**/*.test
|
||||
core
|
||||
_obj
|
||||
_test
|
||||
out.png
|
||||
_test*
|
||||
|
||||
**/*.dll
|
||||
**/core*[0-9]
|
||||
.private
|
||||
|
||||
go.sum
|
||||
|
23
.hgignore
23
.hgignore
|
@ -1,23 +0,0 @@
|
|||
syntax:glob
|
||||
.DS_Store
|
||||
.git
|
||||
.gitignore
|
||||
*.[568ao]
|
||||
*.ao
|
||||
*.so
|
||||
*.pyc
|
||||
._*
|
||||
.nfs.*
|
||||
[568a].out
|
||||
*.exe
|
||||
*~
|
||||
*.orig
|
||||
core
|
||||
_obj
|
||||
_test
|
||||
out.png
|
||||
_test*
|
||||
|
||||
syntax: regexp
|
||||
\.dll$
|
||||
^.*/core.[0-9]*$
|
17
.project
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>
|
5
AUTHORS
5
AUTHORS
|
@ -1 +1,4 @@
|
|||
Laurent Le Goff
|
||||
Laurent Le Goff
|
||||
Stani Michiels, gmail:stani.be
|
||||
Drahoslav Bednář
|
||||
Sebastien Binet
|
||||
|
|
30
Makefile
30
Makefile
|
@ -1,30 +0,0 @@
|
|||
|
||||
#include $(GOROOT)/src/Make.inc
|
||||
|
||||
all: install
|
||||
|
||||
install:
|
||||
cd draw2d && make install
|
||||
# cd draw2dgl && make install
|
||||
cd postscript && make install
|
||||
# cd wingui && make install
|
||||
|
||||
clean:
|
||||
cd draw2d && make clean
|
||||
# cd draw2dgl && make clean
|
||||
cd postscript && make clean
|
||||
cd cmd && make clean
|
||||
# cd wingui && make clean
|
||||
|
||||
nuke:
|
||||
cd draw2d && make nuke
|
||||
# cd draw2dgl && make nuke
|
||||
cd postscript && make nuke
|
||||
# cd wingui && make nuke
|
||||
|
||||
command:
|
||||
cd cmd && make
|
||||
|
||||
fmt:
|
||||
gofmt -w .
|
||||
|
35
README
35
README
|
@ -1,35 +0,0 @@
|
|||
|
||||
|
||||
This package (written in *[http://golang.org go]*) provide an API to draw 2d geometrical form on [http://golang.org/pkg/image/ images].
|
||||
This library is largely inspired by [http://www.tailrecursive.org/postscript/ postscript], [http://cairographics.org/ cairo], [http://dev.w3.org/html5/canvas-api/canvas-2d-api.html#the-2d-drawing-context HTML5 canvas].
|
||||
|
||||
The package depends on [http://code.google.com/p/freetype-go/ freetype-go] package thanks to its rasterization algorithm.
|
||||
|
||||
Some algorithm have been translated from http://www.antigrain.com project ([http://www.antigrain.com/research/adaptive_bezier/index.html adaptive bezier], and arc drawing)
|
||||
|
||||
=== Installation ====
|
||||
Once you have Go installed, to install draw2d:
|
||||
|
||||
* First see the installation procedure of [http://code.google.com/p/freetype-go/ freetype-go]
|
||||
* goinstall draw2d.googlecode.com/svn/trunk/draw2d/src/pkg/draw2d
|
||||
|
||||
a good starting point is the [http://code.google.com/p/draw2d/wiki/GettingStarted getting started]
|
||||
|
||||
|
||||
=== [http://code.google.com/p/draw2d/wiki/Samples Samples] ===
|
||||
Sample images generated by draw2d (inspired by [http://cairographics.org/samples/ cairo samples]):
|
||||
there's already some bugs please refer to [http://code.google.com/p/draw2d/issues/list issue tracking]
|
||||
|
||||
[http://draw2d.googlecode.com/svn/wiki/test_results/TestPath.png]
|
||||
[http://draw2d.googlecode.com/svn/wiki/test_results/TestDrawArc.png]
|
||||
[http://draw2d.googlecode.com/svn/wiki/test_results/TestDrawArcNegative.png]
|
||||
[http://draw2d.googlecode.com/svn/wiki/test_results/TestCurveRectangle.png]
|
||||
[http://draw2d.googlecode.com/svn/wiki/test_results/TestDrawCubicCurve.png]
|
||||
[http://draw2d.googlecode.com/svn/wiki/test_results/TestDash.png]
|
||||
[http://draw2d.googlecode.com/svn/wiki/test_results/TestFillStroke.png]
|
||||
[http://draw2d.googlecode.com/svn/wiki/test_results/TestFillStyle.png]
|
||||
[http://draw2d.googlecode.com/svn/wiki/test_results/TestMultiSegmentCaps.png]
|
||||
[http://draw2d.googlecode.com/svn/wiki/test_results/TestRoundRectangle.png]
|
||||
[http://draw2d.googlecode.com/svn/wiki/test_results/TestLineCap.png]
|
||||
[http://draw2d.googlecode.com/svn/wiki/test_results/TestLineJoin.png]
|
||||
|
|
@ -0,0 +1,144 @@
|
|||
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.
|
||||
|
||||
[![geometry](https://raw.githubusercontent.com/llgcode/draw2d/master/output/samples/geometry.png)](https://raw.githubusercontent.com/llgcode/draw2d/master/resource/image/geometry.pdf)[![postscript](https://raw.githubusercontent.com/llgcode/draw2d/master/output/samples/postscript.png)](https://raw.githubusercontent.com/llgcode/draw2d/master/resource/image/postscript.pdf)
|
||||
|
||||
Click on an image above to get the pdf, generated with exactly the same draw2d code. The first image is the output of `samples/geometry`. The second image is the result of `samples/postcript`, which demonstrates that draw2d can draw postscript files into images or pdf documents with the [ps](https://github.com/llgcode/ps) package.
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
Operations in draw2d include stroking and filling polygons, arcs, Bézier curves, drawing images and text rendering with truetype fonts. All drawing operations can be transformed by affine transformations (scale, rotation, translation).
|
||||
|
||||
Package draw2d follows the conventions of the [HTML Canvas 2D Context](http://www.w3.org/TR/2dcontext/) for coordinate system, angles, etc...
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
|
||||
Quick Start
|
||||
-----------
|
||||
|
||||
The following Go code generates a simple drawing and saves it to an image file with package draw2d:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/llgcode/draw2d/draw2dimg"
|
||||
"image"
|
||||
"image/color"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Initialize the graphic context on an RGBA image
|
||||
dest := image.NewRGBA(image.Rect(0, 0, 297, 210.0))
|
||||
gc := draw2dimg.NewGraphicContext(dest)
|
||||
|
||||
// Set some properties
|
||||
gc.SetFillColor(color.RGBA{0x44, 0xff, 0x44, 0xff})
|
||||
gc.SetStrokeColor(color.RGBA{0x44, 0x44, 0x44, 0xff})
|
||||
gc.SetLineWidth(5)
|
||||
|
||||
// Draw a closed shape
|
||||
gc.BeginPath() // Initialize a new path
|
||||
gc.MoveTo(10, 10) // Move to a position to start the new path
|
||||
gc.LineTo(100, 50)
|
||||
gc.QuadCurveTo(100, 10, 10, 10)
|
||||
gc.Close()
|
||||
gc.FillStroke()
|
||||
|
||||
// Save to file
|
||||
draw2dimg.SaveToPngFile("hello.png", dest)
|
||||
}
|
||||
```
|
||||
|
||||
The same Go code can also generate a pdf document with package draw2dpdf:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/llgcode/draw2d/draw2dpdf"
|
||||
"image/color"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Initialize the graphic context on an RGBA image
|
||||
dest := draw2dpdf.NewPdf("L", "mm", "A4")
|
||||
gc := draw2dpdf.NewGraphicContext(dest)
|
||||
|
||||
// Set some properties
|
||||
gc.SetFillColor(color.RGBA{0x44, 0xff, 0x44, 0xff})
|
||||
gc.SetStrokeColor(color.RGBA{0x44, 0x44, 0x44, 0xff})
|
||||
gc.SetLineWidth(5)
|
||||
|
||||
// Draw a closed shape
|
||||
gc.MoveTo(10, 10) // should always be called first for a new path
|
||||
gc.LineTo(100, 50)
|
||||
gc.QuadCurveTo(100, 10, 10, 10)
|
||||
gc.Close()
|
||||
gc.FillStroke()
|
||||
|
||||
// Save to file
|
||||
draw2dpdf.SaveToPdfFile("hello.pdf", dest)
|
||||
}
|
||||
```
|
||||
|
||||
There are more examples here: https://github.com/llgcode/draw2d/tree/master/samples
|
||||
|
||||
Drawing on opengl is provided by the draw2dgl package.
|
||||
|
||||
Testing
|
||||
-------
|
||||
|
||||
The samples are run as tests from the root package folder `draw2d` by:
|
||||
```
|
||||
go test ./...
|
||||
```
|
||||
Or if you want to run with test coverage:
|
||||
```
|
||||
go test -cover ./... | grep -v "no test"
|
||||
```
|
||||
This will generate output by the different backends in the output folder.
|
||||
|
||||
Acknowledgments
|
||||
---------------
|
||||
|
||||
[Laurent Le Goff](https://github.com/llgcode) wrote this library, inspired by [Postscript](http://www.tailrecursive.org/postscript) and [HTML5 canvas](http://www.w3.org/TR/2dcontext/). He implemented the image and opengl backend with the [freetype-go](https://code.google.com/p/freetype-go/) package. Also he created a pure go [Postscript interpreter](https://github.com/llgcode/ps), which can read postscript images and draw to a draw2d graphic context. [Stani Michiels](https://github.com/stanim) implemented the pdf backend with the [gofpdf](https://github.com/jung-kurt/gofpdf) package.
|
||||
|
||||
|
||||
|
||||
Packages using draw2d
|
||||
---------------------
|
||||
|
||||
- [ps](https://github.com/llgcode/ps): Postscript interpreter written in Go
|
||||
- [gonum/plot](https://github.com/gonum/plot): drawing plots in Go
|
||||
- [go.uik](https://github.com/skelterjohn/go.uik): a concurrent UI kit written in pure go.
|
||||
- [smartcrop](https://github.com/muesli/smartcrop): content aware image cropping
|
||||
- [karta](https://github.com/peterhellberg/karta): drawing Voronoi diagrams
|
||||
- [chart](https://github.com/vdobler/chart): basic charts in Go
|
||||
- [hilbert](https://github.com/google/hilbert): package for drawing Hilbert curves
|
||||
|
||||
References
|
||||
---------
|
||||
|
||||
- [antigrain.com](http://www.antigrain.com)
|
||||
- [freetype-go](http://code.google.com/p/freetype-go)
|
||||
-
|
17
cmd/Makefile
17
cmd/Makefile
|
@ -1,17 +0,0 @@
|
|||
|
||||
include $(GOROOT)/src/Make.inc
|
||||
|
||||
TARG=gettingStarted testdraw2d testX11draw testandroid testgopher testimage testpostscript #draw2dgl
|
||||
|
||||
OFILES=$(TARG:%=%.$O)
|
||||
|
||||
all: $(TARG)
|
||||
|
||||
$(TARG): %: %.$O
|
||||
$(LD) -o $@ $<
|
||||
|
||||
$(OFILES): %.$O: %.go Makefile
|
||||
$(GC) -o $@ $<
|
||||
|
||||
clean:
|
||||
rm -f *.[$(OS)] $(TARG) $(CLEANFILES)
|
|
@ -1,96 +0,0 @@
|
|||
// Ported from GLUT's samples. Original copyright below applies.
|
||||
|
||||
/* Copyright (c) Mark J. Kilgard, 1996. */
|
||||
|
||||
/* This program is freely distributable without licensing fees
|
||||
and is provided without guarantee or warrantee expressed or
|
||||
implied. This program is -not- in the public domain. */
|
||||
|
||||
/* This program is a response to a question posed by Gil Colgate
|
||||
<gcolgate@sirius.com> about how lengthy a program is required using
|
||||
OpenGL compared to using Direct3D immediate mode to "draw a
|
||||
triangle at screen coordinates 0,0, to 200,200 to 20,200, and I
|
||||
want it to be blue at the top vertex, red at the left vertex, and
|
||||
green at the right vertex". I'm not sure how long the Direct3D
|
||||
program is; Gil has used Direct3D and his guess is "about 3000
|
||||
lines of code". */
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"math"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"gl"
|
||||
"glut"
|
||||
"draw2d.googlecode.com/hg/draw2dgl"
|
||||
"draw2d.googlecode.com/hg/postscript"
|
||||
"log"
|
||||
"time"
|
||||
)
|
||||
|
||||
var postscriptContent string
|
||||
|
||||
var (
|
||||
width, height int
|
||||
rotate int
|
||||
)
|
||||
|
||||
func reshape(w, h int) {
|
||||
/* Because Gil specified "screen coordinates" (presumably with an
|
||||
upper-left origin), this short bit of code sets up the coordinate
|
||||
system to correspond to actual window coodrinates. This code
|
||||
wouldn't be required if you chose a (more typical in 3D) abstract
|
||||
coordinate system. */
|
||||
gl.ClearColor(1, 1, 1, 1)
|
||||
//fmt.Println(gl.GetString(gl.EXTENSIONS))
|
||||
gl.Viewport(0, 0, w, h) /* Establish viewing area to cover entire window. */
|
||||
gl.MatrixMode(gl.PROJECTION) /* Start modifying the projection matrix. */
|
||||
gl.LoadIdentity() /* Reset project matrix. */
|
||||
gl.Ortho(0, float64(w), 0, float64(h), -1, 1) /* Map abstract coords directly to window coords. */
|
||||
gl.Scalef(1, -1, 1) /* Invert Y axis so increasing Y goes down. */
|
||||
gl.Translatef(0, float32(-h), 0) /* Shift origin up to upper-left corner. */
|
||||
gl.Enable(gl.BLEND)
|
||||
gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
|
||||
gl.Disable(gl.DEPTH_TEST)
|
||||
width, height = w, h
|
||||
}
|
||||
|
||||
func display() {
|
||||
gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
|
||||
gl.LineWidth(1)
|
||||
gc := draw2dgl.NewGraphicContext(width, height)
|
||||
|
||||
gc.Translate(380, 400)
|
||||
gc.Scale(1, -1)
|
||||
rotate = (rotate + 1) % 360
|
||||
gc.Rotate(float64(rotate) * math.Pi / 180)
|
||||
gc.Translate(-380, -400)
|
||||
interpreter := postscript.NewInterpreter(gc)
|
||||
reader := strings.NewReader(postscriptContent)
|
||||
lastTime := time.Nanoseconds()
|
||||
interpreter.Execute(reader)
|
||||
dt := time.Nanoseconds() - lastTime
|
||||
log.Printf("Redraw in : %f ms\n", float64(dt)*1e-6)
|
||||
gl.Flush() /* Single buffered, so needs a flush. */
|
||||
glut.PostRedisplay()
|
||||
}
|
||||
|
||||
func main() {
|
||||
src, err := os.OpenFile("../resource/postscript/tiger.ps", 0, 0)
|
||||
if err != nil {
|
||||
log.Println("can't find postscript file.")
|
||||
return
|
||||
}
|
||||
defer src.Close()
|
||||
bytes, err := ioutil.ReadAll(src)
|
||||
postscriptContent = string(bytes)
|
||||
glut.Init()
|
||||
glut.InitWindowSize(800, 800)
|
||||
glut.CreateWindow("Show Tiger in Opengl")
|
||||
|
||||
glut.DisplayFunc(display)
|
||||
glut.ReshapeFunc(reshape)
|
||||
glut.MainLoop()
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
// Copyright 2010 The draw2d Authors. All rights reserved.
|
||||
// created: 21/11/2010 by Laurent Le Goff
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"bufio"
|
||||
|
||||
"image"
|
||||
"image/png"
|
||||
"draw2d.googlecode.com/hg/draw2d"
|
||||
)
|
||||
|
||||
|
||||
func saveToPngFile(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)
|
||||
}
|
||||
fmt.Printf("Wrote %s OK.\n", filePath)
|
||||
}
|
||||
|
||||
func main() {
|
||||
i := image.NewRGBA(image.Rect(0,0,200, 200))
|
||||
gc := draw2d.NewGraphicContext(i)
|
||||
gc.MoveTo(10.0, 10.0)
|
||||
gc.LineTo(100.0, 10.0)
|
||||
gc.Stroke()
|
||||
saveToPngFile("TestPath.png", i)
|
||||
}
|
|
@ -1,235 +0,0 @@
|
|||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"syscall"
|
||||
"os"
|
||||
"unsafe"
|
||||
"image"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"time"
|
||||
"draw2d.googlecode.com/hg/draw2d"
|
||||
"draw2d.googlecode.com/hg/postscript"
|
||||
"draw2d.googlecode.com/hg/wingui"
|
||||
)
|
||||
|
||||
// some help functions
|
||||
|
||||
func abortf(format string, a ...interface{}) {
|
||||
fmt.Fprintf(os.Stdout, format, a...)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func abortErrNo(funcname string, err int) {
|
||||
abortf("%s failed: %d %s\n", funcname, err, syscall.Errstr(err))
|
||||
}
|
||||
|
||||
// global vars
|
||||
|
||||
|
||||
func TestDrawCubicCurve(gc draw2d.GraphicContext) {
|
||||
// draw a cubic curve
|
||||
x, y := 25.6, 128.0
|
||||
x1, y1 := 102.4, 230.4
|
||||
x2, y2 := 153.6, 25.6
|
||||
x3, y3 := 230.4, 128.0
|
||||
|
||||
gc.SetFillColor(image.NRGBAColor{0xAA, 0xAA, 0xAA, 0xFF})
|
||||
gc.SetLineWidth(10)
|
||||
gc.MoveTo(x, y)
|
||||
gc.CubicCurveTo(x1, y1, x2, y2, x3, y3)
|
||||
gc.Stroke()
|
||||
|
||||
gc.SetStrokeColor(image.NRGBAColor{0xFF, 0x33, 0x33, 0x88})
|
||||
|
||||
gc.SetLineWidth(6)
|
||||
// draw segment of curve
|
||||
gc.MoveTo(x, y)
|
||||
gc.LineTo(x1, y1)
|
||||
gc.LineTo(x2, y2)
|
||||
gc.LineTo(x3, y3)
|
||||
gc.Stroke()
|
||||
}
|
||||
|
||||
var (
|
||||
mh uint32
|
||||
wndBufferHeader uint32
|
||||
wndBuffer wingui.BITMAP
|
||||
hdcWndBuffer uint32
|
||||
ppvBits *image.RGBAColor
|
||||
backBuffer *image.RGBA
|
||||
postscriptContent string
|
||||
)
|
||||
|
||||
// WinProc called by windows to notify us of all windows events we might be interested in.
|
||||
func WndProc(hwnd, msg uint32, wparam, lparam int32) uintptr {
|
||||
var rc int32
|
||||
|
||||
switch msg {
|
||||
case wingui.WM_CREATE:
|
||||
hdc := wingui.GetDC(hwnd)
|
||||
hdcWndBuffer = wingui.CreateCompatibleDC(hdc)
|
||||
wndBufferHeader = wingui.CreateCompatibleBitmap(hdc, 600, 800)
|
||||
wingui.GetObject(wndBufferHeader, unsafe.Sizeof(wndBuffer), uintptr(unsafe.Pointer(&wndBuffer)))
|
||||
wingui.SelectObject(hdcWndBuffer, wndBufferHeader)
|
||||
|
||||
var bmp_header wingui.BITMAPINFOHEADER
|
||||
bmp_header.Size = uint32(unsafe.Sizeof(bmp_header))
|
||||
bmp_header.Width = 600
|
||||
bmp_header.Height = 800
|
||||
bmp_header.SizeImage = 0 // the api says this must be 0 for BI_RGB images
|
||||
bmp_header.Compression = wingui.BI_RGB
|
||||
bmp_header.BitCount = 32
|
||||
bmp_header.Planes = 1
|
||||
bmp_header.XPelsPerMeter = 0
|
||||
bmp_header.YPelsPerMeter = 0
|
||||
bmp_header.ClrUsed = 0
|
||||
bmp_header.ClrImportant = 0
|
||||
//bitmap info
|
||||
var bmpinfo wingui.BITMAPINFO
|
||||
bmpinfo.Colors[0].Blue = 0
|
||||
bmpinfo.Colors[0].Green = 0
|
||||
bmpinfo.Colors[0].Red = 0
|
||||
bmpinfo.Colors[0].Reserved = 0
|
||||
bmpinfo.Header = bmp_header
|
||||
wndBufferHeader = wingui.CreateDIBSection(hdc, &bmpinfo, wingui.DIB_RGB_COLORS, uintptr(unsafe.Pointer(&ppvBits)), 0, 0)
|
||||
wingui.GetObject(wndBufferHeader, unsafe.Sizeof(wndBufferHeader), uintptr(unsafe.Pointer(&wndBuffer)))
|
||||
hdcWndBuffer = wingui.CreateCompatibleDC(hdc)
|
||||
wingui.SelectObject(hdcWndBuffer, wndBufferHeader)
|
||||
|
||||
pixel := (*[600 * 800]image.RGBAColor)(unsafe.Pointer(ppvBits))
|
||||
pixelSlice := pixel[:]
|
||||
backBuffer = &image.RGBA{pixelSlice, 600, image.Rect(0, 0, 600, 800)}
|
||||
fmt.Println("Create windows")
|
||||
rc = wingui.DefWindowProc(hwnd, msg, wparam, lparam)
|
||||
case wingui.WM_COMMAND:
|
||||
switch uint32(lparam) {
|
||||
default:
|
||||
rc = wingui.DefWindowProc(hwnd, msg, wparam, lparam)
|
||||
}
|
||||
case wingui.WM_PAINT:
|
||||
var ps wingui.PAINTSTRUCT
|
||||
lastTime := time.Nanoseconds()
|
||||
hdc := wingui.BeginPaint(hwnd, &ps)
|
||||
gc := draw2d.NewGraphicContext(backBuffer)
|
||||
gc.SetFillColor(image.RGBAColor{0xFF, 0xFF, 0xFF, 0xFF})
|
||||
// gc.Clear()
|
||||
gc.Save()
|
||||
//gc.Translate(0, -380)
|
||||
interpreter := postscript.NewInterpreter(gc)
|
||||
reader := strings.NewReader(postscriptContent)
|
||||
interpreter.Execute(reader)
|
||||
dt := time.Nanoseconds() - lastTime
|
||||
gc.Restore()
|
||||
// back buf in
|
||||
|
||||
wingui.BitBlt(hdc, 0, 0, int(wndBuffer.Width), int(wndBuffer.Height), hdcWndBuffer, 0, 0, wingui.SRCCOPY)
|
||||
wingui.EndPaint(hwnd, &ps)
|
||||
rc = wingui.DefWindowProc(hwnd, msg, wparam, lparam)
|
||||
fmt.Printf("Redraw in : %f ms\n", float64(dt)*1e-6)
|
||||
case wingui.WM_CLOSE:
|
||||
wingui.DestroyWindow(hwnd)
|
||||
case wingui.WM_DESTROY:
|
||||
wingui.PostQuitMessage(0)
|
||||
default:
|
||||
rc = wingui.DefWindowProc(hwnd, msg, wparam, lparam)
|
||||
}
|
||||
return uintptr(rc)
|
||||
}
|
||||
|
||||
func rungui() int {
|
||||
var e int
|
||||
|
||||
// GetModuleHandle
|
||||
mh, e = wingui.GetModuleHandle(nil)
|
||||
if e != 0 {
|
||||
abortErrNo("GetModuleHandle", e)
|
||||
}
|
||||
|
||||
// Get icon we're going to use.
|
||||
myicon, e := wingui.LoadIcon(0, wingui.IDI_APPLICATION)
|
||||
if e != 0 {
|
||||
abortErrNo("LoadIcon", e)
|
||||
}
|
||||
|
||||
// Get cursor we're going to use.
|
||||
mycursor, e := wingui.LoadCursor(0, wingui.IDC_ARROW)
|
||||
if e != 0 {
|
||||
abortErrNo("LoadCursor", e)
|
||||
}
|
||||
|
||||
// Create callback
|
||||
wproc := syscall.NewCallback(WndProc)
|
||||
|
||||
// RegisterClassEx
|
||||
wcname := syscall.StringToUTF16Ptr("Test Draw2d")
|
||||
var wc wingui.Wndclassex
|
||||
wc.Size = uint32(unsafe.Sizeof(wc))
|
||||
wc.WndProc = wproc
|
||||
//wc.Style = wingui.CS_HREDRAW | wingui.CS_VREDRAW
|
||||
wc.Instance = mh
|
||||
wc.Icon = myicon
|
||||
wc.Cursor = mycursor
|
||||
wc.Background = 0
|
||||
wc.MenuName = nil
|
||||
wc.ClassName = wcname
|
||||
wc.IconSm = myicon
|
||||
if _, e := wingui.RegisterClassEx(&wc); e != 0 {
|
||||
abortErrNo("RegisterClassEx", e)
|
||||
}
|
||||
|
||||
// CreateWindowEx
|
||||
wh, e := wingui.CreateWindowEx(
|
||||
wingui.WS_EX_CLIENTEDGE,
|
||||
wcname,
|
||||
syscall.StringToUTF16Ptr("My window"),
|
||||
wingui.WS_OVERLAPPEDWINDOW,
|
||||
wingui.CW_USEDEFAULT, wingui.CW_USEDEFAULT, 600, 800,
|
||||
0, 0, mh, 0)
|
||||
if e != 0 {
|
||||
abortErrNo("CreateWindowEx", e)
|
||||
}
|
||||
fmt.Printf("main window handle is %x\n", wh)
|
||||
|
||||
// ShowWindow
|
||||
wingui.ShowWindow(wh, wingui.SW_SHOWDEFAULT)
|
||||
|
||||
// UpdateWindow
|
||||
if e := wingui.UpdateWindow(wh); e != 0 {
|
||||
abortErrNo("UpdateWindow", e)
|
||||
}
|
||||
|
||||
// Process all windows messages until WM_QUIT.
|
||||
var m wingui.Msg
|
||||
for {
|
||||
r, e := wingui.GetMessage(&m, 0, 0, 0)
|
||||
if e != 0 {
|
||||
abortErrNo("GetMessage", e)
|
||||
}
|
||||
if r == 0 {
|
||||
// WM_QUIT received -> get out
|
||||
break
|
||||
}
|
||||
wingui.TranslateMessage(&m)
|
||||
wingui.DispatchMessage(&m)
|
||||
}
|
||||
return int(m.Wparam)
|
||||
}
|
||||
|
||||
func main() {
|
||||
src, err := os.OpenFile("../resource/postscript/tiger.ps", 0, 0)
|
||||
if err != nil {
|
||||
fmt.Println("can't find postscript file.")
|
||||
return
|
||||
}
|
||||
defer src.Close()
|
||||
bytes, err := ioutil.ReadAll(src)
|
||||
postscriptContent = string(bytes)
|
||||
rc := rungui()
|
||||
os.Exit(rc)
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"exp/gui"
|
||||
"exp/gui/x11"
|
||||
"image"
|
||||
"math"
|
||||
"draw2d.googlecode.com/hg/draw2d"
|
||||
)
|
||||
|
||||
func main() {
|
||||
window, err := x11.NewWindow()
|
||||
if err != nil {
|
||||
fmt.Printf("Cannot open an x11 window\n")
|
||||
return
|
||||
}
|
||||
screen := window.Screen()
|
||||
gc := draw2d.NewGraphicContext(screen)
|
||||
gc.SetStrokeColor(image.Black)
|
||||
gc.SetFillColor(image.White)
|
||||
gc.Clear()
|
||||
for i := 0.0; i < 360; i = i + 10 { // Go from 0 to 360 degrees in 10 degree steps
|
||||
gc.BeginPath() // Start a new path
|
||||
gc.Save() // Keep rotations temporary
|
||||
gc.MoveTo(144, 144)
|
||||
gc.Rotate(i * (math.Pi / 180.0)) // Rotate by degrees on stack from 'for'
|
||||
gc.RLineTo(72, 0)
|
||||
gc.Stroke()
|
||||
gc.Restore() // Get back the unrotated state
|
||||
}
|
||||
|
||||
window.FlushImage()
|
||||
|
||||
gc.SetLineWidth(3)
|
||||
nbclick := 0
|
||||
for {
|
||||
|
||||
switch evt := (<-window.EventChan()).(type) {
|
||||
case gui.KeyEvent:
|
||||
if evt.Key == 'q' {
|
||||
window.Close()
|
||||
}
|
||||
case gui.MouseEvent:
|
||||
if evt.Buttons&1 != 0 {
|
||||
if nbclick%2 == 0 {
|
||||
gc.MoveTo(float64(evt.Loc.X), float64(evt.Loc.Y))
|
||||
} else {
|
||||
gc.LineTo(float64(evt.Loc.X), float64(evt.Loc.Y))
|
||||
gc.Stroke()
|
||||
window.FlushImage()
|
||||
}
|
||||
nbclick = nbclick + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,98 +0,0 @@
|
|||
package main
|
||||
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"bufio"
|
||||
"time"
|
||||
|
||||
"math"
|
||||
"image"
|
||||
"image/png"
|
||||
"draw2d.googlecode.com/hg/draw2d"
|
||||
)
|
||||
|
||||
const (
|
||||
width, height = 178, 224
|
||||
)
|
||||
|
||||
var (
|
||||
lastTime int64
|
||||
folder = "../resource/result/"
|
||||
)
|
||||
|
||||
func initGc(w, h int) (image.Image, draw2d.GraphicContext) {
|
||||
i := image.NewRGBA(image.Rect(0, 0,w, h))
|
||||
gc := draw2d.NewGraphicContext(i)
|
||||
lastTime = time.Nanoseconds()
|
||||
|
||||
gc.SetStrokeColor(image.Black)
|
||||
gc.SetFillColor(image.White)
|
||||
// fill the background
|
||||
//gc.Clear()
|
||||
|
||||
return i, gc
|
||||
}
|
||||
|
||||
func saveToPngFile(TestName string, m image.Image) {
|
||||
dt := time.Nanoseconds() - lastTime
|
||||
fmt.Printf("%s during: %f ms\n", TestName, float64(dt)*1e-6)
|
||||
filePath := folder + TestName + ".png"
|
||||
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)
|
||||
}
|
||||
fmt.Printf("Wrote %s OK.\n", filePath)
|
||||
}
|
||||
|
||||
func android(gc draw2d.GraphicContext, x, y float64) {
|
||||
gc.SetLineCap(draw2d.RoundCap)
|
||||
gc.SetLineWidth(5)
|
||||
gc.ArcTo(x+80, y+70, 50, 50, 180*(math.Pi/180), 360*(math.Pi/180)) // head
|
||||
gc.FillStroke()
|
||||
gc.MoveTo(x+60, y+25)
|
||||
gc.LineTo(x+50, y+10)
|
||||
gc.MoveTo(x+100, y+25)
|
||||
gc.LineTo(x+110, y+10)
|
||||
gc.Stroke()
|
||||
draw2d.Circle(gc, x+60, y+45, 5) // left eye
|
||||
gc.FillStroke()
|
||||
draw2d.Circle(gc, x+100, y+45, 5) // right eye
|
||||
gc.FillStroke()
|
||||
draw2d.RoundRect(gc, x+30, y+75, x+30+100, y+75+90, 10, 10) // body
|
||||
gc.FillStroke()
|
||||
draw2d.Rect(gc, x+30, y+75, x+30+100, y+75+80)
|
||||
gc.FillStroke()
|
||||
draw2d.RoundRect(gc, x+5, y+80, x+5+20, y+80+70, 10, 10) // left arm
|
||||
gc.FillStroke()
|
||||
draw2d.RoundRect(gc, x+135, y+80, x+135+20, y+80+70, 10, 10) // right arm
|
||||
gc.FillStroke()
|
||||
draw2d.RoundRect(gc, x+50, y+150, x+50+20, y+150+50, 10, 10) // left leg
|
||||
gc.FillStroke()
|
||||
draw2d.RoundRect(gc, x+90, y+150, x+90+20, y+150+50, 10, 10) // right leg
|
||||
gc.FillStroke()
|
||||
}
|
||||
|
||||
|
||||
func main() {
|
||||
i, gc := initGc(width, height)
|
||||
gc.SetFillColor(image.RGBAColor{0x44, 0xff, 0x44, 0xff})
|
||||
gc.SetStrokeColor(image.RGBAColor{0x44, 0x44, 0x44, 0xff})
|
||||
android(gc, 10, 10)
|
||||
saveToPngFile("TestAndroid", i)
|
||||
}
|
|
@ -1,537 +0,0 @@
|
|||
// Copyright 2010 The draw2d Authors. All rights reserved.
|
||||
// created: 21/11/2010 by Laurent Le Goff
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"bufio"
|
||||
"time"
|
||||
|
||||
"math"
|
||||
"image"
|
||||
"image/png"
|
||||
"draw2d.googlecode.com/hg/draw2d"
|
||||
)
|
||||
|
||||
const (
|
||||
w, h = 512, 512
|
||||
)
|
||||
|
||||
var (
|
||||
lastTime int64
|
||||
folder = "../resource/result/"
|
||||
)
|
||||
|
||||
func initGc(w, h int) (image.Image, draw2d.GraphicContext) {
|
||||
i := image.NewRGBA(image.Rect(0, 0, w, h))
|
||||
gc := draw2d.NewGraphicContext(i)
|
||||
lastTime = time.Nanoseconds()
|
||||
|
||||
gc.SetStrokeColor(image.Black)
|
||||
gc.SetFillColor(image.White)
|
||||
// fill the background
|
||||
//gc.Clear()
|
||||
|
||||
return i, gc
|
||||
}
|
||||
|
||||
func saveToPngFile(TestName string, m image.Image) {
|
||||
t := time.Nanoseconds()
|
||||
dt := t - lastTime
|
||||
fmt.Printf("%s during: %f ms\n", TestName, float64(dt)*1e-6)
|
||||
filePath := folder + TestName + ".png"
|
||||
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)
|
||||
}
|
||||
dt = time.Nanoseconds() - t
|
||||
fmt.Printf("Wrote %s OK in %f ms.\n", filePath, float64(dt)*1e-6)
|
||||
}
|
||||
|
||||
/*
|
||||
<img src="../test_results/TestPath.png"/>
|
||||
*/
|
||||
func TestPath() {
|
||||
i, gc := initGc(w, h)
|
||||
gc.Translate(10, 10)
|
||||
gc.MoveTo(0.0, 0.0)
|
||||
gc.LineTo(100.0, 00.0)
|
||||
gc.LineTo(100.0, 100.0)
|
||||
gc.LineTo(0.0, 100.0)
|
||||
gc.LineTo(0.0, 0.0)
|
||||
gc.FillStroke()
|
||||
saveToPngFile("TestPath", i)
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
<img src="../test_results/TestDrawArc.png"/>
|
||||
*/
|
||||
func TestDrawArc() {
|
||||
i, gc := initGc(w, h)
|
||||
// draw an arc
|
||||
xc, yc := 128.0, 128.0
|
||||
radiusX, radiusY := 100.0, 100.0
|
||||
startAngle := 45.0 * (math.Pi / 180.0) /* angles are specified */
|
||||
angle := 135 * (math.Pi / 180.0) /* in radians */
|
||||
gc.SetLineWidth(10)
|
||||
gc.SetLineCap(draw2d.ButtCap)
|
||||
gc.SetStrokeColor(image.Black)
|
||||
gc.ArcTo(xc, yc, radiusX, radiusY, startAngle, angle)
|
||||
gc.Stroke()
|
||||
// fill a circle
|
||||
gc.SetStrokeColor(image.NRGBAColor{255, 0x33, 0x33, 0x80})
|
||||
gc.SetFillColor(image.NRGBAColor{255, 0x33, 0x33, 0x80})
|
||||
gc.SetLineWidth(6)
|
||||
|
||||
gc.MoveTo(xc, yc)
|
||||
gc.LineTo(xc+math.Cos(startAngle)*radiusX, yc+math.Sin(startAngle)*radiusY)
|
||||
gc.MoveTo(xc, yc)
|
||||
gc.LineTo(xc-radiusX, yc)
|
||||
gc.Stroke()
|
||||
|
||||
gc.ArcTo(xc, yc, 10.0, 10.0, 0, 2*math.Pi)
|
||||
gc.Fill()
|
||||
saveToPngFile("TestDrawArc", i)
|
||||
}
|
||||
/*
|
||||
<img src="../test_results/TestDrawArc.png"/>
|
||||
*/
|
||||
func TestDrawArcNegative() {
|
||||
i, gc := initGc(w, h)
|
||||
// draw an arc
|
||||
xc, yc := 128.0, 128.0
|
||||
radiusX, radiusY := 100.0, 100.0
|
||||
startAngle := 45.0 * (math.Pi / 180.0) /* angles are specified */
|
||||
angle := -225 * (math.Pi / 180.0) /* in radians */
|
||||
gc.SetLineWidth(10)
|
||||
gc.SetLineCap(draw2d.ButtCap)
|
||||
gc.SetStrokeColor(image.Black)
|
||||
|
||||
gc.ArcTo(xc, yc, radiusX, radiusY, startAngle, angle)
|
||||
gc.Stroke()
|
||||
// fill a circle
|
||||
gc.SetStrokeColor(image.NRGBAColor{255, 0x33, 0x33, 0x80})
|
||||
gc.SetFillColor(image.NRGBAColor{255, 0x33, 0x33, 0x80})
|
||||
gc.SetLineWidth(6)
|
||||
|
||||
gc.MoveTo(xc, yc)
|
||||
gc.LineTo(xc+math.Cos(startAngle)*radiusX, yc+math.Sin(startAngle)*radiusY)
|
||||
gc.MoveTo(xc, yc)
|
||||
gc.LineTo(xc-radiusX, yc)
|
||||
gc.Stroke()
|
||||
|
||||
gc.ArcTo(xc, yc, 10.0, 10.0, 0, 2*math.Pi)
|
||||
gc.Fill()
|
||||
saveToPngFile("TestDrawArcNegative", i)
|
||||
}
|
||||
|
||||
func TestCurveRectangle() {
|
||||
i, gc := initGc(w, h)
|
||||
|
||||
/* a custom shape that could be wrapped in a function */
|
||||
x0, y0 := 25.6, 25.6 /* parameters like cairo_rectangle */
|
||||
rect_width, rect_height := 204.8, 204.8
|
||||
radius := 102.4 /* and an approximate curvature radius */
|
||||
|
||||
x1 := x0 + rect_width
|
||||
y1 := y0 + rect_height
|
||||
if rect_width/2 < radius {
|
||||
if rect_height/2 < radius {
|
||||
gc.MoveTo(x0, (y0+y1)/2)
|
||||
gc.CubicCurveTo(x0, y0, x0, y0, (x0+x1)/2, y0)
|
||||
gc.CubicCurveTo(x1, y0, x1, y0, x1, (y0+y1)/2)
|
||||
gc.CubicCurveTo(x1, y1, x1, y1, (x1+x0)/2, y1)
|
||||
gc.CubicCurveTo(x0, y1, x0, y1, x0, (y0+y1)/2)
|
||||
} else {
|
||||
gc.MoveTo(x0, y0+radius)
|
||||
gc.CubicCurveTo(x0, y0, x0, y0, (x0+x1)/2, y0)
|
||||
gc.CubicCurveTo(x1, y0, x1, y0, x1, y0+radius)
|
||||
gc.LineTo(x1, y1-radius)
|
||||
gc.CubicCurveTo(x1, y1, x1, y1, (x1+x0)/2, y1)
|
||||
gc.CubicCurveTo(x0, y1, x0, y1, x0, y1-radius)
|
||||
}
|
||||
} else {
|
||||
if rect_height/2 < radius {
|
||||
gc.MoveTo(x0, (y0+y1)/2)
|
||||
gc.CubicCurveTo(x0, y0, x0, y0, x0+radius, y0)
|
||||
gc.LineTo(x1-radius, y0)
|
||||
gc.CubicCurveTo(x1, y0, x1, y0, x1, (y0+y1)/2)
|
||||
gc.CubicCurveTo(x1, y1, x1, y1, x1-radius, y1)
|
||||
gc.LineTo(x0+radius, y1)
|
||||
gc.CubicCurveTo(x0, y1, x0, y1, x0, (y0+y1)/2)
|
||||
} else {
|
||||
gc.MoveTo(x0, y0+radius)
|
||||
gc.CubicCurveTo(x0, y0, x0, y0, x0+radius, y0)
|
||||
gc.LineTo(x1-radius, y0)
|
||||
gc.CubicCurveTo(x1, y0, x1, y0, x1, y0+radius)
|
||||
gc.LineTo(x1, y1-radius)
|
||||
gc.CubicCurveTo(x1, y1, x1, y1, x1-radius, y1)
|
||||
gc.LineTo(x0+radius, y1)
|
||||
gc.CubicCurveTo(x0, y1, x0, y1, x0, y1-radius)
|
||||
}
|
||||
}
|
||||
gc.Close()
|
||||
|
||||
gc.SetFillColor(image.NRGBAColor{0x80, 0x80, 0xFF, 0xFF})
|
||||
gc.SetStrokeColor(image.NRGBAColor{0x80, 0, 0, 0x80})
|
||||
gc.SetLineWidth(10.0)
|
||||
gc.FillStroke()
|
||||
|
||||
saveToPngFile("TestCurveRectangle", i)
|
||||
}
|
||||
/*
|
||||
<img src="../test_results/TestDrawCubicCurve.png"/>
|
||||
*/
|
||||
func TestDrawCubicCurve() {
|
||||
i, gc := initGc(w, h)
|
||||
// draw a cubic curve
|
||||
x, y := 25.6, 128.0
|
||||
x1, y1 := 102.4, 230.4
|
||||
x2, y2 := 153.6, 25.6
|
||||
x3, y3 := 230.4, 128.0
|
||||
|
||||
gc.SetFillColor(image.NRGBAColor{0xAA, 0xAA, 0xAA, 0xFF})
|
||||
gc.SetLineWidth(10)
|
||||
gc.MoveTo(x, y)
|
||||
gc.CubicCurveTo(x1, y1, x2, y2, x3, y3)
|
||||
gc.Stroke()
|
||||
|
||||
gc.SetStrokeColor(image.NRGBAColor{0xFF, 0x33, 0x33, 0x88})
|
||||
|
||||
gc.SetLineWidth(6)
|
||||
// draw segment of curve
|
||||
gc.MoveTo(x, y)
|
||||
gc.LineTo(x1, y1)
|
||||
gc.LineTo(x2, y2)
|
||||
gc.LineTo(x3, y3)
|
||||
gc.Stroke()
|
||||
saveToPngFile("TestDrawCubicCurve", i)
|
||||
}
|
||||
|
||||
/*
|
||||
<img src="../test_results/TestDash.png"/>
|
||||
*/
|
||||
func TestDash() {
|
||||
i, gc := initGc(w, h)
|
||||
gc.SetLineDash([]float64{50, 10, 10, 10}, -50.0)
|
||||
gc.SetLineCap(draw2d.ButtCap)
|
||||
gc.SetLineJoin(draw2d.BevelJoin)
|
||||
gc.SetLineWidth(10)
|
||||
|
||||
gc.MoveTo(128.0, 25.6)
|
||||
gc.LineTo(128.0, 25.6)
|
||||
gc.LineTo(230.4, 230.4)
|
||||
gc.RLineTo(-102.4, 0.0)
|
||||
gc.CubicCurveTo(51.2, 230.4, 51.2, 128.0, 128.0, 128.0)
|
||||
gc.Stroke()
|
||||
gc.SetLineDash(nil, 0.0)
|
||||
saveToPngFile("TestDash", i)
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
<img src="../test_results/TestFillStroke.png"/>
|
||||
*/
|
||||
func TestFillStroke() {
|
||||
i, gc := initGc(w, h)
|
||||
gc.MoveTo(128.0, 25.6)
|
||||
gc.LineTo(230.4, 230.4)
|
||||
gc.RLineTo(-102.4, 0.0)
|
||||
gc.CubicCurveTo(51.2, 230.4, 51.2, 128.0, 128.0, 128.0)
|
||||
gc.Close()
|
||||
|
||||
gc.MoveTo(64.0, 25.6)
|
||||
gc.RLineTo(51.2, 51.2)
|
||||
gc.RLineTo(-51.2, 51.2)
|
||||
gc.RLineTo(-51.2, -51.2)
|
||||
gc.Close()
|
||||
|
||||
gc.SetLineWidth(10.0)
|
||||
gc.SetFillColor(image.NRGBAColor{0, 0, 0xFF, 0xFF})
|
||||
gc.SetStrokeColor(image.Black)
|
||||
gc.FillStroke()
|
||||
saveToPngFile("TestFillStroke", i)
|
||||
}
|
||||
|
||||
/*
|
||||
<img src="../test_results/TestFillStyle.png"/>
|
||||
*/
|
||||
func TestFillStyle() {
|
||||
i, gc := initGc(w, h)
|
||||
gc.SetLineWidth(6)
|
||||
|
||||
draw2d.Rect(gc, 12, 12, 244, 70)
|
||||
|
||||
wheel1 := new(draw2d.PathStorage)
|
||||
wheel1.ArcTo(64, 64, 40, 40, 0, 2*math.Pi)
|
||||
wheel2 := new(draw2d.PathStorage)
|
||||
wheel2.ArcTo(192, 64, 40, 40, 0, 2*math.Pi)
|
||||
|
||||
gc.SetFillRule(draw2d.FillRuleEvenOdd)
|
||||
gc.SetFillColor(image.NRGBAColor{0, 0xB2, 0, 0xFF})
|
||||
|
||||
gc.SetStrokeColor(image.Black)
|
||||
gc.FillStroke(wheel1, wheel2)
|
||||
|
||||
draw2d.Rect(gc, 12, 140, 244, 198)
|
||||
wheel1 = new(draw2d.PathStorage)
|
||||
wheel1.ArcTo(64, 192, 40, 40, 0, 2*math.Pi)
|
||||
wheel2 = new(draw2d.PathStorage)
|
||||
wheel2.ArcTo(192, 192, 40, 40, 0, -2*math.Pi)
|
||||
|
||||
gc.SetFillRule(draw2d.FillRuleWinding)
|
||||
gc.SetFillColor(image.NRGBAColor{0, 0, 0xE5, 0xFF})
|
||||
gc.FillStroke(wheel1, wheel2)
|
||||
saveToPngFile("TestFillStyle", i)
|
||||
}
|
||||
|
||||
func TestMultiSegmentCaps() {
|
||||
i, gc := initGc(w, h)
|
||||
gc.MoveTo(50.0, 75.0)
|
||||
gc.LineTo(200.0, 75.0)
|
||||
|
||||
gc.MoveTo(50.0, 125.0)
|
||||
gc.LineTo(200.0, 125.0)
|
||||
|
||||
gc.MoveTo(50.0, 175.0)
|
||||
gc.LineTo(200.0, 175.0)
|
||||
|
||||
gc.SetLineWidth(30.0)
|
||||
gc.SetLineCap(draw2d.RoundCap)
|
||||
gc.Stroke()
|
||||
saveToPngFile("TestMultiSegmentCaps", i)
|
||||
}
|
||||
|
||||
|
||||
func TestRoundRectangle() {
|
||||
i, gc := initGc(w, h)
|
||||
/* a custom shape that could be wrapped in a function */
|
||||
x, y := 25.6, 25.6
|
||||
width, height := 204.8, 204.8
|
||||
aspect := 1.0 /* aspect ratio */
|
||||
corner_radius := height / 10.0 /* and corner curvature radius */
|
||||
|
||||
radius := corner_radius / aspect
|
||||
degrees := math.Pi / 180.0
|
||||
|
||||
gc.ArcTo(x+width-radius, y+radius, radius, radius, -90*degrees, 90*degrees)
|
||||
gc.ArcTo(x+width-radius, y+height-radius, radius, radius, 0*degrees, 90*degrees)
|
||||
gc.ArcTo(x+radius, y+height-radius, radius, radius, 90*degrees, 90*degrees)
|
||||
gc.ArcTo(x+radius, y+radius, radius, radius, 180*degrees, 90*degrees)
|
||||
gc.Close()
|
||||
|
||||
gc.SetFillColor(image.NRGBAColor{0x80, 0x80, 0xFF, 0xFF})
|
||||
gc.SetStrokeColor(image.NRGBAColor{0x80, 0, 0, 0x80})
|
||||
gc.SetLineWidth(10.0)
|
||||
gc.FillStroke()
|
||||
|
||||
saveToPngFile("TestRoundRectangle", i)
|
||||
}
|
||||
|
||||
func TestLineCap() {
|
||||
i, gc := initGc(w, h)
|
||||
gc.SetLineWidth(30.0)
|
||||
gc.SetLineCap(draw2d.ButtCap)
|
||||
gc.MoveTo(64.0, 50.0)
|
||||
gc.LineTo(64.0, 200.0)
|
||||
gc.Stroke()
|
||||
gc.SetLineCap(draw2d.RoundCap)
|
||||
gc.MoveTo(128.0, 50.0)
|
||||
gc.LineTo(128.0, 200.0)
|
||||
gc.Stroke()
|
||||
gc.SetLineCap(draw2d.SquareCap)
|
||||
gc.MoveTo(192.0, 50.0)
|
||||
gc.LineTo(192.0, 200.0)
|
||||
gc.Stroke()
|
||||
|
||||
/* draw helping lines */
|
||||
gc.SetStrokeColor(image.NRGBAColor{0xFF, 0x33, 0x33, 0xFF})
|
||||
gc.SetLineWidth(2.56)
|
||||
gc.MoveTo(64.0, 50.0)
|
||||
gc.LineTo(64.0, 200.0)
|
||||
gc.MoveTo(128.0, 50.0)
|
||||
gc.LineTo(128.0, 200.0)
|
||||
gc.MoveTo(192.0, 50.0)
|
||||
gc.LineTo(192.0, 200.0)
|
||||
gc.Stroke()
|
||||
saveToPngFile("TestLineCap", i)
|
||||
}
|
||||
func TestLineJoin() {
|
||||
i, gc := initGc(w, h)
|
||||
gc.SetLineWidth(40.96)
|
||||
gc.MoveTo(76.8, 84.48)
|
||||
gc.RLineTo(51.2, -51.2)
|
||||
gc.RLineTo(51.2, 51.2)
|
||||
gc.SetLineJoin(draw2d.MiterJoin) /* default */
|
||||
gc.Stroke()
|
||||
|
||||
gc.MoveTo(76.8, 161.28)
|
||||
gc.RLineTo(51.2, -51.2)
|
||||
gc.RLineTo(51.2, 51.2)
|
||||
gc.SetLineJoin(draw2d.BevelJoin)
|
||||
gc.Stroke()
|
||||
|
||||
gc.MoveTo(76.8, 238.08)
|
||||
gc.RLineTo(51.2, -51.2)
|
||||
gc.RLineTo(51.2, 51.2)
|
||||
gc.SetLineJoin(draw2d.RoundJoin)
|
||||
gc.Stroke()
|
||||
saveToPngFile("TestLineJoin", i)
|
||||
}
|
||||
|
||||
func TestBubble() {
|
||||
i, gc := initGc(w, h)
|
||||
gc.BeginPath()
|
||||
gc.MoveTo(75, 25)
|
||||
gc.QuadCurveTo(25, 25, 25, 62.5)
|
||||
gc.QuadCurveTo(25, 100, 50, 100)
|
||||
gc.QuadCurveTo(50, 120, 30, 125)
|
||||
gc.QuadCurveTo(60, 120, 65, 100)
|
||||
gc.QuadCurveTo(125, 100, 125, 62.5)
|
||||
gc.QuadCurveTo(125, 25, 75, 25)
|
||||
gc.Stroke()
|
||||
saveToPngFile("TestBubble", i)
|
||||
}
|
||||
|
||||
func TestStar() {
|
||||
i, gc := initGc(w, h)
|
||||
for i := 0.0; i < 360; i = i + 10 { // Go from 0 to 360 degrees in 10 degree steps
|
||||
gc.Save()
|
||||
gc.SetLineWidth(5) // Keep rotations temporary
|
||||
gc.Translate(144, 144)
|
||||
gc.Rotate(i * (math.Pi / 180.0)) // Rotate by degrees on stack from 'for'
|
||||
gc.MoveTo(0, 0)
|
||||
gc.LineTo(72, 0)
|
||||
gc.Stroke()
|
||||
gc.Restore()
|
||||
}
|
||||
saveToPngFile("TestStar", i)
|
||||
}
|
||||
|
||||
func TestTransform() {
|
||||
i, gc := initGc(800, 600)
|
||||
|
||||
gc.Save()
|
||||
gc.Translate(40, 40) // Set origin to (40, 40)
|
||||
gc.BeginPath()
|
||||
gc.MoveTo(0, 0)
|
||||
gc.RLineTo(72, 0)
|
||||
gc.RLineTo(0, 72)
|
||||
gc.RLineTo(-72, 0)
|
||||
gc.Close()
|
||||
gc.Stroke()
|
||||
gc.Restore()
|
||||
|
||||
gc.Save()
|
||||
gc.Translate(100, 150) // Translate origin to (100, 150)
|
||||
gc.Rotate(30 * (math.Pi / 180.0)) // Rotate counter-clockwise by 30 degrees
|
||||
gc.BeginPath()
|
||||
gc.MoveTo(0, 0)
|
||||
gc.RLineTo(72, 0)
|
||||
gc.RLineTo(0, 72)
|
||||
gc.RLineTo(-72, 0)
|
||||
gc.Close() // Draw box...
|
||||
gc.Stroke()
|
||||
gc.Restore()
|
||||
|
||||
gc.Save()
|
||||
gc.Translate(40, 300) // Translate to (40, 300)
|
||||
gc.Scale(0.5, 1) // Reduce x coord by 1/2, y coord left alone
|
||||
gc.BeginPath()
|
||||
gc.MoveTo(0, 0)
|
||||
gc.RLineTo(72, 0)
|
||||
gc.RLineTo(0, 72)
|
||||
gc.RLineTo(-72, 0)
|
||||
gc.Close() // Draw box...
|
||||
gc.Stroke()
|
||||
gc.Restore()
|
||||
|
||||
gc.Save()
|
||||
gc.Translate(300, 300) // Set origin to (300, 300)
|
||||
gc.Rotate(45 * (math.Pi / 180.0)) // Rotate coordinates by 45 degrees
|
||||
gc.Scale(0.5, 1) // Scale coordinates
|
||||
gc.BeginPath()
|
||||
gc.MoveTo(0, 0)
|
||||
gc.RLineTo(72, 0)
|
||||
gc.RLineTo(0, 72)
|
||||
gc.RLineTo(-72, 0)
|
||||
gc.Close() // Draw box
|
||||
gc.Stroke()
|
||||
gc.Restore()
|
||||
|
||||
saveToPngFile("TestTransform", i)
|
||||
}
|
||||
|
||||
func TestPathTransform() {
|
||||
i, gc := initGc(800, 600)
|
||||
gc.SetLineWidth(20)
|
||||
gc.Scale(1, 4)
|
||||
gc.ArcTo(200, 80, 50, 50, 0, math.Pi*2)
|
||||
gc.Close()
|
||||
gc.Stroke()
|
||||
saveToPngFile("TestPathTransform", i)
|
||||
}
|
||||
|
||||
func TestFillString() {
|
||||
draw2d.SetFontFolder("../resource/font/")
|
||||
i, gc := initGc(100, 100)
|
||||
draw2d.RoundRect(gc, 5, 5, 95, 95, 10, 10)
|
||||
gc.FillStroke()
|
||||
gc.SetFontSize(18)
|
||||
gc.MoveTo(10, 52)
|
||||
gc.SetFontData(draw2d.FontData{"luxi", draw2d.FontFamilyMono, draw2d.FontStyleBold | draw2d.FontStyleItalic})
|
||||
width := gc.FillString("cou")
|
||||
gc.RMoveTo(width+1, 0)
|
||||
gc.FillString("cou")
|
||||
saveToPngFile("TestFillString", i)
|
||||
}
|
||||
|
||||
func TestBigPicture() {
|
||||
i, gc := initGc(w, h)
|
||||
gc.SetLineWidth(10)
|
||||
|
||||
draw2d.Rect(gc, 0, 0, w, h)
|
||||
gc.Fill()
|
||||
saveToPngFile("TestBigPicture", i)
|
||||
}
|
||||
|
||||
func main() {
|
||||
t := time.Nanoseconds()
|
||||
TestPath()
|
||||
TestDrawArc()
|
||||
TestDrawArcNegative()
|
||||
TestCurveRectangle()
|
||||
TestDrawCubicCurve()
|
||||
TestDash()
|
||||
TestFillStroke()
|
||||
TestFillStyle()
|
||||
TestMultiSegmentCaps()
|
||||
TestRoundRectangle()
|
||||
TestLineCap()
|
||||
TestLineJoin()
|
||||
TestBubble()
|
||||
TestStar()
|
||||
TestTransform()
|
||||
TestPathTransform()
|
||||
TestFillString()
|
||||
TestBigPicture()
|
||||
dt := time.Nanoseconds() - t
|
||||
fmt.Printf("All tests during: %f ms\n", float64(dt)*1e-6)
|
||||
}
|
|
@ -1,134 +0,0 @@
|
|||
package main
|
||||
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"bufio"
|
||||
"time"
|
||||
"math"
|
||||
|
||||
"image"
|
||||
"image/png"
|
||||
"draw2d.googlecode.com/hg/draw2d"
|
||||
)
|
||||
|
||||
const (
|
||||
width, height = 300, 200
|
||||
)
|
||||
|
||||
var (
|
||||
lastTime int64
|
||||
folder = "../resource/result/"
|
||||
)
|
||||
|
||||
func initGc(w, h int) (image.Image, draw2d.GraphicContext) {
|
||||
i := image.NewRGBA(image.Rect(0, 0, w, h))
|
||||
gc := draw2d.NewGraphicContext(i)
|
||||
lastTime = time.Nanoseconds()
|
||||
|
||||
gc.SetStrokeColor(image.Black)
|
||||
gc.SetFillColor(image.White)
|
||||
// fill the background
|
||||
//gc.Clear()
|
||||
|
||||
return i, gc
|
||||
}
|
||||
|
||||
func saveToPngFile(TestName string, m image.Image) {
|
||||
dt := time.Nanoseconds() - lastTime
|
||||
fmt.Printf("%s during: %f ms\n", TestName, float64(dt)*1e-6)
|
||||
filePath := folder + TestName + ".png"
|
||||
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)
|
||||
}
|
||||
fmt.Printf("Wrote %s OK.\n", filePath)
|
||||
}
|
||||
|
||||
func gordon(gc draw2d.GraphicContext, x, y, w, h float64) {
|
||||
h23 := (h * 2) / 3
|
||||
|
||||
blf := image.RGBAColor{0, 0, 0, 0xff}
|
||||
wf := image.RGBAColor{0xff, 0xff, 0xff, 0xff}
|
||||
nf := image.RGBAColor{0x8B, 0x45, 0x13, 0xff}
|
||||
brf := image.RGBAColor{0x8B, 0x45, 0x13, 0x99}
|
||||
brb := image.RGBAColor{0x8B, 0x45, 0x13, 0xBB}
|
||||
|
||||
gc.MoveTo(x, y+h)
|
||||
gc.CubicCurveTo(x, y+h, x+w/2, y-h, x+w, y+h)
|
||||
gc.Close()
|
||||
gc.SetFillColor(brb)
|
||||
gc.Fill()
|
||||
draw2d.RoundRect(gc, x, y+h, x+w, y+h+h, 10, 10)
|
||||
gc.Fill()
|
||||
draw2d.Circle(gc, x, y+h, w/12) // left ear
|
||||
gc.SetFillColor(brf)
|
||||
gc.Fill()
|
||||
draw2d.Circle(gc, x, y+h, w/12-10)
|
||||
gc.SetFillColor(nf)
|
||||
gc.Fill()
|
||||
|
||||
draw2d.Circle(gc, x+w, y+h, w/12) // right ear
|
||||
gc.SetFillColor(brf)
|
||||
gc.Fill()
|
||||
draw2d.Circle(gc, x+w, y+h, w/12-10)
|
||||
gc.SetFillColor(nf)
|
||||
gc.Fill()
|
||||
|
||||
draw2d.Circle(gc, x+w/3, y+h23, w/9) // left eye
|
||||
gc.SetFillColor(wf)
|
||||
gc.Fill()
|
||||
draw2d.Circle(gc, x+w/3+10, y+h23, w/10-10)
|
||||
gc.SetFillColor(blf)
|
||||
gc.Fill()
|
||||
draw2d.Circle(gc, x+w/3+15, y+h23, 5)
|
||||
gc.SetFillColor(wf)
|
||||
gc.Fill()
|
||||
|
||||
draw2d.Circle(gc, x+w-w/3, y+h23, w/9) // right eye
|
||||
gc.Fill()
|
||||
draw2d.Circle(gc, x+w-w/3+10, y+h23, w/10-10)
|
||||
gc.SetFillColor(blf)
|
||||
gc.Fill()
|
||||
draw2d.Circle(gc, x+w-(w/3)+15, y+h23, 5)
|
||||
gc.SetFillColor(wf)
|
||||
gc.Fill()
|
||||
|
||||
gc.SetFillColor(wf)
|
||||
draw2d.RoundRect(gc, x+w/2-w/8, y+h+30, x+w/2-w/8+w/8, y+h+30+w/6, 5, 5) // left tooth
|
||||
gc.Fill()
|
||||
draw2d.RoundRect(gc, x+w/2, y+h+30, x+w/2+w/8, y+h+30+w/6, 5, 5) // right tooth
|
||||
gc.Fill()
|
||||
|
||||
draw2d.Ellipse(gc, x+(w/2), y+h+30, w/6, w/12) // snout
|
||||
gc.SetFillColor(nf)
|
||||
gc.Fill()
|
||||
draw2d.Ellipse(gc, x+(w/2), y+h+10, w/10, w/12) // nose
|
||||
gc.SetFillColor(blf)
|
||||
gc.Fill()
|
||||
|
||||
}
|
||||
|
||||
func main() {
|
||||
i, gc := initGc(width, height)
|
||||
gc.Clear()
|
||||
gc.Translate(-75, 58)
|
||||
gc.Rotate(-30 * (math.Pi / 180.0))
|
||||
gordon(gc, 48, 48, 240, 72)
|
||||
saveToPngFile("TestGopher", i)
|
||||
}
|
|
@ -1,71 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"bufio"
|
||||
"math"
|
||||
"image"
|
||||
"time"
|
||||
"image/png"
|
||||
"image/draw"
|
||||
"draw2d.googlecode.com/hg/draw2d"
|
||||
)
|
||||
|
||||
|
||||
func saveToPngFile(filePath string, m image.Image) {
|
||||
f, err := os.Create(filePath)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
b := bufio.NewWriter(f)
|
||||
err = png.Encode(b, m)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
err = b.Flush()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("Wrote %s OK.\n", filePath)
|
||||
}
|
||||
|
||||
func loadFromPngFile(filePath string) image.Image {
|
||||
f, err := os.OpenFile(filePath, 0, 0)
|
||||
if f == nil {
|
||||
log.Printf("can't open file; err=%s\n", err.String())
|
||||
return nil
|
||||
}
|
||||
defer f.Close()
|
||||
b := bufio.NewReader(f)
|
||||
i, err := png.Decode(b)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Read %s OK.\n", filePath)
|
||||
return i
|
||||
}
|
||||
|
||||
|
||||
func main() {
|
||||
source := loadFromPngFile("../resource/image/TestAndroid.png")
|
||||
dest := image.NewRGBA(image.Rect(0, 0, 1024, 768))
|
||||
width, height := float64(source.Bounds().Dx()), float64(source.Bounds().Dy())
|
||||
tr := draw2d.NewIdentityMatrix()
|
||||
tr.Translate(width/2, height/2)
|
||||
tr.Rotate(30 * math.Pi / 180)
|
||||
//tr.Scale(3, 3)
|
||||
tr.Translate(-width/2, -height/2)
|
||||
tr.Translate(200, 5)
|
||||
lastTime := time.Nanoseconds()
|
||||
draw2d.DrawImage(source, dest, tr, draw.Over, draw2d.BilinearFilter)
|
||||
dt := time.Nanoseconds() - lastTime
|
||||
fmt.Printf("Draw image: %f ms\n", float64(dt)*1e-6)
|
||||
saveToPngFile("../resource/result/TestDrawImage.png", dest)
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
package main
|
||||
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
"log"
|
||||
"os"
|
||||
"io/ioutil"
|
||||
"bufio"
|
||||
"strings"
|
||||
"image"
|
||||
"image/png"
|
||||
"draw2d.googlecode.com/hg/draw2d"
|
||||
"draw2d.googlecode.com/hg/postscript"
|
||||
)
|
||||
|
||||
|
||||
func saveToPngFile(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)
|
||||
}
|
||||
fmt.Printf("Wrote %s OK.\n", filePath)
|
||||
}
|
||||
|
||||
func main() {
|
||||
i := image.NewRGBA(image.Rect(0, 0, 600, 800))
|
||||
gc := draw2d.NewGraphicContext(i)
|
||||
gc.Translate(0, 380)
|
||||
gc.Scale(1, -1)
|
||||
gc.Translate(0, -380)
|
||||
src, err := os.OpenFile("../resource/postscript/tiger.ps", 0, 0)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer src.Close()
|
||||
bytes, err := ioutil.ReadAll(src)
|
||||
reader := strings.NewReader(string(bytes))
|
||||
interpreter := postscript.NewInterpreter(gc)
|
||||
lastTime := time.Nanoseconds()
|
||||
interpreter.Execute(reader)
|
||||
dt := time.Nanoseconds() - lastTime
|
||||
fmt.Printf("Draw image: %f ms\n", float64(dt)*1e-6)
|
||||
saveToPngFile("../resource/result/TestPostscript.png", i)
|
||||
}
|
306339
doc/PLRM.pdf
306339
doc/PLRM.pdf
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,255 @@
|
|||
// Copyright 2010 The draw2d Authors. All rights reserved.
|
||||
// created: 13/12/2010 by Laurent Le Goff
|
||||
|
||||
// Package draw2d is a pure go 2D vector graphics library with support
|
||||
// for multiple output devices such as images (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 alternative.
|
||||
// draw2d is released under the BSD license.
|
||||
//
|
||||
// Features
|
||||
//
|
||||
// Operations in draw2d include stroking and filling polygons, arcs,
|
||||
// Bézier curves, drawing images and text rendering with truetype fonts.
|
||||
// All drawing operations can be transformed by affine transformations
|
||||
// (scale, rotation, translation).
|
||||
//
|
||||
// Package draw2d follows the conventions of http://www.w3.org/TR/2dcontext for coordinate system, angles, etc...
|
||||
//
|
||||
// Installation
|
||||
//
|
||||
// To install or update the package draw2d on your system, run:
|
||||
// go get -u github.com/llgcode/draw2d
|
||||
//
|
||||
// Quick Start
|
||||
//
|
||||
// Package draw2d itself provides a graphic context that can draw vector
|
||||
// graphics and text on an image canvas. The following Go code
|
||||
// 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
|
||||
// dest := image.NewRGBA(image.Rect(0, 0, 297, 210.0))
|
||||
// gc := draw2dimg.NewGraphicContext(dest)
|
||||
//
|
||||
// // Set some properties
|
||||
// gc.SetFillColor(color.RGBA{0x44, 0xff, 0x44, 0xff})
|
||||
// gc.SetStrokeColor(color.RGBA{0x44, 0x44, 0x44, 0xff})
|
||||
// gc.SetLineWidth(5)
|
||||
//
|
||||
// // Draw a closed shape
|
||||
// gc.MoveTo(10, 10) // should always be called first for a new path
|
||||
// gc.LineTo(100, 50)
|
||||
// gc.QuadCurveTo(100, 10, 10, 10)
|
||||
// gc.Close()
|
||||
// gc.FillStroke()
|
||||
//
|
||||
// // Save to file
|
||||
// draw2dimg.SaveToPngFile("hello.png", dest)
|
||||
// }
|
||||
//
|
||||
//
|
||||
// There are more examples here:
|
||||
// https://github.com/llgcode/draw2d/tree/master/samples
|
||||
//
|
||||
// Drawing on pdf documents is provided by the draw2dpdf package.
|
||||
// Drawing on opengl is provided by the draw2dgl package.
|
||||
// See subdirectories at the bottom of this page.
|
||||
//
|
||||
// Testing
|
||||
//
|
||||
// The samples are run as tests from the root package folder `draw2d` by:
|
||||
// go test ./...
|
||||
//
|
||||
// Or if you want to run with test coverage:
|
||||
// go test -cover ./... | grep -v "no test"
|
||||
//
|
||||
// This will generate output by the different backends in the output folder.
|
||||
//
|
||||
// Acknowledgments
|
||||
//
|
||||
// Laurent Le Goff wrote this library, inspired by Postscript and
|
||||
// HTML5 canvas. He implemented the image and opengl backend with the
|
||||
// freetype-go package. Also he created a pure go Postscript
|
||||
// interpreter, which can read postscript images and draw to a draw2d
|
||||
// graphic context (https://github.com/llgcode/ps). Stani Michiels
|
||||
// implemented the pdf backend with the gofpdf package.
|
||||
//
|
||||
// Packages using draw2d
|
||||
//
|
||||
// - https://github.com/llgcode/ps: Postscript interpreter written in Go
|
||||
//
|
||||
// - https://github.com/gonum/plot: drawing plots in Go
|
||||
//
|
||||
// - https://github.com/muesli/smartcrop: content aware image cropping
|
||||
//
|
||||
// - https://github.com/peterhellberg/karta: drawing Voronoi diagrams
|
||||
//
|
||||
// - https://github.com/vdobler/chart: basic charts in Go
|
||||
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
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
include $(GOROOT)/src/Make.inc
|
||||
|
||||
TARG=draw2d.googlecode.com/hg/draw2d
|
||||
GOFILES=\
|
||||
draw2d.go\
|
||||
arc.go\
|
||||
curves.go\
|
||||
image.go\
|
||||
math.go\
|
||||
path.go\
|
||||
transform.go\
|
||||
dasher.go\
|
||||
demux_converter.go\
|
||||
font.go\
|
||||
path_adder.go\
|
||||
path_converter.go\
|
||||
path_storage.go\
|
||||
stroker.go\
|
||||
advanced_path.go\
|
||||
vertex2d.go\
|
||||
gc.go\
|
||||
paint.go\
|
||||
stack_gc.go\
|
||||
rgba_interpolation.go\
|
||||
|
||||
include $(GOROOT)/src/Make.pkg
|
|
@ -1,67 +0,0 @@
|
|||
// Copyright 2010 The draw2d Authors. All rights reserved.
|
||||
// created: 21/11/2010 by Laurent Le Goff
|
||||
package draw2d
|
||||
|
||||
import (
|
||||
"freetype-go.googlecode.com/hg/freetype/raster"
|
||||
"math"
|
||||
)
|
||||
|
||||
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.Fabs(rx) + math.Fabs(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)
|
||||
}
|
||||
return 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.Fabs(rx) + math.Fabs(ry)) / 2
|
||||
da := math.Acos(ra/(ra+0.125/scale)) * 2
|
||||
//normalize
|
||||
if !clockWise {
|
||||
da = -da
|
||||
}
|
||||
angle = start + da
|
||||
var curX, curY float64
|
||||
for {
|
||||
if (angle < end-da/4) != clockWise {
|
||||
curX = x + math.Cos(end)*rx
|
||||
curY = y + math.Sin(end)*ry
|
||||
return raster.Point{raster.Fix32(curX * 256), raster.Fix32(curY * 256)}
|
||||
}
|
||||
curX = x + math.Cos(angle)*rx
|
||||
curY = y + math.Sin(angle)*ry
|
||||
|
||||
angle += da
|
||||
adder.Add1(raster.Point{raster.Fix32(curX * 256), raster.Fix32(curY * 256)})
|
||||
}
|
||||
return raster.Point{raster.Fix32(curX * 256), raster.Fix32(curY * 256)}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
include $(GOROOT)/src/Make.inc
|
||||
|
||||
TARG=draw2d.googlecode.com/hg/draw2d/curve
|
||||
GOFILES=\
|
||||
cubic_float64.go\
|
||||
quad_float64.go\
|
||||
cubic_float64_others.go\
|
||||
|
||||
|
||||
|
||||
include $(GOROOT)/src/Make.pkg
|
|
@ -1,41 +0,0 @@
|
|||
package main
|
||||
|
||||
import "draw2d.googlecode.com/hg/draw2d/curve"
|
||||
import "testing"
|
||||
import __os__ "os"
|
||||
import __regexp__ "regexp"
|
||||
|
||||
var tests = []testing.InternalTest{
|
||||
{"curve.TestCubicCurveRec", curve.TestCubicCurveRec},
|
||||
{"curve.TestCubicCurve", curve.TestCubicCurve},
|
||||
{"curve.TestCubicCurveAdaptiveRec", curve.TestCubicCurveAdaptiveRec},
|
||||
{"curve.TestCubicCurveAdaptive", curve.TestCubicCurveAdaptive},
|
||||
{"curve.TestCubicCurveParabolic", curve.TestCubicCurveParabolic},
|
||||
{"curve.TestQuadCurve", curve.TestQuadCurve},
|
||||
}
|
||||
|
||||
var benchmarks = []testing.InternalBenchmark{ {"curve.BenchmarkCubicCurveRec", curve.BenchmarkCubicCurveRec},
|
||||
{"curve.BenchmarkCubicCurve", curve.BenchmarkCubicCurve},
|
||||
{"curve.BenchmarkCubicCurveAdaptiveRec", curve.BenchmarkCubicCurveAdaptiveRec},
|
||||
{"curve.BenchmarkCubicCurveAdaptive", curve.BenchmarkCubicCurveAdaptive},
|
||||
{"curve.BenchmarkCubicCurveParabolic", curve.BenchmarkCubicCurveParabolic},
|
||||
{"curve.BenchmarkQuadCurve", curve.BenchmarkQuadCurve},
|
||||
}
|
||||
|
||||
var matchPat string
|
||||
var matchRe *__regexp__.Regexp
|
||||
|
||||
func matchString(pat, str string) (result bool, err __os__.Error) {
|
||||
if matchRe == nil || matchPat != pat {
|
||||
matchPat = pat
|
||||
matchRe, err = __regexp__.Compile(matchPat)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return matchRe.MatchString(str), nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
testing.Main(matchString, tests, benchmarks)
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
// Copyright 2010 The draw2d Authors. All rights reserved.
|
||||
// created: 21/11/2010 by Laurent Le Goff
|
||||
package draw2d
|
||||
|
||||
import (
|
||||
"freetype-go.googlecode.com/hg/freetype/raster"
|
||||
"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.Fabs(rx) + math.Fabs(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)
|
||||
}
|
||||
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.Fabs(((c[2]-c[6])*dy - (c[3]-c[7])*dx))
|
||||
d3 = math.Fabs(((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,700 +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.Fabs(((c[2]-c[6])*dy - (c[3]-c[7])*dx))
|
||||
d3 := math.Fabs(((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.Fabs(((c[2]-c[6])*dy - (c[3]-c[7])*dx))
|
||||
d3 := math.Fabs(((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.Fabs(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.Fabs(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.Fabs(k - math.Atan2(c[3]-c[1], c[2]-c[0]))
|
||||
da2 := math.Fabs(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.Fabs(((c[2]-c[6])*dy - (c[3]-c[7])*dx))
|
||||
d3 = math.Fabs(((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.Fabs(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.Fabs(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.Fabs(k - math.Atan2(c[3]-c[1], c[2]-c[0]))
|
||||
da2 := math.Fabs(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.Fabs(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.Fabs(((c[2]-c[6])*dy - (c[3]-c[7])*dx))
|
||||
d3 = math.Fabs(((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.Fabs(((b1[2]-b1[6])*dy - (b1[3]-b1[7])*dx))
|
||||
d3 = math.Fabs(((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 (
|
||||
"testing"
|
||||
"log"
|
||||
"fmt"
|
||||
"os"
|
||||
"bufio"
|
||||
"image"
|
||||
"image/png"
|
||||
"image/draw"
|
||||
"draw2d.googlecode.com/hg/draw2d/raster"
|
||||
)
|
||||
|
||||
|
||||
var (
|
||||
flattening_threshold float64 = 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("_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 image.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, flattening_threshold)
|
||||
img := image.NewNRGBA(300, 300)
|
||||
raster.PolylineBresenham(img, image.NRGBAColor{0xff, 0, 0, 0xff}, curve[:]...)
|
||||
raster.PolylineBresenham(img, image.Black, p.points...)
|
||||
//drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, curve[:]...)
|
||||
drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, p.points...)
|
||||
savepng(fmt.Sprintf("_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, flattening_threshold)
|
||||
img := image.NewNRGBA(300, 300)
|
||||
raster.PolylineBresenham(img, image.NRGBAColor{0xff, 0, 0, 0xff}, curve[:]...)
|
||||
raster.PolylineBresenham(img, image.Black, p.points...)
|
||||
//drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, curve[:]...)
|
||||
drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, p.points...)
|
||||
savepng(fmt.Sprintf("_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(300, 300)
|
||||
raster.PolylineBresenham(img, image.NRGBAColor{0xff, 0, 0, 0xff}, curve[:]...)
|
||||
raster.PolylineBresenham(img, image.Black, p.points...)
|
||||
//drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, curve[:]...)
|
||||
drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, p.points...)
|
||||
savepng(fmt.Sprintf("_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(300, 300)
|
||||
raster.PolylineBresenham(img, image.NRGBAColor{0xff, 0, 0, 0xff}, curve[:]...)
|
||||
raster.PolylineBresenham(img, image.Black, p.points...)
|
||||
//drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, curve[:]...)
|
||||
drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, p.points...)
|
||||
savepng(fmt.Sprintf("_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, flattening_threshold)
|
||||
img := image.NewNRGBA(300, 300)
|
||||
raster.PolylineBresenham(img, image.NRGBAColor{0xff, 0, 0, 0xff}, curve[:]...)
|
||||
raster.PolylineBresenham(img, image.Black, p.points...)
|
||||
//drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, curve[:]...)
|
||||
drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, p.points...)
|
||||
savepng(fmt.Sprintf("_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, flattening_threshold)
|
||||
img := image.NewNRGBA(300, 300)
|
||||
raster.PolylineBresenham(img, image.NRGBAColor{0xff, 0, 0, 0xff}, curve[:]...)
|
||||
raster.PolylineBresenham(img, image.Black, p.points...)
|
||||
//drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, curve[:]...)
|
||||
drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, p.points...)
|
||||
savepng(fmt.Sprintf("_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, flattening_threshold)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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, flattening_threshold)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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, flattening_threshold)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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, flattening_threshold)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,53 +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.Fabs(((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++
|
||||
}
|
||||
}
|
||||
}
|
338
draw2d/curves.go
338
draw2d/curves.go
|
@ -1,338 +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.Fabs(((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.Fabs(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.Fabs(((x2-x4)*dy - (y2-y4)*dx))
|
||||
d3 := math.Fabs(((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.Fabs(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.Fabs(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.Fabs(k - math.Atan2(y2-y1, x2-x1))
|
||||
da2 := math.Fabs(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,22 +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)
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
// Copyright 2011 The draw2d Authors. All rights reserved.
|
||||
// created: 29/09/2011 by Laurent Le Goff
|
||||
|
||||
// The package draw2d provide a Graphic Context that can draw vectorial figure on surface.
|
||||
package draw2d
|
|
@ -1,93 +0,0 @@
|
|||
// Copyright 2010 The draw2d Authors. All rights reserved.
|
||||
// created: 13/12/2010 by Laurent Le Goff
|
||||
package draw2d
|
||||
|
||||
|
||||
import (
|
||||
"freetype-go.googlecode.com/hg/freetype"
|
||||
"freetype-go.googlecode.com/hg/freetype/truetype"
|
||||
"path"
|
||||
"log"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
|
||||
var (
|
||||
fontFolder = "../resource/font/"
|
||||
fonts = make(map[string]*truetype.Font)
|
||||
)
|
||||
|
||||
|
||||
type FontStyle byte
|
||||
|
||||
const (
|
||||
FontStyleNormal FontStyle = iota
|
||||
FontStyleBold
|
||||
FontStyleItalic
|
||||
)
|
||||
|
||||
type FontFamily byte
|
||||
|
||||
const (
|
||||
FontFamilySans FontFamily = iota
|
||||
FontFamilySerif
|
||||
FontFamilyMono
|
||||
)
|
||||
|
||||
|
||||
type FontData struct {
|
||||
Name string
|
||||
Family FontFamily
|
||||
Style FontStyle
|
||||
}
|
||||
|
||||
|
||||
func GetFont(fontData FontData) *truetype.Font {
|
||||
fontFileName := fontData.Name
|
||||
switch fontData.Family {
|
||||
case FontFamilySans:
|
||||
fontFileName += "s"
|
||||
case FontFamilySerif:
|
||||
fontFileName += "r"
|
||||
case FontFamilyMono:
|
||||
fontFileName += "m"
|
||||
}
|
||||
if fontData.Style&FontStyleBold != 0 {
|
||||
fontFileName += "b"
|
||||
} else {
|
||||
fontFileName += "r"
|
||||
}
|
||||
|
||||
if fontData.Style&FontStyleItalic != 0 {
|
||||
fontFileName += "i"
|
||||
}
|
||||
fontFileName += ".ttf"
|
||||
font := fonts[fontFileName]
|
||||
if font != nil {
|
||||
return font
|
||||
}
|
||||
fonts[fontFileName] = loadFont(fontFileName)
|
||||
return fonts[fontFileName]
|
||||
}
|
||||
|
||||
func GetFontFolder() string {
|
||||
return fontFolder
|
||||
}
|
||||
|
||||
func SetFontFolder(folder string) {
|
||||
fontFolder = folder
|
||||
}
|
||||
|
||||
func loadFont(fontFileName string) *truetype.Font {
|
||||
fontBytes, err := ioutil.ReadFile(path.Join(fontFolder, fontFileName))
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return nil
|
||||
}
|
||||
font, err := freetype.ParseFont(fontBytes)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return nil
|
||||
}
|
||||
return font
|
||||
}
|
47
draw2d/gc.go
47
draw2d/gc.go
|
@ -1,47 +0,0 @@
|
|||
// Copyright 2010 The draw2d Authors. All rights reserved.
|
||||
// created: 21/11/2010 by Laurent Le Goff
|
||||
package draw2d
|
||||
|
||||
import (
|
||||
"image"
|
||||
)
|
||||
|
||||
type FillRule int
|
||||
|
||||
const (
|
||||
FillRuleEvenOdd FillRule = iota
|
||||
FillRuleWinding
|
||||
)
|
||||
|
||||
type GraphicContext interface {
|
||||
BeginPath()
|
||||
Path
|
||||
GetMatrixTransform() MatrixTransform
|
||||
SetMatrixTransform(tr MatrixTransform)
|
||||
ComposeMatrixTransform(tr MatrixTransform)
|
||||
Rotate(angle float64)
|
||||
Translate(tx, ty float64)
|
||||
Scale(sx, sy float64)
|
||||
SetStrokeColor(c image.Color)
|
||||
SetFillColor(c image.Color)
|
||||
SetFillRule(f FillRule)
|
||||
SetLineWidth(lineWidth float64)
|
||||
SetLineCap(cap Cap)
|
||||
SetLineJoin(join Join)
|
||||
SetLineDash(dash []float64, dashOffset float64)
|
||||
SetFontSize(fontSize float64)
|
||||
GetFontSize() float64
|
||||
SetFontData(fontData FontData)
|
||||
GetFontData() FontData
|
||||
DrawImage(image image.Image)
|
||||
Save()
|
||||
Restore()
|
||||
Clear()
|
||||
ClearRect(x1, y1, x2, y2 int)
|
||||
SetDPI(dpi int)
|
||||
GetDPI() int
|
||||
FillString(text string) (cursor float64)
|
||||
Stroke(paths ...*PathStorage)
|
||||
Fill(paths ...*PathStorage)
|
||||
FillStroke(paths ...*PathStorage)
|
||||
}
|
207
draw2d/image.go
207
draw2d/image.go
|
@ -1,207 +0,0 @@
|
|||
// Copyright 2010 The draw2d Authors. All rights reserved.
|
||||
// created: 21/11/2010 by Laurent Le Goff
|
||||
package draw2d
|
||||
|
||||
import (
|
||||
"image/draw"
|
||||
"image"
|
||||
"log"
|
||||
"freetype-go.googlecode.com/hg/freetype"
|
||||
"freetype-go.googlecode.com/hg/freetype/raster"
|
||||
)
|
||||
|
||||
type Painter interface {
|
||||
raster.Painter
|
||||
SetColor(color image.Color)
|
||||
}
|
||||
|
||||
var (
|
||||
defaultFontData = FontData{"luxi", FontFamilySans, FontStyleNormal}
|
||||
)
|
||||
|
||||
type ImageGraphicContext struct {
|
||||
*StackGraphicContext
|
||||
img draw.Image
|
||||
painter Painter
|
||||
fillRasterizer *raster.Rasterizer
|
||||
strokeRasterizer *raster.Rasterizer
|
||||
freetype *freetype.Context
|
||||
DPI int
|
||||
}
|
||||
|
||||
/**
|
||||
* Create 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)
|
||||
//case *image.NRGBA:
|
||||
// painter = NewNRGBAPainter(selectImage)
|
||||
default:
|
||||
panic("Image type not supported")
|
||||
}
|
||||
width, height := img.Bounds().Dx(), img.Bounds().Dy()
|
||||
dpi := 92
|
||||
ftContext := freetype.NewContext()
|
||||
ftContext.SetDPI(dpi)
|
||||
ftContext.SetClip(img.Bounds())
|
||||
ftContext.SetDst(img)
|
||||
gc := &ImageGraphicContext{
|
||||
NewStackGraphicContext(),
|
||||
img,
|
||||
painter,
|
||||
raster.NewRasterizer(width, height),
|
||||
raster.NewRasterizer(width, height),
|
||||
ftContext,
|
||||
dpi,
|
||||
}
|
||||
return gc
|
||||
}
|
||||
|
||||
|
||||
func (gc *ImageGraphicContext) SetDPI(dpi int) {
|
||||
gc.DPI = dpi
|
||||
gc.freetype.SetDPI(dpi)
|
||||
}
|
||||
|
||||
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.NewColorImage(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) {
|
||||
gc.freetype.SetSrc(image.NewColorImage(gc.Current.StrokeColor))
|
||||
// Draw the text.
|
||||
x, y := gc.Current.Path.LastPoint()
|
||||
gc.Current.Tr.Transform(&x, &y)
|
||||
x0, fontSize := 0.0, gc.Current.FontSize
|
||||
gc.Current.Tr.VectorTransform(&x0, &fontSize)
|
||||
font := GetFont(gc.Current.FontData)
|
||||
if font == nil {
|
||||
font = GetFont(defaultFontData)
|
||||
}
|
||||
if font == nil {
|
||||
return 0
|
||||
}
|
||||
gc.freetype.SetFont(font)
|
||||
gc.freetype.SetFontSize(fontSize)
|
||||
pt := freetype.Pt(int(x), int(y))
|
||||
p, err := gc.freetype.DrawString(text, pt)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
x1, _ := gc.Current.Path.LastPoint()
|
||||
x2, y2 := float64(p.X)/256, float64(p.Y)/256
|
||||
gc.Current.Tr.InverseTransform(&x2, &y2)
|
||||
width := x2 - x1
|
||||
return width
|
||||
}
|
||||
|
||||
|
||||
func (gc *ImageGraphicContext) paint(rasterizer *raster.Rasterizer, color image.Color) {
|
||||
gc.painter.SetColor(color)
|
||||
rasterizer.Rasterize(gc.painter)
|
||||
rasterizer.Clear()
|
||||
gc.Current.Path.Clear()
|
||||
}
|
||||
|
||||
/**** second method ****/
|
||||
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)
|
||||
}
|
||||
|
||||
/**** second method ****/
|
||||
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)
|
||||
}
|
||||
|
||||
/* second method */
|
||||
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
|
||||
}
|
|
@ -1,51 +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
|
||||
}
|
|
@ -1,89 +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}
|
||||
}
|
||||
*/
|
|
@ -1,18 +0,0 @@
|
|||
// Copyright 2010 The draw2d Authors. All rights reserved.
|
||||
// created: 21/11/2010 by Laurent Le Goff
|
||||
package draw2d
|
||||
|
||||
type Path interface {
|
||||
LastPoint() (x, y float64)
|
||||
MoveTo(x, y float64)
|
||||
RMoveTo(dx, dy float64)
|
||||
LineTo(x, y float64)
|
||||
RLineTo(dx, dy float64)
|
||||
QuadCurveTo(cx, cy, x, y float64)
|
||||
RQuadCurveTo(dcx, dcy, dx, dy float64)
|
||||
CubicCurveTo(cx1, cy1, cx2, cy2, x, y float64)
|
||||
RCubicCurveTo(dcx1, dcy1, dcx2, dcy2, dx, dy float64)
|
||||
ArcTo(cx, cy, rx, ry, startAngle, angle float64)
|
||||
RArcTo(dcx, dcy, rx, ry, startAngle, angle float64)
|
||||
Close()
|
||||
}
|
|
@ -1,73 +0,0 @@
|
|||
// Copyright 2010 The draw2d Authors. All rights reserved.
|
||||
// created: 13/12/2010 by Laurent Le Goff
|
||||
package draw2d
|
||||
|
||||
|
||||
import (
|
||||
"freetype-go.googlecode.com/hg/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{raster.Fix32(x * 256), raster.Fix32(y * 256)})
|
||||
default:
|
||||
vertexAdder.adder.Add1(raster.Point{raster.Fix32(x * 256), 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{0, 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{raster.Fix32(path.vertices[j] * 256), raster.Fix32(path.vertices[j+1] * 256)}
|
||||
pathAdder.adder.Start(pathAdder.firstPoint)
|
||||
j += 2
|
||||
case LineTo:
|
||||
pathAdder.adder.Add1(raster.Point{raster.Fix32(path.vertices[j] * 256), raster.Fix32(path.vertices[j+1] * 256)})
|
||||
j += 2
|
||||
case QuadCurveTo:
|
||||
pathAdder.adder.Add2(raster.Point{raster.Fix32(path.vertices[j] * 256), raster.Fix32(path.vertices[j+1] * 256)}, raster.Point{raster.Fix32(path.vertices[j+2] * 256), raster.Fix32(path.vertices[j+3] * 256)})
|
||||
j += 4
|
||||
case CubicCurveTo:
|
||||
pathAdder.adder.Add3(raster.Point{raster.Fix32(path.vertices[j] * 256), raster.Fix32(path.vertices[j+1] * 256)}, raster.Point{raster.Fix32(path.vertices[j+2] * 256), raster.Fix32(path.vertices[j+3] * 256)}, raster.Point{raster.Fix32(path.vertices[j+4] * 256), 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
|
||||
}
|
|
@ -1,189 +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) Clear() {
|
||||
p.commands = p.commands[0:0]
|
||||
p.vertices = p.vertices[0:0]
|
||||
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,12 +0,0 @@
|
|||
include $(GOROOT)/src/Make.inc
|
||||
|
||||
TARG=draw2d.googlecode.com/hg/draw2d/raster
|
||||
GOFILES=\
|
||||
line.go\
|
||||
polygon.go\
|
||||
coverage_table.go\
|
||||
fillerAA.go\
|
||||
fixed_point.go
|
||||
|
||||
|
||||
include $(GOROOT)/src/Make.pkg
|
|
@ -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"
|
||||
"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 *image.RGBAColor, 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 *image.RGBAColor, 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 *image.RGBAColor, 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 *image.RGBAColor, 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,302 +0,0 @@
|
|||
// Copyright 2011 The draw2d Authors. All rights reserved.
|
||||
// created: 27/05/2011 by Laurent Le Goff
|
||||
package raster
|
||||
|
||||
import (
|
||||
"image"
|
||||
"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 *image.RGBAColor, 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 *image.RGBAColor, 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 *image.RGBAColor, 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 *image.RGBAColor, 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,320 +0,0 @@
|
|||
// Copyright 2011 The draw2d Authors. All rights reserved.
|
||||
// created: 27/05/2011 by Laurent Le Goff
|
||||
package raster
|
||||
|
||||
import (
|
||||
"image"
|
||||
"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 *image.RGBAColor, 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 *image.RGBAColor, 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 *image.RGBAColor, 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 *image.RGBAColor, 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,584 +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,201 +0,0 @@
|
|||
package raster
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"log"
|
||||
"image"
|
||||
"os"
|
||||
"bufio"
|
||||
"image/png"
|
||||
"draw2d.googlecode.com/hg/draw2d/curve"
|
||||
"freetype-go.googlecode.com/hg/freetype/raster"
|
||||
)
|
||||
|
||||
var flattening_threshold float64 = 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, flattening_threshold)
|
||||
poly := Polygon(p.points)
|
||||
color := image.RGBAColor{0, 0, 0, 0xff}
|
||||
|
||||
img := image.NewRGBA(200, 200)
|
||||
rasterizer := raster.NewRasterizer(200, 200)
|
||||
rasterizer.UseNonZeroWinding = false
|
||||
rasterizer.Start(raster.Point{raster.Fix32(10 * 256), raster.Fix32(190 * 256)})
|
||||
for j := 0; j < len(poly); j = j + 2 {
|
||||
rasterizer.Add1(raster.Point{raster.Fix32(poly[j] * 256), raster.Fix32(poly[j+1] * 256)})
|
||||
}
|
||||
painter := raster.NewRGBAPainter(img)
|
||||
painter.SetColor(color)
|
||||
rasterizer.Rasterize(painter)
|
||||
|
||||
savepng("_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, flattening_threshold)
|
||||
poly := Polygon(p.points)
|
||||
color := image.RGBAColor{0, 0, 0, 0xff}
|
||||
|
||||
img := image.NewRGBA(200, 200)
|
||||
rasterizer := raster.NewRasterizer(200, 200)
|
||||
rasterizer.UseNonZeroWinding = true
|
||||
rasterizer.Start(raster.Point{raster.Fix32(10 * 256), raster.Fix32(190 * 256)})
|
||||
for j := 0; j < len(poly); j = j + 2 {
|
||||
rasterizer.Add1(raster.Point{raster.Fix32(poly[j] * 256), raster.Fix32(poly[j+1] * 256)})
|
||||
}
|
||||
painter := raster.NewRGBAPainter(img)
|
||||
painter.SetColor(color)
|
||||
rasterizer.Rasterize(painter)
|
||||
|
||||
savepng("_testFreetypeNonZeroWinding.png", img)
|
||||
}
|
||||
|
||||
func TestRasterizer(t *testing.T) {
|
||||
img := image.NewRGBA(200, 200)
|
||||
var p Path
|
||||
p.LineTo(10, 190)
|
||||
c := curve.CubicCurveFloat64{10, 190, 10, 10, 190, 10, 190, 190}
|
||||
c.Segment(&p, flattening_threshold)
|
||||
poly := Polygon(p.points)
|
||||
color := image.RGBAColor{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("_testRasterizer.png", img)
|
||||
}
|
||||
|
||||
func TestRasterizerNonZeroWinding(t *testing.T) {
|
||||
img := image.NewRGBA(200, 200)
|
||||
var p Path
|
||||
p.LineTo(10, 190)
|
||||
c := curve.CubicCurveFloat64{10, 190, 10, 10, 190, 10, 190, 190}
|
||||
c.Segment(&p, flattening_threshold)
|
||||
poly := Polygon(p.points)
|
||||
color := image.RGBAColor{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("_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, flattening_threshold)
|
||||
poly := Polygon(p.points)
|
||||
color := image.RGBAColor{0, 0, 0, 0xff}
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
img := image.NewRGBA(200, 200)
|
||||
rasterizer := raster.NewRasterizer(200, 200)
|
||||
rasterizer.UseNonZeroWinding = false
|
||||
rasterizer.Start(raster.Point{raster.Fix32(10 * 256), raster.Fix32(190 * 256)})
|
||||
for j := 0; j < len(poly); j = j + 2 {
|
||||
rasterizer.Add1(raster.Point{raster.Fix32(poly[j] * 256), 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, flattening_threshold)
|
||||
poly := Polygon(p.points)
|
||||
color := image.RGBAColor{0, 0, 0, 0xff}
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
img := image.NewRGBA(200, 200)
|
||||
rasterizer := raster.NewRasterizer(200, 200)
|
||||
rasterizer.UseNonZeroWinding = true
|
||||
rasterizer.Start(raster.Point{raster.Fix32(10 * 256), raster.Fix32(190 * 256)})
|
||||
for j := 0; j < len(poly); j = j + 2 {
|
||||
rasterizer.Add1(raster.Point{raster.Fix32(poly[j] * 256), 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, flattening_threshold)
|
||||
poly := Polygon(p.points)
|
||||
color := image.RGBAColor{0, 0, 0, 0xff}
|
||||
tr := [6]float64{1, 0, 0, 1, 0, 0}
|
||||
for i := 0; i < b.N; i++ {
|
||||
img := image.NewRGBA(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, flattening_threshold)
|
||||
poly := Polygon(p.points)
|
||||
color := image.RGBAColor{0, 0, 0, 0xff}
|
||||
tr := [6]float64{1, 0, 0, 1, 0, 0}
|
||||
for i := 0; i < b.N; i++ {
|
||||
img := image.NewRGBA(200, 200)
|
||||
rasterizer := NewRasterizer8BitsSample(200, 200)
|
||||
rasterizer.RenderEvenOdd(img, &color, &poly, tr)
|
||||
}
|
||||
}
|
|
@ -1,144 +0,0 @@
|
|||
// see http://pippin.gimp.org/image_processing/chap_resampling.html
|
||||
package draw2d
|
||||
|
||||
import (
|
||||
"image/draw"
|
||||
"image"
|
||||
"math"
|
||||
)
|
||||
|
||||
type ImageFilter int
|
||||
|
||||
const (
|
||||
LinearFilter ImageFilter = iota
|
||||
BilinearFilter
|
||||
BicubicFilter
|
||||
)
|
||||
|
||||
//see http://pippin.gimp.org/image_processing/chap_resampling.html
|
||||
func getColorLinear(img image.Image, x, y float64) image.Color {
|
||||
return img.At(int(x), int(y))
|
||||
}
|
||||
|
||||
func getColorBilinear(img image.Image, x, y float64) image.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 image.RGBAColor{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) image.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 image.RGBAColor{uint8(r >> 8), uint8(g >> 8), uint8(b >> 8), uint8(a >> 8)}
|
||||
}
|
||||
|
||||
func getColorBicubic(img image.Image, x, y float64) image.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 image.RGBAColor{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)
|
||||
}
|
||||
|
||||
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 image.Color
|
||||
var r, g, b, a, ia, r1, g1, b1, a1, r2, g2, b2, a2 uint32
|
||||
var color image.RGBAColor
|
||||
for x = x0; x < x1; x++ {
|
||||
for y = y0; y < y1; y++ {
|
||||
u = x
|
||||
v = y
|
||||
tr.InverseTransform(&u, &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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,199 +0,0 @@
|
|||
// Copyright 2010 The draw2d Authors. All rights reserved.
|
||||
// created: 21/11/2010 by Laurent Le Goff
|
||||
package draw2d
|
||||
|
||||
import (
|
||||
"image"
|
||||
)
|
||||
|
||||
type StackGraphicContext struct {
|
||||
Current *ContextStack
|
||||
}
|
||||
|
||||
type ContextStack struct {
|
||||
Tr MatrixTransform
|
||||
Path *PathStorage
|
||||
LineWidth float64
|
||||
Dash []float64
|
||||
DashOffset float64
|
||||
StrokeColor image.Color
|
||||
FillColor image.Color
|
||||
FillRule FillRule
|
||||
Cap Cap
|
||||
Join Join
|
||||
FontSize float64
|
||||
FontData FontData
|
||||
previous *ContextStack
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a new Graphic context from an image
|
||||
*/
|
||||
func NewStackGraphicContext() *StackGraphicContext {
|
||||
gc := &StackGraphicContext{}
|
||||
gc.Current = new(ContextStack)
|
||||
gc.Current.Tr = NewIdentityMatrix()
|
||||
gc.Current.Path = NewPathStorage()
|
||||
gc.Current.LineWidth = 1.0
|
||||
gc.Current.StrokeColor = image.Black
|
||||
gc.Current.FillColor = image.White
|
||||
gc.Current.Cap = RoundCap
|
||||
gc.Current.FillRule = FillRuleEvenOdd
|
||||
gc.Current.Join = RoundJoin
|
||||
gc.Current.FontSize = 10
|
||||
gc.Current.FontData = defaultFontData
|
||||
return gc
|
||||
}
|
||||
|
||||
|
||||
func (gc *StackGraphicContext) GetMatrixTransform() MatrixTransform {
|
||||
return gc.Current.Tr
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) SetMatrixTransform(Tr MatrixTransform) {
|
||||
gc.Current.Tr = Tr
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) ComposeMatrixTransform(Tr MatrixTransform) {
|
||||
gc.Current.Tr = Tr.Multiply(gc.Current.Tr)
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) Rotate(angle float64) {
|
||||
gc.Current.Tr = NewRotationMatrix(angle).Multiply(gc.Current.Tr)
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) Translate(tx, ty float64) {
|
||||
gc.Current.Tr = NewTranslationMatrix(tx, ty).Multiply(gc.Current.Tr)
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) Scale(sx, sy float64) {
|
||||
gc.Current.Tr = NewScaleMatrix(sx, sy).Multiply(gc.Current.Tr)
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) SetStrokeColor(c image.Color) {
|
||||
gc.Current.StrokeColor = c
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) SetFillColor(c image.Color) {
|
||||
gc.Current.FillColor = c
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) SetFillRule(f FillRule) {
|
||||
gc.Current.FillRule = f
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) SetLineWidth(LineWidth float64) {
|
||||
gc.Current.LineWidth = LineWidth
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) SetLineCap(Cap Cap) {
|
||||
gc.Current.Cap = Cap
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) SetLineJoin(Join Join) {
|
||||
gc.Current.Join = Join
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) SetLineDash(Dash []float64, DashOffset float64) {
|
||||
gc.Current.Dash = Dash
|
||||
gc.Current.DashOffset = DashOffset
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) SetFontSize(FontSize float64) {
|
||||
gc.Current.FontSize = FontSize
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) GetFontSize() float64 {
|
||||
return gc.Current.FontSize
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) SetFontData(FontData FontData) {
|
||||
gc.Current.FontData = FontData
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) GetFontData() FontData {
|
||||
return gc.Current.FontData
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) BeginPath() {
|
||||
gc.Current.Path.Clear()
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) IsEmpty() bool {
|
||||
return gc.Current.Path.IsEmpty()
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) LastPoint() (float64, float64) {
|
||||
return gc.Current.Path.LastPoint()
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) MoveTo(x, y float64) {
|
||||
gc.Current.Path.MoveTo(x, y)
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) RMoveTo(dx, dy float64) {
|
||||
gc.Current.Path.RMoveTo(dx, dy)
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) LineTo(x, y float64) {
|
||||
gc.Current.Path.LineTo(x, y)
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) RLineTo(dx, dy float64) {
|
||||
gc.Current.Path.RLineTo(dx, dy)
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) QuadCurveTo(cx, cy, x, y float64) {
|
||||
gc.Current.Path.QuadCurveTo(cx, cy, x, y)
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) RQuadCurveTo(dcx, dcy, dx, dy float64) {
|
||||
gc.Current.Path.RQuadCurveTo(dcx, dcy, dx, dy)
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) CubicCurveTo(cx1, cy1, cx2, cy2, x, y float64) {
|
||||
gc.Current.Path.CubicCurveTo(cx1, cy1, cx2, cy2, x, y)
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) RCubicCurveTo(dcx1, dcy1, dcx2, dcy2, dx, dy float64) {
|
||||
gc.Current.Path.RCubicCurveTo(dcx1, dcy1, dcx2, dcy2, dx, dy)
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) ArcTo(cx, cy, rx, ry, startAngle, angle float64) {
|
||||
gc.Current.Path.ArcTo(cx, cy, rx, ry, startAngle, angle)
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) RArcTo(dcx, dcy, rx, ry, startAngle, angle float64) {
|
||||
gc.Current.Path.RArcTo(dcx, dcy, rx, ry, startAngle, angle)
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) Close() {
|
||||
gc.Current.Path.Close()
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) Save() {
|
||||
context := new(ContextStack)
|
||||
context.FontSize = gc.Current.FontSize
|
||||
context.FontData = gc.Current.FontData
|
||||
context.LineWidth = gc.Current.LineWidth
|
||||
context.StrokeColor = gc.Current.StrokeColor
|
||||
context.FillColor = gc.Current.FillColor
|
||||
context.FillRule = gc.Current.FillRule
|
||||
context.Dash = gc.Current.Dash
|
||||
context.DashOffset = gc.Current.DashOffset
|
||||
context.Cap = gc.Current.Cap
|
||||
context.Join = gc.Current.Join
|
||||
context.Path = gc.Current.Path.Copy()
|
||||
copy(context.Tr[:], gc.Current.Tr[:])
|
||||
context.previous = gc.Current
|
||||
gc.Current = context
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) Restore() {
|
||||
if gc.Current.previous != nil {
|
||||
oldContext := gc.Current
|
||||
gc.Current = gc.Current.previous
|
||||
oldContext.previous = nil
|
||||
}
|
||||
}
|
|
@ -1,137 +0,0 @@
|
|||
// Copyright 2010 The draw2d Authors. All rights reserved.
|
||||
// created: 13/12/2010 by Laurent Le Goff
|
||||
package draw2d
|
||||
|
||||
type Cap int
|
||||
|
||||
const (
|
||||
RoundCap Cap = iota
|
||||
ButtCap
|
||||
SquareCap
|
||||
)
|
||||
|
||||
type Join int
|
||||
|
||||
const (
|
||||
BevelJoin Join = iota
|
||||
RoundJoin
|
||||
MiterJoin
|
||||
)
|
||||
|
||||
|
||||
type LineStroker struct {
|
||||
Next VertexConverter
|
||||
HalfLineWidth float64
|
||||
Cap Cap
|
||||
Join Join
|
||||
vertices []float64
|
||||
rewind []float64
|
||||
x, y, nx, ny float64
|
||||
command VertexCommand
|
||||
}
|
||||
|
||||
func NewLineStroker(c Cap, j Join, converter VertexConverter) *LineStroker {
|
||||
l := new(LineStroker)
|
||||
l.Next = converter
|
||||
l.HalfLineWidth = 0.5
|
||||
l.vertices = make([]float64, 0, 256)
|
||||
l.rewind = make([]float64, 0, 256)
|
||||
l.Cap = c
|
||||
l.Join = j
|
||||
l.command = VertexNoCommand
|
||||
return l
|
||||
}
|
||||
|
||||
|
||||
func (l *LineStroker) NextCommand(command VertexCommand) {
|
||||
l.command = command
|
||||
if command == VertexStopCommand {
|
||||
l.Next.NextCommand(VertexStartCommand)
|
||||
for i, j := 0, 1; j < len(l.vertices); i, j = i+2, j+2 {
|
||||
l.Next.Vertex(l.vertices[i], l.vertices[j])
|
||||
l.Next.NextCommand(VertexNoCommand)
|
||||
}
|
||||
for i, j := len(l.rewind)-2, len(l.rewind)-1; j > 0; i, j = i-2, j-2 {
|
||||
l.Next.NextCommand(VertexNoCommand)
|
||||
l.Next.Vertex(l.rewind[i], l.rewind[j])
|
||||
}
|
||||
if len(l.vertices) > 1 {
|
||||
l.Next.NextCommand(VertexNoCommand)
|
||||
l.Next.Vertex(l.vertices[0], l.vertices[1])
|
||||
}
|
||||
l.Next.NextCommand(VertexStopCommand)
|
||||
// reinit vertices
|
||||
l.vertices = l.vertices[0:0]
|
||||
l.rewind = l.rewind[0:0]
|
||||
l.x, l.y, l.nx, l.ny = 0, 0, 0, 0
|
||||
}
|
||||
}
|
||||
|
||||
func (l *LineStroker) Vertex(x, y float64) {
|
||||
switch l.command {
|
||||
case VertexNoCommand:
|
||||
l.line(l.x, l.y, x, y)
|
||||
case VertexJoinCommand:
|
||||
l.joinLine(l.x, l.y, l.nx, l.ny, x, y)
|
||||
case VertexStartCommand:
|
||||
l.x, l.y = x, y
|
||||
case VertexCloseCommand:
|
||||
l.line(l.x, l.y, x, y)
|
||||
l.joinLine(l.x, l.y, l.nx, l.ny, x, y)
|
||||
l.closePolygon()
|
||||
}
|
||||
l.command = VertexNoCommand
|
||||
}
|
||||
|
||||
func (l *LineStroker) appendVertex(vertices ...float64) {
|
||||
s := len(vertices) / 2
|
||||
if len(l.vertices)+s >= cap(l.vertices) {
|
||||
v := make([]float64, len(l.vertices), cap(l.vertices)+128)
|
||||
copy(v, l.vertices)
|
||||
l.vertices = v
|
||||
v = make([]float64, len(l.rewind), cap(l.rewind)+128)
|
||||
copy(v, l.rewind)
|
||||
l.rewind = v
|
||||
}
|
||||
|
||||
copy(l.vertices[len(l.vertices):len(l.vertices)+s], vertices[:s])
|
||||
l.vertices = l.vertices[0 : len(l.vertices)+s]
|
||||
copy(l.rewind[len(l.rewind):len(l.rewind)+s], vertices[s:])
|
||||
l.rewind = l.rewind[0 : len(l.rewind)+s]
|
||||
|
||||
}
|
||||
|
||||
func (l *LineStroker) closePolygon() {
|
||||
if len(l.vertices) > 1 {
|
||||
l.appendVertex(l.vertices[0], l.vertices[1], l.rewind[0], l.rewind[1])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func (l *LineStroker) line(x1, y1, x2, y2 float64) {
|
||||
dx := (x2 - x1)
|
||||
dy := (y2 - y1)
|
||||
d := vectorDistance(dx, dy)
|
||||
if d != 0 {
|
||||
nx := dy * l.HalfLineWidth / d
|
||||
ny := -(dx * l.HalfLineWidth / d)
|
||||
l.appendVertex(x1+nx, y1+ny, x2+nx, y2+ny, x1-nx, y1-ny, x2-nx, y2-ny)
|
||||
l.x, l.y, l.nx, l.ny = x2, y2, nx, ny
|
||||
}
|
||||
}
|
||||
|
||||
func (l *LineStroker) joinLine(x1, y1, nx1, ny1, x2, y2 float64) {
|
||||
dx := (x2 - x1)
|
||||
dy := (y2 - y1)
|
||||
d := vectorDistance(dx, dy)
|
||||
|
||||
if d != 0 {
|
||||
nx := dy * l.HalfLineWidth / d
|
||||
ny := -(dx * l.HalfLineWidth / d)
|
||||
/* l.join(x1, y1, x1 + nx, y1 - ny, nx, ny, x1 + ny2, y1 + nx2, nx2, ny2)
|
||||
l.join(x1, y1, x1 - ny1, y1 - nx1, nx1, ny1, x1 - ny2, y1 - nx2, nx2, ny2)*/
|
||||
|
||||
l.appendVertex(x1+nx, y1+ny, x2+nx, y2+ny, x1-nx, y1-ny, x2-nx, y2-ny)
|
||||
l.x, l.y, l.nx, l.ny = x2, y2, nx, ny
|
||||
}
|
||||
}
|
|
@ -1,309 +0,0 @@
|
|||
// Copyright 2010 The draw2d Authors. All rights reserved.
|
||||
// created: 21/11/2010 by Laurent Le Goff
|
||||
package draw2d
|
||||
|
||||
import (
|
||||
"freetype-go.googlecode.com/hg/freetype/raster"
|
||||
"math"
|
||||
)
|
||||
|
||||
type MatrixTransform [6]float64
|
||||
|
||||
const (
|
||||
epsilon = 1e-6
|
||||
)
|
||||
|
||||
func (tr MatrixTransform) Determinant() float64 {
|
||||
return tr[0]*tr[3] - tr[1]*tr[2]
|
||||
}
|
||||
|
||||
func (tr MatrixTransform) Transform(points ...*float64) {
|
||||
for i, j := 0, 1; j < len(points); i, j = i+2, j+2 {
|
||||
x := *points[i]
|
||||
y := *points[j]
|
||||
*points[i] = x*tr[0] + y*tr[2] + tr[4]
|
||||
*points[j] = x*tr[1] + y*tr[3] + tr[5]
|
||||
}
|
||||
}
|
||||
|
||||
func (tr MatrixTransform) TransformArray(points []float64) {
|
||||
for i, j := 0, 1; j < len(points); i, j = i+2, j+2 {
|
||||
x := points[i]
|
||||
y := points[j]
|
||||
points[i] = x*tr[0] + y*tr[2] + tr[4]
|
||||
points[j] = x*tr[1] + y*tr[3] + tr[5]
|
||||
}
|
||||
}
|
||||
|
||||
func (tr MatrixTransform) TransformRectangle(x0, y0, x2, y2 *float64) {
|
||||
x1 := *x2
|
||||
y1 := *y0
|
||||
x3 := *x0
|
||||
y3 := *y2
|
||||
tr.Transform(x0, y0, &x1, &y1, x2, y2, &x3, &y3)
|
||||
*x0, x1 = minMax(*x0, x1)
|
||||
*x2, x3 = minMax(*x2, x3)
|
||||
*y0, y1 = minMax(*y0, y1)
|
||||
*y2, y3 = minMax(*y2, y3)
|
||||
|
||||
*x0 = min(*x0, *x2)
|
||||
*y0 = min(*y0, *y2)
|
||||
*x2 = max(x1, x3)
|
||||
*y2 = max(y1, y3)
|
||||
}
|
||||
|
||||
func (tr MatrixTransform) TransformRasterPoint(points ...*raster.Point) {
|
||||
for _, point := range points {
|
||||
x := float64(point.X) / 256
|
||||
y := float64(point.Y) / 256
|
||||
point.X = raster.Fix32((x*tr[0] + y*tr[2] + tr[4]) * 256)
|
||||
point.Y = raster.Fix32((x*tr[1] + y*tr[3] + tr[5]) * 256)
|
||||
}
|
||||
}
|
||||
|
||||
func (tr MatrixTransform) InverseTransform(points ...*float64) {
|
||||
d := tr.Determinant() // matrix determinant
|
||||
for i, j := 0, 1; j < len(points); i, j = i+2, j+2 {
|
||||
x := *points[i]
|
||||
y := *points[j]
|
||||
*points[i] = ((x-tr[4])*tr[3] - (y-tr[5])*tr[2]) / d
|
||||
*points[j] = ((y-tr[5])*tr[0] - (x-tr[4])*tr[1]) / d
|
||||
}
|
||||
}
|
||||
|
||||
// ******************** Vector transformations ********************
|
||||
|
||||
func (tr MatrixTransform) VectorTransform(points ...*float64) {
|
||||
for i, j := 0, 1; j < len(points); i, j = i+2, j+2 {
|
||||
x := *points[i]
|
||||
y := *points[j]
|
||||
*points[i] = x*tr[0] + y*tr[2]
|
||||
*points[j] = x*tr[1] + y*tr[3]
|
||||
}
|
||||
}
|
||||
|
||||
// ******************** Transformations creation ********************
|
||||
|
||||
/** Creates an identity transformation. */
|
||||
func NewIdentityMatrix() MatrixTransform {
|
||||
return [6]float64{1, 0, 0, 1, 0, 0}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a transformation with a translation, that,
|
||||
* transform point1 into point2.
|
||||
*/
|
||||
func NewTranslationMatrix(tx, ty float64) MatrixTransform {
|
||||
return [6]float64{1, 0, 0, 1, tx, ty}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a transformation with a sx, sy scale factor
|
||||
*/
|
||||
func NewScaleMatrix(sx, sy float64) MatrixTransform {
|
||||
return [6]float64{sx, 0, 0, sy, 0, 0}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a rotation transformation.
|
||||
*/
|
||||
func NewRotationMatrix(angle float64) MatrixTransform {
|
||||
c := math.Cos(angle)
|
||||
s := math.Sin(angle)
|
||||
return [6]float64{c, s, -s, c, 0, 0}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a transformation, combining a scale and a translation, that transform rectangle1 into rectangle2.
|
||||
*/
|
||||
func NewMatrixTransform(rectangle1, rectangle2 [4]float64) MatrixTransform {
|
||||
xScale := (rectangle2[2] - rectangle2[0]) / (rectangle1[2] - rectangle1[0])
|
||||
yScale := (rectangle2[3] - rectangle2[1]) / (rectangle1[3] - rectangle1[1])
|
||||
xOffset := rectangle2[0] - (rectangle1[0] * xScale)
|
||||
yOffset := rectangle2[1] - (rectangle1[1] * yScale)
|
||||
return [6]float64{xScale, 0, 0, yScale, xOffset, yOffset}
|
||||
}
|
||||
|
||||
// ******************** Transformations operations ********************
|
||||
|
||||
/**
|
||||
* Returns a transformation that is the inverse of the given transformation.
|
||||
*/
|
||||
func (tr MatrixTransform) GetInverseTransformation() MatrixTransform {
|
||||
d := tr.Determinant() // matrix determinant
|
||||
return [6]float64{
|
||||
tr[3] / d,
|
||||
-tr[1] / d,
|
||||
-tr[2] / d,
|
||||
tr[0] / d,
|
||||
(tr[2]*tr[5] - tr[3]*tr[4]) / d,
|
||||
(tr[1]*tr[4] - tr[0]*tr[5]) / d}
|
||||
}
|
||||
|
||||
|
||||
func (tr1 MatrixTransform) Multiply(tr2 MatrixTransform) MatrixTransform {
|
||||
return [6]float64{
|
||||
tr1[0]*tr2[0] + tr1[1]*tr2[2],
|
||||
tr1[1]*tr2[3] + tr1[0]*tr2[1],
|
||||
tr1[2]*tr2[0] + tr1[3]*tr2[2],
|
||||
tr1[3]*tr2[3] + tr1[2]*tr2[1],
|
||||
tr1[4]*tr2[0] + tr1[5]*tr2[2] + tr2[4],
|
||||
tr1[5]*tr2[3] + tr1[4]*tr2[1] + tr2[5]}
|
||||
}
|
||||
|
||||
|
||||
func (tr *MatrixTransform) Scale(sx, sy float64) *MatrixTransform {
|
||||
tr[0] = sx * tr[0]
|
||||
tr[1] = sx * tr[1]
|
||||
tr[2] = sy * tr[2]
|
||||
tr[3] = sy * tr[3]
|
||||
return tr
|
||||
}
|
||||
|
||||
func (tr *MatrixTransform) Translate(tx, ty float64) *MatrixTransform {
|
||||
tr[4] = tx*tr[0] + ty*tr[2] + tr[4]
|
||||
tr[5] = ty*tr[3] + tx*tr[1] + tr[5]
|
||||
return tr
|
||||
}
|
||||
|
||||
func (tr *MatrixTransform) Rotate(angle float64) *MatrixTransform {
|
||||
c := math.Cos(angle)
|
||||
s := math.Sin(angle)
|
||||
t0 := c*tr[0] + s*tr[2]
|
||||
t1 := s*tr[3] + c*tr[1]
|
||||
t2 := c*tr[2] - s*tr[0]
|
||||
t3 := c*tr[3] - s*tr[1]
|
||||
tr[0] = t0
|
||||
tr[1] = t1
|
||||
tr[2] = t2
|
||||
tr[3] = t3
|
||||
return tr
|
||||
}
|
||||
|
||||
func (tr MatrixTransform) GetTranslation() (x, y float64) {
|
||||
return tr[4], tr[5]
|
||||
}
|
||||
|
||||
func (tr MatrixTransform) GetScaling() (x, y float64) {
|
||||
return tr[0], tr[3]
|
||||
}
|
||||
|
||||
func (tr MatrixTransform) GetScale() float64 {
|
||||
x := 0.707106781*tr[0] + 0.707106781*tr[1]
|
||||
y := 0.707106781*tr[2] + 0.707106781*tr[3]
|
||||
return math.Sqrt(x*x + y*y)
|
||||
}
|
||||
|
||||
func (tr MatrixTransform) GetMaxAbsScaling() (s float64) {
|
||||
sx := math.Fabs(tr[0])
|
||||
sy := math.Fabs(tr[3])
|
||||
if sx > sy {
|
||||
return sx
|
||||
}
|
||||
return sy
|
||||
}
|
||||
|
||||
func (tr MatrixTransform) GetMinAbsScaling() (s float64) {
|
||||
sx := math.Fabs(tr[0])
|
||||
sy := math.Fabs(tr[3])
|
||||
if sx > sy {
|
||||
return sy
|
||||
}
|
||||
return sx
|
||||
}
|
||||
|
||||
// ******************** Testing ********************
|
||||
|
||||
/**
|
||||
* Tests if a two transformation are equal. A tolerance is applied when
|
||||
* comparing matrix elements.
|
||||
*/
|
||||
func (tr1 MatrixTransform) Equals(tr2 MatrixTransform) bool {
|
||||
for i := 0; i < 6; i = i + 1 {
|
||||
if !fequals(tr1[i], tr2[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if a transformation is the identity transformation. A tolerance
|
||||
* is applied when comparing matrix elements.
|
||||
*/
|
||||
func (tr MatrixTransform) IsIdentity() bool {
|
||||
return fequals(tr[4], 0) && fequals(tr[5], 0) && tr.IsTranslation()
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if a transformation is is a pure translation. A tolerance
|
||||
* is applied when comparing matrix elements.
|
||||
*/
|
||||
func (tr MatrixTransform) IsTranslation() bool {
|
||||
return fequals(tr[0], 1) && fequals(tr[1], 0) && fequals(tr[2], 0) && fequals(tr[3], 1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two floats.
|
||||
* return true if the distance between the two floats is less than epsilon, false otherwise
|
||||
*/
|
||||
func fequals(float1, float2 float64) bool {
|
||||
return math.Fabs(float1-float2) <= epsilon
|
||||
}
|
||||
|
||||
// this VertexConverter apply the Matrix transformation tr
|
||||
type VertexMatrixTransform struct {
|
||||
tr MatrixTransform
|
||||
Next VertexConverter
|
||||
}
|
||||
|
||||
func NewVertexMatrixTransform(tr MatrixTransform, converter VertexConverter) *VertexMatrixTransform {
|
||||
return &VertexMatrixTransform{tr, converter}
|
||||
}
|
||||
|
||||
// Vertex Matrix Transform
|
||||
func (vmt *VertexMatrixTransform) NextCommand(command VertexCommand) {
|
||||
vmt.Next.NextCommand(command)
|
||||
}
|
||||
|
||||
func (vmt *VertexMatrixTransform) Vertex(x, y float64) {
|
||||
u := x*vmt.tr[0] + y*vmt.tr[2] + vmt.tr[4]
|
||||
v := x*vmt.tr[1] + y*vmt.tr[3] + vmt.tr[5]
|
||||
vmt.Next.Vertex(u, v)
|
||||
}
|
||||
|
||||
|
||||
// this adder apply a Matrix transformation to points
|
||||
type MatrixTransformAdder struct {
|
||||
tr MatrixTransform
|
||||
next raster.Adder
|
||||
}
|
||||
|
||||
func NewMatrixTransformAdder(tr MatrixTransform, adder raster.Adder) *MatrixTransformAdder {
|
||||
return &MatrixTransformAdder{tr, adder}
|
||||
}
|
||||
|
||||
|
||||
// Start starts a new curve at the given point.
|
||||
func (mta MatrixTransformAdder) Start(a raster.Point) {
|
||||
mta.tr.TransformRasterPoint(&a)
|
||||
mta.next.Start(a)
|
||||
}
|
||||
|
||||
// Add1 adds a linear segment to the current curve.
|
||||
func (mta MatrixTransformAdder) Add1(b raster.Point) {
|
||||
mta.tr.TransformRasterPoint(&b)
|
||||
mta.next.Add1(b)
|
||||
}
|
||||
|
||||
// Add2 adds a quadratic segment to the current curve.
|
||||
func (mta MatrixTransformAdder) Add2(b, c raster.Point) {
|
||||
mta.tr.TransformRasterPoint(&b, &c)
|
||||
mta.next.Add2(b, c)
|
||||
}
|
||||
|
||||
// Add3 adds a cubic segment to the current curve.
|
||||
func (mta MatrixTransformAdder) Add3(b, c, d raster.Point) {
|
||||
mta.tr.TransformRasterPoint(&b, &c, &d)
|
||||
mta.next.Add3(b, c, d)
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
// Copyright 2010 The draw2d Authors. All rights reserved.
|
||||
// created: 21/11/2010 by Laurent Le Goff
|
||||
package draw2d
|
||||
|
||||
type VertexCommand byte
|
||||
|
||||
const (
|
||||
VertexNoCommand VertexCommand = iota
|
||||
VertexStartCommand
|
||||
VertexJoinCommand
|
||||
VertexCloseCommand
|
||||
VertexStopCommand
|
||||
)
|
||||
|
||||
type VertexConverter interface {
|
||||
NextCommand(cmd VertexCommand)
|
||||
Vertex(x, y float64)
|
||||
}
|
|
@ -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.
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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,50 +1,48 @@
|
|||
// Copyright 2010 The draw2d Authors. All rights reserved.
|
||||
// created: 13/12/2010 by Laurent Le Goff
|
||||
package draw2d
|
||||
|
||||
package draw2dbase
|
||||
|
||||
type DashVertexConverter struct {
|
||||
command VertexCommand
|
||||
next VertexConverter
|
||||
next Flattener
|
||||
x, y, distance float64
|
||||
dash []float64
|
||||
currentDash int
|
||||
dashOffset float64
|
||||
}
|
||||
|
||||
func NewDashConverter(dash []float64, dashOffset float64, converter VertexConverter) *DashVertexConverter {
|
||||
func NewDashConverter(dash []float64, dashOffset float64, flattener Flattener) *DashVertexConverter {
|
||||
var dasher DashVertexConverter
|
||||
dasher.dash = dash
|
||||
dasher.currentDash = 0
|
||||
dasher.dashOffset = dashOffset
|
||||
dasher.next = converter
|
||||
dasher.next = flattener
|
||||
return &dasher
|
||||
}
|
||||
|
||||
func (dasher *DashVertexConverter) NextCommand(cmd VertexCommand) {
|
||||
dasher.command = cmd
|
||||
if dasher.command == VertexStopCommand {
|
||||
dasher.next.NextCommand(VertexStopCommand)
|
||||
}
|
||||
func (dasher *DashVertexConverter) LineTo(x, y float64) {
|
||||
dasher.lineTo(x, y)
|
||||
}
|
||||
|
||||
func (dasher *DashVertexConverter) Vertex(x, y float64) {
|
||||
switch dasher.command {
|
||||
case VertexStartCommand:
|
||||
dasher.start(x, y)
|
||||
default:
|
||||
dasher.lineTo(x, y)
|
||||
}
|
||||
dasher.command = VertexNoCommand
|
||||
}
|
||||
|
||||
func (dasher *DashVertexConverter) start(x, y float64) {
|
||||
dasher.next.NextCommand(VertexStartCommand)
|
||||
dasher.next.Vertex(x, y)
|
||||
func (dasher *DashVertexConverter) MoveTo(x, y float64) {
|
||||
dasher.next.MoveTo(x, y)
|
||||
dasher.x, dasher.y = x, y
|
||||
dasher.distance = dasher.dashOffset
|
||||
dasher.currentDash = 0
|
||||
}
|
||||
|
||||
func (dasher *DashVertexConverter) LineJoin() {
|
||||
dasher.next.LineJoin()
|
||||
}
|
||||
|
||||
func (dasher *DashVertexConverter) Close() {
|
||||
dasher.next.Close()
|
||||
}
|
||||
|
||||
func (dasher *DashVertexConverter) End() {
|
||||
dasher.next.End()
|
||||
}
|
||||
|
||||
func (dasher *DashVertexConverter) lineTo(x, y float64) {
|
||||
rest := dasher.dash[dasher.currentDash] - dasher.distance
|
||||
for rest < 0 {
|
||||
|
@ -59,12 +57,11 @@ func (dasher *DashVertexConverter) lineTo(x, y float64) {
|
|||
ly := dasher.y + k*(y-dasher.y)
|
||||
if dasher.currentDash%2 == 0 {
|
||||
// line
|
||||
dasher.next.Vertex(lx, ly)
|
||||
dasher.next.LineTo(lx, ly)
|
||||
} else {
|
||||
// gap
|
||||
dasher.next.NextCommand(VertexStopCommand)
|
||||
dasher.next.NextCommand(VertexStartCommand)
|
||||
dasher.next.Vertex(lx, ly)
|
||||
dasher.next.End()
|
||||
dasher.next.MoveTo(lx, ly)
|
||||
}
|
||||
d = d - rest
|
||||
dasher.x, dasher.y = lx, ly
|
||||
|
@ -74,12 +71,11 @@ func (dasher *DashVertexConverter) lineTo(x, y float64) {
|
|||
dasher.distance = d
|
||||
if dasher.currentDash%2 == 0 {
|
||||
// line
|
||||
dasher.next.Vertex(x, y)
|
||||
dasher.next.LineTo(x, y)
|
||||
} else {
|
||||
// gap
|
||||
dasher.next.NextCommand(VertexStopCommand)
|
||||
dasher.next.NextCommand(VertexStartCommand)
|
||||
dasher.next.Vertex(x, y)
|
||||
dasher.next.End()
|
||||
dasher.next.MoveTo(x, y)
|
||||
}
|
||||
if dasher.distance >= dasher.dash[dasher.currentDash] {
|
||||
dasher.distance = dasher.distance - dasher.dash[dasher.currentDash]
|
||||
|
@ -87,3 +83,7 @@ func (dasher *DashVertexConverter) lineTo(x, y float64) {
|
|||
}
|
||||
dasher.x, dasher.y = x, y
|
||||
}
|
||||
|
||||
func distance(x1, y1, x2, y2 float64) float64 {
|
||||
return vectorDistance(x2-x1, y2-y1)
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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,10 +1,11 @@
|
|||
// Copyright 2011 The draw2d Authors. All rights reserved.
|
||||
// created: 27/05/2011 by Laurent Le Goff
|
||||
package raster
|
||||
// Copyright 2011 The draw2d Authors. All rights reserved.
|
||||
// created: 27/05/2011 by Laurent Le Goff
|
||||
|
||||
package draw2dbase
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"image/draw"
|
||||
"image"
|
||||
)
|
||||
|
||||
func abs(i int) int {
|
||||
|
@ -14,13 +15,15 @@ func abs(i int) int {
|
|||
return i
|
||||
}
|
||||
|
||||
func PolylineBresenham(img draw.Image, c image.Color, s ...float64) {
|
||||
// PolylineBresenham draws a polyline to an image
|
||||
func PolylineBresenham(img draw.Image, c color.Color, s ...float64) {
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
func Bresenham(img draw.Image, color image.Color, x0, y0, x1, y1 int) {
|
||||
// Bresenham draws a line between (x0, y0) and (x1, y1)
|
||||
func Bresenham(img draw.Image, color color.Color, x0, y0, x1, y1 int) {
|
||||
dx := abs(x1 - x0)
|
||||
dy := abs(y1 - y0)
|
||||
var sx, sy int
|
|
@ -0,0 +1,208 @@
|
|||
// Copyright 2010 The draw2d Authors. All rights reserved.
|
||||
// created: 21/11/2010 by Laurent Le Goff
|
||||
|
||||
package draw2dbase
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
|
||||
"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 {
|
||||
Current *ContextStack
|
||||
}
|
||||
|
||||
type ContextStack struct {
|
||||
Tr draw2d.Matrix
|
||||
Path *draw2d.Path
|
||||
LineWidth float64
|
||||
Dash []float64
|
||||
DashOffset float64
|
||||
StrokeColor color.Color
|
||||
FillColor color.Color
|
||||
FillRule draw2d.FillRule
|
||||
Cap draw2d.LineCap
|
||||
Join draw2d.LineJoin
|
||||
FontSize float64
|
||||
FontData draw2d.FontData
|
||||
|
||||
Font *truetype.Font
|
||||
// fontSize and dpi are used to calculate scale. scale is the number of
|
||||
// 26.6 fixed point units in 1 em.
|
||||
Scale float64
|
||||
|
||||
Previous *ContextStack
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Graphic context from an image
|
||||
*/
|
||||
func NewStackGraphicContext() *StackGraphicContext {
|
||||
gc := &StackGraphicContext{}
|
||||
gc.Current = new(ContextStack)
|
||||
gc.Current.Tr = draw2d.NewIdentityMatrix()
|
||||
gc.Current.Path = new(draw2d.Path)
|
||||
gc.Current.LineWidth = 1.0
|
||||
gc.Current.StrokeColor = image.Black
|
||||
gc.Current.FillColor = image.White
|
||||
gc.Current.Cap = draw2d.RoundCap
|
||||
gc.Current.FillRule = draw2d.FillRuleEvenOdd
|
||||
gc.Current.Join = draw2d.RoundJoin
|
||||
gc.Current.FontSize = 10
|
||||
gc.Current.FontData = DefaultFontData
|
||||
return gc
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) GetMatrixTransform() draw2d.Matrix {
|
||||
return gc.Current.Tr
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) SetMatrixTransform(Tr draw2d.Matrix) {
|
||||
gc.Current.Tr = Tr
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) ComposeMatrixTransform(Tr draw2d.Matrix) {
|
||||
gc.Current.Tr.Compose(Tr)
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) Rotate(angle float64) {
|
||||
gc.Current.Tr.Rotate(angle)
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) Translate(tx, ty float64) {
|
||||
gc.Current.Tr.Translate(tx, ty)
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) Scale(sx, sy float64) {
|
||||
gc.Current.Tr.Scale(sx, sy)
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) SetStrokeColor(c color.Color) {
|
||||
gc.Current.StrokeColor = c
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) SetFillColor(c color.Color) {
|
||||
gc.Current.FillColor = c
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) SetFillRule(f draw2d.FillRule) {
|
||||
gc.Current.FillRule = f
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) SetLineWidth(lineWidth float64) {
|
||||
gc.Current.LineWidth = lineWidth
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) SetLineCap(cap draw2d.LineCap) {
|
||||
gc.Current.Cap = cap
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) SetLineJoin(join draw2d.LineJoin) {
|
||||
gc.Current.Join = join
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) SetLineDash(dash []float64, dashOffset float64) {
|
||||
gc.Current.Dash = dash
|
||||
gc.Current.DashOffset = dashOffset
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) SetFontSize(fontSize float64) {
|
||||
gc.Current.FontSize = fontSize
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) GetFontSize() float64 {
|
||||
return gc.Current.FontSize
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) SetFontData(fontData draw2d.FontData) {
|
||||
gc.Current.FontData = fontData
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) GetFontData() draw2d.FontData {
|
||||
return gc.Current.FontData
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) BeginPath() {
|
||||
gc.Current.Path.Clear()
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) GetPath() draw2d.Path {
|
||||
return *gc.Current.Path.Copy()
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) IsEmpty() bool {
|
||||
return gc.Current.Path.IsEmpty()
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) LastPoint() (float64, float64) {
|
||||
return gc.Current.Path.LastPoint()
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) MoveTo(x, y float64) {
|
||||
gc.Current.Path.MoveTo(x, y)
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) LineTo(x, y float64) {
|
||||
gc.Current.Path.LineTo(x, y)
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) QuadCurveTo(cx, cy, x, y float64) {
|
||||
gc.Current.Path.QuadCurveTo(cx, cy, x, y)
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) CubicCurveTo(cx1, cy1, cx2, cy2, x, y float64) {
|
||||
gc.Current.Path.CubicCurveTo(cx1, cy1, cx2, cy2, x, y)
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) ArcTo(cx, cy, rx, ry, startAngle, angle float64) {
|
||||
gc.Current.Path.ArcTo(cx, cy, rx, ry, startAngle, angle)
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) Close() {
|
||||
gc.Current.Path.Close()
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) Save() {
|
||||
context := new(ContextStack)
|
||||
context.FontSize = gc.Current.FontSize
|
||||
context.FontData = gc.Current.FontData
|
||||
context.LineWidth = gc.Current.LineWidth
|
||||
context.StrokeColor = gc.Current.StrokeColor
|
||||
context.FillColor = gc.Current.FillColor
|
||||
context.FillRule = gc.Current.FillRule
|
||||
context.Dash = gc.Current.Dash
|
||||
context.DashOffset = gc.Current.DashOffset
|
||||
context.Cap = gc.Current.Cap
|
||||
context.Join = gc.Current.Join
|
||||
context.Path = gc.Current.Path.Copy()
|
||||
context.Font = gc.Current.Font
|
||||
context.Scale = gc.Current.Scale
|
||||
copy(context.Tr[:], gc.Current.Tr[:])
|
||||
context.Previous = gc.Current
|
||||
gc.Current = context
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) Restore() {
|
||||
if gc.Current.Previous != nil {
|
||||
oldContext := gc.Current
|
||||
gc.Current = gc.Current.Previous
|
||||
oldContext.Previous = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (gc *StackGraphicContext) GetFontName() string {
|
||||
return gc.Current.GetFontName()
|
||||
}
|
|
@ -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))
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
include $(GOROOT)/src/Make.inc
|
||||
|
||||
TARG=draw2d.googlecode.com/hg/draw2dgl
|
||||
GOFILES=\
|
||||
gc.go\
|
||||
|
||||
include $(GOROOT)/src/Make.pkg
|
|
@ -0,0 +1,3 @@
|
|||
// Package draw2dgl provides a graphic context that can draw vector
|
||||
// graphics and text on OpenGL.
|
||||
package draw2dgl
|
351
draw2dgl/gc.go
351
draw2dgl/gc.go
|
@ -2,14 +2,28 @@ package draw2dgl
|
|||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
"image/draw"
|
||||
"gl"
|
||||
"freetype-go.googlecode.com/hg/freetype/raster"
|
||||
"draw2d.googlecode.com/hg/draw2d"
|
||||
//"log"
|
||||
"log"
|
||||
"math"
|
||||
"runtime"
|
||||
|
||||
"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/golang/freetype/raster"
|
||||
"github.com/golang/freetype/truetype"
|
||||
|
||||
"golang.org/x/image/font"
|
||||
"golang.org/x/image/math/fixed"
|
||||
)
|
||||
|
||||
type GLPainter struct {
|
||||
func init() {
|
||||
runtime.LockOSThread()
|
||||
}
|
||||
|
||||
type Painter struct {
|
||||
// The Porter-Duff composition operator.
|
||||
Op draw.Op
|
||||
// The 16-bit color to paint the spans.
|
||||
|
@ -22,7 +36,7 @@ type GLPainter struct {
|
|||
const M16 uint32 = 1<<16 - 1
|
||||
|
||||
// Paint satisfies the Painter interface by painting ss onto an image.RGBA.
|
||||
func (p *GLPainter) Paint(ss []raster.Span, done bool) {
|
||||
func (p *Painter) Paint(ss []raster.Span, done bool) {
|
||||
//gl.Begin(gl.LINES)
|
||||
sslen := len(ss)
|
||||
clenrequired := sslen * 8
|
||||
|
@ -44,8 +58,8 @@ func (p *GLPainter) Paint(ss []raster.Span, done bool) {
|
|||
vertices []int32
|
||||
)
|
||||
for _, s := range ss {
|
||||
ma := s.A >> 16
|
||||
a := uint8((ma * p.ca / M16) >> 8)
|
||||
a := uint8((s.Alpha * p.ca / M16) >> 8)
|
||||
|
||||
colors = p.colors[ci:]
|
||||
colors[0] = p.cr
|
||||
colors[1] = p.cg
|
||||
|
@ -65,15 +79,15 @@ func (p *GLPainter) Paint(ss []raster.Span, done bool) {
|
|||
}
|
||||
}
|
||||
|
||||
func (p *GLPainter) Flush() {
|
||||
func (p *Painter) Flush() {
|
||||
if len(p.vertices) != 0 {
|
||||
gl.EnableClientState(gl.COLOR_ARRAY)
|
||||
gl.EnableClientState(gl.VERTEX_ARRAY)
|
||||
gl.ColorPointer(4, 0, p.colors)
|
||||
gl.VertexPointer(2, 0, p.vertices)
|
||||
gl.ColorPointer(4, gl.UNSIGNED_BYTE, 0, gl.Ptr(p.colors))
|
||||
gl.VertexPointer(2, gl.INT, 0, gl.Ptr(p.vertices))
|
||||
|
||||
// draw lines
|
||||
gl.DrawArrays(gl.LINES, 0, len(p.vertices)/2)
|
||||
gl.DrawArrays(gl.LINES, 0, int32(len(p.vertices)/2))
|
||||
gl.DisableClientState(gl.VERTEX_ARRAY)
|
||||
gl.DisableClientState(gl.COLOR_ARRAY)
|
||||
p.vertices = p.vertices[0:0]
|
||||
|
@ -81,9 +95,8 @@ func (p *GLPainter) Flush() {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// SetColor sets the color to paint the spans.
|
||||
func (p *GLPainter) SetColor(c image.Color) {
|
||||
func (p *Painter) SetColor(c color.Color) {
|
||||
r, g, b, a := c.RGBA()
|
||||
if a == 0 {
|
||||
p.cr = 0
|
||||
|
@ -99,144 +112,302 @@ func (p *GLPainter) SetColor(c image.Color) {
|
|||
}
|
||||
|
||||
// NewRGBAPainter creates a new RGBAPainter for the given image.
|
||||
func NewGLPainter() *GLPainter {
|
||||
p := new(GLPainter)
|
||||
func NewPainter() *Painter {
|
||||
p := new(Painter)
|
||||
p.vertices = make([]int32, 0, 1024)
|
||||
p.colors = make([]uint8, 0, 1024)
|
||||
return p
|
||||
}
|
||||
|
||||
|
||||
type GraphicContext struct {
|
||||
*draw2d.StackGraphicContext
|
||||
painter *GLPainter
|
||||
*draw2dbase.StackGraphicContext
|
||||
painter *Painter
|
||||
fillRasterizer *raster.Rasterizer
|
||||
strokeRasterizer *raster.Rasterizer
|
||||
FontCache draw2d.FontCache
|
||||
glyphCache draw2dbase.GlyphCache
|
||||
glyphBuf *truetype.GlyphBuf
|
||||
DPI int
|
||||
}
|
||||
|
||||
type GLVertex struct {
|
||||
x, y float64
|
||||
}
|
||||
|
||||
|
||||
func NewGLVertex() *GLVertex {
|
||||
return &GLVertex{}
|
||||
}
|
||||
|
||||
func (glVertex *GLVertex) NextCommand(cmd draw2d.VertexCommand) {
|
||||
|
||||
}
|
||||
|
||||
func (glVertex *GLVertex) Vertex(x, y float64) {
|
||||
gl.Vertex2d(x, y)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Graphic context from an image
|
||||
*/
|
||||
// NewGraphicContext creates a new Graphic context from an image.
|
||||
func NewGraphicContext(width, height int) *GraphicContext {
|
||||
gc := &GraphicContext{
|
||||
draw2d.NewStackGraphicContext(),
|
||||
NewGLPainter(),
|
||||
draw2dbase.NewStackGraphicContext(),
|
||||
NewPainter(),
|
||||
raster.NewRasterizer(width, height),
|
||||
raster.NewRasterizer(width, height),
|
||||
draw2d.GetGlobalFontCache(),
|
||||
draw2dbase.NewGlyphCache(),
|
||||
&truetype.GlyphBuf{},
|
||||
92,
|
||||
}
|
||||
return gc
|
||||
}
|
||||
|
||||
func (gc *GraphicContext) SetDPI(dpi int) {
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
// FillString draws the text at point (0, 0)
|
||||
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) {
|
||||
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
|
||||
}
|
||||
|
||||
// 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 _, 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) {
|
||||
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 {
|
||||
return -1
|
||||
return gc.DPI
|
||||
}
|
||||
|
||||
//TODO
|
||||
func (gc *GraphicContext) Clear() {
|
||||
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
//TODO
|
||||
func (gc *GraphicContext) ClearRect(x1, y1, x2, y2 int) {
|
||||
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
//TODO
|
||||
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 image.Color) {
|
||||
func (gc *GraphicContext) paint(rasterizer *raster.Rasterizer, color color.Color) {
|
||||
gc.painter.SetColor(color)
|
||||
rasterizer.Rasterize(gc.painter)
|
||||
rasterizer.Clear()
|
||||
gc.painter.Flush()
|
||||
gc.Current.Path.Clear()
|
||||
}
|
||||
|
||||
func (gc *GraphicContext) Stroke(paths ...*draw2d.PathStorage) {
|
||||
func (gc *GraphicContext) Stroke(paths ...*draw2d.Path) {
|
||||
paths = append(paths, gc.Current.Path)
|
||||
gc.strokeRasterizer.UseNonZeroWinding = true
|
||||
|
||||
stroker := draw2d.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2d.NewVertexMatrixTransform(gc.Current.Tr, draw2d.NewVertexAdder(gc.strokeRasterizer)))
|
||||
stroker := draw2dbase.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2dbase.Transformer{Tr: gc.Current.Tr, Flattener: draw2dimg.FtLineBuilder{Adder: gc.strokeRasterizer}})
|
||||
stroker.HalfLineWidth = gc.Current.LineWidth / 2
|
||||
var pathConverter *draw2d.PathConverter
|
||||
|
||||
var liner draw2dbase.Flattener
|
||||
if gc.Current.Dash != nil && len(gc.Current.Dash) > 0 {
|
||||
dasher := draw2d.NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker)
|
||||
pathConverter = draw2d.NewPathConverter(dasher)
|
||||
liner = draw2dbase.NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker)
|
||||
} else {
|
||||
pathConverter = draw2d.NewPathConverter(stroker)
|
||||
liner = stroker
|
||||
}
|
||||
for _, p := range paths {
|
||||
draw2dbase.Flatten(p, liner, gc.Current.Tr.GetScale())
|
||||
}
|
||||
pathConverter.ApproximationScale = gc.Current.Tr.GetScale() // From agg code
|
||||
pathConverter.Convert(paths...)
|
||||
|
||||
gc.paint(gc.strokeRasterizer, gc.Current.StrokeColor)
|
||||
gc.Current.Path.Clear()
|
||||
}
|
||||
|
||||
func (gc *GraphicContext) Fill(paths ...*draw2d.PathStorage) {
|
||||
func (gc *GraphicContext) Fill(paths ...*draw2d.Path) {
|
||||
paths = append(paths, gc.Current.Path)
|
||||
gc.fillRasterizer.UseNonZeroWinding = gc.Current.FillRule.UseNonZeroWinding()
|
||||
gc.fillRasterizer.UseNonZeroWinding = useNonZeroWinding(gc.Current.FillRule)
|
||||
|
||||
pathConverter := draw2d.NewPathConverter(draw2d.NewVertexMatrixTransform(gc.Current.Tr, draw2d.NewVertexAdder(gc.fillRasterizer)))
|
||||
pathConverter.ApproximationScale = gc.Current.Tr.GetScale() // From agg code
|
||||
pathConverter.Convert(paths...)
|
||||
/**** first method ****/
|
||||
flattener := draw2dbase.Transformer{Tr: gc.Current.Tr, Flattener: draw2dimg.FtLineBuilder{Adder: gc.fillRasterizer}}
|
||||
for _, p := range paths {
|
||||
draw2dbase.Flatten(p, flattener, gc.Current.Tr.GetScale())
|
||||
}
|
||||
|
||||
gc.paint(gc.fillRasterizer, gc.Current.FillColor)
|
||||
gc.Current.Path.Clear()
|
||||
}
|
||||
/*
|
||||
func (gc *GraphicContext) Fill(paths ...*draw2d.PathStorage) {
|
||||
|
||||
func (gc *GraphicContext) FillStroke(paths ...*draw2d.Path) {
|
||||
paths = append(paths, gc.Current.Path)
|
||||
gc.fillRasterizer.UseNonZeroWinding = gc.Current.FillRule.UseNonZeroWinding()
|
||||
|
||||
pathConverter := draw2d.NewPathAdder(draw2d.NewMatrixTransformAdder(gc.Current.Tr, gc.fillRasterizer))
|
||||
pathConverter.ApproximationScale = gc.Current.Tr.GetScale()
|
||||
pathConverter.Convert(paths...)
|
||||
|
||||
gc.paint(gc.fillRasterizer, gc.Current.FillColor)
|
||||
gc.Current.Path.Clear()
|
||||
}
|
||||
*/
|
||||
|
||||
func (gc *GraphicContext) FillStroke(paths ...*draw2d.PathStorage) {
|
||||
gc.fillRasterizer.UseNonZeroWinding = gc.Current.FillRule.UseNonZeroWinding()
|
||||
gc.fillRasterizer.UseNonZeroWinding = useNonZeroWinding(gc.Current.FillRule)
|
||||
gc.strokeRasterizer.UseNonZeroWinding = true
|
||||
|
||||
filler := draw2d.NewVertexMatrixTransform(gc.Current.Tr, draw2d.NewVertexAdder(gc.fillRasterizer))
|
||||
flattener := draw2dbase.Transformer{Tr: gc.Current.Tr, Flattener: draw2dimg.FtLineBuilder{Adder: gc.fillRasterizer}}
|
||||
|
||||
stroker := draw2d.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2d.NewVertexMatrixTransform(gc.Current.Tr, draw2d.NewVertexAdder(gc.strokeRasterizer)))
|
||||
stroker := draw2dbase.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2dbase.Transformer{Tr: gc.Current.Tr, Flattener: draw2dimg.FtLineBuilder{Adder: gc.strokeRasterizer}})
|
||||
stroker.HalfLineWidth = gc.Current.LineWidth / 2
|
||||
|
||||
demux := draw2d.NewDemuxConverter(filler, stroker)
|
||||
paths = append(paths, gc.Current.Path)
|
||||
pathConverter := draw2d.NewPathConverter(demux)
|
||||
pathConverter.ApproximationScale = gc.Current.Tr.GetScale() // From agg code
|
||||
pathConverter.Convert(paths...)
|
||||
var liner draw2dbase.Flattener
|
||||
if gc.Current.Dash != nil && len(gc.Current.Dash) > 0 {
|
||||
liner = draw2dbase.NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker)
|
||||
} else {
|
||||
liner = stroker
|
||||
}
|
||||
|
||||
demux := draw2dbase.DemuxFlattener{Flatteners: []draw2dbase.Flattener{flattener, liner}}
|
||||
for _, p := range paths {
|
||||
draw2dbase.Flatten(p, demux, gc.Current.Tr.GetScale())
|
||||
}
|
||||
|
||||
// Fill
|
||||
gc.paint(gc.fillRasterizer, gc.Current.FillColor)
|
||||
// Stroke
|
||||
gc.paint(gc.strokeRasterizer, gc.Current.StrokeColor)
|
||||
gc.Current.Path = draw2d.NewPathStorage()
|
||||
}
|
||||
|
||||
func useNonZeroWinding(f draw2d.FillRule) bool {
|
||||
switch f {
|
||||
case draw2d.FillRuleEvenOdd:
|
||||
return false
|
||||
case draw2d.FillRuleWinding:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
Rendering Vector Art OpenGl
|
||||
References:
|
||||
* https://www.youtube.com/watch?v=K5u8ZZBWSdw
|
||||
* http://http.developer.nvidia.com/GPUGems3/gpugems3_ch25.html
|
||||
* http://research.microsoft.com/en-us/um/people/cloop/loopblinn05.pdf
|
||||
* https://github.com/openframeworks/openFrameworks/issues/1190
|
|
@ -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,
|
||||
}
|
||||
}
|
|
@ -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.
|
|
@ -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)
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
package draw2dimg
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"image"
|
||||
"image/png"
|
||||
"os"
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// LoadFromPngFile Open a png file
|
||||
func LoadFromPngFile(filePath string) (image.Image, error) {
|
||||
// Open file
|
||||
f, err := os.OpenFile(filePath, 0, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
b := bufio.NewReader(f)
|
||||
img, err := png.Decode(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return img, nil
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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() {
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
// created: 13/12/2010 by Laurent Le Goff
|
||||
package draw2d
|
||||
|
||||
// Package draw2dkit provides helpers to draw common figures using a Path
|
||||
package draw2dkit
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/llgcode/draw2d"
|
||||
)
|
||||
|
||||
|
||||
//high level path creation
|
||||
|
||||
|
||||
func Rect(path Path, x1, y1, x2, y2 float64) {
|
||||
// Rectangle draws a rectangle using a path between (x1,y1) and (x2,y2)
|
||||
func Rectangle(path draw2d.PathBuilder, x1, y1, x2, y2 float64) {
|
||||
path.MoveTo(x1, y1)
|
||||
path.LineTo(x2, y1)
|
||||
path.LineTo(x2, y2)
|
||||
|
@ -18,7 +19,8 @@ func Rect(path Path, x1, y1, x2, y2 float64) {
|
|||
path.Close()
|
||||
}
|
||||
|
||||
func RoundRect(path Path, x1, y1, x2, y2, arcWidth, arcHeight float64) {
|
||||
// RoundedRectangle draws a rectangle using a path between (x1,y1) and (x2,y2)
|
||||
func RoundedRectangle(path draw2d.PathBuilder, x1, y1, x2, y2, arcWidth, arcHeight float64) {
|
||||
arcWidth = arcWidth / 2
|
||||
arcHeight = arcHeight / 2
|
||||
path.MoveTo(x1, y1+arcHeight)
|
||||
|
@ -32,12 +34,14 @@ func RoundRect(path Path, x1, y1, x2, y2, arcWidth, arcHeight float64) {
|
|||
path.Close()
|
||||
}
|
||||
|
||||
func Ellipse(path Path, cx, cy, rx, ry float64) {
|
||||
// Ellipse draws an ellipse using a path with center (cx,cy) and radius (rx,ry)
|
||||
func Ellipse(path draw2d.PathBuilder, cx, cy, rx, ry float64) {
|
||||
path.ArcTo(cx, cy, rx, ry, 0, -math.Pi*2)
|
||||
path.Close()
|
||||
}
|
||||
|
||||
func Circle(path Path, cx, cy, radius float64) {
|
||||
// Circle draws a circle using a path with center (cx,cy) and radius
|
||||
func Circle(path draw2d.PathBuilder, cx, cy, radius float64) {
|
||||
path.ArcTo(cx, cy, radius, radius, 0, -math.Pi*2)
|
||||
path.Close()
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
draw2d pdf
|
||||
==========
|
||||
|
||||
Package draw2dpdf provides a graphic context that can draw vector graphics and text on pdf file with the [gofpdf](https://github.com/jung-kurt/gofpdf) package.
|
||||
|
||||
Quick Start
|
||||
-----------
|
||||
|
||||
The following Go code generates a simple drawing and saves it to a pdf document:
|
||||
```go
|
||||
// Initialize the graphic context on an RGBA image
|
||||
dest := draw2dpdf.NewPdf("L", "mm", "A4")
|
||||
gc := draw2dpdf.NewGraphicContext(dest)
|
||||
|
||||
// Set some properties
|
||||
gc.SetFillColor(color.RGBA{0x44, 0xff, 0x44, 0xff})
|
||||
gc.SetStrokeColor(color.RGBA{0x44, 0x44, 0x44, 0xff})
|
||||
gc.SetLineWidth(5)
|
||||
|
||||
// Draw a closed shape
|
||||
gc.MoveTo(10, 10) // should always be called first for a new path
|
||||
gc.LineTo(100, 50)
|
||||
gc.QuadCurveTo(100, 10, 10, 10)
|
||||
gc.Close()
|
||||
gc.FillStroke()
|
||||
|
||||
// Save to file
|
||||
draw2dpdf.SaveToPdfFile("hello.pdf", dest)
|
||||
```
|
||||
|
||||
There are more examples here: https://github.com/llgcode/draw2d/tree/master/samples
|
||||
|
||||
Alternative backends
|
||||
--------------------
|
||||
|
||||
- Drawing on images is provided by the draw2d package.
|
||||
- Drawing on opengl is provided by the draw2dgl package.
|
||||
|
||||
Acknowledgments
|
||||
---------------
|
||||
|
||||
The pdf backend uses https://github.com/jung-kurt/gofpdf
|
|
@ -0,0 +1,41 @@
|
|||
// Copyright 2015 The draw2d Authors. All rights reserved.
|
||||
// created: 26/06/2015 by Stani Michiels
|
||||
|
||||
// Package draw2dpdf provides a graphic context that can draw vector
|
||||
// graphics and text on pdf file with the gofpdf package.
|
||||
//
|
||||
// Quick Start
|
||||
//
|
||||
// The following Go code generates a simple drawing and saves it to a
|
||||
// pdf document:
|
||||
// // Initialize the graphic context on an RGBA image
|
||||
// dest := draw2dpdf.NewPdf("L", "mm", "A4")
|
||||
// gc := draw2dpdf.NewGraphicContext(dest)
|
||||
//
|
||||
// // Set some properties
|
||||
// gc.SetFillColor(color.RGBA{0x44, 0xff, 0x44, 0xff})
|
||||
// gc.SetStrokeColor(color.RGBA{0x44, 0x44, 0x44, 0xff})
|
||||
// gc.SetLineWidth(5)
|
||||
//
|
||||
// // Draw a closed shape
|
||||
// gc.MoveTo(10, 10) // should always be called first for a new path
|
||||
// gc.LineTo(100, 50)
|
||||
// gc.QuadCurveTo(100, 10, 10, 10)
|
||||
// gc.Close()
|
||||
// gc.FillStroke()
|
||||
//
|
||||
// // Save to file
|
||||
// draw2dpdf.SaveToPdfFile("hello.pdf", dest)
|
||||
//
|
||||
// There are more examples here:
|
||||
// https://github.com/llgcode/draw2d/tree/master/samples
|
||||
//
|
||||
// Alternative backends
|
||||
//
|
||||
// Drawing on images is provided by the draw2d package.
|
||||
// Drawing on opengl is provided by the draw2dgl package.
|
||||
//
|
||||
// Acknowledgments
|
||||
//
|
||||
// The pdf backend uses https://github.com/jung-kurt/gofpdf
|
||||
package draw2dpdf
|
|
@ -0,0 +1,11 @@
|
|||
// Copyright 2015 The draw2d Authors. All rights reserved.
|
||||
// created: 26/06/2015 by Stani Michiels
|
||||
|
||||
package draw2dpdf
|
||||
|
||||
import "github.com/jung-kurt/gofpdf"
|
||||
|
||||
// SaveToPdfFile creates and saves a pdf document to a file
|
||||
func SaveToPdfFile(filePath string, pdf *gofpdf.Fpdf) error {
|
||||
return pdf.OutputFileAndClose(filePath)
|
||||
}
|
|
@ -0,0 +1,379 @@
|
|||
// Copyright 2015 The draw2d Authors. All rights reserved.
|
||||
// created: 26/06/2015 by Stani Michiels
|
||||
// TODO: dashed line
|
||||
|
||||
package draw2dpdf
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/png"
|
||||
"log"
|
||||
"math"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"git.fromouter.space/crunchy-rocks/freetype/truetype"
|
||||
|
||||
"github.com/jung-kurt/gofpdf"
|
||||
"github.com/llgcode/draw2d"
|
||||
"github.com/llgcode/draw2d/draw2dbase"
|
||||
"github.com/llgcode/draw2d/draw2dkit"
|
||||
)
|
||||
|
||||
const (
|
||||
// DPI of a pdf document is fixed at 72.
|
||||
DPI = 72
|
||||
c255 = 255.0 / 65535.0
|
||||
)
|
||||
|
||||
var (
|
||||
caps = map[draw2d.LineCap]string{
|
||||
draw2d.RoundCap: "round",
|
||||
draw2d.ButtCap: "butt",
|
||||
draw2d.SquareCap: "square"}
|
||||
joins = map[draw2d.LineJoin]string{
|
||||
draw2d.RoundJoin: "round",
|
||||
draw2d.BevelJoin: "bevel",
|
||||
draw2d.MiterJoin: "miter",
|
||||
}
|
||||
imageCount uint32
|
||||
white color.Color = color.RGBA{255, 255, 255, 255}
|
||||
)
|
||||
|
||||
// NewPdf creates a new pdf document with the draw2d fontfolder, adds
|
||||
// a page and set fill color to white.
|
||||
func NewPdf(orientationStr, unitStr, sizeStr string) *gofpdf.Fpdf {
|
||||
pdf := gofpdf.New(orientationStr, unitStr, sizeStr, draw2d.GetFontFolder())
|
||||
// to be compatible with draw2d
|
||||
pdf.SetMargins(0, 0, 0)
|
||||
pdf.SetDrawColor(0, 0, 0)
|
||||
pdf.SetFillColor(255, 255, 255)
|
||||
pdf.SetLineCapStyle("round")
|
||||
pdf.SetLineJoinStyle("round")
|
||||
pdf.SetLineWidth(1)
|
||||
pdf.AddPage()
|
||||
return pdf
|
||||
}
|
||||
|
||||
// rgb converts a color (used by draw2d) into 3 int (used by gofpdf)
|
||||
func rgb(c color.Color) (int, int, int) {
|
||||
r, g, b, _ := c.RGBA()
|
||||
return int(float64(r) * c255), int(float64(g) * c255), int(float64(b) * c255)
|
||||
}
|
||||
|
||||
// clearRect draws a white rectangle
|
||||
func clearRect(gc *GraphicContext, x1, y1, x2, y2 float64) {
|
||||
// save state
|
||||
f := gc.Current.FillColor
|
||||
x, y := gc.pdf.GetXY()
|
||||
// cover page with white rectangle
|
||||
gc.SetFillColor(white)
|
||||
draw2dkit.Rectangle(gc, x1, y1, x2, y2)
|
||||
gc.Fill()
|
||||
// restore state
|
||||
gc.SetFillColor(f)
|
||||
gc.pdf.MoveTo(x, y)
|
||||
}
|
||||
|
||||
// GraphicContext implements the draw2d.GraphicContext interface
|
||||
// It provides draw2d with a pdf backend (based on gofpdf)
|
||||
type GraphicContext struct {
|
||||
*draw2dbase.StackGraphicContext
|
||||
pdf *gofpdf.Fpdf
|
||||
DPI int
|
||||
}
|
||||
|
||||
// NewGraphicContext creates a new pdf GraphicContext
|
||||
func NewGraphicContext(pdf *gofpdf.Fpdf) *GraphicContext {
|
||||
gc := &GraphicContext{draw2dbase.NewStackGraphicContext(), pdf, DPI}
|
||||
gc.SetDPI(DPI)
|
||||
return gc
|
||||
}
|
||||
|
||||
// DrawImage draws an image as PNG
|
||||
// TODO: add type (tp) as parameter to argument list?
|
||||
func (gc *GraphicContext) DrawImage(image image.Image) {
|
||||
name := strconv.Itoa(int(imageCount))
|
||||
imageCount++
|
||||
tp := "PNG" // "JPG", "JPEG", "PNG" and "GIF"
|
||||
b := &bytes.Buffer{}
|
||||
png.Encode(b, image)
|
||||
gc.pdf.RegisterImageReader(name, tp, b)
|
||||
bounds := image.Bounds()
|
||||
x0, y0 := float64(bounds.Min.X), float64(bounds.Min.Y)
|
||||
w, h := float64(bounds.Dx()), float64(bounds.Dy())
|
||||
gc.pdf.Image(name, x0, y0, w, h, false, tp, 0, "")
|
||||
}
|
||||
|
||||
// Clear draws a white rectangle over the whole page
|
||||
func (gc *GraphicContext) Clear() {
|
||||
width, height := gc.pdf.GetPageSize()
|
||||
clearRect(gc, 0, 0, width, height)
|
||||
}
|
||||
|
||||
// ClearRect draws a white rectangle over the specified area.
|
||||
// Samples: line.
|
||||
func (gc *GraphicContext) ClearRect(x1, y1, x2, y2 int) {
|
||||
clearRect(gc, float64(x1), float64(y1), float64(x2), float64(y2))
|
||||
}
|
||||
|
||||
// recalc recalculates scale and bounds values from the font size, screen
|
||||
// resolution and font metrics, and invalidates the glyph cache.
|
||||
func (gc *GraphicContext) recalc() {
|
||||
// TODO: resolve properly the font size for pdf and bitmap
|
||||
gc.Current.Scale = 3 * float64(gc.DPI) / 72
|
||||
}
|
||||
|
||||
// SetDPI sets the DPI which influences the font size.
|
||||
func (gc *GraphicContext) SetDPI(dpi int) {
|
||||
gc.DPI = dpi
|
||||
gc.recalc()
|
||||
}
|
||||
|
||||
// GetDPI returns the DPI which influences the font size.
|
||||
// (Note that gofpdf uses a fixed dpi of 72:
|
||||
// https://godoc.org/code.google.com/p/gofpdf#Fpdf.PointConvert)
|
||||
func (gc *GraphicContext) GetDPI() int {
|
||||
return gc.DPI
|
||||
}
|
||||
|
||||
// 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 *GraphicContext) GetStringBounds(s string) (left, top, right, bottom float64) {
|
||||
_, h := gc.pdf.GetFontSize()
|
||||
d := gc.pdf.GetFontDesc("", "")
|
||||
if d.Ascent == 0 {
|
||||
// not defined (standard font?), use average of 81%
|
||||
top = 0.81 * h
|
||||
} else {
|
||||
top = -float64(d.Ascent) * h / float64(d.Ascent-d.Descent)
|
||||
}
|
||||
return 0, top, gc.pdf.GetStringWidth(s), top + h
|
||||
}
|
||||
|
||||
// CreateStringPath creates a path from the string s at x, y, and returns the string width.
|
||||
func (gc *GraphicContext) CreateStringPath(text string, x, y float64) (cursor float64) {
|
||||
//fpdf uses the top left corner
|
||||
left, top, right, bottom := gc.GetStringBounds(text)
|
||||
w := right - left
|
||||
h := bottom - top
|
||||
// gc.pdf.SetXY(x, y-h) do not use this as y-h might be negative
|
||||
margin := gc.pdf.GetCellMargin()
|
||||
gc.pdf.MoveTo(x-left-margin, y+top)
|
||||
gc.pdf.CellFormat(w, h, text, "", 0, "BL", false, 0, "")
|
||||
return w
|
||||
}
|
||||
|
||||
// FillString draws a string at 0, 0
|
||||
func (gc *GraphicContext) FillString(text string) (cursor float64) {
|
||||
return gc.FillStringAt(text, 0, 0)
|
||||
}
|
||||
|
||||
// FillStringAt draws a string at x, y
|
||||
func (gc *GraphicContext) FillStringAt(text string, x, y float64) (cursor float64) {
|
||||
return gc.CreateStringPath(text, x, y)
|
||||
}
|
||||
|
||||
// StrokeString draws a string at 0, 0 (stroking is unsupported,
|
||||
// string will be filled)
|
||||
func (gc *GraphicContext) StrokeString(text string) (cursor float64) {
|
||||
return gc.StrokeStringAt(text, 0, 0)
|
||||
}
|
||||
|
||||
// StrokeStringAt draws a string at x, y (stroking is unsupported,
|
||||
// string will be filled)
|
||||
func (gc *GraphicContext) StrokeStringAt(text string, x, y float64) (cursor float64) {
|
||||
return gc.CreateStringPath(text, x, y)
|
||||
}
|
||||
|
||||
// Stroke strokes the paths with the color specified by SetStrokeColor
|
||||
func (gc *GraphicContext) Stroke(paths ...*draw2d.Path) {
|
||||
_, _, _, alphaS := gc.Current.StrokeColor.RGBA()
|
||||
gc.draw("D", alphaS, paths...)
|
||||
gc.Current.Path.Clear()
|
||||
}
|
||||
|
||||
// Fill fills the paths with the color specified by SetFillColor
|
||||
func (gc *GraphicContext) Fill(paths ...*draw2d.Path) {
|
||||
style := "F"
|
||||
if gc.Current.FillRule != draw2d.FillRuleWinding {
|
||||
style += "*"
|
||||
}
|
||||
_, _, _, alphaF := gc.Current.FillColor.RGBA()
|
||||
gc.draw(style, alphaF, paths...)
|
||||
gc.Current.Path.Clear()
|
||||
}
|
||||
|
||||
// FillStroke first fills the paths and than strokes them
|
||||
func (gc *GraphicContext) FillStroke(paths ...*draw2d.Path) {
|
||||
var rule string
|
||||
if gc.Current.FillRule != draw2d.FillRuleWinding {
|
||||
rule = "*"
|
||||
}
|
||||
_, _, _, alphaS := gc.Current.StrokeColor.RGBA()
|
||||
_, _, _, alphaF := gc.Current.FillColor.RGBA()
|
||||
if alphaS == alphaF {
|
||||
gc.draw("FD"+rule, alphaF, paths...)
|
||||
} else {
|
||||
gc.draw("F"+rule, alphaF, paths...)
|
||||
gc.draw("S", alphaS, paths...)
|
||||
}
|
||||
gc.Current.Path.Clear()
|
||||
}
|
||||
|
||||
var logger = log.New(os.Stdout, "", log.Lshortfile)
|
||||
|
||||
const alphaMax = float64(0xFFFF)
|
||||
|
||||
// draw fills and/or strokes paths
|
||||
func (gc *GraphicContext) draw(style string, alpha uint32, paths ...*draw2d.Path) {
|
||||
paths = append(paths, gc.Current.Path)
|
||||
for _, p := range paths {
|
||||
ConvertPath(p, gc.pdf)
|
||||
}
|
||||
a := float64(alpha) / alphaMax
|
||||
current, blendMode := gc.pdf.GetAlpha()
|
||||
if a != current {
|
||||
gc.pdf.SetAlpha(a, blendMode)
|
||||
}
|
||||
gc.pdf.DrawPath(style)
|
||||
}
|
||||
|
||||
// overwrite StackGraphicContext methods
|
||||
|
||||
// SetStrokeColor sets the stroke color
|
||||
func (gc *GraphicContext) SetStrokeColor(c color.Color) {
|
||||
gc.StackGraphicContext.SetStrokeColor(c)
|
||||
gc.pdf.SetDrawColor(rgb(c))
|
||||
}
|
||||
|
||||
// SetFillColor sets the fill and text color
|
||||
func (gc *GraphicContext) SetFillColor(c color.Color) {
|
||||
gc.StackGraphicContext.SetFillColor(c)
|
||||
gc.pdf.SetFillColor(rgb(c))
|
||||
gc.pdf.SetTextColor(rgb(c))
|
||||
}
|
||||
|
||||
// SetFont is unsupported by the pdf graphic context, use SetFontData
|
||||
// instead.
|
||||
func (gc *GraphicContext) SetFont(font *truetype.Font) {
|
||||
// TODO: what to do with this api conflict between draw2d and gofpdf?!
|
||||
}
|
||||
|
||||
// SetFontData sets the current font used to draw text. Always use
|
||||
// this method, as SetFont is unsupported by the pdf graphic context.
|
||||
// It is mandatory to call this method at least once before printing
|
||||
// text or the resulting document will not be valid.
|
||||
// It is necessary to generate a font definition file first with the
|
||||
// makefont utility. It is not necessary to call this function for the
|
||||
// core PDF fonts (courier, helvetica, times, zapfdingbats).
|
||||
// go get github.com/jung-kurt/gofpdf/makefont
|
||||
// http://godoc.org/github.com/jung-kurt/gofpdf#Fpdf.AddFont
|
||||
func (gc *GraphicContext) SetFontData(fontData draw2d.FontData) {
|
||||
// TODO: call Makefont embed if json file does not exist yet
|
||||
gc.StackGraphicContext.SetFontData(fontData)
|
||||
var style string
|
||||
if fontData.Style&draw2d.FontStyleBold != 0 {
|
||||
style += "B"
|
||||
}
|
||||
if fontData.Style&draw2d.FontStyleItalic != 0 {
|
||||
style += "I"
|
||||
}
|
||||
fn := draw2d.FontFileName(fontData)
|
||||
fn = fn[:len(fn)-4]
|
||||
size, _ := gc.pdf.GetFontSize()
|
||||
gc.pdf.AddFont(fontData.Name, style, fn+".json")
|
||||
gc.pdf.SetFont(fontData.Name, style, size)
|
||||
}
|
||||
|
||||
// SetFontSize sets the font size in points (as in ``a 12 point font'').
|
||||
// TODO: resolve this with ImgGraphicContext (now done with gc.Current.Scale)
|
||||
func (gc *GraphicContext) SetFontSize(fontSize float64) {
|
||||
gc.StackGraphicContext.SetFontSize(fontSize)
|
||||
gc.recalc()
|
||||
gc.pdf.SetFontSize(fontSize * gc.Current.Scale)
|
||||
}
|
||||
|
||||
// SetLineDash sets the line dash pattern
|
||||
func (gc *GraphicContext) SetLineDash(Dash []float64, DashOffset float64) {
|
||||
gc.StackGraphicContext.SetLineDash(Dash, DashOffset)
|
||||
gc.pdf.SetDashPattern(Dash, DashOffset)
|
||||
}
|
||||
|
||||
// SetLineWidth sets the line width
|
||||
func (gc *GraphicContext) SetLineWidth(LineWidth float64) {
|
||||
gc.StackGraphicContext.SetLineWidth(LineWidth)
|
||||
gc.pdf.SetLineWidth(LineWidth)
|
||||
}
|
||||
|
||||
// SetLineCap sets the line cap (round, but or square)
|
||||
func (gc *GraphicContext) SetLineCap(Cap draw2d.LineCap) {
|
||||
gc.StackGraphicContext.SetLineCap(Cap)
|
||||
gc.pdf.SetLineCapStyle(caps[Cap])
|
||||
}
|
||||
|
||||
// SetLineJoin sets the line cap (round, bevel or miter)
|
||||
func (gc *GraphicContext) SetLineJoin(Join draw2d.LineJoin) {
|
||||
gc.StackGraphicContext.SetLineJoin(Join)
|
||||
gc.pdf.SetLineJoinStyle(joins[Join])
|
||||
}
|
||||
|
||||
// Transformations
|
||||
|
||||
// Scale generally scales the following text, drawings and images.
|
||||
// sx and sy are the scaling factors for width and height.
|
||||
// This must be placed between gc.Save() and gc.Restore(), otherwise
|
||||
// the pdf is invalid.
|
||||
func (gc *GraphicContext) Scale(sx, sy float64) {
|
||||
gc.StackGraphicContext.Scale(sx, sy)
|
||||
gc.pdf.TransformScale(sx*100, sy*100, 0, 0)
|
||||
}
|
||||
|
||||
// Rotate rotates the following text, drawings and images.
|
||||
// Angle is specified in radians and measured clockwise from the
|
||||
// 3 o'clock position.
|
||||
// This must be placed between gc.Save() and gc.Restore(), otherwise
|
||||
// the pdf is invalid.
|
||||
func (gc *GraphicContext) Rotate(angle float64) {
|
||||
gc.StackGraphicContext.Rotate(angle)
|
||||
gc.pdf.TransformRotate(-angle*180/math.Pi, 0, 0)
|
||||
}
|
||||
|
||||
// Translate moves the following text, drawings and images
|
||||
// horizontally and vertically by the amounts specified by tx and ty.
|
||||
// This must be placed between gc.Save() and gc.Restore(), otherwise
|
||||
// the pdf is invalid.
|
||||
func (gc *GraphicContext) Translate(tx, ty float64) {
|
||||
gc.StackGraphicContext.Translate(tx, ty)
|
||||
gc.pdf.TransformTranslate(tx, ty)
|
||||
}
|
||||
|
||||
// Save saves the current context stack
|
||||
// (transformation, font, color,...).
|
||||
func (gc *GraphicContext) Save() {
|
||||
gc.StackGraphicContext.Save()
|
||||
gc.pdf.TransformBegin()
|
||||
}
|
||||
|
||||
// Restore restores the current context stack
|
||||
// (transformation, color,...). Restoring the font is not supported.
|
||||
func (gc *GraphicContext) Restore() {
|
||||
gc.pdf.TransformEnd()
|
||||
gc.StackGraphicContext.Restore()
|
||||
c := gc.Current
|
||||
gc.SetFontSize(c.FontSize)
|
||||
// gc.SetFontData(c.FontData) unsupported, causes bug (do not enable)
|
||||
gc.SetLineWidth(c.LineWidth)
|
||||
gc.SetStrokeColor(c.StrokeColor)
|
||||
gc.SetFillColor(c.FillColor)
|
||||
gc.SetFillRule(c.FillRule)
|
||||
// gc.SetLineDash(c.Dash, c.DashOffset) // TODO
|
||||
gc.SetLineCap(c.Cap)
|
||||
gc.SetLineJoin(c.Join)
|
||||
// c.Path unsupported
|
||||
// c.Font unsupported
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
// Copyright 2015 The draw2d Authors. All rights reserved.
|
||||
// created: 26/06/2015 by Stani Michiels
|
||||
|
||||
package draw2dpdf
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/llgcode/draw2d"
|
||||
)
|
||||
|
||||
const deg = 180 / math.Pi
|
||||
|
||||
// ConvertPath converts a paths to the pdf api
|
||||
func ConvertPath(path *draw2d.Path, pdf Vectorizer) {
|
||||
var startX, startY float64 = 0, 0
|
||||
i := 0
|
||||
for _, cmp := range path.Components {
|
||||
switch cmp {
|
||||
case draw2d.MoveToCmp:
|
||||
startX, startY = path.Points[i], path.Points[i+1]
|
||||
pdf.MoveTo(startX, startY)
|
||||
i += 2
|
||||
case draw2d.LineToCmp:
|
||||
pdf.LineTo(path.Points[i], path.Points[i+1])
|
||||
i += 2
|
||||
case draw2d.QuadCurveToCmp:
|
||||
pdf.CurveTo(path.Points[i], path.Points[i+1], path.Points[i+2], path.Points[i+3])
|
||||
i += 4
|
||||
case draw2d.CubicCurveToCmp:
|
||||
pdf.CurveBezierCubicTo(path.Points[i], path.Points[i+1], path.Points[i+2], path.Points[i+3], path.Points[i+4], path.Points[i+5])
|
||||
i += 6
|
||||
case draw2d.ArcToCmp:
|
||||
pdf.ArcTo(path.Points[i], path.Points[i+1], path.Points[i+2], path.Points[i+3],
|
||||
0, // degRotate
|
||||
path.Points[i+4]*deg, // degStart = startAngle
|
||||
(path.Points[i+4]-path.Points[i+5])*deg) // degEnd = startAngle-angle
|
||||
i += 6
|
||||
case draw2d.CloseCmp:
|
||||
pdf.LineTo(startX, startY)
|
||||
pdf.ClosePath()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 draw2dpdf_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/llgcode/draw2d"
|
||||
"github.com/llgcode/draw2d/samples/android"
|
||||
"github.com/llgcode/draw2d/samples/frameimage"
|
||||
"github.com/llgcode/draw2d/samples/geometry"
|
||||
"github.com/llgcode/draw2d/samples/gopher"
|
||||
"github.com/llgcode/draw2d/samples/gopher2"
|
||||
"github.com/llgcode/draw2d/samples/helloworld"
|
||||
"github.com/llgcode/draw2d/samples/line"
|
||||
"github.com/llgcode/draw2d/samples/linecapjoin"
|
||||
"github.com/llgcode/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)
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
// Copyright 2015 The draw2d Authors. All rights reserved.
|
||||
// created: 26/06/2015 by Stani Michiels
|
||||
|
||||
// Package draw2dpdf_test gives test coverage with the command:
|
||||
// go test -cover ./... | grep -v "no test"
|
||||
// (It should be run from its parent draw2d directory.)
|
||||
package draw2dpdf_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/llgcode/draw2d"
|
||||
"github.com/llgcode/draw2d/draw2dpdf"
|
||||
)
|
||||
|
||||
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 := draw2dpdf.NewPdf("L", "mm", "A4")
|
||||
gc := draw2dpdf.NewGraphicContext(dest)
|
||||
// Draw sample
|
||||
output, err := draw(gc, "pdf")
|
||||
if err != nil {
|
||||
t.Errorf("Drawing %q failed: %v", output, err)
|
||||
return
|
||||
}
|
||||
/*
|
||||
// Save to pdf only if it doesn't exist because of git
|
||||
if _, err = os.Stat(output); err == nil {
|
||||
t.Skipf("Saving %q skipped, as it exists already. (Git would consider it modified.)", output)
|
||||
return
|
||||
}
|
||||
*/
|
||||
err = draw2dpdf.SaveToPdfFile(output, dest)
|
||||
if err != nil {
|
||||
t.Errorf("Saving %q failed: %v", output, err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
// Copyright 2015 The draw2d Authors. All rights reserved.
|
||||
// created: 26/06/2015 by Stani Michiels
|
||||
|
||||
package draw2dpdf
|
||||
|
||||
// Vectorizer defines the minimal interface for gofpdf.Fpdf
|
||||
// to be passed to a PathConvertor.
|
||||
// It is also implemented by for example VertexMatrixTransform
|
||||
type Vectorizer interface {
|
||||
// MoveTo creates a new subpath that start at the specified point
|
||||
MoveTo(x, y float64)
|
||||
// LineTo adds a line to the current subpath
|
||||
LineTo(x, y float64)
|
||||
// CurveTo adds a quadratic bezier curve to the current subpath
|
||||
CurveTo(cx, cy, x, y float64)
|
||||
// CurveTo adds a cubic bezier curve to the current subpath
|
||||
CurveBezierCubicTo(cx1, cy1, cx2, cy2, x, y float64)
|
||||
// ArcTo adds an arc to the current subpath
|
||||
ArcTo(x, y, rx, ry, degRotate, degStart, degEnd float64)
|
||||
// ClosePath closes the subpath
|
||||
ClosePath()
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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"`
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,230 @@
|
|||
// Copyright 2010 The draw2d Authors. All rights reserved.
|
||||
// created: 13/12/2010 by Laurent Le Goff
|
||||
|
||||
package draw2d
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"path/filepath"
|
||||
|
||||
"sync"
|
||||
|
||||
"git.fromouter.space/crunchy-rocks/freetype/truetype"
|
||||
)
|
||||
|
||||
// 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
|
||||
|
||||
const (
|
||||
FontStyleNormal FontStyle = iota
|
||||
FontStyleBold
|
||||
FontStyleItalic
|
||||
)
|
||||
|
||||
type FontFamily byte
|
||||
|
||||
const (
|
||||
FontFamilySans FontFamily = iota
|
||||
FontFamilySerif
|
||||
FontFamilyMono
|
||||
)
|
||||
|
||||
type FontData struct {
|
||||
Name string
|
||||
Family FontFamily
|
||||
Style FontStyle
|
||||
}
|
||||
|
||||
type FontFileNamer func(fontData FontData) string
|
||||
|
||||
func FontFileName(fontData FontData) string {
|
||||
fontFileName := fontData.Name
|
||||
switch fontData.Family {
|
||||
case FontFamilySans:
|
||||
fontFileName += "s"
|
||||
case FontFamilySerif:
|
||||
fontFileName += "r"
|
||||
case FontFamilyMono:
|
||||
fontFileName += "m"
|
||||
}
|
||||
if fontData.Style&FontStyleBold != 0 {
|
||||
fontFileName += "b"
|
||||
} else {
|
||||
fontFileName += "r"
|
||||
}
|
||||
|
||||
if fontData.Style&FontStyleItalic != 0 {
|
||||
fontFileName += "i"
|
||||
}
|
||||
fontFileName += ".ttf"
|
||||
return fontFileName
|
||||
}
|
||||
|
||||
func RegisterFont(fontData FontData, font *truetype.Font) {
|
||||
fontCache.Store(fontData, font)
|
||||
}
|
||||
|
||||
func GetFont(fontData FontData) (font *truetype.Font) {
|
||||
var err error
|
||||
|
||||
if font, err = fontCache.Load(fontData); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func GetFontFolder() string {
|
||||
return defaultFonts.folder
|
||||
}
|
||||
|
||||
func SetFontFolder(folder string) {
|
||||
defaultFonts.setFolder(filepath.Clean(folder))
|
||||
}
|
||||
|
||||
func GetGlobalFontCache() FontCache {
|
||||
return fontCache
|
||||
}
|
||||
|
||||
func SetFontNamer(fn FontFileNamer) {
|
||||
defaultFonts.setNamer(fn)
|
||||
}
|
||||
|
||||
// Types implementing this interface can be passed to SetFontCache to change the
|
||||
// way fonts are being stored and retrieved.
|
||||
type FontCache interface {
|
||||
// Loads a truetype font represented by the FontData object passed as
|
||||
// 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)
|
||||
}
|
||||
|
||||
// Changes the font cache backend used by the package. After calling this
|
||||
// functionSetFontFolder and SetFontNamer will not affect anymore how fonts are
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
)
|
|
@ -0,0 +1,87 @@
|
|||
// Copyright 2010 The draw2d Authors. All rights reserved.
|
||||
// created: 21/11/2010 by Laurent Le Goff
|
||||
|
||||
package draw2d
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
)
|
||||
|
||||
// GraphicContext describes the interface for the various backends (images, pdf, opengl, ...)
|
||||
type GraphicContext interface {
|
||||
// PathBuilder describes the interface for path drawing
|
||||
PathBuilder
|
||||
// BeginPath creates a new path
|
||||
BeginPath()
|
||||
// GetPath copies the current path, then returns it
|
||||
GetPath() Path
|
||||
// GetMatrixTransform returns the current transformation matrix
|
||||
GetMatrixTransform() Matrix
|
||||
// SetMatrixTransform sets the current transformation matrix
|
||||
SetMatrixTransform(tr Matrix)
|
||||
// ComposeMatrixTransform composes the current transformation matrix with tr
|
||||
ComposeMatrixTransform(tr Matrix)
|
||||
// Rotate applies a rotation to the current transformation matrix. angle is in radian.
|
||||
Rotate(angle float64)
|
||||
// Translate applies a translation to the current transformation matrix.
|
||||
Translate(tx, ty float64)
|
||||
// Scale applies a scale to the current transformation matrix.
|
||||
Scale(sx, sy float64)
|
||||
// SetStrokeColor sets the current stroke color
|
||||
SetStrokeColor(c color.Color)
|
||||
// SetFillColor sets the current fill color
|
||||
SetFillColor(c color.Color)
|
||||
// SetFillRule sets the current fill rule
|
||||
SetFillRule(f FillRule)
|
||||
// SetLineWidth sets the current line width
|
||||
SetLineWidth(lineWidth float64)
|
||||
// SetLineCap sets the current line cap
|
||||
SetLineCap(cap LineCap)
|
||||
// SetLineJoin sets the current line join
|
||||
SetLineJoin(join LineJoin)
|
||||
// SetLineDash sets the current dash
|
||||
SetLineDash(dash []float64, dashOffset float64)
|
||||
// SetFontSize sets the current font size
|
||||
SetFontSize(fontSize float64)
|
||||
// GetFontSize gets the current font size
|
||||
GetFontSize() float64
|
||||
// SetFontData sets the current FontData
|
||||
SetFontData(fontData FontData)
|
||||
// GetFontData gets the current 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)
|
||||
// Save the context and push it to the context stack
|
||||
Save()
|
||||
// Restore remove the current context and restore the last one
|
||||
Restore()
|
||||
// Clear fills the current canvas with a default transparent color
|
||||
Clear()
|
||||
// ClearRect fills the specified rectangle with a default transparent color
|
||||
ClearRect(x1, y1, x2, y2 int)
|
||||
// SetDPI sets the current DPI
|
||||
SetDPI(dpi int)
|
||||
// GetDPI gets the current DPI
|
||||
GetDPI() int
|
||||
// GetStringBounds gets pixel bounds(dimensions) of given string
|
||||
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)
|
||||
// FillString draws the text at point (0, 0)
|
||||
FillString(text string) (cursor float64)
|
||||
// FillStringAt draws the text at the specified point (x, y)
|
||||
FillStringAt(text string, x, y float64) (cursor float64)
|
||||
// StrokeString draws the contour of the text at point (0, 0)
|
||||
StrokeString(text string) (cursor float64)
|
||||
// StrokeStringAt draws the contour of the text at point (x, y)
|
||||
StrokeStringAt(text string, x, y float64) (cursor float64)
|
||||
// Stroke strokes the paths with the color specified by SetStrokeColor
|
||||
Stroke(paths ...*Path)
|
||||
// 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)
|
||||
}
|
|
@ -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
|
||||
)
|
|
@ -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
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
Demo output
|
||||
===========
|
||||
|
||||
These folders are empty when you check out the git repository. The output is generated by the tests:
|
||||
```
|
||||
go test ./...
|
||||
```
|
||||
or with coverage:
|
||||
```
|
||||
go test -cover ./... | grep -v "no test"
|
||||
```
|
|
@ -0,0 +1,4 @@
|
|||
# Ignore everything in this directory
|
||||
*
|
||||
# Except this file
|
||||
!.gitignore
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue