Compare commits

...

101 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
114 changed files with 3798 additions and 12268 deletions

4
.gitignore vendored
View file

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

View file

@ -1,19 +1,29 @@
# Go canvas [![GoDoc](https://godoc.org/github.com/tfriedel6/canvas?status.svg)](https://godoc.org/github.com/tfriedel6/canvas)
Canvas is a pure Go library based on OpenGL 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 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.
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

View file

@ -17,13 +17,13 @@ type Backend interface {
LoadLinearGradient(data Gradient) LinearGradient
LoadRadialGradient(data Gradient) RadialGradient
Clear(pts [4][2]float64)
Fill(style *FillStyle, pts [][2]float64)
DrawImage(dimg Image, sx, sy, sw, sh float64, pts [4][2]float64, alpha float64)
FillImageMask(style *FillStyle, mask *image.Alpha, pts [][2]float64) // pts must have four points
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 [][2]float64)
Clip(pts []Vec)
GetImageData(x, y, w, h int) *image.RGBA
PutImageData(img *image.RGBA, x, y int)

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

@ -1,67 +0,0 @@
package goglbackend
import (
"unsafe"
"github.com/tfriedel6/canvas/backend/gogl/gl"
)
func (b *GoGLBackend) ClearClip() {
b.activate()
gl.StencilMask(0xFF)
gl.Clear(gl.STENCIL_BUFFER_BIT)
}
func (b *GoGLBackend) Clip(pts [][2]float64) {
b.activate()
b.ptsBuf = b.ptsBuf[:0]
b.ptsBuf = append(b.ptsBuf,
0, 0,
0, float32(b.fh),
float32(b.fw), float32(b.fh),
float32(b.fw), 0)
for _, pt := range pts {
b.ptsBuf = append(b.ptsBuf, float32(pt[0]), float32(pt[1]))
}
mode := uint32(gl.TRIANGLES)
if len(pts) == 4 {
mode = gl.TRIANGLE_FAN
}
gl.BindBuffer(gl.ARRAY_BUFFER, b.buf)
gl.BufferData(gl.ARRAY_BUFFER, len(b.ptsBuf)*4, unsafe.Pointer(&b.ptsBuf[0]), gl.STREAM_DRAW)
gl.VertexAttribPointer(b.sr.Vertex, 2, gl.FLOAT, false, 0, nil)
gl.UseProgram(b.sr.ID)
gl.Uniform4f(b.sr.Color, 1, 1, 1, 1)
gl.Uniform2f(b.sr.CanvasSize, float32(b.fw), float32(b.fh))
gl.Uniform1f(b.sr.GlobalAlpha, 1)
gl.EnableVertexAttribArray(b.sr.Vertex)
gl.ColorMask(false, false, false, false)
gl.StencilMask(0x04)
gl.StencilFunc(gl.ALWAYS, 4, 0x04)
gl.StencilOp(gl.REPLACE, gl.REPLACE, gl.REPLACE)
gl.DrawArrays(mode, 4, int32(len(pts)))
gl.StencilMask(0x02)
gl.StencilFunc(gl.EQUAL, 0, 0x06)
gl.StencilOp(gl.KEEP, gl.INVERT, gl.INVERT)
gl.DrawArrays(gl.TRIANGLE_FAN, 0, 4)
gl.StencilMask(0x04)
gl.StencilFunc(gl.ALWAYS, 0, 0x04)
gl.StencilOp(gl.ZERO, gl.ZERO, gl.ZERO)
gl.DrawArrays(gl.TRIANGLE_FAN, 0, 4)
gl.DisableVertexAttribArray(b.sr.Vertex)
gl.ColorMask(true, true, true, true)
gl.StencilOp(gl.KEEP, gl.KEEP, gl.KEEP)
gl.StencilMask(0xFF)
gl.StencilFunc(gl.EQUAL, 0, 0xFF)
}

View file

@ -1,308 +0,0 @@
package goglbackend
import (
"image"
"math"
"unsafe"
"github.com/tfriedel6/canvas/backend/backendbase"
"github.com/tfriedel6/canvas/backend/gogl/gl"
)
func (b *GoGLBackend) Clear(pts [4][2]float64) {
b.activate()
// first check if the four points are aligned to form a nice rectangle, which can be more easily
// cleared using glScissor and glClear
aligned := pts[0][0] == pts[1][0] && pts[2][0] == pts[3][0] && pts[0][1] == pts[3][1] && pts[1][1] == pts[2][1]
if !aligned {
aligned = pts[0][0] == pts[3][0] && pts[1][0] == pts[2][0] && pts[0][1] == pts[1][1] && pts[2][1] == pts[3][1]
}
if aligned {
minX := math.Floor(math.Min(pts[0][0], pts[2][0]))
maxX := math.Ceil(math.Max(pts[0][0], pts[2][0]))
minY := math.Floor(math.Min(pts[0][1], pts[2][1]))
maxY := math.Ceil(math.Max(pts[0][1], pts[2][1]))
b.clearRect(int(minX), int(minY), int(maxX)-int(minX), int(maxY)-int(minY))
return
}
data := [8]float32{
float32(pts[0][0]), float32(pts[0][1]),
float32(pts[1][0]), float32(pts[1][1]),
float32(pts[2][0]), float32(pts[2][1]),
float32(pts[3][0]), float32(pts[3][1])}
gl.UseProgram(b.sr.ID)
gl.Uniform2f(b.sr.CanvasSize, float32(b.fw), float32(b.fh))
gl.Uniform4f(b.sr.Color, 0, 0, 0, 0)
gl.Uniform1f(b.sr.GlobalAlpha, 1)
gl.Disable(gl.BLEND)
gl.BindBuffer(gl.ARRAY_BUFFER, b.buf)
gl.BufferData(gl.ARRAY_BUFFER, len(data)*4, unsafe.Pointer(&data[0]), gl.STREAM_DRAW)
gl.StencilFunc(gl.EQUAL, 0, 0xFF)
gl.VertexAttribPointer(b.sr.Vertex, 2, gl.FLOAT, false, 0, nil)
gl.EnableVertexAttribArray(b.sr.Vertex)
gl.DrawArrays(gl.TRIANGLE_FAN, 0, 4)
gl.DisableVertexAttribArray(b.sr.Vertex)
gl.StencilFunc(gl.ALWAYS, 0, 0xFF)
gl.Enable(gl.BLEND)
}
func (b *GoGLBackend) clearRect(x, y, w, h int) {
gl.Enable(gl.SCISSOR_TEST)
var box [4]int32
gl.GetIntegerv(gl.SCISSOR_BOX, &box[0])
gl.Scissor(int32(x), int32(b.h-y-h), int32(w), int32(h))
gl.ClearColor(0, 0, 0, 0)
gl.Clear(gl.COLOR_BUFFER_BIT)
gl.Scissor(box[0], box[1], box[2], box[3])
gl.Disable(gl.SCISSOR_TEST)
}
func (b *GoGLBackend) Fill(style *backendbase.FillStyle, pts [][2]float64) {
b.activate()
if style.Blur > 0 {
b.offscr1.alpha = true
b.enableTextureRenderTarget(&b.offscr1)
gl.ClearColor(0, 0, 0, 0)
gl.Clear(gl.COLOR_BUFFER_BIT | gl.STENCIL_BUFFER_BIT)
}
b.ptsBuf = b.ptsBuf[:0]
b.ptsBuf = append(b.ptsBuf,
0, 0,
0, float32(b.fh),
float32(b.fw), float32(b.fh),
float32(b.fw), 0)
for _, pt := range pts {
b.ptsBuf = append(b.ptsBuf, float32(pt[0]), float32(pt[1]))
}
mode := uint32(gl.TRIANGLES)
if len(pts) == 4 {
mode = gl.TRIANGLE_FAN
}
gl.BindBuffer(gl.ARRAY_BUFFER, b.buf)
gl.BufferData(gl.ARRAY_BUFFER, len(b.ptsBuf)*4, unsafe.Pointer(&b.ptsBuf[0]), gl.STREAM_DRAW)
if style.Color.A >= 255 {
vertex := b.useShader(style)
gl.StencilFunc(gl.EQUAL, 0, 0xFF)
gl.EnableVertexAttribArray(vertex)
gl.VertexAttribPointer(vertex, 2, gl.FLOAT, false, 0, nil)
gl.DrawArrays(mode, 4, int32(len(pts)))
gl.DisableVertexAttribArray(vertex)
gl.StencilFunc(gl.ALWAYS, 0, 0xFF)
} else {
gl.ColorMask(false, false, false, false)
gl.StencilFunc(gl.ALWAYS, 1, 0xFF)
gl.StencilOp(gl.REPLACE, gl.REPLACE, gl.REPLACE)
gl.StencilMask(0x01)
gl.UseProgram(b.sr.ID)
gl.Uniform4f(b.sr.Color, 0, 0, 0, 0)
gl.Uniform2f(b.sr.CanvasSize, float32(b.fw), float32(b.fh))
gl.Uniform1f(b.sr.GlobalAlpha, 1)
gl.EnableVertexAttribArray(b.sr.Vertex)
gl.VertexAttribPointer(b.sr.Vertex, 2, gl.FLOAT, false, 0, nil)
gl.DrawArrays(mode, 4, int32(len(pts)))
gl.DisableVertexAttribArray(b.sr.Vertex)
gl.ColorMask(true, true, true, true)
gl.StencilFunc(gl.EQUAL, 1, 0xFF)
vertex := b.useShader(style)
gl.EnableVertexAttribArray(vertex)
gl.VertexAttribPointer(vertex, 2, gl.FLOAT, false, 0, nil)
b.ptsBuf = append(b.ptsBuf[:0], 0, 0, float32(b.fw), 0, float32(b.fw), float32(b.fh), 0, float32(b.fh))
gl.DrawArrays(gl.TRIANGLE_FAN, 0, 4)
gl.DisableVertexAttribArray(vertex)
gl.StencilOp(gl.KEEP, gl.KEEP, gl.KEEP)
gl.StencilFunc(gl.ALWAYS, 0, 0xFF)
gl.Clear(gl.STENCIL_BUFFER_BIT)
gl.StencilMask(0xFF)
}
if style.Blur > 0 {
b.drawBlurred(style.Blur)
}
}
func (b *GoGLBackend) FillImageMask(style *backendbase.FillStyle, mask *image.Alpha, pts [][2]float64) {
b.activate()
w, h := mask.Rect.Dx(), mask.Rect.Dy()
gl.ActiveTexture(gl.TEXTURE1)
gl.BindTexture(gl.TEXTURE_2D, b.alphaTex)
for y := 0; y < h; y++ {
off := y * mask.Stride
gl.TexSubImage2D(gl.TEXTURE_2D, 0, 0, int32(alphaTexSize-1-y), int32(w), 1, gl.ALPHA, gl.UNSIGNED_BYTE, gl.Ptr(&mask.Pix[off]))
}
if style.Blur > 0 {
b.offscr1.alpha = true
b.enableTextureRenderTarget(&b.offscr1)
gl.ClearColor(0, 0, 0, 0)
gl.Clear(gl.COLOR_BUFFER_BIT | gl.STENCIL_BUFFER_BIT)
}
gl.StencilFunc(gl.EQUAL, 0, 0xFF)
gl.BindBuffer(gl.ARRAY_BUFFER, b.buf)
vertex, alphaTexCoord := b.useAlphaShader(style, 1)
gl.EnableVertexAttribArray(vertex)
gl.EnableVertexAttribArray(alphaTexCoord)
tw := float64(w) / alphaTexSize
th := float64(h) / alphaTexSize
var buf [16]float32
data := buf[:0]
for _, pt := range pts {
data = append(data, float32(pt[0]), float32(pt[1]))
}
data = append(data, 0, 1, 0, float32(1-th), float32(tw), float32(1-th), float32(tw), 1)
gl.BufferData(gl.ARRAY_BUFFER, len(data)*4, unsafe.Pointer(&data[0]), gl.STREAM_DRAW)
gl.VertexAttribPointer(vertex, 2, gl.FLOAT, false, 0, nil)
gl.VertexAttribPointer(alphaTexCoord, 2, gl.FLOAT, false, 0, gl.PtrOffset(8*4))
gl.DrawArrays(gl.TRIANGLE_FAN, 0, 4)
gl.DisableVertexAttribArray(vertex)
gl.DisableVertexAttribArray(alphaTexCoord)
gl.ActiveTexture(gl.TEXTURE1)
gl.BindTexture(gl.TEXTURE_2D, b.alphaTex)
gl.StencilFunc(gl.ALWAYS, 0, 0xFF)
for y := 0; y < h; y++ {
gl.TexSubImage2D(gl.TEXTURE_2D, 0, 0, int32(alphaTexSize-1-y), int32(w), 1, gl.ALPHA, gl.UNSIGNED_BYTE, gl.Ptr(&zeroes[0]))
}
gl.ActiveTexture(gl.TEXTURE0)
if style.Blur > 0 {
b.drawBlurred(style.Blur)
}
}
func (b *GoGLBackend) drawBlurred(blur float64) {
gl.BlendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA)
var kernel []float32
var kernelBuf [255]float32
var gs *gaussianShader
if blur < 3 {
gs = &b.gauss15r
kernel = kernelBuf[:15]
} else if blur < 12 {
gs = &b.gauss63r
kernel = kernelBuf[:63]
} else {
gs = &b.gauss127r
kernel = kernelBuf[:127]
}
gaussianKernel(blur, kernel)
b.offscr2.alpha = true
b.enableTextureRenderTarget(&b.offscr2)
gl.ClearColor(0, 0, 0, 0)
gl.Clear(gl.COLOR_BUFFER_BIT | gl.STENCIL_BUFFER_BIT)
gl.StencilFunc(gl.EQUAL, 0, 0xFF)
gl.BindBuffer(gl.ARRAY_BUFFER, b.shadowBuf)
data := [16]float32{0, 0, 0, float32(b.h), float32(b.w), float32(b.h), float32(b.w), 0, 0, 0, 0, 1, 1, 1, 1, 0}
gl.BufferData(gl.ARRAY_BUFFER, len(data)*4, unsafe.Pointer(&data[0]), gl.STREAM_DRAW)
gl.ActiveTexture(gl.TEXTURE0)
gl.BindTexture(gl.TEXTURE_2D, b.offscr1.tex)
gl.UseProgram(gs.ID)
gl.Uniform1i(gs.Image, 0)
gl.Uniform2f(gs.CanvasSize, float32(b.fw), float32(b.fh))
gl.Uniform2f(gs.KernelScale, 1.0/float32(b.fw), 0.0)
gl.Uniform1fv(gs.Kernel, int32(len(kernel)), &kernel[0])
gl.VertexAttribPointer(gs.Vertex, 2, gl.FLOAT, false, 0, nil)
gl.VertexAttribPointer(gs.TexCoord, 2, gl.FLOAT, false, 0, gl.PtrOffset(8*4))
gl.EnableVertexAttribArray(gs.Vertex)
gl.EnableVertexAttribArray(gs.TexCoord)
gl.DrawArrays(gl.TRIANGLE_FAN, 0, 4)
gl.DisableVertexAttribArray(gs.Vertex)
gl.DisableVertexAttribArray(gs.TexCoord)
gl.StencilFunc(gl.ALWAYS, 0, 0xFF)
b.disableTextureRenderTarget()
gl.StencilFunc(gl.EQUAL, 0, 0xFF)
gl.BindBuffer(gl.ARRAY_BUFFER, b.shadowBuf)
data = [16]float32{0, 0, 0, float32(b.h), float32(b.w), float32(b.h), float32(b.w), 0, 0, 0, 0, 1, 1, 1, 1, 0}
gl.BufferData(gl.ARRAY_BUFFER, len(data)*4, unsafe.Pointer(&data[0]), gl.STREAM_DRAW)
gl.ActiveTexture(gl.TEXTURE0)
gl.BindTexture(gl.TEXTURE_2D, b.offscr2.tex)
gl.UseProgram(gs.ID)
gl.Uniform1i(gs.Image, 0)
gl.Uniform2f(gs.CanvasSize, float32(b.fw), float32(b.fh))
gl.Uniform2f(gs.KernelScale, 0.0, 1.0/float32(b.fh))
gl.Uniform1fv(gs.Kernel, int32(len(kernel)), &kernel[0])
gl.VertexAttribPointer(gs.Vertex, 2, gl.FLOAT, false, 0, nil)
gl.VertexAttribPointer(gs.TexCoord, 2, gl.FLOAT, false, 0, gl.PtrOffset(8*4))
gl.EnableVertexAttribArray(gs.Vertex)
gl.EnableVertexAttribArray(gs.TexCoord)
gl.DrawArrays(gl.TRIANGLE_FAN, 0, 4)
gl.DisableVertexAttribArray(gs.Vertex)
gl.DisableVertexAttribArray(gs.TexCoord)
gl.StencilFunc(gl.ALWAYS, 0, 0xFF)
gl.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
}
}

View file

@ -1,109 +0,0 @@
// Code generated by glow (https://github.com/go-gl/glow). DO NOT EDIT.
package gl
import (
"fmt"
"reflect"
"strings"
"unsafe"
)
// #include <stdlib.h>
import "C"
// Ptr takes a slice or pointer (to a singular scalar value or the first
// element of an array or slice) and returns its GL-compatible address.
//
// For example:
//
// var data []uint8
// ...
// gl.TexImage2D(gl.TEXTURE_2D, ..., gl.UNSIGNED_BYTE, gl.Ptr(&data[0]))
func 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
}
// PtrOffset takes a pointer offset and returns a GL-compatible pointer.
// Useful for functions such as glVertexAttribPointer that take pointer
// parameters indicating an offset rather than an absolute memory address.
func PtrOffset(offset int) unsafe.Pointer {
return unsafe.Pointer(uintptr(offset))
}
// Str takes a null-terminated Go string and returns its GL-compatible address.
// This function reaches into Go string storage in an unsafe way so the caller
// must ensure the string is not garbage collected.
func Str(str string) *uint8 {
if !strings.HasSuffix(str, "\x00") {
panic("str argument missing null terminator: " + str)
}
header := (*reflect.StringHeader)(unsafe.Pointer(&str))
return (*uint8)(unsafe.Pointer(header.Data))
}
// GoStr takes a null-terminated string returned by OpenGL and constructs a
// corresponding Go string.
func GoStr(cstr *uint8) string {
return C.GoString((*C.char)(unsafe.Pointer(cstr)))
}
// Strs takes a list of Go strings (with or without null-termination) and
// returns their C counterpart.
//
// The returned free function must be called once you are done using the strings
// in order to free the memory.
//
// If no strings are provided as a parameter this function will panic.
func Strs(strs ...string) (cstrs **uint8, free func()) {
if len(strs) == 0 {
panic("Strs: expected at least 1 string")
}
// Allocate a contiguous array large enough to hold all the strings' contents.
n := 0
for i := range strs {
n += len(strs[i])
}
data := C.malloc(C.size_t(n))
// Copy all the strings into data.
dataSlice := *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{
Data: uintptr(data),
Len: n,
Cap: n,
}))
css := make([]*uint8, len(strs)) // Populated with pointers to each string.
offset := 0
for i := range strs {
copy(dataSlice[offset:offset+len(strs[i])], strs[i][:]) // Copy strs[i] into proper data location.
css[i] = (*uint8)(unsafe.Pointer(&dataSlice[offset])) // Set a pointer to it.
offset += len(strs[i])
}
return (**uint8)(&css[0]), func() { C.free(data) }
}

View file

