Compare commits

...

338 Commits

Author SHA1 Message Date
Hamcha 675e82cb64
Update go.mod (for real) 2019-02-08 11:15:35 +01:00
Hamcha ff82ab9451 Update 'go.mod' 2019-02-08 11:13:35 +01:00
Hamcha a7539fd29b
Fix bound calculation with just emojis (again, super hacky) 2018-11-16 17:36:38 +01:00
Hamcha 41b8d7304e
Should fix centering issues with trailing emojis 2018-11-16 17:13:49 +01:00
Hamcha 9dbc14edd6
Better approximation of emoji's bounds 2018-11-16 17:00:00 +01:00
Hamcha 9941d77460
Add emojis to bound calculation 2018-11-16 15:19:48 +01:00
Hamcha e3566f7fc4
Add emoji support 2018-11-16 12:21:26 +01:00
Hamcha 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 ce9c7f76f7 Merge pull request #71 from stephenwithav/fontfilenamer
Font naming enhancement
2015-08-09 21:38:03 +02:00
Steven Edwards a78b24d408 Modify font loading functions to use fontNamer. 2015-08-09 12:47:30 -04:00
Steven Edwards be5a5617ef Add SetFontNamer func.
SetFontNamer changs draw2d's default font naming convention.  Accepts a
func of type FontFileNamer, which accepts a FontData and returns the
filename as a string.
2015-08-09 12:44:50 -04:00
Steven Edwards d0cd538261 Add FontFileNamer type, set default fontNamer. 2015-08-09 12:43:37 -04:00
Stani 994e11cd6d Update draw2d.go 2015-07-14 13:53:27 +02:00
Stani c9398530eb fix typo of draw2dgl in docstring of draw2d.go 2015-07-14 13:04:10 +02:00
Stani 27ca8d66a7 Merge pull request #69 from stanim/master
add transparency support to pdf backend
2015-07-14 12:57:39 +02:00
Stani 64dc974458 geometry: make line continuous in arcs and replace "coucou" with "HugHug" to test better ascent, descent and capheight of font 2015-07-13 00:37:15 +02:00
Stani b1e17999b6 fix gopher drawing in gopher2 2015-07-13 00:33:03 +02:00
Stani fcd849f2e4 support different transparencies for fillstroke 2015-07-13 00:31:57 +02:00
Stani 3450d01825 Merge pull request #68 from stanim/fixtext
fix horizontal alignment of text
2015-07-12 21:18:49 +02:00
Stani 6b72c6dec7 fix horizontal alignment of text 2015-07-12 21:17:39 +02:00
Stani bc823442ba Merge pull request #67 from stanim/text
Fix text alignment
2015-07-12 20:50:14 +02:00
Stani 1f0b304e2e fix string alignment with new gofpdf api 2015-07-12 20:48:52 +02:00
Stani bbb2a4b372 add extra lines to check baseline 2015-07-12 20:47:35 +02:00
Stani 88ee58870c Merge pull request #66 from stanim/filename
Use filenames in examples of README
2015-07-12 03:07:01 +02:00
Stani 9bd0ecf14e use filenames in examples README 2015-07-12 03:04:30 +02:00
Stani 316bf1b039 Add README to draw2dpdf 2015-07-12 03:03:27 +02:00
Stani 339f012445 Merge pull request #65 from stanim/master
Fixes for text in draw2dpdf
2015-07-12 02:43:32 +02:00
Stani b4e9f9c266 fix SetFontData for pdf 2015-07-12 02:36:44 +02:00
Stani da9799659c fix colors for stroke font in helloworld 2015-07-12 02:34:34 +02:00
Stani 5d7c08c52f Merge pull request #64 from stanim/pdf
update readme
2015-07-11 22:54:08 +02:00
Stani 6e58827921 update readme 2015-07-11 22:53:24 +02:00
Stani ce7f6b422e Update README.md 2015-07-11 22:07:20 +02:00
Stani 8d08b5e816 Update README.md 2015-07-11 19:42:10 +02:00
Stani a819f6b102 Merge pull request #63 from stanim/samples
Add extra samples, fix arc angle and implement SetLineDash for pdf
2015-07-11 19:36:09 +02:00
Stani 2099d15a26 add readme to output folder 2015-07-11 19:34:48 +02:00
Stani 790eaf6842 add image to README 2015-07-11 19:31:36 +02:00
Stani d3a9977ae5 Added Stani to authors 2015-07-11 19:21:23 +02:00
Stani a5918dc963 only regenerate pdf if it has been deleted 2015-07-11 19:19:03 +02:00
Stani 99aee45045 golint fixes 2015-07-11 19:17:24 +02:00
Stani 16a86e7588 remove resource/result 2015-07-11 19:06:10 +02:00
Stani 224b85d99a include geometry sample to show on the frontpage 2015-07-11 19:04:02 +02:00
Stani 99f1fece69 implement SetLineDash for draw2dpdf 2015-07-11 18:58:28 +02:00
Stani a43544c31d add geometry and gopher2 samples 2015-07-11 18:44:36 +02:00
Stani 6e1b43c32f fix typo 2015-07-11 18:41:38 +02:00
Stani 743e113349 fix arc angle bug 2015-07-11 18:39:51 +02:00
Stani 6a06be3c7c Merge pull request #62 from stanim/fix
Fix pdf bugs and remove gc.Current.Path.Clear()
2015-07-11 01:25:04 +02:00
Stani 08a6c87a0b replace gc.Current.Path.Clear() with gc.Current.Path = draw2d.NewPathStorage(); fixes #61 2015-07-11 01:22:50 +02:00
Stani 781a0defe5 previous fix makes Save and Restore for line caps join unnecessary 2015-07-11 01:13:39 +02:00
Stani a826fc7216 fix path drawing 2015-07-11 01:12:09 +02:00
Stani bb523db1f4 Merge pull request #60 from stanim/coverage
get 100% test coverage for draw2dpdf
2015-07-10 21:57:42 +02:00
Stani f879ad32a7 get 100% test coverage for draw2dpdf 2015-07-10 21:50:07 +02:00
Stani fbb061ceef Merge pull request #59 from stanim/stani
golint fixes and redirect curve tests results to output.curve folder
2015-07-10 17:23:04 +02:00
Stani d00980730f redirect curve tests results to output.curve folder 2015-07-10 17:20:23 +02:00
Stani 55b5c931cf golint fixes 2015-07-10 16:58:36 +02:00
Stani fe06e5046c fix license position in README 2015-07-10 16:57:21 +02:00
Stani 1da2ef6b1e Merge pull request #58 from stanim/raster
redirect results of raster_test to output
2015-07-10 16:21:05 +02:00
Stani 572711ae7e redirect results of raster_test to output 2015-07-10 16:18:49 +02:00
Stani d4e1581526 Merge pull request #57 from stanim/stani
Fix go vet issues
2015-07-10 16:01:04 +02:00
Stani bb793d237f go vet fixes 2015-07-10 15:24:23 +02:00
Stani fb6189e246 add test script 2015-07-10 15:11:57 +02:00
Stani 72c9be4ed0 add copyright information 2015-07-10 15:11:19 +02:00
Stani 089f4efae2 Merge pull request #56 from stanim/samples
Add license in readme
2015-07-10 12:49:52 +02:00
Stani 202ecbdf85 merge upstream 2015-07-10 12:48:04 +02:00
Stani bf2e1c0174 add license to README 2015-07-10 12:45:52 +02:00
Stani 195892ccb9 Merge pull request #55 from stanim/samples
correct draw2d/samples path
2015-07-10 02:43:05 +02:00
Stani 61b036038c correct draw2d/samples path 2015-07-10 02:42:12 +02:00
Stani bdd6cf67be Merge pull request #54 from stanim/samples
correct draw2d/samples path
2015-07-10 02:36:50 +02:00
Stani 730589cb95 correct draw2d/samples path 2015-07-10 02:33:06 +02:00
Stani cec365b96a Merge pull request #53 from stanim/master
Add samples to draw2d
2015-07-10 02:22:08 +02:00
Stani a6fc7c06e1 add sample: postscriptgl 2015-07-10 02:15:23 +02:00
Stani dc12ec8ca5 add sample: postscript 2015-07-10 02:15:13 +02:00
Stani aee8e42541 add sample: linecapjoin 2015-07-10 02:15:00 +02:00
Stani f24cc0d0ec add sample: line 2015-07-10 02:14:48 +02:00
Stani 18946de153 add sample: helloworldgl 2015-07-10 02:14:14 +02:00
Stani bdf30d74eb add sample: helloworld 2015-07-10 02:14:03 +02:00
Stani 401ff36d5a add sample: gopher 2015-07-10 02:13:43 +02:00
Stani 14bf3f5052 add sample: frameimage 2015-07-10 02:13:27 +02:00
Stani 0c99623341 add sample: appengine 2015-07-10 02:12:40 +02:00
Stani 851731f191 add android sample 2015-07-10 02:12:22 +02:00
Stani 51d0dc23f4 add samples folder 2015-07-10 02:11:52 +02:00
Stani 7e64fd0a68 add resource images for sample tests 2015-07-10 02:10:50 +02:00
Stani b1417d86fa add makefont files of luxi so it can be loaded by gofpdf 2015-07-10 02:10:10 +02:00
Stani 96c95d0346 add empty output folder for sample tests 2015-07-10 02:09:19 +02:00
Stani 10796a3100 prepare testing for including samples 2015-07-10 02:07:18 +02:00
Stani f5ef2c0154 use clean path for font folder 2015-07-10 02:06:47 +02:00
Stani 903829eb54 fix fontdir bug 2015-07-10 02:05:22 +02:00
Stani 990ae8fd84 update documentation 2015-07-10 02:03:59 +02:00
Laurent Le Goff 04427cabf5 Merge with master 2015-07-09 18:06:14 +02:00
Laurent Le Goff ed5fa0e5a7 Merge pull request #52 from stanim/doc
improve README.md
2015-07-09 16:12:14 +02:00
Stani 7749b30624 improve README.md 2015-07-09 16:03:05 +02:00
Laurent Le Goff 91a9aff94d Merge pull request #51 from stanim/doc
Godoc improvement.
2015-07-09 09:19:48 +02:00
Stani 4c03f51911 fix imageCount 2015-07-08 11:59:35 +02:00
Stani 33f067e4dd add docstrings to path.go 2015-07-08 10:53:35 +02:00
Stani 437a88ccda improve docstrings of image.go 2015-07-08 00:49:12 +02:00
Stani 62e23c1693 improve docstrings of draw2dpdf/gc.go 2015-07-08 00:48:15 +02:00
Stani dbf591ab97 fix golint for gc.go 2015-07-08 00:42:00 +02:00
Stani 8c8e0b1760 fix draw2d installation documentation 2015-07-08 00:18:47 +02:00
Stani d446bebfa1 fix golint for advanced_path.go 2015-07-08 00:17:58 +02:00
Stani 67213e40fc improve documentation for godoc 2015-07-08 00:01:01 +02:00
Stani 8c807d1289 make sample interface private 2015-07-07 23:35:03 +02:00
Stani cf81b0b120 add documentation fix 2015-07-07 23:32:27 +02:00
Stani e94fab8197 rename draw2dpdf graphiccontext.go to gc.go as in draw2d 2015-07-07 21:48:16 +02:00
Laurent Le Goff 7a817d6c88 Merge pull request #48 from stanim/pdf
Pdf support
2015-07-07 09:24:13 +02:00
Stani dbdf3e22bc rename pdf2d to draw2dpdf 2015-07-07 00:25:56 +02:00
Stani dbd88a6569 rename pdf2d to draw2dpdf 2015-07-07 00:25:24 +02:00
Stani 96e73eab52 updated repository names 2015-07-07 00:07:44 +02:00
Stani 8ca9a0bbe3 fix comment 2015-07-01 20:27:41 +02:00
Stani 1841a3e90f golint fix 2015-07-01 17:13:33 +02:00
Stani 4262ff5718 update gitignore 2015-07-01 15:38:14 +02:00
Stani 99dce02263 remove path_logger 2015-07-01 15:37:27 +02:00
Stani a861276f40 add postscript sample test 2015-07-01 15:15:37 +02:00
Stani 8e69b2d8d6 add linecapsjoin sample test 2015-07-01 14:44:51 +02:00
Stani 19b20e1fba skip overwriting pdfs during tests 2015-07-01 14:44:01 +02:00
Stani f6e57f4712 let fill color define text color for pdf and implement SetLineJoin 2015-07-01 14:13:54 +02:00
Stani 71c1aba4fe separate out test function and refactor line sample 2015-07-01 10:34:23 +02:00
Stani 0144ae516b added docstring to package draw2d_test 2015-07-01 01:39:01 +02:00
Stani 225f790d40 replace gc.scale with gc.Current.Scale 2015-07-01 01:37:19 +02:00
Stani 0acdcf4fff fix recalc 2015-07-01 01:36:27 +02:00
Stani 39bb5b6cf6 removed samples folder 2015-07-01 01:28:00 +02:00
Stani 5e8829cc26 refactor pdf2d.GraphicContext samples, so they can be used for test coverage 2015-07-01 01:11:20 +02:00
Stani a3866cec52 refactor ImgGraphicContext samples, so they can be used for test coverage 2015-07-01 01:10:32 +02:00
Stani 2f2bd1937a - fix font size
- make ContextStack Font and Scale public
- make ContextStack.Scale float64, so it can also be used for pdf
2015-07-01 01:06:53 +02:00
Stani 6608ccca67 update gitignore 2015-07-01 00:44:33 +02:00
Stani b3bea4f206 use pdf native transforms 2015-06-29 01:29:04 +02:00
Stani 458c46b28c add file for pdf2d package documentation 2015-06-28 01:41:06 +02:00
Stani 4d393c1035 add transformation to text and images 2015-06-27 20:06:06 +02:00
Stani 244893c6ab added helloworld sample 2015-06-27 19:42:59 +02:00
Stani be91a632c9 fixed the SetFontData method 2015-06-27 19:41:34 +02:00
Stani 13f1147b90 set default fill color to white for NewPdf 2015-06-27 18:33:15 +02:00
Stani 2905171a22 added a NewPdf constructor for convenience 2015-06-27 18:24:16 +02:00
Stani 01150ac981 add gopher sample 2015-06-27 17:55:34 +02:00
Stani eee9c42bb9 moved line in frame-image 2015-06-27 17:55:01 +02:00
Stani 5a3d7085a2 added working frame-image sample 2015-06-27 17:47:29 +02:00
Stani 7ffd99f6bd fix GraphicContext.DrawImage and use PNG as default format 2015-06-27 17:46:03 +02:00
Stani 66b0d91546 move android in its own folder 2015-06-27 17:19:08 +02:00
Stani 464bc2488b improved comment 2015-06-27 17:18:36 +02:00
Stani d140c26465 disable PathLogger 2015-06-27 16:51:37 +02:00
Stani 5d1b0f3315 added an example as documentation and test 2015-06-27 16:47:27 +02:00
Stani 0d629a4957 fix typo 2015-06-27 16:34:30 +02:00
Stani 74e84c4493 implemented more methods for pdf graphic context, fixed the code for 100% golint and go vet 2015-06-27 16:21:13 +02:00
Stani bbcbc3df5e fix c255 for rgb conversion 2015-06-27 13:50:43 +02:00
Stani a422b2462d partial implementation of gofpdf backend 2015-06-27 01:13:20 +02:00
Stani d47e08f7c9 Preparing for pdf backend
- make PathStorage Commands and Vertices public, so they can be accessed by the pdf backend
- start every path with MoveTo (also before ArcTo)
2015-06-27 01:03:41 +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
Laurent Le Goff f8fb5a2052 Update doc 2015-04-22 16:22:24 +02:00
Laurent Le Goff f43b901cb4 Update README.md 2015-04-22 16:05:00 +02:00
Laurent Le Goff d41211e1f1 Update README.md 2015-04-22 16:03:13 +02:00
Laurent Le Goff cfe27c6c0a Change Readme 2015-04-22 15:59:42 +02:00
Laurent Le Goff 26fd99f263 remove deprecated files 2015-04-22 15:59:32 +02:00
Laurent Le Goff 146e6d6c8e Add Save and Load util function 2015-04-22 15:59:01 +02:00
Laurent Le Goff 93abbc2231 move to draw2d.samples 2015-04-22 15:58:36 +02:00
Laurent Le Goff 78e0449cca Move samples to draw2d.samples 2015-04-22 15:58:13 +02:00
Laurent Le Goff b7cc153384 remove unecessary folder 2015-04-22 11:08:18 +02:00
Laurent Le Goff 5908f1da11 remove unecessary folder 2015-04-22 11:06:40 +02:00
Laurent Le Goff 17aa105606 Change root documentation and rename doc.go in draw2d.go 2015-04-22 10:49:37 +02:00
Laurent Le Goff a211e06ea7 remove sample cmd 2015-04-19 23:21:31 +02:00
Laurent Le Goff cef48beb1f remove sample cmd 2015-04-19 23:20:44 +02:00
Laurent Le Goff fff6b05c93 Fix img url 2015-04-19 17:30:15 +02:00
Laurent Le Goff b4f19d35fe Merge simpler import path 2015-04-19 17:14:42 +02:00
Laurent Le Goff 870a1a827e Remove deprecated Code 2015-04-19 16:45:43 +02:00
Laurent Le Goff 690ce767e2 Merge pull request #36 from sbinet/mkdown-readme
doc: markdown-ify
2015-04-19 15:07:55 +02:00
Sebastien Binet f1d2545bc5 gitignore: translate from hgignore 2015-04-19 00:32:22 +02:00
Sebastien Binet 2e7baa0204 doc: markdown-ify
wiki: add test results images
2015-04-19 00:15:40 +02:00
Laurent Le Goff 8b0d5d5048 Clean 2015-04-17 17:03:50 +02:00
Laurent Le Goff 5990ba619c Fix SwapBuffers 2015-04-17 16:54:29 +02:00
Laurent Le Goff 0bbef16af2 Add rounded Rect sample 2015-04-17 16:54:12 +02:00
Laurent Le Goff dba783c8af call reshape at start up to initiliaze opengl context 2015-04-17 15:35:50 +02:00
Laurent Le Goff 0abcba8669 Merge branch 'sbinet-draw2dgl-with-go-gl' 2015-04-17 15:03:49 +02:00
Laurent Le Goff b1b8ea1540 merge sbinet change 2015-04-17 14:57:59 +02:00
Laurent Le Goff 6857de8d5b move postscript code to separate repository llgcode/ps 2015-04-17 12:11:33 +02:00
Sebastien Binet 0eeb62825f make: enable draw2dgl build+test 2015-04-17 11:44:38 +02:00
Sebastien Binet ef603db535 draw2dgl: remove unneeded GLVertex 2015-04-17 11:44:38 +02:00
Sebastien Binet c1b7f443b4 draw2dgl: de-stutter types 2015-04-17 11:44:38 +02:00
Sebastien Binet a8154b175c draw2dgl: use github.com/go-gl/{gl,glfw} 2015-04-17 11:44:38 +02:00
Laurent Le Goff 3d2a09c9e2 Merge branch 'sbinet-github-imports' 2015-04-17 11:17:23 +02:00
Sebastien Binet 49e09051b1 make: add a test target 2015-04-16 17:01:43 +02:00
Sebastien Binet 983b7fb4a9 doc: fix some links 2015-04-16 12:03:35 +02:00
Sebastien Binet f78ac58522 all: gofmt+goimports 2015-04-16 11:51:13 +02:00
Sebastien Binet 7e968a713e all: update imports to github.com/llgcode/draw2d 2015-04-16 11:40:22 +02:00
Laurent Le Goff 91cca64ef9 Update notes.md 2015-03-26 11:02:32 +01:00
Laurent Le Goff 234df7b8d1 Create notes.md 2015-03-26 10:10:11 +01:00
legoff laurent 90b71903ae fix issue 33 2015-01-07 14:35:09 +00:00
Laurent Le Goff 79dabfe7e3 Fix build, follow freetype changes 2014-02-13 09:16:40 +01:00
Jonathan Feinberg 4cfd6dfa78 Add GetStringBounds API method. 2013-05-28 22:48:35 -04:00
Jonathan Feinberg 80a6c6c182 Include new API in interface. 2013-05-27 15:09:57 -04:00
Jonathan Feinberg 19b0ec55b5 Initial import of new freetype font handling. 2013-05-27 14:53:53 -04:00
Laurent Le Goff b864adcd21 Apply patch from issue 26, thanks to Ethan Burns 2012-07-30 12:09:00 +02:00
Laurent Le Goff fb2948a31e issue23 2012-05-28 09:56:02 +02:00
Laurent Le Goff f7dc650faa add Painter Arg 2012-05-28 09:52:49 +02:00
Laurent Le Goff fda5c8e713 fix issue 23 2012-04-24 10:28:31 +02:00
Laurent Le Goff 041bb7cbbb Test win32 api 2012-04-19 16:49:55 +02:00
Laurent Le Goff 492dcdf86f refactoring 2012-04-18 19:05:41 +02:00
Laurent Le Goff cf94206931 Start documenting main package 2012-04-17 11:03:56 +02:00
Laurent Le Goff 489c6261be Change Register parameter 2012-04-17 10:18:26 +02:00
Laurent Le Goff 51a750535c rm Makefiles 2012-04-17 10:10:44 +02:00
Laurent Le Goff 6d19118a1c Add RegisterFont 2012-04-16 23:23:08 +02:00
Laurent Le Goff 7f2c247e7a Clean up for go1 2012-03-29 16:52:12 +02:00
Laurent Le Goff c595982fba gofix 2012-01-13 10:14:12 +01:00
Laurent Le Goff 72c07eba44 remove postscript doc 2011-10-24 05:14:02 +02:00
legoff laurent e174ed5ef3 remove Copyright for godoc 2011-10-06 05:44:37 +00:00
Laurent Le Goff 64935ae1fc Added tag release for changeset 7fd80a94eb43 2011-09-29 10:02:06 +02:00
Laurent Le Goff 72ac0e0688 Added tag weekly for changeset 92ad94222224 2011-09-29 09:55:54 +02:00
191 changed files with 11932 additions and 350487 deletions

