Compare commits

...

194 commits

Author SHA1 Message Date
fb1b1c09ec Fix module naming 2025-04-10 14:12:26 +02:00
560fc9e11f Remove everything that might depend on cgo 2025-04-10 14:05:55 +02:00
Thomas Friedel
364c8706b6 rgba() color parsing now uses a float in range 0.0-1.0 for the alpha component 2020-12-01 16:17:29 +01:00
Thomas Friedel
0c528ecc87 updated dependencies 2020-08-12 15:26:19 +02:00
Thomas Friedel
631290f5f2 regenerated xmobile backend 2020-06-02 11:06:57 +02:00
Thomas Friedel
6586c5c7d1 fixed a bug on macos, nothing got rendered with disabled vertex attrib arrays 2020-06-02 11:06:21 +02:00
Thomas Friedel
5eb5dc34e1 switch to vector text rendering in more cases
fixed text rendering test case
2020-05-28 11:12:55 +02:00
Thomas Friedel
f36e11bdff directly replace image when calling Replace if it is an image 2020-05-10 17:48:18 +02:00
Thomas Friedel
3a0ca2cdcd bugfix in ClosePath 2020-05-09 10:18:56 +02:00
Thomas Friedel
e37f4f5565 added sorting of font contours for correct triangulation 2020-04-04 18:34:53 +02:00
Thomas Friedel
c4d3130770 converted earcut.hpp library to Go and used it to triangulate fonts 2020-03-28 16:08:58 +01:00
Thomas Friedel
1d5a02b1d6 bugfix in rune path 2020-03-28 12:08:33 +01:00
Thomas Friedel
1711693a57 fixed text stroke scaling 2020-03-27 21:39:19 +01:00
Thomas Friedel
ef4c2c3191 fixed font scaling bug 2020-03-27 19:37:49 +01:00
Thomas Friedel
a0a1cea270 faster font contour path 2020-03-24 14:25:51 +01:00
Thomas Friedel
c36395c0c8 fixed stroke text rendering 2020-03-24 11:49:10 +01:00
Thomas Friedel
30531aaab7 new way to fill text (work in progress) 2020-03-23 12:42:04 +01:00
Thomas Friedel
39e9e6400b round coordinates when filling with image mask to improve text rendering 2020-03-23 11:26:48 +01:00
Thomas Friedel
34087abece Path2D now caches the triangulation for filling 2020-03-22 11:52:35 +01:00
Thomas Friedel
804a9c2774 replaced [2]float64 with Vec type 2020-03-22 10:15:42 +01:00
Thomas Friedel
cc9247c627 moved math code to backendbase package so that backends can also use it 2020-03-22 10:07:18 +01:00
Thomas Friedel
066f4f55bb kerning in StrokeText 2020-03-22 09:50:32 +01:00
Thomas Friedel
d3cc20f911 much improved text scaling 2020-03-21 18:55:25 +01:00
Thomas Friedel
978852494a removed dead code
enabled font hinting
2020-03-21 16:49:32 +01:00
Thomas Friedel
59ddfe59c1 more efficient alpha texture loading 2020-03-21 16:35:22 +01:00
Thomas Friedel
7830bb2cc5 removed the alpha tex clear 2020-03-21 16:19:35 +01:00
Thomas Friedel
1b94cf0703 added comments 2020-03-21 15:51:19 +01:00
Thomas Friedel
b39fdd0a48 added panic when an image is loaded with a different canvas 2020-03-21 13:20:54 +01:00
Thomas Friedel
f47d24543d removed explicit deleting of gradients
removed finalizers from backends, instead using a finalizer as an emergency catch on the frontend
2020-03-21 13:05:04 +01:00
Thomas Friedel
7faf3cdcc6 improved caching and added font context cache 2020-03-21 12:50:44 +01:00
Thomas Friedel
9d1e5b306a improved cache code 2020-03-21 11:36:27 +01:00
Thomas Friedel
896af05ba4 updated xmobile backend 2020-03-17 15:22:56 +01:00
Thomas Friedel
a0ba7b2ad3 removed some unused variables 2020-03-17 12:54:33 +01:00
Thomas Friedel
d670f964c8 self intersecting polygon bugfix 2020-03-17 10:21:09 +01:00
Thomas Friedel
421d388f91 faster text rendering 2020-03-16 18:29:05 +01:00
Thomas Friedel
84d2b4f3cb code cleanup 2020-03-16 18:28:37 +01:00
Thomas Friedel
3378745af4 fixed method order 2020-02-28 08:54:33 +01:00
Thomas Friedel
ffd40c721e float colors always in range 0.0-1.0 for consistency 2020-02-28 08:53:59 +01:00
Thomas Friedel
82290ace4a removed some dead code; small code improvements 2020-02-20 12:15:15 +01:00
Thomas Friedel
239026dd49 fixed that gradients weren't reloaded after being changed 2020-02-18 17:05:56 +01:00
Thomas Friedel
e3098ff636 images are likely rgba 2020-02-14 16:36:06 +01:00
Thomas Friedel
3e6e46ca0d reverted a previous change as it was not correct 2020-02-14 16:34:38 +01:00
Thomas Friedel
55572c59da added performance setting for image cache size 2020-02-14 16:30:40 +01:00
Thomas Friedel
637509b5d4 minor fixes 2020-02-14 16:28:24 +01:00
Thomas Friedel
7f7efd5a8a the image cache is now limited to 16mb 2020-02-14 16:28:12 +01:00
Thomas Friedel
0d915f7178 fixed and improved shadows 2020-02-12 17:38:33 +01:00
Thomas Friedel
7213b3edcd much better shadow performance 2020-02-12 15:53:46 +01:00
Thomas Friedel
8b79ad18fa fully unified into one shader
updated shadow test
2020-02-11 15:59:28 +01:00
Thomas Friedel
f5e7e6a060 replaced the gaussian shadow shaders with box blur shaders 2020-02-11 15:45:48 +01:00
Thomas Friedel
598ac1e325 simplified the drawBlurred function 2020-02-11 15:20:29 +01:00
Thomas Friedel
b0230892ab added image shader into unified shader 2020-02-11 14:51:24 +01:00
Thomas Friedel
50bf39fe62 unified some of the shaders to simplify 2020-02-11 13:28:34 +01:00
Thomas Friedel
a80e3778fd changed the way window scaling is done
to support high DPI scaling, use the FramebufferSize functions in glfwcanvas and sdlcanvas to determine the canvas size
2020-02-11 11:36:16 +01:00
Thomas Friedel
259bb9e598 fixed Arc and ArcTo scaling 2020-02-11 11:10:53 +01:00
Thomas Friedel
50c77477c9 another triangulation bugfix, points should be considered inside the polygon if they are on a line, with a small amount of tolerance 2020-02-01 16:25:11 +01:00
Thomas Friedel
3f85d64ff3 ignore files starting with _ 2020-02-01 15:51:45 +01:00
Thomas Friedel
1333730731 fixed a triangulation bug 2020-02-01 15:51:21 +01:00
Thomas Friedel
6912878a8c sdlcanvas and glfwcanvas now scale automatically for hidpi
removed glfw 3.2 dependency
2020-01-25 16:56:32 +01:00
Thomas Friedel
04d4dab3ff fixed resize when using macOS hidpi 2020-01-25 16:35:15 +01:00
Thomas Friedel
ecadd0e5ec added support for macOS hidpi when using SDL
updated to go-sdl 0.4
2020-01-25 16:29:27 +01:00
Thomas Friedel
c8ff508299 updated glfw code to fix scaling on macOS 2020-01-25 15:59:54 +01:00
Thomas Friedel
79fde3ee1d
Merge pull request #22 from kanle-hotstar/bugfix/avoid-panic-when-loading-image-cache
Fix a panic issue of loading an image from cache
2019-12-18 16:57:50 +01:00
Kanle Shi
9729daa6d4 Fix a panic issue of loading an image from cache 2019-12-13 14:49:38 +08:00
Thomas Friedel
f0367ee72d GetImageData bugfix 2019-12-04 12:13:09 +01:00
Thomas Friedel
fd1cca7ba9 LoadImage now also checks cache; updated go.mod/go.sum 2019-11-26 12:13:03 +01:00
Thomas Friedel
71741d4234 fixed a bug with anticlockwise arcs and ellipses
updated to new sdl library version
2019-10-01 12:04:18 +02:00
Thomas Friedel
239ab21259 added ellipse function 2019-10-01 11:49:58 +02:00
Thomas Friedel
950d2bb30a added go version to go.mod 2019-10-01 11:27:10 +02:00
Thomas Friedel
58e7c45597 fixed key name 2019-10-01 11:26:50 +02:00
Thomas Friedel
98a8334efc added a todo, regenerated xmobile backend 2019-07-10 11:36:02 +02:00
Thomas Friedel
9f92f2c5c4 added an error if glGenTextures fails 2019-07-10 11:34:21 +02:00
Thomas Friedel
5ce888f8b9 small performance improvements 2019-05-23 16:32:03 +02:00
Thomas Friedel
b5212c916a added an example for software rendering
updated readme
2019-05-13 12:29:25 +02:00
Thomas Friedel
349e01e301 removed the shaders from the main canvas package, which were no longer used 2019-05-13 12:19:56 +02:00
Thomas Friedel
92d388c3c4 tests should run on OpenGL backend 2019-05-13 12:07:22 +02:00
Thomas Friedel
0386303dd6 updated readme 2019-05-13 12:03:55 +02:00
Thomas Friedel
e44b120ad8 removed gaussian blur code 2019-05-13 11:56:53 +02:00
Thomas Friedel
3695a6ef59 improved box blur accuracy 2019-05-13 11:56:36 +02:00
Thomas Friedel
62e4cae716 image mask uses fill style 2019-05-12 13:06:23 +02:00
Thomas Friedel
ea3cb81231 renamed mask to stencil 2019-05-12 13:06:23 +02:00
Thomas Friedel
c945678725 fixed xmobile backend 2019-05-12 13:06:23 +02:00
Thomas Friedel
505386f199 added shadow blur to software implementation 2019-05-12 13:06:23 +02:00
Thomas Friedel
e0b88c0ca6 implemented MSAA on quad drawing 2019-05-12 13:06:23 +02:00
Thomas Friedel
17b212acba implemented msaa 2019-05-12 13:06:23 +02:00
Thomas Friedel
554fa84a0a added code for MSAA, doesn't work properly yet though 2019-05-12 13:06:23 +02:00
Thomas Friedel
d56f68b2e7 implemented software image pattern filling, added a test 2019-05-12 13:06:23 +02:00
Thomas Friedel
4e5c38b295 implemented mipmapping 2019-05-12 13:06:23 +02:00
Thomas Friedel
bbcb712b73 renamed the backend directories so they match the package names 2019-05-12 13:06:23 +02:00
Thomas Friedel
623a4742ce removed debug code 2019-05-12 13:06:23 +02:00
Thomas Friedel
bfbd54827b ignore .vscode dir 2019-05-12 13:06:23 +02:00
Thomas Friedel
a913b8b33b implemented software gradients 2019-05-12 13:06:23 +02:00
Thomas Friedel
e3bb07a09c fixed image drawing, added some interpolation 2019-05-12 13:06:23 +02:00
Thomas Friedel
a0a1adef12 fixed overlapping alpha 2019-05-12 13:06:23 +02:00
Thomas Friedel
9edbb8da85 added a boolean to run tests on the software backend 2019-05-12 13:06:23 +02:00
Thomas Friedel
ec3bde6374 proper color mixing 2019-05-12 13:06:23 +02:00
Thomas Friedel
4d0f41cc6b more precise rasterization 2019-05-12 13:06:23 +02:00
Thomas Friedel
973e410204 more accurate rasterizing 2019-05-12 13:06:23 +02:00
Thomas Friedel
d1d2186a2f added a function to run the tests on the software backend 2019-05-12 13:06:23 +02:00
Thomas Friedel
7fa1f9096f implemented FillImageMask in software backend 2019-05-12 13:06:23 +02:00
Thomas Friedel
faf179caa0 implemented clipping and drawImage 2019-05-12 13:06:23 +02:00
Thomas Friedel
9f99dff89b started a software backend 2019-05-12 13:06:23 +02:00
Thomas Friedel
a4826a3e12 fixed a problem with closed paths and self intersecting polygons
added the readme example as a test
2019-05-12 13:05:49 +02:00
Thomas Friedel
aa059cfcf3 more image pattern fixes 2019-05-02 12:42:23 +02:00
Thomas Friedel
23a92d2221 fixed image pattern transform matrix 2019-05-02 12:23:53 +02:00
Thomas Friedel
9b7322ff16 moved version history to releases 2019-04-30 21:57:50 +02:00
Thomas Friedel
e4dae7a86f updated xmobile backend with latest gogl code 2019-04-30 21:53:14 +02:00
Thomas Friedel
eed8f3e241 removed greyscale image loading, using full rgb conversion instead 2019-04-30 21:53:01 +02:00
Thomas Friedel
3e21561f29 updated readme 2019-04-25 18:00:30 +02:00
Thomas Friedel
f89b3eccdc implemented imapge pattern repeat types 2019-04-25 17:58:21 +02:00
Thomas Friedel
a5d921223f implemented image pattern transformation 2019-04-25 17:46:53 +02:00
Thomas Friedel
3a749132e2 added a shadow test 2019-04-25 17:30:38 +02:00
Thomas Friedel
9103dfb672 updated go.mod 2019-04-24 17:10:39 +02:00
Thomas Friedel
1f6301a2f9 canvas activate bugfix 2019-04-24 17:10:31 +02:00
Thomas Friedel
b2568a0205 updated readme 2019-04-24 12:37:09 +02:00
Thomas Friedel
7d0fc7ceb8 regenerated 2019-04-24 12:35:11 +02:00
Thomas Friedel
0476a8a85f shadow global alpha fix 2019-04-24 12:34:36 +02:00
Thomas Friedel
312633710b updated go-sdl2 version 2019-04-22 15:28:51 +02:00
Thomas Friedel
abfc73419b set global alpha 2019-04-18 21:13:06 +02:00
Thomas Friedel
287ff8905f another transformation bugfix 2019-04-18 16:33:50 +02:00
Thomas Friedel
41a0ed18af updated readme 2019-04-18 14:47:45 +02:00
Thomas Friedel
6d46d9d554 removed the clipping reapplication after context switching since it didn't work properly 2019-04-18 14:46:20 +02:00
Thomas Friedel
ff4c73b6b7 fixed a transformation issue 2019-04-17 16:11:25 +02:00
Thomas Friedel
857e6c4e47 IsPointInStroke implemented 2019-04-17 11:31:53 +02:00
Thomas Friedel
c9fcfbdd53 clipping bugfix 2019-04-16 16:15:59 +02:00
Thomas Friedel
70ece785e9 implemented self intersecting polygon support 2019-04-16 14:18:51 +02:00
Thomas Friedel
7c4521b227 added a function to check if a point is in a path 2019-04-10 11:59:26 +02:00
Thomas Friedel
e40654181e fixed build tags for macos and ios 2019-04-02 11:29:18 +02:00
Thomas Friedel
2163c982fd updated readme 2019-03-30 11:40:06 +01:00
Thomas Friedel
b7397ee4dc fixed stroke scaling 2019-03-30 11:35:32 +01:00
Thomas Friedel
b2502f5472 simplified matrix type 2019-03-30 11:08:49 +01:00
Thomas Friedel
0e16a8261b added function to create an image pattern 2019-03-28 11:25:59 +01:00
Thomas Friedel
5acbe5452b updated missing features 2019-03-28 10:55:23 +01:00
Thomas Friedel
6a082a74a3 clipping bugfix 2019-03-28 10:53:49 +01:00
Thomas Friedel
3590b73c8b udpated readme 2019-03-20 12:29:43 +01:00
Thomas Friedel
04b3418c3b added an activate function and an exported TextureID so that other GL code can also use the canvas 2019-03-20 12:18:36 +01:00
Thomas Friedel
c98bc6c23d backend can now reuse gl contexts, updated examples and docs 2019-03-20 11:48:52 +01:00
Thomas Friedel
22c3c25d8e removed opaque and deleted flags from backend, deleted state is now maintained in frontend 2019-02-27 16:06:41 +01:00
Thomas Friedel
ba8238ba66 canvas bounds update fix, gomobile example works again 2019-02-27 15:46:08 +01:00
Thomas Friedel
9c3cccabdd offscreen backend now has its own type, can be used as image when drawing images 2019-02-27 13:05:23 +01:00
Thomas Friedel
122488e64c updated glfwcanvas to use gl backend, fixed error handling 2019-02-24 16:34:42 +01:00
Thomas Friedel
2717a0bdd9 removed openglinterface implementations 2019-02-24 16:34:16 +01:00
Thomas Friedel
f8aa4d62ce updated gogl backend to work on ios as well, updated ios example 2019-02-24 16:30:17 +01:00
Thomas Friedel
654d9b1796 reduced the backend/gogl/gl package to GLES2 functions; added android support; adapted android example 2019-02-24 14:31:03 +01:00
Thomas Friedel
549a6f2c2e switched to a simplified generated gl binding that should work with both regular GL and GLES 2019-02-24 13:28:50 +01:00
Thomas Friedel
222c12142a updated tests 2019-02-24 13:25:29 +01:00
Thomas Friedel
77a9d14867 added an xmobile backend that can be generated from the gogl backend 2019-02-24 13:19:21 +01:00
Thomas Friedel
93c75a9b61 clear stencil when bounds change 2019-02-24 11:08:22 +01:00
Thomas Friedel
041cf94c5c updated glfw and sdl examples 2019-02-24 11:07:07 +01:00
Thomas Friedel
1f682f1f31 updated events example 2019-02-24 11:02:16 +01:00
Thomas Friedel
c207447158 SetBounds on GL backend now sets the viewport right away; updated drawing example 2019-02-24 11:01:33 +01:00
Thomas Friedel
a36c635367 renamed the gradient creation methods; removed SetLineEnd function 2019-02-22 16:56:27 +01:00
Thomas Friedel
ed34e3d8c4 removed matrix code in backend 2019-02-22 16:54:37 +01:00
Thomas Friedel
816582dbb3 removed FillMatrix and moved transformations to frontend 2019-02-22 16:53:53 +01:00
Thomas Friedel
3e874d544b updated tests, copied improved gradients from master branch 2019-02-22 16:44:34 +01:00
Thomas Friedel
63b71e5e52 fixed image drawing transformation 2019-02-22 16:39:30 +01:00
Thomas Friedel
97dff96ca2 removed unused shader variables 2019-02-22 16:39:30 +01:00
Thomas Friedel
f962da34c4 removed openglinterface 2019-02-22 16:39:30 +01:00
Thomas Friedel
8dc91b34f5 moved offscreen canvas to backend 2019-02-22 16:39:30 +01:00
Thomas Friedel
a0dfb6f530 shadows.go wasn't used 2019-02-22 16:39:30 +01:00
Thomas Friedel
fe9d6e60e4 moved clipping to backend 2019-02-22 16:39:30 +01:00
Thomas Friedel
cf788d55f3 unified Clear and ClearRect 2019-02-22 16:39:30 +01:00
Thomas Friedel
e17505b056 removed gl code that is no longer needed in frontend 2019-02-22 16:39:30 +01:00
Thomas Friedel
fc4d3dbd9e moved image data code into backend 2019-02-22 16:39:30 +01:00
Thomas Friedel
e7d0b1a7ee text shadow rendering moved to backend 2019-02-22 16:39:30 +01:00
Thomas Friedel
43f040f777 moved text display to backend 2019-02-22 16:39:30 +01:00
Thomas Friedel
ac9a395c05 removed some dead code 2019-02-22 16:39:30 +01:00
Thomas Friedel
c5c13de2a4 moved gradients to backend 2019-02-22 16:39:30 +01:00
Thomas Friedel
a354e192b6 image pattern support in backend 2019-02-22 16:39:18 +01:00
Thomas Friedel
476dbac6e2 moved image loading and drawing into backend 2019-02-22 16:39:18 +01:00
Thomas Friedel
7cef867541 moved backend interface to canvas.go 2019-02-22 16:38:27 +01:00
Thomas Friedel
c28c50a22e added a blurred fill style in backend, used it for blurred shadow drawing 2019-02-22 16:38:27 +01:00
Thomas Friedel
c5c8ac500f some shadow drawing now works using the backend 2019-02-22 16:38:27 +01:00
Thomas Friedel
38eddd2837 moved a lot of fill code to the backend; also started moving shadow drawing code to backend 2019-02-22 16:38:27 +01:00
Thomas Friedel
359b52f473 more general backend fill function 2019-02-22 16:38:27 +01:00
Thomas Friedel
114b292be7 moved some more gl code into the backend 2019-02-22 16:38:27 +01:00
Thomas Friedel
7dbcd9b420 made DrawStyle public for use by backends; changed color to be color.RGBA 2019-02-22 16:38:27 +01:00
Thomas Friedel
1e5180dd65 started work on moving all the gl code into a backend package so that other backends can be created 2019-02-22 16:38:27 +01:00
Thomas Friedel
6091afb755 added some tests; improved gradient and image pattern transformations 2019-02-22 16:37:48 +01:00
Thomas Friedel
65ebe6af69 arc transformation fix 2019-02-21 11:03:38 +01:00
Thomas Friedel
57348acc02 added an optional alpha channel for offscreen canvas, and a DeleteOffscreen function 2019-02-18 11:29:12 +01:00
Thomas Friedel
7aeae444a9 fixed a rect clipping bug 2019-01-26 23:02:02 +01:00
Thomas Friedel
c1f855278d undid some of the changes from the last update since it didn't work properly; added some tests 2019-01-25 13:16:48 +01:00
Thomas Friedel
c5d148331b proper scaling of stroked paths 2019-01-24 16:14:05 +01:00
Thomas Friedel
f77841e973 added StrokePath and FillPath functions 2019-01-24 15:56:47 +01:00
Thomas Friedel
7ce0b7bf9d line dash now gets properly scaled 2019-01-24 10:50:39 +01:00
Thomas Friedel
99b9763f00 added some nonstandard performance flags 2019-01-23 18:11:41 +01:00
Thomas Friedel
bb244c4868 separated out a Path2D type 2019-01-23 17:23:47 +01:00
Thomas Friedel
7565296c42 removed the SetSize method 2019-01-16 10:31:45 +01:00
Thomas Friedel
f62e309d77 updated readme 2019-01-11 11:03:53 +01:00
Thomas Friedel
3800d8519c fixed a bug with self intersecting polygons, added a test 2019-01-11 11:01:12 +01:00
Thomas Friedel
908e88d374 added textBaseline 2019-01-11 10:40:41 +01:00
Thomas Friedel
ef97c9be5c fixed a bug with transformations applying to dashed lines 2018-11-26 10:25:42 +01:00
Thomas Friedel
deaa8f87b6 added flags to track if the path is convex 2018-11-13 13:08:41 +01:00
Thomas Friedel
cb3e694671 changed path to a struct 2018-11-13 12:28:22 +01:00
110 changed files with 4933 additions and 9352 deletions

4
.gitignore vendored
View file

@ -2,6 +2,8 @@ test*.html
plan.txt
glowgen
testapp
testblur
videocap
.DS_Store
.vscode
_*

View file

@ -1,21 +1,29 @@
[GoDoc is available here](https://godoc.org/github.com/tfriedel6/canvas)
# Go canvas [![GoDoc](https://godoc.org/github.com/tfriedel6/canvas?status.svg)](https://godoc.org/github.com/tfriedel6/canvas)
# Go canvas
Canvas is a pure Go library that provides drawing functionality as similar as possible to the HTML5 canvas API. It has nothing to do with HTML or Javascript, the functions are just made to be approximately the same.
Canvas is a Go library based on OpenGL that tries to provide the HTML5 canvas API as closely as possible.
Many of the basic functions are supported, but it is still a work in progress. The library aims to accept a lot of different parameters on each function in a similar way as the Javascript API does.
Most of the functions are supported, but it is still a work in progress. The library aims to accept a lot of different parameters on each function in a similar way as the Javascript API does.
Whereas the Javascript API uses a context that all draw calls go to, here all draw calls are directly on the canvas type. The other difference is that here setters are used instead of properties for things like fonts and line width.
The library is intended to provide decent performance. Obviously it will not be able to rival hand coded OpenGL for a given purpose, but for many purposes it will be enough. It can also be combined with hand coded OpenGL.
## OpenGL backend
# SDL/GLFW convenience packages
The OpenGL backend is intended to provide decent performance. Obviously it will not be able to rival hand coded OpenGL for a given purpose, but for many purposes it will be enough. It can also be combined with hand coded OpenGL.
## Software backend
The software backend can also be used if no OpenGL context is available. It will render into a standard Go RGBA image.
There is experimental MSAA anti-aliasing, but it doesn't fully work properly yet. The best option for anti-aliasing currently is to render to a larger image and then scale it down.
## SDL/GLFW convenience packages
The sdlcanvas and glfwcanvas subpackages provide a very simple way to get started with just a few lines of code. As the names imply they are based on the SDL library and the GLFW library respectively. They create a window for you and give you a canvas to draw with.
# OS support
Both the OpenGL and software backends work on the following operating systems:
- Linux
- Windows
- macOS
@ -99,12 +107,13 @@ These features *should* work just like their HTML5 counterparts, but there are l
- fillText
- measureText
- textAlign
- textBaseline
- fillStyle
- strokeText
- strokeStyle
- linear gradients
- radial gradients
- image patterns
- image patterns with repeat and transform
- lineWidth
- lineEnd (square, butt, round)
- lineJoin (bevel, miter, round)
@ -120,10 +129,12 @@ These features *should* work just like their HTML5 counterparts, but there are l
- shadowColor
- shadowOffset(X/Y)
- shadowBlur
- isPointInPath
- isPointInStroke
- self intersecting polygons
# Missing features
- globalCompositeOperation
- textBaseline
- isPointInPath
- isPointInStroke
- imageSmoothingEnabled
- textBaseline hanging and ideographic (currently work just like top and bottom)

128
backend/backendbase/base.go Normal file
View file

@ -0,0 +1,128 @@
package backendbase
import (
"image"
"image/color"
"math"
)
// Backend is used by the canvas to actually do the final
// drawing. This enables the backend to be implemented by
// various methods (OpenGL, but also other APIs or software)
type Backend interface {
Size() (int, int)
LoadImage(img image.Image) (Image, error)
LoadImagePattern(data ImagePatternData) ImagePattern
LoadLinearGradient(data Gradient) LinearGradient
LoadRadialGradient(data Gradient) RadialGradient
Clear(pts [4]Vec)
Fill(style *FillStyle, pts []Vec, tf Mat, canOverlap bool)
DrawImage(dimg Image, sx, sy, sw, sh float64, pts [4]Vec, alpha float64)
FillImageMask(style *FillStyle, mask *image.Alpha, pts [4]Vec) // pts must have four points
ClearClip()
Clip(pts []Vec)
GetImageData(x, y, w, h int) *image.RGBA
PutImageData(img *image.RGBA, x, y int)
CanUseAsImage(b Backend) bool
AsImage() Image // can return nil if not supported
}
// FillStyle is the color and other details on how to fill
type FillStyle struct {
Color color.RGBA
Blur float64
LinearGradient LinearGradient
RadialGradient RadialGradient
Gradient struct {
X0, Y0 float64
X1, Y1 float64
RadFrom float64
RadTo float64
}
ImagePattern ImagePattern
}
type Gradient []GradientStop
func (g Gradient) ColorAt(pos float64) color.RGBA {
if len(g) == 0 {
return color.RGBA{}
} else if len(g) == 1 {
return g[0].Color
}
beforeIdx, afterIdx := -1, -1
for i, stop := range g {
if stop.Pos > pos {
afterIdx = i
break
}
beforeIdx = i
}
if beforeIdx == -1 {
return g[0].Color
} else if afterIdx == -1 {
return g[len(g)-1].Color
}
before, after := g[beforeIdx], g[afterIdx]
p := (pos - before.Pos) / (after.Pos - before.Pos)
var c [4]float64
c[0] = (float64(after.Color.R)-float64(before.Color.R))*p + float64(before.Color.R)
c[1] = (float64(after.Color.G)-float64(before.Color.G))*p + float64(before.Color.G)
c[2] = (float64(after.Color.B)-float64(before.Color.B))*p + float64(before.Color.B)
c[3] = (float64(after.Color.A)-float64(before.Color.A))*p + float64(before.Color.A)
return color.RGBA{
R: uint8(math.Round(c[0])),
G: uint8(math.Round(c[1])),
B: uint8(math.Round(c[2])),
A: uint8(math.Round(c[3])),
}
}
type GradientStop struct {
Pos float64
Color color.RGBA
}
type LinearGradient interface {
Delete()
Replace(data Gradient)
}
type RadialGradient interface {
Delete()
Replace(data Gradient)
}
type Image interface {
Width() int
Height() int
Size() (w, h int)
Delete()
Replace(src image.Image) error
}
type ImagePatternData struct {
Image Image
Transform [9]float64
Repeat ImagePatternRepeat
}
type ImagePatternRepeat uint8
// Image pattern repeat constants
const (
Repeat ImagePatternRepeat = iota
RepeatX
RepeatY
NoRepeat
)
type ImagePattern interface {
Delete()
Replace(data ImagePatternData)
}

140
backend/backendbase/math.go Normal file
View file

@ -0,0 +1,140 @@
package backendbase
import (
"fmt"
"math"
)
type Vec [2]float64
func (v Vec) String() string {
return fmt.Sprintf("[%f,%f]", v[0], v[1])
}
func (v Vec) Add(v2 Vec) Vec {
return Vec{v[0] + v2[0], v[1] + v2[1]}
}
func (v Vec) Sub(v2 Vec) Vec {
return Vec{v[0] - v2[0], v[1] - v2[1]}
}
func (v Vec) Mul(v2 Vec) Vec {
return Vec{v[0] * v2[0], v[1] * v2[1]}
}
func (v Vec) Mulf(f float64) Vec {
return Vec{v[0] * f, v[1] * f}
}
func (v Vec) MulMat(m Mat) Vec {
return Vec{
m[0]*v[0] + m[2]*v[1] + m[4],
m[1]*v[0] + m[3]*v[1] + m[5]}
}
func (v Vec) MulMat2(m Mat2) Vec {
return Vec{m[0]*v[0] + m[2]*v[1], m[1]*v[0] + m[3]*v[1]}
}
func (v Vec) Div(v2 Vec) Vec {
return Vec{v[0] / v2[0], v[1] / v2[1]}
}
func (v Vec) Divf(f float64) Vec {
return Vec{v[0] / f, v[1] / f}
}
func (v Vec) Dot(v2 Vec) float64 {
return v[0]*v2[0] + v[1]*v2[1]
}
func (v Vec) Len() float64 {
return math.Sqrt(v[0]*v[0] + v[1]*v[1])
}
func (v Vec) LenSqr() float64 {
return v[0]*v[0] + v[1]*v[1]
}
func (v Vec) Norm() Vec {
return v.Mulf(1.0 / v.Len())
}
func (v Vec) Atan2() float64 {
return math.Atan2(v[1], v[0])
}
func (v Vec) Angle() float64 {
return math.Pi*0.5 - math.Atan2(v[1], v[0])
}
func (v Vec) AngleTo(v2 Vec) float64 {
return math.Acos(v.Norm().Dot(v2.Norm()))
}
type Mat [6]float64
func (m *Mat) String() string {
return fmt.Sprintf("[%f,%f,0,\n %f,%f,0,\n %f,%f,1,]", m[0], m[2], m[4], m[1], m[3], m[5])
}
var MatIdentity = Mat{
1, 0,
0, 1,
0, 0}
func MatTranslate(v Vec) Mat {
return Mat{
1, 0,
0, 1,
v[0], v[1]}
}
func MatScale(v Vec) Mat {
return Mat{
v[0], 0,
0, v[1],
0, 0}
}
func MatRotate(radians float64) Mat {
s, c := math.Sincos(radians)
return Mat{
c, s,
-s, c,
0, 0}
}
func (m Mat) Mul(m2 Mat) Mat {
return Mat{
m[0]*m2[0] + m[1]*m2[2],
m[0]*m2[1] + m[1]*m2[3],
m[2]*m2[0] + m[3]*m2[2],
m[2]*m2[1] + m[3]*m2[3],
m[4]*m2[0] + m[5]*m2[2] + m2[4],
m[4]*m2[1] + m[5]*m2[3] + m2[5]}
}
func (m Mat) Invert() Mat {
identity := 1.0 / (m[0]*m[3] - m[2]*m[1])
return Mat{
m[3] * identity,
-m[1] * identity,
-m[2] * identity,
m[0] * identity,
(m[2]*m[5] - m[3]*m[4]) * identity,
(m[1]*m[4] - m[0]*m[5]) * identity,
}
}
type Mat2 [4]float64
func (m Mat) Mat2() Mat2 {
return Mat2{m[0], m[1], m[2], m[3]}
}
func (m *Mat2) String() string {
return fmt.Sprintf("[%f,%f,\n %f,%f]", m[0], m[2], m[1], m[3])
}

View file

@ -0,0 +1,199 @@
package softwarebackend
import (
"image"
"image/color"
"image/draw"
"math"
)
func (b *SoftwareBackend) activateBlurTarget() {
b.blurSwap = b.Image
b.Image = image.NewRGBA(b.Image.Rect)
}
func (b *SoftwareBackend) drawBlurred(size float64) {
blurred := box3(b.Image, size)
b.Image = b.blurSwap
draw.Draw(b.Image, b.Image.Rect, blurred, image.ZP, draw.Over)
}
func box3(img *image.RGBA, size float64) *image.RGBA {
size *= 1 - 1/(size+1) // this just seems to improve the accuracy
fsize := math.Floor(size)
sizea := int(fsize)
sizeb := sizea
sizec := sizea
if size-fsize > 0.333333333 {
sizeb++
}
if size-fsize > 0.666666666 {
sizec++
}
img = box3x(img, sizea)
img = box3x(img, sizeb)
img = box3x(img, sizec)
img = box3y(img, sizea)
img = box3y(img, sizeb)
img = box3y(img, sizec)
return img
}
func box3x(img *image.RGBA, size int) *image.RGBA {
bounds := img.Bounds()
result := image.NewRGBA(bounds)
w, h := bounds.Dx(), bounds.Dy()
for y := 0; y < h; y++ {
if size >= w {
var r, g, b, a float64
for x := 0; x < w; x++ {
col := img.RGBAAt(x, y)
r += float64(col.R)
g += float64(col.G)
b += float64(col.B)
a += float64(col.A)
}
factor := 1.0 / float64(w)
col := color.RGBA{
R: uint8(math.Round(r * factor)),
G: uint8(math.Round(g * factor)),
B: uint8(math.Round(b * factor)),
A: uint8(math.Round(a * factor)),
}
for x := 0; x < w; x++ {
result.SetRGBA(x, y, col)
}
continue
}
var r, g, b, a float64
for x := 0; x <= size; x++ {
col := img.RGBAAt(x, y)
r += float64(col.R)
g += float64(col.G)
b += float64(col.B)
a += float64(col.A)
}
samples := size + 1
x := 0
for {
factor := 1.0 / float64(samples)
col := color.RGBA{
R: uint8(math.Round(r * factor)),
G: uint8(math.Round(g * factor)),
B: uint8(math.Round(b * factor)),
A: uint8(math.Round(a * factor)),
}
result.SetRGBA(x, y, col)
if x >= w-1 {
break
}
if left := x - size; left >= 0 {
col = img.RGBAAt(left, y)
r -= float64(col.R)
g -= float64(col.G)
b -= float64(col.B)
a -= float64(col.A)
samples--
}
x++
if right := x + size; right < w {
col = img.RGBAAt(right, y)
r += float64(col.R)
g += float64(col.G)
b += float64(col.B)
a += float64(col.A)
samples++
}
}
}
return result
}
func box3y(img *image.RGBA, size int) *image.RGBA {
bounds := img.Bounds()
result := image.NewRGBA(bounds)
w, h := bounds.Dx(), bounds.Dy()
for x := 0; x < w; x++ {
if size >= h {
var r, g, b, a float64
for y := 0; y < h; y++ {
col := img.RGBAAt(x, y)
r += float64(col.R)
g += float64(col.G)
b += float64(col.B)
a += float64(col.A)
}
factor := 1.0 / float64(h)
col := color.RGBA{
R: uint8(math.Round(r * factor)),
G: uint8(math.Round(g * factor)),
B: uint8(math.Round(b * factor)),
A: uint8(math.Round(a * factor)),
}
for y := 0; y < h; y++ {
result.SetRGBA(x, y, col)
}
continue
}
var r, g, b, a float64
for y := 0; y <= size; y++ {
col := img.RGBAAt(x, y)
r += float64(col.R)
g += float64(col.G)
b += float64(col.B)
a += float64(col.A)
}
samples := size + 1
y := 0
for {
factor := 1.0 / float64(samples)
col := color.RGBA{
R: uint8(math.Round(r * factor)),
G: uint8(math.Round(g * factor)),
B: uint8(math.Round(b * factor)),
A: uint8(math.Round(a * factor)),
}
result.SetRGBA(x, y, col)
if y >= h-1 {
break
}
if top := y - size; top >= 0 {
col = img.RGBAAt(x, top)
r -= float64(col.R)
g -= float64(col.G)
b -= float64(col.B)
a -= float64(col.A)
samples--
}
y++
if bottom := y + size; bottom < h {
col = img.RGBAAt(x, bottom)
r += float64(col.R)
g += float64(col.G)
b += float64(col.B)
a += float64(col.A)
samples++
}
}
}
return result
}

View file

@ -0,0 +1,82 @@
package softwarebackend
import (
"image/color"
"math"
)
func toRGBA(src color.Color) color.RGBA {
ir, ig, ib, ia := src.RGBA()
return color.RGBA{
R: uint8(ir >> 8),
G: uint8(ig >> 8),
B: uint8(ib >> 8),
A: uint8(ia >> 8),
}
}
func mix(src, dest color.Color) color.RGBA {
ir1, ig1, ib1, ia1 := src.RGBA()
r1 := float64(ir1) / 65535.0
g1 := float64(ig1) / 65535.0
b1 := float64(ib1) / 65535.0
a1 := float64(ia1) / 65535.0
ir2, ig2, ib2, ia2 := dest.RGBA()
r2 := float64(ir2) / 65535.0
g2 := float64(ig2) / 65535.0
b2 := float64(ib2) / 65535.0
a2 := float64(ia2) / 65535.0
r := (r1-r2)*a1 + r2
g := (g1-g2)*a1 + g2
b := (b1-b2)*a1 + b2
a := math.Max((a1-a2)*a1+a2, a2)
return color.RGBA{
R: uint8(math.Round(r * 255.0)),
G: uint8(math.Round(g * 255.0)),
B: uint8(math.Round(b * 255.0)),
A: uint8(math.Round(a * 255.0)),
}
}
func alphaColor(col color.Color, alpha color.Alpha) color.RGBA {
ir, ig, ib, _ := col.RGBA()
a2 := float64(alpha.A) / 255.0
r := float64(ir) * a2 / 65535.0
g := float64(ig) * a2 / 65535.0
b := float64(ib) * a2 / 65535.0
return color.RGBA{
R: uint8(r * 255.0),
G: uint8(g * 255.0),
B: uint8(b * 255.0),
A: 255,
}
}
func lerp(col1, col2 color.Color, ratio float64) color.RGBA {
ir1, ig1, ib1, ia1 := col1.RGBA()
r1 := float64(ir1) / 65535.0
g1 := float64(ig1) / 65535.0
b1 := float64(ib1) / 65535.0
a1 := float64(ia1) / 65535.0
ir2, ig2, ib2, ia2 := col2.RGBA()
r2 := float64(ir2) / 65535.0
g2 := float64(ig2) / 65535.0
b2 := float64(ib2) / 65535.0
a2 := float64(ia2) / 65535.0
r := (r1-r2)*ratio + r2
g := (g1-g2)*ratio + g2
b := (b1-b2)*ratio + b2
a := (a1-a2)*ratio + a2
return color.RGBA{
R: uint8(math.Round(r * 255.0)),
G: uint8(math.Round(g * 255.0)),
B: uint8(math.Round(b * 255.0)),
A: uint8(math.Round(a * 255.0)),
}
}

View file

@ -0,0 +1,178 @@
package softwarebackend
import (
"image"
"image/color"
"math"
"git.mstar.dev/mstar/canvas/backend/backendbase"
)
func (b *SoftwareBackend) Clear(pts [4]backendbase.Vec) {
iterateTriangles(pts[:], func(tri []backendbase.Vec) {
b.fillTriangleNoAA(tri, func(x, y int) {
if b.clip.AlphaAt(x, y).A == 0 {
return
}
b.Image.SetRGBA(x, y, color.RGBA{})
})
})
}
func (b *SoftwareBackend) Fill(
style *backendbase.FillStyle,
pts []backendbase.Vec,
tf backendbase.Mat,
canOverlap bool,
) {
ffn := fillFunc(style)
var triBuf [500]backendbase.Vec
if tf != backendbase.MatIdentity {
ptsOld := pts
if len(pts) < len(triBuf) {
pts = triBuf[:len(pts)]
} else {
pts = make([]backendbase.Vec, len(pts))
}
for i, pt := range ptsOld {
pts[i] = pt.MulMat(tf)
}
}
if style.Blur > 0 {
b.activateBlurTarget()
b.fillTriangles(pts, ffn)
b.drawBlurred(style.Blur)
} else {
b.fillTriangles(pts, ffn)
}
}
func (b *SoftwareBackend) FillImageMask(
style *backendbase.FillStyle,
mask *image.Alpha,
pts [4]backendbase.Vec,
) {
ffn := fillFunc(style)
mw := float64(mask.Bounds().Dx())
mh := float64(mask.Bounds().Dy())
b.fillQuad(pts, func(x, y, sx2, sy2 float64) color.RGBA {
sxi := int(mw * sx2)
syi := int(mh * sy2)
a := mask.AlphaAt(sxi, syi)
if a.A == 0 {
return color.RGBA{}
}
col := ffn(x, y)
return alphaColor(col, a)
})
}
func fillFunc(style *backendbase.FillStyle) func(x, y float64) color.RGBA {
if lg := style.LinearGradient; lg != nil {
lg := lg.(*LinearGradient)
from := backendbase.Vec{style.Gradient.X0, style.Gradient.Y0}
dir := backendbase.Vec{
style.Gradient.X1 - style.Gradient.X0,
style.Gradient.Y1 - style.Gradient.Y0,
}
dirlen := math.Sqrt(dir[0]*dir[0] + dir[1]*dir[1])
dir[0] /= dirlen
dir[1] /= dirlen
return func(x, y float64) color.RGBA {
pos := backendbase.Vec{x - from[0], y - from[1]}
r := (pos[0]*dir[0] + pos[1]*dir[1]) / dirlen
return lg.data.ColorAt(r)
}
} else if rg := style.RadialGradient; rg != nil {
rg := rg.(*RadialGradient)
from := backendbase.Vec{style.Gradient.X0, style.Gradient.Y0}
to := backendbase.Vec{style.Gradient.X1, style.Gradient.Y1}
radFrom := style.Gradient.RadFrom
radTo := style.Gradient.RadTo
return func(x, y float64) color.RGBA {
pos := backendbase.Vec{x, y}
oa := 0.5 * math.Sqrt(
math.Pow(-2.0*from[0]*from[0]+2.0*from[0]*to[0]+2.0*from[0]*pos[0]-2.0*to[0]*pos[0]-2.0*from[1]*from[1]+2.0*from[1]*to[1]+2.0*from[1]*pos[1]-2.0*to[1]*pos[1]+2.0*radFrom*radFrom-2.0*radFrom*radTo, 2.0)-
4.0*(from[0]*from[0]-2.0*from[0]*pos[0]+pos[0]*pos[0]+from[1]*from[1]-2.0*from[1]*pos[1]+pos[1]*pos[1]-radFrom*radFrom)*
(from[0]*from[0]-2.0*from[0]*to[0]+to[0]*to[0]+from[1]*from[1]-2.0*from[1]*to[1]+to[1]*to[1]-radFrom*radFrom+2.0*radFrom*radTo-radTo*radTo))
ob := (from[0]*from[0] - from[0]*to[0] - from[0]*pos[0] + to[0]*pos[0] + from[1]*from[1] - from[1]*to[1] - from[1]*pos[1] + to[1]*pos[1] - radFrom*radFrom + radFrom*radTo)
oc := (from[0]*from[0] - 2.0*from[0]*to[0] + to[0]*to[0] + from[1]*from[1] - 2.0*from[1]*to[1] + to[1]*to[1] - radFrom*radFrom + 2.0*radFrom*radTo - radTo*radTo)
o1 := (-oa + ob) / oc
o2 := (oa + ob) / oc
if math.IsNaN(o1) && math.IsNaN(o2) {
return color.RGBA{}
}
o := math.Max(o1, o2)
return rg.data.ColorAt(o)
}
} else if ip := style.ImagePattern; ip != nil {
ip := ip.(*ImagePattern)
img := ip.data.Image.(*Image)
mip := img.mips[0] // todo select the right mip size
w, h := img.Size()
fw, fh := float64(w), float64(h)
rx := ip.data.Repeat == backendbase.Repeat || ip.data.Repeat == backendbase.RepeatX
ry := ip.data.Repeat == backendbase.Repeat || ip.data.Repeat == backendbase.RepeatY
return func(x, y float64) color.RGBA {
pos := backendbase.Vec{x, y}
tfptx := pos[0]*ip.data.Transform[0] + pos[1]*ip.data.Transform[1] + ip.data.Transform[2]
tfpty := pos[0]*ip.data.Transform[3] + pos[1]*ip.data.Transform[4] + ip.data.Transform[5]
if !rx && (tfptx < 0 || tfptx >= fw) {
return color.RGBA{}
}
if !ry && (tfpty < 0 || tfpty >= fh) {
return color.RGBA{}
}
mx := int(math.Floor(tfptx)) % w
if mx < 0 {
mx += w
}
my := int(math.Floor(tfpty)) % h
if my < 0 {
my += h
}
return toRGBA(mip.At(mx, my))
}
}
return func(x, y float64) color.RGBA {
return style.Color
}
}
func (b *SoftwareBackend) clearStencil() {
p := b.stencil.Pix
for i := range p {
p[i] = 0
}
}
func (b *SoftwareBackend) ClearClip() {
p := b.clip.Pix
for i := range p {
p[i] = 255
}
}
func (b *SoftwareBackend) Clip(pts []backendbase.Vec) {
b.clearStencil()
iterateTriangles(pts[:], func(tri []backendbase.Vec) {
b.fillTriangleNoAA(tri, func(x, y int) {
b.stencil.SetAlpha(x, y, color.Alpha{A: 255})
})
})
p := b.clip.Pix
p2 := b.stencil.Pix
for i := range p {
if p2[i] == 0 {
p[i] = 0
}
}
}

View file

@ -0,0 +1,147 @@
package softwarebackend
import (
"image"
"image/color"
"math"
"git.mstar.dev/mstar/canvas/backend/backendbase"
)
type Image struct {
mips []image.Image
deleted bool
}
func (b *SoftwareBackend) LoadImage(img image.Image) (backendbase.Image, error) {
bimg := &Image{mips: make([]image.Image, 1, 10)}
bimg.Replace(img)
return bimg, nil
}
func halveImage(img image.Image) (*image.RGBA, int, int) {
bounds := img.Bounds()
w, h := bounds.Dx(), bounds.Dy()
w = w / 2
h = h / 2
rimg := image.NewRGBA(image.Rect(0, 0, w, h))
for y := 0; y < h; y++ {
sy := y * 2
for x := 0; x < w; x++ {
sx := x * 2
r1, g1, b1, a1 := img.At(sx, sy).RGBA()
r2, g2, b2, a2 := img.At(sx+1, sy).RGBA()
r3, g3, b3, a3 := img.At(sx, sy+1).RGBA()
r4, g4, b4, a4 := img.At(sx+1, sy+1).RGBA()
mixr := uint8((int(r1) + int(r2) + int(r3) + int(r4)) / 1024)
mixg := uint8((int(g1) + int(g2) + int(g3) + int(g4)) / 1024)
mixb := uint8((int(b1) + int(b2) + int(b3) + int(b4)) / 1024)
mixa := uint8((int(a1) + int(a2) + int(a3) + int(a4)) / 1024)
rimg.Set(x, y, color.RGBA{R: mixr, G: mixg, B: mixb, A: mixa})
}
}
return rimg, w, h
}
func (b *SoftwareBackend) DrawImage(
dimg backendbase.Image,
sx, sy, sw, sh float64,
pts [4]backendbase.Vec,
alpha float64,
) {
simg := dimg.(*Image)
if simg.deleted {
return
}
bounds := simg.mips[0].Bounds()
w, h := bounds.Dx(), bounds.Dy()
factor := float64(w*h) / (sw * sh)
area := quadArea(pts) * factor
mip := simg.mips[0]
closest := math.MaxFloat64
mipW, mipH := w, h
for _, img := range simg.mips {
bounds := img.Bounds()
w, h := bounds.Dx(), bounds.Dy()
dist := math.Abs(float64(w*h) - area)
if dist < closest {
closest = dist
mip = img
mipW = w
mipH = h
}
}
mipScaleX := float64(mipW) / float64(w)
mipScaleY := float64(mipH) / float64(h)
sx *= mipScaleX
sy *= mipScaleY
sw *= mipScaleX
sh *= mipScaleY
b.fillQuad(pts, func(x, y, tx, ty float64) color.RGBA {
imgx := sx + sw*tx
imgy := sy + sh*ty
imgxf := math.Floor(imgx)
imgyf := math.Floor(imgy)
return toRGBA(mip.At(int(imgxf), int(imgyf)))
// rx := imgx - imgxf
// ry := imgy - imgyf
// ca := mip.At(int(imgxf), int(imgyf))
// cb := mip.At(int(imgxf+1), int(imgyf))
// cc := mip.At(int(imgxf), int(imgyf+1))
// cd := mip.At(int(imgxf+1), int(imgyf+1))
// ctop := lerp(ca, cb, rx)
// cbtm := lerp(cc, cd, rx)
// b.Image.Set(x, y, lerp(ctop, cbtm, ry))
})
}
func (img *Image) Width() int {
return img.mips[0].Bounds().Dx()
}
func (img *Image) Height() int {
return img.mips[0].Bounds().Dy()
}
func (img *Image) Size() (w, h int) {
b := img.mips[0].Bounds()
return b.Dx(), b.Dy()
}
func (img *Image) Delete() {
img.deleted = true
}
func (img *Image) Replace(src image.Image) error {
img.mips = img.mips[:1]
img.mips[0] = src
bounds := src.Bounds()
w, h := bounds.Dx(), bounds.Dy()
for w > 1 && h > 1 {
src, w, h = halveImage(src)
img.mips = append(img.mips, src)
}
return nil
}
type ImagePattern struct {
data backendbase.ImagePatternData
}
func (b *SoftwareBackend) LoadImagePattern(
data backendbase.ImagePatternData,
) backendbase.ImagePattern {
return &ImagePattern{
data: data,
}
}
func (ip *ImagePattern) Delete() {}
func (ip *ImagePattern) Replace(data backendbase.ImagePatternData) { ip.data = data }

View file

@ -0,0 +1,83 @@
package softwarebackend
import (
"image"
"image/draw"
"git.mstar.dev/mstar/canvas/backend/backendbase"
)
type SoftwareBackend struct {
Image *image.RGBA
MSAA int
blurSwap *image.RGBA
clip *image.Alpha
stencil *image.Alpha
w, h int
}
func New(w, h int) *SoftwareBackend {
b := &SoftwareBackend{}
b.SetSize(w, h)
return b
}
func (b *SoftwareBackend) SetSize(w, h int) {
b.w, b.h = w, h
b.Image = image.NewRGBA(image.Rect(0, 0, w, h))
b.clip = image.NewAlpha(image.Rect(0, 0, w, h))
b.stencil = image.NewAlpha(image.Rect(0, 0, w, h))
b.ClearClip()
}
func (b *SoftwareBackend) Size() (int, int) {
return b.w, b.h
}
func (b *SoftwareBackend) GetImageData(x, y, w, h int) *image.RGBA {
return b.Image.SubImage(image.Rect(x, y, w, h)).(*image.RGBA)
}
func (b *SoftwareBackend) PutImageData(img *image.RGBA, x, y int) {
draw.Draw(b.Image, image.Rect(x, y, img.Rect.Dx(), img.Rect.Dy()), img, image.ZP, draw.Src)
}
func (b *SoftwareBackend) CanUseAsImage(b2 backendbase.Backend) bool {
return false
}
func (b *SoftwareBackend) AsImage() backendbase.Image {
return nil
}
type LinearGradient struct {
data backendbase.Gradient
}
type RadialGradient struct {
data backendbase.Gradient
}
func (b *SoftwareBackend) LoadLinearGradient(data backendbase.Gradient) backendbase.LinearGradient {
return &LinearGradient{data: data}
}
func (b *SoftwareBackend) LoadRadialGradient(data backendbase.Gradient) backendbase.RadialGradient {
return &RadialGradient{data: data}
}
func (g *LinearGradient) Delete() {
}
func (g *LinearGradient) Replace(data backendbase.Gradient) {
g.data = data
}
func (g *RadialGradient) Delete() {
}
func (g *RadialGradient) Replace(data backendbase.Gradient) {
g.data = data
}

View file

@ -0,0 +1,614 @@
package softwarebackend
import (
"image/color"
"math"
"git.mstar.dev/mstar/canvas/backend/backendbase"
)
func triangleLR(tri []backendbase.Vec, y float64) (l, r float64, outside bool) {
a, b, c := tri[0], tri[1], tri[2]
// sort by y
if a[1] > b[1] {
a, b = b, a
}
if b[1] > c[1] {
b, c = c, b
if a[1] > b[1] {
a, b = b, a
}
}
// check general bounds
if y <= a[1] {
return a[0], a[0], true
}
if y > c[1] {
return c[0], c[0], true
}
// find left and right x at y
if y >= a[1] && y <= b[1] && a[1] < b[1] {
r0 := (y - a[1]) / (b[1] - a[1])
l = (b[0]-a[0])*r0 + a[0]
r1 := (y - a[1]) / (c[1] - a[1])
r = (c[0]-a[0])*r1 + a[0]
} else {
r0 := (y - b[1]) / (c[1] - b[1])
l = (c[0]-b[0])*r0 + b[0]
r1 := (y - a[1]) / (c[1] - a[1])
r = (c[0]-a[0])*r1 + a[0]
}
if l > r {
l, r = r, l
}
return
}
func (b *SoftwareBackend) fillTriangleNoAA(tri []backendbase.Vec, fn func(x, y int)) {
minY := int(math.Floor(math.Min(math.Min(tri[0][1], tri[1][1]), tri[2][1])))
maxY := int(math.Ceil(math.Max(math.Max(tri[0][1], tri[1][1]), tri[2][1])))
if minY < 0 {
minY = 0
} else if minY >= b.h {
return
}
if maxY < 0 {
return
} else if maxY >= b.h {
maxY = b.h - 1
}
for y := minY; y <= maxY; y++ {
l, r, out := triangleLR(tri, float64(y)+0.5)
if out {
continue
}
if l < 0 {
l = 0
} else if l > float64(b.w) {
continue
}
if r < 0 {
continue
} else if r > float64(b.w) {
r = float64(b.w)
}
if l >= r {
continue
}
fl, cr := int(math.Floor(l)), int(math.Ceil(r))
for x := fl; x <= cr; x++ {
fx := float64(x) + 0.5
if fx < l || fx >= r {
continue
}
fn(x, y)
}
}
}
type msaaPixel struct {
ix, iy int
fx, fy float64
tx, ty float64
}
func (b *SoftwareBackend) fillTriangleMSAA(
tri []backendbase.Vec,
msaaLevel int,
msaaPixels []msaaPixel,
fn func(x, y int),
) []msaaPixel {
msaaStep := 1.0 / float64(msaaLevel+1)
minY := int(math.Floor(math.Min(math.Min(tri[0][1], tri[1][1]), tri[2][1])))
maxY := int(math.Ceil(math.Max(math.Max(tri[0][1], tri[1][1]), tri[2][1])))
if minY < 0 {
minY = 0
} else if minY >= b.h {
return msaaPixels
}
if maxY < 0 {
return msaaPixels
} else if maxY >= b.h {
maxY = b.h - 1
}
for y := minY; y <= maxY; y++ {
var l, r [5]float64
allOut := true
minL, maxR := math.MaxFloat64, 0.0
sy := float64(y) + msaaStep*0.5
for step := 0; step <= msaaLevel; step++ {
var out bool
l[step], r[step], out = triangleLR(tri, sy)
if l[step] < 0 {
l[step] = 0
} else if l[step] > float64(b.w) {
l[step] = float64(b.w)
out = true
}
if r[step] < 0 {
r[step] = 0
out = true
} else if r[step] > float64(b.w) {
r[step] = float64(b.w)
}
if r[step] <= l[step] {
out = true
}
if !out {
allOut = false
minL = math.Min(minL, l[step])
maxR = math.Max(maxR, r[step])
}
sy += msaaStep
}
if allOut {
continue
}
fl, cr := int(math.Floor(minL)), int(math.Ceil(maxR))
for x := fl; x <= cr; x++ {
sy = float64(y) + msaaStep*0.5
allIn := true
check:
for stepy := 0; stepy <= msaaLevel; stepy++ {
sx := float64(x) + msaaStep*0.5
for stepx := 0; stepx <= msaaLevel; stepx++ {
if sx < l[stepy] || sx >= r[stepy] {
allIn = false
break check
}
sx += msaaStep
}
sy += msaaStep
}
if allIn {
fn(x, y)
continue
}
sy = float64(y) + msaaStep*0.5
for stepy := 0; stepy <= msaaLevel; stepy++ {
sx := float64(x) + msaaStep*0.5
for stepx := 0; stepx <= msaaLevel; stepx++ {
if sx >= l[stepy] && sx < r[stepy] {
msaaPixels = addMSAAPixel(
msaaPixels,
msaaPixel{ix: x, iy: y, fx: sx, fy: sy},
)
}
sx += msaaStep
}
sy += msaaStep
}
}
}
return msaaPixels
}
func addMSAAPixel(msaaPixels []msaaPixel, px msaaPixel) []msaaPixel {
for _, px2 := range msaaPixels {
if px == px2 {
return msaaPixels
}
}
return append(msaaPixels, px)
}
func quadArea(quad [4]backendbase.Vec) float64 {
leftv := backendbase.Vec{quad[1][0] - quad[0][0], quad[1][1] - quad[0][1]}
topv := backendbase.Vec{quad[3][0] - quad[0][0], quad[3][1] - quad[0][1]}
return math.Abs(leftv[0]*topv[1] - leftv[1]*topv[0])
}
func (b *SoftwareBackend) fillQuadNoAA(quad [4]backendbase.Vec, fn func(x, y int, tx, ty float64)) {
minY := int(
math.Floor(math.Min(math.Min(quad[0][1], quad[1][1]), math.Min(quad[2][1], quad[3][1]))),
)
maxY := int(
math.Ceil(math.Max(math.Max(quad[0][1], quad[1][1]), math.Max(quad[2][1], quad[3][1]))),
)
if minY < 0 {
minY = 0
} else if minY >= b.h {
return
}
if maxY < 0 {
return
} else if maxY >= b.h {
maxY = b.h - 1
}
leftv := backendbase.Vec{quad[1][0] - quad[0][0], quad[1][1] - quad[0][1]}
leftLen := math.Sqrt(leftv[0]*leftv[0] + leftv[1]*leftv[1])
leftv[0] /= leftLen
leftv[1] /= leftLen
topv := backendbase.Vec{quad[3][0] - quad[0][0], quad[3][1] - quad[0][1]}
topLen := math.Sqrt(topv[0]*topv[0] + topv[1]*topv[1])
topv[0] /= topLen
topv[1] /= topLen
tri1 := [3]backendbase.Vec{quad[0], quad[1], quad[2]}
tri2 := [3]backendbase.Vec{quad[0], quad[2], quad[3]}
for y := minY; y <= maxY; y++ {
lf1, rf1, out1 := triangleLR(tri1[:], float64(y)+0.5)
lf2, rf2, out2 := triangleLR(tri2[:], float64(y)+0.5)
if out1 && out2 {
continue
}
l := math.Min(lf1, lf2)
r := math.Max(rf1, rf2)
if l < 0 {
l = 0
} else if l > float64(b.w) {
continue
}
if r < 0 {
continue
} else if r > float64(b.w) {
r = float64(b.w)
}
if l >= r {
continue
}
tfy := float64(y) + 0.5 - quad[0][1]
fl, cr := int(math.Floor(l)), int(math.Ceil(r))
for x := fl; x <= cr; x++ {
fx := float64(x) + 0.5
if fx < l || fx >= r {
continue
}
tfx := fx - quad[0][0]
var tx, ty float64
if math.Abs(leftv[0]) > math.Abs(leftv[1]) {
tx = (tfy - tfx*(leftv[1]/leftv[0])) / (topv[1] - topv[0]*(leftv[1]/leftv[0]))
ty = (tfx - topv[0]*tx) / leftv[0]
} else {
tx = (tfx - tfy*(leftv[0]/leftv[1])) / (topv[0] - topv[1]*(leftv[0]/leftv[1]))
ty = (tfy - topv[1]*tx) / leftv[1]
}
fn(x, y, tx/topLen, ty/leftLen)
}
}
}
func (b *SoftwareBackend) fillQuadMSAA(
quad [4]backendbase.Vec,
msaaLevel int,
msaaPixels []msaaPixel,
fn func(x, y int, tx, ty float64),
) []msaaPixel {
msaaStep := 1.0 / float64(msaaLevel+1)
minY := int(
math.Floor(math.Min(math.Min(quad[0][1], quad[1][1]), math.Min(quad[2][1], quad[3][1]))),
)
maxY := int(
math.Ceil(math.Max(math.Max(quad[0][1], quad[1][1]), math.Max(quad[2][1], quad[3][1]))),
)
if minY < 0 {
minY = 0
} else if minY >= b.h {
return msaaPixels
}
if maxY < 0 {
return msaaPixels
} else if maxY >= b.h {
maxY = b.h - 1
}
leftv := backendbase.Vec{quad[1][0] - quad[0][0], quad[1][1] - quad[0][1]}
leftLen := math.Sqrt(leftv[0]*leftv[0] + leftv[1]*leftv[1])
leftv[0] /= leftLen
leftv[1] /= leftLen
topv := backendbase.Vec{quad[3][0] - quad[0][0], quad[3][1] - quad[0][1]}
topLen := math.Sqrt(topv[0]*topv[0] + topv[1]*topv[1])
topv[0] /= topLen
topv[1] /= topLen
tri1 := [3]backendbase.Vec{quad[0], quad[1], quad[2]}
tri2 := [3]backendbase.Vec{quad[0], quad[2], quad[3]}
for y := minY; y <= maxY; y++ {
var l, r [5]float64
allOut := true
minL, maxR := math.MaxFloat64, 0.0
sy := float64(y) + msaaStep*0.5
for step := 0; step <= msaaLevel; step++ {
lf1, rf1, out1 := triangleLR(tri1[:], sy)
lf2, rf2, out2 := triangleLR(tri2[:], sy)
l[step] = math.Min(lf1, lf2)
r[step] = math.Max(rf1, rf2)
out := out1 || out2
if l[step] < 0 {
l[step] = 0
} else if l[step] > float64(b.w) {
l[step] = float64(b.w)
out = true
}
if r[step] < 0 {
r[step] = 0
out = true
} else if r[step] > float64(b.w) {
r[step] = float64(b.w)
}
if r[step] <= l[step] {
out = true
}
if !out {
allOut = false
minL = math.Min(minL, l[step])
maxR = math.Max(maxR, r[step])
}
sy += msaaStep
}
if allOut {
continue
}
fl, cr := int(math.Floor(minL)), int(math.Ceil(maxR))
for x := fl; x <= cr; x++ {
sy = float64(y) + msaaStep*0.5
allIn := true
check:
for stepy := 0; stepy <= msaaLevel; stepy++ {
sx := float64(x) + msaaStep*0.5
for stepx := 0; stepx <= msaaLevel; stepx++ {
if sx < l[stepy] || sx >= r[stepy] {
allIn = false
break check
}
sx += msaaStep
}
sy += msaaStep
}
if allIn {
tfx := float64(x) + 0.5 - quad[0][0]
tfy := float64(y) + 0.5 - quad[0][1]
var tx, ty float64
if math.Abs(leftv[0]) > math.Abs(leftv[1]) {
tx = (tfy - tfx*(leftv[1]/leftv[0])) / (topv[1] - topv[0]*(leftv[1]/leftv[0]))
ty = (tfx - topv[0]*tx) / leftv[0]
} else {
tx = (tfx - tfy*(leftv[0]/leftv[1])) / (topv[0] - topv[1]*(leftv[0]/leftv[1]))
ty = (tfy - topv[1]*tx) / leftv[1]
}
fn(x, y, tx/topLen, ty/leftLen)
continue
}
sy = float64(y) + msaaStep*0.5
for stepy := 0; stepy <= msaaLevel; stepy++ {
sx := float64(x) + msaaStep*0.5
for stepx := 0; stepx <= msaaLevel; stepx++ {
if sx >= l[stepy] && sx < r[stepy] {
tfx := sx - quad[0][0]
tfy := sy - quad[0][1]
var tx, ty float64
if math.Abs(leftv[0]) > math.Abs(leftv[1]) {
tx = (tfy - tfx*(leftv[1]/leftv[0])) / (topv[1] - topv[0]*(leftv[1]/leftv[0]))
ty = (tfx - topv[0]*tx) / leftv[0]
} else {
tx = (tfx - tfy*(leftv[0]/leftv[1])) / (topv[0] - topv[1]*(leftv[0]/leftv[1]))
ty = (tfy - topv[1]*tx) / leftv[1]
}
msaaPixels = addMSAAPixel(
msaaPixels,
msaaPixel{
ix: x,
iy: y,
fx: sx,
fy: sy,
tx: tx / topLen,
ty: ty / leftLen,
},
)
}
sx += msaaStep
}
sy += msaaStep
}
}
}
return msaaPixels
}
func (b *SoftwareBackend) fillQuad(
pts [4]backendbase.Vec,
fn func(x, y, tx, ty float64) color.RGBA,
) {
b.clearStencil()
if b.MSAA > 0 {
var msaaPixelBuf [500]msaaPixel
msaaPixels := msaaPixelBuf[:0]
msaaPixels = b.fillQuadMSAA(pts, b.MSAA, msaaPixels, func(x, y int, tx, ty float64) {
if b.clip.AlphaAt(x, y).A == 0 {
return
}
if b.stencil.AlphaAt(x, y).A > 0 {
return
}
b.stencil.SetAlpha(x, y, color.Alpha{A: 255})
col := fn(float64(x)+0.5, float64(y)+0.5, tx, ty)
if col.A > 0 {
b.Image.SetRGBA(x, y, mix(col, b.Image.RGBAAt(x, y)))
}
})
samples := (b.MSAA + 1) * (b.MSAA + 1)
for i, px := range msaaPixels {
if px.ix < 0 || b.clip.AlphaAt(px.ix, px.iy).A == 0 ||
b.stencil.AlphaAt(px.ix, px.iy).A > 0 {
continue
}
b.stencil.SetAlpha(px.ix, px.iy, color.Alpha{A: 255})
var mr, mg, mb, ma int
for j, px2 := range msaaPixels[i:] {
if px2.ix != px.ix || px2.iy != px.iy {
continue
}
col := fn(px2.fx, px2.fy, px2.tx, px2.ty)
mr += int(col.R)
mg += int(col.G)
mb += int(col.B)
ma += int(col.A)
msaaPixels[i+j].ix = -1
}
combined := color.RGBA{
R: uint8(mr / samples),
G: uint8(mg / samples),
B: uint8(mb / samples),
A: uint8(ma / samples),
}
b.Image.SetRGBA(px.ix, px.iy, mix(combined, b.Image.RGBAAt(px.ix, px.iy)))
}
} else {
b.fillQuadNoAA(pts, func(x, y int, tx, ty float64) {
if b.clip.AlphaAt(x, y).A == 0 {
return
}
if b.stencil.AlphaAt(x, y).A > 0 {
return
}
b.stencil.SetAlpha(x, y, color.Alpha{A: 255})
col := fn(float64(x)+0.5, float64(y)+0.5, tx, ty)
if col.A > 0 {
b.Image.SetRGBA(x, y, mix(col, b.Image.RGBAAt(x, y)))
}
})
}
}
func iterateTriangles(pts []backendbase.Vec, fn func(tri []backendbase.Vec)) {
if len(pts) == 4 {
var buf [3]backendbase.Vec
buf[0] = pts[0]
buf[1] = pts[1]
buf[2] = pts[2]
fn(buf[:])
buf[1] = pts[2]
buf[2] = pts[3]
fn(buf[:])
return
}
for i := 3; i <= len(pts); i += 3 {
fn(pts[i-3 : i])
}
}
func (b *SoftwareBackend) fillTrianglesNoAA(
pts []backendbase.Vec,
fn func(x, y float64) color.RGBA,
) {
iterateTriangles(pts[:], func(tri []backendbase.Vec) {
b.fillTriangleNoAA(tri, func(x, y int) {
if b.clip.AlphaAt(x, y).A == 0 {
return
}
if b.stencil.AlphaAt(x, y).A > 0 {
return
}
b.stencil.SetAlpha(x, y, color.Alpha{A: 255})
col := fn(float64(x), float64(y))
if col.A > 0 {
b.Image.SetRGBA(x, y, mix(col, b.Image.RGBAAt(x, y)))
}
})
})
}
func (b *SoftwareBackend) fillTrianglesMSAA(
pts []backendbase.Vec,
msaaLevel int,
fn func(x, y float64) color.RGBA,
) {
var msaaPixelBuf [500]msaaPixel
msaaPixels := msaaPixelBuf[:0]
iterateTriangles(pts[:], func(tri []backendbase.Vec) {
msaaPixels = b.fillTriangleMSAA(tri, msaaLevel, msaaPixels, func(x, y int) {
if b.clip.AlphaAt(x, y).A == 0 {
return
}
if b.stencil.AlphaAt(x, y).A > 0 {
return
}
b.stencil.SetAlpha(x, y, color.Alpha{A: 255})
col := fn(float64(x), float64(y))
if col.A > 0 {
b.Image.SetRGBA(x, y, mix(col, b.Image.RGBAAt(x, y)))
}
})
})
samples := (msaaLevel + 1) * (msaaLevel + 1)
for i, px := range msaaPixels {
if px.ix < 0 || b.clip.AlphaAt(px.ix, px.iy).A == 0 ||
b.stencil.AlphaAt(px.ix, px.iy).A > 0 {
continue
}
b.stencil.SetAlpha(px.ix, px.iy, color.Alpha{A: 255})
var mr, mg, mb, ma int
for j, px2 := range msaaPixels[i:] {
if px2.ix != px.ix || px2.iy != px.iy {
continue
}
col := fn(px2.fx, px2.fy)
mr += int(col.R)
mg += int(col.G)
mb += int(col.B)
ma += int(col.A)
msaaPixels[i+j].ix = -1
}
combined := color.RGBA{
R: uint8(mr / samples),
G: uint8(mg / samples),
B: uint8(mb / samples),
A: uint8(ma / samples),
}
b.Image.SetRGBA(px.ix, px.iy, mix(combined, b.Image.RGBAAt(px.ix, px.iy)))
}
}
func (b *SoftwareBackend) fillTriangles(pts []backendbase.Vec, fn func(x, y float64) color.RGBA) {
b.clearStencil()
if b.MSAA > 0 {
b.fillTrianglesMSAA(pts, b.MSAA, fn)
} else {
b.fillTrianglesNoAA(pts, fn)
}
}

771
canvas.go
View file

@ -3,10 +3,16 @@
package canvas
import (
"fmt"
"os"
"image"
"image/color"
"math"
"time"
"github.com/golang/freetype/truetype"
"golang.org/x/image/font"
"golang.org/x/image/math/fixed"
"git.mstar.dev/mstar/canvas/backend/backendbase"
)
//go:generate go run make_shaders.go
@ -15,32 +21,35 @@ import (
// Canvas represents an area on the viewport on which to draw
// using a set of functions very similar to the HTML5 canvas
type Canvas struct {
x, y, w, h int
fx, fy, fw, fh float64
b backendbase.Backend
path []pathPoint
convex bool
rect bool
path Path2D
state drawState
stateStack []drawState
offscreen bool
offscrBuf offscreenBuffer
offscrImg Image
images map[interface{}]*Image
fonts map[interface{}]*Font
fontCtxs map[fontKey]*frCache
fontPathCache map[*Font]*fontPathCache
fontTriCache map[*Font]*fontTriCache
shadowBuf []backendbase.Vec
}
type drawState struct {
transform mat
transform backendbase.Mat
fill drawStyle
stroke drawStyle
font *Font
fontSize float64
fontSize fixed.Int26_6
fontMetrics font.Metrics
textAlign textAlign
textBaseline textBaseline
lineAlpha float64
lineWidth float64
lineJoin lineJoin
lineEnd lineEnd
lineCap lineCap
miterLimitSqr float64
globalAlpha float64
@ -48,10 +57,9 @@ type drawState struct {
lineDashPoint int
lineDashOffset float64
scissor scissor
clip []pathPoint
clip Path2D
shadowColor glColor
shadowColor color.RGBA
shadowOffsetX float64
shadowOffsetY float64
shadowBlur float64
@ -68,21 +76,16 @@ type drawState struct {
}
type drawStyle struct {
color glColor
color color.RGBA
radialGradient *RadialGradient
linearGradient *LinearGradient
image *Image
}
type scissor struct {
on bool
tl, br vec
imagePattern *ImagePattern
}
type lineJoin uint8
type lineEnd uint8
type lineCap uint8
// Line join and end constants for SetLineJoin and SetLineEnd
// Line join and end constants for SetLineJoin and SetLineCap
const (
Miter = iota
Bevel
@ -102,130 +105,82 @@ const (
End
)
type textBaseline uint8
// Text baseline constants for SetTextBaseline
const (
Alphabetic = iota
Top
Hanging
Middle
Ideographic
Bottom
)
// Performance is a nonstandard setting to improve the
// performance of the rendering in some circumstances.
// Disabling self intersections will lead to incorrect
// rendering of self intersecting polygons, but will
// yield better performance when not using the polygons
// are not self intersecting. Assuming convex polygons
// will break concave polygons, but improve performance
// even further
var Performance = struct {
IgnoreSelfIntersections bool
AssumeConvex bool
// CacheSize is only approximate
CacheSize int
}{
CacheSize: 128_000_000,
}
// New creates a new canvas with the given viewport coordinates.
// While all functions on the canvas use the top left point as
// the origin, since GL uses the bottom left coordinate, the
// coordinates given here also use the bottom left as origin
func New(x, y, w, h int) *Canvas {
if gli == nil {
panic("LoadGL must be called before a canvas can be created")
func New(backend backendbase.Backend) *Canvas {
cv := &Canvas{
b: backend,
stateStack: make([]drawState, 0, 20),
images: make(map[interface{}]*Image),
fonts: make(map[interface{}]*Font),
fontCtxs: make(map[fontKey]*frCache),
fontPathCache: make(map[*Font]*fontPathCache),
fontTriCache: make(map[*Font]*fontTriCache),
}
cv := &Canvas{stateStack: make([]drawState, 0, 20)}
cv.SetBounds(x, y, w, h)
cv.state.lineWidth = 1
cv.state.lineAlpha = 1
cv.state.miterLimitSqr = 100
cv.state.globalAlpha = 1
cv.state.fill.color = glColor{a: 1}
cv.state.stroke.color = glColor{a: 1}
cv.state.transform = matIdentity()
cv.state.fill.color = color.RGBA{A: 255}
cv.state.stroke.color = color.RGBA{A: 255}
cv.state.transform = backendbase.MatIdentity
cv.path.cv = cv
return cv
}
// NewOffscreen creates a new canvas with the given size. It
// does not render directly to the screen but renders to a
// texture instead
func NewOffscreen(w, h int) *Canvas {
cv := New(0, 0, w, h)
cv.offscreen = true
return cv
}
// SetSize changes the internal size of the canvas. This would
// usually be called for example when the window is resized
//
// Deprecated: Use SetBounds instead
func (cv *Canvas) SetSize(w, h int) {
cv.w, cv.h = w, h
cv.fw, cv.fh = float64(w), float64(h)
activeCanvas = nil
}
// SetBounds updates the bounds of the canvas. This would
// usually be called for example when the window is resized
func (cv *Canvas) SetBounds(x, y, w, h int) {
if !cv.offscreen {
cv.x, cv.y = x, y
cv.fx, cv.fy = float64(x), float64(y)
}
cv.w, cv.h = w, h
cv.fw, cv.fh = float64(w), float64(h)
activeCanvas = nil
}
// Width returns the internal width of the canvas
func (cv *Canvas) Width() int { return cv.w }
func (cv *Canvas) Width() int {
w, _ := cv.b.Size()
return w
}
// Height returns the internal height of the canvas
func (cv *Canvas) Height() int { return cv.h }
func (cv *Canvas) Height() int {
_, h := cv.b.Size()
return h
}
// Size returns the internal width and height of the canvas
func (cv *Canvas) Size() (int, int) { return cv.w, cv.h }
func (cv *Canvas) Size() (int, int) { return cv.b.Size() }
func (cv *Canvas) tf(v vec) vec {
v, _ = v.mulMat(cv.state.transform)
return v
}
// Activate makes the canvas active and sets the viewport. Only needs
// to be called if any other GL code changes the viewport
func (cv *Canvas) Activate() {
if cv.offscreen {
gli.Viewport(0, 0, int32(cv.w), int32(cv.h))
cv.enableTextureRenderTarget(&cv.offscrBuf)
cv.offscrImg.w = cv.offscrBuf.w
cv.offscrImg.h = cv.offscrBuf.h
cv.offscrImg.tex = cv.offscrBuf.tex
} else {
gli.Viewport(int32(cv.x), int32(cv.y), int32(cv.w), int32(cv.h))
cv.disableTextureRenderTarget()
}
cv.applyScissor()
gli.Clear(gl_STENCIL_BUFFER_BIT)
}
var activeCanvas *Canvas
func (cv *Canvas) activate() {
if activeCanvas != cv {
activeCanvas = cv
cv.Activate()
}
loop:
for {
select {
case f := <-glChan:
f()
default:
break loop
}
}
func (cv *Canvas) tf(v backendbase.Vec) backendbase.Vec {
return v.MulMat(cv.state.transform)
}
const alphaTexSize = 2048
var (
gli GL
buf uint32
shadowBuf uint32
alphaTex uint32
sr *solidShader
lgr *linearGradientShader
rgr *radialGradientShader
ipr *imagePatternShader
sar *solidAlphaShader
rgar *radialGradientAlphaShader
lgar *linearGradientAlphaShader
ipar *imagePatternAlphaShader
ir *imageShader
gauss15r *gaussianShader
gauss63r *gaussianShader
gauss127r *gaussianShader
offscr1 offscreenBuffer
offscr2 offscreenBuffer
glChan = make(chan func())
)
type offscreenBuffer struct {
tex uint32
w int
@ -235,184 +190,12 @@ type offscreenBuffer struct {
alpha bool
}
type gaussianShader struct {
id uint32
vertex uint32
texCoord uint32
canvasSize int32
kernelScale int32
image int32
kernel int32
}
// LoadGL needs to be called once per GL context to load the GL assets
// that canvas needs. The parameter is an implementation of the GL interface
// in this package that should make this package neutral to GL implementations.
// The goglimpl subpackage contains an implementation based on Go-GL v3.2
func LoadGL(glimpl GL) (err error) {
gli = glimpl
gli.GetError() // clear error state
sr, err = loadSolidShader()
if err != nil {
return
}
err = glError()
if err != nil {
return
}
lgr, err = loadLinearGradientShader()
if err != nil {
return
}
err = glError()
if err != nil {
return
}
rgr, err = loadRadialGradientShader()
if err != nil {
return
}
err = glError()
if err != nil {
return
}
ipr, err = loadImagePatternShader()
if err != nil {
return
}
err = glError()
if err != nil {
return
}
sar, err = loadSolidAlphaShader()
if err != nil {
return
}
err = glError()
if err != nil {
return
}
lgar, err = loadLinearGradientAlphaShader()
if err != nil {
return
}
err = glError()
if err != nil {
return
}
rgar, err = loadRadialGradientAlphaShader()
if err != nil {
return
}
err = glError()
if err != nil {
return
}
ipar, err = loadImagePatternAlphaShader()
if err != nil {
return
}
err = glError()
if err != nil {
return
}
ir, err = loadImageShader()
if err != nil {
return
}
err = glError()
if err != nil {
return
}
gauss15s, err := loadGaussian15Shader()
if err != nil {
return
}
err = glError()
if err != nil {
return
}
gauss15r = (*gaussianShader)(gauss15s)
gauss63s, err := loadGaussian63Shader()
if err != nil {
return
}
err = glError()
if err != nil {
return
}
gauss63r = (*gaussianShader)(gauss63s)
gauss127s, err := loadGaussian127Shader()
if err != nil {
return
}
err = glError()
if err != nil {
return
}
gauss127r = (*gaussianShader)(gauss127s)
gli.GenBuffers(1, &buf)
err = glError()
if err != nil {
return
}
gli.GenBuffers(1, &shadowBuf)
err = glError()
if err != nil {
return
}
gli.ActiveTexture(gl_TEXTURE0)
gli.GenTextures(1, &alphaTex)
gli.BindTexture(gl_TEXTURE_2D, alphaTex)
gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, gl_NEAREST)
gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, gl_NEAREST)
gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_S, gl_CLAMP_TO_EDGE)
gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_T, gl_CLAMP_TO_EDGE)
gli.TexImage2D(gl_TEXTURE_2D, 0, gl_ALPHA, alphaTexSize, alphaTexSize, 0, gl_ALPHA, gl_UNSIGNED_BYTE, nil)
gli.Enable(gl_BLEND)
gli.BlendFunc(gl_SRC_ALPHA, gl_ONE_MINUS_SRC_ALPHA)
gli.Enable(gl_STENCIL_TEST)
gli.StencilMask(0xFF)
gli.Clear(gl_STENCIL_BUFFER_BIT)
gli.StencilOp(gl_KEEP, gl_KEEP, gl_KEEP)
gli.StencilFunc(gl_EQUAL, 0, 0xFF)
gli.Enable(gl_SCISSOR_TEST)
return
}
func glError() error {
glErr := gli.GetError()
if glErr != gl_NO_ERROR {
return fmt.Errorf("GL Error: %x", glErr)
}
return nil
}
// SetFillStyle sets the color, gradient, or image for any fill calls. To set a
// color, there are several acceptable formats: 3 or 4 int values for RGB(A) in
// the range 0-255, 3 or 4 float values for RGB(A) in the range 0-1, hex strings
// in the format "#AABBCC", "#AABBCCDD", "#ABC", or "#ABCD"
func (cv *Canvas) SetFillStyle(value ...interface{}) {
cv.state.fill = parseStyle(value...)
cv.state.fill = cv.parseStyle(value...)
}
// SetStrokeStyle sets the color, gradient, or image for any line drawing calls.
@ -420,10 +203,12 @@ func (cv *Canvas) SetFillStyle(value ...interface{}) {
// RGB(A) in the range 0-255, 3 or 4 float values for RGB(A) in the range 0-1,
// hex strings in the format "#AABBCC", "#AABBCCDD", "#ABC", or "#ABCD"
func (cv *Canvas) SetStrokeStyle(value ...interface{}) {
cv.state.stroke = parseStyle(value...)
cv.state.stroke = cv.parseStyle(value...)
}
func parseStyle(value ...interface{}) drawStyle {
var imagePatterns = make(map[interface{}]*ImagePattern)
func (cv *Canvas) parseStyle(value ...interface{}) drawStyle {
var style drawStyle
if len(value) == 1 {
switch v := value[0].(type) {
@ -433,216 +218,63 @@ func parseStyle(value ...interface{}) drawStyle {
case *RadialGradient:
style.radialGradient = v
return style
case *ImagePattern:
style.imagePattern = v
return style
}
}
c, ok := parseColor(value...)
if ok {
style.color = c
} else if len(value) == 1 {
return style
}
if len(value) == 1 {
switch v := value[0].(type) {
case *Image, string:
style.image = getImage(v)
case *Image, image.Image, string:
if _, ok := imagePatterns[v]; !ok {
imagePatterns[v] = cv.CreatePattern(v, Repeat)
}
style.imagePattern = imagePatterns[v]
}
}
return style
}
func (s *drawStyle) isOpaque() bool {
func (cv *Canvas) backendFillStyle(s *drawStyle, alpha float64) backendbase.FillStyle {
stl := backendbase.FillStyle{Color: s.color}
alpha *= cv.state.globalAlpha
if lg := s.linearGradient; lg != nil {
return lg.opaque
}
if rg := s.radialGradient; rg != nil {
return rg.opaque
}
if img := s.image; img != nil {
return img.opaque
}
return s.color.a >= 1
}
func (cv *Canvas) useShader(style *drawStyle) (vertexLoc uint32) {
if lg := style.linearGradient; lg != nil {
lg.load()
gli.ActiveTexture(gl_TEXTURE0)
gli.BindTexture(gl_TEXTURE_2D, lg.tex)
gli.UseProgram(lgr.id)
stl.LinearGradient = lg.grad
from := cv.tf(lg.from)
to := cv.tf(lg.to)
dir := to.sub(from)
length := dir.len()
dir = dir.divf(length)
gli.Uniform2f(lgr.canvasSize, float32(cv.fw), float32(cv.fh))
inv := cv.state.transform.invert().f32()
gli.UniformMatrix3fv(lgr.invmat, 1, false, &inv[0])
gli.Uniform2f(lgr.from, float32(from[0]), float32(from[1]))
gli.Uniform2f(lgr.dir, float32(dir[0]), float32(dir[1]))
gli.Uniform1f(lgr.len, float32(length))
gli.Uniform1i(lgr.gradient, 0)
gli.Uniform1f(lgr.globalAlpha, float32(cv.state.globalAlpha))
return lgr.vertex
}
if rg := style.radialGradient; rg != nil {
stl.Gradient.X0 = from[0]
stl.Gradient.Y0 = from[1]
stl.Gradient.X1 = to[0]
stl.Gradient.Y1 = to[1]
} else if rg := s.radialGradient; rg != nil {
rg.load()
gli.ActiveTexture(gl_TEXTURE0)
gli.BindTexture(gl_TEXTURE_2D, rg.tex)
gli.UseProgram(rgr.id)
from := cv.tf(rg.from)
to := cv.tf(rg.to)
dir := to.sub(from)
length := dir.len()
dir = dir.divf(length)
gli.Uniform2f(rgr.canvasSize, float32(cv.fw), float32(cv.fh))
inv := cv.state.transform.invert().f32()
gli.UniformMatrix3fv(rgr.invmat, 1, false, &inv[0])
gli.Uniform2f(rgr.from, float32(from[0]), float32(from[1]))
gli.Uniform2f(rgr.to, float32(to[0]), float32(to[1]))
gli.Uniform2f(rgr.dir, float32(dir[0]), float32(dir[1]))
gli.Uniform1f(rgr.radFrom, float32(rg.radFrom))
gli.Uniform1f(rgr.radTo, float32(rg.radTo))
gli.Uniform1f(rgr.len, float32(length))
gli.Uniform1i(rgr.gradient, 0)
gli.Uniform1f(rgr.globalAlpha, float32(cv.state.globalAlpha))
return rgr.vertex
}
if img := style.image; img != nil {
gli.UseProgram(ipr.id)
gli.ActiveTexture(gl_TEXTURE0)
gli.BindTexture(gl_TEXTURE_2D, img.tex)
gli.Uniform2f(ipr.canvasSize, float32(cv.fw), float32(cv.fh))
inv := cv.state.transform.invert().f32()
gli.UniformMatrix3fv(ipr.invmat, 1, false, &inv[0])
gli.Uniform2f(ipr.imageSize, float32(img.w), float32(img.h))
gli.Uniform1i(ipr.image, 0)
gli.Uniform1f(ipr.globalAlpha, float32(cv.state.globalAlpha))
return ipr.vertex
}
gli.UseProgram(sr.id)
gli.Uniform2f(sr.canvasSize, float32(cv.fw), float32(cv.fh))
c := style.color
gli.Uniform4f(sr.color, float32(c.r), float32(c.g), float32(c.b), float32(c.a))
gli.Uniform1f(sr.globalAlpha, float32(cv.state.globalAlpha))
return sr.vertex
}
func (cv *Canvas) useAlphaShader(style *drawStyle, alphaTexSlot int32) (vertexLoc, alphaTexCoordLoc uint32) {
if lg := style.linearGradient; lg != nil {
lg.load()
gli.ActiveTexture(gl_TEXTURE0)
gli.BindTexture(gl_TEXTURE_2D, lg.tex)
gli.UseProgram(lgar.id)
from := cv.tf(lg.from)
to := cv.tf(lg.to)
dir := to.sub(from)
length := dir.len()
dir = dir.divf(length)
gli.Uniform2f(lgar.canvasSize, float32(cv.fw), float32(cv.fh))
inv := cv.state.transform.invert().f32()
gli.UniformMatrix3fv(lgar.invmat, 1, false, &inv[0])
gli.Uniform2f(lgar.from, float32(from[0]), float32(from[1]))
gli.Uniform2f(lgar.dir, float32(dir[0]), float32(dir[1]))
gli.Uniform1f(lgar.len, float32(length))
gli.Uniform1i(lgar.gradient, 0)
gli.Uniform1i(lgar.alphaTex, alphaTexSlot)
gli.Uniform1f(lgar.globalAlpha, float32(cv.state.globalAlpha))
return lgar.vertex, lgar.alphaTexCoord
}
if rg := style.radialGradient; rg != nil {
rg.load()
gli.ActiveTexture(gl_TEXTURE0)
gli.BindTexture(gl_TEXTURE_2D, rg.tex)
gli.UseProgram(rgar.id)
from := cv.tf(rg.from)
to := cv.tf(rg.to)
dir := to.sub(from)
length := dir.len()
dir = dir.divf(length)
gli.Uniform2f(rgar.canvasSize, float32(cv.fw), float32(cv.fh))
inv := cv.state.transform.invert().f32()
gli.UniformMatrix3fv(rgar.invmat, 1, false, &inv[0])
gli.Uniform2f(rgar.from, float32(from[0]), float32(from[1]))
gli.Uniform2f(rgar.to, float32(to[0]), float32(to[1]))
gli.Uniform2f(rgar.dir, float32(dir[0]), float32(dir[1]))
gli.Uniform1f(rgar.radFrom, float32(rg.radFrom))
gli.Uniform1f(rgar.radTo, float32(rg.radTo))
gli.Uniform1f(rgar.len, float32(length))
gli.Uniform1i(rgar.gradient, 0)
gli.Uniform1i(rgar.alphaTex, alphaTexSlot)
gli.Uniform1f(rgar.globalAlpha, float32(cv.state.globalAlpha))
return rgar.vertex, rgar.alphaTexCoord
}
if img := style.image; img != nil {
gli.UseProgram(ipar.id)
gli.ActiveTexture(gl_TEXTURE0)
gli.BindTexture(gl_TEXTURE_2D, img.tex)
gli.Uniform2f(ipar.canvasSize, float32(cv.fw), float32(cv.fh))
inv := cv.state.transform.invert().f32()
gli.UniformMatrix3fv(ipar.invmat, 1, false, &inv[0])
gli.Uniform2f(ipar.imageSize, float32(img.w), float32(img.h))
gli.Uniform1i(ipar.image, 0)
gli.Uniform1i(ipar.alphaTex, alphaTexSlot)
gli.Uniform1f(ipar.globalAlpha, float32(cv.state.globalAlpha))
return ipar.vertex, ipar.alphaTexCoord
}
gli.UseProgram(sar.id)
gli.Uniform2f(sar.canvasSize, float32(cv.fw), float32(cv.fh))
c := style.color
gli.Uniform4f(sar.color, float32(c.r), float32(c.g), float32(c.b), float32(c.a))
gli.Uniform1i(sar.alphaTex, alphaTexSlot)
gli.Uniform1f(sar.globalAlpha, float32(cv.state.globalAlpha))
return sar.vertex, sar.alphaTexCoord
}
func (cv *Canvas) enableTextureRenderTarget(offscr *offscreenBuffer) {
if offscr.w != cv.w || offscr.h != cv.h {
if offscr.w != 0 && offscr.h != 0 {
gli.DeleteTextures(1, &offscr.tex)
gli.DeleteFramebuffers(1, &offscr.frameBuf)
gli.DeleteRenderbuffers(1, &offscr.renderStencilBuf)
}
offscr.w = cv.w
offscr.h = cv.h
gli.ActiveTexture(gl_TEXTURE0)
gli.GenTextures(1, &offscr.tex)
gli.BindTexture(gl_TEXTURE_2D, offscr.tex)
// todo do non-power-of-two textures work everywhere?
if offscr.alpha {
gli.TexImage2D(gl_TEXTURE_2D, 0, gl_RGBA, int32(cv.w), int32(cv.h), 0, gl_RGBA, gl_UNSIGNED_BYTE, nil)
stl.Gradient.X0 = from[0]
stl.Gradient.Y0 = from[1]
stl.Gradient.X1 = to[0]
stl.Gradient.Y1 = to[1]
stl.Gradient.RadFrom = rg.radFrom
stl.Gradient.RadTo = rg.radTo
stl.RadialGradient = rg.grad
} else if ip := s.imagePattern; ip != nil {
if ip.ip == nil {
stl.Color = color.RGBA{}
} else {
gli.TexImage2D(gl_TEXTURE_2D, 0, gl_RGB, int32(cv.w), int32(cv.h), 0, gl_RGB, gl_UNSIGNED_BYTE, nil)
ip.ip.Replace(ip.data(cv.state.transform))
stl.ImagePattern = ip.ip
}
gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, gl_NEAREST)
gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, gl_NEAREST)
gli.GenFramebuffers(1, &offscr.frameBuf)
gli.BindFramebuffer(gl_FRAMEBUFFER, offscr.frameBuf)
gli.GenRenderbuffers(1, &offscr.renderStencilBuf)
gli.BindRenderbuffer(gl_RENDERBUFFER, offscr.renderStencilBuf)
gli.RenderbufferStorage(gl_RENDERBUFFER, gl_DEPTH24_STENCIL8, int32(cv.w), int32(cv.h))
gli.FramebufferRenderbuffer(gl_FRAMEBUFFER, gl_DEPTH_STENCIL_ATTACHMENT, gl_RENDERBUFFER, offscr.renderStencilBuf)
gli.FramebufferTexture(gl_FRAMEBUFFER, gl_COLOR_ATTACHMENT0, offscr.tex, 0)
if err := gli.CheckFramebufferStatus(gl_FRAMEBUFFER); err != gl_FRAMEBUFFER_COMPLETE {
// todo this should maybe not panic
panic(fmt.Sprintf("Failed to set up framebuffer for offscreen texture: %x", err))
}
gli.Clear(gl_COLOR_BUFFER_BIT | gl_STENCIL_BUFFER_BIT)
} else {
gli.BindFramebuffer(gl_FRAMEBUFFER, offscr.frameBuf)
}
}
func (cv *Canvas) disableTextureRenderTarget() {
if cv.offscreen {
cv.enableTextureRenderTarget(&cv.offscrBuf)
} else {
gli.BindFramebuffer(gl_FRAMEBUFFER, 0)
alpha *= float64(s.color.A) / 255
}
stl.Color.A = uint8(alpha * 255)
return stl
}
// SetLineWidth sets the line width for any line drawing calls
@ -663,30 +295,15 @@ func (cv *Canvas) SetLineWidth(width float64) {
// with the LoadFont function, a filename for a font to load (which will be
// cached), or nil, in which case the first loaded font will be used
func (cv *Canvas) SetFont(src interface{}, size float64) {
cv.state.fontSize = fixed.Int26_6(math.Round(size * 64))
if src == nil {
cv.state.font = defaultFont
} else {
switch v := src.(type) {
case *Font:
cv.state.font = v
case *truetype.Font:
cv.state.font = &Font{font: v}
case string:
if f, ok := fonts[v]; ok {
cv.state.font = f
} else {
f, err := LoadFont(v)
if err != nil {
fmt.Fprintf(os.Stderr, "Error loading font %s: %v\n", v, err)
fonts[v] = nil
} else {
fonts[v] = f
cv.state.font = f
}
}
}
cv.state.font = cv.getFont(src)
}
cv.state.fontSize = size
fontFace := truetype.NewFace(cv.state.font.font, &truetype.Options{Size: size})
cv.state.fontMetrics = fontFace.Metrics()
}
// SetTextAlign sets the text align for any text drawing calls.
@ -695,16 +312,23 @@ func (cv *Canvas) SetTextAlign(align textAlign) {
cv.state.textAlign = align
}
// SetTextBaseline sets the text baseline for any text drawing calls.
// The value can be Alphabetic (default), Top, Hanging, Middle,
// Ideographic, or Bottom
func (cv *Canvas) SetTextBaseline(baseline textBaseline) {
cv.state.textBaseline = baseline
}
// SetLineJoin sets the style of line joints for rendering a path with Stroke.
// The value can be Miter, Bevel, or Round
func (cv *Canvas) SetLineJoin(join lineJoin) {
cv.state.lineJoin = join
}
// SetLineEnd sets the style of line endings for rendering a path with Stroke
// SetLineCap sets the style of line endings for rendering a path with Stroke
// The value can be Butt, Square, or Round
func (cv *Canvas) SetLineEnd(end lineEnd) {
cv.state.lineEnd = end
func (cv *Canvas) SetLineCap(cap lineCap) {
cv.state.lineCap = cap
}
// SetLineDash sets the line dash style
@ -724,6 +348,7 @@ func (cv *Canvas) SetLineDash(dash []float64) {
cv.state.lineDashOffset = 0
}
// SetLineDashOffset sets the line dash offset
func (cv *Canvas) SetLineDashOffset(offset float64) {
cv.state.lineDashOffset = offset
}
@ -757,43 +382,39 @@ func (cv *Canvas) Restore() {
if l <= 0 {
return
}
cv.state.scissor = scissor{}
cv.applyScissor()
gli.StencilMask(0xFF)
gli.Clear(gl_STENCIL_BUFFER_BIT)
cv.b.ClearClip()
for _, st := range cv.stateStack {
if len(st.clip) > 0 {
cv.clip(st.clip)
if len(st.clip.p) > 0 {
cv.clip(&st.clip, backendbase.MatIdentity)
}
}
cv.state = cv.stateStack[l-1]
cv.stateStack = cv.stateStack[:l-1]
cv.applyScissor()
}
// Scale updates the current transformation with a scaling by the given values
func (cv *Canvas) Scale(x, y float64) {
cv.state.transform = matScale(vec{x, y}).mul(cv.state.transform)
cv.state.transform = backendbase.MatScale(backendbase.Vec{x, y}).Mul(cv.state.transform)
}
// Translate updates the current transformation with a translation by the given values
func (cv *Canvas) Translate(x, y float64) {
cv.state.transform = matTranslate(vec{x, y}).mul(cv.state.transform)
cv.state.transform = backendbase.MatTranslate(backendbase.Vec{x, y}).Mul(cv.state.transform)
}
// Rotate updates the current transformation with a rotation by the given angle
func (cv *Canvas) Rotate(angle float64) {
cv.state.transform = matRotate(angle).mul(cv.state.transform)
cv.state.transform = backendbase.MatRotate(angle).Mul(cv.state.transform)
}
// Transform updates the current transformation with the given matrix
func (cv *Canvas) Transform(a, b, c, d, e, f float64) {
cv.state.transform = mat{a, b, 0, c, d, 0, e, f, 1}.mul(cv.state.transform)
cv.state.transform = backendbase.Mat{a, b, c, d, e, f}.Mul(cv.state.transform)
}
// SetTransform replaces the current transformation with the given matrix
func (cv *Canvas) SetTransform(a, b, c, d, e, f float64) {
cv.state.transform = mat{a, b, 0, c, d, 0, e, f, 1}
cv.state.transform = backendbase.Mat{a, b, c, d, e, f}
}
// SetShadowColor sets the color of the shadow. If it is fully transparent (default)
@ -825,3 +446,103 @@ func (cv *Canvas) SetShadowOffset(x, y float64) {
func (cv *Canvas) SetShadowBlur(r float64) {
cv.state.shadowBlur = r
}
// IsPointInPath returns true if the point is in the current
// path according to the given rule
func (cv *Canvas) IsPointInPath(x, y float64, rule pathRule) bool {
return cv.path.IsPointInPath(x, y, rule)
}
// IsPointInStroke returns true if the point is in the current
// path stroke
func (cv *Canvas) IsPointInStroke(x, y float64) bool {
if len(cv.path.p) == 0 {
return false
}
var triBuf [500]backendbase.Vec
tris := cv.strokeTris(
&cv.path,
cv.state.transform,
cv.state.transform.Invert(),
true,
triBuf[:0],
)
pt := backendbase.Vec{x, y}
for i := 0; i < len(tris); i += 3 {
a := backendbase.Vec{tris[i][0], tris[i][1]}
b := backendbase.Vec{tris[i+1][0], tris[i+1][1]}
c := backendbase.Vec{tris[i+2][0], tris[i+2][1]}
if triangleContainsPoint(a, b, c, pt) {
return true
}
}
return false
}
func (cv *Canvas) reduceCache(keepSize, rec int) {
if rec > 100 {
return
}
var total int
oldest := time.Now()
var oldestFontKey fontKey
var oldestFontKey2 *Font
var oldestFontKey3 *Font
var oldestImageKey interface{}
for src, img := range cv.images {
w, h := img.img.Size()
total += w * h * 4
if img.lastUsed.Before(oldest) {
oldest = img.lastUsed
oldestImageKey = src
}
}
for key, frctx := range cv.fontCtxs {
total += frctx.ctx.cacheSize()
if frctx.lastUsed.Before(oldest) {
oldest = frctx.lastUsed
oldestFontKey = key
oldestImageKey = nil
}
}
for fnt, cache := range cv.fontPathCache {
total += cache.size()
if cache.lastUsed.Before(oldest) {
oldest = cache.lastUsed
oldestFontKey2 = fnt
oldestFontKey = fontKey{}
oldestImageKey = nil
}
}
for fnt, cache := range cv.fontTriCache {
total += cache.size()
if cache.lastUsed.Before(oldest) {
oldest = cache.lastUsed
oldestFontKey3 = fnt
oldestFontKey2 = nil
oldestFontKey = fontKey{}
oldestImageKey = nil
}
}
if total <= keepSize {
return
}
if oldestImageKey != nil {
cv.images[oldestImageKey].Delete()
delete(cv.images, oldestImageKey)
} else if oldestFontKey2 != nil {
delete(cv.fontPathCache, oldestFontKey2)
} else if oldestFontKey3 != nil {
delete(cv.fontTriCache, oldestFontKey3)
} else {
cv.fontCtxs[oldestFontKey].ctx = nil
delete(cv.fontCtxs, oldestFontKey)
}
cv.reduceCache(keepSize, rec+1)
}

View file

@ -1,404 +0,0 @@
package canvas_test
import (
"fmt"
"image/png"
"os"
"runtime"
"strings"
"testing"
"github.com/go-gl/gl/v3.2-core/gl"
"github.com/tfriedel6/canvas"
_ "github.com/tfriedel6/canvas/glimpl/xmobile"
"github.com/tfriedel6/canvas/sdlcanvas"
)
func run(t *testing.T, fn func(cv *canvas.Canvas)) {
wnd, cv2, err := sdlcanvas.CreateWindow(100, 100, "test")
if err != nil {
t.Fatalf("Failed to crete window: %v", err)
return
}
defer wnd.Destroy()
cv := canvas.NewOffscreen(100, 100)
gl.Disable(gl.MULTISAMPLE)
wnd.StartFrame()
cv.ClearRect(0, 0, 100, 100)
fn(cv)
img := cv.GetImageData(0, 0, 100, 100)
cv2.DrawImage(cv)
img2 := cv2.GetImageData(0, 0, 100, 100)
caller, _, _, ok := runtime.Caller(1)
if !ok {
t.Fatal("Failed to get caller")
}
callerFunc := runtime.FuncForPC(caller)
if callerFunc == nil {
t.Fatal("Failed to get caller function")
}
const prefix = "canvas_test.Test"
callerFuncName := callerFunc.Name()
callerFuncName = callerFuncName[strings.Index(callerFuncName, prefix)+len(prefix):]
fileName := fmt.Sprintf("testdata/%s.png", callerFuncName)
_, err = os.Stat(fileName)
if err != nil && !os.IsNotExist(err) {
t.Fatalf("Failed to stat file \"%s\": %v", fileName, err)
}
if os.IsNotExist(err) {
f, err := os.OpenFile(fileName, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0777)
if err != nil {
t.Fatalf("Failed to create file \"%s\"", fileName)
}
defer f.Close()
err = png.Encode(f, img)
if err != nil {
t.Fatalf("Failed to encode PNG")
}
return
}
f, err := os.Open(fileName)
if err != nil {
t.Fatalf("Failed to open file \"%s\": %v", fileName, err)
}
defer f.Close()
refImg, err := png.Decode(f)
if err != nil {
t.Fatalf("Failed to decode file \"%s\": %v", fileName, err)
}
if b := img.Bounds(); b.Min.X != 0 || b.Min.Y != 0 || b.Max.X != 100 || b.Max.Y != 100 {
t.Fatalf("Image bounds must be 0,0,100,100")
}
if b := refImg.Bounds(); b.Min.X != 0 || b.Min.Y != 0 || b.Max.X != 100 || b.Max.Y != 100 {
t.Fatalf("Image bounds must be 0,0,100,100")
}
for y := 0; y < 100; y++ {
for x := 0; x < 100; x++ {
r1, g1, b1, a1 := img.At(x, y).RGBA()
r2, g2, b2, a2 := img2.At(x, y).RGBA()
r3, g3, b3, a3 := refImg.At(x, y).RGBA()
if r1 != r3 || g1 != g3 || b1 != b3 || a1 != a3 {
t.FailNow()
}
if r2 != r3 || g2 != g3 || b2 != b3 || a2 != a3 {
t.FailNow()
}
}
}
}
func TestFillRect(t *testing.T) {
run(t, func(cv *canvas.Canvas) {
cv.SetFillStyle("#F00")
cv.FillRect(10, 10, 10, 10)
cv.SetFillStyle("#F008")
cv.FillRect(30, 10, 10, 10)
cv.SetFillStyle(64, 96, 128, 160)
cv.FillRect(50, 10, 10, 10)
cv.SetFillStyle(0.5, 0.7, 0.2, 0.8)
cv.FillRect(70, 10, 10, 10)
})
}
func TestFillConvexPath(t *testing.T) {
run(t, func(cv *canvas.Canvas) {
cv.SetFillStyle("#0F0")
cv.BeginPath()
cv.MoveTo(20, 20)
cv.LineTo(60, 20)
cv.LineTo(80, 80)
cv.LineTo(40, 80)
cv.ClosePath()
cv.Fill()
})
}
func TestFillConcavePath(t *testing.T) {
run(t, func(cv *canvas.Canvas) {
cv.SetFillStyle("#0F0")
cv.BeginPath()
cv.MoveTo(20, 20)
cv.LineTo(60, 20)
cv.LineTo(60, 60)
cv.LineTo(50, 60)
cv.LineTo(50, 40)
cv.LineTo(20, 40)
cv.ClosePath()
cv.Fill()
})
}
func TestDrawPath(t *testing.T) {
run(t, func(cv *canvas.Canvas) {
cv.SetStrokeStyle("#00F")
cv.SetLineJoin(canvas.Miter)
cv.SetLineWidth(8)
cv.BeginPath()
cv.MoveTo(10, 10)
cv.LineTo(30, 10)
cv.LineTo(30, 30)
cv.LineTo(10, 30)
cv.ClosePath()
cv.Stroke()
cv.SetLineJoin(canvas.Round)
cv.BeginPath()
cv.MoveTo(40, 10)
cv.LineTo(60, 10)
cv.LineTo(60, 30)
cv.LineTo(40, 30)
cv.ClosePath()
cv.Stroke()
cv.SetLineJoin(canvas.Bevel)
cv.BeginPath()
cv.MoveTo(70, 10)
cv.LineTo(90, 10)
cv.LineTo(90, 30)
cv.LineTo(70, 30)
cv.ClosePath()
cv.Stroke()
cv.SetLineEnd(canvas.Butt)
cv.BeginPath()
cv.MoveTo(10, 40)
cv.LineTo(30, 40)
cv.LineTo(30, 60)
cv.LineTo(10, 60)
cv.Stroke()
cv.SetLineEnd(canvas.Round)
cv.BeginPath()
cv.MoveTo(40, 40)
cv.LineTo(60, 40)
cv.LineTo(60, 60)
cv.LineTo(40, 60)
cv.Stroke()
cv.SetLineEnd(canvas.Square)
cv.BeginPath()
cv.MoveTo(70, 40)
cv.LineTo(90, 40)
cv.LineTo(90, 60)
cv.LineTo(70, 60)
cv.Stroke()
})
}
func TestMiterLimit(t *testing.T) {
run(t, func(cv *canvas.Canvas) {
cv.SetStrokeStyle("#0F0")
cv.SetLineJoin(canvas.Miter)
cv.SetLineWidth(2.5)
cv.SetMiterLimit(30)
y, step := 20.0, 4.0
for i := 0; i < 20; i++ {
cv.LineTo(20, y)
y += step
cv.LineTo(80, y)
y += step
step *= 0.9
}
cv.Stroke()
})
}
func TestLineDash(t *testing.T) {
run(t, func(cv *canvas.Canvas) {
cv.SetStrokeStyle("#0F0")
cv.SetLineWidth(2.5)
cv.SetLineDash([]float64{4, 6, 8})
cv.BeginPath()
cv.MoveTo(20, 20)
cv.LineTo(80, 20)
cv.LineTo(80, 80)
cv.LineTo(50, 80)
cv.LineTo(50, 50)
cv.LineTo(20, 50)
cv.ClosePath()
cv.MoveTo(30, 30)
cv.LineTo(70, 30)
cv.LineTo(70, 70)
cv.LineTo(60, 70)
cv.LineTo(60, 40)
cv.LineTo(30, 40)
cv.ClosePath()
cv.Stroke()
ld := cv.GetLineDash()
if ld[0] != 4 || ld[1] != 6 || ld[2] != 8 || ld[3] != 4 || ld[4] != 6 || ld[5] != 8 {
t.Fail()
}
})
}
func TestLineDashOffset(t *testing.T) {
run(t, func(cv *canvas.Canvas) {
cv.SetStrokeStyle("#0F0")
cv.SetLineWidth(2.5)
cv.SetLineDash([]float64{4, 6, 8})
cv.SetLineDashOffset(5)
cv.BeginPath()
cv.MoveTo(20, 20)
cv.LineTo(80, 20)
cv.LineTo(80, 80)
cv.LineTo(50, 80)
cv.LineTo(50, 50)
cv.LineTo(20, 50)
cv.ClosePath()
cv.MoveTo(30, 30)
cv.LineTo(70, 30)
cv.LineTo(70, 70)
cv.LineTo(60, 70)
cv.LineTo(60, 40)
cv.LineTo(30, 40)
cv.ClosePath()
cv.Stroke()
ld := cv.GetLineDash()
if ld[0] != 4 || ld[1] != 6 || ld[2] != 8 || ld[3] != 4 || ld[4] != 6 || ld[5] != 8 {
t.Fail()
}
})
}
func TestCurves(t *testing.T) {
run(t, func(cv *canvas.Canvas) {
cv.SetStrokeStyle("#00F")
cv.SetLineWidth(2.5)
cv.BeginPath()
cv.Arc(30, 30, 15, 0, 4, false)
cv.ClosePath()
cv.MoveTo(30, 70)
cv.LineTo(40, 70)
cv.ArcTo(50, 70, 50, 55, 5)
cv.ArcTo(50, 40, 55, 40, 5)
cv.QuadraticCurveTo(70, 40, 80, 60)
cv.BezierCurveTo(70, 80, 60, 80, 50, 90)
cv.Stroke()
})
}
func TestAlpha(t *testing.T) {
run(t, func(cv *canvas.Canvas) {
cv.SetStrokeStyle("#F00")
cv.SetLineWidth(2.5)
cv.BeginPath()
cv.Arc(30, 30, 15, 0, 4, false)
cv.ClosePath()
cv.MoveTo(30, 70)
cv.LineTo(40, 70)
cv.ArcTo(50, 70, 50, 55, 5)
cv.ArcTo(50, 40, 55, 40, 5)
cv.QuadraticCurveTo(70, 40, 80, 60)
cv.BezierCurveTo(70, 80, 60, 80, 50, 90)
cv.Stroke()
cv.SetStrokeStyle("#0F08")
cv.SetLineWidth(5)
cv.BeginPath()
cv.MoveTo(10, 10)
cv.LineTo(90, 90)
cv.LineTo(90, 10)
cv.LineTo(10, 90)
cv.ClosePath()
cv.Stroke()
cv.SetGlobalAlpha(0.5)
cv.SetStrokeStyle("#FFF8")
cv.SetLineWidth(8)
cv.BeginPath()
cv.MoveTo(50, 10)
cv.LineTo(50, 90)
cv.Stroke()
})
}
func TestClosePath(t *testing.T) {
run(t, func(cv *canvas.Canvas) {
cv.SetStrokeStyle("#0F0")
cv.SetLineWidth(2.5)
cv.BeginPath()
cv.MoveTo(20, 20)
cv.LineTo(40, 20)
cv.LineTo(40, 40)
cv.LineTo(20, 40)
cv.ClosePath()
cv.MoveTo(60, 20)
cv.LineTo(80, 20)
cv.LineTo(80, 40)
cv.LineTo(60, 40)
cv.ClosePath()
cv.Stroke()
cv.SetFillStyle("#00F")
cv.BeginPath()
cv.MoveTo(20, 60)
cv.LineTo(40, 60)
cv.LineTo(40, 80)
cv.LineTo(20, 80)
cv.ClosePath()
cv.MoveTo(60, 60)
cv.LineTo(80, 60)
cv.LineTo(80, 80)
cv.LineTo(60, 80)
cv.ClosePath()
cv.Fill()
})
}
func TestLineDash2(t *testing.T) {
run(t, func(cv *canvas.Canvas) {
cv.SetStrokeStyle("#0F0")
cv.SetLineWidth(2.5)
cv.BeginPath()
cv.MoveTo(20, 20)
cv.LineTo(40, 20)
cv.LineTo(40, 40)
cv.LineTo(20, 40)
cv.ClosePath()
cv.MoveTo(60, 20)
cv.LineTo(80, 20)
cv.LineTo(80, 40)
cv.LineTo(60, 40)
cv.ClosePath()
cv.SetLineDash([]float64{4, 4})
cv.MoveTo(20, 60)
cv.LineTo(40, 60)
cv.LineTo(40, 80)
cv.LineTo(20, 80)
cv.ClosePath()
cv.MoveTo(60, 60)
cv.LineTo(80, 60)
cv.LineTo(80, 80)
cv.LineTo(60, 80)
cv.ClosePath()
cv.Stroke()
})
}
func TestText(t *testing.T) {
run(t, func(cv *canvas.Canvas) {
cv.SetFont("testdata/Roboto-Light.ttf", 48)
cv.SetFillStyle("#F00")
cv.FillText("A BC", 0, 46)
cv.SetStrokeStyle("#0F0")
cv.SetLineWidth(1)
cv.StrokeText("D EF", 0, 90)
})
}

141
color.go
View file

@ -3,53 +3,11 @@ package canvas
import (
"fmt"
"image/color"
"math"
"strconv"
"strings"
)
type glColor struct {
r, g, b, a float64
}
func colorGoToGL(color color.Color) glColor {
ir, ig, ib, ia := color.RGBA()
var c glColor
c.r = float64(ir) / 65535
c.g = float64(ig) / 65535
c.b = float64(ib) / 65535
c.a = float64(ia) / 65535
return c
}
func colorGLToGo(c glColor) color.Color {
if c.r < 0 {
c.r = 0
} else if c.r > 1 {
c.r = 1
}
if c.g < 0 {
c.g = 0
} else if c.g > 1 {
c.g = 1
}
if c.b < 0 {
c.b = 0
} else if c.b > 1 {
c.b = 1
}
if c.a < 0 {
c.a = 0
} else if c.a > 1 {
c.a = 1
}
return color.RGBA{
R: uint8(c.r * 255),
G: uint8(c.g * 255),
B: uint8(c.b * 255),
A: uint8(c.a * 255),
}
}
func parseHexRune(rn rune) (int, bool) {
switch {
case rn >= '0' && rn <= '9':
@ -74,18 +32,18 @@ func parseHexRunePair(rn1, rn2 rune) (int, bool) {
return i1*16 + i2, true
}
func parseColorComponent(value interface{}) (float64, bool) {
func parseColorComponent(value interface{}) (uint8, bool) {
switch v := value.(type) {
case float32:
return float64(v), true
return uint8(math.Floor(float64(v) * 255)), true
case float64:
return v, true
return uint8(math.Floor(v * 255)), true
case int:
return float64(v) / 255, true
return uint8(v), true
case uint:
return float64(v) / 255, true
return uint8(v), true
case uint8:
return float64(v) / 255, true
return v, true
case string:
if len(v) == 0 {
return 0, false
@ -99,7 +57,7 @@ func parseColorComponent(value interface{}) (float64, bool) {
if err != nil {
return 0, false
}
return float64(conv) / 255, true
return uint8(conv), true
} else if strings.ContainsRune(v, '.') {
conv, err := strconv.ParseFloat(v, 32)
if err != nil {
@ -110,45 +68,78 @@ func parseColorComponent(value interface{}) (float64, bool) {
} else if conv > 1 {
conv = 1
}
return float64(conv), true
return uint8(math.Round(conv * 255.0)), true
} else {
conv, err := strconv.ParseUint(v, 10, 8)
if err != nil {
return 0, false
}
return float64(conv) / 255, true
return uint8(conv), true
}
}
return 0, false
}
func parseColor(value ...interface{}) (c glColor, ok bool) {
func parseColor(value ...interface{}) (c color.RGBA, ok bool) {
if len(value) == 1 {
switch v := value[0].(type) {
case color.Color:
c = colorGoToGL(v)
r, g, b, a := v.RGBA()
c = color.RGBA{R: uint8(r >> 8), G: uint8(g >> 8), B: uint8(b >> 8), A: uint8(a >> 8)}
ok = true
return
case [3]float32:
return glColor{r: float64(v[0]), g: float64(v[1]), b: float64(v[2]), a: 1}, true
return color.RGBA{
R: uint8(math.Floor(float64(v[0] * 255))),
G: uint8(math.Floor(float64(v[1] * 255))),
B: uint8(math.Floor(float64(v[2] * 255))),
A: 255}, true
case [4]float32:
return glColor{r: float64(v[0]), g: float64(v[1]), b: float64(v[2]), a: float64(v[3])}, true
return color.RGBA{
R: uint8(math.Floor(float64(v[0] * 255))),
G: uint8(math.Floor(float64(v[1] * 255))),
B: uint8(math.Floor(float64(v[2] * 255))),
A: uint8(math.Floor(float64(v[3] * 255)))}, true
case [3]float64:
return glColor{r: v[0], g: v[1], b: v[2], a: 1}, true
return color.RGBA{
R: uint8(math.Floor(v[0] * 255)),
G: uint8(math.Floor(v[1] * 255)),
B: uint8(math.Floor(v[2] * 255)),
A: 255}, true
case [4]float64:
return glColor{r: v[0], g: v[1], b: v[2], a: v[3]}, true
return color.RGBA{
R: uint8(math.Floor(v[0] * 255)),
G: uint8(math.Floor(v[1] * 255)),
B: uint8(math.Floor(v[2] * 255)),
A: uint8(math.Floor(v[3] * 255))}, true
case [3]int:
return glColor{r: float64(v[0]) / 255, g: float64(v[1]) / 255, b: float64(v[2]) / 255, a: 1}, true
return color.RGBA{
R: uint8(v[0]),
G: uint8(v[1]),
B: uint8(v[2]),
A: 255}, true
case [4]int:
return glColor{r: float64(v[0]) / 255, g: float64(v[1]) / 255, b: float64(v[2]) / 255, a: float64(v[3]) / 255}, true
return color.RGBA{
R: uint8(v[0]),
G: uint8(v[1]),
B: uint8(v[2]),
A: uint8(v[3])}, true
case [3]uint:
return glColor{r: float64(v[0]) / 255, g: float64(v[1]) / 255, b: float64(v[2]) / 255, a: 1}, true
return color.RGBA{
R: uint8(v[0]),
G: uint8(v[1]),
B: uint8(v[2]),
A: 255}, true
case [4]uint:
return glColor{r: float64(v[0]) / 255, g: float64(v[1]) / 255, b: float64(v[2]) / 255, a: float64(v[3]) / 255}, true
return color.RGBA{
R: uint8(v[0]),
G: uint8(v[1]),
B: uint8(v[2]),
A: uint8(v[3])}, true
case [3]uint8:
return glColor{r: float64(v[0]) / 255, g: float64(v[1]) / 255, b: float64(v[2]) / 255, a: 1}, true
return color.RGBA{R: v[0], G: v[1], B: v[2], A: 255}, true
case [4]uint8:
return glColor{r: float64(v[0]) / 255, g: float64(v[1]) / 255, b: float64(v[2]) / 255, a: float64(v[3]) / 255}, true
return color.RGBA{R: v[0], G: v[1], B: v[2], A: v[3]}, true
case string:
if len(v) == 0 {
return
@ -180,7 +171,7 @@ func parseColor(value ...interface{}) (c glColor, ok bool) {
}
ia = ia*16 + ia
}
return glColor{r: float64(ir) / 255, g: float64(ig) / 255, b: float64(ib) / 255, a: float64(ia) / 255}, true
return color.RGBA{R: uint8(ir), G: uint8(ig), B: uint8(ib), A: uint8(ia)}, true
} else if len(str) == 6 || len(str) == 8 {
var ir, ig, ib int
ia := 255
@ -202,25 +193,27 @@ func parseColor(value ...interface{}) (c glColor, ok bool) {
return
}
}
return glColor{r: float64(ir) / 255, g: float64(ig) / 255, b: float64(ib) / 255, a: float64(ia) / 255}, true
return color.RGBA{R: uint8(ir), G: uint8(ig), B: uint8(ib), A: uint8(ia)}, true
} else {
return
}
} else {
v = strings.Replace(v, " ", "", -1)
var ir, ig, ib, ia int
var ir, ig, ib int
n, err := fmt.Sscanf(v, "rgb(%d,%d,%d)", &ir, &ig, &ib)
if err == nil && n == 3 {
return glColor{r: float64(ir) / 255, g: float64(ig) / 255, b: float64(ib) / 255, a: 1}, true
return color.RGBA{R: uint8(ir), G: uint8(ig), B: uint8(ib), A: 255}, true
}
n, err = fmt.Sscanf(v, "rgba(%d,%d,%d,%d)", &ir, &ig, &ib, &ia)
var fa float64
n, err = fmt.Sscanf(v, "rgba(%d,%d,%d,%f)", &ir, &ig, &ib, &fa)
fa = math.Max(0, math.Min(1, fa))
if err == nil && n == 4 {
return glColor{r: float64(ir) / 255, g: float64(ig) / 255, b: float64(ib) / 255, a: float64(ia) / 255}, true
return color.RGBA{R: uint8(ir), G: uint8(ig), B: uint8(ib), A: uint8(fa * 255)}, true
}
}
}
} else if len(value) == 3 || len(value) == 4 {
var pr, pg, pb, pa float64
var pr, pg, pb, pa uint8
pr, ok = parseColorComponent(value[0])
if !ok {
return
@ -239,10 +232,10 @@ func parseColor(value ...interface{}) (c glColor, ok bool) {
return
}
} else {
pa = 1
pa = 255
}
return glColor{r: pr, g: pg, b: pb, a: pa}, true
return color.RGBA{R: pr, G: pg, B: pb, A: pa}, true
}
return glColor{r: 0, g: 0, b: 0, a: 1}, false
return color.RGBA{A: 255}, false
}

909
earcut.go Normal file
View file

@ -0,0 +1,909 @@
package canvas
import (
"math"
"sort"
"git.mstar.dev/mstar/canvas/backend/backendbase"
)
// Go port of https://github.com/mapbox/earcut.hpp
type node struct {
i int
x, y float64
// previous and next vertice nodes in a polygon ring
prev *node
next *node
// z-order curve value
z int
// previous and next nodes in z-order
prevZ *node
nextZ *node
// indicates whether this is a steiner point
steiner bool
}
type earcut struct {
indices []int
vertices int
hashing bool
minX, minY float64
maxX, maxY float64
invSize float64
nodes []node
}
func (ec *earcut) run(points [][]backendbase.Vec) {
if len(points) == 0 {
return
}
var x, y float64
threshold := 80
ln := 0
for i := 0; threshold >= 0 && i < len(points); i++ {
threshold -= len(points[i])
ln += len(points[i])
}
//estimate size of nodes and indices
ec.nodes = make([]node, 0, ln*3/2)
ec.indices = make([]int, 0, ln+len(points[0]))
ec.vertices = 0
outerNode := ec.linkedList(points[0], true)
if outerNode == nil || outerNode.prev == outerNode.next {
return
}
if len(points) > 1 {
outerNode = ec.eliminateHoles(points, outerNode)
}
// if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox
ec.hashing = threshold < 0
if ec.hashing {
p := outerNode.next
ec.minX, ec.maxX = outerNode.x, outerNode.x
ec.minY, ec.maxY = outerNode.y, outerNode.y
for {
x = p.x
y = p.y
ec.minX = math.Min(ec.minX, x)
ec.minY = math.Min(ec.minY, y)
ec.maxX = math.Min(ec.maxX, x)
ec.maxY = math.Min(ec.maxY, y)
p = p.next
if p != outerNode {
break
}
}
// minX, minY and size are later used to transform coords into integers for z-order calculation
ec.invSize = math.Max(ec.maxX-ec.minX, ec.maxY-ec.minY)
if ec.invSize != 0 {
ec.invSize = 1 / ec.invSize
}
}
ec.earcutLinked(outerNode, 0)
ec.nodes = ec.nodes[:0]
}
// create a circular doubly linked list from polygon points in the specified winding order
func (ec *earcut) linkedList(points []backendbase.Vec, clockwise bool) *node {
var sum float64
ln := len(points)
var i, j int
var last *node
// calculate original winding order of a polygon ring
if ln > 0 {
j = ln - 1
}
for i < ln {
p1 := points[i]
p2 := points[j]
p20 := p2[0]
p10 := p1[0]
p11 := p1[1]
p21 := p2[1]
sum += (p20 - p10) * (p11 + p21)
j = i
i++
}
// link points into circular doubly-linked list in the specified winding order
if clockwise == (sum > 0) {
for i := 0; i < ln; i++ {
last = ec.insertNode(ec.vertices+i, points[i], last)
}
} else {
for i = ln - 1; i >= 0; i-- {
last = ec.insertNode(ec.vertices+i, points[i], last)
}
}
if last != nil && ec.equals(last, last.next) {
ec.removeNode(last)
last = last.next
}
ec.vertices += ln
return last
}
// eliminate colinear or duplicate points
func (ec *earcut) filterPoints(start, end *node) *node {
if end == nil {
end = start
}
p := start
var again bool
for {
again = false
if !p.steiner && (ec.equals(p, p.next) || ec.area(p.prev, p, p.next) == 0) {
ec.removeNode(p)
p, end = p.prev, p.prev
if p == p.next {
break
}
again = true
} else {
p = p.next
}
if !again && p == end {
break
}
}
return end
}
// main ear slicing loop which triangulates a polygon (given as a linked list)
func (ec *earcut) earcutLinked(ear *node, pass int) {
if ear == nil {
return
}
// interlink polygon nodes in z-order
if pass == 0 && ec.hashing {
ec.indexCurve(ear)
}
stop := ear
var prev, next *node
iterations := 0
// iterate through ears, slicing them one by one
for ear.prev != ear.next {
iterations++
prev = ear.prev
next = ear.next
var e bool
if ec.hashing {
e = ec.isEarHashed(ear)
} else {
e = ec.isEar(ear)
}
if e {
// cut off the triangle
ec.indices = append(ec.indices, prev.i, ear.i, next.i)
ec.removeNode(ear)
// skipping the next vertice leads to less sliver triangles
ear = next.next
stop = next.next
continue
}
ear = next
// if we looped through the whole remaining polygon and can't find any more ears
if ear == stop {
// try filtering points and slicing again
if pass == 0 {
ec.earcutLinked(ec.filterPoints(ear, nil), 1)
} else if pass == 1 {
// if this didn't work, try curing all small self-intersections locally
ear = ec.cureLocalIntersections(ec.filterPoints(ear, nil))
ec.earcutLinked(ear, 2)
} else if pass == 2 {
// as a last resort, try splitting the remaining polygon into two
ec.splitEarcut(ear)
}
break
}
}
}
// check whether a polygon node forms a valid ear with adjacent nodes
func (ec *earcut) isEar(ear *node) bool {
a := ear.prev
b := ear
c := ear.next
if ec.area(a, b, c) >= 0 {
return false // reflex, can't be an ear
}
// now make sure we don't have other points inside the potential ear
p := ear.next.next
for p != ear.prev {
if ec.pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) &&
ec.area(p.prev, p, p.next) >= 0 {
return false
}
p = p.next
}
return true
}
func (ec *earcut) isEarHashed(ear *node) bool {
a := ear.prev
b := ear
c := ear.next
if ec.area(a, b, c) >= 0 {
return false // reflex, can't be an ear
}
// triangle bbox; min & max are calculated like this for speed
minTX := math.Min(a.x, math.Min(b.x, c.x))
minTY := math.Min(a.y, math.Min(b.y, c.y))
maxTX := math.Max(a.x, math.Max(b.x, c.x))
maxTY := math.Max(a.y, math.Max(b.y, c.y))
// z-order range for the current triangle bbox;
minZ := ec.zOrder(minTX, minTY)
maxZ := ec.zOrder(maxTX, maxTY)
// first look for points inside the triangle in increasing z-order
p := ear.nextZ
for p != nil && p.z <= maxZ {
if p != ear.prev && p != ear.next &&
ec.pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) &&
ec.area(p.prev, p, p.next) >= 0 {
return false
}
p = p.nextZ
}
// then look for points in decreasing z-order
p = ear.prevZ
for p != nil && p.z >= minZ {
if p != ear.prev && p != ear.next &&
ec.pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) &&
ec.area(p.prev, p, p.next) >= 0 {
return false
}
p = p.prevZ
}
return true
}
// go through all polygon nodes and cure small local self-intersections
func (ec *earcut) cureLocalIntersections(start *node) *node {
p := start
for {
a := p.prev
b := p.next.next
// a self-intersection where edge (v[i-1],v[i]) intersects (v[i+1],v[i+2])
if !ec.equals(a, b) && ec.intersects(a, p, p.next, b) && ec.locallyInside(a, b) &&
ec.locallyInside(b, a) {
ec.indices = append(ec.indices, a.i, p.i, b.i)
// remove two nodes involved
ec.removeNode(p)
ec.removeNode(p.next)
p, start = b, b
}
p = p.next
if p == start {
break
}
}
return ec.filterPoints(p, nil)
}
// try splitting polygon into two and triangulate them independently
func (ec *earcut) splitEarcut(start *node) {
// look for a valid diagonal that divides the polygon into two
a := start
for {
b := a.next.next
for b != a.prev {
if a.i != b.i && ec.isValidDiagonal(a, b) {
// split the polygon in two by the diagonal
c := ec.splitPolygon(a, b)
// filter colinear points around the cuts
a = ec.filterPoints(a, a.next)
c = ec.filterPoints(c, c.next)
// run earcut on each half
ec.earcutLinked(a, 0)
ec.earcutLinked(c, 0)
return
}
b = b.next
}
a = a.next
if a == start {
break
}
}
}
// link every hole into the outer loop, producing a single-ring polygon without holes
func (ec *earcut) eliminateHoles(points [][]backendbase.Vec, outerNode *node) *node {
ln := len(points)
queue := make([]*node, 0, ln)
for i := 1; i < ln; i++ {
list := ec.linkedList(points[i], false)
if list != nil {
if list == list.next {
list.steiner = true
}
queue = append(queue, ec.getLeftmost(list))
}
}
sort.Slice(queue, func(a, b int) bool {
return queue[a].x < queue[b].x
})
// process holes from left to right
for i := 0; i < len(queue); i++ {
ec.eliminateHole(queue[i], outerNode)
outerNode = ec.filterPoints(outerNode, outerNode.next)
}
return outerNode
}
// find a bridge between vertices that connects hole with an outer ring and and link it
func (ec *earcut) eliminateHole(hole, outerNode *node) {
outerNode = ec.findHoleBridge(hole, outerNode)
if outerNode != nil {
b := ec.splitPolygon(outerNode, hole)
// filter out colinear points around cuts
ec.filterPoints(outerNode, outerNode.next)
ec.filterPoints(b, b.next)
}
}
// David Eberly's algorithm for finding a bridge between hole and outer polygon
func (ec *earcut) findHoleBridge(hole, outerNode *node) *node {
p := outerNode
hx := hole.x
hy := hole.y
qx := math.Inf(-1)
var m *node
// find a segment intersected by a ray from the hole's leftmost Vertex to the left;
// segment's endpoint with lesser x will be potential connection Vertex
for {
if hy <= p.y && hy >= p.next.y && p.next.y != p.y {
x := p.x + (hy-p.y)*(p.next.x-p.x)/(p.next.y-p.y)
if x <= hx && x > qx {
qx = x
if x == hx {
if hy == p.y {
return p
}
if hy == p.next.y {
return p.next
}
}
if p.x < p.next.x {
m = p
} else {
m = p.next
}
}
}
p = p.next
if p == outerNode {
break
}
}
if m == nil {
return nil
}
if hx == qx {
return m // hole touches outer segment; pick leftmost endpoint
}
// look for points inside the triangle of hole Vertex, segment intersection and endpoint;
// if there are no points found, we have a valid connection;
// otherwise choose the Vertex of the minimum angle with the ray as connection Vertex
stop := m
tanMin := math.Inf(1)
tanCur := 0.0
p = m
mx := m.x
my := m.y
for {
var pt1, pt2 float64
if hy < my {
pt1 = hx
pt2 = qx
} else {
pt1 = qx
pt2 = hx
}
if hx >= p.x && p.x >= mx && hx != p.x &&
ec.pointInTriangle(pt1, hy, mx, my, pt2, hy, p.x, p.y) {
tanCur = math.Abs(hy-p.y) / (hx - p.x) // tangential
if ec.locallyInside(p, hole) &&
(tanCur < tanMin || (tanCur == tanMin && (p.x > m.x || ec.sectorContainsSector(m, p)))) {
m = p
tanMin = tanCur
}
}
p = p.next
if p == stop {
break
}
}
return m
}
// whether sector in vertex m contains sector in vertex p in the same coordinates
func (ec *earcut) sectorContainsSector(m, p *node) bool {
return ec.area(m.prev, m, p.prev) < 0 && ec.area(p.next, m, m.next) < 0
}
// interlink polygon nodes in z-order
func (ec *earcut) indexCurve(start *node) {
if start == nil {
panic("start must not be nil")
}
p := start
for {
if p.z <= 0 {
p.z = ec.zOrder(p.x, p.y)
}
p.prevZ = p.prev
p.nextZ = p.next
p = p.next
if p == start {
break
}
}
p.prevZ.nextZ = nil
p.prevZ = nil
ec.sortLinked(p)
}
// Simon Tatham's linked list merge sort algorithm
// http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html
func (ec *earcut) sortLinked(list *node) *node {
if list == nil {
panic("list must not be nil")
}
var p, q, e, tail *node
var i, numMerges, pSize, qSize int
inSize := 1
for {
p = list
list = nil
tail = nil
numMerges = 0
for p != nil {
numMerges++
q = p
pSize = 0
for i = 0; i < inSize; i++ {
pSize++
q = q.nextZ
if q == nil {
break
}
}
qSize = inSize
for pSize > 0 || (qSize > 0 && q != nil) {
if pSize == 0 {
e = q
q = q.nextZ
qSize--
} else if qSize == 0 || q == nil {
e = p
p = p.nextZ
pSize--
} else if p.z <= q.z {
e = p
p = p.nextZ
pSize--
} else {
e = q
q = q.nextZ
qSize--
}
if tail != nil {
tail.nextZ = e
} else {
list = e
}
e.prevZ = tail
tail = e
}
p = q
}
tail.nextZ = nil
if numMerges <= 1 {
return list
}
inSize *= 2
}
}
// z-order of a Vertex given coords and size of the data bounding box
func (ec *earcut) zOrder(x, y float64) int {
// coords are transformed into non-negative 15-bit integer range
x2 := int(32767.0 * (x - ec.minX) * ec.invSize)
y2 := int(32767.0 * (y - ec.minY) * ec.invSize)
x2 = (x2 | (x2 << 8)) & 0x00FF00FF
x2 = (x2 | (x2 << 4)) & 0x0F0F0F0F
x2 = (x2 | (x2 << 2)) & 0x33333333
x2 = (x2 | (x2 << 1)) & 0x55555555
y2 = (y2 | (y2 << 8)) & 0x00FF00FF
y2 = (y2 | (y2 << 4)) & 0x0F0F0F0F
y2 = (y2 | (y2 << 2)) & 0x33333333
y2 = (y2 | (y2 << 1)) & 0x55555555
return x2 | (y2 << 1)
}
// find the leftmost node of a polygon ring
func (ec *earcut) getLeftmost(start *node) *node {
p := start
leftmost := start
for {
if p.x < leftmost.x || (p.x == leftmost.x && p.y < leftmost.y) {
leftmost = p
}
p = p.next
if p == start {
break
}
}
return leftmost
}
// check if a point lies within a convex triangle
func (ec *earcut) pointInTriangle(ax, ay, bx, by, cx, cy, px, py float64) bool {
return (cx-px)*(ay-py)-(ax-px)*(cy-py) >= 0 &&
(ax-px)*(by-py)-(bx-px)*(ay-py) >= 0 &&
(bx-px)*(cy-py)-(cx-px)*(by-py) >= 0
}
// check if a diagonal between two polygon nodes is valid (lies in polygon interior)
func (ec *earcut) isValidDiagonal(a, b *node) bool {
return a.next.i != b.i && a.prev.i != b.i &&
!ec.intersectsPolygon(a, b) && // dones't intersect other edges
((ec.locallyInside(a, b) && ec.locallyInside(b, a) && ec.middleInside(a, b) && // locally visible
(ec.area(a.prev, a, b.prev) != 0.0 || ec.area(a, b.prev, b) != 0.0)) || // does not create opposite-facing sectors
(ec.equals(a, b) && ec.area(a.prev, a, a.next) > 0 && ec.area(b.prev, b, b.next) > 0)) // special zero-length case
}
// signed area of a triangle
func (ec *earcut) area(p, q, r *node) float64 {
return (q.y-p.y)*(r.x-q.x) - (q.x-p.x)*(r.y-q.y)
}
// check if two points are equal
func (ec *earcut) equals(p1, p2 *node) bool {
return p1.x == p2.x && p1.y == p2.y
}
// check if two segments intersect
func (ec *earcut) intersects(p1, q1, p2, q2 *node) bool {
o1 := ec.sign(ec.area(p1, q1, p2))
o2 := ec.sign(ec.area(p1, q1, q2))
o3 := ec.sign(ec.area(p2, q2, p1))
o4 := ec.sign(ec.area(p2, q2, q1))
if o1 != o2 && o3 != o4 {
return true // general case
}
if o1 == 0 && ec.onSegment(p1, p2, q1) {
// p1, q1 and p2 are collinear and p2 lies on p1q1
return true
}
if o2 == 0 && ec.onSegment(p1, q2, q1) {
// p1, q1 and q2 are collinear and q2 lies on p1q1
return true
}
if o3 == 0 && ec.onSegment(p2, p1, q2) {
// p2, q2 and p1 are collinear and p1 lies on p2q2
return true
}
if o4 == 0 && ec.onSegment(p2, q1, q2) {
// p2, q2 and q1 are collinear and q1 lies on p2q2
return true
}
return false
}
// for collinear points p, q, r, check if point q lies on segment pr
func (ec *earcut) onSegment(p, q, r *node) bool {
return q.x <= math.Max(p.x, r.x) &&
q.x >= math.Min(p.x, r.x) &&
q.y <= math.Max(p.y, r.y) &&
q.y >= math.Min(p.y, r.y)
}
func (ec *earcut) sign(val float64) int {
if val < 0 {
return -1
} else if val > 0 {
return 1
}
return 0
}
// check if a polygon diagonal intersects any polygon segments
func (ec *earcut) intersectsPolygon(a, b *node) bool {
p := a
for {
if p.i != a.i && p.next.i != a.i && p.i != b.i && p.next.i != b.i &&
ec.intersects(p, p.next, a, b) {
return true
}
p = p.next
if p == a {
break
}
}
return false
}
// check if a polygon diagonal is locally inside the polygon
func (ec *earcut) locallyInside(a, b *node) bool {
if ec.area(a.prev, a, a.next) < 0 {
return ec.area(a, b, a.next) >= 0 && ec.area(a, a.prev, b) >= 0
}
return ec.area(a, b, a.prev) < 0 || ec.area(a, a.next, b) < 0
}
// check if the middle Vertex of a polygon diagonal is inside the polygon
func (ec *earcut) middleInside(a, b *node) bool {
p := a
inside := false
px := (a.x + b.x) / 2
py := (a.y + b.y) / 2
for {
if ((p.y > py) != (p.next.y > py)) && p.next.y != p.y &&
(px < (p.next.x-p.x)*(py-p.y)/(p.next.y-p.y)+p.x) {
inside = !inside
}
p = p.next
if p == a {
break
}
}
return inside
}
// link two polygon vertices with a bridge; if the vertices belong to the same ring, it splits
// polygon into two; if one belongs to the outer ring and another to a hole, it merges it into a
// single ring
func (ec *earcut) splitPolygon(a, b *node) *node {
ec.nodes = append(ec.nodes, node{i: a.i, x: a.x, y: a.y})
a2 := &ec.nodes[len(ec.nodes)-1]
ec.nodes = append(ec.nodes, node{i: b.i, x: b.x, y: b.y})
b2 := &ec.nodes[len(ec.nodes)-1]
an := a.next
bp := b.prev
a.next = b
b.prev = a
a2.next = an
an.prev = a2
b2.next = a2
a2.prev = b2
bp.next = b2
b2.prev = bp
return b2
}
// create a node and util::optionally link it with previous one (in a circular doubly linked list)
func (ec *earcut) insertNode(i int, pt backendbase.Vec, last *node) *node {
ec.nodes = append(ec.nodes, node{i: i, x: pt[0], y: pt[1]})
p := &ec.nodes[len(ec.nodes)-1]
if last == nil {
p.prev = p
p.next = p
} else {
if last == nil {
panic("last must not be nil")
}
p.next = last.next
p.prev = last
last.next.prev = p
last.next = p
}
return p
}
func (ec *earcut) removeNode(p *node) {
p.next.prev = p.prev
p.prev.next = p.next
if p.prevZ != nil {
p.prevZ.nextZ = p.nextZ
}
if p.nextZ != nil {
p.nextZ.prevZ = p.prevZ
}
}
// sortFontContours takes the contours of a font glyph
// and checks whether each contour is the outside or a
// hole, and returns an array that is sorted so that
// it contains an index of an outer contour followed by
// any number of indices of hole contours followed by
// a terminating -1
func sortFontContours(contours [][]backendbase.Vec) []int {
type cut struct {
idx int
count int
}
type info struct {
cuts []cut
cutTotal int
outer bool
}
cutBuf := make([]cut, len(contours)*len(contours))
cinf := make([]info, len(contours))
for i := range contours {
cinf[i].cuts = cutBuf[i*len(contours) : i*len(contours)]
}
// go through each contour, pick one point on it, and
// project that point to the right. count the number of
// other contours that it cuts
for i, p1 := range contours {
pt := p1[0]
for j, p2 := range contours {
if i == j {
continue
}
for k := range p2 {
a, b := p2[k], p2[(k+1)%len(p2)]
if a == b {
continue
}
minY := math.Min(a[1], b[1])
maxY := math.Max(a[1], b[1])
if pt[1] <= minY || pt[1] > maxY {
continue
}
r := (pt[1] - a[1]) / (b[1] - a[1])
x := (b[0]-a[0])*r + a[0]
if x <= pt[0] {
continue
}
found := false
for l := range cinf[i].cuts {
if cinf[i].cuts[l].idx == j {
cinf[i].cuts[l].count++
found = true
break
}
}
if !found {
cinf[i].cuts = append(cinf[i].cuts, cut{idx: j, count: 1})
}
cinf[i].cutTotal++
}
}
}
// any contour with an even number of cuts is outer,
// odd number of cuts means it is a hole
for i := range cinf {
cinf[i].outer = cinf[i].cutTotal%2 == 0
}
// go through them again, pick any outer contour, then
// find any hole where the first outer contour it cuts
// an odd number of times is the picked contour and add
// it to the list of its holes
result := make([]int, 0, len(contours)*2)
for i := range cinf {
if !cinf[i].outer {
continue
}
result = append(result, i)
for j := range cinf {
if cinf[j].outer {
continue
}
for _, cut := range cinf[j].cuts {
if cut.count%2 == 0 {
continue
}
if cut.idx == i {
result = append(result, j)
break
}
}
}
result = append(result, -1)
}
return result
}

View file

@ -1,10 +0,0 @@
*.iml
.gradle
/local.properties
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
.DS_Store
/build
/captures
.externalNativeBuild

View file

@ -1,29 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<Objective-C-extensions>
<file>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Import" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Macro" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Typedef" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Enum" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Constant" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Global" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Struct" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="FunctionPredecl" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Function" />
</file>
<class>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Property" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Synthesize" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InitMethod" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="StaticMethod" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InstanceMethod" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="DeallocMethod" />
</class>
<extensions>
<pair source="cpp" header="h" fileNamingConvention="NONE" />
<pair source="c" header="h" fileNamingConvention="NONE" />
</extensions>
</Objective-C-extensions>
</code_scheme>
</component>

View file

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="PROJECT" charset="UTF-8" />
</component>
</project>

View file

@ -1,18 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />
</GradleProjectSettings>
</option>
</component>
</project>

View file

@ -1,34 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="NullableNotNullManager">
<option name="myDefaultNullable" value="android.support.annotation.Nullable" />
<option name="myDefaultNotNull" value="android.support.annotation.NonNull" />
<option name="myNullables">
<value>
<list size="5">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
<item index="2" class="java.lang.String" itemvalue="javax.annotation.CheckForNull" />
<item index="3" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
<item index="4" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
</list>
</value>
</option>
<option name="myNotNulls">
<value>
<list size="4">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
</list>
</value>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>

View file

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
</set>
</option>
</component>
</project>

View file

@ -1 +0,0 @@
/build

View file

@ -1,27 +0,0 @@
apply plugin: 'com.android.application'
android {
compileSdkVersion 26
defaultConfig {
applicationId "com.example.canvasandroidexample"
minSdkVersion 15
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation fileTree(dir: 'libs', include: ['*.aar'])
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}

View file

@ -1,21 +0,0 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View file

@ -1,26 +0,0 @@
package com.example.canvasandroidexample;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getTargetContext();
assertEquals("com.example.canvasandroidexample", appContext.getPackageName());
}
}

View file

@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.canvasandroidexample">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>

View file

@ -1,61 +0,0 @@
package com.example.canvasandroidexample;
import android.app.Activity;
import android.opengl.*;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import canvasandroidexample.Canvasandroidexample;
public class MainActivity extends Activity implements GLSurfaceView.Renderer {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GLSurfaceView view = new GLSurfaceView(this);
view.setEGLContextClientVersion(2);
view.setEGLConfigChooser(8, 8, 8, 8, 0, 8);
view.setRenderer(this);
view.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
int x = Math.round(event.getX());
int y = Math.round(event.getY());
if (event.getAction() == MotionEvent.ACTION_DOWN) {
Canvasandroidexample.touchEvent("down", x, y);
} else if (event.getAction() == MotionEvent.ACTION_UP) {
Canvasandroidexample.touchEvent("up", x, y);
} else if (event.getAction() == MotionEvent.ACTION_MOVE) {
Canvasandroidexample.touchEvent("move", x, y);
}
return true;
}
});
setContentView(view);
}
@Override
protected void onResume() {
super.onResume();
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
Canvasandroidexample.onSurfaceCreated();
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
Canvasandroidexample.onSurfaceChanged(width, height);
}
@Override
public void onDrawFrame(GL10 gl) {
Canvasandroidexample.onDrawFrame();
}
}

View file

@ -1,34 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportHeight="108"
android:viewportWidth="108">
<path
android:fillType="evenOdd"
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
android:strokeColor="#00000000"
android:strokeWidth="1">
<aapt:attr name="android:fillColor">
<gradient
android:endX="78.5885"
android:endY="90.9159"
android:startX="48.7653"
android:startY="61.0927"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0"/>
<item
android:color="#00000000"
android:offset="1.0"/>
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
android:strokeColor="#00000000"
android:strokeWidth="1"/>
</vector>

View file

@ -1,171 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportHeight="108"
android:viewportWidth="108">
<path
android:fillColor="#26A69A"
android:pathData="M0,0h108v108h-108z"/>
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8"/>
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8"/>
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8"/>
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8"/>
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8"/>
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8"/>
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8"/>
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8"/>
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8"/>
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8"/>
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8"/>
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8"/>
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8"/>
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8"/>
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8"/>
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8"/>
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8"/>
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8"/>
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8"/>
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8"/>
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8"/>
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8"/>
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8"/>
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8"/>
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8"/>
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8"/>
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8"/>
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8"/>
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8"/>
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8"/>
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8"/>
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8"/>
</vector>

View file

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

View file

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View file

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#3F51B5</color>
<color name="colorPrimaryDark">#303F9F</color>
<color name="colorAccent">#FF4081</color>
</resources>

View file

@ -1,3 +0,0 @@
<resources>
<string name="app_name">CanvasAndroidExample</string>
</resources>

View file

@ -1,8 +0,0 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar">
<!-- Customize your theme here. -->
</style>
</resources>

View file

@ -1,17 +0,0 @@
package com.example.canvasandroidexample;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() {
assertEquals(4, 2 + 2);
}
}

View file

@ -1,27 +0,0 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.2'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}

View file

@ -1,13 +0,0 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx1536m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true

View file

@ -1,6 +0,0 @@
#Thu May 10 15:38:02 CEST 2018
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip

View file

@ -1,172 +0,0 @@
#!/usr/bin/env sh
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"

View file

@ -1,84 +0,0 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View file

@ -1 +0,0 @@
include ':app'

View file

@ -1 +0,0 @@
The go bindings are generated with the ```gomobile bind -target android``` command, which results in a .aar and a .jar file. These should be placed in the CanvasAndroidExample/app/libs directory, and then the project should compile.

View file

@ -1,44 +0,0 @@
package canvasandroidexample
import (
"math"
"time"
"github.com/tfriedel6/canvas"
"github.com/tfriedel6/canvas/glimpl/android"
)
var cv *canvas.Canvas
var mx, my float64
func TouchEvent(typ string, x, y int) {
mx, my = float64(x), float64(y)
}
func OnSurfaceCreated() {
}
func OnSurfaceChanged(w, h int) {
err := canvas.LoadGL(glimplandroid.GLImpl{})
if err != nil {
time.Sleep(100 * time.Millisecond)
panic(err)
}
cv = canvas.New(0, 0, w, h)
}
func OnDrawFrame() {
if cv == nil {
return
}
w, h := float64(cv.Width()), float64(cv.Height())
cv.SetFillStyle("#000")
cv.FillRect(0, 0, w, h)
cv.SetFillStyle("#0F0")
cv.FillRect(w*0.25, h*0.25, w*0.5, h*0.5)
cv.SetLineWidth(6)
sqrSize := math.Min(w, h) * 0.1
cv.SetStrokeStyle("#F00")
cv.StrokeRect(mx-sqrSize/2, my-sqrSize/2, sqrSize, sqrSize)
}

View file

@ -1,125 +0,0 @@
package main
import (
"image/color"
"log"
"math"
"github.com/tfriedel6/canvas"
"github.com/tfriedel6/canvas/sdlcanvas"
)
func main() {
wnd, cv, err := sdlcanvas.CreateWindow(1280, 720, "Canvas Example")
if err != nil {
log.Println(err)
return
}
defer wnd.Destroy()
lg := canvas.NewLinearGradient(320, 200, 480, 520)
lg.AddColorStop(0, "#ff000040")
lg.AddColorStop(1, "#00ff0040")
lg.AddColorStop(0.5, "#0000ff40")
rg := canvas.NewRadialGradient(540, 300, 80, 740, 300, 100)
rg.AddColorStop(0, "#ff0000")
rg.AddColorStop(1, "#00ff00")
rg.AddColorStop(0.5, "#0000ff")
wnd.SizeChange = func(w, h int) {
cv.SetBounds(0, 0, w, h)
}
wnd.MainLoop(func() {
w, h := float64(cv.Width()), float64(cv.Height())
// Clear the screen
cv.SetFillStyle("#000")
cv.FillRect(0, 0, w, h)
// Draw lines with different colors and line thickness
for x := 1.0; x < 10.5; x += 1.0 {
cv.SetStrokeStyle(int(x*25), 255, 255)
cv.SetLineWidth(x)
cv.BeginPath()
cv.MoveTo(x*10+20, 20)
cv.LineTo(x*10+20, 120)
cv.Stroke()
}
// Draw a path
cv.BeginPath()
cv.MoveTo(160, 20)
cv.LineTo(180, 20)
cv.LineTo(180, 40)
cv.LineTo(200, 40)
cv.LineTo(200, 60)
cv.LineTo(220, 60)
cv.LineTo(220, 80)
cv.LineTo(240, 80)
cv.LineTo(240, 100)
cv.LineTo(260, 100)
cv.LineTo(260, 120)
cv.ArcTo(160, 120, 160, 100, 20)
cv.ClosePath()
cv.SetStrokeStyle(color.RGBA{R: 255, G: 128, B: 128, A: 255})
cv.SetLineWidth(4)
cv.Stroke()
// Fill a polygon
cv.BeginPath()
cv.MoveTo(300, 20)
cv.LineTo(340, 20)
cv.QuadraticCurveTo(370, 20, 370, 50)
cv.QuadraticCurveTo(370, 80, 400, 80)
cv.LineTo(400, 80)
cv.LineTo(400, 120)
cv.LineTo(360, 120)
cv.BezierCurveTo(330, 120, 330, 80, 300, 80)
cv.ClosePath()
cv.SetFillStyle(color.RGBA{R: 128, G: 255, B: 128, A: 255})
cv.Fill()
// Draw with alpha
cv.SetGlobalAlpha(0.5)
cv.SetFillStyle("#FF0000")
cv.BeginPath()
cv.Arc(100, 275, 60, 0, math.Pi*2, false)
cv.Fill()
cv.SetFillStyle("#00FF00")
cv.BeginPath()
cv.Arc(140, 210, 60, 0, math.Pi*2, false)
cv.Fill()
cv.SetFillStyle("#0000FF")
cv.BeginPath()
cv.Arc(180, 275, 60, 0, math.Pi*2, false)
cv.Fill()
cv.SetGlobalAlpha(1)
// Clipped drawing
cv.Save()
cv.BeginPath()
cv.Arc(340, 240, 80, 0, math.Pi*2, true)
cv.Clip()
cv.SetStrokeStyle(0, 255, 0)
for x := 1.0; x < 12.5; x += 1.0 {
cv.BeginPath()
cv.MoveTo(260, 140+16*x)
cv.LineTo(420, 140+16*x)
cv.Stroke()
}
cv.SetFillStyle(0, 0, 255)
for x := 1.0; x < 12.5; x += 1.0 {
cv.FillRect(246+x*14, 150, 6, 180)
}
cv.Restore()
// Draw images
cv.DrawImage("cat.jpg", 480, 40, 320, 265)
// Draw text
cv.SetFont("Righteous-Regular.ttf", 40)
cv.FillText("<-- Cat", 820, 180)
})
}

View file

@ -1,80 +0,0 @@
package main
import (
"log"
"math"
"time"
"github.com/tfriedel6/canvas/sdlcanvas"
)
type circle struct {
x, y float64
color string
}
func main() {
wnd, cv, err := sdlcanvas.CreateWindow(1280, 720, "Canvas Example")
if err != nil {
log.Println(err)
return
}
defer wnd.Destroy()
var mx, my, action float64
circles := make([]circle, 0, 100)
wnd.MouseMove = func(x, y int) {
mx, my = float64(x), float64(y)
}
wnd.MouseDown = func(button, x, y int) {
action = 1
circles = append(circles, circle{x: mx, y: my, color: "#F00"})
}
wnd.KeyDown = func(scancode int, rn rune, name string) {
switch name {
case "Escape":
wnd.Close()
case "Space":
action = 1
circles = append(circles, circle{x: mx, y: my, color: "#0F0"})
case "Enter":
action = 1
circles = append(circles, circle{x: mx, y: my, color: "#00F"})
}
}
wnd.SizeChange = func(w, h int) {
cv.SetBounds(0, 0, w, h)
}
lastTime := time.Now()
wnd.MainLoop(func() {
now := time.Now()
diff := now.Sub(lastTime)
lastTime = now
action -= diff.Seconds() * 3
action = math.Max(0, action)
w, h := float64(cv.Width()), float64(cv.Height())
// Clear the screen
cv.SetFillStyle("#000")
cv.FillRect(0, 0, w, h)
// Draw a circle around the cursor
cv.SetStrokeStyle("#F00")
cv.SetLineWidth(6)
cv.BeginPath()
cv.Arc(mx, my, 24+action*24, 0, math.Pi*2, false)
cv.Stroke()
// Draw circles where the user has clicked
for _, circle := range circles {
cv.SetFillStyle(circle.color)
cv.BeginPath()
cv.Arc(circle.x, circle.y, 24, 0, math.Pi*2, false)
cv.Fill()
}
})
}

View file

@ -1,82 +0,0 @@
package main
import (
"log"
"runtime"
"github.com/go-gl/gl/v3.2-core/gl"
"github.com/go-gl/glfw/v3.2/glfw"
"github.com/tfriedel6/canvas"
"github.com/tfriedel6/canvas/glimpl/gogl"
)
func main() {
runtime.LockOSThread()
// init GLFW
err := glfw.Init()
if err != nil {
log.Fatalf("Error initializing GLFW: %v", err)
}
defer glfw.Terminate()
// the stencil size setting is required for the canvas to work
glfw.WindowHint(glfw.StencilBits, 8)
glfw.WindowHint(glfw.DepthBits, 0)
// create window
window, err := glfw.CreateWindow(1280, 720, "GLFW Test", nil, nil)
if err != nil {
log.Fatalf("Error creating window: %v", err)
}
window.MakeContextCurrent()
// init GL
err = gl.Init()
if err != nil {
log.Fatalf("Error initializing GL: %v", err)
}
// set vsync on, enable multisample (if available)
glfw.SwapInterval(1)
gl.Enable(gl.MULTISAMPLE)
// load canvas GL assets
err = canvas.LoadGL(glimplgogl.GLImpl{})
if err != nil {
log.Fatalf("Error loading canvas GL assets: %v", err)
}
window.SetCursorPosCallback(func(w *glfw.Window, xpos float64, ypos float64) {
mx, my = xpos, ypos
})
// initialize canvas with zero size, since size is set in main loop
cv := canvas.New(0, 0, 0, 0)
for !window.ShouldClose() {
window.MakeContextCurrent()
glfw.PollEvents()
// set canvas size
ww, wh := window.GetSize()
cv.SetBounds(0, 0, ww, wh)
// call the run function to do all the drawing
run(cv, float64(ww), float64(wh))
// swap back and front buffer
window.SwapBuffers()
}
}
var mx, my float64
func run(cv *canvas.Canvas, w, h float64) {
cv.SetFillStyle("#000")
cv.FillRect(0, 0, w, h)
cv.SetFillStyle("#00F")
cv.FillRect(w*0.25, h*0.25, w*0.5, h*0.5)
cv.SetStrokeStyle("#0F0")
cv.StrokeRect(mx-32, my-32, 64, 64)
}

View file

@ -1,59 +0,0 @@
package main
import (
"math"
"time"
"github.com/tfriedel6/canvas"
"github.com/tfriedel6/canvas/glimpl/xmobile"
"golang.org/x/mobile/app"
"golang.org/x/mobile/event/lifecycle"
"golang.org/x/mobile/event/paint"
"golang.org/x/mobile/event/size"
"golang.org/x/mobile/gl"
)
func main() {
app.Main(func(a app.App) {
var cv, painter *canvas.Canvas
var w, h int
var glctx gl.Context
for e := range a.Events() {
switch e := a.Filter(e).(type) {
case lifecycle.Event:
switch e.Crosses(lifecycle.StageVisible) {
case lifecycle.CrossOn:
glctx, _ = e.DrawContext.(gl.Context)
canvas.LoadGL(glimplxmobile.New(glctx))
cv = canvas.NewOffscreen(0, 0)
painter = canvas.New(0, 0, 0, 0)
a.Send(paint.Event{})
case lifecycle.CrossOff:
glctx = nil
}
case size.Event:
w, h = e.WidthPx, e.HeightPx
case paint.Event:
if glctx != nil {
glctx.ClearColor(0, 0, 0, 0)
glctx.Clear(gl.COLOR_BUFFER_BIT)
cv.SetBounds(0, 0, w, h)
painter.SetBounds(0, 0, w, h)
fw, fh := float64(w), float64(h)
color := math.Sin(float64(time.Now().UnixNano())*0.000000002)*0.3 + 0.7
cv.SetFillStyle(color*0.2, color*0.2, color*0.8)
cv.FillRect(fw*0.25, fh*0.25, fw*0.5, fh*0.5)
painter.DrawImage(cv)
a.Publish()
a.Send(paint.Event{})
}
}
}
})
}

View file

@ -1,4 +0,0 @@
xcuserdata
project.xcworkspace
Example.framework

View file

@ -1,325 +0,0 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 48;
objects = {
/* Begin PBXBuildFile section */
A02D492720AB3AA900E68C35 /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A02D492620AB3AA900E68C35 /* MainViewController.swift */; };
A094B52520AB3D0C000BCEA6 /* Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A094B52420AB3D0C000BCEA6 /* Example.framework */; };
A0F2843E20AAD80F0049BD39 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0F2843D20AAD80F0049BD39 /* AppDelegate.swift */; };
A0F2844520AAD8100049BD39 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A0F2844420AAD8100049BD39 /* Assets.xcassets */; };
A0F2844820AAD8100049BD39 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A0F2844620AAD8100049BD39 /* LaunchScreen.storyboard */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
A02D492620AB3AA900E68C35 /* MainViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = "<group>"; };
A094B52420AB3D0C000BCEA6 /* Example.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Example.framework; sourceTree = "<group>"; };
A0F2843A20AAD80F0049BD39 /* CanvasIOSExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CanvasIOSExample.app; sourceTree = BUILT_PRODUCTS_DIR; };
A0F2843D20AAD80F0049BD39 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
A0F2844420AAD8100049BD39 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
A0F2844720AAD8100049BD39 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
A0F2844920AAD8100049BD39 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
A0F2843720AAD80F0049BD39 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
A094B52520AB3D0C000BCEA6 /* Example.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
A0F2843120AAD80F0049BD39 = {
isa = PBXGroup;
children = (
A0F2843C20AAD80F0049BD39 /* CanvasIOSExample */,
A0F2843B20AAD80F0049BD39 /* Products */,
);
sourceTree = "<group>";
};
A0F2843B20AAD80F0049BD39 /* Products */ = {
isa = PBXGroup;
children = (
A0F2843A20AAD80F0049BD39 /* CanvasIOSExample.app */,
);
name = Products;
sourceTree = "<group>";
};
A0F2843C20AAD80F0049BD39 /* CanvasIOSExample */ = {
isa = PBXGroup;
children = (
A0F2843D20AAD80F0049BD39 /* AppDelegate.swift */,
A02D492620AB3AA900E68C35 /* MainViewController.swift */,
A0F2844420AAD8100049BD39 /* Assets.xcassets */,
A0F2844620AAD8100049BD39 /* LaunchScreen.storyboard */,
A0F2844920AAD8100049BD39 /* Info.plist */,
A094B52420AB3D0C000BCEA6 /* Example.framework */,
);
path = CanvasIOSExample;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
A0F2843920AAD80F0049BD39 /* CanvasIOSExample */ = {
isa = PBXNativeTarget;
buildConfigurationList = A0F2844C20AAD8100049BD39 /* Build configuration list for PBXNativeTarget "CanvasIOSExample" */;
buildPhases = (
A0F2843620AAD80F0049BD39 /* Sources */,
A0F2843720AAD80F0049BD39 /* Frameworks */,
A0F2843820AAD80F0049BD39 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = CanvasIOSExample;
productName = CanvasIOSExample;
productReference = A0F2843A20AAD80F0049BD39 /* CanvasIOSExample.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
A0F2843220AAD80F0049BD39 /* Project object */ = {
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0910;
LastUpgradeCheck = 0910;
ORGANIZATIONNAME = example;
TargetAttributes = {
A0F2843920AAD80F0049BD39 = {
CreatedOnToolsVersion = 9.1;
ProvisioningStyle = Automatic;
};
};
};
buildConfigurationList = A0F2843520AAD80F0049BD39 /* Build configuration list for PBXProject "CanvasIOSExample" */;
compatibilityVersion = "Xcode 8.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = A0F2843120AAD80F0049BD39;
productRefGroup = A0F2843B20AAD80F0049BD39 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
A0F2843920AAD80F0049BD39 /* CanvasIOSExample */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
A0F2843820AAD80F0049BD39 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
A0F2844820AAD8100049BD39 /* LaunchScreen.storyboard in Resources */,
A0F2844520AAD8100049BD39 /* Assets.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
A0F2843620AAD80F0049BD39 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
A0F2843E20AAD80F0049BD39 /* AppDelegate.swift in Sources */,
A02D492720AB3AA900E68C35 /* MainViewController.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
A0F2844620AAD8100049BD39 /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
A0F2844720AAD8100049BD39 /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
A0F2844A20AAD8100049BD39 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.1;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
A0F2844B20AAD8100049BD39 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.1;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
A0F2844D20AAD8100049BD39 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_STYLE = Automatic;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/CanvasIOSExample",
);
INFOPLIST_FILE = CanvasIOSExample/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.example.CanvasIOSExample;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
A0F2844E20AAD8100049BD39 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_STYLE = Automatic;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/CanvasIOSExample",
);
INFOPLIST_FILE = CanvasIOSExample/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.example.CanvasIOSExample;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
A0F2843520AAD80F0049BD39 /* Build configuration list for PBXProject "CanvasIOSExample" */ = {
isa = XCConfigurationList;
buildConfigurations = (
A0F2844A20AAD8100049BD39 /* Debug */,
A0F2844B20AAD8100049BD39 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
A0F2844C20AAD8100049BD39 /* Build configuration list for PBXNativeTarget "CanvasIOSExample" */ = {
isa = XCConfigurationList;
buildConfigurations = (
A0F2844D20AAD8100049BD39 /* Debug */,
A0F2844E20AAD8100049BD39 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = A0F2843220AAD80F0049BD39 /* Project object */;
}

View file

@ -1,38 +0,0 @@
import UIKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
window?.rootViewController = MainViewController()
self.window?.makeKeyAndVisible()
return true
}
func applicationWillResignActive(_ application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
}
func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
func applicationWillEnterForeground(_ application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(_ application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
}

View file

@ -1,93 +0,0 @@
{
"images" : [
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "29x29",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "29x29",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "40x40",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "40x40",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "60x60",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "60x60",
"scale" : "3x"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "29x29",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "29x29",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "40x40",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "40x40",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "76x76",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "76x76",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "83.5x83.5",
"scale" : "2x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View file

@ -1,25 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" systemVersion="17A277" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
</document>

View file

@ -1,43 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>

View file

@ -1,54 +0,0 @@
import UIKit
import GLKit
import Example
class MainViewController : UIViewController, GLKViewDelegate {
var loaded:Bool?
override func viewDidLoad() {
loaded = false
let view = GLKView(frame: UIScreen.main.bounds)
let context : EAGLContext? = EAGLContext(api: .openGLES2)
view.context = context!
view.drawableColorFormat = .RGBA8888
view.drawableDepthFormat = .formatNone
view.drawableStencilFormat = .format8
view.drawableMultisample = .multisample4X
view.delegate = self
self.view = view
}
func glkView(_ view: GLKView, drawIn rect: CGRect) {
if loaded == nil || !loaded! {
loaded = true
let scale = UIScreen.main.nativeScale
ExampleLoadGL(Int(rect.width * scale), Int(rect.height * scale))
}
ExampleDrawFrame()
DispatchQueue.main.async {
view.setNeedsDisplay()
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let scale = UIScreen.main.nativeScale
let touch = touches.first!
let loc = touch.location(in: self.view)
ExampleTouchEvent("down", Int(loc.x*scale), Int(loc.y*scale))
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let scale = UIScreen.main.nativeScale
let touch = touches.first!
let loc = touch.location(in: self.view)
ExampleTouchEvent("move", Int(loc.x*scale), Int(loc.y*scale))
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
let scale = UIScreen.main.nativeScale
let touch = touches.first!
let loc = touch.location(in: self.view)
ExampleTouchEvent("up", Int(loc.x*scale), Int(loc.y*scale))
}
}

View file

@ -1,5 +0,0 @@
Run this command:
gomobile bind -target ios
Then add the resulting Example.framework into the Xcode project, and it should compile and run from there

View file

@ -1,41 +0,0 @@
package example
import (
"math"
"time"
"github.com/tfriedel6/canvas"
"github.com/tfriedel6/canvas/glimpl/ios"
)
var cv *canvas.Canvas
var mx, my float64
func TouchEvent(typ string, x, y int) {
mx, my = float64(x), float64(y)
}
func LoadGL(w, h int) {
err := canvas.LoadGL(glimplios.GLImpl{})
if err != nil {
time.Sleep(100 * time.Millisecond)
panic(err)
}
cv = canvas.New(0, 0, w, h)
}
func DrawFrame() {
if cv == nil {
return
}
w, h := float64(cv.Width()), float64(cv.Height())
cv.SetFillStyle("#000")
cv.FillRect(0, 0, w, h)
cv.SetFillStyle("#0F0")
cv.FillRect(w*0.25, h*0.25, w*0.5, h*0.5)
cv.SetLineWidth(6)
sqrSize := math.Min(w, h) * 0.1
cv.SetStrokeStyle("#F00")
cv.StrokeRect(mx-sqrSize/2, my-sqrSize/2, sqrSize, sqrSize)
}

View file

@ -1,123 +0,0 @@
package main
import (
"log"
"runtime"
"time"
"github.com/go-gl/gl/v3.2-core/gl"
"github.com/tfriedel6/canvas"
"github.com/tfriedel6/canvas/glimpl/gogl"
"github.com/veandco/go-sdl2/sdl"
)
func main() {
runtime.LockOSThread()
// init SDL
err := sdl.Init(sdl.INIT_VIDEO)
if err != nil {
log.Fatalf("Error initializing SDL: %v", err)
}
defer sdl.Quit()
// the stencil size setting is required for the canvas to work
sdl.GLSetAttribute(sdl.GL_RED_SIZE, 8)
sdl.GLSetAttribute(sdl.GL_GREEN_SIZE, 8)
sdl.GLSetAttribute(sdl.GL_BLUE_SIZE, 8)
sdl.GLSetAttribute(sdl.GL_ALPHA_SIZE, 8)
sdl.GLSetAttribute(sdl.GL_DEPTH_SIZE, 0)
sdl.GLSetAttribute(sdl.GL_STENCIL_SIZE, 8)
sdl.GLSetAttribute(sdl.GL_DOUBLEBUFFER, 1)
sdl.GLSetAttribute(sdl.GL_MULTISAMPLEBUFFERS, 1)
sdl.GLSetAttribute(sdl.GL_MULTISAMPLESAMPLES, 4)
// create window
const title = "SDL Test"
window, err := sdl.CreateWindow(title, sdl.WINDOWPOS_CENTERED, sdl.WINDOWPOS_CENTERED, 1280, 720, sdl.WINDOW_RESIZABLE|sdl.WINDOW_OPENGL)
if err != nil {
// fallback in case multisample is not available
sdl.GLSetAttribute(sdl.GL_MULTISAMPLEBUFFERS, 0)
sdl.GLSetAttribute(sdl.GL_MULTISAMPLESAMPLES, 0)
window, err = sdl.CreateWindow(title, sdl.WINDOWPOS_CENTERED, sdl.WINDOWPOS_CENTERED, 1280, 720, sdl.WINDOW_RESIZABLE|sdl.WINDOW_OPENGL)
if err != nil {
log.Fatalf("Error creating window: %v", err)
}
}
defer window.Destroy()
// create GL context
glContext, err := window.GLCreateContext()
if err != nil {
log.Fatalf("Error creating GL context: %v", err)
}
// init GL
err = gl.Init()
if err != nil {
log.Fatalf("Error initializing GL: %v", err)
}
// enable vsync and multisample (if available)
sdl.GLSetSwapInterval(1)
gl.Enable(gl.MULTISAMPLE)
// load canvas GL assets
err = canvas.LoadGL(glimplgogl.GLImpl{})
if err != nil {
log.Fatalf("Error loading canvas GL assets: %v", err)
}
// initialize canvas with zero size, since size is set in main loop
cv := canvas.New(0, 0, 0, 0)
for running := true; running; {
err := window.GLMakeCurrent(glContext)
if err != nil {
time.Sleep(10 * time.Millisecond)
continue
}
// handle events
for {
event := sdl.PollEvent()
if event == nil {
break
}
switch e := event.(type) {
case *sdl.KeyboardEvent:
if e.Type == sdl.KEYDOWN && e.Keysym.Scancode == sdl.SCANCODE_ESCAPE {
running = false
}
case *sdl.MouseMotionEvent:
mx, my = float64(e.X), float64(e.Y)
case *sdl.WindowEvent:
if e.Type == sdl.WINDOWEVENT_CLOSE {
running = false
}
}
}
// set canvas size
ww, wh := window.GetSize()
cv.SetBounds(0, 0, int(ww), int(wh))
// call the run function to do all the drawing
run(cv, float64(ww), float64(wh))
// swap back and front buffer
window.GLSwap()
}
}
var mx, my float64
func run(cv *canvas.Canvas, w, h float64) {
cv.SetFillStyle("#000")
cv.FillRect(0, 0, w, h)
cv.SetFillStyle("#0F0")
cv.FillRect(w*0.25, h*0.25, w*0.5, h*0.5)
cv.SetStrokeStyle("#00F")
cv.StrokeRect(mx-32, my-32, 64, 64)
}

View file

@ -1,51 +0,0 @@
package main
import (
"log"
"github.com/tfriedel6/canvas"
"github.com/tfriedel6/canvas/glimpl/xmobile"
"golang.org/x/exp/shiny/driver/gldriver"
"golang.org/x/exp/shiny/screen"
"golang.org/x/exp/shiny/widget"
"golang.org/x/exp/shiny/widget/glwidget"
"golang.org/x/exp/shiny/widget/node"
)
var cv *canvas.Canvas
var sheet *widget.Sheet
func main() {
gldriver.Main(func(s screen.Screen) {
glw := glwidget.NewGL(draw)
sheet = widget.NewSheet(glw)
canvas.LoadGL(glimplxmobile.New(glw.Ctx))
cv = canvas.New(0, 0, 600, 600)
err := widget.RunWindow(s, sheet, &widget.RunWindowOptions{
NewWindowOptions: screen.NewWindowOptions{
Title: "Shiny Canvas Example",
Width: 600,
Height: 600,
},
})
if err != nil {
log.Fatal(err)
}
})
}
func draw(w *glwidget.GL) {
cv.Save()
defer cv.Restore()
cv.Translate(0, 600)
cv.Scale(1, -1)
cv.ClearRect(0, 0, 600, 600)
cv.SetFillStyle("#FF00FF")
cv.FillRect(100, 100, 200, 200)
w.Publish()
w.Mark(node.MarkNeedsPaintBase)
}

View file

@ -0,0 +1,43 @@
package main
import (
"image/png"
"math"
"os"
"git.mstar.dev/mstar/canvas"
"git.mstar.dev/mstar/canvas/backend/softwarebackend"
)
func main() {
backend := softwarebackend.New(720, 720)
cv := canvas.New(backend)
w, h := float64(cv.Width()), float64(cv.Height())
cv.SetFillStyle("#000")
cv.FillRect(0, 0, w, h)
for r := 0.0; r < math.Pi*2; r += math.Pi * 0.1 {
cv.SetFillStyle(int(r*10), int(r*20), int(r*40))
cv.BeginPath()
cv.MoveTo(w*0.5, h*0.5)
cv.Arc(w*0.5, h*0.5, math.Min(w, h)*0.4, r, r+0.1*math.Pi, false)
cv.ClosePath()
cv.Fill()
}
cv.SetStrokeStyle("#FFF")
cv.SetLineWidth(10)
cv.BeginPath()
cv.Arc(w*0.5, h*0.5, math.Min(w, h)*0.4, 0, math.Pi*2, false)
cv.Stroke()
f, err := os.OpenFile("result.png", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0777)
if err != nil {
panic(err)
}
err = png.Encode(f, backend.Image)
if err != nil {
panic(err)
}
}

View file

@ -8,7 +8,6 @@ package canvas
import (
"errors"
"image"
"image/draw"
"github.com/golang/freetype/raster"
"github.com/golang/freetype/truetype"
@ -42,16 +41,9 @@ type frContext struct {
r *raster.Rasterizer
f *truetype.Font
glyphBuf truetype.GlyphBuf
// clip is the clip rectangle for drawing.
clip image.Rectangle
// dst and src are the destination and source images for drawing.
dst draw.Image
src image.Image
// fontSize and dpi are used to calculate scale. scale is the number of
// 26.6 fixed point units in 1 em. hinting is the hinting policy.
fontSize, dpi float64
scale fixed.Int26_6
hinting font.Hinting
fontSize fixed.Int26_6
hinting font.Hinting
// cache is the glyph cache.
cache [nGlyphs * nXFractions * nYFractions]cacheEntry
}
@ -135,7 +127,7 @@ func (c *frContext) drawContour(ps []truetype.Point, dx, dy fixed.Int26_6) {
// The 26.6 fixed point arguments fx and fy must be in the range [0, 1).
func (c *frContext) rasterize(glyph truetype.Index, fx, fy fixed.Int26_6) (fixed.Int26_6, *image.Alpha, image.Point, error) {
if err := c.glyphBuf.Load(c.f, c.scale, glyph, c.hinting); err != nil {
if err := c.glyphBuf.Load(c.f, c.fontSize, glyph, c.hinting); err != nil {
return 0, nil, image.Point{}, err
}
// Calculate the integer-pixel bounds for the glyph.
@ -193,14 +185,14 @@ func (c *frContext) glyph(glyph truetype.Index, p fixed.Point26_6) (fixed.Int26_
}
func (c *frContext) glyphAdvance(glyph truetype.Index) (fixed.Int26_6, error) {
if err := c.glyphBuf.Load(c.f, c.scale, glyph, c.hinting); err != nil {
if err := c.glyphBuf.Load(c.f, c.fontSize, glyph, c.hinting); err != nil {
return 0, err
}
return c.glyphBuf.AdvanceWidth, nil
}
func (c *frContext) glyphMeasure(glyph truetype.Index, p fixed.Point26_6) (fixed.Int26_6, image.Rectangle, error) {
if err := c.glyphBuf.Load(c.f, c.scale, glyph, c.hinting); err != nil {
if err := c.glyphBuf.Load(c.f, c.fontSize, glyph, c.hinting); err != nil {
return 0, image.Rectangle{}, err
}
@ -224,67 +216,12 @@ func (c *frContext) glyphBounds(glyph truetype.Index, p fixed.Point26_6) (image.
const maxInt = int(^uint(0) >> 1)
// DrawString draws s at p and returns p advanced by the text extent. The text
// is placed so that the left edge of the em square of the first character of s
// and the baseline intersect at p. 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.
//
// p is a fixed.Point26_6 and can therefore represent sub-pixel positions.
func (c *frContext) drawString(s string, p fixed.Point26_6) (fixed.Point26_6, image.Rectangle, error) {
if c.f == nil {
return fixed.Point26_6{}, image.Rectangle{}, errors.New("freetype: DrawText called with a nil font")
}
bounds := image.Rectangle{Min: image.Point{X: maxInt, Y: maxInt}}
prev, hasPrev := truetype.Index(0), false
for _, rune := range s {
index := c.f.Index(rune)
if hasPrev {
kern := c.f.Kern(c.scale, prev, index)
if c.hinting != font.HintingNone {
kern = (kern + 32) &^ 63
}
p.X += kern
}
advanceWidth, mask, offset, err := c.glyph(index, p)
if err != nil {
return fixed.Point26_6{}, image.Rectangle{}, err
}
p.X += advanceWidth
glyphRect := mask.Bounds().Add(offset)
if glyphRect.Min.X < bounds.Min.X {
bounds.Min.X = glyphRect.Min.X
}
if glyphRect.Min.Y < bounds.Min.Y {
bounds.Min.Y = glyphRect.Min.Y
}
if glyphRect.Max.X > bounds.Max.X {
bounds.Max.X = glyphRect.Max.X
}
if glyphRect.Max.Y > bounds.Max.Y {
bounds.Max.Y = glyphRect.Max.Y
}
dr := c.clip.Intersect(glyphRect)
if !dr.Empty() {
mp := image.Point{0, dr.Min.Y - glyphRect.Min.Y}
draw.DrawMask(c.dst, dr, c.src, image.ZP, mask, mp, draw.Src)
}
prev, hasPrev = index, true
}
bounds = c.clip.Intersect(bounds)
return p, bounds, nil
}
// recalc recalculates scale and bounds values from the font size, screen
// resolution and font metrics, and invalidates the glyph cache.
func (c *frContext) recalc() {
c.scale = fixed.Int26_6(c.fontSize * c.dpi * (64.0 / 72.0))
if c.f == nil {
c.r.SetBounds(0, 0)
} else {
// Set the rasterizer's bounds to be big enough to handle the largest glyph.
b := c.f.Bounds(c.scale)
b := c.f.Bounds(c.fontSize)
xmin := +int(b.Min.X) >> 6
ymin := -int(b.Max.Y) >> 6
xmax := +int(b.Max.X+63) >> 6
@ -296,65 +233,25 @@ func (c *frContext) recalc() {
}
}
// SetDPI sets the screen resolution in dots per inch.
func (c *frContext) setDPI(dpi float64) {
if c.dpi == dpi {
return
func (c *frContext) cacheSize() int {
if c.f == nil {
return 0
}
c.dpi = dpi
c.recalc()
b := c.f.Bounds(c.fontSize)
xmin := +int(b.Min.X) >> 6
ymin := -int(b.Max.Y) >> 6
xmax := +int(b.Max.X+63) >> 6
ymax := -int(b.Min.Y-63) >> 6
w := xmax - xmin
h := ymax - ymin
return w * h * len(c.cache)
}
// SetFont sets the font used to draw text.
func (c *frContext) setFont(f *truetype.Font) {
if c.f == f {
return
}
c.f = f
c.recalc()
}
// SetFontSize sets the font size in points (as in "a 12 point font").
func (c *frContext) setFontSize(fontSize float64) {
if c.fontSize == fontSize {
return
}
c.fontSize = fontSize
c.recalc()
}
// SetHinting sets the hinting policy.
func (c *frContext) setHinting(hinting font.Hinting) {
c.hinting = hinting
for i := range c.cache {
c.cache[i] = cacheEntry{}
}
}
// SetDst sets the destination image for draw operations.
func (c *frContext) setDst(dst draw.Image) {
c.dst = dst
}
// SetSrc sets the source image for draw operations. This is typically an
// image.Uniform.
func (c *frContext) setSrc(src image.Image) {
c.src = src
}
// SetClip sets the clip rectangle for drawing.
func (c *frContext) setClip(clip image.Rectangle) {
c.clip = clip
}
// TODO(nigeltao): implement Context.SetGamma.
// NewContext creates a new Context.
func newFRContext() *frContext {
return &frContext{
r: raster.NewRasterizer(0, 0),
fontSize: 12,
dpi: 72,
scale: 12 << 6,
fontSize: fixed.I(12),
hinting: font.HintingFull,
}
}

View file

@ -1,177 +0,0 @@
package glfwcanvas
import (
"fmt"
_ "image/gif" // Imported here so that applications based on this package support these formats by default
_ "image/jpeg"
_ "image/png"
"log"
"math"
"runtime"
"time"
"github.com/go-gl/gl/v3.2-core/gl"
"github.com/go-gl/glfw/v3.2/glfw"
"github.com/tfriedel6/canvas"
"github.com/tfriedel6/canvas/glimpl/gogl"
)
// Window represents the opened window with GL context. The Mouse* and Key*
// functions can be set for callbacks
type Window struct {
Window *glfw.Window
canvas *canvas.Canvas
frameTimes [10]time.Time
frameIndex int
frameCount int
fps float32
close bool
MouseDown func(button, x, y int)
MouseMove func(x, y int)
MouseUp func(button, x, y int)
MouseWheel func(x, y int)
KeyDown func(scancode int, rn rune, name string)
KeyUp func(scancode int, rn rune, name string)
KeyChar func(rn rune)
SizeChange func(w, h int)
}
// CreateWindow creates a window using SDL and initializes the OpenGL context
func CreateWindow(w, h int, title string) (*Window, *canvas.Canvas, error) {
runtime.LockOSThread()
// init GLFW
err := glfw.Init()
if err != nil {
log.Fatalf("Error initializing GLFW: %v", err)
}
// the stencil size setting is required for the canvas to work
glfw.WindowHint(glfw.StencilBits, 8)
glfw.WindowHint(glfw.DepthBits, 0)
// create window
window, err := glfw.CreateWindow(w, h, title, nil, nil)
if err != nil {
log.Fatalf("Error creating window: %v", err)
}
window.MakeContextCurrent()
// init GL
err = gl.Init()
if err != nil {
log.Fatalf("Error initializing GL: %v", err)
}
// set vsync on, enable multisample (if available)
glfw.SwapInterval(1)
gl.Enable(gl.MULTISAMPLE)
// load canvas GL assets
err = canvas.LoadGL(glimplgogl.GLImpl{})
if err != nil {
log.Fatalf("Error loading canvas GL assets: %v", err)
}
err = canvas.LoadGL(glimplgogl.GLImpl{})
if err != nil {
return nil, nil, fmt.Errorf("Error loading canvas GL assets: %v", err)
}
cv := canvas.New(0, 0, w, h)
wnd := &Window{
Window: window,
canvas: cv,
}
var mx, my int
window.SetMouseButtonCallback(func(w *glfw.Window, button glfw.MouseButton, action glfw.Action, mod glfw.ModifierKey) {
if action == glfw.Press && wnd.MouseDown != nil {
wnd.MouseDown(int(button), mx, my)
} else if action == glfw.Release && wnd.MouseUp != nil {
wnd.MouseUp(int(button), mx, my)
}
})
window.SetCursorPosCallback(func(w *glfw.Window, xpos, ypos float64) {
mx, my = int(math.Round(xpos)), int(math.Round(ypos))
if wnd.MouseMove != nil {
wnd.MouseMove(mx, my)
}
})
window.SetScrollCallback(func(w *glfw.Window, xoff, yoff float64) {
if wnd.MouseWheel != nil {
wnd.MouseWheel(int(math.Round(xoff)), int(math.Round(yoff)))
}
})
window.SetKeyCallback(func(w *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) {
if action == glfw.Press && wnd.KeyDown != nil {
wnd.KeyDown(scancode, keyRune(key), keyName(key))
} else if action == glfw.Release && wnd.KeyUp != nil {
wnd.KeyUp(scancode, keyRune(key), keyName(key))
}
})
window.SetCharCallback(func(w *glfw.Window, char rune) {
if wnd.KeyChar != nil {
wnd.KeyChar(char)
}
})
window.SetSizeCallback(func(w *glfw.Window, width, height int) {
if wnd.SizeChange != nil {
wnd.SizeChange(width, height)
} else {
cv.SetBounds(0, 0, width, height)
}
})
window.SetCloseCallback(func(w *glfw.Window) {
wnd.Close()
})
return wnd, cv, nil
}
// FPS returns the frames per second (averaged over 10 frames)
func (wnd *Window) FPS() float32 {
return wnd.fps
}
// Close can be used to end a call to MainLoop
func (wnd *Window) Close() {
wnd.close = true
}
// StartFrame handles events and gets the window ready for rendering
func (wnd *Window) StartFrame() {
wnd.Window.MakeContextCurrent()
glfw.PollEvents()
}
// FinishFrame updates the FPS count and displays the frame
func (wnd *Window) FinishFrame() {
now := time.Now()
wnd.frameTimes[wnd.frameIndex] = now
wnd.frameIndex++
wnd.frameIndex %= len(wnd.frameTimes)
if wnd.frameCount < len(wnd.frameTimes) {
wnd.frameCount++
} else {
diff := now.Sub(wnd.frameTimes[wnd.frameIndex]).Seconds()
wnd.fps = float32(wnd.frameCount-1) / float32(diff)
}
wnd.Window.SwapBuffers()
}
// MainLoop runs a main loop and calls run on every frame
func (wnd *Window) MainLoop(run func()) {
for !wnd.close {
wnd.StartFrame()
run()
wnd.FinishFrame()
}
}
// Size returns the current width and height of the window
func (wnd *Window) Size() (int, int) {
return wnd.Window.GetSize()
}

View file

@ -1,216 +0,0 @@
package glfwcanvas
import "github.com/go-gl/glfw/v3.2/glfw"
var keyNameMap [347]string
var keyRuneMap [347]rune
func init() {
keyNameMap[glfw.KeyEscape] = "Escape"
keyNameMap[glfw.Key0] = "Digit0"
keyNameMap[glfw.Key1] = "Digit1"
keyNameMap[glfw.Key2] = "Digit2"
keyNameMap[glfw.Key3] = "Digit3"
keyNameMap[glfw.Key4] = "Digit4"
keyNameMap[glfw.Key5] = "Digit5"
keyNameMap[glfw.Key6] = "Digit6"
keyNameMap[glfw.Key7] = "Digit7"
keyNameMap[glfw.Key8] = "Digit8"
keyNameMap[glfw.Key9] = "Digit9"
keyNameMap[glfw.KeyMinus] = "Minus"
keyNameMap[glfw.KeyEqual] = "Equal"
keyNameMap[glfw.KeyBackspace] = "Backspace"
keyNameMap[glfw.KeyTab] = "Tab"
keyNameMap[glfw.KeyQ] = "KeyQ"
keyNameMap[glfw.KeyW] = "KeyW"
keyNameMap[glfw.KeyE] = "KeyE"
keyNameMap[glfw.KeyR] = "KeyR"
keyNameMap[glfw.KeyT] = "KeyT"
keyNameMap[glfw.KeyY] = "KeyY"
keyNameMap[glfw.KeyU] = "KeyU"
keyNameMap[glfw.KeyI] = "KeyI"
keyNameMap[glfw.KeyO] = "KeyO"
keyNameMap[glfw.KeyP] = "KeyP"
keyNameMap[glfw.KeyLeftBracket] = "BracketLeft"
keyNameMap[glfw.KeyRightBracket] = "BracketRight"
keyNameMap[glfw.KeyEnter] = "Enter"
keyNameMap[glfw.KeyLeftControl] = "ControlLeft"
keyNameMap[glfw.KeyA] = "KeyA"
keyNameMap[glfw.KeyS] = "KeyS"
keyNameMap[glfw.KeyD] = "KeyD"
keyNameMap[glfw.KeyF] = "KeyF"
keyNameMap[glfw.KeyG] = "KeyG"
keyNameMap[glfw.KeyH] = "KeyH"
keyNameMap[glfw.KeyJ] = "KeyJ"
keyNameMap[glfw.KeyK] = "KeyK"
keyNameMap[glfw.KeyL] = "KeyL"
keyNameMap[glfw.KeySemicolon] = "Semicolon"
keyNameMap[glfw.KeyApostrophe] = "Quote"
keyNameMap[glfw.KeyGraveAccent] = "Backquote"
keyNameMap[glfw.KeyLeftShift] = "ShiftLeft"
keyNameMap[glfw.KeyBackslash] = "Backslash"
keyNameMap[glfw.KeyZ] = "KeyZ"
keyNameMap[glfw.KeyX] = "KeyX"
keyNameMap[glfw.KeyC] = "KeyC"
keyNameMap[glfw.KeyV] = "KeyV"
keyNameMap[glfw.KeyB] = "KeyB"
keyNameMap[glfw.KeyN] = "KeyN"
keyNameMap[glfw.KeyM] = "KeyM"
keyNameMap[glfw.KeyComma] = "Comma"
keyNameMap[glfw.KeyPeriod] = "Period"
keyNameMap[glfw.KeySlash] = "Slash"
keyNameMap[glfw.KeyRightShift] = "RightShift"
keyNameMap[glfw.KeyKPMultiply] = "NumpadMultiply"
keyNameMap[glfw.KeyLeftAlt] = "AltLeft"
keyNameMap[glfw.KeySpace] = "Space"
keyNameMap[glfw.KeyCapsLock] = "CapsLock"
keyNameMap[glfw.KeyF1] = "F1"
keyNameMap[glfw.KeyF2] = "F2"
keyNameMap[glfw.KeyF3] = "F3"
keyNameMap[glfw.KeyF4] = "F4"
keyNameMap[glfw.KeyF5] = "F5"
keyNameMap[glfw.KeyF6] = "F6"
keyNameMap[glfw.KeyF7] = "F7"
keyNameMap[glfw.KeyF8] = "F8"
keyNameMap[glfw.KeyF9] = "F9"
keyNameMap[glfw.KeyF10] = "F10"
keyNameMap[glfw.KeyPause] = "Pause"
keyNameMap[glfw.KeyScrollLock] = "ScrollLock"
keyNameMap[glfw.KeyKP7] = "Numpad7"
keyNameMap[glfw.KeyKP8] = "Numpad8"
keyNameMap[glfw.KeyKP9] = "Numpad9"
keyNameMap[glfw.KeyKPSubtract] = "NumpadSubtract"
keyNameMap[glfw.KeyKP4] = "Numpad4"
keyNameMap[glfw.KeyKP5] = "Numpad5"
keyNameMap[glfw.KeyKP6] = "Numpad6"
keyNameMap[glfw.KeyKPAdd] = "NumpadAdd"
keyNameMap[glfw.KeyKP1] = "Numpad1"
keyNameMap[glfw.KeyKP2] = "Numpad2"
keyNameMap[glfw.KeyKP3] = "Numpad3"
keyNameMap[glfw.KeyKP0] = "Numpad0"
keyNameMap[glfw.KeyKPDecimal] = "NumpadDecimal"
keyNameMap[glfw.KeyPrintScreen] = "PrintScreen"
// keyNameMap[glfw.KeyNonUSBackslash] = "IntlBackslash"
keyNameMap[glfw.KeyF11] = "F11"
keyNameMap[glfw.KeyF12] = "F12"
keyNameMap[glfw.KeyKPEqual] = "NumpadEqual"
keyNameMap[glfw.KeyF13] = "F13"
keyNameMap[glfw.KeyF14] = "F14"
keyNameMap[glfw.KeyF15] = "F15"
keyNameMap[glfw.KeyF16] = "F16"
keyNameMap[glfw.KeyF17] = "F17"
keyNameMap[glfw.KeyF18] = "F18"
keyNameMap[glfw.KeyF19] = "F19"
// keyNameMap[glfw.KeyUndo] = "Undo"
// keyNameMap[glfw.KeyPaste] = "Paste"
// keyNameMap[glfw.KeyAudioNext] = "MediaTrackPrevious"
// keyNameMap[glfw.KeyCut] = "Cut"
// keyNameMap[glfw.KeyCopy] = "Copy"
// keyNameMap[glfw.KeyAudioNext] = "MediaTrackNext"
keyNameMap[glfw.KeyKPEnter] = "NumpadEnter"
keyNameMap[glfw.KeyRightControl] = "ControlRight"
// keyNameMap[glfw.KeyMute] = "AudioVolumeMute"
// keyNameMap[glfw.KeyAudioPlay] = "MediaPlayPause"
// keyNameMap[glfw.KeyAudioStop] = "MediaStop"
// keyNameMap[glfw.KeyVolumeDown] = "AudioVolumeDown"
// keyNameMap[glfw.KeyVolumeUp] = "AudioVolumeUp"
keyNameMap[glfw.KeyKPDivide] = "NumpadDivide"
keyNameMap[glfw.KeyRightAlt] = "AltRight"
// keyNameMap[glfw.KeyHelp] = "Help"
keyNameMap[glfw.KeyHome] = "Home"
keyNameMap[glfw.KeyUp] = "ArrowUp"
keyNameMap[glfw.KeyPageUp] = "PageUp"
keyNameMap[glfw.KeyLeft] = "ArrowLeft"
keyNameMap[glfw.KeyRight] = "ArrowRight"
keyNameMap[glfw.KeyEnd] = "End"
keyNameMap[glfw.KeyDown] = "ArrowDown"
keyNameMap[glfw.KeyInsert] = "Insert"
keyNameMap[glfw.KeyDelete] = "Delete"
// keyNameMap[glfw.KeyApplication] = "ContextMenu"
keyRuneMap[glfw.Key0] = '0'
keyRuneMap[glfw.Key1] = '1'
keyRuneMap[glfw.Key2] = '2'
keyRuneMap[glfw.Key3] = '3'
keyRuneMap[glfw.Key4] = '4'
keyRuneMap[glfw.Key5] = '5'
keyRuneMap[glfw.Key6] = '6'
keyRuneMap[glfw.Key7] = '7'
keyRuneMap[glfw.Key8] = '8'
keyRuneMap[glfw.Key9] = '9'
keyRuneMap[glfw.KeyMinus] = '-'
keyRuneMap[glfw.KeyEqual] = '='
keyRuneMap[glfw.KeyTab] = '\t'
keyRuneMap[glfw.KeyQ] = 'Q'
keyRuneMap[glfw.KeyW] = 'W'
keyRuneMap[glfw.KeyE] = 'E'
keyRuneMap[glfw.KeyR] = 'R'
keyRuneMap[glfw.KeyT] = 'T'
keyRuneMap[glfw.KeyY] = 'Y'
keyRuneMap[glfw.KeyU] = 'U'
keyRuneMap[glfw.KeyI] = 'I'
keyRuneMap[glfw.KeyO] = 'O'
keyRuneMap[glfw.KeyP] = 'P'
keyRuneMap[glfw.KeyLeftBracket] = '['
keyRuneMap[glfw.KeyRightBracket] = ']'
keyRuneMap[glfw.KeyEnter] = '\n'
keyRuneMap[glfw.KeyA] = 'A'
keyRuneMap[glfw.KeyS] = 'S'
keyRuneMap[glfw.KeyD] = 'D'
keyRuneMap[glfw.KeyF] = 'F'
keyRuneMap[glfw.KeyG] = 'G'
keyRuneMap[glfw.KeyH] = 'H'
keyRuneMap[glfw.KeyJ] = 'J'
keyRuneMap[glfw.KeyK] = 'K'
keyRuneMap[glfw.KeyL] = 'L'
keyRuneMap[glfw.KeySemicolon] = ';'
keyRuneMap[glfw.KeyApostrophe] = '\''
keyRuneMap[glfw.KeyGraveAccent] = '`'
keyRuneMap[glfw.KeyBackslash] = '\\'
keyRuneMap[glfw.KeyZ] = 'Z'
keyRuneMap[glfw.KeyX] = 'X'
keyRuneMap[glfw.KeyC] = 'C'
keyRuneMap[glfw.KeyV] = 'V'
keyRuneMap[glfw.KeyB] = 'B'
keyRuneMap[glfw.KeyN] = 'N'
keyRuneMap[glfw.KeyM] = 'M'
keyRuneMap[glfw.KeyComma] = ','
keyRuneMap[glfw.KeyPeriod] = '.'
keyRuneMap[glfw.KeySlash] = '/'
keyRuneMap[glfw.KeyKPMultiply] = '*'
keyRuneMap[glfw.KeySpace] = ' '
keyRuneMap[glfw.KeyKP7] = '7'
keyRuneMap[glfw.KeyKP8] = '8'
keyRuneMap[glfw.KeyKP9] = '9'
keyRuneMap[glfw.KeyKPSubtract] = '-'
keyRuneMap[glfw.KeyKP4] = '4'
keyRuneMap[glfw.KeyKP5] = '5'
keyRuneMap[glfw.KeyKP6] = '6'
keyRuneMap[glfw.KeyKPAdd] = '+'
keyRuneMap[glfw.KeyKP1] = '1'
keyRuneMap[glfw.KeyKP2] = '2'
keyRuneMap[glfw.KeyKP3] = '3'
keyRuneMap[glfw.KeyKP0] = '0'
keyRuneMap[glfw.KeyKPDecimal] = '.'
keyRuneMap[glfw.KeyKPEqual] = '='
keyRuneMap[glfw.KeyKPEnter] = '\n'
keyRuneMap[glfw.KeyKPDivide] = '/'
}
func keyName(key glfw.Key) string {
if int(key) >= len(keyNameMap) {
return "Unidentified"
}
name := keyNameMap[key]
if name == "" {
return "Unidentified"
}
return name
}
func keyRune(key glfw.Key) rune {
if int(key) >= len(keyNameMap) {
return 0
}
return keyRuneMap[key]
}

View file

@ -1,263 +0,0 @@
package glimplandroid
// #include <stdlib.h>
// #include <GLES2/gl2.h>
// #cgo android LDFLAGS: -lGLESv2
import "C"
import (
"fmt"
"reflect"
"unsafe"
"github.com/tfriedel6/canvas"
)
type GLImpl struct{}
var _ canvas.GL = GLImpl{}
func (GLImpl) Ptr(data interface{}) unsafe.Pointer {
if data == nil {
return unsafe.Pointer(nil)
}
var addr unsafe.Pointer
v := reflect.ValueOf(data)
switch v.Type().Kind() {
case reflect.Ptr:
e := v.Elem()
switch e.Kind() {
case
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
reflect.Float32, reflect.Float64:
addr = unsafe.Pointer(e.UnsafeAddr())
default:
panic(fmt.Errorf("unsupported pointer to type %s; must be a slice or pointer to a singular scalar value or the first element of an array or slice", e.Kind()))
}
case reflect.Uintptr:
addr = unsafe.Pointer(v.Pointer())
case reflect.Slice:
addr = unsafe.Pointer(v.Index(0).UnsafeAddr())
default:
panic(fmt.Errorf("unsupported type %s; must be a slice or pointer to a singular scalar value or the first element of an array or slice", v.Type()))
}
return addr
}
func (GLImpl) ActiveTexture(texture uint32) {
C.glActiveTexture(C.uint(texture))
}
func (GLImpl) AttachShader(program uint32, shader uint32) {
C.glAttachShader(C.uint(program), C.uint(shader))
}
func (GLImpl) BindBuffer(target uint32, buffer uint32) {
C.glBindBuffer(C.uint(target), C.uint(buffer))
}
func (GLImpl) BindFramebuffer(target uint32, framebuffer uint32) {
C.glBindFramebuffer(C.uint(target), C.uint(framebuffer))
}
func (GLImpl) BindRenderbuffer(target uint32, renderbuffer uint32) {
C.glBindRenderbuffer(C.uint(target), C.uint(renderbuffer))
}
func (GLImpl) BindTexture(target uint32, texture uint32) {
C.glBindTexture(C.uint(target), C.uint(texture))
}
func (GLImpl) BlendFunc(sfactor uint32, dfactor uint32) {
C.glBlendFunc(C.uint(sfactor), C.uint(dfactor))
}
func (GLImpl) BufferData(target uint32, size int, data unsafe.Pointer, usage uint32) {
C.glBufferData(C.uint(target), C.long(size), data, C.uint(usage))
}
func (GLImpl) CheckFramebufferStatus(target uint32) uint32 {
return uint32(C.glCheckFramebufferStatus(C.uint(target)))
}
func (GLImpl) Clear(mask uint32) {
C.glClear(C.uint(mask))
}
func (GLImpl) ClearColor(red float32, green float32, blue float32, alpha float32) {
C.glClearColor(C.float(red), C.float(green), C.float(blue), C.float(alpha))
}
func (GLImpl) ColorMask(red bool, green bool, blue bool, alpha bool) {
var r, g, b, a C.uchar
if red {
r = 1
}
if green {
g = 1
}
if blue {
b = 1
}
if alpha {
a = 1
}
C.glColorMask(r, g, b, a)
}
func (GLImpl) CompileShader(shader uint32) {
C.glCompileShader(C.uint(shader))
}
func (GLImpl) CreateProgram() uint32 {
return uint32(C.glCreateProgram())
}
func (GLImpl) CreateShader(xtype uint32) uint32 {
return uint32(C.glCreateShader(C.uint(xtype)))
}
func (GLImpl) DeleteShader(shader uint32) {
C.glDeleteShader(C.uint(shader))
}
func (GLImpl) DeleteFramebuffers(n int32, framebuffers *uint32) {
C.glDeleteFramebuffers(C.int(n), (*C.uint)(framebuffers))
}
func (GLImpl) DeleteRenderbuffers(n int32, renderbuffers *uint32) {
C.glDeleteRenderbuffers(C.int(n), (*C.uint)(renderbuffers))
}
func (GLImpl) DeleteTextures(n int32, textures *uint32) {
C.glDeleteTextures(C.int(n), (*C.uint)(textures))
}
func (GLImpl) Disable(cap uint32) {
C.glDisable(C.uint(cap))
}
func (GLImpl) DisableVertexAttribArray(index uint32) {
C.glDisableVertexAttribArray(C.uint(index))
}
func (GLImpl) DrawArrays(mode uint32, first int32, count int32) {
C.glDrawArrays(C.uint(mode), C.int(first), C.int(count))
}
func (GLImpl) Enable(cap uint32) {
C.glEnable(C.uint(cap))
}
func (GLImpl) EnableVertexAttribArray(index uint32) {
C.glEnableVertexAttribArray(C.uint(index))
}
func (GLImpl) FramebufferRenderbuffer(target uint32, attachment uint32, renderbuffertarget uint32, renderbuffer uint32) {
C.glFramebufferRenderbuffer(C.uint(target), C.uint(attachment), C.uint(renderbuffertarget), C.uint(renderbuffer))
}
func (GLImpl) FramebufferTexture(target uint32, attachment uint32, texture uint32, level int32) {
C.glFramebufferTexture2D(C.uint(target), C.uint(attachment), C.GL_TEXTURE_2D, C.uint(texture), C.int(level))
}
func (GLImpl) GenBuffers(n int32, buffers *uint32) {
C.glGenBuffers(C.int(n), (*C.uint)(buffers))
}
func (GLImpl) GenFramebuffers(n int32, framebuffers *uint32) {
C.glGenFramebuffers(C.int(n), (*C.uint)(framebuffers))
}
func (GLImpl) GenRenderbuffers(n int32, renderbuffers *uint32) {
C.glGenRenderbuffers(C.int(n), (*C.uint)(renderbuffers))
}
func (GLImpl) GenTextures(n int32, textures *uint32) {
C.glGenTextures(C.int(n), (*C.uint)(textures))
}
func (GLImpl) GenerateMipmap(target uint32) {
C.glGenerateMipmap(C.uint(target))
}
func (GLImpl) GetAttribLocation(program uint32, name string) int32 {
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
return int32(C.glGetAttribLocation(C.uint(program), cname))
}
func (GLImpl) GetError() uint32 {
return uint32(C.glGetError())
}
func (GLImpl) GetProgramInfoLog(program uint32) string {
var length C.int
C.glGetProgramiv(C.uint(program), C.GL_INFO_LOG_LENGTH, &length)
if length == 0 {
return ""
}
clog := C.CBytes(make([]byte, int(length)+1))
defer C.free(clog)
C.glGetProgramInfoLog(C.uint(program), C.int(length), nil, (*C.char)(clog))
return string(C.GoBytes(clog, length))
}
func (GLImpl) GetProgramiv(program uint32, pname uint32, params *int32) {
C.glGetProgramiv(C.uint(program), C.uint(pname), (*C.int)(params))
}
func (GLImpl) GetShaderInfoLog(program uint32) string {
var length C.int
C.glGetShaderiv(C.uint(program), C.GL_INFO_LOG_LENGTH, &length)
if length == 0 {
return ""
}
clog := C.CBytes(make([]byte, int(length)+1))
defer C.free(clog)
C.glGetShaderInfoLog(C.uint(program), C.int(length), nil, (*C.char)(clog))
return string(C.GoBytes(clog, length))
}
func (GLImpl) GetShaderiv(shader uint32, pname uint32, params *int32) {
C.glGetShaderiv(C.uint(shader), C.uint(pname), (*C.int)(params))
}
func (GLImpl) GetUniformLocation(program uint32, name string) int32 {
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
return int32(C.glGetUniformLocation(C.uint(program), cname))
}
func (GLImpl) LinkProgram(program uint32) {
C.glLinkProgram(C.uint(program))
}
func (GLImpl) ReadPixels(x int32, y int32, width int32, height int32, format uint32, xtype uint32, pixels unsafe.Pointer) {
C.glReadPixels(C.int(x), C.int(y), C.int(width), C.int(height), C.uint(format), C.uint(xtype), pixels)
}
func (GLImpl) RenderbufferStorage(target uint32, internalformat uint32, width int32, height int32) {
C.glRenderbufferStorage(C.uint(target), C.uint(internalformat), C.int(width), C.int(height))
}
func (GLImpl) Scissor(x int32, y int32, width int32, height int32) {
C.glScissor(C.int(x), C.int(y), C.int(width), C.int(height))
}
func (GLImpl) ShaderSource(shader uint32, source string) {
csource := C.CString(source)
defer C.free(unsafe.Pointer(csource))
C.glShaderSource(C.uint(shader), 1, &csource, nil)
}
func (GLImpl) StencilFunc(xfunc uint32, ref int32, mask uint32) {
C.glStencilFunc(C.uint(xfunc), C.int(ref), C.uint(mask))
}
func (GLImpl) StencilMask(mask uint32) {
C.glStencilMask(C.uint(mask))
}
func (GLImpl) StencilOp(fail uint32, zfail uint32, zpass uint32) {
C.glStencilOp(C.uint(fail), C.uint(zfail), C.uint(zpass))
}
func (GLImpl) TexImage2D(target uint32, level int32, internalformat int32, width int32, height int32, border int32, format uint32, xtype uint32, pixels unsafe.Pointer) {
C.glTexImage2D(C.uint(target), C.int(level), C.int(internalformat), C.int(width), C.int(height), C.int(border), C.uint(format), C.uint(xtype), pixels)
}
func (GLImpl) TexParameteri(target uint32, pname uint32, param int32) {
C.glTexParameteri(C.uint(target), C.uint(pname), C.int(param))
}
func (GLImpl) TexSubImage2D(target uint32, level int32, xoffset int32, yoffset int32, width int32, height int32, format uint32, xtype uint32, pixels unsafe.Pointer) {
C.glTexSubImage2D(C.uint(target), C.int(level), C.int(xoffset), C.int(yoffset), C.int(width), C.int(height), C.uint(format), C.uint(xtype), pixels)
}
func (GLImpl) Uniform1f(location int32, v0 float32) {
C.glUniform1f(C.int(location), C.float(v0))
}
func (GLImpl) Uniform1fv(location int32, count int32, v *float32) {
C.glUniform1fv(C.int(location), C.int(count), (*C.float)(v))
}
func (GLImpl) Uniform1i(location int32, v0 int32) {
C.glUniform1i(C.int(location), C.int(v0))
}
func (GLImpl) Uniform2f(location int32, v0 float32, v1 float32) {
C.glUniform2f(C.int(location), C.float(v0), C.float(v1))
}
func (GLImpl) Uniform4f(location int32, v0 float32, v1 float32, v2 float32, v3 float32) {
C.glUniform4f(C.int(location), C.float(v0), C.float(v1), C.float(v2), C.float(v3))
}
func (GLImpl) UniformMatrix3fv(location int32, count int32, transpose bool, value *float32) {
var t C.uchar
if transpose {
t = 1
}
C.glUniformMatrix3fv(C.int(location), C.int(count), t, (*C.float)(value))
}
func (GLImpl) UseProgram(program uint32) {
C.glUseProgram(C.uint(program))
}
func (GLImpl) VertexAttribPointer(index uint32, size int32, xtype uint32, normalized bool, stride int32, offset uint32) {
var n C.uchar
if normalized {
n = 1
}
C.glVertexAttribPointer(C.uint(index), C.int(size), C.uint(xtype), n, C.int(stride), unsafe.Pointer(uintptr(offset)))
}
func (GLImpl) Viewport(x int32, y int32, width int32, height int32) {
C.glViewport(C.int(x), C.int(y), C.int(width), C.int(height))
}

View file

@ -1,207 +0,0 @@
package glimplgogl
import (
"strings"
"unsafe"
"github.com/go-gl/gl/v3.2-core/gl"
"github.com/tfriedel6/canvas"
)
type GLImpl struct{}
var _ canvas.GL = GLImpl{}
func (GLImpl) Ptr(data interface{}) unsafe.Pointer {
return gl.Ptr(data)
}
func (GLImpl) ActiveTexture(texture uint32) {
gl.ActiveTexture(texture)
}
func (GLImpl) AttachShader(program uint32, shader uint32) {
gl.AttachShader(program, shader)
}
func (GLImpl) BindBuffer(target uint32, buffer uint32) {
gl.BindBuffer(target, buffer)
}
func (GLImpl) BindFramebuffer(target uint32, framebuffer uint32) {
gl.BindFramebuffer(target, framebuffer)
}
func (GLImpl) BindRenderbuffer(target uint32, renderbuffer uint32) {
gl.BindRenderbuffer(target, renderbuffer)
}
func (GLImpl) BindTexture(target uint32, texture uint32) {
gl.BindTexture(target, texture)
}
func (GLImpl) BlendFunc(sfactor uint32, dfactor uint32) {
gl.BlendFunc(sfactor, dfactor)
}
func (GLImpl) BufferData(target uint32, size int, data unsafe.Pointer, usage uint32) {
gl.BufferData(target, size, data, usage)
}
func (GLImpl) CheckFramebufferStatus(target uint32) uint32 {
return gl.CheckFramebufferStatus(target)
}
func (GLImpl) Clear(mask uint32) {
gl.Clear(mask)
}
func (GLImpl) ClearColor(red float32, green float32, blue float32, alpha float32) {
gl.ClearColor(red, green, blue, alpha)
}
func (GLImpl) ColorMask(red bool, green bool, blue bool, alpha bool) {
gl.ColorMask(red, green, blue, alpha)
}
func (GLImpl) CompileShader(shader uint32) {
gl.CompileShader(shader)
}
func (GLImpl) CreateProgram() uint32 {
return gl.CreateProgram()
}
func (GLImpl) CreateShader(xtype uint32) uint32 {
return gl.CreateShader(xtype)
}
func (GLImpl) DeleteShader(shader uint32) {
gl.DeleteShader(shader)
}
func (GLImpl) DeleteFramebuffers(n int32, framebuffers *uint32) {
gl.DeleteFramebuffers(n, framebuffers)
}
func (GLImpl) DeleteRenderbuffers(n int32, renderbuffers *uint32) {
gl.DeleteRenderbuffers(n, renderbuffers)
}
func (GLImpl) DeleteTextures(n int32, textures *uint32) {
gl.DeleteTextures(n, textures)
}
func (GLImpl) Disable(cap uint32) {
gl.Disable(cap)
}
func (GLImpl) DisableVertexAttribArray(index uint32) {
gl.DisableVertexAttribArray(index)
}
func (GLImpl) DrawArrays(mode uint32, first int32, count int32) {
gl.DrawArrays(mode, first, count)
}
func (GLImpl) Enable(cap uint32) {
gl.Enable(cap)
}
func (GLImpl) EnableVertexAttribArray(index uint32) {
gl.EnableVertexAttribArray(index)
}
func (GLImpl) FramebufferRenderbuffer(target uint32, attachment uint32, renderbuffertarget uint32, renderbuffer uint32) {
gl.FramebufferRenderbuffer(target, attachment, renderbuffertarget, renderbuffer)
}
func (GLImpl) FramebufferTexture(target uint32, attachment uint32, texture uint32, level int32) {
gl.FramebufferTexture(target, attachment, texture, level)
}
func (GLImpl) GenBuffers(n int32, buffers *uint32) {
gl.GenBuffers(n, buffers)
}
func (GLImpl) GenFramebuffers(n int32, framebuffers *uint32) {
gl.GenFramebuffers(n, framebuffers)
}
func (GLImpl) GenRenderbuffers(n int32, renderbuffers *uint32) {
gl.GenRenderbuffers(n, renderbuffers)
}
func (GLImpl) GenTextures(n int32, textures *uint32) {
gl.GenTextures(n, textures)
}
func (GLImpl) GenerateMipmap(target uint32) {
gl.GenerateMipmap(target)
}
func (GLImpl) GetAttribLocation(program uint32, name string) int32 {
return gl.GetAttribLocation(program, gl.Str(name+"\x00"))
}
func (GLImpl) GetError() uint32 {
return gl.GetError()
}
func (GLImpl) GetProgramInfoLog(program uint32) string {
var length int32
gl.GetProgramiv(program, gl.INFO_LOG_LENGTH, &length)
if length == 0 {
return ""
}
log := strings.Repeat("\x00", int(length+1))
gl.GetProgramInfoLog(program, length, nil, gl.Str(log))
return log
}
func (GLImpl) GetProgramiv(program uint32, pname uint32, params *int32) {
gl.GetProgramiv(program, pname, params)
}
func (GLImpl) GetShaderInfoLog(program uint32) string {
var length int32
gl.GetShaderiv(program, gl.INFO_LOG_LENGTH, &length)
if length == 0 {
return ""
}
log := strings.Repeat("\x00", int(length+1))
gl.GetShaderInfoLog(program, length, nil, gl.Str(log))
return log
}
func (GLImpl) GetShaderiv(shader uint32, pname uint32, params *int32) {
gl.GetShaderiv(shader, pname, params)
}
func (GLImpl) GetUniformLocation(program uint32, name string) int32 {
return gl.GetUniformLocation(program, gl.Str(name+"\x00"))
}
func (GLImpl) LinkProgram(program uint32) {
gl.LinkProgram(program)
}
func (GLImpl) RenderbufferStorage(target uint32, internalformat uint32, width int32, height int32) {
gl.RenderbufferStorage(target, internalformat, width, height)
}
func (GLImpl) ReadPixels(x int32, y int32, width int32, height int32, format uint32, xtype uint32, pixels unsafe.Pointer) {
gl.ReadPixels(x, y, width, height, format, xtype, pixels)
}
func (GLImpl) Scissor(x int32, y int32, width int32, height int32) {
gl.Scissor(x, y, width, height)
}
func (GLImpl) ShaderSource(shader uint32, source string) {
csource, freeFunc := gl.Strs(source + "\x00")
gl.ShaderSource(shader, 1, csource, nil)
freeFunc()
}
func (GLImpl) StencilFunc(xfunc uint32, ref int32, mask uint32) {
gl.StencilFunc(xfunc, ref, mask)
}
func (GLImpl) StencilMask(mask uint32) {
gl.StencilMask(mask)
}
func (GLImpl) StencilOp(fail uint32, zfail uint32, zpass uint32) {
gl.StencilOp(fail, zfail, zpass)
}
func (GLImpl) TexImage2D(target uint32, level int32, internalformat int32, width int32, height int32, border int32, format uint32, xtype uint32, pixels unsafe.Pointer) {
gl.TexImage2D(target, level, internalformat, width, height, border, format, xtype, pixels)
}
func (GLImpl) TexParameteri(target uint32, pname uint32, param int32) {
gl.TexParameteri(target, pname, param)
}
func (GLImpl) TexSubImage2D(target uint32, level int32, xoffset int32, yoffset int32, width int32, height int32, format uint32, xtype uint32, pixels unsafe.Pointer) {
gl.TexSubImage2D(target, level, xoffset, yoffset, width, height, format, xtype, pixels)
}
func (GLImpl) Uniform1f(location int32, v0 float32) {
gl.Uniform1f(location, v0)
}
func (GLImpl) Uniform1fv(location int32, count int32, v *float32) {
gl.Uniform1fv(location, count, v)
}
func (GLImpl) Uniform1i(location int32, v0 int32) {
gl.Uniform1i(location, v0)
}
func (GLImpl) Uniform2f(location int32, v0 float32, v1 float32) {
gl.Uniform2f(location, v0, v1)
}
func (GLImpl) Uniform4f(location int32, v0 float32, v1 float32, v2 float32, v3 float32) {
gl.Uniform4f(location, v0, v1, v2, v3)
}
func (GLImpl) UniformMatrix3fv(location int32, count int32, transpose bool, value *float32) {
gl.UniformMatrix3fv(location, count, transpose, value)
}
func (GLImpl) UseProgram(program uint32) {
gl.UseProgram(program)
}
func (GLImpl) VertexAttribPointer(index uint32, size int32, xtype uint32, normalized bool, stride int32, offset uint32) {
gl.VertexAttribPointer(index, size, xtype, normalized, stride, gl.PtrOffset(int(offset)))
}
func (GLImpl) Viewport(x int32, y int32, width int32, height int32) {
gl.Viewport(x, y, width, height)
}

View file

@ -1,263 +0,0 @@
package glimplios
// #include <stdlib.h>
// #include <OpenGLES/ES2/gl.h>
// #cgo ios LDFLAGS: -framework OpenGLES
import "C"
import (
"fmt"
"reflect"
"unsafe"
"github.com/tfriedel6/canvas"
)
type GLImpl struct{}
var _ canvas.GL = GLImpl{}
func (GLImpl) Ptr(data interface{}) unsafe.Pointer {
if data == nil {
return unsafe.Pointer(nil)
}
var addr unsafe.Pointer
v := reflect.ValueOf(data)
switch v.Type().Kind() {
case reflect.Ptr:
e := v.Elem()
switch e.Kind() {
case
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
reflect.Float32, reflect.Float64:
addr = unsafe.Pointer(e.UnsafeAddr())
default:
panic(fmt.Errorf("unsupported pointer to type %s; must be a slice or pointer to a singular scalar value or the first element of an array or slice", e.Kind()))
}
case reflect.Uintptr:
addr = unsafe.Pointer(v.Pointer())
case reflect.Slice:
addr = unsafe.Pointer(v.Index(0).UnsafeAddr())
default:
panic(fmt.Errorf("unsupported type %s; must be a slice or pointer to a singular scalar value or the first element of an array or slice", v.Type()))
}
return addr
}
func (GLImpl) ActiveTexture(texture uint32) {
C.glActiveTexture(C.GLenum(texture))
}
func (GLImpl) AttachShader(program uint32, shader uint32) {
C.glAttachShader(C.GLuint(program), C.GLuint(shader))
}
func (GLImpl) BindBuffer(target uint32, buffer uint32) {
C.glBindBuffer(C.GLenum(target), C.GLuint(buffer))
}
func (GLImpl) BindFramebuffer(target uint32, framebuffer uint32) {
C.glBindFramebuffer(C.GLenum(target), C.GLuint(framebuffer))
}
func (GLImpl) BindRenderbuffer(target uint32, renderbuffer uint32) {
C.glBindRenderbuffer(C.GLenum(target), C.GLuint(renderbuffer))
}
func (GLImpl) BindTexture(target uint32, texture uint32) {
C.glBindTexture(C.GLenum(target), C.GLuint(texture))
}
func (GLImpl) BlendFunc(sfactor uint32, dfactor uint32) {
C.glBlendFunc(C.GLenum(sfactor), C.GLenum(dfactor))
}
func (GLImpl) BufferData(target uint32, size int, data unsafe.Pointer, usage uint32) {
C.glBufferData(C.GLenum(target), C.GLsizeiptr(size), data, C.GLenum(usage))
}
func (GLImpl) CheckFramebufferStatus(target uint32) uint32 {
return uint32(C.glCheckFramebufferStatus(C.GLenum(target)))
}
func (GLImpl) Clear(mask uint32) {
C.glClear(C.GLbitfield(mask))
}
func (GLImpl) ClearColor(red float32, green float32, blue float32, alpha float32) {
C.glClearColor(C.GLfloat(red), C.GLfloat(green), C.GLfloat(blue), C.GLfloat(alpha))
}
func (GLImpl) ColorMask(red bool, green bool, blue bool, alpha bool) {
var r, g, b, a C.GLboolean
if red {
r = 1
}
if green {
g = 1
}
if blue {
b = 1
}
if alpha {
a = 1
}
C.glColorMask(r, g, b, a)
}
func (GLImpl) CompileShader(shader uint32) {
C.glCompileShader(C.GLuint(shader))
}
func (GLImpl) CreateProgram() uint32 {
return uint32(C.glCreateProgram())
}
func (GLImpl) CreateShader(xtype uint32) uint32 {
return uint32(C.glCreateShader(C.GLenum(xtype)))
}
func (GLImpl) DeleteShader(shader uint32) {
C.glDeleteShader(C.GLuint(shader))
}
func (GLImpl) DeleteFramebuffers(n int32, framebuffers *uint32) {
C.glDeleteFramebuffers(C.GLsizei(n), (*C.GLuint)(framebuffers))
}
func (GLImpl) DeleteRenderbuffers(n int32, renderbuffers *uint32) {
C.glDeleteRenderbuffers(C.GLsizei(n), (*C.GLuint)(renderbuffers))
}
func (GLImpl) DeleteTextures(n int32, textures *uint32) {
C.glDeleteTextures(C.GLsizei(n), (*C.GLuint)(textures))
}
func (GLImpl) Disable(cap uint32) {
C.glDisable(C.GLenum(cap))
}
func (GLImpl) DisableVertexAttribArray(index uint32) {
C.glDisableVertexAttribArray(C.GLuint(index))
}
func (GLImpl) DrawArrays(mode uint32, first int32, count int32) {
C.glDrawArrays(C.GLenum(mode), C.GLint(first), C.GLsizei(count))
}
func (GLImpl) Enable(cap uint32) {
C.glEnable(C.GLenum(cap))
}
func (GLImpl) EnableVertexAttribArray(index uint32) {
C.glEnableVertexAttribArray(C.GLuint(index))
}
func (GLImpl) FramebufferRenderbuffer(target uint32, attachment uint32, renderbuffertarget uint32, renderbuffer uint32) {
C.glFramebufferRenderbuffer(C.GLenum(target), C.GLenum(attachment), C.GLenum(renderbuffertarget), C.GLuint(renderbuffer))
}
func (GLImpl) FramebufferTexture(target uint32, attachment uint32, texture uint32, level int32) {
C.glFramebufferTexture2D(C.GLenum(target), C.GLenum(attachment), C.GL_TEXTURE_2D, C.GLuint(texture), C.GLint(level))
}
func (GLImpl) GenBuffers(n int32, buffers *uint32) {
C.glGenBuffers(C.GLsizei(n), (*C.GLuint)(buffers))
}
func (GLImpl) GenFramebuffers(n int32, framebuffers *uint32) {
C.glGenFramebuffers(C.GLsizei(n), (*C.GLuint)(framebuffers))
}
func (GLImpl) GenRenderbuffers(n int32, renderbuffers *uint32) {
C.glGenRenderbuffers(C.GLsizei(n), (*C.GLuint)(renderbuffers))
}
func (GLImpl) GenTextures(n int32, textures *uint32) {
C.glGenTextures(C.GLsizei(n), (*C.GLuint)(textures))
}
func (GLImpl) GenerateMipmap(target uint32) {
C.glGenerateMipmap(C.GLenum(target))
}
func (GLImpl) GetAttribLocation(program uint32, name string) int32 {
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
return int32(C.glGetAttribLocation(C.GLuint(program), (*C.GLchar)(cname)))
}
func (GLImpl) GetError() uint32 {
return uint32(C.glGetError())
}
func (GLImpl) GetProgramInfoLog(program uint32) string {
var length C.GLint
C.glGetProgramiv(C.GLuint(program), C.GL_INFO_LOG_LENGTH, &length)
if length == 0 {
return ""
}
clog := C.CBytes(make([]byte, int(length)+1))
defer C.free(clog)
C.glGetProgramInfoLog(C.GLuint(program), C.GLsizei(length), nil, (*C.GLchar)(clog))
return string(C.GoBytes(clog, C.int(length)))
}
func (GLImpl) GetProgramiv(program uint32, pname uint32, params *int32) {
C.glGetProgramiv(C.GLuint(program), C.GLenum(pname), (*C.GLint)(params))
}
func (GLImpl) GetShaderInfoLog(program uint32) string {
var length C.GLint
C.glGetShaderiv(C.GLuint(program), C.GL_INFO_LOG_LENGTH, &length)
if length == 0 {
return ""
}
clog := C.CBytes(make([]byte, int(length)+1))
defer C.free(clog)
C.glGetShaderInfoLog(C.GLuint(program), C.GLsizei(length), nil, (*C.GLchar)(clog))
return string(C.GoBytes(clog, C.int(length)))
}
func (GLImpl) GetShaderiv(shader uint32, pname uint32, params *int32) {
C.glGetShaderiv(C.GLuint(shader), C.GLenum(pname), (*C.GLint)(params))
}
func (GLImpl) GetUniformLocation(program uint32, name string) int32 {
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
return int32(C.glGetUniformLocation(C.GLuint(program), (*C.GLchar)(cname)))
}
func (GLImpl) LinkProgram(program uint32) {
C.glLinkProgram(C.GLuint(program))
}
func (GLImpl) ReadPixels(x int32, y int32, width int32, height int32, format uint32, xtype uint32, pixels unsafe.Pointer) {
C.glReadPixels(C.GLint(x), C.GLint(y), C.GLsizei(width), C.GLsizei(height), C.GLenum(format), C.GLenum(xtype), pixels)
}
func (GLImpl) RenderbufferStorage(target uint32, internalformat uint32, width int32, height int32) {
C.glRenderbufferStorage(C.GLenum(target), C.GLenum(internalformat), C.GLint(width), C.GLint(height))
}
func (GLImpl) Scissor(x int32, y int32, width int32, height int32) {
C.glScissor(C.GLint(x), C.GLint(y), C.GLsizei(width), C.GLsizei(height))
}
func (GLImpl) ShaderSource(shader uint32, source string) {
csource := (*C.GLchar)(C.CString(source))
defer C.free(unsafe.Pointer(csource))
C.glShaderSource(C.GLuint(shader), 1, &csource, nil)
}
func (GLImpl) StencilFunc(xfunc uint32, ref int32, mask uint32) {
C.glStencilFunc(C.GLenum(xfunc), C.GLint(ref), C.GLuint(mask))
}
func (GLImpl) StencilMask(mask uint32) {
C.glStencilMask(C.GLuint(mask))
}
func (GLImpl) StencilOp(fail uint32, zfail uint32, zpass uint32) {
C.glStencilOp(C.GLenum(fail), C.GLenum(zfail), C.GLenum(zpass))
}
func (GLImpl) TexImage2D(target uint32, level int32, internalformat int32, width int32, height int32, border int32, format uint32, xtype uint32, pixels unsafe.Pointer) {
C.glTexImage2D(C.GLenum(target), C.GLint(level), C.GLint(internalformat), C.GLsizei(width), C.GLsizei(height), C.GLint(border), C.GLenum(format), C.GLenum(xtype), pixels)
}
func (GLImpl) TexParameteri(target uint32, pname uint32, param int32) {
C.glTexParameteri(C.GLenum(target), C.GLenum(pname), C.GLint(param))
}
func (GLImpl) TexSubImage2D(target uint32, level int32, xoffset int32, yoffset int32, width int32, height int32, format uint32, xtype uint32, pixels unsafe.Pointer) {
C.glTexSubImage2D(C.GLenum(target), C.GLint(level), C.GLint(xoffset), C.GLint(yoffset), C.GLsizei(width), C.GLsizei(height), C.GLenum(format), C.GLenum(xtype), pixels)
}
func (GLImpl) Uniform1f(location int32, v0 float32) {
C.glUniform1f(C.GLint(location), C.GLfloat(v0))
}
func (GLImpl) Uniform1fv(location int32, count int32, v *float32) {
C.glUniform1fv(C.GLint(location), C.GLsizei(count), (*C.GLfloat)(v))
}
func (GLImpl) Uniform1i(location int32, v0 int32) {
C.glUniform1i(C.GLint(location), C.GLint(v0))
}
func (GLImpl) Uniform2f(location int32, v0 float32, v1 float32) {
C.glUniform2f(C.GLint(location), C.GLfloat(v0), C.GLfloat(v1))
}
func (GLImpl) Uniform4f(location int32, v0 float32, v1 float32, v2 float32, v3 float32) {
C.glUniform4f(C.GLint(location), C.GLfloat(v0), C.GLfloat(v1), C.GLfloat(v2), C.GLfloat(v3))
}
func (GLImpl) UniformMatrix3fv(location int32, count int32, transpose bool, value *float32) {
var t C.GLboolean
if transpose {
t = 1
}
C.glUniformMatrix3fv(C.GLint(location), C.GLsizei(count), t, (*C.GLfloat)(value))
}
func (GLImpl) UseProgram(program uint32) {
C.glUseProgram(C.GLuint(program))
}
func (GLImpl) VertexAttribPointer(index uint32, size int32, xtype uint32, normalized bool, stride int32, offset uint32) {
var n C.GLboolean
if normalized {
n = 1
}
C.glVertexAttribPointer(C.GLuint(index), C.GLint(size), C.GLenum(xtype), n, C.GLsizei(stride), unsafe.Pointer(uintptr(offset)))
}
func (GLImpl) Viewport(x int32, y int32, width int32, height int32) {
C.glViewport(C.GLint(x), C.GLint(y), C.GLsizei(width), C.GLsizei(height))
}

View file

@ -1,309 +0,0 @@
package glimplxmobile
import (
"fmt"
"reflect"
"unsafe"
"github.com/tfriedel6/canvas"
"golang.org/x/mobile/gl"
)
type GLImpl struct {
gl gl.Context
programs map[uint32]gl.Program
}
var _ canvas.GL = GLImpl{}
func New(ctx gl.Context) *GLImpl {
return &GLImpl{
gl: ctx,
programs: make(map[uint32]gl.Program),
}
}
func (gli GLImpl) Ptr(data interface{}) unsafe.Pointer {
if data == nil {
return unsafe.Pointer(nil)
}
var addr unsafe.Pointer
v := reflect.ValueOf(data)
switch v.Type().Kind() {
case reflect.Ptr:
e := v.Elem()
switch e.Kind() {
case
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
reflect.Float32, reflect.Float64:
addr = unsafe.Pointer(e.UnsafeAddr())
default:
panic(fmt.Errorf("unsupported pointer to type %s; must be a slice or pointer to a singular scalar value or the first element of an array or slice", e.Kind()))
}
case reflect.Uintptr:
addr = unsafe.Pointer(v.Pointer())
case reflect.Slice:
addr = unsafe.Pointer(v.Index(0).UnsafeAddr())
default:
panic(fmt.Errorf("unsupported type %s; must be a slice or pointer to a singular scalar value or the first element of an array or slice", v.Type()))
}
return addr
}
func (gli GLImpl) ActiveTexture(texture uint32) {
gli.gl.ActiveTexture(gl.Enum(texture))
}
func (gli GLImpl) AttachShader(program uint32, shader uint32) {
gli.gl.AttachShader(gli.programs[program], gl.Shader{Value: shader})
}
func (gli GLImpl) BindBuffer(target uint32, buffer uint32) {
gli.gl.BindBuffer(gl.Enum(target), gl.Buffer{Value: buffer})
}
func (gli GLImpl) BindFramebuffer(target uint32, framebuffer uint32) {
gli.gl.BindFramebuffer(gl.Enum(target), gl.Framebuffer{Value: framebuffer})
}
func (gli GLImpl) BindRenderbuffer(target uint32, renderbuffer uint32) {
gli.gl.BindRenderbuffer(gl.Enum(target), gl.Renderbuffer{Value: renderbuffer})
}
func (gli GLImpl) BindTexture(target uint32, texture uint32) {
gli.gl.BindTexture(gl.Enum(target), gl.Texture{Value: texture})
}
func (gli GLImpl) BlendFunc(sfactor uint32, dfactor uint32) {
gli.gl.BlendFunc(gl.Enum(sfactor), gl.Enum(dfactor))
}
func (gli GLImpl) BufferData(target uint32, size int, data unsafe.Pointer, usage uint32) {
var buf []byte
sh := (*reflect.SliceHeader)(unsafe.Pointer(&buf))
sh.Cap = size
sh.Len = size
sh.Data = uintptr(data)
gli.gl.BufferData(gl.Enum(target), buf, gl.Enum(usage))
}
func (gli GLImpl) CheckFramebufferStatus(target uint32) uint32 {
return uint32(gli.gl.CheckFramebufferStatus(gl.Enum(target)))
}
func (gli GLImpl) Clear(mask uint32) {
gli.gl.Clear(gl.Enum(mask))
}
func (gli GLImpl) ClearColor(red float32, green float32, blue float32, alpha float32) {
gli.gl.ClearColor(red, green, blue, alpha)
}
func (gli GLImpl) ColorMask(red bool, green bool, blue bool, alpha bool) {
gli.gl.ColorMask(red, green, blue, alpha)
}
func (gli GLImpl) CompileShader(shader uint32) {
gli.gl.CompileShader(gl.Shader{Value: shader})
}
func (gli GLImpl) CreateProgram() uint32 {
program := gli.gl.CreateProgram()
gli.programs[program.Value] = program
return program.Value
}
func (gli GLImpl) CreateShader(xtype uint32) uint32 {
return gli.gl.CreateShader(gl.Enum(xtype)).Value
}
func (gli GLImpl) DeleteShader(shader uint32) {
gli.gl.DeleteShader(gl.Shader{Value: shader})
}
func (gli GLImpl) DeleteFramebuffers(n int32, framebuffers *uint32) {
var buf []uint32
sh := (*reflect.SliceHeader)(unsafe.Pointer(&buf))
sh.Cap = int(n)
sh.Len = int(n)
sh.Data = uintptr(unsafe.Pointer(framebuffers))
for i := 0; i < int(n); i++ {
gli.gl.DeleteFramebuffer(gl.Framebuffer{Value: buf[i]})
}
}
func (gli GLImpl) DeleteRenderbuffers(n int32, renderbuffers *uint32) {
var buf []uint32
sh := (*reflect.SliceHeader)(unsafe.Pointer(&buf))
sh.Cap = int(n)
sh.Len = int(n)
sh.Data = uintptr(unsafe.Pointer(renderbuffers))
for i := 0; i < int(n); i++ {
gli.gl.DeleteRenderbuffer(gl.Renderbuffer{Value: buf[i]})
}
}
func (gli GLImpl) DeleteTextures(n int32, textures *uint32) {
var buf []uint32
sh := (*reflect.SliceHeader)(unsafe.Pointer(&buf))
sh.Cap = int(n)
sh.Len = int(n)
sh.Data = uintptr(unsafe.Pointer(textures))
for i := 0; i < int(n); i++ {
gli.gl.DeleteTexture(gl.Texture{Value: buf[i]})
}
}
func (gli GLImpl) Disable(cap uint32) {
gli.gl.Disable(gl.Enum(cap))
}
func (gli GLImpl) DisableVertexAttribArray(index uint32) {
gli.gl.DisableVertexAttribArray(gl.Attrib{Value: uint(index)})
}
func (gli GLImpl) DrawArrays(mode uint32, first int32, count int32) {
gli.gl.DrawArrays(gl.Enum(mode), int(first), int(count))
}
func (gli GLImpl) Enable(cap uint32) {
gli.gl.Enable(gl.Enum(cap))
}
func (gli GLImpl) EnableVertexAttribArray(index uint32) {
gli.gl.EnableVertexAttribArray(gl.Attrib{Value: uint(index)})
}
func (gli GLImpl) FramebufferRenderbuffer(target uint32, attachment uint32, renderbuffertarget uint32, renderbuffer uint32) {
gli.gl.FramebufferRenderbuffer(gl.Enum(target), gl.Enum(attachment), gl.Enum(renderbuffertarget), gl.Renderbuffer{Value: renderbuffer})
}
func (gli GLImpl) FramebufferTexture(target uint32, attachment uint32, texture uint32, level int32) {
gli.gl.FramebufferTexture2D(gl.Enum(target), gl.Enum(attachment), gl.TEXTURE_2D, gl.Texture{Value: texture}, int(level))
}
func (gli GLImpl) GenBuffers(n int32, buffers *uint32) {
var buf []uint32
sh := (*reflect.SliceHeader)(unsafe.Pointer(&buf))
sh.Cap = int(n)
sh.Len = int(n)
sh.Data = uintptr(unsafe.Pointer(buffers))
for i := 0; i < int(n); i++ {
buf[i] = gli.gl.CreateBuffer().Value
}
}
func (gli GLImpl) GenFramebuffers(n int32, framebuffers *uint32) {
var buf []uint32
sh := (*reflect.SliceHeader)(unsafe.Pointer(&buf))
sh.Cap = int(n)
sh.Len = int(n)
sh.Data = uintptr(unsafe.Pointer(framebuffers))
for i := 0; i < int(n); i++ {
buf[i] = gli.gl.CreateFramebuffer().Value
}
}
func (gli GLImpl) GenRenderbuffers(n int32, renderbuffers *uint32) {
var buf []uint32
sh := (*reflect.SliceHeader)(unsafe.Pointer(&buf))
sh.Cap = int(n)
sh.Len = int(n)
sh.Data = uintptr(unsafe.Pointer(renderbuffers))
for i := 0; i < int(n); i++ {
buf[i] = gli.gl.CreateRenderbuffer().Value
}
}
func (gli GLImpl) GenTextures(n int32, textures *uint32) {
var buf []uint32
sh := (*reflect.SliceHeader)(unsafe.Pointer(&buf))
sh.Cap = int(n)
sh.Len = int(n)
sh.Data = uintptr(unsafe.Pointer(textures))
for i := 0; i < int(n); i++ {
buf[i] = gli.gl.CreateTexture().Value
}
}
func (gli GLImpl) GenerateMipmap(target uint32) {
gli.gl.GenerateMipmap(gl.Enum(target))
}
func (gli GLImpl) GetAttribLocation(program uint32, name string) int32 {
return int32(gli.gl.GetAttribLocation(gli.programs[program], name).Value)
}
func (gli GLImpl) GetError() uint32 {
return uint32(gli.gl.GetError())
}
func (gli GLImpl) GetProgramInfoLog(program uint32) string {
return gli.gl.GetProgramInfoLog(gli.programs[program])
}
func (gli GLImpl) GetProgramiv(program uint32, pname uint32, params *int32) {
i := gli.gl.GetProgrami(gli.programs[program], gl.Enum(pname))
*params = int32(i)
}
func (gli GLImpl) GetShaderInfoLog(program uint32) string {
return gli.gl.GetShaderInfoLog(gl.Shader{Value: program})
}
func (gli GLImpl) GetShaderiv(shader uint32, pname uint32, params *int32) {
i := gli.gl.GetShaderi(gl.Shader{Value: shader}, gl.Enum(pname))
*params = int32(i)
}
func (gli GLImpl) GetUniformLocation(program uint32, name string) int32 {
return gli.gl.GetUniformLocation(gli.programs[program], name).Value
}
func (gli GLImpl) LinkProgram(program uint32) {
gli.gl.LinkProgram(gli.programs[program])
}
func (gli GLImpl) ReadPixels(x int32, y int32, width int32, height int32, format uint32, xtype uint32, pixels unsafe.Pointer) {
var buf []byte
sh := (*reflect.SliceHeader)(unsafe.Pointer(&buf))
sh.Cap = int(width * height * 4)
sh.Len = int(width * height * 4)
sh.Data = uintptr(pixels)
gli.gl.ReadPixels(buf, int(x), int(y), int(width), int(height), gl.Enum(format), gl.Enum(xtype))
}
func (gli GLImpl) RenderbufferStorage(target uint32, internalformat uint32, width int32, height int32) {
gli.gl.RenderbufferStorage(gl.Enum(target), gl.Enum(internalformat), int(width), int(height))
}
func (gli GLImpl) Scissor(x int32, y int32, width int32, height int32) {
gli.gl.Scissor(x, y, width, height)
}
func (gli GLImpl) ShaderSource(shader uint32, source string) {
gli.gl.ShaderSource(gl.Shader{Value: shader}, source)
}
func (gli GLImpl) StencilFunc(xfunc uint32, ref int32, mask uint32) {
gli.gl.StencilFunc(gl.Enum(xfunc), int(ref), mask)
}
func (gli GLImpl) StencilMask(mask uint32) {
gli.gl.StencilMask(mask)
}
func (gli GLImpl) StencilOp(fail uint32, zfail uint32, zpass uint32) {
gli.gl.StencilOp(gl.Enum(fail), gl.Enum(zfail), gl.Enum(zpass))
}
func (gli GLImpl) TexImage2D(target uint32, level int32, internalformat int32, width int32, height int32, border int32, format uint32, xtype uint32, pixels unsafe.Pointer) {
var buf []byte
sh := (*reflect.SliceHeader)(unsafe.Pointer(&buf))
sh.Cap = int(width * height * 4)
sh.Len = int(width * height * 4)
sh.Data = uintptr(pixels)
gli.gl.TexImage2D(gl.Enum(target), int(level), int(internalformat), int(width), int(height), gl.Enum(format), gl.Enum(xtype), buf)
}
func (gli GLImpl) TexParameteri(target uint32, pname uint32, param int32) {
gli.gl.TexParameteri(gl.Enum(target), gl.Enum(pname), int(param))
}
func (gli GLImpl) TexSubImage2D(target uint32, level int32, xoffset int32, yoffset int32, width int32, height int32, format uint32, xtype uint32, pixels unsafe.Pointer) {
var buf []byte
sh := (*reflect.SliceHeader)(unsafe.Pointer(&buf))
sh.Cap = int(width * height * 4)
sh.Len = int(width * height * 4)
sh.Data = uintptr(pixels)
gli.gl.TexSubImage2D(gl.Enum(target), int(level), int(xoffset), int(yoffset), int(width), int(height), gl.Enum(format), gl.Enum(xtype), buf)
}
func (gli GLImpl) Uniform1f(location int32, v0 float32) {
gli.gl.Uniform1f(gl.Uniform{Value: location}, v0)
}
func (gli GLImpl) Uniform1fv(location int32, count int32, v *float32) {
var buf []float32
sh := (*reflect.SliceHeader)(unsafe.Pointer(&buf))
sh.Cap = int(count)
sh.Len = int(count)
sh.Data = uintptr(unsafe.Pointer(v))
gli.gl.Uniform1fv(gl.Uniform{Value: location}, buf)
}
func (gli GLImpl) Uniform1i(location int32, v0 int32) {
gli.gl.Uniform1i(gl.Uniform{Value: location}, int(v0))
}
func (gli GLImpl) Uniform2f(location int32, v0 float32, v1 float32) {
gli.gl.Uniform2f(gl.Uniform{Value: location}, v0, v1)
}
func (gli GLImpl) Uniform4f(location int32, v0 float32, v1 float32, v2 float32, v3 float32) {
gli.gl.Uniform4f(gl.Uniform{Value: location}, v0, v1, v2, v3)
}
func (gli GLImpl) UniformMatrix3fv(location int32, count int32, transpose bool, value *float32) {
var buf []float32
sh := (*reflect.SliceHeader)(unsafe.Pointer(&buf))
sh.Cap = 9
sh.Len = 9
sh.Data = uintptr(unsafe.Pointer(value))
gli.gl.UniformMatrix3fv(gl.Uniform{Value: location}, buf)
}
func (gli GLImpl) UseProgram(program uint32) {
gli.gl.UseProgram(gli.programs[program])
}
func (gli GLImpl) VertexAttribPointer(index uint32, size int32, xtype uint32, normalized bool, stride int32, offset uint32) {
gli.gl.VertexAttribPointer(gl.Attrib{Value: uint(index)}, int(size), gl.Enum(xtype), normalized, int(stride), int(offset))
}
func (gli GLImpl) Viewport(x int32, y int32, width int32, height int32) {
gli.gl.Viewport(int(x), int(y), int(width), int(height))
}

14
go.mod
View file

@ -1,14 +1,8 @@
module github.com/tfriedel6/canvas
module git.mstar.dev/mstar/canvas
require (
github.com/3d0c/gmf v0.0.0-20181011122539-af78d7462257
github.com/go-gl/gl v0.0.0-20181026044259-55b76b7df9d2
github.com/go-gl/glfw v0.0.0-20181014061658-691ee1b84c51
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
github.com/samuel/go-pcx v0.0.0-20180426214139-d9c017170db4
github.com/veandco/go-sdl2 v0.0.0-20180925095440-75ff82abc4e3
golang.org/x/exp v0.0.0-20181106170214-d68db9428509
golang.org/x/image v0.0.0-20181109002202-aa35264064ba
golang.org/x/mobile v0.0.0-20181026062114-a27dd33d354d
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8 // indirect
golang.org/x/image v0.0.0-20200801110659-972c09e46d76
)
go 1.13

21
go.sum
View file

@ -1,20 +1,5 @@
github.com/3d0c/gmf v0.0.0-20181011122539-af78d7462257 h1:1KJwjPgvqPK1vqUDRW3f6uczuX2NRvgjK0SKPUBxAnM=
github.com/3d0c/gmf v0.0.0-20181011122539-af78d7462257/go.mod h1:vFu/aQImUVU4s38rUuJr6KrEdkMOOnPIGE5ThNUavtg=
github.com/go-gl/gl v0.0.0-20181026044259-55b76b7df9d2 h1:78Hza2KHn2PX1jdydQnffaU2A/xM0g3Nx1xmMdep9Gk=
github.com/go-gl/gl v0.0.0-20181026044259-55b76b7df9d2/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk=
github.com/go-gl/glfw v0.0.0-20181014061658-691ee1b84c51 h1:elGSwayRx7uAsfA5PnVKeTHh+AVsUTmas0CkHOw/DSk=
github.com/go-gl/glfw v0.0.0-20181014061658-691ee1b84c51/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/samuel/go-pcx v0.0.0-20180426214139-d9c017170db4 h1:Y/KOCu+ZLB730PudefxfsKVjtI0m0RhvFk9a0l4O1+c=
github.com/samuel/go-pcx v0.0.0-20180426214139-d9c017170db4/go.mod h1:qxuIawynlRhuaHowuXvd1xjyFWx87Ro4gkZlKRXtHnQ=
github.com/veandco/go-sdl2 v0.0.0-20180925095440-75ff82abc4e3 h1:S1dvzp6ZDdmz1M4oRQ6ceLwU6ZDCNvIa20zhJRHyKgE=
github.com/veandco/go-sdl2 v0.0.0-20180925095440-75ff82abc4e3/go.mod h1:FB+kTpX9YTE+urhYiClnRzpOXbiWgaU3+5F2AB78DPg=
golang.org/x/exp v0.0.0-20181106170214-d68db9428509 h1:k21GX33vzpH/syMF7TgrLxe8ILtvwbyuHtEO3ebR82E=
golang.org/x/exp v0.0.0-20181106170214-d68db9428509/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/image v0.0.0-20181109002202-aa35264064ba h1:tKfAeDKyjJZwxAJ8TPBZaf6LpvauubUHT8wwpdz+OMM=
golang.org/x/image v0.0.0-20181109002202-aa35264064ba/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
golang.org/x/mobile v0.0.0-20181026062114-a27dd33d354d h1:DuZZDdMFwDrzmycNhCaWSve7Vh+BIrjm7ttgb4fD3Os=
golang.org/x/mobile v0.0.0-20181026062114-a27dd33d354d/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8 h1:YoY1wS6JYVRpIfFngRf2HHo9R9dAne3xbkGOQ5rJXjU=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/image v0.0.0-20200801110659-972c09e46d76 h1:U7GPaoQyQmX+CBRWXKrvRzWTbd+slqeSh8uARsIyhAw=
golang.org/x/image v0.0.0-20200801110659-972c09e46d76/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

View file

@ -1,8 +1,10 @@
package canvas
import (
"math"
"image/color"
"runtime"
"git.mstar.dev/mstar/canvas/backend/backendbase"
)
// LinearGradient is a gradient with any number of
@ -10,7 +12,13 @@ import (
// be drawn such that each point on the gradient
// will correspond to a straight line
type LinearGradient struct {
gradient
cv *Canvas
from, to backendbase.Vec
created bool
loaded bool
opaque bool
grad backendbase.LinearGradient
data backendbase.Gradient
}
// RadialGradient is a gradient with any number of
@ -18,146 +26,123 @@ type LinearGradient struct {
// be drawn such that each point on the gradient
// will correspond to a circle
type RadialGradient struct {
gradient
radFrom, radTo float64
}
type gradient struct {
from, to vec
stops []gradientStop
tex uint32
cv *Canvas
from, to backendbase.Vec
radFrom float64
radTo float64
created bool
loaded bool
deleted bool
opaque bool
grad backendbase.RadialGradient
data backendbase.Gradient
}
type gradientStop struct {
pos float64
color glColor
}
// NewLinearGradient creates a new linear gradient with
// CreateLinearGradient creates a new linear gradient with
// the coordinates from where to where the gradient
// will apply on the canvas
func NewLinearGradient(x0, y0, x1, y1 float64) *LinearGradient {
if gli == nil {
panic("LoadGL must be called before gradients can be created")
func (cv *Canvas) CreateLinearGradient(x0, y0, x1, y1 float64) *LinearGradient {
lg := &LinearGradient{
cv: cv,
opaque: true,
from: backendbase.Vec{x0, y0},
to: backendbase.Vec{x1, y1},
data: make(backendbase.Gradient, 0, 20),
}
lg := &LinearGradient{gradient: gradient{from: vec{x0, y0}, to: vec{x1, y1}, opaque: true}}
gli.GenTextures(1, &lg.tex)
gli.ActiveTexture(gl_TEXTURE0)
gli.BindTexture(gl_TEXTURE_2D, lg.tex)
gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, gl_LINEAR)
gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, gl_LINEAR)
gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_S, gl_CLAMP_TO_EDGE)
gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_T, gl_CLAMP_TO_EDGE)
runtime.SetFinalizer(lg, func(lg *LinearGradient) {
glChan <- func() {
gli.DeleteTextures(1, &lg.tex)
}
runtime.SetFinalizer(lg, func(*LinearGradient) {
lg.grad.Delete()
})
return lg
}
// NewRadialGradient creates a new linear gradient with
// CreateRadialGradient creates a new radial gradient with
// the coordinates and the radii for two circles. The
// gradient will apply from the first to the second
// circle
func NewRadialGradient(x0, y0, r0, x1, y1, r1 float64) *RadialGradient {
if gli == nil {
panic("LoadGL must be called before gradients can be created")
func (cv *Canvas) CreateRadialGradient(x0, y0, r0, x1, y1, r1 float64) *RadialGradient {
rg := &RadialGradient{
cv: cv,
opaque: true,
from: backendbase.Vec{x0, y0},
to: backendbase.Vec{x1, y1},
radFrom: r0,
radTo: r1,
data: make(backendbase.Gradient, 0, 20),
}
rg := &RadialGradient{gradient: gradient{from: vec{x0, y0}, to: vec{x1, y1}, opaque: true}, radFrom: r0, radTo: r1}
gli.GenTextures(1, &rg.tex)
gli.ActiveTexture(gl_TEXTURE0)
gli.BindTexture(gl_TEXTURE_2D, rg.tex)
gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, gl_LINEAR)
gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, gl_LINEAR)
gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_S, gl_CLAMP_TO_EDGE)
gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_T, gl_CLAMP_TO_EDGE)
runtime.SetFinalizer(rg, func(rg *RadialGradient) {
glChan <- func() {
gli.DeleteTextures(1, &rg.tex)
}
runtime.SetFinalizer(rg, func(*RadialGradient) {
rg.grad.Delete()
})
return rg
}
// Delete explicitly deletes the gradient
func (g *gradient) Delete() {
gli.DeleteTextures(1, &g.tex)
g.deleted = true
}
func (g *gradient) load() {
if g.loaded {
func (lg *LinearGradient) load() {
if lg.loaded || len(lg.data) < 1 {
return
}
gli.ActiveTexture(gl_TEXTURE0)
gli.BindTexture(gl_TEXTURE_2D, g.tex)
var pixels [2048 * 4]byte
pp := 0
for i := 0; i < 2048; i++ {
c := g.colorAt(float64(i) / 2047)
pixels[pp] = byte(math.Floor(c.r*255 + 0.5))
pixels[pp+1] = byte(math.Floor(c.g*255 + 0.5))
pixels[pp+2] = byte(math.Floor(c.b*255 + 0.5))
pixels[pp+3] = byte(math.Floor(c.a*255 + 0.5))
pp += 4
if !lg.created {
lg.grad = lg.cv.b.LoadLinearGradient(lg.data)
} else {
lg.grad.Replace(lg.data)
}
gli.TexImage2D(gl_TEXTURE_2D, 0, gl_RGBA, 2048, 1, 0, gl_RGBA, gl_UNSIGNED_BYTE, gli.Ptr(&pixels[0]))
g.loaded = true
lg.created = true
lg.loaded = true
}
func (g *gradient) colorAt(pos float64) glColor {
if len(g.stops) == 0 {
return glColor{}
} else if len(g.stops) == 1 {
return g.stops[0].color
func (rg *RadialGradient) load() {
if rg.loaded || len(rg.data) < 1 {
return
}
beforeIdx, afterIdx := -1, -1
for i, stop := range g.stops {
if stop.pos > pos {
afterIdx = i
break
}
beforeIdx = i
if !rg.created {
rg.grad = rg.cv.b.LoadRadialGradient(rg.data)
} else {
rg.grad.Replace(rg.data)
}
if beforeIdx == -1 {
return g.stops[0].color
} else if afterIdx == -1 {
return g.stops[len(g.stops)-1].color
}
before, after := g.stops[beforeIdx], g.stops[afterIdx]
p := (pos - before.pos) / (after.pos - before.pos)
var c glColor
c.r = (after.color.r-before.color.r)*p + before.color.r
c.g = (after.color.g-before.color.g)*p + before.color.g
c.b = (after.color.b-before.color.b)*p + before.color.b
c.a = (after.color.a-before.color.a)*p + before.color.a
return c
rg.created = true
rg.loaded = true
}
// AddColorStop adds a color stop to the gradient. The stops
// don't have to be added in order, they are sorted into the
// right place
func (g *gradient) AddColorStop(pos float64, color ...interface{}) {
c, _ := parseColor(color...)
if c.a < 1 {
g.opaque = false
func (lg *LinearGradient) AddColorStop(pos float64, stopColor ...interface{}) {
var c color.RGBA
lg.data, c = addColorStop(lg.data, pos, stopColor...)
if c.A < 255 {
lg.opaque = false
}
insert := len(g.stops)
for i, stop := range g.stops {
if stop.pos > pos {
lg.loaded = false
}
// AddColorStop adds a color stop to the gradient. The stops
// don't have to be added in order, they are sorted into the
// right place
func (rg *RadialGradient) AddColorStop(pos float64, stopColor ...interface{}) {
var c color.RGBA
rg.data, c = addColorStop(rg.data, pos, stopColor...)
if c.A < 255 {
rg.opaque = false
}
rg.loaded = false
}
func addColorStop(
stops backendbase.Gradient,
pos float64,
stopColor ...interface{},
) (backendbase.Gradient, color.RGBA) {
c, _ := parseColor(stopColor...)
insert := len(stops)
for i, stop := range stops {
if stop.Pos > pos {
insert = i
break
}
}
g.stops = append(g.stops, gradientStop{})
if insert < len(g.stops)-1 {
copy(g.stops[insert+1:], g.stops[insert:len(g.stops)-1])
stops = append(stops, backendbase.GradientStop{})
if insert < len(stops)-1 {
copy(stops[insert+1:], stops[insert:len(stops)-1])
}
g.stops[insert] = gradientStop{pos: pos, color: c}
g.loaded = false
stops[insert] = backendbase.GradientStop{Pos: pos, Color: c}
return stops, c
}

View file

@ -1,96 +0,0 @@
package canvas
import (
"image"
"image/color"
"unsafe"
)
var imageBufTex uint32
var imageBuf []byte
// GetImageData returns an RGBA image of the currently displayed image. The
// alpha channel is always opaque
func (cv *Canvas) GetImageData(x, y, w, h int) *image.RGBA {
cv.activate()
if x < 0 {
w += x
x = 0
}
if y < 0 {
h += y
y = 0
}
if w > cv.w {
w = cv.w
}
if h > cv.h {
h = cv.h
}
if len(imageBuf) < w*h*3 {
imageBuf = make([]byte, w*h*3)
}
gli.ReadPixels(int32(x), int32(y), int32(w), int32(h), gl_RGB, gl_UNSIGNED_BYTE, gli.Ptr(&imageBuf[0]))
rgba := image.NewRGBA(image.Rect(x, y, x+w, y+h))
bp := 0
for cy := y; cy < y+h; cy++ {
for cx := x; cx < x+w; cx++ {
rgba.SetRGBA(cx, y+h-1-cy, color.RGBA{R: imageBuf[bp], G: imageBuf[bp+1], B: imageBuf[bp+2], A: 255})
bp += 3
}
}
return rgba
}
// PutImageData puts the given image at the given x/y coordinates
func (cv *Canvas) PutImageData(img *image.RGBA, x, y int) {
cv.activate()
gli.ActiveTexture(gl_TEXTURE0)
if imageBufTex == 0 {
gli.GenTextures(1, &imageBufTex)
gli.BindTexture(gl_TEXTURE_2D, imageBufTex)
gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, gl_LINEAR)
gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, gl_LINEAR)
gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_S, gl_CLAMP_TO_EDGE)
gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_T, gl_CLAMP_TO_EDGE)
} else {
gli.BindTexture(gl_TEXTURE_2D, imageBufTex)
}
w, h := img.Bounds().Dx(), img.Bounds().Dy()
if img.Stride == img.Bounds().Dx()*4 {
gli.TexImage2D(gl_TEXTURE_2D, 0, gl_RGBA, int32(w), int32(h), 0, gl_RGBA, gl_UNSIGNED_BYTE, gli.Ptr(&img.Pix[0]))
} else {
data := make([]uint8, 0, w*h*4)
for cy := 0; cy < h; cy++ {
start := cy * img.Stride
end := start + w*4
data = append(data, img.Pix[start:end]...)
}
gli.TexImage2D(gl_TEXTURE_2D, 0, gl_RGBA, int32(w), int32(h), 0, gl_RGBA, gl_UNSIGNED_BYTE, gli.Ptr(&data[0]))
}
dx, dy := float32(x), float32(y)
dw, dh := float32(w), float32(h)
gli.BindBuffer(gl_ARRAY_BUFFER, buf)
data := [16]float32{dx, dy, dx + dw, dy, dx + dw, dy + dh, dx, dy + dh,
0, 0, 1, 0, 1, 1, 0, 1}
gli.BufferData(gl_ARRAY_BUFFER, len(data)*4, unsafe.Pointer(&data[0]), gl_STREAM_DRAW)
gli.UseProgram(ir.id)
gli.Uniform1i(ir.image, 0)
gli.Uniform2f(ir.canvasSize, float32(cv.fw), float32(cv.fh))
gli.Uniform1f(ir.globalAlpha, 1)
gli.VertexAttribPointer(ir.vertex, 2, gl_FLOAT, false, 0, 0)
gli.VertexAttribPointer(ir.texCoord, 2, gl_FLOAT, false, 0, 8*4)
gli.EnableVertexAttribArray(ir.vertex)
gli.EnableVertexAttribArray(ir.texCoord)
gli.DrawArrays(gl_TRIANGLE_FAN, 0, 4)
gli.DisableVertexAttribArray(ir.vertex)
gli.DisableVertexAttribArray(ir.texCoord)
}

453
images.go
View file

@ -5,262 +5,153 @@ import (
"errors"
"fmt"
"image"
"io/ioutil"
"os"
"runtime"
"strings"
"unsafe"
"time"
"git.mstar.dev/mstar/canvas/backend/backendbase"
)
// Image represents a loaded image that can be used in various drawing functions
// Image is a type holding information on an image loaded with the LoadImage
// function
type Image struct {
w, h int
tex uint32
deleted bool
opaque bool
src interface{}
cv *Canvas
img backendbase.Image
deleted bool
lastUsed time.Time
}
var images = make(map[interface{}]*Image)
// LoadImage loads an image. The src parameter can be either an image from the
// standard image package, a byte slice that will be loaded, or a file name
// string. If you want the canvas package to load the image, make sure you
// import the required format packages
func LoadImage(src interface{}) (*Image, error) {
if gli == nil {
panic("LoadGL must be called before images can be loaded")
}
var tex uint32
gli.GenTextures(1, &tex)
gli.ActiveTexture(gl_TEXTURE0)
gli.BindTexture(gl_TEXTURE_2D, tex)
if src == nil {
return &Image{tex: tex}, nil
}
img, err := loadImage(src, tex)
if err != nil {
return nil, err
}
runtime.SetFinalizer(img, func(img *Image) {
if !img.deleted {
glChan <- func() {
gli.DeleteTextures(1, &img.tex)
}
func (cv *Canvas) LoadImage(src interface{}) (*Image, error) {
var reload *Image
if img, ok := src.(*Image); ok {
if img.cv != cv {
panic("image loaded with different canvas")
}
})
return img, nil
}
func loadImage(src interface{}, tex uint32) (*Image, error) {
var img *Image
var err error
if img.deleted {
reload = img
src = img.src
} else {
img.lastUsed = time.Now()
return img, nil
}
} else if _, ok := src.([]byte); !ok {
if img, ok := cv.images[src]; ok {
img.lastUsed = time.Now()
return img, nil
}
}
cv.reduceCache(Performance.CacheSize, 0)
var srcImg image.Image
switch v := src.(type) {
case *image.RGBA:
img, err = loadImageRGBA(v, tex)
if err != nil {
return nil, err
}
case *image.Gray:
img, err = loadImageGray(v, tex)
if err != nil {
return nil, err
}
case image.Image:
img, err = loadImageConverted(v, tex)
if err != nil {
return nil, err
}
srcImg = v
case string:
data, err := ioutil.ReadFile(v)
data, err := os.ReadFile(v)
if err != nil {
return nil, err
}
srcImg, _, err := image.Decode(bytes.NewReader(data))
srcImg, _, err = image.Decode(bytes.NewReader(data))
if err != nil {
return nil, err
}
return loadImage(srcImg, tex)
case []byte:
srcImg, _, err := image.Decode(bytes.NewReader(v))
var err error
srcImg, _, err = image.Decode(bytes.NewReader(v))
if err != nil {
return nil, err
}
return loadImage(srcImg, tex)
case *Canvas:
w, h := cv.b.Size()
src = cv.GetImageData(0, 0, w, h)
default:
return nil, errors.New("Unsupported source type")
}
return img, nil
backendImg, err := cv.b.LoadImage(srcImg)
if err != nil {
return nil, err
}
cvimg := &Image{cv: cv, img: backendImg, lastUsed: time.Now(), src: src}
if reload != nil {
*reload = *cvimg
return reload, nil
}
if _, ok := src.([]byte); !ok {
cv.images[src] = cvimg
}
return cvimg, nil
}
func getImage(src interface{}) *Image {
if img, ok := images[src]; ok {
return img
}
switch v := src.(type) {
case *Image:
return v
case image.Image:
img, err := LoadImage(v)
if err != nil {
fmt.Fprintf(os.Stderr, "Error loading image: %v\n", err)
images[src] = nil
return nil
func (cv *Canvas) getImage(src interface{}) *Image {
if cv2, ok := src.(*Canvas); ok {
if !cv.b.CanUseAsImage(cv2.b) {
w, h := cv2.Size()
return cv.getImage(cv2.GetImageData(0, 0, w, h))
}
images[v] = img
return img
case string:
img, err := LoadImage(v)
if err != nil {
bimg := cv2.b.AsImage()
if bimg == nil {
w, h := cv2.Size()
return cv.getImage(cv2.GetImageData(0, 0, w, h))
}
return &Image{cv: cv, img: bimg}
}
img, err := cv.LoadImage(src)
if err != nil {
cv.images[src] = nil
switch v := src.(type) {
case image.Image:
fmt.Fprintf(os.Stderr, "Error loading image: %v\n", err)
case string:
if strings.Contains(strings.ToLower(err.Error()), "format") {
fmt.Fprintf(os.Stderr, "Error loading image %s: %v\nIt may be necessary to import the appropriate decoder, e.g.\nimport _ \"image/jpeg\"\n", v, err)
} else {
fmt.Fprintf(os.Stderr, "Error loading image %s: %v\n", v, err)
}
images[src] = nil
return nil
}
images[v] = img
return img
}
fmt.Fprintf(os.Stderr, "Unknown image type: %T\n", src)
images[src] = nil
return nil
}
func loadImageRGBA(src *image.RGBA, tex uint32) (*Image, error) {
img := &Image{tex: tex, w: src.Bounds().Dx(), h: src.Bounds().Dy(), opaque: true}
checkOpaque:
for y := 0; y < img.h; y++ {
off := src.PixOffset(0, y) + 3
for x := 0; x < img.w; x++ {
if src.Pix[off] < 255 {
img.opaque = false
break checkOpaque
}
off += 4
default:
fmt.Fprintf(os.Stderr, "Failed to load image: %v\n", err)
}
}
gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, gl_LINEAR_MIPMAP_LINEAR)
gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, gl_LINEAR)
gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_S, gl_CLAMP_TO_EDGE)
gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_T, gl_CLAMP_TO_EDGE)
if err := glError(); err != nil {
return nil, err
}
if src.Stride == img.w*4 {
gli.TexImage2D(gl_TEXTURE_2D, 0, gl_RGBA, int32(img.w), int32(img.h), 0, gl_RGBA, gl_UNSIGNED_BYTE, gli.Ptr(&src.Pix[0]))
} else {
data := make([]uint8, 0, img.w*img.h*4)
for y := 0; y < img.h; y++ {
start := y * src.Stride
end := start + img.w*4
data = append(data, src.Pix[start:end]...)
}
gli.TexImage2D(gl_TEXTURE_2D, 0, gl_RGBA, int32(img.w), int32(img.h), 0, gl_RGBA, gl_UNSIGNED_BYTE, gli.Ptr(&data[0]))
}
if err := glError(); err != nil {
return nil, err
}
gli.GenerateMipmap(gl_TEXTURE_2D)
if err := glError(); err != nil {
return nil, err
}
return img, nil
}
func loadImageGray(src *image.Gray, tex uint32) (*Image, error) {
img := &Image{tex: tex, w: src.Bounds().Dx(), h: src.Bounds().Dy()}
gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, gl_LINEAR_MIPMAP_LINEAR)
gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, gl_LINEAR)
gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_S, gl_CLAMP_TO_EDGE)
gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_T, gl_CLAMP_TO_EDGE)
if err := glError(); err != nil {
return nil, err
}
if src.Stride == img.w {
gli.TexImage2D(gl_TEXTURE_2D, 0, gl_RED, int32(img.w), int32(img.h), 0, gl_RED, gl_UNSIGNED_BYTE, gli.Ptr(&src.Pix[0]))
} else {
data := make([]uint8, 0, img.w*img.h)
for y := 0; y < img.h; y++ {
start := y * src.Stride
end := start + img.w
data = append(data, src.Pix[start:end]...)
}
gli.TexImage2D(gl_TEXTURE_2D, 0, gl_RED, int32(img.w), int32(img.h), 0, gl_RED, gl_UNSIGNED_BYTE, gli.Ptr(&data[0]))
}
if err := glError(); err != nil {
return nil, err
}
gli.GenerateMipmap(gl_TEXTURE_2D)
if err := glError(); err != nil {
return nil, err
}
return img, nil
}
func loadImageConverted(src image.Image, tex uint32) (*Image, error) {
img := &Image{tex: tex, w: src.Bounds().Dx(), h: src.Bounds().Dy(), opaque: true}
gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, gl_LINEAR_MIPMAP_LINEAR)
gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, gl_LINEAR)
gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_S, gl_CLAMP_TO_EDGE)
gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_T, gl_CLAMP_TO_EDGE)
if err := glError(); err != nil {
return nil, err
}
data := make([]uint8, 0, img.w*img.h*4)
for y := 0; y < img.h; y++ {
for x := 0; x < img.w; x++ {
ir, ig, ib, ia := src.At(x, y).RGBA()
r, g, b, a := uint8(ir>>8), uint8(ig>>8), uint8(ib>>8), uint8(ia>>8)
data = append(data, r, g, b, a)
if a < 255 {
img.opaque = false
}
}
}
gli.TexImage2D(gl_TEXTURE_2D, 0, gl_RGBA, int32(img.w), int32(img.h), 0, gl_RGBA, gl_UNSIGNED_BYTE, gli.Ptr(&data[0]))
if err := glError(); err != nil {
return nil, err
}
gli.GenerateMipmap(gl_TEXTURE_2D)
if err := glError(); err != nil {
return nil, err
}
return img, nil
return img
}
// Width returns the width of the image
func (img *Image) Width() int { return img.w }
func (img *Image) Width() int { return img.img.Width() }
// Height returns the height of the image
func (img *Image) Height() int { return img.h }
func (img *Image) Height() int { return img.img.Height() }
// Size returns the width and height of the image
func (img *Image) Size() (int, int) { return img.w, img.h }
func (img *Image) Size() (int, int) { return img.img.Size() }
// Delete deletes the image from memory. Any draw calls with a deleted image
// will not do anything
// Delete deletes the image from memory
func (img *Image) Delete() {
gli.DeleteTextures(1, &img.tex)
if img == nil || img.deleted {
return
}
img.deleted = true
img.img.Delete()
delete(img.cv.images, img.src)
}
// Replace replaces the image with the new one
func (img *Image) Replace(src interface{}) {
gli.ActiveTexture(gl_TEXTURE0)
gli.BindTexture(gl_TEXTURE_2D, img.tex)
newImg, err := loadImage(src, img.tex)
if err != nil {
fmt.Fprintf(os.Stderr, "Error replacing image: %v\n", err)
return
func (img *Image) Replace(src interface{}) error {
if img.src == src {
if origImg, ok := img.src.(image.Image); ok {
img.img.Replace(origImg)
return nil
}
}
*img = *newImg
newImg, err := img.cv.LoadImage(src)
if err != nil {
return err
}
img.img = newImg.img
return nil
}
// DrawImage draws the given image to the given coordinates. The image
@ -269,34 +160,22 @@ func (img *Image) Replace(src interface{}) {
// loaded image with the same name parameter.
//
// The coordinates must be one of the following:
// DrawImage("image", dx, dy)
// DrawImage("image", dx, dy, dw, dh)
// DrawImage("image", sx, sy, sw, sh, dx, dy, dw, dh)
//
// DrawImage("image", dx, dy)
// DrawImage("image", dx, dy, dw, dh)
// DrawImage("image", sx, sy, sw, sh, dx, dy, dw, dh)
//
// Where dx/dy/dw/dh are the destination coordinates and sx/sy/sw/sh are the
// source coordinates
func (cv *Canvas) DrawImage(image interface{}, coords ...float64) {
var img *Image
var flip bool
if cv2, ok := image.(*Canvas); ok && cv2.offscreen {
img = &cv2.offscrImg
flip = true
} else {
img = getImage(image)
}
img := cv.getImage(image)
if img == nil {
return
}
if img.deleted {
return
}
cv.activate()
var sx, sy, sw, sh, dx, dy, dw, dh float64
sw, sh = float64(img.w), float64(img.h)
dw, dh = float64(img.w), float64(img.h)
sw, sh = float64(img.Width()), float64(img.Height())
dw, dh = float64(img.Width()), float64(img.Height())
if len(coords) == 2 {
dx, dy = coords[0], coords[1]
} else if len(coords) == 4 {
@ -309,60 +188,78 @@ func (cv *Canvas) DrawImage(image interface{}, coords ...float64) {
dw, dh = coords[6], coords[7]
}
if flip {
dy += dh
dh = -dh
}
var data [4]backendbase.Vec
data[0] = cv.tf(backendbase.Vec{dx, dy})
data[1] = cv.tf(backendbase.Vec{dx, dy + dh})
data[2] = cv.tf(backendbase.Vec{dx + dw, dy + dh})
data[3] = cv.tf(backendbase.Vec{dx + dw, dy})
sx /= float64(img.w)
sy /= float64(img.h)
sw /= float64(img.w)
sh /= float64(img.h)
cv.drawShadow(data[:], nil, false)
p0 := cv.tf(vec{dx, dy})
p1 := cv.tf(vec{dx, dy + dh})
p2 := cv.tf(vec{dx + dw, dy + dh})
p3 := cv.tf(vec{dx + dw, dy})
if cv.state.shadowColor.a != 0 {
tris := [24]float32{
0, 0,
float32(cv.fw), 0,
float32(cv.fw), float32(cv.fh),
0, 0,
float32(cv.fw), float32(cv.fh),
0, float32(cv.fh),
float32(p0[0]), float32(p0[1]),
float32(p3[0]), float32(p3[1]),
float32(p2[0]), float32(p2[1]),
float32(p0[0]), float32(p0[1]),
float32(p2[0]), float32(p2[1]),
float32(p1[0]), float32(p1[1]),
}
cv.drawShadow(tris[:])
}
gli.StencilFunc(gl_EQUAL, 0, 0xFF)
gli.BindBuffer(gl_ARRAY_BUFFER, buf)
data := [16]float32{float32(p0[0]), float32(p0[1]), float32(p1[0]), float32(p1[1]), float32(p2[0]), float32(p2[1]), float32(p3[0]), float32(p3[1]),
float32(sx), float32(sy), float32(sx), float32(sy + sh), float32(sx + sw), float32(sy + sh), float32(sx + sw), float32(sy)}
gli.BufferData(gl_ARRAY_BUFFER, len(data)*4, unsafe.Pointer(&data[0]), gl_STREAM_DRAW)
gli.ActiveTexture(gl_TEXTURE0)
gli.BindTexture(gl_TEXTURE_2D, img.tex)
gli.UseProgram(ir.id)
gli.Uniform1i(ir.image, 0)
gli.Uniform2f(ir.canvasSize, float32(cv.fw), float32(cv.fh))
gli.Uniform1f(ir.globalAlpha, float32(cv.state.globalAlpha))
gli.VertexAttribPointer(ir.vertex, 2, gl_FLOAT, false, 0, 0)
gli.VertexAttribPointer(ir.texCoord, 2, gl_FLOAT, false, 0, 8*4)
gli.EnableVertexAttribArray(ir.vertex)
gli.EnableVertexAttribArray(ir.texCoord)
gli.DrawArrays(gl_TRIANGLE_FAN, 0, 4)
gli.DisableVertexAttribArray(ir.vertex)
gli.DisableVertexAttribArray(ir.texCoord)
gli.StencilFunc(gl_ALWAYS, 0, 0xFF)
cv.b.DrawImage(img.img, sx, sy, sw, sh, data, cv.state.globalAlpha)
}
// GetImageData returns an RGBA image of the current image
func (cv *Canvas) GetImageData(x, y, w, h int) *image.RGBA {
return cv.b.GetImageData(x, y, w, h)
}
// PutImageData puts the given image at the given x/y coordinates
func (cv *Canvas) PutImageData(img *image.RGBA, x, y int) {
cv.b.PutImageData(img, x, y)
}
// ImagePattern is an image pattern that can be used for any
// fill call
type ImagePattern struct {
cv *Canvas
img *Image
tf backendbase.Mat
rep imagePatternRepeat
ip backendbase.ImagePattern
}
type imagePatternRepeat uint8
// Image pattern repeat constants
const (
Repeat imagePatternRepeat = imagePatternRepeat(backendbase.Repeat)
RepeatX = imagePatternRepeat(backendbase.RepeatX)
RepeatY = imagePatternRepeat(backendbase.RepeatY)
NoRepeat = imagePatternRepeat(backendbase.NoRepeat)
)
func (ip *ImagePattern) data(tf backendbase.Mat) backendbase.ImagePatternData {
m := tf.Invert().Mul(ip.tf.Invert())
return backendbase.ImagePatternData{
Image: ip.img.img,
Transform: [9]float64{
m[0], m[2], m[4],
m[1], m[3], m[5],
0, 0, 1,
},
Repeat: backendbase.ImagePatternRepeat(ip.rep),
}
}
// SetTransform changes the transformation of the image pattern
// to the given matrix. The matrix is a 3x3 matrix, but three
// of the values are always identity values
func (ip *ImagePattern) SetTransform(tf [6]float64) {
ip.tf = backendbase.Mat(tf)
}
// CreatePattern creates a new image pattern with the specified
// image and repetition
func (cv *Canvas) CreatePattern(src interface{}, repeat imagePatternRepeat) *ImagePattern {
ip := &ImagePattern{
cv: cv,
img: cv.getImage(src),
rep: repeat,
tf: backendbase.Mat{1, 0, 0, 1, 0, 0},
}
if ip.img != nil {
ip.ip = cv.b.LoadImagePattern(ip.data(cv.state.transform))
}
return ip
}

File diff suppressed because it is too large Load diff

View file

@ -1,312 +0,0 @@
//+build ignore
package main
import (
"bytes"
"fmt"
"go/ast"
"go/build"
"go/parser"
"go/token"
"io/ioutil"
"log"
"regexp"
"strings"
"unicode"
)
func main() {
// find the go files in the current directory
pkg, err := build.Default.ImportDir(".", 0)
if err != nil {
log.Fatalf("Could not process directory: %s", err)
}
vsMap := make(map[string]string)
fsMap := make(map[string]string)
// go through each file and find const raw string literals with
fset := token.NewFileSet()
for _, goFile := range pkg.GoFiles {
parsedFile, err := parser.ParseFile(fset, goFile, nil, 0)
if err != nil {
log.Fatalf("Failed to parse file %s: %s", goFile, err)
}
for _, obj := range parsedFile.Scope.Objects {
isVS := strings.HasSuffix(obj.Name, "VS")
isFS := strings.HasSuffix(obj.Name, "FS")
var shaderCode string
if isVS || isFS {
if value, ok := obj.Decl.(*ast.ValueSpec); ok && len(value.Values) == 1 {
if lit, ok := value.Values[0].(*ast.BasicLit); ok {
shaderCode = lit.Value
}
}
}
if shaderCode != "" {
baseName := obj.Name[:len(obj.Name)-2]
if isVS {
vsMap[baseName] = shaderCode
} else if isFS {
fsMap[baseName] = shaderCode
}
}
}
}
for name := range vsMap {
if _, ok := fsMap[name]; !ok {
log.Println("Warning: Vertex shader with no corresponding fragment shader (" + name + ")")
delete(vsMap, name)
continue
}
}
for name := range fsMap {
if _, ok := vsMap[name]; !ok {
log.Println("Warning: Fragment shader with no corresponding vertex shader (" + name + ")")
delete(fsMap, name)
continue
}
}
goCode := &bytes.Buffer{}
buildCodeHeader(goCode)
for name, vs := range vsMap {
fs := fsMap[name]
vs = vs[1 : len(vs)-1]
fs = fs[1 : len(fs)-1]
inputs := shaderFindInputVariables(vs + "\n" + fs)
buildCode(goCode, name, inputs)
}
err = ioutil.WriteFile("made_shaders.go", goCode.Bytes(), 0777)
if err != nil {
log.Fatalf("Could not write made_shaders.go: %s", err)
}
}
type ShaderInput struct {
Name string
IsAttrib bool
IsUniform bool
}
func shaderFindInputVariables(source string) []ShaderInput {
inputs := make([]ShaderInput, 0, 10)
varDefSplitter := regexp.MustCompile("[ \t\r\n,]+")
lines := shaderGetTopLevelLines(source)
for _, line := range lines {
line = strings.TrimSpace(line)
if len(line) == 0 || line[0] == '#' {
continue
}
if strings.Contains(line, "{") {
break
}
parts := varDefSplitter.Split(line, -1)
isAttrib := parts[0] == "attribute"
isUniform := parts[0] == "uniform"
if !isAttrib && !isUniform {
continue
}
for _, part := range parts[2:] {
if idx := strings.IndexByte(part, '['); idx >= 0 {
part = part[:idx]
}
inputs = append(inputs, ShaderInput{
Name: part,
IsAttrib: isAttrib,
IsUniform: isUniform})
}
}
return inputs
}
func shaderGetTopLevelLines(source string) []string {
sourceBytes := []byte(source)
l := len(sourceBytes)
if l == 0 {
return make([]string, 0)
}
var inPrecompiledStatement, inLineComment, inBlockComment, inString, stringEscape, lastWasWhitespace bool
curlyBraceDepth := 0
topLevelLines := make([]string, 0, 100)
currentLine := make([]byte, 0, 1000)
var c0, c1 byte = ' ', ' '
for i := 0; i < l; i++ {
c1 = sourceBytes[i]
isWhitespace := unicode.IsSpace(rune(c1))
if !inBlockComment && !inString && c0 == '/' && c1 == '/' {
inLineComment = true
if len(currentLine) > 0 {
currentLine = currentLine[:len(currentLine)-1]
}
} else if !inBlockComment && !inString && c1 == '#' {
inPrecompiledStatement = true
} else if !inBlockComment && !inLineComment && !inPrecompiledStatement && !inString && c0 == '/' && c1 == '*' {
inBlockComment = true
if len(currentLine) > 0 {
currentLine = currentLine[:len(currentLine)-1]
}
} else if !inBlockComment && !inString && !inLineComment && !inPrecompiledStatement && c1 == '"' {
inString = true
} else if inString && !stringEscape && c1 == '\\' {
stringEscape = true
} else if inString && stringEscape {
stringEscape = false
} else if inString && !stringEscape && c1 == '"' {
inString = false
} else if !inBlockComment && !inLineComment && !inPrecompiledStatement && !inString && c1 == '{' {
if curlyBraceDepth == 0 {
topLevelLines = append(topLevelLines, string(currentLine))
currentLine = currentLine[:0]
}
curlyBraceDepth++
}
if !inBlockComment && !inLineComment && !inPrecompiledStatement && !inString && curlyBraceDepth == 0 {
if c1 == ';' {
topLevelLines = append(topLevelLines, string(currentLine))
currentLine = currentLine[:0]
} else if !isWhitespace {
currentLine = append(currentLine, c1)
} else if !lastWasWhitespace {
currentLine = append(currentLine, ' ')
}
}
if !inBlockComment && !inLineComment && !inPrecompiledStatement && !inString && c1 == '}' {
curlyBraceDepth--
} else if (inLineComment || inPrecompiledStatement) && (c1 == '\r' || c1 == '\n') {
inLineComment = false
inPrecompiledStatement = false
} else if inBlockComment && c0 == '*' && c1 == '/' {
inBlockComment = false
}
lastWasWhitespace = isWhitespace
c0 = c1
}
topLevelLines = append(topLevelLines, string(currentLine))
return topLevelLines
}
const compilePart = `
{
SHADER_VAR = gli.CreateShader(gl_SHADER_TYPE)
gli.ShaderSource(SHADER_VAR, SHADER_SRC)
gli.CompileShader(SHADER_VAR)
shLog := gli.GetShaderInfoLog(SHADER_VAR)
if len(shLog) > 0 {
fmt.Printf("SHADER_TYPE compilation log for SHADER_SRC:\n\n%s\n", shLog)
}
var status int32
gli.GetShaderiv(SHADER_VAR, gl_COMPILE_STATUS, &status)
if status != gl_TRUE {
gli.DeleteShader(SHADER_VAR)
return nil, errors.New("Error compiling GL_SHADER_TYPE shader part for SHADER_SRC")
}
if glErr := gli.GetError(); glErr != gl_NO_ERROR {
return nil, errors.New("error compiling shader part for SHADER_SRC, glError: " + fmt.Sprint(glErr))
}
}
`
const linkPart = `
{
program = gli.CreateProgram()
gli.AttachShader(program, vs)
gli.AttachShader(program, fs)
gli.LinkProgram(program)
shLog := gli.GetProgramInfoLog(program)
if len(shLog) > 0 {
fmt.Printf("Shader link log for SHADER_SRC:\n\n%s\n", shLog)
}
var status int32
gli.GetProgramiv(program, gl_LINK_STATUS, &status)
if status != gl_TRUE {
gli.DeleteShader(vs)
gli.DeleteShader(fs)
return nil, errors.New("error linking shader for SHADER_SRC")
}
if glErr := gli.GetError(); glErr != gl_NO_ERROR {
return nil, errors.New("error linking shader for SHADER_SRC, glError: " + fmt.Sprint(glErr))
}
}
`
func capitalize(s string) string {
return strings.ToUpper(s[:1]) + s[1:]
}
func buildCodeHeader(buf *bytes.Buffer) {
fmt.Fprint(buf, "package canvas\n\n")
fmt.Fprint(buf, "import (\n")
fmt.Fprint(buf, "\t\"errors\"\n")
fmt.Fprint(buf, "\t\"fmt\"\n")
fmt.Fprint(buf, ")\n\n")
}
func buildCode(buf *bytes.Buffer, baseName string, inputs []ShaderInput) {
shaderName := baseName + "Shader"
vsName := baseName + "VS"
fsName := baseName + "FS"
fmt.Fprintf(buf, "type %s struct {\n", shaderName)
fmt.Fprint(buf, "\tid uint32\n")
for _, input := range inputs {
if input.IsAttrib {
fmt.Fprintf(buf, "\t%s uint32\n", input.Name)
} else if input.IsUniform {
fmt.Fprintf(buf, "\t%s int32\n", input.Name)
}
}
fmt.Fprint(buf, "}\n\n")
fmt.Fprintf(buf, "func load%s() (*%s, error) {\n", capitalize(shaderName), shaderName)
fmt.Fprint(buf, "\tvar vs, fs, program uint32\n")
var part string
part = strings.Replace(compilePart, "SHADER_SRC", vsName, -1)
part = strings.Replace(part, "SHADER_TYPE", "VERTEX_SHADER", -1)
part = strings.Replace(part, "SHADER_VAR", "vs", -1)
fmt.Fprint(buf, part)
part = strings.Replace(compilePart, "SHADER_SRC", fsName, -1)
part = strings.Replace(part, "SHADER_TYPE", "FRAGMENT_SHADER", -1)
part = strings.Replace(part, "SHADER_VAR", "fs", -1)
fmt.Fprint(buf, part)
part = strings.Replace(linkPart, "SHADER_SRC", fsName, -1)
fmt.Fprint(buf, part)
fmt.Fprint(buf, "\n")
fmt.Fprintf(buf, "\tresult := &%s{}\n", shaderName)
fmt.Fprint(buf, "\tresult.id = program\n")
for _, input := range inputs {
if input.IsAttrib {
fmt.Fprintf(buf, "\tresult.%s = uint32(gli.GetAttribLocation(program, \"%s\"))\n", input.Name, input.Name)
} else if input.IsUniform {
fmt.Fprintf(buf, "\tresult.%s = gli.GetUniformLocation(program, \"%s\")\n", input.Name, input.Name)
}
}
fmt.Fprint(buf, "\n")
fmt.Fprint(buf, "\treturn result, nil\n")
fmt.Fprint(buf, "}\n")
}

141
math.go
View file

@ -1,141 +0,0 @@
package canvas
import (
"fmt"
"math"
)
type vec [2]float64
func (v vec) String() string {
return fmt.Sprintf("[%f,%f]", v[0], v[1])
}
func (v1 vec) add(v2 vec) vec {
return vec{v1[0] + v2[0], v1[1] + v2[1]}
}
func (v1 vec) sub(v2 vec) vec {
return vec{v1[0] - v2[0], v1[1] - v2[1]}
}
func (v1 vec) mul(v2 vec) vec {
return vec{v1[0] * v2[0], v1[1] * v2[1]}
}
func (v vec) mulf(f float64) vec {
return vec{v[0] * f, v[1] * f}
}
func (v vec) mulMat(m mat) (vec, float64) {
return vec{
m[0]*v[0] + m[3]*v[1] + m[6],
m[1]*v[0] + m[4]*v[1] + m[7]},
m[2]*v[0] + m[5]*v[1] + m[8]
}
func (v1 vec) div(v2 vec) vec {
return vec{v1[0] / v2[0], v1[1] / v2[1]}
}
func (v vec) divf(f float64) vec {
return vec{v[0] / f, v[1] / f}
}
func (v1 vec) dot(v2 vec) float64 {
return v1[0]*v2[0] + v1[1]*v2[1]
}
func (v vec) len() float64 {
return math.Sqrt(v[0]*v[0] + v[1]*v[1])
}
func (v vec) lenSqr() float64 {
return v[0]*v[0] + v[1]*v[1]
}
func (v vec) norm() vec {
return v.mulf(1.0 / v.len())
}
func (v vec) atan2() float64 {
return math.Atan2(v[1], v[0])
}
func (v vec) angle() float64 {
return math.Pi*0.5 - math.Atan2(v[1], v[0])
}
func (v vec) angleTo(v2 vec) float64 {
return math.Acos(v.norm().dot(v2.norm()))
}
type mat [9]float64
func (m *mat) String() string {
return fmt.Sprintf("[%f,%f,%f,\n %f,%f,%f,\n %f,%f,%f,]", m[0], m[3], m[6], m[1], m[4], m[7], m[2], m[5], m[8])
}
func matIdentity() mat {
return mat{
1, 0, 0,
0, 1, 0,
0, 0, 1}
}
func matTranslate(v vec) mat {
return mat{
1, 0, 0,
0, 1, 0,
v[0], v[1], 1}
}
func matScale(v vec) mat {
return mat{
v[0], 0, 0,
0, v[1], 0,
0, 0, 1}
}
func matRotate(radians float64) mat {
s, c := math.Sincos(radians)
return mat{
c, s, 0,
-s, c, 0,
0, 0, 1}
}
func (m1 mat) mul(m2 mat) mat {
return mat{
m1[0]*m2[0] + m1[1]*m2[3] + m1[2]*m2[6],
m1[0]*m2[1] + m1[1]*m2[4] + m1[2]*m2[7],
m1[0]*m2[2] + m1[1]*m2[5] + m1[2]*m2[8],
m1[3]*m2[0] + m1[4]*m2[3] + m1[5]*m2[6],
m1[3]*m2[1] + m1[4]*m2[4] + m1[5]*m2[7],
m1[3]*m2[2] + m1[4]*m2[5] + m1[5]*m2[8],
m1[6]*m2[0] + m1[7]*m2[3] + m1[8]*m2[6],
m1[6]*m2[1] + m1[7]*m2[4] + m1[8]*m2[7],
m1[6]*m2[2] + m1[7]*m2[5] + m1[8]*m2[8]}
}
func (m mat) invert() mat {
var identity float64 = 1.0 / (m[0]*m[4]*m[8] + m[3]*m[7]*m[2] + m[6]*m[1]*m[5] - m[6]*m[4]*m[2] - m[3]*m[1]*m[8] - m[0]*m[7]*m[5])
return mat{
(m[4]*m[8] - m[5]*m[7]) * identity,
(m[2]*m[7] - m[1]*m[8]) * identity,
(m[1]*m[5] - m[2]*m[4]) * identity,
(m[5]*m[6] - m[3]*m[8]) * identity,
(m[0]*m[8] - m[2]*m[6]) * identity,
(m[2]*m[3] - m[0]*m[5]) * identity,
(m[3]*m[7] - m[4]*m[6]) * identity,
(m[1]*m[6] - m[0]*m[7]) * identity,
(m[0]*m[4] - m[1]*m[3]) * identity}
}
func (m mat) f32() [9]float32 {
return [9]float32{
float32(m[0]), float32(m[1]), float32(m[2]),
float32(m[3]), float32(m[4]), float32(m[5]),
float32(m[6]), float32(m[7]), float32(m[8])}
}

View file

@ -1,855 +0,0 @@
package canvas
import "unsafe"
const (
gl_ACTIVE_ATTRIBUTES = 0x8B89
gl_ACTIVE_ATTRIBUTE_MAX_LENGTH = 0x8B8A
gl_ACTIVE_TEXTURE = 0x84E0
gl_ACTIVE_UNIFORMS = 0x8B86
gl_ACTIVE_UNIFORM_BLOCKS = 0x8A36
gl_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH = 0x8A35
gl_ACTIVE_UNIFORM_MAX_LENGTH = 0x8B87
gl_ALIASED_LINE_WIDTH_RANGE = 0x846E
gl_ALPHA = 0x1906
gl_ALREADY_SIGNALED = 0x911A
gl_ALWAYS = 0x0207
gl_AND = 0x1501
gl_AND_INVERTED = 0x1504
gl_AND_REVERSE = 0x1502
gl_ARRAY_BUFFER = 0x8892
gl_ARRAY_BUFFER_BINDING = 0x8894
gl_ATTACHED_SHADERS = 0x8B85
gl_BACK = 0x0405
gl_BACK_LEFT = 0x0402
gl_BACK_RIGHT = 0x0403
gl_BGR = 0x80E0
gl_BGRA = 0x80E1
gl_BGRA_INTEGER = 0x8D9B
gl_BGR_INTEGER = 0x8D9A
gl_BLEND = 0x0BE2
gl_BLEND_DST = 0x0BE0
gl_BLEND_DST_ALPHA = 0x80CA
gl_BLEND_DST_RGB = 0x80C8
gl_BLEND_EQUATION_ALPHA = 0x883D
gl_BLEND_EQUATION_RGB = 0x8009
gl_BLEND_SRC = 0x0BE1
gl_BLEND_SRC_ALPHA = 0x80CB
gl_BLEND_SRC_RGB = 0x80C9
gl_BLUE = 0x1905
gl_BLUE_INTEGER = 0x8D96
gl_BOOL = 0x8B56
gl_BOOL_VEC2 = 0x8B57
gl_BOOL_VEC3 = 0x8B58
gl_BOOL_VEC4 = 0x8B59
gl_BUFFER_ACCESS = 0x88BB
gl_BUFFER_ACCESS_FLAGS = 0x911F
gl_BUFFER_MAPPED = 0x88BC
gl_BUFFER_MAP_LENGTH = 0x9120
gl_BUFFER_MAP_OFFSET = 0x9121
gl_BUFFER_MAP_POINTER = 0x88BD
gl_BUFFER_SIZE = 0x8764
gl_BUFFER_USAGE = 0x8765
gl_BYTE = 0x1400
gl_CCW = 0x0901
gl_CLAMP_READ_COLOR = 0x891C
gl_CLAMP_TO_BORDER = 0x812D
gl_CLAMP_TO_EDGE = 0x812F
gl_CLEAR = 0x1500
gl_CLIP_DISTANCE0 = 0x3000
gl_CLIP_DISTANCE1 = 0x3001
gl_CLIP_DISTANCE2 = 0x3002
gl_CLIP_DISTANCE3 = 0x3003
gl_CLIP_DISTANCE4 = 0x3004
gl_CLIP_DISTANCE5 = 0x3005
gl_CLIP_DISTANCE6 = 0x3006
gl_CLIP_DISTANCE7 = 0x3007
gl_COLOR = 0x1800
gl_COLOR_ATTACHMENT0 = 0x8CE0
gl_COLOR_ATTACHMENT1 = 0x8CE1
gl_COLOR_ATTACHMENT10 = 0x8CEA
gl_COLOR_ATTACHMENT11 = 0x8CEB
gl_COLOR_ATTACHMENT12 = 0x8CEC
gl_COLOR_ATTACHMENT13 = 0x8CED
gl_COLOR_ATTACHMENT14 = 0x8CEE
gl_COLOR_ATTACHMENT15 = 0x8CEF
gl_COLOR_ATTACHMENT2 = 0x8CE2
gl_COLOR_ATTACHMENT3 = 0x8CE3
gl_COLOR_ATTACHMENT4 = 0x8CE4
gl_COLOR_ATTACHMENT5 = 0x8CE5
gl_COLOR_ATTACHMENT6 = 0x8CE6
gl_COLOR_ATTACHMENT7 = 0x8CE7
gl_COLOR_ATTACHMENT8 = 0x8CE8
gl_COLOR_ATTACHMENT9 = 0x8CE9
gl_COLOR_BUFFER_BIT = 0x00004000
gl_COLOR_CLEAR_VALUE = 0x0C22
gl_COLOR_LOGIC_OP = 0x0BF2
gl_COLOR_WRITEMASK = 0x0C23
gl_COMPARE_REF_TO_TEXTURE = 0x884E
gl_COMPILE_STATUS = 0x8B81
gl_COMPRESSED_RED = 0x8225
gl_COMPRESSED_RED_RGTC1 = 0x8DBB
gl_COMPRESSED_RG = 0x8226
gl_COMPRESSED_RGB = 0x84ED
gl_COMPRESSED_RGBA = 0x84EE
gl_COMPRESSED_RG_RGTC2 = 0x8DBD
gl_COMPRESSED_SIGNED_RED_RGTC1 = 0x8DBC
gl_COMPRESSED_SIGNED_RG_RGTC2 = 0x8DBE
gl_COMPRESSED_SRGB = 0x8C48
gl_COMPRESSED_SRGB_ALPHA = 0x8C49
gl_COMPRESSED_TEXTURE_FORMATS = 0x86A3
gl_CONDITION_SATISFIED = 0x911C
gl_CONSTANT_ALPHA = 0x8003
gl_CONSTANT_COLOR = 0x8001
gl_CONTEXT_COMPATIBILITY_PROFILE_BIT = 0x00000002
gl_CONTEXT_CORE_PROFILE_BIT = 0x00000001
gl_CONTEXT_FLAGS = 0x821E
gl_CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT = 0x00000001
gl_CONTEXT_PROFILE_MASK = 0x9126
gl_COPY = 0x1503
gl_COPY_INVERTED = 0x150C
gl_COPY_READ_BUFFER = 0x8F36
gl_COPY_WRITE_BUFFER = 0x8F37
gl_CULL_FACE = 0x0B44
gl_CULL_FACE_MODE = 0x0B45
gl_CURRENT_PROGRAM = 0x8B8D
gl_CURRENT_QUERY = 0x8865
gl_CURRENT_VERTEX_ATTRIB = 0x8626
gl_CW = 0x0900
gl_DECR = 0x1E03
gl_DECR_WRAP = 0x8508
gl_DELETE_STATUS = 0x8B80
gl_DEPTH = 0x1801
gl_DEPTH24_STENCIL8 = 0x88F0
gl_DEPTH32F_STENCIL8 = 0x8CAD
gl_DEPTH_ATTACHMENT = 0x8D00
gl_DEPTH_BUFFER_BIT = 0x00000100
gl_DEPTH_CLAMP = 0x864F
gl_DEPTH_CLEAR_VALUE = 0x0B73
gl_DEPTH_COMPONENT = 0x1902
gl_DEPTH_COMPONENT16 = 0x81A5
gl_DEPTH_COMPONENT24 = 0x81A6
gl_DEPTH_COMPONENT32 = 0x81A7
gl_DEPTH_COMPONENT32F = 0x8CAC
gl_DEPTH_FUNC = 0x0B74
gl_DEPTH_RANGE = 0x0B70
gl_DEPTH_STENCIL = 0x84F9
gl_DEPTH_STENCIL_ATTACHMENT = 0x821A
gl_DEPTH_TEST = 0x0B71
gl_DEPTH_WRITEMASK = 0x0B72
gl_DITHER = 0x0BD0
gl_DONT_CARE = 0x1100
gl_DOUBLE = 0x140A
gl_DOUBLEBUFFER = 0x0C32
gl_DRAW_BUFFER = 0x0C01
gl_DRAW_BUFFER0 = 0x8825
gl_DRAW_BUFFER1 = 0x8826
gl_DRAW_BUFFER10 = 0x882F
gl_DRAW_BUFFER11 = 0x8830
gl_DRAW_BUFFER12 = 0x8831
gl_DRAW_BUFFER13 = 0x8832
gl_DRAW_BUFFER14 = 0x8833
gl_DRAW_BUFFER15 = 0x8834
gl_DRAW_BUFFER2 = 0x8827
gl_DRAW_BUFFER3 = 0x8828
gl_DRAW_BUFFER4 = 0x8829
gl_DRAW_BUFFER5 = 0x882A
gl_DRAW_BUFFER6 = 0x882B
gl_DRAW_BUFFER7 = 0x882C
gl_DRAW_BUFFER8 = 0x882D
gl_DRAW_BUFFER9 = 0x882E
gl_DRAW_FRAMEBUFFER = 0x8CA9
gl_DRAW_FRAMEBUFFER_BINDING = 0x8CA6
gl_DST_ALPHA = 0x0304
gl_DST_COLOR = 0x0306
gl_DYNAMIC_COPY = 0x88EA
gl_DYNAMIC_DRAW = 0x88E8
gl_DYNAMIC_READ = 0x88E9
gl_ELEMENT_ARRAY_BUFFER = 0x8893
gl_ELEMENT_ARRAY_BUFFER_BINDING = 0x8895
gl_EQUAL = 0x0202
gl_EQUIV = 0x1509
gl_EXTENSIONS = 0x1F03
gl_FALSE = 0
gl_FASTEST = 0x1101
gl_FILL = 0x1B02
gl_FIRST_VERTEX_CONVENTION = 0x8E4D
gl_FIXED_ONLY = 0x891D
gl_FLOAT = 0x1406
gl_FLOAT_32_UNSIGNED_INT_24_8_REV = 0x8DAD
gl_FLOAT_MAT2 = 0x8B5A
gl_FLOAT_MAT2x3 = 0x8B65
gl_FLOAT_MAT2x4 = 0x8B66
gl_FLOAT_MAT3 = 0x8B5B
gl_FLOAT_MAT3x2 = 0x8B67
gl_FLOAT_MAT3x4 = 0x8B68
gl_FLOAT_MAT4 = 0x8B5C
gl_FLOAT_MAT4x2 = 0x8B69
gl_FLOAT_MAT4x3 = 0x8B6A
gl_FLOAT_VEC2 = 0x8B50
gl_FLOAT_VEC3 = 0x8B51
gl_FLOAT_VEC4 = 0x8B52
gl_FRAGMENT_SHADER = 0x8B30
gl_FRAGMENT_SHADER_DERIVATIVE_HINT = 0x8B8B
gl_FRAMEBUFFER = 0x8D40
gl_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE = 0x8215
gl_FRAMEBUFFER_ATTACHMENT_BLUE_SIZE = 0x8214
gl_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING = 0x8210
gl_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE = 0x8211
gl_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE = 0x8216
gl_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE = 0x8213
gl_FRAMEBUFFER_ATTACHMENT_LAYERED = 0x8DA7
gl_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME = 0x8CD1
gl_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE = 0x8CD0
gl_FRAMEBUFFER_ATTACHMENT_RED_SIZE = 0x8212
gl_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE = 0x8217
gl_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE = 0x8CD3
gl_FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER = 0x8CD4
gl_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL = 0x8CD2
gl_FRAMEBUFFER_BINDING = 0x8CA6
gl_FRAMEBUFFER_COMPLETE = 0x8CD5
gl_FRAMEBUFFER_DEFAULT = 0x8218
gl_FRAMEBUFFER_INCOMPLETE_ATTACHMENT = 0x8CD6
gl_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER = 0x8CDB
gl_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS = 0x8DA8
gl_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = 0x8CD7
gl_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE = 0x8D56
gl_FRAMEBUFFER_INCOMPLETE_READ_BUFFER = 0x8CDC
gl_FRAMEBUFFER_SRGB = 0x8DB9
gl_FRAMEBUFFER_UNDEFINED = 0x8219
gl_FRAMEBUFFER_UNSUPPORTED = 0x8CDD
gl_FRONT = 0x0404
gl_FRONT_AND_BACK = 0x0408
gl_FRONT_FACE = 0x0B46
gl_FRONT_LEFT = 0x0400
gl_FRONT_RIGHT = 0x0401
gl_FUNC_ADD = 0x8006
gl_FUNC_REVERSE_SUBTRACT = 0x800B
gl_FUNC_SUBTRACT = 0x800A
gl_GEOMETRY_INPUT_TYPE = 0x8917
gl_GEOMETRY_OUTPUT_TYPE = 0x8918
gl_GEOMETRY_SHADER = 0x8DD9
gl_GEOMETRY_VERTICES_OUT = 0x8916
gl_GEQUAL = 0x0206
gl_GREATER = 0x0204
gl_GREEN = 0x1904
gl_GREEN_INTEGER = 0x8D95
gl_HALF_FLOAT = 0x140B
gl_INCR = 0x1E02
gl_INCR_WRAP = 0x8507
gl_INFO_LOG_LENGTH = 0x8B84
gl_INT = 0x1404
gl_INTERLEAVED_ATTRIBS = 0x8C8C
gl_INT_SAMPLER_1D = 0x8DC9
gl_INT_SAMPLER_1D_ARRAY = 0x8DCE
gl_INT_SAMPLER_2D = 0x8DCA
gl_INT_SAMPLER_2D_ARRAY = 0x8DCF
gl_INT_SAMPLER_2D_MULTISAMPLE = 0x9109
gl_INT_SAMPLER_2D_MULTISAMPLE_ARRAY = 0x910C
gl_INT_SAMPLER_2D_RECT = 0x8DCD
gl_INT_SAMPLER_3D = 0x8DCB
gl_INT_SAMPLER_BUFFER = 0x8DD0
gl_INT_SAMPLER_CUBE = 0x8DCC
gl_INT_VEC2 = 0x8B53
gl_INT_VEC3 = 0x8B54
gl_INT_VEC4 = 0x8B55
gl_INVALID_ENUM = 0x0500
gl_INVALID_FRAMEBUFFER_OPERATION = 0x0506
gl_INVALID_INDEX = 0xFFFFFFFF
gl_INVALID_OPERATION = 0x0502
gl_INVALID_VALUE = 0x0501
gl_INVERT = 0x150A
gl_KEEP = 0x1E00
gl_LAST_VERTEX_CONVENTION = 0x8E4E
gl_LEFT = 0x0406
gl_LEQUAL = 0x0203
gl_LESS = 0x0201
gl_LINE = 0x1B01
gl_LINEAR = 0x2601
gl_LINEAR_MIPMAP_LINEAR = 0x2703
gl_LINEAR_MIPMAP_NEAREST = 0x2701
gl_LINES = 0x0001
gl_LINES_ADJACENCY = 0x000A
gl_LINE_LOOP = 0x0002
gl_LINE_SMOOTH = 0x0B20
gl_LINE_SMOOTH_HINT = 0x0C52
gl_LINE_STRIP = 0x0003
gl_LINE_STRIP_ADJACENCY = 0x000B
gl_LINE_WIDTH = 0x0B21
gl_LINE_WIDTH_GRANULARITY = 0x0B23
gl_LINE_WIDTH_RANGE = 0x0B22
gl_LINK_STATUS = 0x8B82
gl_LOGIC_OP_MODE = 0x0BF0
gl_LOWER_LEFT = 0x8CA1
gl_MAJOR_VERSION = 0x821B
gl_MAP_FLUSH_EXPLICIT_BIT = 0x0010
gl_MAP_INVALIDATE_BUFFER_BIT = 0x0008
gl_MAP_INVALIDATE_RANGE_BIT = 0x0004
gl_MAP_READ_BIT = 0x0001
gl_MAP_UNSYNCHRONIZED_BIT = 0x0020
gl_MAP_WRITE_BIT = 0x0002
gl_MAX = 0x8008
gl_MAX_3D_TEXTURE_SIZE = 0x8073
gl_MAX_ARRAY_TEXTURE_LAYERS = 0x88FF
gl_MAX_CLIP_DISTANCES = 0x0D32
gl_MAX_COLOR_ATTACHMENTS = 0x8CDF
gl_MAX_COLOR_TEXTURE_SAMPLES = 0x910E
gl_MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS = 0x8A33
gl_MAX_COMBINED_GEOMETRY_UNIFORM_COMPONENTS = 0x8A32
gl_MAX_COMBINED_TEXTURE_IMAGE_UNITS = 0x8B4D
gl_MAX_COMBINED_UNIFORM_BLOCKS = 0x8A2E
gl_MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS = 0x8A31
gl_MAX_CUBE_MAP_TEXTURE_SIZE = 0x851C
gl_MAX_DEPTH_TEXTURE_SAMPLES = 0x910F
gl_MAX_DRAW_BUFFERS = 0x8824
gl_MAX_ELEMENTS_INDICES = 0x80E9
gl_MAX_ELEMENTS_VERTICES = 0x80E8
gl_MAX_FRAGMENT_INPUT_COMPONENTS = 0x9125
gl_MAX_FRAGMENT_UNIFORM_BLOCKS = 0x8A2D
gl_MAX_FRAGMENT_UNIFORM_COMPONENTS = 0x8B49
gl_MAX_GEOMETRY_INPUT_COMPONENTS = 0x9123
gl_MAX_GEOMETRY_OUTPUT_COMPONENTS = 0x9124
gl_MAX_GEOMETRY_OUTPUT_VERTICES = 0x8DE0
gl_MAX_GEOMETRY_TEXTURE_IMAGE_UNITS = 0x8C29
gl_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS = 0x8DE1
gl_MAX_GEOMETRY_UNIFORM_BLOCKS = 0x8A2C
gl_MAX_GEOMETRY_UNIFORM_COMPONENTS = 0x8DDF
gl_MAX_INTEGER_SAMPLES = 0x9110
gl_MAX_PROGRAM_TEXEL_OFFSET = 0x8905
gl_MAX_RECTANGLE_TEXTURE_SIZE = 0x84F8
gl_MAX_RENDERBUFFER_SIZE = 0x84E8
gl_MAX_SAMPLES = 0x8D57
gl_MAX_SAMPLE_MASK_WORDS = 0x8E59
gl_MAX_SERVER_WAIT_TIMEOUT = 0x9111
gl_MAX_TEXTURE_BUFFER_SIZE = 0x8C2B
gl_MAX_TEXTURE_IMAGE_UNITS = 0x8872
gl_MAX_TEXTURE_LOD_BIAS = 0x84FD
gl_MAX_TEXTURE_SIZE = 0x0D33
gl_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS = 0x8C8A
gl_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS = 0x8C8B
gl_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS = 0x8C80
gl_MAX_UNIFORM_BLOCK_SIZE = 0x8A30
gl_MAX_UNIFORM_BUFFER_BINDINGS = 0x8A2F
gl_MAX_VARYING_COMPONENTS = 0x8B4B
gl_MAX_VARYING_FLOATS = 0x8B4B
gl_MAX_VERTEX_ATTRIBS = 0x8869
gl_MAX_VERTEX_OUTPUT_COMPONENTS = 0x9122
gl_MAX_VERTEX_TEXTURE_IMAGE_UNITS = 0x8B4C
gl_MAX_VERTEX_UNIFORM_BLOCKS = 0x8A2B
gl_MAX_VERTEX_UNIFORM_COMPONENTS = 0x8B4A
gl_MAX_VIEWPORT_DIMS = 0x0D3A
gl_MIN = 0x8007
gl_MINOR_VERSION = 0x821C
gl_MIN_PROGRAM_TEXEL_OFFSET = 0x8904
gl_MIRRORED_REPEAT = 0x8370
gl_MULTISAMPLE = 0x809D
gl_NAND = 0x150E
gl_NEAREST = 0x2600
gl_NEAREST_MIPMAP_LINEAR = 0x2702
gl_NEAREST_MIPMAP_NEAREST = 0x2700
gl_NEVER = 0x0200
gl_NICEST = 0x1102
gl_NONE = 0
gl_NOOP = 0x1505
gl_NOR = 0x1508
gl_NOTEQUAL = 0x0205
gl_NO_ERROR = 0
gl_NUM_COMPRESSED_TEXTURE_FORMATS = 0x86A2
gl_NUM_EXTENSIONS = 0x821D
gl_OBJECT_TYPE = 0x9112
gl_ONE = 1
gl_ONE_MINUS_CONSTANT_ALPHA = 0x8004
gl_ONE_MINUS_CONSTANT_COLOR = 0x8002
gl_ONE_MINUS_DST_ALPHA = 0x0305
gl_ONE_MINUS_DST_COLOR = 0x0307
gl_ONE_MINUS_SRC_ALPHA = 0x0303
gl_ONE_MINUS_SRC_COLOR = 0x0301
gl_OR = 0x1507
gl_OR_INVERTED = 0x150D
gl_OR_REVERSE = 0x150B
gl_OUT_OF_MEMORY = 0x0505
gl_PACK_ALIGNMENT = 0x0D05
gl_PACK_IMAGE_HEIGHT = 0x806C
gl_PACK_LSB_FIRST = 0x0D01
gl_PACK_ROW_LENGTH = 0x0D02
gl_PACK_SKIP_IMAGES = 0x806B
gl_PACK_SKIP_PIXELS = 0x0D04
gl_PACK_SKIP_ROWS = 0x0D03
gl_PACK_SWAP_BYTES = 0x0D00
gl_PIXEL_PACK_BUFFER = 0x88EB
gl_PIXEL_PACK_BUFFER_BINDING = 0x88ED
gl_PIXEL_UNPACK_BUFFER = 0x88EC
gl_PIXEL_UNPACK_BUFFER_BINDING = 0x88EF
gl_POINT = 0x1B00
gl_POINTS = 0x0000
gl_POINT_FADE_THRESHOLD_SIZE = 0x8128
gl_POINT_SIZE = 0x0B11
gl_POINT_SIZE_GRANULARITY = 0x0B13
gl_POINT_SIZE_RANGE = 0x0B12
gl_POINT_SPRITE_COORD_ORIGIN = 0x8CA0
gl_POLYGON_MODE = 0x0B40
gl_POLYGON_OFFSET_FACTOR = 0x8038
gl_POLYGON_OFFSET_FILL = 0x8037
gl_POLYGON_OFFSET_LINE = 0x2A02
gl_POLYGON_OFFSET_POINT = 0x2A01
gl_POLYGON_OFFSET_UNITS = 0x2A00
gl_POLYGON_SMOOTH = 0x0B41
gl_POLYGON_SMOOTH_HINT = 0x0C53
gl_PRIMITIVES_GENERATED = 0x8C87
gl_PRIMITIVE_RESTART = 0x8F9D
gl_PRIMITIVE_RESTART_INDEX = 0x8F9E
gl_PROGRAM_POINT_SIZE = 0x8642
gl_PROVOKING_VERTEX = 0x8E4F
gl_PROXY_TEXTURE_1D = 0x8063
gl_PROXY_TEXTURE_1D_ARRAY = 0x8C19
gl_PROXY_TEXTURE_2D = 0x8064
gl_PROXY_TEXTURE_2D_ARRAY = 0x8C1B
gl_PROXY_TEXTURE_2D_MULTISAMPLE = 0x9101
gl_PROXY_TEXTURE_2D_MULTISAMPLE_ARRAY = 0x9103
gl_PROXY_TEXTURE_3D = 0x8070
gl_PROXY_TEXTURE_CUBE_MAP = 0x851B
gl_PROXY_TEXTURE_RECTANGLE = 0x84F7
gl_QUADS_FOLLOW_PROVOKING_VERTEX_CONVENTION = 0x8E4C
gl_QUERY_BY_REGION_NO_WAIT = 0x8E16
gl_QUERY_BY_REGION_WAIT = 0x8E15
gl_QUERY_COUNTER_BITS = 0x8864
gl_QUERY_NO_WAIT = 0x8E14
gl_QUERY_RESULT = 0x8866
gl_QUERY_RESULT_AVAILABLE = 0x8867
gl_QUERY_WAIT = 0x8E13
gl_R11F_G11F_B10F = 0x8C3A
gl_R16 = 0x822A
gl_R16F = 0x822D
gl_R16I = 0x8233
gl_R16UI = 0x8234
gl_R16_SNORM = 0x8F98
gl_R32F = 0x822E
gl_R32I = 0x8235
gl_R32UI = 0x8236
gl_R3_G3_B2 = 0x2A10
gl_R8 = 0x8229
gl_R8I = 0x8231
gl_R8UI = 0x8232
gl_R8_SNORM = 0x8F94
gl_RASTERIZER_DISCARD = 0x8C89
gl_READ_BUFFER = 0x0C02
gl_READ_FRAMEBUFFER = 0x8CA8
gl_READ_FRAMEBUFFER_BINDING = 0x8CAA
gl_READ_ONLY = 0x88B8
gl_READ_WRITE = 0x88BA
gl_RED = 0x1903
gl_RED_INTEGER = 0x8D94
gl_RENDERBUFFER = 0x8D41
gl_RENDERBUFFER_ALPHA_SIZE = 0x8D53
gl_RENDERBUFFER_BINDING = 0x8CA7
gl_RENDERBUFFER_BLUE_SIZE = 0x8D52
gl_RENDERBUFFER_DEPTH_SIZE = 0x8D54
gl_RENDERBUFFER_GREEN_SIZE = 0x8D51
gl_RENDERBUFFER_HEIGHT = 0x8D43
gl_RENDERBUFFER_INTERNAL_FORMAT = 0x8D44
gl_RENDERBUFFER_RED_SIZE = 0x8D50
gl_RENDERBUFFER_SAMPLES = 0x8CAB
gl_RENDERBUFFER_STENCIL_SIZE = 0x8D55
gl_RENDERBUFFER_WIDTH = 0x8D42
gl_RENDERER = 0x1F01
gl_REPEAT = 0x2901
gl_REPLACE = 0x1E01
gl_RG = 0x8227
gl_RG16 = 0x822C
gl_RG16F = 0x822F
gl_RG16I = 0x8239
gl_RG16UI = 0x823A
gl_RG16_SNORM = 0x8F99
gl_RG32F = 0x8230
gl_RG32I = 0x823B
gl_RG32UI = 0x823C
gl_RG8 = 0x822B
gl_RG8I = 0x8237
gl_RG8UI = 0x8238
gl_RG8_SNORM = 0x8F95
gl_RGB = 0x1907
gl_RGB10 = 0x8052
gl_RGB10_A2 = 0x8059
gl_RGB12 = 0x8053
gl_RGB16 = 0x8054
gl_RGB16F = 0x881B
gl_RGB16I = 0x8D89
gl_RGB16UI = 0x8D77
gl_RGB16_SNORM = 0x8F9A
gl_RGB32F = 0x8815
gl_RGB32I = 0x8D83
gl_RGB32UI = 0x8D71
gl_RGB4 = 0x804F
gl_RGB5 = 0x8050
gl_RGB5_A1 = 0x8057
gl_RGB8 = 0x8051
gl_RGB8I = 0x8D8F
gl_RGB8UI = 0x8D7D
gl_RGB8_SNORM = 0x8F96
gl_RGB9_E5 = 0x8C3D
gl_RGBA = 0x1908
gl_RGBA12 = 0x805A
gl_RGBA16 = 0x805B
gl_RGBA16F = 0x881A
gl_RGBA16I = 0x8D88
gl_RGBA16UI = 0x8D76
gl_RGBA16_SNORM = 0x8F9B
gl_RGBA2 = 0x8055
gl_RGBA32F = 0x8814
gl_RGBA32I = 0x8D82
gl_RGBA32UI = 0x8D70
gl_RGBA4 = 0x8056
gl_RGBA8 = 0x8058
gl_RGBA8I = 0x8D8E
gl_RGBA8UI = 0x8D7C
gl_RGBA8_SNORM = 0x8F97
gl_RGBA_INTEGER = 0x8D99
gl_RGB_INTEGER = 0x8D98
gl_RG_INTEGER = 0x8228
gl_RIGHT = 0x0407
gl_SAMPLER_1D = 0x8B5D
gl_SAMPLER_1D_ARRAY = 0x8DC0
gl_SAMPLER_1D_ARRAY_SHADOW = 0x8DC3
gl_SAMPLER_1D_SHADOW = 0x8B61
gl_SAMPLER_2D = 0x8B5E
gl_SAMPLER_2D_ARRAY = 0x8DC1
gl_SAMPLER_2D_ARRAY_SHADOW = 0x8DC4
gl_SAMPLER_2D_MULTISAMPLE = 0x9108
gl_SAMPLER_2D_MULTISAMPLE_ARRAY = 0x910B
gl_SAMPLER_2D_RECT = 0x8B63
gl_SAMPLER_2D_RECT_SHADOW = 0x8B64
gl_SAMPLER_2D_SHADOW = 0x8B62
gl_SAMPLER_3D = 0x8B5F
gl_SAMPLER_BUFFER = 0x8DC2
gl_SAMPLER_CUBE = 0x8B60
gl_SAMPLER_CUBE_SHADOW = 0x8DC5
gl_SAMPLES = 0x80A9
gl_SAMPLES_PASSED = 0x8914
gl_SAMPLE_ALPHA_TO_COVERAGE = 0x809E
gl_SAMPLE_ALPHA_TO_ONE = 0x809F
gl_SAMPLE_BUFFERS = 0x80A8
gl_SAMPLE_COVERAGE = 0x80A0
gl_SAMPLE_COVERAGE_INVERT = 0x80AB
gl_SAMPLE_COVERAGE_VALUE = 0x80AA
gl_SAMPLE_MASK = 0x8E51
gl_SAMPLE_MASK_VALUE = 0x8E52
gl_SAMPLE_POSITION = 0x8E50
gl_SCISSOR_BOX = 0x0C10
gl_SCISSOR_TEST = 0x0C11
gl_SEPARATE_ATTRIBS = 0x8C8D
gl_SET = 0x150F
gl_SHADER_SOURCE_LENGTH = 0x8B88
gl_SHADER_TYPE = 0x8B4F
gl_SHADING_LANGUAGE_VERSION = 0x8B8C
gl_SHORT = 0x1402
gl_SIGNALED = 0x9119
gl_SIGNED_NORMALIZED = 0x8F9C
gl_SMOOTH_LINE_WIDTH_GRANULARITY = 0x0B23
gl_SMOOTH_LINE_WIDTH_RANGE = 0x0B22
gl_SMOOTH_POINT_SIZE_GRANULARITY = 0x0B13
gl_SMOOTH_POINT_SIZE_RANGE = 0x0B12
gl_SRC1_ALPHA = 0x8589
gl_SRC_ALPHA = 0x0302
gl_SRC_ALPHA_SATURATE = 0x0308
gl_SRC_COLOR = 0x0300
gl_SRGB = 0x8C40
gl_SRGB8 = 0x8C41
gl_SRGB8_ALPHA8 = 0x8C43
gl_SRGB_ALPHA = 0x8C42
gl_STATIC_COPY = 0x88E6
gl_STATIC_DRAW = 0x88E4
gl_STATIC_READ = 0x88E5
gl_STENCIL = 0x1802
gl_STENCIL_ATTACHMENT = 0x8D20
gl_STENCIL_BACK_FAIL = 0x8801
gl_STENCIL_BACK_FUNC = 0x8800
gl_STENCIL_BACK_PASS_DEPTH_FAIL = 0x8802
gl_STENCIL_BACK_PASS_DEPTH_PASS = 0x8803
gl_STENCIL_BACK_REF = 0x8CA3
gl_STENCIL_BACK_VALUE_MASK = 0x8CA4
gl_STENCIL_BACK_WRITEMASK = 0x8CA5
gl_STENCIL_BUFFER_BIT = 0x00000400
gl_STENCIL_CLEAR_VALUE = 0x0B91
gl_STENCIL_FAIL = 0x0B94
gl_STENCIL_FUNC = 0x0B92
gl_STENCIL_INDEX = 0x1901
gl_STENCIL_INDEX1 = 0x8D46
gl_STENCIL_INDEX16 = 0x8D49
gl_STENCIL_INDEX4 = 0x8D47
gl_STENCIL_INDEX8 = 0x8D48
gl_STENCIL_PASS_DEPTH_FAIL = 0x0B95
gl_STENCIL_PASS_DEPTH_PASS = 0x0B96
gl_STENCIL_REF = 0x0B97
gl_STENCIL_TEST = 0x0B90
gl_STENCIL_VALUE_MASK = 0x0B93
gl_STENCIL_WRITEMASK = 0x0B98
gl_STEREO = 0x0C33
gl_STREAM_COPY = 0x88E2
gl_STREAM_DRAW = 0x88E0
gl_STREAM_READ = 0x88E1
gl_SUBPIXEL_BITS = 0x0D50
gl_SYNC_CONDITION = 0x9113
gl_SYNC_FENCE = 0x9116
gl_SYNC_FLAGS = 0x9115
gl_SYNC_FLUSH_COMMANDS_BIT = 0x00000001
gl_SYNC_GPU_COMMANDS_COMPLETE = 0x9117
gl_SYNC_STATUS = 0x9114
gl_TEXTURE = 0x1702
gl_TEXTURE0 = 0x84C0
gl_TEXTURE1 = 0x84C1
gl_TEXTURE10 = 0x84CA
gl_TEXTURE11 = 0x84CB
gl_TEXTURE12 = 0x84CC
gl_TEXTURE13 = 0x84CD
gl_TEXTURE14 = 0x84CE
gl_TEXTURE15 = 0x84CF
gl_TEXTURE16 = 0x84D0
gl_TEXTURE17 = 0x84D1
gl_TEXTURE18 = 0x84D2
gl_TEXTURE19 = 0x84D3
gl_TEXTURE2 = 0x84C2
gl_TEXTURE20 = 0x84D4
gl_TEXTURE21 = 0x84D5
gl_TEXTURE22 = 0x84D6
gl_TEXTURE23 = 0x84D7
gl_TEXTURE24 = 0x84D8
gl_TEXTURE25 = 0x84D9
gl_TEXTURE26 = 0x84DA
gl_TEXTURE27 = 0x84DB
gl_TEXTURE28 = 0x84DC
gl_TEXTURE29 = 0x84DD
gl_TEXTURE3 = 0x84C3
gl_TEXTURE30 = 0x84DE
gl_TEXTURE31 = 0x84DF
gl_TEXTURE4 = 0x84C4
gl_TEXTURE5 = 0x84C5
gl_TEXTURE6 = 0x84C6
gl_TEXTURE7 = 0x84C7
gl_TEXTURE8 = 0x84C8
gl_TEXTURE9 = 0x84C9
gl_TEXTURE_1D = 0x0DE0
gl_TEXTURE_1D_ARRAY = 0x8C18
gl_TEXTURE_2D = 0x0DE1
gl_TEXTURE_2D_ARRAY = 0x8C1A
gl_TEXTURE_2D_MULTISAMPLE = 0x9100
gl_TEXTURE_2D_MULTISAMPLE_ARRAY = 0x9102
gl_TEXTURE_3D = 0x806F
gl_TEXTURE_ALPHA_SIZE = 0x805F
gl_TEXTURE_ALPHA_TYPE = 0x8C13
gl_TEXTURE_BASE_LEVEL = 0x813C
gl_TEXTURE_BINDING_1D = 0x8068
gl_TEXTURE_BINDING_1D_ARRAY = 0x8C1C
gl_TEXTURE_BINDING_2D = 0x8069
gl_TEXTURE_BINDING_2D_ARRAY = 0x8C1D
gl_TEXTURE_BINDING_2D_MULTISAMPLE = 0x9104
gl_TEXTURE_BINDING_2D_MULTISAMPLE_ARRAY = 0x9105
gl_TEXTURE_BINDING_3D = 0x806A
gl_TEXTURE_BINDING_BUFFER = 0x8C2C
gl_TEXTURE_BINDING_CUBE_MAP = 0x8514
gl_TEXTURE_BINDING_RECTANGLE = 0x84F6
gl_TEXTURE_BLUE_SIZE = 0x805E
gl_TEXTURE_BLUE_TYPE = 0x8C12
gl_TEXTURE_BORDER_COLOR = 0x1004
gl_TEXTURE_BUFFER = 0x8C2A
gl_TEXTURE_BUFFER_DATA_STORE_BINDING = 0x8C2D
gl_TEXTURE_COMPARE_FUNC = 0x884D
gl_TEXTURE_COMPARE_MODE = 0x884C
gl_TEXTURE_COMPRESSED = 0x86A1
gl_TEXTURE_COMPRESSED_IMAGE_SIZE = 0x86A0
gl_TEXTURE_COMPRESSION_HINT = 0x84EF
gl_TEXTURE_CUBE_MAP = 0x8513
gl_TEXTURE_CUBE_MAP_NEGATIVE_X = 0x8516
gl_TEXTURE_CUBE_MAP_NEGATIVE_Y = 0x8518
gl_TEXTURE_CUBE_MAP_NEGATIVE_Z = 0x851A
gl_TEXTURE_CUBE_MAP_POSITIVE_X = 0x8515
gl_TEXTURE_CUBE_MAP_POSITIVE_Y = 0x8517
gl_TEXTURE_CUBE_MAP_POSITIVE_Z = 0x8519
gl_TEXTURE_CUBE_MAP_SEAMLESS = 0x884F
gl_TEXTURE_DEPTH = 0x8071
gl_TEXTURE_DEPTH_SIZE = 0x884A
gl_TEXTURE_DEPTH_TYPE = 0x8C16
gl_TEXTURE_FIXED_SAMPLE_LOCATIONS = 0x9107
gl_TEXTURE_GREEN_SIZE = 0x805D
gl_TEXTURE_GREEN_TYPE = 0x8C11
gl_TEXTURE_HEIGHT = 0x1001
gl_TEXTURE_INTERNAL_FORMAT = 0x1003
gl_TEXTURE_LOD_BIAS = 0x8501
gl_TEXTURE_MAG_FILTER = 0x2800
gl_TEXTURE_MAX_LEVEL = 0x813D
gl_TEXTURE_MAX_LOD = 0x813B
gl_TEXTURE_MIN_FILTER = 0x2801
gl_TEXTURE_MIN_LOD = 0x813A
gl_TEXTURE_RECTANGLE = 0x84F5
gl_TEXTURE_RED_SIZE = 0x805C
gl_TEXTURE_RED_TYPE = 0x8C10
gl_TEXTURE_SAMPLES = 0x9106
gl_TEXTURE_SHARED_SIZE = 0x8C3F
gl_TEXTURE_STENCIL_SIZE = 0x88F1
gl_TEXTURE_WIDTH = 0x1000
gl_TEXTURE_WRAP_R = 0x8072
gl_TEXTURE_WRAP_S = 0x2802
gl_TEXTURE_WRAP_T = 0x2803
gl_TIMEOUT_EXPIRED = 0x911B
gl_TIMEOUT_IGNORED = 0xFFFFFFFFFFFFFFFF
gl_TRANSFORM_FEEDBACK_BUFFER = 0x8C8E
gl_TRANSFORM_FEEDBACK_BUFFER_BINDING = 0x8C8F
gl_TRANSFORM_FEEDBACK_BUFFER_MODE = 0x8C7F
gl_TRANSFORM_FEEDBACK_BUFFER_SIZE = 0x8C85
gl_TRANSFORM_FEEDBACK_BUFFER_START = 0x8C84
gl_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN = 0x8C88
gl_TRANSFORM_FEEDBACK_VARYINGS = 0x8C83
gl_TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH = 0x8C76
gl_TRIANGLES = 0x0004
gl_TRIANGLES_ADJACENCY = 0x000C
gl_TRIANGLE_FAN = 0x0006
gl_TRIANGLE_STRIP = 0x0005
gl_TRIANGLE_STRIP_ADJACENCY = 0x000D
gl_TRUE = 1
gl_UNIFORM_ARRAY_STRIDE = 0x8A3C
gl_UNIFORM_BLOCK_ACTIVE_UNIFORMS = 0x8A42
gl_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES = 0x8A43
gl_UNIFORM_BLOCK_BINDING = 0x8A3F
gl_UNIFORM_BLOCK_DATA_SIZE = 0x8A40
gl_UNIFORM_BLOCK_INDEX = 0x8A3A
gl_UNIFORM_BLOCK_NAME_LENGTH = 0x8A41
gl_UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER = 0x8A46
gl_UNIFORM_BLOCK_REFERENCED_BY_GEOMETRY_SHADER = 0x8A45
gl_UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER = 0x8A44
gl_UNIFORM_BUFFER = 0x8A11
gl_UNIFORM_BUFFER_BINDING = 0x8A28
gl_UNIFORM_BUFFER_OFFSET_ALIGNMENT = 0x8A34
gl_UNIFORM_BUFFER_SIZE = 0x8A2A
gl_UNIFORM_BUFFER_START = 0x8A29
gl_UNIFORM_IS_ROW_MAJOR = 0x8A3E
gl_UNIFORM_MATRIX_STRIDE = 0x8A3D
gl_UNIFORM_NAME_LENGTH = 0x8A39
gl_UNIFORM_OFFSET = 0x8A3B
gl_UNIFORM_SIZE = 0x8A38
gl_UNIFORM_TYPE = 0x8A37
gl_UNPACK_ALIGNMENT = 0x0CF5
gl_UNPACK_IMAGE_HEIGHT = 0x806E
gl_UNPACK_LSB_FIRST = 0x0CF1
gl_UNPACK_ROW_LENGTH = 0x0CF2
gl_UNPACK_SKIP_IMAGES = 0x806D
gl_UNPACK_SKIP_PIXELS = 0x0CF4
gl_UNPACK_SKIP_ROWS = 0x0CF3
gl_UNPACK_SWAP_BYTES = 0x0CF0
gl_UNSIGNALED = 0x9118
gl_UNSIGNED_BYTE = 0x1401
gl_UNSIGNED_BYTE_2_3_3_REV = 0x8362
gl_UNSIGNED_BYTE_3_3_2 = 0x8032
gl_UNSIGNED_INT = 0x1405
gl_UNSIGNED_INT_10F_11F_11F_REV = 0x8C3B
gl_UNSIGNED_INT_10_10_10_2 = 0x8036
gl_UNSIGNED_INT_24_8 = 0x84FA
gl_UNSIGNED_INT_2_10_10_10_REV = 0x8368
gl_UNSIGNED_INT_5_9_9_9_REV = 0x8C3E
gl_UNSIGNED_INT_8_8_8_8 = 0x8035
gl_UNSIGNED_INT_8_8_8_8_REV = 0x8367
gl_UNSIGNED_INT_SAMPLER_1D = 0x8DD1
gl_UNSIGNED_INT_SAMPLER_1D_ARRAY = 0x8DD6
gl_UNSIGNED_INT_SAMPLER_2D = 0x8DD2
gl_UNSIGNED_INT_SAMPLER_2D_ARRAY = 0x8DD7
gl_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE = 0x910A
gl_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY = 0x910D
gl_UNSIGNED_INT_SAMPLER_2D_RECT = 0x8DD5
gl_UNSIGNED_INT_SAMPLER_3D = 0x8DD3
gl_UNSIGNED_INT_SAMPLER_BUFFER = 0x8DD8
gl_UNSIGNED_INT_SAMPLER_CUBE = 0x8DD4
gl_UNSIGNED_INT_VEC2 = 0x8DC6
gl_UNSIGNED_INT_VEC3 = 0x8DC7
gl_UNSIGNED_INT_VEC4 = 0x8DC8
gl_UNSIGNED_NORMALIZED = 0x8C17
gl_UNSIGNED_SHORT = 0x1403
gl_UNSIGNED_SHORT_1_5_5_5_REV = 0x8366
gl_UNSIGNED_SHORT_4_4_4_4 = 0x8033
gl_UNSIGNED_SHORT_4_4_4_4_REV = 0x8365
gl_UNSIGNED_SHORT_5_5_5_1 = 0x8034
gl_UNSIGNED_SHORT_5_6_5 = 0x8363
gl_UNSIGNED_SHORT_5_6_5_REV = 0x8364
gl_UPPER_LEFT = 0x8CA2
gl_VALIDATE_STATUS = 0x8B83
gl_VENDOR = 0x1F00
gl_VERSION = 0x1F02
gl_VERTEX_ARRAY_BINDING = 0x85B5
gl_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING = 0x889F
gl_VERTEX_ATTRIB_ARRAY_ENABLED = 0x8622
gl_VERTEX_ATTRIB_ARRAY_INTEGER = 0x88FD
gl_VERTEX_ATTRIB_ARRAY_NORMALIZED = 0x886A
gl_VERTEX_ATTRIB_ARRAY_POINTER = 0x8645
gl_VERTEX_ATTRIB_ARRAY_SIZE = 0x8623
gl_VERTEX_ATTRIB_ARRAY_STRIDE = 0x8624
gl_VERTEX_ATTRIB_ARRAY_TYPE = 0x8625
gl_VERTEX_PROGRAM_POINT_SIZE = 0x8642
gl_VERTEX_SHADER = 0x8B31
gl_VIEWPORT = 0x0BA2
gl_WAIT_FAILED = 0x911D
gl_WRITE_ONLY = 0x88B9
gl_XOR = 0x1506
gl_ZERO = 0
)
// The GL interface is used to make this package independent of any particular
// OpenGL implementation. The goglimpl subpackage conatins an implementation of
// this interface based on Go-GL v3.2
type GL interface {
Ptr(data interface{}) unsafe.Pointer
ActiveTexture(texture uint32)
AttachShader(program uint32, shader uint32)
BindBuffer(target uint32, buffer uint32)
BindFramebuffer(target uint32, framebuffer uint32)
BindRenderbuffer(target uint32, renderbuffer uint32)
BindTexture(target uint32, texture uint32)
BlendFunc(sfactor uint32, dfactor uint32)
BufferData(target uint32, size int, data unsafe.Pointer, usage uint32)
CheckFramebufferStatus(target uint32) uint32
Clear(mask uint32)
ClearColor(red float32, green float32, blue float32, alpha float32)
ColorMask(red bool, green bool, blue bool, alpha bool)
CompileShader(shader uint32)
CreateProgram() uint32
CreateShader(xtype uint32) uint32
DeleteShader(shader uint32)
DeleteFramebuffers(n int32, framebuffers *uint32)
DeleteRenderbuffers(n int32, renderbuffers *uint32)
DeleteTextures(n int32, textures *uint32)
Disable(cap uint32)
DisableVertexAttribArray(index uint32)
DrawArrays(mode uint32, first int32, count int32)
Enable(cap uint32)
EnableVertexAttribArray(index uint32)
FramebufferRenderbuffer(target uint32, attachment uint32, renderbuffertarget uint32, renderbuffer uint32)
FramebufferTexture(target uint32, attachment uint32, texture uint32, level int32)
GenBuffers(n int32, buffers *uint32)
GenFramebuffers(n int32, framebuffers *uint32)
GenRenderbuffers(n int32, renderbuffers *uint32)
GenTextures(n int32, textures *uint32)
GenerateMipmap(target uint32)
GetAttribLocation(program uint32, name string) int32
GetError() uint32
GetProgramInfoLog(program uint32) string
GetProgramiv(program uint32, pname uint32, params *int32)
GetShaderInfoLog(shader uint32) string
GetShaderiv(shader uint32, pname uint32, params *int32)
GetUniformLocation(program uint32, name string) int32
LinkProgram(program uint32)
ReadPixels(x int32, y int32, width int32, height int32, format uint32, xtype uint32, pixels unsafe.Pointer)
RenderbufferStorage(target uint32, internalformat uint32, width int32, height int32)
Scissor(x int32, y int32, width int32, height int32)
ShaderSource(shader uint32, source string)
StencilFunc(xfunc uint32, ref int32, mask uint32)
StencilMask(mask uint32)
StencilOp(fail uint32, zfail uint32, zpass uint32)
TexImage2D(target uint32, level int32, internalformat int32, width int32, height int32, border int32, format uint32, xtype uint32, pixels unsafe.Pointer)
TexParameteri(target uint32, pname uint32, param int32)
TexSubImage2D(target uint32, level int32, xoffset int32, yoffset int32, width int32, height int32, format uint32, xtype uint32, pixels unsafe.Pointer)
Uniform1f(location int32, v0 float32)
Uniform1fv(location int32, count int32, v *float32)
Uniform1i(location int32, v0 int32)
Uniform2f(location int32, v0 float32, v1 float32)
Uniform4f(location int32, v0 float32, v1 float32, v2 float32, v3 float32)
UniformMatrix3fv(location int32, count int32, transpose bool, value *float32)
UseProgram(program uint32)
VertexAttribPointer(index uint32, size int32, xtype uint32, normalized bool, stride int32, offset uint32)
Viewport(x int32, y int32, width int32, height int32)
}

503
path2d.go Normal file
View file

@ -0,0 +1,503 @@
package canvas
import (
"math"
"git.mstar.dev/mstar/canvas/backend/backendbase"
)
// Path2D is a type that holds a predefined path which can be drawn
// with a single call
type Path2D struct {
cv *Canvas
p []pathPoint
move backendbase.Vec
cwSum float64
standalone bool
fillCache []backendbase.Vec
noSelfIntersection bool
}
type pathPoint struct {
pos backendbase.Vec
next backendbase.Vec
flags pathPointFlag
}
type pathPointFlag uint8
const (
pathMove pathPointFlag = 1 << iota
pathAttach
pathIsRect
pathIsConvex
pathIsClockwise
pathSelfIntersects
)
// NewPath2D creates a new Path2D and returns it
func (cv *Canvas) NewPath2D() *Path2D {
return &Path2D{cv: cv, p: make([]pathPoint, 0, 20), standalone: true}
}
func (p *Path2D) clearCache() {
p.fillCache = nil
}
// func (p *Path2D) AddPath(p2 *Path2D) {
// }
// MoveTo (see equivalent function on canvas type)
func (p *Path2D) MoveTo(x, y float64) {
if len(p.p) > 0 && isSamePoint(p.p[len(p.p)-1].pos, backendbase.Vec{x, y}, 0.1) {
return
}
p.clearCache()
p.p = append(p.p, pathPoint{pos: backendbase.Vec{x, y}, flags: pathMove | pathIsConvex})
p.cwSum = 0
p.move = backendbase.Vec{x, y}
}
// LineTo (see equivalent function on canvas type)
func (p *Path2D) LineTo(x, y float64) {
p.lineTo(x, y, true)
}
func (p *Path2D) lineTo(x, y float64, checkSelfIntersection bool) {
count := len(p.p)
if count > 0 && isSamePoint(p.p[len(p.p)-1].pos, backendbase.Vec{x, y}, 0.1) {
return
}
p.clearCache()
if count == 0 {
p.MoveTo(x, y)
return
}
prev := &p.p[count-1]
prev.next = backendbase.Vec{x, y}
prev.flags |= pathAttach
p.p = append(p.p, pathPoint{pos: backendbase.Vec{x, y}})
newp := &p.p[count]
if prev.flags&pathIsConvex > 0 {
px, py := prev.pos[0], prev.pos[1]
p.cwSum += (x - px) * (y + py)
cwTotal := p.cwSum
cwTotal += (p.move[0] - x) * (p.move[1] + y)
if cwTotal <= 0 {
newp.flags |= pathIsClockwise
}
}
if len(p.p) < 4 || Performance.AssumeConvex {
newp.flags |= pathIsConvex
} else if prev.flags&pathIsConvex > 0 {
prev2 := &p.p[count-2]
cw := (prev.flags & pathIsClockwise) > 0
ln := prev.pos.Sub(prev2.pos)
lo := backendbase.Vec{ln[1], -ln[0]}
dot := newp.pos.Sub(prev2.pos).Dot(lo)
if (cw && dot <= 0) || (!cw && dot >= 0) {
newp.flags |= pathIsConvex
}
}
csi := checkSelfIntersection && !Performance.IgnoreSelfIntersections && !p.noSelfIntersection
if prev.flags&pathSelfIntersects > 0 {
newp.flags |= pathSelfIntersects
} else if newp.flags&pathIsConvex == 0 && newp.flags&pathSelfIntersects == 0 && csi {
cuts := false
var cutPoint backendbase.Vec
b0, b1 := prev.pos, backendbase.Vec{x, y}
for i := 1; i < count; i++ {
a0, a1 := p.p[i-1].pos, p.p[i].pos
var r1, r2 float64
cutPoint, r1, r2 = lineIntersection(a0, a1, b0, b1)
if r1 > 0 && r1 < 1 && r2 > 0 && r2 < 1 {
cuts = true
break
}
}
if cuts && !isSamePoint(cutPoint, backendbase.Vec{x, y}, samePointTolerance) {
newp.flags |= pathSelfIntersects
}
}
}
// Arc (see equivalent function on canvas type)
func (p *Path2D) Arc(x, y, radius, startAngle, endAngle float64, anticlockwise bool) {
p.arc(x, y, radius, startAngle, endAngle, anticlockwise, backendbase.MatIdentity, true)
}
func (p *Path2D) arc(
x, y, radius, startAngle, endAngle float64,
anticlockwise bool,
m backendbase.Mat,
ident bool,
) {
checkSelfIntersection := len(p.p) > 0
lastWasMove := len(p.p) == 0 || p.p[len(p.p)-1].flags&pathMove != 0
if endAngle == startAngle {
s, c := math.Sincos(endAngle)
pt := backendbase.Vec{x + radius*c, y + radius*s}
if !ident {
pt = pt.MulMat(m)
}
p.lineTo(pt[0], pt[1], checkSelfIntersection)
if lastWasMove {
p.p[len(p.p)-1].flags |= pathIsConvex
}
return
}
if !anticlockwise && endAngle < startAngle {
endAngle = startAngle + (2*math.Pi - math.Mod(startAngle-endAngle, math.Pi*2))
} else if anticlockwise && endAngle > startAngle {
endAngle = startAngle - (2*math.Pi - math.Mod(endAngle-startAngle, math.Pi*2))
}
if !anticlockwise {
diff := endAngle - startAngle
if diff >= math.Pi*4 {
diff = math.Mod(diff, math.Pi*2) + math.Pi*2
endAngle = startAngle + diff
}
} else {
diff := startAngle - endAngle
if diff >= math.Pi*4 {
diff = math.Mod(diff, math.Pi*2) + math.Pi*2
endAngle = startAngle - diff
}
}
const step = math.Pi * 2 / 90
if !anticlockwise {
for a := startAngle; a < endAngle; a += step {
s, c := math.Sincos(a)
pt := backendbase.Vec{x + radius*c, y + radius*s}
if !ident {
pt = pt.MulMat(m)
}
p.lineTo(pt[0], pt[1], checkSelfIntersection)
}
} else {
for a := startAngle; a > endAngle; a -= step {
s, c := math.Sincos(a)
pt := backendbase.Vec{x + radius*c, y + radius*s}
if !ident {
pt = pt.MulMat(m)
}
p.lineTo(pt[0], pt[1], checkSelfIntersection)
}
}
s, c := math.Sincos(endAngle)
pt := backendbase.Vec{x + radius*c, y + radius*s}
if !ident {
pt = pt.MulMat(m)
}
p.lineTo(pt[0], pt[1], checkSelfIntersection)
if lastWasMove {
p.p[len(p.p)-1].flags |= pathIsConvex
}
}
// ArcTo (see equivalent function on canvas type)
func (p *Path2D) ArcTo(x1, y1, x2, y2, radius float64) {
p.arcTo(x1, y1, x2, y2, radius, backendbase.MatIdentity, true)
}
func (p *Path2D) arcTo(x1, y1, x2, y2, radius float64, m backendbase.Mat, ident bool) {
if len(p.p) == 0 {
return
}
p0, p1, p2 := p.p[len(p.p)-1].pos, backendbase.Vec{x1, y1}, backendbase.Vec{x2, y2}
if !ident {
p0 = p0.MulMat(m.Invert())
}
v0, v1 := p0.Sub(p1).Norm(), p2.Sub(p1).Norm()
angle := math.Acos(v0.Dot(v1))
// should be in the range [0-pi]. if parallel, use a straight line
if angle <= 0 || angle >= math.Pi {
p.LineTo(x2, y2)
return
}
// cv0 and cv1 are vectors that point to the center of the circle
cv0 := backendbase.Vec{-v0[1], v0[0]}
cv1 := backendbase.Vec{v1[1], -v1[0]}
x := cv1.Sub(cv0).Div(v0.Sub(v1))[0] * radius
if x < 0 {
cv0 = cv0.Mulf(-1)
cv1 = cv1.Mulf(-1)
}
center := p1.Add(v0.Mulf(math.Abs(x))).Add(cv0.Mulf(radius))
a0, a1 := cv0.Mulf(-1).Atan2(), cv1.Mulf(-1).Atan2()
if x > 0 {
if a1-a0 > 0 {
a0 += math.Pi * 2
}
} else {
if a0-a1 > 0 {
a1 += math.Pi * 2
}
}
p.arc(center[0], center[1], radius, a0, a1, x > 0, m, ident)
}
// QuadraticCurveTo (see equivalent function on canvas type)
func (p *Path2D) QuadraticCurveTo(x1, y1, x2, y2 float64) {
if len(p.p) == 0 {
return
}
p0 := p.p[len(p.p)-1].pos
p1 := backendbase.Vec{x1, y1}
p2 := backendbase.Vec{x2, y2}
v0 := p1.Sub(p0)
v1 := p2.Sub(p1)
const step = 0.01
for r := 0.0; r < 1; r += step {
i0 := v0.Mulf(r).Add(p0)
i1 := v1.Mulf(r).Add(p1)
pt := i1.Sub(i0).Mulf(r).Add(i0)
p.LineTo(pt[0], pt[1])
}
p.LineTo(x2, y2)
}
// BezierCurveTo (see equivalent function on canvas type)
func (p *Path2D) BezierCurveTo(x1, y1, x2, y2, x3, y3 float64) {
if len(p.p) == 0 {
return
}
p0 := p.p[len(p.p)-1].pos
p1 := backendbase.Vec{x1, y1}
p2 := backendbase.Vec{x2, y2}
p3 := backendbase.Vec{x3, y3}
v0 := p1.Sub(p0)
v1 := p2.Sub(p1)
v2 := p3.Sub(p2)
const step = 0.01
for r := 0.0; r < 1; r += step {
i0 := v0.Mulf(r).Add(p0)
i1 := v1.Mulf(r).Add(p1)
i2 := v2.Mulf(r).Add(p2)
iv0 := i1.Sub(i0)
iv1 := i2.Sub(i1)
j0 := iv0.Mulf(r).Add(i0)
j1 := iv1.Mulf(r).Add(i1)
pt := j1.Sub(j0).Mulf(r).Add(j0)
p.LineTo(pt[0], pt[1])
}
p.LineTo(x3, y3)
}
// Ellipse (see equivalent function on canvas type)
func (p *Path2D) Ellipse(
x, y, radiusX, radiusY, rotation, startAngle, endAngle float64,
anticlockwise bool,
) {
checkSelfIntersection := len(p.p) > 0
rs, rc := math.Sincos(rotation)
lastWasMove := len(p.p) == 0 || p.p[len(p.p)-1].flags&pathMove != 0
if endAngle == startAngle {
s, c := math.Sincos(endAngle)
rx, ry := radiusX*c, radiusY*s
rx, ry = rx*rc-ry*rs, rx*rs+ry*rc
p.lineTo(x+rx, y+ry, checkSelfIntersection)
if lastWasMove {
p.p[len(p.p)-1].flags |= pathIsConvex
}
return
}
if !anticlockwise && endAngle < startAngle {
endAngle = startAngle + (2*math.Pi - math.Mod(startAngle-endAngle, math.Pi*2))
} else if anticlockwise && endAngle > startAngle {
endAngle = startAngle - (2*math.Pi - math.Mod(endAngle-startAngle, math.Pi*2))
}
if !anticlockwise {
diff := endAngle - startAngle
if diff >= math.Pi*4 {
diff = math.Mod(diff, math.Pi*2) + math.Pi*2
endAngle = startAngle + diff
}
} else {
diff := startAngle - endAngle
if diff >= math.Pi*4 {
diff = math.Mod(diff, math.Pi*2) + math.Pi*2
endAngle = startAngle - diff
}
}
const step = math.Pi * 2 / 90
if !anticlockwise {
for a := startAngle; a < endAngle; a += step {
s, c := math.Sincos(a)
rx, ry := radiusX*c, radiusY*s
rx, ry = rx*rc-ry*rs, rx*rs+ry*rc
p.lineTo(x+rx, y+ry, checkSelfIntersection)
}
} else {
for a := startAngle; a > endAngle; a -= step {
s, c := math.Sincos(a)
rx, ry := radiusX*c, radiusY*s
rx, ry = rx*rc-ry*rs, rx*rs+ry*rc
p.lineTo(x+rx, y+ry, checkSelfIntersection)
}
}
s, c := math.Sincos(endAngle)
rx, ry := radiusX*c, radiusY*s
rx, ry = rx*rc-ry*rs, rx*rs+ry*rc
p.lineTo(x+rx, y+ry, checkSelfIntersection)
if lastWasMove {
p.p[len(p.p)-1].flags |= pathIsConvex
}
}
// ClosePath (see equivalent function on canvas type)
func (p *Path2D) ClosePath() {
if len(p.p) < 2 {
return
}
closeIdx := 0
for i := len(p.p) - 1; i >= 0; i-- {
if p.p[i].flags&pathMove != 0 {
closeIdx = i
break
}
}
if !isSamePoint(p.p[len(p.p)-1].pos, p.p[0].pos, 0.1) {
p.LineTo(p.p[closeIdx].pos[0], p.p[closeIdx].pos[1])
}
p.p[len(p.p)-1].next = p.p[closeIdx].next
p.p[len(p.p)-1].flags |= pathAttach
}
// Rect (see equivalent function on canvas type)
func (p *Path2D) Rect(x, y, w, h float64) {
lastWasMove := len(p.p) == 0 || p.p[len(p.p)-1].flags&pathMove != 0
p.MoveTo(x, y)
p.LineTo(x+w, y)
p.LineTo(x+w, y+h)
p.LineTo(x, y+h)
p.LineTo(x, y)
if lastWasMove {
p.p[len(p.p)-1].flags |= pathIsRect
p.p[len(p.p)-1].flags |= pathIsConvex
}
}
func runSubPaths(path []pathPoint, close bool, fn func(subPath []pathPoint) bool) {
start := 0
for i, p := range path {
if p.flags&pathMove == 0 {
continue
}
if i >= start+3 {
if runSubPath(path[start:i], close, fn) {
return
}
}
start = i
}
if len(path) >= start+3 {
runSubPath(path[start:], close, fn)
}
}
func runSubPath(path []pathPoint, close bool, fn func(subPath []pathPoint) bool) bool {
if !close || path[0].pos == path[len(path)-1].pos {
return fn(path)
}
var buf [64]pathPoint
path2 := Path2D{
p: append(buf[:0], path...),
move: path[0].pos,
}
path2.lineTo(path[0].pos[0], path[0].pos[1], true)
return fn(path2.p)
}
type pathRule uint8
// Path rule constants. See https://en.wikipedia.org/wiki/Nonzero-rule
// and https://en.wikipedia.org/wiki/Even%E2%80%93odd_rule
const (
NonZero pathRule = iota
EvenOdd
)
// IsPointInPath returns true if the point is in the path according
// to the given rule
func (p *Path2D) IsPointInPath(x, y float64, rule pathRule) bool {
inside := false
runSubPaths(p.p, false, func(sp []pathPoint) bool {
num := 0
prev := sp[len(sp)-1].pos
for _, pt := range sp {
r, dir := pointIsRightOfLine(prev, pt.pos, backendbase.Vec{x, y})
prev = pt.pos
if !r {
continue
}
if dir {
num++
} else {
num--
}
}
if rule == NonZero {
inside = num != 0
} else {
inside = num%2 == 0
}
return inside
})
return inside
}
// IsPointInStroke returns true if the point is in the stroke
func (p *Path2D) IsPointInStroke(x, y float64) bool {
if len(p.p) == 0 {
return false
}
var triBuf [500]backendbase.Vec
tris := p.cv.strokeTris(p, p.cv.state.transform, backendbase.Mat{}, false, triBuf[:0])
pt := backendbase.Vec{x, y}
for i := 0; i < len(tris); i += 3 {
a := backendbase.Vec{tris[i][0], tris[i][1]}
b := backendbase.Vec{tris[i+1][0], tris[i+1][1]}
c := backendbase.Vec{tris[i+2][0], tris[i+2][1]}
if triangleContainsPoint(a, b, c, pt) {
return true
}
}
return false
}

866
paths.go

File diff suppressed because it is too large Load diff

View file

@ -1,216 +0,0 @@
package sdlcanvas
import "github.com/veandco/go-sdl2/sdl"
var keyNameMap [262]string
var keyRuneMap [262]rune
func init() {
keyNameMap[sdl.SCANCODE_ESCAPE] = "Escape"
keyNameMap[sdl.SCANCODE_0] = "Digit0"
keyNameMap[sdl.SCANCODE_1] = "Digit1"
keyNameMap[sdl.SCANCODE_2] = "Digit2"
keyNameMap[sdl.SCANCODE_3] = "Digit3"
keyNameMap[sdl.SCANCODE_4] = "Digit4"
keyNameMap[sdl.SCANCODE_5] = "Digit5"
keyNameMap[sdl.SCANCODE_6] = "Digit6"
keyNameMap[sdl.SCANCODE_7] = "Digit7"
keyNameMap[sdl.SCANCODE_8] = "Digit8"
keyNameMap[sdl.SCANCODE_9] = "Digit9"
keyNameMap[sdl.SCANCODE_MINUS] = "Minus"
keyNameMap[sdl.SCANCODE_EQUALS] = "Equal"
keyNameMap[sdl.SCANCODE_BACKSPACE] = "Backspace"
keyNameMap[sdl.SCANCODE_TAB] = "Tab"
keyNameMap[sdl.SCANCODE_Q] = "KeyQ"
keyNameMap[sdl.SCANCODE_W] = "KeyW"
keyNameMap[sdl.SCANCODE_E] = "KeyE"
keyNameMap[sdl.SCANCODE_R] = "KeyR"
keyNameMap[sdl.SCANCODE_T] = "KeyT"
keyNameMap[sdl.SCANCODE_Y] = "KeyY"
keyNameMap[sdl.SCANCODE_U] = "KeyU"
keyNameMap[sdl.SCANCODE_I] = "KeyI"
keyNameMap[sdl.SCANCODE_O] = "KeyO"
keyNameMap[sdl.SCANCODE_P] = "KeyP"
keyNameMap[sdl.SCANCODE_LEFTBRACKET] = "BracketLeft"
keyNameMap[sdl.SCANCODE_RIGHTBRACKET] = "BracketRight"
keyNameMap[sdl.SCANCODE_RETURN] = "Enter"
keyNameMap[sdl.SCANCODE_LCTRL] = "ControlLeft"
keyNameMap[sdl.SCANCODE_A] = "KeyA"
keyNameMap[sdl.SCANCODE_S] = "KeyS"
keyNameMap[sdl.SCANCODE_D] = "KeyD"
keyNameMap[sdl.SCANCODE_F] = "KeyF"
keyNameMap[sdl.SCANCODE_G] = "KeyG"
keyNameMap[sdl.SCANCODE_H] = "KeyH"
keyNameMap[sdl.SCANCODE_J] = "KeyJ"
keyNameMap[sdl.SCANCODE_K] = "KeyK"
keyNameMap[sdl.SCANCODE_L] = "KeyL"
keyNameMap[sdl.SCANCODE_SEMICOLON] = "Semicolon"
keyNameMap[sdl.SCANCODE_APOSTROPHE] = "Quote"
keyNameMap[sdl.SCANCODE_GRAVE] = "Backquote"
keyNameMap[sdl.SCANCODE_LSHIFT] = "ShiftLeft"
keyNameMap[sdl.SCANCODE_BACKSLASH] = "Backslash"
keyNameMap[sdl.SCANCODE_Z] = "KeyZ"
keyNameMap[sdl.SCANCODE_X] = "KeyX"
keyNameMap[sdl.SCANCODE_C] = "KeyC"
keyNameMap[sdl.SCANCODE_V] = "KeyV"
keyNameMap[sdl.SCANCODE_B] = "KeyB"
keyNameMap[sdl.SCANCODE_N] = "KeyN"
keyNameMap[sdl.SCANCODE_M] = "KeyM"
keyNameMap[sdl.SCANCODE_COMMA] = "Comma"
keyNameMap[sdl.SCANCODE_PERIOD] = "Period"
keyNameMap[sdl.SCANCODE_SLASH] = "Slash"
keyNameMap[sdl.SCANCODE_RSHIFT] = "RightShift"
keyNameMap[sdl.SCANCODE_KP_MULTIPLY] = "NumpadMultiply"
keyNameMap[sdl.SCANCODE_LALT] = "AltLeft"
keyNameMap[sdl.SCANCODE_SPACE] = "Space"
keyNameMap[sdl.SCANCODE_CAPSLOCK] = "CapsLock"
keyNameMap[sdl.SCANCODE_F1] = "F1"
keyNameMap[sdl.SCANCODE_F2] = "F2"
keyNameMap[sdl.SCANCODE_F3] = "F3"
keyNameMap[sdl.SCANCODE_F4] = "F4"
keyNameMap[sdl.SCANCODE_F5] = "F5"
keyNameMap[sdl.SCANCODE_F6] = "F6"
keyNameMap[sdl.SCANCODE_F7] = "F7"
keyNameMap[sdl.SCANCODE_F8] = "F8"
keyNameMap[sdl.SCANCODE_F9] = "F9"
keyNameMap[sdl.SCANCODE_F10] = "F10"
keyNameMap[sdl.SCANCODE_PAUSE] = "Pause"
keyNameMap[sdl.SCANCODE_SCROLLLOCK] = "ScrollLock"
keyNameMap[sdl.SCANCODE_KP_7] = "Numpad7"
keyNameMap[sdl.SCANCODE_KP_8] = "Numpad8"
keyNameMap[sdl.SCANCODE_KP_9] = "Numpad9"
keyNameMap[sdl.SCANCODE_KP_MINUS] = "NumpadSubtract"
keyNameMap[sdl.SCANCODE_KP_4] = "Numpad4"
keyNameMap[sdl.SCANCODE_KP_5] = "Numpad5"
keyNameMap[sdl.SCANCODE_KP_6] = "Numpad6"
keyNameMap[sdl.SCANCODE_KP_PLUS] = "NumpadAdd"
keyNameMap[sdl.SCANCODE_KP_1] = "Numpad1"
keyNameMap[sdl.SCANCODE_KP_2] = "Numpad2"
keyNameMap[sdl.SCANCODE_KP_3] = "Numpad3"
keyNameMap[sdl.SCANCODE_KP_0] = "Numpad0"
keyNameMap[sdl.SCANCODE_KP_DECIMAL] = "NumpadDecimal"
keyNameMap[sdl.SCANCODE_PRINTSCREEN] = "PrintScreen"
keyNameMap[sdl.SCANCODE_NONUSBACKSLASH] = "IntlBackslash"
keyNameMap[sdl.SCANCODE_F11] = "F11"
keyNameMap[sdl.SCANCODE_F12] = "F12"
keyNameMap[sdl.SCANCODE_KP_EQUALS] = "NumpadEqual"
keyNameMap[sdl.SCANCODE_F13] = "F13"
keyNameMap[sdl.SCANCODE_F14] = "F14"
keyNameMap[sdl.SCANCODE_F15] = "F15"
keyNameMap[sdl.SCANCODE_F16] = "F16"
keyNameMap[sdl.SCANCODE_F17] = "F17"
keyNameMap[sdl.SCANCODE_F18] = "F18"
keyNameMap[sdl.SCANCODE_F19] = "F19"
keyNameMap[sdl.SCANCODE_UNDO] = "Undo"
keyNameMap[sdl.SCANCODE_PASTE] = "Paste"
keyNameMap[sdl.SCANCODE_AUDIOPREV] = "MediaTrackPrevious"
keyNameMap[sdl.SCANCODE_CUT] = "Cut"
keyNameMap[sdl.SCANCODE_COPY] = "Copy"
keyNameMap[sdl.SCANCODE_AUDIONEXT] = "MediaTrackNext"
keyNameMap[sdl.SCANCODE_KP_ENTER] = "NumpadEnter"
keyNameMap[sdl.SCANCODE_RCTRL] = "ControlRight"
keyNameMap[sdl.SCANCODE_MUTE] = "AudioVolumeMute"
keyNameMap[sdl.SCANCODE_AUDIOPLAY] = "MediaPlayPause"
keyNameMap[sdl.SCANCODE_AUDIOSTOP] = "MediaStop"
keyNameMap[sdl.SCANCODE_VOLUMEDOWN] = "AudioVolumeDown"
keyNameMap[sdl.SCANCODE_VOLUMEUP] = "AudioVolumeUp"
keyNameMap[sdl.SCANCODE_KP_DIVIDE] = "NumpadDivide"
keyNameMap[sdl.SCANCODE_RALT] = "AltRight"
keyNameMap[sdl.SCANCODE_HELP] = "Help"
keyNameMap[sdl.SCANCODE_HOME] = "Home"
keyNameMap[sdl.SCANCODE_UP] = "ArrowUp"
keyNameMap[sdl.SCANCODE_PAGEUP] = "PageUp"
keyNameMap[sdl.SCANCODE_LEFT] = "ArrowLeft"
keyNameMap[sdl.SCANCODE_RIGHT] = "ArrowRight"
keyNameMap[sdl.SCANCODE_END] = "End"
keyNameMap[sdl.SCANCODE_DOWN] = "ArrowDown"
keyNameMap[sdl.SCANCODE_INSERT] = "Insert"
keyNameMap[sdl.SCANCODE_DELETE] = "Delete"
keyNameMap[sdl.SCANCODE_APPLICATION] = "ContextMenu"
keyRuneMap[sdl.SCANCODE_0] = '0'
keyRuneMap[sdl.SCANCODE_1] = '1'
keyRuneMap[sdl.SCANCODE_2] = '2'
keyRuneMap[sdl.SCANCODE_3] = '3'
keyRuneMap[sdl.SCANCODE_4] = '4'
keyRuneMap[sdl.SCANCODE_5] = '5'
keyRuneMap[sdl.SCANCODE_6] = '6'
keyRuneMap[sdl.SCANCODE_7] = '7'
keyRuneMap[sdl.SCANCODE_8] = '8'
keyRuneMap[sdl.SCANCODE_9] = '9'
keyRuneMap[sdl.SCANCODE_MINUS] = '-'
keyRuneMap[sdl.SCANCODE_EQUALS] = '='
keyRuneMap[sdl.SCANCODE_TAB] = '\t'
keyRuneMap[sdl.SCANCODE_Q] = 'Q'
keyRuneMap[sdl.SCANCODE_W] = 'W'
keyRuneMap[sdl.SCANCODE_E] = 'E'
keyRuneMap[sdl.SCANCODE_R] = 'R'
keyRuneMap[sdl.SCANCODE_T] = 'T'
keyRuneMap[sdl.SCANCODE_Y] = 'Y'
keyRuneMap[sdl.SCANCODE_U] = 'U'
keyRuneMap[sdl.SCANCODE_I] = 'I'
keyRuneMap[sdl.SCANCODE_O] = 'O'
keyRuneMap[sdl.SCANCODE_P] = 'P'
keyRuneMap[sdl.SCANCODE_LEFTBRACKET] = '['
keyRuneMap[sdl.SCANCODE_RIGHTBRACKET] = ']'
keyRuneMap[sdl.SCANCODE_RETURN] = '\n'
keyRuneMap[sdl.SCANCODE_A] = 'A'
keyRuneMap[sdl.SCANCODE_S] = 'S'
keyRuneMap[sdl.SCANCODE_D] = 'D'
keyRuneMap[sdl.SCANCODE_F] = 'F'
keyRuneMap[sdl.SCANCODE_G] = 'G'
keyRuneMap[sdl.SCANCODE_H] = 'H'
keyRuneMap[sdl.SCANCODE_J] = 'J'
keyRuneMap[sdl.SCANCODE_K] = 'K'
keyRuneMap[sdl.SCANCODE_L] = 'L'
keyRuneMap[sdl.SCANCODE_SEMICOLON] = ';'
keyRuneMap[sdl.SCANCODE_APOSTROPHE] = '\''
keyRuneMap[sdl.SCANCODE_GRAVE] = '`'
keyRuneMap[sdl.SCANCODE_BACKSLASH] = '\\'
keyRuneMap[sdl.SCANCODE_Z] = 'Z'
keyRuneMap[sdl.SCANCODE_X] = 'X'
keyRuneMap[sdl.SCANCODE_C] = 'C'
keyRuneMap[sdl.SCANCODE_V] = 'V'
keyRuneMap[sdl.SCANCODE_B] = 'B'
keyRuneMap[sdl.SCANCODE_N] = 'N'
keyRuneMap[sdl.SCANCODE_M] = 'M'
keyRuneMap[sdl.SCANCODE_COMMA] = ','
keyRuneMap[sdl.SCANCODE_PERIOD] = '.'
keyRuneMap[sdl.SCANCODE_SLASH] = '/'
keyRuneMap[sdl.SCANCODE_KP_MULTIPLY] = '*'
keyRuneMap[sdl.SCANCODE_SPACE] = ' '
keyRuneMap[sdl.SCANCODE_KP_7] = '7'
keyRuneMap[sdl.SCANCODE_KP_8] = '8'
keyRuneMap[sdl.SCANCODE_KP_9] = '9'
keyRuneMap[sdl.SCANCODE_KP_MINUS] = '-'
keyRuneMap[sdl.SCANCODE_KP_4] = '4'
keyRuneMap[sdl.SCANCODE_KP_5] = '5'
keyRuneMap[sdl.SCANCODE_KP_6] = '6'
keyRuneMap[sdl.SCANCODE_KP_PLUS] = '+'
keyRuneMap[sdl.SCANCODE_KP_1] = '1'
keyRuneMap[sdl.SCANCODE_KP_2] = '2'
keyRuneMap[sdl.SCANCODE_KP_3] = '3'
keyRuneMap[sdl.SCANCODE_KP_0] = '0'
keyRuneMap[sdl.SCANCODE_KP_DECIMAL] = '.'
keyRuneMap[sdl.SCANCODE_KP_EQUALS] = '='
keyRuneMap[sdl.SCANCODE_KP_ENTER] = '\n'
keyRuneMap[sdl.SCANCODE_KP_DIVIDE] = '/'
}
func keyName(s sdl.Scancode) string {
if int(s) >= len(keyNameMap) {
return "Unidentified"
}
name := keyNameMap[s]
if name == "" {
return "Unidentified"
}
return name
}
func keyRune(s sdl.Scancode) rune {
if int(s) >= len(keyNameMap) {
return 0
}
return keyRuneMap[s]
}

View file

@ -1,258 +0,0 @@
package sdlcanvas
import (
"fmt"
_ "image/gif" // Imported here so that applications based on this package support these formats by default
_ "image/jpeg"
_ "image/png"
"runtime"
"time"
"unicode/utf8"
"github.com/go-gl/gl/v3.2-core/gl"
"github.com/tfriedel6/canvas"
"github.com/tfriedel6/canvas/glimpl/gogl"
"github.com/veandco/go-sdl2/sdl"
)
// Window represents the opened window with GL context. The Mouse* and Key*
// functions can be set for callbacks
type Window struct {
Window *sdl.Window
WindowID uint32
GLContext sdl.GLContext
canvas *canvas.Canvas
frameTimes [10]time.Time
frameIndex int
frameCount int
fps float32
close bool
events []sdl.Event
Event func(event sdl.Event)
MouseDown func(button, x, y int)
MouseMove func(x, y int)
MouseUp func(button, x, y int)
MouseWheel func(x, y int)
KeyDown func(scancode int, rn rune, name string)
KeyUp func(scancode int, rn rune, name string)
KeyChar func(rn rune)
SizeChange func(w, h int)
}
// CreateWindow creates a window using SDL and initializes the OpenGL context
func CreateWindow(w, h int, title string) (*Window, *canvas.Canvas, error) {
runtime.LockOSThread()
// init SDL
err := sdl.Init(sdl.INIT_VIDEO)
if err != nil {
return nil, nil, fmt.Errorf("Error initializing SDL: %v", err)
}
sdl.GLSetAttribute(sdl.GL_RED_SIZE, 8)
sdl.GLSetAttribute(sdl.GL_GREEN_SIZE, 8)
sdl.GLSetAttribute(sdl.GL_BLUE_SIZE, 8)
sdl.GLSetAttribute(sdl.GL_ALPHA_SIZE, 8)
sdl.GLSetAttribute(sdl.GL_DEPTH_SIZE, 0)
sdl.GLSetAttribute(sdl.GL_STENCIL_SIZE, 8)
sdl.GLSetAttribute(sdl.GL_DOUBLEBUFFER, 1)
sdl.GLSetAttribute(sdl.GL_MULTISAMPLEBUFFERS, 1)
sdl.GLSetAttribute(sdl.GL_MULTISAMPLESAMPLES, 4)
// create window
window, err := sdl.CreateWindow(title, sdl.WINDOWPOS_CENTERED, sdl.WINDOWPOS_CENTERED, int32(w), int32(h), sdl.WINDOW_RESIZABLE|sdl.WINDOW_OPENGL)
if err != nil {
sdl.GLSetAttribute(sdl.GL_MULTISAMPLEBUFFERS, 0)
sdl.GLSetAttribute(sdl.GL_MULTISAMPLESAMPLES, 0)
window, err = sdl.CreateWindow(title, sdl.WINDOWPOS_CENTERED, sdl.WINDOWPOS_CENTERED, int32(w), int32(h), sdl.WINDOW_RESIZABLE|sdl.WINDOW_OPENGL)
if err != nil {
return nil, nil, fmt.Errorf("Error creating window: %v", err)
}
}
windowID, err := window.GetID()
if err != nil {
return nil, nil, fmt.Errorf("Error getting window ID: %v", err)
}
// create GL context
glContext, err := window.GLCreateContext()
if err != nil {
return nil, nil, fmt.Errorf("Error creating GL context: %v", err)
}
// init GL
err = gl.Init()
if err != nil {
return nil, nil, fmt.Errorf("Error initializing GL: %v", err)
}
sdl.GLSetSwapInterval(1)
gl.Enable(gl.MULTISAMPLE)
err = canvas.LoadGL(glimplgogl.GLImpl{})
if err != nil {
return nil, nil, fmt.Errorf("Error loading canvas GL assets: %v", err)
}
cv := canvas.New(0, 0, w, h)
wnd := &Window{
Window: window,
WindowID: windowID,
GLContext: glContext,
canvas: cv,
events: make([]sdl.Event, 0, 100),
}
return wnd, cv, nil
}
// Destroy destroys the GL context and the window
func (wnd *Window) Destroy() {
sdl.GLDeleteContext(wnd.GLContext)
wnd.Window.Destroy()
}
// FPS returns the frames per second (averaged over 10 frames)
func (wnd *Window) FPS() float32 {
return wnd.fps
}
// Close can be used to end a call to MainLoop
func (wnd *Window) Close() {
wnd.close = true
}
// StartFrame handles events and gets the window ready for rendering
func (wnd *Window) StartFrame() error {
err := wnd.Window.GLMakeCurrent(wnd.GLContext)
if err != nil {
return err
}
wnd.events = wnd.events[:0]
for {
event := sdl.PollEvent()
if event == nil {
break
}
handled := false
switch e := event.(type) {
case *sdl.MouseButtonEvent:
if e.Type == sdl.MOUSEBUTTONDOWN {
if wnd.MouseDown != nil {
wnd.MouseDown(int(e.Button), int(e.X), int(e.Y))
handled = true
}
} else if e.Type == sdl.MOUSEBUTTONUP {
if wnd.MouseUp != nil {
wnd.MouseUp(int(e.Button), int(e.X), int(e.Y))
handled = true
}
}
case *sdl.MouseMotionEvent:
if wnd.MouseMove != nil {
wnd.MouseMove(int(e.X), int(e.Y))
handled = true
}
case *sdl.MouseWheelEvent:
if wnd.MouseWheel != nil {
wnd.MouseWheel(int(e.X), int(e.Y))
handled = true
}
case *sdl.KeyboardEvent:
if e.Type == sdl.KEYDOWN {
if wnd.KeyDown != nil {
wnd.KeyDown(int(e.Keysym.Scancode), keyRune(e.Keysym.Scancode), keyName(e.Keysym.Scancode))
handled = true
}
} else if e.Type == sdl.KEYUP {
if wnd.KeyUp != nil {
wnd.KeyUp(int(e.Keysym.Scancode), keyRune(e.Keysym.Scancode), keyName(e.Keysym.Scancode))
handled = true
}
}
case *sdl.TextInputEvent:
if wnd.KeyChar != nil {
rn, _ := utf8.DecodeRune(e.Text[:])
wnd.KeyChar(rn)
handled = true
}
case *sdl.WindowEvent:
if e.WindowID == wnd.WindowID {
if e.Event == sdl.WINDOWEVENT_SIZE_CHANGED {
if wnd.SizeChange != nil {
wnd.SizeChange(int(e.Data1), int(e.Data2))
handled = true
} else {
wnd.canvas.SetBounds(0, 0, int(e.Data1), int(e.Data2))
}
}
}
}
if !handled && wnd.Event != nil {
wnd.Event(event)
handled = true
}
if !handled {
wnd.events = append(wnd.events, event)
}
}
return nil
}
// FinishFrame updates the FPS count and displays the frame
func (wnd *Window) FinishFrame() {
now := time.Now()
wnd.frameTimes[wnd.frameIndex] = now
wnd.frameIndex++
wnd.frameIndex %= len(wnd.frameTimes)
if wnd.frameCount < len(wnd.frameTimes) {
wnd.frameCount++
} else {
diff := now.Sub(wnd.frameTimes[wnd.frameIndex]).Seconds()
wnd.fps = float32(wnd.frameCount-1) / float32(diff)
}
wnd.Window.GLSwap()
}
// MainLoop runs a main loop and calls run on every frame
func (wnd *Window) MainLoop(run func()) {
// main loop
for !wnd.close {
err := wnd.StartFrame()
if err != nil {
time.Sleep(10 * time.Millisecond)
continue
}
for _, event := range wnd.events {
switch e := event.(type) {
case *sdl.WindowEvent:
if e.Event == sdl.WINDOWEVENT_CLOSE {
wnd.close = true
}
case *sdl.QuitEvent:
wnd.close = true
case *sdl.KeyboardEvent:
if e.Type == sdl.KEYDOWN && e.Keysym.Scancode == sdl.SCANCODE_ESCAPE {
wnd.close = true
}
}
}
run()
wnd.FinishFrame()
}
}
// Size returns the current width and height of the window
func (wnd *Window) Size() (int, int) {
w, h := wnd.Window.GetSize()
return int(w), int(h)
}

View file

@ -1,373 +0,0 @@
package canvas
import (
"bytes"
"fmt"
"strings"
)
var imageVS = `
attribute vec2 vertex, texCoord;
uniform vec2 canvasSize;
varying vec2 v_texCoord;
void main() {
v_texCoord = texCoord;
vec2 glp = vertex * 2.0 / canvasSize - 1.0;
gl_Position = vec4(glp.x, -glp.y, 0.0, 1.0);
}`
var imageFS = `
#ifdef GL_ES
precision mediump float;
#endif
varying vec2 v_texCoord;
uniform sampler2D image;
uniform float globalAlpha;
void main() {
vec4 col = texture2D(image, v_texCoord);
col.a *= globalAlpha;
gl_FragColor = col;
}`
var solidVS = `
attribute vec2 vertex;
uniform vec2 canvasSize;
void main() {
vec2 glp = vertex * 2.0 / canvasSize - 1.0;
gl_Position = vec4(glp.x, -glp.y, 0.0, 1.0);
}`
var solidFS = `
#ifdef GL_ES
precision mediump float;
#endif
uniform vec4 color;
uniform float globalAlpha;
void main() {
vec4 col = color;
col.a *= globalAlpha;
gl_FragColor = col;
}`
var linearGradientVS = `
attribute vec2 vertex;
uniform vec2 canvasSize;
varying vec2 v_cp;
void main() {
v_cp = vertex;
vec2 glp = vertex * 2.0 / canvasSize - 1.0;
gl_Position = vec4(glp.x, -glp.y, 0.0, 1.0);
}`
var linearGradientFS = `
#ifdef GL_ES
precision mediump float;
#endif
varying vec2 v_cp;
uniform mat3 invmat;
uniform sampler2D gradient;
uniform vec2 from, dir;
uniform float len;
uniform float globalAlpha;
void main() {
vec3 untf = vec3(v_cp, 1.0) * invmat;
vec2 v = untf.xy - from;
float r = dot(v, dir) / len;
r = clamp(r, 0.0, 1.0);
vec4 col = texture2D(gradient, vec2(r, 0.0));
col.a *= globalAlpha;
gl_FragColor = col;
}`
var radialGradientVS = `
attribute vec2 vertex;
uniform vec2 canvasSize;
varying vec2 v_cp;
void main() {
v_cp = vertex;
vec2 glp = vertex * 2.0 / canvasSize - 1.0;
gl_Position = vec4(glp.x, -glp.y, 0.0, 1.0);
}`
var radialGradientFS = `
#ifdef GL_ES
precision mediump float;
#endif
varying vec2 v_cp;
uniform mat3 invmat;
uniform sampler2D gradient;
uniform vec2 from, to, dir;
uniform float radFrom, radTo;
uniform float len;
uniform float globalAlpha;
bool isNaN(float v) {
return v < 0.0 || 0.0 < v || v == 0.0 ? false : true;
}
void main() {
vec3 untf = vec3(v_cp, 1.0) * invmat;
float o_a = 0.5 * sqrt(
pow(-2.0*from.x*from.x+2.0*from.x*to.x+2.0*from.x*untf.x-2.0*to.x*untf.x-2.0*from.y*from.y+2.0*from.y*to.y+2.0*from.y*untf.y-2.0*to.y*untf.y+2.0*radFrom*radFrom-2.0*radFrom*radTo, 2.0)
-4.0*(from.x*from.x-2.0*from.x*untf.x+untf.x*untf.x+from.y*from.y-2.0*from.y*untf.y+untf.y*untf.y-radFrom*radFrom)
*(from.x*from.x-2.0*from.x*to.x+to.x*to.x+from.y*from.y-2.0*from.y*to.y+to.y*to.y-radFrom*radFrom+2.0*radFrom*radTo-radTo*radTo)
);
float o_b = (from.x*from.x-from.x*to.x-from.x*untf.x+to.x*untf.x+from.y*from.y-from.y*to.y-from.y*untf.y+to.y*untf.y-radFrom*radFrom+radFrom*radTo);
float o_c = (from.x*from.x-2.0*from.x*to.x+to.x*to.x+from.y*from.y-2.0*from.y*to.y+to.y*to.y-radFrom*radFrom+2.0*radFrom*radTo-radTo*radTo);
float o1 = (-o_a + o_b) / o_c;
float o2 = (o_a + o_b) / o_c;
if (isNaN(o1) && isNaN(o2)) {
gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);
return;
}
float o = max(o1, o2);
//float r = radFrom + o * (radTo - radFrom);
vec4 col = texture2D(gradient, vec2(o, 0.0));
col.a *= globalAlpha;
gl_FragColor = col;
}`
var imagePatternVS = `
attribute vec2 vertex;
uniform vec2 canvasSize;
varying vec2 v_cp;
void main() {
v_cp = vertex;
vec2 glp = vertex * 2.0 / canvasSize - 1.0;
gl_Position = vec4(glp.x, -glp.y, 0.0, 1.0);
}`
var imagePatternFS = `
#ifdef GL_ES
precision mediump float;
#endif
varying vec2 v_cp;
uniform vec2 imageSize;
uniform mat3 invmat;
uniform sampler2D image;
uniform float globalAlpha;
void main() {
vec3 untf = vec3(v_cp, 1.0) * invmat;
vec4 col = texture2D(image, mod(untf.xy / imageSize, 1.0));
col.a *= globalAlpha;
gl_FragColor = col;
}`
var solidAlphaVS = `
attribute vec2 vertex, alphaTexCoord;
uniform vec2 canvasSize;
varying vec2 v_atc;
void main() {
v_atc = alphaTexCoord;
vec2 glp = vertex * 2.0 / canvasSize - 1.0;
gl_Position = vec4(glp.x, -glp.y, 0.0, 1.0);
}`
var solidAlphaFS = `
#ifdef GL_ES
precision mediump float;
#endif
varying vec2 v_atc;
uniform vec4 color;
uniform sampler2D alphaTex;
uniform float globalAlpha;
void main() {
vec4 col = color;
col.a *= texture2D(alphaTex, v_atc).a * globalAlpha;
gl_FragColor = col;
}`
var linearGradientAlphaVS = `
attribute vec2 vertex, alphaTexCoord;
uniform vec2 canvasSize;
varying vec2 v_cp;
varying vec2 v_atc;
void main() {
v_cp = vertex;
v_atc = alphaTexCoord;
vec2 glp = vertex * 2.0 / canvasSize - 1.0;
gl_Position = vec4(glp.x, -glp.y, 0.0, 1.0);
}`
var linearGradientAlphaFS = `
#ifdef GL_ES
precision mediump float;
#endif
varying vec2 v_cp;
varying vec2 v_atc;
varying vec2 v_texCoord;
uniform mat3 invmat;
uniform sampler2D gradient;
uniform vec2 from, dir;
uniform float len;
uniform sampler2D alphaTex;
uniform float globalAlpha;
void main() {
vec3 untf = vec3(v_cp, 1.0) * invmat;
vec2 v = untf.xy - from;
float r = dot(v, dir) / len;
r = clamp(r, 0.0, 1.0);
vec4 col = texture2D(gradient, vec2(r, 0.0));
col.a *= texture2D(alphaTex, v_atc).a * globalAlpha;
gl_FragColor = col;
}`
var radialGradientAlphaVS = `
attribute vec2 vertex, alphaTexCoord;
uniform vec2 canvasSize;
varying vec2 v_cp;
varying vec2 v_atc;
void main() {
v_cp = vertex;
v_atc = alphaTexCoord;
vec2 glp = vertex * 2.0 / canvasSize - 1.0;
gl_Position = vec4(glp.x, -glp.y, 0.0, 1.0);
}`
var radialGradientAlphaFS = `
#ifdef GL_ES
precision mediump float;
#endif
varying vec2 v_cp;
varying vec2 v_atc;
uniform mat3 invmat;
uniform sampler2D gradient;
uniform vec2 from, to, dir;
uniform float radFrom, radTo;
uniform float len;
uniform sampler2D alphaTex;
uniform float globalAlpha;
bool isNaN(float v) {
return v < 0.0 || 0.0 < v || v == 0.0 ? false : true;
}
void main() {
vec3 untf = vec3(v_cp, 1.0) * invmat;
float o_a = 0.5 * sqrt(
pow(-2.0*from.x*from.x+2.0*from.x*to.x+2.0*from.x*untf.x-2.0*to.x*untf.x-2.0*from.y*from.y+2.0*from.y*to.y+2.0*from.y*untf.y-2.0*to.y*untf.y+2.0*radFrom*radFrom-2.0*radFrom*radTo, 2.0)
-4.0*(from.x*from.x-2.0*from.x*untf.x+untf.x*untf.x+from.y*from.y-2.0*from.y*untf.y+untf.y*untf.y-radFrom*radFrom)
*(from.x*from.x-2.0*from.x*to.x+to.x*to.x+from.y*from.y-2.0*from.y*to.y+to.y*to.y-radFrom*radFrom+2.0*radFrom*radTo-radTo*radTo)
);
float o_b = (from.x*from.x-from.x*to.x-from.x*untf.x+to.x*untf.x+from.y*from.y-from.y*to.y-from.y*untf.y+to.y*untf.y-radFrom*radFrom+radFrom*radTo);
float o_c = (from.x*from.x-2.0*from.x*to.x+to.x*to.x+from.y*from.y-2.0*from.y*to.y+to.y*to.y-radFrom*radFrom+2.0*radFrom*radTo-radTo*radTo);
float o1 = (-o_a + o_b) / o_c;
float o2 = (o_a + o_b) / o_c;
if (isNaN(o1) && isNaN(o2)) {
gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);
return;
}
float o = max(o1, o2);
float r = radFrom + o * (radTo - radFrom);
vec4 col = texture2D(gradient, vec2(o, 0.0));
col.a *= texture2D(alphaTex, v_atc).a * globalAlpha;
gl_FragColor = col;
}`
var imagePatternAlphaVS = `
attribute vec2 vertex, alphaTexCoord;
uniform vec2 canvasSize;
varying vec2 v_cp;
varying vec2 v_atc;
void main() {
v_cp = vertex;
v_atc = alphaTexCoord;
vec2 glp = vertex * 2.0 / canvasSize - 1.0;
gl_Position = vec4(glp.x, -glp.y, 0.0, 1.0);
}`
var imagePatternAlphaFS = `
#ifdef GL_ES
precision mediump float;
#endif
varying vec2 v_cp;
varying vec2 v_atc;
uniform vec2 imageSize;
uniform mat3 invmat;
uniform sampler2D image;
uniform sampler2D alphaTex;
uniform float globalAlpha;
void main() {
vec3 untf = vec3(v_cp, 1.0) * invmat;
vec4 col = texture2D(image, mod(untf.xy / imageSize, 1.0));
col.a *= texture2D(alphaTex, v_atc).a * globalAlpha;
gl_FragColor = col;
}`
var gaussian15VS = `
attribute vec2 vertex, texCoord;
uniform vec2 canvasSize;
varying vec2 v_texCoord;
void main() {
v_texCoord = texCoord;
vec2 glp = vertex * 2.0 / canvasSize - 1.0;
gl_Position = vec4(glp.x, -glp.y, 0.0, 1.0);
}`
var gaussian15FS = `
#ifdef GL_ES
precision mediump float;
#endif
varying vec2 v_texCoord;
uniform vec2 kernelScale;
uniform sampler2D image;
uniform float kernel[15];
void main() {
vec4 color = vec4(0.0, 0.0, 0.0, 0.0);
_SUM_
gl_FragColor = color;
}`
var gaussian63VS = `
attribute vec2 vertex, texCoord;
uniform vec2 canvasSize;
varying vec2 v_texCoord;
void main() {
v_texCoord = texCoord;
vec2 glp = vertex * 2.0 / canvasSize - 1.0;
gl_Position = vec4(glp.x, -glp.y, 0.0, 1.0);
}`
var gaussian63FS = `
#ifdef GL_ES
precision mediump float;
#endif
varying vec2 v_texCoord;
uniform vec2 kernelScale;
uniform sampler2D image;
uniform float kernel[63];
void main() {
vec4 color = vec4(0.0, 0.0, 0.0, 0.0);
_SUM_
gl_FragColor = color;
}`
var gaussian127VS = `
attribute vec2 vertex, texCoord;
uniform vec2 canvasSize;
varying vec2 v_texCoord;
void main() {
v_texCoord = texCoord;
vec2 glp = vertex * 2.0 / canvasSize - 1.0;
gl_Position = vec4(glp.x, -glp.y, 0.0, 1.0);
}`
var gaussian127FS = `
#ifdef GL_ES
precision mediump float;
#endif
varying vec2 v_texCoord;
uniform vec2 kernelScale;
uniform sampler2D image;
uniform float kernel[127];
void main() {
vec4 color = vec4(0.0, 0.0, 0.0, 0.0);
_SUM_
gl_FragColor = color;
}`
func init() {
fstr := "\tcolor += texture2D(image, v_texCoord + vec2(%.1f * kernelScale.x, %.1f * kernelScale.y)) * kernel[%d];\n"
bb := bytes.Buffer{}
for i := 0; i < 127; i++ {
off := float64(i) - 63
fmt.Fprintf(&bb, fstr, off, off, i)
}
gaussian127FS = strings.Replace(gaussian127FS, "_SUM_", bb.String(), -1)
bb.Reset()
for i := 0; i < 63; i++ {
off := float64(i) - 31
fmt.Fprintf(&bb, fstr, off, off, i)
}
gaussian63FS = strings.Replace(gaussian63FS, "_SUM_", bb.String(), -1)
bb.Reset()
for i := 0; i < 15; i++ {
off := float64(i) - 7
fmt.Fprintf(&bb, fstr, off, off, i)
}
gaussian15FS = strings.Replace(gaussian15FS, "_SUM_", bb.String(), -1)
}

View file

@ -3,215 +3,41 @@ package canvas
import (
"image"
"math"
"unsafe"
"git.mstar.dev/mstar/canvas/backend/backendbase"
)
func (cv *Canvas) drawShadow(tris []float32) {
if len(tris) == 0 || cv.state.shadowColor.a == 0 {
func (cv *Canvas) drawShadow(pts []backendbase.Vec, mask *image.Alpha, canOverlap bool) {
if cv.state.shadowColor.A == 0 {
return
}
if cv.state.shadowOffsetX == 0 && cv.state.shadowOffsetY == 0 {
return
}
if cv.state.shadowBlur > 0 {
offscr1.alpha = true
cv.enableTextureRenderTarget(&offscr1)
gli.ClearColor(0, 0, 0, 0)
gli.Clear(gl_COLOR_BUFFER_BIT | gl_STENCIL_BUFFER_BIT)
if cv.shadowBuf == nil || cap(cv.shadowBuf) < len(pts) {
cv.shadowBuf = make([]backendbase.Vec, 0, len(pts)+1000)
}
cv.shadowBuf = cv.shadowBuf[:0]
for _, pt := range pts {
cv.shadowBuf = append(cv.shadowBuf, backendbase.Vec{
pt[0] + cv.state.shadowOffsetX,
pt[1] + cv.state.shadowOffsetY,
})
}
ox, oy := float32(cv.state.shadowOffsetX), float32(cv.state.shadowOffsetY)
count := len(tris)
for i := 12; i < count; i += 2 {
tris[i] += ox
tris[i+1] += oy
}
gli.BindBuffer(gl_ARRAY_BUFFER, shadowBuf)
gli.BufferData(gl_ARRAY_BUFFER, len(tris)*4, unsafe.Pointer(&tris[0]), gl_STREAM_DRAW)
gli.ColorMask(false, false, false, false)
gli.StencilFunc(gl_ALWAYS, 1, 0xFF)
gli.StencilOp(gl_REPLACE, gl_REPLACE, gl_REPLACE)
gli.StencilMask(0x01)
gli.UseProgram(sr.id)
gli.Uniform4f(sr.color, 0, 0, 0, 0)
gli.Uniform2f(sr.canvasSize, float32(cv.fw), float32(cv.fh))
gli.EnableVertexAttribArray(sr.vertex)
gli.VertexAttribPointer(sr.vertex, 2, gl_FLOAT, false, 0, 0)
gli.DrawArrays(gl_TRIANGLES, 6, int32(len(tris)/2-6))
gli.DisableVertexAttribArray(sr.vertex)
gli.ColorMask(true, true, true, true)
gli.StencilFunc(gl_EQUAL, 1, 0xFF)
var style drawStyle
style.color = cv.state.shadowColor
vertex := cv.useShader(&style)
gli.EnableVertexAttribArray(vertex)
gli.VertexAttribPointer(vertex, 2, gl_FLOAT, false, 0, 0)
gli.DrawArrays(gl_TRIANGLES, 0, 6)
gli.DisableVertexAttribArray(vertex)
gli.StencilOp(gl_KEEP, gl_KEEP, gl_KEEP)
gli.StencilFunc(gl_ALWAYS, 0, 0xFF)
gli.Clear(gl_STENCIL_BUFFER_BIT)
gli.StencilMask(0xFF)
if cv.state.shadowBlur > 0 {
cv.drawBlurredShadow()
}
}
func (cv *Canvas) drawTextShadow(offset image.Point, strWidth, strHeight int, x, y float64) {
x += cv.state.shadowOffsetX
y += cv.state.shadowOffsetY
if cv.state.shadowBlur > 0 {
offscr1.alpha = true
cv.enableTextureRenderTarget(&offscr1)
gli.ClearColor(0, 0, 0, 0)
gli.Clear(gl_COLOR_BUFFER_BIT | gl_STENCIL_BUFFER_BIT)
}
gli.StencilFunc(gl_EQUAL, 0, 0xFF)
gli.BindBuffer(gl_ARRAY_BUFFER, buf)
var style drawStyle
style.color = cv.state.shadowColor
vertex, alphaTexCoord := cv.useAlphaShader(&style, 1)
gli.EnableVertexAttribArray(vertex)
gli.EnableVertexAttribArray(alphaTexCoord)
p0 := cv.tf(vec{float64(offset.X) + x, float64(offset.Y) + y})
p1 := cv.tf(vec{float64(offset.X) + x, float64(offset.Y+strHeight) + y})
p2 := cv.tf(vec{float64(offset.X+strWidth) + x, float64(offset.Y+strHeight) + y})
p3 := cv.tf(vec{float64(offset.X+strWidth) + x, float64(offset.Y) + y})
tw := float64(strWidth) / alphaTexSize
th := float64(strHeight) / alphaTexSize
data := [16]float32{float32(p0[0]), float32(p0[1]), float32(p1[0]), float32(p1[1]), float32(p2[0]), float32(p2[1]), float32(p3[0]), float32(p3[1]),
0, 1, 0, float32(1 - th), float32(tw), float32(1 - th), float32(tw), 1}
gli.BufferData(gl_ARRAY_BUFFER, len(data)*4, unsafe.Pointer(&data[0]), gl_STREAM_DRAW)
gli.VertexAttribPointer(vertex, 2, gl_FLOAT, false, 0, 0)
gli.VertexAttribPointer(alphaTexCoord, 2, gl_FLOAT, false, 0, 8*4)
gli.DrawArrays(gl_TRIANGLE_FAN, 0, 4)
gli.DisableVertexAttribArray(vertex)
gli.DisableVertexAttribArray(alphaTexCoord)
gli.ActiveTexture(gl_TEXTURE0)
gli.StencilFunc(gl_ALWAYS, 0, 0xFF)
if cv.state.shadowBlur > 0 {
cv.drawBlurredShadow()
}
}
func (cv *Canvas) drawBlurredShadow() {
gli.BlendFunc(gl_ONE, gl_ONE_MINUS_SRC_ALPHA)
var kernel []float32
var kernelBuf [255]float32
var gs *gaussianShader
if cv.state.shadowBlur < 3 {
gs = gauss15r
kernel = kernelBuf[:15]
} else if cv.state.shadowBlur < 12 {
gs = gauss63r
kernel = kernelBuf[:63]
color := cv.state.shadowColor
color.A = uint8(math.Round(((float64(color.A) / 255.0) * cv.state.globalAlpha) * 255.0))
style := backendbase.FillStyle{Color: color, Blur: cv.state.shadowBlur}
if mask != nil {
if len(cv.shadowBuf) != 4 {
panic("invalid number of points to fill with mask, must be 4")
}
var quad [4]backendbase.Vec
copy(quad[:], cv.shadowBuf)
cv.b.FillImageMask(&style, mask, quad)
} else {
gs = gauss127r
kernel = kernelBuf[:127]
}
gaussianKernel(cv.state.shadowBlur, kernel)
offscr2.alpha = true
cv.enableTextureRenderTarget(&offscr2)
gli.ClearColor(0, 0, 0, 0)
gli.Clear(gl_COLOR_BUFFER_BIT | gl_STENCIL_BUFFER_BIT)
gli.StencilFunc(gl_EQUAL, 0, 0xFF)
gli.BindBuffer(gl_ARRAY_BUFFER, shadowBuf)
data := [16]float32{0, 0, 0, float32(cv.h), float32(cv.w), float32(cv.h), float32(cv.w), 0, 0, 0, 0, 1, 1, 1, 1, 0}
gli.BufferData(gl_ARRAY_BUFFER, len(data)*4, unsafe.Pointer(&data[0]), gl_STREAM_DRAW)
gli.ActiveTexture(gl_TEXTURE0)
gli.BindTexture(gl_TEXTURE_2D, offscr1.tex)
gli.UseProgram(gs.id)
gli.Uniform1i(gs.image, 0)
gli.Uniform2f(gs.canvasSize, float32(cv.fw), float32(cv.fh))
gli.Uniform2f(gs.kernelScale, 1.0/float32(cv.fw), 0.0)
gli.Uniform1fv(gs.kernel, int32(len(kernel)), &kernel[0])
gli.VertexAttribPointer(gs.vertex, 2, gl_FLOAT, false, 0, 0)
gli.VertexAttribPointer(gs.texCoord, 2, gl_FLOAT, false, 0, 8*4)
gli.EnableVertexAttribArray(gs.vertex)
gli.EnableVertexAttribArray(gs.texCoord)
gli.DrawArrays(gl_TRIANGLE_FAN, 0, 4)
gli.DisableVertexAttribArray(gs.vertex)
gli.DisableVertexAttribArray(gs.texCoord)
gli.StencilFunc(gl_ALWAYS, 0, 0xFF)
cv.disableTextureRenderTarget()
gli.StencilFunc(gl_EQUAL, 0, 0xFF)
gli.BindBuffer(gl_ARRAY_BUFFER, shadowBuf)
data = [16]float32{0, 0, 0, float32(cv.h), float32(cv.w), float32(cv.h), float32(cv.w), 0, 0, 0, 0, 1, 1, 1, 1, 0}
gli.BufferData(gl_ARRAY_BUFFER, len(data)*4, unsafe.Pointer(&data[0]), gl_STREAM_DRAW)
gli.ActiveTexture(gl_TEXTURE0)
gli.BindTexture(gl_TEXTURE_2D, offscr2.tex)
gli.UseProgram(gs.id)
gli.Uniform1i(gs.image, 0)
gli.Uniform2f(gs.canvasSize, float32(cv.fw), float32(cv.fh))
gli.Uniform2f(gs.kernelScale, 0.0, 1.0/float32(cv.fh))
gli.Uniform1fv(gs.kernel, int32(len(kernel)), &kernel[0])
gli.VertexAttribPointer(gs.vertex, 2, gl_FLOAT, false, 0, 0)
gli.VertexAttribPointer(gs.texCoord, 2, gl_FLOAT, false, 0, 8*4)
gli.EnableVertexAttribArray(gs.vertex)
gli.EnableVertexAttribArray(gs.texCoord)
gli.DrawArrays(gl_TRIANGLE_FAN, 0, 4)
gli.DisableVertexAttribArray(gs.vertex)
gli.DisableVertexAttribArray(gs.texCoord)
gli.StencilFunc(gl_ALWAYS, 0, 0xFF)
gli.BlendFunc(gl_SRC_ALPHA, gl_ONE_MINUS_SRC_ALPHA)
}
func gaussianKernel(stddev float64, target []float32) {
stddevSqr := stddev * stddev
center := float64(len(target) / 2)
factor := 1.0 / math.Sqrt(2*math.Pi*stddevSqr)
for i := range target {
x := float64(i) - center
target[i] = float32(factor * math.Pow(math.E, -x*x/(2*stddevSqr)))
}
// normalizeKernel(target)
}
func normalizeKernel(kernel []float32) {
var sum float32
for _, v := range kernel {
sum += v
}
factor := 1.0 / sum
for i := range kernel {
kernel[i] *= factor
cv.b.Fill(&style, cv.shadowBuf, backendbase.MatIdentity, canOverlap)
}
}

BIN
testdata/Alpha.png vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
testdata/Convex.png vendored Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 392 B

BIN
testdata/ConvexSelfIntersecting.png vendored Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 415 B

BIN
testdata/Curves.png vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 641 B

After

Width:  |  Height:  |  Size: 651 B

BIN
testdata/FillHammer.png vendored Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 336 B

BIN
testdata/Gradient.png vendored Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
testdata/Image.png vendored Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

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