@ -1,282 +0,0 @@
#ifndef __khrplatform_h_
#define __khrplatform_h_
/*
** Copyright (c) 2008-2009 The Khronos Group Inc.
**
** Permission is hereby granted, free of charge, to any person obtaining a
** copy of this software and/or associated documentation files (the
** "Materials"), to deal in the Materials without restriction, including
** without limitation the rights to use, copy, modify, merge, publish,
** distribute, sublicense, and/or sell copies of the Materials, and to
** permit persons to whom the Materials are furnished to do so, subject to
** the following conditions:
**
** The above copyright notice and this permission notice shall be included
** in all copies or substantial portions of the Materials.
**
** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
*/
/* Khronos platform-specific types and definitions.
*
* $Revision: 23298 $ on $Date: 2013-09-30 17:07:13 -0700 (Mon, 30 Sep 2013) $
*
* Adopters may modify this file to suit their platform. Adopters are
* encouraged to submit platform specific modifications to the Khronos
* group so that they can be included in future versions of this file.
* Please submit changes by sending them to the public Khronos Bugzilla
* (http://khronos.org/bugzilla) by filing a bug against product
* "Khronos (general)" component "Registry".
*
* A predefined template which fills in some of the bug fields can be
* reached using http://tinyurl.com/khrplatform-h-bugreport, but you
* must create a Bugzilla login first.
*
*
* See the Implementer's Guidelines for information about where this file
* should be located on your system and for more details of its use:
* http://www.khronos.org/registry/implementers_guide.pdf
*
* This file should be included as
* #include <KHR/khrplatform.h>
* by Khronos client API header files that use its types and defines.
*
* The types in khrplatform.h should only be used to define API-specific types.
*
* Types defined in khrplatform.h:
* khronos_int8_t signed 8 bit
* khronos_uint8_t unsigned 8 bit
* khronos_int16_t signed 16 bit
* khronos_uint16_t unsigned 16 bit
* khronos_int32_t signed 32 bit
* khronos_uint32_t unsigned 32 bit
* khronos_int64_t signed 64 bit
* khronos_uint64_t unsigned 64 bit
* khronos_intptr_t signed same number of bits as a pointer
* khronos_uintptr_t unsigned same number of bits as a pointer
* khronos_ssize_t signed size
* khronos_usize_t unsigned size
* khronos_float_t signed 32 bit floating point
* khronos_time_ns_t unsigned 64 bit time in nanoseconds
* khronos_utime_nanoseconds_t unsigned time interval or absolute time in
* nanoseconds
* khronos_stime_nanoseconds_t signed time interval in nanoseconds
* khronos_boolean_enum_t enumerated boolean type. This should
* only be used as a base type when a client API's boolean type is
* an enum. Client APIs which use an integer or other type for
* booleans cannot use this as the base type for their boolean.
*
* Tokens defined in khrplatform.h:
*
* KHRONOS_FALSE, KHRONOS_TRUE Enumerated boolean false/true values.
*
* KHRONOS_SUPPORT_INT64 is 1 if 64 bit integers are supported; otherwise 0.
* KHRONOS_SUPPORT_FLOAT is 1 if floats are supported; otherwise 0.
*
* Calling convention macros defined in this file:
* KHRONOS_APICALL
* KHRONOS_APIENTRY
* KHRONOS_APIATTRIBUTES
*
* These may be used in function prototypes as:
*
* KHRONOS_APICALL void KHRONOS_APIENTRY funcname(
* int arg1,
* int arg2) KHRONOS_APIATTRIBUTES;
*/
/*-------------------------------------------------------------------------
* Definition of KHRONOS_APICALL
*-------------------------------------------------------------------------
* This precedes the return type of the function in the function prototype.
*/
#if defined(_WIN32) && !defined(__SCITECH_SNAP__)
# define KHRONOS_APICALL __declspec(dllimport)
#elif defined (__SYMBIAN32__)
# define KHRONOS_APICALL IMPORT_C
#else
# define KHRONOS_APICALL
#endif
/*-------------------------------------------------------------------------
* Definition of KHRONOS_APIENTRY
*-------------------------------------------------------------------------
* This follows the return type of the function and precedes the function
* name in the function prototype.
*/
#if defined(_WIN32) && !defined(_WIN32_WCE) && !defined(__SCITECH_SNAP__)
/* Win32 but not WinCE */
# define KHRONOS_APIENTRY __stdcall
#else
# define KHRONOS_APIENTRY
#endif
/*-------------------------------------------------------------------------
* Definition of KHRONOS_APIATTRIBUTES
*-------------------------------------------------------------------------
* This follows the closing parenthesis of the function prototype arguments.
*/
#if defined (__ARMCC_2__)
#define KHRONOS_APIATTRIBUTES __softfp
#else
#define KHRONOS_APIATTRIBUTES
#endif
/*-------------------------------------------------------------------------
* basic type definitions
*-----------------------------------------------------------------------*/
#if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__GNUC__) || defined(__SCO__) || defined(__USLC__)
/*
* Using <stdint.h>
*/
#include <stdint.h>
typedef int32_t khronos_int32_t;
typedef uint32_t khronos_uint32_t;
typedef int64_t khronos_int64_t;
typedef uint64_t khronos_uint64_t;
#define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1
#elif defined(__VMS ) || defined(__sgi)
/*
* Using <inttypes.h>
*/
#include <inttypes.h>
typedef int32_t khronos_int32_t;
typedef uint32_t khronos_uint32_t;
typedef int64_t khronos_int64_t;
typedef uint64_t khronos_uint64_t;
#define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1
#elif defined(_WIN32) && !defined(__SCITECH_SNAP__)
/*
* Win32
*/
typedef __int32 khronos_int32_t;
typedef unsigned __int32 khronos_uint32_t;
typedef __int64 khronos_int64_t;
typedef unsigned __int64 khronos_uint64_t;
#define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1
#elif defined(__sun__) || defined(__digital__)
/*
* Sun or Digital
*/
typedef int khronos_int32_t;
typedef unsigned int khronos_uint32_t;
#if defined(__arch64__) || defined(_LP64)
typedef long int khronos_int64_t;
typedef unsigned long int khronos_uint64_t;
#else
typedef long long int khronos_int64_t;
typedef unsigned long long int khronos_uint64_t;
#endif /* __arch64__ */
#define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1
#elif 0
/*
* Hypothetical platform with no float or int64 support
*/
typedef int khronos_int32_t;
typedef unsigned int khronos_uint32_t;
#define KHRONOS_SUPPORT_INT64 0
#define KHRONOS_SUPPORT_FLOAT 0
#else
/*
* Generic fallback
*/
#include <stdint.h>
typedef int32_t khronos_int32_t;
typedef uint32_t khronos_uint32_t;
typedef int64_t khronos_int64_t;
typedef uint64_t khronos_uint64_t;
#define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1
#endif
/*
* Types that are (so far) the same on all platforms
*/
typedef signed char khronos_int8_t;
typedef unsigned char khronos_uint8_t;
typedef signed short int khronos_int16_t;
typedef unsigned short int khronos_uint16_t;
/*
* Types that differ between LLP64 and LP64 architectures - in LLP64,
* pointers are 64 bits, but 'long' is still 32 bits. Win64 appears
* to be the only LLP64 architecture in current use.
*/
#ifdef _WIN64
typedef signed long long int khronos_intptr_t;
typedef unsigned long long int khronos_uintptr_t;
typedef signed long long int khronos_ssize_t;
typedef unsigned long long int khronos_usize_t;
#else
typedef signed long int khronos_intptr_t;
typedef unsigned long int khronos_uintptr_t;
typedef signed long int khronos_ssize_t;
typedef unsigned long int khronos_usize_t;
#endif
#if KHRONOS_SUPPORT_FLOAT
/*
* Float type
*/
typedef float khronos_float_t;
#endif
#if KHRONOS_SUPPORT_INT64
/* Time types
*
* These types can be used to represent a time interval in nanoseconds or
* an absolute Unadjusted System Time. Unadjusted System Time is the number
* of nanoseconds since some arbitrary system event (e.g. since the last
* time the system booted). The Unadjusted System Time is an unsigned
* 64 bit value that wraps back to 0 every 584 years. Time intervals
* may be either signed or unsigned.
*/
typedef khronos_uint64_t khronos_utime_nanoseconds_t;
typedef khronos_int64_t khronos_stime_nanoseconds_t;
#endif
/*
* Dummy value used to pad enum types to 32 bits.
*/
#ifndef KHRONOS_MAX_ENUM
#define KHRONOS_MAX_ENUM 0x7FFFFFFF
#endif
/*
* Enumerated boolean type
*
* Values other than zero should be considered to be true. Therefore
* comparisons should not be made against KHRONOS_TRUE.
*/
typedef enum {
KHRONOS_FALSE = 0,
KHRONOS_TRUE = 1,
KHRONOS_BOOLEAN_ENUM_FORCE_SIZE = KHRONOS_MAX_ENUM
} khronos_boolean_enum_t;
#endif /* __khrplatform_h_ */

File diff suppressed because it is too large Load diff

View file

@ -1,72 +0,0 @@
// +build !android,!ios
// Code generated by glow (https://github.com/go-gl/glow). DO NOT EDIT.
// This file implements GlowGetProcAddress for every supported platform. The
// correct version is chosen automatically based on build tags:
//
// windows: WGL
// darwin: CGL
// linux freebsd: GLX
//
// Use of EGL instead of the platform's default (listed above) is made possible
// via the "egl" build tag.
//
// It is also possible to install your own function outside this package for
// retrieving OpenGL function pointers, to do this see InitWithProcAddrFunc.
package gl
/*
#cgo windows CFLAGS: -DTAG_WINDOWS
#cgo windows LDFLAGS: -lopengl32
#cgo darwin CFLAGS: -DTAG_DARWIN
#cgo darwin,!ios LDFLAGS: -framework OpenGL
#cgo linux freebsd CFLAGS: -DTAG_POSIX
#cgo linux freebsd LDFLAGS: -lGL
#cgo egl CFLAGS: -DTAG_EGL
#cgo egl LDFLAGS: -lEGL
// Check the EGL tag first as it takes priority over the platform's default
// configuration of WGL/GLX/CGL.
#if defined(TAG_EGL)
#include <stdlib.h>
#include <EGL/egl.h>
void* GlowGetProcAddress_glglesunion(const char* name) {
return eglGetProcAddress(name);
}
#elif defined(TAG_WINDOWS)
#define WIN32_LEAN_AND_MEAN 1
#include <windows.h>
#include <stdlib.h>
static HMODULE ogl32dll = NULL;
void* GlowGetProcAddress_glglesunion(const char* name) {
void* pf = wglGetProcAddress((LPCSTR) name);
if (pf) {
return pf;
}
if (ogl32dll == NULL) {
ogl32dll = LoadLibraryA("opengl32.dll");
}
return GetProcAddress(ogl32dll, (LPCSTR) name);
}
#elif defined(TAG_DARWIN)
#include <stdlib.h>
#include <dlfcn.h>
void* GlowGetProcAddress_glglesunion(const char* name) {
return dlsym(RTLD_DEFAULT, name);
}
#elif defined(TAG_POSIX)
#include <stdlib.h>
#include <GL/glx.h>
void* GlowGetProcAddress_glglesunion(const char* name) {
return glXGetProcAddress((const GLubyte *) name);
}
#endif
*/
import "C"
import "unsafe"
func getProcAddress(namea string) unsafe.Pointer {
cname := C.CString(namea)
defer C.free(unsafe.Pointer(cname))
return C.GlowGetProcAddress_glglesunion(cname)
}

View file

@ -1,312 +0,0 @@
// +build android ios
// Code generated by glow (https://github.com/go-gl/glow). DO NOT EDIT.
// This file implements GlowGetProcAddress for every supported platform. The
// correct version is chosen automatically based on build tags:
//
// windows: WGL
// darwin: CGL
// linux freebsd: GLX
//
// Use of EGL instead of the platform's default (listed above) is made possible
// via the "egl" build tag.
//
// It is also possible to install your own function outside this package for
// retrieving OpenGL function pointers, to do this see InitWithProcAddrFunc.
package gl
/*
#cgo android LDFLAGS: -lGLESv2
#cgo android CFLAGS: -DANDROID
#cgo ios CFLAGS: -DIOS
#ifdef ANDROID
#include <GLES2/gl2.h>
#endif
#ifdef IOS
#include <OpenGLES/ES2/GL.h>
#endif
*/
import "C"
import "unsafe"
func getProcAddress(namea string) unsafe.Pointer {
switch namea {
case "glActiveTexture":
return unsafe.Pointer(C.glActiveTexture)
case "glAttachShader":
return unsafe.Pointer(C.glAttachShader)
case "glBindAttribLocation":
return unsafe.Pointer(C.glBindAttribLocation)
case "glBindBuffer":
return unsafe.Pointer(C.glBindBuffer)
case "glBindFramebuffer":
return unsafe.Pointer(C.glBindFramebuffer)
case "glBindRenderbuffer":
return unsafe.Pointer(C.glBindRenderbuffer)
case "glBindTexture":
return unsafe.Pointer(C.glBindTexture)
case "glBlendColor":
return unsafe.Pointer(C.glBlendColor)
case "glBlendEquation":
return unsafe.Pointer(C.glBlendEquation)
case "glBlendEquationSeparate":
return unsafe.Pointer(C.glBlendEquationSeparate)
case "glBlendFunc":
return unsafe.Pointer(C.glBlendFunc)
case "glBlendFuncSeparate":
return unsafe.Pointer(C.glBlendFuncSeparate)
case "glBufferData":
return unsafe.Pointer(C.glBufferData)
case "glBufferSubData":
return unsafe.Pointer(C.glBufferSubData)
case "glCheckFramebufferStatus":
return unsafe.Pointer(C.glCheckFramebufferStatus)
case "glClear":
return unsafe.Pointer(C.glClear)
case "glClearColor":
return unsafe.Pointer(C.glClearColor)
case "glClearStencil":
return unsafe.Pointer(C.glClearStencil)
case "glColorMask":
return unsafe.Pointer(C.glColorMask)
case "glCompileShader":
return unsafe.Pointer(C.glCompileShader)
case "glCompressedTexImage2D":
return unsafe.Pointer(C.glCompressedTexImage2D)
case "glCompressedTexSubImage2D":
return unsafe.Pointer(C.glCompressedTexSubImage2D)
case "glCopyTexImage2D":
return unsafe.Pointer(C.glCopyTexImage2D)
case "glCopyTexSubImage2D":
return unsafe.Pointer(C.glCopyTexSubImage2D)
case "glCreateProgram":
return unsafe.Pointer(C.glCreateProgram)
case "glCreateShader":
return unsafe.Pointer(C.glCreateShader)
case "glCullFace":
return unsafe.Pointer(C.glCullFace)
case "glDeleteBuffers":
return unsafe.Pointer(C.glDeleteBuffers)
case "glDeleteFramebuffers":
return unsafe.Pointer(C.glDeleteFramebuffers)
case "glDeleteProgram":
return unsafe.Pointer(C.glDeleteProgram)
case "glDeleteRenderbuffers":
return unsafe.Pointer(C.glDeleteRenderbuffers)
case "glDeleteShader":
return unsafe.Pointer(C.glDeleteShader)
case "glDeleteTextures":
return unsafe.Pointer(C.glDeleteTextures)
case "glDepthFunc":
return unsafe.Pointer(C.glDepthFunc)
case "glDepthMask":
return unsafe.Pointer(C.glDepthMask)
case "glDetachShader":
return unsafe.Pointer(C.glDetachShader)
case "glDisable":
return unsafe.Pointer(C.glDisable)
case "glDisableVertexAttribArray":
return unsafe.Pointer(C.glDisableVertexAttribArray)
case "glDrawArrays":
return unsafe.Pointer(C.glDrawArrays)
case "glDrawElements":
return unsafe.Pointer(C.glDrawElements)
case "glEnable":
return unsafe.Pointer(C.glEnable)
case "glEnableVertexAttribArray":
return unsafe.Pointer(C.glEnableVertexAttribArray)
case "glFinish":
return unsafe.Pointer(C.glFinish)
case "glFlush":
return unsafe.Pointer(C.glFlush)
case "glFramebufferRenderbuffer":
return unsafe.Pointer(C.glFramebufferRenderbuffer)
case "glFramebufferTexture2D":
return unsafe.Pointer(C.glFramebufferTexture2D)
case "glFrontFace":
return unsafe.Pointer(C.glFrontFace)
case "glGenBuffers":
return unsafe.Pointer(C.glGenBuffers)
case "glGenFramebuffers":
return unsafe.Pointer(C.glGenFramebuffers)
case "glGenRenderbuffers":
return unsafe.Pointer(C.glGenRenderbuffers)
case "glGenTextures":
return unsafe.Pointer(C.glGenTextures)
case "glGenerateMipmap":
return unsafe.Pointer(C.glGenerateMipmap)
case "glGetActiveAttrib":
return unsafe.Pointer(C.glGetActiveAttrib)
case "glGetActiveUniform":
return unsafe.Pointer(C.glGetActiveUniform)
case "glGetAttachedShaders":
return unsafe.Pointer(C.glGetAttachedShaders)
case "glGetAttribLocation":
return unsafe.Pointer(C.glGetAttribLocation)
case "glGetBooleanv":
return unsafe.Pointer(C.glGetBooleanv)
case "glGetBufferParameteriv":
return unsafe.Pointer(C.glGetBufferParameteriv)
case "glGetError":
return unsafe.Pointer(C.glGetError)
case "glGetFloatv":
return unsafe.Pointer(C.glGetFloatv)
case "glGetFramebufferAttachmentParameteriv":
return unsafe.Pointer(C.glGetFramebufferAttachmentParameteriv)
case "glGetIntegerv":
return unsafe.Pointer(C.glGetIntegerv)
case "glGetProgramInfoLog":
return unsafe.Pointer(C.glGetProgramInfoLog)
case "glGetProgramiv":
return unsafe.Pointer(C.glGetProgramiv)
case "glGetRenderbufferParameteriv":
return unsafe.Pointer(C.glGetRenderbufferParameteriv)
case "glGetShaderInfoLog":
return unsafe.Pointer(C.glGetShaderInfoLog)
case "glGetShaderSource":
return unsafe.Pointer(C.glGetShaderSource)
case "glGetShaderiv":
return unsafe.Pointer(C.glGetShaderiv)
case "glGetString":
return unsafe.Pointer(C.glGetString)
case "glGetTexParameterfv":
return unsafe.Pointer(C.glGetTexParameterfv)
case "glGetTexParameteriv":
return unsafe.Pointer(C.glGetTexParameteriv)
case "glGetUniformLocation":
return unsafe.Pointer(C.glGetUniformLocation)
case "glGetUniformfv":
return unsafe.Pointer(C.glGetUniformfv)
case "glGetUniformiv":
return unsafe.Pointer(C.glGetUniformiv)
case "glGetVertexAttribPointerv":
return unsafe.Pointer(C.glGetVertexAttribPointerv)
case "glGetVertexAttribfv":
return unsafe.Pointer(C.glGetVertexAttribfv)
case "glGetVertexAttribiv":
return unsafe.Pointer(C.glGetVertexAttribiv)
case "glHint":
return unsafe.Pointer(C.glHint)
case "glIsBuffer":
return unsafe.Pointer(C.glIsBuffer)
case "glIsEnabled":
return unsafe.Pointer(C.glIsEnabled)
case "glIsFramebuffer":
return unsafe.Pointer(C.glIsFramebuffer)
case "glIsProgram":
return unsafe.Pointer(C.glIsProgram)
case "glIsRenderbuffer":
return unsafe.Pointer(C.glIsRenderbuffer)
case "glIsShader":
return unsafe.Pointer(C.glIsShader)
case "glIsTexture":
return unsafe.Pointer(C.glIsTexture)
case "glLineWidth":
return unsafe.Pointer(C.glLineWidth)
case "glLinkProgram":
return unsafe.Pointer(C.glLinkProgram)
case "glPixelStorei":
return unsafe.Pointer(C.glPixelStorei)
case "glPolygonOffset":
return unsafe.Pointer(C.glPolygonOffset)
case "glReadPixels":
return unsafe.Pointer(C.glReadPixels)
case "glRenderbufferStorage":
return unsafe.Pointer(C.glRenderbufferStorage)
case "glSampleCoverage":
return unsafe.Pointer(C.glSampleCoverage)
case "glScissor":
return unsafe.Pointer(C.glScissor)
case "glShaderSource":
return unsafe.Pointer(C.glShaderSource)
case "glStencilFunc":
return unsafe.Pointer(C.glStencilFunc)
case "glStencilFuncSeparate":
return unsafe.Pointer(C.glStencilFuncSeparate)
case "glStencilMask":
return unsafe.Pointer(C.glStencilMask)
case "glStencilMaskSeparate":
return unsafe.Pointer(C.glStencilMaskSeparate)
case "glStencilOp":
return unsafe.Pointer(C.glStencilOp)
case "glStencilOpSeparate":
return unsafe.Pointer(C.glStencilOpSeparate)
case "glTexImage2D":
return unsafe.Pointer(C.glTexImage2D)
case "glTexParameterf":
return unsafe.Pointer(C.glTexParameterf)
case "glTexParameterfv":
return unsafe.Pointer(C.glTexParameterfv)
case "glTexParameteri":
return unsafe.Pointer(C.glTexParameteri)
case "glTexParameteriv":
return unsafe.Pointer(C.glTexParameteriv)
case "glTexSubImage2D":
return unsafe.Pointer(C.glTexSubImage2D)
case "glUniform1f":
return unsafe.Pointer(C.glUniform1f)
case "glUniform1fv":
return unsafe.Pointer(C.glUniform1fv)
case "glUniform1i":
return unsafe.Pointer(C.glUniform1i)
case "glUniform1iv":
return unsafe.Pointer(C.glUniform1iv)
case "glUniform2f":
return unsafe.Pointer(C.glUniform2f)
case "glUniform2fv":
return unsafe.Pointer(C.glUniform2fv)
case "glUniform2i":
return unsafe.Pointer(C.glUniform2i)
case "glUniform2iv":
return unsafe.Pointer(C.glUniform2iv)
case "glUniform3f":
return unsafe.Pointer(C.glUniform3f)
case "glUniform3fv":
return unsafe.Pointer(C.glUniform3fv)
case "glUniform3i":
return unsafe.Pointer(C.glUniform3i)
case "glUniform3iv":
return unsafe.Pointer(C.glUniform3iv)
case "glUniform4f":
return unsafe.Pointer(C.glUniform4f)
case "glUniform4fv":
return unsafe.Pointer(C.glUniform4fv)
case "glUniform4i":
return unsafe.Pointer(C.glUniform4i)
case "glUniform4iv":
return unsafe.Pointer(C.glUniform4iv)
case "glUniformMatrix2fv":
return unsafe.Pointer(C.glUniformMatrix2fv)
case "glUniformMatrix3fv":
return unsafe.Pointer(C.glUniformMatrix3fv)
case "glUniformMatrix4fv":
return unsafe.Pointer(C.glUniformMatrix4fv)
case "glUseProgram":
return unsafe.Pointer(C.glUseProgram)
case "glValidateProgram":
return unsafe.Pointer(C.glValidateProgram)
case "glVertexAttrib1f":
return unsafe.Pointer(C.glVertexAttrib1f)
case "glVertexAttrib1fv":
return unsafe.Pointer(C.glVertexAttrib1fv)
case "glVertexAttrib2f":
return unsafe.Pointer(C.glVertexAttrib2f)
case "glVertexAttrib2fv":
return unsafe.Pointer(C.glVertexAttrib2fv)
case "glVertexAttrib3f":
return unsafe.Pointer(C.glVertexAttrib3f)
case "glVertexAttrib3fv":
return unsafe.Pointer(C.glVertexAttrib3fv)
case "glVertexAttrib4f":
return unsafe.Pointer(C.glVertexAttrib4f)
case "glVertexAttrib4fv":
return unsafe.Pointer(C.glVertexAttrib4fv)
case "glVertexAttribPointer":
return unsafe.Pointer(C.glVertexAttribPointer)
case "glViewport":
return unsafe.Pointer(C.glViewport)
}
return nil
}

View file