25
.gitignore vendored Normal file
View File

@ -0,0 +1,25 @@
.DS_Store
**/*.[568ao]
**/*.ao
**/*.so
**/*.pyc
**/._*
**/.nfs.*
**/[568a].out
**/*.exe
**/*~
**/*.orig
**/*.out
**/*.test
core
_obj
_test
out.png
_test*
**/*.dll
**/core*[0-9]
.private
go.sum

View File

@ -1,23 +0,0 @@
syntax:glob
.DS_Store
.git
.gitignore
*.[568ao]
*.ao
*.so
*.pyc
._*
.nfs.*
[568a].out
*.exe
*~
*.orig
core
_obj
_test
out.png
_test*
syntax: regexp
\.dll$
^.*/core.[0-9]*$

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 +1,4 @@
Laurent Le Goff
Laurent Le Goff
Stani Michiels, gmail:stani.be
Drahoslav Bednář
Sebastien Binet

View File

@ -1,30 +0,0 @@
#include $(GOROOT)/src/Make.inc
all: install
install:
cd draw2d && make install
# cd draw2dgl && make install
cd postscript && make install
# cd wingui && make install
clean:
cd draw2d && make clean
# cd draw2dgl && make clean
cd postscript && make clean
cd cmd && make clean
# cd wingui && make clean
nuke:
cd draw2d && make nuke
# cd draw2dgl && make nuke
cd postscript && make nuke
# cd wingui && make nuke
command:
cd cmd && make
fmt:
gofmt -w .

35
README
View File

