Compare commits
31 commits
Author | SHA1 | Date | |
---|---|---|---|
fb1b1c09ec | |||
560fc9e11f | |||
![]() |
364c8706b6 | ||
![]() |
0c528ecc87 | ||
![]() |
631290f5f2 | ||
![]() |
6586c5c7d1 | ||
![]() |
5eb5dc34e1 | ||
![]() |
f36e11bdff | ||
![]() |
3a0ca2cdcd | ||
![]() |
e37f4f5565 | ||
![]() |
c4d3130770 | ||
![]() |
1d5a02b1d6 | ||
![]() |
1711693a57 | ||
![]() |
ef4c2c3191 | ||
![]() |
a0a1cea270 | ||
![]() |
c36395c0c8 | ||
![]() |
30531aaab7 | ||
![]() |
39e9e6400b | ||
![]() |
34087abece | ||
![]() |
804a9c2774 | ||
![]() |
cc9247c627 | ||
![]() |
066f4f55bb | ||
![]() |
d3cc20f911 | ||
![]() |
978852494a | ||
![]() |
59ddfe59c1 | ||
![]() |
7830bb2cc5 | ||
![]() |
1b94cf0703 | ||
![]() |
b39fdd0a48 | ||
![]() |
f47d24543d | ||
![]() |
7faf3cdcc6 | ||
![]() |
9d1e5b306a |
|
@ -17,13 +17,13 @@ type Backend interface {
|
||||||
LoadLinearGradient(data Gradient) LinearGradient
|
LoadLinearGradient(data Gradient) LinearGradient
|
||||||
LoadRadialGradient(data Gradient) RadialGradient
|
LoadRadialGradient(data Gradient) RadialGradient
|
||||||
|
|
||||||
Clear(pts [4][2]float64)
|
Clear(pts [4]Vec)
|
||||||
Fill(style *FillStyle, pts [][2]float64, canOverlap bool)
|
Fill(style *FillStyle, pts []Vec, tf Mat, canOverlap bool)
|
||||||
DrawImage(dimg Image, sx, sy, sw, sh float64, pts [4][2]float64, alpha float64)
|
DrawImage(dimg Image, sx, sy, sw, sh float64, pts [4]Vec, alpha float64)
|
||||||
FillImageMask(style *FillStyle, mask *image.Alpha, pts [4][2]float64) // pts must have four points
|
FillImageMask(style *FillStyle, mask *image.Alpha, pts [4]Vec) // pts must have four points
|
||||||
|
|
||||||
ClearClip()
|
ClearClip()
|
||||||
Clip(pts [][2]float64)
|
Clip(pts []Vec)
|
||||||
|
|
||||||
GetImageData(x, y, w, h int) *image.RGBA
|
GetImageData(x, y, w, h int) *image.RGBA
|
||||||
PutImageData(img *image.RGBA, x, y int)
|
PutImageData(img *image.RGBA, x, y int)
|
||||||
|
|
140
backend/backendbase/math.go
Normal 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])
|
||||||
|
}
|
|
@ -1,72 +0,0 @@
|
||||||
package goglbackend
|
|
||||||
|
|
||||||
import (
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"github.com/tfriedel6/canvas/backend/goglbackend/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.shd.Vertex, 2, gl.FLOAT, false, 0, nil)
|
|
||||||
|
|
||||||
gl.UseProgram(b.shd.ID)
|
|
||||||
gl.Uniform4f(b.shd.Color, 1, 1, 1, 1)
|
|
||||||
gl.Uniform2f(b.shd.CanvasSize, float32(b.fw), float32(b.fh))
|
|
||||||
gl.Uniform1f(b.shd.GlobalAlpha, 1)
|
|
||||||
gl.Uniform1i(b.shd.UseAlphaTex, 0)
|
|
||||||
gl.Uniform1i(b.shd.Func, shdFuncSolid)
|
|
||||||
gl.EnableVertexAttribArray(b.shd.Vertex)
|
|
||||||
|
|
||||||
gl.ColorMask(false, false, false, false)
|
|
||||||
|
|
||||||
// set bit 2 in the stencil buffer in the given shape
|
|
||||||
gl.StencilMask(0x04)
|
|
||||||
gl.StencilFunc(gl.ALWAYS, 4, 0)
|
|
||||||
gl.StencilOp(gl.REPLACE, gl.REPLACE, gl.REPLACE)
|
|
||||||
gl.DrawArrays(mode, 4, int32(len(pts)))
|
|
||||||
|
|
||||||
// on entire screen, where neither bit 1 or 2 are set, invert bit 1
|
|
||||||
gl.StencilMask(0x02)
|
|
||||||
gl.StencilFunc(gl.EQUAL, 0, 0x06)
|
|
||||||
gl.StencilOp(gl.KEEP, gl.INVERT, gl.INVERT)
|
|
||||||
gl.DrawArrays(gl.TRIANGLE_FAN, 0, 4)
|
|
||||||
|
|
||||||
// on entire screen, clear bit 2
|
|
||||||
gl.StencilMask(0x04)
|
|
||||||
gl.StencilFunc(gl.ALWAYS, 0, 0)
|
|
||||||
gl.StencilOp(gl.ZERO, gl.ZERO, gl.ZERO)
|
|
||||||
gl.DrawArrays(gl.TRIANGLE_FAN, 0, 4)
|
|
||||||
|
|
||||||
gl.DisableVertexAttribArray(b.shd.Vertex)
|
|
||||||
|
|
||||||
gl.ColorMask(true, true, true, true)
|
|
||||||
gl.StencilOp(gl.KEEP, gl.KEEP, gl.KEEP)
|
|
||||||
gl.StencilMask(0xFF)
|
|
||||||
gl.StencilFunc(gl.EQUAL, 0, 0xFF)
|
|
||||||
}
|
|
|
@ -1,324 +0,0 @@
|
||||||
package goglbackend
|
|
||||||
|
|
||||||
import (
|
|
||||||
"image"
|
|
||||||
"math"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"github.com/tfriedel6/canvas/backend/backendbase"
|
|
||||||
"github.com/tfriedel6/canvas/backend/goglbackend/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.shd.ID)
|
|
||||||
gl.Uniform2f(b.shd.CanvasSize, float32(b.fw), float32(b.fh))
|
|
||||||
gl.Uniform4f(b.shd.Color, 0, 0, 0, 0)
|
|
||||||
gl.Uniform1f(b.shd.GlobalAlpha, 1)
|
|
||||||
gl.Uniform1i(b.shd.UseAlphaTex, 0)
|
|
||||||
gl.Uniform1i(b.shd.Func, shdFuncSolid)
|
|
||||||
|
|
||||||
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.shd.Vertex, 2, gl.FLOAT, false, 0, nil)
|
|
||||||
gl.EnableVertexAttribArray(b.shd.Vertex)
|
|
||||||
gl.DrawArrays(gl.TRIANGLE_FAN, 0, 4)
|
|
||||||
gl.DisableVertexAttribArray(b.shd.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 extent(pts [][2]float64) (min, max vec) {
|
|
||||||
max[0] = -math.MaxFloat64
|
|
||||||
max[1] = -math.MaxFloat64
|
|
||||||
min[0] = math.MaxFloat64
|
|
||||||
min[1] = math.MaxFloat64
|
|
||||||
for _, v := range pts {
|
|
||||||
min[0] = math.Min(min[0], v[0])
|
|
||||||
min[1] = math.Min(min[1], v[1])
|
|
||||||
max[0] = math.Max(max[0], v[0])
|
|
||||||
max[1] = math.Max(max[1], v[1])
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *GoGLBackend) Fill(style *backendbase.FillStyle, pts [][2]float64, canOverlap bool) {
|
|
||||||
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]
|
|
||||||
min, max := extent(pts)
|
|
||||||
b.ptsBuf = append(b.ptsBuf,
|
|
||||||
float32(min[0]), float32(min[1]),
|
|
||||||
float32(min[0]), float32(max[1]),
|
|
||||||
float32(max[0]), float32(max[1]),
|
|
||||||
float32(max[0]), float32(min[1]))
|
|
||||||
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 !canOverlap || style.Color.A >= 255 {
|
|
||||||
vertex, _ := b.useShader(style, false, 0)
|
|
||||||
|
|
||||||
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.shd.ID)
|
|
||||||
gl.Uniform4f(b.shd.Color, 0, 0, 0, 0)
|
|
||||||
gl.Uniform2f(b.shd.CanvasSize, float32(b.fw), float32(b.fh))
|
|
||||||
gl.Uniform1f(b.shd.GlobalAlpha, 1)
|
|
||||||
gl.Uniform1i(b.shd.UseAlphaTex, 0)
|
|
||||||
gl.Uniform1i(b.shd.Func, shdFuncSolid)
|
|
||||||
|
|
||||||
gl.EnableVertexAttribArray(b.shd.Vertex)
|
|
||||||
gl.VertexAttribPointer(b.shd.Vertex, 2, gl.FLOAT, false, 0, nil)
|
|
||||||
gl.DrawArrays(mode, 4, int32(len(pts)))
|
|
||||||
gl.DisableVertexAttribArray(b.shd.Vertex)
|
|
||||||
|
|
||||||
gl.ColorMask(true, true, true, true)
|
|
||||||
|
|
||||||
gl.StencilFunc(gl.EQUAL, 1, 0xFF)
|
|
||||||
|
|
||||||
vertex, _ := b.useShader(style, false, 0)
|
|
||||||
gl.EnableVertexAttribArray(vertex)
|
|
||||||
gl.VertexAttribPointer(vertex, 2, gl.FLOAT, false, 0, nil)
|
|
||||||
|
|
||||||
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, min, max)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *GoGLBackend) FillImageMask(style *backendbase.FillStyle, mask *image.Alpha, pts [4][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.useShader(style, true, 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 {
|
|
||||||
min, max := extent(pts[:])
|
|
||||||
b.drawBlurred(style.Blur, min, max)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *GoGLBackend) drawBlurred(size float64, min, max vec) {
|
|
||||||
b.offscr1.alpha = true
|
|
||||||
b.offscr2.alpha = true
|
|
||||||
|
|
||||||
// calculate box blur size
|
|
||||||
fsize := math.Max(1, math.Floor(size))
|
|
||||||
sizea := int(fsize)
|
|
||||||
sizeb := sizea
|
|
||||||
sizec := sizea
|
|
||||||
if size-fsize > 0.333333333 {
|
|
||||||
sizeb++
|
|
||||||
}
|
|
||||||
if size-fsize > 0.666666666 {
|
|
||||||
sizec++
|
|
||||||
}
|
|
||||||
|
|
||||||
min[0] -= fsize * 3
|
|
||||||
min[1] -= fsize * 3
|
|
||||||
max[0] += fsize * 3
|
|
||||||
max[1] += fsize * 3
|
|
||||||
min[0] = math.Max(0.0, math.Min(b.fw, min[0]))
|
|
||||||
min[1] = math.Max(0.0, math.Min(b.fh, min[1]))
|
|
||||||
max[0] = math.Max(0.0, math.Min(b.fw, max[0]))
|
|
||||||
max[1] = math.Max(0.0, math.Min(b.fh, max[1]))
|
|
||||||
|
|
||||||
gl.BindBuffer(gl.ARRAY_BUFFER, b.shadowBuf)
|
|
||||||
data := [16]float32{
|
|
||||||
float32(min[0]), float32(min[1]),
|
|
||||||
float32(min[0]), float32(max[1]),
|
|
||||||
float32(max[0]), float32(max[1]),
|
|
||||||
float32(max[0]), float32(min[1]),
|
|
||||||
float32(min[0] / b.fw), 1 - float32(min[1]/b.fh),
|
|
||||||
float32(min[0] / b.fw), 1 - float32(max[1]/b.fh),
|
|
||||||
float32(max[0] / b.fw), 1 - float32(max[1]/b.fh),
|
|
||||||
float32(max[0] / b.fw), 1 - float32(min[1]/b.fh),
|
|
||||||
}
|
|
||||||
gl.BufferData(gl.ARRAY_BUFFER, len(data)*4, unsafe.Pointer(&data[0]), gl.STREAM_DRAW)
|
|
||||||
|
|
||||||
gl.UseProgram(b.shd.ID)
|
|
||||||
gl.Uniform1i(b.shd.Image, 0)
|
|
||||||
gl.Uniform2f(b.shd.CanvasSize, float32(b.fw), float32(b.fh))
|
|
||||||
gl.Uniform1i(b.shd.UseAlphaTex, 0)
|
|
||||||
gl.Uniform1i(b.shd.Func, shdFuncBoxBlur)
|
|
||||||
|
|
||||||
gl.VertexAttribPointer(b.shd.Vertex, 2, gl.FLOAT, false, 0, nil)
|
|
||||||
gl.VertexAttribPointer(b.shd.TexCoord, 2, gl.FLOAT, false, 0, gl.PtrOffset(8*4))
|
|
||||||
gl.EnableVertexAttribArray(b.shd.Vertex)
|
|
||||||
gl.EnableVertexAttribArray(b.shd.TexCoord)
|
|
||||||
|
|
||||||
gl.Disable(gl.BLEND)
|
|
||||||
|
|
||||||
gl.ActiveTexture(gl.TEXTURE0)
|
|
||||||
|
|
||||||
gl.ClearColor(0, 0, 0, 0)
|
|
||||||
|
|
||||||
b.enableTextureRenderTarget(&b.offscr2)
|
|
||||||
gl.BindTexture(gl.TEXTURE_2D, b.offscr1.tex)
|
|
||||||
gl.Clear(gl.COLOR_BUFFER_BIT | gl.STENCIL_BUFFER_BIT)
|
|
||||||
b.box3(sizea, 0, false)
|
|
||||||
b.enableTextureRenderTarget(&b.offscr1)
|
|
||||||
gl.BindTexture(gl.TEXTURE_2D, b.offscr2.tex)
|
|
||||||
b.box3(sizeb, -0.5, false)
|
|
||||||
b.enableTextureRenderTarget(&b.offscr2)
|
|
||||||
gl.BindTexture(gl.TEXTURE_2D, b.offscr1.tex)
|
|
||||||
b.box3(sizec, 0, false)
|
|
||||||
b.enableTextureRenderTarget(&b.offscr1)
|
|
||||||
gl.BindTexture(gl.TEXTURE_2D, b.offscr2.tex)
|
|
||||||
b.box3(sizea, 0, true)
|
|
||||||
b.enableTextureRenderTarget(&b.offscr2)
|
|
||||||
gl.BindTexture(gl.TEXTURE_2D, b.offscr1.tex)
|
|
||||||
b.box3(sizeb, -0.5, true)
|
|
||||||
gl.Enable(gl.BLEND)
|
|
||||||
gl.BlendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA)
|
|
||||||
b.disableTextureRenderTarget()
|
|
||||||
gl.BindTexture(gl.TEXTURE_2D, b.offscr2.tex)
|
|
||||||
b.box3(sizec, 0, true)
|
|
||||||
|
|
||||||
gl.DisableVertexAttribArray(b.shd.Vertex)
|
|
||||||
gl.DisableVertexAttribArray(b.shd.TexCoord)
|
|
||||||
|
|
||||||
gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *GoGLBackend) box3(size int, offset float32, vertical bool) {
|
|
||||||
gl.Uniform1i(b.shd.BoxSize, int32(size))
|
|
||||||
if vertical {
|
|
||||||
gl.Uniform1i(b.shd.BoxVertical, 1)
|
|
||||||
gl.Uniform1f(b.shd.BoxScale, 1/float32(b.fh))
|
|
||||||
} else {
|
|
||||||
gl.Uniform1i(b.shd.BoxVertical, 0)
|
|
||||||
gl.Uniform1f(b.shd.BoxScale, 1/float32(b.fw))
|
|
||||||
}
|
|
||||||
gl.Uniform1f(b.shd.BoxOffset, offset)
|
|
||||||
gl.DrawArrays(gl.TRIANGLE_FAN, 0, 4)
|
|
||||||
}
|
|
|
@ -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) }
|
|
||||||
}
|
|
|
@ -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_ */
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -1,416 +0,0 @@
|
||||||
package goglbackend
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"math"
|
|
||||||
|
|
||||||
"github.com/tfriedel6/canvas/backend/backendbase"
|
|
||||||
"github.com/tfriedel6/canvas/backend/goglbackend/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
|
|
||||||
|
|
||||||
shd unifiedShader
|
|
||||||
|
|
||||||
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(unifiedVS, unifiedFS, &ctx.shd.shaderProgram)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ctx.shd.shaderProgram.mustLoadLocations(&ctx.shd)
|
|
||||||
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)
|
|
||||||
// todo should use gl.RED on OpenGL, gl.ALPHA on OpenGL ES
|
|
||||||
|
|
||||||
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)
|
|
||||||
gl.Viewport(int32(b.x), int32(b.y), int32(b.w), int32(b.h))
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *GoGLBackend) useShader(style *backendbase.FillStyle, useAlpha bool, alphaTexSlot int32) (vertexLoc, alphaTexCoordLoc uint32) {
|
|
||||||
gl.UseProgram(b.shd.ID)
|
|
||||||
gl.Uniform2f(b.shd.CanvasSize, float32(b.fw), float32(b.fh))
|
|
||||||
if useAlpha {
|
|
||||||
gl.Uniform1i(b.shd.UseAlphaTex, 1)
|
|
||||||
gl.Uniform1i(b.shd.AlphaTex, alphaTexSlot)
|
|
||||||
} else {
|
|
||||||
gl.Uniform1i(b.shd.UseAlphaTex, 0)
|
|
||||||
}
|
|
||||||
gl.Uniform1f(b.shd.GlobalAlpha, float32(style.Color.A)/255)
|
|
||||||
|
|
||||||
if lg := style.LinearGradient; lg != nil {
|
|
||||||
lg := lg.(*LinearGradient)
|
|
||||||
gl.ActiveTexture(gl.TEXTURE0)
|
|
||||||
gl.BindTexture(gl.TEXTURE_2D, lg.tex)
|
|
||||||
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.shd.From, float32(from[0]), float32(from[1]))
|
|
||||||
gl.Uniform2f(b.shd.Dir, float32(dir[0]), float32(dir[1]))
|
|
||||||
gl.Uniform1f(b.shd.Len, float32(length))
|
|
||||||
gl.Uniform1i(b.shd.Gradient, 0)
|
|
||||||
gl.Uniform1i(b.shd.Func, shdFuncLinearGradient)
|
|
||||||
return b.shd.Vertex, b.shd.TexCoord
|
|
||||||
}
|
|
||||||
if rg := style.RadialGradient; rg != nil {
|
|
||||||
rg := rg.(*RadialGradient)
|
|
||||||
gl.ActiveTexture(gl.TEXTURE0)
|
|
||||||
gl.BindTexture(gl.TEXTURE_2D, rg.tex)
|
|
||||||
from := vec{style.Gradient.X0, style.Gradient.Y0}
|
|
||||||
to := vec{style.Gradient.X1, style.Gradient.Y1}
|
|
||||||
gl.Uniform2f(b.shd.From, float32(from[0]), float32(from[1]))
|
|
||||||
gl.Uniform2f(b.shd.To, float32(to[0]), float32(to[1]))
|
|
||||||
gl.Uniform1f(b.shd.RadFrom, float32(style.Gradient.RadFrom))
|
|
||||||
gl.Uniform1f(b.shd.RadTo, float32(style.Gradient.RadTo))
|
|
||||||
gl.Uniform1i(b.shd.Gradient, 0)
|
|
||||||
gl.Uniform1i(b.shd.Func, shdFuncRadialGradient)
|
|
||||||
return b.shd.Vertex, b.shd.TexCoord
|
|
||||||
}
|
|
||||||
if ip := style.ImagePattern; ip != nil {
|
|
||||||
ipd := ip.(*ImagePattern).data
|
|
||||||
img := ipd.Image.(*Image)
|
|
||||||
gl.ActiveTexture(gl.TEXTURE0)
|
|
||||||
gl.BindTexture(gl.TEXTURE_2D, img.tex)
|
|
||||||
gl.Uniform2f(b.shd.ImageSize, float32(img.w), float32(img.h))
|
|
||||||
gl.Uniform1i(b.shd.Image, 0)
|
|
||||||
var f32mat [9]float32
|
|
||||||
for i, v := range ipd.Transform {
|
|
||||||
f32mat[i] = float32(v)
|
|
||||||
}
|
|
||||||
gl.UniformMatrix3fv(b.shd.ImageTransform, 1, false, &f32mat[0])
|
|
||||||
switch ipd.Repeat {
|
|
||||||
case backendbase.Repeat:
|
|
||||||
gl.Uniform2f(b.shd.Repeat, 1, 1)
|
|
||||||
case backendbase.RepeatX:
|
|
||||||
gl.Uniform2f(b.shd.Repeat, 1, 0)
|
|
||||||
case backendbase.RepeatY:
|
|
||||||
gl.Uniform2f(b.shd.Repeat, 0, 1)
|
|
||||||
case backendbase.NoRepeat:
|
|
||||||
gl.Uniform2f(b.shd.Repeat, 0, 0)
|
|
||||||
}
|
|
||||||
gl.Uniform1i(b.shd.Func, shdFuncImagePattern)
|
|
||||||
return b.shd.Vertex, b.shd.TexCoord
|
|
||||||
}
|
|
||||||
|
|
||||||
cr := float32(style.Color.R) / 255
|
|
||||||
cg := float32(style.Color.G) / 255
|
|
||||||
cb := float32(style.Color.B) / 255
|
|
||||||
ca := float32(style.Color.A) / 255
|
|
||||||
gl.Uniform4f(b.shd.Color, cr, cg, cb, ca)
|
|
||||||
gl.Uniform1f(b.shd.GlobalAlpha, 1)
|
|
||||||
gl.Uniform1i(b.shd.Func, shdFuncSolid)
|
|
||||||
return b.shd.Vertex, b.shd.TexCoord
|
|
||||||
}
|
|
||||||
|
|
||||||
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}
|
|
||||||
}
|
|
|
@ -1,102 +0,0 @@
|
||||||
package goglbackend
|
|
||||||
|
|
||||||
import (
|
|
||||||
"runtime"
|
|
||||||
|
|
||||||
"github.com/tfriedel6/canvas/backend/backendbase"
|
|
||||||
"github.com/tfriedel6/canvas/backend/goglbackend/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
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
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]))
|
|
||||||
}
|
|
|
@ -1,101 +0,0 @@
|
||||||
package goglbackend
|
|
||||||
|
|
||||||
import (
|
|
||||||
"image"
|
|
||||||
"image/color"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"github.com/tfriedel6/canvas/backend/goglbackend/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
|
|
||||||
}
|
|
||||||
|
|
||||||
var vp [4]int32
|
|
||||||
gl.GetIntegerv(gl.VIEWPORT, &vp[0])
|
|
||||||
|
|
||||||
size := int(vp[2] * vp[3] * 3)
|
|
||||||
if len(b.imageBuf) < size {
|
|
||||||
b.imageBuf = make([]byte, size)
|
|
||||||
}
|
|
||||||
gl.ReadPixels(vp[0], vp[1], vp[2], vp[3], gl.RGB, gl.UNSIGNED_BYTE, gl.Ptr(&b.imageBuf[0]))
|
|
||||||
|
|
||||||
rgba := image.NewRGBA(image.Rect(x, y, x+w, y+h))
|
|
||||||
for cy := y; cy < y+h; cy++ {
|
|
||||||
bp := (int(vp[3])-h+cy)*int(vp[2])*3 + x*3
|
|
||||||
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.shd.ID)
|
|
||||||
gl.Uniform1i(b.shd.Image, 0)
|
|
||||||
gl.Uniform2f(b.shd.CanvasSize, float32(b.fw), float32(b.fh))
|
|
||||||
gl.Uniform1f(b.shd.GlobalAlpha, 1)
|
|
||||||
gl.Uniform1i(b.shd.UseAlphaTex, 0)
|
|
||||||
gl.Uniform1i(b.shd.Func, shdFuncImage)
|
|
||||||
gl.VertexAttribPointer(b.shd.Vertex, 2, gl.FLOAT, false, 0, nil)
|
|
||||||
gl.VertexAttribPointer(b.shd.TexCoord, 2, gl.FLOAT, false, 0, gl.PtrOffset(8*4))
|
|
||||||
gl.EnableVertexAttribArray(b.shd.Vertex)
|
|
||||||
gl.EnableVertexAttribArray(b.shd.TexCoord)
|
|
||||||
gl.DrawArrays(gl.TRIANGLE_FAN, 0, 4)
|
|
||||||
gl.DisableVertexAttribArray(b.shd.Vertex)
|
|
||||||
gl.DisableVertexAttribArray(b.shd.TexCoord)
|
|
||||||
}
|
|
|
@ -1,227 +0,0 @@
|
||||||
package goglbackend
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"image"
|
|
||||||
"runtime"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"github.com/tfriedel6/canvas/backend/backendbase"
|
|
||||||
"github.com/tfriedel6/canvas/backend/goglbackend/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)
|
|
||||||
if tex == 0 {
|
|
||||||
return nil, errors.New("glGenTextures failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
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.shd.ID)
|
|
||||||
gl.Uniform1i(b.shd.Image, 0)
|
|
||||||
gl.Uniform2f(b.shd.CanvasSize, float32(b.fw), float32(b.fh))
|
|
||||||
gl.Uniform1f(b.shd.GlobalAlpha, float32(alpha))
|
|
||||||
gl.Uniform1i(b.shd.UseAlphaTex, 0)
|
|
||||||
gl.Uniform1i(b.shd.Func, shdFuncImage)
|
|
||||||
gl.VertexAttribPointer(b.shd.Vertex, 2, gl.FLOAT, false, 0, nil)
|
|
||||||
gl.VertexAttribPointer(b.shd.TexCoord, 2, gl.FLOAT, false, 0, gl.PtrOffset(8*4))
|
|
||||||
gl.EnableVertexAttribArray(b.shd.Vertex)
|
|
||||||
gl.EnableVertexAttribArray(b.shd.TexCoord)
|
|
||||||
gl.DrawArrays(gl.TRIANGLE_FAN, 0, 4)
|
|
||||||
gl.DisableVertexAttribArray(b.shd.Vertex)
|
|
||||||
gl.DisableVertexAttribArray(b.shd.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 }
|
|
|
@ -1,202 +0,0 @@
|
||||||
package goglbackend
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"unicode"
|
|
||||||
"unicode/utf8"
|
|
||||||
|
|
||||||
"github.com/tfriedel6/canvas/backend/goglbackend/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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,154 +0,0 @@
|
||||||
package goglbackend
|
|
||||||
|
|
||||||
var unifiedVS = `
|
|
||||||
attribute vec2 vertex, texCoord;
|
|
||||||
|
|
||||||
uniform vec2 canvasSize;
|
|
||||||
|
|
||||||
varying vec2 v_cp, v_tc;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
v_tc = texCoord;
|
|
||||||
v_cp = vertex;
|
|
||||||
vec2 glp = vertex * 2.0 / canvasSize - 1.0;
|
|
||||||
gl_Position = vec4(glp.x, -glp.y, 0.0, 1.0);
|
|
||||||
}
|
|
||||||
`
|
|
||||||
var unifiedFS = `
|
|
||||||
#ifdef GL_ES
|
|
||||||
precision mediump float;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
varying vec2 v_cp, v_tc;
|
|
||||||
|
|
||||||
uniform int func;
|
|
||||||
|
|
||||||
uniform vec4 color;
|
|
||||||
uniform float globalAlpha;
|
|
||||||
|
|
||||||
uniform sampler2D gradient;
|
|
||||||
uniform vec2 from, dir, to;
|
|
||||||
uniform float len, radFrom, radTo;
|
|
||||||
|
|
||||||
uniform vec2 imageSize;
|
|
||||||
uniform sampler2D image;
|
|
||||||
uniform mat3 imageTransform;
|
|
||||||
uniform vec2 repeat;
|
|
||||||
|
|
||||||
uniform bool useAlphaTex;
|
|
||||||
uniform sampler2D alphaTex;
|
|
||||||
|
|
||||||
uniform int boxSize;
|
|
||||||
uniform bool boxVertical;
|
|
||||||
uniform float boxScale;
|
|
||||||
uniform float boxOffset;
|
|
||||||
|
|
||||||
bool isNaN(float v) {
|
|
||||||
return v < 0.0 || 0.0 < v || v == 0.0 ? false : true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vec4 col = color;
|
|
||||||
|
|
||||||
if (func == 5) {
|
|
||||||
vec4 sum = vec4(0.0);
|
|
||||||
if (boxVertical) {
|
|
||||||
vec2 start = v_tc - vec2(0.0, (float(boxSize) * 0.5 + boxOffset) * boxScale);
|
|
||||||
for (int i=0; i <= boxSize; i++) {
|
|
||||||
sum += texture2D(image, start + vec2(0.0, float(i) * boxScale));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
vec2 start = v_tc - vec2((float(boxSize) * 0.5 + boxOffset) * boxScale, 0.0);
|
|
||||||
for (int i=0; i <= boxSize; i++) {
|
|
||||||
sum += texture2D(image, start + vec2(float(i) * boxScale, 0.0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
gl_FragColor = sum / float(boxSize+1);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (func == 1) {
|
|
||||||
vec2 v = v_cp - from;
|
|
||||||
float r = dot(v, dir) / len;
|
|
||||||
r = clamp(r, 0.0, 1.0);
|
|
||||||
col = texture2D(gradient, vec2(r, 0.0));
|
|
||||||
} else if (func == 2) {
|
|
||||||
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);
|
|
||||||
o = clamp(o, 0.0, 1.0);
|
|
||||||
col = texture2D(gradient, vec2(o, 0.0));
|
|
||||||
} else if (func == 3) {
|
|
||||||
vec3 tfpt = vec3(v_cp, 1.0) * imageTransform;
|
|
||||||
vec2 imgpt = tfpt.xy / imageSize;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
} else if (func == 4) {
|
|
||||||
col = texture2D(image, v_tc);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (useAlphaTex) {
|
|
||||||
col.a *= texture2D(alphaTex, v_tc).a * globalAlpha;
|
|
||||||
} else {
|
|
||||||
col.a *= globalAlpha;
|
|
||||||
}
|
|
||||||
|
|
||||||
gl_FragColor = col;
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
const (
|
|
||||||
shdFuncSolid int32 = iota
|
|
||||||
shdFuncLinearGradient
|
|
||||||
shdFuncRadialGradient
|
|
||||||
shdFuncImagePattern
|
|
||||||
shdFuncImage
|
|
||||||
shdFuncBoxBlur
|
|
||||||
)
|
|
||||||
|
|
||||||
type unifiedShader struct {
|
|
||||||
shaderProgram
|
|
||||||
|
|
||||||
Vertex uint32
|
|
||||||
TexCoord uint32
|
|
||||||
|
|
||||||
CanvasSize int32
|
|
||||||
Color int32
|
|
||||||
GlobalAlpha int32
|
|
||||||
|
|
||||||
Func int32
|
|
||||||
|
|
||||||
UseAlphaTex int32
|
|
||||||
AlphaTex int32
|
|
||||||
|
|
||||||
Gradient int32
|
|
||||||
From, To, Dir int32
|
|
||||||
Len int32
|
|
||||||
RadFrom, RadTo int32
|
|
||||||
|
|
||||||
ImageSize int32
|
|
||||||
Image int32
|
|
||||||
ImageTransform int32
|
|
||||||
Repeat int32
|
|
||||||
|
|
||||||
BoxSize int32
|
|
||||||
BoxVertical int32
|
|
||||||
BoxScale int32
|
|
||||||
BoxOffset int32
|
|
||||||
}
|
|
|
@ -5,11 +5,11 @@ import (
|
||||||
"image/color"
|
"image/color"
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
"github.com/tfriedel6/canvas/backend/backendbase"
|
"git.mstar.dev/mstar/canvas/backend/backendbase"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (b *SoftwareBackend) Clear(pts [4][2]float64) {
|
func (b *SoftwareBackend) Clear(pts [4]backendbase.Vec) {
|
||||||
iterateTriangles(pts[:], func(tri [][2]float64) {
|
iterateTriangles(pts[:], func(tri []backendbase.Vec) {
|
||||||
b.fillTriangleNoAA(tri, func(x, y int) {
|
b.fillTriangleNoAA(tri, func(x, y int) {
|
||||||
if b.clip.AlphaAt(x, y).A == 0 {
|
if b.clip.AlphaAt(x, y).A == 0 {
|
||||||
return
|
return
|
||||||
|
@ -19,9 +19,27 @@ func (b *SoftwareBackend) Clear(pts [4][2]float64) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *SoftwareBackend) Fill(style *backendbase.FillStyle, pts [][2]float64, canOverlap bool) {
|
func (b *SoftwareBackend) Fill(
|
||||||
|
style *backendbase.FillStyle,
|
||||||
|
pts []backendbase.Vec,
|
||||||
|
tf backendbase.Mat,
|
||||||
|
canOverlap bool,
|
||||||
|
) {
|
||||||
ffn := fillFunc(style)
|
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 {
|
if style.Blur > 0 {
|
||||||
b.activateBlurTarget()
|
b.activateBlurTarget()
|
||||||
b.fillTriangles(pts, ffn)
|
b.fillTriangles(pts, ffn)
|
||||||
|
@ -31,7 +49,11 @@ func (b *SoftwareBackend) Fill(style *backendbase.FillStyle, pts [][2]float64, c
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *SoftwareBackend) FillImageMask(style *backendbase.FillStyle, mask *image.Alpha, pts [4][2]float64) {
|
func (b *SoftwareBackend) FillImageMask(
|
||||||
|
style *backendbase.FillStyle,
|
||||||
|
mask *image.Alpha,
|
||||||
|
pts [4]backendbase.Vec,
|
||||||
|
) {
|
||||||
ffn := fillFunc(style)
|
ffn := fillFunc(style)
|
||||||
|
|
||||||
mw := float64(mask.Bounds().Dx())
|
mw := float64(mask.Bounds().Dx())
|
||||||
|
@ -51,24 +73,27 @@ func (b *SoftwareBackend) FillImageMask(style *backendbase.FillStyle, mask *imag
|
||||||
func fillFunc(style *backendbase.FillStyle) func(x, y float64) color.RGBA {
|
func fillFunc(style *backendbase.FillStyle) func(x, y float64) color.RGBA {
|
||||||
if lg := style.LinearGradient; lg != nil {
|
if lg := style.LinearGradient; lg != nil {
|
||||||
lg := lg.(*LinearGradient)
|
lg := lg.(*LinearGradient)
|
||||||
from := [2]float64{style.Gradient.X0, style.Gradient.Y0}
|
from := backendbase.Vec{style.Gradient.X0, style.Gradient.Y0}
|
||||||
dir := [2]float64{style.Gradient.X1 - style.Gradient.X0, style.Gradient.Y1 - 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])
|
dirlen := math.Sqrt(dir[0]*dir[0] + dir[1]*dir[1])
|
||||||
dir[0] /= dirlen
|
dir[0] /= dirlen
|
||||||
dir[1] /= dirlen
|
dir[1] /= dirlen
|
||||||
return func(x, y float64) color.RGBA {
|
return func(x, y float64) color.RGBA {
|
||||||
pos := [2]float64{x - from[0], y - from[1]}
|
pos := backendbase.Vec{x - from[0], y - from[1]}
|
||||||
r := (pos[0]*dir[0] + pos[1]*dir[1]) / dirlen
|
r := (pos[0]*dir[0] + pos[1]*dir[1]) / dirlen
|
||||||
return lg.data.ColorAt(r)
|
return lg.data.ColorAt(r)
|
||||||
}
|
}
|
||||||
} else if rg := style.RadialGradient; rg != nil {
|
} else if rg := style.RadialGradient; rg != nil {
|
||||||
rg := rg.(*RadialGradient)
|
rg := rg.(*RadialGradient)
|
||||||
from := [2]float64{style.Gradient.X0, style.Gradient.Y0}
|
from := backendbase.Vec{style.Gradient.X0, style.Gradient.Y0}
|
||||||
to := [2]float64{style.Gradient.X1, style.Gradient.Y1}
|
to := backendbase.Vec{style.Gradient.X1, style.Gradient.Y1}
|
||||||
radFrom := style.Gradient.RadFrom
|
radFrom := style.Gradient.RadFrom
|
||||||
radTo := style.Gradient.RadTo
|
radTo := style.Gradient.RadTo
|
||||||
return func(x, y float64) color.RGBA {
|
return func(x, y float64) color.RGBA {
|
||||||
pos := [2]float64{x, y}
|
pos := backendbase.Vec{x, y}
|
||||||
oa := 0.5 * math.Sqrt(
|
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)-
|
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)*
|
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)*
|
||||||
|
@ -92,7 +117,7 @@ func fillFunc(style *backendbase.FillStyle) func(x, y float64) color.RGBA {
|
||||||
rx := ip.data.Repeat == backendbase.Repeat || ip.data.Repeat == backendbase.RepeatX
|
rx := ip.data.Repeat == backendbase.Repeat || ip.data.Repeat == backendbase.RepeatX
|
||||||
ry := ip.data.Repeat == backendbase.Repeat || ip.data.Repeat == backendbase.RepeatY
|
ry := ip.data.Repeat == backendbase.Repeat || ip.data.Repeat == backendbase.RepeatY
|
||||||
return func(x, y float64) color.RGBA {
|
return func(x, y float64) color.RGBA {
|
||||||
pos := [2]float64{x, y}
|
pos := backendbase.Vec{x, y}
|
||||||
tfptx := pos[0]*ip.data.Transform[0] + pos[1]*ip.data.Transform[1] + ip.data.Transform[2]
|
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]
|
tfpty := pos[0]*ip.data.Transform[3] + pos[1]*ip.data.Transform[4] + ip.data.Transform[5]
|
||||||
|
|
||||||
|
@ -134,10 +159,10 @@ func (b *SoftwareBackend) ClearClip() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *SoftwareBackend) Clip(pts [][2]float64) {
|
func (b *SoftwareBackend) Clip(pts []backendbase.Vec) {
|
||||||
b.clearStencil()
|
b.clearStencil()
|
||||||
|
|
||||||
iterateTriangles(pts[:], func(tri [][2]float64) {
|
iterateTriangles(pts[:], func(tri []backendbase.Vec) {
|
||||||
b.fillTriangleNoAA(tri, func(x, y int) {
|
b.fillTriangleNoAA(tri, func(x, y int) {
|
||||||
b.stencil.SetAlpha(x, y, color.Alpha{A: 255})
|
b.stencil.SetAlpha(x, y, color.Alpha{A: 255})
|
||||||
})
|
})
|
||||||
|
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"image/color"
|
"image/color"
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
"github.com/tfriedel6/canvas/backend/backendbase"
|
"git.mstar.dev/mstar/canvas/backend/backendbase"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Image struct {
|
type Image struct {
|
||||||
|
@ -43,7 +43,12 @@ func halveImage(img image.Image) (*image.RGBA, int, int) {
|
||||||
return rimg, w, h
|
return rimg, w, h
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *SoftwareBackend) DrawImage(dimg backendbase.Image, sx, sy, sw, sh float64, pts [4][2]float64, alpha float64) {
|
func (b *SoftwareBackend) DrawImage(
|
||||||
|
dimg backendbase.Image,
|
||||||
|
sx, sy, sw, sh float64,
|
||||||
|
pts [4]backendbase.Vec,
|
||||||
|
alpha float64,
|
||||||
|
) {
|
||||||
simg := dimg.(*Image)
|
simg := dimg.(*Image)
|
||||||
if simg.deleted {
|
if simg.deleted {
|
||||||
return
|
return
|
||||||
|
@ -130,7 +135,9 @@ type ImagePattern struct {
|
||||||
data backendbase.ImagePatternData
|
data backendbase.ImagePatternData
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *SoftwareBackend) LoadImagePattern(data backendbase.ImagePatternData) backendbase.ImagePattern {
|
func (b *SoftwareBackend) LoadImagePattern(
|
||||||
|
data backendbase.ImagePatternData,
|
||||||
|
) backendbase.ImagePattern {
|
||||||
return &ImagePattern{
|
return &ImagePattern{
|
||||||
data: data,
|
data: data,
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"image"
|
"image"
|
||||||
"image/draw"
|
"image/draw"
|
||||||
|
|
||||||
"github.com/tfriedel6/canvas/backend/backendbase"
|
"git.mstar.dev/mstar/canvas/backend/backendbase"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SoftwareBackend struct {
|
type SoftwareBackend struct {
|
||||||
|
|
|
@ -3,9 +3,11 @@ package softwarebackend
|
||||||
import (
|
import (
|
||||||
"image/color"
|
"image/color"
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
|
"git.mstar.dev/mstar/canvas/backend/backendbase"
|
||||||
)
|
)
|
||||||
|
|
||||||
func triangleLR(tri [][2]float64, y float64) (l, r float64, outside bool) {
|
func triangleLR(tri []backendbase.Vec, y float64) (l, r float64, outside bool) {
|
||||||
a, b, c := tri[0], tri[1], tri[2]
|
a, b, c := tri[0], tri[1], tri[2]
|
||||||
|
|
||||||
// sort by y
|
// sort by y
|
||||||
|
@ -46,7 +48,7 @@ func triangleLR(tri [][2]float64, y float64) (l, r float64, outside bool) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *SoftwareBackend) fillTriangleNoAA(tri [][2]float64, fn func(x, y int)) {
|
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])))
|
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])))
|
maxY := int(math.Ceil(math.Max(math.Max(tri[0][1], tri[1][1]), tri[2][1])))
|
||||||
if minY < 0 {
|
if minY < 0 {
|
||||||
|
@ -94,7 +96,12 @@ type msaaPixel struct {
|
||||||
tx, ty float64
|
tx, ty float64
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *SoftwareBackend) fillTriangleMSAA(tri [][2]float64, msaaLevel int, msaaPixels []msaaPixel, fn func(x, y int)) []msaaPixel {
|
func (b *SoftwareBackend) fillTriangleMSAA(
|
||||||
|
tri []backendbase.Vec,
|
||||||
|
msaaLevel int,
|
||||||
|
msaaPixels []msaaPixel,
|
||||||
|
fn func(x, y int),
|
||||||
|
) []msaaPixel {
|
||||||
msaaStep := 1.0 / float64(msaaLevel+1)
|
msaaStep := 1.0 / float64(msaaLevel+1)
|
||||||
|
|
||||||
minY := int(math.Floor(math.Min(math.Min(tri[0][1], tri[1][1]), tri[2][1])))
|
minY := int(math.Floor(math.Min(math.Min(tri[0][1], tri[1][1]), tri[2][1])))
|
||||||
|
@ -173,7 +180,10 @@ func (b *SoftwareBackend) fillTriangleMSAA(tri [][2]float64, msaaLevel int, msaa
|
||||||
sx := float64(x) + msaaStep*0.5
|
sx := float64(x) + msaaStep*0.5
|
||||||
for stepx := 0; stepx <= msaaLevel; stepx++ {
|
for stepx := 0; stepx <= msaaLevel; stepx++ {
|
||||||
if sx >= l[stepy] && sx < r[stepy] {
|
if sx >= l[stepy] && sx < r[stepy] {
|
||||||
msaaPixels = addMSAAPixel(msaaPixels, msaaPixel{ix: x, iy: y, fx: sx, fy: sy})
|
msaaPixels = addMSAAPixel(
|
||||||
|
msaaPixels,
|
||||||
|
msaaPixel{ix: x, iy: y, fx: sx, fy: sy},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
sx += msaaStep
|
sx += msaaStep
|
||||||
}
|
}
|
||||||
|
@ -194,15 +204,19 @@ func addMSAAPixel(msaaPixels []msaaPixel, px msaaPixel) []msaaPixel {
|
||||||
return append(msaaPixels, px)
|
return append(msaaPixels, px)
|
||||||
}
|
}
|
||||||
|
|
||||||
func quadArea(quad [4][2]float64) float64 {
|
func quadArea(quad [4]backendbase.Vec) float64 {
|
||||||
leftv := [2]float64{quad[1][0] - quad[0][0], quad[1][1] - quad[0][1]}
|
leftv := backendbase.Vec{quad[1][0] - quad[0][0], quad[1][1] - quad[0][1]}
|
||||||
topv := [2]float64{quad[3][0] - quad[0][0], quad[3][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])
|
return math.Abs(leftv[0]*topv[1] - leftv[1]*topv[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *SoftwareBackend) fillQuadNoAA(quad [4][2]float64, fn func(x, y int, tx, ty float64)) {
|
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]))))
|
minY := int(
|
||||||
maxY := int(math.Ceil(math.Max(math.Max(quad[0][1], quad[1][1]), math.Max(quad[2][1], quad[3][1]))))
|
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 {
|
if minY < 0 {
|
||||||
minY = 0
|
minY = 0
|
||||||
} else if minY >= b.h {
|
} else if minY >= b.h {
|
||||||
|
@ -214,17 +228,17 @@ func (b *SoftwareBackend) fillQuadNoAA(quad [4][2]float64, fn func(x, y int, tx,
|
||||||
maxY = b.h - 1
|
maxY = b.h - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
leftv := [2]float64{quad[1][0] - quad[0][0], quad[1][1] - quad[0][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])
|
leftLen := math.Sqrt(leftv[0]*leftv[0] + leftv[1]*leftv[1])
|
||||||
leftv[0] /= leftLen
|
leftv[0] /= leftLen
|
||||||
leftv[1] /= leftLen
|
leftv[1] /= leftLen
|
||||||
topv := [2]float64{quad[3][0] - quad[0][0], quad[3][1] - quad[0][1]}
|
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])
|
topLen := math.Sqrt(topv[0]*topv[0] + topv[1]*topv[1])
|
||||||
topv[0] /= topLen
|
topv[0] /= topLen
|
||||||
topv[1] /= topLen
|
topv[1] /= topLen
|
||||||
|
|
||||||
tri1 := [3][2]float64{quad[0], quad[1], quad[2]}
|
tri1 := [3]backendbase.Vec{quad[0], quad[1], quad[2]}
|
||||||
tri2 := [3][2]float64{quad[0], quad[2], quad[3]}
|
tri2 := [3]backendbase.Vec{quad[0], quad[2], quad[3]}
|
||||||
for y := minY; y <= maxY; y++ {
|
for y := minY; y <= maxY; y++ {
|
||||||
lf1, rf1, out1 := triangleLR(tri1[:], float64(y)+0.5)
|
lf1, rf1, out1 := triangleLR(tri1[:], float64(y)+0.5)
|
||||||
lf2, rf2, out2 := triangleLR(tri2[:], float64(y)+0.5)
|
lf2, rf2, out2 := triangleLR(tri2[:], float64(y)+0.5)
|
||||||
|
@ -270,11 +284,20 @@ func (b *SoftwareBackend) fillQuadNoAA(quad [4][2]float64, fn func(x, y int, tx,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *SoftwareBackend) fillQuadMSAA(quad [4][2]float64, msaaLevel int, msaaPixels []msaaPixel, fn func(x, y int, tx, ty float64)) []msaaPixel {
|
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)
|
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]))))
|
minY := int(
|
||||||
maxY := int(math.Ceil(math.Max(math.Max(quad[0][1], quad[1][1]), math.Max(quad[2][1], quad[3][1]))))
|
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 {
|
if minY < 0 {
|
||||||
minY = 0
|
minY = 0
|
||||||
} else if minY >= b.h {
|
} else if minY >= b.h {
|
||||||
|
@ -286,17 +309,17 @@ func (b *SoftwareBackend) fillQuadMSAA(quad [4][2]float64, msaaLevel int, msaaPi
|
||||||
maxY = b.h - 1
|
maxY = b.h - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
leftv := [2]float64{quad[1][0] - quad[0][0], quad[1][1] - quad[0][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])
|
leftLen := math.Sqrt(leftv[0]*leftv[0] + leftv[1]*leftv[1])
|
||||||
leftv[0] /= leftLen
|
leftv[0] /= leftLen
|
||||||
leftv[1] /= leftLen
|
leftv[1] /= leftLen
|
||||||
topv := [2]float64{quad[3][0] - quad[0][0], quad[3][1] - quad[0][1]}
|
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])
|
topLen := math.Sqrt(topv[0]*topv[0] + topv[1]*topv[1])
|
||||||
topv[0] /= topLen
|
topv[0] /= topLen
|
||||||
topv[1] /= topLen
|
topv[1] /= topLen
|
||||||
|
|
||||||
tri1 := [3][2]float64{quad[0], quad[1], quad[2]}
|
tri1 := [3]backendbase.Vec{quad[0], quad[1], quad[2]}
|
||||||
tri2 := [3][2]float64{quad[0], quad[2], quad[3]}
|
tri2 := [3]backendbase.Vec{quad[0], quad[2], quad[3]}
|
||||||
for y := minY; y <= maxY; y++ {
|
for y := minY; y <= maxY; y++ {
|
||||||
var l, r [5]float64
|
var l, r [5]float64
|
||||||
allOut := true
|
allOut := true
|
||||||
|
@ -388,7 +411,17 @@ func (b *SoftwareBackend) fillQuadMSAA(quad [4][2]float64, msaaLevel int, msaaPi
|
||||||
ty = (tfy - topv[1]*tx) / 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})
|
msaaPixels = addMSAAPixel(
|
||||||
|
msaaPixels,
|
||||||
|
msaaPixel{
|
||||||
|
ix: x,
|
||||||
|
iy: y,
|
||||||
|
fx: sx,
|
||||||
|
fy: sy,
|
||||||
|
tx: tx / topLen,
|
||||||
|
ty: ty / leftLen,
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
sx += msaaStep
|
sx += msaaStep
|
||||||
}
|
}
|
||||||
|
@ -400,7 +433,10 @@ func (b *SoftwareBackend) fillQuadMSAA(quad [4][2]float64, msaaLevel int, msaaPi
|
||||||
return msaaPixels
|
return msaaPixels
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *SoftwareBackend) fillQuad(pts [4][2]float64, fn func(x, y, tx, ty float64) color.RGBA) {
|
func (b *SoftwareBackend) fillQuad(
|
||||||
|
pts [4]backendbase.Vec,
|
||||||
|
fn func(x, y, tx, ty float64) color.RGBA,
|
||||||
|
) {
|
||||||
b.clearStencil()
|
b.clearStencil()
|
||||||
|
|
||||||
if b.MSAA > 0 {
|
if b.MSAA > 0 {
|
||||||
|
@ -424,7 +460,8 @@ func (b *SoftwareBackend) fillQuad(pts [4][2]float64, fn func(x, y, tx, ty float
|
||||||
samples := (b.MSAA + 1) * (b.MSAA + 1)
|
samples := (b.MSAA + 1) * (b.MSAA + 1)
|
||||||
|
|
||||||
for i, px := range msaaPixels {
|
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 {
|
if px.ix < 0 || b.clip.AlphaAt(px.ix, px.iy).A == 0 ||
|
||||||
|
b.stencil.AlphaAt(px.ix, px.iy).A > 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
b.stencil.SetAlpha(px.ix, px.iy, color.Alpha{A: 255})
|
b.stencil.SetAlpha(px.ix, px.iy, color.Alpha{A: 255})
|
||||||
|
@ -470,9 +507,9 @@ func (b *SoftwareBackend) fillQuad(pts [4][2]float64, fn func(x, y, tx, ty float
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func iterateTriangles(pts [][2]float64, fn func(tri [][2]float64)) {
|
func iterateTriangles(pts []backendbase.Vec, fn func(tri []backendbase.Vec)) {
|
||||||
if len(pts) == 4 {
|
if len(pts) == 4 {
|
||||||
var buf [3][2]float64
|
var buf [3]backendbase.Vec
|
||||||
buf[0] = pts[0]
|
buf[0] = pts[0]
|
||||||
buf[1] = pts[1]
|
buf[1] = pts[1]
|
||||||
buf[2] = pts[2]
|
buf[2] = pts[2]
|
||||||
|
@ -487,8 +524,11 @@ func iterateTriangles(pts [][2]float64, fn func(tri [][2]float64)) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *SoftwareBackend) fillTrianglesNoAA(pts [][2]float64, fn func(x, y float64) color.RGBA) {
|
func (b *SoftwareBackend) fillTrianglesNoAA(
|
||||||
iterateTriangles(pts[:], func(tri [][2]float64) {
|
pts []backendbase.Vec,
|
||||||
|
fn func(x, y float64) color.RGBA,
|
||||||
|
) {
|
||||||
|
iterateTriangles(pts[:], func(tri []backendbase.Vec) {
|
||||||
b.fillTriangleNoAA(tri, func(x, y int) {
|
b.fillTriangleNoAA(tri, func(x, y int) {
|
||||||
if b.clip.AlphaAt(x, y).A == 0 {
|
if b.clip.AlphaAt(x, y).A == 0 {
|
||||||
return
|
return
|
||||||
|
@ -505,11 +545,15 @@ func (b *SoftwareBackend) fillTrianglesNoAA(pts [][2]float64, fn func(x, y float
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *SoftwareBackend) fillTrianglesMSAA(pts [][2]float64, msaaLevel int, fn func(x, y float64) color.RGBA) {
|
func (b *SoftwareBackend) fillTrianglesMSAA(
|
||||||
|
pts []backendbase.Vec,
|
||||||
|
msaaLevel int,
|
||||||
|
fn func(x, y float64) color.RGBA,
|
||||||
|
) {
|
||||||
var msaaPixelBuf [500]msaaPixel
|
var msaaPixelBuf [500]msaaPixel
|
||||||
msaaPixels := msaaPixelBuf[:0]
|
msaaPixels := msaaPixelBuf[:0]
|
||||||
|
|
||||||
iterateTriangles(pts[:], func(tri [][2]float64) {
|
iterateTriangles(pts[:], func(tri []backendbase.Vec) {
|
||||||
msaaPixels = b.fillTriangleMSAA(tri, msaaLevel, msaaPixels, func(x, y int) {
|
msaaPixels = b.fillTriangleMSAA(tri, msaaLevel, msaaPixels, func(x, y int) {
|
||||||
if b.clip.AlphaAt(x, y).A == 0 {
|
if b.clip.AlphaAt(x, y).A == 0 {
|
||||||
return
|
return
|
||||||
|
@ -528,7 +572,8 @@ func (b *SoftwareBackend) fillTrianglesMSAA(pts [][2]float64, msaaLevel int, fn
|
||||||
samples := (msaaLevel + 1) * (msaaLevel + 1)
|
samples := (msaaLevel + 1) * (msaaLevel + 1)
|
||||||
|
|
||||||
for i, px := range msaaPixels {
|
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 {
|
if px.ix < 0 || b.clip.AlphaAt(px.ix, px.iy).A == 0 ||
|
||||||
|
b.stencil.AlphaAt(px.ix, px.iy).A > 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
b.stencil.SetAlpha(px.ix, px.iy, color.Alpha{A: 255})
|
b.stencil.SetAlpha(px.ix, px.iy, color.Alpha{A: 255})
|
||||||
|
@ -558,7 +603,7 @@ func (b *SoftwareBackend) fillTrianglesMSAA(pts [][2]float64, msaaLevel int, fn
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *SoftwareBackend) fillTriangles(pts [][2]float64, fn func(x, y float64) color.RGBA) {
|
func (b *SoftwareBackend) fillTriangles(pts []backendbase.Vec, fn func(x, y float64) color.RGBA) {
|
||||||
b.clearStencil()
|
b.clearStencil()
|
||||||
|
|
||||||
if b.MSAA > 0 {
|
if b.MSAA > 0 {
|
||||||
|
|
|
@ -1,72 +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.shd.Vertex, 2, gl.FLOAT, false, 0, 0)
|
|
||||||
|
|
||||||
b.glctx.UseProgram(b.shd.ID)
|
|
||||||
b.glctx.Uniform4f(b.shd.Color, 1, 1, 1, 1)
|
|
||||||
b.glctx.Uniform2f(b.shd.CanvasSize, float32(b.fw), float32(b.fh))
|
|
||||||
b.glctx.Uniform1f(b.shd.GlobalAlpha, 1)
|
|
||||||
b.glctx.Uniform1i(b.shd.UseAlphaTex, 0)
|
|
||||||
b.glctx.Uniform1i(b.shd.Func, shdFuncSolid)
|
|
||||||
b.glctx.EnableVertexAttribArray(b.shd.Vertex)
|
|
||||||
|
|
||||||
b.glctx.ColorMask(false, false, false, false)
|
|
||||||
|
|
||||||
// set bit 2 in the stencil buffer in the given shape
|
|
||||||
b.glctx.StencilMask(0x04)
|
|
||||||
b.glctx.StencilFunc(gl.ALWAYS, 4, 0)
|
|
||||||
b.glctx.StencilOp(gl.REPLACE, gl.REPLACE, gl.REPLACE)
|
|
||||||
b.glctx.DrawArrays(mode, 4, len(pts))
|
|
||||||
|
|
||||||
// on entire screen, where neither bit 1 or 2 are set, invert bit 1
|
|
||||||
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)
|
|
||||||
|
|
||||||
// on entire screen, clear bit 2
|
|
||||||
b.glctx.StencilMask(0x04)
|
|
||||||
b.glctx.StencilFunc(gl.ALWAYS, 0, 0)
|
|
||||||
b.glctx.StencilOp(gl.ZERO, gl.ZERO, gl.ZERO)
|
|
||||||
b.glctx.DrawArrays(gl.TRIANGLE_FAN, 0, 4)
|
|
||||||
|
|
||||||
b.glctx.DisableVertexAttribArray(b.shd.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)
|
|
||||||
}
|
|
|
@ -1,324 +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.shd.ID)
|
|
||||||
b.glctx.Uniform2f(b.shd.CanvasSize, float32(b.fw), float32(b.fh))
|
|
||||||
b.glctx.Uniform4f(b.shd.Color, 0, 0, 0, 0)
|
|
||||||
b.glctx.Uniform1f(b.shd.GlobalAlpha, 1)
|
|
||||||
b.glctx.Uniform1i(b.shd.UseAlphaTex, 0)
|
|
||||||
b.glctx.Uniform1i(b.shd.Func, shdFuncSolid)
|
|
||||||
|
|
||||||
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.shd.Vertex, 2, gl.FLOAT, false, 0, 0)
|
|
||||||
b.glctx.EnableVertexAttribArray(b.shd.Vertex)
|
|
||||||
b.glctx.DrawArrays(gl.TRIANGLE_FAN, 0, 4)
|
|
||||||
b.glctx.DisableVertexAttribArray(b.shd.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 extent(pts [][2]float64) (min, max vec) {
|
|
||||||
max[0] = -math.MaxFloat64
|
|
||||||
max[1] = -math.MaxFloat64
|
|
||||||
min[0] = math.MaxFloat64
|
|
||||||
min[1] = math.MaxFloat64
|
|
||||||
for _, v := range pts {
|
|
||||||
min[0] = math.Min(min[0], v[0])
|
|
||||||
min[1] = math.Min(min[1], v[1])
|
|
||||||
max[0] = math.Max(max[0], v[0])
|
|
||||||
max[1] = math.Max(max[1], v[1])
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *XMobileBackend) Fill(style *backendbase.FillStyle, pts [][2]float64, canOverlap bool) {
|
|
||||||
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]
|
|
||||||
min, max := extent(pts)
|
|
||||||
b.ptsBuf = append(b.ptsBuf,
|
|
||||||
float32(min[0]), float32(min[1]),
|
|
||||||
float32(min[0]), float32(max[1]),
|
|
||||||
float32(max[0]), float32(max[1]),
|
|
||||||
float32(max[0]), float32(min[1]))
|
|
||||||
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 !canOverlap || style.Color.A >= 255 {
|
|
||||||
vertex, _ := b.useShader(style, false, 0)
|
|
||||||
|
|
||||||
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.shd.ID)
|
|
||||||
b.glctx.Uniform4f(b.shd.Color, 0, 0, 0, 0)
|
|
||||||
b.glctx.Uniform2f(b.shd.CanvasSize, float32(b.fw), float32(b.fh))
|
|
||||||
b.glctx.Uniform1f(b.shd.GlobalAlpha, 1)
|
|
||||||
b.glctx.Uniform1i(b.shd.UseAlphaTex, 0)
|
|
||||||
b.glctx.Uniform1i(b.shd.Func, shdFuncSolid)
|
|
||||||
|
|
||||||
b.glctx.EnableVertexAttribArray(b.shd.Vertex)
|
|
||||||
b.glctx.VertexAttribPointer(b.shd.Vertex, 2, gl.FLOAT, false, 0, 0)
|
|
||||||
b.glctx.DrawArrays(mode, 4, len(pts))
|
|
||||||
b.glctx.DisableVertexAttribArray(b.shd.Vertex)
|
|
||||||
|
|
||||||
b.glctx.ColorMask(true, true, true, true)
|
|
||||||
|
|
||||||
b.glctx.StencilFunc(gl.EQUAL, 1, 0xFF)
|
|
||||||
|
|
||||||
vertex, _ := b.useShader(style, false, 0)
|
|
||||||
b.glctx.EnableVertexAttribArray(vertex)
|
|
||||||
b.glctx.VertexAttribPointer(vertex, 2, gl.FLOAT, false, 0, 0)
|
|
||||||
|
|
||||||
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, min, max)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *XMobileBackend) FillImageMask(style *backendbase.FillStyle, mask *image.Alpha, pts [4][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.useShader(style, true, 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 {
|
|
||||||
min, max := extent(pts[:])
|
|
||||||
b.drawBlurred(style.Blur, min, max)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *XMobileBackend) drawBlurred(size float64, min, max vec) {
|
|
||||||
b.offscr1.alpha = true
|
|
||||||
b.offscr2.alpha = true
|
|
||||||
|
|
||||||
// calculate box blur size
|
|
||||||
fsize := math.Max(1, math.Floor(size))
|
|
||||||
sizea := int(fsize)
|
|
||||||
sizeb := sizea
|
|
||||||
sizec := sizea
|
|
||||||
if size-fsize > 0.333333333 {
|
|
||||||
sizeb++
|
|
||||||
}
|
|
||||||
if size-fsize > 0.666666666 {
|
|
||||||
sizec++
|
|
||||||
}
|
|
||||||
|
|
||||||
min[0] -= fsize * 3
|
|
||||||
min[1] -= fsize * 3
|
|
||||||
max[0] += fsize * 3
|
|
||||||
max[1] += fsize * 3
|
|
||||||
min[0] = math.Max(0.0, math.Min(b.fw, min[0]))
|
|
||||||
min[1] = math.Max(0.0, math.Min(b.fh, min[1]))
|
|
||||||
max[0] = math.Max(0.0, math.Min(b.fw, max[0]))
|
|
||||||
max[1] = math.Max(0.0, math.Min(b.fh, max[1]))
|
|
||||||
|
|
||||||
b.glctx.BindBuffer(gl.ARRAY_BUFFER, b.shadowBuf)
|
|
||||||
data := [16]float32{
|
|
||||||
float32(min[0]), float32(min[1]),
|
|
||||||
float32(min[0]), float32(max[1]),
|
|
||||||
float32(max[0]), float32(max[1]),
|
|
||||||
float32(max[0]), float32(min[1]),
|
|
||||||
float32(min[0] / b.fw), 1 - float32(min[1]/b.fh),
|
|
||||||
float32(min[0] / b.fw), 1 - float32(max[1]/b.fh),
|
|
||||||
float32(max[0] / b.fw), 1 - float32(max[1]/b.fh),
|
|
||||||
float32(max[0] / b.fw), 1 - float32(min[1]/b.fh),
|
|
||||||
}
|
|
||||||
b.glctx.BufferData(gl.ARRAY_BUFFER, byteSlice(unsafe.Pointer(&data[0]), len(data)*4), gl.STREAM_DRAW)
|
|
||||||
|
|
||||||
b.glctx.UseProgram(b.shd.ID)
|
|
||||||
b.glctx.Uniform1i(b.shd.Image, 0)
|
|
||||||
b.glctx.Uniform2f(b.shd.CanvasSize, float32(b.fw), float32(b.fh))
|
|
||||||
b.glctx.Uniform1i(b.shd.UseAlphaTex, 0)
|
|
||||||
b.glctx.Uniform1i(b.shd.Func, shdFuncBoxBlur)
|
|
||||||
|
|
||||||
b.glctx.VertexAttribPointer(b.shd.Vertex, 2, gl.FLOAT, false, 0, 0)
|
|
||||||
b.glctx.VertexAttribPointer(b.shd.TexCoord, 2, gl.FLOAT, false, 0, 8*4)
|
|
||||||
b.glctx.EnableVertexAttribArray(b.shd.Vertex)
|
|
||||||
b.glctx.EnableVertexAttribArray(b.shd.TexCoord)
|
|
||||||
|
|
||||||
b.glctx.Disable(gl.BLEND)
|
|
||||||
|
|
||||||
b.glctx.ActiveTexture(gl.TEXTURE0)
|
|
||||||
|
|
||||||
b.glctx.ClearColor(0, 0, 0, 0)
|
|
||||||
|
|
||||||
b.enableTextureRenderTarget(&b.offscr2)
|
|
||||||
b.glctx.BindTexture(gl.TEXTURE_2D, b.offscr1.tex)
|
|
||||||
b.glctx.Clear(gl.COLOR_BUFFER_BIT | gl.STENCIL_BUFFER_BIT)
|
|
||||||
b.box3(sizea, 0, false)
|
|
||||||
b.enableTextureRenderTarget(&b.offscr1)
|
|
||||||
b.glctx.BindTexture(gl.TEXTURE_2D, b.offscr2.tex)
|
|
||||||
b.box3(sizeb, -0.5, false)
|
|
||||||
b.enableTextureRenderTarget(&b.offscr2)
|
|
||||||
b.glctx.BindTexture(gl.TEXTURE_2D, b.offscr1.tex)
|
|
||||||
b.box3(sizec, 0, false)
|
|
||||||
b.enableTextureRenderTarget(&b.offscr1)
|
|
||||||
b.glctx.BindTexture(gl.TEXTURE_2D, b.offscr2.tex)
|
|
||||||
b.box3(sizea, 0, true)
|
|
||||||
b.enableTextureRenderTarget(&b.offscr2)
|
|
||||||
b.glctx.BindTexture(gl.TEXTURE_2D, b.offscr1.tex)
|
|
||||||
b.box3(sizeb, -0.5, true)
|
|
||||||
b.glctx.Enable(gl.BLEND)
|
|
||||||
b.glctx.BlendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA)
|
|
||||||
b.disableTextureRenderTarget()
|
|
||||||
b.glctx.BindTexture(gl.TEXTURE_2D, b.offscr2.tex)
|
|
||||||
b.box3(sizec, 0, true)
|
|
||||||
|
|
||||||
b.glctx.DisableVertexAttribArray(b.shd.Vertex)
|
|
||||||
b.glctx.DisableVertexAttribArray(b.shd.TexCoord)
|
|
||||||
|
|
||||||
b.glctx.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *XMobileBackend) box3(size int, offset float32, vertical bool) {
|
|
||||||
b.glctx.Uniform1i(b.shd.BoxSize, size)
|
|
||||||
if vertical {
|
|
||||||
b.glctx.Uniform1i(b.shd.BoxVertical, 1)
|
|
||||||
b.glctx.Uniform1f(b.shd.BoxScale, 1/float32(b.fh))
|
|
||||||
} else {
|
|
||||||
b.glctx.Uniform1i(b.shd.BoxVertical, 0)
|
|
||||||
b.glctx.Uniform1f(b.shd.BoxScale, 1/float32(b.fw))
|
|
||||||
}
|
|
||||||
b.glctx.Uniform1f(b.shd.BoxOffset, offset)
|
|
||||||
b.glctx.DrawArrays(gl.TRIANGLE_FAN, 0, 4)
|
|
||||||
}
|
|
|
@ -1,439 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"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 != "xmobilebackend" {
|
|
||||||
log.Fatalln("This must be run in the backend/xmobilebackend 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("../goglbackend")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Failed to read dir ../goglbackend: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, fi := range fis {
|
|
||||||
if !strings.HasSuffix(fi.Name(), ".go") || fi.Name() == "shader.go" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
path := filepath.Join("../goglbackend", 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/goglbackend/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.Uniform1i", func(params []string) string {
|
|
||||||
if strings.HasPrefix(params[1], "int32(") {
|
|
||||||
params[1] = params[1][6 : len(params[1])-1]
|
|
||||||
}
|
|
||||||
return "b.glctx.Uniform1i(" + strings.Join(params, ",") + ")"
|
|
||||||
})
|
|
||||||
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] + ":]"
|
|
||||||
} else if len(param) >= 5 && param[:3] == "vp[" {
|
|
||||||
params[i] = fmt.Sprintf("int(%s)", param)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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, ", ") + ")"
|
|
||||||
})
|
|
||||||
src = strings.ReplaceAll(src, "if tex == 0 {", "if tex.Value == 0 {")
|
|
||||||
|
|
||||||
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,
|
|
||||||
`package xmobilebackend
|
|
||||||
`,
|
|
||||||
`package xmobilebackend
|
|
||||||
|
|
||||||
import (
|
|
||||||
"golang.org/x/mobile/gl"
|
|
||||||
)
|
|
||||||
`, 1)
|
|
||||||
|
|
||||||
src = strings.Replace(src, "uint32", "gl.Attrib", -1)
|
|
||||||
src = strings.Replace(src, "int32", "gl.Uniform", -1)
|
|
||||||
src = strings.Replace(src, "shdFuncSolid gl.Uniform", "shdFuncSolid int", -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
|
|
||||||
}
|
|
|
@ -1,104 +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
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
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:])
|
|
||||||
}
|
|
|
@ -1,101 +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
|
|
||||||
}
|
|
||||||
|
|
||||||
var vp [4]int32
|
|
||||||
b.glctx.GetIntegerv(vp[:], gl.VIEWPORT)
|
|
||||||
|
|
||||||
size := int(vp[2] * vp[3] * 3)
|
|
||||||
if len(b.imageBuf) < size {
|
|
||||||
b.imageBuf = make([]byte, size)
|
|
||||||
}
|
|
||||||
b.glctx.ReadPixels(b.imageBuf[0:], int(vp[0]), int(vp[1]), int(vp[2]), int(vp[3]), gl.RGB, gl.UNSIGNED_BYTE)
|
|
||||||
|
|
||||||
rgba := image.NewRGBA(image.Rect(x, y, x+w, y+h))
|
|
||||||
for cy := y; cy < y+h; cy++ {
|
|
||||||
bp := (int(vp[3])-h+cy)*int(vp[2])*3 + x*3
|
|
||||||
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.shd.ID)
|
|
||||||
b.glctx.Uniform1i(b.shd.Image, 0)
|
|
||||||
b.glctx.Uniform2f(b.shd.CanvasSize, float32(b.fw), float32(b.fh))
|
|
||||||
b.glctx.Uniform1f(b.shd.GlobalAlpha, 1)
|
|
||||||
b.glctx.Uniform1i(b.shd.UseAlphaTex, 0)
|
|
||||||
b.glctx.Uniform1i(b.shd.Func, shdFuncImage)
|
|
||||||
b.glctx.VertexAttribPointer(b.shd.Vertex, 2, gl.FLOAT, false, 0, 0)
|
|
||||||
b.glctx.VertexAttribPointer(b.shd.TexCoord, 2, gl.FLOAT, false, 0, 8*4)
|
|
||||||
b.glctx.EnableVertexAttribArray(b.shd.Vertex)
|
|
||||||
b.glctx.EnableVertexAttribArray(b.shd.TexCoord)
|
|
||||||
b.glctx.DrawArrays(gl.TRIANGLE_FAN, 0, 4)
|
|
||||||
b.glctx.DisableVertexAttribArray(b.shd.Vertex)
|
|
||||||
b.glctx.DisableVertexAttribArray(b.shd.TexCoord)
|
|
||||||
}
|
|
|
@ -1,229 +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()
|
|
||||||
if tex.Value == 0 {
|
|
||||||
return nil, errors.New("glGenTextures failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
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.shd.ID)
|
|
||||||
b.glctx.Uniform1i(b.shd.Image, 0)
|
|
||||||
b.glctx.Uniform2f(b.shd.CanvasSize, float32(b.fw), float32(b.fh))
|
|
||||||
b.glctx.Uniform1f(b.shd.GlobalAlpha, float32(alpha))
|
|
||||||
b.glctx.Uniform1i(b.shd.UseAlphaTex, 0)
|
|
||||||
b.glctx.Uniform1i(b.shd.Func, shdFuncImage)
|
|
||||||
b.glctx.VertexAttribPointer(b.shd.Vertex, 2, gl.FLOAT, false, 0, 0)
|
|
||||||
b.glctx.VertexAttribPointer(b.shd.TexCoord, 2, gl.FLOAT, false, 0, 8*4)
|
|
||||||
b.glctx.EnableVertexAttribArray(b.shd.Vertex)
|
|
||||||
b.glctx.EnableVertexAttribArray(b.shd.TexCoord)
|
|
||||||
b.glctx.DrawArrays(gl.TRIANGLE_FAN, 0, 4)
|
|
||||||
b.glctx.DisableVertexAttribArray(b.shd.Vertex)
|
|
||||||
b.glctx.DisableVertexAttribArray(b.shd.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 }
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,158 +0,0 @@
|
||||||
package xmobilebackend
|
|
||||||
|
|
||||||
import (
|
|
||||||
"golang.org/x/mobile/gl"
|
|
||||||
)
|
|
||||||
|
|
||||||
var unifiedVS = `
|
|
||||||
attribute vec2 vertex, texCoord;
|
|
||||||
|
|
||||||
uniform vec2 canvasSize;
|
|
||||||
|
|
||||||
varying vec2 v_cp, v_tc;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
v_tc = texCoord;
|
|
||||||
v_cp = vertex;
|
|
||||||
vec2 glp = vertex * 2.0 / canvasSize - 1.0;
|
|
||||||
gl_Position = vec4(glp.x, -glp.y, 0.0, 1.0);
|
|
||||||
}
|
|
||||||
`
|
|
||||||
var unifiedFS = `
|
|
||||||
#ifdef GL_ES
|
|
||||||
precision mediump float;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
varying vec2 v_cp, v_tc;
|
|
||||||
|
|
||||||
uniform int func;
|
|
||||||
|
|
||||||
uniform vec4 color;
|
|
||||||
uniform float globalAlpha;
|
|
||||||
|
|
||||||
uniform sampler2D gradient;
|
|
||||||
uniform vec2 from, dir, to;
|
|
||||||
uniform float len, radFrom, radTo;
|
|
||||||
|
|
||||||
uniform vec2 imageSize;
|
|
||||||
uniform sampler2D image;
|
|
||||||
uniform mat3 imageTransform;
|
|
||||||
uniform vec2 repeat;
|
|
||||||
|
|
||||||
uniform bool useAlphaTex;
|
|
||||||
uniform sampler2D alphaTex;
|
|
||||||
|
|
||||||
uniform int boxSize;
|
|
||||||
uniform bool boxVertical;
|
|
||||||
uniform float boxScale;
|
|
||||||
uniform float boxOffset;
|
|
||||||
|
|
||||||
bool isNaN(float v) {
|
|
||||||
return v < 0.0 || 0.0 < v || v == 0.0 ? false : true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vec4 col = color;
|
|
||||||
|
|
||||||
if (func == 5) {
|
|
||||||
vec4 sum = vec4(0.0);
|
|
||||||
if (boxVertical) {
|
|
||||||
vec2 start = v_tc - vec2(0.0, (float(boxSize) * 0.5 + boxOffset) * boxScale);
|
|
||||||
for (int i=0; i <= boxSize; i++) {
|
|
||||||
sum += texture2D(image, start + vec2(0.0, float(i) * boxScale));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
vec2 start = v_tc - vec2((float(boxSize) * 0.5 + boxOffset) * boxScale, 0.0);
|
|
||||||
for (int i=0; i <= boxSize; i++) {
|
|
||||||
sum += texture2D(image, start + vec2(float(i) * boxScale, 0.0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
gl_FragColor = sum / float(boxSize+1);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (func == 1) {
|
|
||||||
vec2 v = v_cp - from;
|
|
||||||
float r = dot(v, dir) / len;
|
|
||||||
r = clamp(r, 0.0, 1.0);
|
|
||||||
col = texture2D(gradient, vec2(r, 0.0));
|
|
||||||
} else if (func == 2) {
|
|
||||||
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);
|
|
||||||
o = clamp(o, 0.0, 1.0);
|
|
||||||
col = texture2D(gradient, vec2(o, 0.0));
|
|
||||||
} else if (func == 3) {
|
|
||||||
vec3 tfpt = vec3(v_cp, 1.0) * imageTransform;
|
|
||||||
vec2 imgpt = tfpt.xy / imageSize;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
} else if (func == 4) {
|
|
||||||
col = texture2D(image, v_tc);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (useAlphaTex) {
|
|
||||||
col.a *= texture2D(alphaTex, v_tc).a * globalAlpha;
|
|
||||||
} else {
|
|
||||||
col.a *= globalAlpha;
|
|
||||||
}
|
|
||||||
|
|
||||||
gl_FragColor = col;
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
const (
|
|
||||||
shdFuncSolid int = iota
|
|
||||||
shdFuncLinearGradient
|
|
||||||
shdFuncRadialGradient
|
|
||||||
shdFuncImagePattern
|
|
||||||
shdFuncImage
|
|
||||||
shdFuncBoxBlur
|
|
||||||
)
|
|
||||||
|
|
||||||
type unifiedShader struct {
|
|
||||||
shaderProgram
|
|
||||||
|
|
||||||
Vertex gl.Attrib
|
|
||||||
TexCoord gl.Attrib
|
|
||||||
|
|
||||||
CanvasSize gl.Uniform
|
|
||||||
Color gl.Uniform
|
|
||||||
GlobalAlpha gl.Uniform
|
|
||||||
|
|
||||||
Func gl.Uniform
|
|
||||||
|
|
||||||
UseAlphaTex gl.Uniform
|
|
||||||
AlphaTex gl.Uniform
|
|
||||||
|
|
||||||
Gradient gl.Uniform
|
|
||||||
From, To, Dir gl.Uniform
|
|
||||||
Len gl.Uniform
|
|
||||||
RadFrom, RadTo gl.Uniform
|
|
||||||
|
|
||||||
ImageSize gl.Uniform
|
|
||||||
Image gl.Uniform
|
|
||||||
ImageTransform gl.Uniform
|
|
||||||
Repeat gl.Uniform
|
|
||||||
|
|
||||||
BoxSize gl.Uniform
|
|
||||||
BoxVertical gl.Uniform
|
|
||||||
BoxScale gl.Uniform
|
|
||||||
BoxOffset gl.Uniform
|
|
||||||
}
|
|
|
@ -1,418 +0,0 @@
|
||||||
package xmobilebackend
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"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
|
|
||||||
|
|
||||||
shd unifiedShader
|
|
||||||
|
|
||||||
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, unifiedVS, unifiedFS, &ctx.shd.shaderProgram)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ctx.shd.shaderProgram.mustLoadLocations(&ctx.shd)
|
|
||||||
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)
|
|
||||||
// todo should use gl.RED on OpenGL, gl.ALPHA on OpenGL ES
|
|
||||||
|
|
||||||
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})
|
|
||||||
b.glctx.Viewport(b.x, b.y, b.w, b.h)
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *XMobileBackend) useShader(style *backendbase.FillStyle, useAlpha bool, alphaTexSlot int) (vertexLoc, alphaTexCoordLoc gl.Attrib) {
|
|
||||||
b.glctx.UseProgram(b.shd.ID)
|
|
||||||
b.glctx.Uniform2f(b.shd.CanvasSize, float32(b.fw), float32(b.fh))
|
|
||||||
if useAlpha {
|
|
||||||
b.glctx.Uniform1i(b.shd.UseAlphaTex, 1)
|
|
||||||
b.glctx.Uniform1i(b.shd.AlphaTex, alphaTexSlot)
|
|
||||||
} else {
|
|
||||||
b.glctx.Uniform1i(b.shd.UseAlphaTex, 0)
|
|
||||||
}
|
|
||||||
b.glctx.Uniform1f(b.shd.GlobalAlpha, float32(style.Color.A)/255)
|
|
||||||
|
|
||||||
if lg := style.LinearGradient; lg != nil {
|
|
||||||
lg := lg.(*LinearGradient)
|
|
||||||
b.glctx.ActiveTexture(gl.TEXTURE0)
|
|
||||||
b.glctx.BindTexture(gl.TEXTURE_2D, lg.tex)
|
|
||||||
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.shd.From, float32(from[0]), float32(from[1]))
|
|
||||||
b.glctx.Uniform2f(b.shd.Dir, float32(dir[0]), float32(dir[1]))
|
|
||||||
b.glctx.Uniform1f(b.shd.Len, float32(length))
|
|
||||||
b.glctx.Uniform1i(b.shd.Gradient, 0)
|
|
||||||
b.glctx.Uniform1i(b.shd.Func, shdFuncLinearGradient)
|
|
||||||
return b.shd.Vertex, b.shd.TexCoord
|
|
||||||
}
|
|
||||||
if rg := style.RadialGradient; rg != nil {
|
|
||||||
rg := rg.(*RadialGradient)
|
|
||||||
b.glctx.ActiveTexture(gl.TEXTURE0)
|
|
||||||
b.glctx.BindTexture(gl.TEXTURE_2D, rg.tex)
|
|
||||||
from := vec{style.Gradient.X0, style.Gradient.Y0}
|
|
||||||
to := vec{style.Gradient.X1, style.Gradient.Y1}
|
|
||||||
b.glctx.Uniform2f(b.shd.From, float32(from[0]), float32(from[1]))
|
|
||||||
b.glctx.Uniform2f(b.shd.To, float32(to[0]), float32(to[1]))
|
|
||||||
b.glctx.Uniform1f(b.shd.RadFrom, float32(style.Gradient.RadFrom))
|
|
||||||
b.glctx.Uniform1f(b.shd.RadTo, float32(style.Gradient.RadTo))
|
|
||||||
b.glctx.Uniform1i(b.shd.Gradient, 0)
|
|
||||||
b.glctx.Uniform1i(b.shd.Func, shdFuncRadialGradient)
|
|
||||||
return b.shd.Vertex, b.shd.TexCoord
|
|
||||||
}
|
|
||||||
if ip := style.ImagePattern; ip != nil {
|
|
||||||
ipd := ip.(*ImagePattern).data
|
|
||||||
img := ipd.Image.(*Image)
|
|
||||||
b.glctx.ActiveTexture(gl.TEXTURE0)
|
|
||||||
b.glctx.BindTexture(gl.TEXTURE_2D, img.tex)
|
|
||||||
b.glctx.Uniform2f(b.shd.ImageSize, float32(img.w), float32(img.h))
|
|
||||||
b.glctx.Uniform1i(b.shd.Image, 0)
|
|
||||||
var f32mat [9]float32
|
|
||||||
for i, v := range ipd.Transform {
|
|
||||||
f32mat[i] = float32(v)
|
|
||||||
}
|
|
||||||
b.glctx.UniformMatrix3fv(b.shd.ImageTransform, f32mat[:])
|
|
||||||
switch ipd.Repeat {
|
|
||||||
case backendbase.Repeat:
|
|
||||||
b.glctx.Uniform2f(b.shd.Repeat, 1, 1)
|
|
||||||
case backendbase.RepeatX:
|
|
||||||
b.glctx.Uniform2f(b.shd.Repeat, 1, 0)
|
|
||||||
case backendbase.RepeatY:
|
|
||||||
b.glctx.Uniform2f(b.shd.Repeat, 0, 1)
|
|
||||||
case backendbase.NoRepeat:
|
|
||||||
b.glctx.Uniform2f(b.shd.Repeat, 0, 0)
|
|
||||||
}
|
|
||||||
b.glctx.Uniform1i(b.shd.Func, shdFuncImagePattern)
|
|
||||||
return b.shd.Vertex, b.shd.TexCoord
|
|
||||||
}
|
|
||||||
|
|
||||||
cr := float32(style.Color.R) / 255
|
|
||||||
cg := float32(style.Color.G) / 255
|
|
||||||
cb := float32(style.Color.B) / 255
|
|
||||||
ca := float32(style.Color.A) / 255
|
|
||||||
b.glctx.Uniform4f(b.shd.Color, cr, cg, cb, ca)
|
|
||||||
b.glctx.Uniform1f(b.shd.GlobalAlpha, 1)
|
|
||||||
b.glctx.Uniform1i(b.shd.Func, shdFuncSolid)
|
|
||||||
return b.shd.Vertex, b.shd.TexCoord
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
159
canvas.go
|
@ -3,14 +3,16 @@
|
||||||
package canvas
|
package canvas
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
"os"
|
"math"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/golang/freetype/truetype"
|
"github.com/golang/freetype/truetype"
|
||||||
"github.com/tfriedel6/canvas/backend/backendbase"
|
|
||||||
"golang.org/x/image/font"
|
"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
|
//go:generate go run make_shaders.go
|
||||||
|
@ -26,17 +28,21 @@ type Canvas struct {
|
||||||
state drawState
|
state drawState
|
||||||
stateStack []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 {
|
type drawState struct {
|
||||||
transform mat
|
transform backendbase.Mat
|
||||||
fill drawStyle
|
fill drawStyle
|
||||||
stroke drawStyle
|
stroke drawStyle
|
||||||
font *Font
|
font *Font
|
||||||
fontSize float64
|
fontSize fixed.Int26_6
|
||||||
fontMetrics font.Metrics
|
fontMetrics font.Metrics
|
||||||
textAlign textAlign
|
textAlign textAlign
|
||||||
textBaseline textBaseline
|
textBaseline textBaseline
|
||||||
|
@ -122,9 +128,11 @@ const (
|
||||||
var Performance = struct {
|
var Performance = struct {
|
||||||
IgnoreSelfIntersections bool
|
IgnoreSelfIntersections bool
|
||||||
AssumeConvex bool
|
AssumeConvex bool
|
||||||
ImageCacheSize int
|
|
||||||
|
// CacheSize is only approximate
|
||||||
|
CacheSize int
|
||||||
}{
|
}{
|
||||||
ImageCacheSize: 16_000_000,
|
CacheSize: 128_000_000,
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new canvas with the given viewport coordinates.
|
// New creates a new canvas with the given viewport coordinates.
|
||||||
|
@ -133,9 +141,13 @@ var Performance = struct {
|
||||||
// coordinates given here also use the bottom left as origin
|
// coordinates given here also use the bottom left as origin
|
||||||
func New(backend backendbase.Backend) *Canvas {
|
func New(backend backendbase.Backend) *Canvas {
|
||||||
cv := &Canvas{
|
cv := &Canvas{
|
||||||
b: backend,
|
b: backend,
|
||||||
stateStack: make([]drawState, 0, 20),
|
stateStack: make([]drawState, 0, 20),
|
||||||
images: make(map[interface{}]*Image),
|
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.lineWidth = 1
|
||||||
cv.state.lineAlpha = 1
|
cv.state.lineAlpha = 1
|
||||||
|
@ -143,7 +155,7 @@ func New(backend backendbase.Backend) *Canvas {
|
||||||
cv.state.globalAlpha = 1
|
cv.state.globalAlpha = 1
|
||||||
cv.state.fill.color = color.RGBA{A: 255}
|
cv.state.fill.color = color.RGBA{A: 255}
|
||||||
cv.state.stroke.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
|
cv.path.cv = cv
|
||||||
return cv
|
return cv
|
||||||
}
|
}
|
||||||
|
@ -163,8 +175,8 @@ func (cv *Canvas) Height() int {
|
||||||
// Size returns the internal width and height of the canvas
|
// Size returns the internal width and height of the canvas
|
||||||
func (cv *Canvas) Size() (int, int) { return cv.b.Size() }
|
func (cv *Canvas) Size() (int, int) { return cv.b.Size() }
|
||||||
|
|
||||||
func (cv *Canvas) tf(v vec) vec {
|
func (cv *Canvas) tf(v backendbase.Vec) backendbase.Vec {
|
||||||
return v.mulMat(cv.state.transform)
|
return v.MulMat(cv.state.transform)
|
||||||
}
|
}
|
||||||
|
|
||||||
const alphaTexSize = 2048
|
const alphaTexSize = 2048
|
||||||
|
@ -283,30 +295,12 @@ func (cv *Canvas) SetLineWidth(width float64) {
|
||||||
// with the LoadFont function, a filename for a font to load (which will be
|
// 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
|
// cached), or nil, in which case the first loaded font will be used
|
||||||
func (cv *Canvas) SetFont(src interface{}, size float64) {
|
func (cv *Canvas) SetFont(src interface{}, size float64) {
|
||||||
|
cv.state.fontSize = fixed.Int26_6(math.Round(size * 64))
|
||||||
if src == nil {
|
if src == nil {
|
||||||
cv.state.font = defaultFont
|
cv.state.font = defaultFont
|
||||||
} else {
|
} else {
|
||||||
switch v := src.(type) {
|
cv.state.font = cv.getFont(src)
|
||||||
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.fontSize = size
|
|
||||||
|
|
||||||
fontFace := truetype.NewFace(cv.state.font.font, &truetype.Options{Size: size})
|
fontFace := truetype.NewFace(cv.state.font.font, &truetype.Options{Size: size})
|
||||||
cv.state.fontMetrics = fontFace.Metrics()
|
cv.state.fontMetrics = fontFace.Metrics()
|
||||||
|
@ -391,7 +385,7 @@ func (cv *Canvas) Restore() {
|
||||||
cv.b.ClearClip()
|
cv.b.ClearClip()
|
||||||
for _, st := range cv.stateStack {
|
for _, st := range cv.stateStack {
|
||||||
if len(st.clip.p) > 0 {
|
if len(st.clip.p) > 0 {
|
||||||
cv.clip(&st.clip, matIdentity)
|
cv.clip(&st.clip, backendbase.MatIdentity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cv.state = cv.stateStack[l-1]
|
cv.state = cv.stateStack[l-1]
|
||||||
|
@ -400,27 +394,27 @@ func (cv *Canvas) Restore() {
|
||||||
|
|
||||||
// Scale updates the current transformation with a scaling by the given values
|
// Scale updates the current transformation with a scaling by the given values
|
||||||
func (cv *Canvas) Scale(x, y float64) {
|
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
|
// Translate updates the current transformation with a translation by the given values
|
||||||
func (cv *Canvas) Translate(x, y float64) {
|
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
|
// Rotate updates the current transformation with a rotation by the given angle
|
||||||
func (cv *Canvas) Rotate(angle float64) {
|
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
|
// Transform updates the current transformation with the given matrix
|
||||||
func (cv *Canvas) Transform(a, b, c, d, e, f float64) {
|
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
|
// SetTransform replaces the current transformation with the given matrix
|
||||||
func (cv *Canvas) SetTransform(a, b, c, d, e, f float64) {
|
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)
|
// SetShadowColor sets the color of the shadow. If it is fully transparent (default)
|
||||||
|
@ -466,18 +460,89 @@ func (cv *Canvas) IsPointInStroke(x, y float64) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
var triBuf [500][2]float64
|
var triBuf [500]backendbase.Vec
|
||||||
tris := cv.strokeTris(&cv.path, cv.state.transform.invert(), true, triBuf[:0])
|
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 {
|
for i := 0; i < len(tris); i += 3 {
|
||||||
a := vec{tris[i][0], tris[i][1]}
|
a := backendbase.Vec{tris[i][0], tris[i][1]}
|
||||||
b := vec{tris[i+1][0], tris[i+1][1]}
|
b := backendbase.Vec{tris[i+1][0], tris[i+1][1]}
|
||||||
c := vec{tris[i+2][0], tris[i+2][1]}
|
c := backendbase.Vec{tris[i+2][0], tris[i+2][1]}
|
||||||
if triangleContainsPoint(a, b, c, pt) {
|
if triangleContainsPoint(a, b, c, pt) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
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)
|
||||||
|
}
|
||||||
|
|
657
canvas_test.go
|
@ -1,657 +0,0 @@
|
||||||
package canvas_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"image"
|
|
||||||
_ "image/jpeg"
|
|
||||||
"image/png"
|
|
||||||
"math"
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/go-gl/gl/v3.2-core/gl"
|
|
||||||
"github.com/tfriedel6/canvas"
|
|
||||||
"github.com/tfriedel6/canvas/backend/softwarebackend"
|
|
||||||
"github.com/tfriedel6/canvas/sdlcanvas"
|
|
||||||
)
|
|
||||||
|
|
||||||
var usesw = false
|
|
||||||
|
|
||||||
func run(t *testing.T, fn func(cv *canvas.Canvas)) {
|
|
||||||
var img *image.RGBA
|
|
||||||
if !usesw {
|
|
||||||
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)
|
|
||||||
} else {
|
|
||||||
backend := softwarebackend.New(100, 100)
|
|
||||||
cv := canvas.New(backend)
|
|
||||||
|
|
||||||
cv.SetFillStyle("#000")
|
|
||||||
cv.FillRect(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 TestFillHammer(t *testing.T) {
|
|
||||||
run(t, func(cv *canvas.Canvas) {
|
|
||||||
cv.SetFillStyle("#0F0")
|
|
||||||
cv.BeginPath()
|
|
||||||
cv.Translate(50, 50)
|
|
||||||
cv.Scale(0.7, 0.7)
|
|
||||||
cv.MoveTo(-6, 60)
|
|
||||||
cv.LineTo(-6, -50)
|
|
||||||
cv.LineTo(-25, -50)
|
|
||||||
cv.LineTo(-12, -60)
|
|
||||||
cv.LineTo(25, -60)
|
|
||||||
cv.LineTo(25, -50)
|
|
||||||
cv.LineTo(6, -50)
|
|
||||||
cv.LineTo(6, 60)
|
|
||||||
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 TestImagePattern2(t *testing.T) {
|
|
||||||
run(t, func(cv *canvas.Canvas) {
|
|
||||||
ptrn := cv.CreatePattern("testdata/cat.jpg", canvas.NoRepeat)
|
|
||||||
ptrn.SetTransform([6]float64{0, 0.1, 0.1, 0, 0, 0})
|
|
||||||
|
|
||||||
cv.Translate(50, 50)
|
|
||||||
cv.Scale(0.95, 1.05)
|
|
||||||
cv.Rotate(-math.Pi * 0.1)
|
|
||||||
|
|
||||||
cv.SetFillStyle(ptrn)
|
|
||||||
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()
|
|
||||||
})
|
|
||||||
}
|
|
8
color.go
|
@ -199,14 +199,16 @@ func parseColor(value ...interface{}) (c color.RGBA, ok bool) {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
v = strings.Replace(v, " ", "", -1)
|
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)
|
n, err := fmt.Sscanf(v, "rgb(%d,%d,%d)", &ir, &ig, &ib)
|
||||||
if err == nil && n == 3 {
|
if err == nil && n == 3 {
|
||||||
return color.RGBA{R: uint8(ir), G: uint8(ig), B: uint8(ib), A: 255}, true
|
return color.RGBA{R: uint8(ir), G: uint8(ig), B: uint8(ib), A: 255}, true
|
||||||
}
|
}
|
||||||
n, err = fmt.Sscanf(v, "rgba(%d,%d,%d,%d)", &ir, &ig, &ib, &ia)
|
var fa float64
|
||||||
|
n, err = fmt.Sscanf(v, "rgba(%d,%d,%d,%f)", &ir, &ig, &ib, &fa)
|
||||||
|
fa = math.Max(0, math.Min(1, fa))
|
||||||
if err == nil && n == 4 {
|
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
|
@ -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
|
||||||
|
}
|
10
examples/android/CanvasAndroidExample/.gitignore
vendored
|
@ -1,10 +0,0 @@
|
||||||
*.iml
|
|
||||||
.gradle
|
|
||||||
/local.properties
|
|
||||||
/.idea/libraries
|
|
||||||
/.idea/modules.xml
|
|
||||||
/.idea/workspace.xml
|
|
||||||
.DS_Store
|
|
||||||
/build
|
|
||||||
/captures
|
|
||||||
.externalNativeBuild
|
|
|
@ -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>
|
|
|
@ -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>
|
|
|
@ -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>
|
|
|
@ -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>
|
|
|
@ -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>
|
|
|
@ -1,6 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="VcsDirectoryMappings">
|
|
||||||
<mapping directory="$PROJECT_DIR$/../../.." vcs="Git" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
|
@ -1 +0,0 @@
|
||||||
/build
|
|
|
@ -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'
|
|
||||||
}
|
|
|
@ -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
|
|
|
@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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>
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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>
|
|
|
@ -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>
|
|
|
@ -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>
|
|
|
@ -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>
|
|
Before Width: | Height: | Size: 3 KiB |
Before Width: | Height: | Size: 4.9 KiB |
Before Width: | Height: | Size: 2 KiB |
Before Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 4.5 KiB |
Before Width: | Height: | Size: 6.9 KiB |
Before Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 9 KiB |
Before Width: | Height: | Size: 15 KiB |
|
@ -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>
|
|
|
@ -1,3 +0,0 @@
|
||||||
<resources>
|
|
||||||
<string name="app_name">CanvasAndroidExample</string>
|
|
||||||
</resources>
|
|
|
@ -1,8 +0,0 @@
|
||||||
<resources>
|
|
||||||
|
|
||||||
<!-- Base application theme. -->
|
|
||||||
<style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar">
|
|
||||||
<!-- Customize your theme here. -->
|
|
||||||
</style>
|
|
||||||
|
|
||||||
</resources>
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
|
@ -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
|
|
172
examples/android/CanvasAndroidExample/gradlew
vendored
|
@ -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" "$@"
|
|
|
@ -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
|
|
|
@ -1 +0,0 @@
|
||||||
include ':app'
|
|
|
@ -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.
|
|
|
@ -1,42 +0,0 @@
|
||||||
package canvasandroidexample
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math"
|
|
||||||
|
|
||||||
"github.com/tfriedel6/canvas"
|
|
||||||
"github.com/tfriedel6/canvas/backend/goglbackend"
|
|
||||||
)
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
Before Width: | Height: | Size: 132 KiB |
|
@ -1,134 +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)
|
|
||||||
|
|
||||||
// Estimated size used for scaling
|
|
||||||
const (
|
|
||||||
contentWidth = 1000
|
|
||||||
contentHeight = 350
|
|
||||||
)
|
|
||||||
|
|
||||||
// Calculate scaling
|
|
||||||
sx := w / contentWidth
|
|
||||||
sy := h / contentHeight
|
|
||||||
scale := math.Min(sx, sy)
|
|
||||||
cv.Save()
|
|
||||||
defer cv.Restore()
|
|
||||||
cv.Scale(scale, scale)
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -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()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,89 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"runtime"
|
|
||||||
|
|
||||||
"github.com/go-gl/gl/v3.2-core/gl"
|
|
||||||
"github.com/go-gl/glfw/v3.3/glfw"
|
|
||||||
"github.com/tfriedel6/canvas"
|
|
||||||
"github.com/tfriedel6/canvas/backend/goglbackend"
|
|
||||||
)
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
var sx, sy float64 = 1, 1
|
|
||||||
window.SetCursorPosCallback(func(w *glfw.Window, xpos, ypos float64) {
|
|
||||||
mx, my = xpos*sx, ypos*sy
|
|
||||||
})
|
|
||||||
|
|
||||||
// initialize canvas with zero size, since size is set in main loop
|
|
||||||
cv := canvas.New(backend)
|
|
||||||
|
|
||||||
for !window.ShouldClose() {
|
|
||||||
window.MakeContextCurrent()
|
|
||||||
|
|
||||||
// find window size and scaling
|
|
||||||
ww, wh := window.GetSize()
|
|
||||||
fbw, fbh := window.GetFramebufferSize()
|
|
||||||
sx = float64(fbw) / float64(ww)
|
|
||||||
sy = float64(fbh) / float64(wh)
|
|
||||||
|
|
||||||
glfw.PollEvents()
|
|
||||||
|
|
||||||
// set canvas size
|
|
||||||
backend.SetBounds(0, 0, fbw, fbh)
|
|
||||||
|
|
||||||
// call the run function to do all the drawing
|
|
||||||
run(cv, float64(fbw), float64(fbh))
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
|
|
@ -1,71 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"math"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/tfriedel6/canvas"
|
|
||||||
"github.com/tfriedel6/canvas/backend/xmobilebackend"
|
|
||||||
"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{})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
4
examples/ios/CanvasIOSExample/.gitignore
vendored
|
@ -1,4 +0,0 @@
|
||||||
xcuserdata
|
|
||||||
project.xcworkspace
|
|
||||||
Example.framework
|
|
||||||
|
|
|
@ -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 */;
|
|
||||||
}
|
|
|
@ -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:.
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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>
|
|
|
@ -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>
|
|
|
@ -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))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
|
@ -1,39 +0,0 @@
|
||||||
package example
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math"
|
|
||||||
|
|
||||||
"github.com/tfriedel6/canvas"
|
|
||||||
"github.com/tfriedel6/canvas/backend/goglbackend"
|
|
||||||
)
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
|
@ -1,130 +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/goglbackend"
|
|
||||||
"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|sdl.WINDOW_ALLOW_HIGHDPI)
|
|
||||||
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|sdl.WINDOW_ALLOW_HIGHDPI)
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// find window size and scaling
|
|
||||||
ww, wh := window.GetSize()
|
|
||||||
fbw, fbh := window.GLGetDrawableSize()
|
|
||||||
sx := float64(fbw) / float64(ww)
|
|
||||||
sy := float64(fbh) / float64(wh)
|
|
||||||
|
|
||||||
// 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)*sx, float64(e.Y)*sy
|
|
||||||
case *sdl.QuitEvent:
|
|
||||||
running = false
|
|
||||||
case *sdl.WindowEvent:
|
|
||||||
if e.Type == sdl.WINDOWEVENT_CLOSE {
|
|
||||||
running = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// set canvas size
|
|
||||||
backend.SetBounds(0, 0, int(fbw), int(fbh))
|
|
||||||
|
|
||||||
// call the run function to do all the drawing
|
|
||||||
run(cv, float64(fbw), float64(fbh))
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
|
|
@ -1,58 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/tfriedel6/canvas"
|
|
||||||
"github.com/tfriedel6/canvas/backend/xmobilebackend"
|
|
||||||
"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)
|
|
||||||
}
|
|
|
@ -5,8 +5,8 @@ import (
|
||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/tfriedel6/canvas"
|
"git.mstar.dev/mstar/canvas"
|
||||||
"github.com/tfriedel6/canvas/backend/softwarebackend"
|
"git.mstar.dev/mstar/canvas/backend/softwarebackend"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
79
freetype.go
|
@ -8,7 +8,6 @@ package canvas
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"image"
|
"image"
|
||||||
"image/draw"
|
|
||||||
|
|
||||||
"github.com/golang/freetype/raster"
|
"github.com/golang/freetype/raster"
|
||||||
"github.com/golang/freetype/truetype"
|
"github.com/golang/freetype/truetype"
|
||||||
|
@ -42,13 +41,9 @@ type frContext struct {
|
||||||
r *raster.Rasterizer
|
r *raster.Rasterizer
|
||||||
f *truetype.Font
|
f *truetype.Font
|
||||||
glyphBuf truetype.GlyphBuf
|
glyphBuf truetype.GlyphBuf
|
||||||
// dst and src are the destination and source images for drawing.
|
|
||||||
dst draw.Image
|
fontSize fixed.Int26_6
|
||||||
// fontSize and dpi are used to calculate scale. scale is the number of
|
hinting font.Hinting
|
||||||
// 26.6 fixed point units in 1 em. hinting is the hinting policy.
|
|
||||||
fontSize, dpi float64
|
|
||||||
scale fixed.Int26_6
|
|
||||||
hinting font.Hinting
|
|
||||||
// cache is the glyph cache.
|
// cache is the glyph cache.
|
||||||
cache [nGlyphs * nXFractions * nYFractions]cacheEntry
|
cache [nGlyphs * nXFractions * nYFractions]cacheEntry
|
||||||
}
|
}
|
||||||
|
@ -132,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).
|
// 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) {
|
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
|
return 0, nil, image.Point{}, err
|
||||||
}
|
}
|
||||||
// Calculate the integer-pixel bounds for the glyph.
|
// Calculate the integer-pixel bounds for the glyph.
|
||||||
|
@ -190,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) {
|
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 0, err
|
||||||
}
|
}
|
||||||
return c.glyphBuf.AdvanceWidth, nil
|
return c.glyphBuf.AdvanceWidth, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *frContext) glyphMeasure(glyph truetype.Index, p fixed.Point26_6) (fixed.Int26_6, image.Rectangle, error) {
|
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
|
return 0, image.Rectangle{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -221,15 +216,12 @@ func (c *frContext) glyphBounds(glyph truetype.Index, p fixed.Point26_6) (image.
|
||||||
|
|
||||||
const maxInt = int(^uint(0) >> 1)
|
const maxInt = int(^uint(0) >> 1)
|
||||||
|
|
||||||
// recalc recalculates scale and bounds values from the font size, screen
|
|
||||||
// resolution and font metrics, and invalidates the glyph cache.
|
|
||||||
func (c *frContext) recalc() {
|
func (c *frContext) recalc() {
|
||||||
c.scale = fixed.Int26_6(c.fontSize * c.dpi * (64.0 / 72.0))
|
|
||||||
if c.f == nil {
|
if c.f == nil {
|
||||||
c.r.SetBounds(0, 0)
|
c.r.SetBounds(0, 0)
|
||||||
} else {
|
} else {
|
||||||
// Set the rasterizer's bounds to be big enough to handle the largest glyph.
|
// 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
|
xmin := +int(b.Min.X) >> 6
|
||||||
ymin := -int(b.Max.Y) >> 6
|
ymin := -int(b.Max.Y) >> 6
|
||||||
xmax := +int(b.Max.X+63) >> 6
|
xmax := +int(b.Max.X+63) >> 6
|
||||||
|
@ -241,54 +233,25 @@ func (c *frContext) recalc() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetDPI sets the screen resolution in dots per inch.
|
func (c *frContext) cacheSize() int {
|
||||||
func (c *frContext) setDPI(dpi float64) {
|
if c.f == nil {
|
||||||
if c.dpi == dpi {
|
return 0
|
||||||
return
|
|
||||||
}
|
}
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(nigeltao): implement Context.SetGamma.
|
|
||||||
|
|
||||||
// NewContext creates a new Context.
|
|
||||||
func newFRContext() *frContext {
|
func newFRContext() *frContext {
|
||||||
return &frContext{
|
return &frContext{
|
||||||
r: raster.NewRasterizer(0, 0),
|
r: raster.NewRasterizer(0, 0),
|
||||||
fontSize: 12,
|
fontSize: fixed.I(12),
|
||||||
dpi: 72,
|
hinting: font.HintingFull,
|
||||||
scale: 12 << 6,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,184 +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.3/glfw"
|
|
||||||
"github.com/tfriedel6/canvas"
|
|
||||||
"github.com/tfriedel6/canvas/backend/goglbackend"
|
|
||||||
)
|
|
||||||
|
|
||||||
// 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
|
|
||||||
fbw, fbh := window.GetFramebufferSize()
|
|
||||||
backend, err := goglbackend.New(0, 0, fbw, fbh, 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 {
|
|
||||||
fbw, fbh := window.GetFramebufferSize()
|
|
||||||
backend.SetBounds(0, 0, fbw, fbh)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
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.
|
|
||||||
// Note that this size may not be the same as the size of the
|
|
||||||
// framebuffer, since some operating systems scale the window.
|
|
||||||
// Use the Width/Height/Size function on Canvas to determine
|
|
||||||
// the drawing size
|
|
||||||
func (wnd *Window) Size() (int, int) {
|
|
||||||
return wnd.Window.GetSize()
|
|
||||||
}
|
|
||||||
|
|
||||||
// FramebufferSize returns the current width and height of
|
|
||||||
// the framebuffer, which is also the internal size of the
|
|
||||||
// canvas
|
|
||||||
func (wnd *Window) FramebufferSize() (int, int) {
|
|
||||||
return wnd.Window.GetFramebufferSize()
|
|
||||||
}
|
|
|
@ -1,216 +0,0 @@
|
||||||
package glfwcanvas
|
|
||||||
|
|
||||||
import "github.com/go-gl/glfw/v3.3/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] = "ShiftRight"
|
|
||||||
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]
|
|
||||||
}
|
|
10
go.mod
|
@ -1,14 +1,8 @@
|
||||||
module github.com/tfriedel6/canvas
|
module git.mstar.dev/mstar/canvas
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/go-gl/gl v0.0.0-20181026044259-55b76b7df9d2
|
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72
|
|
||||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
|
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
|
||||||
github.com/veandco/go-sdl2 v0.4.0
|
golang.org/x/image v0.0.0-20200801110659-972c09e46d76
|
||||||
golang.org/x/exp v0.0.0-20181106170214-d68db9428509
|
|
||||||
golang.org/x/image v0.0.0-20200119044424-58c23975cae1
|
|
||||||
golang.org/x/mobile v0.0.0-20181026062114-a27dd33d354d
|
|
||||||
golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35 // indirect
|
|
||||||
)
|
)
|
||||||
|
|
||||||
go 1.13
|
go 1.13
|
||||||
|
|
16
go.sum
|
@ -1,17 +1,5 @@
|
||||||
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/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72 h1:b+9H1GAsx5RsjvDFLoS5zkNBzIQMuVKUYQDmxU3N5XE=
|
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
|
||||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
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/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||||
github.com/veandco/go-sdl2 v0.4.0 h1:l9q6K+Dvpd/VlZdw2ufApKnWhAQqx9UL8Zrvbjtm3Lw=
|
golang.org/x/image v0.0.0-20200801110659-972c09e46d76 h1:U7GPaoQyQmX+CBRWXKrvRzWTbd+slqeSh8uARsIyhAw=
|
||||||
github.com/veandco/go-sdl2 v0.4.0/go.mod h1:FB+kTpX9YTE+urhYiClnRzpOXbiWgaU3+5F2AB78DPg=
|
golang.org/x/image v0.0.0-20200801110659-972c09e46d76/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
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-20200119044424-58c23975cae1 h1:5h3ngYt7+vXCDZCup/HkCQgW5XwmSvR/nA2JmJ0RErg=
|
|
||||||
golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
|
||||||
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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
|
59
gradients.go
|
@ -2,8 +2,9 @@ package canvas
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"image/color"
|
"image/color"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
"github.com/tfriedel6/canvas/backend/backendbase"
|
"git.mstar.dev/mstar/canvas/backend/backendbase"
|
||||||
)
|
)
|
||||||
|
|
||||||
// LinearGradient is a gradient with any number of
|
// LinearGradient is a gradient with any number of
|
||||||
|
@ -12,10 +13,9 @@ import (
|
||||||
// will correspond to a straight line
|
// will correspond to a straight line
|
||||||
type LinearGradient struct {
|
type LinearGradient struct {
|
||||||
cv *Canvas
|
cv *Canvas
|
||||||
from, to vec
|
from, to backendbase.Vec
|
||||||
created bool
|
created bool
|
||||||
loaded bool
|
loaded bool
|
||||||
deleted bool
|
|
||||||
opaque bool
|
opaque bool
|
||||||
grad backendbase.LinearGradient
|
grad backendbase.LinearGradient
|
||||||
data backendbase.Gradient
|
data backendbase.Gradient
|
||||||
|
@ -27,12 +27,11 @@ type LinearGradient struct {
|
||||||
// will correspond to a circle
|
// will correspond to a circle
|
||||||
type RadialGradient struct {
|
type RadialGradient struct {
|
||||||
cv *Canvas
|
cv *Canvas
|
||||||
from, to vec
|
from, to backendbase.Vec
|
||||||
radFrom float64
|
radFrom float64
|
||||||
radTo float64
|
radTo float64
|
||||||
created bool
|
created bool
|
||||||
loaded bool
|
loaded bool
|
||||||
deleted bool
|
|
||||||
opaque bool
|
opaque bool
|
||||||
grad backendbase.RadialGradient
|
grad backendbase.RadialGradient
|
||||||
data backendbase.Gradient
|
data backendbase.Gradient
|
||||||
|
@ -42,13 +41,17 @@ type RadialGradient struct {
|
||||||
// the coordinates from where to where the gradient
|
// the coordinates from where to where the gradient
|
||||||
// will apply on the canvas
|
// will apply on the canvas
|
||||||
func (cv *Canvas) CreateLinearGradient(x0, y0, x1, y1 float64) *LinearGradient {
|
func (cv *Canvas) CreateLinearGradient(x0, y0, x1, y1 float64) *LinearGradient {
|
||||||
return &LinearGradient{
|
lg := &LinearGradient{
|
||||||
cv: cv,
|
cv: cv,
|
||||||
opaque: true,
|
opaque: true,
|
||||||
from: vec{x0, y0},
|
from: backendbase.Vec{x0, y0},
|
||||||
to: vec{x1, y1},
|
to: backendbase.Vec{x1, y1},
|
||||||
data: make(backendbase.Gradient, 0, 20),
|
data: make(backendbase.Gradient, 0, 20),
|
||||||
}
|
}
|
||||||
|
runtime.SetFinalizer(lg, func(*LinearGradient) {
|
||||||
|
lg.grad.Delete()
|
||||||
|
})
|
||||||
|
return lg
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateRadialGradient creates a new radial gradient with
|
// 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
|
// gradient will apply from the first to the second
|
||||||
// circle
|
// circle
|
||||||
func (cv *Canvas) CreateRadialGradient(x0, y0, r0, x1, y1, r1 float64) *RadialGradient {
|
func (cv *Canvas) CreateRadialGradient(x0, y0, r0, x1, y1, r1 float64) *RadialGradient {
|
||||||
return &RadialGradient{
|
rg := &RadialGradient{
|
||||||
cv: cv,
|
cv: cv,
|
||||||
opaque: true,
|
opaque: true,
|
||||||
from: vec{x0, y0},
|
from: backendbase.Vec{x0, y0},
|
||||||
to: vec{x1, y1},
|
to: backendbase.Vec{x1, y1},
|
||||||
radFrom: r0,
|
radFrom: r0,
|
||||||
radTo: r1,
|
radTo: r1,
|
||||||
data: make(backendbase.Gradient, 0, 20),
|
data: make(backendbase.Gradient, 0, 20),
|
||||||
}
|
}
|
||||||
}
|
runtime.SetFinalizer(rg, func(*RadialGradient) {
|
||||||
|
rg.grad.Delete()
|
||||||
// Delete explicitly deletes the gradient
|
})
|
||||||
func (lg *LinearGradient) Delete() {
|
return rg
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (lg *LinearGradient) load() {
|
func (lg *LinearGradient) load() {
|
||||||
if lg.loaded || len(lg.data) < 1 || lg.deleted {
|
if lg.loaded || len(lg.data) < 1 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,7 +89,7 @@ func (lg *LinearGradient) load() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rg *RadialGradient) load() {
|
func (rg *RadialGradient) load() {
|
||||||
if rg.loaded || len(rg.data) < 1 || rg.deleted {
|
if rg.loaded || len(rg.data) < 1 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,7 +126,11 @@ func (rg *RadialGradient) AddColorStop(pos float64, stopColor ...interface{}) {
|
||||||
rg.loaded = false
|
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...)
|
c, _ := parseColor(stopColor...)
|
||||||
insert := len(stops)
|
insert := len(stops)
|
||||||
for i, stop := range stops {
|
for i, stop := range stops {
|
||||||
|
|
162
images.go
|
@ -5,15 +5,15 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/tfriedel6/canvas/backend/backendbase"
|
"git.mstar.dev/mstar/canvas/backend/backendbase"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Image is a type holding information on an image loaded with the LoadImage
|
||||||
|
// function
|
||||||
type Image struct {
|
type Image struct {
|
||||||
src interface{}
|
src interface{}
|
||||||
cv *Canvas
|
cv *Canvas
|
||||||
|
@ -22,53 +22,36 @@ type Image struct {
|
||||||
lastUsed time.Time
|
lastUsed time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cv *Canvas) reduceCache(keepSize int) {
|
|
||||||
var total int
|
|
||||||
for _, img := range cv.images {
|
|
||||||
w, h := img.img.Size()
|
|
||||||
total += w * h * 4
|
|
||||||
}
|
|
||||||
if total <= keepSize {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
list := make([]*Image, 0, len(cv.images))
|
|
||||||
for _, img := range cv.images {
|
|
||||||
list = append(list, img)
|
|
||||||
}
|
|
||||||
sort.Slice(list, func(i, j int) bool {
|
|
||||||
return list[i].lastUsed.Before(list[j].lastUsed)
|
|
||||||
})
|
|
||||||
pos := 0
|
|
||||||
for total > keepSize {
|
|
||||||
img := list[pos]
|
|
||||||
pos++
|
|
||||||
delete(cv.images, img.src)
|
|
||||||
w, h := img.img.Size()
|
|
||||||
total -= w * h * 4
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadImage loads an image. The src parameter can be either an image from the
|
// LoadImage loads an image. The src parameter can be either an image from the
|
||||||
// standard image package, a byte slice that will be loaded, or a file name
|
// standard image package, a byte slice that will be loaded, or a file name
|
||||||
// string. If you want the canvas package to load the image, make sure you
|
// string. If you want the canvas package to load the image, make sure you
|
||||||
// import the required format packages
|
// import the required format packages
|
||||||
func (cv *Canvas) LoadImage(src interface{}) (*Image, error) {
|
func (cv *Canvas) LoadImage(src interface{}) (*Image, error) {
|
||||||
|
var reload *Image
|
||||||
if img, ok := src.(*Image); ok {
|
if img, ok := src.(*Image); ok {
|
||||||
img.lastUsed = time.Now()
|
if img.cv != cv {
|
||||||
return img, nil
|
panic("image loaded with different canvas")
|
||||||
|
}
|
||||||
|
if img.deleted {
|
||||||
|
reload = img
|
||||||
|
src = img.src
|
||||||
|
} else {
|
||||||
|
img.lastUsed = time.Now()
|
||||||
|
return img, nil
|
||||||
|
}
|
||||||
} else if _, ok := src.([]byte); !ok {
|
} else if _, ok := src.([]byte); !ok {
|
||||||
if img, ok := cv.images[src]; ok {
|
if img, ok := cv.images[src]; ok {
|
||||||
img.lastUsed = time.Now()
|
img.lastUsed = time.Now()
|
||||||
return img, nil
|
return img, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cv.reduceCache(Performance.ImageCacheSize)
|
cv.reduceCache(Performance.CacheSize, 0)
|
||||||
var srcImg image.Image
|
var srcImg image.Image
|
||||||
switch v := src.(type) {
|
switch v := src.(type) {
|
||||||
case image.Image:
|
case image.Image:
|
||||||
srcImg = v
|
srcImg = v
|
||||||
case string:
|
case string:
|
||||||
data, err := ioutil.ReadFile(v)
|
data, err := os.ReadFile(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -93,6 +76,10 @@ func (cv *Canvas) LoadImage(src interface{}) (*Image, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
cvimg := &Image{cv: cv, img: backendImg, lastUsed: time.Now(), src: src}
|
cvimg := &Image{cv: cv, img: backendImg, lastUsed: time.Now(), src: src}
|
||||||
|
if reload != nil {
|
||||||
|
*reload = *cvimg
|
||||||
|
return reload, nil
|
||||||
|
}
|
||||||
if _, ok := src.([]byte); !ok {
|
if _, ok := src.([]byte); !ok {
|
||||||
cv.images[src] = cvimg
|
cv.images[src] = cvimg
|
||||||
}
|
}
|
||||||
|
@ -100,54 +87,36 @@ func (cv *Canvas) LoadImage(src interface{}) (*Image, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cv *Canvas) getImage(src interface{}) *Image {
|
func (cv *Canvas) getImage(src interface{}) *Image {
|
||||||
if img, ok := src.(*Image); ok {
|
if cv2, ok := src.(*Canvas); ok {
|
||||||
return img
|
if !cv.b.CanUseAsImage(cv2.b) {
|
||||||
} else if img, ok := cv.images[src]; ok {
|
w, h := cv2.Size()
|
||||||
return img
|
return cv.getImage(cv2.GetImageData(0, 0, w, h))
|
||||||
}
|
|
||||||
cv.reduceCache(Performance.ImageCacheSize)
|
|
||||||
switch v := src.(type) {
|
|
||||||
case *Image:
|
|
||||||
return v
|
|
||||||
case image.Image:
|
|
||||||
img, err := cv.LoadImage(v)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "Error loading image: %v\n", err)
|
|
||||||
cv.images[src] = nil
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
cv.images[v] = img
|
bimg := cv2.b.AsImage()
|
||||||
return img
|
if bimg == nil {
|
||||||
case string:
|
w, h := cv2.Size()
|
||||||
img, err := cv.LoadImage(v)
|
return cv.getImage(cv2.GetImageData(0, 0, w, h))
|
||||||
if err != nil {
|
}
|
||||||
|
return &Image{cv: cv, img: bimg}
|
||||||
|
}
|
||||||
|
|
||||||
|
img, err := cv.LoadImage(src)
|
||||||
|
if err != nil {
|
||||||
|
cv.images[src] = nil
|
||||||
|
switch v := src.(type) {
|
||||||
|
case image.Image:
|
||||||
|
fmt.Fprintf(os.Stderr, "Error loading image: %v\n", err)
|
||||||
|
case string:
|
||||||
if strings.Contains(strings.ToLower(err.Error()), "format") {
|
if strings.Contains(strings.ToLower(err.Error()), "format") {
|
||||||
fmt.Fprintf(os.Stderr, "Error loading image %s: %v\nIt may be necessary to import the appropriate decoder, e.g.\nimport _ \"image/jpeg\"\n", v, err)
|
fmt.Fprintf(os.Stderr, "Error loading image %s: %v\nIt may be necessary to import the appropriate decoder, e.g.\nimport _ \"image/jpeg\"\n", v, err)
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprintf(os.Stderr, "Error loading image %s: %v\n", v, err)
|
fmt.Fprintf(os.Stderr, "Error loading image %s: %v\n", v, err)
|
||||||
}
|
}
|
||||||
cv.images[src] = nil
|
default:
|
||||||
return nil
|
fmt.Fprintf(os.Stderr, "Failed to load image: %v\n", err)
|
||||||
}
|
}
|
||||||
cv.images[v] = img
|
|
||||||
return img
|
|
||||||
case *Canvas:
|
|
||||||
if !cv.b.CanUseAsImage(v.b) {
|
|
||||||
w, h := v.Size()
|
|
||||||
return cv.getImage(v.GetImageData(0, 0, w, h))
|
|
||||||
}
|
|
||||||
bimg := v.b.AsImage()
|
|
||||||
if bimg == nil {
|
|
||||||
w, h := v.Size()
|
|
||||||
return cv.getImage(v.GetImageData(0, 0, w, h))
|
|
||||||
}
|
|
||||||
img := &Image{cv: cv, img: bimg}
|
|
||||||
cv.images[v] = img
|
|
||||||
return img
|
|
||||||
}
|
}
|
||||||
fmt.Fprintf(os.Stderr, "Unknown image type: %T\n", src)
|
return img
|
||||||
cv.images[src] = nil
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Width returns the width of the image
|
// Width returns the width of the image
|
||||||
|
@ -159,16 +128,24 @@ func (img *Image) Height() int { return img.img.Height() }
|
||||||
// Size returns the width and height of the image
|
// Size returns the width and height of the image
|
||||||
func (img *Image) Size() (int, int) { return img.img.Size() }
|
func (img *Image) Size() (int, int) { return img.img.Size() }
|
||||||
|
|
||||||
// Delete deletes the image from memory. Any draw calls with a deleted image
|
// Delete deletes the image from memory
|
||||||
// will not do anything
|
|
||||||
func (img *Image) Delete() {
|
func (img *Image) Delete() {
|
||||||
img.img.Delete()
|
if img == nil || img.deleted {
|
||||||
img.img = nil
|
return
|
||||||
|
}
|
||||||
img.deleted = true
|
img.deleted = true
|
||||||
|
img.img.Delete()
|
||||||
|
delete(img.cv.images, img.src)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace replaces the image with the new one
|
// Replace replaces the image with the new one
|
||||||
func (img *Image) Replace(src interface{}) error {
|
func (img *Image) Replace(src interface{}) error {
|
||||||
|
if img.src == src {
|
||||||
|
if origImg, ok := img.src.(image.Image); ok {
|
||||||
|
img.img.Replace(origImg)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
newImg, err := img.cv.LoadImage(src)
|
newImg, err := img.cv.LoadImage(src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -183,15 +160,16 @@ func (img *Image) Replace(src interface{}) error {
|
||||||
// loaded image with the same name parameter.
|
// loaded image with the same name parameter.
|
||||||
//
|
//
|
||||||
// The coordinates must be one of the following:
|
// The coordinates must be one of the following:
|
||||||
// DrawImage("image", dx, dy)
|
//
|
||||||
// DrawImage("image", dx, dy, dw, dh)
|
// DrawImage("image", dx, dy)
|
||||||
// DrawImage("image", sx, sy, sw, sh, dx, dy, dw, dh)
|
// DrawImage("image", dx, dy, dw, dh)
|
||||||
|
// DrawImage("image", sx, sy, sw, sh, dx, dy, dw, dh)
|
||||||
|
//
|
||||||
// Where dx/dy/dw/dh are the destination coordinates and sx/sy/sw/sh are the
|
// Where dx/dy/dw/dh are the destination coordinates and sx/sy/sw/sh are the
|
||||||
// source coordinates
|
// source coordinates
|
||||||
func (cv *Canvas) DrawImage(image interface{}, coords ...float64) {
|
func (cv *Canvas) DrawImage(image interface{}, coords ...float64) {
|
||||||
img := cv.getImage(image)
|
img := cv.getImage(image)
|
||||||
|
if img == nil {
|
||||||
if img == nil || img.deleted {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -210,11 +188,11 @@ func (cv *Canvas) DrawImage(image interface{}, coords ...float64) {
|
||||||
dw, dh = coords[6], coords[7]
|
dw, dh = coords[6], coords[7]
|
||||||
}
|
}
|
||||||
|
|
||||||
var data [4][2]float64
|
var data [4]backendbase.Vec
|
||||||
data[0] = cv.tf(vec{dx, dy})
|
data[0] = cv.tf(backendbase.Vec{dx, dy})
|
||||||
data[1] = cv.tf(vec{dx, dy + dh})
|
data[1] = cv.tf(backendbase.Vec{dx, dy + dh})
|
||||||
data[2] = cv.tf(vec{dx + dw, dy + dh})
|
data[2] = cv.tf(backendbase.Vec{dx + dw, dy + dh})
|
||||||
data[3] = cv.tf(vec{dx + dw, dy})
|
data[3] = cv.tf(backendbase.Vec{dx + dw, dy})
|
||||||
|
|
||||||
cv.drawShadow(data[:], nil, false)
|
cv.drawShadow(data[:], nil, false)
|
||||||
|
|
||||||
|
@ -236,7 +214,7 @@ func (cv *Canvas) PutImageData(img *image.RGBA, x, y int) {
|
||||||
type ImagePattern struct {
|
type ImagePattern struct {
|
||||||
cv *Canvas
|
cv *Canvas
|
||||||
img *Image
|
img *Image
|
||||||
tf mat
|
tf backendbase.Mat
|
||||||
rep imagePatternRepeat
|
rep imagePatternRepeat
|
||||||
ip backendbase.ImagePattern
|
ip backendbase.ImagePattern
|
||||||
}
|
}
|
||||||
|
@ -251,8 +229,8 @@ const (
|
||||||
NoRepeat = imagePatternRepeat(backendbase.NoRepeat)
|
NoRepeat = imagePatternRepeat(backendbase.NoRepeat)
|
||||||
)
|
)
|
||||||
|
|
||||||
func (ip *ImagePattern) data(tf mat) backendbase.ImagePatternData {
|
func (ip *ImagePattern) data(tf backendbase.Mat) backendbase.ImagePatternData {
|
||||||
m := tf.invert().mul(ip.tf.invert())
|
m := tf.Invert().Mul(ip.tf.Invert())
|
||||||
return backendbase.ImagePatternData{
|
return backendbase.ImagePatternData{
|
||||||
Image: ip.img.img,
|
Image: ip.img.img,
|
||||||
Transform: [9]float64{
|
Transform: [9]float64{
|
||||||
|
@ -268,7 +246,7 @@ func (ip *ImagePattern) data(tf mat) backendbase.ImagePatternData {
|
||||||
// to the given matrix. The matrix is a 3x3 matrix, but three
|
// to the given matrix. The matrix is a 3x3 matrix, but three
|
||||||
// of the values are always identity values
|
// of the values are always identity values
|
||||||
func (ip *ImagePattern) SetTransform(tf [6]float64) {
|
func (ip *ImagePattern) SetTransform(tf [6]float64) {
|
||||||
ip.tf = mat(tf)
|
ip.tf = backendbase.Mat(tf)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreatePattern creates a new image pattern with the specified
|
// CreatePattern creates a new image pattern with the specified
|
||||||
|
@ -278,7 +256,7 @@ func (cv *Canvas) CreatePattern(src interface{}, repeat imagePatternRepeat) *Ima
|
||||||
cv: cv,
|
cv: cv,
|
||||||
img: cv.getImage(src),
|
img: cv.getImage(src),
|
||||||
rep: repeat,
|
rep: repeat,
|
||||||
tf: mat{1, 0, 0, 1, 0, 0},
|
tf: backendbase.Mat{1, 0, 0, 1, 0, 0},
|
||||||
}
|
}
|
||||||
if ip.img != nil {
|
if ip.img != nil {
|
||||||
ip.ip = cv.b.LoadImagePattern(ip.data(cv.state.transform))
|
ip.ip = cv.b.LoadImagePattern(ip.data(cv.state.transform))
|
||||||
|
|
140
math.go
|
@ -1,140 +0,0 @@
|
||||||
package canvas
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"math"
|
|
||||||
)
|
|
||||||
|
|
||||||
type vec [2]float64
|
|
||||||
|
|
||||||
func (v vec) String() string {
|
|
||||||
return fmt.Sprintf("[%f,%f]", v[0], v[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (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])
|
|
||||||
}
|
|
171
path2d.go
|
@ -2,18 +2,27 @@ package canvas
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
|
"git.mstar.dev/mstar/canvas/backend/backendbase"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Path2D is a type that holds a predefined path which can be drawn
|
||||||
|
// with a single call
|
||||||
type Path2D struct {
|
type Path2D struct {
|
||||||
cv *Canvas
|
cv *Canvas
|
||||||
p []pathPoint
|
p []pathPoint
|
||||||
move vec
|
move backendbase.Vec
|
||||||
cwSum float64
|
cwSum float64
|
||||||
|
|
||||||
|
standalone bool
|
||||||
|
fillCache []backendbase.Vec
|
||||||
|
|
||||||
|
noSelfIntersection bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type pathPoint struct {
|
type pathPoint struct {
|
||||||
pos vec
|
pos backendbase.Vec
|
||||||
next vec
|
next backendbase.Vec
|
||||||
flags pathPointFlag
|
flags pathPointFlag
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,7 +39,11 @@ const (
|
||||||
|
|
||||||
// NewPath2D creates a new Path2D and returns it
|
// NewPath2D creates a new Path2D and returns it
|
||||||
func (cv *Canvas) NewPath2D() *Path2D {
|
func (cv *Canvas) NewPath2D() *Path2D {
|
||||||
return &Path2D{cv: cv, p: make([]pathPoint, 0, 20)}
|
return &Path2D{cv: cv, p: make([]pathPoint, 0, 20), standalone: true}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Path2D) clearCache() {
|
||||||
|
p.fillCache = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// func (p *Path2D) AddPath(p2 *Path2D) {
|
// func (p *Path2D) AddPath(p2 *Path2D) {
|
||||||
|
@ -38,12 +51,13 @@ func (cv *Canvas) NewPath2D() *Path2D {
|
||||||
|
|
||||||
// MoveTo (see equivalent function on canvas type)
|
// MoveTo (see equivalent function on canvas type)
|
||||||
func (p *Path2D) MoveTo(x, y float64) {
|
func (p *Path2D) MoveTo(x, y float64) {
|
||||||
if len(p.p) > 0 && isSamePoint(p.p[len(p.p)-1].pos, vec{x, y}, 0.1) {
|
if len(p.p) > 0 && isSamePoint(p.p[len(p.p)-1].pos, backendbase.Vec{x, y}, 0.1) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
p.p = append(p.p, pathPoint{pos: vec{x, y}, flags: pathMove | pathIsConvex})
|
p.clearCache()
|
||||||
|
p.p = append(p.p, pathPoint{pos: backendbase.Vec{x, y}, flags: pathMove | pathIsConvex})
|
||||||
p.cwSum = 0
|
p.cwSum = 0
|
||||||
p.move = vec{x, y}
|
p.move = backendbase.Vec{x, y}
|
||||||
}
|
}
|
||||||
|
|
||||||
// LineTo (see equivalent function on canvas type)
|
// LineTo (see equivalent function on canvas type)
|
||||||
|
@ -53,17 +67,18 @@ func (p *Path2D) LineTo(x, y float64) {
|
||||||
|
|
||||||
func (p *Path2D) lineTo(x, y float64, checkSelfIntersection bool) {
|
func (p *Path2D) lineTo(x, y float64, checkSelfIntersection bool) {
|
||||||
count := len(p.p)
|
count := len(p.p)
|
||||||
if count > 0 && isSamePoint(p.p[len(p.p)-1].pos, vec{x, y}, 0.1) {
|
if count > 0 && isSamePoint(p.p[len(p.p)-1].pos, backendbase.Vec{x, y}, 0.1) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
p.clearCache()
|
||||||
if count == 0 {
|
if count == 0 {
|
||||||
p.MoveTo(x, y)
|
p.MoveTo(x, y)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
prev := &p.p[count-1]
|
prev := &p.p[count-1]
|
||||||
prev.next = vec{x, y}
|
prev.next = backendbase.Vec{x, y}
|
||||||
prev.flags |= pathAttach
|
prev.flags |= pathAttach
|
||||||
p.p = append(p.p, pathPoint{pos: vec{x, y}})
|
p.p = append(p.p, pathPoint{pos: backendbase.Vec{x, y}})
|
||||||
newp := &p.p[count]
|
newp := &p.p[count]
|
||||||
|
|
||||||
if prev.flags&pathIsConvex > 0 {
|
if prev.flags&pathIsConvex > 0 {
|
||||||
|
@ -82,21 +97,24 @@ func (p *Path2D) lineTo(x, y float64, checkSelfIntersection bool) {
|
||||||
prev2 := &p.p[count-2]
|
prev2 := &p.p[count-2]
|
||||||
cw := (prev.flags & pathIsClockwise) > 0
|
cw := (prev.flags & pathIsClockwise) > 0
|
||||||
|
|
||||||
ln := prev.pos.sub(prev2.pos)
|
ln := prev.pos.Sub(prev2.pos)
|
||||||
lo := vec{ln[1], -ln[0]}
|
lo := backendbase.Vec{ln[1], -ln[0]}
|
||||||
dot := newp.pos.sub(prev2.pos).dot(lo)
|
dot := newp.pos.Sub(prev2.pos).Dot(lo)
|
||||||
|
|
||||||
if (cw && dot <= 0) || (!cw && dot >= 0) {
|
if (cw && dot <= 0) || (!cw && dot >= 0) {
|
||||||
newp.flags |= pathIsConvex
|
newp.flags |= pathIsConvex
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
csi := checkSelfIntersection && !Performance.IgnoreSelfIntersections && !p.noSelfIntersection
|
||||||
|
|
||||||
if prev.flags&pathSelfIntersects > 0 {
|
if prev.flags&pathSelfIntersects > 0 {
|
||||||
newp.flags |= pathSelfIntersects
|
newp.flags |= pathSelfIntersects
|
||||||
} else if newp.flags&pathIsConvex == 0 && newp.flags&pathSelfIntersects == 0 && checkSelfIntersection && !Performance.IgnoreSelfIntersections {
|
} else if newp.flags&pathIsConvex == 0 && newp.flags&pathSelfIntersects == 0 && csi {
|
||||||
|
|
||||||
cuts := false
|
cuts := false
|
||||||
var cutPoint vec
|
var cutPoint backendbase.Vec
|
||||||
b0, b1 := prev.pos, vec{x, y}
|
b0, b1 := prev.pos, backendbase.Vec{x, y}
|
||||||
for i := 1; i < count; i++ {
|
for i := 1; i < count; i++ {
|
||||||
a0, a1 := p.p[i-1].pos, p.p[i].pos
|
a0, a1 := p.p[i-1].pos, p.p[i].pos
|
||||||
var r1, r2 float64
|
var r1, r2 float64
|
||||||
|
@ -106,7 +124,7 @@ func (p *Path2D) lineTo(x, y float64, checkSelfIntersection bool) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if cuts && !isSamePoint(cutPoint, vec{x, y}, samePointTolerance) {
|
if cuts && !isSamePoint(cutPoint, backendbase.Vec{x, y}, samePointTolerance) {
|
||||||
newp.flags |= pathSelfIntersects
|
newp.flags |= pathSelfIntersects
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -114,19 +132,24 @@ func (p *Path2D) lineTo(x, y float64, checkSelfIntersection bool) {
|
||||||
|
|
||||||
// Arc (see equivalent function on canvas type)
|
// Arc (see equivalent function on canvas type)
|
||||||
func (p *Path2D) Arc(x, y, radius, startAngle, endAngle float64, anticlockwise bool) {
|
func (p *Path2D) Arc(x, y, radius, startAngle, endAngle float64, anticlockwise bool) {
|
||||||
p.arc(x, y, radius, startAngle, endAngle, anticlockwise, matIdentity, true)
|
p.arc(x, y, radius, startAngle, endAngle, anticlockwise, backendbase.MatIdentity, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Path2D) arc(x, y, radius, startAngle, endAngle float64, anticlockwise bool, m mat, ident bool) {
|
func (p *Path2D) arc(
|
||||||
|
x, y, radius, startAngle, endAngle float64,
|
||||||
|
anticlockwise bool,
|
||||||
|
m backendbase.Mat,
|
||||||
|
ident bool,
|
||||||
|
) {
|
||||||
checkSelfIntersection := len(p.p) > 0
|
checkSelfIntersection := len(p.p) > 0
|
||||||
|
|
||||||
lastWasMove := len(p.p) == 0 || p.p[len(p.p)-1].flags&pathMove != 0
|
lastWasMove := len(p.p) == 0 || p.p[len(p.p)-1].flags&pathMove != 0
|
||||||
|
|
||||||
if endAngle == startAngle {
|
if endAngle == startAngle {
|
||||||
s, c := math.Sincos(endAngle)
|
s, c := math.Sincos(endAngle)
|
||||||
pt := vec{x + radius*c, y + radius*s}
|
pt := backendbase.Vec{x + radius*c, y + radius*s}
|
||||||
if !ident {
|
if !ident {
|
||||||
pt = pt.mulMat(m)
|
pt = pt.MulMat(m)
|
||||||
}
|
}
|
||||||
p.lineTo(pt[0], pt[1], checkSelfIntersection)
|
p.lineTo(pt[0], pt[1], checkSelfIntersection)
|
||||||
|
|
||||||
|
@ -161,26 +184,26 @@ func (p *Path2D) arc(x, y, radius, startAngle, endAngle float64, anticlockwise b
|
||||||
if !anticlockwise {
|
if !anticlockwise {
|
||||||
for a := startAngle; a < endAngle; a += step {
|
for a := startAngle; a < endAngle; a += step {
|
||||||
s, c := math.Sincos(a)
|
s, c := math.Sincos(a)
|
||||||
pt := vec{x + radius*c, y + radius*s}
|
pt := backendbase.Vec{x + radius*c, y + radius*s}
|
||||||
if !ident {
|
if !ident {
|
||||||
pt = pt.mulMat(m)
|
pt = pt.MulMat(m)
|
||||||
}
|
}
|
||||||
p.lineTo(pt[0], pt[1], checkSelfIntersection)
|
p.lineTo(pt[0], pt[1], checkSelfIntersection)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for a := startAngle; a > endAngle; a -= step {
|
for a := startAngle; a > endAngle; a -= step {
|
||||||
s, c := math.Sincos(a)
|
s, c := math.Sincos(a)
|
||||||
pt := vec{x + radius*c, y + radius*s}
|
pt := backendbase.Vec{x + radius*c, y + radius*s}
|
||||||
if !ident {
|
if !ident {
|
||||||
pt = pt.mulMat(m)
|
pt = pt.MulMat(m)
|
||||||
}
|
}
|
||||||
p.lineTo(pt[0], pt[1], checkSelfIntersection)
|
p.lineTo(pt[0], pt[1], checkSelfIntersection)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s, c := math.Sincos(endAngle)
|
s, c := math.Sincos(endAngle)
|
||||||
pt := vec{x + radius*c, y + radius*s}
|
pt := backendbase.Vec{x + radius*c, y + radius*s}
|
||||||
if !ident {
|
if !ident {
|
||||||
pt = pt.mulMat(m)
|
pt = pt.MulMat(m)
|
||||||
}
|
}
|
||||||
p.lineTo(pt[0], pt[1], checkSelfIntersection)
|
p.lineTo(pt[0], pt[1], checkSelfIntersection)
|
||||||
|
|
||||||
|
@ -191,34 +214,34 @@ func (p *Path2D) arc(x, y, radius, startAngle, endAngle float64, anticlockwise b
|
||||||
|
|
||||||
// ArcTo (see equivalent function on canvas type)
|
// ArcTo (see equivalent function on canvas type)
|
||||||
func (p *Path2D) ArcTo(x1, y1, x2, y2, radius float64) {
|
func (p *Path2D) ArcTo(x1, y1, x2, y2, radius float64) {
|
||||||
p.arcTo(x1, y1, x2, y2, radius, matIdentity, true)
|
p.arcTo(x1, y1, x2, y2, radius, backendbase.MatIdentity, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Path2D) arcTo(x1, y1, x2, y2, radius float64, m mat, ident bool) {
|
func (p *Path2D) arcTo(x1, y1, x2, y2, radius float64, m backendbase.Mat, ident bool) {
|
||||||
if len(p.p) == 0 {
|
if len(p.p) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
p0, p1, p2 := p.p[len(p.p)-1].pos, vec{x1, y1}, vec{x2, y2}
|
p0, p1, p2 := p.p[len(p.p)-1].pos, backendbase.Vec{x1, y1}, backendbase.Vec{x2, y2}
|
||||||
if !ident {
|
if !ident {
|
||||||
p0 = p0.mulMat(m.invert())
|
p0 = p0.MulMat(m.Invert())
|
||||||
}
|
}
|
||||||
v0, v1 := p0.sub(p1).norm(), p2.sub(p1).norm()
|
v0, v1 := p0.Sub(p1).Norm(), p2.Sub(p1).Norm()
|
||||||
angle := math.Acos(v0.dot(v1))
|
angle := math.Acos(v0.Dot(v1))
|
||||||
// should be in the range [0-pi]. if parallel, use a straight line
|
// should be in the range [0-pi]. if parallel, use a straight line
|
||||||
if angle <= 0 || angle >= math.Pi {
|
if angle <= 0 || angle >= math.Pi {
|
||||||
p.LineTo(x2, y2)
|
p.LineTo(x2, y2)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// cv0 and cv1 are vectors that point to the center of the circle
|
// cv0 and cv1 are vectors that point to the center of the circle
|
||||||
cv0 := vec{-v0[1], v0[0]}
|
cv0 := backendbase.Vec{-v0[1], v0[0]}
|
||||||
cv1 := vec{v1[1], -v1[0]}
|
cv1 := backendbase.Vec{v1[1], -v1[0]}
|
||||||
x := cv1.sub(cv0).div(v0.sub(v1))[0] * radius
|
x := cv1.Sub(cv0).Div(v0.Sub(v1))[0] * radius
|
||||||
if x < 0 {
|
if x < 0 {
|
||||||
cv0 = cv0.mulf(-1)
|
cv0 = cv0.Mulf(-1)
|
||||||
cv1 = cv1.mulf(-1)
|
cv1 = cv1.Mulf(-1)
|
||||||
}
|
}
|
||||||
center := p1.add(v0.mulf(math.Abs(x))).add(cv0.mulf(radius))
|
center := p1.Add(v0.Mulf(math.Abs(x))).Add(cv0.Mulf(radius))
|
||||||
a0, a1 := cv0.mulf(-1).atan2(), cv1.mulf(-1).atan2()
|
a0, a1 := cv0.Mulf(-1).Atan2(), cv1.Mulf(-1).Atan2()
|
||||||
if x > 0 {
|
if x > 0 {
|
||||||
if a1-a0 > 0 {
|
if a1-a0 > 0 {
|
||||||
a0 += math.Pi * 2
|
a0 += math.Pi * 2
|
||||||
|
@ -237,17 +260,17 @@ func (p *Path2D) QuadraticCurveTo(x1, y1, x2, y2 float64) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
p0 := p.p[len(p.p)-1].pos
|
p0 := p.p[len(p.p)-1].pos
|
||||||
p1 := vec{x1, y1}
|
p1 := backendbase.Vec{x1, y1}
|
||||||
p2 := vec{x2, y2}
|
p2 := backendbase.Vec{x2, y2}
|
||||||
v0 := p1.sub(p0)
|
v0 := p1.Sub(p0)
|
||||||
v1 := p2.sub(p1)
|
v1 := p2.Sub(p1)
|
||||||
|
|
||||||
const step = 0.01
|
const step = 0.01
|
||||||
|
|
||||||
for r := 0.0; r < 1; r += step {
|
for r := 0.0; r < 1; r += step {
|
||||||
i0 := v0.mulf(r).add(p0)
|
i0 := v0.Mulf(r).Add(p0)
|
||||||
i1 := v1.mulf(r).add(p1)
|
i1 := v1.Mulf(r).Add(p1)
|
||||||
pt := i1.sub(i0).mulf(r).add(i0)
|
pt := i1.Sub(i0).Mulf(r).Add(i0)
|
||||||
p.LineTo(pt[0], pt[1])
|
p.LineTo(pt[0], pt[1])
|
||||||
}
|
}
|
||||||
p.LineTo(x2, y2)
|
p.LineTo(x2, y2)
|
||||||
|
@ -259,31 +282,34 @@ func (p *Path2D) BezierCurveTo(x1, y1, x2, y2, x3, y3 float64) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
p0 := p.p[len(p.p)-1].pos
|
p0 := p.p[len(p.p)-1].pos
|
||||||
p1 := vec{x1, y1}
|
p1 := backendbase.Vec{x1, y1}
|
||||||
p2 := vec{x2, y2}
|
p2 := backendbase.Vec{x2, y2}
|
||||||
p3 := vec{x3, y3}
|
p3 := backendbase.Vec{x3, y3}
|
||||||
v0 := p1.sub(p0)
|
v0 := p1.Sub(p0)
|
||||||
v1 := p2.sub(p1)
|
v1 := p2.Sub(p1)
|
||||||
v2 := p3.sub(p2)
|
v2 := p3.Sub(p2)
|
||||||
|
|
||||||
const step = 0.01
|
const step = 0.01
|
||||||
|
|
||||||
for r := 0.0; r < 1; r += step {
|
for r := 0.0; r < 1; r += step {
|
||||||
i0 := v0.mulf(r).add(p0)
|
i0 := v0.Mulf(r).Add(p0)
|
||||||
i1 := v1.mulf(r).add(p1)
|
i1 := v1.Mulf(r).Add(p1)
|
||||||
i2 := v2.mulf(r).add(p2)
|
i2 := v2.Mulf(r).Add(p2)
|
||||||
iv0 := i1.sub(i0)
|
iv0 := i1.Sub(i0)
|
||||||
iv1 := i2.sub(i1)
|
iv1 := i2.Sub(i1)
|
||||||
j0 := iv0.mulf(r).add(i0)
|
j0 := iv0.Mulf(r).Add(i0)
|
||||||
j1 := iv1.mulf(r).add(i1)
|
j1 := iv1.Mulf(r).Add(i1)
|
||||||
pt := j1.sub(j0).mulf(r).add(j0)
|
pt := j1.Sub(j0).Mulf(r).Add(j0)
|
||||||
p.LineTo(pt[0], pt[1])
|
p.LineTo(pt[0], pt[1])
|
||||||
}
|
}
|
||||||
p.LineTo(x3, y3)
|
p.LineTo(x3, y3)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ellipse (see equivalent function on canvas type)
|
// Ellipse (see equivalent function on canvas type)
|
||||||
func (p *Path2D) Ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle float64, anticlockwise bool) {
|
func (p *Path2D) Ellipse(
|
||||||
|
x, y, radiusX, radiusY, rotation, startAngle, endAngle float64,
|
||||||
|
anticlockwise bool,
|
||||||
|
) {
|
||||||
checkSelfIntersection := len(p.p) > 0
|
checkSelfIntersection := len(p.p) > 0
|
||||||
|
|
||||||
rs, rc := math.Sincos(rotation)
|
rs, rc := math.Sincos(rotation)
|
||||||
|
@ -354,9 +380,6 @@ func (p *Path2D) ClosePath() {
|
||||||
if len(p.p) < 2 {
|
if len(p.p) < 2 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if isSamePoint(p.p[len(p.p)-1].pos, p.p[0].pos, 0.1) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
closeIdx := 0
|
closeIdx := 0
|
||||||
for i := len(p.p) - 1; i >= 0; i-- {
|
for i := len(p.p) - 1; i >= 0; i-- {
|
||||||
if p.p[i].flags&pathMove != 0 {
|
if p.p[i].flags&pathMove != 0 {
|
||||||
|
@ -364,7 +387,9 @@ func (p *Path2D) ClosePath() {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
p.LineTo(p.p[closeIdx].pos[0], p.p[closeIdx].pos[1])
|
if !isSamePoint(p.p[len(p.p)-1].pos, p.p[0].pos, 0.1) {
|
||||||
|
p.LineTo(p.p[closeIdx].pos[0], p.p[closeIdx].pos[1])
|
||||||
|
}
|
||||||
p.p[len(p.p)-1].next = p.p[closeIdx].next
|
p.p[len(p.p)-1].next = p.p[closeIdx].next
|
||||||
p.p[len(p.p)-1].flags |= pathAttach
|
p.p[len(p.p)-1].flags |= pathAttach
|
||||||
}
|
}
|
||||||
|
@ -432,7 +457,7 @@ func (p *Path2D) IsPointInPath(x, y float64, rule pathRule) bool {
|
||||||
num := 0
|
num := 0
|
||||||
prev := sp[len(sp)-1].pos
|
prev := sp[len(sp)-1].pos
|
||||||
for _, pt := range sp {
|
for _, pt := range sp {
|
||||||
r, dir := pointIsRightOfLine(prev, pt.pos, vec{x, y})
|
r, dir := pointIsRightOfLine(prev, pt.pos, backendbase.Vec{x, y})
|
||||||
prev = pt.pos
|
prev = pt.pos
|
||||||
if !r {
|
if !r {
|
||||||
continue
|
continue
|
||||||
|
@ -461,15 +486,15 @@ func (p *Path2D) IsPointInStroke(x, y float64) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
var triBuf [500][2]float64
|
var triBuf [500]backendbase.Vec
|
||||||
tris := p.cv.strokeTris(p, mat{}, false, triBuf[:0])
|
tris := p.cv.strokeTris(p, p.cv.state.transform, backendbase.Mat{}, false, triBuf[:0])
|
||||||
|
|
||||||
pt := vec{x, y}
|
pt := backendbase.Vec{x, y}
|
||||||
|
|
||||||
for i := 0; i < len(tris); i += 3 {
|
for i := 0; i < len(tris); i += 3 {
|
||||||
a := vec{tris[i][0], tris[i][1]}
|
a := backendbase.Vec{tris[i][0], tris[i][1]}
|
||||||
b := vec{tris[i+1][0], tris[i+1][1]}
|
b := backendbase.Vec{tris[i+1][0], tris[i+1][1]}
|
||||||
c := vec{tris[i+2][0], tris[i+2][1]}
|
c := backendbase.Vec{tris[i+2][0], tris[i+2][1]}
|
||||||
if triangleContainsPoint(a, b, c, pt) {
|
if triangleContainsPoint(a, b, c, pt) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|