@ -1,607 +0,0 @@
package goglbackend
import (
"fmt"
"image/color"
"math"
"github.com/tfriedel6/canvas/backend/backendbase"
"github.com/tfriedel6/canvas/backend/gogl/gl"
)
const alphaTexSize = 2048
var zeroes [alphaTexSize]byte
// GLContext is a context that contains all the
// shaders and buffers necessary for rendering
type GLContext struct {
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
imageBufTex uint32
imageBuf []byte
ptsBuf []float32
glChan chan func()
}
// NewGLContext creates all the necessary GL resources,
// like shaders and buffers
func NewGLContext() (*GLContext, error) {
ctx := &GLContext{
ptsBuf: make([]float32, 0, 4096),
glChan: make(chan func()),
}
err := gl.Init()
if err != nil {
return nil, err
}
gl.GetError() // clear error state
err = loadShader(solidVS, solidFS, &ctx.sr.shaderProgram)
if err != nil {
return nil, err
}
ctx.sr.shaderProgram.mustLoadLocations(&ctx.sr)
if err = glError(); err != nil {
return nil, err
}
err = loadShader(linearGradientVS, linearGradientFS, &ctx.lgr.shaderProgram)
if err != nil {
return nil, err
}
ctx.lgr.shaderProgram.mustLoadLocations(&ctx.lgr)
if err = glError(); err != nil {
return nil, err
}
err = loadShader(radialGradientVS, radialGradientFS, &ctx.rgr.shaderProgram)
if err != nil {
return nil, err
}
ctx.rgr.shaderProgram.mustLoadLocations(&ctx.rgr)
if err = glError(); err != nil {
return nil, err
}
err = loadShader(imagePatternVS, imagePatternFS, &ctx.ipr.shaderProgram)
if err != nil {
return nil, err
}
ctx.ipr.shaderProgram.mustLoadLocations(&ctx.ipr)
if err = glError(); err != nil {
return nil, err
}
err = loadShader(solidAlphaVS, solidAlphaFS, &ctx.sar.shaderProgram)
if err != nil {
return nil, err
}
ctx.sar.shaderProgram.mustLoadLocations(&ctx.sar)
if err = glError(); err != nil {
return nil, err
}
err = loadShader(linearGradientAlphaVS, linearGradientFS, &ctx.lgar.shaderProgram)
if err != nil {
return nil, err
}
ctx.lgar.shaderProgram.mustLoadLocations(&ctx.lgar)
if err = glError(); err != nil {
return nil, err
}
err = loadShader(radialGradientAlphaVS, radialGradientAlphaFS, &ctx.rgar.shaderProgram)
if err != nil {
return nil, err
}
ctx.rgar.shaderProgram.mustLoadLocations(&ctx.rgar)
if err = glError(); err != nil {
return nil, err
}
err = loadShader(imagePatternAlphaVS, imagePatternAlphaFS, &ctx.ipar.shaderProgram)
if err != nil {
return nil, err
}
ctx.ipar.shaderProgram.mustLoadLocations(&ctx.ipar)
if err = glError(); err != nil {
return nil, err
}
err = loadShader(imageVS, imageFS, &ctx.ir.shaderProgram)
if err != nil {
return nil, err
}
ctx.ir.shaderProgram.mustLoadLocations(&ctx.ir)
if err = glError(); err != nil {
return nil, err
}
err = loadShader(gaussian15VS, gaussian15FS, &ctx.gauss15r.shaderProgram)
if err != nil {
return nil, err
}
ctx.gauss15r.shaderProgram.mustLoadLocations(&ctx.gauss15r)
if err = glError(); err != nil {
return nil, err
}
err = loadShader(gaussian63VS, gaussian63FS, &ctx.gauss63r.shaderProgram)
if err != nil {
return nil, err
}
ctx.gauss63r.shaderProgram.mustLoadLocations(&ctx.gauss63r)
if err = glError(); err != nil {
return nil, err
}
err = loadShader(gaussian127VS, gaussian127FS, &ctx.gauss127r.shaderProgram)
if err != nil {
return nil, err
}
ctx.gauss127r.shaderProgram.mustLoadLocations(&ctx.gauss127r)
if err = glError(); err != nil {
return nil, err
}
gl.GenBuffers(1, &ctx.buf)
if err = glError(); err != nil {
return nil, err
}
gl.GenBuffers(1, &ctx.shadowBuf)
if err = glError(); err != nil {
return nil, err
}
gl.ActiveTexture(gl.TEXTURE0)
gl.GenTextures(1, &ctx.alphaTex)
gl.BindTexture(gl.TEXTURE_2D, ctx.alphaTex)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.ALPHA, alphaTexSize, alphaTexSize, 0, gl.ALPHA, gl.UNSIGNED_BYTE, nil)
gl.Enable(gl.BLEND)
gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
gl.Enable(gl.STENCIL_TEST)
gl.StencilMask(0xFF)
gl.Clear(gl.STENCIL_BUFFER_BIT)
gl.StencilOp(gl.KEEP, gl.KEEP, gl.KEEP)
gl.StencilFunc(gl.EQUAL, 0, 0xFF)
gl.Disable(gl.SCISSOR_TEST)
return ctx, nil
}
// GoGLBackend is a canvas backend using Go-GL
type GoGLBackend struct {
x, y, w, h int
fx, fy, fw, fh float64
*GLContext
activateFn func()
disableTextureRenderTarget func()
}
type offscreenBuffer struct {
tex uint32
w int
h int
renderStencilBuf uint32
frameBuf uint32
alpha bool
}
// New returns a new canvas backend. x, y, w, h define the target
// rectangle in the window. ctx is a GLContext created with
// NewGLContext, but can be nil for a default one. It makes sense
// to pass one in when using for example an onscreen and an
// offscreen backend using the same GL context.
func New(x, y, w, h int, ctx *GLContext) (*GoGLBackend, error) {
if ctx == nil {
var err error
ctx, err = NewGLContext()
if err != nil {
return nil, err
}
}
b := &GoGLBackend{
w: w,
h: h,
fw: float64(w),
fh: float64(h),
GLContext: ctx,
}
b.activateFn = func() {
gl.BindFramebuffer(gl.FRAMEBUFFER, 0)
gl.Viewport(int32(b.x), int32(b.y), int32(b.w), int32(b.h))
// todo reapply clipping since another application may have used the stencil buffer
}
b.disableTextureRenderTarget = func() {
gl.BindFramebuffer(gl.FRAMEBUFFER, 0)
}
return b, nil
}
// GoGLBackendOffscreen is a canvas backend using an offscreen
// texture
type GoGLBackendOffscreen struct {
GoGLBackend
TextureID uint32
offscrBuf offscreenBuffer
offscrImg Image
}
// NewOffscreen returns a new offscreen canvas backend. w, h define
// the size of the offscreen texture. ctx is a GLContext created
// with NewGLContext, but can be nil for a default one. It makes
// sense to pass one in when using for example an onscreen and an
// offscreen backend using the same GL context.
func NewOffscreen(w, h int, alpha bool, ctx *GLContext) (*GoGLBackendOffscreen, error) {
b, err := New(0, 0, w, h, ctx)
if err != nil {
return nil, err
}
bo := &GoGLBackendOffscreen{GoGLBackend: *b}
bo.offscrBuf.alpha = alpha
bo.offscrImg.flip = true
bo.activateFn = func() {
bo.enableTextureRenderTarget(&bo.offscrBuf)
gl.Viewport(0, 0, int32(bo.w), int32(bo.h))
bo.offscrImg.w = bo.offscrBuf.w
bo.offscrImg.h = bo.offscrBuf.h
bo.offscrImg.tex = bo.offscrBuf.tex
bo.TextureID = bo.offscrBuf.tex
}
bo.disableTextureRenderTarget = func() {
bo.enableTextureRenderTarget(&bo.offscrBuf)
}
return bo, nil
}
// SetBounds updates the bounds of the canvas. This would
// usually be called for example when the window is resized
func (b *GoGLBackend) SetBounds(x, y, w, h int) {
b.x, b.y = x, y
b.fx, b.fy = float64(x), float64(y)
b.w, b.h = w, h
b.fw, b.fh = float64(w), float64(h)
if b == activeContext {
gl.Viewport(0, 0, int32(b.w), int32(b.h))
gl.Clear(gl.STENCIL_BUFFER_BIT)
}
}
// SetSize updates the size of the offscreen texture
func (b *GoGLBackendOffscreen) SetSize(w, h int) {
b.GoGLBackend.SetBounds(0, 0, w, h)
b.offscrImg.w = b.offscrBuf.w
b.offscrImg.h = b.offscrBuf.h
}
// Size returns the size of the window or offscreen
// texture
func (b *GoGLBackend) Size() (int, int) {
return b.w, b.h
}
func glError() error {
glErr := gl.GetError()
if glErr != gl.NO_ERROR {
return fmt.Errorf("GL Error: %x", glErr)
}
return nil
}
// Activate only needs to be called if there is other
// code also using the GL state
func (b *GoGLBackend) Activate() {
b.activate()
}
var activeContext *GoGLBackend
func (b *GoGLBackend) activate() {
if activeContext != b {
activeContext = b
b.activateFn()
}
b.runGLQueue()
}
func (b *GoGLBackend) runGLQueue() {
for {
select {
case f := <-b.glChan:
f()
default:
return
}
}
}
// Delete deletes the offscreen texture. After calling this
// the backend can no longer be used
func (b *GoGLBackendOffscreen) Delete() {
gl.DeleteTextures(1, &b.offscrBuf.tex)
gl.DeleteFramebuffers(1, &b.offscrBuf.frameBuf)
gl.DeleteRenderbuffers(1, &b.offscrBuf.renderStencilBuf)
}
// CanUseAsImage returns true if the given backend can be
// directly used by this backend to avoid a conversion.
// Used internally
func (b *GoGLBackend) CanUseAsImage(b2 backendbase.Backend) bool {
_, ok := b2.(*GoGLBackendOffscreen)
return ok
}
// AsImage returns nil, since this backend cannot be directly
// used as an image. Used internally
func (b *GoGLBackend) AsImage() backendbase.Image {
return nil
}
// AsImage returns an implementation of the Image interface
// that can be used to render this offscreen texture
// directly. Used internally
func (b *GoGLBackendOffscreen) AsImage() backendbase.Image {
return &b.offscrImg
}
type glColor struct {
r, g, b, a float64
}
func colorGoToGL(c color.RGBA) glColor {
var glc glColor
glc.r = float64(c.R) / 255
glc.g = float64(c.G) / 255
glc.b = float64(c.B) / 255
glc.a = float64(c.A) / 255
return glc
}
func (b *GoGLBackend) useShader(style *backendbase.FillStyle) (vertexLoc uint32) {
if lg := style.LinearGradient; lg != nil {
lg := lg.(*LinearGradient)
gl.ActiveTexture(gl.TEXTURE0)
gl.BindTexture(gl.TEXTURE_2D, lg.tex)
gl.UseProgram(b.lgr.ID)
from := vec{style.Gradient.X0, style.Gradient.Y0}
to := vec{style.Gradient.X1, style.Gradient.Y1}
dir := to.sub(from)
length := dir.len()
dir = dir.scale(1 / length)
gl.Uniform2f(b.lgr.CanvasSize, float32(b.fw), float32(b.fh))
gl.Uniform2f(b.lgr.From, float32(from[0]), float32(from[1]))
gl.Uniform2f(b.lgr.Dir, float32(dir[0]), float32(dir[1]))
gl.Uniform1f(b.lgr.Len, float32(length))
gl.Uniform1i(b.lgr.Gradient, 0)
gl.Uniform1f(b.lgr.GlobalAlpha, float32(style.Color.A)/255)
return b.lgr.Vertex
}
if rg := style.RadialGradient; rg != nil {
rg := rg.(*RadialGradient)
gl.ActiveTexture(gl.TEXTURE0)
gl.BindTexture(gl.TEXTURE_2D, rg.tex)
gl.UseProgram(b.rgr.ID)
from := vec{style.Gradient.X0, style.Gradient.Y0}
to := vec{style.Gradient.X1, style.Gradient.Y1}
gl.Uniform2f(b.rgr.CanvasSize, float32(b.fw), float32(b.fh))
gl.Uniform2f(b.rgr.From, float32(from[0]), float32(from[1]))
gl.Uniform2f(b.rgr.To, float32(to[0]), float32(to[1]))
gl.Uniform1f(b.rgr.RadFrom, float32(style.Gradient.RadFrom))
gl.Uniform1f(b.rgr.RadTo, float32(style.Gradient.RadTo))
gl.Uniform1i(b.rgr.Gradient, 0)
gl.Uniform1f(b.rgr.GlobalAlpha, float32(style.Color.A)/255)
return b.rgr.Vertex
}
if ip := style.ImagePattern; ip != nil {
ipd := ip.(*ImagePattern).data
img := ipd.Image.(*Image)
gl.UseProgram(b.ipr.ID)
gl.ActiveTexture(gl.TEXTURE0)
gl.BindTexture(gl.TEXTURE_2D, img.tex)
gl.Uniform2f(b.ipr.CanvasSize, float32(b.fw), float32(b.fh))
gl.Uniform2f(b.ipr.ImageSize, float32(img.w), float32(img.h))
gl.Uniform1i(b.ipr.Image, 0)
var f32mat [9]float32
for i, v := range ipd.Transform {
f32mat[i] = float32(v)
}
gl.UniformMatrix3fv(b.ipr.ImageTransform, 1, false, &f32mat[0])
switch ipd.Repeat {
case backendbase.Repeat:
gl.Uniform2f(b.ipr.Repeat, 1, 1)
case backendbase.RepeatX:
gl.Uniform2f(b.ipr.Repeat, 1, 0)
case backendbase.RepeatY:
gl.Uniform2f(b.ipr.Repeat, 0, 1)
case backendbase.NoRepeat:
gl.Uniform2f(b.ipr.Repeat, 0, 0)
}
gl.Uniform1f(b.ipr.GlobalAlpha, float32(style.Color.A)/255)
return b.ipr.Vertex
}
gl.UseProgram(b.sr.ID)
gl.Uniform2f(b.sr.CanvasSize, float32(b.fw), float32(b.fh))
c := colorGoToGL(style.Color)
gl.Uniform4f(b.sr.Color, float32(c.r), float32(c.g), float32(c.b), float32(c.a))
gl.Uniform1f(b.sr.GlobalAlpha, 1)
return b.sr.Vertex
}
func (b *GoGLBackend) useAlphaShader(style *backendbase.FillStyle, alphaTexSlot int32) (vertexLoc, alphaTexCoordLoc uint32) {
if lg := style.LinearGradient; lg != nil {
lg := lg.(*LinearGradient)
gl.ActiveTexture(gl.TEXTURE0)
gl.BindTexture(gl.TEXTURE_2D, lg.tex)
gl.UseProgram(b.lgar.ID)
from := vec{style.Gradient.X0, style.Gradient.Y0}
to := vec{style.Gradient.X1, style.Gradient.Y1}
dir := to.sub(from)
length := dir.len()
dir = dir.scale(1 / length)
gl.Uniform2f(b.lgar.CanvasSize, float32(b.fw), float32(b.fh))
gl.Uniform2f(b.lgar.From, float32(from[0]), float32(from[1]))
gl.Uniform2f(b.lgar.Dir, float32(dir[0]), float32(dir[1]))
gl.Uniform1f(b.lgar.Len, float32(length))
gl.Uniform1i(b.lgar.Gradient, 0)
gl.Uniform1i(b.lgar.AlphaTex, alphaTexSlot)
gl.Uniform1f(b.lgar.GlobalAlpha, float32(style.Color.A)/255)
return b.lgar.Vertex, b.lgar.AlphaTexCoord
}
if rg := style.RadialGradient; rg != nil {
rg := rg.(*RadialGradient)
gl.ActiveTexture(gl.TEXTURE0)
gl.BindTexture(gl.TEXTURE_2D, rg.tex)
gl.UseProgram(b.rgar.ID)
from := vec{style.Gradient.X0, style.Gradient.Y0}
to := vec{style.Gradient.X1, style.Gradient.Y1}
gl.Uniform2f(b.rgar.CanvasSize, float32(b.fw), float32(b.fh))
gl.Uniform2f(b.rgar.From, float32(from[0]), float32(from[1]))
gl.Uniform2f(b.rgar.To, float32(to[0]), float32(to[1]))
gl.Uniform1f(b.rgar.RadFrom, float32(style.Gradient.RadFrom))
gl.Uniform1f(b.rgar.RadTo, float32(style.Gradient.RadTo))
gl.Uniform1i(b.rgar.Gradient, 0)
gl.Uniform1i(b.rgar.AlphaTex, alphaTexSlot)
gl.Uniform1f(b.rgar.GlobalAlpha, float32(style.Color.A)/255)
return b.rgar.Vertex, b.rgar.AlphaTexCoord
}
if ip := style.ImagePattern; ip != nil {
ipd := ip.(*ImagePattern).data
img := ipd.Image.(*Image)
gl.UseProgram(b.ipar.ID)
gl.ActiveTexture(gl.TEXTURE0)
gl.BindTexture(gl.TEXTURE_2D, img.tex)
gl.Uniform2f(b.ipar.CanvasSize, float32(b.fw), float32(b.fh))
gl.Uniform2f(b.ipar.ImageSize, float32(img.w), float32(img.h))
gl.Uniform1i(b.ipar.Image, 0)
var f32mat [9]float32
for i, v := range ipd.Transform {
f32mat[i] = float32(v)
}
gl.UniformMatrix3fv(b.ipr.ImageTransform, 1, false, &f32mat[0])
switch ipd.Repeat {
case backendbase.Repeat:
gl.Uniform2f(b.ipr.Repeat, 1, 1)
case backendbase.RepeatX:
gl.Uniform2f(b.ipr.Repeat, 1, 0)
case backendbase.RepeatY:
gl.Uniform2f(b.ipr.Repeat, 0, 1)
case backendbase.NoRepeat:
gl.Uniform2f(b.ipr.Repeat, 0, 0)
}
gl.Uniform1i(b.ipar.AlphaTex, alphaTexSlot)
gl.Uniform1f(b.ipar.GlobalAlpha, float32(style.Color.A)/255)
return b.ipar.Vertex, b.ipar.AlphaTexCoord
}
gl.UseProgram(b.sar.ID)
gl.Uniform2f(b.sar.CanvasSize, float32(b.fw), float32(b.fh))
c := colorGoToGL(style.Color)
gl.Uniform4f(b.sar.Color, float32(c.r), float32(c.g), float32(c.b), float32(c.a))
gl.Uniform1i(b.sar.AlphaTex, alphaTexSlot)
gl.Uniform1f(b.sar.GlobalAlpha, 1)
return b.sar.Vertex, b.sar.AlphaTexCoord
}
func (b *GoGLBackend) enableTextureRenderTarget(offscr *offscreenBuffer) {
if offscr.w == b.w && offscr.h == b.h {
gl.BindFramebuffer(gl.FRAMEBUFFER, offscr.frameBuf)
return
}
if b.w == 0 || b.h == 0 {
return
}
if offscr.w != 0 && offscr.h != 0 {
gl.DeleteTextures(1, &offscr.tex)
gl.DeleteFramebuffers(1, &offscr.frameBuf)
gl.DeleteRenderbuffers(1, &offscr.renderStencilBuf)
}
offscr.w = b.w
offscr.h = b.h
gl.ActiveTexture(gl.TEXTURE0)
gl.GenTextures(1, &offscr.tex)
gl.BindTexture(gl.TEXTURE_2D, offscr.tex)
// todo do non-power-of-two textures work everywhere?
if offscr.alpha {
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, int32(b.w), int32(b.h), 0, gl.RGBA, gl.UNSIGNED_BYTE, nil)
} else {
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGB, int32(b.w), int32(b.h), 0, gl.RGB, gl.UNSIGNED_BYTE, nil)
}
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
gl.GenFramebuffers(1, &offscr.frameBuf)
gl.BindFramebuffer(gl.FRAMEBUFFER, offscr.frameBuf)
gl.GenRenderbuffers(1, &offscr.renderStencilBuf)
gl.BindRenderbuffer(gl.RENDERBUFFER, offscr.renderStencilBuf)
gl.RenderbufferStorage(gl.RENDERBUFFER, gl.STENCIL_INDEX8, int32(b.w), int32(b.h))
gl.FramebufferRenderbuffer(gl.FRAMEBUFFER, gl.STENCIL_ATTACHMENT, gl.RENDERBUFFER, offscr.renderStencilBuf)
gl.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, offscr.tex, 0)
if err := gl.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))
}
gl.Clear(gl.COLOR_BUFFER_BIT | gl.STENCIL_BUFFER_BIT)
}
type vec [2]float64
func (v vec) sub(v2 vec) vec {
return vec{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) scale(f float64) vec {
return vec{v[0] * f, v[1] * f}
}

View file

@ -1,108 +0,0 @@
package goglbackend
import (
"runtime"
"github.com/tfriedel6/canvas/backend/backendbase"
"github.com/tfriedel6/canvas/backend/gogl/gl"
)
// LinearGradient is a gradient with any number of
// stops and any number of colors. The gradient will
// be drawn such that each point on the gradient
// will correspond to a straight line
type LinearGradient struct {
gradient
}
// RadialGradient is a gradient with any number of
// stops and any number of colors. The gradient will
// be drawn such that each point on the gradient
// will correspond to a circle
type RadialGradient struct {
gradient
}
type gradient struct {
b *GoGLBackend
tex uint32
loaded bool
}
func (b *GoGLBackend) LoadLinearGradient(data backendbase.Gradient) backendbase.LinearGradient {
b.activate()
lg := &LinearGradient{
gradient: gradient{b: b},
}
gl.GenTextures(1, &lg.tex)
gl.ActiveTexture(gl.TEXTURE0)
gl.BindTexture(gl.TEXTURE_2D, lg.tex)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
lg.load(data)
runtime.SetFinalizer(lg, func(lg *LinearGradient) {
b.glChan <- func() {
gl.DeleteTextures(1, &lg.tex)
}
})
return lg
}
func (b *GoGLBackend) LoadRadialGradient(data backendbase.Gradient) backendbase.RadialGradient {
b.activate()
rg := &RadialGradient{
gradient: gradient{b: b},
}
gl.GenTextures(1, &rg.tex)
gl.ActiveTexture(gl.TEXTURE0)
gl.BindTexture(gl.TEXTURE_2D, rg.tex)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
rg.load(data)
runtime.SetFinalizer(rg, func(rg *RadialGradient) {
b.glChan <- func() {
gl.DeleteTextures(1, &rg.tex)
}
})
return rg
}
// Delete explicitly deletes the gradient
func (g *gradient) Delete() {
g.b.activate()
gl.DeleteTextures(1, &g.tex)
}
func (lg *LinearGradient) Replace(data backendbase.Gradient) { lg.load(data) }
func (rg *RadialGradient) Replace(data backendbase.Gradient) { rg.load(data) }
func (g *gradient) load(stops backendbase.Gradient) {
if g.loaded {
return
}
g.b.activate()
gl.ActiveTexture(gl.TEXTURE0)
gl.BindTexture(gl.TEXTURE_2D, g.tex)
var pixels [2048 * 4]byte
pp := 0
for i := 0; i < 2048; i++ {
c := stops.ColorAt(float64(i) / 2047)
pixels[pp] = c.R
pixels[pp+1] = c.G
pixels[pp+2] = c.B
pixels[pp+3] = c.A
pp += 4
}
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 2048, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, gl.Ptr(&pixels[0]))
g.loaded = true
}

View file

@ -1,94 +0,0 @@
package goglbackend
import (
"image"
"image/color"
"unsafe"
"github.com/tfriedel6/canvas/backend/gogl/gl"
)
// GetImageData returns an RGBA image of the current image
func (b *GoGLBackend) GetImageData(x, y, w, h int) *image.RGBA {
b.activate()
if x < 0 {
w += x
x = 0
}
if y < 0 {
h += y
y = 0
}
if w > b.w {
w = b.w
}
if h > b.h {
h = b.h
}
if len(b.imageBuf) < w*h*3 {
b.imageBuf = make([]byte, w*h*3)
}
gl.ReadPixels(int32(x), int32(y), int32(w), int32(h), gl.RGB, gl.UNSIGNED_BYTE, gl.Ptr(&b.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: b.imageBuf[bp], G: b.imageBuf[bp+1], B: b.imageBuf[bp+2], A: 255})
bp += 3
}
}
return rgba
}
// PutImageData puts the given image at the given x/y coordinates
func (b *GoGLBackend) PutImageData(img *image.RGBA, x, y int) {
b.activate()
gl.ActiveTexture(gl.TEXTURE0)
if b.imageBufTex == 0 {
gl.GenTextures(1, &b.imageBufTex)
gl.BindTexture(gl.TEXTURE_2D, b.imageBufTex)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
} else {
gl.BindTexture(gl.TEXTURE_2D, b.imageBufTex)
}
w, h := img.Bounds().Dx(), img.Bounds().Dy()
if img.Stride == img.Bounds().Dx()*4 {
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, int32(w), int32(h), 0, gl.RGBA, gl.UNSIGNED_BYTE, gl.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]...)
}
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, int32(w), int32(h), 0, gl.RGBA, gl.UNSIGNED_BYTE, gl.Ptr(&data[0]))
}
dx, dy := float32(x), float32(y)
dw, dh := float32(w), float32(h)
gl.BindBuffer(gl.ARRAY_BUFFER, b.buf)
data := [16]float32{dx, dy, dx + dw, dy, dx + dw, dy + dh, dx, dy + dh,
0, 0, 1, 0, 1, 1, 0, 1}
gl.BufferData(gl.ARRAY_BUFFER, len(data)*4, unsafe.Pointer(&data[0]), gl.STREAM_DRAW)
gl.UseProgram(b.ir.ID)
gl.Uniform1i(b.ir.Image, 0)
gl.Uniform2f(b.ir.CanvasSize, float32(b.fw), float32(b.fh))
gl.Uniform1f(b.ir.GlobalAlpha, 1)
gl.VertexAttribPointer(b.ir.Vertex, 2, gl.FLOAT, false, 0, nil)
gl.VertexAttribPointer(b.ir.TexCoord, 2, gl.FLOAT, false, 0, gl.PtrOffset(8*4))
gl.EnableVertexAttribArray(b.ir.Vertex)
gl.EnableVertexAttribArray(b.ir.TexCoord)
gl.DrawArrays(gl.TRIANGLE_FAN, 0, 4)
gl.DisableVertexAttribArray(b.ir.Vertex)
gl.DisableVertexAttribArray(b.ir.TexCoord)
}