@ -1,35 +0,0 @@
This package (written in *[http://golang.org go]*) provide an API to draw 2d geometrical form on [http://golang.org/pkg/image/ images].
This library is largely inspired by [http://www.tailrecursive.org/postscript/ postscript], [http://cairographics.org/ cairo], [http://dev.w3.org/html5/canvas-api/canvas-2d-api.html#the-2d-drawing-context HTML5 canvas].
The package depends on [http://code.google.com/p/freetype-go/ freetype-go] package thanks to its rasterization algorithm.
Some algorithm have been translated from http://www.antigrain.com project ([http://www.antigrain.com/research/adaptive_bezier/index.html adaptive bezier], and arc drawing)
=== Installation ====
Once you have Go installed, to install draw2d:
* First see the installation procedure of [http://code.google.com/p/freetype-go/ freetype-go]
* goinstall draw2d.googlecode.com/svn/trunk/draw2d/src/pkg/draw2d
a good starting point is the [http://code.google.com/p/draw2d/wiki/GettingStarted getting started]
=== [http://code.google.com/p/draw2d/wiki/Samples Samples] ===
Sample images generated by draw2d (inspired by [http://cairographics.org/samples/ cairo samples]):
there's already some bugs please refer to [http://code.google.com/p/draw2d/issues/list issue tracking]
[http://draw2d.googlecode.com/svn/wiki/test_results/TestPath.png]
[http://draw2d.googlecode.com/svn/wiki/test_results/TestDrawArc.png]
[http://draw2d.googlecode.com/svn/wiki/test_results/TestDrawArcNegative.png]
[http://draw2d.googlecode.com/svn/wiki/test_results/TestCurveRectangle.png]
[http://draw2d.googlecode.com/svn/wiki/test_results/TestDrawCubicCurve.png]
[http://draw2d.googlecode.com/svn/wiki/test_results/TestDash.png]
[http://draw2d.googlecode.com/svn/wiki/test_results/TestFillStroke.png]
[http://draw2d.googlecode.com/svn/wiki/test_results/TestFillStyle.png]
[http://draw2d.googlecode.com/svn/wiki/test_results/TestMultiSegmentCaps.png]
[http://draw2d.googlecode.com/svn/wiki/test_results/TestRoundRectangle.png]
[http://draw2d.googlecode.com/svn/wiki/test_results/TestLineCap.png]
[http://draw2d.googlecode.com/svn/wiki/test_results/TestLineJoin.png]

144
README.md Normal file
View File

@ -0,0 +1,144 @@
draw2d
======
[![Coverage](http://gocover.io/_badge/github.com/llgcode/draw2d?0)](http://gocover.io/github.com/llgcode/draw2d)
[![GoDoc](https://godoc.org/github.com/llgcode/draw2d?status.svg)](https://godoc.org/github.com/llgcode/draw2d)
Package draw2d is a pure [go](http://golang.org) 2D vector graphics library with support for multiple output devices such as [images](http://golang.org/pkg/image) (draw2d), pdf documents (draw2dpdf) and opengl (draw2dgl), which can also be used on the google app engine. It can be used as a pure go [Cairo](http://www.cairographics.org/) alternative. draw2d is released under the BSD license. See the [documentation](http://godoc.org/github.com/llgcode/draw2d) for more details.
[![geometry](https://raw.githubusercontent.com/llgcode/draw2d/master/output/samples/geometry.png)](https://raw.githubusercontent.com/llgcode/draw2d/master/resource/image/geometry.pdf)[![postscript](https://raw.githubusercontent.com/llgcode/draw2d/master/output/samples/postscript.png)](https://raw.githubusercontent.com/llgcode/draw2d/master/resource/image/postscript.pdf)
Click on an image above to get the pdf, generated with exactly the same draw2d code. The first image is the output of `samples/geometry`. The second image is the result of `samples/postcript`, which demonstrates that draw2d can draw postscript files into images or pdf documents with the [ps](https://github.com/llgcode/ps) package.
Features
--------
Operations in draw2d include stroking and filling polygons, arcs, Bézier curves, drawing images and text rendering with truetype fonts. All drawing operations can be transformed by affine transformations (scale, rotation, translation).
Package draw2d follows the conventions of the [HTML Canvas 2D Context](http://www.w3.org/TR/2dcontext/) for coordinate system, angles, etc...
Installation
------------
Install [golang](http://golang.org/doc/install). To install or update the package draw2d on your system, run:
Stable release
```
go get -u gopkg.in/llgcode/draw2d.v1
```
or Current release
```
go get -u github.com/llgcode/draw2d
```
Quick Start
-----------
The following Go code generates a simple drawing and saves it to an image file with package draw2d:
```go
package main
import (
"github.com/llgcode/draw2d/draw2dimg"
"image"
"image/color"
)
func main() {
// Initialize the graphic context on an RGBA image
dest := image.NewRGBA(image.Rect(0, 0, 297, 210.0))
gc := draw2dimg.NewGraphicContext(dest)
// Set some properties
gc.SetFillColor(color.RGBA{0x44, 0xff, 0x44, 0xff})
gc.SetStrokeColor(color.RGBA{0x44, 0x44, 0x44, 0xff})
gc.SetLineWidth(5)
// Draw a closed shape
gc.BeginPath() // Initialize a new path
gc.MoveTo(10, 10) // Move to a position to start the new path
gc.LineTo(100, 50)
gc.QuadCurveTo(100, 10, 10, 10)
gc.Close()
gc.FillStroke()
// Save to file
draw2dimg.SaveToPngFile("hello.png", dest)
}
```
The same Go code can also generate a pdf document with package draw2dpdf:
```go
package main
import (
"github.com/llgcode/draw2d/draw2dpdf"
"image/color"
)
func main() {
// Initialize the graphic context on an RGBA image
dest := draw2dpdf.NewPdf("L", "mm", "A4")
gc := draw2dpdf.NewGraphicContext(dest)
// Set some properties
gc.SetFillColor(color.RGBA{0x44, 0xff, 0x44, 0xff})
gc.SetStrokeColor(color.RGBA{0x44, 0x44, 0x44, 0xff})
gc.SetLineWidth(5)
// Draw a closed shape
gc.MoveTo(10, 10) // should always be called first for a new path
gc.LineTo(100, 50)
gc.QuadCurveTo(100, 10, 10, 10)
gc.Close()
gc.FillStroke()
// Save to file
draw2dpdf.SaveToPdfFile("hello.pdf", dest)
}
```
There are more examples here: https://github.com/llgcode/draw2d/tree/master/samples
Drawing on opengl is provided by the draw2dgl package.
Testing
-------
The samples are run as tests from the root package folder `draw2d` by:
```
go test ./...
```
Or if you want to run with test coverage:
```
go test -cover ./... | grep -v "no test"
```
This will generate output by the different backends in the output folder.
Acknowledgments
---------------
[Laurent Le Goff](https://github.com/llgcode) wrote this library, inspired by [Postscript](http://www.tailrecursive.org/postscript) and [HTML5 canvas](http://www.w3.org/TR/2dcontext/). He implemented the image and opengl backend with the [freetype-go](https://code.google.com/p/freetype-go/) package. Also he created a pure go [Postscript interpreter](https://github.com/llgcode/ps), which can read postscript images and draw to a draw2d graphic context. [Stani Michiels](https://github.com/stanim) implemented the pdf backend with the [gofpdf](https://github.com/jung-kurt/gofpdf) package.
Packages using draw2d
---------------------
- [ps](https://github.com/llgcode/ps): Postscript interpreter written in Go
- [gonum/plot](https://github.com/gonum/plot): drawing plots in Go
- [go.uik](https://github.com/skelterjohn/go.uik): a concurrent UI kit written in pure go.
- [smartcrop](https://github.com/muesli/smartcrop): content aware image cropping
- [karta](https://github.com/peterhellberg/karta): drawing Voronoi diagrams
- [chart](https://github.com/vdobler/chart): basic charts in Go
- [hilbert](https://github.com/google/hilbert): package for drawing Hilbert curves
References
---------
- [antigrain.com](http://www.antigrain.com)
- [freetype-go](http://code.google.com/p/freetype-go)
-

View File

@ -1,17 +0,0 @@
include $(GOROOT)/src/Make.inc
TARG=gettingStarted testdraw2d testX11draw testandroid testgopher testimage testpostscript #draw2dgl
OFILES=$(TARG:%=%.$O)
all: $(TARG)
$(TARG): %: %.$O
$(LD) -o $@ $<
$(OFILES): %.$O: %.go Makefile
$(GC) -o $@ $<
clean:
rm -f *.[$(OS)] $(TARG) $(CLEANFILES)

View File

@ -1,96 +0,0 @@
// Ported from GLUT's samples. Original copyright below applies.
/* Copyright (c) Mark J. Kilgard, 1996. */
/* This program is freely distributable without licensing fees
and is provided without guarantee or warrantee expressed or
implied. This program is -not- in the public domain. */
/* This program is a response to a question posed by Gil Colgate
<gcolgate@sirius.com> about how lengthy a program is required using
OpenGL compared to using Direct3D immediate mode to "draw a
triangle at screen coordinates 0,0, to 200,200 to 20,200, and I
want it to be blue at the top vertex, red at the left vertex, and
green at the right vertex". I'm not sure how long the Direct3D
program is; Gil has used Direct3D and his guess is "about 3000
lines of code". */
package main
import (
"os"
"math"
"io/ioutil"
"strings"
"gl"
"glut"
"draw2d.googlecode.com/hg/draw2dgl"
"draw2d.googlecode.com/hg/postscript"
"log"
"time"
)
var postscriptContent string
var (
width, height int
rotate int
)
func reshape(w, h int) {
/* Because Gil specified "screen coordinates" (presumably with an
upper-left origin), this short bit of code sets up the coordinate
system to correspond to actual window coodrinates. This code
wouldn't be required if you chose a (more typical in 3D) abstract
coordinate system. */
gl.ClearColor(1, 1, 1, 1)
//fmt.Println(gl.GetString(gl.EXTENSIONS))
gl.Viewport(0, 0, w, h) /* Establish viewing area to cover entire window. */
gl.MatrixMode(gl.PROJECTION) /* Start modifying the projection matrix. */
gl.LoadIdentity() /* Reset project matrix. */
gl.Ortho(0, float64(w), 0, float64(h), -1, 1) /* Map abstract coords directly to window coords. */
gl.Scalef(1, -1, 1) /* Invert Y axis so increasing Y goes down. */
gl.Translatef(0, float32(-h), 0) /* Shift origin up to upper-left corner. */
gl.Enable(gl.BLEND)
gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
gl.Disable(gl.DEPTH_TEST)
width, height = w, h
}
func display() {
gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
gl.LineWidth(1)
gc := draw2dgl.NewGraphicContext(width, height)
gc.Translate(380, 400)
gc.Scale(1, -1)
rotate = (rotate + 1) % 360
gc.Rotate(float64(rotate) * math.Pi / 180)
gc.Translate(-380, -400)
interpreter := postscript.NewInterpreter(gc)
reader := strings.NewReader(postscriptContent)
lastTime := time.Nanoseconds()
interpreter.Execute(reader)
dt := time.Nanoseconds() - lastTime
log.Printf("Redraw in : %f ms\n", float64(dt)*1e-6)
gl.Flush() /* Single buffered, so needs a flush. */
glut.PostRedisplay()
}
func main() {
src, err := os.OpenFile("../resource/postscript/tiger.ps", 0, 0)
if err != nil {
log.Println("can't find postscript file.")
return
}
defer src.Close()
bytes, err := ioutil.ReadAll(src)
postscriptContent = string(bytes)
glut.Init()
glut.InitWindowSize(800, 800)
glut.CreateWindow("Show Tiger in Opengl")
glut.DisplayFunc(display)
glut.ReshapeFunc(reshape)
glut.MainLoop()
}

View File

@ -1,46 +0,0 @@
// Copyright 2010 The draw2d Authors. All rights reserved.
// created: 21/11/2010 by Laurent Le Goff
package main
import (
"fmt"
"log"
"os"
"bufio"
"image"
"image/png"
"draw2d.googlecode.com/hg/draw2d"
)
func saveToPngFile(filePath string, m image.Image) {
f, err := os.Create(filePath)
if err != nil {
log.Println(err)
os.Exit(1)
}
defer f.Close()
b := bufio.NewWriter(f)
err = png.Encode(b, m)
if err != nil {
log.Println(err)
os.Exit(1)
}
err = b.Flush()
if err != nil {
log.Println(err)
os.Exit(1)
}
fmt.Printf("Wrote %s OK.\n", filePath)
}
func main() {
i := image.NewRGBA(image.Rect(0,0,200, 200))
gc := draw2d.NewGraphicContext(i)
gc.MoveTo(10.0, 10.0)
gc.LineTo(100.0, 10.0)
gc.Stroke()
saveToPngFile("TestPath.png", i)
}

View File

@ -1,235 +0,0 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"fmt"
"syscall"
"os"
"unsafe"
"image"
"io/ioutil"
"strings"
"time"
"draw2d.googlecode.com/hg/draw2d"
"draw2d.googlecode.com/hg/postscript"
"draw2d.googlecode.com/hg/wingui"
)
// some help functions
func abortf(format string, a ...interface{}) {
fmt.Fprintf(os.Stdout, format, a...)
os.Exit(1)
}
func abortErrNo(funcname string, err int) {
abortf("%s failed: %d %s\n", funcname, err, syscall.Errstr(err))
}
// global vars
func TestDrawCubicCurve(gc draw2d.GraphicContext) {
// draw a cubic curve
x, y := 25.6, 128.0
x1, y1 := 102.4, 230.4
x2, y2 := 153.6, 25.6
x3, y3 := 230.4, 128.0
gc.SetFillColor(image.NRGBAColor{0xAA, 0xAA, 0xAA, 0xFF})
gc.SetLineWidth(10)
gc.MoveTo(x, y)
gc.CubicCurveTo(x1, y1, x2, y2, x3, y3)
gc.Stroke()
gc.SetStrokeColor(image.NRGBAColor{0xFF, 0x33, 0x33, 0x88})
gc.SetLineWidth(6)
// draw segment of curve
gc.MoveTo(x, y)
gc.LineTo(x1, y1)
gc.LineTo(x2, y2)
gc.LineTo(x3, y3)
gc.Stroke()
}
var (
mh uint32
wndBufferHeader uint32
wndBuffer wingui.BITMAP
hdcWndBuffer uint32
ppvBits *image.RGBAColor
backBuffer *image.RGBA
postscriptContent string
)
// WinProc called by windows to notify us of all windows events we might be interested in.
func WndProc(hwnd, msg uint32, wparam, lparam int32) uintptr {
var rc int32
switch msg {
case wingui.WM_CREATE:
hdc := wingui.GetDC(hwnd)
hdcWndBuffer = wingui.CreateCompatibleDC(hdc)
wndBufferHeader = wingui.CreateCompatibleBitmap(hdc, 600, 800)
wingui.GetObject(wndBufferHeader, unsafe.Sizeof(wndBuffer), uintptr(unsafe.Pointer(&wndBuffer)))
wingui.SelectObject(hdcWndBuffer, wndBufferHeader)
var bmp_header wingui.BITMAPINFOHEADER
bmp_header.Size = uint32(unsafe.Sizeof(bmp_header))
bmp_header.Width = 600
bmp_header.Height = 800
bmp_header.SizeImage = 0 // the api says this must be 0 for BI_RGB images
bmp_header.Compression = wingui.BI_RGB
bmp_header.BitCount = 32
bmp_header.Planes = 1
bmp_header.XPelsPerMeter = 0
bmp_header.YPelsPerMeter = 0
bmp_header.ClrUsed = 0
bmp_header.ClrImportant = 0
//bitmap info
var bmpinfo wingui.BITMAPINFO
bmpinfo.Colors[0].Blue = 0
bmpinfo.Colors[0].Green = 0
bmpinfo.Colors[0].Red = 0
bmpinfo.Colors[0].Reserved = 0
bmpinfo.Header = bmp_header
wndBufferHeader = wingui.CreateDIBSection(hdc, &bmpinfo, wingui.DIB_RGB_COLORS, uintptr(unsafe.Pointer(&ppvBits)), 0, 0)
wingui.GetObject(wndBufferHeader, unsafe.Sizeof(wndBufferHeader), uintptr(unsafe.Pointer(&wndBuffer)))
hdcWndBuffer = wingui.CreateCompatibleDC(hdc)
wingui.SelectObject(hdcWndBuffer, wndBufferHeader)
pixel := (*[600 * 800]image.RGBAColor)(unsafe.Pointer(ppvBits))
pixelSlice := pixel[:]
backBuffer = &image.RGBA{pixelSlice, 600, image.Rect(0, 0, 600, 800)}
fmt.Println("Create windows")
rc = wingui.DefWindowProc(hwnd, msg, wparam, lparam)
case wingui.WM_COMMAND:
switch uint32(lparam) {
default:
rc = wingui.DefWindowProc(hwnd, msg, wparam, lparam)
}
case wingui.WM_PAINT:
var ps wingui.PAINTSTRUCT
lastTime := time.Nanoseconds()
hdc := wingui.BeginPaint(hwnd, &ps)
gc := draw2d.NewGraphicContext(backBuffer)
gc.SetFillColor(image.RGBAColor{0xFF, 0xFF, 0xFF, 0xFF})
// gc.Clear()
gc.Save()
//gc.Translate(0, -380)
interpreter := postscript.NewInterpreter(gc)
reader := strings.NewReader(postscriptContent)
interpreter.Execute(reader)
dt := time.Nanoseconds() - lastTime
gc.Restore()
// back buf in
wingui.BitBlt(hdc, 0, 0, int(wndBuffer.Width), int(wndBuffer.Height), hdcWndBuffer, 0, 0, wingui.SRCCOPY)
wingui.EndPaint(hwnd, &ps)
rc = wingui.DefWindowProc(hwnd, msg, wparam, lparam)
fmt.Printf("Redraw in : %f ms\n", float64(dt)*1e-6)
case wingui.WM_CLOSE:
wingui.DestroyWindow(hwnd)
case wingui.WM_DESTROY:
wingui.PostQuitMessage(0)
default:
rc = wingui.DefWindowProc(hwnd, msg, wparam, lparam)
}
return uintptr(rc)
}
func rungui() int {
var e int
// GetModuleHandle
mh, e = wingui.GetModuleHandle(nil)
if e != 0 {
abortErrNo("GetModuleHandle", e)
}
// Get icon we're going to use.
myicon, e := wingui.LoadIcon(0, wingui.IDI_APPLICATION)
if e != 0 {
abortErrNo("LoadIcon", e)
}
// Get cursor we're going to use.
mycursor, e := wingui.LoadCursor(0, wingui.IDC_ARROW)
if e != 0 {
abortErrNo("LoadCursor", e)
}
// Create callback
wproc := syscall.NewCallback(WndProc)
// RegisterClassEx
wcname := syscall.StringToUTF16Ptr("Test Draw2d")
var wc wingui.Wndclassex
wc.Size = uint32(unsafe.Sizeof(wc))
wc.WndProc = wproc
//wc.Style = wingui.CS_HREDRAW | wingui.CS_VREDRAW
wc.Instance = mh
wc.Icon = myicon
wc.Cursor = mycursor
wc.Background = 0
wc.MenuName = nil
wc.ClassName = wcname
wc.IconSm = myicon
if _, e := wingui.RegisterClassEx(&wc); e != 0 {
abortErrNo("RegisterClassEx", e)
}
// CreateWindowEx
wh, e := wingui.CreateWindowEx(
wingui.WS_EX_CLIENTEDGE,
wcname,
syscall.StringToUTF16Ptr("My window"),
wingui.WS_OVERLAPPEDWINDOW,
wingui.CW_USEDEFAULT, wingui.CW_USEDEFAULT, 600, 800,
0, 0, mh, 0)
if e != 0 {
abortErrNo("CreateWindowEx", e)
}
fmt.Printf("main window handle is %x\n", wh)
// ShowWindow
wingui.ShowWindow(wh, wingui.SW_SHOWDEFAULT)
// UpdateWindow
if e := wingui.UpdateWindow(wh); e != 0 {
abortErrNo("UpdateWindow", e)
}
// Process all windows messages until WM_QUIT.
var m wingui.Msg
for {
r, e := wingui.GetMessage(&m, 0, 0, 0)
if e != 0 {
abortErrNo("GetMessage", e)
}
if r == 0 {
// WM_QUIT received -> get out
break
}
wingui.TranslateMessage(&m)
wingui.DispatchMessage(&m)
}
return int(m.Wparam)
}
func main() {
src, err := os.OpenFile("../resource/postscript/tiger.ps", 0, 0)
if err != nil {
fmt.Println("can't find postscript file.")
return
}
defer src.Close()
bytes, err := ioutil.ReadAll(src)
postscriptContent = string(bytes)
rc := rungui()
os.Exit(rc)
}

View File

@ -1,57 +0,0 @@
package main
import (
"fmt"
"exp/gui"
"exp/gui/x11"
"image"
"math"
"draw2d.googlecode.com/hg/draw2d"
)
func main() {
window, err := x11.NewWindow()
if err != nil {
fmt.Printf("Cannot open an x11 window\n")
return
}
screen := window.Screen()
gc := draw2d.NewGraphicContext(screen)
gc.SetStrokeColor(image.Black)
gc.SetFillColor(image.White)
gc.Clear()
for i := 0.0; i < 360; i = i + 10 { // Go from 0 to 360 degrees in 10 degree steps
gc.BeginPath() // Start a new path
gc.Save() // Keep rotations temporary
gc.MoveTo(144, 144)
gc.Rotate(i * (math.Pi / 180.0)) // Rotate by degrees on stack from 'for'
gc.RLineTo(72, 0)
gc.Stroke()
gc.Restore() // Get back the unrotated state
}
window.FlushImage()
gc.SetLineWidth(3)
nbclick := 0
for {
switch evt := (<-window.EventChan()).(type) {
case gui.KeyEvent:
if evt.Key == 'q' {
window.Close()
}
case gui.MouseEvent:
if evt.Buttons&1 != 0 {
if nbclick%2 == 0 {
gc.MoveTo(float64(evt.Loc.X), float64(evt.Loc.Y))
} else {
gc.LineTo(float64(evt.Loc.X), float64(evt.Loc.Y))
gc.Stroke()
window.FlushImage()
}
nbclick = nbclick + 1
}
}
}
}

View File

@ -1,98 +0,0 @@
package main
import (
"fmt"
"log"
"os"
"bufio"
"time"
"math"
"image"
"image/png"
"draw2d.googlecode.com/hg/draw2d"
)
const (
width, height = 178, 224
)
var (
lastTime int64
folder = "../resource/result/"
)
func initGc(w, h int) (image.Image, draw2d.GraphicContext) {
i := image.NewRGBA(image.Rect(0, 0,w, h))
gc := draw2d.NewGraphicContext(i)
lastTime = time.Nanoseconds()
gc.SetStrokeColor(image.Black)
gc.SetFillColor(image.White)
// fill the background
//gc.Clear()
return i, gc
}
func saveToPngFile(TestName string, m image.Image) {
dt := time.Nanoseconds() - lastTime
fmt.Printf("%s during: %f ms\n", TestName, float64(dt)*1e-6)
filePath := folder + TestName + ".png"
f, err := os.Create(filePath)
if err != nil {
log.Println(err)
os.Exit(1)
}
defer f.Close()
b := bufio.NewWriter(f)
err = png.Encode(b, m)
if err != nil {
log.Println(err)
os.Exit(1)
}
err = b.Flush()
if err != nil {
log.Println(err)
os.Exit(1)
}
fmt.Printf("Wrote %s OK.\n", filePath)
}
func android(gc draw2d.GraphicContext, x, y float64) {
gc.SetLineCap(draw2d.RoundCap)
gc.SetLineWidth(5)
gc.ArcTo(x+80, y+70, 50, 50, 180*(math.Pi/180), 360*(math.Pi/180)) // head
gc.FillStroke()
gc.MoveTo(x+60, y+25)
gc.LineTo(x+50, y+10)
gc.MoveTo(x+100, y+25)
gc.LineTo(x+110, y+10)
gc.Stroke()
draw2d.Circle(gc, x+60, y+45, 5) // left eye
gc.FillStroke()
draw2d.Circle(gc, x+100, y+45, 5) // right eye
gc.FillStroke()
draw2d.RoundRect(gc, x+30, y+75, x+30+100, y+75+90, 10, 10) // body
gc.FillStroke()
draw2d.Rect(gc, x+30, y+75, x+30+100, y+75+80)
gc.FillStroke()
draw2d.RoundRect(gc, x+5, y+80, x+5+20, y+80+70, 10, 10) // left arm
gc.FillStroke()
draw2d.RoundRect(gc, x+135, y+80, x+135+20, y+80+70, 10, 10) // right arm
gc.FillStroke()
draw2d.RoundRect(gc, x+50, y+150, x+50+20, y+150+50, 10, 10) // left leg
gc.FillStroke()
draw2d.RoundRect(gc, x+90, y+150, x+90+20, y+150+50, 10, 10) // right leg
gc.FillStroke()
}
func main() {
i, gc := initGc(width, height)
gc.SetFillColor(image.RGBAColor{0x44, 0xff, 0x44, 0xff})
gc.SetStrokeColor(image.RGBAColor{0x44, 0x44, 0x44, 0xff})
android(gc, 10, 10)
saveToPngFile("TestAndroid", i)
}

View File

@ -1,537 +0,0 @@
// Copyright 2010 The draw2d Authors. All rights reserved.
// created: 21/11/2010 by Laurent Le Goff
package main
import (
"fmt"
"log"
"os"
"bufio"
"time"
"math"
"image"
"image/png"
"draw2d.googlecode.com/hg/draw2d"
)
const (
w, h = 512, 512
)
var (
lastTime int64
folder = "../resource/result/"
)
func initGc(w, h int) (image.Image, draw2d.GraphicContext) {
i := image.NewRGBA(image.Rect(0, 0, w, h))
gc := draw2d.NewGraphicContext(i)
lastTime = time.Nanoseconds()
gc.SetStrokeColor(image.Black)
gc.SetFillColor(image.White)
// fill the background
//gc.Clear()
return i, gc
}
func saveToPngFile(TestName string, m image.Image) {
t := time.Nanoseconds()
dt := t - lastTime
fmt.Printf("%s during: %f ms\n", TestName, float64(dt)*1e-6)
filePath := folder + TestName + ".png"
f, err := os.Create(filePath)
if err != nil {
log.Println(err)
os.Exit(1)
}
defer f.Close()
b := bufio.NewWriter(f)
err = png.Encode(b, m)
if err != nil {
log.Println(err)
os.Exit(1)
}
err = b.Flush()
if err != nil {
log.Println(err)
os.Exit(1)
}
dt = time.Nanoseconds() - t
fmt.Printf("Wrote %s OK in %f ms.\n", filePath, float64(dt)*1e-6)
}
/*
<img src="../test_results/TestPath.png"/>
*/
func TestPath() {
i, gc := initGc(w, h)
gc.Translate(10, 10)
gc.MoveTo(0.0, 0.0)
gc.LineTo(100.0, 00.0)
gc.LineTo(100.0, 100.0)
gc.LineTo(0.0, 100.0)
gc.LineTo(0.0, 0.0)
gc.FillStroke()
saveToPngFile("TestPath", i)
}
/*
<img src="../test_results/TestDrawArc.png"/>
*/
func TestDrawArc() {
i, gc := initGc(w, h)
// draw an arc
xc, yc := 128.0, 128.0
radiusX, radiusY := 100.0, 100.0
startAngle := 45.0 * (math.Pi / 180.0) /* angles are specified */
angle := 135 * (math.Pi / 180.0) /* in radians */
gc.SetLineWidth(10)
gc.SetLineCap(draw2d.ButtCap)
gc.SetStrokeColor(image.Black)
gc.ArcTo(xc, yc, radiusX, radiusY, startAngle, angle)
gc.Stroke()
// fill a circle
gc.SetStrokeColor(image.NRGBAColor{255, 0x33, 0x33, 0x80})
gc.SetFillColor(image.NRGBAColor{255, 0x33, 0x33, 0x80})
gc.SetLineWidth(6)
gc.MoveTo(xc, yc)
gc.LineTo(xc+math.Cos(startAngle)*radiusX, yc+math.Sin(startAngle)*radiusY)
gc.MoveTo(xc, yc)
gc.LineTo(xc-radiusX, yc)
gc.Stroke()
gc.ArcTo(xc, yc, 10.0, 10.0, 0, 2*math.Pi)
gc.Fill()
saveToPngFile("TestDrawArc", i)
}
/*
<img src="../test_results/TestDrawArc.png"/>
*/
func TestDrawArcNegative() {
i, gc := initGc(w, h)
// draw an arc
xc, yc := 128.0, 128.0
radiusX, radiusY := 100.0, 100.0
startAngle := 45.0 * (math.Pi / 180.0) /* angles are specified */
angle := -225 * (math.Pi / 180.0) /* in radians */
gc.SetLineWidth(10)
gc.SetLineCap(draw2d.ButtCap)
gc.SetStrokeColor(image.Black)
gc.ArcTo(xc, yc, radiusX, radiusY, startAngle, angle)
gc.Stroke()
// fill a circle
gc.SetStrokeColor(image.NRGBAColor{255, 0x33, 0x33, 0x80})
gc.SetFillColor(image.NRGBAColor{255, 0x33, 0x33, 0x80})
gc.SetLineWidth(6)
gc.MoveTo(xc, yc)
gc.LineTo(xc+math.Cos(startAngle)*radiusX, yc+math.Sin(startAngle)*radiusY)
gc.MoveTo(xc, yc)
gc.LineTo(xc-radiusX, yc)
gc.Stroke()
gc.ArcTo(xc, yc, 10.0, 10.0, 0, 2*math.Pi)
gc.Fill()
saveToPngFile("TestDrawArcNegative", i)
}
func TestCurveRectangle() {
i, gc := initGc(w, h)
/* a custom shape that could be wrapped in a function */
x0, y0 := 25.6, 25.6 /* parameters like cairo_rectangle */
rect_width, rect_height := 204.8, 204.8
radius := 102.4 /* and an approximate curvature radius */
x1 := x0 + rect_width
y1 := y0 + rect_height
if rect_width/2 < radius {
if rect_height/2 < radius {
gc.MoveTo(x0, (y0+y1)/2)
gc.CubicCurveTo(x0, y0, x0, y0, (x0+x1)/2, y0)
gc.CubicCurveTo(x1, y0, x1, y0, x1, (y0+y1)/2)
gc.CubicCurveTo(x1, y1, x1, y1, (x1+x0)/2, y1)
gc.CubicCurveTo(x0, y1, x0, y1, x0, (y0+y1)/2)
} else {
gc.MoveTo(x0, y0+radius)
gc.CubicCurveTo(x0, y0, x0, y0, (x0+x1)/2, y0)
gc.CubicCurveTo(x1, y0, x1, y0, x1, y0+radius)
gc.LineTo(x1, y1-radius)
gc.CubicCurveTo(x1, y1, x1, y1, (x1+x0)/2, y1)
gc.CubicCurveTo(x0, y1, x0, y1, x0, y1-radius)
}
} else {
if rect_height/2 < radius {
gc.MoveTo(x0, (y0+y1)/2)
gc.CubicCurveTo(x0, y0, x0, y0, x0+radius, y0)
gc.LineTo(x1-radius, y0)
gc.CubicCurveTo(x1, y0, x1, y0, x1, (y0+y1)/2)
gc.CubicCurveTo(x1, y1, x1, y1, x1-radius, y1)
gc.LineTo(x0+radius, y1)
gc.CubicCurveTo(x0, y1, x0, y1, x0, (y0+y1)/2)
} else {
gc.MoveTo(x0, y0+radius)
gc.CubicCurveTo(x0, y0, x0, y0, x0+radius, y0)
gc.LineTo(x1-radius, y0)
gc.CubicCurveTo(x1, y0, x1, y0, x1, y0+radius)
gc.LineTo(x1, y1-radius)
gc.CubicCurveTo(x1, y1, x1, y1, x1-radius, y1)
gc.LineTo(x0+radius, y1)
gc.CubicCurveTo(x0, y1, x0, y1, x0, y1-radius)
}
}
gc.Close()
gc.SetFillColor(image.NRGBAColor{0x80, 0x80, 0xFF, 0xFF})
gc.SetStrokeColor(image.NRGBAColor{0x80, 0, 0, 0x80})
gc.SetLineWidth(10.0)
gc.FillStroke()
saveToPngFile("TestCurveRectangle", i)
}
/*
<img src="../test_results/TestDrawCubicCurve.png"/>
*/
func TestDrawCubicCurve() {
i, gc := initGc(w, h)
// draw a cubic curve
x, y := 25.6, 128.0
x1, y1 := 102.4, 230.4
x2, y2 := 153.6, 25.6
x3, y3 := 230.4, 128.0
gc.SetFillColor(image.NRGBAColor{0xAA, 0xAA, 0xAA, 0xFF})
gc.SetLineWidth(10)
gc.MoveTo(x, y)
gc.CubicCurveTo(x1, y1, x2, y2, x3, y3)
gc.Stroke()
gc.SetStrokeColor(image.NRGBAColor{0xFF, 0x33, 0x33, 0x88})
gc.SetLineWidth(6)
// draw segment of curve
gc.MoveTo(x, y)
gc.LineTo(x1, y1)
gc.LineTo(x2, y2)
gc.LineTo(x3, y3)
gc.Stroke()
saveToPngFile("TestDrawCubicCurve", i)
}
/*
<img src="../test_results/TestDash.png"/>
*/
func TestDash() {
i, gc := initGc(w, h)
gc.SetLineDash([]float64{50, 10, 10, 10}, -50.0)
gc.SetLineCap(draw2d.ButtCap)
gc.SetLineJoin(draw2d.BevelJoin)
gc.SetLineWidth(10)
gc.MoveTo(128.0, 25.6)
gc.LineTo(128.0, 25.6)
gc.LineTo(230.4, 230.4)
gc.RLineTo(-102.4, 0.0)
gc.CubicCurveTo(51.2, 230.4, 51.2, 128.0, 128.0, 128.0)
gc.Stroke()
gc.SetLineDash(nil, 0.0)
saveToPngFile("TestDash", i)
}
/*
<img src="../test_results/TestFillStroke.png"/>
*/
func TestFillStroke() {
i, gc := initGc(w, h)
gc.MoveTo(128.0, 25.6)
gc.LineTo(230.4, 230.4)
gc.RLineTo(-102.4, 0.0)
gc.CubicCurveTo(51.2, 230.4, 51.2, 128.0, 128.0, 128.0)
gc.Close()
gc.MoveTo(64.0, 25.6)
gc.RLineTo(51.2, 51.2)
gc.RLineTo(-51.2, 51.2)
gc.RLineTo(-51.2, -51.2)
gc.Close()
gc.SetLineWidth(10.0)
gc.SetFillColor(image.NRGBAColor{0, 0, 0xFF, 0xFF})
gc.SetStrokeColor(image.Black)
gc.FillStroke()
saveToPngFile("TestFillStroke", i)
}
/*
<img src="../test_results/TestFillStyle.png"/>
*/
func TestFillStyle() {
i, gc := initGc(w, h)
gc.SetLineWidth(6)
draw2d.Rect(gc, 12, 12, 244, 70)
wheel1 := new(draw2d.PathStorage)
wheel1.ArcTo(64, 64, 40, 40, 0, 2*math.Pi)
wheel2 := new(draw2d.PathStorage)
wheel2.ArcTo(192, 64, 40, 40, 0, 2*math.Pi)
gc.SetFillRule(draw2d.FillRuleEvenOdd)
gc.SetFillColor(image.NRGBAColor{0, 0xB2, 0, 0xFF})
gc.SetStrokeColor(image.Black)
gc.FillStroke(wheel1, wheel2)
draw2d.Rect(gc, 12, 140, 244, 198)
wheel1 = new(draw2d.PathStorage)
wheel1.ArcTo(64, 192, 40, 40, 0, 2*math.Pi)
wheel2 = new(draw2d.PathStorage)
wheel2.ArcTo(192, 192, 40, 40, 0, -2*math.Pi)
gc.SetFillRule(draw2d.FillRuleWinding)
gc.SetFillColor(image.NRGBAColor{0, 0, 0xE5, 0xFF})
gc.FillStroke(wheel1, wheel2)
saveToPngFile("TestFillStyle", i)
}
func TestMultiSegmentCaps() {
i, gc := initGc(w, h)
gc.MoveTo(50.0, 75.0)
gc.LineTo(200.0, 75.0)
gc.MoveTo(50.0, 125.0)
gc.LineTo(200.0, 125.0)
gc.MoveTo(50.0, 175.0)
gc.LineTo(200.0, 175.0)
gc.SetLineWidth(30.0)
gc.SetLineCap(draw2d.RoundCap)
gc.Stroke()
saveToPngFile("TestMultiSegmentCaps", i)
}
func TestRoundRectangle() {
i, gc := initGc(w, h)
/* a custom shape that could be wrapped in a function */
x, y := 25.6, 25.6
width, height := 204.8, 204.8
aspect := 1.0 /* aspect ratio */
corner_radius := height / 10.0 /* and corner curvature radius */
radius := corner_radius / aspect
degrees := math.Pi / 180.0
gc.ArcTo(x+width-radius, y+radius, radius, radius, -90*degrees, 90*degrees)
gc.ArcTo(x+width-radius, y+height-radius, radius, radius, 0*degrees, 90*degrees)
gc.ArcTo(x+radius, y+height-radius, radius, radius, 90*degrees, 90*degrees)
gc.ArcTo(x+radius, y+radius, radius, radius, 180*degrees, 90*degrees)
gc.Close()
gc.SetFillColor(image.NRGBAColor{0x80, 0x80, 0xFF, 0xFF})
gc.SetStrokeColor(image.NRGBAColor{0x80, 0, 0, 0x80})
gc.SetLineWidth(10.0)
gc.FillStroke()
saveToPngFile("TestRoundRectangle", i)
}
func TestLineCap() {
i, gc := initGc(w, h)
gc.SetLineWidth(30.0)
gc.SetLineCap(draw2d.ButtCap)
gc.MoveTo(64.0, 50.0)
gc.LineTo(64.0, 200.0)
gc.Stroke()
gc.SetLineCap(draw2d.RoundCap)
gc.MoveTo(128.0, 50.0)
gc.LineTo(128.0, 200.0)
gc.Stroke()
gc.SetLineCap(draw2d.SquareCap)
gc.MoveTo(192.0, 50.0)
gc.LineTo(192.0, 200.0)
gc.Stroke()
/* draw helping lines */
gc.SetStrokeColor(image.NRGBAColor{0xFF, 0x33, 0x33, 0xFF})
gc.SetLineWidth(2.56)
gc.MoveTo(64.0, 50.0)
gc.LineTo(64.0, 200.0)
gc.MoveTo(128.0, 50.0)
gc.LineTo(128.0, 200.0)
gc.MoveTo(192.0, 50.0)
gc.LineTo(192.0, 200.0)
gc.Stroke()
saveToPngFile("TestLineCap", i)
}
func TestLineJoin() {
i, gc := initGc(w, h)
gc.SetLineWidth(40.96)
gc.MoveTo(76.8, 84.48)
gc.RLineTo(51.2, -51.2)
gc.RLineTo(51.2, 51.2)
gc.SetLineJoin(draw2d.MiterJoin) /* default */
gc.Stroke()
gc.MoveTo(76.8, 161.28)
gc.RLineTo(51.2, -51.2)
gc.RLineTo(51.2, 51.2)
gc.SetLineJoin(draw2d.BevelJoin)
gc.Stroke()
gc.MoveTo(76.8, 238.08)
gc.RLineTo(51.2, -51.2)
gc.RLineTo(51.2, 51.2)
gc.SetLineJoin(draw2d.RoundJoin)
gc.Stroke()
saveToPngFile("TestLineJoin", i)
}
func TestBubble() {
i, gc := initGc(w, h)
gc.BeginPath()
gc.MoveTo(75, 25)
gc.QuadCurveTo(25, 25, 25, 62.5)
gc.QuadCurveTo(25, 100, 50, 100)
gc.QuadCurveTo(50, 120, 30, 125)
gc.QuadCurveTo(60, 120, 65, 100)
gc.QuadCurveTo(125, 100, 125, 62.5)
gc.QuadCurveTo(125, 25, 75, 25)
gc.Stroke()
saveToPngFile("TestBubble", i)
}
func TestStar() {
i, gc := initGc(w, h)
for i := 0.0; i < 360; i = i + 10 { // Go from 0 to 360 degrees in 10 degree steps
gc.Save()
gc.SetLineWidth(5) // Keep rotations temporary
gc.Translate(144, 144)
gc.Rotate(i * (math.Pi / 180.0)) // Rotate by degrees on stack from 'for'
gc.MoveTo(0, 0)
gc.LineTo(72, 0)
gc.Stroke()
gc.Restore()
}
saveToPngFile("TestStar", i)
}
func TestTransform() {
i, gc := initGc(800, 600)
gc.Save()
gc.Translate(40, 40) // Set origin to (40, 40)
gc.BeginPath()
gc.MoveTo(0, 0)
gc.RLineTo(72, 0)
gc.RLineTo(0, 72)
gc.RLineTo(-72, 0)
gc.Close()
gc.Stroke()
gc.Restore()
gc.Save()
gc.Translate(100, 150) // Translate origin to (100, 150)
gc.Rotate(30 * (math.Pi / 180.0)) // Rotate counter-clockwise by 30 degrees
gc.BeginPath()
gc.MoveTo(0, 0)
gc.RLineTo(72, 0)
gc.RLineTo(0, 72)
gc.RLineTo(-72, 0)
gc.Close() // Draw box...
gc.Stroke()
gc.Restore()
gc.Save()
gc.Translate(40, 300) // Translate to (40, 300)
gc.Scale(0.5, 1) // Reduce x coord by 1/2, y coord left alone
gc.BeginPath()
gc.MoveTo(0, 0)
gc.RLineTo(72, 0)
gc.RLineTo(0, 72)
gc.RLineTo(-72, 0)
gc.Close() // Draw box...
gc.Stroke()
gc.Restore()
gc.Save()
gc.Translate(300, 300) // Set origin to (300, 300)
gc.Rotate(45 * (math.Pi / 180.0)) // Rotate coordinates by 45 degrees
gc.Scale(0.5, 1) // Scale coordinates
gc.BeginPath()
gc.MoveTo(0, 0)
gc.RLineTo(72, 0)
gc.RLineTo(0, 72)
gc.RLineTo(-72, 0)
gc.Close() // Draw box
gc.Stroke()
gc.Restore()
saveToPngFile("TestTransform", i)
}
func TestPathTransform() {
i, gc := initGc(800, 600)
gc.SetLineWidth(20)
gc.Scale(1, 4)
gc.ArcTo(200, 80, 50, 50, 0, math.Pi*2)
gc.Close()
gc.Stroke()
saveToPngFile("TestPathTransform", i)
}
func TestFillString() {
draw2d.SetFontFolder("../resource/font/")
i, gc := initGc(100, 100)
draw2d.RoundRect(gc, 5, 5, 95, 95, 10, 10)
gc.FillStroke()
gc.SetFontSize(18)
gc.MoveTo(10, 52)
gc.SetFontData(draw2d.FontData{"luxi", draw2d.FontFamilyMono, draw2d.FontStyleBold | draw2d.FontStyleItalic})
width := gc.FillString("cou")
gc.RMoveTo(width+1, 0)
gc.FillString("cou")
saveToPngFile("TestFillString", i)
}
func TestBigPicture() {
i, gc := initGc(w, h)
gc.SetLineWidth(10)
draw2d.Rect(gc, 0, 0, w, h)
gc.Fill()
saveToPngFile("TestBigPicture", i)
}
func main() {
t := time.Nanoseconds()
TestPath()
TestDrawArc()
TestDrawArcNegative()
TestCurveRectangle()
TestDrawCubicCurve()
TestDash()
TestFillStroke()
TestFillStyle()
TestMultiSegmentCaps()
TestRoundRectangle()
TestLineCap()
TestLineJoin()
TestBubble()
TestStar()
TestTransform()
TestPathTransform()
TestFillString()
TestBigPicture()
dt := time.Nanoseconds() - t
fmt.Printf("All tests during: %f ms\n", float64(dt)*1e-6)
}

View File

@ -1,134 +0,0 @@
package main
import (
"fmt"
"log"
"os"
"bufio"
"time"
"math"
"image"
"image/png"
"draw2d.googlecode.com/hg/draw2d"
)
const (
width, height = 300, 200
)
var (
lastTime int64
folder = "../resource/result/"
)
func initGc(w, h int) (image.Image, draw2d.GraphicContext) {
i := image.NewRGBA(image.Rect(0, 0, w, h))
gc := draw2d.NewGraphicContext(i)
lastTime = time.Nanoseconds()
gc.SetStrokeColor(image.Black)
gc.SetFillColor(image.White)
// fill the background
//gc.Clear()
return i, gc
}
func saveToPngFile(TestName string, m image.Image) {
dt := time.Nanoseconds() - lastTime
fmt.Printf("%s during: %f ms\n", TestName, float64(dt)*1e-6)
filePath := folder + TestName + ".png"
f, err := os.Create(filePath)
if err != nil {
log.Println(err)
os.Exit(1)
}
defer f.Close()
b := bufio.NewWriter(f)
err = png.Encode(b, m)
if err != nil {
log.Println(err)
os.Exit(1)
}
err = b.Flush()
if err != nil {
log.Println(err)
os.Exit(1)
}
fmt.Printf("Wrote %s OK.\n", filePath)
}
func gordon(gc draw2d.GraphicContext, x, y, w, h float64) {
h23 := (h * 2) / 3
blf := image.RGBAColor{0, 0, 0, 0xff}
wf := image.RGBAColor{0xff, 0xff, 0xff, 0xff}
nf := image.RGBAColor{0x8B, 0x45, 0x13, 0xff}
brf := image.RGBAColor{0x8B, 0x45, 0x13, 0x99}
brb := image.RGBAColor{0x8B, 0x45, 0x13, 0xBB}
gc.MoveTo(x, y+h)
gc.CubicCurveTo(x, y+h, x+w/2, y-h, x+w, y+h)
gc.Close()
gc.SetFillColor(brb)
gc.Fill()
draw2d.RoundRect(gc, x, y+h, x+w, y+h+h, 10, 10)
gc.Fill()
draw2d.Circle(gc, x, y+h, w/12) // left ear
gc.SetFillColor(brf)
gc.Fill()
draw2d.Circle(gc, x, y+h, w/12-10)
gc.SetFillColor(nf)
gc.Fill()
draw2d.Circle(gc, x+w, y+h, w/12) // right ear
gc.SetFillColor(brf)
gc.Fill()
draw2d.Circle(gc, x+w, y+h, w/12-10)
gc.SetFillColor(nf)
gc.Fill()
draw2d.Circle(gc, x+w/3, y+h23, w/9) // left eye
gc.SetFillColor(wf)
gc.Fill()
draw2d.Circle(gc, x+w/3+10, y+h23, w/10-10)
gc.SetFillColor(blf)
gc.Fill()
draw2d.Circle(gc, x+w/3+15, y+h23, 5)
gc.SetFillColor(wf)
gc.Fill()
draw2d.Circle(gc, x+w-w/3, y+h23, w/9) // right eye
gc.Fill()
draw2d.Circle(gc, x+w-w/3+10, y+h23, w/10-10)
gc.SetFillColor(blf)
gc.Fill()
draw2d.Circle(gc, x+w-(w/3)+15, y+h23, 5)
gc.SetFillColor(wf)
gc.Fill()
gc.SetFillColor(wf)
draw2d.RoundRect(gc, x+w/2-w/8, y+h+30, x+w/2-w/8+w/8, y+h+30+w/6, 5, 5) // left tooth
gc.Fill()
draw2d.RoundRect(gc, x+w/2, y+h+30, x+w/2+w/8, y+h+30+w/6, 5, 5) // right tooth
gc.Fill()
draw2d.Ellipse(gc, x+(w/2), y+h+30, w/6, w/12) // snout
gc.SetFillColor(nf)
gc.Fill()
draw2d.Ellipse(gc, x+(w/2), y+h+10, w/10, w/12) // nose
gc.SetFillColor(blf)
gc.Fill()
}
func main() {
i, gc := initGc(width, height)
gc.Clear()
gc.Translate(-75, 58)
gc.Rotate(-30 * (math.Pi / 180.0))
gordon(gc, 48, 48, 240, 72)
saveToPngFile("TestGopher", i)
}

View File

@ -1,71 +0,0 @@
package main
import (
"fmt"
"log"
"os"
"bufio"
"math"
"image"
"time"
"image/png"
"image/draw"
"draw2d.googlecode.com/hg/draw2d"
)
func saveToPngFile(filePath string, m image.Image) {
f, err := os.Create(filePath)
if err != nil {
log.Println(err)
return
}
defer f.Close()
b := bufio.NewWriter(f)
err = png.Encode(b, m)
if err != nil {
log.Println(err)
return
}
err = b.Flush()
if err != nil {
log.Println(err)
return
}
fmt.Printf("Wrote %s OK.\n", filePath)
}
func loadFromPngFile(filePath string) image.Image {
f, err := os.OpenFile(filePath, 0, 0)
if f == nil {
log.Printf("can't open file; err=%s\n", err.String())
return nil
}
defer f.Close()
b := bufio.NewReader(f)
i, err := png.Decode(b)
if err != nil {
log.Println(err)
os.Exit(1)
}
fmt.Printf("Read %s OK.\n", filePath)
return i
}
func main() {
source := loadFromPngFile("../resource/image/TestAndroid.png")
dest := image.NewRGBA(image.Rect(0, 0, 1024, 768))
width, height := float64(source.Bounds().Dx()), float64(source.Bounds().Dy())
tr := draw2d.NewIdentityMatrix()
tr.Translate(width/2, height/2)
tr.Rotate(30 * math.Pi / 180)
//tr.Scale(3, 3)
tr.Translate(-width/2, -height/2)
tr.Translate(200, 5)
lastTime := time.Nanoseconds()
draw2d.DrawImage(source, dest, tr, draw.Over, draw2d.BilinearFilter)
dt := time.Nanoseconds() - lastTime
fmt.Printf("Draw image: %f ms\n", float64(dt)*1e-6)
saveToPngFile("../resource/result/TestDrawImage.png", dest)
}

View File

@ -1,59 +0,0 @@
package main
import (
"fmt"
"time"
"log"
"os"
"io/ioutil"
"bufio"
"strings"
"image"
"image/png"
"draw2d.googlecode.com/hg/draw2d"
"draw2d.googlecode.com/hg/postscript"
)
func saveToPngFile(filePath string, m image.Image) {
f, err := os.Create(filePath)
if err != nil {
log.Println(err)
os.Exit(1)
}
defer f.Close()
b := bufio.NewWriter(f)
err = png.Encode(b, m)
if err != nil {
log.Println(err)
os.Exit(1)
}
err = b.Flush()
if err != nil {
log.Println(err)
os.Exit(1)
}
fmt.Printf("Wrote %s OK.\n", filePath)
}
func main() {
i := image.NewRGBA(image.Rect(0, 0, 600, 800))
gc := draw2d.NewGraphicContext(i)
gc.Translate(0, 380)
gc.Scale(1, -1)
gc.Translate(0, -380)
src, err := os.OpenFile("../resource/postscript/tiger.ps", 0, 0)
if err != nil {
return
}
defer src.Close()
bytes, err := ioutil.ReadAll(src)
reader := strings.NewReader(string(bytes))
interpreter := postscript.NewInterpreter(gc)
lastTime := time.Nanoseconds()
interpreter.Execute(reader)
dt := time.Nanoseconds() - lastTime
fmt.Printf("Draw image: %f ms\n", float64(dt)*1e-6)
saveToPngFile("../resource/result/TestPostscript.png", i)
}

306339
doc/PLRM.pdf

File diff suppressed because one or more lines are too long

255
draw2d.go Normal file
View File

@ -0,0 +1,255 @@
// Copyright 2010 The draw2d Authors. All rights reserved.
// created: 13/12/2010 by Laurent Le Goff
// Package draw2d is a pure go 2D vector graphics library with support
// for multiple output devices such as images (draw2d), pdf documents
// (draw2dpdf) and opengl (draw2dgl), which can also be used on the
// google app engine. It can be used as a pure go Cairo alternative.
// draw2d is released under the BSD license.
//
// Features
//
// Operations in draw2d include stroking and filling polygons, arcs,
// Bézier curves, drawing images and text rendering with truetype fonts.
// All drawing operations can be transformed by affine transformations
// (scale, rotation, translation).
//
// Package draw2d follows the conventions of http://www.w3.org/TR/2dcontext for coordinate system, angles, etc...
//
// Installation
//
// To install or update the package draw2d on your system, run:
// go get -u github.com/llgcode/draw2d
//
// Quick Start
//
// Package draw2d itself provides a graphic context that can draw vector
// graphics and text on an image canvas. The following Go code
// generates a simple drawing and saves it to an image file:
// package main
//
// import (
// "github.com/llgcode/draw2d/draw2dimg"
// "image"
// "image/color"
// )
//
// func main() {
// // Initialize the graphic context on an RGBA image
// dest := image.NewRGBA(image.Rect(0, 0, 297, 210.0))
// gc := draw2dimg.NewGraphicContext(dest)
//
// // Set some properties
// gc.SetFillColor(color.RGBA{0x44, 0xff, 0x44, 0xff})
// gc.SetStrokeColor(color.RGBA{0x44, 0x44, 0x44, 0xff})
// gc.SetLineWidth(5)
//
// // Draw a closed shape
// gc.MoveTo(10, 10) // should always be called first for a new path
// gc.LineTo(100, 50)
// gc.QuadCurveTo(100, 10, 10, 10)
// gc.Close()
// gc.FillStroke()
//
// // Save to file
// draw2dimg.SaveToPngFile("hello.png", dest)
// }
//
//
// There are more examples here:
// https://github.com/llgcode/draw2d/tree/master/samples
//
// Drawing on pdf documents is provided by the draw2dpdf package.
// Drawing on opengl is provided by the draw2dgl package.
// See subdirectories at the bottom of this page.
//
// Testing
//
// The samples are run as tests from the root package folder `draw2d` by:
// go test ./...
//
// Or if you want to run with test coverage:
// go test -cover ./... | grep -v "no test"
//
// This will generate output by the different backends in the output folder.
//
// Acknowledgments
//
// Laurent Le Goff wrote this library, inspired by Postscript and
// HTML5 canvas. He implemented the image and opengl backend with the
// freetype-go package. Also he created a pure go Postscript
// interpreter, which can read postscript images and draw to a draw2d
// graphic context (https://github.com/llgcode/ps). Stani Michiels
// implemented the pdf backend with the gofpdf package.
//
// Packages using draw2d
//
// - https://github.com/llgcode/ps: Postscript interpreter written in Go
//
// - https://github.com/gonum/plot: drawing plots in Go
//
// - https://github.com/muesli/smartcrop: content aware image cropping
//
// - https://github.com/peterhellberg/karta: drawing Voronoi diagrams
//
// - https://github.com/vdobler/chart: basic charts in Go
package draw2d
import "image/color"
// FillRule defines the type for fill rules
type FillRule int
const (
// FillRuleEvenOdd determines the "insideness" of a point in the shape
// by drawing a ray from that point to infinity in any direction
// and counting the number of path segments from the given shape that the ray crosses.
// If this number is odd, the point is inside; if even, the point is outside.
FillRuleEvenOdd FillRule = iota
// FillRuleWinding determines the "insideness" of a point in the shape
// by drawing a ray from that point to infinity in any direction
// and then examining the places where a segment of the shape crosses the ray.
// Starting with a count of zero, add one each time a path segment crosses
// the ray from left to right and subtract one each time
// a path segment crosses the ray from right to left. After counting the crossings,
// if the result is zero then the point is outside the path. Otherwise, it is inside.
FillRuleWinding
)
// LineCap is the style of line extremities
type LineCap int
const (
// RoundCap defines a rounded shape at the end of the line
RoundCap LineCap = iota
// ButtCap defines a squared shape exactly at the end of the line
ButtCap
// SquareCap defines a squared shape at the end of the line
SquareCap
)
func (cap LineCap) String() string {
return map[LineCap]string{
RoundCap: "round",
ButtCap: "cap",
SquareCap: "square",
}[cap]
}
// LineJoin is the style of segments joint
type LineJoin int
const (
// BevelJoin represents cut segments joint
BevelJoin LineJoin = iota
// RoundJoin represents rounded segments joint
RoundJoin
// MiterJoin represents peaker segments joint
MiterJoin
)
func (join LineJoin) String() string {
return map[LineJoin]string{
RoundJoin: "round",
BevelJoin: "bevel",
MiterJoin: "miter",
}[join]
}
// StrokeStyle keeps stroke style attributes
// that is used by the Stroke method of a Drawer
type StrokeStyle struct {
// Color defines the color of stroke
Color color.Color
// Line width
Width float64
// Line cap style rounded, butt or square
LineCap LineCap
// Line join style bevel, round or miter
LineJoin LineJoin
// offset of the first dash
DashOffset float64
// array represented dash length pair values are plain dash and impair are space between dash
// if empty display plain line
Dash []float64
}
// SolidFillStyle define style attributes for a solid fill style
type SolidFillStyle struct {
// Color defines the line color
Color color.Color
// FillRule defines the file rule to used
FillRule FillRule
}
// Valign Vertical Alignment of the text
type Valign int
const (
// ValignTop top align text
ValignTop Valign = iota
// ValignCenter centered text
ValignCenter
// ValignBottom bottom aligned text
ValignBottom
// ValignBaseline align text with the baseline of the font
ValignBaseline
)
// Halign Horizontal Alignment of the text
type Halign int
const (
// HalignLeft Horizontally align to left
HalignLeft = iota
// HalignCenter Horizontally align to center
HalignCenter
// HalignRight Horizontally align to right
HalignRight
)
// TextStyle describe text property
type TextStyle struct {
// Color defines the color of text
Color color.Color
// Size font size
Size float64
// The font to use
Font FontData
// Horizontal Alignment of the text
Halign Halign
// Vertical Alignment of the text
Valign Valign
}
// ScalingPolicy is a constant to define how to scale an image
type ScalingPolicy int
const (
// ScalingNone no scaling applied
ScalingNone ScalingPolicy = iota
// ScalingStretch the image is stretched so that its width and height are exactly the given width and height
ScalingStretch
// ScalingWidth the image is scaled so that its width is exactly the given width
ScalingWidth
// ScalingHeight the image is scaled so that its height is exactly the given height
ScalingHeight
// ScalingFit the image is scaled to the largest scale that allow the image to fit within a rectangle width x height
ScalingFit
// ScalingSameArea the image is scaled so that its area is exactly the area of the given rectangle width x height
ScalingSameArea
// ScalingFill the image is scaled to the smallest scale that allow the image to fully cover a rectangle width x height
ScalingFill
)
// ImageScaling style attributes used to display the image
type ImageScaling struct {
// Horizontal Alignment of the image
Halign Halign
// Vertical Alignment of the image
Valign Valign
// Width Height used by scaling policy
Width, Height float64
// ScalingPolicy defines the scaling policy to applied to the image
ScalingPolicy ScalingPolicy
}

View File

@ -1,26 +0,0 @@
include $(GOROOT)/src/Make.inc
TARG=draw2d.googlecode.com/hg/draw2d
GOFILES=\
draw2d.go\
arc.go\
curves.go\
image.go\
math.go\
path.go\
transform.go\
dasher.go\
demux_converter.go\
font.go\
path_adder.go\
path_converter.go\
path_storage.go\
stroker.go\
advanced_path.go\
vertex2d.go\
gc.go\
paint.go\
stack_gc.go\
rgba_interpolation.go\
include $(GOROOT)/src/Make.pkg

View File

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

View File

@ -1,11 +0,0 @@
include $(GOROOT)/src/Make.inc
TARG=draw2d.googlecode.com/hg/draw2d/curve
GOFILES=\
cubic_float64.go\
quad_float64.go\
cubic_float64_others.go\
include $(GOROOT)/src/Make.pkg

View File

@ -1,41 +0,0 @@
package main
import "draw2d.googlecode.com/hg/draw2d/curve"
import "testing"
import __os__ "os"
import __regexp__ "regexp"
var tests = []testing.InternalTest{
{"curve.TestCubicCurveRec", curve.TestCubicCurveRec},
{"curve.TestCubicCurve", curve.TestCubicCurve},
{"curve.TestCubicCurveAdaptiveRec", curve.TestCubicCurveAdaptiveRec},
{"curve.TestCubicCurveAdaptive", curve.TestCubicCurveAdaptive},
{"curve.TestCubicCurveParabolic", curve.TestCubicCurveParabolic},
{"curve.TestQuadCurve", curve.TestQuadCurve},
}
var benchmarks = []testing.InternalBenchmark{ {"curve.BenchmarkCubicCurveRec", curve.BenchmarkCubicCurveRec},
{"curve.BenchmarkCubicCurve", curve.BenchmarkCubicCurve},
{"curve.BenchmarkCubicCurveAdaptiveRec", curve.BenchmarkCubicCurveAdaptiveRec},
{"curve.BenchmarkCubicCurveAdaptive", curve.BenchmarkCubicCurveAdaptive},
{"curve.BenchmarkCubicCurveParabolic", curve.BenchmarkCubicCurveParabolic},
{"curve.BenchmarkQuadCurve", curve.BenchmarkQuadCurve},
}
var matchPat string
var matchRe *__regexp__.Regexp
func matchString(pat, str string) (result bool, err __os__.Error) {
if matchRe == nil || matchPat != pat {
matchPat = pat
matchRe, err = __regexp__.Compile(matchPat)
if err != nil {
return
}
}
return matchRe.MatchString(str), nil
}
func main() {
testing.Main(matchString, tests, benchmarks)
}

View File

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

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.Fabs(((c[2]-c[6])*dy - (c[3]-c[7])*dx))
d3 = math.Fabs(((c[4]-c[6])*dy - (c[5]-c[7])*dx))
if (d2+d3)*(d2+d3) < flattening_threshold*(dx*dx+dy*dy) || i == len(curves)-1 {
t.LineTo(c[6], c[7])
i--
} else {
// second half of bezier go lower onto the stack
c.Subdivide(&curves[i+1], &curves[i])
i++
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +0,0 @@
// Copyright 2011 The draw2d Authors. All rights reserved.
// created: 29/09/2011 by Laurent Le Goff
// The package draw2d provide a Graphic Context that can draw vectorial figure on surface.
package draw2d

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 (
"freetype-go.googlecode.com/hg/freetype"
"freetype-go.googlecode.com/hg/freetype/truetype"
"path"
"log"
"io/ioutil"
)
var (
fontFolder = "../resource/font/"
fonts = make(map[string]*truetype.Font)
)
type FontStyle byte
const (
FontStyleNormal FontStyle = iota
FontStyleBold
FontStyleItalic
)
type FontFamily byte
const (
FontFamilySans FontFamily = iota
FontFamilySerif
FontFamilyMono
)
type FontData struct {
Name string
Family FontFamily
Style FontStyle
}
func GetFont(fontData FontData) *truetype.Font {
fontFileName := fontData.Name
switch fontData.Family {
case FontFamilySans:
fontFileName += "s"
case FontFamilySerif:
fontFileName += "r"
case FontFamilyMono:
fontFileName += "m"
}
if fontData.Style&FontStyleBold != 0 {
fontFileName += "b"
} else {
fontFileName += "r"
}
if fontData.Style&FontStyleItalic != 0 {
fontFileName += "i"
}
fontFileName += ".ttf"
font := fonts[fontFileName]
if font != nil {
return font
}
fonts[fontFileName] = loadFont(fontFileName)
return fonts[fontFileName]
}
func GetFontFolder() string {
return fontFolder
}
func SetFontFolder(folder string) {
fontFolder = folder
}
func loadFont(fontFileName string) *truetype.Font {
fontBytes, err := ioutil.ReadFile(path.Join(fontFolder, fontFileName))
if err != nil {
log.Println(err)
return nil
}
font, err := freetype.ParseFont(fontBytes)
if err != nil {
log.Println(err)
return nil
}
return font
}

View File

@ -1,47 +0,0 @@
// Copyright 2010 The draw2d Authors. All rights reserved.
// created: 21/11/2010 by Laurent Le Goff
package draw2d
import (
"image"
)
type FillRule int
const (
FillRuleEvenOdd FillRule = iota
FillRuleWinding
)
type GraphicContext interface {
BeginPath()
Path
GetMatrixTransform() MatrixTransform
SetMatrixTransform(tr MatrixTransform)
ComposeMatrixTransform(tr MatrixTransform)
Rotate(angle float64)
Translate(tx, ty float64)
Scale(sx, sy float64)
SetStrokeColor(c image.Color)
SetFillColor(c image.Color)
SetFillRule(f FillRule)
SetLineWidth(lineWidth float64)
SetLineCap(cap Cap)
SetLineJoin(join Join)
SetLineDash(dash []float64, dashOffset float64)
SetFontSize(fontSize float64)
GetFontSize() float64
SetFontData(fontData FontData)
GetFontData() FontData
DrawImage(image image.Image)
Save()
Restore()
Clear()
ClearRect(x1, y1, x2, y2 int)
SetDPI(dpi int)
GetDPI() int
FillString(text string) (cursor float64)
Stroke(paths ...*PathStorage)
Fill(paths ...*PathStorage)
FillStroke(paths ...*PathStorage)
}

View File

@ -1,207 +0,0 @@
// Copyright 2010 The draw2d Authors. All rights reserved.
// created: 21/11/2010 by Laurent Le Goff
package draw2d
import (
"image/draw"
"image"
"log"
"freetype-go.googlecode.com/hg/freetype"
"freetype-go.googlecode.com/hg/freetype/raster"
)
type Painter interface {
raster.Painter
SetColor(color image.Color)
}
var (
defaultFontData = FontData{"luxi", FontFamilySans, FontStyleNormal}
)
type ImageGraphicContext struct {
*StackGraphicContext
img draw.Image
painter Painter
fillRasterizer *raster.Rasterizer
strokeRasterizer *raster.Rasterizer
freetype *freetype.Context
DPI int
}
/**
* Create a new Graphic context from an image
*/
func NewGraphicContext(img draw.Image) *ImageGraphicContext {
var painter Painter
switch selectImage := img.(type) {
case *image.RGBA:
painter = raster.NewRGBAPainter(selectImage)
//case *image.NRGBA:
// painter = NewNRGBAPainter(selectImage)
default:
panic("Image type not supported")
}
width, height := img.Bounds().Dx(), img.Bounds().Dy()
dpi := 92
ftContext := freetype.NewContext()
ftContext.SetDPI(dpi)
ftContext.SetClip(img.Bounds())
ftContext.SetDst(img)
gc := &ImageGraphicContext{
NewStackGraphicContext(),
img,
painter,
raster.NewRasterizer(width, height),
raster.NewRasterizer(width, height),
ftContext,
dpi,
}
return gc
}
func (gc *ImageGraphicContext) SetDPI(dpi int) {
gc.DPI = dpi
gc.freetype.SetDPI(dpi)
}
func (gc *ImageGraphicContext) GetDPI() int {
return gc.DPI
}
func (gc *ImageGraphicContext) Clear() {
width, height := gc.img.Bounds().Dx(), gc.img.Bounds().Dy()
gc.ClearRect(0, 0, width, height)
}
func (gc *ImageGraphicContext) ClearRect(x1, y1, x2, y2 int) {
imageColor := image.NewColorImage(gc.Current.FillColor)
draw.Draw(gc.img, image.Rect(x1, y1, x2, y2), imageColor, image.ZP, draw.Over)
}
func (gc *ImageGraphicContext) DrawImage(img image.Image) {
DrawImage(img, gc.img, gc.Current.Tr, draw.Over, BilinearFilter)
}
func (gc *ImageGraphicContext) FillString(text string) (cursor float64) {
gc.freetype.SetSrc(image.NewColorImage(gc.Current.StrokeColor))
// Draw the text.
x, y := gc.Current.Path.LastPoint()
gc.Current.Tr.Transform(&x, &y)
x0, fontSize := 0.0, gc.Current.FontSize
gc.Current.Tr.VectorTransform(&x0, &fontSize)
font := GetFont(gc.Current.FontData)
if font == nil {
font = GetFont(defaultFontData)
}
if font == nil {
return 0
}
gc.freetype.SetFont(font)
gc.freetype.SetFontSize(fontSize)
pt := freetype.Pt(int(x), int(y))
p, err := gc.freetype.DrawString(text, pt)
if err != nil {
log.Println(err)
}
x1, _ := gc.Current.Path.LastPoint()
x2, y2 := float64(p.X)/256, float64(p.Y)/256
gc.Current.Tr.InverseTransform(&x2, &y2)
width := x2 - x1
return width
}
func (gc *ImageGraphicContext) paint(rasterizer *raster.Rasterizer, color image.Color) {
gc.painter.SetColor(color)
rasterizer.Rasterize(gc.painter)
rasterizer.Clear()
gc.Current.Path.Clear()
}
/**** second method ****/
func (gc *ImageGraphicContext) Stroke(paths ...*PathStorage) {
paths = append(paths, gc.Current.Path)
gc.strokeRasterizer.UseNonZeroWinding = true
stroker := NewLineStroker(gc.Current.Cap, gc.Current.Join, NewVertexMatrixTransform(gc.Current.Tr, NewVertexAdder(gc.strokeRasterizer)))
stroker.HalfLineWidth = gc.Current.LineWidth / 2
var pathConverter *PathConverter
if gc.Current.Dash != nil && len(gc.Current.Dash) > 0 {
dasher := NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker)
pathConverter = NewPathConverter(dasher)
} else {
pathConverter = NewPathConverter(stroker)
}
pathConverter.ApproximationScale = gc.Current.Tr.GetScale()
pathConverter.Convert(paths...)
gc.paint(gc.strokeRasterizer, gc.Current.StrokeColor)
}
/**** second method ****/
func (gc *ImageGraphicContext) Fill(paths ...*PathStorage) {
paths = append(paths, gc.Current.Path)
gc.fillRasterizer.UseNonZeroWinding = gc.Current.FillRule.UseNonZeroWinding()
/**** first method ****/
pathConverter := NewPathConverter(NewVertexMatrixTransform(gc.Current.Tr, NewVertexAdder(gc.fillRasterizer)))
pathConverter.ApproximationScale = gc.Current.Tr.GetScale()
pathConverter.Convert(paths...)
gc.paint(gc.fillRasterizer, gc.Current.FillColor)
}
/* second method */
func (gc *ImageGraphicContext) FillStroke(paths ...*PathStorage) {
gc.fillRasterizer.UseNonZeroWinding = gc.Current.FillRule.UseNonZeroWinding()
gc.strokeRasterizer.UseNonZeroWinding = true
filler := NewVertexMatrixTransform(gc.Current.Tr, NewVertexAdder(gc.fillRasterizer))
stroker := NewLineStroker(gc.Current.Cap, gc.Current.Join, NewVertexMatrixTransform(gc.Current.Tr, NewVertexAdder(gc.strokeRasterizer)))
stroker.HalfLineWidth = gc.Current.LineWidth / 2
demux := NewDemuxConverter(filler, stroker)
paths = append(paths, gc.Current.Path)
pathConverter := NewPathConverter(demux)
pathConverter.ApproximationScale = gc.Current.Tr.GetScale()
pathConverter.Convert(paths...)
gc.paint(gc.fillRasterizer, gc.Current.FillColor)
gc.paint(gc.strokeRasterizer, gc.Current.StrokeColor)
}
func (f FillRule) UseNonZeroWinding() bool {
switch f {
case FillRuleEvenOdd:
return false
case FillRuleWinding:
return true
}
return false
}
func (c Cap) Convert() raster.Capper {
switch c {
case RoundCap:
return raster.RoundCapper
case ButtCap:
return raster.ButtCapper
case SquareCap:
return raster.SquareCapper
}
return raster.RoundCapper
}
func (j Join) Convert() raster.Joiner {
switch j {
case RoundJoin:
return raster.RoundJoiner
case BevelJoin:
return raster.BevelJoiner
}
return raster.RoundJoiner
}

View File

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

View File

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

View File

@ -1,18 +0,0 @@
// Copyright 2010 The draw2d Authors. All rights reserved.
// created: 21/11/2010 by Laurent Le Goff
package draw2d
type Path interface {
LastPoint() (x, y float64)
MoveTo(x, y float64)
RMoveTo(dx, dy float64)
LineTo(x, y float64)
RLineTo(dx, dy float64)
QuadCurveTo(cx, cy, x, y float64)
RQuadCurveTo(dcx, dcy, dx, dy float64)
CubicCurveTo(cx1, cy1, cx2, cy2, x, y float64)
RCubicCurveTo(dcx1, dcy1, dcx2, dcy2, dx, dy float64)
ArcTo(cx, cy, rx, ry, startAngle, angle float64)
RArcTo(dcx, dcy, rx, ry, startAngle, angle float64)
Close()
}

View File

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

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

View File

@ -1,12 +0,0 @@
include $(GOROOT)/src/Make.inc
TARG=draw2d.googlecode.com/hg/draw2d/raster
GOFILES=\
line.go\
polygon.go\
coverage_table.go\
fillerAA.go\
fixed_point.go
include $(GOROOT)/src/Make.pkg

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

View File

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

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

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

View File

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

View File

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

View File

@ -1,199 +0,0 @@
// Copyright 2010 The draw2d Authors. All rights reserved.
// created: 21/11/2010 by Laurent Le Goff
package draw2d
import (
"image"
)
type StackGraphicContext struct {
Current *ContextStack
}
type ContextStack struct {
Tr MatrixTransform
Path *PathStorage
LineWidth float64
Dash []float64
DashOffset float64
StrokeColor image.Color
FillColor image.Color
FillRule FillRule
Cap Cap
Join Join
FontSize float64
FontData FontData
previous *ContextStack
}
/**
* Create a new Graphic context from an image
*/
func NewStackGraphicContext() *StackGraphicContext {
gc := &StackGraphicContext{}
gc.Current = new(ContextStack)
gc.Current.Tr = NewIdentityMatrix()
gc.Current.Path = NewPathStorage()
gc.Current.LineWidth = 1.0
gc.Current.StrokeColor = image.Black
gc.Current.FillColor = image.White
gc.Current.Cap = RoundCap
gc.Current.FillRule = FillRuleEvenOdd
gc.Current.Join = RoundJoin
gc.Current.FontSize = 10
gc.Current.FontData = defaultFontData
return gc
}
func (gc *StackGraphicContext) GetMatrixTransform() MatrixTransform {
return gc.Current.Tr
}
func (gc *StackGraphicContext) SetMatrixTransform(Tr MatrixTransform) {
gc.Current.Tr = Tr
}
func (gc *StackGraphicContext) ComposeMatrixTransform(Tr MatrixTransform) {
gc.Current.Tr = Tr.Multiply(gc.Current.Tr)
}
func (gc *StackGraphicContext) Rotate(angle float64) {
gc.Current.Tr = NewRotationMatrix(angle).Multiply(gc.Current.Tr)
}
func (gc *StackGraphicContext) Translate(tx, ty float64) {
gc.Current.Tr = NewTranslationMatrix(tx, ty).Multiply(gc.Current.Tr)
}
func (gc *StackGraphicContext) Scale(sx, sy float64) {
gc.Current.Tr = NewScaleMatrix(sx, sy).Multiply(gc.Current.Tr)
}
func (gc *StackGraphicContext) SetStrokeColor(c image.Color) {
gc.Current.StrokeColor = c
}
func (gc *StackGraphicContext) SetFillColor(c image.Color) {
gc.Current.FillColor = c
}
func (gc *StackGraphicContext) SetFillRule(f FillRule) {
gc.Current.FillRule = f
}
func (gc *StackGraphicContext) SetLineWidth(LineWidth float64) {
gc.Current.LineWidth = LineWidth
}
func (gc *StackGraphicContext) SetLineCap(Cap Cap) {
gc.Current.Cap = Cap
}
func (gc *StackGraphicContext) SetLineJoin(Join Join) {
gc.Current.Join = Join
}
func (gc *StackGraphicContext) SetLineDash(Dash []float64, DashOffset float64) {
gc.Current.Dash = Dash
gc.Current.DashOffset = DashOffset
}
func (gc *StackGraphicContext) SetFontSize(FontSize float64) {
gc.Current.FontSize = FontSize
}
func (gc *StackGraphicContext) GetFontSize() float64 {
return gc.Current.FontSize
}
func (gc *StackGraphicContext) SetFontData(FontData FontData) {
gc.Current.FontData = FontData
}
func (gc *StackGraphicContext) GetFontData() FontData {
return gc.Current.FontData
}
func (gc *StackGraphicContext) BeginPath() {
gc.Current.Path.Clear()
}
func (gc *StackGraphicContext) IsEmpty() bool {
return gc.Current.Path.IsEmpty()
}
func (gc *StackGraphicContext) LastPoint() (float64, float64) {
return gc.Current.Path.LastPoint()
}
func (gc *StackGraphicContext) MoveTo(x, y float64) {
gc.Current.Path.MoveTo(x, y)
}
func (gc *StackGraphicContext) RMoveTo(dx, dy float64) {
gc.Current.Path.RMoveTo(dx, dy)
}
func (gc *StackGraphicContext) LineTo(x, y float64) {
gc.Current.Path.LineTo(x, y)
}
func (gc *StackGraphicContext) RLineTo(dx, dy float64) {
gc.Current.Path.RLineTo(dx, dy)
}
func (gc *StackGraphicContext) QuadCurveTo(cx, cy, x, y float64) {
gc.Current.Path.QuadCurveTo(cx, cy, x, y)
}
func (gc *StackGraphicContext) RQuadCurveTo(dcx, dcy, dx, dy float64) {
gc.Current.Path.RQuadCurveTo(dcx, dcy, dx, dy)
}
func (gc *StackGraphicContext) CubicCurveTo(cx1, cy1, cx2, cy2, x, y float64) {
gc.Current.Path.CubicCurveTo(cx1, cy1, cx2, cy2, x, y)
}
func (gc *StackGraphicContext) RCubicCurveTo(dcx1, dcy1, dcx2, dcy2, dx, dy float64) {
gc.Current.Path.RCubicCurveTo(dcx1, dcy1, dcx2, dcy2, dx, dy)
}
func (gc *StackGraphicContext) ArcTo(cx, cy, rx, ry, startAngle, angle float64) {
gc.Current.Path.ArcTo(cx, cy, rx, ry, startAngle, angle)
}
func (gc *StackGraphicContext) RArcTo(dcx, dcy, rx, ry, startAngle, angle float64) {
gc.Current.Path.RArcTo(dcx, dcy, rx, ry, startAngle, angle)
}
func (gc *StackGraphicContext) Close() {
gc.Current.Path.Close()
}
func (gc *StackGraphicContext) Save() {
context := new(ContextStack)
context.FontSize = gc.Current.FontSize
context.FontData = gc.Current.FontData
context.LineWidth = gc.Current.LineWidth
context.StrokeColor = gc.Current.StrokeColor
context.FillColor = gc.Current.FillColor
context.FillRule = gc.Current.FillRule
context.Dash = gc.Current.Dash
context.DashOffset = gc.Current.DashOffset
context.Cap = gc.Current.Cap
context.Join = gc.Current.Join
context.Path = gc.Current.Path.Copy()
copy(context.Tr[:], gc.Current.Tr[:])
context.previous = gc.Current
gc.Current = context
}
func (gc *StackGraphicContext) Restore() {
if gc.Current.previous != nil {
oldContext := gc.Current
gc.Current = gc.Current.previous
oldContext.previous = nil
}
}

View File

@ -1,137 +0,0 @@
// Copyright 2010 The draw2d Authors. All rights reserved.
// created: 13/12/2010 by Laurent Le Goff
package draw2d
type Cap int
const (
RoundCap Cap = iota
ButtCap
SquareCap
)
type Join int
const (
BevelJoin Join = iota
RoundJoin
MiterJoin
)
type LineStroker struct {
Next VertexConverter
HalfLineWidth float64
Cap Cap
Join Join
vertices []float64
rewind []float64
x, y, nx, ny float64
command VertexCommand
}
func NewLineStroker(c Cap, j Join, converter VertexConverter) *LineStroker {
l := new(LineStroker)
l.Next = converter
l.HalfLineWidth = 0.5
l.vertices = make([]float64, 0, 256)
l.rewind = make([]float64, 0, 256)
l.Cap = c
l.Join = j
l.command = VertexNoCommand
return l
}
func (l *LineStroker) NextCommand(command VertexCommand) {
l.command = command
if command == VertexStopCommand {
l.Next.NextCommand(VertexStartCommand)
for i, j := 0, 1; j < len(l.vertices); i, j = i+2, j+2 {
l.Next.Vertex(l.vertices[i], l.vertices[j])
l.Next.NextCommand(VertexNoCommand)
}
for i, j := len(l.rewind)-2, len(l.rewind)-1; j > 0; i, j = i-2, j-2 {
l.Next.NextCommand(VertexNoCommand)
l.Next.Vertex(l.rewind[i], l.rewind[j])
}
if len(l.vertices) > 1 {
l.Next.NextCommand(VertexNoCommand)
l.Next.Vertex(l.vertices[0], l.vertices[1])
}
l.Next.NextCommand(VertexStopCommand)
// reinit vertices
l.vertices = l.vertices[0:0]
l.rewind = l.rewind[0:0]
l.x, l.y, l.nx, l.ny = 0, 0, 0, 0
}
}
func (l *LineStroker) Vertex(x, y float64) {
switch l.command {
case VertexNoCommand:
l.line(l.x, l.y, x, y)
case VertexJoinCommand:
l.joinLine(l.x, l.y, l.nx, l.ny, x, y)
case VertexStartCommand:
l.x, l.y = x, y
case VertexCloseCommand:
l.line(l.x, l.y, x, y)
l.joinLine(l.x, l.y, l.nx, l.ny, x, y)
l.closePolygon()
}
l.command = VertexNoCommand
}
func (l *LineStroker) appendVertex(vertices ...float64) {
s := len(vertices) / 2
if len(l.vertices)+s >= cap(l.vertices) {
v := make([]float64, len(l.vertices), cap(l.vertices)+128)
copy(v, l.vertices)
l.vertices = v
v = make([]float64, len(l.rewind), cap(l.rewind)+128)
copy(v, l.rewind)
l.rewind = v
}
copy(l.vertices[len(l.vertices):len(l.vertices)+s], vertices[:s])
l.vertices = l.vertices[0 : len(l.vertices)+s]
copy(l.rewind[len(l.rewind):len(l.rewind)+s], vertices[s:])
l.rewind = l.rewind[0 : len(l.rewind)+s]
}
func (l *LineStroker) closePolygon() {
if len(l.vertices) > 1 {
l.appendVertex(l.vertices[0], l.vertices[1], l.rewind[0], l.rewind[1])
}
}
func (l *LineStroker) line(x1, y1, x2, y2 float64) {
dx := (x2 - x1)
dy := (y2 - y1)
d := vectorDistance(dx, dy)
if d != 0 {
nx := dy * l.HalfLineWidth / d
ny := -(dx * l.HalfLineWidth / d)
l.appendVertex(x1+nx, y1+ny, x2+nx, y2+ny, x1-nx, y1-ny, x2-nx, y2-ny)
l.x, l.y, l.nx, l.ny = x2, y2, nx, ny
}
}
func (l *LineStroker) joinLine(x1, y1, nx1, ny1, x2, y2 float64) {
dx := (x2 - x1)
dy := (y2 - y1)
d := vectorDistance(dx, dy)
if d != 0 {
nx := dy * l.HalfLineWidth / d
ny := -(dx * l.HalfLineWidth / d)
/* l.join(x1, y1, x1 + nx, y1 - ny, nx, ny, x1 + ny2, y1 + nx2, nx2, ny2)
l.join(x1, y1, x1 - ny1, y1 - nx1, nx1, ny1, x1 - ny2, y1 - nx2, nx2, ny2)*/
l.appendVertex(x1+nx, y1+ny, x2+nx, y2+ny, x1-nx, y1-ny, x2-nx, y2-ny)
l.x, l.y, l.nx, l.ny = x2, y2, nx, ny
}
}

View File

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

View File

@ -1,18 +0,0 @@
// Copyright 2010 The draw2d Authors. All rights reserved.
// created: 21/11/2010 by Laurent Le Goff
package draw2d
type VertexCommand byte
const (
VertexNoCommand VertexCommand = iota
VertexStartCommand
VertexJoinCommand
VertexCloseCommand
VertexStopCommand
)
type VertexConverter interface {
NextCommand(cmd VertexCommand)
Vertex(x, y float64)
}

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

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

208
draw2dbase/stack_gc.go Normal file
View File

@ -0,0 +1,208 @@
// Copyright 2010 The draw2d Authors. All rights reserved.
// created: 21/11/2010 by Laurent Le Goff
package draw2dbase
import (
"fmt"
"image"
"image/color"
"git.fromouter.space/crunchy-rocks/draw2d"
"git.fromouter.space/crunchy-rocks/freetype/truetype"
)
var DefaultFontData = draw2d.FontData{Name: "luxi", Family: draw2d.FontFamilySans, Style: draw2d.FontStyleNormal}
type StackGraphicContext struct {
Current *ContextStack
}
type ContextStack struct {
Tr draw2d.Matrix
Path *draw2d.Path
LineWidth float64
Dash []float64
DashOffset float64
StrokeColor color.Color
FillColor color.Color
FillRule draw2d.FillRule
Cap draw2d.LineCap
Join draw2d.LineJoin
FontSize float64
FontData draw2d.FontData
Font *truetype.Font
// fontSize and dpi are used to calculate scale. scale is the number of
// 26.6 fixed point units in 1 em.
Scale float64
Previous *ContextStack
}
// GetFontName gets the current FontData with fontSize as a string
func (cs *ContextStack) GetFontName() string {
fontData := cs.FontData
return fmt.Sprintf("%s:%d:%d:%9.2f", fontData.Name, fontData.Family, fontData.Style, cs.FontSize)
}
/**
* Create a new Graphic context from an image
*/
func NewStackGraphicContext() *StackGraphicContext {
gc := &StackGraphicContext{}
gc.Current = new(ContextStack)
gc.Current.Tr = draw2d.NewIdentityMatrix()
gc.Current.Path = new(draw2d.Path)
gc.Current.LineWidth = 1.0
gc.Current.StrokeColor = image.Black
gc.Current.FillColor = image.White
gc.Current.Cap = draw2d.RoundCap
gc.Current.FillRule = draw2d.FillRuleEvenOdd
gc.Current.Join = draw2d.RoundJoin
gc.Current.FontSize = 10
gc.Current.FontData = DefaultFontData
return gc
}
func (gc *StackGraphicContext) GetMatrixTransform() draw2d.Matrix {
return gc.Current.Tr
}
func (gc *StackGraphicContext) SetMatrixTransform(Tr draw2d.Matrix) {
gc.Current.Tr = Tr
}
func (gc *StackGraphicContext) ComposeMatrixTransform(Tr draw2d.Matrix) {
gc.Current.Tr.Compose(Tr)
}
func (gc *StackGraphicContext) Rotate(angle float64) {
gc.Current.Tr.Rotate(angle)
}
func (gc *StackGraphicContext) Translate(tx, ty float64) {
gc.Current.Tr.Translate(tx, ty)
}
func (gc *StackGraphicContext) Scale(sx, sy float64) {
gc.Current.Tr.Scale(sx, sy)
}
func (gc *StackGraphicContext) SetStrokeColor(c color.Color) {
gc.Current.StrokeColor = c
}
func (gc *StackGraphicContext) SetFillColor(c color.Color) {
gc.Current.FillColor = c
}
func (gc *StackGraphicContext) SetFillRule(f draw2d.FillRule) {
gc.Current.FillRule = f
}
func (gc *StackGraphicContext) SetLineWidth(lineWidth float64) {
gc.Current.LineWidth = lineWidth
}
func (gc *StackGraphicContext) SetLineCap(cap draw2d.LineCap) {
gc.Current.Cap = cap
}
func (gc *StackGraphicContext) SetLineJoin(join draw2d.LineJoin) {
gc.Current.Join = join
}
func (gc *StackGraphicContext) SetLineDash(dash []float64, dashOffset float64) {
gc.Current.Dash = dash
gc.Current.DashOffset = dashOffset
}
func (gc *StackGraphicContext) SetFontSize(fontSize float64) {
gc.Current.FontSize = fontSize
}
func (gc *StackGraphicContext) GetFontSize() float64 {
return gc.Current.FontSize
}
func (gc *StackGraphicContext) SetFontData(fontData draw2d.FontData) {
gc.Current.FontData = fontData
}
func (gc *StackGraphicContext) GetFontData() draw2d.FontData {
return gc.Current.FontData
}
func (gc *StackGraphicContext) BeginPath() {
gc.Current.Path.Clear()
}
func (gc *StackGraphicContext) GetPath() draw2d.Path {
return *gc.Current.Path.Copy()
}
func (gc *StackGraphicContext) IsEmpty() bool {
return gc.Current.Path.IsEmpty()
}
func (gc *StackGraphicContext) LastPoint() (float64, float64) {
return gc.Current.Path.LastPoint()
}
func (gc *StackGraphicContext) MoveTo(x, y float64) {
gc.Current.Path.MoveTo(x, y)
}
func (gc *StackGraphicContext) LineTo(x, y float64) {
gc.Current.Path.LineTo(x, y)
}
func (gc *StackGraphicContext) QuadCurveTo(cx, cy, x, y float64) {
gc.Current.Path.QuadCurveTo(cx, cy, x, y)
}
func (gc *StackGraphicContext) CubicCurveTo(cx1, cy1, cx2, cy2, x, y float64) {
gc.Current.Path.CubicCurveTo(cx1, cy1, cx2, cy2, x, y)
}
func (gc *StackGraphicContext) ArcTo(cx, cy, rx, ry, startAngle, angle float64) {
gc.Current.Path.ArcTo(cx, cy, rx, ry, startAngle, angle)
}
func (gc *StackGraphicContext) Close() {
gc.Current.Path.Close()
}
func (gc *StackGraphicContext) Save() {
context := new(ContextStack)
context.FontSize = gc.Current.FontSize
context.FontData = gc.Current.FontData
context.LineWidth = gc.Current.LineWidth
context.StrokeColor = gc.Current.StrokeColor
context.FillColor = gc.Current.FillColor
context.FillRule = gc.Current.FillRule
context.Dash = gc.Current.Dash
context.DashOffset = gc.Current.DashOffset
context.Cap = gc.Current.Cap
context.Join = gc.Current.Join
context.Path = gc.Current.Path.Copy()
context.Font = gc.Current.Font
context.Scale = gc.Current.Scale
copy(context.Tr[:], gc.Current.Tr[:])
context.Previous = gc.Current
gc.Current = context
}
func (gc *StackGraphicContext) Restore() {
if gc.Current.Previous != nil {
oldContext := gc.Current
gc.Current = gc.Current.Previous
oldContext.Previous = nil
}
}
func (gc *StackGraphicContext) GetFontName() string {
return gc.Current.GetFontName()
}

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

@ -1,7 +0,0 @@
include $(GOROOT)/src/Make.inc
TARG=draw2d.googlecode.com/hg/draw2dgl
GOFILES=\
gc.go\
include $(GOROOT)/src/Make.pkg

3
draw2dgl/doc.go Normal file
View File

@ -0,0 +1,3 @@
// Package draw2dgl provides a graphic context that can draw vector
// graphics and text on OpenGL.
package draw2dgl

View File

@ -2,14 +2,28 @@ package draw2dgl
import (
"image"
"image/color"
"image/draw"
"gl"
"freetype-go.googlecode.com/hg/freetype/raster"
"draw2d.googlecode.com/hg/draw2d"
//"log"
"log"
"math"
"runtime"
"git.fromouter.space/crunchy-rocks/draw2d"
"git.fromouter.space/crunchy-rocks/draw2d/draw2dbase"
"git.fromouter.space/crunchy-rocks/draw2d/draw2dimg"
"github.com/go-gl/gl/v2.1/gl"
"github.com/golang/freetype/raster"
"github.com/golang/freetype/truetype"
"golang.org/x/image/font"
"golang.org/x/image/math/fixed"
)
type GLPainter struct {
func init() {
runtime.LockOSThread()
}
type Painter struct {
// The Porter-Duff composition operator.
Op draw.Op
// The 16-bit color to paint the spans.
@ -22,7 +36,7 @@ type GLPainter struct {
const M16 uint32 = 1<<16 - 1
// Paint satisfies the Painter interface by painting ss onto an image.RGBA.
func (p *GLPainter) Paint(ss []raster.Span, done bool) {
func (p *Painter) Paint(ss []raster.Span, done bool) {
//gl.Begin(gl.LINES)
sslen := len(ss)
clenrequired := sslen * 8
@ -44,8 +58,8 @@ func (p *GLPainter) Paint(ss []raster.Span, done bool) {
vertices []int32
)
for _, s := range ss {
ma := s.A >> 16
a := uint8((ma * p.ca / M16) >> 8)
a := uint8((s.Alpha * p.ca / M16) >> 8)
colors = p.colors[ci:]
colors[0] = p.cr
colors[1] = p.cg
@ -65,15 +79,15 @@ func (p *GLPainter) Paint(ss []raster.Span, done bool) {
}
}
func (p *GLPainter) Flush() {
func (p *Painter) Flush() {
if len(p.vertices) != 0 {
gl.EnableClientState(gl.COLOR_ARRAY)
gl.EnableClientState(gl.VERTEX_ARRAY)
gl.ColorPointer(4, 0, p.colors)
gl.VertexPointer(2, 0, p.vertices)
gl.ColorPointer(4, gl.UNSIGNED_BYTE, 0, gl.Ptr(p.colors))
gl.VertexPointer(2, gl.INT, 0, gl.Ptr(p.vertices))
// draw lines
gl.DrawArrays(gl.LINES, 0, len(p.vertices)/2)
gl.DrawArrays(gl.LINES, 0, int32(len(p.vertices)/2))
gl.DisableClientState(gl.VERTEX_ARRAY)
gl.DisableClientState(gl.COLOR_ARRAY)
p.vertices = p.vertices[0:0]
@ -81,9 +95,8 @@ func (p *GLPainter) Flush() {
}
}
// SetColor sets the color to paint the spans.
func (p *GLPainter) SetColor(c image.Color) {
func (p *Painter) SetColor(c color.Color) {
r, g, b, a := c.RGBA()
if a == 0 {
p.cr = 0
@ -99,144 +112,302 @@ func (p *GLPainter) SetColor(c image.Color) {
}
// NewRGBAPainter creates a new RGBAPainter for the given image.
func NewGLPainter() *GLPainter {
p := new(GLPainter)
func NewPainter() *Painter {
p := new(Painter)
p.vertices = make([]int32, 0, 1024)
p.colors = make([]uint8, 0, 1024)
return p
}
type GraphicContext struct {
*draw2d.StackGraphicContext
painter *GLPainter
*draw2dbase.StackGraphicContext
painter *Painter
fillRasterizer *raster.Rasterizer
strokeRasterizer *raster.Rasterizer
FontCache draw2d.FontCache
glyphCache draw2dbase.GlyphCache
glyphBuf *truetype.GlyphBuf
DPI int
}
type GLVertex struct {
x, y float64
}
func NewGLVertex() *GLVertex {
return &GLVertex{}
}
func (glVertex *GLVertex) NextCommand(cmd draw2d.VertexCommand) {
}
func (glVertex *GLVertex) Vertex(x, y float64) {
gl.Vertex2d(x, y)
}
/**
* Create a new Graphic context from an image
*/
// NewGraphicContext creates a new Graphic context from an image.
func NewGraphicContext(width, height int) *GraphicContext {
gc := &GraphicContext{
draw2d.NewStackGraphicContext(),
NewGLPainter(),
draw2dbase.NewStackGraphicContext(),
NewPainter(),
raster.NewRasterizer(width, height),
raster.NewRasterizer(width, height),
draw2d.GetGlobalFontCache(),
draw2dbase.NewGlyphCache(),
&truetype.GlyphBuf{},
92,
}
return gc
}
func (gc *GraphicContext) SetDPI(dpi int) {
func (gc *GraphicContext) loadCurrentFont() (*truetype.Font, error) {
font, err := gc.FontCache.Load(gc.Current.FontData)
if err != nil {
font, err = gc.FontCache.Load(draw2dbase.DefaultFontData)
}
if font != nil {
gc.SetFont(font)
gc.SetFontSize(gc.Current.FontSize)
}
return font, err
}
func (gc *GraphicContext) drawGlyph(glyph truetype.Index, dx, dy float64) error {
if err := gc.glyphBuf.Load(gc.Current.Font, fixed.Int26_6(gc.Current.Scale), glyph, font.HintingNone); err != nil {
return err
}
e0 := 0
for _, e1 := range gc.glyphBuf.Ends {
DrawContour(gc, gc.glyphBuf.Points[e0:e1], dx, dy)
e0 = e1
}
return nil
}
// CreateStringPath creates a path from the string s at x, y, and returns the string width.
// The text is placed so that the left edge of the em square of the first character of s
// and the baseline intersect at x, y. The majority of the affected pixels will be
// above and to the right of the point, but some may be below or to the left.
// For example, drawing a string that starts with a 'J' in an italic font may
// affect pixels below and left of the point.
func (gc *GraphicContext) CreateStringPath(s string, x, y float64) float64 {
f, err := gc.loadCurrentFont()
if err != nil {
log.Println(err)
return 0.0
}
startx := x
prev, hasPrev := truetype.Index(0), false
for _, rune := range s {
index := f.Index(rune)
if hasPrev {
x += fUnitsToFloat64(f.Kern(fixed.Int26_6(gc.Current.Scale), prev, index))
}
err := gc.drawGlyph(index, x, y)
if err != nil {
log.Println(err)
return startx - x
}
x += fUnitsToFloat64(f.HMetric(fixed.Int26_6(gc.Current.Scale), index).AdvanceWidth)
prev, hasPrev = index, true
}
return x - startx
}
// FillString draws the text at point (0, 0)
func (gc *GraphicContext) FillString(text string) (width float64) {
return gc.FillStringAt(text, 0, 0)
}
// FillStringAt draws the text at the specified point (x, y)
func (gc *GraphicContext) FillStringAt(text string, x, y float64) (width float64) {
f, err := gc.loadCurrentFont()
if err != nil {
log.Println(err)
return 0.0
}
startx := x
prev, hasPrev := truetype.Index(0), false
fontName := gc.GetFontName()
for _, r := range text {
index := f.Index(r)
if hasPrev {
x += fUnitsToFloat64(f.Kern(fixed.Int26_6(gc.Current.Scale), prev, index))
}
glyph := gc.glyphCache.Fetch(gc, fontName, r)
x += glyph.Fill(gc, x, y)
prev, hasPrev = index, true
}
return x - startx
}
// GetStringBounds returns the approximate pixel bounds of the string s at x, y.
// The the left edge of the em square of the first character of s
// and the baseline intersect at 0, 0 in the returned coordinates.
// Therefore the top and left coordinates may well be negative.
func (gc *GraphicContext) GetStringBounds(s string) (left, top, right, bottom float64) {
f, err := gc.loadCurrentFont()
if err != nil {
log.Println(err)
return 0, 0, 0, 0
}
top, left, bottom, right = 10e6, 10e6, -10e6, -10e6
cursor := 0.0
prev, hasPrev := truetype.Index(0), false
for _, rune := range s {
index := f.Index(rune)
if hasPrev {
cursor += fUnitsToFloat64(f.Kern(fixed.Int26_6(gc.Current.Scale), prev, index))
}
if err := gc.glyphBuf.Load(gc.Current.Font, fixed.Int26_6(gc.Current.Scale), index, font.HintingNone); err != nil {
log.Println(err)
return 0, 0, 0, 0
}
e0 := 0
for _, e1 := range gc.glyphBuf.Ends {
ps := gc.glyphBuf.Points[e0:e1]
for _, p := range ps {
x, y := pointToF64Point(p)
top = math.Min(top, y)
bottom = math.Max(bottom, y)
left = math.Min(left, x+cursor)
right = math.Max(right, x+cursor)
}
}
cursor += fUnitsToFloat64(f.HMetric(fixed.Int26_6(gc.Current.Scale), index).AdvanceWidth)
prev, hasPrev = index, true
}
return left, top, right, bottom
}
// StrokeString draws the contour of the text at point (0, 0)
func (gc *GraphicContext) StrokeString(text string) (width float64) {
return gc.StrokeStringAt(text, 0, 0)
}
// StrokeStringAt draws the contour of the text at point (x, y)
func (gc *GraphicContext) StrokeStringAt(text string, x, y float64) (width float64) {
f, err := gc.loadCurrentFont()
if err != nil {
log.Println(err)
return 0.0
}
startx := x
prev, hasPrev := truetype.Index(0), false
fontName := gc.GetFontName()
for _, r := range text {
index := f.Index(r)
if hasPrev {
x += fUnitsToFloat64(f.Kern(fixed.Int26_6(gc.Current.Scale), prev, index))
}
glyph := gc.glyphCache.Fetch(gc, fontName, r)
x += glyph.Stroke(gc, x, y)
prev, hasPrev = index, true
}
return x - startx
}
// recalc recalculates scale and bounds values from the font size, screen
// resolution and font metrics, and invalidates the glyph cache.
func (gc *GraphicContext) recalc() {
gc.Current.Scale = gc.Current.FontSize * float64(gc.DPI) * (64.0 / 72.0)
}
func (gc *GraphicContext) SetDPI(dpi int) {
gc.DPI = dpi
gc.recalc()
}
// SetFont sets the font used to draw text.
func (gc *GraphicContext) SetFont(font *truetype.Font) {
gc.Current.Font = font
}
// SetFontSize sets the font size in points (as in ``a 12 point font'').
func (gc *GraphicContext) SetFontSize(fontSize float64) {
gc.Current.FontSize = fontSize
gc.recalc()
}
func (gc *GraphicContext) GetDPI() int {
return -1
return gc.DPI
}
//TODO
func (gc *GraphicContext) Clear() {
panic("not implemented")
}
//TODO
func (gc *GraphicContext) ClearRect(x1, y1, x2, y2 int) {
panic("not implemented")
}
//TODO
func (gc *GraphicContext) DrawImage(img image.Image) {
panic("not implemented")
}
func (gc *GraphicContext) FillString(text string) (cursor float64) {
return 0
}
func (gc *GraphicContext) paint(rasterizer *raster.Rasterizer, color image.Color) {
func (gc *GraphicContext) paint(rasterizer *raster.Rasterizer, color color.Color) {
gc.painter.SetColor(color)
rasterizer.Rasterize(gc.painter)
rasterizer.Clear()
gc.painter.Flush()
gc.Current.Path.Clear()
}
func (gc *GraphicContext) Stroke(paths ...*draw2d.PathStorage) {
func (gc *GraphicContext) Stroke(paths ...*draw2d.Path) {
paths = append(paths, gc.Current.Path)
gc.strokeRasterizer.UseNonZeroWinding = true
stroker := draw2d.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2d.NewVertexMatrixTransform(gc.Current.Tr, draw2d.NewVertexAdder(gc.strokeRasterizer)))
stroker := draw2dbase.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2dbase.Transformer{Tr: gc.Current.Tr, Flattener: draw2dimg.FtLineBuilder{Adder: gc.strokeRasterizer}})
stroker.HalfLineWidth = gc.Current.LineWidth / 2
var pathConverter *draw2d.PathConverter
var liner draw2dbase.Flattener
if gc.Current.Dash != nil && len(gc.Current.Dash) > 0 {
dasher := draw2d.NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker)
pathConverter = draw2d.NewPathConverter(dasher)
liner = draw2dbase.NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker)
} else {
pathConverter = draw2d.NewPathConverter(stroker)
liner = stroker
}
for _, p := range paths {
draw2dbase.Flatten(p, liner, gc.Current.Tr.GetScale())
}
pathConverter.ApproximationScale = gc.Current.Tr.GetScale() // From agg code
pathConverter.Convert(paths...)
gc.paint(gc.strokeRasterizer, gc.Current.StrokeColor)
gc.Current.Path.Clear()
}
func (gc *GraphicContext) Fill(paths ...*draw2d.PathStorage) {
func (gc *GraphicContext) Fill(paths ...*draw2d.Path) {
paths = append(paths, gc.Current.Path)
gc.fillRasterizer.UseNonZeroWinding = gc.Current.FillRule.UseNonZeroWinding()
gc.fillRasterizer.UseNonZeroWinding = useNonZeroWinding(gc.Current.FillRule)
pathConverter := draw2d.NewPathConverter(draw2d.NewVertexMatrixTransform(gc.Current.Tr, draw2d.NewVertexAdder(gc.fillRasterizer)))
pathConverter.ApproximationScale = gc.Current.Tr.GetScale() // From agg code
pathConverter.Convert(paths...)
/**** first method ****/
flattener := draw2dbase.Transformer{Tr: gc.Current.Tr, Flattener: draw2dimg.FtLineBuilder{Adder: gc.fillRasterizer}}
for _, p := range paths {
draw2dbase.Flatten(p, flattener, gc.Current.Tr.GetScale())
}
gc.paint(gc.fillRasterizer, gc.Current.FillColor)
gc.Current.Path.Clear()
}
/*
func (gc *GraphicContext) Fill(paths ...*draw2d.PathStorage) {
func (gc *GraphicContext) FillStroke(paths ...*draw2d.Path) {
paths = append(paths, gc.Current.Path)
gc.fillRasterizer.UseNonZeroWinding = gc.Current.FillRule.UseNonZeroWinding()
pathConverter := draw2d.NewPathAdder(draw2d.NewMatrixTransformAdder(gc.Current.Tr, gc.fillRasterizer))
pathConverter.ApproximationScale = gc.Current.Tr.GetScale()
pathConverter.Convert(paths...)
gc.paint(gc.fillRasterizer, gc.Current.FillColor)
gc.Current.Path.Clear()
}
*/
func (gc *GraphicContext) FillStroke(paths ...*draw2d.PathStorage) {
gc.fillRasterizer.UseNonZeroWinding = gc.Current.FillRule.UseNonZeroWinding()
gc.fillRasterizer.UseNonZeroWinding = useNonZeroWinding(gc.Current.FillRule)
gc.strokeRasterizer.UseNonZeroWinding = true
filler := draw2d.NewVertexMatrixTransform(gc.Current.Tr, draw2d.NewVertexAdder(gc.fillRasterizer))
flattener := draw2dbase.Transformer{Tr: gc.Current.Tr, Flattener: draw2dimg.FtLineBuilder{Adder: gc.fillRasterizer}}
stroker := draw2d.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2d.NewVertexMatrixTransform(gc.Current.Tr, draw2d.NewVertexAdder(gc.strokeRasterizer)))
stroker := draw2dbase.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2dbase.Transformer{Tr: gc.Current.Tr, Flattener: draw2dimg.FtLineBuilder{Adder: gc.strokeRasterizer}})
stroker.HalfLineWidth = gc.Current.LineWidth / 2
demux := draw2d.NewDemuxConverter(filler, stroker)
paths = append(paths, gc.Current.Path)
pathConverter := draw2d.NewPathConverter(demux)
pathConverter.ApproximationScale = gc.Current.Tr.GetScale() // From agg code
pathConverter.Convert(paths...)
var liner draw2dbase.Flattener
if gc.Current.Dash != nil && len(gc.Current.Dash) > 0 {
liner = draw2dbase.NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker)
} else {
liner = stroker
}
demux := draw2dbase.DemuxFlattener{Flatteners: []draw2dbase.Flattener{flattener, liner}}
for _, p := range paths {
draw2dbase.Flatten(p, demux, gc.Current.Tr.GetScale())
}
// Fill
gc.paint(gc.fillRasterizer, gc.Current.FillColor)
// Stroke
gc.paint(gc.strokeRasterizer, gc.Current.StrokeColor)
gc.Current.Path = draw2d.NewPathStorage()
}
func useNonZeroWinding(f draw2d.FillRule) bool {
switch f {
case draw2d.FillRuleEvenOdd:
return false
case draw2d.FillRuleWinding:
return true
}
return false
}

6
draw2dgl/notes.md Normal file
View File

@ -0,0 +1,6 @@
Rendering Vector Art OpenGl
References:
* https://www.youtube.com/watch?v=K5u8ZZBWSdw
* http://http.developer.nvidia.com/GPUGems3/gpugems3_ch25.html
* http://research.microsoft.com/en-us/um/people/cloop/loopblinn05.pdf
* https://github.com/openframeworks/openFrameworks/issues/1190

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

46
draw2dimg/fileutil.go Normal file
View File

@ -0,0 +1,46 @@
package draw2dimg
import (
"bufio"
"image"
"image/png"
"os"
)
// SaveToPngFile create and save an image to a file using PNG format
func SaveToPngFile(filePath string, m image.Image) error {
// Create the file
f, err := os.Create(filePath)
if err != nil {
return err
}
defer f.Close()
// Create Writer from file
b := bufio.NewWriter(f)
// Write the image into the buffer
err = png.Encode(b, m)
if err != nil {
return err
}
err = b.Flush()
if err != nil {
return err
}
return nil
}
// LoadFromPngFile Open a png file
func LoadFromPngFile(filePath string) (image.Image, error) {
// Open file
f, err := os.OpenFile(filePath, 0, 0)
if err != nil {
return nil, err
}
defer f.Close()
b := bufio.NewReader(f)
img, err := png.Decode(b)
if err != nil {
return nil, err
}
return img, nil
}

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

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

42
draw2dpdf/README.md Normal file
View File

@ -0,0 +1,42 @@
draw2d pdf
==========
Package draw2dpdf provides a graphic context that can draw vector graphics and text on pdf file with the [gofpdf](https://github.com/jung-kurt/gofpdf) package.
Quick Start
-----------
The following Go code generates a simple drawing and saves it to a pdf document:
```go
// Initialize the graphic context on an RGBA image
dest := draw2dpdf.NewPdf("L", "mm", "A4")
gc := draw2dpdf.NewGraphicContext(dest)
// Set some properties
gc.SetFillColor(color.RGBA{0x44, 0xff, 0x44, 0xff})
gc.SetStrokeColor(color.RGBA{0x44, 0x44, 0x44, 0xff})
gc.SetLineWidth(5)
// Draw a closed shape
gc.MoveTo(10, 10) // should always be called first for a new path
gc.LineTo(100, 50)
gc.QuadCurveTo(100, 10, 10, 10)
gc.Close()
gc.FillStroke()
// Save to file
draw2dpdf.SaveToPdfFile("hello.pdf", dest)
```
There are more examples here: https://github.com/llgcode/draw2d/tree/master/samples
Alternative backends
--------------------
- Drawing on images is provided by the draw2d package.
- Drawing on opengl is provided by the draw2dgl package.
Acknowledgments
---------------
The pdf backend uses https://github.com/jung-kurt/gofpdf

41
draw2dpdf/doc.go Normal file
View File

@ -0,0 +1,41 @@
// Copyright 2015 The draw2d Authors. All rights reserved.
// created: 26/06/2015 by Stani Michiels
// Package draw2dpdf provides a graphic context that can draw vector
// graphics and text on pdf file with the gofpdf package.
//
// Quick Start
//
// The following Go code generates a simple drawing and saves it to a
// pdf document:
// // Initialize the graphic context on an RGBA image
// dest := draw2dpdf.NewPdf("L", "mm", "A4")
// gc := draw2dpdf.NewGraphicContext(dest)
//
// // Set some properties
// gc.SetFillColor(color.RGBA{0x44, 0xff, 0x44, 0xff})
// gc.SetStrokeColor(color.RGBA{0x44, 0x44, 0x44, 0xff})
// gc.SetLineWidth(5)
//
// // Draw a closed shape
// gc.MoveTo(10, 10) // should always be called first for a new path
// gc.LineTo(100, 50)
// gc.QuadCurveTo(100, 10, 10, 10)
// gc.Close()
// gc.FillStroke()
//
// // Save to file
// draw2dpdf.SaveToPdfFile("hello.pdf", dest)
//
// There are more examples here:
// https://github.com/llgcode/draw2d/tree/master/samples
//
// Alternative backends
//
// Drawing on images is provided by the draw2d package.
// Drawing on opengl is provided by the draw2dgl package.
//
// Acknowledgments
//
// The pdf backend uses https://github.com/jung-kurt/gofpdf
package draw2dpdf

11
draw2dpdf/fileutil.go Normal file
View File

@ -0,0 +1,11 @@
// Copyright 2015 The draw2d Authors. All rights reserved.
// created: 26/06/2015 by Stani Michiels
package draw2dpdf
import "github.com/jung-kurt/gofpdf"
// SaveToPdfFile creates and saves a pdf document to a file
func SaveToPdfFile(filePath string, pdf *gofpdf.Fpdf) error {
return pdf.OutputFileAndClose(filePath)
}

379
draw2dpdf/gc.go Normal file
View File

@ -0,0 +1,379 @@
// Copyright 2015 The draw2d Authors. All rights reserved.
// created: 26/06/2015 by Stani Michiels
// TODO: dashed line
package draw2dpdf
import (
"bytes"
"image"
"image/color"
"image/png"
"log"
"math"
"os"
"strconv"
"git.fromouter.space/crunchy-rocks/freetype/truetype"
"github.com/jung-kurt/gofpdf"
"github.com/llgcode/draw2d"
"github.com/llgcode/draw2d/draw2dbase"
"github.com/llgcode/draw2d/draw2dkit"
)
const (
// DPI of a pdf document is fixed at 72.
DPI = 72
c255 = 255.0 / 65535.0
)
var (
caps = map[draw2d.LineCap]string{
draw2d.RoundCap: "round",
draw2d.ButtCap: "butt",
draw2d.SquareCap: "square"}
joins = map[draw2d.LineJoin]string{
draw2d.RoundJoin: "round",
draw2d.BevelJoin: "bevel",
draw2d.MiterJoin: "miter",
}
imageCount uint32
white color.Color = color.RGBA{255, 255, 255, 255}
)
// NewPdf creates a new pdf document with the draw2d fontfolder, adds
// a page and set fill color to white.
func NewPdf(orientationStr, unitStr, sizeStr string) *gofpdf.Fpdf {
pdf := gofpdf.New(orientationStr, unitStr, sizeStr, draw2d.GetFontFolder())
// to be compatible with draw2d
pdf.SetMargins(0, 0, 0)
pdf.SetDrawColor(0, 0, 0)
pdf.SetFillColor(255, 255, 255)
pdf.SetLineCapStyle("round")
pdf.SetLineJoinStyle("round")
pdf.SetLineWidth(1)
pdf.AddPage()
return pdf
}
// rgb converts a color (used by draw2d) into 3 int (used by gofpdf)
func rgb(c color.Color) (int, int, int) {
r, g, b, _ := c.RGBA()
return int(float64(r) * c255), int(float64(g) * c255), int(float64(b) * c255)
}
// clearRect draws a white rectangle
func clearRect(gc *GraphicContext, x1, y1, x2, y2 float64) {
// save state
f := gc.Current.FillColor
x, y := gc.pdf.GetXY()
// cover page with white rectangle
gc.SetFillColor(white)
draw2dkit.Rectangle(gc, x1, y1, x2, y2)
gc.Fill()
// restore state
gc.SetFillColor(f)
gc.pdf.MoveTo(x, y)
}
// GraphicContext implements the draw2d.GraphicContext interface
// It provides draw2d with a pdf backend (based on gofpdf)
type GraphicContext struct {
*draw2dbase.StackGraphicContext
pdf *gofpdf.Fpdf
DPI int
}
// NewGraphicContext creates a new pdf GraphicContext
func NewGraphicContext(pdf *gofpdf.Fpdf) *GraphicContext {
gc := &GraphicContext{draw2dbase.NewStackGraphicContext(), pdf, DPI}
gc.SetDPI(DPI)
return gc
}
// DrawImage draws an image as PNG
// TODO: add type (tp) as parameter to argument list?
func (gc *GraphicContext) DrawImage(image image.Image) {
name := strconv.Itoa(int(imageCount))
imageCount++
tp := "PNG" // "JPG", "JPEG", "PNG" and "GIF"
b := &bytes.Buffer{}
png.Encode(b, image)
gc.pdf.RegisterImageReader(name, tp, b)
bounds := image.Bounds()
x0, y0 := float64(bounds.Min.X), float64(bounds.Min.Y)
w, h := float64(bounds.Dx()), float64(bounds.Dy())
gc.pdf.Image(name, x0, y0, w, h, false, tp, 0, "")
}
// Clear draws a white rectangle over the whole page
func (gc *GraphicContext) Clear() {
width, height := gc.pdf.GetPageSize()
clearRect(gc, 0, 0, width, height)
}
// ClearRect draws a white rectangle over the specified area.
// Samples: line.
func (gc *GraphicContext) ClearRect(x1, y1, x2, y2 int) {
clearRect(gc, float64(x1), float64(y1), float64(x2), float64(y2))
}
// recalc recalculates scale and bounds values from the font size, screen
// resolution and font metrics, and invalidates the glyph cache.
func (gc *GraphicContext) recalc() {
// TODO: resolve properly the font size for pdf and bitmap
gc.Current.Scale = 3 * float64(gc.DPI) / 72
}
// SetDPI sets the DPI which influences the font size.
func (gc *GraphicContext) SetDPI(dpi int) {
gc.DPI = dpi
gc.recalc()
}
// GetDPI returns the DPI which influences the font size.
// (Note that gofpdf uses a fixed dpi of 72:
// https://godoc.org/code.google.com/p/gofpdf#Fpdf.PointConvert)
func (gc *GraphicContext) GetDPI() int {
return gc.DPI
}
// GetStringBounds returns the approximate pixel bounds of the string s at x, y.
// The left edge of the em square of the first character of s
// and the baseline intersect at 0, 0 in the returned coordinates.
// Therefore the top and left coordinates may well be negative.
func (gc *GraphicContext) GetStringBounds(s string) (left, top, right, bottom float64) {
_, h := gc.pdf.GetFontSize()
d := gc.pdf.GetFontDesc("", "")
if d.Ascent == 0 {
// not defined (standard font?), use average of 81%
top = 0.81 * h
} else {
top = -float64(d.Ascent) * h / float64(d.Ascent-d.Descent)
}
return 0, top, gc.pdf.GetStringWidth(s), top + h
}
// CreateStringPath creates a path from the string s at x, y, and returns the string width.
func (gc *GraphicContext) CreateStringPath(text string, x, y float64) (cursor float64) {
//fpdf uses the top left corner
left, top, right, bottom := gc.GetStringBounds(text)
w := right - left
h := bottom - top
// gc.pdf.SetXY(x, y-h) do not use this as y-h might be negative
margin := gc.pdf.GetCellMargin()
gc.pdf.MoveTo(x-left-margin, y+top)
gc.pdf.CellFormat(w, h, text, "", 0, "BL", false, 0, "")
return w
}
// FillString draws a string at 0, 0
func (gc *GraphicContext) FillString(text string) (cursor float64) {
return gc.FillStringAt(text, 0, 0)
}
// FillStringAt draws a string at x, y
func (gc *GraphicContext) FillStringAt(text string, x, y float64) (cursor float64) {
return gc.CreateStringPath(text, x, y)
}
// StrokeString draws a string at 0, 0 (stroking is unsupported,
// string will be filled)
func (gc *GraphicContext) StrokeString(text string) (cursor float64) {
return gc.StrokeStringAt(text, 0, 0)
}
// StrokeStringAt draws a string at x, y (stroking is unsupported,
// string will be filled)
func (gc *GraphicContext) StrokeStringAt(text string, x, y float64) (cursor float64) {
return gc.CreateStringPath(text, x, y)
}
// Stroke strokes the paths with the color specified by SetStrokeColor
func (gc *GraphicContext) Stroke(paths ...*draw2d.Path) {
_, _, _, alphaS := gc.Current.StrokeColor.RGBA()
gc.draw("D", alphaS, paths...)
gc.Current.Path.Clear()
}
// Fill fills the paths with the color specified by SetFillColor
func (gc *GraphicContext) Fill(paths ...*draw2d.Path) {
style := "F"
if gc.Current.FillRule != draw2d.FillRuleWinding {
style += "*"
}
_, _, _, alphaF := gc.Current.FillColor.RGBA()
gc.draw(style, alphaF, paths...)
gc.Current.Path.Clear()
}
// FillStroke first fills the paths and than strokes them
func (gc *GraphicContext) FillStroke(paths ...*draw2d.Path) {
var rule string
if gc.Current.FillRule != draw2d.FillRuleWinding {
rule = "*"
}
_, _, _, alphaS := gc.Current.StrokeColor.RGBA()
_, _, _, alphaF := gc.Current.FillColor.RGBA()
if alphaS == alphaF {
gc.draw("FD"+rule, alphaF, paths...)
} else {
gc.draw("F"+rule, alphaF, paths...)
gc.draw("S", alphaS, paths...)
}
gc.Current.Path.Clear()
}
var logger = log.New(os.Stdout, "", log.Lshortfile)
const alphaMax = float64(0xFFFF)
// draw fills and/or strokes paths
func (gc *GraphicContext) draw(style string, alpha uint32, paths ...*draw2d.Path) {
paths = append(paths, gc.Current.Path)
for _, p := range paths {
ConvertPath(p, gc.pdf)
}
a := float64(alpha) / alphaMax
current, blendMode := gc.pdf.GetAlpha()
if a != current {
gc.pdf.SetAlpha(a, blendMode)
}
gc.pdf.DrawPath(style)
}
// overwrite StackGraphicContext methods
// SetStrokeColor sets the stroke color
func (gc *GraphicContext) SetStrokeColor(c color.Color) {
gc.StackGraphicContext.SetStrokeColor(c)
gc.pdf.SetDrawColor(rgb(c))
}
// SetFillColor sets the fill and text color
func (gc *GraphicContext) SetFillColor(c color.Color) {
gc.StackGraphicContext.SetFillColor(c)
gc.pdf.SetFillColor(rgb(c))
gc.pdf.SetTextColor(rgb(c))
}
// SetFont is unsupported by the pdf graphic context, use SetFontData
// instead.
func (gc *GraphicContext) SetFont(font *truetype.Font) {
// TODO: what to do with this api conflict between draw2d and gofpdf?!
}
// SetFontData sets the current font used to draw text. Always use
// this method, as SetFont is unsupported by the pdf graphic context.
// It is mandatory to call this method at least once before printing
// text or the resulting document will not be valid.
// It is necessary to generate a font definition file first with the
// makefont utility. It is not necessary to call this function for the
// core PDF fonts (courier, helvetica, times, zapfdingbats).
// go get github.com/jung-kurt/gofpdf/makefont
// http://godoc.org/github.com/jung-kurt/gofpdf#Fpdf.AddFont
func (gc *GraphicContext) SetFontData(fontData draw2d.FontData) {
// TODO: call Makefont embed if json file does not exist yet
gc.StackGraphicContext.SetFontData(fontData)
var style string
if fontData.Style&draw2d.FontStyleBold != 0 {
style += "B"
}
if fontData.Style&draw2d.FontStyleItalic != 0 {
style += "I"
}
fn := draw2d.FontFileName(fontData)
fn = fn[:len(fn)-4]
size, _ := gc.pdf.GetFontSize()
gc.pdf.AddFont(fontData.Name, style, fn+".json")
gc.pdf.SetFont(fontData.Name, style, size)
}
// SetFontSize sets the font size in points (as in ``a 12 point font'').
// TODO: resolve this with ImgGraphicContext (now done with gc.Current.Scale)
func (gc *GraphicContext) SetFontSize(fontSize float64) {
gc.StackGraphicContext.SetFontSize(fontSize)
gc.recalc()
gc.pdf.SetFontSize(fontSize * gc.Current.Scale)
}
// SetLineDash sets the line dash pattern
func (gc *GraphicContext) SetLineDash(Dash []float64, DashOffset float64) {
gc.StackGraphicContext.SetLineDash(Dash, DashOffset)
gc.pdf.SetDashPattern(Dash, DashOffset)
}
// SetLineWidth sets the line width
func (gc *GraphicContext) SetLineWidth(LineWidth float64) {
gc.StackGraphicContext.SetLineWidth(LineWidth)
gc.pdf.SetLineWidth(LineWidth)
}
// SetLineCap sets the line cap (round, but or square)
func (gc *GraphicContext) SetLineCap(Cap draw2d.LineCap) {
gc.StackGraphicContext.SetLineCap(Cap)
gc.pdf.SetLineCapStyle(caps[Cap])
}
// SetLineJoin sets the line cap (round, bevel or miter)
func (gc *GraphicContext) SetLineJoin(Join draw2d.LineJoin) {
gc.StackGraphicContext.SetLineJoin(Join)
gc.pdf.SetLineJoinStyle(joins[Join])
}
// Transformations
// Scale generally scales the following text, drawings and images.
// sx and sy are the scaling factors for width and height.
// This must be placed between gc.Save() and gc.Restore(), otherwise
// the pdf is invalid.
func (gc *GraphicContext) Scale(sx, sy float64) {
gc.StackGraphicContext.Scale(sx, sy)
gc.pdf.TransformScale(sx*100, sy*100, 0, 0)
}
// Rotate rotates the following text, drawings and images.
// Angle is specified in radians and measured clockwise from the
// 3 o'clock position.
// This must be placed between gc.Save() and gc.Restore(), otherwise
// the pdf is invalid.
func (gc *GraphicContext) Rotate(angle float64) {
gc.StackGraphicContext.Rotate(angle)
gc.pdf.TransformRotate(-angle*180/math.Pi, 0, 0)
}
// Translate moves the following text, drawings and images
// horizontally and vertically by the amounts specified by tx and ty.
// This must be placed between gc.Save() and gc.Restore(), otherwise
// the pdf is invalid.
func (gc *GraphicContext) Translate(tx, ty float64) {
gc.StackGraphicContext.Translate(tx, ty)
gc.pdf.TransformTranslate(tx, ty)
}
// Save saves the current context stack
// (transformation, font, color,...).
func (gc *GraphicContext) Save() {
gc.StackGraphicContext.Save()
gc.pdf.TransformBegin()
}
// Restore restores the current context stack
// (transformation, color,...). Restoring the font is not supported.
func (gc *GraphicContext) Restore() {
gc.pdf.TransformEnd()
gc.StackGraphicContext.Restore()
c := gc.Current
gc.SetFontSize(c.FontSize)
// gc.SetFontData(c.FontData) unsupported, causes bug (do not enable)
gc.SetLineWidth(c.LineWidth)
gc.SetStrokeColor(c.StrokeColor)
gc.SetFillColor(c.FillColor)
gc.SetFillRule(c.FillRule)
// gc.SetLineDash(c.Dash, c.DashOffset) // TODO
gc.SetLineCap(c.Cap)
gc.SetLineJoin(c.Join)
// c.Path unsupported
// c.Font unsupported
}

View File

@ -0,0 +1,44 @@
// Copyright 2015 The draw2d Authors. All rights reserved.
// created: 26/06/2015 by Stani Michiels
package draw2dpdf
import (
"math"
"github.com/llgcode/draw2d"
)
const deg = 180 / math.Pi
// ConvertPath converts a paths to the pdf api
func ConvertPath(path *draw2d.Path, pdf Vectorizer) {
var startX, startY float64 = 0, 0
i := 0
for _, cmp := range path.Components {
switch cmp {
case draw2d.MoveToCmp:
startX, startY = path.Points[i], path.Points[i+1]
pdf.MoveTo(startX, startY)
i += 2
case draw2d.LineToCmp:
pdf.LineTo(path.Points[i], path.Points[i+1])
i += 2
case draw2d.QuadCurveToCmp:
pdf.CurveTo(path.Points[i], path.Points[i+1], path.Points[i+2], path.Points[i+3])
i += 4
case draw2d.CubicCurveToCmp:
pdf.CurveBezierCubicTo(path.Points[i], path.Points[i+1], path.Points[i+2], path.Points[i+3], path.Points[i+4], path.Points[i+5])
i += 6
case draw2d.ArcToCmp:
pdf.ArcTo(path.Points[i], path.Points[i+1], path.Points[i+2], path.Points[i+3],
0, // degRotate
path.Points[i+4]*deg, // degStart = startAngle
(path.Points[i+4]-path.Points[i+5])*deg) // degEnd = startAngle-angle
i += 6
case draw2d.CloseCmp:
pdf.LineTo(startX, startY)
pdf.ClosePath()
}
}
}

65
draw2dpdf/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 draw2dpdf_test
import (
"testing"
"github.com/llgcode/draw2d"
"github.com/llgcode/draw2d/samples/android"
"github.com/llgcode/draw2d/samples/frameimage"
"github.com/llgcode/draw2d/samples/geometry"
"github.com/llgcode/draw2d/samples/gopher"
"github.com/llgcode/draw2d/samples/gopher2"
"github.com/llgcode/draw2d/samples/helloworld"
"github.com/llgcode/draw2d/samples/line"
"github.com/llgcode/draw2d/samples/linecapjoin"
"github.com/llgcode/draw2d/samples/postscript"
)
func TestSampleAndroid(t *testing.T) {
test(t, android.Main)
}
// TODO: FillString: w (width) is incorrect
func TestSampleGeometry(t *testing.T) {
// Set the global folder for searching fonts
// The pdf backend needs for every ttf file its corresponding
// json/.z file which is generated by gofpdf/makefont.
draw2d.SetFontFolder("../resource/font")
test(t, geometry.Main)
}
func TestSampleGopher(t *testing.T) {
test(t, gopher.Main)
}
func TestSampleGopher2(t *testing.T) {
test(t, gopher2.Main)
}
func TestSampleHelloWorld(t *testing.T) {
// Set the global folder for searching fonts
// The pdf backend needs for every ttf file its corresponding
// json/.z file which is generated by gofpdf/makefont.
draw2d.SetFontFolder("../resource/font")
test(t, helloworld.Main)
}
func TestSampleFrameImage(t *testing.T) {
test(t, frameimage.Main)
}
func TestSampleLine(t *testing.T) {
test(t, line.Main)
}
func TestSampleLineCap(t *testing.T) {
test(t, linecapjoin.Main)
}
func TestSamplePostscript(t *testing.T) {
test(t, postscript.Main)
}

39
draw2dpdf/test_test.go Normal file
View File

@ -0,0 +1,39 @@
// Copyright 2015 The draw2d Authors. All rights reserved.
// created: 26/06/2015 by Stani Michiels
// Package draw2dpdf_test gives test coverage with the command:
// go test -cover ./... | grep -v "no test"
// (It should be run from its parent draw2d directory.)
package draw2dpdf_test
import (
"testing"
"github.com/llgcode/draw2d"
"github.com/llgcode/draw2d/draw2dpdf"
)
type sample func(gc draw2d.GraphicContext, ext string) (string, error)
func test(t *testing.T, draw sample) {
// Initialize the graphic context on an pdf document
dest := draw2dpdf.NewPdf("L", "mm", "A4")
gc := draw2dpdf.NewGraphicContext(dest)
// Draw sample
output, err := draw(gc, "pdf")
if err != nil {
t.Errorf("Drawing %q failed: %v", output, err)
return
}
/*
// Save to pdf only if it doesn't exist because of git
if _, err = os.Stat(output); err == nil {
t.Skipf("Saving %q skipped, as it exists already. (Git would consider it modified.)", output)
return
}
*/
err = draw2dpdf.SaveToPdfFile(output, dest)
if err != nil {
t.Errorf("Saving %q failed: %v", output, err)
}
}

22
draw2dpdf/vectorizer.go Normal file
View File

@ -0,0 +1,22 @@
// Copyright 2015 The draw2d Authors. All rights reserved.
// created: 26/06/2015 by Stani Michiels
package draw2dpdf
// Vectorizer defines the minimal interface for gofpdf.Fpdf
// to be passed to a PathConvertor.
// It is also implemented by for example VertexMatrixTransform
type Vectorizer interface {
// MoveTo creates a new subpath that start at the specified point
MoveTo(x, y float64)
// LineTo adds a line to the current subpath
LineTo(x, y float64)
// CurveTo adds a quadratic bezier curve to the current subpath
CurveTo(cx, cy, x, y float64)
// CurveTo adds a cubic bezier curve to the current subpath
CurveBezierCubicTo(cx1, cy1, cx2, cy2, x, y float64)
// ArcTo adds an arc to the current subpath
ArcTo(x, y, rx, ry, degRotate, degStart, degEnd float64)
// ClosePath closes the subpath
ClosePath()
}

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,
)
}
}

230
font.go Normal file
View File

@ -0,0 +1,230 @@
// Copyright 2010 The draw2d Authors. All rights reserved.
// created: 13/12/2010 by Laurent Le Goff
package draw2d
import (
"io/ioutil"
"log"
"path/filepath"
"sync"
"git.fromouter.space/crunchy-rocks/freetype/truetype"
)
// FontStyle defines bold and italic styles for the font
// It is possible to combine values for mixed styles, eg.
// FontData.Style = FontStyleBold | FontStyleItalic
type FontStyle byte
const (
FontStyleNormal FontStyle = iota
FontStyleBold
FontStyleItalic
)
type FontFamily byte
const (
FontFamilySans FontFamily = iota
FontFamilySerif
FontFamilyMono
)
type FontData struct {
Name string
Family FontFamily
Style FontStyle
}
type FontFileNamer func(fontData FontData) string
func FontFileName(fontData FontData) string {
fontFileName := fontData.Name
switch fontData.Family {
case FontFamilySans:
fontFileName += "s"
case FontFamilySerif:
fontFileName += "r"
case FontFamilyMono:
fontFileName += "m"
}
if fontData.Style&FontStyleBold != 0 {
fontFileName += "b"
} else {
fontFileName += "r"
}
if fontData.Style&FontStyleItalic != 0 {
fontFileName += "i"
}
fontFileName += ".ttf"
return fontFileName
}
func RegisterFont(fontData FontData, font *truetype.Font) {
fontCache.Store(fontData, font)
}
func GetFont(fontData FontData) (font *truetype.Font) {
var err error
if font, err = fontCache.Load(fontData); err != nil {
log.Println(err)
}
return
}
func GetFontFolder() string {
return defaultFonts.folder
}
func SetFontFolder(folder string) {
defaultFonts.setFolder(filepath.Clean(folder))
}
func GetGlobalFontCache() FontCache {
return fontCache
}
func SetFontNamer(fn FontFileNamer) {
defaultFonts.setNamer(fn)
}
// Types implementing this interface can be passed to SetFontCache to change the
// way fonts are being stored and retrieved.
type FontCache interface {
// Loads a truetype font represented by the FontData object passed as
// argument.
// The method returns an error if the font could not be loaded, either
// because it didn't exist or the resource it was loaded from was corrupted.
Load(FontData) (*truetype.Font, error)
// Sets the truetype font that will be returned by Load when given the font
// data passed as first argument.
Store(FontData, *truetype.Font)
}
// Changes the font cache backend used by the package. After calling this
// functionSetFontFolder and SetFontNamer will not affect anymore how fonts are
// loaded.
// To restore the default font cache, call this function passing nil as argument.
func SetFontCache(cache FontCache) {
if cache == nil {
fontCache = defaultFonts
} else {
fontCache = cache
}
}
// FolderFontCache can Load font from folder
type FolderFontCache struct {
fonts map[string]*truetype.Font
folder string
namer FontFileNamer
}
// NewFolderFontCache creates FolderFontCache
func NewFolderFontCache(folder string) *FolderFontCache {
return &FolderFontCache{
fonts: make(map[string]*truetype.Font),
folder: folder,
namer: FontFileName,
}
}
// Load a font from cache if exists otherwise it will load the font from file
func (cache *FolderFontCache) Load(fontData FontData) (font *truetype.Font, err error) {
if font = cache.fonts[cache.namer(fontData)]; font != nil {
return font, nil
}
var data []byte
var file = cache.namer(fontData)
if data, err = ioutil.ReadFile(filepath.Join(cache.folder, file)); err != nil {
return
}
if font, err = truetype.Parse(data); err != nil {
return
}
cache.fonts[file] = font
return
}
// Store a font to this cache
func (cache *FolderFontCache) Store(fontData FontData, font *truetype.Font) {
cache.fonts[cache.namer(fontData)] = font
}
// SyncFolderFontCache can Load font from folder
type SyncFolderFontCache struct {
sync.RWMutex
fonts map[string]*truetype.Font
folder string
namer FontFileNamer
}
// NewSyncFolderFontCache creates SyncFolderFontCache
func NewSyncFolderFontCache(folder string) *SyncFolderFontCache {
return &SyncFolderFontCache{
fonts: make(map[string]*truetype.Font),
folder: folder,
namer: FontFileName,
}
}
func (cache *SyncFolderFontCache) setFolder(folder string) {
cache.Lock()
cache.folder = folder
cache.Unlock()
}
func (cache *SyncFolderFontCache) setNamer(namer FontFileNamer) {
cache.Lock()
cache.namer = namer
cache.Unlock()
}
// Load a font from cache if exists otherwise it will load the font from file
func (cache *SyncFolderFontCache) Load(fontData FontData) (font *truetype.Font, err error) {
cache.RLock()
font = cache.fonts[cache.namer(fontData)]
cache.RUnlock()
if font != nil {
return font, nil
}
var data []byte
var file = cache.namer(fontData)
if data, err = ioutil.ReadFile(filepath.Join(cache.folder, file)); err != nil {
return
}
if font, err = truetype.Parse(data); err != nil {
return
}
cache.Lock()
cache.fonts[file] = font
cache.Unlock()
return
}
// Store a font to this cache
func (cache *SyncFolderFontCache) Store(fontData FontData, font *truetype.Font) {
cache.Lock()
cache.fonts[cache.namer(fontData)] = font
cache.Unlock()
}
var (
defaultFonts = NewSyncFolderFontCache("../resource/font")
fontCache FontCache = defaultFonts
)

87
gc.go Normal file
View File

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

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
)

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
}

11
output/README.md Normal file
View File

@ -0,0 +1,11 @@
Demo output
===========
These folders are empty when you check out the git repository. The output is generated by the tests:
```
go test ./...
```
or with coverage:
```
go test -cover ./... | grep -v "no test"
```

4
output/curve/.gitignore vendored Normal file
View File

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

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