Compare commits

..

146 commits
v1 ... master

Author SHA1 Message Date
675e82cb64
Update go.mod (for real) 2019-02-08 11:15:35 +01:00
ff82ab9451 Update 'go.mod' 2019-02-08 11:13:35 +01:00
a7539fd29b
Fix bound calculation with just emojis (again, super hacky) 2018-11-16 17:36:38 +01:00
41b8d7304e
Should fix centering issues with trailing emojis 2018-11-16 17:13:49 +01:00
9dbc14edd6
Better approximation of emoji's bounds 2018-11-16 17:00:00 +01:00
9941d77460
Add emojis to bound calculation 2018-11-16 15:19:48 +01:00
e3566f7fc4
Add emoji support 2018-11-16 12:21:26 +01:00
219501b99b
Change import paths 2018-11-16 12:21:14 +01:00
llgcode
f52c8a71af
Merge pull request #146 from piotrkowalczuk/get-font-name-proper-font-size-serialization
GetFontName serialize font size properly
thanks @piotrkowalczuk
2018-08-25 15:34:48 +02:00
Piotr Kowalczuk
bdf3a69827 single GetFontName implementation that serialize font size properly 2018-08-20 12:17:57 +02:00
llgcode
587a55234c
Merge pull request #144 from sbinet/go-module-support
Go module support
2018-08-17 15:29:18 +02:00
Sebastien Binet
94de6e33b6 draw2d: add support for Go modules 2018-07-24 12:59:37 +02:00
Sebastien Binet
cd0433711b draw2d: add Sebastien Binet to AUTHORS 2018-07-24 12:59:37 +02:00
llgcode
274031cf2a
Merge pull request #140 from Drahoslav7/patch-1
Update AUTHORS, Thanks @Drahoslav7
2018-01-24 14:33:39 +01:00
llgcode
bc151d5e2c
Merge pull request #137 from Drahoslav7/feature/svg-context
Feature/svg context
2018-01-24 14:27:22 +01:00
Drahoslav Bednář
72e6a3c750
Update AUTHORS 2018-01-22 16:10:39 +01:00
Drahoslav
0b72959009 Implement PathFontMode and make it default 2018-01-10 23:00:49 +01:00
Drahoslav
7419075cb6 Implement ClearRect in svg context 2018-01-10 22:13:38 +01:00
Drahoslav
1588b49f0d Optimize svg output sligtly 2018-01-10 20:21:07 +01:00
llgcode
50aafedab4
Good Practice: always use BeginPath 2018-01-08 17:55:31 +01:00
Drahoslav
99cc16d0ac Implement DrawImage in svg context 2018-01-08 00:25:25 +01:00
Drahoslav
1b49270d08 Experimental embed svg font functionality 2018-01-07 23:05:53 +01:00
Drahoslav
c1e5edea41 Implement CreateStringPath and GetStringBounds in svg context 2018-01-07 17:47:54 +01:00
Drahoslav
6f03f106f6 Minor changes 2017-12-27 12:32:23 +01:00
Drahoslav
3af25f5588 Use font-size in texts of svg context 2017-12-26 19:52:06 +01:00
Drahoslav
90f962641f Implement basic text drawing in svg context
fonts, bounds and string paths not yet implemented
2017-12-26 19:38:19 +01:00
Drahoslav
215a761ccb Use transformations in svg context
also few minor changes in to svg transformation functions
2017-12-26 14:25:28 +01:00
Drahoslav
6d31bfac59 Use fill-rule attribute in svg context 2017-12-24 16:05:15 +01:00
Drahoslav
d297a025cd Do minor refactoring of svg context 2017-12-24 15:31:41 +01:00
Drahoslav
484fe1caef Use dasharray and dashoffset attributes in svg context 2017-12-24 14:46:58 +01:00
Drahoslav
0b3b26d85f Gofmt 2017-12-24 13:45:56 +01:00
Drahoslav
41d8a21ba2 Improve toSvgPathDesc
some toArcs paths still does not work correctly
2017-12-24 13:27:44 +01:00
Drahoslav
6c0a15c624 Use line-{width,cap,join} attributes in svg context 2017-12-24 12:34:36 +01:00
Drahoslav
ca83e24222 Basic path implementation 2017-12-22 22:40:56 +01:00
Drahoslav
bd7567e331 Add samples test - no outputs yet 2017-12-22 09:59:31 +01:00
Drahoslav
cdf301b7be Use encoding/xml to encode svg 2017-12-21 18:18:29 +01:00
Drahoslav
295a8365b3 Remove svgo dependency 2017-12-21 15:52:26 +01:00
Drahoslav
96883adea4 Initial commit for svg context
Satisfy draw2dc.GrapghicContext using empty methods
2017-12-16 19:26:34 +01:00
llgcode
c41aa97d30 fix #136 2017-12-11 17:32:57 +01:00
llgcode
647da9ceaa fix draw2dgl compil' error 2017-12-11 11:02:06 +01:00
llgcode
f3e35015aa synchronize global font cache fixes #131 2017-12-04 18:06:33 +01:00
llgcode
b81f74eb39 Refactor Api using an interface 2017-12-04 16:49:08 +01:00
llgcode
a5f7ac8ebe fix #131 2017-12-04 16:29:40 +01:00
llgcode
8167230c09 go fmt 2017-12-04 09:46:40 +01:00
llgcode
3e4c36c4c9
Merge pull request #126 from gerald1248/sync
test for concurrent text drawing
2017-12-04 09:43:19 +01:00
llgcode
4cdcb11e52 run all tests 2017-11-28 11:12:29 +01:00
llgcode
e4816c5375 fix issue 135
SubdivideQuad and TraceQuad index out of range errors
2017-11-28 10:52:49 +01:00
llgcode
dd69e0c822 Test curve range 2017-10-11 10:25:10 +02:00
llgcode
dcbfbe505d Merge pull request #134 from AndreKR/convert-lineto-to-moveto
Convert LineTo to MoveTo for the first point of a path
2017-05-13 11:26:31 +02:00
André Hänsel
1f71aa3f15 Convert LineTo to MoveTo for the first point of a path
Same for QuadCurveTo and CubicCurveTo.

Closes #133
2017-04-25 07:59:14 +02:00
gerald1248
0cf6b8d61f fixed typo 2017-01-06 22:24:37 +01:00
gerald1248
7c57ea38bb removed all trickery, left only plain draw2d calls 2017-01-06 22:22:34 +01:00
gerald1248
eca7b76ebc fmt 2016-12-13 22:23:50 +01:00
gerald1248
dfbef878aa added test for concurrent text drawing 2016-12-13 22:22:19 +01:00
llgcode
1286d3b203 Merge pull request #120 from redstarcoder/kerning_fix
Factor in kerning when using cached glyphs. fix #119
2016-11-04 09:10:29 +01:00
redstarcoder
c12070824c Factor in kerning when using cached glyphs 2016-10-30 12:41:31 -04:00
llgcode
0d961cd299 Merge pull request #118 from redstarcoder/text_cache_upstream
Seamless Glyph Cache
2016-10-26 10:35:50 +02:00
redstarcoder
7cc6abeee3 Made FetchGlyph and Glyph public, made FillGlyph and StrokeGlyph methods of Glyph, made Glyph.Path private 2016-10-25 21:24:40 -04:00
llgcode
401ee667f2 Merge pull request #113 from redstarcoder/pathbuilder_copypath
Add GetPath to the GraphicContext interface.
2016-10-24 10:08:26 +02:00
redstarcoder
c2920005d6 Moved cache code to draw2dbase 2016-10-22 22:50:11 -04:00
redstarcoder
c2851a6eb6 Improved speed via new gc.GetFontName method 2016-10-22 22:36:52 -04:00
redstarcoder
3a5a1d8830 Implement seamless glyph cache 2016-10-22 22:31:55 -04:00
redstarcoder
b9005c988d Fixed comment 2016-10-19 15:01:45 -04:00
redstarcoder
4a3322e29e CopyPath -> GetPath. Also return Path instead of *Path. 2016-10-19 14:53:36 -04:00
redstarcoder
8380dd9458 Added CopyPath to the GraphicContext interface. 2016-10-17 19:36:31 -04:00
llgcode
51ba099819 fix comment 2016-10-17 10:46:10 +02:00
llgcode
a6ceba03c8 Merge pull request #112 from redstarcoder/draw2dgl_fonts
Draw2dgl Font Support
2016-10-16 20:47:51 +02:00
redstarcoder
475a830567 Cleaned up extra FIXMEs 2016-10-15 16:15:10 -04:00
redstarcoder
105a963210 Copied font functions from draw2dimg to draw2dgl
cursor -> width for certain functions for clarity
Some panics added, some TODOs added
2016-10-15 16:15:10 -04:00
llgcode
13548be874 Merge pull request #106 from kortschak/fix-font-cache
Fix unnecessary filesystem access
2016-07-12 10:12:54 +02:00
kortschak
e0e534f3a5 Fix unnecessary filesystem access
Fixes #105.
2016-07-12 16:24:44 +09:30
llgcode
155ff5c755 Merge pull request #94 from achille-roussel/master
draw2d.FontCache
2016-07-08 10:26:28 +02:00
Laurent Le Goff
3f01cfe277 use parameter op fix #103 2016-06-27 21:46:37 +02:00
llgcode
5e675a3055 Merge pull request #102 from zstyblik/fix-typos
Fix typos and add more comments/documentation
2016-04-28 20:44:47 +02:00
Zdenek Styblik
3bb234e85b Fix typos and add more comments/documentation
Commit fixes couple typos and adds more documentation to
GraphicContext interface in `gc.go`.

Fixes #101
2016-04-27 15:25:21 +02:00
llgcode
0545b30698 Fix #98: Alpha spec has changed
need to update package
2016-02-19 11:05:01 +01:00
Laurent Le Goff
f444aacdd7 Merge pull request #96 from iopred/scale
Replace DrawImage implementation for draw.Transformer. thx @iopred
2016-01-03 01:21:57 +01:00
Chris Rhodes
11b6fa221b Use transformer.Transform. 2016-01-02 11:05:47 -08:00
Chris Rhodes
001a24bc17 Replace DrawImage implementation for draw.Scaler.
This change has no API modifications.
2016-01-01 23:57:04 -08:00
Achille Roussel
6c047429f6 remove unused global variables 2015-11-18 15:00:27 -08:00
Achille Roussel
598513aa60 add FontCache 2015-11-18 14:59:19 -08:00
Laurent Le Goff
9ffe0e7eb5 comment FontStyle enum type 2015-11-05 14:43:36 +01:00
llgcode
56180d8101 Change getting started in Readme and godoc 2015-10-08 15:58:39 +02:00
Laurent Le Goff
d6d74f19f9 Merge pull request #89 from bramp/patch-1
Updated README to include github.com/google/hilbert as a user of draw2d
2015-09-13 18:09:24 +02:00
Andrew Brampton
7b20985151 Updated README to include github.com/google/hilbert as a user of draw2d 2015-09-12 15:54:21 -07:00
Laurent Le Goff
835d17ca7c Create README.md 2015-09-11 10:15:00 +02:00
Laurent Le Goff
caad194462 Create README.md 2015-09-11 10:11:40 +02:00
Laurent Le Goff
96d42f14c0 Add README.md with badges 2015-09-11 10:08:31 +02:00
Laurent Le Goff
35dcbff3f7 Add badge 2015-09-11 10:04:26 +02:00
Laurent Le Goff
37f345f4d3 Merge pull request #87 from bramp/issue-86
Update code.google.com/p/freetype-go to github.com/golang/freetype.
2015-09-08 09:39:53 +02:00
Andrew Brampton
c378327bfa Update code.google.com/p/freetype-go to github.com/golang/freetype. Fixes #86.
Updates the dependency, as well as other changes needed to support the newer version of freetype.
2015-09-06 18:08:19 -07:00
Laurent Le Goff
7510d72d52 fix import in test 2015-08-27 15:50:55 +02:00
Laurent Le Goff
48a313740b Merge pull request #83 from llgcode/remove-raster-experiment
Remove raster experiment
2015-08-27 14:29:22 +02:00
Laurent Le Goff
82a7e1e58e Remove raster experiment 2015-08-27 14:12:22 +02:00
Laurent Le Goff
c8d67448a9 generate test image result in output folder 2015-08-27 10:51:09 +02:00
Laurent Le Goff
094e39780f add circle test 2015-08-27 10:42:02 +02:00
Laurent Le Goff
ad6b615bc4 Merge pull request #78 from stephenwithav/newgraphicontexts
Replace remaining draw2d.NewGraphicContext instances.
2015-08-20 09:41:00 +02:00
Steven Edwards
0e0aa125a3 Replace remaining draw2d.NewGraphicContext instances. 2015-08-19 15:42:16 -04:00
Laurent Le Goff
bf42fff416 Add version info to README 2015-08-19 09:56:57 +02:00
Laurent Le Goff
ed44998c46 Merge pull request #74 from stephenwithav/fiximageexample
Change .NewGraphicContext package.
2015-08-17 11:54:28 +02:00
Steven Edwards
1a2db78d7b Change .NewGraphicContext package.
NewGraphicContext is also now in draw2dimg.
2015-08-17 05:44:03 -04:00
Laurent Le Goff
755362132b Merge pull request #73 from stephenwithav/fiximageexample
Fix Getting Started example in README.md.
2015-08-17 11:41:23 +02:00
Steven Edwards
a238a47879 Fix Getting Started example in README.md.
SaveToPngFile is now in draw2dimg.
2015-08-17 05:36:40 -04:00
Laurent Le Goff
b07a8ba2e0 Remove Drawer interface and unecessary draw2dkit 2015-08-14 23:00:23 +02:00
Laurent Le Goff
93c5712ecc Merge pull request #72 from llgcode/Clean-Up
Clean Up
2015-08-14 22:48:08 +02:00
Laurent Le Goff
7e94968f5e Correct some golint 2015-08-14 22:38:18 +02:00
Laurent Le Goff
1e0467b8fc move freetype dependency to draw2dimg 2015-08-14 22:22:01 +02:00
Laurent Le Goff
3b19ab855e use key fields 2015-08-14 22:07:02 +02:00
Laurent Le Goff
9b55e34990 Change file permission 2015-08-14 22:00:33 +02:00
Laurent Le Goff
7ef94ce784 Merge branch 'master' into Clean-Up
Conflicts:
	arc.go
	curve/curve_test.go
	draw2dgl/gc.go
	draw2dimg/rgba_interpolation.go
	draw2dpdf/gc.go
	draw2dpdf/path_converter.go
	path_adder.go
	path_storage.go
	raster/raster_test.go
	vertex2d.go
2015-08-14 21:40:32 +02:00
Laurent Le Goff
04427cabf5 Merge with master 2015-07-09 18:06:14 +02:00
Laurent Le Goff
b14683a552 Start working on text 2015-04-30 18:06:25 +02:00
Laurent Le Goff
f6e1ada0f2 Fix bug 2015-04-30 16:30:50 +02:00
Laurent Le Goff
f2563306e4 Start implementing drawer 2015-04-30 16:27:23 +02:00
Laurent Le Goff
72643a28b2 Rename MatrixTransform to Matrix 2015-04-30 14:11:23 +02:00
Laurent Le Goff
383fef0d7d clean Api 2015-04-30 12:20:22 +02:00
Laurent Le Goff
0345095002 Starts a non Contextual Graphics API 2015-04-29 22:06:59 +02:00
Laurent Le Goff
c7ef18681a comment 2015-04-29 18:09:07 +02:00
Laurent Le Goff
c686a7fcf6 add comments on path 2015-04-29 17:59:39 +02:00
Laurent Le Goff
cf01bf3026 document 2015-04-29 17:52:43 +02:00
Laurent Le Goff
94ef483cbd draw2dkit 2015-04-29 17:49:18 +02:00
Laurent Le Goff
9012e5e580 move helpers to draw2dkit 2015-04-29 17:32:28 +02:00
Laurent Le Goff
47f90d3414 remove old .project file 2015-04-29 17:16:55 +02:00
Laurent Le Goff
74e6b9b1ec move flattening code in draw2dbase 2015-04-29 17:16:44 +02:00
Laurent Le Goff
82ef300f1d move flattening code in draw2dbase 2015-04-29 17:16:15 +02:00
Laurent Le Goff
ee83fedb10 Document path 2015-04-29 16:53:36 +02:00
Laurent Le Goff
409365e40f transform clean up 2015-04-29 16:37:07 +02:00
Laurent Le Goff
511954196b clean Transform 2015-04-29 16:19:49 +02:00
Laurent Le Goff
966a9b73f7 remove path package and create draw2dimg package 2015-04-29 14:33:32 +02:00
Laurent Le Goff
24d62b9aa7 remove unused file 2015-04-29 11:27:59 +02:00
Laurent Le Goff
ce6fbe94f3 Remove old functions 2015-04-29 11:24:09 +02:00
Laurent Le Goff
79f25c1ea2 Refactoring path things 2015-04-29 11:16:03 +02:00
Laurent Le Goff
0b7a049f3e refactoring mv path things in path package 2015-04-29 10:28:05 +02:00
Laurent Le Goff
1d191b3eaf Start path package 2015-04-27 12:16:50 +02:00
Laurent Le Goff
61a6e03fdb Start path package 2015-04-27 12:16:18 +02:00
Laurent Le Goff
d6812fd8e6 Start path package 2015-04-27 12:14:34 +02:00
Laurent Le Goff
06178b5d2d rename Path to PathBuilder 2015-04-23 18:12:31 +02:00
Laurent Le Goff
fef7265145 Remove unecessary method in Path interface 2015-04-23 18:09:41 +02:00
Laurent Le Goff
41809b9132 remove LineMarker 2015-04-23 17:43:26 +02:00
Laurent Le Goff
565dfa9eb9 Replace copy things by append 2015-04-23 17:24:41 +02:00
Laurent Le Goff
5df1705bb4 Remove LineNoMarker 2015-04-23 17:14:16 +02:00
Laurent Le Goff
42d0eb260f Replace LineCloseMarker by Close() Method 2015-04-23 17:08:20 +02:00
Laurent Le Goff
4fa829a373 replace LineEndMarker by End() Method 2015-04-23 16:18:21 +02:00
Laurent Le Goff
ceb331894d Rename LineTracer and VertexConverter to LineBuilder 2015-04-23 15:36:56 +02:00
Laurent Le Goff
4b3ba53f4c migrating to new curve package 2015-04-23 10:05:48 +02:00
Laurent Le Goff
216d3f60dd Clean curve package 2015-04-22 19:07:03 +02:00
112 changed files with 4079 additions and 5577 deletions

2
.gitignore vendored
View file

@ -21,3 +21,5 @@ _test*
**/core*[0-9] **/core*[0-9]
.private .private
go.sum

View file

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

View file

@ -1,2 +1,4 @@
Laurent Le Goff Laurent Le Goff
Stani Michiels, gmail:stani.be Stani Michiels, gmail:stani.be
Drahoslav Bednář
Sebastien Binet

View file