View file

@ -1,221 +0,0 @@
package goglbackend
import (
"errors"
"image"
"runtime"
"unsafe"
"github.com/tfriedel6/canvas/backend/backendbase"
"github.com/tfriedel6/canvas/backend/gogl/gl"
)
// Image represents a loaded image that can be used in various drawing functions
type Image struct {
b *GoGLBackend
w, h int
tex uint32
flip bool
}
func (b *GoGLBackend) LoadImage(src image.Image) (backendbase.Image, error) {
b.activate()
var tex uint32
gl.GenTextures(1, &tex)
gl.ActiveTexture(gl.TEXTURE0)
gl.BindTexture(gl.TEXTURE_2D, tex)
if src == nil {
return &Image{tex: tex}, nil
}
img, err := loadImage(src, tex)
if err != nil {
return nil, err
}
img.b = b
runtime.SetFinalizer(img, func(img *Image) {
b.glChan <- func() {
gl.DeleteTextures(1, &img.tex)
}
})
return img, nil
}
func loadImage(src image.Image, tex uint32) (*Image, error) {
var img *Image
var err error
switch v := src.(type) {
case *image.RGBA:
img, err = loadImageRGBA(v, tex)
if err != nil {
return nil, err
}
case image.Image:
img, err = loadImageConverted(v, tex)
if err != nil {
return nil, err
}
default:
return nil, errors.New("Unsupported source type")
}
return img, nil
}
func loadImageRGBA(src *image.RGBA, tex uint32) (*Image, error) {
img := &Image{tex: tex, w: src.Bounds().Dx(), h: src.Bounds().Dy()}
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
gl.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 {
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, int32(img.w), int32(img.h), 0, gl.RGBA, gl.UNSIGNED_BYTE, gl.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]...)
}
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, int32(img.w), int32(img.h), 0, gl.RGBA, gl.UNSIGNED_BYTE, gl.Ptr(&data[0]))
}
if err := glError(); err != nil {
return nil, err
}
gl.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()}
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
gl.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)
}
}
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, int32(img.w), int32(img.h), 0, gl.RGBA, gl.UNSIGNED_BYTE, gl.Ptr(&data[0]))
if err := glError(); err != nil {
return nil, err
}
gl.GenerateMipmap(gl.TEXTURE_2D)
if err := glError(); err != nil {
return nil, err
}
return img, nil
}
// Width returns the width of the image
func (img *Image) Width() int { return img.w }
// Height returns the height of the image
func (img *Image) Height() int { return img.h }
// Size returns the width and height of the image
func (img *Image) Size() (int, int) { return img.w, img.h }
// Delete deletes the image from memory. Any draw calls
// with a deleted image will not do anything
func (img *Image) Delete() {
img.b.activate()
gl.DeleteTextures(1, &img.tex)
}
// Replace replaces the image with the new one
func (img *Image) Replace(src image.Image) error {
img.b.activate()
gl.ActiveTexture(gl.TEXTURE0)
gl.BindTexture(gl.TEXTURE_2D, img.tex)
newImg, err := loadImage(src, img.tex)
if err != nil {
return err
}
newImg.b = img.b
*img = *newImg
return nil
}
func (b *GoGLBackend) DrawImage(dimg backendbase.Image, sx, sy, sw, sh float64, pts [4][2]float64, alpha float64) {
b.activate()
img := dimg.(*Image)
sx /= float64(img.w)
sy /= float64(img.h)
sw /= float64(img.w)
sh /= float64(img.h)
if img.flip {
sy += sh
sh = -sh
}
var buf [16]float32
data := buf[:0]
for _, pt := range pts {
data = append(data, float32(pt[0]), float32(pt[1]))
}
data = append(data,
float32(sx), float32(sy),
float32(sx), float32(sy+sh),
float32(sx+sw), float32(sy+sh),
float32(sx+sw), float32(sy),
)
gl.StencilFunc(gl.EQUAL, 0, 0xFF)
gl.BindBuffer(gl.ARRAY_BUFFER, b.buf)
gl.BufferData(gl.ARRAY_BUFFER, len(data)*4, unsafe.Pointer(&data[0]), gl.STREAM_DRAW)
gl.ActiveTexture(gl.TEXTURE0)
gl.BindTexture(gl.TEXTURE_2D, img.tex)
gl.UseProgram(b.ir.ID)
gl.Uniform1i(b.ir.Image, 0)
gl.Uniform2f(b.ir.CanvasSize, float32(b.fw), float32(b.fh))
gl.Uniform1f(b.ir.GlobalAlpha, float32(alpha))
gl.VertexAttribPointer(b.ir.Vertex, 2, gl.FLOAT, false, 0, nil)
gl.VertexAttribPointer(b.ir.TexCoord, 2, gl.FLOAT, false, 0, gl.PtrOffset(8*4))
gl.EnableVertexAttribArray(b.ir.Vertex)
gl.EnableVertexAttribArray(b.ir.TexCoord)
gl.DrawArrays(gl.TRIANGLE_FAN, 0, 4)
gl.DisableVertexAttribArray(b.ir.Vertex)
gl.DisableVertexAttribArray(b.ir.TexCoord)
gl.StencilFunc(gl.ALWAYS, 0, 0xFF)
}
type ImagePattern struct {
b *GoGLBackend
data backendbase.ImagePatternData
}
func (b *GoGLBackend) LoadImagePattern(data backendbase.ImagePatternData) backendbase.ImagePattern {
return &ImagePattern{
b: b,
data: data,
}
}
func (ip *ImagePattern) Delete() {}
func (ip *ImagePattern) Replace(data backendbase.ImagePatternData) { ip.data = data }

View file

@ -1,202 +0,0 @@
package goglbackend
import (
"errors"
"fmt"
"reflect"
"strings"
"unicode"
"unicode/utf8"
"github.com/tfriedel6/canvas/backend/gogl/gl"
)
type shaderProgram struct {
ID, vs, fs uint32
attribs map[string]uint32
uniforms map[string]int32
}
func loadShader(vs, fs string, sp *shaderProgram) error {
glError() // clear the current error
// compile vertex shader
{
sp.vs = gl.CreateShader(gl.VERTEX_SHADER)
csrc, freeFunc := gl.Strs(vs + "\x00")
defer freeFunc()
gl.ShaderSource(sp.vs, 1, csrc, nil)
gl.CompileShader(sp.vs)
var status int32
gl.GetShaderiv(sp.vs, gl.COMPILE_STATUS, &status)
if status != gl.TRUE {
var buf [65536]byte
var length int32
gl.GetShaderInfoLog(sp.vs, int32(len(buf)), &length, &buf[0])
clog := string(buf[:length])
gl.DeleteShader(sp.vs)
return fmt.Errorf("failed to compile vertex shader:\n\n%s", clog)
}
if err := glError(); err != nil {
return fmt.Errorf("gl error after compiling vertex shader: %v", err)
}
}
// compile fragment shader
{
sp.fs = gl.CreateShader(gl.FRAGMENT_SHADER)
csrc, freeFunc := gl.Strs(fs + "\x00")
defer freeFunc()
gl.ShaderSource(sp.fs, 1, csrc, nil)
gl.CompileShader(sp.fs)
var status int32
gl.GetShaderiv(sp.fs, gl.COMPILE_STATUS, &status)
if status != gl.TRUE {
var buf [65536]byte
var length int32
gl.GetShaderInfoLog(sp.fs, int32(len(buf)), &length, &buf[0])
clog := string(buf[:length])
gl.DeleteShader(sp.fs)
return fmt.Errorf("failed to compile fragment shader:\n\n%s", clog)
}
if err := glError(); err != nil {
return fmt.Errorf("gl error after compiling fragment shader: %v", err)
}
}
// link shader program
{
sp.ID = gl.CreateProgram()
gl.AttachShader(sp.ID, sp.vs)
gl.AttachShader(sp.ID, sp.fs)
gl.LinkProgram(sp.ID)
var status int32
gl.GetProgramiv(sp.ID, gl.LINK_STATUS, &status)
if status != gl.TRUE {
var buf [65536]byte
var length int32
gl.GetProgramInfoLog(sp.ID, int32(len(buf)), &length, &buf[0])
clog := string(buf[:length])
gl.DeleteProgram(sp.ID)
gl.DeleteShader(sp.vs)
gl.DeleteShader(sp.fs)
return fmt.Errorf("failed to link shader program:\n\n%s", clog)
}
if err := glError(); err != nil {
return fmt.Errorf("gl error after linking shader: %v", err)
}
}
gl.UseProgram(sp.ID)
var nameBuf [256]byte
var length, size int32
var xtype uint32
var count int32
// load the attributes
gl.GetProgramiv(sp.ID, gl.ACTIVE_ATTRIBUTES, &count)
sp.attribs = make(map[string]uint32, int(count))
for i := int32(0); i < count; i++ {
gl.GetActiveAttrib(sp.ID, uint32(i), int32(len(nameBuf)), &length, &size, &xtype, &nameBuf[0])
name := string(nameBuf[:length])
loc := gl.GetAttribLocation(sp.ID, &nameBuf[0])
sp.attribs[name] = uint32(loc)
}
// load the uniforms
gl.GetProgramiv(sp.ID, gl.ACTIVE_UNIFORMS, &count)
sp.uniforms = make(map[string]int32, int(count))
for i := int32(0); i < count; i++ {
gl.GetActiveUniform(sp.ID, uint32(i), int32(len(nameBuf)), &length, &size, &xtype, &nameBuf[0])
name := string(nameBuf[:length])
loc := gl.GetUniformLocation(sp.ID, &nameBuf[0])
sp.uniforms[name] = loc
}
return nil
}
func (sp *shaderProgram) use() {
gl.UseProgram(sp.ID)
}
func (sp *shaderProgram) delete() {
gl.DeleteProgram(sp.ID)
gl.DeleteShader(sp.vs)
gl.DeleteShader(sp.fs)
}
func (sp *shaderProgram) loadLocations(target interface{}) error {
val := reflect.ValueOf(target)
if val.Kind() != reflect.Ptr {
panic("target must be a pointer to a struct")
}
val = val.Elem()
if val.Kind() != reflect.Struct {
panic("target must be a pointer to a struct")
}
gl.UseProgram(sp.ID)
var errs strings.Builder
for name, loc := range sp.attribs {
field := val.FieldByName(sp.structName(name))
if field == (reflect.Value{}) {
fmt.Fprintf(&errs, "field for attribute \"%s\" not found; ", name)
} else if field.Type() != reflect.TypeOf(uint32(0)) {
fmt.Fprintf(&errs, "field for attribute \"%s\" must have type uint32; ", name)
} else {
field.Set(reflect.ValueOf(uint32(loc)))
}
}
for name, loc := range sp.uniforms {
field := val.FieldByName(sp.structName(name))
if field == (reflect.Value{}) {
fmt.Fprintf(&errs, "field for uniform \"%s\" not found; ", name)
} else if field.Type() != reflect.TypeOf(int32(0)) {
fmt.Fprintf(&errs, "field for uniform \"%s\" must have type int32; ", name)
} else {
field.Set(reflect.ValueOf(int32(loc)))
}
}
if errs.Len() > 0 {
return errors.New(strings.TrimSpace(errs.String()))
}
return nil
}
func (sp *shaderProgram) structName(name string) string {
rn, sz := utf8.DecodeRuneInString(name)
name = fmt.Sprintf("%c%s", unicode.ToUpper(rn), name[sz:])
idx := strings.IndexByte(name, '[')
if idx > 0 {
name = name[:idx]
}
return name
}
func (sp *shaderProgram) mustLoadLocations(target interface{}) {
err := sp.loadLocations(target)
if err != nil {
panic(err)
}
}
func (sp *shaderProgram) enableAllVertexAttribArrays() {
for _, loc := range sp.attribs {
gl.EnableVertexAttribArray(loc)
}
}
func (sp *shaderProgram) disableAllVertexAttribArrays() {
for _, loc := range sp.attribs {
gl.DisableVertexAttribArray(loc)
}
}

View file

@ -1,490 +0,0 @@
package goglbackend
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 sampler2D gradient;
uniform vec2 from, dir;
uniform float len;
uniform float globalAlpha;
void main() {
vec2 v = v_cp - 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 sampler2D gradient;
uniform vec2 from, to;
uniform float radFrom, radTo;
uniform float globalAlpha;
bool isNaN(float v) {
return v < 0.0 || 0.0 < v || v == 0.0 ? false : true;
}
void main() {
float o_a = 0.5 * sqrt(
pow(-2.0*from.x*from.x+2.0*from.x*to.x+2.0*from.x*v_cp.x-2.0*to.x*v_cp.x-2.0*from.y*from.y+2.0*from.y*to.y+2.0*from.y*v_cp.y-2.0*to.y*v_cp.y+2.0*radFrom*radFrom-2.0*radFrom*radTo, 2.0)
-4.0*(from.x*from.x-2.0*from.x*v_cp.x+v_cp.x*v_cp.x+from.y*from.y-2.0*from.y*v_cp.y+v_cp.y*v_cp.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*v_cp.x+to.x*v_cp.x+from.y*from.y-from.y*to.y-from.y*v_cp.y+to.y*v_cp.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 sampler2D image;
uniform mat3 imageTransform;
uniform vec2 repeat;
uniform float globalAlpha;
void main() {
vec3 tfpt = vec3(v_cp, 1.0) * imageTransform;
vec2 imgpt = tfpt.xy / imageSize;
vec4 col = texture2D(image, mod(imgpt, 1.0));
if (imgpt.x < 0.0 || imgpt.x > 1.0) {
col *= repeat.x;
}
if (imgpt.y < 0.0 || imgpt.y > 1.0) {
col *= repeat.y;
}
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 sampler2D gradient;
uniform vec2 from, dir;
uniform float len;
uniform sampler2D alphaTex;
uniform float globalAlpha;
void main() {
vec2 v = v_cp - 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 sampler2D gradient;
uniform vec2 from, to;
uniform float radFrom, radTo;
uniform sampler2D alphaTex;
uniform float globalAlpha;
bool isNaN(float v) {
return v < 0.0 || 0.0 < v || v == 0.0 ? false : true;
}
void main() {
float o_a = 0.5 * sqrt(
pow(-2.0*from.x*from.x+2.0*from.x*to.x+2.0*from.x*v_cp.x-2.0*to.x*v_cp.x-2.0*from.y*from.y+2.0*from.y*to.y+2.0*from.y*v_cp.y-2.0*to.y*v_cp.y+2.0*radFrom*radFrom-2.0*radFrom*radTo, 2.0)
-4.0*(from.x*from.x-2.0*from.x*v_cp.x+v_cp.x*v_cp.x+from.y*from.y-2.0*from.y*v_cp.y+v_cp.y*v_cp.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*v_cp.x+to.x*v_cp.x+from.y*from.y-from.y*to.y-from.y*v_cp.y+to.y*v_cp.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 sampler2D image;
uniform mat3 imageTransform;
uniform vec2 repeat;
uniform sampler2D alphaTex;
uniform float globalAlpha;
void main() {
vec3 tfpt = vec3(v_cp, 1.0) * imageTransform;
vec2 imgpt = tfpt.xy / imageSize;
vec4 col = texture2D(image, mod(imgpt, 1.0));
if (imgpt.x < 0.0 || imgpt.x > 1.0) {
col *= repeat.x;
}
if (imgpt.y < 0.0 || imgpt.y > 1.0) {
col *= repeat.y;
}
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)
}
type solidShader struct {
shaderProgram
Vertex uint32
CanvasSize int32
Color int32
GlobalAlpha int32
}
type imageShader struct {
shaderProgram
Vertex uint32
TexCoord uint32
CanvasSize int32
Image int32
GlobalAlpha int32
}
type linearGradientShader struct {
shaderProgram
Vertex uint32
CanvasSize int32
Gradient int32
From int32
Dir int32
Len int32
GlobalAlpha int32
}
type radialGradientShader struct {
shaderProgram
Vertex uint32
CanvasSize int32
Gradient int32
From int32
To int32
RadFrom int32
RadTo int32
GlobalAlpha int32
}
type imagePatternShader struct {
shaderProgram
Vertex uint32
CanvasSize int32
ImageSize int32
Image int32
ImageTransform int32
Repeat int32
GlobalAlpha int32
}
type solidAlphaShader struct {
shaderProgram
Vertex uint32
AlphaTexCoord uint32
CanvasSize int32
Color int32
AlphaTex int32
GlobalAlpha int32
}
type linearGradientAlphaShader struct {
shaderProgram
Vertex uint32
AlphaTexCoord uint32
CanvasSize int32
Gradient int32
From int32
Dir int32
Len int32
AlphaTex int32
GlobalAlpha int32
}
type radialGradientAlphaShader struct {
shaderProgram
Vertex uint32
AlphaTexCoord uint32
CanvasSize int32
Gradient int32
From int32
To int32
RadFrom int32
RadTo int32
AlphaTex int32
GlobalAlpha int32
}
type imagePatternAlphaShader struct {
shaderProgram
Vertex uint32
AlphaTexCoord uint32
CanvasSize int32
ImageSize int32
Image int32
ImageTransform int32
Repeat int32
AlphaTex int32
GlobalAlpha int32
}
type gaussianShader struct {
shaderProgram
Vertex uint32
TexCoord uint32
CanvasSize int32
KernelScale int32
Image int32
Kernel int32
}

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

View file

@ -1,67 +0,0 @@
package xmobilebackend
import (
"unsafe"
"golang.org/x/mobile/gl"
)
func (b *XMobileBackend) ClearClip() {
b.activate()
b.glctx.StencilMask(0xFF)
b.glctx.Clear(gl.STENCIL_BUFFER_BIT)
}
func (b *XMobileBackend) Clip(pts [][2]float64) {
b.activate()
b.ptsBuf = b.ptsBuf[:0]
b.ptsBuf = append(b.ptsBuf,
0, 0,
0, float32(b.fh),
float32(b.fw), float32(b.fh),
float32(b.fw), 0)
for _, pt := range pts {
b.ptsBuf = append(b.ptsBuf, float32(pt[0]), float32(pt[1]))
}
mode := gl.Enum(gl.TRIANGLES)
if len(pts) == 4 {
mode = gl.TRIANGLE_FAN
}
b.glctx.BindBuffer(gl.ARRAY_BUFFER, b.buf)
b.glctx.BufferData(gl.ARRAY_BUFFER, byteSlice(unsafe.Pointer(&b.ptsBuf[0]), len(b.ptsBuf)*4), gl.STREAM_DRAW)
b.glctx.VertexAttribPointer(b.sr.Vertex, 2, gl.FLOAT, false, 0, 0)
b.glctx.UseProgram(b.sr.ID)
b.glctx.Uniform4f(b.sr.Color, 1, 1, 1, 1)
b.glctx.Uniform2f(b.sr.CanvasSize, float32(b.fw), float32(b.fh))
b.glctx.Uniform1f(b.sr.GlobalAlpha, 1)
b.glctx.EnableVertexAttribArray(b.sr.Vertex)
b.glctx.ColorMask(false, false, false, false)
b.glctx.StencilMask(0x04)
b.glctx.StencilFunc(gl.ALWAYS, 4, 0x04)
b.glctx.StencilOp(gl.REPLACE, gl.REPLACE, gl.REPLACE)
b.glctx.DrawArrays(mode, 4, len(pts))
b.glctx.StencilMask(0x02)
b.glctx.StencilFunc(gl.EQUAL, 0, 0x06)
b.glctx.StencilOp(gl.KEEP, gl.INVERT, gl.INVERT)
b.glctx.DrawArrays(gl.TRIANGLE_FAN, 0, 4)
b.glctx.StencilMask(0x04)
b.glctx.StencilFunc(gl.ALWAYS, 0, 0x04)
b.glctx.StencilOp(gl.ZERO, gl.ZERO, gl.ZERO)
b.glctx.DrawArrays(gl.TRIANGLE_FAN, 0, 4)
b.glctx.DisableVertexAttribArray(b.sr.Vertex)
b.glctx.ColorMask(true, true, true, true)
b.glctx.StencilOp(gl.KEEP, gl.KEEP, gl.KEEP)
b.glctx.StencilMask(0xFF)
b.glctx.StencilFunc(gl.EQUAL, 0, 0xFF)
}

View file

@ -1,308 +0,0 @@
package xmobilebackend
import (
"image"
"math"
"unsafe"
"github.com/tfriedel6/canvas/backend/backendbase"
"golang.org/x/mobile/gl"
)
func (b *XMobileBackend) Clear(pts [4][2]float64) {
b.activate()
// first check if the four points are aligned to form a nice rectangle, which can be more easily
// cleared using glScissor and glClear
aligned := pts[0][0] == pts[1][0] && pts[2][0] == pts[3][0] && pts[0][1] == pts[3][1] && pts[1][1] == pts[2][1]
if !aligned {
aligned = pts[0][0] == pts[3][0] && pts[1][0] == pts[2][0] && pts[0][1] == pts[1][1] && pts[2][1] == pts[3][1]
}
if aligned {
minX := math.Floor(math.Min(pts[0][0], pts[2][0]))
maxX := math.Ceil(math.Max(pts[0][0], pts[2][0]))
minY := math.Floor(math.Min(pts[0][1], pts[2][1]))
maxY := math.Ceil(math.Max(pts[0][1], pts[2][1]))
b.clearRect(int(minX), int(minY), int(maxX)-int(minX), int(maxY)-int(minY))
return
}
data := [8]float32{
float32(pts[0][0]), float32(pts[0][1]),
float32(pts[1][0]), float32(pts[1][1]),
float32(pts[2][0]), float32(pts[2][1]),
float32(pts[3][0]), float32(pts[3][1])}
b.glctx.UseProgram(b.sr.ID)
b.glctx.Uniform2f(b.sr.CanvasSize, float32(b.fw), float32(b.fh))
b.glctx.Uniform4f(b.sr.Color, 0, 0, 0, 0)
b.glctx.Uniform1f(b.sr.GlobalAlpha, 1)
b.glctx.Disable(gl.BLEND)
b.glctx.BindBuffer(gl.ARRAY_BUFFER, b.buf)
b.glctx.BufferData(gl.ARRAY_BUFFER, byteSlice(unsafe.Pointer(&data[0]), len(data)*4), gl.STREAM_DRAW)
b.glctx.StencilFunc(gl.EQUAL, 0, 0xFF)
b.glctx.VertexAttribPointer(b.sr.Vertex, 2, gl.FLOAT, false, 0, 0)
b.glctx.EnableVertexAttribArray(b.sr.Vertex)
b.glctx.DrawArrays(gl.TRIANGLE_FAN, 0, 4)
b.glctx.DisableVertexAttribArray(b.sr.Vertex)
b.glctx.StencilFunc(gl.ALWAYS, 0, 0xFF)
b.glctx.Enable(gl.BLEND)
}
func (b *XMobileBackend) clearRect(x, y, w, h int) {
b.glctx.Enable(gl.SCISSOR_TEST)
var box [4]int32
b.glctx.GetIntegerv(box[:], gl.SCISSOR_BOX)
b.glctx.Scissor(int32(x), int32(b.h-y-h), int32(w), int32(h))
b.glctx.ClearColor(0, 0, 0, 0)
b.glctx.Clear(gl.COLOR_BUFFER_BIT)
b.glctx.Scissor(box[0], box[1], box[2], box[3])
b.glctx.Disable(gl.SCISSOR_TEST)
}
func (b *XMobileBackend) Fill(style *backendbase.FillStyle, pts [][2]float64) {
b.activate()
if style.Blur > 0 {
b.offscr1.alpha = true
b.enableTextureRenderTarget(&b.offscr1)
b.glctx.ClearColor(0, 0, 0, 0)
b.glctx.Clear(gl.COLOR_BUFFER_BIT | gl.STENCIL_BUFFER_BIT)
}
b.ptsBuf = b.ptsBuf[:0]
b.ptsBuf = append(b.ptsBuf,
0, 0,
0, float32(b.fh),
float32(b.fw), float32(b.fh),
float32(b.fw), 0)
for _, pt := range pts {
b.ptsBuf = append(b.ptsBuf, float32(pt[0]), float32(pt[1]))
}
mode := gl.Enum(gl.TRIANGLES)
if len(pts) == 4 {
mode = gl.TRIANGLE_FAN
}
b.glctx.BindBuffer(gl.ARRAY_BUFFER, b.buf)
b.glctx.BufferData(gl.ARRAY_BUFFER, byteSlice(unsafe.Pointer(&b.ptsBuf[0]), len(b.ptsBuf)*4), gl.STREAM_DRAW)
if style.Color.A >= 255 {
vertex := b.useShader(style)
b.glctx.StencilFunc(gl.EQUAL, 0, 0xFF)
b.glctx.EnableVertexAttribArray(vertex)
b.glctx.VertexAttribPointer(vertex, 2, gl.FLOAT, false, 0, 0)
b.glctx.DrawArrays(mode, 4, len(pts))
b.glctx.DisableVertexAttribArray(vertex)
b.glctx.StencilFunc(gl.ALWAYS, 0, 0xFF)
} else {
b.glctx.ColorMask(false, false, false, false)
b.glctx.StencilFunc(gl.ALWAYS, 1, 0xFF)
b.glctx.StencilOp(gl.REPLACE, gl.REPLACE, gl.REPLACE)
b.glctx.StencilMask(0x01)
b.glctx.UseProgram(b.sr.ID)
b.glctx.Uniform4f(b.sr.Color, 0, 0, 0, 0)
b.glctx.Uniform2f(b.sr.CanvasSize, float32(b.fw), float32(b.fh))
b.glctx.Uniform1f(b.sr.GlobalAlpha, 1)
b.glctx.EnableVertexAttribArray(b.sr.Vertex)
b.glctx.VertexAttribPointer(b.sr.Vertex, 2, gl.FLOAT, false, 0, 0)
b.glctx.DrawArrays(mode, 4, len(pts))
b.glctx.DisableVertexAttribArray(b.sr.Vertex)
b.glctx.ColorMask(true, true, true, true)
b.glctx.StencilFunc(gl.EQUAL, 1, 0xFF)
vertex := b.useShader(style)
b.glctx.EnableVertexAttribArray(vertex)
b.glctx.VertexAttribPointer(vertex, 2, gl.FLOAT, false, 0, 0)
b.ptsBuf = append(b.ptsBuf[:0], 0, 0, float32(b.fw), 0, float32(b.fw), float32(b.fh), 0, float32(b.fh))
b.glctx.DrawArrays(gl.TRIANGLE_FAN, 0, 4)
b.glctx.DisableVertexAttribArray(vertex)
b.glctx.StencilOp(gl.KEEP, gl.KEEP, gl.KEEP)
b.glctx.StencilFunc(gl.ALWAYS, 0, 0xFF)
b.glctx.Clear(gl.STENCIL_BUFFER_BIT)
b.glctx.StencilMask(0xFF)
}
if style.Blur > 0 {
b.drawBlurred(style.Blur)
}
}
func (b *XMobileBackend) FillImageMask(style *backendbase.FillStyle, mask *image.Alpha, pts [][2]float64) {
b.activate()
w, h := mask.Rect.Dx(), mask.Rect.Dy()
b.glctx.ActiveTexture(gl.TEXTURE1)
b.glctx.BindTexture(gl.TEXTURE_2D, b.alphaTex)
for y := 0; y < h; y++ {
off := y * mask.Stride
b.glctx.TexSubImage2D(gl.TEXTURE_2D, 0, 0, alphaTexSize-1-y, w, 1, gl.ALPHA, gl.UNSIGNED_BYTE, mask.Pix[off:])
}
if style.Blur > 0 {
b.offscr1.alpha = true
b.enableTextureRenderTarget(&b.offscr1)
b.glctx.ClearColor(0, 0, 0, 0)
b.glctx.Clear(gl.COLOR_BUFFER_BIT | gl.STENCIL_BUFFER_BIT)
}
b.glctx.StencilFunc(gl.EQUAL, 0, 0xFF)
b.glctx.BindBuffer(gl.ARRAY_BUFFER, b.buf)
vertex, alphaTexCoord := b.useAlphaShader(style, 1)
b.glctx.EnableVertexAttribArray(vertex)
b.glctx.EnableVertexAttribArray(alphaTexCoord)
tw := float64(w) / alphaTexSize
th := float64(h) / alphaTexSize
var buf [16]float32
data := buf[:0]
for _, pt := range pts {
data = append(data, float32(pt[0]), float32(pt[1]))
}
data = append(data, 0, 1, 0, float32(1-th), float32(tw), float32(1-th), float32(tw), 1)
b.glctx.BufferData(gl.ARRAY_BUFFER, byteSlice(unsafe.Pointer(&data[0]), len(data)*4), gl.STREAM_DRAW)
b.glctx.VertexAttribPointer(vertex, 2, gl.FLOAT, false, 0, 0)
b.glctx.VertexAttribPointer(alphaTexCoord, 2, gl.FLOAT, false, 0, 8*4)
b.glctx.DrawArrays(gl.TRIANGLE_FAN, 0, 4)
b.glctx.DisableVertexAttribArray(vertex)
b.glctx.DisableVertexAttribArray(alphaTexCoord)
b.glctx.ActiveTexture(gl.TEXTURE1)
b.glctx.BindTexture(gl.TEXTURE_2D, b.alphaTex)
b.glctx.StencilFunc(gl.ALWAYS, 0, 0xFF)
for y := 0; y < h; y++ {
b.glctx.TexSubImage2D(gl.TEXTURE_2D, 0, 0, alphaTexSize-1-y, w, 1, gl.ALPHA, gl.UNSIGNED_BYTE, zeroes[0:])
}
b.glctx.ActiveTexture(gl.TEXTURE0)
if style.Blur > 0 {
b.drawBlurred(style.Blur)
}
}
func (b *XMobileBackend) drawBlurred(blur float64) {
b.glctx.BlendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA)
var kernel []float32
var kernelBuf [255]float32
var gs *gaussianShader
if blur < 3 {
gs = &b.gauss15r
kernel = kernelBuf[:15]
} else if blur < 12 {
gs = &b.gauss63r
kernel = kernelBuf[:63]
} else {
gs = &b.gauss127r
kernel = kernelBuf[:127]
}
gaussianKernel(blur, kernel)
b.offscr2.alpha = true
b.enableTextureRenderTarget(&b.offscr2)
b.glctx.ClearColor(0, 0, 0, 0)
b.glctx.Clear(gl.COLOR_BUFFER_BIT | gl.STENCIL_BUFFER_BIT)
b.glctx.StencilFunc(gl.EQUAL, 0, 0xFF)
b.glctx.BindBuffer(gl.ARRAY_BUFFER, b.shadowBuf)
data := [16]float32{0, 0, 0, float32(b.h), float32(b.w), float32(b.h), float32(b.w), 0, 0, 0, 0, 1, 1, 1, 1, 0}
b.glctx.BufferData(gl.ARRAY_BUFFER, byteSlice(unsafe.Pointer(&data[0]), len(data)*4), gl.STREAM_DRAW)
b.glctx.ActiveTexture(gl.TEXTURE0)
b.glctx.BindTexture(gl.TEXTURE_2D, b.offscr1.tex)
b.glctx.UseProgram(gs.ID)
b.glctx.Uniform1i(gs.Image, 0)
b.glctx.Uniform2f(gs.CanvasSize, float32(b.fw), float32(b.fh))
b.glctx.Uniform2f(gs.KernelScale, 1.0/float32(b.fw), 0.0)
b.glctx.Uniform1fv(gs.Kernel, kernel)
b.glctx.VertexAttribPointer(gs.Vertex, 2, gl.FLOAT, false, 0, 0)
b.glctx.VertexAttribPointer(gs.TexCoord, 2, gl.FLOAT, false, 0, 8*4)
b.glctx.EnableVertexAttribArray(gs.Vertex)
b.glctx.EnableVertexAttribArray(gs.TexCoord)
b.glctx.DrawArrays(gl.TRIANGLE_FAN, 0, 4)
b.glctx.DisableVertexAttribArray(gs.Vertex)
b.glctx.DisableVertexAttribArray(gs.TexCoord)
b.glctx.StencilFunc(gl.ALWAYS, 0, 0xFF)
b.disableTextureRenderTarget()
b.glctx.StencilFunc(gl.EQUAL, 0, 0xFF)
b.glctx.BindBuffer(gl.ARRAY_BUFFER, b.shadowBuf)
data = [16]float32{0, 0, 0, float32(b.h), float32(b.w), float32(b.h), float32(b.w), 0, 0, 0, 0, 1, 1, 1, 1, 0}
b.glctx.BufferData(gl.ARRAY_BUFFER, byteSlice(unsafe.Pointer(&data[0]), len(data)*4), gl.STREAM_DRAW)
b.glctx.ActiveTexture(gl.TEXTURE0)
b.glctx.BindTexture(gl.TEXTURE_2D, b.offscr2.tex)
b.glctx.UseProgram(gs.ID)
b.glctx.Uniform1i(gs.Image, 0)
b.glctx.Uniform2f(gs.CanvasSize, float32(b.fw), float32(b.fh))
b.glctx.Uniform2f(gs.KernelScale, 0.0, 1.0/float32(b.fh))
b.glctx.Uniform1fv(gs.Kernel, kernel)
b.glctx.VertexAttribPointer(gs.Vertex, 2, gl.FLOAT, false, 0, 0)
b.glctx.VertexAttribPointer(gs.TexCoord, 2, gl.FLOAT, false, 0, 8*4)
b.glctx.EnableVertexAttribArray(gs.Vertex)
b.glctx.EnableVertexAttribArray(gs.TexCoord)
b.glctx.DrawArrays(gl.TRIANGLE_FAN, 0, 4)
b.glctx.DisableVertexAttribArray(gs.Vertex)
b.glctx.DisableVertexAttribArray(gs.TexCoord)
b.glctx.StencilFunc(gl.ALWAYS, 0, 0xFF)
b.glctx.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
}
}

View file

@ -1,434 +0,0 @@
package main
import (
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
)
func main() {
{ // make sure we are in the right directory
dir, err := os.Getwd()
if err != nil {
log.Fatalf("Failed to get working directory: %v", err)
}
d1 := filepath.Base(dir)
d2 := filepath.Base(filepath.Dir(dir))
if d2 != "backend" || d1 != "xmobile" {
log.Fatalln("This must be run in the backend/xmobile directory")
}
}
{ // delete existing files
fis, err := ioutil.ReadDir(".")
if err != nil {
log.Fatalf("Failed to read current dir: %v", err)
}
for _, fi := range fis {
if fi.IsDir() || fi.Name() == "shader.go" {
continue
}
err = os.Remove(fi.Name())
if err != nil {
log.Fatalf("Failed to delete file %s: %v", fi.Name(), err)
}
}
}
{ // copy gogl files
fis, err := ioutil.ReadDir("../gogl")
if err != nil {
log.Fatalf("Failed to read dir ../gogl: %v", err)
}
for _, fi := range fis {
if !strings.HasSuffix(fi.Name(), ".go") || fi.Name() == "shader.go" {
continue
}
path := filepath.Join("../gogl", fi.Name())
data, err := ioutil.ReadFile(path)
if err != nil {
log.Fatalf("Failed to read file %s: %v", path, err)
}
filename, rewritten := rewrite(fi.Name(), string(data))
err = ioutil.WriteFile(filename, ([]byte)(rewritten), 0777)
if err != nil {
log.Fatalf("Failed to write file %s: %v", fi.Name(), err)
}
}
}
err := exec.Command("go", "fmt").Run()
if err != nil {
log.Fatalf("Failed to run go fmt: %v", err)
}
}
func rewrite(filename, src string) (string, string) {
src = strings.Replace(src, `package goglbackend`, `package xmobilebackend`, 1)
src = strings.Replace(src, `"github.com/tfriedel6/canvas/backend/gogl/gl"`, `"golang.org/x/mobile/gl"`, 1)
src = strings.Replace(src, "\tgl.", "\tb.glctx.", -1)
src = strings.Replace(src, "GoGLBackend", "XMobileBackend", -1)
src = strings.Replace(src, "uint32(gl.TRIANGLES)", "gl.Enum(gl.TRIANGLES)", -1)
src = strings.Replace(src, `func (g *gradient) Delete() {`,
`func (g *gradient) Delete() {
b := g.b`, -1)
src = strings.Replace(src, `func (g *gradient) load(stops backendbase.Gradient) {`,
`func (g *gradient) load(stops backendbase.Gradient) {
b := g.b`, -1)
src = strings.Replace(src, `func (img *Image) Delete() {`,
`func (img *Image) Delete() {
b := img.b`, -1)
src = strings.Replace(src, `func (img *Image) Replace(src image.Image) error {`,
`func (img *Image) Replace(src image.Image) error {
b := img.b`, -1)
src = strings.Replace(src, `imageBufTex == 0`, `imageBufTex.Value == 0`, -1)
src = strings.Replace(src,
`loadImage(src image.Image, tex uint32)`,
`loadImage(b *XMobileBackend, src image.Image, tex gl.Texture)`, -1)
src = strings.Replace(src,
`loadImageRGBA(src *image.RGBA, tex uint32)`,
`loadImageRGBA(b *XMobileBackend, src *image.RGBA, tex gl.Texture)`, -1)
src = strings.Replace(src,
`loadImageGray(src *image.Gray, tex uint32)`,
`loadImageGray(b *XMobileBackend, src *image.Gray, tex gl.Texture)`, -1)
src = strings.Replace(src,
`loadImageConverted(src image.Image, tex uint32)`,
`loadImageConverted(b *XMobileBackend, src image.Image, tex gl.Texture)`, -1)
src = strings.Replace(src,
`func loadShader(vs, fs string, sp *shaderProgram) error {`,
`func loadShader(b *XMobileBackend, vs, fs string, sp *shaderProgram) error {
sp.b = b`, -1)
src = strings.Replace(src, `func glError() error {
glErr := gl.GetError()
`, `func glError(b *XMobileBackend) error {
glErr := b.glctx.GetError()
`, -1)
src = rewriteCalls(src, "glError", func(params []string) string {
return "glError(b)"
})
src = regexp.MustCompile(`[ \t]+tex[ ]+uint32`).ReplaceAllString(src, "\ttex gl.Texture")
src = rewriteCalls(src, "b.glctx.BufferData", func(params []string) string {
return "b.glctx.BufferData(" + params[0] + ", byteSlice(" + params[2] + ", " + params[1] + "), " + params[3] + ")"
})
src = rewriteCalls(src, "b.glctx.VertexAttribPointer", func(params []string) string {
params[5] = strings.Replace(params[5], "gl.PtrOffset(", "", 1)
params[5] = strings.Replace(params[5], ")", "", 1)
params[5] = strings.Replace(params[5], "nil", "0", 1)
return "b.glctx.VertexAttribPointer(" + strings.Join(params, ",") + ")"
})
src = rewriteCalls(src, "b.glctx.DrawArrays", func(params []string) string {
if strings.HasPrefix(params[2], "int32(") {
params[2] = params[2][6 : len(params[2])-1]
}
return "b.glctx.DrawArrays(" + strings.Join(params, ",") + ")"
})
src = rewriteCalls(src, "b.glctx.Uniform1fv", func(params []string) string {
params[2] = params[2][1 : len(params[2])-3]
return "b.glctx.Uniform1fv(" + params[0] + ", " + params[2] + ")"
})
src = rewriteCalls(src, "b.glctx.UniformMatrix3fv", func(params []string) string {
return "b.glctx.UniformMatrix3fv(" + params[0] + ", " + params[3][1:len(params[3])-3] + "[:])"
})
src = rewriteCalls(src, "b.glctx.TexImage2D", func(params []string) string {
params = append(params[:5], params[6:]...)
for i, param := range params {
if strings.HasPrefix(param, "int32(") {
params[i] = param[6 : len(param)-1]
} else if strings.HasPrefix(param, "gl.Ptr(") {
params[i] = param[8:len(param)-2] + ":]"
}
}
return "b.glctx.TexImage2D(" + strings.Join(params, ", ") + ")"
})
src = rewriteCalls(src, "b.glctx.TexSubImage2D", func(params []string) string {
for i, param := range params {
if strings.HasPrefix(param, "int32(") {
params[i] = param[6 : len(param)-1]
} else if strings.HasPrefix(param, "gl.Ptr(") {
params[i] = param[8:len(param)-2] + ":]"
}
}
return "b.glctx.TexSubImage2D(" + strings.Join(params, ", ") + ")"
})
src = rewriteCalls(src, "b.glctx.GetIntegerv", func(params []string) string {
return "b.glctx.GetIntegerv(" + params[1][1:len(params[1])-2] + ":], " + params[0] + ")"
})
src = rewriteCalls(src, "b.glctx.GenTextures", func(params []string) string {
return params[1][1:] + " = b.glctx.CreateTexture()"
})
src = rewriteCalls(src, "b.glctx.DeleteTextures", func(params []string) string {
return "b.glctx.DeleteTexture(" + params[1][1:] + ")"
})
src = rewriteCalls(src, "b.glctx.GenBuffers", func(params []string) string {
return params[1][1:] + " = b.glctx.CreateBuffer()"
})
src = rewriteCalls(src, "b.glctx.GenFramebuffers", func(params []string) string {
return params[1][1:] + " = b.glctx.CreateFramebuffer()"
})
src = rewriteCalls(src, "b.glctx.DeleteFramebuffers", func(params []string) string {
return "b.glctx.DeleteFramebuffer(" + params[1][1:] + ")"
})
src = rewriteCalls(src, "b.glctx.GenRenderbuffers", func(params []string) string {
return params[1][1:] + " = b.glctx.CreateRenderbuffer()"
})
src = rewriteCalls(src, "b.glctx.DeleteRenderbuffers", func(params []string) string {
return "b.glctx.DeleteRenderbuffer(" + params[1][1:] + ")"
})
src = rewriteCalls(src, "b.glctx.RenderbufferStorage", func(params []string) string {
for i, param := range params {
if strings.HasPrefix(param, "int32(") {
params[i] = param[6 : len(param)-1]
}
}
return "b.glctx.RenderbufferStorage(" + strings.Join(params, ", ") + ")"
})
src = rewriteCalls(src, "gl.CheckFramebufferStatus", func(params []string) string {
return "b.glctx.CheckFramebufferStatus(" + strings.Join(params, ", ") + ")"
})
src = rewriteCalls(src, "b.glctx.BindFramebuffer", func(params []string) string {
if params[1] == "0" {
params[1] = "gl.Framebuffer{Value: 0}"
}
return "b.glctx.BindFramebuffer(" + strings.Join(params, ", ") + ")"
})
src = rewriteCalls(src, "b.glctx.ReadPixels", func(params []string) string {
for i, param := range params {
if strings.HasPrefix(param, "int32(") {
params[i] = param[6 : len(param)-1]
} else if strings.HasPrefix(param, "gl.Ptr(") {
params[i] = param[8:len(param)-2] + ":]"
}
}
return "b.glctx.ReadPixels(" + params[6] + ", " + strings.Join(params[:len(params)-1], ", ") + ")"
})
src = rewriteCalls(src, "b.glctx.Viewport", func(params []string) string {
for i, param := range params {
if strings.HasPrefix(param, "int32(") {
params[i] = param[6 : len(param)-1]
}
}
return "b.glctx.Viewport(" + strings.Join(params, ", ") + ")"
})
src = rewriteCalls(src, "loadImage", func(params []string) string {
return "loadImage(b, " + strings.Join(params, ", ") + ")"
})
src = rewriteCalls(src, "loadImageRGBA", func(params []string) string {
return "loadImageRGBA(b, " + strings.Join(params, ", ") + ")"
})
src = rewriteCalls(src, "loadImageGray", func(params []string) string {
return "loadImageGray(b, " + strings.Join(params, ", ") + ")"
})
src = rewriteCalls(src, "loadImageConverted", func(params []string) string {
return "loadImageConverted(b, " + strings.Join(params, ", ") + ")"
})
src = rewriteCalls(src, "loadShader", func(params []string) string {
return "loadShader(b, " + strings.Join(params, ", ") + ")"
})
if filename == "gogl.go" {
filename = "xmobile.go"
src = rewriteMain(src)
} else if filename == "shaders.go" {
src = rewriteShaders(src)
}
return filename, src
}
func rewriteMain(src string) string {
src = strings.Replace(src, "type GLContext struct {\n",
"type GLContext struct {\n\tglctx gl.Context\n\n", 1)
src = strings.Replace(src, "ctx := &GLContext{\n",
"ctx := &GLContext{\n\t\tglctx: glctx,\n\n", 1)
src = strings.Replace(src, "\tb.glctx.GetError() // clear error state\n",
"\tb := &XMobileBackend{GLContext: ctx}\n\n\tb.glctx.GetError() // clear error state\n\n", 1)
src = strings.Replace(src, "type XMobileBackend struct {\n",
"type XMobileBackend struct {\n", 1)
src = strings.Replace(src, "func NewGLContext() (*GLContext, error) {",
"func NewGLContext(glctx gl.Context) (*GLContext, error) {", 1)
src = strings.Replace(src, "TextureID uint32", "TextureID gl.Texture", 1)
src = strings.Replace(src,
` err := gl.Init()
if err != nil {
return nil, err
}
`, ` var err error
`, 1)
src = strings.Replace(src,
`// New returns a new canvas backend. x, y, w, h define the target
// rectangle in the window. ctx is a GLContext created with
// NewGLContext, but can be nil for a default one. It makes sense
// to pass one in when using for example an onscreen and an
// offscreen backend using the same GL context.
`, `// New returns a new canvas backend. x, y, w, h define the target
// rectangle in the window. ctx is a GLContext created with
// NewGLContext
`, 1)
src = strings.Replace(src,
`// NewOffscreen returns a new offscreen canvas backend. w, h define
// the size of the offscreen texture. ctx is a GLContext created
// with NewGLContext, but can be nil for a default one. It makes
// sense to pass one in when using for example an onscreen and an
// offscreen backend using the same GL context.
`, `// NewOffscreen returns a new offscreen canvas backend. w, h define
// the size of the offscreen texture. ctx is a GLContext created
// with NewGLContext
`, 1)
src = strings.Replace(src,
` if ctx == nil {
var err error
ctx, err = NewGLContext()
if err != nil {
return nil, err
}
}
`, "", 1)
src = strings.Replace(src,
` buf uint32
shadowBuf uint32
alphaTex uint32
`,
` buf gl.Buffer
shadowBuf gl.Buffer
alphaTex gl.Texture
`, 1)
src = strings.Replace(src, `imageBufTex uint32`, `imageBufTex gl.Texture`, 1)
src = strings.Replace(src,
`type offscreenBuffer struct {
tex gl.Texture
w int
h int
renderStencilBuf uint32
frameBuf uint32
alpha bool
}
`,
`type offscreenBuffer struct {
tex gl.Texture
w int
h int
renderStencilBuf gl.Renderbuffer
frameBuf gl.Framebuffer
alpha bool
}
`, 1)
src = src + `
func byteSlice(ptr unsafe.Pointer, size int) []byte {
var buf []byte
sh := (*reflect.SliceHeader)(unsafe.Pointer(&buf))
sh.Cap = size
sh.Len = size
sh.Data = uintptr(ptr)
return buf
}
`
src = strings.Replace(src, "import (\n",
`import (
"unsafe"
"reflect"
`, 1)
src = strings.Replace(src, "vertexLoc uint32", "vertexLoc gl.Attrib", 1)
src = strings.Replace(src, "alphaTexSlot int32", "alphaTexSlot int", 1)
src = strings.Replace(src, "vertexLoc, alphaTexCoordLoc uint32", "vertexLoc, alphaTexCoordLoc gl.Attrib", 1)
return src
}
func rewriteShaders(src string) string {
src = strings.Replace(src,
`import (
"bytes"
"fmt"
"strings"
)
`,
`import (
"bytes"
"fmt"
"strings"
"golang.org/x/mobile/gl"
)
`, 1)
src = strings.Replace(src, "uint32", "gl.Attrib", -1)
src = strings.Replace(src, "int32", "gl.Uniform", -1)
return src
}
func rewriteCalls(src, funcName string, fn func([]string) string) string {
rewritten := ""
pos := 0
for {
idx := strings.Index(src[pos:], funcName)
if idx == -1 {
rewritten += src[pos:]
break
}
idx += pos
rewritten += src[pos:idx]
parenStart := idx + len(funcName)
if idx > 5 && src[idx-5:idx] == "func " {
rewritten += src[idx:parenStart]
pos = parenStart
continue
}
if src[parenStart] != '(' {
rewritten += src[idx:parenStart]
pos = parenStart
continue
}
params := make([]string, 0, 10)
parenDepth := 0
paramStart := 0
paramsStr := src[parenStart+1:]
paramloop:
for i, rn := range paramsStr {
switch rn {
case '(':
parenDepth++
case ')':
parenDepth--
if parenDepth == -1 {
params = append(params, strings.TrimSpace(paramsStr[paramStart:i]))
pos = parenStart + i + 2
break paramloop
}
case ',':
params = append(params, strings.TrimSpace(paramsStr[paramStart:i]))
paramStart = i + 1
}
}
rewritten += fn(params)
}
return rewritten
}

View file

@ -1,110 +0,0 @@
package xmobilebackend
import (
"runtime"
"github.com/tfriedel6/canvas/backend/backendbase"
"golang.org/x/mobile/gl"
)
// LinearGradient is a gradient with any number of
// stops and any number of colors. The gradient will
// be drawn such that each point on the gradient
// will correspond to a straight line
type LinearGradient struct {
gradient
}
// RadialGradient is a gradient with any number of
// stops and any number of colors. The gradient will
// be drawn such that each point on the gradient
// will correspond to a circle
type RadialGradient struct {
gradient
}
type gradient struct {
b *XMobileBackend
tex gl.Texture
loaded bool
}
func (b *XMobileBackend) LoadLinearGradient(data backendbase.Gradient) backendbase.LinearGradient {
b.activate()
lg := &LinearGradient{
gradient: gradient{b: b},
}
lg.tex = b.glctx.CreateTexture()
b.glctx.ActiveTexture(gl.TEXTURE0)
b.glctx.BindTexture(gl.TEXTURE_2D, lg.tex)
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
lg.load(data)
runtime.SetFinalizer(lg, func(lg *LinearGradient) {
b.glChan <- func() {
b.glctx.DeleteTexture(lg.tex)
}
})
return lg
}
func (b *XMobileBackend) LoadRadialGradient(data backendbase.Gradient) backendbase.RadialGradient {
b.activate()
rg := &RadialGradient{
gradient: gradient{b: b},
}
rg.tex = b.glctx.CreateTexture()
b.glctx.ActiveTexture(gl.TEXTURE0)
b.glctx.BindTexture(gl.TEXTURE_2D, rg.tex)
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
rg.load(data)
runtime.SetFinalizer(rg, func(rg *RadialGradient) {
b.glChan <- func() {
b.glctx.DeleteTexture(rg.tex)
}
})
return rg
}
// Delete explicitly deletes the gradient
func (g *gradient) Delete() {
b := g.b
g.b.activate()
b.glctx.DeleteTexture(g.tex)
}
func (lg *LinearGradient) Replace(data backendbase.Gradient) { lg.load(data) }
func (rg *RadialGradient) Replace(data backendbase.Gradient) { rg.load(data) }
func (g *gradient) load(stops backendbase.Gradient) {
b := g.b
if g.loaded {
return
}
g.b.activate()
b.glctx.ActiveTexture(gl.TEXTURE0)
b.glctx.BindTexture(gl.TEXTURE_2D, g.tex)
var pixels [2048 * 4]byte
pp := 0
for i := 0; i < 2048; i++ {
c := stops.ColorAt(float64(i) / 2047)
pixels[pp] = c.R
pixels[pp+1] = c.G
pixels[pp+2] = c.B
pixels[pp+3] = c.A
pp += 4
}
b.glctx.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 2048, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixels[0:])
g.loaded = true
}

View file

@ -1,94 +0,0 @@
package xmobilebackend
import (
"image"
"image/color"
"unsafe"
"golang.org/x/mobile/gl"
)
// GetImageData returns an RGBA image of the current image
func (b *XMobileBackend) GetImageData(x, y, w, h int) *image.RGBA {
b.activate()
if x < 0 {
w += x
x = 0
}
if y < 0 {
h += y
y = 0
}
if w > b.w {
w = b.w
}
if h > b.h {
h = b.h
}
if len(b.imageBuf) < w*h*3 {
b.imageBuf = make([]byte, w*h*3)
}
b.glctx.ReadPixels(b.imageBuf[0:], x, y, w, h, gl.RGB, gl.UNSIGNED_BYTE)
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: b.imageBuf[bp], G: b.imageBuf[bp+1], B: b.imageBuf[bp+2], A: 255})
bp += 3
}
}
return rgba
}
// PutImageData puts the given image at the given x/y coordinates
func (b *XMobileBackend) PutImageData(img *image.RGBA, x, y int) {
b.activate()
b.glctx.ActiveTexture(gl.TEXTURE0)
if b.imageBufTex.Value == 0 {
b.imageBufTex = b.glctx.CreateTexture()
b.glctx.BindTexture(gl.TEXTURE_2D, b.imageBufTex)
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
} else {
b.glctx.BindTexture(gl.TEXTURE_2D, b.imageBufTex)
}
w, h := img.Bounds().Dx(), img.Bounds().Dy()
if img.Stride == img.Bounds().Dx()*4 {
b.glctx.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, w, h, gl.RGBA, gl.UNSIGNED_BYTE, 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]...)
}
b.glctx.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, w, h, gl.RGBA, gl.UNSIGNED_BYTE, data[0:])
}
dx, dy := float32(x), float32(y)
dw, dh := float32(w), float32(h)
b.glctx.BindBuffer(gl.ARRAY_BUFFER, b.buf)
data := [16]float32{dx, dy, dx + dw, dy, dx + dw, dy + dh, dx, dy + dh,
0, 0, 1, 0, 1, 1, 0, 1}
b.glctx.BufferData(gl.ARRAY_BUFFER, byteSlice(unsafe.Pointer(&data[0]), len(data)*4), gl.STREAM_DRAW)
b.glctx.UseProgram(b.ir.ID)
b.glctx.Uniform1i(b.ir.Image, 0)
b.glctx.Uniform2f(b.ir.CanvasSize, float32(b.fw), float32(b.fh))
b.glctx.Uniform1f(b.ir.GlobalAlpha, 1)
b.glctx.VertexAttribPointer(b.ir.Vertex, 2, gl.FLOAT, false, 0, 0)
b.glctx.VertexAttribPointer(b.ir.TexCoord, 2, gl.FLOAT, false, 0, 8*4)
b.glctx.EnableVertexAttribArray(b.ir.Vertex)
b.glctx.EnableVertexAttribArray(b.ir.TexCoord)
b.glctx.DrawArrays(gl.TRIANGLE_FAN, 0, 4)
b.glctx.DisableVertexAttribArray(b.ir.Vertex)
b.glctx.DisableVertexAttribArray(b.ir.TexCoord)
}