@ -1,5 +1,7 @@
draw2d draw2d
====== ======
[![Coverage](http://gocover.io/_badge/github.com/llgcode/draw2d?0)](http://gocover.io/github.com/llgcode/draw2d)
[![GoDoc](https://godoc.org/github.com/llgcode/draw2d?status.svg)](https://godoc.org/github.com/llgcode/draw2d)
Package draw2d is a pure [go](http://golang.org) 2D vector graphics library with support for multiple output devices such as [images](http://golang.org/pkg/image) (draw2d), pdf documents (draw2dpdf) and opengl (draw2dgl), which can also be used on the google app engine. It can be used as a pure go [Cairo](http://www.cairographics.org/) alternative. draw2d is released under the BSD license. See the [documentation](http://godoc.org/github.com/llgcode/draw2d) for more details. Package draw2d is a pure [go](http://golang.org) 2D vector graphics library with support for multiple output devices such as [images](http://golang.org/pkg/image) (draw2d), pdf documents (draw2dpdf) and opengl (draw2dgl), which can also be used on the google app engine. It can be used as a pure go [Cairo](http://www.cairographics.org/) alternative. draw2d is released under the BSD license. See the [documentation](http://godoc.org/github.com/llgcode/draw2d) for more details.
@ -19,57 +21,84 @@ Installation
Install [golang](http://golang.org/doc/install). To install or update the package draw2d on your system, run: Install [golang](http://golang.org/doc/install). To install or update the package draw2d on your system, run:
Stable release
```
go get -u gopkg.in/llgcode/draw2d.v1
```
or Current release
``` ```
go get -u github.com/llgcode/draw2d go get -u github.com/llgcode/draw2d
``` ```
Quick Start Quick Start
----------- -----------
The following Go code generates a simple drawing and saves it to an image file with package draw2d: The following Go code generates a simple drawing and saves it to an image file with package draw2d:
```go ```go
// Initialize the graphic context on an RGBA image package main
dest := image.NewRGBA(image.Rect(0, 0, 297, 210.0))
gc := draw2d.NewGraphicContext(dest)
// Set some properties import (
gc.SetFillColor(color.RGBA{0x44, 0xff, 0x44, 0xff}) "github.com/llgcode/draw2d/draw2dimg"
gc.SetStrokeColor(color.RGBA{0x44, 0x44, 0x44, 0xff}) "image"
gc.SetLineWidth(5) "image/color"
)
// Draw a closed shape func main() {
gc.MoveTo(10, 10) // should always be called first for a new path // Initialize the graphic context on an RGBA image
gc.LineTo(100, 50) dest := image.NewRGBA(image.Rect(0, 0, 297, 210.0))
gc.QuadCurveTo(100, 10, 10, 10) gc := draw2dimg.NewGraphicContext(dest)
gc.Close()
gc.FillStroke()
// Save to file // Set some properties
draw2d.SaveToPngFile("hello.png", dest) 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: The same Go code can also generate a pdf document with package draw2dpdf:
```go ```go
// Initialize the graphic context on an RGBA image package main
dest := draw2dpdf.NewPdf("L", "mm", "A4")
gc := draw2d.NewGraphicContext(dest)
// Set some properties import (
gc.SetFillColor(color.RGBA{0x44, 0xff, 0x44, 0xff}) "github.com/llgcode/draw2d/draw2dpdf"
gc.SetStrokeColor(color.RGBA{0x44, 0x44, 0x44, 0xff}) "image/color"
gc.SetLineWidth(5) )
// Draw a closed shape func main() {
gc.MoveTo(10, 10) // should always be called first for a new path // Initialize the graphic context on an RGBA image
gc.LineTo(100, 50) dest := draw2dpdf.NewPdf("L", "mm", "A4")
gc.QuadCurveTo(100, 10, 10, 10) gc := draw2dpdf.NewGraphicContext(dest)
gc.Close()
gc.FillStroke()
// Save to file // Set some properties
draw2dpdf.SaveToPdfFile("hello.pdf", dest) 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 There are more examples here: https://github.com/llgcode/draw2d/tree/master/samples
@ -105,6 +134,7 @@ Packages using draw2d
- [smartcrop](https://github.com/muesli/smartcrop): content aware image cropping - [smartcrop](https://github.com/muesli/smartcrop): content aware image cropping
- [karta](https://github.com/peterhellberg/karta): drawing Voronoi diagrams - [karta](https://github.com/peterhellberg/karta): drawing Voronoi diagrams
- [chart](https://github.com/vdobler/chart): basic charts in Go - [chart](https://github.com/vdobler/chart): basic charts in Go
- [hilbert](https://github.com/google/hilbert): package for drawing Hilbert curves
References References
--------- ---------

70
arc.go
View file

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

View file

@ -1,36 +0,0 @@
// Copyright 2010 The draw2d Authors. All rights reserved.
// created: 21/11/2010 by Laurent Le Goff
package curve
import (
"math"
)
func SegmentArc(t LineTracer, x, y, rx, ry, start, angle, scale float64) {
end := start + angle
clockWise := true
if angle < 0 {
clockWise = false
}
ra := (math.Abs(rx) + math.Abs(ry)) / 2
da := math.Acos(ra/(ra+0.125/scale)) * 2
//normalize
if !clockWise {
da = -da
}
angle = start + da
var curX, curY float64
for {
if (angle < end-da/4) != clockWise {
curX = x + math.Cos(end)*rx
curY = y + math.Sin(end)*ry
break
}
curX = x + math.Cos(angle)*rx
curY = y + math.Sin(angle)*ry
angle += da
t.LineTo(curX, curY)
}
t.LineTo(curX, curY)
}

View file

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

View file

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

View file

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

View file

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

336
curves.go
View file

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

View file

@ -1,23 +0,0 @@
// Copyright 2010 The draw2d Authors. All rights reserved.
// created: 13/12/2010 by Laurent Le Goff
package draw2d
type DemuxConverter struct {
converters []VertexConverter
}
func NewDemuxConverter(converters ...VertexConverter) *DemuxConverter {
return &DemuxConverter{converters}
}
func (dc *DemuxConverter) NextCommand(cmd VertexCommand) {
for _, converter := range dc.converters {
converter.NextCommand(cmd)
}
}
func (dc *DemuxConverter) Vertex(x, y float64) {
for _, converter := range dc.converters {
converter.Vertex(x, y)
}
}

204
draw2d.go
View file

@ -19,34 +19,45 @@
// Installation // Installation
// //
// To install or update the package draw2d on your system, run: // To install or update the package draw2d on your system, run:
// go get -u gopkg.in/llgcode/draw2d.v1 // go get -u github.com/llgcode/draw2d
// //
// Quick Start // Quick Start
// //
// Package draw2d itself provides a graphic context that can draw vector // Package draw2d itself provides a graphic context that can draw vector
// graphics and text on an image canvas. The following Go code // graphics and text on an image canvas. The following Go code
// generates a simple drawing and saves it to an image file: // generates a simple drawing and saves it to an image file:
// // Initialize the graphic context on an RGBA image // package main
// dest := image.NewRGBA(image.Rect(0, 0, 297, 210.0))
// gc := draw2d.NewGraphicContext(dest)
// //
// // Set some properties // import (
// gc.SetFillColor(color.RGBA{0x44, 0xff, 0x44, 0xff}) // "github.com/llgcode/draw2d/draw2dimg"
// gc.SetStrokeColor(color.RGBA{0x44, 0x44, 0x44, 0xff}) // "image"
// gc.SetLineWidth(5) // "image/color"
// )
// //
// // Draw a closed shape // func main() {
// gc.MoveTo(10, 10) // should always be called first for a new path // // Initialize the graphic context on an RGBA image
// gc.LineTo(100, 50) // dest := image.NewRGBA(image.Rect(0, 0, 297, 210.0))
// gc.QuadCurveTo(100, 10, 10, 10) // gc := draw2dimg.NewGraphicContext(dest)
// gc.Close() //
// gc.FillStroke() // // 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)
// }
// //
// // Save to file
// draw2d.SaveToPngFile("hello.png", dest)
// //
// There are more examples here: // There are more examples here:
// https://gopkg.in/llgcode/draw2d.v1/tree/master/samples // https://github.com/llgcode/draw2d/tree/master/samples
// //
// Drawing on pdf documents is provided by the draw2dpdf package. // Drawing on pdf documents is provided by the draw2dpdf package.
// Drawing on opengl is provided by the draw2dgl package. // Drawing on opengl is provided by the draw2dgl package.
@ -83,3 +94,162 @@
// //
// - https://github.com/vdobler/chart: basic charts in Go // - https://github.com/vdobler/chart: basic charts in Go
package draw2d package draw2d
import "image/color"
// FillRule defines the type for fill rules
type FillRule int
const (
// FillRuleEvenOdd determines the "insideness" of a point in the shape
// by drawing a ray from that point to infinity in any direction
// and counting the number of path segments from the given shape that the ray crosses.
// If this number is odd, the point is inside; if even, the point is outside.
FillRuleEvenOdd FillRule = iota
// FillRuleWinding determines the "insideness" of a point in the shape
// by drawing a ray from that point to infinity in any direction
// and then examining the places where a segment of the shape crosses the ray.
// Starting with a count of zero, add one each time a path segment crosses
// the ray from left to right and subtract one each time
// a path segment crosses the ray from right to left. After counting the crossings,
// if the result is zero then the point is outside the path. Otherwise, it is inside.
FillRuleWinding
)
// LineCap is the style of line extremities
type LineCap int
const (
// RoundCap defines a rounded shape at the end of the line
RoundCap LineCap = iota
// ButtCap defines a squared shape exactly at the end of the line
ButtCap
// SquareCap defines a squared shape at the end of the line
SquareCap
)
func (cap LineCap) String() string {
return map[LineCap]string{
RoundCap: "round",
ButtCap: "cap",
SquareCap: "square",
}[cap]
}
// LineJoin is the style of segments joint
type LineJoin int
const (
// BevelJoin represents cut segments joint
BevelJoin LineJoin = iota
// RoundJoin represents rounded segments joint
RoundJoin
// MiterJoin represents peaker segments joint
MiterJoin
)
func (join LineJoin) String() string {
return map[LineJoin]string{
RoundJoin: "round",
BevelJoin: "bevel",
MiterJoin: "miter",
}[join]
}
// StrokeStyle keeps stroke style attributes
// that is used by the Stroke method of a Drawer
type StrokeStyle struct {
// Color defines the color of stroke
Color color.Color
// Line width
Width float64
// Line cap style rounded, butt or square
LineCap LineCap
// Line join style bevel, round or miter
LineJoin LineJoin
// offset of the first dash
DashOffset float64
// array represented dash length pair values are plain dash and impair are space between dash
// if empty display plain line
Dash []float64
}
// SolidFillStyle define style attributes for a solid fill style
type SolidFillStyle struct {
// Color defines the line color
Color color.Color
// FillRule defines the file rule to used
FillRule FillRule
}
// Valign Vertical Alignment of the text
type Valign int
const (
// ValignTop top align text
ValignTop Valign = iota
// ValignCenter centered text
ValignCenter
// ValignBottom bottom aligned text
ValignBottom
// ValignBaseline align text with the baseline of the font
ValignBaseline
)
// Halign Horizontal Alignment of the text
type Halign int
const (
// HalignLeft Horizontally align to left
HalignLeft = iota
// HalignCenter Horizontally align to center
HalignCenter
// HalignRight Horizontally align to right
HalignRight
)
// TextStyle describe text property
type TextStyle struct {
// Color defines the color of text
Color color.Color
// Size font size
Size float64
// The font to use
Font FontData
// Horizontal Alignment of the text
Halign Halign
// Vertical Alignment of the text
Valign Valign
}
// ScalingPolicy is a constant to define how to scale an image
type ScalingPolicy int
const (
// ScalingNone no scaling applied
ScalingNone ScalingPolicy = iota
// ScalingStretch the image is stretched so that its width and height are exactly the given width and height
ScalingStretch
// ScalingWidth the image is scaled so that its width is exactly the given width
ScalingWidth
// ScalingHeight the image is scaled so that its height is exactly the given height
ScalingHeight
// ScalingFit the image is scaled to the largest scale that allow the image to fit within a rectangle width x height
ScalingFit
// ScalingSameArea the image is scaled so that its area is exactly the area of the given rectangle width x height
ScalingSameArea
// ScalingFill the image is scaled to the smallest scale that allow the image to fully cover a rectangle width x height
ScalingFill
)
// ImageScaling style attributes used to display the image
type ImageScaling struct {
// Horizontal Alignment of the image
Halign Halign
// Vertical Alignment of the image
Valign Valign
// Width Height used by scaling policy
Width, Height float64
// ScalingPolicy defines the scaling policy to applied to the image
ScalingPolicy ScalingPolicy
}

7
draw2dbase/README.md Normal file
View file

@ -0,0 +1,7 @@
draw2d/draw2dbase
=================
[![Coverage](http://gocover.io/_badge/github.com/llgcode/draw2d/draw2dbase?0)](http://gocover.io/github.com/llgcode/draw2d/draw2dbase)
[![GoDoc](https://godoc.org/github.com/llgcode/draw2d/draw2dbase?status.svg)](https://godoc.org/github.com/llgcode/draw2d/draw2dbase)
Base package implementation that is used by pdf, svg, img, gl implementations.

172
draw2dbase/curve.go Normal file
View file

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

176
draw2dbase/curve_test.go Normal file
View file

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

View file

@ -1,51 +1,48 @@
// Copyright 2010 The draw2d Authors. All rights reserved. // Copyright 2010 The draw2d Authors. All rights reserved.
// created: 13/12/2010 by Laurent Le Goff // created: 13/12/2010 by Laurent Le Goff
package draw2d package draw2dbase
type DashVertexConverter struct { type DashVertexConverter struct {
command VertexCommand next Flattener
next VertexConverter
x, y, distance float64 x, y, distance float64
dash []float64 dash []float64
currentDash int currentDash int
dashOffset float64 dashOffset float64
} }
func NewDashConverter(dash []float64, dashOffset float64, converter VertexConverter) *DashVertexConverter { func NewDashConverter(dash []float64, dashOffset float64, flattener Flattener) *DashVertexConverter {
var dasher DashVertexConverter var dasher DashVertexConverter
dasher.dash = dash dasher.dash = dash
dasher.currentDash = 0 dasher.currentDash = 0
dasher.dashOffset = dashOffset dasher.dashOffset = dashOffset
dasher.next = converter dasher.next = flattener
return &dasher return &dasher
} }
func (dasher *DashVertexConverter) NextCommand(cmd VertexCommand) { func (dasher *DashVertexConverter) LineTo(x, y float64) {
dasher.command = cmd dasher.lineTo(x, y)
if dasher.command == VertexStopCommand {
dasher.next.NextCommand(VertexStopCommand)
}
} }
func (dasher *DashVertexConverter) Vertex(x, y float64) { func (dasher *DashVertexConverter) MoveTo(x, y float64) {
switch dasher.command { dasher.next.MoveTo(x, y)
case VertexStartCommand:
dasher.start(x, y)
default:
dasher.lineTo(x, y)
}
dasher.command = VertexNoCommand
}
func (dasher *DashVertexConverter) start(x, y float64) {
dasher.next.NextCommand(VertexStartCommand)
dasher.next.Vertex(x, y)
dasher.x, dasher.y = x, y dasher.x, dasher.y = x, y
dasher.distance = dasher.dashOffset dasher.distance = dasher.dashOffset
dasher.currentDash = 0 dasher.currentDash = 0
} }
func (dasher *DashVertexConverter) LineJoin() {
dasher.next.LineJoin()
}
func (dasher *DashVertexConverter) Close() {
dasher.next.Close()
}
func (dasher *DashVertexConverter) End() {
dasher.next.End()
}
func (dasher *DashVertexConverter) lineTo(x, y float64) { func (dasher *DashVertexConverter) lineTo(x, y float64) {
rest := dasher.dash[dasher.currentDash] - dasher.distance rest := dasher.dash[dasher.currentDash] - dasher.distance
for rest < 0 { for rest < 0 {
@ -60,12 +57,11 @@ func (dasher *DashVertexConverter) lineTo(x, y float64) {
ly := dasher.y + k*(y-dasher.y) ly := dasher.y + k*(y-dasher.y)
if dasher.currentDash%2 == 0 { if dasher.currentDash%2 == 0 {
// line // line
dasher.next.Vertex(lx, ly) dasher.next.LineTo(lx, ly)
} else { } else {
// gap // gap
dasher.next.NextCommand(VertexStopCommand) dasher.next.End()
dasher.next.NextCommand(VertexStartCommand) dasher.next.MoveTo(lx, ly)
dasher.next.Vertex(lx, ly)
} }
d = d - rest d = d - rest
dasher.x, dasher.y = lx, ly dasher.x, dasher.y = lx, ly
@ -75,12 +71,11 @@ func (dasher *DashVertexConverter) lineTo(x, y float64) {
dasher.distance = d dasher.distance = d
if dasher.currentDash%2 == 0 { if dasher.currentDash%2 == 0 {
// line // line
dasher.next.Vertex(x, y) dasher.next.LineTo(x, y)
} else { } else {
// gap // gap
dasher.next.NextCommand(VertexStopCommand) dasher.next.End()
dasher.next.NextCommand(VertexStartCommand) dasher.next.MoveTo(x, y)
dasher.next.Vertex(x, y)
} }
if dasher.distance >= dasher.dash[dasher.currentDash] { if dasher.distance >= dasher.dash[dasher.currentDash] {
dasher.distance = dasher.distance - dasher.dash[dasher.currentDash] dasher.distance = dasher.distance - dasher.dash[dasher.currentDash]
@ -88,3 +83,7 @@ func (dasher *DashVertexConverter) lineTo(x, y float64) {
} }
dasher.x, dasher.y = x, y dasher.x, dasher.y = x, y
} }
func distance(x1, y1, x2, y2 float64) float64 {
return vectorDistance(x2-x1, y2-y1)
}

View file

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

127
draw2dbase/flattener.go Normal file
View file

@ -0,0 +1,127 @@
// Copyright 2010 The draw2d Authors. All rights reserved.
// created: 06/12/2010 by Laurent Le Goff
package draw2dbase
import (
"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
}

View file

@ -1,6 +1,7 @@
// Copyright 2011 The draw2d Authors. All rights reserved. // Copyright 2011 The draw2d Authors. All rights reserved.
// created: 27/05/2011 by Laurent Le Goff // created: 27/05/2011 by Laurent Le Goff
package raster
package draw2dbase
import ( import (
"image/color" "image/color"
@ -14,12 +15,14 @@ func abs(i int) int {
return i return i
} }
// PolylineBresenham draws a polyline to an image
func PolylineBresenham(img draw.Image, c color.Color, s ...float64) { func PolylineBresenham(img draw.Image, c color.Color, s ...float64) {
for i := 2; i < len(s); i += 2 { for i := 2; i < len(s); i += 2 {
Bresenham(img, c, int(s[i-2]+0.5), int(s[i-1]+0.5), int(s[i]+0.5), int(s[i+1]+0.5)) Bresenham(img, c, int(s[i-2]+0.5), int(s[i-1]+0.5), int(s[i]+0.5), int(s[i+1]+0.5))
} }
} }
// Bresenham draws a line between (x0, y0) and (x1, y1)
func Bresenham(img draw.Image, color color.Color, x0, y0, x1, y1 int) { func Bresenham(img draw.Image, color color.Color, x0, y0, x1, y1 int) {
dx := abs(x1 - x0) dx := abs(x1 - x0)
dy := abs(y1 - y0) dy := abs(y1 - y0)

View file

@ -1,39 +1,50 @@
// Copyright 2010 The draw2d Authors. All rights reserved. // Copyright 2010 The draw2d Authors. All rights reserved.
// created: 21/11/2010 by Laurent Le Goff // created: 21/11/2010 by Laurent Le Goff
package draw2d package draw2dbase
import ( import (
"fmt"
"image" "image"
"image/color" "image/color"
"code.google.com/p/freetype-go/freetype/truetype" "git.fromouter.space/crunchy-rocks/draw2d"
"git.fromouter.space/crunchy-rocks/freetype/truetype"
) )
var DefaultFontData = draw2d.FontData{Name: "luxi", Family: draw2d.FontFamilySans, Style: draw2d.FontStyleNormal}
type StackGraphicContext struct { type StackGraphicContext struct {
Current *ContextStack Current *ContextStack
} }
type ContextStack struct { type ContextStack struct {
Tr MatrixTransform Tr draw2d.Matrix
Path *PathStorage Path *draw2d.Path
LineWidth float64 LineWidth float64
Dash []float64 Dash []float64
DashOffset float64 DashOffset float64
StrokeColor color.Color StrokeColor color.Color
FillColor color.Color FillColor color.Color
FillRule FillRule FillRule draw2d.FillRule
Cap Cap Cap draw2d.LineCap
Join Join Join draw2d.LineJoin
FontSize float64 FontSize float64
FontData FontData FontData draw2d.FontData
Font *truetype.Font Font *truetype.Font
// fontSize and dpi are used to calculate scale. scale is the number of // fontSize and dpi are used to calculate scale. scale is the number of
// 26.6 fixed point units in 1 em. // 26.6 fixed point units in 1 em.
Scale float64 Scale float64
previous *ContextStack Previous *ContextStack
}
// GetFontName gets the current FontData with fontSize as a string
func (cs *ContextStack) GetFontName() string {
fontData := cs.FontData
return fmt.Sprintf("%s:%d:%d:%9.2f", fontData.Name, fontData.Family, fontData.Style, cs.FontSize)
} }
/** /**
@ -42,41 +53,41 @@ type ContextStack struct {
func NewStackGraphicContext() *StackGraphicContext { func NewStackGraphicContext() *StackGraphicContext {
gc := &StackGraphicContext{} gc := &StackGraphicContext{}
gc.Current = new(ContextStack) gc.Current = new(ContextStack)
gc.Current.Tr = NewIdentityMatrix() gc.Current.Tr = draw2d.NewIdentityMatrix()
gc.Current.Path = NewPathStorage() gc.Current.Path = new(draw2d.Path)
gc.Current.LineWidth = 1.0 gc.Current.LineWidth = 1.0
gc.Current.StrokeColor = image.Black gc.Current.StrokeColor = image.Black
gc.Current.FillColor = image.White gc.Current.FillColor = image.White
gc.Current.Cap = RoundCap gc.Current.Cap = draw2d.RoundCap
gc.Current.FillRule = FillRuleEvenOdd gc.Current.FillRule = draw2d.FillRuleEvenOdd
gc.Current.Join = RoundJoin gc.Current.Join = draw2d.RoundJoin
gc.Current.FontSize = 10 gc.Current.FontSize = 10
gc.Current.FontData = defaultFontData gc.Current.FontData = DefaultFontData
return gc return gc
} }
func (gc *StackGraphicContext) GetMatrixTransform() MatrixTransform { func (gc *StackGraphicContext) GetMatrixTransform() draw2d.Matrix {
return gc.Current.Tr return gc.Current.Tr
} }
func (gc *StackGraphicContext) SetMatrixTransform(Tr MatrixTransform) { func (gc *StackGraphicContext) SetMatrixTransform(Tr draw2d.Matrix) {
gc.Current.Tr = Tr gc.Current.Tr = Tr
} }
func (gc *StackGraphicContext) ComposeMatrixTransform(Tr MatrixTransform) { func (gc *StackGraphicContext) ComposeMatrixTransform(Tr draw2d.Matrix) {
gc.Current.Tr = Tr.Multiply(gc.Current.Tr) gc.Current.Tr.Compose(Tr)
} }
func (gc *StackGraphicContext) Rotate(angle float64) { func (gc *StackGraphicContext) Rotate(angle float64) {
gc.Current.Tr = NewRotationMatrix(angle).Multiply(gc.Current.Tr) gc.Current.Tr.Rotate(angle)
} }
func (gc *StackGraphicContext) Translate(tx, ty float64) { func (gc *StackGraphicContext) Translate(tx, ty float64) {
gc.Current.Tr = NewTranslationMatrix(tx, ty).Multiply(gc.Current.Tr) gc.Current.Tr.Translate(tx, ty)
} }
func (gc *StackGraphicContext) Scale(sx, sy float64) { func (gc *StackGraphicContext) Scale(sx, sy float64) {
gc.Current.Tr = NewScaleMatrix(sx, sy).Multiply(gc.Current.Tr) gc.Current.Tr.Scale(sx, sy)
} }
func (gc *StackGraphicContext) SetStrokeColor(c color.Color) { func (gc *StackGraphicContext) SetStrokeColor(c color.Color) {
@ -87,45 +98,49 @@ func (gc *StackGraphicContext) SetFillColor(c color.Color) {
gc.Current.FillColor = c gc.Current.FillColor = c
} }
func (gc *StackGraphicContext) SetFillRule(f FillRule) { func (gc *StackGraphicContext) SetFillRule(f draw2d.FillRule) {
gc.Current.FillRule = f gc.Current.FillRule = f
} }
func (gc *StackGraphicContext) SetLineWidth(LineWidth float64) { func (gc *StackGraphicContext) SetLineWidth(lineWidth float64) {
gc.Current.LineWidth = LineWidth gc.Current.LineWidth = lineWidth
} }
func (gc *StackGraphicContext) SetLineCap(Cap Cap) { func (gc *StackGraphicContext) SetLineCap(cap draw2d.LineCap) {
gc.Current.Cap = Cap gc.Current.Cap = cap
} }
func (gc *StackGraphicContext) SetLineJoin(Join Join) { func (gc *StackGraphicContext) SetLineJoin(join draw2d.LineJoin) {
gc.Current.Join = Join gc.Current.Join = join
} }
func (gc *StackGraphicContext) SetLineDash(Dash []float64, DashOffset float64) { func (gc *StackGraphicContext) SetLineDash(dash []float64, dashOffset float64) {
gc.Current.Dash = Dash gc.Current.Dash = dash
gc.Current.DashOffset = DashOffset gc.Current.DashOffset = dashOffset
} }
func (gc *StackGraphicContext) SetFontSize(FontSize float64) { func (gc *StackGraphicContext) SetFontSize(fontSize float64) {
gc.Current.FontSize = FontSize gc.Current.FontSize = fontSize
} }
func (gc *StackGraphicContext) GetFontSize() float64 { func (gc *StackGraphicContext) GetFontSize() float64 {
return gc.Current.FontSize return gc.Current.FontSize
} }
func (gc *StackGraphicContext) SetFontData(FontData FontData) { func (gc *StackGraphicContext) SetFontData(fontData draw2d.FontData) {
gc.Current.FontData = FontData gc.Current.FontData = fontData
} }
func (gc *StackGraphicContext) GetFontData() FontData { func (gc *StackGraphicContext) GetFontData() draw2d.FontData {
return gc.Current.FontData return gc.Current.FontData
} }
func (gc *StackGraphicContext) BeginPath() { func (gc *StackGraphicContext) BeginPath() {
gc.Current.Path = NewPathStorage() gc.Current.Path.Clear()
}
func (gc *StackGraphicContext) GetPath() draw2d.Path {
return *gc.Current.Path.Copy()
} }
func (gc *StackGraphicContext) IsEmpty() bool { func (gc *StackGraphicContext) IsEmpty() bool {
@ -140,42 +155,22 @@ func (gc *StackGraphicContext) MoveTo(x, y float64) {
gc.Current.Path.MoveTo(x, y) gc.Current.Path.MoveTo(x, y)
} }
func (gc *StackGraphicContext) RMoveTo(dx, dy float64) {
gc.Current.Path.RMoveTo(dx, dy)
}
func (gc *StackGraphicContext) LineTo(x, y float64) { func (gc *StackGraphicContext) LineTo(x, y float64) {
gc.Current.Path.LineTo(x, y) gc.Current.Path.LineTo(x, y)
} }
func (gc *StackGraphicContext) RLineTo(dx, dy float64) {
gc.Current.Path.RLineTo(dx, dy)
}
func (gc *StackGraphicContext) QuadCurveTo(cx, cy, x, y float64) { func (gc *StackGraphicContext) QuadCurveTo(cx, cy, x, y float64) {
gc.Current.Path.QuadCurveTo(cx, cy, x, y) gc.Current.Path.QuadCurveTo(cx, cy, x, y)
} }
func (gc *StackGraphicContext) RQuadCurveTo(dcx, dcy, dx, dy float64) {
gc.Current.Path.RQuadCurveTo(dcx, dcy, dx, dy)
}
func (gc *StackGraphicContext) CubicCurveTo(cx1, cy1, cx2, cy2, x, y float64) { func (gc *StackGraphicContext) CubicCurveTo(cx1, cy1, cx2, cy2, x, y float64) {
gc.Current.Path.CubicCurveTo(cx1, cy1, cx2, cy2, x, y) gc.Current.Path.CubicCurveTo(cx1, cy1, cx2, cy2, x, y)
} }
func (gc *StackGraphicContext) RCubicCurveTo(dcx1, dcy1, dcx2, dcy2, dx, dy float64) {
gc.Current.Path.RCubicCurveTo(dcx1, dcy1, dcx2, dcy2, dx, dy)
}
func (gc *StackGraphicContext) ArcTo(cx, cy, rx, ry, startAngle, angle float64) { func (gc *StackGraphicContext) ArcTo(cx, cy, rx, ry, startAngle, angle float64) {
gc.Current.Path.ArcTo(cx, cy, rx, ry, startAngle, angle) gc.Current.Path.ArcTo(cx, cy, rx, ry, startAngle, angle)
} }
func (gc *StackGraphicContext) RArcTo(dcx, dcy, rx, ry, startAngle, angle float64) {
gc.Current.Path.RArcTo(dcx, dcy, rx, ry, startAngle, angle)
}
func (gc *StackGraphicContext) Close() { func (gc *StackGraphicContext) Close() {
gc.Current.Path.Close() gc.Current.Path.Close()
} }
@ -196,14 +191,18 @@ func (gc *StackGraphicContext) Save() {
context.Font = gc.Current.Font context.Font = gc.Current.Font
context.Scale = gc.Current.Scale context.Scale = gc.Current.Scale
copy(context.Tr[:], gc.Current.Tr[:]) copy(context.Tr[:], gc.Current.Tr[:])
context.previous = gc.Current context.Previous = gc.Current
gc.Current = context gc.Current = context
} }
func (gc *StackGraphicContext) Restore() { func (gc *StackGraphicContext) Restore() {
if gc.Current.previous != nil { if gc.Current.Previous != nil {
oldContext := gc.Current oldContext := gc.Current
gc.Current = gc.Current.previous gc.Current = gc.Current.Previous
oldContext.previous = nil oldContext.Previous = nil
} }
} }
func (gc *StackGraphicContext) GetFontName() string {
return gc.Current.GetFontName()
}

90
draw2dbase/stroker.go Normal file
View file

@ -0,0 +1,90 @@
// Copyright 2010 The draw2d Authors. All rights reserved.
// created: 13/12/2010 by Laurent Le Goff
package draw2dbase
import (
"math"
"git.fromouter.space/crunchy-rocks/draw2d"
)
type LineStroker struct {
Flattener Flattener
HalfLineWidth float64
Cap draw2d.LineCap
Join draw2d.LineJoin
vertices []float64
rewind []float64
x, y, nx, ny float64
}
func NewLineStroker(c draw2d.LineCap, j draw2d.LineJoin, flattener Flattener) *LineStroker {
l := new(LineStroker)
l.Flattener = flattener
l.HalfLineWidth = 0.5
l.Cap = c
l.Join = j
return l
}
func (l *LineStroker) MoveTo(x, y float64) {
l.x, l.y = x, y
}
func (l *LineStroker) LineTo(x, y float64) {
l.line(l.x, l.y, x, y)
}
func (l *LineStroker) LineJoin() {
}
func (l *LineStroker) line(x1, y1, x2, y2 float64) {
dx := (x2 - x1)
dy := (y2 - y1)
d := vectorDistance(dx, dy)
if d != 0 {
nx := dy * l.HalfLineWidth / d
ny := -(dx * l.HalfLineWidth / d)
l.appendVertex(x1+nx, y1+ny, x2+nx, y2+ny, x1-nx, y1-ny, x2-nx, y2-ny)
l.x, l.y, l.nx, l.ny = x2, y2, nx, ny
}
}
func (l *LineStroker) Close() {
if len(l.vertices) > 1 {
l.appendVertex(l.vertices[0], l.vertices[1], l.rewind[0], l.rewind[1])
}
}
func (l *LineStroker) End() {
if len(l.vertices) > 1 {
l.Flattener.MoveTo(l.vertices[0], l.vertices[1])
for i, j := 2, 3; j < len(l.vertices); i, j = i+2, j+2 {
l.Flattener.LineTo(l.vertices[i], l.vertices[j])
}
}
for i, j := len(l.rewind)-2, len(l.rewind)-1; j > 0; i, j = i-2, j-2 {
l.Flattener.LineTo(l.rewind[i], l.rewind[j])
}
if len(l.vertices) > 1 {
l.Flattener.LineTo(l.vertices[0], l.vertices[1])
}
l.Flattener.End()
// reinit vertices
l.vertices = l.vertices[0:0]
l.rewind = l.rewind[0:0]
l.x, l.y, l.nx, l.ny = 0, 0, 0, 0
}
func (l *LineStroker) appendVertex(vertices ...float64) {
s := len(vertices) / 2
l.vertices = append(l.vertices, vertices[:s]...)
l.rewind = append(l.rewind, vertices[s:]...)
}
func vectorDistance(dx, dy float64) float64 {
return float64(math.Sqrt(dx*dx + dy*dy))
}

82
draw2dbase/text.go Normal file
View file

@ -0,0 +1,82 @@
package draw2dbase
import "git.fromouter.space/crunchy-rocks/draw2d"
// GlyphCache manage a cache of glyphs
type GlyphCache interface {
// Fetch fetches a glyph from the cache, storing with Render first if it doesn't already exist
Fetch(gc draw2d.GraphicContext, fontName string, chr rune) *Glyph
}
// GlyphCacheImp manage a map of glyphs without sync mecanism, not thread safe
type GlyphCacheImp struct {
glyphs map[string]map[rune]*Glyph
}
// NewGlyphCache initializes a GlyphCache
func NewGlyphCache() *GlyphCacheImp {
glyphs := make(map[string]map[rune]*Glyph)
return &GlyphCacheImp{
glyphs: glyphs,
}
}
// Fetch fetches a glyph from the cache, calling renderGlyph first if it doesn't already exist
func (glyphCache *GlyphCacheImp) Fetch(gc draw2d.GraphicContext, fontName string, chr rune) *Glyph {
if glyphCache.glyphs[fontName] == nil {
glyphCache.glyphs[fontName] = make(map[rune]*Glyph, 60)
}
if glyphCache.glyphs[fontName][chr] == nil {
glyphCache.glyphs[fontName][chr] = renderGlyph(gc, fontName, chr)
}
return glyphCache.glyphs[fontName][chr].Copy()
}
// renderGlyph renders a glyph then caches and returns it
func renderGlyph(gc draw2d.GraphicContext, fontName string, chr rune) *Glyph {
gc.Save()
defer gc.Restore()
gc.BeginPath()
width := gc.CreateStringPath(string(chr), 0, 0)
path := gc.GetPath()
return &Glyph{
Path: &path,
Width: width,
}
}
// Glyph represents a rune which has been converted to a Path and width
type Glyph struct {
// path represents a glyph, it is always at (0, 0)
Path *draw2d.Path
// Width of the glyph
Width float64
}
// Copy Returns a copy of a Glyph
func (g *Glyph) Copy() *Glyph {
return &Glyph{
Path: g.Path.Copy(),
Width: g.Width,
}
}
// Fill copies a glyph from the cache, and fills it
func (g *Glyph) Fill(gc draw2d.GraphicContext, x, y float64) float64 {
gc.Save()
gc.BeginPath()
gc.Translate(x, y)
gc.Fill(g.Path)
gc.Restore()
return g.Width
}
// Stroke fetches a glyph from the cache, and strokes it
func (g *Glyph) Stroke(gc draw2d.GraphicContext, x, y float64) float64 {
gc.Save()
gc.BeginPath()
gc.Translate(x, y)
gc.Stroke(g.Path)
gc.Restore()
return g.Width
}

View file

@ -4,11 +4,19 @@ import (
"image" "image"
"image/color" "image/color"
"image/draw" "image/draw"
"log"
"math"
"runtime" "runtime"
"code.google.com/p/freetype-go/freetype/raster" "git.fromouter.space/crunchy-rocks/draw2d"
"git.fromouter.space/crunchy-rocks/draw2d/draw2dbase"
"git.fromouter.space/crunchy-rocks/draw2d/draw2dimg"
"github.com/go-gl/gl/v2.1/gl" "github.com/go-gl/gl/v2.1/gl"
"gopkg.in/llgcode/draw2d.v1" "github.com/golang/freetype/raster"
"github.com/golang/freetype/truetype"
"golang.org/x/image/font"
"golang.org/x/image/math/fixed"
) )
func init() { func init() {
@ -50,8 +58,8 @@ func (p *Painter) Paint(ss []raster.Span, done bool) {
vertices []int32 vertices []int32
) )
for _, s := range ss { for _, s := range ss {
ma := s.A >> 16 a := uint8((s.Alpha * p.ca / M16) >> 8)
a := uint8((ma * p.ca / M16) >> 8)
colors = p.colors[ci:] colors = p.colors[ci:]
colors[0] = p.cr colors[0] = p.cr
colors[1] = p.cg colors[1] = p.cg
@ -112,67 +120,217 @@ func NewPainter() *Painter {
} }
type GraphicContext struct { type GraphicContext struct {
*draw2d.StackGraphicContext *draw2dbase.StackGraphicContext
painter *Painter painter *Painter
fillRasterizer *raster.Rasterizer fillRasterizer *raster.Rasterizer
strokeRasterizer *raster.Rasterizer strokeRasterizer *raster.Rasterizer
FontCache draw2d.FontCache
glyphCache draw2dbase.GlyphCache
glyphBuf *truetype.GlyphBuf
DPI int
} }
// NewGraphicContext creates a new Graphic context from an image. // NewGraphicContext creates a new Graphic context from an image.
func NewGraphicContext(width, height int) *GraphicContext { func NewGraphicContext(width, height int) *GraphicContext {
gc := &GraphicContext{ gc := &GraphicContext{
draw2d.NewStackGraphicContext(), draw2dbase.NewStackGraphicContext(),
NewPainter(), NewPainter(),
raster.NewRasterizer(width, height), raster.NewRasterizer(width, height),
raster.NewRasterizer(width, height), raster.NewRasterizer(width, height),
draw2d.GetGlobalFontCache(),
draw2dbase.NewGlyphCache(),
&truetype.GlyphBuf{},
92,
} }
return gc return gc
} }
func (gc *GraphicContext) loadCurrentFont() (*truetype.Font, error) {
font, err := gc.FontCache.Load(gc.Current.FontData)
if err != nil {
font, err = gc.FontCache.Load(draw2dbase.DefaultFontData)
}
if font != nil {
gc.SetFont(font)
gc.SetFontSize(gc.Current.FontSize)
}
return font, err
}
func (gc *GraphicContext) drawGlyph(glyph truetype.Index, dx, dy float64) error {
if err := gc.glyphBuf.Load(gc.Current.Font, fixed.Int26_6(gc.Current.Scale), glyph, font.HintingNone); err != nil {
return err
}
e0 := 0
for _, e1 := range gc.glyphBuf.Ends {
DrawContour(gc, gc.glyphBuf.Points[e0:e1], dx, dy)
e0 = e1
}
return nil
}
// CreateStringPath creates a path from the string s at x, y, and returns the string width.
// The text is placed so that the left edge of the em square of the first character of s
// and the baseline intersect at x, y. The majority of the affected pixels will be
// above and to the right of the point, but some may be below or to the left.
// For example, drawing a string that starts with a 'J' in an italic font may
// affect pixels below and left of the point.
func (gc *GraphicContext) CreateStringPath(s string, x, y float64) float64 { func (gc *GraphicContext) CreateStringPath(s string, x, y float64) float64 {
panic("not implemented") f, err := gc.loadCurrentFont()
if err != nil {
log.Println(err)
return 0.0
}
startx := x
prev, hasPrev := truetype.Index(0), false
for _, rune := range s {
index := f.Index(rune)
if hasPrev {
x += fUnitsToFloat64(f.Kern(fixed.Int26_6(gc.Current.Scale), prev, index))
}
err := gc.drawGlyph(index, x, y)
if err != nil {
log.Println(err)
return startx - x
}
x += fUnitsToFloat64(f.HMetric(fixed.Int26_6(gc.Current.Scale), index).AdvanceWidth)
prev, hasPrev = index, true
}
return x - startx
} }
func (gc *GraphicContext) FillStringAt(text string, x, y float64) (cursor float64) { // FillString draws the text at point (0, 0)
panic("not implemented") func (gc *GraphicContext) FillString(text string) (width float64) {
return gc.FillStringAt(text, 0, 0)
} }
// FillStringAt draws the text at the specified point (x, y)
func (gc *GraphicContext) FillStringAt(text string, x, y float64) (width float64) {
f, err := gc.loadCurrentFont()
if err != nil {
log.Println(err)
return 0.0
}
startx := x
prev, hasPrev := truetype.Index(0), false
fontName := gc.GetFontName()
for _, r := range text {
index := f.Index(r)
if hasPrev {
x += fUnitsToFloat64(f.Kern(fixed.Int26_6(gc.Current.Scale), prev, index))
}
glyph := gc.glyphCache.Fetch(gc, fontName, r)
x += glyph.Fill(gc, x, y)
prev, hasPrev = index, true
}
return x - startx
}
// GetStringBounds returns the approximate pixel bounds of the string s at x, y.
// The the left edge of the em square of the first character of s
// and the baseline intersect at 0, 0 in the returned coordinates.
// Therefore the top and left coordinates may well be negative.
func (gc *GraphicContext) GetStringBounds(s string) (left, top, right, bottom float64) { func (gc *GraphicContext) GetStringBounds(s string) (left, top, right, bottom float64) {
panic("not implemented") f, err := gc.loadCurrentFont()
if err != nil {
log.Println(err)
return 0, 0, 0, 0
}
top, left, bottom, right = 10e6, 10e6, -10e6, -10e6
cursor := 0.0
prev, hasPrev := truetype.Index(0), false
for _, rune := range s {
index := f.Index(rune)
if hasPrev {
cursor += fUnitsToFloat64(f.Kern(fixed.Int26_6(gc.Current.Scale), prev, index))
}
if err := gc.glyphBuf.Load(gc.Current.Font, fixed.Int26_6(gc.Current.Scale), index, font.HintingNone); err != nil {
log.Println(err)
return 0, 0, 0, 0
}
e0 := 0
for _, e1 := range gc.glyphBuf.Ends {
ps := gc.glyphBuf.Points[e0:e1]
for _, p := range ps {
x, y := pointToF64Point(p)
top = math.Min(top, y)
bottom = math.Max(bottom, y)
left = math.Min(left, x+cursor)
right = math.Max(right, x+cursor)
}
}
cursor += fUnitsToFloat64(f.HMetric(fixed.Int26_6(gc.Current.Scale), index).AdvanceWidth)
prev, hasPrev = index, true
}
return left, top, right, bottom
} }
func (gc *GraphicContext) StrokeString(text string) (cursor float64) { // StrokeString draws the contour of the text at point (0, 0)
func (gc *GraphicContext) StrokeString(text string) (width float64) {
return gc.StrokeStringAt(text, 0, 0) return gc.StrokeStringAt(text, 0, 0)
} }
func (gc *GraphicContext) StrokeStringAt(text string, x, y float64) (cursor float64) { // StrokeStringAt draws the contour of the text at point (x, y)
width := gc.CreateStringPath(text, x, y) func (gc *GraphicContext) StrokeStringAt(text string, x, y float64) (width float64) {
gc.Stroke() f, err := gc.loadCurrentFont()
return width if err != nil {
log.Println(err)
return 0.0
}
startx := x
prev, hasPrev := truetype.Index(0), false
fontName := gc.GetFontName()
for _, r := range text {
index := f.Index(r)
if hasPrev {
x += fUnitsToFloat64(f.Kern(fixed.Int26_6(gc.Current.Scale), prev, index))
}
glyph := gc.glyphCache.Fetch(gc, fontName, r)
x += glyph.Stroke(gc, x, y)
prev, hasPrev = index, true
}
return x - startx
}
// recalc recalculates scale and bounds values from the font size, screen
// resolution and font metrics, and invalidates the glyph cache.
func (gc *GraphicContext) recalc() {
gc.Current.Scale = gc.Current.FontSize * float64(gc.DPI) * (64.0 / 72.0)
} }
func (gc *GraphicContext) SetDPI(dpi int) { func (gc *GraphicContext) SetDPI(dpi int) {
gc.DPI = dpi
gc.recalc()
}
// SetFont sets the font used to draw text.
func (gc *GraphicContext) SetFont(font *truetype.Font) {
gc.Current.Font = font
}
// SetFontSize sets the font size in points (as in ``a 12 point font'').
func (gc *GraphicContext) SetFontSize(fontSize float64) {
gc.Current.FontSize = fontSize
gc.recalc()
} }
func (gc *GraphicContext) GetDPI() int { func (gc *GraphicContext) GetDPI() int {
return -1 return gc.DPI
} }
//TODO
func (gc *GraphicContext) Clear() { func (gc *GraphicContext) Clear() {
panic("not implemented")
} }
//TODO
func (gc *GraphicContext) ClearRect(x1, y1, x2, y2 int) { func (gc *GraphicContext) ClearRect(x1, y1, x2, y2 int) {
panic("not implemented")
} }
//TODO
func (gc *GraphicContext) DrawImage(img image.Image) { func (gc *GraphicContext) DrawImage(img image.Image) {
panic("not implemented")
}
func (gc *GraphicContext) FillString(text string) (cursor float64) {
return 0
} }
func (gc *GraphicContext) paint(rasterizer *raster.Rasterizer, color color.Color) { func (gc *GraphicContext) paint(rasterizer *raster.Rasterizer, color color.Color) {
@ -180,56 +338,76 @@ func (gc *GraphicContext) paint(rasterizer *raster.Rasterizer, color color.Color
rasterizer.Rasterize(gc.painter) rasterizer.Rasterize(gc.painter)
rasterizer.Clear() rasterizer.Clear()
gc.painter.Flush() gc.painter.Flush()
gc.Current.Path.Clear()
} }
func (gc *GraphicContext) Stroke(paths ...*draw2d.PathStorage) { func (gc *GraphicContext) Stroke(paths ...*draw2d.Path) {
paths = append(paths, gc.Current.Path) paths = append(paths, gc.Current.Path)
gc.strokeRasterizer.UseNonZeroWinding = true gc.strokeRasterizer.UseNonZeroWinding = true
stroker := draw2d.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2d.NewVertexMatrixTransform(gc.Current.Tr, draw2d.NewVertexAdder(gc.strokeRasterizer))) stroker := draw2dbase.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2dbase.Transformer{Tr: gc.Current.Tr, Flattener: draw2dimg.FtLineBuilder{Adder: gc.strokeRasterizer}})
stroker.HalfLineWidth = gc.Current.LineWidth / 2 stroker.HalfLineWidth = gc.Current.LineWidth / 2
var pathConverter *draw2d.PathConverter
var liner draw2dbase.Flattener
if gc.Current.Dash != nil && len(gc.Current.Dash) > 0 { if gc.Current.Dash != nil && len(gc.Current.Dash) > 0 {
dasher := draw2d.NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker) liner = draw2dbase.NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker)
pathConverter = draw2d.NewPathConverter(dasher)
} else { } else {
pathConverter = draw2d.NewPathConverter(stroker) liner = stroker
}
for _, p := range paths {
draw2dbase.Flatten(p, liner, gc.Current.Tr.GetScale())
} }
pathConverter.ApproximationScale = gc.Current.Tr.GetScale() // From agg code
pathConverter.Convert(paths...)
gc.paint(gc.strokeRasterizer, gc.Current.StrokeColor) gc.paint(gc.strokeRasterizer, gc.Current.StrokeColor)
gc.Current.Path = draw2d.NewPathStorage()
} }
func (gc *GraphicContext) Fill(paths ...*draw2d.PathStorage) { func (gc *GraphicContext) Fill(paths ...*draw2d.Path) {
paths = append(paths, gc.Current.Path) paths = append(paths, gc.Current.Path)
gc.fillRasterizer.UseNonZeroWinding = gc.Current.FillRule.UseNonZeroWinding() gc.fillRasterizer.UseNonZeroWinding = useNonZeroWinding(gc.Current.FillRule)
pathConverter := draw2d.NewPathConverter(draw2d.NewVertexMatrixTransform(gc.Current.Tr, draw2d.NewVertexAdder(gc.fillRasterizer))) /**** first method ****/
pathConverter.ApproximationScale = gc.Current.Tr.GetScale() // From agg code flattener := draw2dbase.Transformer{Tr: gc.Current.Tr, Flattener: draw2dimg.FtLineBuilder{Adder: gc.fillRasterizer}}
pathConverter.Convert(paths...) for _, p := range paths {
draw2dbase.Flatten(p, flattener, gc.Current.Tr.GetScale())
}
gc.paint(gc.fillRasterizer, gc.Current.FillColor) gc.paint(gc.fillRasterizer, gc.Current.FillColor)
gc.Current.Path = draw2d.NewPathStorage()
} }
func (gc *GraphicContext) FillStroke(paths ...*draw2d.PathStorage) { func (gc *GraphicContext) FillStroke(paths ...*draw2d.Path) {
gc.fillRasterizer.UseNonZeroWinding = gc.Current.FillRule.UseNonZeroWinding() paths = append(paths, gc.Current.Path)
gc.fillRasterizer.UseNonZeroWinding = useNonZeroWinding(gc.Current.FillRule)
gc.strokeRasterizer.UseNonZeroWinding = true gc.strokeRasterizer.UseNonZeroWinding = true
filler := draw2d.NewVertexMatrixTransform(gc.Current.Tr, draw2d.NewVertexAdder(gc.fillRasterizer)) flattener := draw2dbase.Transformer{Tr: gc.Current.Tr, Flattener: draw2dimg.FtLineBuilder{Adder: gc.fillRasterizer}}
stroker := draw2d.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2d.NewVertexMatrixTransform(gc.Current.Tr, draw2d.NewVertexAdder(gc.strokeRasterizer))) stroker := draw2dbase.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2dbase.Transformer{Tr: gc.Current.Tr, Flattener: draw2dimg.FtLineBuilder{Adder: gc.strokeRasterizer}})
stroker.HalfLineWidth = gc.Current.LineWidth / 2 stroker.HalfLineWidth = gc.Current.LineWidth / 2
demux := draw2d.NewDemuxConverter(filler, stroker) var liner draw2dbase.Flattener
paths = append(paths, gc.Current.Path) if gc.Current.Dash != nil && len(gc.Current.Dash) > 0 {
pathConverter := draw2d.NewPathConverter(demux) liner = draw2dbase.NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker)
pathConverter.ApproximationScale = gc.Current.Tr.GetScale() // From agg code } else {
pathConverter.Convert(paths...) liner = stroker
}
demux := draw2dbase.DemuxFlattener{Flatteners: []draw2dbase.Flattener{flattener, liner}}
for _, p := range paths {
draw2dbase.Flatten(p, demux, gc.Current.Tr.GetScale())
}
// Fill
gc.paint(gc.fillRasterizer, gc.Current.FillColor) gc.paint(gc.fillRasterizer, gc.Current.FillColor)
// Stroke
gc.paint(gc.strokeRasterizer, gc.Current.StrokeColor) gc.paint(gc.strokeRasterizer, gc.Current.StrokeColor)
gc.Current.Path = draw2d.NewPathStorage() }
func useNonZeroWinding(f draw2d.FillRule) bool {
switch f {
case draw2d.FillRuleEvenOdd:
return false
case draw2d.FillRuleWinding:
return true
}
return false
} }

82
draw2dgl/text.go Normal file
View file

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

8
draw2dimg/README.md Normal file
View file

@ -0,0 +1,8 @@
draw2d/draw2dimg
=================
[![Coverage](http://gocover.io/_badge/github.com/llgcode/draw2d/draw2dimg?0)](http://gocover.io/github.com/llgcode/draw2d/draw2dimg)
[![GoDoc](https://godoc.org/github.com/llgcode/draw2d/draw2dimg?status.svg)](https://godoc.org/github.com/llgcode/draw2d/draw2dimg)
draw2d implementation that generates raster images using https://github.com/golang/freetype package.

View file

@ -0,0 +1,190 @@
package draw2dimg
import (
"fmt"
"image"
"image/color"
"testing"
"git.fromouter.space/crunchy-rocks/draw2d"
"git.fromouter.space/crunchy-rocks/draw2d/draw2dkit"
"git.fromouter.space/crunchy-rocks/freetype/truetype"
"golang.org/x/image/font/gofont/goregular"
)
// font generated from icomoon.io and converted to go byte slice
// contains only two glyphs
// \u2716 - which should look like a cross
// \u25cb - which should look like an empty circle
var icoTTF = []byte{
0, 1, 0, 0, 0, 12, 0, 128, 0, 3, 0, 64, 71, 83, 85, 66, 219, 7, 221, 185,
0, 0, 0, 204, 0, 0, 0, 188, 79, 83, 47, 50, 175, 17, 51, 150, 0, 0, 1, 136,
0, 0, 0, 96, 99, 109, 97, 112, 37, 204, 43, 67, 0, 0, 1, 232, 0, 0, 0, 148,
103, 97, 115, 112, 0, 0, 0, 16, 0, 0, 2, 124, 0, 0, 0, 8, 103, 108, 121, 102,
163, 112, 233, 32, 0, 0, 2, 132, 0, 0, 3, 64, 104, 101, 97, 100, 15, 49, 194, 135,
0, 0, 5, 196, 0, 0, 0, 54, 104, 104, 101, 97, 7, 194, 3, 217, 0, 0, 5, 252,
0, 0, 0, 36, 104, 109, 116, 120, 14, 0, 0, 2, 0, 0, 6, 32, 0, 0, 0, 96,
108, 111, 99, 97, 6, 168, 5, 226, 0, 0, 6, 128, 0, 0, 0, 50, 109, 97, 120, 112,
0, 27, 0, 86, 0, 0, 6, 180, 0, 0, 0, 32, 110, 97, 109, 101, 108, 36, 213, 69,
0, 0, 6, 212, 0, 0, 1, 170, 112, 111, 115, 116, 0, 3, 0, 0, 0, 0, 8, 128,
0, 0, 0, 32, 0, 1, 0, 0, 0, 10, 0, 30, 0, 44, 0, 1, 108, 97, 116, 110,
0, 8, 0, 4, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 108, 105, 103, 97,
0, 8, 0, 0, 0, 1, 0, 0, 0, 1, 0, 4, 0, 4, 0, 0, 0, 1, 0, 10,
0, 0, 0, 1, 0, 12, 0, 3, 0, 22, 0, 54, 0, 120, 0, 1, 0, 3, 0, 8,
0, 17, 0, 23, 0, 2, 0, 6, 0, 18, 0, 22, 0, 5, 0, 17, 0, 16, 0, 18,
0, 18, 0, 22, 0, 6, 0, 6, 0, 15, 0, 8, 0, 10, 0, 14, 0, 2, 0, 6,
0, 38, 0, 21, 0, 15, 0, 6, 0, 9, 0, 12, 0, 16, 0, 4, 0, 20, 0, 15,
0, 8, 0, 11, 0, 10, 0, 8, 0, 13, 0, 10, 0, 9, 0, 21, 0, 13, 0, 6,
0, 9, 0, 12, 0, 16, 0, 4, 0, 7, 0, 20, 0, 19, 0, 19, 0, 16, 0, 15,
0, 5, 0, 1, 0, 4, 0, 22, 0, 2, 0, 23, 0, 3, 3, 85, 1, 144, 0, 5,
0, 0, 2, 153, 2, 204, 0, 0, 0, 143, 2, 153, 2, 204, 0, 0, 1, 235, 0, 51,
1, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 39, 23,
3, 192, 255, 192, 0, 64, 3, 192, 0, 64, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 3, 0, 0, 0, 3, 0, 0, 0, 28,
0, 1, 0, 3, 0, 0, 0, 28, 0, 3, 0, 1, 0, 0, 0, 28, 0, 4, 0, 120,
0, 0, 0, 26, 0, 16, 0, 3, 0, 10, 0, 1, 0, 32, 0, 45, 0, 51, 0, 101,
0, 105, 0, 108, 0, 111, 0, 117, 37, 203, 39, 23, 255, 253, 255, 255, 0, 0, 0, 0,
0, 32, 0, 45, 0, 51, 0, 97, 0, 104, 0, 107, 0, 110, 0, 114, 37, 203, 39, 22,
255, 253, 255, 255, 0, 1, 255, 227, 255, 215, 255, 210, 255, 165, 255, 163, 255, 162, 255, 161,
255, 159, 218, 74, 217, 0, 0, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1,
255, 255, 0, 15, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
1, 0, 0, 0, 0, 2, 0, 0, 255, 192, 4, 0, 3, 192, 0, 27, 0, 55, 0, 0,
1, 34, 7, 14, 1, 7, 6, 21, 20, 23, 30, 1, 23, 22, 51, 50, 55, 62, 1, 55,
54, 53, 52, 39, 46, 1, 39, 38, 3, 34, 39, 46, 1, 39, 38, 53, 52, 55, 62, 1,
55, 54, 51, 50, 23, 30, 1, 23, 22, 21, 20, 7, 14, 1, 7, 6, 2, 0, 106, 93,
94, 139, 40, 40, 40, 40, 139, 94, 93, 106, 106, 93, 94, 139, 40, 40, 40, 40, 139, 94,
93, 106, 80, 69, 70, 105, 30, 30, 30, 30, 105, 70, 69, 80, 80, 69, 70, 105, 30, 30,
30, 30, 105, 70, 69, 3, 192, 40, 40, 139, 94, 93, 106, 106, 93, 94, 139, 40, 40, 40,
40, 139, 94, 93, 106, 106, 93, 94, 139, 40, 40, 252, 128, 30, 30, 105, 70, 69, 80, 80,
69, 70, 105, 30, 30, 30, 30, 105, 70, 69, 80, 80, 69, 70, 105, 30, 30, 0, 0, 0,
0, 1, 0, 2, 255, 194, 3, 254, 3, 190, 0, 83, 0, 0, 37, 56, 1, 49, 9, 1,
56, 1, 49, 62, 1, 55, 54, 38, 47, 1, 46, 1, 7, 14, 1, 7, 56, 1, 49, 9,
1, 56, 1, 49, 46, 1, 39, 38, 6, 15, 1, 14, 1, 23, 30, 1, 23, 56, 1, 49,
9, 1, 56, 1, 49, 14, 1, 7, 6, 22, 31, 1, 30, 1, 55, 62, 1, 55, 56, 1,
49, 9, 1, 56, 1, 49, 30, 1, 23, 22, 54, 63, 1, 62, 1, 39, 46, 1, 3, 247,
254, 201, 1, 55, 2, 4, 1, 3, 3, 7, 147, 7, 18, 9, 3, 6, 2, 254, 201, 254,
201, 2, 6, 3, 9, 18, 7, 147, 7, 3, 3, 1, 4, 2, 1, 55, 254, 201, 2, 4,
1, 3, 3, 7, 147, 7, 18, 9, 3, 6, 2, 1, 55, 1, 55, 2, 6, 3, 9, 18,
7, 147, 7, 3, 3, 1, 4, 137, 1, 55, 1, 55, 2, 6, 3, 9, 18, 7, 147, 7,
3, 3, 1, 4, 2, 254, 201, 1, 55, 2, 4, 1, 3, 3, 7, 147, 7, 18, 9, 3,
6, 2, 254, 201, 254, 201, 2, 6, 3, 9, 18, 7, 147, 7, 3, 3, 1, 4, 2, 1,
55, 254, 201, 2, 4, 1, 3, 3, 7, 147, 7, 18, 9, 3, 6, 0, 0, 1, 0, 0,
0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57, 1, 0, 0, 0, 0, 1, 0, 0,
0, 1, 0, 0, 32, 120, 21, 165, 95, 15, 60, 245, 0, 11, 4, 0, 0, 0, 0, 0,
214, 9, 63, 5, 0, 0, 0, 0, 214, 9, 63, 5, 0, 0, 255, 192, 4, 0, 3, 192,
0, 0, 0, 8, 0, 2, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 3, 192, 255, 192,
0, 0, 4, 0, 0, 0, 0, 0, 4, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 24, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 2,
0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 20, 0, 30, 0, 40, 0, 50, 0, 60,
0, 70, 0, 80, 0, 90, 0, 100, 0, 110, 0, 120, 0, 130, 0, 140, 0, 150, 0, 160,
0, 170, 0, 180, 0, 190, 0, 200, 1, 32, 1, 150, 1, 160, 0, 0, 0, 1, 0, 0,
0, 24, 0, 84, 0, 2, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 0, 174, 0, 1, 0, 0, 0, 0,
0, 1, 0, 10, 0, 0, 0, 1, 0, 0, 0, 0, 0, 2, 0, 7, 0, 123, 0, 1,
0, 0, 0, 0, 0, 3, 0, 10, 0, 63, 0, 1, 0, 0, 0, 0, 0, 4, 0, 10,
0, 144, 0, 1, 0, 0, 0, 0, 0, 5, 0, 11, 0, 30, 0, 1, 0, 0, 0, 0,
0, 6, 0, 10, 0, 93, 0, 1, 0, 0, 0, 0, 0, 10, 0, 26, 0, 174, 0, 3,
0, 1, 4, 9, 0, 1, 0, 20, 0, 10, 0, 3, 0, 1, 4, 9, 0, 2, 0, 14,
0, 130, 0, 3, 0, 1, 4, 9, 0, 3, 0, 20, 0, 73, 0, 3, 0, 1, 4, 9,
0, 4, 0, 20, 0, 154, 0, 3, 0, 1, 4, 9, 0, 5, 0, 22, 0, 41, 0, 3,
0, 1, 4, 9, 0, 6, 0, 20, 0, 103, 0, 3, 0, 1, 4, 9, 0, 10, 0, 52,
0, 200, 105, 99, 111, 45, 112, 101, 110, 101, 103, 111, 0, 105, 0, 99, 0, 111, 0, 45,
0, 112, 0, 101, 0, 110, 0, 101, 0, 103, 0, 111, 86, 101, 114, 115, 105, 111, 110, 32,
49, 46, 48, 0, 86, 0, 101, 0, 114, 0, 115, 0, 105, 0, 111, 0, 110, 0, 32, 0,
49, 0, 46, 0, 48, 105, 99, 111, 45, 112, 101, 110, 101, 103, 111, 0, 105, 0, 99, 0,
111, 0, 45, 0, 112, 0, 101, 0, 110, 0, 101, 0, 103, 0, 111, 105, 99, 111, 45, 112,
101, 110, 101, 103, 111, 0, 105, 0, 99, 0, 111, 0, 45, 0, 112, 0, 101, 0, 110, 0,
101, 0, 103, 0, 111, 82, 101, 103, 117, 108, 97, 114, 0, 82, 0, 101, 0, 103, 0, 117,
0, 108, 0, 97, 0, 114, 105, 99, 111, 45, 112, 101, 110, 101, 103, 111, 0, 105, 0, 99,
0, 111, 0, 45, 0, 112, 0, 101, 0, 110, 0, 101, 0, 103, 0, 111, 70, 111, 110, 116,
32, 103, 101, 110, 101, 114, 97, 116, 101, 100, 32, 98, 121, 32, 73, 99, 111, 77, 111, 111,
110, 46, 0, 70, 0, 111, 0, 110, 0, 116, 0, 32, 0, 103, 0, 101, 0, 110, 0, 101,
0, 114, 0, 97, 0, 116, 0, 101, 0, 100, 0, 32, 0, 98, 0, 121, 0, 32, 0, 73,
0, 99, 0, 111, 0, 77, 0, 111, 0, 111, 0, 110, 0, 46, 0, 0, 0, 3, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
}
type customFontCache map[string]*truetype.Font
func (fc customFontCache) Store(fd draw2d.FontData, font *truetype.Font) {
fc[fd.Name] = font
}
func (fc customFontCache) Load(fd draw2d.FontData) (*truetype.Font, error) {
font, stored := fc[fd.Name]
if !stored {
return nil, fmt.Errorf("font %s is not stored in font cache", fd.Name)
}
return font, nil
}
func initFontCache() { // init font cache
fontCache := customFontCache{}
// add gofont to cache
gofont, err := truetype.Parse(goregular.TTF)
if err != nil {
panic(err)
}
fontCache.Store(draw2d.FontData{Name: "goregular"}, gofont)
// add icofont to cache
icofont, err := truetype.Parse(icoTTF)
if err != nil {
panic(err)
}
fontCache.Store(draw2d.FontData{Name: "ico"}, icofont)
draw2d.SetFontCache(fontCache)
}
func TestCurveIndexOutOfRange(t *testing.T) {
initFontCache()
// Initialize the graphic context on an RGBA image
dest := image.NewRGBA(image.Rect(0, 0, 512, 512))
gc := NewGraphicContext(dest)
// background
gc.SetFillColor(color.RGBA{0xef, 0xef, 0xef, 0xff})
draw2dkit.Rectangle(gc, 0, 0, 512, 512)
gc.Fill()
// text
gc.SetFontSize(20)
gc.SetFillColor(color.RGBA{0x10, 0x10, 0x10, 0xff})
gc.SetFontData(draw2d.FontData{Name: "goregular"})
// gc.FillStringAt("Hello", 128, 120) // this works well
gc.SetFontData(draw2d.FontData{Name: "ico"})
gc.FillStringAt("\u25cb", 128, 150) // this also works
gc.FillStringAt("\u2716", 128, 170) // Works now
SaveToPngFile("_test_hello.png", dest)
}

View file

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

449
draw2dimg/ftgc.go Normal file
View file

@ -0,0 +1,449 @@
// Copyright 2010 The draw2d Authors. All rights reserved.
// created: 21/11/2010 by Laurent Le Goff
package draw2dimg
import (
"image"
"image/color"
"log"
"math"
"git.fromouter.space/crunchy-rocks/draw2d"
"git.fromouter.space/crunchy-rocks/draw2d/draw2dbase"
"git.fromouter.space/crunchy-rocks/emoji"
"git.fromouter.space/crunchy-rocks/freetype/raster"
"git.fromouter.space/crunchy-rocks/freetype/truetype"
"golang.org/x/image/draw"
"golang.org/x/image/font"
"golang.org/x/image/math/f64"
"golang.org/x/image/math/fixed"
)
// Painter implements the freetype raster.Painter and has a SetColor method like the RGBAPainter
type Painter interface {
raster.Painter
SetColor(color color.Color)
}
// GraphicContext is the implementation of draw2d.GraphicContext for a raster image
type GraphicContext struct {
*draw2dbase.StackGraphicContext
img draw.Image
painter Painter
fillRasterizer *raster.Rasterizer
strokeRasterizer *raster.Rasterizer
FontCache draw2d.FontCache
glyphCache draw2dbase.GlyphCache
glyphBuf *truetype.GlyphBuf
DPI int
Emojis emoji.Table
}
// ImageFilter defines the type of filter to use
type ImageFilter int
const (
// LinearFilter defines a linear filter
LinearFilter ImageFilter = iota
// BilinearFilter defines a bilinear filter
BilinearFilter
// BicubicFilter defines a bicubic filter
BicubicFilter
)
// NewGraphicContext creates a new Graphic context from an image.
func NewGraphicContext(img draw.Image) *GraphicContext {
var painter Painter
switch selectImage := img.(type) {
case *image.RGBA:
painter = raster.NewRGBAPainter(selectImage)
default:
panic("Image type not supported")
}
return NewGraphicContextWithPainter(img, painter)
}
// NewGraphicContextWithPainter creates a new Graphic context from an image and a Painter (see Freetype-go)
func NewGraphicContextWithPainter(img draw.Image, painter Painter) *GraphicContext {
width, height := img.Bounds().Dx(), img.Bounds().Dy()
dpi := 92
gc := &GraphicContext{
draw2dbase.NewStackGraphicContext(),
img,
painter,
raster.NewRasterizer(width, height),
raster.NewRasterizer(width, height),
draw2d.GetGlobalFontCache(),
draw2dbase.NewGlyphCache(),
&truetype.GlyphBuf{},
dpi,
make(emoji.Table),
}
return gc
}
// GetDPI returns the resolution of the Image GraphicContext
func (gc *GraphicContext) GetDPI() int {
return gc.DPI
}
// Clear fills the current canvas with a default transparent color
func (gc *GraphicContext) Clear() {
width, height := gc.img.Bounds().Dx(), gc.img.Bounds().Dy()
gc.ClearRect(0, 0, width, height)
}
// ClearRect fills the current canvas with a default transparent color at the specified rectangle
func (gc *GraphicContext) ClearRect(x1, y1, x2, y2 int) {
imageColor := image.NewUniform(gc.Current.FillColor)
draw.Draw(gc.img, image.Rect(x1, y1, x2, y2), imageColor, image.ZP, draw.Over)
}
// DrawImage draws an image into dest using an affine transformation matrix, an op and a filter
func DrawImage(src image.Image, dest draw.Image, tr draw2d.Matrix, op draw.Op, filter ImageFilter) {
var transformer draw.Transformer
switch filter {
case LinearFilter:
transformer = draw.NearestNeighbor
case BilinearFilter:
transformer = draw.BiLinear
case BicubicFilter:
transformer = draw.CatmullRom
}
transformer.Transform(dest, f64.Aff3{tr[0], tr[1], tr[4], tr[2], tr[3], tr[5]}, src, src.Bounds(), op, nil)
}
// DrawImage draws the raster image in the current canvas
func (gc *GraphicContext) DrawImage(img image.Image) {
DrawImage(img, gc.img, gc.Current.Tr, draw.Over, BilinearFilter)
}
// FillString draws the text at point (0, 0)
func (gc *GraphicContext) FillString(text string) (width float64) {
return gc.FillStringAt(text, 0, 0)
}
const emojiSpacing = 10
const emojiScale = 110
// FillStringAt draws the text at the specified point (x, y)
func (gc *GraphicContext) FillStringAt(text string, x, y float64) (width float64) {
f, err := gc.loadCurrentFont()
if err != nil {
log.Println(err)
return 0.0
}
startx := x
prev, hasPrev := truetype.Index(0), false
fontName := gc.GetFontName()
for fragment := range gc.Emojis.Iterate(text) {
if fragment.IsEmoji {
img, err := LoadFromPngFile(fragment.Emoji.Path)
if err == nil {
gc.Save()
scale := gc.GetFontSize() / 100
gc.Translate(x+scale*emojiSpacing, y-scale*emojiScale)
gc.Scale(scale, scale)
gc.DrawImage(img)
gc.Restore()
x += scale*float64(img.Bounds().Size().X) + scale*emojiSpacing*2
}
continue
}
index := f.Index(fragment.Rune)
if hasPrev {
x += fUnitsToFloat64(f.Kern(fixed.Int26_6(gc.Current.Scale), prev, index))
}
glyph := gc.glyphCache.Fetch(gc, fontName, fragment.Rune)
x += glyph.Fill(gc, x, y)
prev, hasPrev = index, true
}
return x - startx
}
// StrokeString draws the contour of the text at point (0, 0)
func (gc *GraphicContext) StrokeString(text string) (width float64) {
return gc.StrokeStringAt(text, 0, 0)
}
// StrokeStringAt draws the contour of the text at point (x, y)
func (gc *GraphicContext) StrokeStringAt(text string, x, y float64) (width float64) {
f, err := gc.loadCurrentFont()
if err != nil {
log.Println(err)
return 0.0
}
startx := x
prev, hasPrev := truetype.Index(0), false
fontName := gc.GetFontName()
for fragment := range gc.Emojis.Iterate(text) {
if fragment.IsEmoji {
img, err := LoadFromPngFile(fragment.Emoji.Path)
if err == nil {
gc.Save()
scale := gc.GetFontSize() / 100
gc.Translate(x+scale*emojiSpacing, y-scale*emojiScale)
gc.Scale(scale, scale)
gc.DrawImage(img)
gc.Restore()
x += scale*float64(img.Bounds().Size().X) + scale*emojiSpacing*2
}
continue
}
index := f.Index(fragment.Rune)
if hasPrev {
x += fUnitsToFloat64(f.Kern(fixed.Int26_6(gc.Current.Scale), prev, index))
}
glyph := gc.glyphCache.Fetch(gc, fontName, fragment.Rune)
x += glyph.Stroke(gc, x, y)
prev, hasPrev = index, true
}
return x - startx
}
func (gc *GraphicContext) loadCurrentFont() (*truetype.Font, error) {
font, err := gc.FontCache.Load(gc.Current.FontData)
if err != nil {
font, err = gc.FontCache.Load(draw2dbase.DefaultFontData)
}
if font != nil {
gc.SetFont(font)
gc.SetFontSize(gc.Current.FontSize)
}
return font, err
}
// p is a truetype.Point measured in FUnits and positive Y going upwards.
// The returned value is the same thing measured in floating point and positive Y
// going downwards.
func (gc *GraphicContext) drawGlyph(glyph truetype.Index, dx, dy float64) error {
if err := gc.glyphBuf.Load(gc.Current.Font, fixed.Int26_6(gc.Current.Scale), glyph, font.HintingNone); err != nil {
return err
}
e0 := 0
for _, e1 := range gc.glyphBuf.Ends {
DrawContour(gc, gc.glyphBuf.Points[e0:e1], dx, dy)
e0 = e1
}
return nil
}
// CreateStringPath creates a path from the string s at x, y, and returns the string width.
// The text is placed so that the left edge of the em square of the first character of s
// and the baseline intersect at x, y. The majority of the affected pixels will be
// above and to the right of the point, but some may be below or to the left.
// For example, drawing a string that starts with a 'J' in an italic font may
// affect pixels below and left of the point.
func (gc *GraphicContext) CreateStringPath(s string, x, y float64) float64 {
f, err := gc.loadCurrentFont()
if err != nil {
log.Println(err)
return 0.0
}
startx := x
prev, hasPrev := truetype.Index(0), false
for _, rune := range s {
index := f.Index(rune)
if hasPrev {
x += fUnitsToFloat64(f.Kern(fixed.Int26_6(gc.Current.Scale), prev, index))
}
err := gc.drawGlyph(index, x, y)
if err != nil {
log.Println(err)
return startx - x
}
x += fUnitsToFloat64(f.HMetric(fixed.Int26_6(gc.Current.Scale), index).AdvanceWidth)
prev, hasPrev = index, true
}
return x - startx
}
// GetStringBounds returns the approximate pixel bounds of the string s at x, y.
// The the left edge of the em square of the first character of s
// and the baseline intersect at 0, 0 in the returned coordinates.
// Therefore the top and left coordinates may well be negative.
func (gc *GraphicContext) GetStringBounds(s string) (left, top, right, bottom float64) {
f, err := gc.loadCurrentFont()
if err != nil {
log.Println(err)
return 0, 0, 0, 0
}
top, left, bottom, right = 10e6, 10e6, -10e6, -10e6
cursor := 0.0
prev, hasPrev := truetype.Index(0), false
// Get sample letter for approximated emoji calculations
const letter = 'M'
mindex := f.Index(letter)
mtop, mleft, mheight, mwidth := 10e6, 10e6, -10e6, -10e6
if err := gc.glyphBuf.Load(gc.Current.Font, fixed.Int26_6(gc.Current.Scale), mindex, font.HintingNone); err != nil {
log.Println(err)
return 0, 0, 0, 0
}
e0 := 0
for _, e1 := range gc.glyphBuf.Ends {
ps := gc.glyphBuf.Points[e0:e1]
for _, p := range ps {
x, y := pointToF64Point(p)
mtop = math.Min(mtop, y)
mheight = math.Max(mheight, y)
mleft = math.Min(mleft, x)
mwidth = math.Max(mwidth, x)
}
}
mtop *= 1.2
mwidth *= 1.55
mheight += math.Abs(mtop-mheight) * 0.2
// Actually iterate through the string
for fragment := range gc.Emojis.Iterate(s) {
if fragment.IsEmoji {
cursor += mwidth
top = math.Min(top, mtop)
bottom = math.Max(bottom, mheight)
left = math.Min(left, mleft)
right = math.Max(right, cursor)
continue
}
index := f.Index(fragment.Rune)
if hasPrev {
cursor += fUnitsToFloat64(f.Kern(fixed.Int26_6(gc.Current.Scale), prev, index))
}
if err := gc.glyphBuf.Load(gc.Current.Font, fixed.Int26_6(gc.Current.Scale), index, font.HintingNone); err != nil {
log.Println(err)
return 0, 0, 0, 0
}
e0 := 0
for _, e1 := range gc.glyphBuf.Ends {
ps := gc.glyphBuf.Points[e0:e1]
for _, p := range ps {
x, y := pointToF64Point(p)
top = math.Min(top, y)
bottom = math.Max(bottom, y)
left = math.Min(left, x+cursor)
right = math.Max(right, x+cursor)
}
}
cursor += fUnitsToFloat64(f.HMetric(fixed.Int26_6(gc.Current.Scale), index).AdvanceWidth)
prev, hasPrev = index, true
}
return left, top, right, bottom
}
// recalc recalculates scale and bounds values from the font size, screen
// resolution and font metrics, and invalidates the glyph cache.
func (gc *GraphicContext) recalc() {
gc.Current.Scale = gc.Current.FontSize * float64(gc.DPI) * (64.0 / 72.0)
}
// SetDPI sets the screen resolution in dots per inch.
func (gc *GraphicContext) SetDPI(dpi int) {
gc.DPI = dpi
gc.recalc()
}
// SetFont sets the font used to draw text.
func (gc *GraphicContext) SetFont(font *truetype.Font) {
gc.Current.Font = font
}
// SetFontSize sets the font size in points (as in ``a 12 point font'').
func (gc *GraphicContext) SetFontSize(fontSize float64) {
gc.Current.FontSize = fontSize
gc.recalc()
}
func (gc *GraphicContext) paint(rasterizer *raster.Rasterizer, color color.Color) {
gc.painter.SetColor(color)
rasterizer.Rasterize(gc.painter)
rasterizer.Clear()
gc.Current.Path.Clear()
}
// Stroke strokes the paths with the color specified by SetStrokeColor
func (gc *GraphicContext) Stroke(paths ...*draw2d.Path) {
paths = append(paths, gc.Current.Path)
gc.strokeRasterizer.UseNonZeroWinding = true
stroker := draw2dbase.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2dbase.Transformer{Tr: gc.Current.Tr, Flattener: FtLineBuilder{Adder: gc.strokeRasterizer}})
stroker.HalfLineWidth = gc.Current.LineWidth / 2
var liner draw2dbase.Flattener
if gc.Current.Dash != nil && len(gc.Current.Dash) > 0 {
liner = draw2dbase.NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker)
} else {
liner = stroker
}
for _, p := range paths {
draw2dbase.Flatten(p, liner, gc.Current.Tr.GetScale())
}
gc.paint(gc.strokeRasterizer, gc.Current.StrokeColor)
}
// Fill fills the paths with the color specified by SetFillColor
func (gc *GraphicContext) Fill(paths ...*draw2d.Path) {
paths = append(paths, gc.Current.Path)
gc.fillRasterizer.UseNonZeroWinding = gc.Current.FillRule == draw2d.FillRuleWinding
/**** first method ****/
flattener := draw2dbase.Transformer{Tr: gc.Current.Tr, Flattener: FtLineBuilder{Adder: gc.fillRasterizer}}
for _, p := range paths {
draw2dbase.Flatten(p, flattener, gc.Current.Tr.GetScale())
}
gc.paint(gc.fillRasterizer, gc.Current.FillColor)
}
// FillStroke first fills the paths and than strokes them
func (gc *GraphicContext) FillStroke(paths ...*draw2d.Path) {
paths = append(paths, gc.Current.Path)
gc.fillRasterizer.UseNonZeroWinding = gc.Current.FillRule == draw2d.FillRuleWinding
gc.strokeRasterizer.UseNonZeroWinding = true
flattener := draw2dbase.Transformer{Tr: gc.Current.Tr, Flattener: FtLineBuilder{Adder: gc.fillRasterizer}}
stroker := draw2dbase.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2dbase.Transformer{Tr: gc.Current.Tr, Flattener: FtLineBuilder{Adder: gc.strokeRasterizer}})
stroker.HalfLineWidth = gc.Current.LineWidth / 2
var liner draw2dbase.Flattener
if gc.Current.Dash != nil && len(gc.Current.Dash) > 0 {
liner = draw2dbase.NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker)
} else {
liner = stroker
}
demux := draw2dbase.DemuxFlattener{Flatteners: []draw2dbase.Flattener{flattener, liner}}
for _, p := range paths {
draw2dbase.Flatten(p, demux, gc.Current.Tr.GetScale())
}
// Fill
gc.paint(gc.fillRasterizer, gc.Current.FillColor)
// Stroke
gc.paint(gc.strokeRasterizer, gc.Current.StrokeColor)
}
func toFtCap(c draw2d.LineCap) raster.Capper {
switch c {
case draw2d.RoundCap:
return raster.RoundCapper
case draw2d.ButtCap:
return raster.ButtCapper
case draw2d.SquareCap:
return raster.SquareCapper
}
return raster.RoundCapper
}
func toFtJoin(j draw2d.LineJoin) raster.Joiner {
switch j {
case draw2d.RoundJoin:
return raster.RoundJoiner
case draw2d.BevelJoin:
return raster.BevelJoiner
}
return raster.RoundJoiner
}

30
draw2dimg/ftpath.go Normal file
View file

@ -0,0 +1,30 @@
// Copyright 2010 The draw2d Authors. All rights reserved.
// created: 13/12/2010 by Laurent Le Goff
package draw2dimg
import (
"git.fromouter.space/crunchy-rocks/freetype/raster"
"golang.org/x/image/math/fixed"
)
type FtLineBuilder struct {
Adder raster.Adder
}
func (liner FtLineBuilder) MoveTo(x, y float64) {
liner.Adder.Start(fixed.Point26_6{X: fixed.Int26_6(x * 64), Y: fixed.Int26_6(y * 64)})
}
func (liner FtLineBuilder) LineTo(x, y float64) {
liner.Adder.Add1(fixed.Point26_6{X: fixed.Int26_6(x * 64), Y: fixed.Int26_6(y * 64)})
}
func (liner FtLineBuilder) LineJoin() {
}
func (liner FtLineBuilder) Close() {
}
func (liner FtLineBuilder) End() {
}

82
draw2dimg/text.go Normal file
View file

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

7
draw2dkit/README.md Normal file
View file

@ -0,0 +1,7 @@
draw2d/draw2dkit
=================
[![Coverage](http://gocover.io/_badge/github.com/llgcode/draw2d/draw2dkit?0)](http://gocover.io/github.com/llgcode/draw2d/draw2dkit)
[![GoDoc](https://godoc.org/github.com/llgcode/draw2d/draw2dkit?status.svg)](https://godoc.org/github.com/llgcode/draw2d/draw2dkit)
Util package that help drawing common vectorial draw.

View file

@ -1,16 +1,17 @@
// Copyright 2010 The draw2d Authors. All rights reserved. // Copyright 2010 The draw2d Authors. All rights reserved.
// created: 13/12/2010 by Laurent Le Goff // created: 13/12/2010 by Laurent Le Goff
//high level path creation // Package draw2dkit provides helpers to draw common figures using a Path
package draw2dkit
package draw2d
import ( import (
"math" "math"
"github.com/llgcode/draw2d"
) )
// Rect draws a rectangle between (x1,y1) and (x2,y2) // Rectangle draws a rectangle using a path between (x1,y1) and (x2,y2)
func Rect(path Path, x1, y1, x2, y2 float64) { func Rectangle(path draw2d.PathBuilder, x1, y1, x2, y2 float64) {
path.MoveTo(x1, y1) path.MoveTo(x1, y1)
path.LineTo(x2, y1) path.LineTo(x2, y1)
path.LineTo(x2, y2) path.LineTo(x2, y2)
@ -18,8 +19,8 @@ func Rect(path Path, x1, y1, x2, y2 float64) {
path.Close() path.Close()
} }
// RoundRect draws a rectangle between (x1,y1) and (x2,y2) with rounded corners // RoundedRectangle draws a rectangle using a path between (x1,y1) and (x2,y2)
func RoundRect(path Path, x1, y1, x2, y2, arcWidth, arcHeight float64) { func RoundedRectangle(path draw2d.PathBuilder, x1, y1, x2, y2, arcWidth, arcHeight float64) {
arcWidth = arcWidth / 2 arcWidth = arcWidth / 2
arcHeight = arcHeight / 2 arcHeight = arcHeight / 2
path.MoveTo(x1, y1+arcHeight) path.MoveTo(x1, y1+arcHeight)
@ -33,16 +34,14 @@ func RoundRect(path Path, x1, y1, x2, y2, arcWidth, arcHeight float64) {
path.Close() path.Close()
} }
// Ellipse is drawn with center (cx,cy) and radius (rx,ry) // Ellipse draws an ellipse using a path with center (cx,cy) and radius (rx,ry)
func Ellipse(path Path, cx, cy, rx, ry float64) { func Ellipse(path draw2d.PathBuilder, cx, cy, rx, ry float64) {
path.MoveTo(cx-rx, cy)
path.ArcTo(cx, cy, rx, ry, 0, -math.Pi*2) path.ArcTo(cx, cy, rx, ry, 0, -math.Pi*2)
path.Close() path.Close()
} }
// Circle is drawn with center (cx,cy) and radius // Circle draws a circle using a path with center (cx,cy) and radius
func Circle(path Path, cx, cy, radius float64) { func Circle(path draw2d.PathBuilder, cx, cy, radius float64) {
path.MoveTo(cx-radius, cy)
path.ArcTo(cx, cy, radius, radius, 0, -math.Pi*2) path.ArcTo(cx, cy, radius, radius, 0, -math.Pi*2)
path.Close() path.Close()
} }

View file

@ -0,0 +1,29 @@
package draw2dkit
import (
"image"
"image/color"
"testing"
"github.com/llgcode/draw2d/draw2dimg"
)
func TestCircle(t *testing.T) {
width := 200
height := 200
img := image.NewRGBA(image.Rect(0, 0, width, height))
gc := draw2dimg.NewGraphicContext(img)
gc.SetStrokeColor(color.NRGBA{255, 255, 255, 255})
gc.SetFillColor(color.NRGBA{255, 255, 255, 255})
gc.Clear()
gc.SetStrokeColor(color.NRGBA{255, 0, 0, 255})
gc.SetLineWidth(1)
// Draw a circle
Circle(gc, 100, 100, 50)
gc.Stroke()
draw2dimg.SaveToPngFile("../output/draw2dkit/TestCircle.png", img)
}

View file

@ -10,7 +10,7 @@ The following Go code generates a simple drawing and saves it to a pdf document:
```go ```go
// Initialize the graphic context on an RGBA image // Initialize the graphic context on an RGBA image
dest := draw2dpdf.NewPdf("L", "mm", "A4") dest := draw2dpdf.NewPdf("L", "mm", "A4")
gc := draw2d.NewGraphicContext(dest) gc := draw2dpdf.NewGraphicContext(dest)
// Set some properties // Set some properties
gc.SetFillColor(color.RGBA{0x44, 0xff, 0x44, 0xff}) gc.SetFillColor(color.RGBA{0x44, 0xff, 0x44, 0xff})

View file

@ -10,7 +10,7 @@
// pdf document: // pdf document:
// // Initialize the graphic context on an RGBA image // // Initialize the graphic context on an RGBA image
// dest := draw2dpdf.NewPdf("L", "mm", "A4") // dest := draw2dpdf.NewPdf("L", "mm", "A4")
// gc := draw2d.NewGraphicContext(dest) // gc := draw2dpdf.NewGraphicContext(dest)
// //
// // Set some properties // // Set some properties
// gc.SetFillColor(color.RGBA{0x44, 0xff, 0x44, 0xff}) // gc.SetFillColor(color.RGBA{0x44, 0xff, 0x44, 0xff})
@ -28,7 +28,7 @@
// draw2dpdf.SaveToPdfFile("hello.pdf", dest) // draw2dpdf.SaveToPdfFile("hello.pdf", dest)
// //
// There are more examples here: // There are more examples here:
// https://gopkg.in/llgcode/draw2d.v1/tree/master/samples // https://github.com/llgcode/draw2d/tree/master/samples
// //
// Alternative backends // Alternative backends
// //

View file

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

View file

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

View file

@ -7,16 +7,16 @@ package draw2dpdf_test
import ( import (
"testing" "testing"
"gopkg.in/llgcode/draw2d.v1" "github.com/llgcode/draw2d"
"gopkg.in/llgcode/draw2d.v1/samples/android" "github.com/llgcode/draw2d/samples/android"
"gopkg.in/llgcode/draw2d.v1/samples/frameimage" "github.com/llgcode/draw2d/samples/frameimage"
"gopkg.in/llgcode/draw2d.v1/samples/geometry" "github.com/llgcode/draw2d/samples/geometry"
"gopkg.in/llgcode/draw2d.v1/samples/gopher" "github.com/llgcode/draw2d/samples/gopher"
"gopkg.in/llgcode/draw2d.v1/samples/gopher2" "github.com/llgcode/draw2d/samples/gopher2"
"gopkg.in/llgcode/draw2d.v1/samples/helloworld" "github.com/llgcode/draw2d/samples/helloworld"
"gopkg.in/llgcode/draw2d.v1/samples/line" "github.com/llgcode/draw2d/samples/line"
"gopkg.in/llgcode/draw2d.v1/samples/linecapjoin" "github.com/llgcode/draw2d/samples/linecapjoin"
"gopkg.in/llgcode/draw2d.v1/samples/postscript" "github.com/llgcode/draw2d/samples/postscript"
) )
func TestSampleAndroid(t *testing.T) { func TestSampleAndroid(t *testing.T) {

View file

@ -9,8 +9,8 @@ package draw2dpdf_test
import ( import (
"testing" "testing"
"gopkg.in/llgcode/draw2d.v1" "github.com/llgcode/draw2d"
"gopkg.in/llgcode/draw2d.v1/draw2dpdf" "github.com/llgcode/draw2d/draw2dpdf"
) )
type sample func(gc draw2d.GraphicContext, ext string) (string, error) type sample func(gc draw2d.GraphicContext, ext string) (string, error)

176
draw2dsvg/converters.go Normal file
View file

@ -0,0 +1,176 @@
// Copyright 2015 The draw2d Authors. All rights reserved.
// created: 16/12/2017 by Drahoslav Bednářpackage draw2dsvg
package draw2dsvg
import (
"bytes"
"encoding/base64"
"fmt"
"image"
"image/color"
"image/png"
"math"
"strconv"
"strings"
"git.fromouter.space/crunchy-rocks/draw2d"
)
func toSvgRGBA(c color.Color) string {
r, g, b, a := c.RGBA()
r, g, b, a = r>>8, g>>8, b>>8, a>>8
if a == 255 {
return optiSprintf("#%02X%02X%02X", r, g, b)
}
return optiSprintf("rgba(%v,%v,%v,%f)", r, g, b, float64(a)/255)
}
func toSvgLength(l float64) string {
if math.IsInf(l, 1) {
return "100%"
}
return optiSprintf("%f", l)
}
func toSvgArray(nums []float64) string {
arr := make([]string, len(nums))
for i, num := range nums {
arr[i] = optiSprintf("%f", num)
}
return strings.Join(arr, ",")
}
func toSvgFillRule(rule draw2d.FillRule) string {
return map[draw2d.FillRule]string{
draw2d.FillRuleEvenOdd: "evenodd",
draw2d.FillRuleWinding: "nonzero",
}[rule]
}
func toSvgPathDesc(p *draw2d.Path) string {
parts := make([]string, len(p.Components))
ps := p.Points
for i, cmp := range p.Components {
switch cmp {
case draw2d.MoveToCmp:
parts[i] = optiSprintf("M %f,%f", ps[0], ps[1])
ps = ps[2:]
case draw2d.LineToCmp:
parts[i] = optiSprintf("L %f,%f", ps[0], ps[1])
ps = ps[2:]
case draw2d.QuadCurveToCmp:
parts[i] = optiSprintf("Q %f,%f %f,%f", ps[0], ps[1], ps[2], ps[3])
ps = ps[4:]
case draw2d.CubicCurveToCmp:
parts[i] = optiSprintf("C %f,%f %f,%f %f,%f", ps[0], ps[1], ps[2], ps[3], ps[4], ps[5])
ps = ps[6:]
case draw2d.ArcToCmp:
cx, cy := ps[0], ps[1] // center
rx, ry := ps[2], ps[3] // radii
fi := ps[4] + ps[5] // startAngle + angle
// compute endpoint
sinfi, cosfi := math.Sincos(fi)
nom := math.Hypot(ry*cosfi, rx*sinfi)
x := cx + (rx*ry*cosfi)/nom
y := cy + (rx*ry*sinfi)/nom
// compute large and sweep flags
large := 0
sweep := 0
if math.Abs(ps[5]) > math.Pi {
large = 1
}
if !math.Signbit(ps[5]) {
sweep = 1
}
// dirty hack to ensure whole arc is drawn
// if start point equals end point
if sweep == 1 {
x += 0.01 * sinfi
y += 0.01 * -cosfi
} else {
x += 0.01 * sinfi
y += 0.01 * cosfi
}
// rx ry x-axis-rotation large-arc-flag sweep-flag x y
parts[i] = optiSprintf("A %f %f %v %v %v %F %F",
rx, ry, 0, large, sweep, x, y,
)
ps = ps[6:]
case draw2d.CloseCmp:
parts[i] = "Z"
}
}
return strings.Join(parts, " ")
}
func toSvgTransform(mat draw2d.Matrix) string {
if mat.IsIdentity() {
return ""
}
if mat.IsTranslation() {
x, y := mat.GetTranslation()
return optiSprintf("translate(%f,%f)", x, y)
}
return optiSprintf("matrix(%f,%f,%f,%f,%f,%f)",
mat[0], mat[1], mat[2], mat[3], mat[4], mat[5],
)
}
func imageToSvgHref(image image.Image) string {
out := "data:image/png;base64,"
pngBuf := &bytes.Buffer{}
png.Encode(pngBuf, image)
out += base64.RawStdEncoding.EncodeToString(pngBuf.Bytes())
return out
}
// Do the same thing as fmt.Sprintf
// except it uses the optimal precition for floats: (0-3) for f and (0-6) for F
// eg.:
// optiSprintf("%f", 3.0) => fmt.Sprintf("%.0f", 3.0)
// optiSprintf("%f", 3.33) => fmt.Sprintf("%.2f", 3.33)
// optiSprintf("%f", 3.3001) => fmt.Sprintf("%.1f", 3.3001)
// optiSprintf("%f", 3.333333333333333) => fmt.Sprintf("%.3f", 3.333333333333333)
// optiSprintf("%F", 3.333333333333333) => fmt.Sprintf("%.6f", 3.333333333333333)
func optiSprintf(format string, a ...interface{}) string {
chunks := strings.Split(format, "%")
newChunks := make([]string, len(chunks))
for i, chunk := range chunks {
if i != 0 {
verb := chunk[0]
if verb == 'f' || verb == 'F' {
num := a[i-1].(float64)
p := strconv.Itoa(getPrec(num, verb == 'F'))
chunk = strings.Replace(chunk, string(verb), "."+p+"f", 1)
}
}
newChunks[i] = chunk
}
format = strings.Join(newChunks, "%")
return fmt.Sprintf(format, a...)
}
// TODO needs test, since it is not quiet right
func getPrec(num float64, better bool) int {
max := 3
eps := 0.0005
if better {
max = 6
eps = 0.0000005
}
prec := 0
for math.Mod(num, 1) > eps {
num *= 10
eps *= 10
prec++
}
if max < prec {
return max
}
return prec
}

11
draw2dsvg/doc.go Normal file
View file

@ -0,0 +1,11 @@
// Copyright 2015 The draw2d Authors. All rights reserved.
// created: 16/12/2017 by Drahoslav Bednář
// Package draw2svg provides a graphic context that can draw
// vector graphics and text on svg file.
//
// Quick Start
// The following Go code geneartes a simple drawing and saves it
// to a svg document:
// TODO
package draw2dsvg

22
draw2dsvg/fileutil.go Normal file
View file

@ -0,0 +1,22 @@
package draw2dsvg
import (
"encoding/xml"
_ "errors"
"os"
)
func SaveToSvgFile(filePath string, svg *Svg) error {
f, err := os.Create(filePath)
if err != nil {
return err
}
defer f.Close()
f.Write([]byte(xml.Header))
encoder := xml.NewEncoder(f)
encoder.Indent("", "\t")
err = encoder.Encode(svg)
return err
}

403
draw2dsvg/gc.go Normal file
View file

@ -0,0 +1,403 @@
// Copyright 2015 The draw2d Authors. All rights reserved.
// created: 16/12/2017 by Drahoslav Bednář
package draw2dsvg
import (
"image"
"log"
"math"
"strconv"
"strings"
"git.fromouter.space/crunchy-rocks/draw2d"
"git.fromouter.space/crunchy-rocks/draw2d/draw2dbase"
"git.fromouter.space/crunchy-rocks/freetype/truetype"
"golang.org/x/image/font"
"golang.org/x/image/math/fixed"
)
type drawType int
const (
filled drawType = 1 << iota
stroked
)
// GraphicContext implements the draw2d.GraphicContext interface
// It provides draw2d with a svg backend
type GraphicContext struct {
*draw2dbase.StackGraphicContext
FontCache draw2d.FontCache
glyphCache draw2dbase.GlyphCache
glyphBuf *truetype.GlyphBuf
svg *Svg
DPI int
}
func NewGraphicContext(svg *Svg) *GraphicContext {
gc := &GraphicContext{
draw2dbase.NewStackGraphicContext(),
draw2d.GetGlobalFontCache(),
draw2dbase.NewGlyphCache(),
&truetype.GlyphBuf{},
svg,
92,
}
return gc
}
// Clear fills the current canvas with a default transparent color
func (gc *GraphicContext) Clear() {
gc.svg.Groups = nil
}
// Stroke strokes the paths with the color specified by SetStrokeColor
func (gc *GraphicContext) Stroke(paths ...*draw2d.Path) {
gc.drawPaths(stroked, paths...)
gc.Current.Path.Clear()
}
// Fill fills the paths with the color specified by SetFillColor
func (gc *GraphicContext) Fill(paths ...*draw2d.Path) {
gc.drawPaths(filled, paths...)
gc.Current.Path.Clear()
}
// FillStroke first fills the paths and than strokes them
func (gc *GraphicContext) FillStroke(paths ...*draw2d.Path) {
gc.drawPaths(filled|stroked, paths...)
gc.Current.Path.Clear()
}
// FillString draws the text at point (0, 0)
func (gc *GraphicContext) FillString(text string) (cursor float64) {
return gc.FillStringAt(text, 0, 0)
}
// FillStringAt draws the text at the specified point (x, y)
func (gc *GraphicContext) FillStringAt(text string, x, y float64) (cursor float64) {
return gc.drawString(text, filled, x, y)
}
// StrokeString draws the contour of the text at point (0, 0)
func (gc *GraphicContext) StrokeString(text string) (cursor float64) {
return gc.StrokeStringAt(text, 0, 0)
}
// StrokeStringAt draws the contour of the text at point (x, y)
func (gc *GraphicContext) StrokeStringAt(text string, x, y float64) (cursor float64) {
return gc.drawString(text, stroked, x, y)
}
// Save the context and push it to the context stack
func (gc *GraphicContext) Save() {
gc.StackGraphicContext.Save()
// TODO use common transformation group for multiple elements
}
// Restore remove the current context and restore the last one
func (gc *GraphicContext) Restore() {
gc.StackGraphicContext.Restore()
// TODO use common transformation group for multiple elements
}
func (gc *GraphicContext) SetDPI(dpi int) {
gc.DPI = dpi
gc.recalc()
}
func (gc *GraphicContext) GetDPI() int {
return gc.DPI
}
// SetFont sets the font used to draw text.
func (gc *GraphicContext) SetFont(font *truetype.Font) {
gc.Current.Font = font
}
// SetFontSize sets the font size in points (as in “a 12 point font”).
func (gc *GraphicContext) SetFontSize(fontSize float64) {
gc.Current.FontSize = fontSize
gc.recalc()
}
// DrawImage draws the raster image in the current canvas
func (gc *GraphicContext) DrawImage(image image.Image) {
bounds := image.Bounds()
svgImage := &Image{Href: imageToSvgHref(image)}
svgImage.X = float64(bounds.Min.X)
svgImage.Y = float64(bounds.Min.Y)
svgImage.Width = toSvgLength(float64(bounds.Max.X - bounds.Min.X))
svgImage.Height = toSvgLength(float64(bounds.Max.Y - bounds.Min.Y))
gc.newGroup(0).Image = svgImage
}
// ClearRect fills the specified rectangle with a default transparent color
func (gc *GraphicContext) ClearRect(x1, y1, x2, y2 int) {
mask := gc.newMask(x1, y1, x2-x1, y2-y1)
newGroup := &Group{
Groups: gc.svg.Groups,
Mask: "url(#" + mask.Id + ")",
}
// replace groups with new masked group
gc.svg.Groups = []*Group{newGroup}
}
// NOTE following two functions and soe other further below copied from dwra2d{img|gl}
// TODO move them all to common draw2dbase?
// CreateStringPath creates a path from the string s at x, y, and returns the string width.
// The text is placed so that the left edge of the em square of the first character of s
// and the baseline intersect at x, y. The majority of the affected pixels will be
// above and to the right of the point, but some may be below or to the left.
// For example, drawing a string that starts with a 'J' in an italic font may
// affect pixels below and left of the point.
func (gc *GraphicContext) CreateStringPath(s string, x, y float64) (cursor float64) {
f, err := gc.loadCurrentFont()
if err != nil {
log.Println(err)
return 0.0
}
startx := x
prev, hasPrev := truetype.Index(0), false
for _, rune := range s {
index := f.Index(rune)
if hasPrev {
x += fUnitsToFloat64(f.Kern(fixed.Int26_6(gc.Current.Scale), prev, index))
}
err := gc.drawGlyph(index, x, y)
if err != nil {
log.Println(err)
return startx - x
}
x += fUnitsToFloat64(f.HMetric(fixed.Int26_6(gc.Current.Scale), index).AdvanceWidth)
prev, hasPrev = index, true
}
return x - startx
}
// GetStringBounds returns the approximate pixel bounds of the string s at x, y.
// The the left edge of the em square of the first character of s
// and the baseline intersect at 0, 0 in the returned coordinates.
// Therefore the top and left coordinates may well be negative.
func (gc *GraphicContext) GetStringBounds(s string) (left, top, right, bottom float64) {
f, err := gc.loadCurrentFont()
if err != nil {
log.Println(err)
return 0, 0, 0, 0
}
if gc.Current.Scale == 0 {
panic("zero scale")
}
top, left, bottom, right = 10e6, 10e6, -10e6, -10e6
cursor := 0.0
prev, hasPrev := truetype.Index(0), false
for _, rune := range s {
index := f.Index(rune)
if hasPrev {
cursor += fUnitsToFloat64(f.Kern(fixed.Int26_6(gc.Current.Scale), prev, index))
}
if err := gc.glyphBuf.Load(gc.Current.Font, fixed.Int26_6(gc.Current.Scale), index, font.HintingNone); err != nil {
log.Println(err)
return 0, 0, 0, 0
}
e0 := 0
for _, e1 := range gc.glyphBuf.Ends {
ps := gc.glyphBuf.Points[e0:e1]
for _, p := range ps {
x, y := pointToF64Point(p)
top = math.Min(top, y)
bottom = math.Max(bottom, y)
left = math.Min(left, x+cursor)
right = math.Max(right, x+cursor)
}
}
cursor += fUnitsToFloat64(f.HMetric(fixed.Int26_6(gc.Current.Scale), index).AdvanceWidth)
prev, hasPrev = index, true
}
return left, top, right, bottom
}
////////////////////
// private funcitons
func (gc *GraphicContext) drawPaths(drawType drawType, paths ...*draw2d.Path) {
// create elements
svgPath := Path{}
group := gc.newGroup(drawType)
// set attrs to path element
paths = append(paths, gc.Current.Path)
svgPathsDesc := make([]string, len(paths))
// multiple pathes has to be joined to single svg path description
// because fill-rule wont work for whole group as excepted
for i, path := range paths {
svgPathsDesc[i] = toSvgPathDesc(path)
}
svgPath.Desc = strings.Join(svgPathsDesc, " ")
// attach to group
group.Paths = []*Path{&svgPath}
}
// Add text element to svg and returns its expected width
func (gc *GraphicContext) drawString(text string, drawType drawType, x, y float64) float64 {
switch gc.svg.FontMode {
case PathFontMode:
w := gc.CreateStringPath(text, x, y)
gc.drawPaths(drawType)
gc.Current.Path.Clear()
return w
case SvgFontMode:
gc.embedSvgFont(text)
}
// create elements
svgText := Text{}
group := gc.newGroup(drawType)
// set attrs to text element
svgText.Text = text
svgText.FontSize = gc.Current.FontSize
svgText.X = x
svgText.Y = y
svgText.FontFamily = gc.Current.FontData.Name
// attach to group
group.Texts = []*Text{&svgText}
left, _, right, _ := gc.GetStringBounds(text)
return right - left
}
// Creates new group from current context
// attach it to svg and return
func (gc *GraphicContext) newGroup(drawType drawType) *Group {
group := Group{}
// set attrs to group
if drawType&stroked == stroked {
group.Stroke = toSvgRGBA(gc.Current.StrokeColor)
group.StrokeWidth = toSvgLength(gc.Current.LineWidth)
group.StrokeLinecap = gc.Current.Cap.String()
group.StrokeLinejoin = gc.Current.Join.String()
if len(gc.Current.Dash) > 0 {
group.StrokeDasharray = toSvgArray(gc.Current.Dash)
group.StrokeDashoffset = toSvgLength(gc.Current.DashOffset)
}
}
if drawType&filled == filled {
group.Fill = toSvgRGBA(gc.Current.FillColor)
group.FillRule = toSvgFillRule(gc.Current.FillRule)
}
group.Transform = toSvgTransform(gc.Current.Tr)
// attach
gc.svg.Groups = append(gc.svg.Groups, &group)
return &group
}
// creates new mask attached to svg
func (gc *GraphicContext) newMask(x, y, width, height int) *Mask {
mask := &Mask{}
mask.X = float64(x)
mask.Y = float64(y)
mask.Width = toSvgLength(float64(width))
mask.Height = toSvgLength(float64(height))
// attach mask
gc.svg.Masks = append(gc.svg.Masks, mask)
mask.Id = "mask-" + strconv.Itoa(len(gc.svg.Masks))
return mask
}
// Embed svg font definition to svg tree itself
// Or update existing if already exists for curent font data
func (gc *GraphicContext) embedSvgFont(text string) *Font {
fontName := gc.Current.FontData.Name
gc.loadCurrentFont()
// find or create font Element
svgFont := (*Font)(nil)
for _, font := range gc.svg.Fonts {
if font.Name == fontName {
svgFont = font
break
}
}
if svgFont == nil {
// create new
svgFont = &Font{}
// and attach
gc.svg.Fonts = append(gc.svg.Fonts, svgFont)
}
// fill with glyphs
gc.Save()
defer gc.Restore()
gc.SetFontSize(2048)
defer gc.SetDPI(gc.GetDPI())
gc.SetDPI(92)
filling:
for _, rune := range text {
for _, g := range svgFont.Glyphs {
if g.Rune == Rune(rune) {
continue filling
}
}
glyph := gc.glyphCache.Fetch(gc, gc.GetFontName(), rune)
// glyphCache.Load indirectly calls CreateStringPath for single rune string
glypPath := glyph.Path.VerticalFlip() // svg font glyphs have oposite y axe
svgFont.Glyphs = append(svgFont.Glyphs, &Glyph{
Rune: Rune(rune),
Desc: toSvgPathDesc(glypPath),
HorizAdvX: glyph.Width,
})
}
// set attrs
svgFont.Id = "font-" + strconv.Itoa(len(gc.svg.Fonts))
svgFont.Name = fontName
// TODO use css @font-face with id instead of this
svgFont.Face = &Face{Family: fontName, Units: 2048, HorizAdvX: 2048}
return svgFont
}
func (gc *GraphicContext) loadCurrentFont() (*truetype.Font, error) {
font, err := gc.FontCache.Load(gc.Current.FontData)
if err != nil {
font, err = gc.FontCache.Load(draw2dbase.DefaultFontData)
}
if font != nil {
gc.SetFont(font)
gc.SetFontSize(gc.Current.FontSize)
}
return font, err
}
func (gc *GraphicContext) drawGlyph(glyph truetype.Index, dx, dy float64) error {
if err := gc.glyphBuf.Load(gc.Current.Font, fixed.Int26_6(gc.Current.Scale), glyph, font.HintingNone); err != nil {
return err
}
e0 := 0
for _, e1 := range gc.glyphBuf.Ends {
DrawContour(gc, gc.glyphBuf.Points[e0:e1], dx, dy)
e0 = e1
}
return nil
}
// recalc recalculates scale and bounds values from the font size, screen
// resolution and font metrics, and invalidates the glyph cache.
func (gc *GraphicContext) recalc() {
gc.Current.Scale = gc.Current.FontSize * float64(gc.DPI) * (64.0 / 72.0)
}

65
draw2dsvg/samples_test.go Normal file
View file

@ -0,0 +1,65 @@
// Copyright 2015 The draw2d Authors. All rights reserved.
// created: 26/06/2015 by Stani Michiels
// See also test_test.go
package draw2dsvg_test
import (
"testing"
"git.fromouter.space/crunchy-rocks/draw2d"
"git.fromouter.space/crunchy-rocks/draw2d/samples/android"
"git.fromouter.space/crunchy-rocks/draw2d/samples/frameimage"
"git.fromouter.space/crunchy-rocks/draw2d/samples/geometry"
"git.fromouter.space/crunchy-rocks/draw2d/samples/gopher"
"git.fromouter.space/crunchy-rocks/draw2d/samples/gopher2"
"git.fromouter.space/crunchy-rocks/draw2d/samples/helloworld"
"git.fromouter.space/crunchy-rocks/draw2d/samples/line"
"git.fromouter.space/crunchy-rocks/draw2d/samples/linecapjoin"
"git.fromouter.space/crunchy-rocks/draw2d/samples/postscript"
)
func TestSampleAndroid(t *testing.T) {
test(t, android.Main)
}
// TODO: FillString: w (width) is incorrect
func TestSampleGeometry(t *testing.T) {
// Set the global folder for searching fonts
// The pdf backend needs for every ttf file its corresponding
// json/.z file which is generated by gofpdf/makefont.
draw2d.SetFontFolder("../resource/font")
test(t, geometry.Main)
}
func TestSampleGopher(t *testing.T) {
test(t, gopher.Main)
}
func TestSampleGopher2(t *testing.T) {
test(t, gopher2.Main)
}
func TestSampleHelloWorld(t *testing.T) {
// Set the global folder for searching fonts
// The pdf backend needs for every ttf file its corresponding
// json/.z file which is generated by gofpdf/makefont.
draw2d.SetFontFolder("../resource/font")
test(t, helloworld.Main)
}
func TestSampleFrameImage(t *testing.T) {
test(t, frameimage.Main)
}
func TestSampleLine(t *testing.T) {
test(t, line.Main)
}
func TestSampleLineCap(t *testing.T) {
test(t, linecapjoin.Main)
}
func TestSamplePostscript(t *testing.T) {
test(t, postscript.Main)
}

172
draw2dsvg/svg.go Normal file
View file

@ -0,0 +1,172 @@
// Copyright 2015 The draw2d Authors. All rights reserved.
// created: 16/12/2017 by Drahoslav Bednář
package draw2dsvg
import (
"encoding/xml"
)
/* svg elements */
type FontMode int
// Modes of font handling in svg
const (
// Does nothing special
// Makes sense only for common system fonts
SysFontMode FontMode = 1 << iota
// Links font files in css def
// Requires distribution of font files with outputed svg
LinkFontMode // TODO implement
// Embeds glyphs definition in svg file itself in svg font format
// Has poor browser support
SvgFontMode
// Embeds font definiton in svg file itself in woff format as part of css def
CssFontMode // TODO implement
// Converts texts to paths
PathFontMode
)
type Svg struct {
XMLName xml.Name `xml:"svg"`
Xmlns string `xml:"xmlns,attr"`
Fonts []*Font `xml:"defs>font"`
Masks []*Mask `xml:"defs>mask"`
Groups []*Group `xml:"g"`
FontMode FontMode `xml:"-"`
FillStroke
}
func NewSvg() *Svg {
return &Svg{
Xmlns: "http://www.w3.org/2000/svg",
FillStroke: FillStroke{Fill: "none", Stroke: "none"},
FontMode: PathFontMode,
}
}
type Group struct {
FillStroke
Transform string `xml:"transform,attr,omitempty"`
Groups []*Group `xml:"g"`
Paths []*Path `xml:"path"`
Texts []*Text `xml:"text"`
Image *Image `xml:"image"`
Mask string `xml:"mask,attr,omitempty"`
}
type Path struct {
FillStroke
Desc string `xml:"d,attr"`
}
type Text struct {
FillStroke
Position
FontSize float64 `xml:"font-size,attr,omitempty"`
FontFamily string `xml:"font-family,attr,omitempty"`
Text string `xml:",innerxml"`
Style string `xml:"style,attr,omitempty"`
}
type Image struct {
Position
Dimension
Href string `xml:"href,attr"`
}
type Mask struct {
Identity
Position
Dimension
}
type Rect struct {
Position
Dimension
FillStroke
}
func (m Mask) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
bigRect := Rect{}
bigRect.X, bigRect.Y = 0, 0
bigRect.Width, bigRect.Height = "100%", "100%"
bigRect.Fill = "#fff"
rect := Rect{}
rect.X, rect.Y = m.X, m.Y
rect.Width, rect.Height = m.Width, m.Height
rect.Fill = "#000"
return e.EncodeElement(struct {
XMLName xml.Name `xml:"mask"`
Rects [2]Rect `xml:"rect"`
Id string `xml:"id,attr"`
}{
Rects: [2]Rect{bigRect, rect},
Id: m.Id,
}, start)
}
/* font related elements */
type Font struct {
Identity
Face *Face `xml:"font-face"`
Glyphs []*Glyph `xml:"glyph"`
}
type Face struct {
Family string `xml:"font-family,attr"`
Units int `xml:"units-per-em,attr"`
HorizAdvX float64 `xml:"horiz-adv-x,attr"`
// TODO add other attrs, like style, variant, weight...
}
type Glyph struct {
Rune Rune `xml:"unicode,attr"`
Desc string `xml:"d,attr"`
HorizAdvX float64 `xml:"horiz-adv-x,attr"`
}
type Rune rune
func (r Rune) MarshalXMLAttr(name xml.Name) (xml.Attr, error) {
return xml.Attr{
Name: name,
Value: string(rune(r)),
}, nil
}
/* shared attrs */
type Identity struct {
Id string `xml:"id,attr"`
Name string `xml:"name,attr"`
}
type Position struct {
X float64 `xml:"x,attr,omitempty"`
Y float64 `xml:"y,attr,omitempty"`
}
type Dimension struct {
Width string `xml:"width,attr"`
Height string `xml:"height,attr"`
}
type FillStroke struct {
Fill string `xml:"fill,attr,omitempty"`
FillRule string `xml:"fill-rule,attr,omitempty"`
Stroke string `xml:"stroke,attr,omitempty"`
StrokeWidth string `xml:"stroke-width,attr,omitempty"`
StrokeLinecap string `xml:"stroke-linecap,attr,omitempty"`
StrokeLinejoin string `xml:"stroke-linejoin,attr,omitempty"`
StrokeDasharray string `xml:"stroke-dasharray,attr,omitempty"`
StrokeDashoffset string `xml:"stroke-dashoffset,attr,omitempty"`
}

32
draw2dsvg/test_test.go Normal file
View file

@ -0,0 +1,32 @@
// Copyright 2015 The draw2d Authors. All rights reserved.
// created: 16/12/2017 by Drahoslav Bednář
// Package draw2dsvg_test gives test coverage with the command:
// go test -cover ./... | grep -v "no test"
// (It should be run from its parent draw2d directory.)
package draw2dsvg_test
import (
"testing"
"git.fromouter.space/crunchy-rocks/draw2d"
"git.fromouter.space/crunchy-rocks/draw2d/draw2dsvg"
)
type sample func(gc draw2d.GraphicContext, ext string) (string, error)
func test(t *testing.T, draw sample) {
// Initialize the graphic context on an pdf document
dest := draw2dsvg.NewSvg()
gc := draw2dsvg.NewGraphicContext(dest)
// Draw sample
output, err := draw(gc, "svg")
if err != nil {
t.Errorf("Drawing %q failed: %v", output, err)
return
}
err = draw2dsvg.SaveToSvgFile(output, dest)
if err != nil {
t.Errorf("Saving %q failed: %v", output, err)
}
}

83
draw2dsvg/text.go Normal file
View file

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

58
draw2dsvg/xml_test.go Normal file
View file

@ -0,0 +1,58 @@
// Copyright 2015 The draw2d Authors. All rights reserved.
// created: 16/12/2017 by Drahoslav Bednář
// Package draw2dsvg_test gives test coverage with the command:
// go test -cover ./... | grep -v "no test"
// (It should be run from its parent draw2d directory.)
package draw2dsvg
import (
"encoding/xml"
"testing"
)
// Test basic encoding of svg/xml elements
func TestXml(t *testing.T) {
svg := NewSvg()
svg.Groups = []*Group{&Group{
Groups: []*Group{
&Group{}, // nested groups
&Group{},
},
Texts: []*Text{
&Text{Text: "Hello"}, // text
&Text{Text: "world", Style: "opacity: 0.5"}, // text with style
},
Paths: []*Path{
&Path{Desc: "M100,200 C100,100 250,100 250,200 S400,300 400,200"}, // simple path
&Path{}, // empty path
},
}}
expectedOut := `<svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke="none">
<defs></defs>
<g>
<g></g>
<g></g>
<path d="M100,200 C100,100 250,100 250,200 S400,300 400,200"></path>
<path d=""></path>
<text>Hello</text>
<text style="opacity: 0.5">world</text>
</g>
</svg>`
out, err := xml.MarshalIndent(svg, "", " ")
if err != nil {
t.Error(err)
}
if string(out) != expectedOut {
t.Errorf("svg output is not as expected\n"+
"got:\n%s\n\n"+
"want:\n%s\n",
string(out),
expectedOut,
)
}
}

186
font.go
View file

@ -6,18 +6,16 @@ package draw2d
import ( import (
"io/ioutil" "io/ioutil"
"log" "log"
"path"
"path/filepath" "path/filepath"
"code.google.com/p/freetype-go/freetype/truetype" "sync"
)
"git.fromouter.space/crunchy-rocks/freetype/truetype"
var (
fontFolder = "../resource/font/"
fonts = make(map[string]*truetype.Font)
fontNamer FontFileNamer = FontFileName
) )
// FontStyle defines bold and italic styles for the font
// It is possible to combine values for mixed styles, eg.
// FontData.Style = FontStyleBold | FontStyleItalic
type FontStyle byte type FontStyle byte
const ( const (
@ -66,41 +64,167 @@ func FontFileName(fontData FontData) string {
} }
func RegisterFont(fontData FontData, font *truetype.Font) { func RegisterFont(fontData FontData, font *truetype.Font) {
fonts[fontNamer(fontData)] = font fontCache.Store(fontData, font)
} }
func GetFont(fontData FontData) *truetype.Font { func GetFont(fontData FontData) (font *truetype.Font) {
fontFileName := fontNamer(fontData) var err error
font := fonts[fontFileName]
if font != nil { if font, err = fontCache.Load(fontData); err != nil {
return font log.Println(err)
} }
fonts[fontFileName] = loadFont(fontFileName)
return fonts[fontFileName] return
} }
func GetFontFolder() string { func GetFontFolder() string {
return fontFolder return defaultFonts.folder
} }
func SetFontFolder(folder string) { func SetFontFolder(folder string) {
fontFolder = filepath.Clean(folder) defaultFonts.setFolder(filepath.Clean(folder))
}
func GetGlobalFontCache() FontCache {
return fontCache
} }
func SetFontNamer(fn FontFileNamer) { func SetFontNamer(fn FontFileNamer) {
fontNamer = fn defaultFonts.setNamer(fn)
} }
func loadFont(fontFileName string) *truetype.Font { // Types implementing this interface can be passed to SetFontCache to change the
fontBytes, err := ioutil.ReadFile(path.Join(fontFolder, fontFileName)) // way fonts are being stored and retrieved.
if err != nil { type FontCache interface {
log.Println(err) // Loads a truetype font represented by the FontData object passed as
return nil // argument.
} // The method returns an error if the font could not be loaded, either
font, err := truetype.Parse(fontBytes) // because it didn't exist or the resource it was loaded from was corrupted.
if err != nil { Load(FontData) (*truetype.Font, error)
log.Println(err)
return nil // Sets the truetype font that will be returned by Load when given the font
} // data passed as first argument.
return font 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
)

68
gc.go
View file

@ -8,52 +8,80 @@ import (
"image/color" "image/color"
) )
// FillRule defines the type for fill rules
type FillRule int
const (
// FillRuleEvenOdd defines the even odd filling rule
FillRuleEvenOdd FillRule = iota
// FillRuleWinding defines the non zero winding rule
FillRuleWinding
)
// GraphicContext describes the interface for the various backends (images, pdf, opengl, ...) // GraphicContext describes the interface for the various backends (images, pdf, opengl, ...)
type GraphicContext interface { type GraphicContext interface {
Path // PathBuilder describes the interface for path drawing
// Create a new path PathBuilder
// BeginPath creates a new path
BeginPath() BeginPath()
GetMatrixTransform() MatrixTransform // GetPath copies the current path, then returns it
SetMatrixTransform(tr MatrixTransform) GetPath() Path
ComposeMatrixTransform(tr MatrixTransform) // GetMatrixTransform returns the current transformation matrix
GetMatrixTransform() Matrix
// SetMatrixTransform sets the current transformation matrix
SetMatrixTransform(tr Matrix)
// ComposeMatrixTransform composes the current transformation matrix with tr
ComposeMatrixTransform(tr Matrix)
// Rotate applies a rotation to the current transformation matrix. angle is in radian.
Rotate(angle float64) Rotate(angle float64)
// Translate applies a translation to the current transformation matrix.
Translate(tx, ty float64) Translate(tx, ty float64)
// Scale applies a scale to the current transformation matrix.
Scale(sx, sy float64) Scale(sx, sy float64)
// SetStrokeColor sets the current stroke color
SetStrokeColor(c color.Color) SetStrokeColor(c color.Color)
// SetFillColor sets the current fill color
SetFillColor(c color.Color) SetFillColor(c color.Color)
// SetFillRule sets the current fill rule
SetFillRule(f FillRule) SetFillRule(f FillRule)
// SetLineWidth sets the current line width
SetLineWidth(lineWidth float64) SetLineWidth(lineWidth float64)
SetLineCap(cap Cap) // SetLineCap sets the current line cap
SetLineJoin(join Join) SetLineCap(cap LineCap)
// SetLineJoin sets the current line join
SetLineJoin(join LineJoin)
// SetLineDash sets the current dash
SetLineDash(dash []float64, dashOffset float64) SetLineDash(dash []float64, dashOffset float64)
// SetFontSize sets the current font size
SetFontSize(fontSize float64) SetFontSize(fontSize float64)
// GetFontSize gets the current font size
GetFontSize() float64 GetFontSize() float64
// SetFontData sets the current FontData
SetFontData(fontData FontData) SetFontData(fontData FontData)
// GetFontData gets the current FontData
GetFontData() FontData GetFontData() FontData
// GetFontName gets the current FontData as a string
GetFontName() string
// DrawImage draws the raster image in the current canvas
DrawImage(image image.Image) DrawImage(image image.Image)
// Save the context and push it to the context stack
Save() Save()
// Restore remove the current context and restore the last one
Restore() Restore()
// Clear fills the current canvas with a default transparent color
Clear() Clear()
// ClearRect fills the specified rectangle with a default transparent color
ClearRect(x1, y1, x2, y2 int) ClearRect(x1, y1, x2, y2 int)
// SetDPI sets the current DPI
SetDPI(dpi int) SetDPI(dpi int)
// GetDPI gets the current DPI
GetDPI() int GetDPI() int
// GetStringBounds gets pixel bounds(dimensions) of given string
GetStringBounds(s string) (left, top, right, bottom float64) GetStringBounds(s string) (left, top, right, bottom float64)
// CreateStringPath creates a path from the string s at x, y
CreateStringPath(text string, x, y float64) (cursor float64) CreateStringPath(text string, x, y float64) (cursor float64)
// FillString draws the text at point (0, 0)
FillString(text string) (cursor float64) FillString(text string) (cursor float64)
// FillStringAt draws the text at the specified point (x, y)
FillStringAt(text string, x, y float64) (cursor float64) FillStringAt(text string, x, y float64) (cursor float64)
// StrokeString draws the contour of the text at point (0, 0)
StrokeString(text string) (cursor float64) StrokeString(text string) (cursor float64)
// StrokeStringAt draws the contour of the text at point (x, y)
StrokeStringAt(text string, x, y float64) (cursor float64) StrokeStringAt(text string, x, y float64) (cursor float64)
Stroke(paths ...*PathStorage) // Stroke strokes the paths with the color specified by SetStrokeColor
Fill(paths ...*PathStorage) Stroke(paths ...*Path)
FillStroke(paths ...*PathStorage) // Fill fills the paths with the color specified by SetFillColor
Fill(paths ...*Path)
// FillStroke first fills the paths and than strokes them
FillStroke(paths ...*Path)
} }

13
go.mod Normal file
View file

@ -0,0 +1,13 @@
module git.fromouter.space/crunchy-rocks/draw2d
require (
git.fromouter.space/crunchy-rocks/emoji v0.0.0-20181116142102-2188aadaf093
git.fromouter.space/crunchy-rocks/freetype v0.0.0-20181116104610-3115318f2577
github.com/go-gl/gl v0.0.0-20180407155706-68e253793080
github.com/go-gl/glfw v0.0.0-20180426074136-46a8d530c326
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
github.com/jung-kurt/gofpdf v1.0.0
github.com/llgcode/draw2d v0.0.0-20180825133448-f52c8a71aff0
github.com/llgcode/ps v0.0.0-20150911083025-f1443b32eedb
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81
)

358
image.go
View file

@ -1,358 +0,0 @@
// Copyright 2010 The draw2d Authors. All rights reserved.
// created: 21/11/2010 by Laurent Le Goff
package draw2d
import (
"errors"
"image"
"image/color"
"image/draw"
"log"
"math"
"code.google.com/p/freetype-go/freetype/raster"
"code.google.com/p/freetype-go/freetype/truetype"
)
type Painter interface {
raster.Painter
SetColor(color color.Color)
}
var (
defaultFontData = FontData{"luxi", FontFamilySans, FontStyleNormal}
)
type ImageGraphicContext struct {
*StackGraphicContext
img draw.Image
painter Painter
fillRasterizer *raster.Rasterizer
strokeRasterizer *raster.Rasterizer
glyphBuf *truetype.GlyphBuf
DPI int
}
// NewGraphicContext creates a new Graphic context from an image.
func NewGraphicContext(img draw.Image) *ImageGraphicContext {
var painter Painter
switch selectImage := img.(type) {
case *image.RGBA:
painter = raster.NewRGBAPainter(selectImage)
default:
panic("Image type not supported")
}
return NewGraphicContextWithPainter(img, painter)
}
// NewGraphicContextWithPainter creates a new Graphic context from an image and a Painter (see Freetype-go)
func NewGraphicContextWithPainter(img draw.Image, painter Painter) *ImageGraphicContext {
width, height := img.Bounds().Dx(), img.Bounds().Dy()
dpi := 92
gc := &ImageGraphicContext{
NewStackGraphicContext(),
img,
painter,
raster.NewRasterizer(width, height),
raster.NewRasterizer(width, height),
truetype.NewGlyphBuf(),
dpi,
}
return gc
}
func (gc *ImageGraphicContext) GetDPI() int {
return gc.DPI
}
func (gc *ImageGraphicContext) Clear() {
width, height := gc.img.Bounds().Dx(), gc.img.Bounds().Dy()
gc.ClearRect(0, 0, width, height)
}
func (gc *ImageGraphicContext) ClearRect(x1, y1, x2, y2 int) {
imageColor := image.NewUniform(gc.Current.FillColor)
draw.Draw(gc.img, image.Rect(x1, y1, x2, y2), imageColor, image.ZP, draw.Over)
}
func (gc *ImageGraphicContext) DrawImage(img image.Image) {
DrawImage(img, gc.img, gc.Current.Tr, draw.Over, BilinearFilter)
}
func (gc *ImageGraphicContext) FillString(text string) (cursor float64) {
return gc.FillStringAt(text, 0, 0)
}
func (gc *ImageGraphicContext) FillStringAt(text string, x, y float64) (cursor float64) {
width := gc.CreateStringPath(text, x, y)
gc.Fill()
return width
}
func (gc *ImageGraphicContext) StrokeString(text string) (cursor float64) {
return gc.StrokeStringAt(text, 0, 0)
}
func (gc *ImageGraphicContext) StrokeStringAt(text string, x, y float64) (cursor float64) {
width := gc.CreateStringPath(text, x, y)
gc.Stroke()
return width
}
func (gc *ImageGraphicContext) loadCurrentFont() (*truetype.Font, error) {
font := GetFont(gc.Current.FontData)
if font == nil {
font = GetFont(defaultFontData)
}
if font == nil {
return nil, errors.New("No font set, and no default font available.")
}
gc.SetFont(font)
gc.SetFontSize(gc.Current.FontSize)
return font, nil
}
func fUnitsToFloat64(x int32) float64 {
scaled := x << 2
return float64(scaled/256) + float64(scaled%256)/256.0
}
// p is a truetype.Point measured in FUnits and positive Y going upwards.
// The returned value is the same thing measured in floating point and positive Y
// going downwards.
func pointToF64Point(p truetype.Point) (x, y float64) {
return fUnitsToFloat64(p.X), -fUnitsToFloat64(p.Y)
}
// drawContour draws the given closed contour at the given sub-pixel offset.
func (gc *ImageGraphicContext) drawContour(ps []truetype.Point, dx, dy float64) {
if len(ps) == 0 {
return
}
startX, startY := pointToF64Point(ps[0])
gc.MoveTo(startX+dx, startY+dy)
q0X, q0Y, on0 := startX, startY, true
for _, p := range ps[1:] {
qX, qY := pointToF64Point(p)
on := p.Flags&0x01 != 0
if on {
if on0 {
gc.LineTo(qX+dx, qY+dy)
} else {
gc.QuadCurveTo(q0X+dx, q0Y+dy, qX+dx, qY+dy)
}
} else {
if on0 {
// No-op.
} else {
midX := (q0X + qX) / 2
midY := (q0Y + qY) / 2
gc.QuadCurveTo(q0X+dx, q0Y+dy, midX+dx, midY+dy)
}
}
q0X, q0Y, on0 = qX, qY, on
}
// Close the curve.
if on0 {
gc.LineTo(startX+dx, startY+dy)
} else {
gc.QuadCurveTo(q0X+dx, q0Y+dy, startX+dx, startY+dy)
}
}
func (gc *ImageGraphicContext) drawGlyph(glyph truetype.Index, dx, dy float64) error {
if err := gc.glyphBuf.Load(gc.Current.Font, int32(gc.Current.Scale), glyph, truetype.NoHinting); err != nil {
return err
}
e0 := 0
for _, e1 := range gc.glyphBuf.End {
gc.drawContour(gc.glyphBuf.Point[e0:e1], dx, dy)
e0 = e1
}
return nil
}
// CreateStringPath creates a path from the string s at x, y, and returns the string width.
// The text is placed so that the left edge of the em square of the first character of s
// and the baseline intersect at x, y. The majority of the affected pixels will be
// above and to the right of the point, but some may be below or to the left.
// For example, drawing a string that starts with a 'J' in an italic font may
// affect pixels below and left of the point.
func (gc *ImageGraphicContext) CreateStringPath(s string, x, y float64) float64 {
font, err := gc.loadCurrentFont()
if err != nil {
log.Println(err)
return 0.0
}
startx := x
prev, hasPrev := truetype.Index(0), false
for _, rune := range s {
index := font.Index(rune)
if hasPrev {
x += fUnitsToFloat64(font.Kerning(int32(gc.Current.Scale), prev, index))
}
err := gc.drawGlyph(index, x, y)
if err != nil {
log.Println(err)
return startx - x
}
x += fUnitsToFloat64(font.HMetric(int32(gc.Current.Scale), index).AdvanceWidth)
prev, hasPrev = index, true
}
return x - startx
}
// GetStringBounds returns the approximate pixel bounds of the string s at x, y.
// The left edge of the em square of the first character of s
// and the baseline intersect at 0, 0 in the returned coordinates.
// Therefore the top and left coordinates may well be negative.
func (gc *ImageGraphicContext) GetStringBounds(s string) (left, top, right, bottom float64) {
font, err := gc.loadCurrentFont()
if err != nil {
log.Println(err)
return 0, 0, 0, 0
}
top, left, bottom, right = 10e6, 10e6, -10e6, -10e6
cursor := 0.0
prev, hasPrev := truetype.Index(0), false
for _, rune := range s {
index := font.Index(rune)
if hasPrev {
cursor += fUnitsToFloat64(font.Kerning(int32(gc.Current.Scale), prev, index))
}
if err := gc.glyphBuf.Load(gc.Current.Font, int32(gc.Current.Scale), index, truetype.NoHinting); err != nil {
log.Println(err)
return 0, 0, 0, 0
}
e0 := 0
for _, e1 := range gc.glyphBuf.End {
ps := gc.glyphBuf.Point[e0:e1]
for _, p := range ps {
x, y := pointToF64Point(p)
top = math.Min(top, y)
bottom = math.Max(bottom, y)
left = math.Min(left, x+cursor)
right = math.Max(right, x+cursor)
}
}
cursor += fUnitsToFloat64(font.HMetric(int32(gc.Current.Scale), index).AdvanceWidth)
prev, hasPrev = index, true
}
return left, top, right, bottom
}
// recalc recalculates scale and bounds values from the font size, screen
// resolution and font metrics, and invalidates the glyph cache.
func (gc *ImageGraphicContext) recalc() {
gc.Current.Scale = gc.Current.FontSize * float64(gc.DPI) * (64.0 / 72.0)
}
// SetDPI sets the screen resolution in dots per inch.
func (gc *ImageGraphicContext) SetDPI(dpi int) {
gc.DPI = dpi
gc.recalc()
}
// SetFont sets the font used to draw text.
func (gc *ImageGraphicContext) SetFont(font *truetype.Font) {
gc.Current.Font = font
}
// SetFontSize sets the font size in points (as in ``a 12 point font'').
func (gc *ImageGraphicContext) SetFontSize(fontSize float64) {
gc.Current.FontSize = fontSize
gc.recalc()
}
func (gc *ImageGraphicContext) paint(rasterizer *raster.Rasterizer, color color.Color) {
gc.painter.SetColor(color)
rasterizer.Rasterize(gc.painter)
rasterizer.Clear()
gc.Current.Path = NewPathStorage()
}
// Stroke strokes the paths with the color specified by SetStrokeColor
func (gc *ImageGraphicContext) Stroke(paths ...*PathStorage) {
paths = append(paths, gc.Current.Path)
gc.strokeRasterizer.UseNonZeroWinding = true
stroker := NewLineStroker(gc.Current.Cap, gc.Current.Join, NewVertexMatrixTransform(gc.Current.Tr, NewVertexAdder(gc.strokeRasterizer)))
stroker.HalfLineWidth = gc.Current.LineWidth / 2
var pathConverter *PathConverter
if gc.Current.Dash != nil && len(gc.Current.Dash) > 0 {
dasher := NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker)
pathConverter = NewPathConverter(dasher)
} else {
pathConverter = NewPathConverter(stroker)
}
pathConverter.ApproximationScale = gc.Current.Tr.GetScale()
pathConverter.Convert(paths...)
gc.paint(gc.strokeRasterizer, gc.Current.StrokeColor)
}
// Fill fills the paths with the color specified by SetFillColor
func (gc *ImageGraphicContext) Fill(paths ...*PathStorage) {
paths = append(paths, gc.Current.Path)
gc.fillRasterizer.UseNonZeroWinding = gc.Current.FillRule.UseNonZeroWinding()
/**** first method ****/
pathConverter := NewPathConverter(NewVertexMatrixTransform(gc.Current.Tr, NewVertexAdder(gc.fillRasterizer)))
pathConverter.ApproximationScale = gc.Current.Tr.GetScale()
pathConverter.Convert(paths...)
gc.paint(gc.fillRasterizer, gc.Current.FillColor)
}
// FillStroke first fills the paths and than strokes them
func (gc *ImageGraphicContext) FillStroke(paths ...*PathStorage) {
gc.fillRasterizer.UseNonZeroWinding = gc.Current.FillRule.UseNonZeroWinding()
gc.strokeRasterizer.UseNonZeroWinding = true
filler := NewVertexMatrixTransform(gc.Current.Tr, NewVertexAdder(gc.fillRasterizer))
stroker := NewLineStroker(gc.Current.Cap, gc.Current.Join, NewVertexMatrixTransform(gc.Current.Tr, NewVertexAdder(gc.strokeRasterizer)))
stroker.HalfLineWidth = gc.Current.LineWidth / 2
demux := NewDemuxConverter(filler, stroker)
paths = append(paths, gc.Current.Path)
pathConverter := NewPathConverter(demux)
pathConverter.ApproximationScale = gc.Current.Tr.GetScale()
pathConverter.Convert(paths...)
gc.paint(gc.fillRasterizer, gc.Current.FillColor)
gc.paint(gc.strokeRasterizer, gc.Current.StrokeColor)
}
func (f FillRule) UseNonZeroWinding() bool {
switch f {
case FillRuleEvenOdd:
return false
case FillRuleWinding:
return true
}
return false
}
func (c Cap) Convert() raster.Capper {
switch c {
case RoundCap:
return raster.RoundCapper
case ButtCap:
return raster.ButtCapper
case SquareCap:
return raster.SquareCapper
}
return raster.RoundCapper
}
func (j Join) Convert() raster.Joiner {
switch j {
case RoundJoin:
return raster.RoundJoiner
case BevelJoin:
return raster.BevelJoiner
}
return raster.RoundJoiner
}

52
math.go
View file

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

222
matrix.go Normal file
View file

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

4
output/draw2dkit/.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
# Ignore everything in this directory
*
# Except this file
!.gitignore

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 42 KiB

View file

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

220
path.go
View file

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

View file

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

View file

@ -1,173 +0,0 @@
// Copyright 2010 The draw2d Authors. All rights reserved.
// created: 06/12/2010 by Laurent Le Goff
package draw2d
import (
"math"
)
type PathConverter struct {
converter VertexConverter
ApproximationScale, AngleTolerance, CuspLimit float64
startX, startY, x, y float64
}
func NewPathConverter(converter VertexConverter) *PathConverter {
return &PathConverter{converter, 1, 0, 0, 0, 0, 0, 0}
}
func (c *PathConverter) Convert(paths ...*PathStorage) {
for _, path := range paths {
j := 0
for _, cmd := range path.Commands {
j = j + c.ConvertCommand(cmd, path.Vertices[j:]...)
}
c.converter.NextCommand(VertexStopCommand)
}
}
func (c *PathConverter) ConvertCommand(cmd PathCmd, vertices ...float64) int {
switch cmd {
case MoveTo:
c.x, c.y = vertices[0], vertices[1]
c.startX, c.startY = c.x, c.y
c.converter.NextCommand(VertexStopCommand)
c.converter.NextCommand(VertexStartCommand)
c.converter.Vertex(c.x, c.y)
return 2
case LineTo:
c.x, c.y = vertices[0], vertices[1]
if c.startX == c.x && c.startY == c.y {
c.converter.NextCommand(VertexCloseCommand)
}
c.converter.Vertex(c.x, c.y)
c.converter.NextCommand(VertexJoinCommand)
return 2
case QuadCurveTo:
quadraticBezier(c.converter, c.x, c.y, vertices[0], vertices[1], vertices[2], vertices[3], c.ApproximationScale, c.AngleTolerance)
c.x, c.y = vertices[2], vertices[3]
if c.startX == c.x && c.startY == c.y {
c.converter.NextCommand(VertexCloseCommand)
}
c.converter.Vertex(c.x, c.y)
return 4
case CubicCurveTo:
cubicBezier(c.converter, c.x, c.y, vertices[0], vertices[1], vertices[2], vertices[3], vertices[4], vertices[5], c.ApproximationScale, c.AngleTolerance, c.CuspLimit)
c.x, c.y = vertices[4], vertices[5]
if c.startX == c.x && c.startY == c.y {
c.converter.NextCommand(VertexCloseCommand)
}
c.converter.Vertex(c.x, c.y)
return 6
case ArcTo:
c.x, c.y = arc(c.converter, vertices[0], vertices[1], vertices[2], vertices[3], vertices[4], vertices[5], c.ApproximationScale)
if c.startX == c.x && c.startY == c.y {
c.converter.NextCommand(VertexCloseCommand)
}
c.converter.Vertex(c.x, c.y)
return 6
case Close:
c.converter.NextCommand(VertexCloseCommand)
c.converter.Vertex(c.startX, c.startY)
return 0
}
return 0
}
func (c *PathConverter) MoveTo(x, y float64) *PathConverter {
c.x, c.y = x, y
c.startX, c.startY = c.x, c.y
c.converter.NextCommand(VertexStopCommand)
c.converter.NextCommand(VertexStartCommand)
c.converter.Vertex(c.x, c.y)
return c
}
func (c *PathConverter) RMoveTo(dx, dy float64) *PathConverter {
c.MoveTo(c.x+dx, c.y+dy)
return c
}
func (c *PathConverter) LineTo(x, y float64) *PathConverter {
c.x, c.y = x, y
if c.startX == c.x && c.startY == c.y {
c.converter.NextCommand(VertexCloseCommand)
}
c.converter.Vertex(c.x, c.y)
c.converter.NextCommand(VertexJoinCommand)
return c
}
func (c *PathConverter) RLineTo(dx, dy float64) *PathConverter {
c.LineTo(c.x+dx, c.y+dy)
return c
}
func (c *PathConverter) QuadCurveTo(cx, cy, x, y float64) *PathConverter {
quadraticBezier(c.converter, c.x, c.y, cx, cy, x, y, c.ApproximationScale, c.AngleTolerance)
c.x, c.y = x, y
if c.startX == c.x && c.startY == c.y {
c.converter.NextCommand(VertexCloseCommand)
}
c.converter.Vertex(c.x, c.y)
return c
}
func (c *PathConverter) RQuadCurveTo(dcx, dcy, dx, dy float64) *PathConverter {
c.QuadCurveTo(c.x+dcx, c.y+dcy, c.x+dx, c.y+dy)
return c
}
func (c *PathConverter) CubicCurveTo(cx1, cy1, cx2, cy2, x, y float64) *PathConverter {
cubicBezier(c.converter, c.x, c.y, cx1, cy1, cx2, cy2, x, y, c.ApproximationScale, c.AngleTolerance, c.CuspLimit)
c.x, c.y = x, y
if c.startX == c.x && c.startY == c.y {
c.converter.NextCommand(VertexCloseCommand)
}
c.converter.Vertex(c.x, c.y)
return c
}
func (c *PathConverter) RCubicCurveTo(dcx1, dcy1, dcx2, dcy2, dx, dy float64) *PathConverter {
c.CubicCurveTo(c.x+dcx1, c.y+dcy1, c.x+dcx2, c.y+dcy2, c.x+dx, c.y+dy)
return c
}
func (c *PathConverter) ArcTo(cx, cy, rx, ry, startAngle, angle float64) *PathConverter {
endAngle := startAngle + angle
clockWise := true
if angle < 0 {
clockWise = false
}
// normalize
if clockWise {
for endAngle < startAngle {
endAngle += math.Pi * 2.0
}
} else {
for startAngle < endAngle {
startAngle += math.Pi * 2.0
}
}
startX := cx + math.Cos(startAngle)*rx
startY := cy + math.Sin(startAngle)*ry
c.MoveTo(startX, startY)
c.x, c.y = arc(c.converter, cx, cy, rx, ry, startAngle, angle, c.ApproximationScale)
if c.startX == c.x && c.startY == c.y {
c.converter.NextCommand(VertexCloseCommand)
}
c.converter.Vertex(c.x, c.y)
return c
}
func (c *PathConverter) RArcTo(dcx, dcy, rx, ry, startAngle, angle float64) *PathConverter {
c.ArcTo(c.x+dcx, c.y+dcy, rx, ry, startAngle, angle)
return c
}
func (c *PathConverter) Close() *PathConverter {
c.converter.NextCommand(VertexCloseCommand)
c.converter.Vertex(c.startX, c.startY)
return c
}

View file

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

View file

@ -1,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
}

View file

@ -1,320 +0,0 @@
// Copyright 2011 The draw2d Authors. All rights reserved.
// created: 27/05/2011 by Laurent Le Goff
package raster
import (
"image"
"image/color"
"unsafe"
)
const (
SUBPIXEL_SHIFT = 3
SUBPIXEL_COUNT = 1 << SUBPIXEL_SHIFT
)
var SUBPIXEL_OFFSETS = SUBPIXEL_OFFSETS_SAMPLE_8_FIXED
type SUBPIXEL_DATA uint8
type NON_ZERO_MASK_DATA_UNIT uint8
type Rasterizer8BitsSample struct {
MaskBuffer []SUBPIXEL_DATA
WindingBuffer []NON_ZERO_MASK_DATA_UNIT
Width int
BufferWidth int
Height int
ClipBound [4]float64
RemappingMatrix [6]float64
}
/* width and height define the maximum output size for the filler.
* The filler will output to larger bitmaps as well, but the output will
* be cropped.
*/
func NewRasterizer8BitsSample(width, height int) *Rasterizer8BitsSample {
var r Rasterizer8BitsSample
// Scale the coordinates by SUBPIXEL_COUNT in vertical direction
// The sampling point for the sub-pixel is at the top right corner. This
// adjustment moves it to the pixel center.
r.RemappingMatrix = [6]float64{1, 0, 0, SUBPIXEL_COUNT, 0.5 / SUBPIXEL_COUNT, -0.5 * SUBPIXEL_COUNT}
r.Width = width
r.Height = height
// The buffer used for filling needs to be one pixel wider than the bitmap.
// This is because the end flag that turns the fill of is the first pixel
// after the actually drawn edge.
r.BufferWidth = width + 1
r.MaskBuffer = make([]SUBPIXEL_DATA, r.BufferWidth*height)
r.WindingBuffer = make([]NON_ZERO_MASK_DATA_UNIT, r.BufferWidth*height*SUBPIXEL_COUNT)
r.ClipBound = clip(0, 0, width, height, SUBPIXEL_COUNT)
return &r
}
func clip(x, y, width, height, scale int) [4]float64 {
var clipBound [4]float64
offset := 0.99 / float64(scale)
clipBound[0] = float64(x) + offset
clipBound[2] = float64(x+width) - offset
clipBound[1] = float64(y * scale)
clipBound[3] = float64((y + height) * scale)
return clipBound
}
func intersect(r1, r2 [4]float64) [4]float64 {
if r1[0] < r2[0] {
r1[0] = r2[0]
}
if r1[2] > r2[2] {
r1[2] = r2[2]
}
if r1[0] > r1[2] {
r1[0] = r1[2]
}
if r1[1] < r2[1] {
r1[1] = r2[1]
}
if r1[3] > r2[3] {
r1[3] = r2[3]
}
if r1[1] > r1[3] {
r1[1] = r1[3]
}
return r1
}
func (r *Rasterizer8BitsSample) RenderEvenOdd(img *image.RGBA, color *color.RGBA, polygon *Polygon, tr [6]float64) {
// memset 0 the mask buffer
r.MaskBuffer = make([]SUBPIXEL_DATA, r.BufferWidth*r.Height)
// inline matrix multiplication
transform := [6]float64{
tr[0]*r.RemappingMatrix[0] + tr[1]*r.RemappingMatrix[2],
tr[1]*r.RemappingMatrix[3] + tr[0]*r.RemappingMatrix[1],
tr[2]*r.RemappingMatrix[0] + tr[3]*r.RemappingMatrix[2],
tr[3]*r.RemappingMatrix[3] + tr[2]*r.RemappingMatrix[1],
tr[4]*r.RemappingMatrix[0] + tr[5]*r.RemappingMatrix[2] + r.RemappingMatrix[4],
tr[5]*r.RemappingMatrix[3] + tr[4]*r.RemappingMatrix[1] + r.RemappingMatrix[5],
}
clipRect := clip(img.Bounds().Min.X, img.Bounds().Min.Y, img.Bounds().Dx(), img.Bounds().Dy(), SUBPIXEL_COUNT)
clipRect = intersect(clipRect, r.ClipBound)
p := 0
l := len(*polygon) / 2
var edges [32]PolygonEdge
for p < l {
edgeCount := polygon.getEdges(p, 16, edges[:], transform, clipRect)
for k := 0; k < edgeCount; k++ {
r.addEvenOddEdge(&edges[k])
}
p += 16
}
r.fillEvenOdd(img, color, clipRect)
}
//! Adds an edge to be used with even-odd fill.
func (r *Rasterizer8BitsSample) addEvenOddEdge(edge *PolygonEdge) {
x := Fix(edge.X * FIXED_FLOAT_COEF)
slope := Fix(edge.Slope * FIXED_FLOAT_COEF)
slopeFix := Fix(0)
if edge.LastLine-edge.FirstLine >= SLOPE_FIX_STEP {
slopeFix = Fix(edge.Slope*SLOPE_FIX_STEP*FIXED_FLOAT_COEF) - slope<<SLOPE_FIX_SHIFT
}
var mask SUBPIXEL_DATA
var ySub uint32
var xp, yLine int
for y := edge.FirstLine; y <= edge.LastLine; y++ {
ySub = uint32(y & (SUBPIXEL_COUNT - 1))
xp = int((x + SUBPIXEL_OFFSETS[ySub]) >> FIXED_SHIFT)
mask = SUBPIXEL_DATA(1 << ySub)
yLine = y >> SUBPIXEL_SHIFT
r.MaskBuffer[yLine*r.BufferWidth+xp] ^= mask
x += slope
if y&SLOPE_FIX_MASK == 0 {
x += slopeFix
}
}
}
//! Adds an edge to be used with non-zero winding fill.
func (r *Rasterizer8BitsSample) addNonZeroEdge(edge *PolygonEdge) {
x := Fix(edge.X * FIXED_FLOAT_COEF)
slope := Fix(edge.Slope * FIXED_FLOAT_COEF)
slopeFix := Fix(0)
if edge.LastLine-edge.FirstLine >= SLOPE_FIX_STEP {
slopeFix = Fix(edge.Slope*SLOPE_FIX_STEP*FIXED_FLOAT_COEF) - slope<<SLOPE_FIX_SHIFT
}
var mask SUBPIXEL_DATA
var ySub uint32
var xp, yLine int
winding := NON_ZERO_MASK_DATA_UNIT(edge.Winding)
for y := edge.FirstLine; y <= edge.LastLine; y++ {
ySub = uint32(y & (SUBPIXEL_COUNT - 1))
xp = int((x + SUBPIXEL_OFFSETS[ySub]) >> FIXED_SHIFT)
mask = SUBPIXEL_DATA(1 << ySub)
yLine = y >> SUBPIXEL_SHIFT
r.MaskBuffer[yLine*r.BufferWidth+xp] |= mask
r.WindingBuffer[(yLine*r.BufferWidth+xp)*SUBPIXEL_COUNT+int(ySub)] += winding
x += slope
if y&SLOPE_FIX_MASK == 0 {
x += slopeFix
}
}
}
// Renders the mask to the canvas with even-odd fill.
func (r *Rasterizer8BitsSample) fillEvenOdd(img *image.RGBA, color *color.RGBA, clipBound [4]float64) {
var x, y uint32
minX := uint32(clipBound[0])
maxX := uint32(clipBound[2])
minY := uint32(clipBound[1]) >> SUBPIXEL_SHIFT
maxY := uint32(clipBound[3]) >> SUBPIXEL_SHIFT
//pixColor := (uint32(color.R) << 24) | (uint32(color.G) << 16) | (uint32(color.B) << 8) | uint32(color.A)
pixColor := (*uint32)(unsafe.Pointer(color))
cs1 := *pixColor & 0xff00ff
cs2 := *pixColor >> 8 & 0xff00ff
stride := uint32(img.Stride)
var mask SUBPIXEL_DATA
for y = minY; y < maxY; y++ {
tp := img.Pix[y*stride:]
mask = 0
for x = minX; x <= maxX; x++ {
p := (*uint32)(unsafe.Pointer(&tp[x]))
mask ^= r.MaskBuffer[y*uint32(r.BufferWidth)+x]
// 8bits
alpha := uint32(coverageTable[mask])
// 16bits
//alpha := uint32(coverageTable[mask & 0xff] + coverageTable[(mask >> 8) & 0xff])
// 32bits
//alpha := uint32(coverageTable[mask & 0xff] + coverageTable[(mask >> 8) & 0xff] + coverageTable[(mask >> 16) & 0xff] + coverageTable[(mask >> 24) & 0xff])
// alpha is in range of 0 to SUBPIXEL_COUNT
invAlpha := SUBPIXEL_COUNT - alpha
ct1 := *p & 0xff00ff * invAlpha
ct2 := *p >> 8 & 0xff00ff * invAlpha
ct1 = (ct1 + cs1*alpha) >> SUBPIXEL_SHIFT & 0xff00ff
ct2 = (ct2 + cs2*alpha) << (8 - SUBPIXEL_SHIFT) & 0xff00ff00
*p = ct1 + ct2
}
}
}
/*
* Renders the polygon with non-zero winding fill.
* param aTarget the target bitmap.
* param aPolygon the polygon to render.
* param aColor the color to be used for rendering.
* param aTransformation the transformation matrix.
*/
func (r *Rasterizer8BitsSample) RenderNonZeroWinding(img *image.RGBA, color *color.RGBA, polygon *Polygon, tr [6]float64) {
r.MaskBuffer = make([]SUBPIXEL_DATA, r.BufferWidth*r.Height)
r.WindingBuffer = make([]NON_ZERO_MASK_DATA_UNIT, r.BufferWidth*r.Height*SUBPIXEL_COUNT)
// inline matrix multiplication
transform := [6]float64{
tr[0]*r.RemappingMatrix[0] + tr[1]*r.RemappingMatrix[2],
tr[1]*r.RemappingMatrix[3] + tr[0]*r.RemappingMatrix[1],
tr[2]*r.RemappingMatrix[0] + tr[3]*r.RemappingMatrix[2],
tr[3]*r.RemappingMatrix[3] + tr[2]*r.RemappingMatrix[1],
tr[4]*r.RemappingMatrix[0] + tr[5]*r.RemappingMatrix[2] + r.RemappingMatrix[4],
tr[5]*r.RemappingMatrix[3] + tr[4]*r.RemappingMatrix[1] + r.RemappingMatrix[5],
}
clipRect := clip(img.Bounds().Min.X, img.Bounds().Min.Y, img.Bounds().Dx(), img.Bounds().Dy(), SUBPIXEL_COUNT)
clipRect = intersect(clipRect, r.ClipBound)
p := 0
l := len(*polygon) / 2
var edges [32]PolygonEdge
for p < l {
edgeCount := polygon.getEdges(p, 16, edges[:], transform, clipRect)
for k := 0; k < edgeCount; k++ {
r.addNonZeroEdge(&edges[k])
}
p += 16
}
r.fillNonZero(img, color, clipRect)
}
//! Renders the mask to the canvas with non-zero winding fill.
func (r *Rasterizer8BitsSample) fillNonZero(img *image.RGBA, color *color.RGBA, clipBound [4]float64) {
var x, y uint32
minX := uint32(clipBound[0])
maxX := uint32(clipBound[2])
minY := uint32(clipBound[1]) >> SUBPIXEL_SHIFT
maxY := uint32(clipBound[3]) >> SUBPIXEL_SHIFT
//pixColor := (uint32(color.R) << 24) | (uint32(color.G) << 16) | (uint32(color.B) << 8) | uint32(color.A)
pixColor := (*uint32)(unsafe.Pointer(color))
cs1 := *pixColor & 0xff00ff
cs2 := *pixColor >> 8 & 0xff00ff
stride := uint32(img.Stride)
var mask SUBPIXEL_DATA
var n uint32
var values [SUBPIXEL_COUNT]NON_ZERO_MASK_DATA_UNIT
for n = 0; n < SUBPIXEL_COUNT; n++ {
values[n] = 0
}
for y = minY; y < maxY; y++ {
tp := img.Pix[y*stride:]
mask = 0
for x = minX; x <= maxX; x++ {
p := (*uint32)(unsafe.Pointer(&tp[x]))
temp := r.MaskBuffer[y*uint32(r.BufferWidth)+x]
if temp != 0 {
var bit SUBPIXEL_DATA = 1
for n = 0; n < SUBPIXEL_COUNT; n++ {
if temp&bit != 0 {
t := values[n]
values[n] += r.WindingBuffer[(y*uint32(r.BufferWidth)+x)*SUBPIXEL_COUNT+n]
if (t == 0 || values[n] == 0) && t != values[n] {
mask ^= bit
}
}
bit <<= 1
}
}
// 8bits
alpha := uint32(coverageTable[mask])
// 16bits
//alpha := uint32(coverageTable[mask & 0xff] + coverageTable[(mask >> 8) & 0xff])
// 32bits
//alpha := uint32(coverageTable[mask & 0xff] + coverageTable[(mask >> 8) & 0xff] + coverageTable[(mask >> 16) & 0xff] + coverageTable[(mask >> 24) & 0xff])
// alpha is in range of 0 to SUBPIXEL_COUNT
invAlpha := uint32(SUBPIXEL_COUNT) - alpha
ct1 := *p & 0xff00ff * invAlpha
ct2 := *p >> 8 & 0xff00ff * invAlpha
ct1 = (ct1 + cs1*alpha) >> SUBPIXEL_SHIFT & 0xff00ff
ct2 = (ct2 + cs2*alpha) << (8 - SUBPIXEL_SHIFT) & 0xff00ff00
*p = ct1 + ct2
}
}
}

View file

@ -1,306 +0,0 @@
// Copyright 2011 The draw2d Authors. All rights reserved.
// created: 27/05/2011 by Laurent Le Goff
// +build ignore
package raster
import (
"image"
"image/color"
"unsafe"
)
const (
SUBPIXEL_SHIFT = 3
SUBPIXEL_COUNT = 1 << SUBPIXEL_SHIFT
)
var SUBPIXEL_OFFSETS = SUBPIXEL_OFFSETS_SAMPLE_8
type SUBPIXEL_DATA uint16
type NON_ZERO_MASK_DATA_UNIT uint8
type Rasterizer8BitsSample struct {
MaskBuffer []SUBPIXEL_DATA
WindingBuffer []NON_ZERO_MASK_DATA_UNIT
Width int
BufferWidth int
Height int
ClipBound [4]float64
RemappingMatrix [6]float64
}
/* width and height define the maximum output size for the filler.
* The filler will output to larger bitmaps as well, but the output will
* be cropped.
*/
func NewRasterizer8BitsSample(width, height int) *Rasterizer8BitsSample {
var r Rasterizer8BitsSample
// Scale the coordinates by SUBPIXEL_COUNT in vertical direction
// The sampling point for the sub-pixel is at the top right corner. This
// adjustment moves it to the pixel center.
r.RemappingMatrix = [6]float64{1, 0, 0, SUBPIXEL_COUNT, 0.5 / SUBPIXEL_COUNT, -0.5 * SUBPIXEL_COUNT}
r.Width = width
r.Height = height
// The buffer used for filling needs to be one pixel wider than the bitmap.
// This is because the end flag that turns the fill of is the first pixel
// after the actually drawn edge.
r.BufferWidth = width + 1
r.MaskBuffer = make([]SUBPIXEL_DATA, r.BufferWidth*height)
r.WindingBuffer = make([]NON_ZERO_MASK_DATA_UNIT, r.BufferWidth*height*SUBPIXEL_COUNT)
r.ClipBound = clip(0, 0, width, height, SUBPIXEL_COUNT)
return &r
}
func clip(x, y, width, height, scale int) [4]float64 {
var clipBound [4]float64
offset := 0.99 / float64(scale)
clipBound[0] = float64(x) + offset
clipBound[2] = float64(x+width) - offset
clipBound[1] = float64(y * scale)
clipBound[3] = float64((y + height) * scale)
return clipBound
}
func intersect(r1, r2 [4]float64) [4]float64 {
if r1[0] < r2[0] {
r1[0] = r2[0]
}
if r1[2] > r2[2] {
r1[2] = r2[2]
}
if r1[0] > r1[2] {
r1[0] = r1[2]
}
if r1[1] < r2[1] {
r1[1] = r2[1]
}
if r1[3] > r2[3] {
r1[3] = r2[3]
}
if r1[1] > r1[3] {
r1[1] = r1[3]
}
return r1
}
func (r *Rasterizer8BitsSample) RenderEvenOdd(img *image.RGBA, color *color.RGBA, polygon *Polygon, tr [6]float64) {
// memset 0 the mask buffer
r.MaskBuffer = make([]SUBPIXEL_DATA, r.BufferWidth*r.Height)
// inline matrix multiplication
transform := [6]float64{
tr[0]*r.RemappingMatrix[0] + tr[1]*r.RemappingMatrix[2],
tr[1]*r.RemappingMatrix[3] + tr[0]*r.RemappingMatrix[1],
tr[2]*r.RemappingMatrix[0] + tr[3]*r.RemappingMatrix[2],
tr[3]*r.RemappingMatrix[3] + tr[2]*r.RemappingMatrix[1],
tr[4]*r.RemappingMatrix[0] + tr[5]*r.RemappingMatrix[2] + r.RemappingMatrix[4],
tr[5]*r.RemappingMatrix[3] + tr[4]*r.RemappingMatrix[1] + r.RemappingMatrix[5],
}
clipRect := clip(img.Bounds().Min.X, img.Bounds().Min.Y, img.Bounds().Dx(), img.Bounds().Dy(), SUBPIXEL_COUNT)
clipRect = intersect(clipRect, r.ClipBound)
p := 0
l := len(*polygon) / 2
var edges [32]PolygonEdge
for p < l {
edgeCount := polygon.getEdges(p, 16, edges[:], transform, clipRect)
for k := 0; k < edgeCount; k++ {
r.addEvenOddEdge(&edges[k])
}
p += 16
}
r.fillEvenOdd(img, color, clipRect)
}
//! Adds an edge to be used with even-odd fill.
func (r *Rasterizer8BitsSample) addEvenOddEdge(edge *PolygonEdge) {
x := edge.X
slope := edge.Slope
var ySub, mask SUBPIXEL_DATA
var xp, yLine int
for y := edge.FirstLine; y <= edge.LastLine; y++ {
ySub = SUBPIXEL_DATA(y & (SUBPIXEL_COUNT - 1))
xp = int(x + SUBPIXEL_OFFSETS[ySub])
mask = SUBPIXEL_DATA(1 << ySub)
yLine = y >> SUBPIXEL_SHIFT
r.MaskBuffer[yLine*r.BufferWidth+xp] ^= mask
x += slope
}
}
// Renders the mask to the canvas with even-odd fill.
func (r *Rasterizer8BitsSample) fillEvenOdd(img *image.RGBA, color *color.RGBA, clipBound [4]float64) {
var x, y uint32
minX := uint32(clipBound[0])
maxX := uint32(clipBound[2])
minY := uint32(clipBound[1]) >> SUBPIXEL_SHIFT
maxY := uint32(clipBound[3]) >> SUBPIXEL_SHIFT
//pixColor := (uint32(color.R) << 24) | (uint32(color.G) << 16) | (uint32(color.B) << 8) | uint32(color.A)
pixColor := (*uint32)(unsafe.Pointer(color))
cs1 := *pixColor & 0xff00ff
cs2 := *pixColor >> 8 & 0xff00ff
stride := uint32(img.Stride)
var mask SUBPIXEL_DATA
for y = minY; y < maxY; y++ {
tp := img.Pix[y*stride:]
mask = 0
for x = minX; x <= maxX; x++ {
p := (*uint32)(unsafe.Pointer(&tp[x]))
mask ^= r.MaskBuffer[y*uint32(r.BufferWidth)+x]
// 8bits
alpha := uint32(coverageTable[mask])
// 16bits
//alpha := uint32(coverageTable[mask & 0xff] + coverageTable[(mask >> 8) & 0xff])
// 32bits
//alpha := uint32(coverageTable[mask & 0xff] + coverageTable[(mask >> 8) & 0xff] + coverageTable[(mask >> 16) & 0xff] + coverageTable[(mask >> 24) & 0xff])
// alpha is in range of 0 to SUBPIXEL_COUNT
invAlpha := uint32(SUBPIXEL_COUNT) - alpha
ct1 := *p & 0xff00ff * invAlpha
ct2 := *p >> 8 & 0xff00ff * invAlpha
ct1 = (ct1 + cs1*alpha) >> SUBPIXEL_SHIFT & 0xff00ff
ct2 = (ct2 + cs2*alpha) << (8 - SUBPIXEL_SHIFT) & 0xff00ff00
*p = ct1 + ct2
}
}
}
/*
* Renders the polygon with non-zero winding fill.
* param aTarget the target bitmap.
* param aPolygon the polygon to render.
* param aColor the color to be used for rendering.
* param aTransformation the transformation matrix.
*/
func (r *Rasterizer8BitsSample) RenderNonZeroWinding(img *image.RGBA, color *color.RGBA, polygon *Polygon, tr [6]float64) {
r.MaskBuffer = make([]SUBPIXEL_DATA, r.BufferWidth*r.Height)
r.WindingBuffer = make([]NON_ZERO_MASK_DATA_UNIT, r.BufferWidth*r.Height*SUBPIXEL_COUNT)
// inline matrix multiplication
transform := [6]float64{
tr[0]*r.RemappingMatrix[0] + tr[1]*r.RemappingMatrix[2],
tr[1]*r.RemappingMatrix[3] + tr[0]*r.RemappingMatrix[1],
tr[2]*r.RemappingMatrix[0] + tr[3]*r.RemappingMatrix[2],
tr[3]*r.RemappingMatrix[3] + tr[2]*r.RemappingMatrix[1],
tr[4]*r.RemappingMatrix[0] + tr[5]*r.RemappingMatrix[2] + r.RemappingMatrix[4],
tr[5]*r.RemappingMatrix[3] + tr[4]*r.RemappingMatrix[1] + r.RemappingMatrix[5],
}
clipRect := clip(img.Bounds().Min.X, img.Bounds().Min.Y, img.Bounds().Dx(), img.Bounds().Dy(), SUBPIXEL_COUNT)
clipRect = intersect(clipRect, r.ClipBound)
p := 0
l := len(*polygon) / 2
var edges [32]PolygonEdge
for p < l {
edgeCount := polygon.getEdges(p, 16, edges[:], transform, clipRect)
for k := 0; k < edgeCount; k++ {
r.addNonZeroEdge(&edges[k])
}
p += 16
}
r.fillNonZero(img, color, clipRect)
}
//! Adds an edge to be used with non-zero winding fill.
func (r *Rasterizer8BitsSample) addNonZeroEdge(edge *PolygonEdge) {
x := edge.X
slope := edge.Slope
var ySub, mask SUBPIXEL_DATA
var xp, yLine int
winding := NON_ZERO_MASK_DATA_UNIT(edge.Winding)
for y := edge.FirstLine; y <= edge.LastLine; y++ {
ySub = SUBPIXEL_DATA(y & (SUBPIXEL_COUNT - 1))
xp = int(x + SUBPIXEL_OFFSETS[ySub])
mask = SUBPIXEL_DATA(1 << ySub)
yLine = y >> SUBPIXEL_SHIFT
r.MaskBuffer[yLine*r.BufferWidth+xp] |= mask
r.WindingBuffer[(yLine*r.BufferWidth+xp)*SUBPIXEL_COUNT+int(ySub)] += winding
x += slope
}
}
//! Renders the mask to the canvas with non-zero winding fill.
func (r *Rasterizer8BitsSample) fillNonZero(img *image.RGBA, color *color.RGBA, clipBound [4]float64) {
var x, y uint32
minX := uint32(clipBound[0])
maxX := uint32(clipBound[2])
minY := uint32(clipBound[1]) >> SUBPIXEL_SHIFT
maxY := uint32(clipBound[3]) >> SUBPIXEL_SHIFT
//pixColor := (uint32(color.R) << 24) | (uint32(color.G) << 16) | (uint32(color.B) << 8) | uint32(color.A)
pixColor := (*uint32)(unsafe.Pointer(color))
cs1 := *pixColor & 0xff00ff
cs2 := *pixColor >> 8 & 0xff00ff
stride := uint32(img.Stride)
var mask SUBPIXEL_DATA
var n uint32
var values [SUBPIXEL_COUNT]NON_ZERO_MASK_DATA_UNIT
for n = 0; n < SUBPIXEL_COUNT; n++ {
values[n] = 0
}
for y = minY; y < maxY; y++ {
tp := img.Pix[y*stride:]
mask = 0
for x = minX; x <= maxX; x++ {
p := (*uint32)(unsafe.Pointer(&tp[x]))
temp := r.MaskBuffer[y*uint32(r.BufferWidth)+x]
if temp != 0 {
var bit SUBPIXEL_DATA = 1
for n = 0; n < SUBPIXEL_COUNT; n++ {
if temp&bit != 0 {
t := values[n]
values[n] += r.WindingBuffer[(y*uint32(r.BufferWidth)+x)*SUBPIXEL_COUNT+n]
if (t == 0 || values[n] == 0) && t != values[n] {
mask ^= bit
}
}
bit <<= 1
}
}
// 8bits
alpha := uint32(coverageTable[mask])
// 16bits
//alpha := uint32(coverageTable[mask & 0xff] + coverageTable[(mask >> 8) & 0xff])
// 32bits
//alpha := uint32(coverageTable[mask & 0xff] + coverageTable[(mask >> 8) & 0xff] + coverageTable[(mask >> 16) & 0xff] + coverageTable[(mask >> 24) & 0xff])
// alpha is in range of 0 to SUBPIXEL_COUNT
invAlpha := uint32(SUBPIXEL_COUNT) - alpha
ct1 := *p & 0xff00ff * invAlpha
ct2 := *p >> 8 & 0xff00ff * invAlpha
ct1 = (ct1 + cs1*alpha) >> SUBPIXEL_SHIFT & 0xff00ff
ct2 = (ct2 + cs2*alpha) << (8 - SUBPIXEL_SHIFT) & 0xff00ff00
*p = ct1 + ct2
}
}
}

View file

@ -1,323 +0,0 @@
// Copyright 2011 The draw2d Authors. All rights reserved.
// created: 27/05/2011 by Laurent Le Goff
// +build ignore
package raster
import (
"image"
"image/color"
"unsafe"
)
const (
SUBPIXEL_SHIFT = 5
SUBPIXEL_COUNT = 1 << SUBPIXEL_SHIFT
)
var SUBPIXEL_OFFSETS = SUBPIXEL_OFFSETS_SAMPLE_32_FIXED
type SUBPIXEL_DATA uint32
type NON_ZERO_MASK_DATA_UNIT uint8
type Rasterizer8BitsSample struct {
MaskBuffer []SUBPIXEL_DATA
WindingBuffer []NON_ZERO_MASK_DATA_UNIT
Width int
BufferWidth int
Height int
ClipBound [4]float64
RemappingMatrix [6]float64
}
/* width and height define the maximum output size for the filler.
* The filler will output to larger bitmaps as well, but the output will
* be cropped.
*/
func NewRasterizer8BitsSample(width, height int) *Rasterizer8BitsSample {
var r Rasterizer8BitsSample
// Scale the coordinates by SUBPIXEL_COUNT in vertical direction
// The sampling point for the sub-pixel is at the top right corner. This
// adjustment moves it to the pixel center.
r.RemappingMatrix = [6]float64{1, 0, 0, SUBPIXEL_COUNT, 0.5 / SUBPIXEL_COUNT, -0.5 * SUBPIXEL_COUNT}
r.Width = width
r.Height = height
// The buffer used for filling needs to be one pixel wider than the bitmap.
// This is because the end flag that turns the fill of is the first pixel
// after the actually drawn edge.
r.BufferWidth = width + 1
r.MaskBuffer = make([]SUBPIXEL_DATA, r.BufferWidth*height)
r.WindingBuffer = make([]NON_ZERO_MASK_DATA_UNIT, r.BufferWidth*height*SUBPIXEL_COUNT)
r.ClipBound = clip(0, 0, width, height, SUBPIXEL_COUNT)
return &r
}
func clip(x, y, width, height, scale int) [4]float64 {
var clipBound [4]float64
offset := 0.99 / float64(scale)
clipBound[0] = float64(x) + offset
clipBound[2] = float64(x+width) - offset
clipBound[1] = float64(y * scale)
clipBound[3] = float64((y + height) * scale)
return clipBound
}
func intersect(r1, r2 [4]float64) [4]float64 {
if r1[0] < r2[0] {
r1[0] = r2[0]
}
if r1[2] > r2[2] {
r1[2] = r2[2]
}
if r1[0] > r1[2] {
r1[0] = r1[2]
}
if r1[1] < r2[1] {
r1[1] = r2[1]
}
if r1[3] > r2[3] {
r1[3] = r2[3]
}
if r1[1] > r1[3] {
r1[1] = r1[3]
}
return r1
}
func (r *Rasterizer8BitsSample) RenderEvenOdd(img *image.RGBA, color *color.RGBA, polygon *Polygon, tr [6]float64) {
// memset 0 the mask buffer
r.MaskBuffer = make([]SUBPIXEL_DATA, r.BufferWidth*r.Height)
// inline matrix multiplication
transform := [6]float64{
tr[0]*r.RemappingMatrix[0] + tr[1]*r.RemappingMatrix[2],
tr[1]*r.RemappingMatrix[3] + tr[0]*r.RemappingMatrix[1],
tr[2]*r.RemappingMatrix[0] + tr[3]*r.RemappingMatrix[2],
tr[3]*r.RemappingMatrix[3] + tr[2]*r.RemappingMatrix[1],
tr[4]*r.RemappingMatrix[0] + tr[5]*r.RemappingMatrix[2] + r.RemappingMatrix[4],
tr[5]*r.RemappingMatrix[3] + tr[4]*r.RemappingMatrix[1] + r.RemappingMatrix[5],
}
clipRect := clip(img.Bounds().Min.X, img.Bounds().Min.Y, img.Bounds().Dx(), img.Bounds().Dy(), SUBPIXEL_COUNT)
clipRect = intersect(clipRect, r.ClipBound)
p := 0
l := len(*polygon) / 2
var edges [32]PolygonEdge
for p < l {
edgeCount := polygon.getEdges(p, 16, edges[:], transform, clipRect)
for k := 0; k < edgeCount; k++ {
r.addEvenOddEdge(&edges[k])
}
p += 16
}
r.fillEvenOdd(img, color, clipRect)
}
//! Adds an edge to be used with even-odd fill.
func (r *Rasterizer8BitsSample) addEvenOddEdge(edge *PolygonEdge) {
x := Fix(edge.X * FIXED_FLOAT_COEF)
slope := Fix(edge.Slope * FIXED_FLOAT_COEF)
slopeFix := Fix(0)
if edge.LastLine-edge.FirstLine >= SLOPE_FIX_STEP {
slopeFix = Fix(edge.Slope*SLOPE_FIX_STEP*FIXED_FLOAT_COEF) - slope<<SLOPE_FIX_SHIFT
}
var mask SUBPIXEL_DATA
var ySub uint32
var xp, yLine int
for y := edge.FirstLine; y <= edge.LastLine; y++ {
ySub = uint32(y & (SUBPIXEL_COUNT - 1))
xp = int((x + SUBPIXEL_OFFSETS[ySub]) >> FIXED_SHIFT)
mask = SUBPIXEL_DATA(1 << ySub)
yLine = y >> SUBPIXEL_SHIFT
r.MaskBuffer[yLine*r.BufferWidth+xp] ^= mask
x += slope
if y&SLOPE_FIX_MASK == 0 {
x += slopeFix
}
}
}
//! Adds an edge to be used with non-zero winding fill.
func (r *Rasterizer8BitsSample) addNonZeroEdge(edge *PolygonEdge) {
x := Fix(edge.X * FIXED_FLOAT_COEF)
slope := Fix(edge.Slope * FIXED_FLOAT_COEF)
slopeFix := Fix(0)
if edge.LastLine-edge.FirstLine >= SLOPE_FIX_STEP {
slopeFix = Fix(edge.Slope*SLOPE_FIX_STEP*FIXED_FLOAT_COEF) - slope<<SLOPE_FIX_SHIFT
}
var mask SUBPIXEL_DATA
var ySub uint32
var xp, yLine int
winding := NON_ZERO_MASK_DATA_UNIT(edge.Winding)
for y := edge.FirstLine; y <= edge.LastLine; y++ {
ySub = uint32(y & (SUBPIXEL_COUNT - 1))
xp = int((x + SUBPIXEL_OFFSETS[ySub]) >> FIXED_SHIFT)
mask = SUBPIXEL_DATA(1 << ySub)
yLine = y >> SUBPIXEL_SHIFT
r.MaskBuffer[yLine*r.BufferWidth+xp] |= mask
r.WindingBuffer[(yLine*r.BufferWidth+xp)*SUBPIXEL_COUNT+int(ySub)] += winding
x += slope
if y&SLOPE_FIX_MASK == 0 {
x += slopeFix
}
}
}
// Renders the mask to the canvas with even-odd fill.
func (r *Rasterizer8BitsSample) fillEvenOdd(img *image.RGBA, color *color.RGBA, clipBound [4]float64) {
var x, y uint32
minX := uint32(clipBound[0])
maxX := uint32(clipBound[2])
minY := uint32(clipBound[1]) >> SUBPIXEL_SHIFT
maxY := uint32(clipBound[3]) >> SUBPIXEL_SHIFT
//pixColor := (uint32(color.R) << 24) | (uint32(color.G) << 16) | (uint32(color.B) << 8) | uint32(color.A)
pixColor := (*uint32)(unsafe.Pointer(color))
cs1 := *pixColor & 0xff00ff
cs2 := *pixColor >> 8 & 0xff00ff
stride := uint32(img.Stride)
var mask SUBPIXEL_DATA
for y = minY; y < maxY; y++ {
tp := img.Pix[y*stride:]
mask = 0
for x = minX; x <= maxX; x++ {
p := (*uint32)(unsafe.Pointer(&tp[x]))
mask ^= r.MaskBuffer[y*uint32(r.BufferWidth)+x]
// 8bits
//alpha := uint32(coverageTable[mask])
// 16bits
//alpha := uint32(coverageTable[mask & 0xff] + coverageTable[(mask >> 8) & 0xff])
// 32bits
alpha := uint32(coverageTable[mask&0xff] + coverageTable[mask>>8&0xff] + coverageTable[mask>>16&0xff] + coverageTable[mask>>24&0xff])
// alpha is in range of 0 to SUBPIXEL_COUNT
invAlpha := uint32(SUBPIXEL_COUNT) - alpha
ct1 := *p & 0xff00ff * invAlpha
ct2 := *p >> 8 & 0xff00ff * invAlpha
ct1 = (ct1 + cs1*alpha) >> SUBPIXEL_SHIFT & 0xff00ff
ct2 = (ct2 + cs2*alpha) << (8 - SUBPIXEL_SHIFT) & 0xff00ff00
*p = ct1 + ct2
}
}
}
/*
* Renders the polygon with non-zero winding fill.
* param aTarget the target bitmap.
* param aPolygon the polygon to render.
* param aColor the color to be used for rendering.
* param aTransformation the transformation matrix.
*/
func (r *Rasterizer8BitsSample) RenderNonZeroWinding(img *image.RGBA, color *color.RGBA, polygon *Polygon, tr [6]float64) {
r.MaskBuffer = make([]SUBPIXEL_DATA, r.BufferWidth*r.Height)
r.WindingBuffer = make([]NON_ZERO_MASK_DATA_UNIT, r.BufferWidth*r.Height*SUBPIXEL_COUNT)
// inline matrix multiplication
transform := [6]float64{
tr[0]*r.RemappingMatrix[0] + tr[1]*r.RemappingMatrix[2],
tr[1]*r.RemappingMatrix[3] + tr[0]*r.RemappingMatrix[1],
tr[2]*r.RemappingMatrix[0] + tr[3]*r.RemappingMatrix[2],
tr[3]*r.RemappingMatrix[3] + tr[2]*r.RemappingMatrix[1],
tr[4]*r.RemappingMatrix[0] + tr[5]*r.RemappingMatrix[2] + r.RemappingMatrix[4],
tr[5]*r.RemappingMatrix[3] + tr[4]*r.RemappingMatrix[1] + r.RemappingMatrix[5],
}
clipRect := clip(img.Bounds().Min.X, img.Bounds().Min.Y, img.Bounds().Dx(), img.Bounds().Dy(), SUBPIXEL_COUNT)
clipRect = intersect(clipRect, r.ClipBound)
p := 0
l := len(*polygon) / 2
var edges [32]PolygonEdge
for p < l {
edgeCount := polygon.getEdges(p, 16, edges[:], transform, clipRect)
for k := 0; k < edgeCount; k++ {
r.addNonZeroEdge(&edges[k])
}
p += 16
}
r.fillNonZero(img, color, clipRect)
}
//! Renders the mask to the canvas with non-zero winding fill.
func (r *Rasterizer8BitsSample) fillNonZero(img *image.RGBA, color *color.RGBA, clipBound [4]float64) {
var x, y uint32
minX := uint32(clipBound[0])
maxX := uint32(clipBound[2])
minY := uint32(clipBound[1]) >> SUBPIXEL_SHIFT
maxY := uint32(clipBound[3]) >> SUBPIXEL_SHIFT
//pixColor := (uint32(color.R) << 24) | (uint32(color.G) << 16) | (uint32(color.B) << 8) | uint32(color.A)
pixColor := (*uint32)(unsafe.Pointer(color))
cs1 := *pixColor & 0xff00ff
cs2 := *pixColor >> 8 & 0xff00ff
stride := uint32(img.Stride)
var mask SUBPIXEL_DATA
var n uint32
var values [SUBPIXEL_COUNT]NON_ZERO_MASK_DATA_UNIT
for n = 0; n < SUBPIXEL_COUNT; n++ {
values[n] = 0
}
for y = minY; y < maxY; y++ {
tp := img.Pix[y*stride:]
mask = 0
for x = minX; x <= maxX; x++ {
p := (*uint32)(unsafe.Pointer(&tp[x]))
temp := r.MaskBuffer[y*uint32(r.BufferWidth)+x]
if temp != 0 {
var bit SUBPIXEL_DATA = 1
for n = 0; n < SUBPIXEL_COUNT; n++ {
if temp&bit != 0 {
t := values[n]
values[n] += r.WindingBuffer[(y*uint32(r.BufferWidth)+x)*SUBPIXEL_COUNT+n]
if (t == 0 || values[n] == 0) && t != values[n] {
mask ^= bit
}
}
bit <<= 1
}
}
// 8bits
//alpha := uint32(coverageTable[mask])
// 16bits
//alpha := uint32(coverageTable[mask & 0xff] + coverageTable[(mask >> 8) & 0xff])
// 32bits
alpha := uint32(coverageTable[mask&0xff] + coverageTable[mask>>8&0xff] + coverageTable[mask>>16&0xff] + coverageTable[mask>>24&0xff])
// alpha is in range of 0 to SUBPIXEL_COUNT
invAlpha := uint32(SUBPIXEL_COUNT) - alpha
ct1 := *p & 0xff00ff * invAlpha
ct2 := *p >> 8 & 0xff00ff * invAlpha
ct1 = (ct1 + cs1*alpha) >> SUBPIXEL_SHIFT & 0xff00ff
ct2 = (ct2 + cs2*alpha) << (8 - SUBPIXEL_SHIFT) & 0xff00ff00
*p = ct1 + ct2
}
}
}

View file

@ -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
)

View file

@ -1,581 +0,0 @@
// Copyright 2011 The draw2d Authors. All rights reserved.
// created: 27/05/2011 by Laurent Le Goff
package raster
const (
POLYGON_CLIP_NONE = iota
POLYGON_CLIP_LEFT
POLYGON_CLIP_RIGHT
POLYGON_CLIP_TOP
POLYGON_CLIP_BOTTOM
)
type Polygon []float64
type PolygonEdge struct {
X, Slope float64
FirstLine, LastLine int
Winding int16
}
//! A more optimized representation of a polygon edge.
type PolygonScanEdge struct {
FirstLine, LastLine int
Winding int16
X Fix
Slope Fix
SlopeFix Fix
NextEdge *PolygonScanEdge
}
//! Calculates the edges of the polygon with transformation and clipping to edges array.
/*! \param startIndex the index for the first vertex.
* \param vertexCount the amount of vertices to convert.
* \param edges the array for result edges. This should be able to contain 2*aVertexCount edges.
* \param tr the transformation matrix for the polygon.
* \param aClipRectangle the clip rectangle.
* \return the amount of edges in the result.
*/
func (p Polygon) getEdges(startIndex, vertexCount int, edges []PolygonEdge, tr [6]float64, clipBound [4]float64) int {
startIndex = startIndex * 2
endIndex := startIndex + vertexCount*2
if endIndex > len(p) {
endIndex = len(p)
}
x := p[startIndex]
y := p[startIndex+1]
// inline transformation
prevX := x*tr[0] + y*tr[2] + tr[4]
prevY := x*tr[1] + y*tr[3] + tr[5]
//! Calculates the clip flags for a point.
prevClipFlags := POLYGON_CLIP_NONE
if prevX < clipBound[0] {
prevClipFlags |= POLYGON_CLIP_LEFT
} else if prevX >= clipBound[2] {
prevClipFlags |= POLYGON_CLIP_RIGHT
}
if prevY < clipBound[1] {
prevClipFlags |= POLYGON_CLIP_TOP
} else if prevY >= clipBound[3] {
prevClipFlags |= POLYGON_CLIP_BOTTOM
}
edgeCount := 0
var k, clipFlags, clipSum, clipUnion int
var xleft, yleft, xright, yright, oldY, maxX, minX float64
var swapWinding int16
for n := startIndex; n < endIndex; n = n + 2 {
k = (n + 2) % len(p)
x = p[k]*tr[0] + p[k+1]*tr[2] + tr[4]
y = p[k]*tr[1] + p[k+1]*tr[3] + tr[5]
//! Calculates the clip flags for a point.
clipFlags = POLYGON_CLIP_NONE
if prevX < clipBound[0] {
clipFlags |= POLYGON_CLIP_LEFT
} else if prevX >= clipBound[2] {
clipFlags |= POLYGON_CLIP_RIGHT
}
if prevY < clipBound[1] {
clipFlags |= POLYGON_CLIP_TOP
} else if prevY >= clipBound[3] {
clipFlags |= POLYGON_CLIP_BOTTOM
}
clipSum = prevClipFlags | clipFlags
clipUnion = prevClipFlags & clipFlags
// Skip all edges that are either completely outside at the top or at the bottom.
if clipUnion&(POLYGON_CLIP_TOP|POLYGON_CLIP_BOTTOM) == 0 {
if clipUnion&POLYGON_CLIP_RIGHT != 0 {
// Both clip to right, edge is a vertical line on the right side
if getVerticalEdge(prevY, y, clipBound[2], &edges[edgeCount], clipBound) {
edgeCount++
}
} else if clipUnion&POLYGON_CLIP_LEFT != 0 {
// Both clip to left, edge is a vertical line on the left side
if getVerticalEdge(prevY, y, clipBound[0], &edges[edgeCount], clipBound) {
edgeCount++
}
} else if clipSum&(POLYGON_CLIP_RIGHT|POLYGON_CLIP_LEFT) == 0 {
// No clipping in the horizontal direction
if getEdge(prevX, prevY, x, y, &edges[edgeCount], clipBound) {
edgeCount++
}
} else {
// Clips to left or right or both.
if x < prevX {
xleft, yleft = x, y
xright, yright = prevX, prevY
swapWinding = -1
} else {
xleft, yleft = prevX, prevY
xright, yright = x, y
swapWinding = 1
}
slope := (yright - yleft) / (xright - xleft)
if clipSum&POLYGON_CLIP_RIGHT != 0 {
// calculate new position for the right vertex
oldY = yright
maxX = clipBound[2]
yright = yleft + (maxX-xleft)*slope
xright = maxX
// add vertical edge for the overflowing part
if getVerticalEdge(yright, oldY, maxX, &edges[edgeCount], clipBound) {
edges[edgeCount].Winding *= swapWinding
edgeCount++
}
}
if clipSum&POLYGON_CLIP_LEFT != 0 {
// calculate new position for the left vertex
oldY = yleft
minX = clipBound[0]
yleft = yleft + (minX-xleft)*slope
xleft = minX
// add vertical edge for the overflowing part
if getVerticalEdge(oldY, yleft, minX, &edges[edgeCount], clipBound) {
edges[edgeCount].Winding *= swapWinding
edgeCount++
}
}
if getEdge(xleft, yleft, xright, yright, &edges[edgeCount], clipBound) {
edges[edgeCount].Winding *= swapWinding
edgeCount++
}
}
}
prevClipFlags = clipFlags
prevX = x
prevY = y
}
return edgeCount
}
//! Creates a polygon edge between two vectors.
/*! Clips the edge vertically to the clip rectangle. Returns true for edges that
* should be rendered, false for others.
*/
func getEdge(x0, y0, x1, y1 float64, edge *PolygonEdge, clipBound [4]float64) bool {
var startX, startY, endX, endY float64
var winding int16
if y0 <= y1 {
startX = x0
startY = y0
endX = x1
endY = y1
winding = 1
} else {
startX = x1
startY = y1
endX = x0
endY = y0
winding = -1
}
// Essentially, firstLine is floor(startY + 1) and lastLine is floor(endY).
// These are refactored to integer casts in order to avoid function
// calls. The difference with integer cast is that numbers are always
// rounded towards zero. Since values smaller than zero get clipped away,
// only coordinates between 0 and -1 require greater attention as they
// also round to zero. The problems in this range can be avoided by
// adding one to the values before conversion and subtracting after it.
firstLine := int(startY + 1)
lastLine := int(endY+1) - 1
minClip := int(clipBound[1])
maxClip := int(clipBound[3])
// If start and end are on the same line, the edge doesn't cross
// any lines and thus can be ignored.
// If the end is smaller than the first line, edge is out.
// If the start is larger than the last line, edge is out.
if firstLine > lastLine || lastLine < minClip || firstLine >= maxClip {
return false
}
// Adjust the start based on the target.
if firstLine < minClip {
firstLine = minClip
}
if lastLine >= maxClip {
lastLine = maxClip - 1
}
edge.Slope = (endX - startX) / (endY - startY)
edge.X = startX + (float64(firstLine)-startY)*edge.Slope
edge.Winding = winding
edge.FirstLine = firstLine
edge.LastLine = lastLine
return true
}
//! Creates a vertical polygon edge between two y values.
/*! Clips the edge vertically to the clip rectangle. Returns true for edges that
* should be rendered, false for others.
*/
func getVerticalEdge(startY, endY, x float64, edge *PolygonEdge, clipBound [4]float64) bool {
var start, end float64
var winding int16
if startY < endY {
start = startY
end = endY
winding = 1
} else {
start = endY
end = startY
winding = -1
}
firstLine := int(start + 1)
lastLine := int(end+1) - 1
minClip := int(clipBound[1])
maxClip := int(clipBound[3])
// If start and end are on the same line, the edge doesn't cross
// any lines and thus can be ignored.
// If the end is smaller than the first line, edge is out.
// If the start is larger than the last line, edge is out.
if firstLine > lastLine || lastLine < minClip || firstLine >= maxClip {
return false
}
// Adjust the start based on the clip rect.
if firstLine < minClip {
firstLine = minClip
}
if lastLine >= maxClip {
lastLine = maxClip - 1
}
edge.Slope = 0
edge.X = x
edge.Winding = winding
edge.FirstLine = firstLine
edge.LastLine = lastLine
return true
}
type VertexData struct {
X, Y float64
ClipFlags int
Line int
}
//! Calculates the edges of the polygon with transformation and clipping to edges array.
/*! Note that this may return upto three times the amount of edges that the polygon has vertices,
* in the unlucky case where both left and right side get clipped for all edges.
* \param edges the array for result edges. This should be able to contain 2*aVertexCount edges.
* \param aTransformation the transformation matrix for the polygon.
* \param aClipRectangle the clip rectangle.
* \return the amount of edges in the result.
*/
func (p Polygon) getScanEdges(edges []PolygonScanEdge, tr [6]float64, clipBound [4]float64) int {
var n int
vertexData := make([]VertexData, len(p)/2+1)
for n = 0; n < len(vertexData)-1; n = n + 1 {
k := n * 2
vertexData[n].X = p[k]*tr[0] + p[k+1]*tr[2] + tr[4]
vertexData[n].Y = p[k]*tr[1] + p[k+1]*tr[3] + tr[5]
// Calculate clip flags for all vertices.
vertexData[n].ClipFlags = POLYGON_CLIP_NONE
if vertexData[n].X < clipBound[0] {
vertexData[n].ClipFlags |= POLYGON_CLIP_LEFT
} else if vertexData[n].X >= clipBound[2] {
vertexData[n].ClipFlags |= POLYGON_CLIP_RIGHT
}
if vertexData[n].Y < clipBound[1] {
vertexData[n].ClipFlags |= POLYGON_CLIP_TOP
} else if vertexData[n].Y >= clipBound[3] {
vertexData[n].ClipFlags |= POLYGON_CLIP_BOTTOM
}
// Calculate line of the vertex. If the vertex is clipped by top or bottom, the line
// is determined by the clip rectangle.
if vertexData[n].ClipFlags&POLYGON_CLIP_TOP != 0 {
vertexData[n].Line = int(clipBound[1])
} else if vertexData[n].ClipFlags&POLYGON_CLIP_BOTTOM != 0 {
vertexData[n].Line = int(clipBound[3] - 1)
} else {
vertexData[n].Line = int(vertexData[n].Y+1) - 1
}
}
// Copy the data from 0 to the last entry to make the data to loop.
vertexData[len(vertexData)-1] = vertexData[0]
// Transform the first vertex; store.
// Process mVertexCount - 1 times, next is n+1
// copy the first vertex to
// Process 1 time, next is n
edgeCount := 0
for n = 0; n < len(vertexData)-1; n++ {
clipSum := vertexData[n].ClipFlags | vertexData[n+1].ClipFlags
clipUnion := vertexData[n].ClipFlags & vertexData[n+1].ClipFlags
if clipUnion&(POLYGON_CLIP_TOP|POLYGON_CLIP_BOTTOM) == 0 &&
vertexData[n].Line != vertexData[n+1].Line {
var startIndex, endIndex int
var winding int16
if vertexData[n].Y < vertexData[n+1].Y {
startIndex = n
endIndex = n + 1
winding = 1
} else {
startIndex = n + 1
endIndex = n
winding = -1
}
firstLine := vertexData[startIndex].Line + 1
lastLine := vertexData[endIndex].Line
if clipUnion&POLYGON_CLIP_RIGHT != 0 {
// Both clip to right, edge is a vertical line on the right side
edges[edgeCount].FirstLine = firstLine
edges[edgeCount].LastLine = lastLine
edges[edgeCount].Winding = winding
edges[edgeCount].X = Fix(clipBound[2] * FIXED_FLOAT_COEF)
edges[edgeCount].Slope = 0
edges[edgeCount].SlopeFix = 0
edgeCount++
} else if clipUnion&POLYGON_CLIP_LEFT != 0 {
// Both clip to left, edge is a vertical line on the left side
edges[edgeCount].FirstLine = firstLine
edges[edgeCount].LastLine = lastLine
edges[edgeCount].Winding = winding
edges[edgeCount].X = Fix(clipBound[0] * FIXED_FLOAT_COEF)
edges[edgeCount].Slope = 0
edges[edgeCount].SlopeFix = 0
edgeCount++
} else if clipSum&(POLYGON_CLIP_RIGHT|POLYGON_CLIP_LEFT) == 0 {
// No clipping in the horizontal direction
slope := (vertexData[endIndex].X -
vertexData[startIndex].X) /
(vertexData[endIndex].Y -
vertexData[startIndex].Y)
// If there is vertical clip (for the top) it will be processed here. The calculation
// should be done for all non-clipping edges as well to determine the accurate position
// where the edge crosses the first scanline.
startx := vertexData[startIndex].X +
(float64(firstLine)-vertexData[startIndex].Y)*slope
edges[edgeCount].FirstLine = firstLine
edges[edgeCount].LastLine = lastLine
edges[edgeCount].Winding = winding
edges[edgeCount].X = Fix(startx * FIXED_FLOAT_COEF)
edges[edgeCount].Slope = Fix(slope * FIXED_FLOAT_COEF)
if lastLine-firstLine >= SLOPE_FIX_STEP {
edges[edgeCount].SlopeFix = Fix(slope*SLOPE_FIX_STEP*FIXED_FLOAT_COEF) -
edges[edgeCount].Slope<<SLOPE_FIX_SHIFT
} else {
edges[edgeCount].SlopeFix = 0
}
edgeCount++
} else {
// Clips to left or right or both.
slope := (vertexData[endIndex].X -
vertexData[startIndex].X) /
(vertexData[endIndex].Y -
vertexData[startIndex].Y)
// The edge may clip to both left and right.
// The clip results in one or two new vertices, and one to three segments.
// The rounding for scanlines may produce a result where any of the segments is
// ignored.
// The start is always above the end. Calculate the clip positions to clipVertices.
// It is possible that only one of the vertices exist. This will be detected from the
// clip flags of the vertex later, so they are initialized here.
var clipVertices [2]VertexData
if vertexData[startIndex].X <
vertexData[endIndex].X {
clipVertices[0].X = clipBound[0]
clipVertices[1].X = clipBound[2]
clipVertices[0].ClipFlags = POLYGON_CLIP_LEFT
clipVertices[1].ClipFlags = POLYGON_CLIP_RIGHT
} else {
clipVertices[0].X = clipBound[2]
clipVertices[1].X = clipBound[0]
clipVertices[0].ClipFlags = POLYGON_CLIP_RIGHT
clipVertices[1].ClipFlags = POLYGON_CLIP_LEFT
}
var p int
for p = 0; p < 2; p++ {
// Check if either of the vertices crosses the edge marked for the clip vertex
if clipSum&clipVertices[p].ClipFlags != 0 {
// The the vertex is required, calculate it.
clipVertices[p].Y = vertexData[startIndex].Y +
(clipVertices[p].X-
vertexData[startIndex].X)/slope
// If there is clipping in the vertical direction, the new vertex may be clipped.
if clipSum&(POLYGON_CLIP_TOP|POLYGON_CLIP_BOTTOM) != 0 {
if clipVertices[p].Y < clipBound[1] {
clipVertices[p].ClipFlags = POLYGON_CLIP_TOP
clipVertices[p].Line = int(clipBound[1])
} else if clipVertices[p].Y > clipBound[3] {
clipVertices[p].ClipFlags = POLYGON_CLIP_BOTTOM
clipVertices[p].Line = int(clipBound[3] - 1)
} else {
clipVertices[p].ClipFlags = 0
clipVertices[p].Line = int(clipVertices[p].Y+1) - 1
}
} else {
clipVertices[p].ClipFlags = 0
clipVertices[p].Line = int(clipVertices[p].Y+1) - 1
}
}
}
// Now there are three or four vertices, in the top-to-bottom order of start, clip0, clip1,
// end. What kind of edges are required for connecting these can be determined from the
// clip flags.
// -if clip vertex has horizontal clip flags, it doesn't exist. No edge is generated.
// -if start vertex or end vertex has horizontal clip flag, the edge to/from the clip vertex is vertical
// -if the line of two vertices is the same, the edge is not generated, since the edge doesn't
// cross any scanlines.
// The alternative patterns are:
// start - clip0 - clip1 - end
// start - clip0 - end
// start - clip1 - end
var topClipIndex, bottomClipIndex int
if (clipVertices[0].ClipFlags|clipVertices[1].ClipFlags)&
(POLYGON_CLIP_LEFT|POLYGON_CLIP_RIGHT) == 0 {
// Both sides are clipped, the order is start-clip0-clip1-end
topClipIndex = 0
bottomClipIndex = 1
// Add the edge from clip0 to clip1
// Check that the line is different for the vertices.
if clipVertices[0].Line != clipVertices[1].Line {
firstClipLine := clipVertices[0].Line + 1
startx := vertexData[startIndex].X +
(float64(firstClipLine)-vertexData[startIndex].Y)*slope
edges[edgeCount].X = Fix(startx * FIXED_FLOAT_COEF)
edges[edgeCount].Slope = Fix(slope * FIXED_FLOAT_COEF)
edges[edgeCount].FirstLine = firstClipLine
edges[edgeCount].LastLine = clipVertices[1].Line
edges[edgeCount].Winding = winding
if edges[edgeCount].LastLine-edges[edgeCount].FirstLine >= SLOPE_FIX_STEP {
edges[edgeCount].SlopeFix = Fix(slope*SLOPE_FIX_STEP*FIXED_FLOAT_COEF) -
edges[edgeCount].Slope<<SLOPE_FIX_SHIFT
} else {
edges[edgeCount].SlopeFix = 0
}
edgeCount++
}
} else {
// Clip at either side, check which side. The clip flag is on for the vertex
// that doesn't exist, i.e. has not been clipped to be inside the rect.
if clipVertices[0].ClipFlags&(POLYGON_CLIP_LEFT|POLYGON_CLIP_RIGHT) != 0 {
topClipIndex = 1
bottomClipIndex = 1
} else {
topClipIndex = 0
bottomClipIndex = 0
}
}
// Generate the edges from start - clip top and clip bottom - end
// Clip top and clip bottom may be the same vertex if there is only one
// clipped vertex.
// Check that the line is different for the vertices.
if vertexData[startIndex].Line != clipVertices[topClipIndex].Line {
edges[edgeCount].FirstLine = firstLine
edges[edgeCount].LastLine = clipVertices[topClipIndex].Line
edges[edgeCount].Winding = winding
// If startIndex is clipped, the edge is a vertical one.
if vertexData[startIndex].ClipFlags&(POLYGON_CLIP_LEFT|POLYGON_CLIP_RIGHT) != 0 {
edges[edgeCount].X = Fix(clipVertices[topClipIndex].X * FIXED_FLOAT_COEF)
edges[edgeCount].Slope = 0
edges[edgeCount].SlopeFix = 0
} else {
startx := vertexData[startIndex].X +
(float64(firstLine)-vertexData[startIndex].Y)*slope
edges[edgeCount].X = Fix(startx * FIXED_FLOAT_COEF)
edges[edgeCount].Slope = Fix(slope * FIXED_FLOAT_COEF)
if edges[edgeCount].LastLine-edges[edgeCount].FirstLine >= SLOPE_FIX_STEP {
edges[edgeCount].SlopeFix = Fix(slope*SLOPE_FIX_STEP*FIXED_FLOAT_COEF) -
edges[edgeCount].Slope<<SLOPE_FIX_SHIFT
} else {
edges[edgeCount].SlopeFix = 0
}
}
edgeCount++
}
// Check that the line is different for the vertices.
if clipVertices[bottomClipIndex].Line != vertexData[endIndex].Line {
firstClipLine := clipVertices[bottomClipIndex].Line + 1
edges[edgeCount].FirstLine = firstClipLine
edges[edgeCount].LastLine = lastLine
edges[edgeCount].Winding = winding
// If endIndex is clipped, the edge is a vertical one.
if vertexData[endIndex].ClipFlags&(POLYGON_CLIP_LEFT|POLYGON_CLIP_RIGHT) != 0 {
edges[edgeCount].X = Fix(clipVertices[bottomClipIndex].X * FIXED_FLOAT_COEF)
edges[edgeCount].Slope = 0
edges[edgeCount].SlopeFix = 0
} else {
startx := vertexData[startIndex].X +
(float64(firstClipLine)-vertexData[startIndex].Y)*slope
edges[edgeCount].X = Fix(startx * FIXED_FLOAT_COEF)
edges[edgeCount].Slope = Fix(slope * FIXED_FLOAT_COEF)
if edges[edgeCount].LastLine-edges[edgeCount].FirstLine >= SLOPE_FIX_STEP {
edges[edgeCount].SlopeFix = Fix(slope*SLOPE_FIX_STEP*FIXED_FLOAT_COEF) -
edges[edgeCount].Slope<<SLOPE_FIX_SHIFT
} else {
edges[edgeCount].SlopeFix = 0
}
}
edgeCount++
}
}
}
}
return edgeCount
}

View file

@ -1,218 +0,0 @@
package raster
import (
"bufio"
"image"
"image/color"
"image/png"
"log"
"os"
"testing"
"code.google.com/p/freetype-go/freetype/raster"
"gopkg.in/llgcode/draw2d.v1/curve"
)
var flatteningThreshold = 0.5
func savepng(filePath string, m image.Image) {
f, err := os.Create(filePath)
if err != nil {
log.Println(err)
os.Exit(1)
}
defer f.Close()
b := bufio.NewWriter(f)
err = png.Encode(b, m)
if err != nil {
log.Println(err)
os.Exit(1)
}
err = b.Flush()
if err != nil {
log.Println(err)
os.Exit(1)
}
}
type Path struct {
points []float64
}
func (p *Path) LineTo(x, y float64) {
if len(p.points)+2 > cap(p.points) {
points := make([]float64, len(p.points)+2, len(p.points)+32)
copy(points, p.points)
p.points = points
} else {
p.points = p.points[0 : len(p.points)+2]
}
p.points[len(p.points)-2] = x
p.points[len(p.points)-1] = y
}
func TestFreetype(t *testing.T) {
var p Path
p.LineTo(10, 190)
c := curve.CubicCurveFloat64{10, 190, 10, 10, 190, 10, 190, 190}
c.Segment(&p, flatteningThreshold)
poly := Polygon(p.points)
color := color.RGBA{0, 0, 0, 0xff}
img := image.NewRGBA(image.Rect(0, 0, 200, 200))
rasterizer := raster.NewRasterizer(200, 200)
rasterizer.UseNonZeroWinding = false
rasterizer.Start(raster.Point{
X: raster.Fix32(10 * 256),
Y: raster.Fix32(190 * 256)})
for j := 0; j < len(poly); j = j + 2 {
rasterizer.Add1(raster.Point{
X: raster.Fix32(poly[j] * 256),
Y: raster.Fix32(poly[j+1] * 256)})
}
painter := raster.NewRGBAPainter(img)
painter.SetColor(color)
rasterizer.Rasterize(painter)
savepng("../output/raster/TestFreetype.png", img)
}
func TestFreetypeNonZeroWinding(t *testing.T) {
var p Path
p.LineTo(10, 190)
c := curve.CubicCurveFloat64{10, 190, 10, 10, 190, 10, 190, 190}
c.Segment(&p, flatteningThreshold)
poly := Polygon(p.points)
color := color.RGBA{0, 0, 0, 0xff}
img := image.NewRGBA(image.Rect(0, 0, 200, 200))
rasterizer := raster.NewRasterizer(200, 200)
rasterizer.UseNonZeroWinding = true
rasterizer.Start(raster.Point{
X: raster.Fix32(10 * 256),
Y: raster.Fix32(190 * 256)})
for j := 0; j < len(poly); j = j + 2 {
rasterizer.Add1(raster.Point{
X: raster.Fix32(poly[j] * 256),
Y: raster.Fix32(poly[j+1] * 256)})
}
painter := raster.NewRGBAPainter(img)
painter.SetColor(color)
rasterizer.Rasterize(painter)
savepng("../output/raster/TestFreetypeNonZeroWinding.png", img)
}
func TestRasterizer(t *testing.T) {
img := image.NewRGBA(image.Rect(0, 0, 200, 200))
var p Path
p.LineTo(10, 190)
c := curve.CubicCurveFloat64{10, 190, 10, 10, 190, 10, 190, 190}
c.Segment(&p, flatteningThreshold)
poly := Polygon(p.points)
color := color.RGBA{0, 0, 0, 0xff}
tr := [6]float64{1, 0, 0, 1, 0, 0}
r := NewRasterizer8BitsSample(200, 200)
//PolylineBresenham(img, image.Black, poly...)
r.RenderEvenOdd(img, &color, &poly, tr)
savepng("../output/raster/TestRasterizer.png", img)
}
func TestRasterizerNonZeroWinding(t *testing.T) {
img := image.NewRGBA(image.Rect(0, 0, 200, 200))
var p Path
p.LineTo(10, 190)
c := curve.CubicCurveFloat64{10, 190, 10, 10, 190, 10, 190, 190}
c.Segment(&p, flatteningThreshold)
poly := Polygon(p.points)
color := color.RGBA{0, 0, 0, 0xff}
tr := [6]float64{1, 0, 0, 1, 0, 0}
r := NewRasterizer8BitsSample(200, 200)
//PolylineBresenham(img, image.Black, poly...)
r.RenderNonZeroWinding(img, &color, &poly, tr)
savepng("../output/raster/TestRasterizerNonZeroWinding.png", img)
}
func BenchmarkFreetype(b *testing.B) {
var p Path
p.LineTo(10, 190)
c := curve.CubicCurveFloat64{10, 190, 10, 10, 190, 10, 190, 190}
c.Segment(&p, flatteningThreshold)
poly := Polygon(p.points)
color := color.RGBA{0, 0, 0, 0xff}
for i := 0; i < b.N; i++ {
img := image.NewRGBA(image.Rect(0, 0, 200, 200))
rasterizer := raster.NewRasterizer(200, 200)
rasterizer.UseNonZeroWinding = false
rasterizer.Start(raster.Point{
X: raster.Fix32(10 * 256),
Y: raster.Fix32(190 * 256)})
for j := 0; j < len(poly); j = j + 2 {
rasterizer.Add1(raster.Point{
X: raster.Fix32(poly[j] * 256),
Y: raster.Fix32(poly[j+1] * 256)})
}
painter := raster.NewRGBAPainter(img)
painter.SetColor(color)
rasterizer.Rasterize(painter)
}
}
func BenchmarkFreetypeNonZeroWinding(b *testing.B) {
var p Path
p.LineTo(10, 190)
c := curve.CubicCurveFloat64{10, 190, 10, 10, 190, 10, 190, 190}
c.Segment(&p, flatteningThreshold)
poly := Polygon(p.points)
color := color.RGBA{0, 0, 0, 0xff}
for i := 0; i < b.N; i++ {
img := image.NewRGBA(image.Rect(0, 0, 200, 200))
rasterizer := raster.NewRasterizer(200, 200)
rasterizer.UseNonZeroWinding = true
rasterizer.Start(raster.Point{
X: raster.Fix32(10 * 256),
Y: raster.Fix32(190 * 256)})
for j := 0; j < len(poly); j = j + 2 {
rasterizer.Add1(raster.Point{
X: raster.Fix32(poly[j] * 256),
Y: raster.Fix32(poly[j+1] * 256)})
}
painter := raster.NewRGBAPainter(img)
painter.SetColor(color)
rasterizer.Rasterize(painter)
}
}
func BenchmarkRasterizerNonZeroWinding(b *testing.B) {
var p Path
p.LineTo(10, 190)
c := curve.CubicCurveFloat64{10, 190, 10, 10, 190, 10, 190, 190}
c.Segment(&p, flatteningThreshold)
poly := Polygon(p.points)
color := color.RGBA{0, 0, 0, 0xff}
tr := [6]float64{1, 0, 0, 1, 0, 0}
for i := 0; i < b.N; i++ {
img := image.NewRGBA(image.Rect(0, 0, 200, 200))
rasterizer := NewRasterizer8BitsSample(200, 200)
rasterizer.RenderNonZeroWinding(img, &color, &poly, tr)
}
}
func BenchmarkRasterizer(b *testing.B) {
var p Path
p.LineTo(10, 190)
c := curve.CubicCurveFloat64{10, 190, 10, 10, 190, 10, 190, 190}
c.Segment(&p, flatteningThreshold)
poly := Polygon(p.points)
color := color.RGBA{0, 0, 0, 0xff}
tr := [6]float64{1, 0, 0, 1, 0, 0}
for i := 0; i < b.N; i++ {
img := image.NewRGBA(image.Rect(0, 0, 200, 200))
rasterizer := NewRasterizer8BitsSample(200, 200)
rasterizer.RenderEvenOdd(img, &color, &poly, tr)
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

View file

@ -1,155 +0,0 @@
// Copyright 2010 The draw2d Authors. All rights reserved.
// created: 21/11/2010 by Laurent Le Goff
// see http://pippin.gimp.org/image_processing/chap_resampling.html
package draw2d
import (
"image"
"image/color"
"image/draw"
"math"
)
// ImageFilter defines sampling filter (linear, bilinear or bicubic)
type ImageFilter int
const (
// LinearFilter uses linear interpolation
LinearFilter ImageFilter = iota
// BilinearFilter uses bilinear interpolation
BilinearFilter
// BicubicFilter uses bicubic interpolation
BicubicFilter
)
//see http://pippin.gimp.org/image_processing/chap_resampling.html
func getColorLinear(img image.Image, x, y float64) color.Color {
return img.At(int(x), int(y))
}
func getColorBilinear(img image.Image, x, y float64) color.Color {
x0 := math.Floor(x)
y0 := math.Floor(y)
dx := x - x0
dy := y - y0
rt, gt, bt, at := img.At(int(x0), int(y0)).RGBA()
r0, g0, b0, a0 := float64(rt), float64(gt), float64(bt), float64(at)
rt, gt, bt, at = img.At(int(x0+1), int(y0)).RGBA()
r1, g1, b1, a1 := float64(rt), float64(gt), float64(bt), float64(at)
rt, gt, bt, at = img.At(int(x0+1), int(y0+1)).RGBA()
r2, g2, b2, a2 := float64(rt), float64(gt), float64(bt), float64(at)
rt, gt, bt, at = img.At(int(x0), int(y0+1)).RGBA()
r3, g3, b3, a3 := float64(rt), float64(gt), float64(bt), float64(at)
r := int(lerp(lerp(r0, r1, dx), lerp(r3, r2, dx), dy))
g := int(lerp(lerp(g0, g1, dx), lerp(g3, g2, dx), dy))
b := int(lerp(lerp(b0, b1, dx), lerp(b3, b2, dx), dy))
a := int(lerp(lerp(a0, a1, dx), lerp(a3, a2, dx), dy))
return color.RGBA{uint8(r >> 8), uint8(g >> 8), uint8(b >> 8), uint8(a >> 8)}
}
/**
-- LERP
-- /lerp/, vi.,n.
--
-- Quasi-acronym for Linear Interpolation, used as a verb or noun for
-- the operation. "Bresenham's algorithm lerps incrementally between the
-- two endpoints of the line." (From Jargon File (4.4.4, 14 Aug 2003)
*/
func lerp(v1, v2, ratio float64) float64 {
return v1*(1-ratio) + v2*ratio
}
func getColorCubicRow(img image.Image, x, y, offset float64) color.Color {
c0 := img.At(int(x), int(y))
c1 := img.At(int(x+1), int(y))
c2 := img.At(int(x+2), int(y))
c3 := img.At(int(x+3), int(y))
rt, gt, bt, at := c0.RGBA()
r0, g0, b0, a0 := float64(rt), float64(gt), float64(bt), float64(at)
rt, gt, bt, at = c1.RGBA()
r1, g1, b1, a1 := float64(rt), float64(gt), float64(bt), float64(at)
rt, gt, bt, at = c2.RGBA()
r2, g2, b2, a2 := float64(rt), float64(gt), float64(bt), float64(at)
rt, gt, bt, at = c3.RGBA()
r3, g3, b3, a3 := float64(rt), float64(gt), float64(bt), float64(at)
r, g, b, a := cubic(offset, r0, r1, r2, r3), cubic(offset, g0, g1, g2, g3), cubic(offset, b0, b1, b2, b3), cubic(offset, a0, a1, a2, a3)
return color.RGBA{uint8(r >> 8), uint8(g >> 8), uint8(b >> 8), uint8(a >> 8)}
}
func getColorBicubic(img image.Image, x, y float64) color.Color {
x0 := math.Floor(x)
y0 := math.Floor(y)
dx := x - x0
dy := y - y0
c0 := getColorCubicRow(img, x0-1, y0-1, dx)
c1 := getColorCubicRow(img, x0-1, y0, dx)
c2 := getColorCubicRow(img, x0-1, y0+1, dx)
c3 := getColorCubicRow(img, x0-1, y0+2, dx)
rt, gt, bt, at := c0.RGBA()
r0, g0, b0, a0 := float64(rt), float64(gt), float64(bt), float64(at)
rt, gt, bt, at = c1.RGBA()
r1, g1, b1, a1 := float64(rt), float64(gt), float64(bt), float64(at)
rt, gt, bt, at = c2.RGBA()
r2, g2, b2, a2 := float64(rt), float64(gt), float64(bt), float64(at)
rt, gt, bt, at = c3.RGBA()
r3, g3, b3, a3 := float64(rt), float64(gt), float64(bt), float64(at)
r, g, b, a := cubic(dy, r0, r1, r2, r3), cubic(dy, g0, g1, g2, g3), cubic(dy, b0, b1, b2, b3), cubic(dy, a0, a1, a2, a3)
return color.RGBA{uint8(r >> 8), uint8(g >> 8), uint8(b >> 8), uint8(a >> 8)}
}
func cubic(offset, v0, v1, v2, v3 float64) uint32 {
// offset is the offset of the sampled value between v1 and v2
return uint32(((((-7*v0+21*v1-21*v2+7*v3)*offset+
(15*v0-36*v1+27*v2-6*v3))*offset+
(-9*v0+9*v2))*offset + (v0 + 16*v1 + v2)) / 18.0)
}
// DrawImage draws a source image on an destination image.
func DrawImage(src image.Image, dest draw.Image, tr MatrixTransform, op draw.Op, filter ImageFilter) {
bounds := src.Bounds()
x0, y0, x1, y1 := float64(bounds.Min.X), float64(bounds.Min.Y), float64(bounds.Max.X), float64(bounds.Max.Y)
tr.TransformRectangle(&x0, &y0, &x1, &y1)
var x, y, u, v float64
var c1, c2, cr color.Color
var r, g, b, a, ia, r1, g1, b1, a1, r2, g2, b2, a2 uint32
var color color.RGBA
for x = x0; x < x1; x++ {
for y = y0; y < y1; y++ {
u = x
v = y
tr.InverseTransform(&u, &v)
if bounds.Min.X <= int(u) && bounds.Max.X > int(u) && bounds.Min.Y <= int(v) && bounds.Max.Y > int(v) {
c1 = dest.At(int(x), int(y))
switch filter {
case LinearFilter:
c2 = src.At(int(u), int(v))
case BilinearFilter:
c2 = getColorBilinear(src, u, v)
case BicubicFilter:
c2 = getColorBicubic(src, u, v)
}
switch op {
case draw.Over:
r1, g1, b1, a1 = c1.RGBA()
r2, g2, b2, a2 = c2.RGBA()
ia = M - a2
r = ((r1 * ia) / M) + r2
g = ((g1 * ia) / M) + g2
b = ((b1 * ia) / M) + b2
a = ((a1 * ia) / M) + a2
color.R = uint8(r >> 8)
color.G = uint8(g >> 8)
color.B = uint8(b >> 8)
color.A = uint8(a >> 8)
cr = color
default:
cr = c2
}
dest.Set(int(x), int(y), cr)
}
}
}
}

View file

@ -19,7 +19,7 @@ import (
function main(){} function main(){}
// Initialize the graphic context on an RGBA image // Initialize the graphic context on an RGBA image
dest := image.NewRGBA(image.Rect(0, 0, 297, 210.0)) dest := image.NewRGBA(image.Rect(0, 0, 297, 210.0))
gc := draw2d.NewGraphicContext(dest) gc := draw2dimg.NewGraphicContext(dest)
// Draw Android logo // Draw Android logo
fn, err := android.Main(gc, "png") fn, err := android.Main(gc, "png")
if err != nil { if err != nil {

View file

@ -8,8 +8,9 @@ import (
"image/color" "image/color"
"math" "math"
"gopkg.in/llgcode/draw2d.v1" "git.fromouter.space/crunchy-rocks/draw2d"
"gopkg.in/llgcode/draw2d.v1/samples" "git.fromouter.space/crunchy-rocks/draw2d/draw2dkit"
"git.fromouter.space/crunchy-rocks/draw2d/samples"
) )
// Main draws a droid and returns the filename. This should only be // Main draws a droid and returns the filename. This should only be
@ -44,32 +45,32 @@ func Draw(gc draw2d.GraphicContext, x, y float64) {
gc.Stroke() gc.Stroke()
// left eye // left eye
draw2d.Circle(gc, x+60, y+45, 5) draw2dkit.Circle(gc, x+60, y+45, 5)
gc.FillStroke() gc.FillStroke()
// right eye // right eye
draw2d.Circle(gc, x+100, y+45, 5) draw2dkit.Circle(gc, x+100, y+45, 5)
gc.FillStroke() gc.FillStroke()
// body // body
draw2d.RoundRect(gc, x+30, y+75, x+30+100, y+75+90, 10, 10) draw2dkit.RoundedRectangle(gc, x+30, y+75, x+30+100, y+75+90, 10, 10)
gc.FillStroke() gc.FillStroke()
draw2d.Rect(gc, x+30, y+75, x+30+100, y+75+80) draw2dkit.Rectangle(gc, x+30, y+75, x+30+100, y+75+80)
gc.FillStroke() gc.FillStroke()
// left arm // left arm
draw2d.RoundRect(gc, x+5, y+80, x+5+20, y+80+70, 10, 10) draw2dkit.RoundedRectangle(gc, x+5, y+80, x+5+20, y+80+70, 10, 10)
gc.FillStroke() gc.FillStroke()
// right arm // right arm
draw2d.RoundRect(gc, x+135, y+80, x+135+20, y+80+70, 10, 10) draw2dkit.RoundedRectangle(gc, x+135, y+80, x+135+20, y+80+70, 10, 10)
gc.FillStroke() gc.FillStroke()
// left leg // left leg
draw2d.RoundRect(gc, x+50, y+150, x+50+20, y+150+50, 10, 10) draw2dkit.RoundedRectangle(gc, x+50, y+150, x+50+20, y+150+50, 10, 10)
gc.FillStroke() gc.FillStroke()
// right leg // right leg
draw2d.RoundRect(gc, x+90, y+150, x+90+20, y+150+50, 10, 10) draw2dkit.RoundedRectangle(gc, x+90, y+150, x+90+20, y+150+50, 10, 10)
gc.FillStroke() gc.FillStroke()
} }

View file

@ -9,9 +9,9 @@ import (
"image/png" "image/png"
"net/http" "net/http"
"gopkg.in/llgcode/draw2d.v1" "git.fromouter.space/crunchy-rocks/draw2d/draw2dimg"
"gopkg.in/llgcode/draw2d.v1/draw2dpdf" "git.fromouter.space/crunchy-rocks/draw2d/draw2dpdf"
"gopkg.in/llgcode/draw2d.v1/samples/android" "git.fromouter.space/crunchy-rocks/draw2d/samples/android"
"appengine" "appengine"
) )
@ -59,7 +59,7 @@ func imgPng(w http.ResponseWriter, r *http.Request) *appError {
// Initialize the graphic context on an RGBA image // Initialize the graphic context on an RGBA image
dest := image.NewRGBA(image.Rect(0, 0, 297, 210.0)) dest := image.NewRGBA(image.Rect(0, 0, 297, 210.0))
gc := draw2d.NewGraphicContext(dest) gc := draw2dimg.NewGraphicContext(dest)
// Draw sample // Draw sample
android.Draw(gc, 65, 0) android.Draw(gc, 65, 0)

View file

@ -7,8 +7,10 @@ package frameimage
import ( import (
"math" "math"
"gopkg.in/llgcode/draw2d.v1" "git.fromouter.space/crunchy-rocks/draw2d"
"gopkg.in/llgcode/draw2d.v1/samples" "git.fromouter.space/crunchy-rocks/draw2d/draw2dimg"
"git.fromouter.space/crunchy-rocks/draw2d/draw2dkit"
"git.fromouter.space/crunchy-rocks/draw2d/samples"
) )
// Main draws the image frame and returns the filename. // Main draws the image frame and returns the filename.
@ -33,13 +35,12 @@ func Main(gc draw2d.GraphicContext, ext string) (string, error) {
func Draw(gc draw2d.GraphicContext, png string, func Draw(gc draw2d.GraphicContext, png string,
dw, dh, margin, lineWidth float64) error { dw, dh, margin, lineWidth float64) error {
// Draw frame // Draw frame
draw2d.RoundRect(gc, lineWidth, lineWidth, draw2dkit.RoundedRectangle(gc, lineWidth, lineWidth, dw-lineWidth, dh-lineWidth, 100, 100)
dw-lineWidth, dh-lineWidth, 100, 100)
gc.SetLineWidth(lineWidth) gc.SetLineWidth(lineWidth)
gc.FillStroke() gc.FillStroke()
// load the source image // load the source image
source, err := draw2d.LoadFromPngFile(png) source, err := draw2dimg.LoadFromPngFile(png)
if err != nil { if err != nil {
return err return err
} }

View file

@ -9,9 +9,10 @@ import (
"image/color" "image/color"
"math" "math"
"gopkg.in/llgcode/draw2d.v1" "git.fromouter.space/crunchy-rocks/draw2d"
"gopkg.in/llgcode/draw2d.v1/samples" "git.fromouter.space/crunchy-rocks/draw2d/draw2dkit"
"gopkg.in/llgcode/draw2d.v1/samples/gopher2" "git.fromouter.space/crunchy-rocks/draw2d/samples"
"git.fromouter.space/crunchy-rocks/draw2d/samples/gopher2"
) )
// Main draws geometry and returns the filename. This should only be // Main draws geometry and returns the filename. This should only be
@ -98,7 +99,7 @@ func Dash(gc draw2d.GraphicContext, x, y, width, height float64) {
gc.MoveTo(x+sx*60.0, y) gc.MoveTo(x+sx*60.0, y)
gc.LineTo(x+sx*60.0, y) gc.LineTo(x+sx*60.0, y)
gc.LineTo(x+sx*162, y+sy*205) gc.LineTo(x+sx*162, y+sy*205)
gc.RLineTo(sx*-102.4, 0.0) rLineTo(gc, sx*-102.4, 0)
gc.CubicCurveTo(x+sx*-17, y+sy*205, x+sx*-17, y+sy*103, x+sx*60.0, y+sy*103.0) gc.CubicCurveTo(x+sx*-17, y+sy*205, x+sx*-17, y+sy*103, x+sx*60.0, y+sy*103.0)
gc.Stroke() gc.Stroke()
gc.SetLineDash(nil, 0.0) gc.SetLineDash(nil, 0.0)
@ -188,12 +189,13 @@ func CubicCurve(gc draw2d.GraphicContext, x, y, width, height float64) {
} }
// FillString draws a filled and stroked string. // FillString draws a filled and stroked string.
// And filles/stroked path created from string. Which may have different - unselectable - output in non raster gc implementations.
func FillString(gc draw2d.GraphicContext, x, y, width, height float64) { func FillString(gc draw2d.GraphicContext, x, y, width, height float64) {
sx, sy := width/100, height/100 sx, sy := width/100, height/100
gc.Save() gc.Save()
gc.SetStrokeColor(image.Black) gc.SetStrokeColor(image.Black)
gc.SetLineWidth(1) gc.SetLineWidth(1)
draw2d.RoundRect(gc, x+sx*5, y+sy*5, x+sx*95, y+sy*95, sx*10, sy*10) draw2dkit.RoundedRectangle(gc, x+sx*5, y+sy*5, x+sx*95, y+sy*95, sx*10, sy*10)
gc.FillStroke() gc.FillStroke()
gc.SetFillColor(image.Black) gc.SetFillColor(image.Black)
gc.SetFontSize(height / 6) gc.SetFontSize(height / 6)
@ -201,18 +203,27 @@ func FillString(gc draw2d.GraphicContext, x, y, width, height float64) {
gc.SetFontData(draw2d.FontData{ gc.SetFontData(draw2d.FontData{
Name: "luxi", Name: "luxi",
Family: draw2d.FontFamilyMono, Family: draw2d.FontFamilyMono,
Style: draw2d.FontStyleBold | draw2d.FontStyleItalic}) Style: draw2d.FontStyleBold | draw2d.FontStyleItalic,
})
w := gc.FillString("Hug") w := gc.FillString("Hug")
gc.Translate(w+sx, 0) gc.Translate(w+sx, 0)
left, top, right, bottom := gc.GetStringBounds("cou") left, top, right, bottom := gc.GetStringBounds("cou")
gc.SetStrokeColor(color.NRGBA{255, 0x33, 0x33, 0x80}) gc.SetStrokeColor(color.NRGBA{255, 0x33, 0x33, 0x80})
draw2d.Rect(gc, left, top, right, bottom) draw2dkit.Rectangle(gc, left, top, right, bottom)
gc.SetLineWidth(height / 50) gc.SetLineWidth(height / 50)
gc.Stroke() gc.Stroke()
gc.SetFillColor(color.NRGBA{0x33, 0x33, 0xff, 0xff}) gc.SetFillColor(color.NRGBA{0x33, 0x33, 0xff, 0xff})
gc.SetStrokeColor(color.NRGBA{0x33, 0x33, 0xff, 0xff}) gc.SetStrokeColor(color.NRGBA{0x33, 0x33, 0xff, 0xff})
gc.SetLineWidth(height / 100) gc.SetLineWidth(height / 100)
gc.StrokeString("Hug") gc.StrokeString("Hug")
gc.Translate(-(w + sx), sy*24)
w = gc.CreateStringPath("Hug", 0, 0)
gc.Fill()
gc.Translate(w+sx, 0)
gc.CreateStringPath("Hug", 0, 0)
path := gc.GetPath()
gc.Stroke((&path).VerticalFlip())
gc.Restore() gc.Restore()
} }
@ -221,14 +232,14 @@ func FillStroke(gc draw2d.GraphicContext, x, y, width, height float64) {
sx, sy := width/210, height/215 sx, sy := width/210, height/215
gc.MoveTo(x+sx*113.0, y) gc.MoveTo(x+sx*113.0, y)
gc.LineTo(x+sx*215.0, y+sy*215) gc.LineTo(x+sx*215.0, y+sy*215)
gc.RLineTo(sx*-100, 0) rLineTo(gc, sx*-100, 0)
gc.CubicCurveTo(x+sx*35, y+sy*215, x+sx*35, y+sy*113, x+sx*113.0, y+sy*113) gc.CubicCurveTo(x+sx*35, y+sy*215, x+sx*35, y+sy*113, x+sx*113.0, y+sy*113)
gc.Close() gc.Close()
gc.MoveTo(x+sx*50.0, y) gc.MoveTo(x+sx*50.0, y)
gc.RLineTo(sx*51.2, sy*51.2) rLineTo(gc, sx*51.2, sy*51.2)
gc.RLineTo(sx*-51.2, sy*51.2) rLineTo(gc, sx*-51.2, sy*51.2)
gc.RLineTo(sx*-51.2, sy*-51.2) rLineTo(gc, sx*-51.2, sy*-51.2)
gc.Close() gc.Close()
gc.SetLineWidth(width / 20.0) gc.SetLineWidth(width / 20.0)
@ -237,33 +248,37 @@ func FillStroke(gc draw2d.GraphicContext, x, y, width, height float64) {
gc.FillStroke() gc.FillStroke()
} }
func rLineTo(path draw2d.PathBuilder, x, y float64) {
x0, y0 := path.LastPoint()
path.LineTo(x0+x, y0+y)
}
// FillStyle demonstrates the difference between even odd and non zero winding rule. // FillStyle demonstrates the difference between even odd and non zero winding rule.
func FillStyle(gc draw2d.GraphicContext, x, y, width, height float64) { func FillStyle(gc draw2d.GraphicContext, x, y, width, height float64) {
sx, sy := width/232, height/220 sx, sy := width/232, height/220
gc.SetLineWidth(width / 40) gc.SetLineWidth(width / 40)
draw2d.Rect(gc, x+sx*0, y+sy*12, x+sx*232, y+sy*70) draw2dkit.Rectangle(gc, x+sx*0, y+sy*12, x+sx*232, y+sy*70)
wheel1 := new(draw2d.PathStorage) var wheel1, wheel2 draw2d.Path
wheel1.ArcTo(x+sx*52, y+sy*70, sx*40, sy*40, 0, 2*math.Pi) wheel1.ArcTo(x+sx*52, y+sy*70, sx*40, sy*40, 0, 2*math.Pi)
wheel2 := new(draw2d.PathStorage)
wheel2.ArcTo(x+sx*180, y+sy*70, sx*40, sy*40, 0, -2*math.Pi) wheel2.ArcTo(x+sx*180, y+sy*70, sx*40, sy*40, 0, -2*math.Pi)
gc.SetFillRule(draw2d.FillRuleEvenOdd) gc.SetFillRule(draw2d.FillRuleEvenOdd)
gc.SetFillColor(color.NRGBA{0, 0xB2, 0, 0xFF}) gc.SetFillColor(color.NRGBA{0, 0xB2, 0, 0xFF})
gc.SetStrokeColor(image.Black) gc.SetStrokeColor(image.Black)
gc.FillStroke(wheel1, wheel2) gc.FillStroke(&wheel1, &wheel2)
draw2d.Rect(gc, x, y+sy*140, x+sx*232, y+sy*198) draw2dkit.Rectangle(gc, x, y+sy*140, x+sx*232, y+sy*198)
wheel1 = new(draw2d.PathStorage) wheel1.Clear()
wheel1.ArcTo(x+sx*52, y+sy*198, sx*40, sy*40, 0, 2*math.Pi) wheel1.ArcTo(x+sx*52, y+sy*198, sx*40, sy*40, 0, 2*math.Pi)
wheel2 = new(draw2d.PathStorage) wheel2.Clear()
wheel2.ArcTo(x+sx*180, y+sy*198, sx*40, sy*40, 0, -2*math.Pi) wheel2.ArcTo(x+sx*180, y+sy*198, sx*40, sy*40, 0, -2*math.Pi)
gc.SetFillRule(draw2d.FillRuleWinding) gc.SetFillRule(draw2d.FillRuleWinding)
gc.SetFillColor(color.NRGBA{0, 0, 0xE5, 0xFF}) gc.SetFillColor(color.NRGBA{0, 0, 0xE5, 0xFF})
gc.FillStroke(wheel1, wheel2) gc.FillStroke(&wheel1, &wheel2)
} }
// PathTransform scales a path differently in horizontal and vertical direction. // PathTransform scales a path differently in horizontal and vertical direction.

View file

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

View file

@ -10,8 +10,9 @@ import (
"image/color" "image/color"
"math" "math"
"gopkg.in/llgcode/draw2d.v1" "git.fromouter.space/crunchy-rocks/draw2d"
"gopkg.in/llgcode/draw2d.v1/samples" "git.fromouter.space/crunchy-rocks/draw2d/draw2dkit"
"git.fromouter.space/crunchy-rocks/draw2d/samples"
) )
// Main draws a rotated face of the gopher. Afterwards it returns // Main draws a rotated face of the gopher. Afterwards it returns
@ -46,61 +47,76 @@ func Draw(gc draw2d.GraphicContext, x, y, w, h float64) {
gc.Close() gc.Close()
gc.SetFillColor(brb) gc.SetFillColor(brb)
gc.Fill() gc.Fill()
// rectangle head bottom // rectangle head bottom
draw2d.RoundRect(gc, x, y+h, x+w, y+h+h, h/5, h/5) draw2dkit.RoundedRectangle(gc, x, y+h, x+w, y+h+h, h/5, h/5)
gc.Fill() gc.Fill()
// left ear outside // left ear outside
draw2d.Circle(gc, x, y+h, w/12) draw2dkit.Circle(gc, x, y+h, w/12)
gc.SetFillColor(brf) gc.SetFillColor(brf)
gc.Fill() gc.Fill()
// left ear inside // left ear inside
draw2d.Circle(gc, x, y+h, 0.5*w/12) draw2dkit.Circle(gc, x, y+h, 0.5*w/12)
gc.SetFillColor(nf) gc.SetFillColor(nf)
gc.Fill() gc.Fill()
// right ear outside // right ear outside
draw2d.Circle(gc, x+w, y+h, w/12) draw2dkit.Circle(gc, x+w, y+h, w/12)
gc.SetFillColor(brf) gc.SetFillColor(brf)
gc.Fill() gc.Fill()
// right ear inside // right ear inside
draw2d.Circle(gc, x+w, y+h, 0.5*w/12) draw2dkit.Circle(gc, x+w, y+h, 0.5*w/12)
gc.SetFillColor(nf) gc.SetFillColor(nf)
gc.Fill() gc.Fill()
// left eye outside white // left eye outside white
draw2d.Circle(gc, x+w/3, y+h23, w/9) draw2dkit.Circle(gc, x+w/3, y+h23, w/9)
gc.SetFillColor(wf) gc.SetFillColor(wf)
gc.Fill() gc.Fill()
// left eye black // left eye black
draw2d.Circle(gc, x+w/3+w/24, y+h23, 0.5*w/9) draw2dkit.Circle(gc, x+w/3+w/24, y+h23, 0.5*w/9)
gc.SetFillColor(blf) gc.SetFillColor(blf)
gc.Fill() gc.Fill()
// left eye inside white // left eye inside white
draw2d.Circle(gc, x+w/3+w/24+w/48, y+h23, 0.2*w/9) draw2dkit.Circle(gc, x+w/3+w/24+w/48, y+h23, 0.2*w/9)
gc.SetFillColor(wf) gc.SetFillColor(wf)
gc.Fill() gc.Fill()
// right eye outside white // right eye outside white
draw2d.Circle(gc, x+w-w/3, y+h23, w/9) draw2dkit.Circle(gc, x+w-w/3, y+h23, w/9)
gc.Fill() gc.Fill()
// right eye black // right eye black
draw2d.Circle(gc, x+w-w/3+w/24, y+h23, 0.5*w/9) draw2dkit.Circle(gc, x+w-w/3+w/24, y+h23, 0.5*w/9)
gc.SetFillColor(blf) gc.SetFillColor(blf)
gc.Fill() gc.Fill()
// right eye inside white // right eye inside white
draw2d.Circle(gc, x+w-(w/3)+w/24+w/48, y+h23, 0.2*w/9) draw2dkit.Circle(gc, x+w-(w/3)+w/24+w/48, y+h23, 0.2*w/9)
gc.SetFillColor(wf) gc.SetFillColor(wf)
gc.Fill() gc.Fill()
// left tooth // left tooth
gc.SetFillColor(wf) gc.SetFillColor(wf)
draw2d.RoundRect(gc, x+w/2-w/8, y+h+h/2.5, x+w/2-w/8+w/8, y+h+h/2.5+w/6, w/10, w/10) draw2dkit.RoundedRectangle(gc, x+w/2-w/8, y+h+h/2.5, x+w/2-w/8+w/8, y+h+h/2.5+w/6, w/10, w/10)
gc.Fill() gc.Fill()
// right tooth // right tooth
draw2d.RoundRect(gc, x+w/2, y+h+h/2.5, x+w/2+w/8, y+h+h/2.5+w/6, w/10, w/10) draw2dkit.RoundedRectangle(gc, x+w/2, y+h+h/2.5, x+w/2+w/8, y+h+h/2.5+w/6, w/10, w/10)
gc.Fill() gc.Fill()
// snout // snout
draw2d.Ellipse(gc, x+(w/2), y+h+h/2.5, w/6, w/12) draw2dkit.Ellipse(gc, x+(w/2), y+h+h/2.5, w/6, w/12)
gc.SetFillColor(nf) gc.SetFillColor(nf)
gc.Fill() gc.Fill()
// nose // nose
draw2d.Ellipse(gc, x+(w/2), y+h+h/7, w/10, w/12) draw2dkit.Ellipse(gc, x+(w/2), y+h+h/7, w/10, w/12)
gc.SetFillColor(blf) gc.SetFillColor(blf)
gc.Fill() gc.Fill()
} }

View file

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

Some files were not shown because too many files have changed in this diff Show more