View file

@ -1,223 +0,0 @@
package xmobilebackend
import (
"errors"
"image"
"runtime"
"unsafe"
"github.com/tfriedel6/canvas/backend/backendbase"
"golang.org/x/mobile/gl"
)
// Image represents a loaded image that can be used in various drawing functions
type Image struct {
b *XMobileBackend
w, h int
tex gl.Texture
flip bool
}
func (b *XMobileBackend) LoadImage(src image.Image) (backendbase.Image, error) {
b.activate()
var tex gl.Texture
tex = b.glctx.CreateTexture()
b.glctx.ActiveTexture(gl.TEXTURE0)
b.glctx.BindTexture(gl.TEXTURE_2D, tex)
if src == nil {
return &Image{tex: tex}, nil
}
img, err := loadImage(b, src, tex)
if err != nil {
return nil, err
}
img.b = b
runtime.SetFinalizer(img, func(img *Image) {
b.glChan <- func() {
b.glctx.DeleteTexture(img.tex)
}
})
return img, nil
}
func loadImage(b *XMobileBackend, src image.Image, tex gl.Texture) (*Image, error) {
var img *Image
var err error
switch v := src.(type) {
case *image.RGBA:
img, err = loadImageRGBA(b, v, tex)
if err != nil {
return nil, err
}
case image.Image:
img, err = loadImageConverted(b, v, tex)
if err != nil {
return nil, err
}
default:
return nil, errors.New("Unsupported source type")
}
return img, nil
}
func loadImageRGBA(b *XMobileBackend, src *image.RGBA, tex gl.Texture) (*Image, error) {
img := &Image{tex: tex, w: src.Bounds().Dx(), h: src.Bounds().Dy()}
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR)
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
if err := glError(b); err != nil {
return nil, err
}
if src.Stride == img.w*4 {
b.glctx.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, img.w, img.h, gl.RGBA, gl.UNSIGNED_BYTE, 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]...)
}
b.glctx.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, img.w, img.h, gl.RGBA, gl.UNSIGNED_BYTE, data[0:])
}
if err := glError(b); err != nil {
return nil, err
}
b.glctx.GenerateMipmap(gl.TEXTURE_2D)
if err := glError(b); err != nil {
return nil, err
}
return img, nil
}
func loadImageConverted(b *XMobileBackend, src image.Image, tex gl.Texture) (*Image, error) {
img := &Image{tex: tex, w: src.Bounds().Dx(), h: src.Bounds().Dy()}
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR)
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
if err := glError(b); 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)
}
}
b.glctx.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, img.w, img.h, gl.RGBA, gl.UNSIGNED_BYTE, data[0:])
if err := glError(b); err != nil {
return nil, err
}
b.glctx.GenerateMipmap(gl.TEXTURE_2D)
if err := glError(b); err != nil {
return nil, err
}
return img, nil
}
// Width returns the width of the image
func (img *Image) Width() int { return img.w }
// Height returns the height of the image
func (img *Image) Height() int { return img.h }
// Size returns the width and height of the image
func (img *Image) Size() (int, int) { return img.w, img.h }
// Delete deletes the image from memory. Any draw calls
// with a deleted image will not do anything
func (img *Image) Delete() {
b := img.b
img.b.activate()
b.glctx.DeleteTexture(img.tex)
}
// Replace replaces the image with the new one
func (img *Image) Replace(src image.Image) error {
b := img.b
img.b.activate()
b.glctx.ActiveTexture(gl.TEXTURE0)
b.glctx.BindTexture(gl.TEXTURE_2D, img.tex)
newImg, err := loadImage(b, src, img.tex)
if err != nil {
return err
}
newImg.b = img.b
*img = *newImg
return nil
}
func (b *XMobileBackend) DrawImage(dimg backendbase.Image, sx, sy, sw, sh float64, pts [4][2]float64, alpha float64) {
b.activate()
img := dimg.(*Image)
sx /= float64(img.w)
sy /= float64(img.h)
sw /= float64(img.w)
sh /= float64(img.h)
if img.flip {
sy += sh
sh = -sh
}
var buf [16]float32
data := buf[:0]
for _, pt := range pts {
data = append(data, float32(pt[0]), float32(pt[1]))
}
data = append(data,
float32(sx), float32(sy),
float32(sx), float32(sy+sh),
float32(sx+sw), float32(sy+sh),
float32(sx+sw), float32(sy),
)
b.glctx.StencilFunc(gl.EQUAL, 0, 0xFF)
b.glctx.BindBuffer(gl.ARRAY_BUFFER, b.buf)
b.glctx.BufferData(gl.ARRAY_BUFFER, byteSlice(unsafe.Pointer(&data[0]), len(data)*4), gl.STREAM_DRAW)
b.glctx.ActiveTexture(gl.TEXTURE0)
b.glctx.BindTexture(gl.TEXTURE_2D, img.tex)
b.glctx.UseProgram(b.ir.ID)
b.glctx.Uniform1i(b.ir.Image, 0)
b.glctx.Uniform2f(b.ir.CanvasSize, float32(b.fw), float32(b.fh))
b.glctx.Uniform1f(b.ir.GlobalAlpha, float32(alpha))
b.glctx.VertexAttribPointer(b.ir.Vertex, 2, gl.FLOAT, false, 0, 0)
b.glctx.VertexAttribPointer(b.ir.TexCoord, 2, gl.FLOAT, false, 0, 8*4)
b.glctx.EnableVertexAttribArray(b.ir.Vertex)
b.glctx.EnableVertexAttribArray(b.ir.TexCoord)
b.glctx.DrawArrays(gl.TRIANGLE_FAN, 0, 4)
b.glctx.DisableVertexAttribArray(b.ir.Vertex)
b.glctx.DisableVertexAttribArray(b.ir.TexCoord)
b.glctx.StencilFunc(gl.ALWAYS, 0, 0xFF)
}
type ImagePattern struct {
b *XMobileBackend
data backendbase.ImagePatternData
}
func (b *XMobileBackend) LoadImagePattern(data backendbase.ImagePatternData) backendbase.ImagePattern {
return &ImagePattern{
b: b,
data: data,
}
}
func (ip *ImagePattern) Delete() {}
func (ip *ImagePattern) Replace(data backendbase.ImagePatternData) { ip.data = data }

View file

@ -1,180 +0,0 @@
package xmobilebackend
import (
"errors"
"fmt"
"reflect"
"strings"
"unicode"
"unicode/utf8"
"golang.org/x/mobile/gl"
)
type shaderProgram struct {
b *XMobileBackend
ID gl.Program
vs, fs gl.Shader
attribs map[string]gl.Attrib
uniforms map[string]gl.Uniform
}
func loadShader(b *XMobileBackend, vs, fs string, sp *shaderProgram) error {
sp.b = b
glError(b) // clear the current error
// compile vertex shader
{
sp.vs = b.glctx.CreateShader(gl.VERTEX_SHADER)
b.glctx.ShaderSource(sp.vs, vs)
b.glctx.CompileShader(sp.vs)
status := b.glctx.GetShaderi(sp.vs, gl.COMPILE_STATUS)
if status != gl.TRUE {
clog := b.glctx.GetShaderInfoLog(sp.vs)
b.glctx.DeleteShader(sp.vs)
return fmt.Errorf("failed to compile vertex shader:\n\n%s", clog)
}
if err := glError(b); err != nil {
return fmt.Errorf("gl error after compiling vertex shader: %v", err)
}
}
// compile fragment shader
{
sp.fs = b.glctx.CreateShader(gl.FRAGMENT_SHADER)
b.glctx.ShaderSource(sp.fs, fs)
b.glctx.CompileShader(sp.fs)
status := b.glctx.GetShaderi(sp.fs, gl.COMPILE_STATUS)
if status != gl.TRUE {
clog := b.glctx.GetShaderInfoLog(sp.fs)
b.glctx.DeleteShader(sp.fs)
return fmt.Errorf("failed to compile fragment shader:\n\n%s", clog)
}
if err := glError(b); err != nil {
return fmt.Errorf("gl error after compiling fragment shader: %v", err)
}
}
// link shader program
{
sp.ID = b.glctx.CreateProgram()
b.glctx.AttachShader(sp.ID, sp.vs)
b.glctx.AttachShader(sp.ID, sp.fs)
b.glctx.LinkProgram(sp.ID)
status := b.glctx.GetProgrami(sp.ID, gl.LINK_STATUS)
if status != gl.TRUE {
clog := b.glctx.GetProgramInfoLog(sp.ID)
b.glctx.DeleteProgram(sp.ID)
b.glctx.DeleteShader(sp.vs)
b.glctx.DeleteShader(sp.fs)
return fmt.Errorf("failed to link shader program:\n\n%s", clog)
}
if err := glError(b); err != nil {
return fmt.Errorf("gl error after linking shader: %v", err)
}
}
b.glctx.UseProgram(sp.ID)
// load the attributes
count := b.glctx.GetProgrami(sp.ID, gl.ACTIVE_ATTRIBUTES)
sp.attribs = make(map[string]gl.Attrib, int(count))
for i := 0; i < count; i++ {
name, _, _ := b.glctx.GetActiveAttrib(sp.ID, uint32(i))
sp.attribs[name] = b.glctx.GetAttribLocation(sp.ID, name)
}
// load the uniforms
count = b.glctx.GetProgrami(sp.ID, gl.ACTIVE_UNIFORMS)
sp.uniforms = make(map[string]gl.Uniform, int(count))
for i := 0; i < count; i++ {
name, _, _ := b.glctx.GetActiveUniform(sp.ID, uint32(i))
sp.uniforms[name] = b.glctx.GetUniformLocation(sp.ID, name)
}
return nil
}
func (sp *shaderProgram) use() {
sp.b.glctx.UseProgram(sp.ID)
}
func (sp *shaderProgram) delete() {
sp.b.glctx.DeleteProgram(sp.ID)
sp.b.glctx.DeleteShader(sp.vs)
sp.b.glctx.DeleteShader(sp.fs)
}
func (sp *shaderProgram) loadLocations(target interface{}) error {
val := reflect.ValueOf(target)
if val.Kind() != reflect.Ptr {
panic("target must be a pointer to a struct")
}
val = val.Elem()
if val.Kind() != reflect.Struct {
panic("target must be a pointer to a struct")
}
sp.b.glctx.UseProgram(sp.ID)
var errs strings.Builder
for name, loc := range sp.attribs {
field := val.FieldByName(sp.structName(name))
if field == (reflect.Value{}) {
fmt.Fprintf(&errs, "field for attribute \"%s\" not found; ", name)
} else if field.Type() != reflect.TypeOf(gl.Attrib{}) {
fmt.Fprintf(&errs, "field for attribute \"%s\" must have type gl.Attrib; ", name)
} else {
field.Set(reflect.ValueOf(loc))
}
}
for name, loc := range sp.uniforms {
field := val.FieldByName(sp.structName(name))
if field == (reflect.Value{}) {
fmt.Fprintf(&errs, "field for uniform \"%s\" not found; ", name)
} else if field.Type() != reflect.TypeOf(gl.Uniform{}) {
fmt.Fprintf(&errs, "field for uniform \"%s\" must have type gl.Uniform; ", name)
} else {
field.Set(reflect.ValueOf(loc))
}
}
if errs.Len() > 0 {
return errors.New(strings.TrimSpace(errs.String()))
}
return nil
}
func (sp *shaderProgram) structName(name string) string {
rn, sz := utf8.DecodeRuneInString(name)
name = fmt.Sprintf("%c%s", unicode.ToUpper(rn), name[sz:])
idx := strings.IndexByte(name, '[')
if idx > 0 {
name = name[:idx]
}
return name
}
func (sp *shaderProgram) mustLoadLocations(target interface{}) {
err := sp.loadLocations(target)
if err != nil {
panic(err)
}
}
func (sp *shaderProgram) enableAllVertexAttribArrays() {
for _, loc := range sp.attribs {
sp.b.glctx.EnableVertexAttribArray(loc)
}
}
func (sp *shaderProgram) disableAllVertexAttribArrays() {
for _, loc := range sp.attribs {
sp.b.glctx.DisableVertexAttribArray(loc)
}
}

View file

@ -1,492 +0,0 @@
package xmobilebackend
import (
"bytes"
"fmt"
"strings"
"golang.org/x/mobile/gl"
)
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 sampler2D gradient;
uniform vec2 from, dir;
uniform float len;
uniform float globalAlpha;
void main() {
vec2 v = v_cp - 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 sampler2D gradient;
uniform vec2 from, to;
uniform float radFrom, radTo;
uniform float globalAlpha;
bool isNaN(float v) {
return v < 0.0 || 0.0 < v || v == 0.0 ? false : true;
}
void main() {
float o_a = 0.5 * sqrt(
pow(-2.0*from.x*from.x+2.0*from.x*to.x+2.0*from.x*v_cp.x-2.0*to.x*v_cp.x-2.0*from.y*from.y+2.0*from.y*to.y+2.0*from.y*v_cp.y-2.0*to.y*v_cp.y+2.0*radFrom*radFrom-2.0*radFrom*radTo, 2.0)
-4.0*(from.x*from.x-2.0*from.x*v_cp.x+v_cp.x*v_cp.x+from.y*from.y-2.0*from.y*v_cp.y+v_cp.y*v_cp.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*v_cp.x+to.x*v_cp.x+from.y*from.y-from.y*to.y-from.y*v_cp.y+to.y*v_cp.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 sampler2D image;
uniform mat3 imageTransform;
uniform vec2 repeat;
uniform float globalAlpha;
void main() {
vec3 tfpt = vec3(v_cp, 1.0) * imageTransform;
vec2 imgpt = tfpt.xy / imageSize;
vec4 col = texture2D(image, mod(imgpt, 1.0));
if (imgpt.x < 0.0 || imgpt.x > 1.0) {
col *= repeat.x;
}
if (imgpt.y < 0.0 || imgpt.y > 1.0) {
col *= repeat.y;
}
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 sampler2D gradient;
uniform vec2 from, dir;
uniform float len;
uniform sampler2D alphaTex;
uniform float globalAlpha;
void main() {
vec2 v = v_cp - 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 sampler2D gradient;
uniform vec2 from, to;
uniform float radFrom, radTo;
uniform sampler2D alphaTex;
uniform float globalAlpha;
bool isNaN(float v) {
return v < 0.0 || 0.0 < v || v == 0.0 ? false : true;
}
void main() {
float o_a = 0.5 * sqrt(
pow(-2.0*from.x*from.x+2.0*from.x*to.x+2.0*from.x*v_cp.x-2.0*to.x*v_cp.x-2.0*from.y*from.y+2.0*from.y*to.y+2.0*from.y*v_cp.y-2.0*to.y*v_cp.y+2.0*radFrom*radFrom-2.0*radFrom*radTo, 2.0)
-4.0*(from.x*from.x-2.0*from.x*v_cp.x+v_cp.x*v_cp.x+from.y*from.y-2.0*from.y*v_cp.y+v_cp.y*v_cp.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*v_cp.x+to.x*v_cp.x+from.y*from.y-from.y*to.y-from.y*v_cp.y+to.y*v_cp.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 sampler2D image;
uniform mat3 imageTransform;
uniform vec2 repeat;
uniform sampler2D alphaTex;
uniform float globalAlpha;
void main() {
vec3 tfpt = vec3(v_cp, 1.0) * imageTransform;
vec2 imgpt = tfpt.xy / imageSize;
vec4 col = texture2D(image, mod(imgpt, 1.0));
if (imgpt.x < 0.0 || imgpt.x > 1.0) {
col *= repeat.x;
}
if (imgpt.y < 0.0 || imgpt.y > 1.0) {
col *= repeat.y;
}
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)
}
type solidShader struct {
shaderProgram
Vertex gl.Attrib
CanvasSize gl.Uniform
Color gl.Uniform
GlobalAlpha gl.Uniform
}
type imageShader struct {
shaderProgram
Vertex gl.Attrib
TexCoord gl.Attrib
CanvasSize gl.Uniform
Image gl.Uniform
GlobalAlpha gl.Uniform
}
type linearGradientShader struct {
shaderProgram
Vertex gl.Attrib
CanvasSize gl.Uniform
Gradient gl.Uniform
From gl.Uniform
Dir gl.Uniform
Len gl.Uniform
GlobalAlpha gl.Uniform
}
type radialGradientShader struct {
shaderProgram
Vertex gl.Attrib
CanvasSize gl.Uniform
Gradient gl.Uniform
From gl.Uniform
To gl.Uniform
RadFrom gl.Uniform
RadTo gl.Uniform
GlobalAlpha gl.Uniform
}
type imagePatternShader struct {
shaderProgram
Vertex gl.Attrib
CanvasSize gl.Uniform
ImageSize gl.Uniform
Image gl.Uniform
ImageTransform gl.Uniform
Repeat gl.Uniform
GlobalAlpha gl.Uniform
}
type solidAlphaShader struct {
shaderProgram
Vertex gl.Attrib
AlphaTexCoord gl.Attrib
CanvasSize gl.Uniform
Color gl.Uniform
AlphaTex gl.Uniform
GlobalAlpha gl.Uniform
}
type linearGradientAlphaShader struct {
shaderProgram
Vertex gl.Attrib
AlphaTexCoord gl.Attrib
CanvasSize gl.Uniform
Gradient gl.Uniform
From gl.Uniform
Dir gl.Uniform
Len gl.Uniform
AlphaTex gl.Uniform
GlobalAlpha gl.Uniform
}
type radialGradientAlphaShader struct {
shaderProgram
Vertex gl.Attrib
AlphaTexCoord gl.Attrib
CanvasSize gl.Uniform
Gradient gl.Uniform
From gl.Uniform
To gl.Uniform
RadFrom gl.Uniform
RadTo gl.Uniform
AlphaTex gl.Uniform
GlobalAlpha gl.Uniform
}
type imagePatternAlphaShader struct {
shaderProgram
Vertex gl.Attrib
AlphaTexCoord gl.Attrib
CanvasSize gl.Uniform
ImageSize gl.Uniform
Image gl.Uniform
ImageTransform gl.Uniform
Repeat gl.Uniform
AlphaTex gl.Uniform
GlobalAlpha gl.Uniform
}
type gaussianShader struct {
shaderProgram
Vertex gl.Attrib
TexCoord gl.Attrib
CanvasSize gl.Uniform
KernelScale gl.Uniform
Image gl.Uniform
Kernel gl.Uniform
}

View file

@ -1,609 +0,0 @@
package xmobilebackend
import (
"fmt"
"image/color"
"math"
"reflect"
"unsafe"
"github.com/tfriedel6/canvas/backend/backendbase"
"golang.org/x/mobile/gl"
)
const alphaTexSize = 2048
var zeroes [alphaTexSize]byte
// GLContext is a context that contains all the
// shaders and buffers necessary for rendering
type GLContext struct {
glctx gl.Context
buf gl.Buffer
shadowBuf gl.Buffer
alphaTex gl.Texture
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
imageBufTex gl.Texture
imageBuf []byte
ptsBuf []float32
glChan chan func()
}
// NewGLContext creates all the necessary GL resources,
// like shaders and buffers
func NewGLContext(glctx gl.Context) (*GLContext, error) {
ctx := &GLContext{
glctx: glctx,
ptsBuf: make([]float32, 0, 4096),
glChan: make(chan func()),
}
var err error
b := &XMobileBackend{GLContext: ctx}
b.glctx.GetError() // clear error state
err = loadShader(b, solidVS, solidFS, &ctx.sr.shaderProgram)
if err != nil {
return nil, err
}
ctx.sr.shaderProgram.mustLoadLocations(&ctx.sr)
if err = glError(b); err != nil {
return nil, err
}
err = loadShader(b, linearGradientVS, linearGradientFS, &ctx.lgr.shaderProgram)
if err != nil {
return nil, err
}
ctx.lgr.shaderProgram.mustLoadLocations(&ctx.lgr)
if err = glError(b); err != nil {
return nil, err
}
err = loadShader(b, radialGradientVS, radialGradientFS, &ctx.rgr.shaderProgram)
if err != nil {
return nil, err
}
ctx.rgr.shaderProgram.mustLoadLocations(&ctx.rgr)
if err = glError(b); err != nil {
return nil, err
}
err = loadShader(b, imagePatternVS, imagePatternFS, &ctx.ipr.shaderProgram)
if err != nil {
return nil, err
}
ctx.ipr.shaderProgram.mustLoadLocations(&ctx.ipr)
if err = glError(b); err != nil {
return nil, err
}
err = loadShader(b, solidAlphaVS, solidAlphaFS, &ctx.sar.shaderProgram)
if err != nil {
return nil, err
}
ctx.sar.shaderProgram.mustLoadLocations(&ctx.sar)
if err = glError(b); err != nil {
return nil, err
}
err = loadShader(b, linearGradientAlphaVS, linearGradientFS, &ctx.lgar.shaderProgram)
if err != nil {
return nil, err
}
ctx.lgar.shaderProgram.mustLoadLocations(&ctx.lgar)
if err = glError(b); err != nil {
return nil, err
}
err = loadShader(b, radialGradientAlphaVS, radialGradientAlphaFS, &ctx.rgar.shaderProgram)
if err != nil {
return nil, err
}
ctx.rgar.shaderProgram.mustLoadLocations(&ctx.rgar)
if err = glError(b); err != nil {
return nil, err
}
err = loadShader(b, imagePatternAlphaVS, imagePatternAlphaFS, &ctx.ipar.shaderProgram)
if err != nil {
return nil, err
}
ctx.ipar.shaderProgram.mustLoadLocations(&ctx.ipar)
if err = glError(b); err != nil {
return nil, err
}
err = loadShader(b, imageVS, imageFS, &ctx.ir.shaderProgram)
if err != nil {
return nil, err
}
ctx.ir.shaderProgram.mustLoadLocations(&ctx.ir)
if err = glError(b); err != nil {
return nil, err
}
err = loadShader(b, gaussian15VS, gaussian15FS, &ctx.gauss15r.shaderProgram)
if err != nil {
return nil, err
}
ctx.gauss15r.shaderProgram.mustLoadLocations(&ctx.gauss15r)
if err = glError(b); err != nil {
return nil, err
}
err = loadShader(b, gaussian63VS, gaussian63FS, &ctx.gauss63r.shaderProgram)
if err != nil {
return nil, err
}
ctx.gauss63r.shaderProgram.mustLoadLocations(&ctx.gauss63r)
if err = glError(b); err != nil {
return nil, err
}
err = loadShader(b, gaussian127VS, gaussian127FS, &ctx.gauss127r.shaderProgram)
if err != nil {
return nil, err
}
ctx.gauss127r.shaderProgram.mustLoadLocations(&ctx.gauss127r)
if err = glError(b); err != nil {
return nil, err
}
ctx.buf = b.glctx.CreateBuffer()
if err = glError(b); err != nil {
return nil, err
}
ctx.shadowBuf = b.glctx.CreateBuffer()
if err = glError(b); err != nil {
return nil, err
}
b.glctx.ActiveTexture(gl.TEXTURE0)
ctx.alphaTex = b.glctx.CreateTexture()
b.glctx.BindTexture(gl.TEXTURE_2D, ctx.alphaTex)
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
b.glctx.TexImage2D(gl.TEXTURE_2D, 0, gl.ALPHA, alphaTexSize, alphaTexSize, gl.ALPHA, gl.UNSIGNED_BYTE, nil)
b.glctx.Enable(gl.BLEND)
b.glctx.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
b.glctx.Enable(gl.STENCIL_TEST)
b.glctx.StencilMask(0xFF)
b.glctx.Clear(gl.STENCIL_BUFFER_BIT)
b.glctx.StencilOp(gl.KEEP, gl.KEEP, gl.KEEP)
b.glctx.StencilFunc(gl.EQUAL, 0, 0xFF)
b.glctx.Disable(gl.SCISSOR_TEST)
return ctx, nil
}
// XMobileBackend is a canvas backend using Go-GL
type XMobileBackend struct {
x, y, w, h int
fx, fy, fw, fh float64
*GLContext
activateFn func()
disableTextureRenderTarget func()
}
type offscreenBuffer struct {
tex gl.Texture
w int
h int
renderStencilBuf gl.Renderbuffer
frameBuf gl.Framebuffer
alpha bool
}
// New returns a new canvas backend. x, y, w, h define the target
// rectangle in the window. ctx is a GLContext created with
// NewGLContext
func New(x, y, w, h int, ctx *GLContext) (*XMobileBackend, error) {
b := &XMobileBackend{
w: w,
h: h,
fw: float64(w),
fh: float64(h),
GLContext: ctx,
}
b.activateFn = func() {
b.glctx.BindFramebuffer(gl.FRAMEBUFFER, gl.Framebuffer{Value: 0})
b.glctx.Viewport(b.x, b.y, b.w, b.h)
// todo reapply clipping since another application may have used the stencil buffer
}
b.disableTextureRenderTarget = func() {
b.glctx.BindFramebuffer(gl.FRAMEBUFFER, gl.Framebuffer{Value: 0})
}
return b, nil
}
// XMobileBackendOffscreen is a canvas backend using an offscreen
// texture
type XMobileBackendOffscreen struct {
XMobileBackend
TextureID gl.Texture
offscrBuf offscreenBuffer
offscrImg Image
}
// NewOffscreen returns a new offscreen canvas backend. w, h define
// the size of the offscreen texture. ctx is a GLContext created
// with NewGLContext
func NewOffscreen(w, h int, alpha bool, ctx *GLContext) (*XMobileBackendOffscreen, error) {
b, err := New(0, 0, w, h, ctx)
if err != nil {
return nil, err
}
bo := &XMobileBackendOffscreen{XMobileBackend: *b}
bo.offscrBuf.alpha = alpha
bo.offscrImg.flip = true
bo.activateFn = func() {
bo.enableTextureRenderTarget(&bo.offscrBuf)
b.glctx.Viewport(0, 0, bo.w, bo.h)
bo.offscrImg.w = bo.offscrBuf.w
bo.offscrImg.h = bo.offscrBuf.h
bo.offscrImg.tex = bo.offscrBuf.tex
bo.TextureID = bo.offscrBuf.tex
}
bo.disableTextureRenderTarget = func() {
bo.enableTextureRenderTarget(&bo.offscrBuf)
}
return bo, nil
}
// SetBounds updates the bounds of the canvas. This would
// usually be called for example when the window is resized
func (b *XMobileBackend) SetBounds(x, y, w, h int) {
b.x, b.y = x, y
b.fx, b.fy = float64(x), float64(y)
b.w, b.h = w, h
b.fw, b.fh = float64(w), float64(h)
if b == activeContext {
b.glctx.Viewport(0, 0, b.w, b.h)
b.glctx.Clear(gl.STENCIL_BUFFER_BIT)
}
}
// SetSize updates the size of the offscreen texture
func (b *XMobileBackendOffscreen) SetSize(w, h int) {
b.XMobileBackend.SetBounds(0, 0, w, h)
b.offscrImg.w = b.offscrBuf.w
b.offscrImg.h = b.offscrBuf.h
}
// Size returns the size of the window or offscreen
// texture
func (b *XMobileBackend) Size() (int, int) {
return b.w, b.h
}
func glError(b *XMobileBackend) error {
glErr := b.glctx.GetError()
if glErr != gl.NO_ERROR {
return fmt.Errorf("GL Error: %x", glErr)
}
return nil
}
// Activate only needs to be called if there is other
// code also using the GL state
func (b *XMobileBackend) Activate() {
b.activate()
}
var activeContext *XMobileBackend
func (b *XMobileBackend) activate() {
if activeContext != b {
activeContext = b
b.activateFn()
}
b.runGLQueue()
}
func (b *XMobileBackend) runGLQueue() {
for {
select {
case f := <-b.glChan:
f()
default:
return
}
}
}
// Delete deletes the offscreen texture. After calling this
// the backend can no longer be used
func (b *XMobileBackendOffscreen) Delete() {
b.glctx.DeleteTexture(b.offscrBuf.tex)
b.glctx.DeleteFramebuffer(b.offscrBuf.frameBuf)
b.glctx.DeleteRenderbuffer(b.offscrBuf.renderStencilBuf)
}
// CanUseAsImage returns true if the given backend can be
// directly used by this backend to avoid a conversion.
// Used internally
func (b *XMobileBackend) CanUseAsImage(b2 backendbase.Backend) bool {
_, ok := b2.(*XMobileBackendOffscreen)
return ok
}
// AsImage returns nil, since this backend cannot be directly
// used as an image. Used internally
func (b *XMobileBackend) AsImage() backendbase.Image {
return nil
}
// AsImage returns an implementation of the Image interface
// that can be used to render this offscreen texture
// directly. Used internally
func (b *XMobileBackendOffscreen) AsImage() backendbase.Image {
return &b.offscrImg
}
type glColor struct {
r, g, b, a float64
}
func colorGoToGL(c color.RGBA) glColor {
var glc glColor
glc.r = float64(c.R) / 255
glc.g = float64(c.G) / 255
glc.b = float64(c.B) / 255
glc.a = float64(c.A) / 255
return glc
}
func (b *XMobileBackend) useShader(style *backendbase.FillStyle) (vertexLoc gl.Attrib) {
if lg := style.LinearGradient; lg != nil {
lg := lg.(*LinearGradient)
b.glctx.ActiveTexture(gl.TEXTURE0)
b.glctx.BindTexture(gl.TEXTURE_2D, lg.tex)
b.glctx.UseProgram(b.lgr.ID)
from := vec{style.Gradient.X0, style.Gradient.Y0}
to := vec{style.Gradient.X1, style.Gradient.Y1}
dir := to.sub(from)
length := dir.len()
dir = dir.scale(1 / length)
b.glctx.Uniform2f(b.lgr.CanvasSize, float32(b.fw), float32(b.fh))
b.glctx.Uniform2f(b.lgr.From, float32(from[0]), float32(from[1]))
b.glctx.Uniform2f(b.lgr.Dir, float32(dir[0]), float32(dir[1]))
b.glctx.Uniform1f(b.lgr.Len, float32(length))
b.glctx.Uniform1i(b.lgr.Gradient, 0)
b.glctx.Uniform1f(b.lgr.GlobalAlpha, float32(style.Color.A)/255)
return b.lgr.Vertex
}
if rg := style.RadialGradient; rg != nil {
rg := rg.(*RadialGradient)
b.glctx.ActiveTexture(gl.TEXTURE0)
b.glctx.BindTexture(gl.TEXTURE_2D, rg.tex)
b.glctx.UseProgram(b.rgr.ID)
from := vec{style.Gradient.X0, style.Gradient.Y0}
to := vec{style.Gradient.X1, style.Gradient.Y1}
b.glctx.Uniform2f(b.rgr.CanvasSize, float32(b.fw), float32(b.fh))
b.glctx.Uniform2f(b.rgr.From, float32(from[0]), float32(from[1]))
b.glctx.Uniform2f(b.rgr.To, float32(to[0]), float32(to[1]))
b.glctx.Uniform1f(b.rgr.RadFrom, float32(style.Gradient.RadFrom))
b.glctx.Uniform1f(b.rgr.RadTo, float32(style.Gradient.RadTo))
b.glctx.Uniform1i(b.rgr.Gradient, 0)
b.glctx.Uniform1f(b.rgr.GlobalAlpha, float32(style.Color.A)/255)
return b.rgr.Vertex
}
if ip := style.ImagePattern; ip != nil {
ipd := ip.(*ImagePattern).data
img := ipd.Image.(*Image)
b.glctx.UseProgram(b.ipr.ID)
b.glctx.ActiveTexture(gl.TEXTURE0)
b.glctx.BindTexture(gl.TEXTURE_2D, img.tex)
b.glctx.Uniform2f(b.ipr.CanvasSize, float32(b.fw), float32(b.fh))
b.glctx.Uniform2f(b.ipr.ImageSize, float32(img.w), float32(img.h))
b.glctx.Uniform1i(b.ipr.Image, 0)
var f32mat [9]float32
for i, v := range ipd.Transform {
f32mat[i] = float32(v)
}
b.glctx.UniformMatrix3fv(b.ipr.ImageTransform, f32mat[:])
switch ipd.Repeat {
case backendbase.Repeat:
b.glctx.Uniform2f(b.ipr.Repeat, 1, 1)
case backendbase.RepeatX:
b.glctx.Uniform2f(b.ipr.Repeat, 1, 0)
case backendbase.RepeatY:
b.glctx.Uniform2f(b.ipr.Repeat, 0, 1)
case backendbase.NoRepeat:
b.glctx.Uniform2f(b.ipr.Repeat, 0, 0)
}
b.glctx.Uniform1f(b.ipr.GlobalAlpha, float32(style.Color.A)/255)
return b.ipr.Vertex
}
b.glctx.UseProgram(b.sr.ID)
b.glctx.Uniform2f(b.sr.CanvasSize, float32(b.fw), float32(b.fh))
c := colorGoToGL(style.Color)
b.glctx.Uniform4f(b.sr.Color, float32(c.r), float32(c.g), float32(c.b), float32(c.a))
b.glctx.Uniform1f(b.sr.GlobalAlpha, 1)
return b.sr.Vertex
}
func (b *XMobileBackend) useAlphaShader(style *backendbase.FillStyle, alphaTexSlot int) (vertexLoc, alphaTexCoordLoc gl.Attrib) {
if lg := style.LinearGradient; lg != nil {
lg := lg.(*LinearGradient)
b.glctx.ActiveTexture(gl.TEXTURE0)
b.glctx.BindTexture(gl.TEXTURE_2D, lg.tex)
b.glctx.UseProgram(b.lgar.ID)
from := vec{style.Gradient.X0, style.Gradient.Y0}
to := vec{style.Gradient.X1, style.Gradient.Y1}
dir := to.sub(from)
length := dir.len()
dir = dir.scale(1 / length)
b.glctx.Uniform2f(b.lgar.CanvasSize, float32(b.fw), float32(b.fh))
b.glctx.Uniform2f(b.lgar.From, float32(from[0]), float32(from[1]))
b.glctx.Uniform2f(b.lgar.Dir, float32(dir[0]), float32(dir[1]))
b.glctx.Uniform1f(b.lgar.Len, float32(length))
b.glctx.Uniform1i(b.lgar.Gradient, 0)
b.glctx.Uniform1i(b.lgar.AlphaTex, alphaTexSlot)
b.glctx.Uniform1f(b.lgar.GlobalAlpha, float32(style.Color.A)/255)
return b.lgar.Vertex, b.lgar.AlphaTexCoord
}
if rg := style.RadialGradient; rg != nil {
rg := rg.(*RadialGradient)
b.glctx.ActiveTexture(gl.TEXTURE0)
b.glctx.BindTexture(gl.TEXTURE_2D, rg.tex)
b.glctx.UseProgram(b.rgar.ID)
from := vec{style.Gradient.X0, style.Gradient.Y0}
to := vec{style.Gradient.X1, style.Gradient.Y1}
b.glctx.Uniform2f(b.rgar.CanvasSize, float32(b.fw), float32(b.fh))
b.glctx.Uniform2f(b.rgar.From, float32(from[0]), float32(from[1]))
b.glctx.Uniform2f(b.rgar.To, float32(to[0]), float32(to[1]))
b.glctx.Uniform1f(b.rgar.RadFrom, float32(style.Gradient.RadFrom))
b.glctx.Uniform1f(b.rgar.RadTo, float32(style.Gradient.RadTo))
b.glctx.Uniform1i(b.rgar.Gradient, 0)
b.glctx.Uniform1i(b.rgar.AlphaTex, alphaTexSlot)
b.glctx.Uniform1f(b.rgar.GlobalAlpha, float32(style.Color.A)/255)
return b.rgar.Vertex, b.rgar.AlphaTexCoord
}
if ip := style.ImagePattern; ip != nil {
ipd := ip.(*ImagePattern).data
img := ipd.Image.(*Image)
b.glctx.UseProgram(b.ipar.ID)
b.glctx.ActiveTexture(gl.TEXTURE0)
b.glctx.BindTexture(gl.TEXTURE_2D, img.tex)
b.glctx.Uniform2f(b.ipar.CanvasSize, float32(b.fw), float32(b.fh))
b.glctx.Uniform2f(b.ipar.ImageSize, float32(img.w), float32(img.h))
b.glctx.Uniform1i(b.ipar.Image, 0)
var f32mat [9]float32
for i, v := range ipd.Transform {
f32mat[i] = float32(v)
}
b.glctx.UniformMatrix3fv(b.ipr.ImageTransform, f32mat[:])
switch ipd.Repeat {
case backendbase.Repeat:
b.glctx.Uniform2f(b.ipr.Repeat, 1, 1)
case backendbase.RepeatX:
b.glctx.Uniform2f(b.ipr.Repeat, 1, 0)
case backendbase.RepeatY:
b.glctx.Uniform2f(b.ipr.Repeat, 0, 1)
case backendbase.NoRepeat:
b.glctx.Uniform2f(b.ipr.Repeat, 0, 0)
}
b.glctx.Uniform1i(b.ipar.AlphaTex, alphaTexSlot)
b.glctx.Uniform1f(b.ipar.GlobalAlpha, float32(style.Color.A)/255)
return b.ipar.Vertex, b.ipar.AlphaTexCoord
}
b.glctx.UseProgram(b.sar.ID)
b.glctx.Uniform2f(b.sar.CanvasSize, float32(b.fw), float32(b.fh))
c := colorGoToGL(style.Color)
b.glctx.Uniform4f(b.sar.Color, float32(c.r), float32(c.g), float32(c.b), float32(c.a))
b.glctx.Uniform1i(b.sar.AlphaTex, alphaTexSlot)
b.glctx.Uniform1f(b.sar.GlobalAlpha, 1)
return b.sar.Vertex, b.sar.AlphaTexCoord
}
func (b *XMobileBackend) enableTextureRenderTarget(offscr *offscreenBuffer) {
if offscr.w == b.w && offscr.h == b.h {
b.glctx.BindFramebuffer(gl.FRAMEBUFFER, offscr.frameBuf)
return
}
if b.w == 0 || b.h == 0 {
return
}
if offscr.w != 0 && offscr.h != 0 {
b.glctx.DeleteTexture(offscr.tex)
b.glctx.DeleteFramebuffer(offscr.frameBuf)
b.glctx.DeleteRenderbuffer(offscr.renderStencilBuf)
}
offscr.w = b.w
offscr.h = b.h
b.glctx.ActiveTexture(gl.TEXTURE0)
offscr.tex = b.glctx.CreateTexture()
b.glctx.BindTexture(gl.TEXTURE_2D, offscr.tex)
// todo do non-power-of-two textures work everywhere?
if offscr.alpha {
b.glctx.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, b.w, b.h, gl.RGBA, gl.UNSIGNED_BYTE, nil)
} else {
b.glctx.TexImage2D(gl.TEXTURE_2D, 0, gl.RGB, b.w, b.h, gl.RGB, gl.UNSIGNED_BYTE, nil)
}
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
offscr.frameBuf = b.glctx.CreateFramebuffer()
b.glctx.BindFramebuffer(gl.FRAMEBUFFER, offscr.frameBuf)
offscr.renderStencilBuf = b.glctx.CreateRenderbuffer()
b.glctx.BindRenderbuffer(gl.RENDERBUFFER, offscr.renderStencilBuf)
b.glctx.RenderbufferStorage(gl.RENDERBUFFER, gl.STENCIL_INDEX8, b.w, b.h)
b.glctx.FramebufferRenderbuffer(gl.FRAMEBUFFER, gl.STENCIL_ATTACHMENT, gl.RENDERBUFFER, offscr.renderStencilBuf)
b.glctx.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, offscr.tex, 0)
if err := b.glctx.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))
}
b.glctx.Clear(gl.COLOR_BUFFER_BIT | gl.STENCIL_BUFFER_BIT)
}
type vec [2]float64
func (v vec) sub(v2 vec) vec {
return vec{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) scale(f float64) vec {
return vec{v[0] * f, v[1] * f}
}
func byteSlice(ptr unsafe.Pointer, size int) []byte {
var buf []byte
sh := (*reflect.SliceHeader)(unsafe.Pointer(&buf))
sh.Cap = size
sh.Len = size
sh.Data = uintptr(ptr)
return buf
}

187
canvas.go
View file

@ -3,13 +3,16 @@
package canvas
import (
"fmt"
"image"
"image/color"
"os"
"math"
"time"
"github.com/golang/freetype/truetype"
"github.com/tfriedel6/canvas/backend/backendbase"
"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
@ -20,24 +23,26 @@ import (
type Canvas struct {
b backendbase.Backend
path Path2D
convex bool
rect bool
path Path2D
state drawState
stateStack []drawState
images map[interface{}]*Image
images map[interface{}]*Image
fonts map[interface{}]*Font
fontCtxs map[fontKey]*frCache
fontPathCache map[*Font]*fontPathCache
fontTriCache map[*Font]*fontTriCache
shadowBuf [][2]float64
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
@ -123,7 +128,12 @@ const (
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
@ -131,9 +141,13 @@ var Performance = struct {
// coordinates given here also use the bottom left as origin
func New(backend backendbase.Backend) *Canvas {
cv := &Canvas{
b: backend,
stateStack: make([]drawState, 0, 20),
images: make(map[interface{}]*Image),
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.state.lineWidth = 1
cv.state.lineAlpha = 1
@ -141,7 +155,7 @@ func New(backend backendbase.Backend) *Canvas {
cv.state.globalAlpha = 1
cv.state.fill.color = color.RGBA{A: 255}
cv.state.stroke.color = color.RGBA{A: 255}
cv.state.transform = matIdentity()
cv.state.transform = backendbase.MatIdentity
cv.path.cv = cv
return cv
}
@ -161,8 +175,8 @@ func (cv *Canvas) Height() int {
// Size returns the internal width and height of the canvas
func (cv *Canvas) Size() (int, int) { return cv.b.Size() }
func (cv *Canvas) tf(v vec) vec {
return v.mulMat(cv.state.transform)
func (cv *Canvas) tf(v backendbase.Vec) backendbase.Vec {
return v.MulMat(cv.state.transform)
}
const alphaTexSize = 2048
@ -176,16 +190,6 @@ type offscreenBuffer struct {
alpha bool
}
type gaussianShader struct {
id uint32
vertex uint32
texCoord uint32
canvasSize int32
kernelScale int32
image int32
kernel int32
}
// 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
@ -214,20 +218,23 @@ func (cv *Canvas) 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:
case *Image, image.Image, string:
if _, ok := imagePatterns[v]; !ok {
imagePatterns[v] = cv.CreatePattern(v, Repeat)
}
style.imagePattern = imagePatterns[v]
case *ImagePattern:
style.imagePattern = v
}
}
return style
@ -288,30 +295,12 @@ 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 := cv.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()
@ -359,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
}
@ -395,7 +385,7 @@ func (cv *Canvas) Restore() {
cv.b.ClearClip()
for _, st := range cv.stateStack {
if len(st.clip.p) > 0 {
cv.clip(&st.clip, matIdentity())
cv.clip(&st.clip, backendbase.MatIdentity)
}
}
cv.state = cv.stateStack[l-1]
@ -404,27 +394,27 @@ func (cv *Canvas) Restore() {
// 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, c, d, e, f}.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, c, d, e, f}
cv.state.transform = backendbase.Mat{a, b, c, d, e, f}
}
// SetShadowColor sets the color of the shadow. If it is fully transparent (default)
@ -470,18 +460,89 @@ func (cv *Canvas) IsPointInStroke(x, y float64) bool {
return false
}
var triBuf [500][2]float64
tris := cv.strokeTris(&cv.path, cv.state.transform.invert(), true, triBuf[:0])
var triBuf [500]backendbase.Vec
tris := cv.strokeTris(
&cv.path,
cv.state.transform,
cv.state.transform.Invert(),
true,
triBuf[:0],
)
pt := vec{x, y}
pt := backendbase.Vec{x, y}
for i := 0; i < len(tris); i += 3 {
a := vec{tris[i][0], tris[i][1]}
b := vec{tris[i+1][0], tris[i+1][1]}
c := vec{tris[i+2][0], tris[i+2][1]}
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,609 +0,0 @@
package canvas_test
import (
"fmt"
"image"
"image/png"
"math"
"os"
"runtime"
"strings"
"testing"
"github.com/go-gl/gl/v3.2-core/gl"
"github.com/tfriedel6/canvas"
"github.com/tfriedel6/canvas/sdlcanvas"
)
func run(t *testing.T, fn func(cv *canvas.Canvas)) {
wnd, cv, err := sdlcanvas.CreateWindow(100, 100, "test")
if err != nil {
t.Fatalf("Failed to create window: %v", err)
return
}
defer wnd.Destroy()
gl.Disable(gl.MULTISAMPLE)
wnd.StartFrame()
cv.ClearRect(0, 0, 100, 100)
fn(cv)
img := cv.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) {
err = writeImage(img, fileName)
if err != nil {
t.Fatal(err)
}
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 := refImg.At(x, y).RGBA()
if r1 != r2 || g1 != g2 || b1 != b2 || a1 != a2 {
writeImage(img, fmt.Sprintf("testdata/%s_fail.png", callerFuncName))
t.FailNow()
}
}
}
}
func writeImage(img *image.RGBA, fileName string) error {
f, err := os.OpenFile(fileName, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0777)
if err != nil {
return fmt.Errorf("Failed to create file \"%s\"", fileName)
}
defer f.Close()
err = png.Encode(f, img)
if err != nil {
return fmt.Errorf("Failed to encode PNG")
}
return nil
}
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.SetLineCap(canvas.Butt)
cv.BeginPath()
cv.MoveTo(10, 40)
cv.LineTo(30, 40)
cv.LineTo(30, 60)
cv.LineTo(10, 60)
cv.Stroke()
cv.SetLineCap(canvas.Round)
cv.BeginPath()
cv.MoveTo(40, 40)
cv.LineTo(60, 40)
cv.LineTo(60, 60)
cv.LineTo(40, 60)
cv.Stroke()
cv.SetLineCap(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)
})
}
func TestConvex(t *testing.T) {
run(t, func(cv *canvas.Canvas) {
cv.SetFillStyle("#F00")
cv.BeginPath()
cv.MoveTo(10, 10)
cv.LineTo(20, 10)
cv.LineTo(20, 20)
cv.LineTo(10, 20)
cv.LineTo(10, 10)
cv.ClosePath()
cv.Fill()
cv.SetFillStyle("#0F0")
cv.BeginPath()
cv.MoveTo(30, 10)
cv.LineTo(40, 10)
cv.LineTo(40, 15)
cv.LineTo(35, 15)
cv.LineTo(35, 20)
cv.LineTo(40, 20)
cv.LineTo(40, 25)
cv.LineTo(30, 25)
cv.ClosePath()
cv.Fill()
cv.SetFillStyle("#00F")
cv.BeginPath()
cv.MoveTo(50, 10)
cv.LineTo(50, 25)
cv.LineTo(60, 25)
cv.LineTo(60, 20)
cv.LineTo(55, 20)
cv.LineTo(55, 15)
cv.LineTo(60, 15)
cv.LineTo(60, 10)
cv.ClosePath()
cv.Fill()
cv.SetFillStyle("#FFF")
cv.BeginPath()
cv.MoveTo(20, 35)
cv.LineTo(80, 35)
cv.ArcTo(90, 35, 90, 45, 10)
cv.LineTo(90, 80)
cv.ArcTo(90, 90, 80, 90, 10)
cv.LineTo(20, 90)
cv.ArcTo(10, 90, 10, 80, 10)
cv.LineTo(10, 45)
cv.ArcTo(10, 35, 20, 35, 10)
cv.ClosePath()
cv.Fill()
})
}
func TestConvexSelfIntersecting(t *testing.T) {
run(t, func(cv *canvas.Canvas) {
cv.SetFillStyle("#F00")
cv.BeginPath()
cv.MoveTo(33.7, 31.5)
cv.LineTo(35.4, 55.0)
cv.LineTo(53.0, 33.5)
cv.LineTo(56.4, 62.4)
cv.ClosePath()
cv.Fill()
})
}
func TestTransform(t *testing.T) {
run(t, func(cv *canvas.Canvas) {
path := cv.NewPath2D()
path.MoveTo(-10, -10)
path.LineTo(10, -10)
path.LineTo(0, 10)
path.ClosePath()
cv.Translate(40, 20)
cv.BeginPath()
cv.LineTo(10, 10)
cv.LineTo(30, 10)
cv.LineTo(20, 30)
cv.ClosePath()
cv.SetStrokeStyle("#F00")
cv.Stroke()
cv.SetStrokeStyle("#0F0")
cv.StrokePath(path)
cv.Translate(20, 0)
cv.SetStrokeStyle("#00F")
cv.StrokePath(path)
cv.Translate(-40, 30)
cv.BeginPath()
cv.LineTo(10, 10)
cv.LineTo(30, 10)
cv.LineTo(20, 30)
cv.ClosePath()
cv.Translate(20, 0)
cv.SetStrokeStyle("#FF0")
cv.Stroke()
cv.Translate(20, 0)
cv.SetStrokeStyle("#F0F")
cv.StrokePath(path)
})
}
func TestTransform2(t *testing.T) {
run(t, func(cv *canvas.Canvas) {
cv.SetStrokeStyle("#FFF")
cv.SetLineWidth(16)
cv.MoveTo(20, 20)
cv.LineTo(20, 50)
cv.Scale(2, 1)
cv.LineTo(45, 80)
cv.SetLineJoin(canvas.Round)
cv.SetLineCap(canvas.Round)
cv.Stroke()
})
}
func TestImage(t *testing.T) {
run(t, func(cv *canvas.Canvas) {
cv.DrawImage("testdata/cat.jpg", 5, 5, 40, 40)
cv.DrawImage("testdata/cat.jpg", 100, 100, 100, 100, 55, 55, 40, 40)
cv.Save()
cv.Translate(75, 25)
cv.Rotate(math.Pi / 2)
cv.Translate(-20, -20)
cv.DrawImage("testdata/cat.jpg", 0, 0, 40, 40)
cv.Restore()
cv.SetTransform(1, 0, 0.2, 1, 0, 0)
cv.DrawImage("testdata/cat.jpg", -8, 55, 40, 40)
})
}
func TestGradient(t *testing.T) {
run(t, func(cv *canvas.Canvas) {
cv.Translate(50, 50)
cv.Scale(0.8, 0.7)
cv.Rotate(math.Pi * 0.1)
cv.Translate(-50, -50)
lg := cv.CreateLinearGradient(10, 10, 40, 20)
lg.AddColorStop(0, 0.5, 0, 0)
lg.AddColorStop(0.5, "#008000")
lg.AddColorStop(1, 0, 0, 128)
cv.SetFillStyle(lg)
cv.FillRect(0, 0, 50, 100)
rg := cv.CreateRadialGradient(75, 15, 10, 75, 75, 20)
rg.AddColorStop(0, 1.0, 0, 0, 0.5)
rg.AddColorStop(0.5, "#00FF0080")
rg.AddColorStop(1, 0, 0, 255, 128)
cv.SetFillStyle(rg)
cv.FillRect(50, 0, 50, 100)
})
}
func TestImagePattern(t *testing.T) {
run(t, func(cv *canvas.Canvas) {
cv.Translate(50, 50)
cv.Scale(0.95, 1.05)
cv.Rotate(-math.Pi * 0.1)
cv.SetFillStyle("testdata/cat.jpg")
cv.FillRect(-40, -40, 80, 80)
})
}
func TestShadow(t *testing.T) {
run(t, func(cv *canvas.Canvas) {
cv.SetFillStyle("#800")
cv.SetShadowColor("#00F")
cv.SetShadowOffset(6, 6)
cv.FillRect(10, 10, 60, 25)
cv.SetShadowBlur(6)
cv.FillRect(10, 55, 60, 25)
cv.SetFillStyle("#0F0")
cv.SetShadowColor("#F0F")
cv.SetGlobalAlpha(0.5)
cv.FillRect(50, 15, 30, 60)
})
}
func TestReadme(t *testing.T) {
run(t, func(cv *canvas.Canvas) {
w, h := 100.0, 100.0
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()
})
}

View file

@ -8,49 +8,6 @@ import (
"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.RGBA {
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':
@ -111,7 +68,7 @@ func parseColorComponent(value interface{}) (uint8, bool) {
} else if conv > 1 {
conv = 1
}
return uint8(conv), true
return uint8(math.Round(conv * 255.0)), true
} else {
conv, err := strconv.ParseUint(v, 10, 8)
if err != nil {
@ -242,14 +199,16 @@ func parseColor(value ...interface{}) (c color.RGBA, ok bool) {
}
} 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 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 color.RGBA{R: uint8(ir), G: uint8(ig), B: uint8(ib), A: uint8(ia)}, true
return color.RGBA{R: uint8(ir), G: uint8(ig), B: uint8(ib), A: uint8(fa * 255)}, true
}
}
}

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,3 +0,0 @@
As of this writing, gomobile does not support go modules. In this case this project can only be compiled while it is in the GOPATH/src directory.
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,42 +0,0 @@
package canvasandroidexample
import (
"math"
"github.com/tfriedel6/canvas"
"github.com/tfriedel6/canvas/backend/gogl"
)
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) {
backend, err := goglbackend.New(0, 0, w, h, nil)
if err != nil {
panic(err)
}
cv = canvas.New(backend)
}
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)
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 KiB

View file

@ -1,120 +0,0 @@
package main
import (
"image/color"
"log"
"math"
"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 := cv.CreateLinearGradient(320, 200, 480, 520)
lg.AddColorStop(0, "#ff000040")
lg.AddColorStop(1, "#00ff0040")
lg.AddColorStop(0.5, "#0000ff40")
rg := cv.CreateRadialGradient(540, 300, 80, 740, 300, 100)
rg.AddColorStop(0, "#ff0000")
rg.AddColorStop(1, "#00ff00")
rg.AddColorStop(0.5, "#0000ff")
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,77 +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"})
}
}
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/backend/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 GL backend
backend, err := goglbackend.New(0, 0, 0, 0, nil)
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(backend)
for !window.ShouldClose() {
window.MakeContextCurrent()
glfw.PollEvents()
// set canvas size
ww, wh := window.GetSize()
backend.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,71 +0,0 @@
package main
import (
"log"
"math"
"time"
"github.com/tfriedel6/canvas"
"github.com/tfriedel6/canvas/backend/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 cvb *xmobilebackend.XMobileBackendOffscreen
var painterb *xmobilebackend.XMobileBackend
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:
var err error
glctx = e.DrawContext.(gl.Context)
ctx, err := xmobilebackend.NewGLContext(glctx)
if err != nil {
log.Fatal(err)
}
cvb, err = xmobilebackend.NewOffscreen(0, 0, false, ctx)
if err != nil {
log.Fatalln(err)
}
painterb, err = xmobilebackend.New(0, 0, 0, 0, ctx)
if err != nil {
log.Fatalln(err)
}
cv = canvas.New(cvb)
painter = canvas.New(painterb)
a.Send(paint.Event{})
case lifecycle.CrossOff:
cvb.Delete()
glctx = nil
}
case size.Event:
w, h = e.WidthPx, e.HeightPx
case paint.Event:
if glctx != nil {
fw, fh := float64(w), float64(h)
color := math.Sin(float64(time.Now().UnixNano())*0.000000002)*0.3 + 0.7
cvb.SetSize(w, h)
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)
painterb.SetBounds(0, 0, w, h)
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,7 +0,0 @@
As of this writing, gomobile does not support go modules. In this case this project can only be compiled while it is in the GOPATH/src directory.
Run this command:
gomobile bind -target ios -tags ios
Then add the resulting Example.framework into the Xcode project, and it should compile and run from there

View file

@ -1,39 +0,0 @@
package example
import (
"math"
"github.com/tfriedel6/canvas"
"github.com/tfriedel6/canvas/backend/gogl"
)
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) {
backend, err := goglbackend.New(0, 0, w, h, nil)
if err != nil {
panic(err)
}
cv = canvas.New(backend)
}
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,125 +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/backend/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 GL backend
backend, err := goglbackend.New(0, 0, 0, 0, nil)
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(backend)
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.QuitEvent:
running = false
case *sdl.WindowEvent:
if e.Type == sdl.WINDOWEVENT_CLOSE {
running = false
}
}
}
// set canvas size
ww, wh := window.GetSize()
backend.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,58 +0,0 @@
package main
import (
"log"
"github.com/tfriedel6/canvas"
"github.com/tfriedel6/canvas/backend/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)
ctx, err := xmobilebackend.NewGLContext(glw.Ctx)
if err != nil {
log.Fatal(err)
}
backend, err := xmobilebackend.New(0, 0, 600, 600, ctx)
if err != nil {
log.Fatal(err)
}
cv = canvas.New(backend)
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,171 +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"
"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/backend/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 {
return nil, nil, fmt.Errorf("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 {
return nil, nil, fmt.Errorf("Error creating window: %v", err)
}
window.MakeContextCurrent()
// init GL
err = gl.Init()
if err != nil {
return nil, nil, fmt.Errorf("Error initializing GL: %v", err)
}
// set vsync on, enable multisample (if available)
glfw.SwapInterval(1)
gl.Enable(gl.MULTISAMPLE)
// load canvas GL backend
backend, err := goglbackend.New(0, 0, w, h, nil)
if err != nil {
return nil, nil, fmt.Errorf("Error loading GoGL backend: %v", err)
}
cv := canvas.New(backend)
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 {
backend.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]
}

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.3.0
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-20181128092732-4ed8d59d0b35 // 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.3.0 h1:IWYkHMp8V3v37NsKjszln8FFnX2+ab0538J371t+rss=
github.com/veandco/go-sdl2 v0.3.0/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-20181128092732-4ed8d59d0b35 h1:YAFjXN64LMvktoUZH9zgY4lGc/msGN7HQfoSuKCgaDU=
golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35/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

@ -2,8 +2,9 @@ package canvas
import (
"image/color"
"runtime"
"github.com/tfriedel6/canvas/backend/backendbase"
"git.mstar.dev/mstar/canvas/backend/backendbase"
)
// LinearGradient is a gradient with any number of
@ -12,10 +13,9 @@ import (
// will correspond to a straight line
type LinearGradient struct {
cv *Canvas
from, to vec
from, to backendbase.Vec
created bool
loaded bool
deleted bool
opaque bool
grad backendbase.LinearGradient
data backendbase.Gradient
@ -27,12 +27,11 @@ type LinearGradient struct {
// will correspond to a circle
type RadialGradient struct {
cv *Canvas
from, to vec
from, to backendbase.Vec
radFrom float64
radTo float64
created bool
loaded bool
deleted bool
opaque bool
grad backendbase.RadialGradient
data backendbase.Gradient
@ -42,13 +41,17 @@ type RadialGradient struct {
// the coordinates from where to where the gradient
// will apply on the canvas
func (cv *Canvas) CreateLinearGradient(x0, y0, x1, y1 float64) *LinearGradient {
return &LinearGradient{
lg := &LinearGradient{
cv: cv,
opaque: true,
from: vec{x0, y0},
to: vec{x1, y1},
from: backendbase.Vec{x0, y0},
to: backendbase.Vec{x1, y1},
data: make(backendbase.Gradient, 0, 20),
}
runtime.SetFinalizer(lg, func(*LinearGradient) {
lg.grad.Delete()
})
return lg
}
// CreateRadialGradient creates a new radial gradient with
@ -56,39 +59,23 @@ func (cv *Canvas) CreateLinearGradient(x0, y0, x1, y1 float64) *LinearGradient {
// gradient will apply from the first to the second
// circle
func (cv *Canvas) CreateRadialGradient(x0, y0, r0, x1, y1, r1 float64) *RadialGradient {
return &RadialGradient{
rg := &RadialGradient{
cv: cv,
opaque: true,
from: vec{x0, y0},
to: vec{x1, y1},
from: backendbase.Vec{x0, y0},
to: backendbase.Vec{x1, y1},
radFrom: r0,
radTo: r1,
data: make(backendbase.Gradient, 0, 20),
}
}
// Delete explicitly deletes the gradient
func (lg *LinearGradient) Delete() {
if lg.deleted {
return
}
lg.grad.Delete()
lg.grad = nil
lg.deleted = true
}
// Delete explicitly deletes the gradient
func (rg *RadialGradient) Delete() {
if rg.deleted {
return
}
rg.grad.Delete()
rg.grad = nil
rg.deleted = true
runtime.SetFinalizer(rg, func(*RadialGradient) {
rg.grad.Delete()
})
return rg
}
func (lg *LinearGradient) load() {
if lg.loaded || len(lg.data) < 1 || lg.deleted {
if lg.loaded || len(lg.data) < 1 {
return
}
@ -102,7 +89,7 @@ func (lg *LinearGradient) load() {
}
func (rg *RadialGradient) load() {
if rg.loaded || len(rg.data) < 1 || rg.deleted {
if rg.loaded || len(rg.data) < 1 {
return
}
@ -139,7 +126,11 @@ func (rg *RadialGradient) AddColorStop(pos float64, stopColor ...interface{}) {
rg.loaded = false
}
func addColorStop(stops backendbase.Gradient, pos float64, stopColor ...interface{}) (backendbase.Gradient, color.RGBA) {
func addColorStop(
stops backendbase.Gradient,
pos float64,
stopColor ...interface{},
) (backendbase.Gradient, color.RGBA) {
c, _ := parseColor(stopColor...)
insert := len(stops)
for i, stop := range stops {

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