Compare commits
194 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 | ||
![]() |
896af05ba4 | ||
![]() |
a0ba7b2ad3 | ||
![]() |
d670f964c8 | ||
![]() |
421d388f91 | ||
![]() |
84d2b4f3cb | ||
![]() |
3378745af4 | ||
![]() |
ffd40c721e | ||
![]() |
82290ace4a | ||
![]() |
239026dd49 | ||
![]() |
e3098ff636 | ||
![]() |
3e6e46ca0d | ||
![]() |
55572c59da | ||
![]() |
637509b5d4 | ||
![]() |
7f7efd5a8a | ||
![]() |
0d915f7178 | ||
![]() |
7213b3edcd | ||
![]() |
8b79ad18fa | ||
![]() |
f5e7e6a060 | ||
![]() |
598ac1e325 | ||
![]() |
b0230892ab | ||
![]() |
50bf39fe62 | ||
![]() |
a80e3778fd | ||
![]() |
259bb9e598 | ||
![]() |
50c77477c9 | ||
![]() |
3f85d64ff3 | ||
![]() |
1333730731 | ||
![]() |
6912878a8c | ||
![]() |
04d4dab3ff | ||
![]() |
ecadd0e5ec | ||
![]() |
c8ff508299 | ||
![]() |
79fde3ee1d | ||
![]() |
9729daa6d4 | ||
![]() |
f0367ee72d | ||
![]() |
fd1cca7ba9 | ||
![]() |
71741d4234 | ||
![]() |
239ab21259 | ||
![]() |
950d2bb30a | ||
![]() |
58e7c45597 | ||
![]() |
98a8334efc | ||
![]() |
9f92f2c5c4 | ||
![]() |
5ce888f8b9 | ||
![]() |
b5212c916a | ||
![]() |
349e01e301 | ||
![]() |
92d388c3c4 | ||
![]() |
0386303dd6 | ||
![]() |
e44b120ad8 | ||
![]() |
3695a6ef59 | ||
![]() |
62e4cae716 | ||
![]() |
ea3cb81231 | ||
![]() |
c945678725 | ||
![]() |
505386f199 | ||
![]() |
e0b88c0ca6 | ||
![]() |
17b212acba | ||
![]() |
554fa84a0a | ||
![]() |
d56f68b2e7 | ||
![]() |
4e5c38b295 | ||
![]() |
bbcb712b73 | ||
![]() |
623a4742ce | ||
![]() |
bfbd54827b | ||
![]() |
a913b8b33b | ||
![]() |
e3bb07a09c | ||
![]() |
a0a1adef12 | ||
![]() |
9edbb8da85 | ||
![]() |
ec3bde6374 | ||
![]() |
4d0f41cc6b | ||
![]() |
973e410204 | ||
![]() |
d1d2186a2f | ||
![]() |
7fa1f9096f | ||
![]() |
faf179caa0 | ||
![]() |
9f99dff89b | ||
![]() |
a4826a3e12 | ||
![]() |
aa059cfcf3 | ||
![]() |
23a92d2221 | ||
![]() |
9b7322ff16 | ||
![]() |
e4dae7a86f | ||
![]() |
eed8f3e241 | ||
![]() |
3e21561f29 | ||
![]() |
f89b3eccdc | ||
![]() |
a5d921223f | ||
![]() |
3a749132e2 | ||
![]() |
9103dfb672 | ||
![]() |
1f6301a2f9 | ||
![]() |
b2568a0205 | ||
![]() |
7d0fc7ceb8 | ||
![]() |
0476a8a85f | ||
![]() |
312633710b | ||
![]() |
abfc73419b | ||
![]() |
287ff8905f | ||
![]() |
41a0ed18af | ||
![]() |
6d46d9d554 | ||
![]() |
ff4c73b6b7 | ||
![]() |
857e6c4e47 | ||
![]() |
c9fcfbdd53 | ||
![]() |
70ece785e9 | ||
![]() |
7c4521b227 | ||
![]() |
e40654181e | ||
![]() |
2163c982fd | ||
![]() |
b7397ee4dc | ||
![]() |
b2502f5472 | ||
![]() |
0e16a8261b | ||
![]() |
5acbe5452b | ||
![]() |
6a082a74a3 | ||
![]() |
3590b73c8b | ||
![]() |
04b3418c3b | ||
![]() |
c98bc6c23d | ||
![]() |
22c3c25d8e | ||
![]() |
ba8238ba66 | ||
![]() |
9c3cccabdd | ||
![]() |
122488e64c | ||
![]() |
2717a0bdd9 | ||
![]() |
f8aa4d62ce | ||
![]() |
654d9b1796 | ||
![]() |
549a6f2c2e | ||
![]() |
222c12142a | ||
![]() |
77a9d14867 | ||
![]() |
93c75a9b61 | ||
![]() |
041cf94c5c | ||
![]() |
1f682f1f31 | ||
![]() |
c207447158 | ||
![]() |
a36c635367 | ||
![]() |
ed34e3d8c4 | ||
![]() |
816582dbb3 | ||
![]() |
3e874d544b | ||
![]() |
63b71e5e52 | ||
![]() |
97dff96ca2 | ||
![]() |
f962da34c4 | ||
![]() |
8dc91b34f5 | ||
![]() |
a0dfb6f530 | ||
![]() |
fe9d6e60e4 | ||
![]() |
cf788d55f3 | ||
![]() |
e17505b056 | ||
![]() |
fc4d3dbd9e | ||
![]() |
e7d0b1a7ee | ||
![]() |
43f040f777 | ||
![]() |
ac9a395c05 | ||
![]() |
c5c13de2a4 | ||
![]() |
a354e192b6 | ||
![]() |
476dbac6e2 | ||
![]() |
7cef867541 | ||
![]() |
c28c50a22e | ||
![]() |
c5c8ac500f | ||
![]() |
38eddd2837 | ||
![]() |
359b52f473 | ||
![]() |
114b292be7 | ||
![]() |
7dbcd9b420 | ||
![]() |
1e5180dd65 | ||
![]() |
6091afb755 | ||
![]() |
65ebe6af69 | ||
![]() |
57348acc02 | ||
![]() |
7aeae444a9 | ||
![]() |
c1f855278d | ||
![]() |
c5d148331b | ||
![]() |
f77841e973 | ||
![]() |
7ce0b7bf9d | ||
![]() |
99b9763f00 | ||
![]() |
bb244c4868 | ||
![]() |
7565296c42 | ||
![]() |
f62e309d77 | ||
![]() |
3800d8519c | ||
![]() |
908e88d374 | ||
![]() |
ef97c9be5c | ||
![]() |
deaa8f87b6 | ||
![]() |
cb3e694671 |
4
.gitignore
vendored
|
@ -2,6 +2,8 @@ test*.html
|
|||
plan.txt
|
||||
glowgen
|
||||
testapp
|
||||
testblur
|
||||
videocap
|
||||
.DS_Store
|
||||
|
||||
.vscode
|
||||
_*
|
||||
|
|
33
README.md
|
@ -1,21 +1,29 @@
|
|||
[GoDoc is available here](https://godoc.org/github.com/tfriedel6/canvas)
|
||||
# Go canvas [](https://godoc.org/github.com/tfriedel6/canvas)
|
||||
|
||||
# Go canvas
|
||||
Canvas is a pure Go library that provides drawing functionality as similar as possible to the HTML5 canvas API. It has nothing to do with HTML or Javascript, the functions are just made to be approximately the same.
|
||||
|
||||
Canvas is a Go library based on OpenGL that tries to provide the HTML5 canvas API as closely as possible.
|
||||
|
||||
Many of the basic functions are supported, but it is still a work in progress. The library aims to accept a lot of different parameters on each function in a similar way as the Javascript API does.
|
||||
Most of the functions are supported, but it is still a work in progress. The library aims to accept a lot of different parameters on each function in a similar way as the Javascript API does.
|
||||
|
||||
Whereas the Javascript API uses a context that all draw calls go to, here all draw calls are directly on the canvas type. The other difference is that here setters are used instead of properties for things like fonts and line width.
|
||||
|
||||
The library is intended to provide decent performance. Obviously it will not be able to rival hand coded OpenGL for a given purpose, but for many purposes it will be enough. It can also be combined with hand coded OpenGL.
|
||||
## OpenGL backend
|
||||
|
||||
# SDL/GLFW convenience packages
|
||||
The OpenGL backend is intended to provide decent performance. Obviously it will not be able to rival hand coded OpenGL for a given purpose, but for many purposes it will be enough. It can also be combined with hand coded OpenGL.
|
||||
|
||||
## Software backend
|
||||
|
||||
The software backend can also be used if no OpenGL context is available. It will render into a standard Go RGBA image.
|
||||
|
||||
There is experimental MSAA anti-aliasing, but it doesn't fully work properly yet. The best option for anti-aliasing currently is to render to a larger image and then scale it down.
|
||||
|
||||
## SDL/GLFW convenience packages
|
||||
|
||||
The sdlcanvas and glfwcanvas subpackages provide a very simple way to get started with just a few lines of code. As the names imply they are based on the SDL library and the GLFW library respectively. They create a window for you and give you a canvas to draw with.
|
||||
|
||||
# OS support
|
||||
|
||||
Both the OpenGL and software backends work on the following operating systems:
|
||||
|
||||
- Linux
|
||||
- Windows
|
||||
- macOS
|
||||
|
@ -99,12 +107,13 @@ These features *should* work just like their HTML5 counterparts, but there are l
|
|||
- fillText
|
||||
- measureText
|
||||
- textAlign
|
||||
- textBaseline
|
||||
- fillStyle
|
||||
- strokeText
|
||||
- strokeStyle
|
||||
- linear gradients
|
||||
- radial gradients
|
||||
- image patterns
|
||||
- image patterns with repeat and transform
|
||||
- lineWidth
|
||||
- lineEnd (square, butt, round)
|
||||
- lineJoin (bevel, miter, round)
|
||||
|
@ -120,10 +129,12 @@ These features *should* work just like their HTML5 counterparts, but there are l
|
|||
- shadowColor
|
||||
- shadowOffset(X/Y)
|
||||
- shadowBlur
|
||||
- isPointInPath
|
||||
- isPointInStroke
|
||||
- self intersecting polygons
|
||||
|
||||
# Missing features
|
||||
|
||||
- globalCompositeOperation
|
||||
- textBaseline
|
||||
- isPointInPath
|
||||
- isPointInStroke
|
||||
- imageSmoothingEnabled
|
||||
- textBaseline hanging and ideographic (currently work just like top and bottom)
|
||||
|
|
128
backend/backendbase/base.go
Normal file
|
@ -0,0 +1,128 @@
|
|||
package backendbase
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
"math"
|
||||
)
|
||||
|
||||
// Backend is used by the canvas to actually do the final
|
||||
// drawing. This enables the backend to be implemented by
|
||||
// various methods (OpenGL, but also other APIs or software)
|
||||
type Backend interface {
|
||||
Size() (int, int)
|
||||
|
||||
LoadImage(img image.Image) (Image, error)
|
||||
LoadImagePattern(data ImagePatternData) ImagePattern
|
||||
LoadLinearGradient(data Gradient) LinearGradient
|
||||
LoadRadialGradient(data Gradient) RadialGradient
|
||||
|
||||
Clear(pts [4]Vec)
|
||||
Fill(style *FillStyle, pts []Vec, tf Mat, canOverlap bool)
|
||||
DrawImage(dimg Image, sx, sy, sw, sh float64, pts [4]Vec, alpha float64)
|
||||
FillImageMask(style *FillStyle, mask *image.Alpha, pts [4]Vec) // pts must have four points
|
||||
|
||||
ClearClip()
|
||||
Clip(pts []Vec)
|
||||
|
||||
GetImageData(x, y, w, h int) *image.RGBA
|
||||
PutImageData(img *image.RGBA, x, y int)
|
||||
|
||||
CanUseAsImage(b Backend) bool
|
||||
AsImage() Image // can return nil if not supported
|
||||
}
|
||||
|
||||
// FillStyle is the color and other details on how to fill
|
||||
type FillStyle struct {
|
||||
Color color.RGBA
|
||||
Blur float64
|
||||
LinearGradient LinearGradient
|
||||
RadialGradient RadialGradient
|
||||
Gradient struct {
|
||||
X0, Y0 float64
|
||||
X1, Y1 float64
|
||||
RadFrom float64
|
||||
RadTo float64
|
||||
}
|
||||
ImagePattern ImagePattern
|
||||
}
|
||||
|
||||
type Gradient []GradientStop
|
||||
|
||||
func (g Gradient) ColorAt(pos float64) color.RGBA {
|
||||
if len(g) == 0 {
|
||||
return color.RGBA{}
|
||||
} else if len(g) == 1 {
|
||||
return g[0].Color
|
||||
}
|
||||
beforeIdx, afterIdx := -1, -1
|
||||
for i, stop := range g {
|
||||
if stop.Pos > pos {
|
||||
afterIdx = i
|
||||
break
|
||||
}
|
||||
beforeIdx = i
|
||||
}
|
||||
if beforeIdx == -1 {
|
||||
return g[0].Color
|
||||
} else if afterIdx == -1 {
|
||||
return g[len(g)-1].Color
|
||||
}
|
||||
before, after := g[beforeIdx], g[afterIdx]
|
||||
p := (pos - before.Pos) / (after.Pos - before.Pos)
|
||||
var c [4]float64
|
||||
c[0] = (float64(after.Color.R)-float64(before.Color.R))*p + float64(before.Color.R)
|
||||
c[1] = (float64(after.Color.G)-float64(before.Color.G))*p + float64(before.Color.G)
|
||||
c[2] = (float64(after.Color.B)-float64(before.Color.B))*p + float64(before.Color.B)
|
||||
c[3] = (float64(after.Color.A)-float64(before.Color.A))*p + float64(before.Color.A)
|
||||
return color.RGBA{
|
||||
R: uint8(math.Round(c[0])),
|
||||
G: uint8(math.Round(c[1])),
|
||||
B: uint8(math.Round(c[2])),
|
||||
A: uint8(math.Round(c[3])),
|
||||
}
|
||||
}
|
||||
|
||||
type GradientStop struct {
|
||||
Pos float64
|
||||
Color color.RGBA
|
||||
}
|
||||
|
||||
type LinearGradient interface {
|
||||
Delete()
|
||||
Replace(data Gradient)
|
||||
}
|
||||
|
||||
type RadialGradient interface {
|
||||
Delete()
|
||||
Replace(data Gradient)
|
||||
}
|
||||
|
||||
type Image interface {
|
||||
Width() int
|
||||
Height() int
|
||||
Size() (w, h int)
|
||||
Delete()
|
||||
Replace(src image.Image) error
|
||||
}
|
||||
|
||||
type ImagePatternData struct {
|
||||
Image Image
|
||||
Transform [9]float64
|
||||
Repeat ImagePatternRepeat
|
||||
}
|
||||
|
||||
type ImagePatternRepeat uint8
|
||||
|
||||
// Image pattern repeat constants
|
||||
const (
|
||||
Repeat ImagePatternRepeat = iota
|
||||
RepeatX
|
||||
RepeatY
|
||||
NoRepeat
|
||||
)
|
||||
|
||||
type ImagePattern interface {
|
||||
Delete()
|
||||
Replace(data ImagePatternData)
|
||||
}
|
140
backend/backendbase/math.go
Normal file
|
@ -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])
|
||||
}
|
199
backend/softwarebackend/blur.go
Normal file
|
@ -0,0 +1,199 @@
|
|||
package softwarebackend
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
"image/draw"
|
||||
"math"
|
||||
)
|
||||
|
||||
func (b *SoftwareBackend) activateBlurTarget() {
|
||||
b.blurSwap = b.Image
|
||||
b.Image = image.NewRGBA(b.Image.Rect)
|
||||
}
|
||||
|
||||
func (b *SoftwareBackend) drawBlurred(size float64) {
|
||||
blurred := box3(b.Image, size)
|
||||
b.Image = b.blurSwap
|
||||
draw.Draw(b.Image, b.Image.Rect, blurred, image.ZP, draw.Over)
|
||||
}
|
||||
|
||||
func box3(img *image.RGBA, size float64) *image.RGBA {
|
||||
size *= 1 - 1/(size+1) // this just seems to improve the accuracy
|
||||
|
||||
fsize := math.Floor(size)
|
||||
sizea := int(fsize)
|
||||
sizeb := sizea
|
||||
sizec := sizea
|
||||
if size-fsize > 0.333333333 {
|
||||
sizeb++
|
||||
}
|
||||
if size-fsize > 0.666666666 {
|
||||
sizec++
|
||||
}
|
||||
img = box3x(img, sizea)
|
||||
img = box3x(img, sizeb)
|
||||
img = box3x(img, sizec)
|
||||
img = box3y(img, sizea)
|
||||
img = box3y(img, sizeb)
|
||||
img = box3y(img, sizec)
|
||||
return img
|
||||
}
|
||||
|
||||
func box3x(img *image.RGBA, size int) *image.RGBA {
|
||||
bounds := img.Bounds()
|
||||
result := image.NewRGBA(bounds)
|
||||
w, h := bounds.Dx(), bounds.Dy()
|
||||
|
||||
for y := 0; y < h; y++ {
|
||||
if size >= w {
|
||||
var r, g, b, a float64
|
||||
for x := 0; x < w; x++ {
|
||||
col := img.RGBAAt(x, y)
|
||||
r += float64(col.R)
|
||||
g += float64(col.G)
|
||||
b += float64(col.B)
|
||||
a += float64(col.A)
|
||||
}
|
||||
|
||||
factor := 1.0 / float64(w)
|
||||
col := color.RGBA{
|
||||
R: uint8(math.Round(r * factor)),
|
||||
G: uint8(math.Round(g * factor)),
|
||||
B: uint8(math.Round(b * factor)),
|
||||
A: uint8(math.Round(a * factor)),
|
||||
}
|
||||
for x := 0; x < w; x++ {
|
||||
result.SetRGBA(x, y, col)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
var r, g, b, a float64
|
||||
for x := 0; x <= size; x++ {
|
||||
col := img.RGBAAt(x, y)
|
||||
r += float64(col.R)
|
||||
g += float64(col.G)
|
||||
b += float64(col.B)
|
||||
a += float64(col.A)
|
||||
}
|
||||
|
||||
samples := size + 1
|
||||
x := 0
|
||||
for {
|
||||
factor := 1.0 / float64(samples)
|
||||
col := color.RGBA{
|
||||
R: uint8(math.Round(r * factor)),
|
||||
G: uint8(math.Round(g * factor)),
|
||||
B: uint8(math.Round(b * factor)),
|
||||
A: uint8(math.Round(a * factor)),
|
||||
}
|
||||
result.SetRGBA(x, y, col)
|
||||
|
||||
if x >= w-1 {
|
||||
break
|
||||
}
|
||||
|
||||
if left := x - size; left >= 0 {
|
||||
col = img.RGBAAt(left, y)
|
||||
r -= float64(col.R)
|
||||
g -= float64(col.G)
|
||||
b -= float64(col.B)
|
||||
a -= float64(col.A)
|
||||
samples--
|
||||
}
|
||||
|
||||
x++
|
||||
|
||||
if right := x + size; right < w {
|
||||
col = img.RGBAAt(right, y)
|
||||
r += float64(col.R)
|
||||
g += float64(col.G)
|
||||
b += float64(col.B)
|
||||
a += float64(col.A)
|
||||
samples++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func box3y(img *image.RGBA, size int) *image.RGBA {
|
||||
bounds := img.Bounds()
|
||||
result := image.NewRGBA(bounds)
|
||||
w, h := bounds.Dx(), bounds.Dy()
|
||||
|
||||
for x := 0; x < w; x++ {
|
||||
if size >= h {
|
||||
var r, g, b, a float64
|
||||
for y := 0; y < h; y++ {
|
||||
col := img.RGBAAt(x, y)
|
||||
r += float64(col.R)
|
||||
g += float64(col.G)
|
||||
b += float64(col.B)
|
||||
a += float64(col.A)
|
||||
}
|
||||
|
||||
factor := 1.0 / float64(h)
|
||||
col := color.RGBA{
|
||||
R: uint8(math.Round(r * factor)),
|
||||
G: uint8(math.Round(g * factor)),
|
||||
B: uint8(math.Round(b * factor)),
|
||||
A: uint8(math.Round(a * factor)),
|
||||
}
|
||||
for y := 0; y < h; y++ {
|
||||
result.SetRGBA(x, y, col)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
var r, g, b, a float64
|
||||
for y := 0; y <= size; y++ {
|
||||
col := img.RGBAAt(x, y)
|
||||
r += float64(col.R)
|
||||
g += float64(col.G)
|
||||
b += float64(col.B)
|
||||
a += float64(col.A)
|
||||
}
|
||||
|
||||
samples := size + 1
|
||||
y := 0
|
||||
for {
|
||||
factor := 1.0 / float64(samples)
|
||||
col := color.RGBA{
|
||||
R: uint8(math.Round(r * factor)),
|
||||
G: uint8(math.Round(g * factor)),
|
||||
B: uint8(math.Round(b * factor)),
|
||||
A: uint8(math.Round(a * factor)),
|
||||
}
|
||||
result.SetRGBA(x, y, col)
|
||||
|
||||
if y >= h-1 {
|
||||
break
|
||||
}
|
||||
|
||||
if top := y - size; top >= 0 {
|
||||
col = img.RGBAAt(x, top)
|
||||
r -= float64(col.R)
|
||||
g -= float64(col.G)
|
||||
b -= float64(col.B)
|
||||
a -= float64(col.A)
|
||||
samples--
|
||||
}
|
||||
|
||||
y++
|
||||
|
||||
if bottom := y + size; bottom < h {
|
||||
col = img.RGBAAt(x, bottom)
|
||||
r += float64(col.R)
|
||||
g += float64(col.G)
|
||||
b += float64(col.B)
|
||||
a += float64(col.A)
|
||||
samples++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
82
backend/softwarebackend/color.go
Normal file
|
@ -0,0 +1,82 @@
|
|||
package softwarebackend
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"math"
|
||||
)
|
||||
|
||||
func toRGBA(src color.Color) color.RGBA {
|
||||
ir, ig, ib, ia := src.RGBA()
|
||||
return color.RGBA{
|
||||
R: uint8(ir >> 8),
|
||||
G: uint8(ig >> 8),
|
||||
B: uint8(ib >> 8),
|
||||
A: uint8(ia >> 8),
|
||||
}
|
||||
}
|
||||
|
||||
func mix(src, dest color.Color) color.RGBA {
|
||||
ir1, ig1, ib1, ia1 := src.RGBA()
|
||||
r1 := float64(ir1) / 65535.0
|
||||
g1 := float64(ig1) / 65535.0
|
||||
b1 := float64(ib1) / 65535.0
|
||||
a1 := float64(ia1) / 65535.0
|
||||
|
||||
ir2, ig2, ib2, ia2 := dest.RGBA()
|
||||
r2 := float64(ir2) / 65535.0
|
||||
g2 := float64(ig2) / 65535.0
|
||||
b2 := float64(ib2) / 65535.0
|
||||
a2 := float64(ia2) / 65535.0
|
||||
|
||||
r := (r1-r2)*a1 + r2
|
||||
g := (g1-g2)*a1 + g2
|
||||
b := (b1-b2)*a1 + b2
|
||||
a := math.Max((a1-a2)*a1+a2, a2)
|
||||
|
||||
return color.RGBA{
|
||||
R: uint8(math.Round(r * 255.0)),
|
||||
G: uint8(math.Round(g * 255.0)),
|
||||
B: uint8(math.Round(b * 255.0)),
|
||||
A: uint8(math.Round(a * 255.0)),
|
||||
}
|
||||
}
|
||||
|
||||
func alphaColor(col color.Color, alpha color.Alpha) color.RGBA {
|
||||
ir, ig, ib, _ := col.RGBA()
|
||||
a2 := float64(alpha.A) / 255.0
|
||||
r := float64(ir) * a2 / 65535.0
|
||||
g := float64(ig) * a2 / 65535.0
|
||||
b := float64(ib) * a2 / 65535.0
|
||||
return color.RGBA{
|
||||
R: uint8(r * 255.0),
|
||||
G: uint8(g * 255.0),
|
||||
B: uint8(b * 255.0),
|
||||
A: 255,
|
||||
}
|
||||
}
|
||||
|
||||
func lerp(col1, col2 color.Color, ratio float64) color.RGBA {
|
||||
ir1, ig1, ib1, ia1 := col1.RGBA()
|
||||
r1 := float64(ir1) / 65535.0
|
||||
g1 := float64(ig1) / 65535.0
|
||||
b1 := float64(ib1) / 65535.0
|
||||
a1 := float64(ia1) / 65535.0
|
||||
|
||||
ir2, ig2, ib2, ia2 := col2.RGBA()
|
||||
r2 := float64(ir2) / 65535.0
|
||||
g2 := float64(ig2) / 65535.0
|
||||
b2 := float64(ib2) / 65535.0
|
||||
a2 := float64(ia2) / 65535.0
|
||||
|
||||
r := (r1-r2)*ratio + r2
|
||||
g := (g1-g2)*ratio + g2
|
||||
b := (b1-b2)*ratio + b2
|
||||
a := (a1-a2)*ratio + a2
|
||||
|
||||
return color.RGBA{
|
||||
R: uint8(math.Round(r * 255.0)),
|
||||
G: uint8(math.Round(g * 255.0)),
|
||||
B: uint8(math.Round(b * 255.0)),
|
||||
A: uint8(math.Round(a * 255.0)),
|
||||
}
|
||||
}
|
178
backend/softwarebackend/fill.go
Normal file
|
@ -0,0 +1,178 @@
|
|||
package softwarebackend
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
"math"
|
||||
|
||||
"git.mstar.dev/mstar/canvas/backend/backendbase"
|
||||
)
|
||||
|
||||
func (b *SoftwareBackend) Clear(pts [4]backendbase.Vec) {
|
||||
iterateTriangles(pts[:], func(tri []backendbase.Vec) {
|
||||
b.fillTriangleNoAA(tri, func(x, y int) {
|
||||
if b.clip.AlphaAt(x, y).A == 0 {
|
||||
return
|
||||
}
|
||||
b.Image.SetRGBA(x, y, color.RGBA{})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func (b *SoftwareBackend) Fill(
|
||||
style *backendbase.FillStyle,
|
||||
pts []backendbase.Vec,
|
||||
tf backendbase.Mat,
|
||||
canOverlap bool,
|
||||
) {
|
||||
ffn := fillFunc(style)
|
||||
|
||||
var triBuf [500]backendbase.Vec
|
||||
if tf != backendbase.MatIdentity {
|
||||
ptsOld := pts
|
||||
if len(pts) < len(triBuf) {
|
||||
pts = triBuf[:len(pts)]
|
||||
} else {
|
||||
pts = make([]backendbase.Vec, len(pts))
|
||||
}
|
||||
for i, pt := range ptsOld {
|
||||
pts[i] = pt.MulMat(tf)
|
||||
}
|
||||
}
|
||||
|
||||
if style.Blur > 0 {
|
||||
b.activateBlurTarget()
|
||||
b.fillTriangles(pts, ffn)
|
||||
b.drawBlurred(style.Blur)
|
||||
} else {
|
||||
b.fillTriangles(pts, ffn)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *SoftwareBackend) FillImageMask(
|
||||
style *backendbase.FillStyle,
|
||||
mask *image.Alpha,
|
||||
pts [4]backendbase.Vec,
|
||||
) {
|
||||
ffn := fillFunc(style)
|
||||
|
||||
mw := float64(mask.Bounds().Dx())
|
||||
mh := float64(mask.Bounds().Dy())
|
||||
b.fillQuad(pts, func(x, y, sx2, sy2 float64) color.RGBA {
|
||||
sxi := int(mw * sx2)
|
||||
syi := int(mh * sy2)
|
||||
a := mask.AlphaAt(sxi, syi)
|
||||
if a.A == 0 {
|
||||
return color.RGBA{}
|
||||
}
|
||||
col := ffn(x, y)
|
||||
return alphaColor(col, a)
|
||||
})
|
||||
}
|
||||
|
||||
func fillFunc(style *backendbase.FillStyle) func(x, y float64) color.RGBA {
|
||||
if lg := style.LinearGradient; lg != nil {
|
||||
lg := lg.(*LinearGradient)
|
||||
from := backendbase.Vec{style.Gradient.X0, style.Gradient.Y0}
|
||||
dir := backendbase.Vec{
|
||||
style.Gradient.X1 - style.Gradient.X0,
|
||||
style.Gradient.Y1 - style.Gradient.Y0,
|
||||
}
|
||||
dirlen := math.Sqrt(dir[0]*dir[0] + dir[1]*dir[1])
|
||||
dir[0] /= dirlen
|
||||
dir[1] /= dirlen
|
||||
return func(x, y float64) color.RGBA {
|
||||
pos := backendbase.Vec{x - from[0], y - from[1]}
|
||||
r := (pos[0]*dir[0] + pos[1]*dir[1]) / dirlen
|
||||
return lg.data.ColorAt(r)
|
||||
}
|
||||
} else if rg := style.RadialGradient; rg != nil {
|
||||
rg := rg.(*RadialGradient)
|
||||
from := backendbase.Vec{style.Gradient.X0, style.Gradient.Y0}
|
||||
to := backendbase.Vec{style.Gradient.X1, style.Gradient.Y1}
|
||||
radFrom := style.Gradient.RadFrom
|
||||
radTo := style.Gradient.RadTo
|
||||
return func(x, y float64) color.RGBA {
|
||||
pos := backendbase.Vec{x, y}
|
||||
oa := 0.5 * math.Sqrt(
|
||||
math.Pow(-2.0*from[0]*from[0]+2.0*from[0]*to[0]+2.0*from[0]*pos[0]-2.0*to[0]*pos[0]-2.0*from[1]*from[1]+2.0*from[1]*to[1]+2.0*from[1]*pos[1]-2.0*to[1]*pos[1]+2.0*radFrom*radFrom-2.0*radFrom*radTo, 2.0)-
|
||||
4.0*(from[0]*from[0]-2.0*from[0]*pos[0]+pos[0]*pos[0]+from[1]*from[1]-2.0*from[1]*pos[1]+pos[1]*pos[1]-radFrom*radFrom)*
|
||||
(from[0]*from[0]-2.0*from[0]*to[0]+to[0]*to[0]+from[1]*from[1]-2.0*from[1]*to[1]+to[1]*to[1]-radFrom*radFrom+2.0*radFrom*radTo-radTo*radTo))
|
||||
ob := (from[0]*from[0] - from[0]*to[0] - from[0]*pos[0] + to[0]*pos[0] + from[1]*from[1] - from[1]*to[1] - from[1]*pos[1] + to[1]*pos[1] - radFrom*radFrom + radFrom*radTo)
|
||||
oc := (from[0]*from[0] - 2.0*from[0]*to[0] + to[0]*to[0] + from[1]*from[1] - 2.0*from[1]*to[1] + to[1]*to[1] - radFrom*radFrom + 2.0*radFrom*radTo - radTo*radTo)
|
||||
o1 := (-oa + ob) / oc
|
||||
o2 := (oa + ob) / oc
|
||||
if math.IsNaN(o1) && math.IsNaN(o2) {
|
||||
return color.RGBA{}
|
||||
}
|
||||
o := math.Max(o1, o2)
|
||||
return rg.data.ColorAt(o)
|
||||
}
|
||||
} else if ip := style.ImagePattern; ip != nil {
|
||||
ip := ip.(*ImagePattern)
|
||||
img := ip.data.Image.(*Image)
|
||||
mip := img.mips[0] // todo select the right mip size
|
||||
w, h := img.Size()
|
||||
fw, fh := float64(w), float64(h)
|
||||
rx := ip.data.Repeat == backendbase.Repeat || ip.data.Repeat == backendbase.RepeatX
|
||||
ry := ip.data.Repeat == backendbase.Repeat || ip.data.Repeat == backendbase.RepeatY
|
||||
return func(x, y float64) color.RGBA {
|
||||
pos := backendbase.Vec{x, y}
|
||||
tfptx := pos[0]*ip.data.Transform[0] + pos[1]*ip.data.Transform[1] + ip.data.Transform[2]
|
||||
tfpty := pos[0]*ip.data.Transform[3] + pos[1]*ip.data.Transform[4] + ip.data.Transform[5]
|
||||
|
||||
if !rx && (tfptx < 0 || tfptx >= fw) {
|
||||
return color.RGBA{}
|
||||
}
|
||||
if !ry && (tfpty < 0 || tfpty >= fh) {
|
||||
return color.RGBA{}
|
||||
}
|
||||
|
||||
mx := int(math.Floor(tfptx)) % w
|
||||
if mx < 0 {
|
||||
mx += w
|
||||
}
|
||||
my := int(math.Floor(tfpty)) % h
|
||||
if my < 0 {
|
||||
my += h
|
||||
}
|
||||
|
||||
return toRGBA(mip.At(mx, my))
|
||||
}
|
||||
}
|
||||
return func(x, y float64) color.RGBA {
|
||||
return style.Color
|
||||
}
|
||||
}
|
||||
|
||||
func (b *SoftwareBackend) clearStencil() {
|
||||
p := b.stencil.Pix
|
||||
for i := range p {
|
||||
p[i] = 0
|
||||
}
|
||||
}
|
||||
|
||||
func (b *SoftwareBackend) ClearClip() {
|
||||
p := b.clip.Pix
|
||||
for i := range p {
|
||||
p[i] = 255
|
||||
}
|
||||
}
|
||||
|
||||
func (b *SoftwareBackend) Clip(pts []backendbase.Vec) {
|
||||
b.clearStencil()
|
||||
|
||||
iterateTriangles(pts[:], func(tri []backendbase.Vec) {
|
||||
b.fillTriangleNoAA(tri, func(x, y int) {
|
||||
b.stencil.SetAlpha(x, y, color.Alpha{A: 255})
|
||||
})
|
||||
})
|
||||
|
||||
p := b.clip.Pix
|
||||
p2 := b.stencil.Pix
|
||||
for i := range p {
|
||||
if p2[i] == 0 {
|
||||
p[i] = 0
|
||||
}
|
||||
}
|
||||
}
|
147
backend/softwarebackend/images.go
Normal file
|
@ -0,0 +1,147 @@
|
|||
package softwarebackend
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
"math"
|
||||
|
||||
"git.mstar.dev/mstar/canvas/backend/backendbase"
|
||||
)
|
||||
|
||||
type Image struct {
|
||||
mips []image.Image
|
||||
deleted bool
|
||||
}
|
||||
|
||||
func (b *SoftwareBackend) LoadImage(img image.Image) (backendbase.Image, error) {
|
||||
bimg := &Image{mips: make([]image.Image, 1, 10)}
|
||||
bimg.Replace(img)
|
||||
return bimg, nil
|
||||
}
|
||||
|
||||
func halveImage(img image.Image) (*image.RGBA, int, int) {
|
||||
bounds := img.Bounds()
|
||||
w, h := bounds.Dx(), bounds.Dy()
|
||||
w = w / 2
|
||||
h = h / 2
|
||||
rimg := image.NewRGBA(image.Rect(0, 0, w, h))
|
||||
for y := 0; y < h; y++ {
|
||||
sy := y * 2
|
||||
for x := 0; x < w; x++ {
|
||||
sx := x * 2
|
||||
r1, g1, b1, a1 := img.At(sx, sy).RGBA()
|
||||
r2, g2, b2, a2 := img.At(sx+1, sy).RGBA()
|
||||
r3, g3, b3, a3 := img.At(sx, sy+1).RGBA()
|
||||
r4, g4, b4, a4 := img.At(sx+1, sy+1).RGBA()
|
||||
mixr := uint8((int(r1) + int(r2) + int(r3) + int(r4)) / 1024)
|
||||
mixg := uint8((int(g1) + int(g2) + int(g3) + int(g4)) / 1024)
|
||||
mixb := uint8((int(b1) + int(b2) + int(b3) + int(b4)) / 1024)
|
||||
mixa := uint8((int(a1) + int(a2) + int(a3) + int(a4)) / 1024)
|
||||
rimg.Set(x, y, color.RGBA{R: mixr, G: mixg, B: mixb, A: mixa})
|
||||
}
|
||||
}
|
||||
return rimg, w, h
|
||||
}
|
||||
|
||||
func (b *SoftwareBackend) DrawImage(
|
||||
dimg backendbase.Image,
|
||||
sx, sy, sw, sh float64,
|
||||
pts [4]backendbase.Vec,
|
||||
alpha float64,
|
||||
) {
|
||||
simg := dimg.(*Image)
|
||||
if simg.deleted {
|
||||
return
|
||||
}
|
||||
|
||||
bounds := simg.mips[0].Bounds()
|
||||
w, h := bounds.Dx(), bounds.Dy()
|
||||
|
||||
factor := float64(w*h) / (sw * sh)
|
||||
area := quadArea(pts) * factor
|
||||
mip := simg.mips[0]
|
||||
closest := math.MaxFloat64
|
||||
mipW, mipH := w, h
|
||||
for _, img := range simg.mips {
|
||||
bounds := img.Bounds()
|
||||
w, h := bounds.Dx(), bounds.Dy()
|
||||
dist := math.Abs(float64(w*h) - area)
|
||||
if dist < closest {
|
||||
closest = dist
|
||||
mip = img
|
||||
mipW = w
|
||||
mipH = h
|
||||
}
|
||||
}
|
||||
|
||||
mipScaleX := float64(mipW) / float64(w)
|
||||
mipScaleY := float64(mipH) / float64(h)
|
||||
sx *= mipScaleX
|
||||
sy *= mipScaleY
|
||||
sw *= mipScaleX
|
||||
sh *= mipScaleY
|
||||
|
||||
b.fillQuad(pts, func(x, y, tx, ty float64) color.RGBA {
|
||||
imgx := sx + sw*tx
|
||||
imgy := sy + sh*ty
|
||||
imgxf := math.Floor(imgx)
|
||||
imgyf := math.Floor(imgy)
|
||||
return toRGBA(mip.At(int(imgxf), int(imgyf)))
|
||||
|
||||
// rx := imgx - imgxf
|
||||
// ry := imgy - imgyf
|
||||
// ca := mip.At(int(imgxf), int(imgyf))
|
||||
// cb := mip.At(int(imgxf+1), int(imgyf))
|
||||
// cc := mip.At(int(imgxf), int(imgyf+1))
|
||||
// cd := mip.At(int(imgxf+1), int(imgyf+1))
|
||||
// ctop := lerp(ca, cb, rx)
|
||||
// cbtm := lerp(cc, cd, rx)
|
||||
// b.Image.Set(x, y, lerp(ctop, cbtm, ry))
|
||||
})
|
||||
}
|
||||
|
||||
func (img *Image) Width() int {
|
||||
return img.mips[0].Bounds().Dx()
|
||||
}
|
||||
|
||||
func (img *Image) Height() int {
|
||||
return img.mips[0].Bounds().Dy()
|
||||
}
|
||||
|
||||
func (img *Image) Size() (w, h int) {
|
||||
b := img.mips[0].Bounds()
|
||||
return b.Dx(), b.Dy()
|
||||
}
|
||||
|
||||
func (img *Image) Delete() {
|
||||
img.deleted = true
|
||||
}
|
||||
|
||||
func (img *Image) Replace(src image.Image) error {
|
||||
img.mips = img.mips[:1]
|
||||
img.mips[0] = src
|
||||
|
||||
bounds := src.Bounds()
|
||||
w, h := bounds.Dx(), bounds.Dy()
|
||||
for w > 1 && h > 1 {
|
||||
src, w, h = halveImage(src)
|
||||
img.mips = append(img.mips, src)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type ImagePattern struct {
|
||||
data backendbase.ImagePatternData
|
||||
}
|
||||
|
||||
func (b *SoftwareBackend) LoadImagePattern(
|
||||
data backendbase.ImagePatternData,
|
||||
) backendbase.ImagePattern {
|
||||
return &ImagePattern{
|
||||
data: data,
|
||||
}
|
||||
}
|
||||
|
||||
func (ip *ImagePattern) Delete() {}
|
||||
func (ip *ImagePattern) Replace(data backendbase.ImagePatternData) { ip.data = data }
|
83
backend/softwarebackend/software.go
Normal file
|
@ -0,0 +1,83 @@
|
|||
package softwarebackend
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/draw"
|
||||
|
||||
"git.mstar.dev/mstar/canvas/backend/backendbase"
|
||||
)
|
||||
|
||||
type SoftwareBackend struct {
|
||||
Image *image.RGBA
|
||||
|
||||
MSAA int
|
||||
|
||||
blurSwap *image.RGBA
|
||||
|
||||
clip *image.Alpha
|
||||
stencil *image.Alpha
|
||||
w, h int
|
||||
}
|
||||
|
||||
func New(w, h int) *SoftwareBackend {
|
||||
b := &SoftwareBackend{}
|
||||
b.SetSize(w, h)
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *SoftwareBackend) SetSize(w, h int) {
|
||||
b.w, b.h = w, h
|
||||
b.Image = image.NewRGBA(image.Rect(0, 0, w, h))
|
||||
b.clip = image.NewAlpha(image.Rect(0, 0, w, h))
|
||||
b.stencil = image.NewAlpha(image.Rect(0, 0, w, h))
|
||||
b.ClearClip()
|
||||
}
|
||||
|
||||
func (b *SoftwareBackend) Size() (int, int) {
|
||||
return b.w, b.h
|
||||
}
|
||||
|
||||
func (b *SoftwareBackend) GetImageData(x, y, w, h int) *image.RGBA {
|
||||
return b.Image.SubImage(image.Rect(x, y, w, h)).(*image.RGBA)
|
||||
}
|
||||
|
||||
func (b *SoftwareBackend) PutImageData(img *image.RGBA, x, y int) {
|
||||
draw.Draw(b.Image, image.Rect(x, y, img.Rect.Dx(), img.Rect.Dy()), img, image.ZP, draw.Src)
|
||||
}
|
||||
|
||||
func (b *SoftwareBackend) CanUseAsImage(b2 backendbase.Backend) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (b *SoftwareBackend) AsImage() backendbase.Image {
|
||||
return nil
|
||||
}
|
||||
|
||||
type LinearGradient struct {
|
||||
data backendbase.Gradient
|
||||
}
|
||||
type RadialGradient struct {
|
||||
data backendbase.Gradient
|
||||
}
|
||||
|
||||
func (b *SoftwareBackend) LoadLinearGradient(data backendbase.Gradient) backendbase.LinearGradient {
|
||||
return &LinearGradient{data: data}
|
||||
}
|
||||
|
||||
func (b *SoftwareBackend) LoadRadialGradient(data backendbase.Gradient) backendbase.RadialGradient {
|
||||
return &RadialGradient{data: data}
|
||||
}
|
||||
|
||||
func (g *LinearGradient) Delete() {
|
||||
}
|
||||
|
||||
func (g *LinearGradient) Replace(data backendbase.Gradient) {
|
||||
g.data = data
|
||||
}
|
||||
|
||||
func (g *RadialGradient) Delete() {
|
||||
}
|
||||
|
||||
func (g *RadialGradient) Replace(data backendbase.Gradient) {
|
||||
g.data = data
|
||||
}
|
614
backend/softwarebackend/triangles.go
Normal file
|
@ -0,0 +1,614 @@
|
|||
package softwarebackend
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"math"
|
||||
|
||||
"git.mstar.dev/mstar/canvas/backend/backendbase"
|
||||
)
|
||||
|
||||
func triangleLR(tri []backendbase.Vec, y float64) (l, r float64, outside bool) {
|
||||
a, b, c := tri[0], tri[1], tri[2]
|
||||
|
||||
// sort by y
|
||||
if a[1] > b[1] {
|
||||
a, b = b, a
|
||||
}
|
||||
if b[1] > c[1] {
|
||||
b, c = c, b
|
||||
if a[1] > b[1] {
|
||||
a, b = b, a
|
||||
}
|
||||
}
|
||||
|
||||
// check general bounds
|
||||
if y <= a[1] {
|
||||
return a[0], a[0], true
|
||||
}
|
||||
if y > c[1] {
|
||||
return c[0], c[0], true
|
||||
}
|
||||
|
||||
// find left and right x at y
|
||||
if y >= a[1] && y <= b[1] && a[1] < b[1] {
|
||||
r0 := (y - a[1]) / (b[1] - a[1])
|
||||
l = (b[0]-a[0])*r0 + a[0]
|
||||
r1 := (y - a[1]) / (c[1] - a[1])
|
||||
r = (c[0]-a[0])*r1 + a[0]
|
||||
} else {
|
||||
r0 := (y - b[1]) / (c[1] - b[1])
|
||||
l = (c[0]-b[0])*r0 + b[0]
|
||||
r1 := (y - a[1]) / (c[1] - a[1])
|
||||
r = (c[0]-a[0])*r1 + a[0]
|
||||
}
|
||||
if l > r {
|
||||
l, r = r, l
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (b *SoftwareBackend) fillTriangleNoAA(tri []backendbase.Vec, fn func(x, y int)) {
|
||||
minY := int(math.Floor(math.Min(math.Min(tri[0][1], tri[1][1]), tri[2][1])))
|
||||
maxY := int(math.Ceil(math.Max(math.Max(tri[0][1], tri[1][1]), tri[2][1])))
|
||||
if minY < 0 {
|
||||
minY = 0
|
||||
} else if minY >= b.h {
|
||||
return
|
||||
}
|
||||
if maxY < 0 {
|
||||
return
|
||||
} else if maxY >= b.h {
|
||||
maxY = b.h - 1
|
||||
}
|
||||
for y := minY; y <= maxY; y++ {
|
||||
l, r, out := triangleLR(tri, float64(y)+0.5)
|
||||
if out {
|
||||
continue
|
||||
}
|
||||
if l < 0 {
|
||||
l = 0
|
||||
} else if l > float64(b.w) {
|
||||
continue
|
||||
}
|
||||
if r < 0 {
|
||||
continue
|
||||
} else if r > float64(b.w) {
|
||||
r = float64(b.w)
|
||||
}
|
||||
if l >= r {
|
||||
continue
|
||||
}
|
||||
fl, cr := int(math.Floor(l)), int(math.Ceil(r))
|
||||
for x := fl; x <= cr; x++ {
|
||||
fx := float64(x) + 0.5
|
||||
if fx < l || fx >= r {
|
||||
continue
|
||||
}
|
||||
fn(x, y)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type msaaPixel struct {
|
||||
ix, iy int
|
||||
fx, fy float64
|
||||
tx, ty float64
|
||||
}
|
||||
|
||||
func (b *SoftwareBackend) fillTriangleMSAA(
|
||||
tri []backendbase.Vec,
|
||||
msaaLevel int,
|
||||
msaaPixels []msaaPixel,
|
||||
fn func(x, y int),
|
||||
) []msaaPixel {
|
||||
msaaStep := 1.0 / float64(msaaLevel+1)
|
||||
|
||||
minY := int(math.Floor(math.Min(math.Min(tri[0][1], tri[1][1]), tri[2][1])))
|
||||
maxY := int(math.Ceil(math.Max(math.Max(tri[0][1], tri[1][1]), tri[2][1])))
|
||||
if minY < 0 {
|
||||
minY = 0
|
||||
} else if minY >= b.h {
|
||||
return msaaPixels
|
||||
}
|
||||
if maxY < 0 {
|
||||
return msaaPixels
|
||||
} else if maxY >= b.h {
|
||||
maxY = b.h - 1
|
||||
}
|
||||
|
||||
for y := minY; y <= maxY; y++ {
|
||||
var l, r [5]float64
|
||||
allOut := true
|
||||
minL, maxR := math.MaxFloat64, 0.0
|
||||
|
||||
sy := float64(y) + msaaStep*0.5
|
||||
for step := 0; step <= msaaLevel; step++ {
|
||||
var out bool
|
||||
l[step], r[step], out = triangleLR(tri, sy)
|
||||
if l[step] < 0 {
|
||||
l[step] = 0
|
||||
} else if l[step] > float64(b.w) {
|
||||
l[step] = float64(b.w)
|
||||
out = true
|
||||
}
|
||||
if r[step] < 0 {
|
||||
r[step] = 0
|
||||
out = true
|
||||
} else if r[step] > float64(b.w) {
|
||||
r[step] = float64(b.w)
|
||||
}
|
||||
if r[step] <= l[step] {
|
||||
out = true
|
||||
}
|
||||
if !out {
|
||||
allOut = false
|
||||
minL = math.Min(minL, l[step])
|
||||
maxR = math.Max(maxR, r[step])
|
||||
}
|
||||
sy += msaaStep
|
||||
}
|
||||
|
||||
if allOut {
|
||||
continue
|
||||
}
|
||||
|
||||
fl, cr := int(math.Floor(minL)), int(math.Ceil(maxR))
|
||||
for x := fl; x <= cr; x++ {
|
||||
sy = float64(y) + msaaStep*0.5
|
||||
allIn := true
|
||||
check:
|
||||
for stepy := 0; stepy <= msaaLevel; stepy++ {
|
||||
sx := float64(x) + msaaStep*0.5
|
||||
for stepx := 0; stepx <= msaaLevel; stepx++ {
|
||||
if sx < l[stepy] || sx >= r[stepy] {
|
||||
allIn = false
|
||||
break check
|
||||
}
|
||||
sx += msaaStep
|
||||
}
|
||||
sy += msaaStep
|
||||
}
|
||||
|
||||
if allIn {
|
||||
fn(x, y)
|
||||
continue
|
||||
}
|
||||
|
||||
sy = float64(y) + msaaStep*0.5
|
||||
for stepy := 0; stepy <= msaaLevel; stepy++ {
|
||||
sx := float64(x) + msaaStep*0.5
|
||||
for stepx := 0; stepx <= msaaLevel; stepx++ {
|
||||
if sx >= l[stepy] && sx < r[stepy] {
|
||||
msaaPixels = addMSAAPixel(
|
||||
msaaPixels,
|
||||
msaaPixel{ix: x, iy: y, fx: sx, fy: sy},
|
||||
)
|
||||
}
|
||||
sx += msaaStep
|
||||
}
|
||||
sy += msaaStep
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return msaaPixels
|
||||
}
|
||||
|
||||
func addMSAAPixel(msaaPixels []msaaPixel, px msaaPixel) []msaaPixel {
|
||||
for _, px2 := range msaaPixels {
|
||||
if px == px2 {
|
||||
return msaaPixels
|
||||
}
|
||||
}
|
||||
return append(msaaPixels, px)
|
||||
}
|
||||
|
||||
func quadArea(quad [4]backendbase.Vec) float64 {
|
||||
leftv := backendbase.Vec{quad[1][0] - quad[0][0], quad[1][1] - quad[0][1]}
|
||||
topv := backendbase.Vec{quad[3][0] - quad[0][0], quad[3][1] - quad[0][1]}
|
||||
return math.Abs(leftv[0]*topv[1] - leftv[1]*topv[0])
|
||||
}
|
||||
|
||||
func (b *SoftwareBackend) fillQuadNoAA(quad [4]backendbase.Vec, fn func(x, y int, tx, ty float64)) {
|
||||
minY := int(
|
||||
math.Floor(math.Min(math.Min(quad[0][1], quad[1][1]), math.Min(quad[2][1], quad[3][1]))),
|
||||
)
|
||||
maxY := int(
|
||||
math.Ceil(math.Max(math.Max(quad[0][1], quad[1][1]), math.Max(quad[2][1], quad[3][1]))),
|
||||
)
|
||||
if minY < 0 {
|
||||
minY = 0
|
||||
} else if minY >= b.h {
|
||||
return
|
||||
}
|
||||
if maxY < 0 {
|
||||
return
|
||||
} else if maxY >= b.h {
|
||||
maxY = b.h - 1
|
||||
}
|
||||
|
||||
leftv := backendbase.Vec{quad[1][0] - quad[0][0], quad[1][1] - quad[0][1]}
|
||||
leftLen := math.Sqrt(leftv[0]*leftv[0] + leftv[1]*leftv[1])
|
||||
leftv[0] /= leftLen
|
||||
leftv[1] /= leftLen
|
||||
topv := backendbase.Vec{quad[3][0] - quad[0][0], quad[3][1] - quad[0][1]}
|
||||
topLen := math.Sqrt(topv[0]*topv[0] + topv[1]*topv[1])
|
||||
topv[0] /= topLen
|
||||
topv[1] /= topLen
|
||||
|
||||
tri1 := [3]backendbase.Vec{quad[0], quad[1], quad[2]}
|
||||
tri2 := [3]backendbase.Vec{quad[0], quad[2], quad[3]}
|
||||
for y := minY; y <= maxY; y++ {
|
||||
lf1, rf1, out1 := triangleLR(tri1[:], float64(y)+0.5)
|
||||
lf2, rf2, out2 := triangleLR(tri2[:], float64(y)+0.5)
|
||||
if out1 && out2 {
|
||||
continue
|
||||
}
|
||||
l := math.Min(lf1, lf2)
|
||||
r := math.Max(rf1, rf2)
|
||||
if l < 0 {
|
||||
l = 0
|
||||
} else if l > float64(b.w) {
|
||||
continue
|
||||
}
|
||||
if r < 0 {
|
||||
continue
|
||||
} else if r > float64(b.w) {
|
||||
r = float64(b.w)
|
||||
}
|
||||
if l >= r {
|
||||
continue
|
||||
}
|
||||
|
||||
tfy := float64(y) + 0.5 - quad[0][1]
|
||||
fl, cr := int(math.Floor(l)), int(math.Ceil(r))
|
||||
for x := fl; x <= cr; x++ {
|
||||
fx := float64(x) + 0.5
|
||||
if fx < l || fx >= r {
|
||||
continue
|
||||
}
|
||||
tfx := fx - quad[0][0]
|
||||
|
||||
var tx, ty float64
|
||||
if math.Abs(leftv[0]) > math.Abs(leftv[1]) {
|
||||
tx = (tfy - tfx*(leftv[1]/leftv[0])) / (topv[1] - topv[0]*(leftv[1]/leftv[0]))
|
||||
ty = (tfx - topv[0]*tx) / leftv[0]
|
||||
} else {
|
||||
tx = (tfx - tfy*(leftv[0]/leftv[1])) / (topv[0] - topv[1]*(leftv[0]/leftv[1]))
|
||||
ty = (tfy - topv[1]*tx) / leftv[1]
|
||||
}
|
||||
|
||||
fn(x, y, tx/topLen, ty/leftLen)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *SoftwareBackend) fillQuadMSAA(
|
||||
quad [4]backendbase.Vec,
|
||||
msaaLevel int,
|
||||
msaaPixels []msaaPixel,
|
||||
fn func(x, y int, tx, ty float64),
|
||||
) []msaaPixel {
|
||||
msaaStep := 1.0 / float64(msaaLevel+1)
|
||||
|
||||
minY := int(
|
||||
math.Floor(math.Min(math.Min(quad[0][1], quad[1][1]), math.Min(quad[2][1], quad[3][1]))),
|
||||
)
|
||||
maxY := int(
|
||||
math.Ceil(math.Max(math.Max(quad[0][1], quad[1][1]), math.Max(quad[2][1], quad[3][1]))),
|
||||
)
|
||||
if minY < 0 {
|
||||
minY = 0
|
||||
} else if minY >= b.h {
|
||||
return msaaPixels
|
||||
}
|
||||
if maxY < 0 {
|
||||
return msaaPixels
|
||||
} else if maxY >= b.h {
|
||||
maxY = b.h - 1
|
||||
}
|
||||
|
||||
leftv := backendbase.Vec{quad[1][0] - quad[0][0], quad[1][1] - quad[0][1]}
|
||||
leftLen := math.Sqrt(leftv[0]*leftv[0] + leftv[1]*leftv[1])
|
||||
leftv[0] /= leftLen
|
||||
leftv[1] /= leftLen
|
||||
topv := backendbase.Vec{quad[3][0] - quad[0][0], quad[3][1] - quad[0][1]}
|
||||
topLen := math.Sqrt(topv[0]*topv[0] + topv[1]*topv[1])
|
||||
topv[0] /= topLen
|
||||
topv[1] /= topLen
|
||||
|
||||
tri1 := [3]backendbase.Vec{quad[0], quad[1], quad[2]}
|
||||
tri2 := [3]backendbase.Vec{quad[0], quad[2], quad[3]}
|
||||
for y := minY; y <= maxY; y++ {
|
||||
var l, r [5]float64
|
||||
allOut := true
|
||||
minL, maxR := math.MaxFloat64, 0.0
|
||||
|
||||
sy := float64(y) + msaaStep*0.5
|
||||
for step := 0; step <= msaaLevel; step++ {
|
||||
lf1, rf1, out1 := triangleLR(tri1[:], sy)
|
||||
lf2, rf2, out2 := triangleLR(tri2[:], sy)
|
||||
l[step] = math.Min(lf1, lf2)
|
||||
r[step] = math.Max(rf1, rf2)
|
||||
out := out1 || out2
|
||||
|
||||
if l[step] < 0 {
|
||||
l[step] = 0
|
||||
} else if l[step] > float64(b.w) {
|
||||
l[step] = float64(b.w)
|
||||
out = true
|
||||
}
|
||||
if r[step] < 0 {
|
||||
r[step] = 0
|
||||
out = true
|
||||
} else if r[step] > float64(b.w) {
|
||||
r[step] = float64(b.w)
|
||||
}
|
||||
if r[step] <= l[step] {
|
||||
out = true
|
||||
}
|
||||
if !out {
|
||||
allOut = false
|
||||
minL = math.Min(minL, l[step])
|
||||
maxR = math.Max(maxR, r[step])
|
||||
}
|
||||
sy += msaaStep
|
||||
}
|
||||
|
||||
if allOut {
|
||||
continue
|
||||
}
|
||||
|
||||
fl, cr := int(math.Floor(minL)), int(math.Ceil(maxR))
|
||||
for x := fl; x <= cr; x++ {
|
||||
sy = float64(y) + msaaStep*0.5
|
||||
allIn := true
|
||||
check:
|
||||
for stepy := 0; stepy <= msaaLevel; stepy++ {
|
||||
sx := float64(x) + msaaStep*0.5
|
||||
for stepx := 0; stepx <= msaaLevel; stepx++ {
|
||||
if sx < l[stepy] || sx >= r[stepy] {
|
||||
allIn = false
|
||||
break check
|
||||
}
|
||||
sx += msaaStep
|
||||
}
|
||||
sy += msaaStep
|
||||
}
|
||||
|
||||
if allIn {
|
||||
tfx := float64(x) + 0.5 - quad[0][0]
|
||||
tfy := float64(y) + 0.5 - quad[0][1]
|
||||
|
||||
var tx, ty float64
|
||||
if math.Abs(leftv[0]) > math.Abs(leftv[1]) {
|
||||
tx = (tfy - tfx*(leftv[1]/leftv[0])) / (topv[1] - topv[0]*(leftv[1]/leftv[0]))
|
||||
ty = (tfx - topv[0]*tx) / leftv[0]
|
||||
} else {
|
||||
tx = (tfx - tfy*(leftv[0]/leftv[1])) / (topv[0] - topv[1]*(leftv[0]/leftv[1]))
|
||||
ty = (tfy - topv[1]*tx) / leftv[1]
|
||||
}
|
||||
|
||||
fn(x, y, tx/topLen, ty/leftLen)
|
||||
continue
|
||||
}
|
||||
|
||||
sy = float64(y) + msaaStep*0.5
|
||||
for stepy := 0; stepy <= msaaLevel; stepy++ {
|
||||
sx := float64(x) + msaaStep*0.5
|
||||
for stepx := 0; stepx <= msaaLevel; stepx++ {
|
||||
if sx >= l[stepy] && sx < r[stepy] {
|
||||
tfx := sx - quad[0][0]
|
||||
tfy := sy - quad[0][1]
|
||||
|
||||
var tx, ty float64
|
||||
if math.Abs(leftv[0]) > math.Abs(leftv[1]) {
|
||||
tx = (tfy - tfx*(leftv[1]/leftv[0])) / (topv[1] - topv[0]*(leftv[1]/leftv[0]))
|
||||
ty = (tfx - topv[0]*tx) / leftv[0]
|
||||
} else {
|
||||
tx = (tfx - tfy*(leftv[0]/leftv[1])) / (topv[0] - topv[1]*(leftv[0]/leftv[1]))
|
||||
ty = (tfy - topv[1]*tx) / leftv[1]
|
||||
}
|
||||
|
||||
msaaPixels = addMSAAPixel(
|
||||
msaaPixels,
|
||||
msaaPixel{
|
||||
ix: x,
|
||||
iy: y,
|
||||
fx: sx,
|
||||
fy: sy,
|
||||
tx: tx / topLen,
|
||||
ty: ty / leftLen,
|
||||
},
|
||||
)
|
||||
}
|
||||
sx += msaaStep
|
||||
}
|
||||
sy += msaaStep
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return msaaPixels
|
||||
}
|
||||
|
||||
func (b *SoftwareBackend) fillQuad(
|
||||
pts [4]backendbase.Vec,
|
||||
fn func(x, y, tx, ty float64) color.RGBA,
|
||||
) {
|
||||
b.clearStencil()
|
||||
|
||||
if b.MSAA > 0 {
|
||||
var msaaPixelBuf [500]msaaPixel
|
||||
msaaPixels := msaaPixelBuf[:0]
|
||||
|
||||
msaaPixels = b.fillQuadMSAA(pts, b.MSAA, msaaPixels, func(x, y int, tx, ty float64) {
|
||||
if b.clip.AlphaAt(x, y).A == 0 {
|
||||
return
|
||||
}
|
||||
if b.stencil.AlphaAt(x, y).A > 0 {
|
||||
return
|
||||
}
|
||||
b.stencil.SetAlpha(x, y, color.Alpha{A: 255})
|
||||
col := fn(float64(x)+0.5, float64(y)+0.5, tx, ty)
|
||||
if col.A > 0 {
|
||||
b.Image.SetRGBA(x, y, mix(col, b.Image.RGBAAt(x, y)))
|
||||
}
|
||||
})
|
||||
|
||||
samples := (b.MSAA + 1) * (b.MSAA + 1)
|
||||
|
||||
for i, px := range msaaPixels {
|
||||
if px.ix < 0 || b.clip.AlphaAt(px.ix, px.iy).A == 0 ||
|
||||
b.stencil.AlphaAt(px.ix, px.iy).A > 0 {
|
||||
continue
|
||||
}
|
||||
b.stencil.SetAlpha(px.ix, px.iy, color.Alpha{A: 255})
|
||||
|
||||
var mr, mg, mb, ma int
|
||||
for j, px2 := range msaaPixels[i:] {
|
||||
if px2.ix != px.ix || px2.iy != px.iy {
|
||||
continue
|
||||
}
|
||||
|
||||
col := fn(px2.fx, px2.fy, px2.tx, px2.ty)
|
||||
mr += int(col.R)
|
||||
mg += int(col.G)
|
||||
mb += int(col.B)
|
||||
ma += int(col.A)
|
||||
|
||||
msaaPixels[i+j].ix = -1
|
||||
}
|
||||
|
||||
combined := color.RGBA{
|
||||
R: uint8(mr / samples),
|
||||
G: uint8(mg / samples),
|
||||
B: uint8(mb / samples),
|
||||
A: uint8(ma / samples),
|
||||
}
|
||||
b.Image.SetRGBA(px.ix, px.iy, mix(combined, b.Image.RGBAAt(px.ix, px.iy)))
|
||||
}
|
||||
|
||||
} else {
|
||||
b.fillQuadNoAA(pts, func(x, y int, tx, ty float64) {
|
||||
if b.clip.AlphaAt(x, y).A == 0 {
|
||||
return
|
||||
}
|
||||
if b.stencil.AlphaAt(x, y).A > 0 {
|
||||
return
|
||||
}
|
||||
b.stencil.SetAlpha(x, y, color.Alpha{A: 255})
|
||||
col := fn(float64(x)+0.5, float64(y)+0.5, tx, ty)
|
||||
if col.A > 0 {
|
||||
b.Image.SetRGBA(x, y, mix(col, b.Image.RGBAAt(x, y)))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func iterateTriangles(pts []backendbase.Vec, fn func(tri []backendbase.Vec)) {
|
||||
if len(pts) == 4 {
|
||||
var buf [3]backendbase.Vec
|
||||
buf[0] = pts[0]
|
||||
buf[1] = pts[1]
|
||||
buf[2] = pts[2]
|
||||
fn(buf[:])
|
||||
buf[1] = pts[2]
|
||||
buf[2] = pts[3]
|
||||
fn(buf[:])
|
||||
return
|
||||
}
|
||||
for i := 3; i <= len(pts); i += 3 {
|
||||
fn(pts[i-3 : i])
|
||||
}
|
||||
}
|
||||
|
||||
func (b *SoftwareBackend) fillTrianglesNoAA(
|
||||
pts []backendbase.Vec,
|
||||
fn func(x, y float64) color.RGBA,
|
||||
) {
|
||||
iterateTriangles(pts[:], func(tri []backendbase.Vec) {
|
||||
b.fillTriangleNoAA(tri, func(x, y int) {
|
||||
if b.clip.AlphaAt(x, y).A == 0 {
|
||||
return
|
||||
}
|
||||
if b.stencil.AlphaAt(x, y).A > 0 {
|
||||
return
|
||||
}
|
||||
b.stencil.SetAlpha(x, y, color.Alpha{A: 255})
|
||||
col := fn(float64(x), float64(y))
|
||||
if col.A > 0 {
|
||||
b.Image.SetRGBA(x, y, mix(col, b.Image.RGBAAt(x, y)))
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func (b *SoftwareBackend) fillTrianglesMSAA(
|
||||
pts []backendbase.Vec,
|
||||
msaaLevel int,
|
||||
fn func(x, y float64) color.RGBA,
|
||||
) {
|
||||
var msaaPixelBuf [500]msaaPixel
|
||||
msaaPixels := msaaPixelBuf[:0]
|
||||
|
||||
iterateTriangles(pts[:], func(tri []backendbase.Vec) {
|
||||
msaaPixels = b.fillTriangleMSAA(tri, msaaLevel, msaaPixels, func(x, y int) {
|
||||
if b.clip.AlphaAt(x, y).A == 0 {
|
||||
return
|
||||
}
|
||||
if b.stencil.AlphaAt(x, y).A > 0 {
|
||||
return
|
||||
}
|
||||
b.stencil.SetAlpha(x, y, color.Alpha{A: 255})
|
||||
col := fn(float64(x), float64(y))
|
||||
if col.A > 0 {
|
||||
b.Image.SetRGBA(x, y, mix(col, b.Image.RGBAAt(x, y)))
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
samples := (msaaLevel + 1) * (msaaLevel + 1)
|
||||
|
||||
for i, px := range msaaPixels {
|
||||
if px.ix < 0 || b.clip.AlphaAt(px.ix, px.iy).A == 0 ||
|
||||
b.stencil.AlphaAt(px.ix, px.iy).A > 0 {
|
||||
continue
|
||||
}
|
||||
b.stencil.SetAlpha(px.ix, px.iy, color.Alpha{A: 255})
|
||||
|
||||
var mr, mg, mb, ma int
|
||||
for j, px2 := range msaaPixels[i:] {
|
||||
if px2.ix != px.ix || px2.iy != px.iy {
|
||||
continue
|
||||
}
|
||||
|
||||
col := fn(px2.fx, px2.fy)
|
||||
mr += int(col.R)
|
||||
mg += int(col.G)
|
||||
mb += int(col.B)
|
||||
ma += int(col.A)
|
||||
|
||||
msaaPixels[i+j].ix = -1
|
||||
}
|
||||
|
||||
combined := color.RGBA{
|
||||
R: uint8(mr / samples),
|
||||
G: uint8(mg / samples),
|
||||
B: uint8(mb / samples),
|
||||
A: uint8(ma / samples),
|
||||
}
|
||||
b.Image.SetRGBA(px.ix, px.iy, mix(combined, b.Image.RGBAAt(px.ix, px.iy)))
|
||||
}
|
||||
}
|
||||
|
||||
func (b *SoftwareBackend) fillTriangles(pts []backendbase.Vec, fn func(x, y float64) color.RGBA) {
|
||||
b.clearStencil()
|
||||
|
||||
if b.MSAA > 0 {
|
||||
b.fillTrianglesMSAA(pts, b.MSAA, fn)
|
||||
} else {
|
||||
b.fillTrianglesNoAA(pts, fn)
|
||||
}
|
||||
}
|
771
canvas.go
|
@ -3,10 +3,16 @@
|
|||
package canvas
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"image"
|
||||
"image/color"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"github.com/golang/freetype/truetype"
|
||||
"golang.org/x/image/font"
|
||||
"golang.org/x/image/math/fixed"
|
||||
|
||||
"git.mstar.dev/mstar/canvas/backend/backendbase"
|
||||
)
|
||||
|
||||
//go:generate go run make_shaders.go
|
||||
|
@ -15,32 +21,35 @@ import (
|
|||
// Canvas represents an area on the viewport on which to draw
|
||||
// using a set of functions very similar to the HTML5 canvas
|
||||
type Canvas struct {
|
||||
x, y, w, h int
|
||||
fx, fy, fw, fh float64
|
||||
b backendbase.Backend
|
||||
|
||||
path []pathPoint
|
||||
convex bool
|
||||
rect bool
|
||||
path Path2D
|
||||
|
||||
state drawState
|
||||
stateStack []drawState
|
||||
|
||||
offscreen bool
|
||||
offscrBuf offscreenBuffer
|
||||
offscrImg Image
|
||||
images map[interface{}]*Image
|
||||
fonts map[interface{}]*Font
|
||||
fontCtxs map[fontKey]*frCache
|
||||
fontPathCache map[*Font]*fontPathCache
|
||||
fontTriCache map[*Font]*fontTriCache
|
||||
|
||||
shadowBuf []backendbase.Vec
|
||||
}
|
||||
|
||||
type drawState struct {
|
||||
transform mat
|
||||
transform backendbase.Mat
|
||||
fill drawStyle
|
||||
stroke drawStyle
|
||||
font *Font
|
||||
fontSize float64
|
||||
fontSize fixed.Int26_6
|
||||
fontMetrics font.Metrics
|
||||
textAlign textAlign
|
||||
textBaseline textBaseline
|
||||
lineAlpha float64
|
||||
lineWidth float64
|
||||
lineJoin lineJoin
|
||||
lineEnd lineEnd
|
||||
lineCap lineCap
|
||||
miterLimitSqr float64
|
||||
globalAlpha float64
|
||||
|
||||
|
@ -48,10 +57,9 @@ type drawState struct {
|
|||
lineDashPoint int
|
||||
lineDashOffset float64
|
||||
|
||||
scissor scissor
|
||||
clip []pathPoint
|
||||
clip Path2D
|
||||
|
||||
shadowColor glColor
|
||||
shadowColor color.RGBA
|
||||
shadowOffsetX float64
|
||||
shadowOffsetY float64
|
||||
shadowBlur float64
|
||||
|
@ -68,21 +76,16 @@ type drawState struct {
|
|||
}
|
||||
|
||||
type drawStyle struct {
|
||||
color glColor
|
||||
color color.RGBA
|
||||
radialGradient *RadialGradient
|
||||
linearGradient *LinearGradient
|
||||
image *Image
|
||||
}
|
||||
|
||||
type scissor struct {
|
||||
on bool
|
||||
tl, br vec
|
||||
imagePattern *ImagePattern
|
||||
}
|
||||
|
||||
type lineJoin uint8
|
||||
type lineEnd uint8
|
||||
type lineCap uint8
|
||||
|
||||
// Line join and end constants for SetLineJoin and SetLineEnd
|
||||
// Line join and end constants for SetLineJoin and SetLineCap
|
||||
const (
|
||||
Miter = iota
|
||||
Bevel
|
||||
|
@ -102,130 +105,82 @@ const (
|
|||
End
|
||||
)
|
||||
|
||||
type textBaseline uint8
|
||||
|
||||
// Text baseline constants for SetTextBaseline
|
||||
const (
|
||||
Alphabetic = iota
|
||||
Top
|
||||
Hanging
|
||||
Middle
|
||||
Ideographic
|
||||
Bottom
|
||||
)
|
||||
|
||||
// Performance is a nonstandard setting to improve the
|
||||
// performance of the rendering in some circumstances.
|
||||
// Disabling self intersections will lead to incorrect
|
||||
// rendering of self intersecting polygons, but will
|
||||
// yield better performance when not using the polygons
|
||||
// are not self intersecting. Assuming convex polygons
|
||||
// will break concave polygons, but improve performance
|
||||
// even further
|
||||
var Performance = struct {
|
||||
IgnoreSelfIntersections bool
|
||||
AssumeConvex bool
|
||||
|
||||
// CacheSize is only approximate
|
||||
CacheSize int
|
||||
}{
|
||||
CacheSize: 128_000_000,
|
||||
}
|
||||
|
||||
// New creates a new canvas with the given viewport coordinates.
|
||||
// While all functions on the canvas use the top left point as
|
||||
// the origin, since GL uses the bottom left coordinate, the
|
||||
// coordinates given here also use the bottom left as origin
|
||||
func New(x, y, w, h int) *Canvas {
|
||||
if gli == nil {
|
||||
panic("LoadGL must be called before a canvas can be created")
|
||||
func New(backend backendbase.Backend) *Canvas {
|
||||
cv := &Canvas{
|
||||
b: backend,
|
||||
stateStack: make([]drawState, 0, 20),
|
||||
images: make(map[interface{}]*Image),
|
||||
fonts: make(map[interface{}]*Font),
|
||||
fontCtxs: make(map[fontKey]*frCache),
|
||||
fontPathCache: make(map[*Font]*fontPathCache),
|
||||
fontTriCache: make(map[*Font]*fontTriCache),
|
||||
}
|
||||
cv := &Canvas{stateStack: make([]drawState, 0, 20)}
|
||||
cv.SetBounds(x, y, w, h)
|
||||
cv.state.lineWidth = 1
|
||||
cv.state.lineAlpha = 1
|
||||
cv.state.miterLimitSqr = 100
|
||||
cv.state.globalAlpha = 1
|
||||
cv.state.fill.color = glColor{a: 1}
|
||||
cv.state.stroke.color = glColor{a: 1}
|
||||
cv.state.transform = matIdentity()
|
||||
cv.state.fill.color = color.RGBA{A: 255}
|
||||
cv.state.stroke.color = color.RGBA{A: 255}
|
||||
cv.state.transform = backendbase.MatIdentity
|
||||
cv.path.cv = cv
|
||||
return cv
|
||||
}
|
||||
|
||||
// NewOffscreen creates a new canvas with the given size. It
|
||||
// does not render directly to the screen but renders to a
|
||||
// texture instead
|
||||
func NewOffscreen(w, h int) *Canvas {
|
||||
cv := New(0, 0, w, h)
|
||||
cv.offscreen = true
|
||||
return cv
|
||||
}
|
||||
|
||||
// SetSize changes the internal size of the canvas. This would
|
||||
// usually be called for example when the window is resized
|
||||
//
|
||||
// Deprecated: Use SetBounds instead
|
||||
func (cv *Canvas) SetSize(w, h int) {
|
||||
cv.w, cv.h = w, h
|
||||
cv.fw, cv.fh = float64(w), float64(h)
|
||||
activeCanvas = nil
|
||||
}
|
||||
|
||||
// SetBounds updates the bounds of the canvas. This would
|
||||
// usually be called for example when the window is resized
|
||||
func (cv *Canvas) SetBounds(x, y, w, h int) {
|
||||
if !cv.offscreen {
|
||||
cv.x, cv.y = x, y
|
||||
cv.fx, cv.fy = float64(x), float64(y)
|
||||
}
|
||||
cv.w, cv.h = w, h
|
||||
cv.fw, cv.fh = float64(w), float64(h)
|
||||
activeCanvas = nil
|
||||
}
|
||||
|
||||
// Width returns the internal width of the canvas
|
||||
func (cv *Canvas) Width() int { return cv.w }
|
||||
func (cv *Canvas) Width() int {
|
||||
w, _ := cv.b.Size()
|
||||
return w
|
||||
}
|
||||
|
||||
// Height returns the internal height of the canvas
|
||||
func (cv *Canvas) Height() int { return cv.h }
|
||||
func (cv *Canvas) Height() int {
|
||||
_, h := cv.b.Size()
|
||||
return h
|
||||
}
|
||||
|
||||
// Size returns the internal width and height of the canvas
|
||||
func (cv *Canvas) Size() (int, int) { return cv.w, cv.h }
|
||||
func (cv *Canvas) Size() (int, int) { return cv.b.Size() }
|
||||
|
||||
func (cv *Canvas) tf(v vec) vec {
|
||||
v, _ = v.mulMat(cv.state.transform)
|
||||
return v
|
||||
}
|
||||
|
||||
// Activate makes the canvas active and sets the viewport. Only needs
|
||||
// to be called if any other GL code changes the viewport
|
||||
func (cv *Canvas) Activate() {
|
||||
if cv.offscreen {
|
||||
gli.Viewport(0, 0, int32(cv.w), int32(cv.h))
|
||||
cv.enableTextureRenderTarget(&cv.offscrBuf)
|
||||
cv.offscrImg.w = cv.offscrBuf.w
|
||||
cv.offscrImg.h = cv.offscrBuf.h
|
||||
cv.offscrImg.tex = cv.offscrBuf.tex
|
||||
} else {
|
||||
gli.Viewport(int32(cv.x), int32(cv.y), int32(cv.w), int32(cv.h))
|
||||
cv.disableTextureRenderTarget()
|
||||
}
|
||||
cv.applyScissor()
|
||||
gli.Clear(gl_STENCIL_BUFFER_BIT)
|
||||
}
|
||||
|
||||
var activeCanvas *Canvas
|
||||
|
||||
func (cv *Canvas) activate() {
|
||||
if activeCanvas != cv {
|
||||
activeCanvas = cv
|
||||
cv.Activate()
|
||||
}
|
||||
loop:
|
||||
for {
|
||||
select {
|
||||
case f := <-glChan:
|
||||
f()
|
||||
default:
|
||||
break loop
|
||||
}
|
||||
}
|
||||
func (cv *Canvas) tf(v backendbase.Vec) backendbase.Vec {
|
||||
return v.MulMat(cv.state.transform)
|
||||
}
|
||||
|
||||
const alphaTexSize = 2048
|
||||
|
||||
var (
|
||||
gli GL
|
||||
buf uint32
|
||||
shadowBuf uint32
|
||||
alphaTex uint32
|
||||
sr *solidShader
|
||||
lgr *linearGradientShader
|
||||
rgr *radialGradientShader
|
||||
ipr *imagePatternShader
|
||||
sar *solidAlphaShader
|
||||
rgar *radialGradientAlphaShader
|
||||
lgar *linearGradientAlphaShader
|
||||
ipar *imagePatternAlphaShader
|
||||
ir *imageShader
|
||||
gauss15r *gaussianShader
|
||||
gauss63r *gaussianShader
|
||||
gauss127r *gaussianShader
|
||||
offscr1 offscreenBuffer
|
||||
offscr2 offscreenBuffer
|
||||
glChan = make(chan func())
|
||||
)
|
||||
|
||||
type offscreenBuffer struct {
|
||||
tex uint32
|
||||
w int
|
||||
|
@ -235,184 +190,12 @@ type offscreenBuffer struct {
|
|||
alpha bool
|
||||
}
|
||||
|
||||
type gaussianShader struct {
|
||||
id uint32
|
||||
vertex uint32
|
||||
texCoord uint32
|
||||
canvasSize int32
|
||||
kernelScale int32
|
||||
image int32
|
||||
kernel int32
|
||||
}
|
||||
|
||||
// LoadGL needs to be called once per GL context to load the GL assets
|
||||
// that canvas needs. The parameter is an implementation of the GL interface
|
||||
// in this package that should make this package neutral to GL implementations.
|
||||
// The goglimpl subpackage contains an implementation based on Go-GL v3.2
|
||||
func LoadGL(glimpl GL) (err error) {
|
||||
gli = glimpl
|
||||
|
||||
gli.GetError() // clear error state
|
||||
|
||||
sr, err = loadSolidShader()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = glError()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
lgr, err = loadLinearGradientShader()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = glError()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
rgr, err = loadRadialGradientShader()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = glError()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
ipr, err = loadImagePatternShader()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = glError()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
sar, err = loadSolidAlphaShader()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = glError()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
lgar, err = loadLinearGradientAlphaShader()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = glError()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
rgar, err = loadRadialGradientAlphaShader()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = glError()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
ipar, err = loadImagePatternAlphaShader()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = glError()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
ir, err = loadImageShader()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = glError()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
gauss15s, err := loadGaussian15Shader()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = glError()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
gauss15r = (*gaussianShader)(gauss15s)
|
||||
|
||||
gauss63s, err := loadGaussian63Shader()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = glError()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
gauss63r = (*gaussianShader)(gauss63s)
|
||||
|
||||
gauss127s, err := loadGaussian127Shader()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = glError()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
gauss127r = (*gaussianShader)(gauss127s)
|
||||
|
||||
gli.GenBuffers(1, &buf)
|
||||
err = glError()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
gli.GenBuffers(1, &shadowBuf)
|
||||
err = glError()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
gli.ActiveTexture(gl_TEXTURE0)
|
||||
gli.GenTextures(1, &alphaTex)
|
||||
gli.BindTexture(gl_TEXTURE_2D, alphaTex)
|
||||
gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, gl_NEAREST)
|
||||
gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, gl_NEAREST)
|
||||
gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_S, gl_CLAMP_TO_EDGE)
|
||||
gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_T, gl_CLAMP_TO_EDGE)
|
||||
gli.TexImage2D(gl_TEXTURE_2D, 0, gl_ALPHA, alphaTexSize, alphaTexSize, 0, gl_ALPHA, gl_UNSIGNED_BYTE, nil)
|
||||
|
||||
gli.Enable(gl_BLEND)
|
||||
gli.BlendFunc(gl_SRC_ALPHA, gl_ONE_MINUS_SRC_ALPHA)
|
||||
gli.Enable(gl_STENCIL_TEST)
|
||||
gli.StencilMask(0xFF)
|
||||
gli.Clear(gl_STENCIL_BUFFER_BIT)
|
||||
gli.StencilOp(gl_KEEP, gl_KEEP, gl_KEEP)
|
||||
gli.StencilFunc(gl_EQUAL, 0, 0xFF)
|
||||
|
||||
gli.Enable(gl_SCISSOR_TEST)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func glError() error {
|
||||
glErr := gli.GetError()
|
||||
if glErr != gl_NO_ERROR {
|
||||
return fmt.Errorf("GL Error: %x", glErr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetFillStyle sets the color, gradient, or image for any fill calls. To set a
|
||||
// color, there are several acceptable formats: 3 or 4 int values for RGB(A) in
|
||||
// the range 0-255, 3 or 4 float values for RGB(A) in the range 0-1, hex strings
|
||||
// in the format "#AABBCC", "#AABBCCDD", "#ABC", or "#ABCD"
|
||||
func (cv *Canvas) SetFillStyle(value ...interface{}) {
|
||||
cv.state.fill = parseStyle(value...)
|
||||
cv.state.fill = cv.parseStyle(value...)
|
||||
}
|
||||
|
||||
// SetStrokeStyle sets the color, gradient, or image for any line drawing calls.
|
||||
|
@ -420,10 +203,12 @@ func (cv *Canvas) SetFillStyle(value ...interface{}) {
|
|||
// RGB(A) in the range 0-255, 3 or 4 float values for RGB(A) in the range 0-1,
|
||||
// hex strings in the format "#AABBCC", "#AABBCCDD", "#ABC", or "#ABCD"
|
||||
func (cv *Canvas) SetStrokeStyle(value ...interface{}) {
|
||||
cv.state.stroke = parseStyle(value...)
|
||||
cv.state.stroke = cv.parseStyle(value...)
|
||||
}
|
||||
|
||||
func parseStyle(value ...interface{}) drawStyle {
|
||||
var imagePatterns = make(map[interface{}]*ImagePattern)
|
||||
|
||||
func (cv *Canvas) parseStyle(value ...interface{}) drawStyle {
|
||||
var style drawStyle
|
||||
if len(value) == 1 {
|
||||
switch v := value[0].(type) {
|
||||
|
@ -433,216 +218,63 @@ func parseStyle(value ...interface{}) drawStyle {
|
|||
case *RadialGradient:
|
||||
style.radialGradient = v
|
||||
return style
|
||||
case *ImagePattern:
|
||||
style.imagePattern = v
|
||||
return style
|
||||
}
|
||||
}
|
||||
c, ok := parseColor(value...)
|
||||
if ok {
|
||||
style.color = c
|
||||
} else if len(value) == 1 {
|
||||
return style
|
||||
}
|
||||
if len(value) == 1 {
|
||||
switch v := value[0].(type) {
|
||||
case *Image, string:
|
||||
style.image = getImage(v)
|
||||
case *Image, image.Image, string:
|
||||
if _, ok := imagePatterns[v]; !ok {
|
||||
imagePatterns[v] = cv.CreatePattern(v, Repeat)
|
||||
}
|
||||
style.imagePattern = imagePatterns[v]
|
||||
}
|
||||
}
|
||||
return style
|
||||
}
|
||||
|
||||
func (s *drawStyle) isOpaque() bool {
|
||||
func (cv *Canvas) backendFillStyle(s *drawStyle, alpha float64) backendbase.FillStyle {
|
||||
stl := backendbase.FillStyle{Color: s.color}
|
||||
alpha *= cv.state.globalAlpha
|
||||
if lg := s.linearGradient; lg != nil {
|
||||
return lg.opaque
|
||||
}
|
||||
if rg := s.radialGradient; rg != nil {
|
||||
return rg.opaque
|
||||
}
|
||||
if img := s.image; img != nil {
|
||||
return img.opaque
|
||||
}
|
||||
return s.color.a >= 1
|
||||
}
|
||||
|
||||
func (cv *Canvas) useShader(style *drawStyle) (vertexLoc uint32) {
|
||||
if lg := style.linearGradient; lg != nil {
|
||||
lg.load()
|
||||
gli.ActiveTexture(gl_TEXTURE0)
|
||||
gli.BindTexture(gl_TEXTURE_2D, lg.tex)
|
||||
gli.UseProgram(lgr.id)
|
||||
stl.LinearGradient = lg.grad
|
||||
from := cv.tf(lg.from)
|
||||
to := cv.tf(lg.to)
|
||||
dir := to.sub(from)
|
||||
length := dir.len()
|
||||
dir = dir.divf(length)
|
||||
gli.Uniform2f(lgr.canvasSize, float32(cv.fw), float32(cv.fh))
|
||||
inv := cv.state.transform.invert().f32()
|
||||
gli.UniformMatrix3fv(lgr.invmat, 1, false, &inv[0])
|
||||
gli.Uniform2f(lgr.from, float32(from[0]), float32(from[1]))
|
||||
gli.Uniform2f(lgr.dir, float32(dir[0]), float32(dir[1]))
|
||||
gli.Uniform1f(lgr.len, float32(length))
|
||||
gli.Uniform1i(lgr.gradient, 0)
|
||||
gli.Uniform1f(lgr.globalAlpha, float32(cv.state.globalAlpha))
|
||||
return lgr.vertex
|
||||
}
|
||||
if rg := style.radialGradient; rg != nil {
|
||||
stl.Gradient.X0 = from[0]
|
||||
stl.Gradient.Y0 = from[1]
|
||||
stl.Gradient.X1 = to[0]
|
||||
stl.Gradient.Y1 = to[1]
|
||||
} else if rg := s.radialGradient; rg != nil {
|
||||
rg.load()
|
||||
gli.ActiveTexture(gl_TEXTURE0)
|
||||
gli.BindTexture(gl_TEXTURE_2D, rg.tex)
|
||||
gli.UseProgram(rgr.id)
|
||||
from := cv.tf(rg.from)
|
||||
to := cv.tf(rg.to)
|
||||
dir := to.sub(from)
|
||||
length := dir.len()
|
||||
dir = dir.divf(length)
|
||||
gli.Uniform2f(rgr.canvasSize, float32(cv.fw), float32(cv.fh))
|
||||
inv := cv.state.transform.invert().f32()
|
||||
gli.UniformMatrix3fv(rgr.invmat, 1, false, &inv[0])
|
||||
gli.Uniform2f(rgr.from, float32(from[0]), float32(from[1]))
|
||||
gli.Uniform2f(rgr.to, float32(to[0]), float32(to[1]))
|
||||
gli.Uniform2f(rgr.dir, float32(dir[0]), float32(dir[1]))
|
||||
gli.Uniform1f(rgr.radFrom, float32(rg.radFrom))
|
||||
gli.Uniform1f(rgr.radTo, float32(rg.radTo))
|
||||
gli.Uniform1f(rgr.len, float32(length))
|
||||
gli.Uniform1i(rgr.gradient, 0)
|
||||
gli.Uniform1f(rgr.globalAlpha, float32(cv.state.globalAlpha))
|
||||
return rgr.vertex
|
||||
}
|
||||
if img := style.image; img != nil {
|
||||
gli.UseProgram(ipr.id)
|
||||
gli.ActiveTexture(gl_TEXTURE0)
|
||||
gli.BindTexture(gl_TEXTURE_2D, img.tex)
|
||||
gli.Uniform2f(ipr.canvasSize, float32(cv.fw), float32(cv.fh))
|
||||
inv := cv.state.transform.invert().f32()
|
||||
gli.UniformMatrix3fv(ipr.invmat, 1, false, &inv[0])
|
||||
gli.Uniform2f(ipr.imageSize, float32(img.w), float32(img.h))
|
||||
gli.Uniform1i(ipr.image, 0)
|
||||
gli.Uniform1f(ipr.globalAlpha, float32(cv.state.globalAlpha))
|
||||
return ipr.vertex
|
||||
}
|
||||
|
||||
gli.UseProgram(sr.id)
|
||||
gli.Uniform2f(sr.canvasSize, float32(cv.fw), float32(cv.fh))
|
||||
c := style.color
|
||||
gli.Uniform4f(sr.color, float32(c.r), float32(c.g), float32(c.b), float32(c.a))
|
||||
gli.Uniform1f(sr.globalAlpha, float32(cv.state.globalAlpha))
|
||||
return sr.vertex
|
||||
}
|
||||
|
||||
func (cv *Canvas) useAlphaShader(style *drawStyle, alphaTexSlot int32) (vertexLoc, alphaTexCoordLoc uint32) {
|
||||
if lg := style.linearGradient; lg != nil {
|
||||
lg.load()
|
||||
gli.ActiveTexture(gl_TEXTURE0)
|
||||
gli.BindTexture(gl_TEXTURE_2D, lg.tex)
|
||||
gli.UseProgram(lgar.id)
|
||||
from := cv.tf(lg.from)
|
||||
to := cv.tf(lg.to)
|
||||
dir := to.sub(from)
|
||||
length := dir.len()
|
||||
dir = dir.divf(length)
|
||||
gli.Uniform2f(lgar.canvasSize, float32(cv.fw), float32(cv.fh))
|
||||
inv := cv.state.transform.invert().f32()
|
||||
gli.UniformMatrix3fv(lgar.invmat, 1, false, &inv[0])
|
||||
gli.Uniform2f(lgar.from, float32(from[0]), float32(from[1]))
|
||||
gli.Uniform2f(lgar.dir, float32(dir[0]), float32(dir[1]))
|
||||
gli.Uniform1f(lgar.len, float32(length))
|
||||
gli.Uniform1i(lgar.gradient, 0)
|
||||
gli.Uniform1i(lgar.alphaTex, alphaTexSlot)
|
||||
gli.Uniform1f(lgar.globalAlpha, float32(cv.state.globalAlpha))
|
||||
return lgar.vertex, lgar.alphaTexCoord
|
||||
}
|
||||
if rg := style.radialGradient; rg != nil {
|
||||
rg.load()
|
||||
gli.ActiveTexture(gl_TEXTURE0)
|
||||
gli.BindTexture(gl_TEXTURE_2D, rg.tex)
|
||||
gli.UseProgram(rgar.id)
|
||||
from := cv.tf(rg.from)
|
||||
to := cv.tf(rg.to)
|
||||
dir := to.sub(from)
|
||||
length := dir.len()
|
||||
dir = dir.divf(length)
|
||||
gli.Uniform2f(rgar.canvasSize, float32(cv.fw), float32(cv.fh))
|
||||
inv := cv.state.transform.invert().f32()
|
||||
gli.UniformMatrix3fv(rgar.invmat, 1, false, &inv[0])
|
||||
gli.Uniform2f(rgar.from, float32(from[0]), float32(from[1]))
|
||||
gli.Uniform2f(rgar.to, float32(to[0]), float32(to[1]))
|
||||
gli.Uniform2f(rgar.dir, float32(dir[0]), float32(dir[1]))
|
||||
gli.Uniform1f(rgar.radFrom, float32(rg.radFrom))
|
||||
gli.Uniform1f(rgar.radTo, float32(rg.radTo))
|
||||
gli.Uniform1f(rgar.len, float32(length))
|
||||
gli.Uniform1i(rgar.gradient, 0)
|
||||
gli.Uniform1i(rgar.alphaTex, alphaTexSlot)
|
||||
gli.Uniform1f(rgar.globalAlpha, float32(cv.state.globalAlpha))
|
||||
return rgar.vertex, rgar.alphaTexCoord
|
||||
}
|
||||
if img := style.image; img != nil {
|
||||
gli.UseProgram(ipar.id)
|
||||
gli.ActiveTexture(gl_TEXTURE0)
|
||||
gli.BindTexture(gl_TEXTURE_2D, img.tex)
|
||||
gli.Uniform2f(ipar.canvasSize, float32(cv.fw), float32(cv.fh))
|
||||
inv := cv.state.transform.invert().f32()
|
||||
gli.UniformMatrix3fv(ipar.invmat, 1, false, &inv[0])
|
||||
gli.Uniform2f(ipar.imageSize, float32(img.w), float32(img.h))
|
||||
gli.Uniform1i(ipar.image, 0)
|
||||
gli.Uniform1i(ipar.alphaTex, alphaTexSlot)
|
||||
gli.Uniform1f(ipar.globalAlpha, float32(cv.state.globalAlpha))
|
||||
return ipar.vertex, ipar.alphaTexCoord
|
||||
}
|
||||
|
||||
gli.UseProgram(sar.id)
|
||||
gli.Uniform2f(sar.canvasSize, float32(cv.fw), float32(cv.fh))
|
||||
c := style.color
|
||||
gli.Uniform4f(sar.color, float32(c.r), float32(c.g), float32(c.b), float32(c.a))
|
||||
gli.Uniform1i(sar.alphaTex, alphaTexSlot)
|
||||
gli.Uniform1f(sar.globalAlpha, float32(cv.state.globalAlpha))
|
||||
return sar.vertex, sar.alphaTexCoord
|
||||
}
|
||||
|
||||
func (cv *Canvas) enableTextureRenderTarget(offscr *offscreenBuffer) {
|
||||
if offscr.w != cv.w || offscr.h != cv.h {
|
||||
if offscr.w != 0 && offscr.h != 0 {
|
||||
gli.DeleteTextures(1, &offscr.tex)
|
||||
gli.DeleteFramebuffers(1, &offscr.frameBuf)
|
||||
gli.DeleteRenderbuffers(1, &offscr.renderStencilBuf)
|
||||
}
|
||||
offscr.w = cv.w
|
||||
offscr.h = cv.h
|
||||
|
||||
gli.ActiveTexture(gl_TEXTURE0)
|
||||
gli.GenTextures(1, &offscr.tex)
|
||||
gli.BindTexture(gl_TEXTURE_2D, offscr.tex)
|
||||
// todo do non-power-of-two textures work everywhere?
|
||||
if offscr.alpha {
|
||||
gli.TexImage2D(gl_TEXTURE_2D, 0, gl_RGBA, int32(cv.w), int32(cv.h), 0, gl_RGBA, gl_UNSIGNED_BYTE, nil)
|
||||
stl.Gradient.X0 = from[0]
|
||||
stl.Gradient.Y0 = from[1]
|
||||
stl.Gradient.X1 = to[0]
|
||||
stl.Gradient.Y1 = to[1]
|
||||
stl.Gradient.RadFrom = rg.radFrom
|
||||
stl.Gradient.RadTo = rg.radTo
|
||||
stl.RadialGradient = rg.grad
|
||||
} else if ip := s.imagePattern; ip != nil {
|
||||
if ip.ip == nil {
|
||||
stl.Color = color.RGBA{}
|
||||
} else {
|
||||
gli.TexImage2D(gl_TEXTURE_2D, 0, gl_RGB, int32(cv.w), int32(cv.h), 0, gl_RGB, gl_UNSIGNED_BYTE, nil)
|
||||
ip.ip.Replace(ip.data(cv.state.transform))
|
||||
stl.ImagePattern = ip.ip
|
||||
}
|
||||
gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, gl_NEAREST)
|
||||
gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, gl_NEAREST)
|
||||
|
||||
gli.GenFramebuffers(1, &offscr.frameBuf)
|
||||
gli.BindFramebuffer(gl_FRAMEBUFFER, offscr.frameBuf)
|
||||
|
||||
gli.GenRenderbuffers(1, &offscr.renderStencilBuf)
|
||||
gli.BindRenderbuffer(gl_RENDERBUFFER, offscr.renderStencilBuf)
|
||||
gli.RenderbufferStorage(gl_RENDERBUFFER, gl_DEPTH24_STENCIL8, int32(cv.w), int32(cv.h))
|
||||
gli.FramebufferRenderbuffer(gl_FRAMEBUFFER, gl_DEPTH_STENCIL_ATTACHMENT, gl_RENDERBUFFER, offscr.renderStencilBuf)
|
||||
|
||||
gli.FramebufferTexture(gl_FRAMEBUFFER, gl_COLOR_ATTACHMENT0, offscr.tex, 0)
|
||||
|
||||
if err := gli.CheckFramebufferStatus(gl_FRAMEBUFFER); err != gl_FRAMEBUFFER_COMPLETE {
|
||||
// todo this should maybe not panic
|
||||
panic(fmt.Sprintf("Failed to set up framebuffer for offscreen texture: %x", err))
|
||||
}
|
||||
|
||||
gli.Clear(gl_COLOR_BUFFER_BIT | gl_STENCIL_BUFFER_BIT)
|
||||
} else {
|
||||
gli.BindFramebuffer(gl_FRAMEBUFFER, offscr.frameBuf)
|
||||
}
|
||||
}
|
||||
|
||||
func (cv *Canvas) disableTextureRenderTarget() {
|
||||
if cv.offscreen {
|
||||
cv.enableTextureRenderTarget(&cv.offscrBuf)
|
||||
} else {
|
||||
gli.BindFramebuffer(gl_FRAMEBUFFER, 0)
|
||||
alpha *= float64(s.color.A) / 255
|
||||
}
|
||||
stl.Color.A = uint8(alpha * 255)
|
||||
return stl
|
||||
}
|
||||
|
||||
// SetLineWidth sets the line width for any line drawing calls
|
||||
|
@ -663,30 +295,15 @@ func (cv *Canvas) SetLineWidth(width float64) {
|
|||
// with the LoadFont function, a filename for a font to load (which will be
|
||||
// cached), or nil, in which case the first loaded font will be used
|
||||
func (cv *Canvas) SetFont(src interface{}, size float64) {
|
||||
cv.state.fontSize = fixed.Int26_6(math.Round(size * 64))
|
||||
if src == nil {
|
||||
cv.state.font = defaultFont
|
||||
} else {
|
||||
switch v := src.(type) {
|
||||
case *Font:
|
||||
cv.state.font = v
|
||||
case *truetype.Font:
|
||||
cv.state.font = &Font{font: v}
|
||||
case string:
|
||||
if f, ok := fonts[v]; ok {
|
||||
cv.state.font = f
|
||||
} else {
|
||||
f, err := LoadFont(v)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error loading font %s: %v\n", v, err)
|
||||
fonts[v] = nil
|
||||
} else {
|
||||
fonts[v] = f
|
||||
cv.state.font = f
|
||||
}
|
||||
}
|
||||
}
|
||||
cv.state.font = cv.getFont(src)
|
||||
}
|
||||
cv.state.fontSize = size
|
||||
|
||||
fontFace := truetype.NewFace(cv.state.font.font, &truetype.Options{Size: size})
|
||||
cv.state.fontMetrics = fontFace.Metrics()
|
||||
}
|
||||
|
||||
// SetTextAlign sets the text align for any text drawing calls.
|
||||
|
@ -695,16 +312,23 @@ func (cv *Canvas) SetTextAlign(align textAlign) {
|
|||
cv.state.textAlign = align
|
||||
}
|
||||
|
||||
// SetTextBaseline sets the text baseline for any text drawing calls.
|
||||
// The value can be Alphabetic (default), Top, Hanging, Middle,
|
||||
// Ideographic, or Bottom
|
||||
func (cv *Canvas) SetTextBaseline(baseline textBaseline) {
|
||||
cv.state.textBaseline = baseline
|
||||
}
|
||||
|
||||
// SetLineJoin sets the style of line joints for rendering a path with Stroke.
|
||||
// The value can be Miter, Bevel, or Round
|
||||
func (cv *Canvas) SetLineJoin(join lineJoin) {
|
||||
cv.state.lineJoin = join
|
||||
}
|
||||
|
||||
// SetLineEnd sets the style of line endings for rendering a path with Stroke
|
||||
// SetLineCap sets the style of line endings for rendering a path with Stroke
|
||||
// The value can be Butt, Square, or Round
|
||||
func (cv *Canvas) SetLineEnd(end lineEnd) {
|
||||
cv.state.lineEnd = end
|
||||
func (cv *Canvas) SetLineCap(cap lineCap) {
|
||||
cv.state.lineCap = cap
|
||||
}
|
||||
|
||||
// SetLineDash sets the line dash style
|
||||
|
@ -724,6 +348,7 @@ func (cv *Canvas) SetLineDash(dash []float64) {
|
|||
cv.state.lineDashOffset = 0
|
||||
}
|
||||
|
||||
// SetLineDashOffset sets the line dash offset
|
||||
func (cv *Canvas) SetLineDashOffset(offset float64) {
|
||||
cv.state.lineDashOffset = offset
|
||||
}
|
||||
|
@ -757,43 +382,39 @@ func (cv *Canvas) Restore() {
|
|||
if l <= 0 {
|
||||
return
|
||||
}
|
||||
cv.state.scissor = scissor{}
|
||||
cv.applyScissor()
|
||||
gli.StencilMask(0xFF)
|
||||
gli.Clear(gl_STENCIL_BUFFER_BIT)
|
||||
cv.b.ClearClip()
|
||||
for _, st := range cv.stateStack {
|
||||
if len(st.clip) > 0 {
|
||||
cv.clip(st.clip)
|
||||
if len(st.clip.p) > 0 {
|
||||
cv.clip(&st.clip, backendbase.MatIdentity)
|
||||
}
|
||||
}
|
||||
cv.state = cv.stateStack[l-1]
|
||||
cv.stateStack = cv.stateStack[:l-1]
|
||||
cv.applyScissor()
|
||||
}
|
||||
|
||||
// Scale updates the current transformation with a scaling by the given values
|
||||
func (cv *Canvas) Scale(x, y float64) {
|
||||
cv.state.transform = matScale(vec{x, y}).mul(cv.state.transform)
|
||||
cv.state.transform = backendbase.MatScale(backendbase.Vec{x, y}).Mul(cv.state.transform)
|
||||
}
|
||||
|
||||
// Translate updates the current transformation with a translation by the given values
|
||||
func (cv *Canvas) Translate(x, y float64) {
|
||||
cv.state.transform = matTranslate(vec{x, y}).mul(cv.state.transform)
|
||||
cv.state.transform = backendbase.MatTranslate(backendbase.Vec{x, y}).Mul(cv.state.transform)
|
||||
}
|
||||
|
||||
// Rotate updates the current transformation with a rotation by the given angle
|
||||
func (cv *Canvas) Rotate(angle float64) {
|
||||
cv.state.transform = matRotate(angle).mul(cv.state.transform)
|
||||
cv.state.transform = backendbase.MatRotate(angle).Mul(cv.state.transform)
|
||||
}
|
||||
|
||||
// Transform updates the current transformation with the given matrix
|
||||
func (cv *Canvas) Transform(a, b, c, d, e, f float64) {
|
||||
cv.state.transform = mat{a, b, 0, c, d, 0, e, f, 1}.mul(cv.state.transform)
|
||||
cv.state.transform = backendbase.Mat{a, b, c, d, e, f}.Mul(cv.state.transform)
|
||||
}
|
||||
|
||||
// SetTransform replaces the current transformation with the given matrix
|
||||
func (cv *Canvas) SetTransform(a, b, c, d, e, f float64) {
|
||||
cv.state.transform = mat{a, b, 0, c, d, 0, e, f, 1}
|
||||
cv.state.transform = backendbase.Mat{a, b, c, d, e, f}
|
||||
}
|
||||
|
||||
// SetShadowColor sets the color of the shadow. If it is fully transparent (default)
|
||||
|
@ -825,3 +446,103 @@ func (cv *Canvas) SetShadowOffset(x, y float64) {
|
|||
func (cv *Canvas) SetShadowBlur(r float64) {
|
||||
cv.state.shadowBlur = r
|
||||
}
|
||||
|
||||
// IsPointInPath returns true if the point is in the current
|
||||
// path according to the given rule
|
||||
func (cv *Canvas) IsPointInPath(x, y float64, rule pathRule) bool {
|
||||
return cv.path.IsPointInPath(x, y, rule)
|
||||
}
|
||||
|
||||
// IsPointInStroke returns true if the point is in the current
|
||||
// path stroke
|
||||
func (cv *Canvas) IsPointInStroke(x, y float64) bool {
|
||||
if len(cv.path.p) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
var triBuf [500]backendbase.Vec
|
||||
tris := cv.strokeTris(
|
||||
&cv.path,
|
||||
cv.state.transform,
|
||||
cv.state.transform.Invert(),
|
||||
true,
|
||||
triBuf[:0],
|
||||
)
|
||||
|
||||
pt := backendbase.Vec{x, y}
|
||||
|
||||
for i := 0; i < len(tris); i += 3 {
|
||||
a := backendbase.Vec{tris[i][0], tris[i][1]}
|
||||
b := backendbase.Vec{tris[i+1][0], tris[i+1][1]}
|
||||
c := backendbase.Vec{tris[i+2][0], tris[i+2][1]}
|
||||
if triangleContainsPoint(a, b, c, pt) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (cv *Canvas) reduceCache(keepSize, rec int) {
|
||||
if rec > 100 {
|
||||
return
|
||||
}
|
||||
|
||||
var total int
|
||||
oldest := time.Now()
|
||||
var oldestFontKey fontKey
|
||||
var oldestFontKey2 *Font
|
||||
var oldestFontKey3 *Font
|
||||
var oldestImageKey interface{}
|
||||
for src, img := range cv.images {
|
||||
w, h := img.img.Size()
|
||||
total += w * h * 4
|
||||
if img.lastUsed.Before(oldest) {
|
||||
oldest = img.lastUsed
|
||||
oldestImageKey = src
|
||||
}
|
||||
}
|
||||
for key, frctx := range cv.fontCtxs {
|
||||
total += frctx.ctx.cacheSize()
|
||||
if frctx.lastUsed.Before(oldest) {
|
||||
oldest = frctx.lastUsed
|
||||
oldestFontKey = key
|
||||
oldestImageKey = nil
|
||||
}
|
||||
}
|
||||
for fnt, cache := range cv.fontPathCache {
|
||||
total += cache.size()
|
||||
if cache.lastUsed.Before(oldest) {
|
||||
oldest = cache.lastUsed
|
||||
oldestFontKey2 = fnt
|
||||
oldestFontKey = fontKey{}
|
||||
oldestImageKey = nil
|
||||
}
|
||||
}
|
||||
for fnt, cache := range cv.fontTriCache {
|
||||
total += cache.size()
|
||||
if cache.lastUsed.Before(oldest) {
|
||||
oldest = cache.lastUsed
|
||||
oldestFontKey3 = fnt
|
||||
oldestFontKey2 = nil
|
||||
oldestFontKey = fontKey{}
|
||||
oldestImageKey = nil
|
||||
}
|
||||
}
|
||||
if total <= keepSize {
|
||||
return
|
||||
}
|
||||
|
||||
if oldestImageKey != nil {
|
||||
cv.images[oldestImageKey].Delete()
|
||||
delete(cv.images, oldestImageKey)
|
||||
} else if oldestFontKey2 != nil {
|
||||
delete(cv.fontPathCache, oldestFontKey2)
|
||||
} else if oldestFontKey3 != nil {
|
||||
delete(cv.fontTriCache, oldestFontKey3)
|
||||
} else {
|
||||
cv.fontCtxs[oldestFontKey].ctx = nil
|
||||
delete(cv.fontCtxs, oldestFontKey)
|
||||
}
|
||||
|
||||
cv.reduceCache(keepSize, rec+1)
|
||||
}
|
||||
|
|
404
canvas_test.go
|
@ -1,404 +0,0 @@
|
|||
package canvas_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image/png"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/go-gl/gl/v3.2-core/gl"
|
||||
"github.com/tfriedel6/canvas"
|
||||
_ "github.com/tfriedel6/canvas/glimpl/xmobile"
|
||||
"github.com/tfriedel6/canvas/sdlcanvas"
|
||||
)
|
||||
|
||||
func run(t *testing.T, fn func(cv *canvas.Canvas)) {
|
||||
wnd, cv2, err := sdlcanvas.CreateWindow(100, 100, "test")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to crete window: %v", err)
|
||||
return
|
||||
}
|
||||
defer wnd.Destroy()
|
||||
|
||||
cv := canvas.NewOffscreen(100, 100)
|
||||
|
||||
gl.Disable(gl.MULTISAMPLE)
|
||||
|
||||
wnd.StartFrame()
|
||||
|
||||
cv.ClearRect(0, 0, 100, 100)
|
||||
fn(cv)
|
||||
img := cv.GetImageData(0, 0, 100, 100)
|
||||
|
||||
cv2.DrawImage(cv)
|
||||
img2 := cv2.GetImageData(0, 0, 100, 100)
|
||||
|
||||
caller, _, _, ok := runtime.Caller(1)
|
||||
if !ok {
|
||||
t.Fatal("Failed to get caller")
|
||||
}
|
||||
|
||||
callerFunc := runtime.FuncForPC(caller)
|
||||
if callerFunc == nil {
|
||||
t.Fatal("Failed to get caller function")
|
||||
}
|
||||
|
||||
const prefix = "canvas_test.Test"
|
||||
callerFuncName := callerFunc.Name()
|
||||
callerFuncName = callerFuncName[strings.Index(callerFuncName, prefix)+len(prefix):]
|
||||
|
||||
fileName := fmt.Sprintf("testdata/%s.png", callerFuncName)
|
||||
|
||||
_, err = os.Stat(fileName)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
t.Fatalf("Failed to stat file \"%s\": %v", fileName, err)
|
||||
}
|
||||
|
||||
if os.IsNotExist(err) {
|
||||
f, err := os.OpenFile(fileName, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0777)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create file \"%s\"", fileName)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
err = png.Encode(f, img)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to encode PNG")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
f, err := os.Open(fileName)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to open file \"%s\": %v", fileName, err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
refImg, err := png.Decode(f)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to decode file \"%s\": %v", fileName, err)
|
||||
}
|
||||
|
||||
if b := img.Bounds(); b.Min.X != 0 || b.Min.Y != 0 || b.Max.X != 100 || b.Max.Y != 100 {
|
||||
t.Fatalf("Image bounds must be 0,0,100,100")
|
||||
}
|
||||
if b := refImg.Bounds(); b.Min.X != 0 || b.Min.Y != 0 || b.Max.X != 100 || b.Max.Y != 100 {
|
||||
t.Fatalf("Image bounds must be 0,0,100,100")
|
||||
}
|
||||
|
||||
for y := 0; y < 100; y++ {
|
||||
for x := 0; x < 100; x++ {
|
||||
r1, g1, b1, a1 := img.At(x, y).RGBA()
|
||||
r2, g2, b2, a2 := img2.At(x, y).RGBA()
|
||||
r3, g3, b3, a3 := refImg.At(x, y).RGBA()
|
||||
if r1 != r3 || g1 != g3 || b1 != b3 || a1 != a3 {
|
||||
t.FailNow()
|
||||
}
|
||||
if r2 != r3 || g2 != g3 || b2 != b3 || a2 != a3 {
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFillRect(t *testing.T) {
|
||||
run(t, func(cv *canvas.Canvas) {
|
||||
cv.SetFillStyle("#F00")
|
||||
cv.FillRect(10, 10, 10, 10)
|
||||
|
||||
cv.SetFillStyle("#F008")
|
||||
cv.FillRect(30, 10, 10, 10)
|
||||
|
||||
cv.SetFillStyle(64, 96, 128, 160)
|
||||
cv.FillRect(50, 10, 10, 10)
|
||||
|
||||
cv.SetFillStyle(0.5, 0.7, 0.2, 0.8)
|
||||
cv.FillRect(70, 10, 10, 10)
|
||||
})
|
||||
}
|
||||
|
||||
func TestFillConvexPath(t *testing.T) {
|
||||
run(t, func(cv *canvas.Canvas) {
|
||||
cv.SetFillStyle("#0F0")
|
||||
cv.BeginPath()
|
||||
cv.MoveTo(20, 20)
|
||||
cv.LineTo(60, 20)
|
||||
cv.LineTo(80, 80)
|
||||
cv.LineTo(40, 80)
|
||||
cv.ClosePath()
|
||||
cv.Fill()
|
||||
})
|
||||
}
|
||||
func TestFillConcavePath(t *testing.T) {
|
||||
run(t, func(cv *canvas.Canvas) {
|
||||
cv.SetFillStyle("#0F0")
|
||||
cv.BeginPath()
|
||||
cv.MoveTo(20, 20)
|
||||
cv.LineTo(60, 20)
|
||||
cv.LineTo(60, 60)
|
||||
cv.LineTo(50, 60)
|
||||
cv.LineTo(50, 40)
|
||||
cv.LineTo(20, 40)
|
||||
cv.ClosePath()
|
||||
cv.Fill()
|
||||
})
|
||||
}
|
||||
|
||||
func TestDrawPath(t *testing.T) {
|
||||
run(t, func(cv *canvas.Canvas) {
|
||||
cv.SetStrokeStyle("#00F")
|
||||
cv.SetLineJoin(canvas.Miter)
|
||||
cv.SetLineWidth(8)
|
||||
cv.BeginPath()
|
||||
cv.MoveTo(10, 10)
|
||||
cv.LineTo(30, 10)
|
||||
cv.LineTo(30, 30)
|
||||
cv.LineTo(10, 30)
|
||||
cv.ClosePath()
|
||||
cv.Stroke()
|
||||
|
||||
cv.SetLineJoin(canvas.Round)
|
||||
cv.BeginPath()
|
||||
cv.MoveTo(40, 10)
|
||||
cv.LineTo(60, 10)
|
||||
cv.LineTo(60, 30)
|
||||
cv.LineTo(40, 30)
|
||||
cv.ClosePath()
|
||||
cv.Stroke()
|
||||
|
||||
cv.SetLineJoin(canvas.Bevel)
|
||||
cv.BeginPath()
|
||||
cv.MoveTo(70, 10)
|
||||
cv.LineTo(90, 10)
|
||||
cv.LineTo(90, 30)
|
||||
cv.LineTo(70, 30)
|
||||
cv.ClosePath()
|
||||
cv.Stroke()
|
||||
|
||||
cv.SetLineEnd(canvas.Butt)
|
||||
cv.BeginPath()
|
||||
cv.MoveTo(10, 40)
|
||||
cv.LineTo(30, 40)
|
||||
cv.LineTo(30, 60)
|
||||
cv.LineTo(10, 60)
|
||||
cv.Stroke()
|
||||
|
||||
cv.SetLineEnd(canvas.Round)
|
||||
cv.BeginPath()
|
||||
cv.MoveTo(40, 40)
|
||||
cv.LineTo(60, 40)
|
||||
cv.LineTo(60, 60)
|
||||
cv.LineTo(40, 60)
|
||||
cv.Stroke()
|
||||
|
||||
cv.SetLineEnd(canvas.Square)
|
||||
cv.BeginPath()
|
||||
cv.MoveTo(70, 40)
|
||||
cv.LineTo(90, 40)
|
||||
cv.LineTo(90, 60)
|
||||
cv.LineTo(70, 60)
|
||||
cv.Stroke()
|
||||
})
|
||||
}
|
||||
|
||||
func TestMiterLimit(t *testing.T) {
|
||||
run(t, func(cv *canvas.Canvas) {
|
||||
cv.SetStrokeStyle("#0F0")
|
||||
cv.SetLineJoin(canvas.Miter)
|
||||
cv.SetLineWidth(2.5)
|
||||
cv.SetMiterLimit(30)
|
||||
y, step := 20.0, 4.0
|
||||
for i := 0; i < 20; i++ {
|
||||
cv.LineTo(20, y)
|
||||
y += step
|
||||
cv.LineTo(80, y)
|
||||
y += step
|
||||
step *= 0.9
|
||||
}
|
||||
cv.Stroke()
|
||||
})
|
||||
}
|
||||
|
||||
func TestLineDash(t *testing.T) {
|
||||
run(t, func(cv *canvas.Canvas) {
|
||||
cv.SetStrokeStyle("#0F0")
|
||||
cv.SetLineWidth(2.5)
|
||||
cv.SetLineDash([]float64{4, 6, 8})
|
||||
cv.BeginPath()
|
||||
cv.MoveTo(20, 20)
|
||||
cv.LineTo(80, 20)
|
||||
cv.LineTo(80, 80)
|
||||
cv.LineTo(50, 80)
|
||||
cv.LineTo(50, 50)
|
||||
cv.LineTo(20, 50)
|
||||
cv.ClosePath()
|
||||
cv.MoveTo(30, 30)
|
||||
cv.LineTo(70, 30)
|
||||
cv.LineTo(70, 70)
|
||||
cv.LineTo(60, 70)
|
||||
cv.LineTo(60, 40)
|
||||
cv.LineTo(30, 40)
|
||||
cv.ClosePath()
|
||||
cv.Stroke()
|
||||
ld := cv.GetLineDash()
|
||||
if ld[0] != 4 || ld[1] != 6 || ld[2] != 8 || ld[3] != 4 || ld[4] != 6 || ld[5] != 8 {
|
||||
t.Fail()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestLineDashOffset(t *testing.T) {
|
||||
run(t, func(cv *canvas.Canvas) {
|
||||
cv.SetStrokeStyle("#0F0")
|
||||
cv.SetLineWidth(2.5)
|
||||
cv.SetLineDash([]float64{4, 6, 8})
|
||||
cv.SetLineDashOffset(5)
|
||||
cv.BeginPath()
|
||||
cv.MoveTo(20, 20)
|
||||
cv.LineTo(80, 20)
|
||||
cv.LineTo(80, 80)
|
||||
cv.LineTo(50, 80)
|
||||
cv.LineTo(50, 50)
|
||||
cv.LineTo(20, 50)
|
||||
cv.ClosePath()
|
||||
cv.MoveTo(30, 30)
|
||||
cv.LineTo(70, 30)
|
||||
cv.LineTo(70, 70)
|
||||
cv.LineTo(60, 70)
|
||||
cv.LineTo(60, 40)
|
||||
cv.LineTo(30, 40)
|
||||
cv.ClosePath()
|
||||
cv.Stroke()
|
||||
ld := cv.GetLineDash()
|
||||
if ld[0] != 4 || ld[1] != 6 || ld[2] != 8 || ld[3] != 4 || ld[4] != 6 || ld[5] != 8 {
|
||||
t.Fail()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestCurves(t *testing.T) {
|
||||
run(t, func(cv *canvas.Canvas) {
|
||||
cv.SetStrokeStyle("#00F")
|
||||
cv.SetLineWidth(2.5)
|
||||
cv.BeginPath()
|
||||
cv.Arc(30, 30, 15, 0, 4, false)
|
||||
cv.ClosePath()
|
||||
cv.MoveTo(30, 70)
|
||||
cv.LineTo(40, 70)
|
||||
cv.ArcTo(50, 70, 50, 55, 5)
|
||||
cv.ArcTo(50, 40, 55, 40, 5)
|
||||
cv.QuadraticCurveTo(70, 40, 80, 60)
|
||||
cv.BezierCurveTo(70, 80, 60, 80, 50, 90)
|
||||
cv.Stroke()
|
||||
})
|
||||
}
|
||||
|
||||
func TestAlpha(t *testing.T) {
|
||||
run(t, func(cv *canvas.Canvas) {
|
||||
cv.SetStrokeStyle("#F00")
|
||||
cv.SetLineWidth(2.5)
|
||||
cv.BeginPath()
|
||||
cv.Arc(30, 30, 15, 0, 4, false)
|
||||
cv.ClosePath()
|
||||
cv.MoveTo(30, 70)
|
||||
cv.LineTo(40, 70)
|
||||
cv.ArcTo(50, 70, 50, 55, 5)
|
||||
cv.ArcTo(50, 40, 55, 40, 5)
|
||||
cv.QuadraticCurveTo(70, 40, 80, 60)
|
||||
cv.BezierCurveTo(70, 80, 60, 80, 50, 90)
|
||||
cv.Stroke()
|
||||
|
||||
cv.SetStrokeStyle("#0F08")
|
||||
cv.SetLineWidth(5)
|
||||
cv.BeginPath()
|
||||
cv.MoveTo(10, 10)
|
||||
cv.LineTo(90, 90)
|
||||
cv.LineTo(90, 10)
|
||||
cv.LineTo(10, 90)
|
||||
cv.ClosePath()
|
||||
cv.Stroke()
|
||||
|
||||
cv.SetGlobalAlpha(0.5)
|
||||
cv.SetStrokeStyle("#FFF8")
|
||||
cv.SetLineWidth(8)
|
||||
cv.BeginPath()
|
||||
cv.MoveTo(50, 10)
|
||||
cv.LineTo(50, 90)
|
||||
cv.Stroke()
|
||||
})
|
||||
}
|
||||
|
||||
func TestClosePath(t *testing.T) {
|
||||
run(t, func(cv *canvas.Canvas) {
|
||||
cv.SetStrokeStyle("#0F0")
|
||||
cv.SetLineWidth(2.5)
|
||||
cv.BeginPath()
|
||||
cv.MoveTo(20, 20)
|
||||
cv.LineTo(40, 20)
|
||||
cv.LineTo(40, 40)
|
||||
cv.LineTo(20, 40)
|
||||
cv.ClosePath()
|
||||
cv.MoveTo(60, 20)
|
||||
cv.LineTo(80, 20)
|
||||
cv.LineTo(80, 40)
|
||||
cv.LineTo(60, 40)
|
||||
cv.ClosePath()
|
||||
cv.Stroke()
|
||||
|
||||
cv.SetFillStyle("#00F")
|
||||
cv.BeginPath()
|
||||
cv.MoveTo(20, 60)
|
||||
cv.LineTo(40, 60)
|
||||
cv.LineTo(40, 80)
|
||||
cv.LineTo(20, 80)
|
||||
cv.ClosePath()
|
||||
cv.MoveTo(60, 60)
|
||||
cv.LineTo(80, 60)
|
||||
cv.LineTo(80, 80)
|
||||
cv.LineTo(60, 80)
|
||||
cv.ClosePath()
|
||||
cv.Fill()
|
||||
})
|
||||
}
|
||||
|
||||
func TestLineDash2(t *testing.T) {
|
||||
run(t, func(cv *canvas.Canvas) {
|
||||
cv.SetStrokeStyle("#0F0")
|
||||
cv.SetLineWidth(2.5)
|
||||
cv.BeginPath()
|
||||
cv.MoveTo(20, 20)
|
||||
cv.LineTo(40, 20)
|
||||
cv.LineTo(40, 40)
|
||||
cv.LineTo(20, 40)
|
||||
cv.ClosePath()
|
||||
cv.MoveTo(60, 20)
|
||||
cv.LineTo(80, 20)
|
||||
cv.LineTo(80, 40)
|
||||
cv.LineTo(60, 40)
|
||||
cv.ClosePath()
|
||||
cv.SetLineDash([]float64{4, 4})
|
||||
cv.MoveTo(20, 60)
|
||||
cv.LineTo(40, 60)
|
||||
cv.LineTo(40, 80)
|
||||
cv.LineTo(20, 80)
|
||||
cv.ClosePath()
|
||||
cv.MoveTo(60, 60)
|
||||
cv.LineTo(80, 60)
|
||||
cv.LineTo(80, 80)
|
||||
cv.LineTo(60, 80)
|
||||
cv.ClosePath()
|
||||
cv.Stroke()
|
||||
})
|
||||
}
|
||||
func TestText(t *testing.T) {
|
||||
run(t, func(cv *canvas.Canvas) {
|
||||
cv.SetFont("testdata/Roboto-Light.ttf", 48)
|
||||
cv.SetFillStyle("#F00")
|
||||
cv.FillText("A BC", 0, 46)
|
||||
cv.SetStrokeStyle("#0F0")
|
||||
cv.SetLineWidth(1)
|
||||
cv.StrokeText("D EF", 0, 90)
|
||||
})
|
||||
}
|
141
color.go
|
@ -3,53 +3,11 @@ package canvas
|
|||
import (
|
||||
"fmt"
|
||||
"image/color"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type glColor struct {
|
||||
r, g, b, a float64
|
||||
}
|
||||
|
||||
func colorGoToGL(color color.Color) glColor {
|
||||
ir, ig, ib, ia := color.RGBA()
|
||||
var c glColor
|
||||
c.r = float64(ir) / 65535
|
||||
c.g = float64(ig) / 65535
|
||||
c.b = float64(ib) / 65535
|
||||
c.a = float64(ia) / 65535
|
||||
return c
|
||||
}
|
||||
|
||||
func colorGLToGo(c glColor) color.Color {
|
||||
if c.r < 0 {
|
||||
c.r = 0
|
||||
} else if c.r > 1 {
|
||||
c.r = 1
|
||||
}
|
||||
if c.g < 0 {
|
||||
c.g = 0
|
||||
} else if c.g > 1 {
|
||||
c.g = 1
|
||||
}
|
||||
if c.b < 0 {
|
||||
c.b = 0
|
||||
} else if c.b > 1 {
|
||||
c.b = 1
|
||||
}
|
||||
if c.a < 0 {
|
||||
c.a = 0
|
||||
} else if c.a > 1 {
|
||||
c.a = 1
|
||||
}
|
||||
return color.RGBA{
|
||||
R: uint8(c.r * 255),
|
||||
G: uint8(c.g * 255),
|
||||
B: uint8(c.b * 255),
|
||||
A: uint8(c.a * 255),
|
||||
}
|
||||
}
|
||||
|
||||
func parseHexRune(rn rune) (int, bool) {
|
||||
switch {
|
||||
case rn >= '0' && rn <= '9':
|
||||
|
@ -74,18 +32,18 @@ func parseHexRunePair(rn1, rn2 rune) (int, bool) {
|
|||
return i1*16 + i2, true
|
||||
}
|
||||
|
||||
func parseColorComponent(value interface{}) (float64, bool) {
|
||||
func parseColorComponent(value interface{}) (uint8, bool) {
|
||||
switch v := value.(type) {
|
||||
case float32:
|
||||
return float64(v), true
|
||||
return uint8(math.Floor(float64(v) * 255)), true
|
||||
case float64:
|
||||
return v, true
|
||||
return uint8(math.Floor(v * 255)), true
|
||||
case int:
|
||||
return float64(v) / 255, true
|
||||
return uint8(v), true
|
||||
case uint:
|
||||
return float64(v) / 255, true
|
||||
return uint8(v), true
|
||||
case uint8:
|
||||
return float64(v) / 255, true
|
||||
return v, true
|
||||
case string:
|
||||
if len(v) == 0 {
|
||||
return 0, false
|
||||
|
@ -99,7 +57,7 @@ func parseColorComponent(value interface{}) (float64, bool) {
|
|||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
return float64(conv) / 255, true
|
||||
return uint8(conv), true
|
||||
} else if strings.ContainsRune(v, '.') {
|
||||
conv, err := strconv.ParseFloat(v, 32)
|
||||
if err != nil {
|
||||
|
@ -110,45 +68,78 @@ func parseColorComponent(value interface{}) (float64, bool) {
|
|||
} else if conv > 1 {
|
||||
conv = 1
|
||||
}
|
||||
return float64(conv), true
|
||||
return uint8(math.Round(conv * 255.0)), true
|
||||
} else {
|
||||
conv, err := strconv.ParseUint(v, 10, 8)
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
return float64(conv) / 255, true
|
||||
return uint8(conv), true
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
func parseColor(value ...interface{}) (c glColor, ok bool) {
|
||||
func parseColor(value ...interface{}) (c color.RGBA, ok bool) {
|
||||
if len(value) == 1 {
|
||||
switch v := value[0].(type) {
|
||||
case color.Color:
|
||||
c = colorGoToGL(v)
|
||||
r, g, b, a := v.RGBA()
|
||||
c = color.RGBA{R: uint8(r >> 8), G: uint8(g >> 8), B: uint8(b >> 8), A: uint8(a >> 8)}
|
||||
ok = true
|
||||
return
|
||||
case [3]float32:
|
||||
return glColor{r: float64(v[0]), g: float64(v[1]), b: float64(v[2]), a: 1}, true
|
||||
return color.RGBA{
|
||||
R: uint8(math.Floor(float64(v[0] * 255))),
|
||||
G: uint8(math.Floor(float64(v[1] * 255))),
|
||||
B: uint8(math.Floor(float64(v[2] * 255))),
|
||||
A: 255}, true
|
||||
case [4]float32:
|
||||
return glColor{r: float64(v[0]), g: float64(v[1]), b: float64(v[2]), a: float64(v[3])}, true
|
||||
return color.RGBA{
|
||||
R: uint8(math.Floor(float64(v[0] * 255))),
|
||||
G: uint8(math.Floor(float64(v[1] * 255))),
|
||||
B: uint8(math.Floor(float64(v[2] * 255))),
|
||||
A: uint8(math.Floor(float64(v[3] * 255)))}, true
|
||||
case [3]float64:
|
||||
return glColor{r: v[0], g: v[1], b: v[2], a: 1}, true
|
||||
return color.RGBA{
|
||||
R: uint8(math.Floor(v[0] * 255)),
|
||||
G: uint8(math.Floor(v[1] * 255)),
|
||||
B: uint8(math.Floor(v[2] * 255)),
|
||||
A: 255}, true
|
||||
case [4]float64:
|
||||
return glColor{r: v[0], g: v[1], b: v[2], a: v[3]}, true
|
||||
return color.RGBA{
|
||||
R: uint8(math.Floor(v[0] * 255)),
|
||||
G: uint8(math.Floor(v[1] * 255)),
|
||||
B: uint8(math.Floor(v[2] * 255)),
|
||||
A: uint8(math.Floor(v[3] * 255))}, true
|
||||
case [3]int:
|
||||
return glColor{r: float64(v[0]) / 255, g: float64(v[1]) / 255, b: float64(v[2]) / 255, a: 1}, true
|
||||
return color.RGBA{
|
||||
R: uint8(v[0]),
|
||||
G: uint8(v[1]),
|
||||
B: uint8(v[2]),
|
||||
A: 255}, true
|
||||
case [4]int:
|
||||
return glColor{r: float64(v[0]) / 255, g: float64(v[1]) / 255, b: float64(v[2]) / 255, a: float64(v[3]) / 255}, true
|
||||
return color.RGBA{
|
||||
R: uint8(v[0]),
|
||||
G: uint8(v[1]),
|
||||
B: uint8(v[2]),
|
||||
A: uint8(v[3])}, true
|
||||
case [3]uint:
|
||||
return glColor{r: float64(v[0]) / 255, g: float64(v[1]) / 255, b: float64(v[2]) / 255, a: 1}, true
|
||||
return color.RGBA{
|
||||
R: uint8(v[0]),
|
||||
G: uint8(v[1]),
|
||||
B: uint8(v[2]),
|
||||
A: 255}, true
|
||||
case [4]uint:
|
||||
return glColor{r: float64(v[0]) / 255, g: float64(v[1]) / 255, b: float64(v[2]) / 255, a: float64(v[3]) / 255}, true
|
||||
return color.RGBA{
|
||||
R: uint8(v[0]),
|
||||
G: uint8(v[1]),
|
||||
B: uint8(v[2]),
|
||||
A: uint8(v[3])}, true
|
||||
case [3]uint8:
|
||||
return glColor{r: float64(v[0]) / 255, g: float64(v[1]) / 255, b: float64(v[2]) / 255, a: 1}, true
|
||||
return color.RGBA{R: v[0], G: v[1], B: v[2], A: 255}, true
|
||||
case [4]uint8:
|
||||
return glColor{r: float64(v[0]) / 255, g: float64(v[1]) / 255, b: float64(v[2]) / 255, a: float64(v[3]) / 255}, true
|
||||
return color.RGBA{R: v[0], G: v[1], B: v[2], A: v[3]}, true
|
||||
case string:
|
||||
if len(v) == 0 {
|
||||
return
|
||||
|
@ -180,7 +171,7 @@ func parseColor(value ...interface{}) (c glColor, ok bool) {
|
|||
}
|
||||
ia = ia*16 + ia
|
||||
}
|
||||
return glColor{r: float64(ir) / 255, g: float64(ig) / 255, b: float64(ib) / 255, a: float64(ia) / 255}, true
|
||||
return color.RGBA{R: uint8(ir), G: uint8(ig), B: uint8(ib), A: uint8(ia)}, true
|
||||
} else if len(str) == 6 || len(str) == 8 {
|
||||
var ir, ig, ib int
|
||||
ia := 255
|
||||
|
@ -202,25 +193,27 @@ func parseColor(value ...interface{}) (c glColor, ok bool) {
|
|||
return
|
||||
}
|
||||
}
|
||||
return glColor{r: float64(ir) / 255, g: float64(ig) / 255, b: float64(ib) / 255, a: float64(ia) / 255}, true
|
||||
return color.RGBA{R: uint8(ir), G: uint8(ig), B: uint8(ib), A: uint8(ia)}, true
|
||||
} else {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
v = strings.Replace(v, " ", "", -1)
|
||||
var ir, ig, ib, ia int
|
||||
var ir, ig, ib int
|
||||
n, err := fmt.Sscanf(v, "rgb(%d,%d,%d)", &ir, &ig, &ib)
|
||||
if err == nil && n == 3 {
|
||||
return glColor{r: float64(ir) / 255, g: float64(ig) / 255, b: float64(ib) / 255, a: 1}, true
|
||||
return color.RGBA{R: uint8(ir), G: uint8(ig), B: uint8(ib), A: 255}, true
|
||||
}
|
||||
n, err = fmt.Sscanf(v, "rgba(%d,%d,%d,%d)", &ir, &ig, &ib, &ia)
|
||||
var fa float64
|
||||
n, err = fmt.Sscanf(v, "rgba(%d,%d,%d,%f)", &ir, &ig, &ib, &fa)
|
||||
fa = math.Max(0, math.Min(1, fa))
|
||||
if err == nil && n == 4 {
|
||||
return glColor{r: float64(ir) / 255, g: float64(ig) / 255, b: float64(ib) / 255, a: float64(ia) / 255}, true
|
||||
return color.RGBA{R: uint8(ir), G: uint8(ig), B: uint8(ib), A: uint8(fa * 255)}, true
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if len(value) == 3 || len(value) == 4 {
|
||||
var pr, pg, pb, pa float64
|
||||
var pr, pg, pb, pa uint8
|
||||
pr, ok = parseColorComponent(value[0])
|
||||
if !ok {
|
||||
return
|
||||
|
@ -239,10 +232,10 @@ func parseColor(value ...interface{}) (c glColor, ok bool) {
|
|||
return
|
||||
}
|
||||
} else {
|
||||
pa = 1
|
||||
pa = 255
|
||||
}
|
||||
return glColor{r: pr, g: pg, b: pb, a: pa}, true
|
||||
return color.RGBA{R: pr, G: pg, B: pb, A: pa}, true
|
||||
}
|
||||
|
||||
return glColor{r: 0, g: 0, b: 0, a: 1}, false
|
||||
return color.RGBA{A: 255}, false
|
||||
}
|
||||
|
|
909
earcut.go
Normal file
|
@ -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 +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 +0,0 @@
|
|||
The go bindings are generated with the ```gomobile bind -target android``` command, which results in a .aar and a .jar file. These should be placed in the CanvasAndroidExample/app/libs directory, and then the project should compile.
|
|
@ -1,44 +0,0 @@
|
|||
package canvasandroidexample
|
||||
|
||||
import (
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"github.com/tfriedel6/canvas"
|
||||
"github.com/tfriedel6/canvas/glimpl/android"
|
||||
)
|
||||
|
||||
var cv *canvas.Canvas
|
||||
var mx, my float64
|
||||
|
||||
func TouchEvent(typ string, x, y int) {
|
||||
mx, my = float64(x), float64(y)
|
||||
}
|
||||
|
||||
func OnSurfaceCreated() {
|
||||
}
|
||||
|
||||
func OnSurfaceChanged(w, h int) {
|
||||
err := canvas.LoadGL(glimplandroid.GLImpl{})
|
||||
if err != nil {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
panic(err)
|
||||
}
|
||||
cv = canvas.New(0, 0, w, h)
|
||||
}
|
||||
|
||||
func OnDrawFrame() {
|
||||
if cv == nil {
|
||||
return
|
||||
}
|
||||
w, h := float64(cv.Width()), float64(cv.Height())
|
||||
|
||||
cv.SetFillStyle("#000")
|
||||
cv.FillRect(0, 0, w, h)
|
||||
cv.SetFillStyle("#0F0")
|
||||
cv.FillRect(w*0.25, h*0.25, w*0.5, h*0.5)
|
||||
cv.SetLineWidth(6)
|
||||
sqrSize := math.Min(w, h) * 0.1
|
||||
cv.SetStrokeStyle("#F00")
|
||||
cv.StrokeRect(mx-sqrSize/2, my-sqrSize/2, sqrSize, sqrSize)
|
||||
}
|
|
@ -1,125 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"log"
|
||||
"math"
|
||||
|
||||
"github.com/tfriedel6/canvas"
|
||||
"github.com/tfriedel6/canvas/sdlcanvas"
|
||||
)
|
||||
|
||||
func main() {
|
||||
wnd, cv, err := sdlcanvas.CreateWindow(1280, 720, "Canvas Example")
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
defer wnd.Destroy()
|
||||
|
||||
lg := canvas.NewLinearGradient(320, 200, 480, 520)
|
||||
lg.AddColorStop(0, "#ff000040")
|
||||
lg.AddColorStop(1, "#00ff0040")
|
||||
lg.AddColorStop(0.5, "#0000ff40")
|
||||
|
||||
rg := canvas.NewRadialGradient(540, 300, 80, 740, 300, 100)
|
||||
rg.AddColorStop(0, "#ff0000")
|
||||
rg.AddColorStop(1, "#00ff00")
|
||||
rg.AddColorStop(0.5, "#0000ff")
|
||||
|
||||
wnd.SizeChange = func(w, h int) {
|
||||
cv.SetBounds(0, 0, w, h)
|
||||
}
|
||||
|
||||
wnd.MainLoop(func() {
|
||||
w, h := float64(cv.Width()), float64(cv.Height())
|
||||
|
||||
// Clear the screen
|
||||
cv.SetFillStyle("#000")
|
||||
cv.FillRect(0, 0, w, h)
|
||||
|
||||
// Draw lines with different colors and line thickness
|
||||
for x := 1.0; x < 10.5; x += 1.0 {
|
||||
cv.SetStrokeStyle(int(x*25), 255, 255)
|
||||
cv.SetLineWidth(x)
|
||||
cv.BeginPath()
|
||||
cv.MoveTo(x*10+20, 20)
|
||||
cv.LineTo(x*10+20, 120)
|
||||
cv.Stroke()
|
||||
}
|
||||
|
||||
// Draw a path
|
||||
cv.BeginPath()
|
||||
cv.MoveTo(160, 20)
|
||||
cv.LineTo(180, 20)
|
||||
cv.LineTo(180, 40)
|
||||
cv.LineTo(200, 40)
|
||||
cv.LineTo(200, 60)
|
||||
cv.LineTo(220, 60)
|
||||
cv.LineTo(220, 80)
|
||||
cv.LineTo(240, 80)
|
||||
cv.LineTo(240, 100)
|
||||
cv.LineTo(260, 100)
|
||||
cv.LineTo(260, 120)
|
||||
cv.ArcTo(160, 120, 160, 100, 20)
|
||||
cv.ClosePath()
|
||||
cv.SetStrokeStyle(color.RGBA{R: 255, G: 128, B: 128, A: 255})
|
||||
cv.SetLineWidth(4)
|
||||
cv.Stroke()
|
||||
|
||||
// Fill a polygon
|
||||
cv.BeginPath()
|
||||
cv.MoveTo(300, 20)
|
||||
cv.LineTo(340, 20)
|
||||
cv.QuadraticCurveTo(370, 20, 370, 50)
|
||||
cv.QuadraticCurveTo(370, 80, 400, 80)
|
||||
cv.LineTo(400, 80)
|
||||
cv.LineTo(400, 120)
|
||||
cv.LineTo(360, 120)
|
||||
cv.BezierCurveTo(330, 120, 330, 80, 300, 80)
|
||||
cv.ClosePath()
|
||||
cv.SetFillStyle(color.RGBA{R: 128, G: 255, B: 128, A: 255})
|
||||
cv.Fill()
|
||||
|
||||
// Draw with alpha
|
||||
cv.SetGlobalAlpha(0.5)
|
||||
cv.SetFillStyle("#FF0000")
|
||||
cv.BeginPath()
|
||||
cv.Arc(100, 275, 60, 0, math.Pi*2, false)
|
||||
cv.Fill()
|
||||
cv.SetFillStyle("#00FF00")
|
||||
cv.BeginPath()
|
||||
cv.Arc(140, 210, 60, 0, math.Pi*2, false)
|
||||
cv.Fill()
|
||||
cv.SetFillStyle("#0000FF")
|
||||
cv.BeginPath()
|
||||
cv.Arc(180, 275, 60, 0, math.Pi*2, false)
|
||||
cv.Fill()
|
||||
cv.SetGlobalAlpha(1)
|
||||
|
||||
// Clipped drawing
|
||||
cv.Save()
|
||||
cv.BeginPath()
|
||||
cv.Arc(340, 240, 80, 0, math.Pi*2, true)
|
||||
cv.Clip()
|
||||
cv.SetStrokeStyle(0, 255, 0)
|
||||
for x := 1.0; x < 12.5; x += 1.0 {
|
||||
cv.BeginPath()
|
||||
cv.MoveTo(260, 140+16*x)
|
||||
cv.LineTo(420, 140+16*x)
|
||||
cv.Stroke()
|
||||
}
|
||||
cv.SetFillStyle(0, 0, 255)
|
||||
for x := 1.0; x < 12.5; x += 1.0 {
|
||||
cv.FillRect(246+x*14, 150, 6, 180)
|
||||
}
|
||||
cv.Restore()
|
||||
|
||||
// Draw images
|
||||
cv.DrawImage("cat.jpg", 480, 40, 320, 265)
|
||||
|
||||
// Draw text
|
||||
cv.SetFont("Righteous-Regular.ttf", 40)
|
||||
cv.FillText("<-- Cat", 820, 180)
|
||||
})
|
||||
}
|
|
@ -1,80 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"github.com/tfriedel6/canvas/sdlcanvas"
|
||||
)
|
||||
|
||||
type circle struct {
|
||||
x, y float64
|
||||
color string
|
||||
}
|
||||
|
||||
func main() {
|
||||
wnd, cv, err := sdlcanvas.CreateWindow(1280, 720, "Canvas Example")
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
defer wnd.Destroy()
|
||||
|
||||
var mx, my, action float64
|
||||
circles := make([]circle, 0, 100)
|
||||
|
||||
wnd.MouseMove = func(x, y int) {
|
||||
mx, my = float64(x), float64(y)
|
||||
}
|
||||
wnd.MouseDown = func(button, x, y int) {
|
||||
action = 1
|
||||
circles = append(circles, circle{x: mx, y: my, color: "#F00"})
|
||||
}
|
||||
wnd.KeyDown = func(scancode int, rn rune, name string) {
|
||||
switch name {
|
||||
case "Escape":
|
||||
wnd.Close()
|
||||
case "Space":
|
||||
action = 1
|
||||
circles = append(circles, circle{x: mx, y: my, color: "#0F0"})
|
||||
case "Enter":
|
||||
action = 1
|
||||
circles = append(circles, circle{x: mx, y: my, color: "#00F"})
|
||||
}
|
||||
}
|
||||
wnd.SizeChange = func(w, h int) {
|
||||
cv.SetBounds(0, 0, w, h)
|
||||
}
|
||||
|
||||
lastTime := time.Now()
|
||||
|
||||
wnd.MainLoop(func() {
|
||||
now := time.Now()
|
||||
diff := now.Sub(lastTime)
|
||||
lastTime = now
|
||||
action -= diff.Seconds() * 3
|
||||
action = math.Max(0, action)
|
||||
|
||||
w, h := float64(cv.Width()), float64(cv.Height())
|
||||
|
||||
// Clear the screen
|
||||
cv.SetFillStyle("#000")
|
||||
cv.FillRect(0, 0, w, h)
|
||||
|
||||
// Draw a circle around the cursor
|
||||
cv.SetStrokeStyle("#F00")
|
||||
cv.SetLineWidth(6)
|
||||
cv.BeginPath()
|
||||
cv.Arc(mx, my, 24+action*24, 0, math.Pi*2, false)
|
||||
cv.Stroke()
|
||||
|
||||
// Draw circles where the user has clicked
|
||||
for _, circle := range circles {
|
||||
cv.SetFillStyle(circle.color)
|
||||
cv.BeginPath()
|
||||
cv.Arc(circle.x, circle.y, 24, 0, math.Pi*2, false)
|
||||
cv.Fill()
|
||||
}
|
||||
})
|
||||
}
|
|
@ -1,82 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"runtime"
|
||||
|
||||
"github.com/go-gl/gl/v3.2-core/gl"
|
||||
"github.com/go-gl/glfw/v3.2/glfw"
|
||||
"github.com/tfriedel6/canvas"
|
||||
"github.com/tfriedel6/canvas/glimpl/gogl"
|
||||
)
|
||||
|
||||
func main() {
|
||||
runtime.LockOSThread()
|
||||
|
||||
// init GLFW
|
||||
err := glfw.Init()
|
||||
if err != nil {
|
||||
log.Fatalf("Error initializing GLFW: %v", err)
|
||||
}
|
||||
defer glfw.Terminate()
|
||||
|
||||
// the stencil size setting is required for the canvas to work
|
||||
glfw.WindowHint(glfw.StencilBits, 8)
|
||||
glfw.WindowHint(glfw.DepthBits, 0)
|
||||
|
||||
// create window
|
||||
window, err := glfw.CreateWindow(1280, 720, "GLFW Test", nil, nil)
|
||||
if err != nil {
|
||||
log.Fatalf("Error creating window: %v", err)
|
||||
}
|
||||
window.MakeContextCurrent()
|
||||
|
||||
// init GL
|
||||
err = gl.Init()
|
||||
if err != nil {
|
||||
log.Fatalf("Error initializing GL: %v", err)
|
||||
}
|
||||
|
||||
// set vsync on, enable multisample (if available)
|
||||
glfw.SwapInterval(1)
|
||||
gl.Enable(gl.MULTISAMPLE)
|
||||
|
||||
// load canvas GL assets
|
||||
err = canvas.LoadGL(glimplgogl.GLImpl{})
|
||||
if err != nil {
|
||||
log.Fatalf("Error loading canvas GL assets: %v", err)
|
||||
}
|
||||
|
||||
window.SetCursorPosCallback(func(w *glfw.Window, xpos float64, ypos float64) {
|
||||
mx, my = xpos, ypos
|
||||
})
|
||||
|
||||
// initialize canvas with zero size, since size is set in main loop
|
||||
cv := canvas.New(0, 0, 0, 0)
|
||||
|
||||
for !window.ShouldClose() {
|
||||
window.MakeContextCurrent()
|
||||
glfw.PollEvents()
|
||||
|
||||
// set canvas size
|
||||
ww, wh := window.GetSize()
|
||||
cv.SetBounds(0, 0, ww, wh)
|
||||
|
||||
// call the run function to do all the drawing
|
||||
run(cv, float64(ww), float64(wh))
|
||||
|
||||
// swap back and front buffer
|
||||
window.SwapBuffers()
|
||||
}
|
||||
}
|
||||
|
||||
var mx, my float64
|
||||
|
||||
func run(cv *canvas.Canvas, w, h float64) {
|
||||
cv.SetFillStyle("#000")
|
||||
cv.FillRect(0, 0, w, h)
|
||||
cv.SetFillStyle("#00F")
|
||||
cv.FillRect(w*0.25, h*0.25, w*0.5, h*0.5)
|
||||
cv.SetStrokeStyle("#0F0")
|
||||
cv.StrokeRect(mx-32, my-32, 64, 64)
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"github.com/tfriedel6/canvas"
|
||||
"github.com/tfriedel6/canvas/glimpl/xmobile"
|
||||
"golang.org/x/mobile/app"
|
||||
"golang.org/x/mobile/event/lifecycle"
|
||||
"golang.org/x/mobile/event/paint"
|
||||
"golang.org/x/mobile/event/size"
|
||||
"golang.org/x/mobile/gl"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app.Main(func(a app.App) {
|
||||
var cv, painter *canvas.Canvas
|
||||
var w, h int
|
||||
|
||||
var glctx gl.Context
|
||||
for e := range a.Events() {
|
||||
switch e := a.Filter(e).(type) {
|
||||
case lifecycle.Event:
|
||||
switch e.Crosses(lifecycle.StageVisible) {
|
||||
case lifecycle.CrossOn:
|
||||
glctx, _ = e.DrawContext.(gl.Context)
|
||||
canvas.LoadGL(glimplxmobile.New(glctx))
|
||||
cv = canvas.NewOffscreen(0, 0)
|
||||
painter = canvas.New(0, 0, 0, 0)
|
||||
a.Send(paint.Event{})
|
||||
case lifecycle.CrossOff:
|
||||
glctx = nil
|
||||
}
|
||||
case size.Event:
|
||||
w, h = e.WidthPx, e.HeightPx
|
||||
case paint.Event:
|
||||
if glctx != nil {
|
||||
glctx.ClearColor(0, 0, 0, 0)
|
||||
glctx.Clear(gl.COLOR_BUFFER_BIT)
|
||||
|
||||
cv.SetBounds(0, 0, w, h)
|
||||
painter.SetBounds(0, 0, w, h)
|
||||
|
||||
fw, fh := float64(w), float64(h)
|
||||
color := math.Sin(float64(time.Now().UnixNano())*0.000000002)*0.3 + 0.7
|
||||
|
||||
cv.SetFillStyle(color*0.2, color*0.2, color*0.8)
|
||||
cv.FillRect(fw*0.25, fh*0.25, fw*0.5, fh*0.5)
|
||||
|
||||
painter.DrawImage(cv)
|
||||
|
||||
a.Publish()
|
||||
a.Send(paint.Event{})
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
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,5 +0,0 @@
|
|||
Run this command:
|
||||
|
||||
gomobile bind -target ios
|
||||
|
||||
Then add the resulting Example.framework into the Xcode project, and it should compile and run from there
|
|
@ -1,41 +0,0 @@
|
|||
package example
|
||||
|
||||
import (
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"github.com/tfriedel6/canvas"
|
||||
"github.com/tfriedel6/canvas/glimpl/ios"
|
||||
)
|
||||
|
||||
var cv *canvas.Canvas
|
||||
var mx, my float64
|
||||
|
||||
func TouchEvent(typ string, x, y int) {
|
||||
mx, my = float64(x), float64(y)
|
||||
}
|
||||
|
||||
func LoadGL(w, h int) {
|
||||
err := canvas.LoadGL(glimplios.GLImpl{})
|
||||
if err != nil {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
panic(err)
|
||||
}
|
||||
cv = canvas.New(0, 0, w, h)
|
||||
}
|
||||
|
||||
func DrawFrame() {
|
||||
if cv == nil {
|
||||
return
|
||||
}
|
||||
w, h := float64(cv.Width()), float64(cv.Height())
|
||||
|
||||
cv.SetFillStyle("#000")
|
||||
cv.FillRect(0, 0, w, h)
|
||||
cv.SetFillStyle("#0F0")
|
||||
cv.FillRect(w*0.25, h*0.25, w*0.5, h*0.5)
|
||||
cv.SetLineWidth(6)
|
||||
sqrSize := math.Min(w, h) * 0.1
|
||||
cv.SetStrokeStyle("#F00")
|
||||
cv.StrokeRect(mx-sqrSize/2, my-sqrSize/2, sqrSize, sqrSize)
|
||||
}
|
|
@ -1,123 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/go-gl/gl/v3.2-core/gl"
|
||||
"github.com/tfriedel6/canvas"
|
||||
"github.com/tfriedel6/canvas/glimpl/gogl"
|
||||
"github.com/veandco/go-sdl2/sdl"
|
||||
)
|
||||
|
||||
func main() {
|
||||
runtime.LockOSThread()
|
||||
|
||||
// init SDL
|
||||
err := sdl.Init(sdl.INIT_VIDEO)
|
||||
if err != nil {
|
||||
log.Fatalf("Error initializing SDL: %v", err)
|
||||
}
|
||||
defer sdl.Quit()
|
||||
|
||||
// the stencil size setting is required for the canvas to work
|
||||
sdl.GLSetAttribute(sdl.GL_RED_SIZE, 8)
|
||||
sdl.GLSetAttribute(sdl.GL_GREEN_SIZE, 8)
|
||||
sdl.GLSetAttribute(sdl.GL_BLUE_SIZE, 8)
|
||||
sdl.GLSetAttribute(sdl.GL_ALPHA_SIZE, 8)
|
||||
sdl.GLSetAttribute(sdl.GL_DEPTH_SIZE, 0)
|
||||
sdl.GLSetAttribute(sdl.GL_STENCIL_SIZE, 8)
|
||||
sdl.GLSetAttribute(sdl.GL_DOUBLEBUFFER, 1)
|
||||
sdl.GLSetAttribute(sdl.GL_MULTISAMPLEBUFFERS, 1)
|
||||
sdl.GLSetAttribute(sdl.GL_MULTISAMPLESAMPLES, 4)
|
||||
|
||||
// create window
|
||||
const title = "SDL Test"
|
||||
window, err := sdl.CreateWindow(title, sdl.WINDOWPOS_CENTERED, sdl.WINDOWPOS_CENTERED, 1280, 720, sdl.WINDOW_RESIZABLE|sdl.WINDOW_OPENGL)
|
||||
if err != nil {
|
||||
// fallback in case multisample is not available
|
||||
sdl.GLSetAttribute(sdl.GL_MULTISAMPLEBUFFERS, 0)
|
||||
sdl.GLSetAttribute(sdl.GL_MULTISAMPLESAMPLES, 0)
|
||||
window, err = sdl.CreateWindow(title, sdl.WINDOWPOS_CENTERED, sdl.WINDOWPOS_CENTERED, 1280, 720, sdl.WINDOW_RESIZABLE|sdl.WINDOW_OPENGL)
|
||||
if err != nil {
|
||||
log.Fatalf("Error creating window: %v", err)
|
||||
}
|
||||
}
|
||||
defer window.Destroy()
|
||||
|
||||
// create GL context
|
||||
glContext, err := window.GLCreateContext()
|
||||
if err != nil {
|
||||
log.Fatalf("Error creating GL context: %v", err)
|
||||
}
|
||||
|
||||
// init GL
|
||||
err = gl.Init()
|
||||
if err != nil {
|
||||
log.Fatalf("Error initializing GL: %v", err)
|
||||
}
|
||||
|
||||
// enable vsync and multisample (if available)
|
||||
sdl.GLSetSwapInterval(1)
|
||||
gl.Enable(gl.MULTISAMPLE)
|
||||
|
||||
// load canvas GL assets
|
||||
err = canvas.LoadGL(glimplgogl.GLImpl{})
|
||||
if err != nil {
|
||||
log.Fatalf("Error loading canvas GL assets: %v", err)
|
||||
}
|
||||
|
||||
// initialize canvas with zero size, since size is set in main loop
|
||||
cv := canvas.New(0, 0, 0, 0)
|
||||
|
||||
for running := true; running; {
|
||||
err := window.GLMakeCurrent(glContext)
|
||||
if err != nil {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
|
||||
// handle events
|
||||
for {
|
||||
event := sdl.PollEvent()
|
||||
if event == nil {
|
||||
break
|
||||
}
|
||||
|
||||
switch e := event.(type) {
|
||||
case *sdl.KeyboardEvent:
|
||||
if e.Type == sdl.KEYDOWN && e.Keysym.Scancode == sdl.SCANCODE_ESCAPE {
|
||||
running = false
|
||||
}
|
||||
case *sdl.MouseMotionEvent:
|
||||
mx, my = float64(e.X), float64(e.Y)
|
||||
case *sdl.WindowEvent:
|
||||
if e.Type == sdl.WINDOWEVENT_CLOSE {
|
||||
running = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// set canvas size
|
||||
ww, wh := window.GetSize()
|
||||
cv.SetBounds(0, 0, int(ww), int(wh))
|
||||
|
||||
// call the run function to do all the drawing
|
||||
run(cv, float64(ww), float64(wh))
|
||||
|
||||
// swap back and front buffer
|
||||
window.GLSwap()
|
||||
}
|
||||
}
|
||||
|
||||
var mx, my float64
|
||||
|
||||
func run(cv *canvas.Canvas, w, h float64) {
|
||||
cv.SetFillStyle("#000")
|
||||
cv.FillRect(0, 0, w, h)
|
||||
cv.SetFillStyle("#0F0")
|
||||
cv.FillRect(w*0.25, h*0.25, w*0.5, h*0.5)
|
||||
cv.SetStrokeStyle("#00F")
|
||||
cv.StrokeRect(mx-32, my-32, 64, 64)
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/tfriedel6/canvas"
|
||||
"github.com/tfriedel6/canvas/glimpl/xmobile"
|
||||
"golang.org/x/exp/shiny/driver/gldriver"
|
||||
"golang.org/x/exp/shiny/screen"
|
||||
"golang.org/x/exp/shiny/widget"
|
||||
"golang.org/x/exp/shiny/widget/glwidget"
|
||||
"golang.org/x/exp/shiny/widget/node"
|
||||
)
|
||||
|
||||
var cv *canvas.Canvas
|
||||
var sheet *widget.Sheet
|
||||
|
||||
func main() {
|
||||
gldriver.Main(func(s screen.Screen) {
|
||||
glw := glwidget.NewGL(draw)
|
||||
sheet = widget.NewSheet(glw)
|
||||
canvas.LoadGL(glimplxmobile.New(glw.Ctx))
|
||||
cv = canvas.New(0, 0, 600, 600)
|
||||
|
||||
err := widget.RunWindow(s, sheet, &widget.RunWindowOptions{
|
||||
NewWindowOptions: screen.NewWindowOptions{
|
||||
Title: "Shiny Canvas Example",
|
||||
Width: 600,
|
||||
Height: 600,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func draw(w *glwidget.GL) {
|
||||
cv.Save()
|
||||
defer cv.Restore()
|
||||
|
||||
cv.Translate(0, 600)
|
||||
cv.Scale(1, -1)
|
||||
|
||||
cv.ClearRect(0, 0, 600, 600)
|
||||
cv.SetFillStyle("#FF00FF")
|
||||
cv.FillRect(100, 100, 200, 200)
|
||||
|
||||
w.Publish()
|
||||
w.Mark(node.MarkNeedsPaintBase)
|
||||
}
|
43
examples/software/software.go
Normal file
|
@ -0,0 +1,43 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"image/png"
|
||||
"math"
|
||||
"os"
|
||||
|
||||
"git.mstar.dev/mstar/canvas"
|
||||
"git.mstar.dev/mstar/canvas/backend/softwarebackend"
|
||||
)
|
||||
|
||||
func main() {
|
||||
backend := softwarebackend.New(720, 720)
|
||||
cv := canvas.New(backend)
|
||||
|
||||
w, h := float64(cv.Width()), float64(cv.Height())
|
||||
cv.SetFillStyle("#000")
|
||||
cv.FillRect(0, 0, w, h)
|
||||
|
||||
for r := 0.0; r < math.Pi*2; r += math.Pi * 0.1 {
|
||||
cv.SetFillStyle(int(r*10), int(r*20), int(r*40))
|
||||
cv.BeginPath()
|
||||
cv.MoveTo(w*0.5, h*0.5)
|
||||
cv.Arc(w*0.5, h*0.5, math.Min(w, h)*0.4, r, r+0.1*math.Pi, false)
|
||||
cv.ClosePath()
|
||||
cv.Fill()
|
||||
}
|
||||
|
||||
cv.SetStrokeStyle("#FFF")
|
||||
cv.SetLineWidth(10)
|
||||
cv.BeginPath()
|
||||
cv.Arc(w*0.5, h*0.5, math.Min(w, h)*0.4, 0, math.Pi*2, false)
|
||||
cv.Stroke()
|
||||
|
||||
f, err := os.OpenFile("result.png", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0777)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = png.Encode(f, backend.Image)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
145
freetype.go
|
@ -8,7 +8,6 @@ package canvas
|
|||
import (
|
||||
"errors"
|
||||
"image"
|
||||
"image/draw"
|
||||
|
||||
"github.com/golang/freetype/raster"
|
||||
"github.com/golang/freetype/truetype"
|
||||
|
@ -42,16 +41,9 @@ type frContext struct {
|
|||
r *raster.Rasterizer
|
||||
f *truetype.Font
|
||||
glyphBuf truetype.GlyphBuf
|
||||
// clip is the clip rectangle for drawing.
|
||||
clip image.Rectangle
|
||||
// dst and src are the destination and source images for drawing.
|
||||
dst draw.Image
|
||||
src image.Image
|
||||
// fontSize and dpi are used to calculate scale. scale is the number of
|
||||
// 26.6 fixed point units in 1 em. hinting is the hinting policy.
|
||||
fontSize, dpi float64
|
||||
scale fixed.Int26_6
|
||||
hinting font.Hinting
|
||||
|
||||
fontSize fixed.Int26_6
|
||||
hinting font.Hinting
|
||||
// cache is the glyph cache.
|
||||
cache [nGlyphs * nXFractions * nYFractions]cacheEntry
|
||||
}
|
||||
|
@ -135,7 +127,7 @@ func (c *frContext) drawContour(ps []truetype.Point, dx, dy fixed.Int26_6) {
|
|||
// The 26.6 fixed point arguments fx and fy must be in the range [0, 1).
|
||||
func (c *frContext) rasterize(glyph truetype.Index, fx, fy fixed.Int26_6) (fixed.Int26_6, *image.Alpha, image.Point, error) {
|
||||
|
||||
if err := c.glyphBuf.Load(c.f, c.scale, glyph, c.hinting); err != nil {
|
||||
if err := c.glyphBuf.Load(c.f, c.fontSize, glyph, c.hinting); err != nil {
|
||||
return 0, nil, image.Point{}, err
|
||||
}
|
||||
// Calculate the integer-pixel bounds for the glyph.
|
||||
|
@ -193,14 +185,14 @@ func (c *frContext) glyph(glyph truetype.Index, p fixed.Point26_6) (fixed.Int26_
|
|||
}
|
||||
|
||||
func (c *frContext) glyphAdvance(glyph truetype.Index) (fixed.Int26_6, error) {
|
||||
if err := c.glyphBuf.Load(c.f, c.scale, glyph, c.hinting); err != nil {
|
||||
if err := c.glyphBuf.Load(c.f, c.fontSize, glyph, c.hinting); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return c.glyphBuf.AdvanceWidth, nil
|
||||
}
|
||||
|
||||
func (c *frContext) glyphMeasure(glyph truetype.Index, p fixed.Point26_6) (fixed.Int26_6, image.Rectangle, error) {
|
||||
if err := c.glyphBuf.Load(c.f, c.scale, glyph, c.hinting); err != nil {
|
||||
if err := c.glyphBuf.Load(c.f, c.fontSize, glyph, c.hinting); err != nil {
|
||||
return 0, image.Rectangle{}, err
|
||||
}
|
||||
|
||||
|
@ -224,67 +216,12 @@ func (c *frContext) glyphBounds(glyph truetype.Index, p fixed.Point26_6) (image.
|
|||
|
||||
const maxInt = int(^uint(0) >> 1)
|
||||
|
||||
// DrawString draws s at p and returns p advanced by the text extent. The text
|
||||
// is placed so that the left edge of the em square of the first character of s
|
||||
// and the baseline intersect at p. The majority of the affected pixels will be
|
||||
// above and to the right of the point, but some may be below or to the left.
|
||||
// For example, drawing a string that starts with a 'J' in an italic font may
|
||||
// affect pixels below and left of the point.
|
||||
//
|
||||
// p is a fixed.Point26_6 and can therefore represent sub-pixel positions.
|
||||
func (c *frContext) drawString(s string, p fixed.Point26_6) (fixed.Point26_6, image.Rectangle, error) {
|
||||
if c.f == nil {
|
||||
return fixed.Point26_6{}, image.Rectangle{}, errors.New("freetype: DrawText called with a nil font")
|
||||
}
|
||||
bounds := image.Rectangle{Min: image.Point{X: maxInt, Y: maxInt}}
|
||||
prev, hasPrev := truetype.Index(0), false
|
||||
for _, rune := range s {
|
||||
index := c.f.Index(rune)
|
||||
if hasPrev {
|
||||
kern := c.f.Kern(c.scale, prev, index)
|
||||
if c.hinting != font.HintingNone {
|
||||
kern = (kern + 32) &^ 63
|
||||
}
|
||||
p.X += kern
|
||||
}
|
||||
advanceWidth, mask, offset, err := c.glyph(index, p)
|
||||
if err != nil {
|
||||
return fixed.Point26_6{}, image.Rectangle{}, err
|
||||
}
|
||||
p.X += advanceWidth
|
||||
glyphRect := mask.Bounds().Add(offset)
|
||||
if glyphRect.Min.X < bounds.Min.X {
|
||||
bounds.Min.X = glyphRect.Min.X
|
||||
}
|
||||
if glyphRect.Min.Y < bounds.Min.Y {
|
||||
bounds.Min.Y = glyphRect.Min.Y
|
||||
}
|
||||
if glyphRect.Max.X > bounds.Max.X {
|
||||
bounds.Max.X = glyphRect.Max.X
|
||||
}
|
||||
if glyphRect.Max.Y > bounds.Max.Y {
|
||||
bounds.Max.Y = glyphRect.Max.Y
|
||||
}
|
||||
dr := c.clip.Intersect(glyphRect)
|
||||
if !dr.Empty() {
|
||||
mp := image.Point{0, dr.Min.Y - glyphRect.Min.Y}
|
||||
draw.DrawMask(c.dst, dr, c.src, image.ZP, mask, mp, draw.Src)
|
||||
}
|
||||
prev, hasPrev = index, true
|
||||
}
|
||||
bounds = c.clip.Intersect(bounds)
|
||||
return p, bounds, nil
|
||||
}
|
||||
|
||||
// recalc recalculates scale and bounds values from the font size, screen
|
||||
// resolution and font metrics, and invalidates the glyph cache.
|
||||
func (c *frContext) recalc() {
|
||||
c.scale = fixed.Int26_6(c.fontSize * c.dpi * (64.0 / 72.0))
|
||||
if c.f == nil {
|
||||
c.r.SetBounds(0, 0)
|
||||
} else {
|
||||
// Set the rasterizer's bounds to be big enough to handle the largest glyph.
|
||||
b := c.f.Bounds(c.scale)
|
||||
b := c.f.Bounds(c.fontSize)
|
||||
xmin := +int(b.Min.X) >> 6
|
||||
ymin := -int(b.Max.Y) >> 6
|
||||
xmax := +int(b.Max.X+63) >> 6
|
||||
|
@ -296,65 +233,25 @@ func (c *frContext) recalc() {
|
|||
}
|
||||
}
|
||||
|
||||
// SetDPI sets the screen resolution in dots per inch.
|
||||
func (c *frContext) setDPI(dpi float64) {
|
||||
if c.dpi == dpi {
|
||||
return
|
||||
func (c *frContext) cacheSize() int {
|
||||
if c.f == nil {
|
||||
return 0
|
||||
}
|
||||
c.dpi = dpi
|
||||
c.recalc()
|
||||
|
||||
b := c.f.Bounds(c.fontSize)
|
||||
xmin := +int(b.Min.X) >> 6
|
||||
ymin := -int(b.Max.Y) >> 6
|
||||
xmax := +int(b.Max.X+63) >> 6
|
||||
ymax := -int(b.Min.Y-63) >> 6
|
||||
w := xmax - xmin
|
||||
h := ymax - ymin
|
||||
return w * h * len(c.cache)
|
||||
}
|
||||
|
||||
// SetFont sets the font used to draw text.
|
||||
func (c *frContext) setFont(f *truetype.Font) {
|
||||
if c.f == f {
|
||||
return
|
||||
}
|
||||
c.f = f
|
||||
c.recalc()
|
||||
}
|
||||
|
||||
// SetFontSize sets the font size in points (as in "a 12 point font").
|
||||
func (c *frContext) setFontSize(fontSize float64) {
|
||||
if c.fontSize == fontSize {
|
||||
return
|
||||
}
|
||||
c.fontSize = fontSize
|
||||
c.recalc()
|
||||
}
|
||||
|
||||
// SetHinting sets the hinting policy.
|
||||
func (c *frContext) setHinting(hinting font.Hinting) {
|
||||
c.hinting = hinting
|
||||
for i := range c.cache {
|
||||
c.cache[i] = cacheEntry{}
|
||||
}
|
||||
}
|
||||
|
||||
// SetDst sets the destination image for draw operations.
|
||||
func (c *frContext) setDst(dst draw.Image) {
|
||||
c.dst = dst
|
||||
}
|
||||
|
||||
// SetSrc sets the source image for draw operations. This is typically an
|
||||
// image.Uniform.
|
||||
func (c *frContext) setSrc(src image.Image) {
|
||||
c.src = src
|
||||
}
|
||||
|
||||
// SetClip sets the clip rectangle for drawing.
|
||||
func (c *frContext) setClip(clip image.Rectangle) {
|
||||
c.clip = clip
|
||||
}
|
||||
|
||||
// TODO(nigeltao): implement Context.SetGamma.
|
||||
|
||||
// NewContext creates a new Context.
|
||||
func newFRContext() *frContext {
|
||||
return &frContext{
|
||||
r: raster.NewRasterizer(0, 0),
|
||||
fontSize: 12,
|
||||
dpi: 72,
|
||||
scale: 12 << 6,
|
||||
fontSize: fixed.I(12),
|
||||
hinting: font.HintingFull,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,177 +0,0 @@
|
|||
package glfwcanvas
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
_ "image/gif" // Imported here so that applications based on this package support these formats by default
|
||||
_ "image/jpeg"
|
||||
_ "image/png"
|
||||
"log"
|
||||
"math"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/go-gl/gl/v3.2-core/gl"
|
||||
"github.com/go-gl/glfw/v3.2/glfw"
|
||||
"github.com/tfriedel6/canvas"
|
||||
"github.com/tfriedel6/canvas/glimpl/gogl"
|
||||
)
|
||||
|
||||
// Window represents the opened window with GL context. The Mouse* and Key*
|
||||
// functions can be set for callbacks
|
||||
type Window struct {
|
||||
Window *glfw.Window
|
||||
canvas *canvas.Canvas
|
||||
frameTimes [10]time.Time
|
||||
frameIndex int
|
||||
frameCount int
|
||||
fps float32
|
||||
close bool
|
||||
MouseDown func(button, x, y int)
|
||||
MouseMove func(x, y int)
|
||||
MouseUp func(button, x, y int)
|
||||
MouseWheel func(x, y int)
|
||||
KeyDown func(scancode int, rn rune, name string)
|
||||
KeyUp func(scancode int, rn rune, name string)
|
||||
KeyChar func(rn rune)
|
||||
SizeChange func(w, h int)
|
||||
}
|
||||
|
||||
// CreateWindow creates a window using SDL and initializes the OpenGL context
|
||||
func CreateWindow(w, h int, title string) (*Window, *canvas.Canvas, error) {
|
||||
runtime.LockOSThread()
|
||||
|
||||
// init GLFW
|
||||
err := glfw.Init()
|
||||
if err != nil {
|
||||
log.Fatalf("Error initializing GLFW: %v", err)
|
||||
}
|
||||
|
||||
// the stencil size setting is required for the canvas to work
|
||||
glfw.WindowHint(glfw.StencilBits, 8)
|
||||
glfw.WindowHint(glfw.DepthBits, 0)
|
||||
|
||||
// create window
|
||||
window, err := glfw.CreateWindow(w, h, title, nil, nil)
|
||||
if err != nil {
|
||||
log.Fatalf("Error creating window: %v", err)
|
||||
}
|
||||
window.MakeContextCurrent()
|
||||
|
||||
// init GL
|
||||
err = gl.Init()
|
||||
if err != nil {
|
||||
log.Fatalf("Error initializing GL: %v", err)
|
||||
}
|
||||
|
||||
// set vsync on, enable multisample (if available)
|
||||
glfw.SwapInterval(1)
|
||||
gl.Enable(gl.MULTISAMPLE)
|
||||
|
||||
// load canvas GL assets
|
||||
err = canvas.LoadGL(glimplgogl.GLImpl{})
|
||||
if err != nil {
|
||||
log.Fatalf("Error loading canvas GL assets: %v", err)
|
||||
}
|
||||
|
||||
err = canvas.LoadGL(glimplgogl.GLImpl{})
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("Error loading canvas GL assets: %v", err)
|
||||
}
|
||||
|
||||
cv := canvas.New(0, 0, w, h)
|
||||
wnd := &Window{
|
||||
Window: window,
|
||||
canvas: cv,
|
||||
}
|
||||
|
||||
var mx, my int
|
||||
|
||||
window.SetMouseButtonCallback(func(w *glfw.Window, button glfw.MouseButton, action glfw.Action, mod glfw.ModifierKey) {
|
||||
if action == glfw.Press && wnd.MouseDown != nil {
|
||||
wnd.MouseDown(int(button), mx, my)
|
||||
} else if action == glfw.Release && wnd.MouseUp != nil {
|
||||
wnd.MouseUp(int(button), mx, my)
|
||||
}
|
||||
})
|
||||
window.SetCursorPosCallback(func(w *glfw.Window, xpos, ypos float64) {
|
||||
mx, my = int(math.Round(xpos)), int(math.Round(ypos))
|
||||
if wnd.MouseMove != nil {
|
||||
wnd.MouseMove(mx, my)
|
||||
}
|
||||
})
|
||||
window.SetScrollCallback(func(w *glfw.Window, xoff, yoff float64) {
|
||||
if wnd.MouseWheel != nil {
|
||||
wnd.MouseWheel(int(math.Round(xoff)), int(math.Round(yoff)))
|
||||
}
|
||||
})
|
||||
window.SetKeyCallback(func(w *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) {
|
||||
if action == glfw.Press && wnd.KeyDown != nil {
|
||||
wnd.KeyDown(scancode, keyRune(key), keyName(key))
|
||||
} else if action == glfw.Release && wnd.KeyUp != nil {
|
||||
wnd.KeyUp(scancode, keyRune(key), keyName(key))
|
||||
}
|
||||
})
|
||||
window.SetCharCallback(func(w *glfw.Window, char rune) {
|
||||
if wnd.KeyChar != nil {
|
||||
wnd.KeyChar(char)
|
||||
}
|
||||
})
|
||||
window.SetSizeCallback(func(w *glfw.Window, width, height int) {
|
||||
if wnd.SizeChange != nil {
|
||||
wnd.SizeChange(width, height)
|
||||
} else {
|
||||
cv.SetBounds(0, 0, width, height)
|
||||
}
|
||||
})
|
||||
window.SetCloseCallback(func(w *glfw.Window) {
|
||||
wnd.Close()
|
||||
})
|
||||
|
||||
return wnd, cv, nil
|
||||
}
|
||||
|
||||
// FPS returns the frames per second (averaged over 10 frames)
|
||||
func (wnd *Window) FPS() float32 {
|
||||
return wnd.fps
|
||||
}
|
||||
|
||||
// Close can be used to end a call to MainLoop
|
||||
func (wnd *Window) Close() {
|
||||
wnd.close = true
|
||||
}
|
||||
|
||||
// StartFrame handles events and gets the window ready for rendering
|
||||
func (wnd *Window) StartFrame() {
|
||||
wnd.Window.MakeContextCurrent()
|
||||
glfw.PollEvents()
|
||||
}
|
||||
|
||||
// FinishFrame updates the FPS count and displays the frame
|
||||
func (wnd *Window) FinishFrame() {
|
||||
now := time.Now()
|
||||
wnd.frameTimes[wnd.frameIndex] = now
|
||||
wnd.frameIndex++
|
||||
wnd.frameIndex %= len(wnd.frameTimes)
|
||||
if wnd.frameCount < len(wnd.frameTimes) {
|
||||
wnd.frameCount++
|
||||
} else {
|
||||
diff := now.Sub(wnd.frameTimes[wnd.frameIndex]).Seconds()
|
||||
wnd.fps = float32(wnd.frameCount-1) / float32(diff)
|
||||
}
|
||||
|
||||
wnd.Window.SwapBuffers()
|
||||
}
|
||||
|
||||
// MainLoop runs a main loop and calls run on every frame
|
||||
func (wnd *Window) MainLoop(run func()) {
|
||||
for !wnd.close {
|
||||
wnd.StartFrame()
|
||||
run()
|
||||
wnd.FinishFrame()
|
||||
}
|
||||
}
|
||||
|
||||
// Size returns the current width and height of the window
|
||||
func (wnd *Window) Size() (int, int) {
|
||||
return wnd.Window.GetSize()
|
||||
}
|
|
@ -1,216 +0,0 @@
|
|||
package glfwcanvas
|
||||
|
||||
import "github.com/go-gl/glfw/v3.2/glfw"
|
||||
|
||||
var keyNameMap [347]string
|
||||
var keyRuneMap [347]rune
|
||||
|
||||
func init() {
|
||||
keyNameMap[glfw.KeyEscape] = "Escape"
|
||||
keyNameMap[glfw.Key0] = "Digit0"
|
||||
keyNameMap[glfw.Key1] = "Digit1"
|
||||
keyNameMap[glfw.Key2] = "Digit2"
|
||||
keyNameMap[glfw.Key3] = "Digit3"
|
||||
keyNameMap[glfw.Key4] = "Digit4"
|
||||
keyNameMap[glfw.Key5] = "Digit5"
|
||||
keyNameMap[glfw.Key6] = "Digit6"
|
||||
keyNameMap[glfw.Key7] = "Digit7"
|
||||
keyNameMap[glfw.Key8] = "Digit8"
|
||||
keyNameMap[glfw.Key9] = "Digit9"
|
||||
keyNameMap[glfw.KeyMinus] = "Minus"
|
||||
keyNameMap[glfw.KeyEqual] = "Equal"
|
||||
keyNameMap[glfw.KeyBackspace] = "Backspace"
|
||||
keyNameMap[glfw.KeyTab] = "Tab"
|
||||
keyNameMap[glfw.KeyQ] = "KeyQ"
|
||||
keyNameMap[glfw.KeyW] = "KeyW"
|
||||
keyNameMap[glfw.KeyE] = "KeyE"
|
||||
keyNameMap[glfw.KeyR] = "KeyR"
|
||||
keyNameMap[glfw.KeyT] = "KeyT"
|
||||
keyNameMap[glfw.KeyY] = "KeyY"
|
||||
keyNameMap[glfw.KeyU] = "KeyU"
|
||||
keyNameMap[glfw.KeyI] = "KeyI"
|
||||
keyNameMap[glfw.KeyO] = "KeyO"
|
||||
keyNameMap[glfw.KeyP] = "KeyP"
|
||||
keyNameMap[glfw.KeyLeftBracket] = "BracketLeft"
|
||||
keyNameMap[glfw.KeyRightBracket] = "BracketRight"
|
||||
keyNameMap[glfw.KeyEnter] = "Enter"
|
||||
keyNameMap[glfw.KeyLeftControl] = "ControlLeft"
|
||||
keyNameMap[glfw.KeyA] = "KeyA"
|
||||
keyNameMap[glfw.KeyS] = "KeyS"
|
||||
keyNameMap[glfw.KeyD] = "KeyD"
|
||||
keyNameMap[glfw.KeyF] = "KeyF"
|
||||
keyNameMap[glfw.KeyG] = "KeyG"
|
||||
keyNameMap[glfw.KeyH] = "KeyH"
|
||||
keyNameMap[glfw.KeyJ] = "KeyJ"
|
||||
keyNameMap[glfw.KeyK] = "KeyK"
|
||||
keyNameMap[glfw.KeyL] = "KeyL"
|
||||
keyNameMap[glfw.KeySemicolon] = "Semicolon"
|
||||
keyNameMap[glfw.KeyApostrophe] = "Quote"
|
||||
keyNameMap[glfw.KeyGraveAccent] = "Backquote"
|
||||
keyNameMap[glfw.KeyLeftShift] = "ShiftLeft"
|
||||
keyNameMap[glfw.KeyBackslash] = "Backslash"
|
||||
keyNameMap[glfw.KeyZ] = "KeyZ"
|
||||
keyNameMap[glfw.KeyX] = "KeyX"
|
||||
keyNameMap[glfw.KeyC] = "KeyC"
|
||||
keyNameMap[glfw.KeyV] = "KeyV"
|
||||
keyNameMap[glfw.KeyB] = "KeyB"
|
||||
keyNameMap[glfw.KeyN] = "KeyN"
|
||||
keyNameMap[glfw.KeyM] = "KeyM"
|
||||
keyNameMap[glfw.KeyComma] = "Comma"
|
||||
keyNameMap[glfw.KeyPeriod] = "Period"
|
||||
keyNameMap[glfw.KeySlash] = "Slash"
|
||||
keyNameMap[glfw.KeyRightShift] = "RightShift"
|
||||
keyNameMap[glfw.KeyKPMultiply] = "NumpadMultiply"
|
||||
keyNameMap[glfw.KeyLeftAlt] = "AltLeft"
|
||||
keyNameMap[glfw.KeySpace] = "Space"
|
||||
keyNameMap[glfw.KeyCapsLock] = "CapsLock"
|
||||
keyNameMap[glfw.KeyF1] = "F1"
|
||||
keyNameMap[glfw.KeyF2] = "F2"
|
||||
keyNameMap[glfw.KeyF3] = "F3"
|
||||
keyNameMap[glfw.KeyF4] = "F4"
|
||||
keyNameMap[glfw.KeyF5] = "F5"
|
||||
keyNameMap[glfw.KeyF6] = "F6"
|
||||
keyNameMap[glfw.KeyF7] = "F7"
|
||||
keyNameMap[glfw.KeyF8] = "F8"
|
||||
keyNameMap[glfw.KeyF9] = "F9"
|
||||
keyNameMap[glfw.KeyF10] = "F10"
|
||||
keyNameMap[glfw.KeyPause] = "Pause"
|
||||
keyNameMap[glfw.KeyScrollLock] = "ScrollLock"
|
||||
keyNameMap[glfw.KeyKP7] = "Numpad7"
|
||||
keyNameMap[glfw.KeyKP8] = "Numpad8"
|
||||
keyNameMap[glfw.KeyKP9] = "Numpad9"
|
||||
keyNameMap[glfw.KeyKPSubtract] = "NumpadSubtract"
|
||||
keyNameMap[glfw.KeyKP4] = "Numpad4"
|
||||
keyNameMap[glfw.KeyKP5] = "Numpad5"
|
||||
keyNameMap[glfw.KeyKP6] = "Numpad6"
|
||||
keyNameMap[glfw.KeyKPAdd] = "NumpadAdd"
|
||||
keyNameMap[glfw.KeyKP1] = "Numpad1"
|
||||
keyNameMap[glfw.KeyKP2] = "Numpad2"
|
||||
keyNameMap[glfw.KeyKP3] = "Numpad3"
|
||||
keyNameMap[glfw.KeyKP0] = "Numpad0"
|
||||
keyNameMap[glfw.KeyKPDecimal] = "NumpadDecimal"
|
||||
keyNameMap[glfw.KeyPrintScreen] = "PrintScreen"
|
||||
// keyNameMap[glfw.KeyNonUSBackslash] = "IntlBackslash"
|
||||
keyNameMap[glfw.KeyF11] = "F11"
|
||||
keyNameMap[glfw.KeyF12] = "F12"
|
||||
keyNameMap[glfw.KeyKPEqual] = "NumpadEqual"
|
||||
keyNameMap[glfw.KeyF13] = "F13"
|
||||
keyNameMap[glfw.KeyF14] = "F14"
|
||||
keyNameMap[glfw.KeyF15] = "F15"
|
||||
keyNameMap[glfw.KeyF16] = "F16"
|
||||
keyNameMap[glfw.KeyF17] = "F17"
|
||||
keyNameMap[glfw.KeyF18] = "F18"
|
||||
keyNameMap[glfw.KeyF19] = "F19"
|
||||
// keyNameMap[glfw.KeyUndo] = "Undo"
|
||||
// keyNameMap[glfw.KeyPaste] = "Paste"
|
||||
// keyNameMap[glfw.KeyAudioNext] = "MediaTrackPrevious"
|
||||
// keyNameMap[glfw.KeyCut] = "Cut"
|
||||
// keyNameMap[glfw.KeyCopy] = "Copy"
|
||||
// keyNameMap[glfw.KeyAudioNext] = "MediaTrackNext"
|
||||
keyNameMap[glfw.KeyKPEnter] = "NumpadEnter"
|
||||
keyNameMap[glfw.KeyRightControl] = "ControlRight"
|
||||
// keyNameMap[glfw.KeyMute] = "AudioVolumeMute"
|
||||
// keyNameMap[glfw.KeyAudioPlay] = "MediaPlayPause"
|
||||
// keyNameMap[glfw.KeyAudioStop] = "MediaStop"
|
||||
// keyNameMap[glfw.KeyVolumeDown] = "AudioVolumeDown"
|
||||
// keyNameMap[glfw.KeyVolumeUp] = "AudioVolumeUp"
|
||||
keyNameMap[glfw.KeyKPDivide] = "NumpadDivide"
|
||||
keyNameMap[glfw.KeyRightAlt] = "AltRight"
|
||||
// keyNameMap[glfw.KeyHelp] = "Help"
|
||||
keyNameMap[glfw.KeyHome] = "Home"
|
||||
keyNameMap[glfw.KeyUp] = "ArrowUp"
|
||||
keyNameMap[glfw.KeyPageUp] = "PageUp"
|
||||
keyNameMap[glfw.KeyLeft] = "ArrowLeft"
|
||||
keyNameMap[glfw.KeyRight] = "ArrowRight"
|
||||
keyNameMap[glfw.KeyEnd] = "End"
|
||||
keyNameMap[glfw.KeyDown] = "ArrowDown"
|
||||
keyNameMap[glfw.KeyInsert] = "Insert"
|
||||
keyNameMap[glfw.KeyDelete] = "Delete"
|
||||
// keyNameMap[glfw.KeyApplication] = "ContextMenu"
|
||||
|
||||
keyRuneMap[glfw.Key0] = '0'
|
||||
keyRuneMap[glfw.Key1] = '1'
|
||||
keyRuneMap[glfw.Key2] = '2'
|
||||
keyRuneMap[glfw.Key3] = '3'
|
||||
keyRuneMap[glfw.Key4] = '4'
|
||||
keyRuneMap[glfw.Key5] = '5'
|
||||
keyRuneMap[glfw.Key6] = '6'
|
||||
keyRuneMap[glfw.Key7] = '7'
|
||||
keyRuneMap[glfw.Key8] = '8'
|
||||
keyRuneMap[glfw.Key9] = '9'
|
||||
keyRuneMap[glfw.KeyMinus] = '-'
|
||||
keyRuneMap[glfw.KeyEqual] = '='
|
||||
keyRuneMap[glfw.KeyTab] = '\t'
|
||||
keyRuneMap[glfw.KeyQ] = 'Q'
|
||||
keyRuneMap[glfw.KeyW] = 'W'
|
||||
keyRuneMap[glfw.KeyE] = 'E'
|
||||
keyRuneMap[glfw.KeyR] = 'R'
|
||||
keyRuneMap[glfw.KeyT] = 'T'
|
||||
keyRuneMap[glfw.KeyY] = 'Y'
|
||||
keyRuneMap[glfw.KeyU] = 'U'
|
||||
keyRuneMap[glfw.KeyI] = 'I'
|
||||
keyRuneMap[glfw.KeyO] = 'O'
|
||||
keyRuneMap[glfw.KeyP] = 'P'
|
||||
keyRuneMap[glfw.KeyLeftBracket] = '['
|
||||
keyRuneMap[glfw.KeyRightBracket] = ']'
|
||||
keyRuneMap[glfw.KeyEnter] = '\n'
|
||||
keyRuneMap[glfw.KeyA] = 'A'
|
||||
keyRuneMap[glfw.KeyS] = 'S'
|
||||
keyRuneMap[glfw.KeyD] = 'D'
|
||||
keyRuneMap[glfw.KeyF] = 'F'
|
||||
keyRuneMap[glfw.KeyG] = 'G'
|
||||
keyRuneMap[glfw.KeyH] = 'H'
|
||||
keyRuneMap[glfw.KeyJ] = 'J'
|
||||
keyRuneMap[glfw.KeyK] = 'K'
|
||||
keyRuneMap[glfw.KeyL] = 'L'
|
||||
keyRuneMap[glfw.KeySemicolon] = ';'
|
||||
keyRuneMap[glfw.KeyApostrophe] = '\''
|
||||
keyRuneMap[glfw.KeyGraveAccent] = '`'
|
||||
keyRuneMap[glfw.KeyBackslash] = '\\'
|
||||
keyRuneMap[glfw.KeyZ] = 'Z'
|
||||
keyRuneMap[glfw.KeyX] = 'X'
|
||||
keyRuneMap[glfw.KeyC] = 'C'
|
||||
keyRuneMap[glfw.KeyV] = 'V'
|
||||
keyRuneMap[glfw.KeyB] = 'B'
|
||||
keyRuneMap[glfw.KeyN] = 'N'
|
||||
keyRuneMap[glfw.KeyM] = 'M'
|
||||
keyRuneMap[glfw.KeyComma] = ','
|
||||
keyRuneMap[glfw.KeyPeriod] = '.'
|
||||
keyRuneMap[glfw.KeySlash] = '/'
|
||||
keyRuneMap[glfw.KeyKPMultiply] = '*'
|
||||
keyRuneMap[glfw.KeySpace] = ' '
|
||||
keyRuneMap[glfw.KeyKP7] = '7'
|
||||
keyRuneMap[glfw.KeyKP8] = '8'
|
||||
keyRuneMap[glfw.KeyKP9] = '9'
|
||||
keyRuneMap[glfw.KeyKPSubtract] = '-'
|
||||
keyRuneMap[glfw.KeyKP4] = '4'
|
||||
keyRuneMap[glfw.KeyKP5] = '5'
|
||||
keyRuneMap[glfw.KeyKP6] = '6'
|
||||
keyRuneMap[glfw.KeyKPAdd] = '+'
|
||||
keyRuneMap[glfw.KeyKP1] = '1'
|
||||
keyRuneMap[glfw.KeyKP2] = '2'
|
||||
keyRuneMap[glfw.KeyKP3] = '3'
|
||||
keyRuneMap[glfw.KeyKP0] = '0'
|
||||
keyRuneMap[glfw.KeyKPDecimal] = '.'
|
||||
keyRuneMap[glfw.KeyKPEqual] = '='
|
||||
keyRuneMap[glfw.KeyKPEnter] = '\n'
|
||||
keyRuneMap[glfw.KeyKPDivide] = '/'
|
||||
}
|
||||
|
||||
func keyName(key glfw.Key) string {
|
||||
if int(key) >= len(keyNameMap) {
|
||||
return "Unidentified"
|
||||
}
|
||||
name := keyNameMap[key]
|
||||
if name == "" {
|
||||
return "Unidentified"
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
func keyRune(key glfw.Key) rune {
|
||||
if int(key) >= len(keyNameMap) {
|
||||
return 0
|
||||
}
|
||||
return keyRuneMap[key]
|
||||
}
|
|
@ -1,263 +0,0 @@
|
|||
package glimplandroid
|
||||
|
||||
// #include <stdlib.h>
|
||||
// #include <GLES2/gl2.h>
|
||||
// #cgo android LDFLAGS: -lGLESv2
|
||||
import "C"
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"unsafe"
|
||||
|
||||
"github.com/tfriedel6/canvas"
|
||||
)
|
||||
|
||||
type GLImpl struct{}
|
||||
|
||||
var _ canvas.GL = GLImpl{}
|
||||
|
||||
func (GLImpl) Ptr(data interface{}) unsafe.Pointer {
|
||||
if data == nil {
|
||||
return unsafe.Pointer(nil)
|
||||
}
|
||||
var addr unsafe.Pointer
|
||||
v := reflect.ValueOf(data)
|
||||
switch v.Type().Kind() {
|
||||
case reflect.Ptr:
|
||||
e := v.Elem()
|
||||
switch e.Kind() {
|
||||
case
|
||||
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
|
||||
reflect.Float32, reflect.Float64:
|
||||
addr = unsafe.Pointer(e.UnsafeAddr())
|
||||
default:
|
||||
panic(fmt.Errorf("unsupported pointer to type %s; must be a slice or pointer to a singular scalar value or the first element of an array or slice", e.Kind()))
|
||||
}
|
||||
case reflect.Uintptr:
|
||||
addr = unsafe.Pointer(v.Pointer())
|
||||
case reflect.Slice:
|
||||
addr = unsafe.Pointer(v.Index(0).UnsafeAddr())
|
||||
default:
|
||||
panic(fmt.Errorf("unsupported type %s; must be a slice or pointer to a singular scalar value or the first element of an array or slice", v.Type()))
|
||||
}
|
||||
return addr
|
||||
}
|
||||
func (GLImpl) ActiveTexture(texture uint32) {
|
||||
C.glActiveTexture(C.uint(texture))
|
||||
}
|
||||
func (GLImpl) AttachShader(program uint32, shader uint32) {
|
||||
C.glAttachShader(C.uint(program), C.uint(shader))
|
||||
}
|
||||
func (GLImpl) BindBuffer(target uint32, buffer uint32) {
|
||||
C.glBindBuffer(C.uint(target), C.uint(buffer))
|
||||
}
|
||||
func (GLImpl) BindFramebuffer(target uint32, framebuffer uint32) {
|
||||
C.glBindFramebuffer(C.uint(target), C.uint(framebuffer))
|
||||
}
|
||||
func (GLImpl) BindRenderbuffer(target uint32, renderbuffer uint32) {
|
||||
C.glBindRenderbuffer(C.uint(target), C.uint(renderbuffer))
|
||||
}
|
||||
func (GLImpl) BindTexture(target uint32, texture uint32) {
|
||||
C.glBindTexture(C.uint(target), C.uint(texture))
|
||||
}
|
||||
func (GLImpl) BlendFunc(sfactor uint32, dfactor uint32) {
|
||||
C.glBlendFunc(C.uint(sfactor), C.uint(dfactor))
|
||||
}
|
||||
func (GLImpl) BufferData(target uint32, size int, data unsafe.Pointer, usage uint32) {
|
||||
C.glBufferData(C.uint(target), C.long(size), data, C.uint(usage))
|
||||
}
|
||||
func (GLImpl) CheckFramebufferStatus(target uint32) uint32 {
|
||||
return uint32(C.glCheckFramebufferStatus(C.uint(target)))
|
||||
}
|
||||
func (GLImpl) Clear(mask uint32) {
|
||||
C.glClear(C.uint(mask))
|
||||
}
|
||||
func (GLImpl) ClearColor(red float32, green float32, blue float32, alpha float32) {
|
||||
C.glClearColor(C.float(red), C.float(green), C.float(blue), C.float(alpha))
|
||||
}
|
||||
func (GLImpl) ColorMask(red bool, green bool, blue bool, alpha bool) {
|
||||
var r, g, b, a C.uchar
|
||||
if red {
|
||||
r = 1
|
||||
}
|
||||
if green {
|
||||
g = 1
|
||||
}
|
||||
if blue {
|
||||
b = 1
|
||||
}
|
||||
if alpha {
|
||||
a = 1
|
||||
}
|
||||
C.glColorMask(r, g, b, a)
|
||||
}
|
||||
func (GLImpl) CompileShader(shader uint32) {
|
||||
C.glCompileShader(C.uint(shader))
|
||||
}
|
||||
func (GLImpl) CreateProgram() uint32 {
|
||||
return uint32(C.glCreateProgram())
|
||||
}
|
||||
func (GLImpl) CreateShader(xtype uint32) uint32 {
|
||||
return uint32(C.glCreateShader(C.uint(xtype)))
|
||||
}
|
||||
func (GLImpl) DeleteShader(shader uint32) {
|
||||
C.glDeleteShader(C.uint(shader))
|
||||
}
|
||||
func (GLImpl) DeleteFramebuffers(n int32, framebuffers *uint32) {
|
||||
C.glDeleteFramebuffers(C.int(n), (*C.uint)(framebuffers))
|
||||
}
|
||||
func (GLImpl) DeleteRenderbuffers(n int32, renderbuffers *uint32) {
|
||||
C.glDeleteRenderbuffers(C.int(n), (*C.uint)(renderbuffers))
|
||||
}
|
||||
func (GLImpl) DeleteTextures(n int32, textures *uint32) {
|
||||
C.glDeleteTextures(C.int(n), (*C.uint)(textures))
|
||||
}
|
||||
func (GLImpl) Disable(cap uint32) {
|
||||
C.glDisable(C.uint(cap))
|
||||
}
|
||||
func (GLImpl) DisableVertexAttribArray(index uint32) {
|
||||
C.glDisableVertexAttribArray(C.uint(index))
|
||||
}
|
||||
func (GLImpl) DrawArrays(mode uint32, first int32, count int32) {
|
||||
C.glDrawArrays(C.uint(mode), C.int(first), C.int(count))
|
||||
}
|
||||
func (GLImpl) Enable(cap uint32) {
|
||||
C.glEnable(C.uint(cap))
|
||||
}
|
||||
func (GLImpl) EnableVertexAttribArray(index uint32) {
|
||||
C.glEnableVertexAttribArray(C.uint(index))
|
||||
}
|
||||
func (GLImpl) FramebufferRenderbuffer(target uint32, attachment uint32, renderbuffertarget uint32, renderbuffer uint32) {
|
||||
C.glFramebufferRenderbuffer(C.uint(target), C.uint(attachment), C.uint(renderbuffertarget), C.uint(renderbuffer))
|
||||
}
|
||||
func (GLImpl) FramebufferTexture(target uint32, attachment uint32, texture uint32, level int32) {
|
||||
C.glFramebufferTexture2D(C.uint(target), C.uint(attachment), C.GL_TEXTURE_2D, C.uint(texture), C.int(level))
|
||||
}
|
||||
func (GLImpl) GenBuffers(n int32, buffers *uint32) {
|
||||
C.glGenBuffers(C.int(n), (*C.uint)(buffers))
|
||||
}
|
||||
func (GLImpl) GenFramebuffers(n int32, framebuffers *uint32) {
|
||||
C.glGenFramebuffers(C.int(n), (*C.uint)(framebuffers))
|
||||
}
|
||||
func (GLImpl) GenRenderbuffers(n int32, renderbuffers *uint32) {
|
||||
C.glGenRenderbuffers(C.int(n), (*C.uint)(renderbuffers))
|
||||
}
|
||||
func (GLImpl) GenTextures(n int32, textures *uint32) {
|
||||
C.glGenTextures(C.int(n), (*C.uint)(textures))
|
||||
}
|
||||
func (GLImpl) GenerateMipmap(target uint32) {
|
||||
C.glGenerateMipmap(C.uint(target))
|
||||
}
|
||||
func (GLImpl) GetAttribLocation(program uint32, name string) int32 {
|
||||
cname := C.CString(name)
|
||||
defer C.free(unsafe.Pointer(cname))
|
||||
return int32(C.glGetAttribLocation(C.uint(program), cname))
|
||||
}
|
||||
func (GLImpl) GetError() uint32 {
|
||||
return uint32(C.glGetError())
|
||||
}
|
||||
func (GLImpl) GetProgramInfoLog(program uint32) string {
|
||||
var length C.int
|
||||
C.glGetProgramiv(C.uint(program), C.GL_INFO_LOG_LENGTH, &length)
|
||||
if length == 0 {
|
||||
return ""
|
||||
}
|
||||
clog := C.CBytes(make([]byte, int(length)+1))
|
||||
defer C.free(clog)
|
||||
C.glGetProgramInfoLog(C.uint(program), C.int(length), nil, (*C.char)(clog))
|
||||
return string(C.GoBytes(clog, length))
|
||||
}
|
||||
func (GLImpl) GetProgramiv(program uint32, pname uint32, params *int32) {
|
||||
C.glGetProgramiv(C.uint(program), C.uint(pname), (*C.int)(params))
|
||||
}
|
||||
func (GLImpl) GetShaderInfoLog(program uint32) string {
|
||||
var length C.int
|
||||
C.glGetShaderiv(C.uint(program), C.GL_INFO_LOG_LENGTH, &length)
|
||||
if length == 0 {
|
||||
return ""
|
||||
}
|
||||
clog := C.CBytes(make([]byte, int(length)+1))
|
||||
defer C.free(clog)
|
||||
C.glGetShaderInfoLog(C.uint(program), C.int(length), nil, (*C.char)(clog))
|
||||
return string(C.GoBytes(clog, length))
|
||||
}
|
||||
func (GLImpl) GetShaderiv(shader uint32, pname uint32, params *int32) {
|
||||
C.glGetShaderiv(C.uint(shader), C.uint(pname), (*C.int)(params))
|
||||
|
||||
}
|
||||
func (GLImpl) GetUniformLocation(program uint32, name string) int32 {
|
||||
cname := C.CString(name)
|
||||
defer C.free(unsafe.Pointer(cname))
|
||||
return int32(C.glGetUniformLocation(C.uint(program), cname))
|
||||
}
|
||||
func (GLImpl) LinkProgram(program uint32) {
|
||||
C.glLinkProgram(C.uint(program))
|
||||
}
|
||||
func (GLImpl) ReadPixels(x int32, y int32, width int32, height int32, format uint32, xtype uint32, pixels unsafe.Pointer) {
|
||||
C.glReadPixels(C.int(x), C.int(y), C.int(width), C.int(height), C.uint(format), C.uint(xtype), pixels)
|
||||
}
|
||||
func (GLImpl) RenderbufferStorage(target uint32, internalformat uint32, width int32, height int32) {
|
||||
C.glRenderbufferStorage(C.uint(target), C.uint(internalformat), C.int(width), C.int(height))
|
||||
}
|
||||
func (GLImpl) Scissor(x int32, y int32, width int32, height int32) {
|
||||
C.glScissor(C.int(x), C.int(y), C.int(width), C.int(height))
|
||||
}
|
||||
func (GLImpl) ShaderSource(shader uint32, source string) {
|
||||
csource := C.CString(source)
|
||||
defer C.free(unsafe.Pointer(csource))
|
||||
C.glShaderSource(C.uint(shader), 1, &csource, nil)
|
||||
}
|
||||
func (GLImpl) StencilFunc(xfunc uint32, ref int32, mask uint32) {
|
||||
C.glStencilFunc(C.uint(xfunc), C.int(ref), C.uint(mask))
|
||||
}
|
||||
func (GLImpl) StencilMask(mask uint32) {
|
||||
C.glStencilMask(C.uint(mask))
|
||||
}
|
||||
func (GLImpl) StencilOp(fail uint32, zfail uint32, zpass uint32) {
|
||||
C.glStencilOp(C.uint(fail), C.uint(zfail), C.uint(zpass))
|
||||
}
|
||||
func (GLImpl) TexImage2D(target uint32, level int32, internalformat int32, width int32, height int32, border int32, format uint32, xtype uint32, pixels unsafe.Pointer) {
|
||||
C.glTexImage2D(C.uint(target), C.int(level), C.int(internalformat), C.int(width), C.int(height), C.int(border), C.uint(format), C.uint(xtype), pixels)
|
||||
}
|
||||
func (GLImpl) TexParameteri(target uint32, pname uint32, param int32) {
|
||||
C.glTexParameteri(C.uint(target), C.uint(pname), C.int(param))
|
||||
}
|
||||
func (GLImpl) TexSubImage2D(target uint32, level int32, xoffset int32, yoffset int32, width int32, height int32, format uint32, xtype uint32, pixels unsafe.Pointer) {
|
||||
C.glTexSubImage2D(C.uint(target), C.int(level), C.int(xoffset), C.int(yoffset), C.int(width), C.int(height), C.uint(format), C.uint(xtype), pixels)
|
||||
}
|
||||
func (GLImpl) Uniform1f(location int32, v0 float32) {
|
||||
C.glUniform1f(C.int(location), C.float(v0))
|
||||
}
|
||||
func (GLImpl) Uniform1fv(location int32, count int32, v *float32) {
|
||||
C.glUniform1fv(C.int(location), C.int(count), (*C.float)(v))
|
||||
}
|
||||
func (GLImpl) Uniform1i(location int32, v0 int32) {
|
||||
C.glUniform1i(C.int(location), C.int(v0))
|
||||
}
|
||||
func (GLImpl) Uniform2f(location int32, v0 float32, v1 float32) {
|
||||
C.glUniform2f(C.int(location), C.float(v0), C.float(v1))
|
||||
}
|
||||
func (GLImpl) Uniform4f(location int32, v0 float32, v1 float32, v2 float32, v3 float32) {
|
||||
C.glUniform4f(C.int(location), C.float(v0), C.float(v1), C.float(v2), C.float(v3))
|
||||
}
|
||||
func (GLImpl) UniformMatrix3fv(location int32, count int32, transpose bool, value *float32) {
|
||||
var t C.uchar
|
||||
if transpose {
|
||||
t = 1
|
||||
}
|
||||
C.glUniformMatrix3fv(C.int(location), C.int(count), t, (*C.float)(value))
|
||||
}
|
||||
func (GLImpl) UseProgram(program uint32) {
|
||||
C.glUseProgram(C.uint(program))
|
||||
}
|
||||
func (GLImpl) VertexAttribPointer(index uint32, size int32, xtype uint32, normalized bool, stride int32, offset uint32) {
|
||||
var n C.uchar
|
||||
if normalized {
|
||||
n = 1
|
||||
}
|
||||
C.glVertexAttribPointer(C.uint(index), C.int(size), C.uint(xtype), n, C.int(stride), unsafe.Pointer(uintptr(offset)))
|
||||
}
|
||||
func (GLImpl) Viewport(x int32, y int32, width int32, height int32) {
|
||||
C.glViewport(C.int(x), C.int(y), C.int(width), C.int(height))
|
||||
}
|
|
@ -1,207 +0,0 @@
|
|||
package glimplgogl
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"github.com/go-gl/gl/v3.2-core/gl"
|
||||
"github.com/tfriedel6/canvas"
|
||||
)
|
||||
|
||||
type GLImpl struct{}
|
||||
|
||||
var _ canvas.GL = GLImpl{}
|
||||
|
||||
func (GLImpl) Ptr(data interface{}) unsafe.Pointer {
|
||||
return gl.Ptr(data)
|
||||
}
|
||||
func (GLImpl) ActiveTexture(texture uint32) {
|
||||
gl.ActiveTexture(texture)
|
||||
}
|
||||
func (GLImpl) AttachShader(program uint32, shader uint32) {
|
||||
gl.AttachShader(program, shader)
|
||||
}
|
||||
func (GLImpl) BindBuffer(target uint32, buffer uint32) {
|
||||
gl.BindBuffer(target, buffer)
|
||||
}
|
||||
func (GLImpl) BindFramebuffer(target uint32, framebuffer uint32) {
|
||||
gl.BindFramebuffer(target, framebuffer)
|
||||
}
|
||||
func (GLImpl) BindRenderbuffer(target uint32, renderbuffer uint32) {
|
||||
gl.BindRenderbuffer(target, renderbuffer)
|
||||
}
|
||||
func (GLImpl) BindTexture(target uint32, texture uint32) {
|
||||
gl.BindTexture(target, texture)
|
||||
}
|
||||
func (GLImpl) BlendFunc(sfactor uint32, dfactor uint32) {
|
||||
gl.BlendFunc(sfactor, dfactor)
|
||||
}
|
||||
func (GLImpl) BufferData(target uint32, size int, data unsafe.Pointer, usage uint32) {
|
||||
gl.BufferData(target, size, data, usage)
|
||||
}
|
||||
func (GLImpl) CheckFramebufferStatus(target uint32) uint32 {
|
||||
return gl.CheckFramebufferStatus(target)
|
||||
}
|
||||
func (GLImpl) Clear(mask uint32) {
|
||||
gl.Clear(mask)
|
||||
}
|
||||
func (GLImpl) ClearColor(red float32, green float32, blue float32, alpha float32) {
|
||||
gl.ClearColor(red, green, blue, alpha)
|
||||
}
|
||||
func (GLImpl) ColorMask(red bool, green bool, blue bool, alpha bool) {
|
||||
gl.ColorMask(red, green, blue, alpha)
|
||||
}
|
||||
func (GLImpl) CompileShader(shader uint32) {
|
||||
gl.CompileShader(shader)
|
||||
}
|
||||
func (GLImpl) CreateProgram() uint32 {
|
||||
return gl.CreateProgram()
|
||||
}
|
||||
func (GLImpl) CreateShader(xtype uint32) uint32 {
|
||||
return gl.CreateShader(xtype)
|
||||
}
|
||||
func (GLImpl) DeleteShader(shader uint32) {
|
||||
gl.DeleteShader(shader)
|
||||
}
|
||||
func (GLImpl) DeleteFramebuffers(n int32, framebuffers *uint32) {
|
||||
gl.DeleteFramebuffers(n, framebuffers)
|
||||
}
|
||||
func (GLImpl) DeleteRenderbuffers(n int32, renderbuffers *uint32) {
|
||||
gl.DeleteRenderbuffers(n, renderbuffers)
|
||||
}
|
||||
func (GLImpl) DeleteTextures(n int32, textures *uint32) {
|
||||
gl.DeleteTextures(n, textures)
|
||||
}
|
||||
func (GLImpl) Disable(cap uint32) {
|
||||
gl.Disable(cap)
|
||||
}
|
||||
func (GLImpl) DisableVertexAttribArray(index uint32) {
|
||||
gl.DisableVertexAttribArray(index)
|
||||
}
|
||||
func (GLImpl) DrawArrays(mode uint32, first int32, count int32) {
|
||||
gl.DrawArrays(mode, first, count)
|
||||
}
|
||||
func (GLImpl) Enable(cap uint32) {
|
||||
gl.Enable(cap)
|
||||
}
|
||||
func (GLImpl) EnableVertexAttribArray(index uint32) {
|
||||
gl.EnableVertexAttribArray(index)
|
||||
}
|
||||
func (GLImpl) FramebufferRenderbuffer(target uint32, attachment uint32, renderbuffertarget uint32, renderbuffer uint32) {
|
||||
gl.FramebufferRenderbuffer(target, attachment, renderbuffertarget, renderbuffer)
|
||||
}
|
||||
func (GLImpl) FramebufferTexture(target uint32, attachment uint32, texture uint32, level int32) {
|
||||
gl.FramebufferTexture(target, attachment, texture, level)
|
||||
}
|
||||
func (GLImpl) GenBuffers(n int32, buffers *uint32) {
|
||||
gl.GenBuffers(n, buffers)
|
||||
}
|
||||
func (GLImpl) GenFramebuffers(n int32, framebuffers *uint32) {
|
||||
gl.GenFramebuffers(n, framebuffers)
|
||||
}
|
||||
func (GLImpl) GenRenderbuffers(n int32, renderbuffers *uint32) {
|
||||
gl.GenRenderbuffers(n, renderbuffers)
|
||||
}
|
||||
func (GLImpl) GenTextures(n int32, textures *uint32) {
|
||||
gl.GenTextures(n, textures)
|
||||
}
|
||||
func (GLImpl) GenerateMipmap(target uint32) {
|
||||
gl.GenerateMipmap(target)
|
||||
}
|
||||
func (GLImpl) GetAttribLocation(program uint32, name string) int32 {
|
||||
return gl.GetAttribLocation(program, gl.Str(name+"\x00"))
|
||||
}
|
||||
func (GLImpl) GetError() uint32 {
|
||||
return gl.GetError()
|
||||
}
|
||||
func (GLImpl) GetProgramInfoLog(program uint32) string {
|
||||
var length int32
|
||||
gl.GetProgramiv(program, gl.INFO_LOG_LENGTH, &length)
|
||||
if length == 0 {
|
||||
return ""
|
||||
}
|
||||
log := strings.Repeat("\x00", int(length+1))
|
||||
gl.GetProgramInfoLog(program, length, nil, gl.Str(log))
|
||||
return log
|
||||
}
|
||||
func (GLImpl) GetProgramiv(program uint32, pname uint32, params *int32) {
|
||||
gl.GetProgramiv(program, pname, params)
|
||||
}
|
||||
func (GLImpl) GetShaderInfoLog(program uint32) string {
|
||||
var length int32
|
||||
gl.GetShaderiv(program, gl.INFO_LOG_LENGTH, &length)
|
||||
if length == 0 {
|
||||
return ""
|
||||
}
|
||||
log := strings.Repeat("\x00", int(length+1))
|
||||
gl.GetShaderInfoLog(program, length, nil, gl.Str(log))
|
||||
return log
|
||||
}
|
||||
func (GLImpl) GetShaderiv(shader uint32, pname uint32, params *int32) {
|
||||
gl.GetShaderiv(shader, pname, params)
|
||||
}
|
||||
func (GLImpl) GetUniformLocation(program uint32, name string) int32 {
|
||||
return gl.GetUniformLocation(program, gl.Str(name+"\x00"))
|
||||
}
|
||||
func (GLImpl) LinkProgram(program uint32) {
|
||||
gl.LinkProgram(program)
|
||||
}
|
||||
func (GLImpl) RenderbufferStorage(target uint32, internalformat uint32, width int32, height int32) {
|
||||
gl.RenderbufferStorage(target, internalformat, width, height)
|
||||
}
|
||||
func (GLImpl) ReadPixels(x int32, y int32, width int32, height int32, format uint32, xtype uint32, pixels unsafe.Pointer) {
|
||||
gl.ReadPixels(x, y, width, height, format, xtype, pixels)
|
||||
}
|
||||
func (GLImpl) Scissor(x int32, y int32, width int32, height int32) {
|
||||
gl.Scissor(x, y, width, height)
|
||||
}
|
||||
func (GLImpl) ShaderSource(shader uint32, source string) {
|
||||
csource, freeFunc := gl.Strs(source + "\x00")
|
||||
gl.ShaderSource(shader, 1, csource, nil)
|
||||
freeFunc()
|
||||
}
|
||||
func (GLImpl) StencilFunc(xfunc uint32, ref int32, mask uint32) {
|
||||
gl.StencilFunc(xfunc, ref, mask)
|
||||
}
|
||||
func (GLImpl) StencilMask(mask uint32) {
|
||||
gl.StencilMask(mask)
|
||||
}
|
||||
func (GLImpl) StencilOp(fail uint32, zfail uint32, zpass uint32) {
|
||||
gl.StencilOp(fail, zfail, zpass)
|
||||
}
|
||||
func (GLImpl) TexImage2D(target uint32, level int32, internalformat int32, width int32, height int32, border int32, format uint32, xtype uint32, pixels unsafe.Pointer) {
|
||||
gl.TexImage2D(target, level, internalformat, width, height, border, format, xtype, pixels)
|
||||
}
|
||||
func (GLImpl) TexParameteri(target uint32, pname uint32, param int32) {
|
||||
gl.TexParameteri(target, pname, param)
|
||||
}
|
||||
func (GLImpl) TexSubImage2D(target uint32, level int32, xoffset int32, yoffset int32, width int32, height int32, format uint32, xtype uint32, pixels unsafe.Pointer) {
|
||||
gl.TexSubImage2D(target, level, xoffset, yoffset, width, height, format, xtype, pixels)
|
||||
}
|
||||
func (GLImpl) Uniform1f(location int32, v0 float32) {
|
||||
gl.Uniform1f(location, v0)
|
||||
}
|
||||
func (GLImpl) Uniform1fv(location int32, count int32, v *float32) {
|
||||
gl.Uniform1fv(location, count, v)
|
||||
}
|
||||
func (GLImpl) Uniform1i(location int32, v0 int32) {
|
||||
gl.Uniform1i(location, v0)
|
||||
}
|
||||
func (GLImpl) Uniform2f(location int32, v0 float32, v1 float32) {
|
||||
gl.Uniform2f(location, v0, v1)
|
||||
}
|
||||
func (GLImpl) Uniform4f(location int32, v0 float32, v1 float32, v2 float32, v3 float32) {
|
||||
gl.Uniform4f(location, v0, v1, v2, v3)
|
||||
}
|
||||
func (GLImpl) UniformMatrix3fv(location int32, count int32, transpose bool, value *float32) {
|
||||
gl.UniformMatrix3fv(location, count, transpose, value)
|
||||
}
|
||||
func (GLImpl) UseProgram(program uint32) {
|
||||
gl.UseProgram(program)
|
||||
}
|
||||
func (GLImpl) VertexAttribPointer(index uint32, size int32, xtype uint32, normalized bool, stride int32, offset uint32) {
|
||||
gl.VertexAttribPointer(index, size, xtype, normalized, stride, gl.PtrOffset(int(offset)))
|
||||
}
|
||||
func (GLImpl) Viewport(x int32, y int32, width int32, height int32) {
|
||||
gl.Viewport(x, y, width, height)
|
||||
}
|
|
@ -1,263 +0,0 @@
|
|||
package glimplios
|
||||
|
||||
// #include <stdlib.h>
|
||||
// #include <OpenGLES/ES2/gl.h>
|
||||
// #cgo ios LDFLAGS: -framework OpenGLES
|
||||
import "C"
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"unsafe"
|
||||
|
||||
"github.com/tfriedel6/canvas"
|
||||
)
|
||||
|
||||
type GLImpl struct{}
|
||||
|
||||
var _ canvas.GL = GLImpl{}
|
||||
|
||||
func (GLImpl) Ptr(data interface{}) unsafe.Pointer {
|
||||
if data == nil {
|
||||
return unsafe.Pointer(nil)
|
||||
}
|
||||
var addr unsafe.Pointer
|
||||
v := reflect.ValueOf(data)
|
||||
switch v.Type().Kind() {
|
||||
case reflect.Ptr:
|
||||
e := v.Elem()
|
||||
switch e.Kind() {
|
||||
case
|
||||
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
|
||||
reflect.Float32, reflect.Float64:
|
||||
addr = unsafe.Pointer(e.UnsafeAddr())
|
||||
default:
|
||||
panic(fmt.Errorf("unsupported pointer to type %s; must be a slice or pointer to a singular scalar value or the first element of an array or slice", e.Kind()))
|
||||
}
|
||||
case reflect.Uintptr:
|
||||
addr = unsafe.Pointer(v.Pointer())
|
||||
case reflect.Slice:
|
||||
addr = unsafe.Pointer(v.Index(0).UnsafeAddr())
|
||||
default:
|
||||
panic(fmt.Errorf("unsupported type %s; must be a slice or pointer to a singular scalar value or the first element of an array or slice", v.Type()))
|
||||
}
|
||||
return addr
|
||||
}
|
||||
func (GLImpl) ActiveTexture(texture uint32) {
|
||||
C.glActiveTexture(C.GLenum(texture))
|
||||
}
|
||||
func (GLImpl) AttachShader(program uint32, shader uint32) {
|
||||
C.glAttachShader(C.GLuint(program), C.GLuint(shader))
|
||||
}
|
||||
func (GLImpl) BindBuffer(target uint32, buffer uint32) {
|
||||
C.glBindBuffer(C.GLenum(target), C.GLuint(buffer))
|
||||
}
|
||||
func (GLImpl) BindFramebuffer(target uint32, framebuffer uint32) {
|
||||
C.glBindFramebuffer(C.GLenum(target), C.GLuint(framebuffer))
|
||||
}
|
||||
func (GLImpl) BindRenderbuffer(target uint32, renderbuffer uint32) {
|
||||
C.glBindRenderbuffer(C.GLenum(target), C.GLuint(renderbuffer))
|
||||
}
|
||||
func (GLImpl) BindTexture(target uint32, texture uint32) {
|
||||
C.glBindTexture(C.GLenum(target), C.GLuint(texture))
|
||||
}
|
||||
func (GLImpl) BlendFunc(sfactor uint32, dfactor uint32) {
|
||||
C.glBlendFunc(C.GLenum(sfactor), C.GLenum(dfactor))
|
||||
}
|
||||
func (GLImpl) BufferData(target uint32, size int, data unsafe.Pointer, usage uint32) {
|
||||
C.glBufferData(C.GLenum(target), C.GLsizeiptr(size), data, C.GLenum(usage))
|
||||
}
|
||||
func (GLImpl) CheckFramebufferStatus(target uint32) uint32 {
|
||||
return uint32(C.glCheckFramebufferStatus(C.GLenum(target)))
|
||||
}
|
||||
func (GLImpl) Clear(mask uint32) {
|
||||
C.glClear(C.GLbitfield(mask))
|
||||
}
|
||||
func (GLImpl) ClearColor(red float32, green float32, blue float32, alpha float32) {
|
||||
C.glClearColor(C.GLfloat(red), C.GLfloat(green), C.GLfloat(blue), C.GLfloat(alpha))
|
||||
}
|
||||
func (GLImpl) ColorMask(red bool, green bool, blue bool, alpha bool) {
|
||||
var r, g, b, a C.GLboolean
|
||||
if red {
|
||||
r = 1
|
||||
}
|
||||
if green {
|
||||
g = 1
|
||||
}
|
||||
if blue {
|
||||
b = 1
|
||||
}
|
||||
if alpha {
|
||||
a = 1
|
||||
}
|
||||
C.glColorMask(r, g, b, a)
|
||||
}
|
||||
func (GLImpl) CompileShader(shader uint32) {
|
||||
C.glCompileShader(C.GLuint(shader))
|
||||
}
|
||||
func (GLImpl) CreateProgram() uint32 {
|
||||
return uint32(C.glCreateProgram())
|
||||
}
|
||||
func (GLImpl) CreateShader(xtype uint32) uint32 {
|
||||
return uint32(C.glCreateShader(C.GLenum(xtype)))
|
||||
}
|
||||
func (GLImpl) DeleteShader(shader uint32) {
|
||||
C.glDeleteShader(C.GLuint(shader))
|
||||
}
|
||||
func (GLImpl) DeleteFramebuffers(n int32, framebuffers *uint32) {
|
||||
C.glDeleteFramebuffers(C.GLsizei(n), (*C.GLuint)(framebuffers))
|
||||
}
|
||||
func (GLImpl) DeleteRenderbuffers(n int32, renderbuffers *uint32) {
|
||||
C.glDeleteRenderbuffers(C.GLsizei(n), (*C.GLuint)(renderbuffers))
|
||||
}
|
||||
func (GLImpl) DeleteTextures(n int32, textures *uint32) {
|
||||
C.glDeleteTextures(C.GLsizei(n), (*C.GLuint)(textures))
|
||||
}
|
||||
func (GLImpl) Disable(cap uint32) {
|
||||
C.glDisable(C.GLenum(cap))
|
||||
}
|
||||
func (GLImpl) DisableVertexAttribArray(index uint32) {
|
||||
C.glDisableVertexAttribArray(C.GLuint(index))
|
||||
}
|
||||
func (GLImpl) DrawArrays(mode uint32, first int32, count int32) {
|
||||
C.glDrawArrays(C.GLenum(mode), C.GLint(first), C.GLsizei(count))
|
||||
}
|
||||
func (GLImpl) Enable(cap uint32) {
|
||||
C.glEnable(C.GLenum(cap))
|
||||
}
|
||||
func (GLImpl) EnableVertexAttribArray(index uint32) {
|
||||
C.glEnableVertexAttribArray(C.GLuint(index))
|
||||
}
|
||||
func (GLImpl) FramebufferRenderbuffer(target uint32, attachment uint32, renderbuffertarget uint32, renderbuffer uint32) {
|
||||
C.glFramebufferRenderbuffer(C.GLenum(target), C.GLenum(attachment), C.GLenum(renderbuffertarget), C.GLuint(renderbuffer))
|
||||
}
|
||||
func (GLImpl) FramebufferTexture(target uint32, attachment uint32, texture uint32, level int32) {
|
||||
C.glFramebufferTexture2D(C.GLenum(target), C.GLenum(attachment), C.GL_TEXTURE_2D, C.GLuint(texture), C.GLint(level))
|
||||
}
|
||||
func (GLImpl) GenBuffers(n int32, buffers *uint32) {
|
||||
C.glGenBuffers(C.GLsizei(n), (*C.GLuint)(buffers))
|
||||
}
|
||||
func (GLImpl) GenFramebuffers(n int32, framebuffers *uint32) {
|
||||
C.glGenFramebuffers(C.GLsizei(n), (*C.GLuint)(framebuffers))
|
||||
}
|
||||
func (GLImpl) GenRenderbuffers(n int32, renderbuffers *uint32) {
|
||||
C.glGenRenderbuffers(C.GLsizei(n), (*C.GLuint)(renderbuffers))
|
||||
}
|
||||
func (GLImpl) GenTextures(n int32, textures *uint32) {
|
||||
C.glGenTextures(C.GLsizei(n), (*C.GLuint)(textures))
|
||||
}
|
||||
func (GLImpl) GenerateMipmap(target uint32) {
|
||||
C.glGenerateMipmap(C.GLenum(target))
|
||||
}
|
||||
func (GLImpl) GetAttribLocation(program uint32, name string) int32 {
|
||||
cname := C.CString(name)
|
||||
defer C.free(unsafe.Pointer(cname))
|
||||
return int32(C.glGetAttribLocation(C.GLuint(program), (*C.GLchar)(cname)))
|
||||
}
|
||||
func (GLImpl) GetError() uint32 {
|
||||
return uint32(C.glGetError())
|
||||
}
|
||||
func (GLImpl) GetProgramInfoLog(program uint32) string {
|
||||
var length C.GLint
|
||||
C.glGetProgramiv(C.GLuint(program), C.GL_INFO_LOG_LENGTH, &length)
|
||||
if length == 0 {
|
||||
return ""
|
||||
}
|
||||
clog := C.CBytes(make([]byte, int(length)+1))
|
||||
defer C.free(clog)
|
||||
C.glGetProgramInfoLog(C.GLuint(program), C.GLsizei(length), nil, (*C.GLchar)(clog))
|
||||
return string(C.GoBytes(clog, C.int(length)))
|
||||
}
|
||||
func (GLImpl) GetProgramiv(program uint32, pname uint32, params *int32) {
|
||||
C.glGetProgramiv(C.GLuint(program), C.GLenum(pname), (*C.GLint)(params))
|
||||
}
|
||||
func (GLImpl) GetShaderInfoLog(program uint32) string {
|
||||
var length C.GLint
|
||||
C.glGetShaderiv(C.GLuint(program), C.GL_INFO_LOG_LENGTH, &length)
|
||||
if length == 0 {
|
||||
return ""
|
||||
}
|
||||
clog := C.CBytes(make([]byte, int(length)+1))
|
||||
defer C.free(clog)
|
||||
C.glGetShaderInfoLog(C.GLuint(program), C.GLsizei(length), nil, (*C.GLchar)(clog))
|
||||
return string(C.GoBytes(clog, C.int(length)))
|
||||
}
|
||||
func (GLImpl) GetShaderiv(shader uint32, pname uint32, params *int32) {
|
||||
C.glGetShaderiv(C.GLuint(shader), C.GLenum(pname), (*C.GLint)(params))
|
||||
|
||||
}
|
||||
func (GLImpl) GetUniformLocation(program uint32, name string) int32 {
|
||||
cname := C.CString(name)
|
||||
defer C.free(unsafe.Pointer(cname))
|
||||
return int32(C.glGetUniformLocation(C.GLuint(program), (*C.GLchar)(cname)))
|
||||
}
|
||||
func (GLImpl) LinkProgram(program uint32) {
|
||||
C.glLinkProgram(C.GLuint(program))
|
||||
}
|
||||
func (GLImpl) ReadPixels(x int32, y int32, width int32, height int32, format uint32, xtype uint32, pixels unsafe.Pointer) {
|
||||
C.glReadPixels(C.GLint(x), C.GLint(y), C.GLsizei(width), C.GLsizei(height), C.GLenum(format), C.GLenum(xtype), pixels)
|
||||
}
|
||||
func (GLImpl) RenderbufferStorage(target uint32, internalformat uint32, width int32, height int32) {
|
||||
C.glRenderbufferStorage(C.GLenum(target), C.GLenum(internalformat), C.GLint(width), C.GLint(height))
|
||||
}
|
||||
func (GLImpl) Scissor(x int32, y int32, width int32, height int32) {
|
||||
C.glScissor(C.GLint(x), C.GLint(y), C.GLsizei(width), C.GLsizei(height))
|
||||
}
|
||||
func (GLImpl) ShaderSource(shader uint32, source string) {
|
||||
csource := (*C.GLchar)(C.CString(source))
|
||||
defer C.free(unsafe.Pointer(csource))
|
||||
C.glShaderSource(C.GLuint(shader), 1, &csource, nil)
|
||||
}
|
||||
func (GLImpl) StencilFunc(xfunc uint32, ref int32, mask uint32) {
|
||||
C.glStencilFunc(C.GLenum(xfunc), C.GLint(ref), C.GLuint(mask))
|
||||
}
|
||||
func (GLImpl) StencilMask(mask uint32) {
|
||||
C.glStencilMask(C.GLuint(mask))
|
||||
}
|
||||
func (GLImpl) StencilOp(fail uint32, zfail uint32, zpass uint32) {
|
||||
C.glStencilOp(C.GLenum(fail), C.GLenum(zfail), C.GLenum(zpass))
|
||||
}
|
||||
func (GLImpl) TexImage2D(target uint32, level int32, internalformat int32, width int32, height int32, border int32, format uint32, xtype uint32, pixels unsafe.Pointer) {
|
||||
C.glTexImage2D(C.GLenum(target), C.GLint(level), C.GLint(internalformat), C.GLsizei(width), C.GLsizei(height), C.GLint(border), C.GLenum(format), C.GLenum(xtype), pixels)
|
||||
}
|
||||
func (GLImpl) TexParameteri(target uint32, pname uint32, param int32) {
|
||||
C.glTexParameteri(C.GLenum(target), C.GLenum(pname), C.GLint(param))
|
||||
}
|
||||
func (GLImpl) TexSubImage2D(target uint32, level int32, xoffset int32, yoffset int32, width int32, height int32, format uint32, xtype uint32, pixels unsafe.Pointer) {
|
||||
C.glTexSubImage2D(C.GLenum(target), C.GLint(level), C.GLint(xoffset), C.GLint(yoffset), C.GLsizei(width), C.GLsizei(height), C.GLenum(format), C.GLenum(xtype), pixels)
|
||||
}
|
||||
func (GLImpl) Uniform1f(location int32, v0 float32) {
|
||||
C.glUniform1f(C.GLint(location), C.GLfloat(v0))
|
||||
}
|
||||
func (GLImpl) Uniform1fv(location int32, count int32, v *float32) {
|
||||
C.glUniform1fv(C.GLint(location), C.GLsizei(count), (*C.GLfloat)(v))
|
||||
}
|
||||
func (GLImpl) Uniform1i(location int32, v0 int32) {
|
||||
C.glUniform1i(C.GLint(location), C.GLint(v0))
|
||||
}
|
||||
func (GLImpl) Uniform2f(location int32, v0 float32, v1 float32) {
|
||||
C.glUniform2f(C.GLint(location), C.GLfloat(v0), C.GLfloat(v1))
|
||||
}
|
||||
func (GLImpl) Uniform4f(location int32, v0 float32, v1 float32, v2 float32, v3 float32) {
|
||||
C.glUniform4f(C.GLint(location), C.GLfloat(v0), C.GLfloat(v1), C.GLfloat(v2), C.GLfloat(v3))
|
||||
}
|
||||
func (GLImpl) UniformMatrix3fv(location int32, count int32, transpose bool, value *float32) {
|
||||
var t C.GLboolean
|
||||
if transpose {
|
||||
t = 1
|
||||
}
|
||||
C.glUniformMatrix3fv(C.GLint(location), C.GLsizei(count), t, (*C.GLfloat)(value))
|
||||
}
|
||||
func (GLImpl) UseProgram(program uint32) {
|
||||
C.glUseProgram(C.GLuint(program))
|
||||
}
|
||||
func (GLImpl) VertexAttribPointer(index uint32, size int32, xtype uint32, normalized bool, stride int32, offset uint32) {
|
||||
var n C.GLboolean
|
||||
if normalized {
|
||||
n = 1
|
||||
}
|
||||
C.glVertexAttribPointer(C.GLuint(index), C.GLint(size), C.GLenum(xtype), n, C.GLsizei(stride), unsafe.Pointer(uintptr(offset)))
|
||||
}
|
||||
func (GLImpl) Viewport(x int32, y int32, width int32, height int32) {
|
||||
C.glViewport(C.GLint(x), C.GLint(y), C.GLsizei(width), C.GLsizei(height))
|
||||
}
|
|
@ -1,309 +0,0 @@
|
|||
package glimplxmobile
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"unsafe"
|
||||
|
||||
"github.com/tfriedel6/canvas"
|
||||
"golang.org/x/mobile/gl"
|
||||
)
|
||||
|
||||
type GLImpl struct {
|
||||
gl gl.Context
|
||||
programs map[uint32]gl.Program
|
||||
}
|
||||
|
||||
var _ canvas.GL = GLImpl{}
|
||||
|
||||
func New(ctx gl.Context) *GLImpl {
|
||||
return &GLImpl{
|
||||
gl: ctx,
|
||||
programs: make(map[uint32]gl.Program),
|
||||
}
|
||||
}
|
||||
|
||||
func (gli GLImpl) Ptr(data interface{}) unsafe.Pointer {
|
||||
if data == nil {
|
||||
return unsafe.Pointer(nil)
|
||||
}
|
||||
var addr unsafe.Pointer
|
||||
v := reflect.ValueOf(data)
|
||||
switch v.Type().Kind() {
|
||||
case reflect.Ptr:
|
||||
e := v.Elem()
|
||||
switch e.Kind() {
|
||||
case
|
||||
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
|
||||
reflect.Float32, reflect.Float64:
|
||||
addr = unsafe.Pointer(e.UnsafeAddr())
|
||||
default:
|
||||
panic(fmt.Errorf("unsupported pointer to type %s; must be a slice or pointer to a singular scalar value or the first element of an array or slice", e.Kind()))
|
||||
}
|
||||
case reflect.Uintptr:
|
||||
addr = unsafe.Pointer(v.Pointer())
|
||||
case reflect.Slice:
|
||||
addr = unsafe.Pointer(v.Index(0).UnsafeAddr())
|
||||
default:
|
||||
panic(fmt.Errorf("unsupported type %s; must be a slice or pointer to a singular scalar value or the first element of an array or slice", v.Type()))
|
||||
}
|
||||
return addr
|
||||
}
|
||||
func (gli GLImpl) ActiveTexture(texture uint32) {
|
||||
gli.gl.ActiveTexture(gl.Enum(texture))
|
||||
}
|
||||
func (gli GLImpl) AttachShader(program uint32, shader uint32) {
|
||||
gli.gl.AttachShader(gli.programs[program], gl.Shader{Value: shader})
|
||||
}
|
||||
func (gli GLImpl) BindBuffer(target uint32, buffer uint32) {
|
||||
gli.gl.BindBuffer(gl.Enum(target), gl.Buffer{Value: buffer})
|
||||
}
|
||||
func (gli GLImpl) BindFramebuffer(target uint32, framebuffer uint32) {
|
||||
gli.gl.BindFramebuffer(gl.Enum(target), gl.Framebuffer{Value: framebuffer})
|
||||
}
|
||||
func (gli GLImpl) BindRenderbuffer(target uint32, renderbuffer uint32) {
|
||||
gli.gl.BindRenderbuffer(gl.Enum(target), gl.Renderbuffer{Value: renderbuffer})
|
||||
}
|
||||
func (gli GLImpl) BindTexture(target uint32, texture uint32) {
|
||||
gli.gl.BindTexture(gl.Enum(target), gl.Texture{Value: texture})
|
||||
}
|
||||
func (gli GLImpl) BlendFunc(sfactor uint32, dfactor uint32) {
|
||||
gli.gl.BlendFunc(gl.Enum(sfactor), gl.Enum(dfactor))
|
||||
}
|
||||
func (gli GLImpl) BufferData(target uint32, size int, data unsafe.Pointer, usage uint32) {
|
||||
var buf []byte
|
||||
sh := (*reflect.SliceHeader)(unsafe.Pointer(&buf))
|
||||
sh.Cap = size
|
||||
sh.Len = size
|
||||
sh.Data = uintptr(data)
|
||||
gli.gl.BufferData(gl.Enum(target), buf, gl.Enum(usage))
|
||||
}
|
||||
func (gli GLImpl) CheckFramebufferStatus(target uint32) uint32 {
|
||||
return uint32(gli.gl.CheckFramebufferStatus(gl.Enum(target)))
|
||||
}
|
||||
func (gli GLImpl) Clear(mask uint32) {
|
||||
gli.gl.Clear(gl.Enum(mask))
|
||||
}
|
||||
func (gli GLImpl) ClearColor(red float32, green float32, blue float32, alpha float32) {
|
||||
gli.gl.ClearColor(red, green, blue, alpha)
|
||||
}
|
||||
func (gli GLImpl) ColorMask(red bool, green bool, blue bool, alpha bool) {
|
||||
gli.gl.ColorMask(red, green, blue, alpha)
|
||||
}
|
||||
func (gli GLImpl) CompileShader(shader uint32) {
|
||||
gli.gl.CompileShader(gl.Shader{Value: shader})
|
||||
}
|
||||
func (gli GLImpl) CreateProgram() uint32 {
|
||||
program := gli.gl.CreateProgram()
|
||||
gli.programs[program.Value] = program
|
||||
return program.Value
|
||||
}
|
||||
func (gli GLImpl) CreateShader(xtype uint32) uint32 {
|
||||
return gli.gl.CreateShader(gl.Enum(xtype)).Value
|
||||
}
|
||||
func (gli GLImpl) DeleteShader(shader uint32) {
|
||||
gli.gl.DeleteShader(gl.Shader{Value: shader})
|
||||
}
|
||||
func (gli GLImpl) DeleteFramebuffers(n int32, framebuffers *uint32) {
|
||||
var buf []uint32
|
||||
sh := (*reflect.SliceHeader)(unsafe.Pointer(&buf))
|
||||
sh.Cap = int(n)
|
||||
sh.Len = int(n)
|
||||
sh.Data = uintptr(unsafe.Pointer(framebuffers))
|
||||
for i := 0; i < int(n); i++ {
|
||||
gli.gl.DeleteFramebuffer(gl.Framebuffer{Value: buf[i]})
|
||||
}
|
||||
}
|
||||
func (gli GLImpl) DeleteRenderbuffers(n int32, renderbuffers *uint32) {
|
||||
var buf []uint32
|
||||
sh := (*reflect.SliceHeader)(unsafe.Pointer(&buf))
|
||||
sh.Cap = int(n)
|
||||
sh.Len = int(n)
|
||||
sh.Data = uintptr(unsafe.Pointer(renderbuffers))
|
||||
for i := 0; i < int(n); i++ {
|
||||
gli.gl.DeleteRenderbuffer(gl.Renderbuffer{Value: buf[i]})
|
||||
}
|
||||
}
|
||||
func (gli GLImpl) DeleteTextures(n int32, textures *uint32) {
|
||||
var buf []uint32
|
||||
sh := (*reflect.SliceHeader)(unsafe.Pointer(&buf))
|
||||
sh.Cap = int(n)
|
||||
sh.Len = int(n)
|
||||
sh.Data = uintptr(unsafe.Pointer(textures))
|
||||
for i := 0; i < int(n); i++ {
|
||||
gli.gl.DeleteTexture(gl.Texture{Value: buf[i]})
|
||||
}
|
||||
}
|
||||
func (gli GLImpl) Disable(cap uint32) {
|
||||
gli.gl.Disable(gl.Enum(cap))
|
||||
}
|
||||
func (gli GLImpl) DisableVertexAttribArray(index uint32) {
|
||||
gli.gl.DisableVertexAttribArray(gl.Attrib{Value: uint(index)})
|
||||
}
|
||||
func (gli GLImpl) DrawArrays(mode uint32, first int32, count int32) {
|
||||
gli.gl.DrawArrays(gl.Enum(mode), int(first), int(count))
|
||||
}
|
||||
func (gli GLImpl) Enable(cap uint32) {
|
||||
gli.gl.Enable(gl.Enum(cap))
|
||||
}
|
||||
func (gli GLImpl) EnableVertexAttribArray(index uint32) {
|
||||
gli.gl.EnableVertexAttribArray(gl.Attrib{Value: uint(index)})
|
||||
}
|
||||
func (gli GLImpl) FramebufferRenderbuffer(target uint32, attachment uint32, renderbuffertarget uint32, renderbuffer uint32) {
|
||||
gli.gl.FramebufferRenderbuffer(gl.Enum(target), gl.Enum(attachment), gl.Enum(renderbuffertarget), gl.Renderbuffer{Value: renderbuffer})
|
||||
}
|
||||
func (gli GLImpl) FramebufferTexture(target uint32, attachment uint32, texture uint32, level int32) {
|
||||
gli.gl.FramebufferTexture2D(gl.Enum(target), gl.Enum(attachment), gl.TEXTURE_2D, gl.Texture{Value: texture}, int(level))
|
||||
}
|
||||
func (gli GLImpl) GenBuffers(n int32, buffers *uint32) {
|
||||
var buf []uint32
|
||||
sh := (*reflect.SliceHeader)(unsafe.Pointer(&buf))
|
||||
sh.Cap = int(n)
|
||||
sh.Len = int(n)
|
||||
sh.Data = uintptr(unsafe.Pointer(buffers))
|
||||
for i := 0; i < int(n); i++ {
|
||||
buf[i] = gli.gl.CreateBuffer().Value
|
||||
}
|
||||
}
|
||||
func (gli GLImpl) GenFramebuffers(n int32, framebuffers *uint32) {
|
||||
var buf []uint32
|
||||
sh := (*reflect.SliceHeader)(unsafe.Pointer(&buf))
|
||||
sh.Cap = int(n)
|
||||
sh.Len = int(n)
|
||||
sh.Data = uintptr(unsafe.Pointer(framebuffers))
|
||||
for i := 0; i < int(n); i++ {
|
||||
buf[i] = gli.gl.CreateFramebuffer().Value
|
||||
}
|
||||
}
|
||||
func (gli GLImpl) GenRenderbuffers(n int32, renderbuffers *uint32) {
|
||||
var buf []uint32
|
||||
sh := (*reflect.SliceHeader)(unsafe.Pointer(&buf))
|
||||
sh.Cap = int(n)
|
||||
sh.Len = int(n)
|
||||
sh.Data = uintptr(unsafe.Pointer(renderbuffers))
|
||||
for i := 0; i < int(n); i++ {
|
||||
buf[i] = gli.gl.CreateRenderbuffer().Value
|
||||
}
|
||||
}
|
||||
func (gli GLImpl) GenTextures(n int32, textures *uint32) {
|
||||
var buf []uint32
|
||||
sh := (*reflect.SliceHeader)(unsafe.Pointer(&buf))
|
||||
sh.Cap = int(n)
|
||||
sh.Len = int(n)
|
||||
sh.Data = uintptr(unsafe.Pointer(textures))
|
||||
for i := 0; i < int(n); i++ {
|
||||
buf[i] = gli.gl.CreateTexture().Value
|
||||
}
|
||||
}
|
||||
func (gli GLImpl) GenerateMipmap(target uint32) {
|
||||
gli.gl.GenerateMipmap(gl.Enum(target))
|
||||
}
|
||||
func (gli GLImpl) GetAttribLocation(program uint32, name string) int32 {
|
||||
return int32(gli.gl.GetAttribLocation(gli.programs[program], name).Value)
|
||||
}
|
||||
func (gli GLImpl) GetError() uint32 {
|
||||
return uint32(gli.gl.GetError())
|
||||
}
|
||||
func (gli GLImpl) GetProgramInfoLog(program uint32) string {
|
||||
return gli.gl.GetProgramInfoLog(gli.programs[program])
|
||||
}
|
||||
func (gli GLImpl) GetProgramiv(program uint32, pname uint32, params *int32) {
|
||||
i := gli.gl.GetProgrami(gli.programs[program], gl.Enum(pname))
|
||||
*params = int32(i)
|
||||
}
|
||||
func (gli GLImpl) GetShaderInfoLog(program uint32) string {
|
||||
return gli.gl.GetShaderInfoLog(gl.Shader{Value: program})
|
||||
}
|
||||
func (gli GLImpl) GetShaderiv(shader uint32, pname uint32, params *int32) {
|
||||
i := gli.gl.GetShaderi(gl.Shader{Value: shader}, gl.Enum(pname))
|
||||
*params = int32(i)
|
||||
}
|
||||
func (gli GLImpl) GetUniformLocation(program uint32, name string) int32 {
|
||||
return gli.gl.GetUniformLocation(gli.programs[program], name).Value
|
||||
}
|
||||
func (gli GLImpl) LinkProgram(program uint32) {
|
||||
gli.gl.LinkProgram(gli.programs[program])
|
||||
}
|
||||
func (gli GLImpl) ReadPixels(x int32, y int32, width int32, height int32, format uint32, xtype uint32, pixels unsafe.Pointer) {
|
||||
var buf []byte
|
||||
sh := (*reflect.SliceHeader)(unsafe.Pointer(&buf))
|
||||
sh.Cap = int(width * height * 4)
|
||||
sh.Len = int(width * height * 4)
|
||||
sh.Data = uintptr(pixels)
|
||||
gli.gl.ReadPixels(buf, int(x), int(y), int(width), int(height), gl.Enum(format), gl.Enum(xtype))
|
||||
}
|
||||
func (gli GLImpl) RenderbufferStorage(target uint32, internalformat uint32, width int32, height int32) {
|
||||
gli.gl.RenderbufferStorage(gl.Enum(target), gl.Enum(internalformat), int(width), int(height))
|
||||
}
|
||||
func (gli GLImpl) Scissor(x int32, y int32, width int32, height int32) {
|
||||
gli.gl.Scissor(x, y, width, height)
|
||||
}
|
||||
func (gli GLImpl) ShaderSource(shader uint32, source string) {
|
||||
gli.gl.ShaderSource(gl.Shader{Value: shader}, source)
|
||||
}
|
||||
func (gli GLImpl) StencilFunc(xfunc uint32, ref int32, mask uint32) {
|
||||
gli.gl.StencilFunc(gl.Enum(xfunc), int(ref), mask)
|
||||
}
|
||||
func (gli GLImpl) StencilMask(mask uint32) {
|
||||
gli.gl.StencilMask(mask)
|
||||
}
|
||||
func (gli GLImpl) StencilOp(fail uint32, zfail uint32, zpass uint32) {
|
||||
gli.gl.StencilOp(gl.Enum(fail), gl.Enum(zfail), gl.Enum(zpass))
|
||||
}
|
||||
func (gli GLImpl) TexImage2D(target uint32, level int32, internalformat int32, width int32, height int32, border int32, format uint32, xtype uint32, pixels unsafe.Pointer) {
|
||||
var buf []byte
|
||||
sh := (*reflect.SliceHeader)(unsafe.Pointer(&buf))
|
||||
sh.Cap = int(width * height * 4)
|
||||
sh.Len = int(width * height * 4)
|
||||
sh.Data = uintptr(pixels)
|
||||
gli.gl.TexImage2D(gl.Enum(target), int(level), int(internalformat), int(width), int(height), gl.Enum(format), gl.Enum(xtype), buf)
|
||||
}
|
||||
func (gli GLImpl) TexParameteri(target uint32, pname uint32, param int32) {
|
||||
gli.gl.TexParameteri(gl.Enum(target), gl.Enum(pname), int(param))
|
||||
}
|
||||
func (gli GLImpl) TexSubImage2D(target uint32, level int32, xoffset int32, yoffset int32, width int32, height int32, format uint32, xtype uint32, pixels unsafe.Pointer) {
|
||||
var buf []byte
|
||||
sh := (*reflect.SliceHeader)(unsafe.Pointer(&buf))
|
||||
sh.Cap = int(width * height * 4)
|
||||
sh.Len = int(width * height * 4)
|
||||
sh.Data = uintptr(pixels)
|
||||
gli.gl.TexSubImage2D(gl.Enum(target), int(level), int(xoffset), int(yoffset), int(width), int(height), gl.Enum(format), gl.Enum(xtype), buf)
|
||||
}
|
||||
func (gli GLImpl) Uniform1f(location int32, v0 float32) {
|
||||
gli.gl.Uniform1f(gl.Uniform{Value: location}, v0)
|
||||
}
|
||||
func (gli GLImpl) Uniform1fv(location int32, count int32, v *float32) {
|
||||
var buf []float32
|
||||
sh := (*reflect.SliceHeader)(unsafe.Pointer(&buf))
|
||||
sh.Cap = int(count)
|
||||
sh.Len = int(count)
|
||||
sh.Data = uintptr(unsafe.Pointer(v))
|
||||
gli.gl.Uniform1fv(gl.Uniform{Value: location}, buf)
|
||||
}
|
||||
func (gli GLImpl) Uniform1i(location int32, v0 int32) {
|
||||
gli.gl.Uniform1i(gl.Uniform{Value: location}, int(v0))
|
||||
}
|
||||
func (gli GLImpl) Uniform2f(location int32, v0 float32, v1 float32) {
|
||||
gli.gl.Uniform2f(gl.Uniform{Value: location}, v0, v1)
|
||||
}
|
||||
func (gli GLImpl) Uniform4f(location int32, v0 float32, v1 float32, v2 float32, v3 float32) {
|
||||
gli.gl.Uniform4f(gl.Uniform{Value: location}, v0, v1, v2, v3)
|
||||
}
|
||||
func (gli GLImpl) UniformMatrix3fv(location int32, count int32, transpose bool, value *float32) {
|
||||
var buf []float32
|
||||
sh := (*reflect.SliceHeader)(unsafe.Pointer(&buf))
|
||||
sh.Cap = 9
|
||||
sh.Len = 9
|
||||
sh.Data = uintptr(unsafe.Pointer(value))
|
||||
gli.gl.UniformMatrix3fv(gl.Uniform{Value: location}, buf)
|
||||
}
|
||||
func (gli GLImpl) UseProgram(program uint32) {
|
||||
gli.gl.UseProgram(gli.programs[program])
|
||||
}
|
||||
func (gli GLImpl) VertexAttribPointer(index uint32, size int32, xtype uint32, normalized bool, stride int32, offset uint32) {
|
||||
gli.gl.VertexAttribPointer(gl.Attrib{Value: uint(index)}, int(size), gl.Enum(xtype), normalized, int(stride), int(offset))
|
||||
}
|
||||
func (gli GLImpl) Viewport(x int32, y int32, width int32, height int32) {
|
||||
gli.gl.Viewport(int(x), int(y), int(width), int(height))
|
||||
}
|
14
go.mod
|
@ -1,14 +1,8 @@
|
|||
module github.com/tfriedel6/canvas
|
||||
module git.mstar.dev/mstar/canvas
|
||||
|
||||
require (
|
||||
github.com/3d0c/gmf v0.0.0-20181011122539-af78d7462257
|
||||
github.com/go-gl/gl v0.0.0-20181026044259-55b76b7df9d2
|
||||
github.com/go-gl/glfw v0.0.0-20181014061658-691ee1b84c51
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
|
||||
github.com/samuel/go-pcx v0.0.0-20180426214139-d9c017170db4
|
||||
github.com/veandco/go-sdl2 v0.0.0-20180925095440-75ff82abc4e3
|
||||
golang.org/x/exp v0.0.0-20181106170214-d68db9428509
|
||||
golang.org/x/image v0.0.0-20181109002202-aa35264064ba
|
||||
golang.org/x/mobile v0.0.0-20181026062114-a27dd33d354d
|
||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8 // indirect
|
||||
golang.org/x/image v0.0.0-20200801110659-972c09e46d76
|
||||
)
|
||||
|
||||
go 1.13
|
||||
|
|
21
go.sum
|
@ -1,20 +1,5 @@
|
|||
github.com/3d0c/gmf v0.0.0-20181011122539-af78d7462257 h1:1KJwjPgvqPK1vqUDRW3f6uczuX2NRvgjK0SKPUBxAnM=
|
||||
github.com/3d0c/gmf v0.0.0-20181011122539-af78d7462257/go.mod h1:vFu/aQImUVU4s38rUuJr6KrEdkMOOnPIGE5ThNUavtg=
|
||||
github.com/go-gl/gl v0.0.0-20181026044259-55b76b7df9d2 h1:78Hza2KHn2PX1jdydQnffaU2A/xM0g3Nx1xmMdep9Gk=
|
||||
github.com/go-gl/gl v0.0.0-20181026044259-55b76b7df9d2/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk=
|
||||
github.com/go-gl/glfw v0.0.0-20181014061658-691ee1b84c51 h1:elGSwayRx7uAsfA5PnVKeTHh+AVsUTmas0CkHOw/DSk=
|
||||
github.com/go-gl/glfw v0.0.0-20181014061658-691ee1b84c51/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
github.com/samuel/go-pcx v0.0.0-20180426214139-d9c017170db4 h1:Y/KOCu+ZLB730PudefxfsKVjtI0m0RhvFk9a0l4O1+c=
|
||||
github.com/samuel/go-pcx v0.0.0-20180426214139-d9c017170db4/go.mod h1:qxuIawynlRhuaHowuXvd1xjyFWx87Ro4gkZlKRXtHnQ=
|
||||
github.com/veandco/go-sdl2 v0.0.0-20180925095440-75ff82abc4e3 h1:S1dvzp6ZDdmz1M4oRQ6ceLwU6ZDCNvIa20zhJRHyKgE=
|
||||
github.com/veandco/go-sdl2 v0.0.0-20180925095440-75ff82abc4e3/go.mod h1:FB+kTpX9YTE+urhYiClnRzpOXbiWgaU3+5F2AB78DPg=
|
||||
golang.org/x/exp v0.0.0-20181106170214-d68db9428509 h1:k21GX33vzpH/syMF7TgrLxe8ILtvwbyuHtEO3ebR82E=
|
||||
golang.org/x/exp v0.0.0-20181106170214-d68db9428509/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/image v0.0.0-20181109002202-aa35264064ba h1:tKfAeDKyjJZwxAJ8TPBZaf6LpvauubUHT8wwpdz+OMM=
|
||||
golang.org/x/image v0.0.0-20181109002202-aa35264064ba/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
|
||||
golang.org/x/mobile v0.0.0-20181026062114-a27dd33d354d h1:DuZZDdMFwDrzmycNhCaWSve7Vh+BIrjm7ttgb4fD3Os=
|
||||
golang.org/x/mobile v0.0.0-20181026062114-a27dd33d354d/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8 h1:YoY1wS6JYVRpIfFngRf2HHo9R9dAne3xbkGOQ5rJXjU=
|
||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/image v0.0.0-20200801110659-972c09e46d76 h1:U7GPaoQyQmX+CBRWXKrvRzWTbd+slqeSh8uARsIyhAw=
|
||||
golang.org/x/image v0.0.0-20200801110659-972c09e46d76/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
|
197
gradients.go
|
@ -1,8 +1,10 @@
|
|||
package canvas
|
||||
|
||||
import (
|
||||
"math"
|
||||
"image/color"
|
||||
"runtime"
|
||||
|
||||
"git.mstar.dev/mstar/canvas/backend/backendbase"
|
||||
)
|
||||
|
||||
// LinearGradient is a gradient with any number of
|
||||
|
@ -10,7 +12,13 @@ import (
|
|||
// be drawn such that each point on the gradient
|
||||
// will correspond to a straight line
|
||||
type LinearGradient struct {
|
||||
gradient
|
||||
cv *Canvas
|
||||
from, to backendbase.Vec
|
||||
created bool
|
||||
loaded bool
|
||||
opaque bool
|
||||
grad backendbase.LinearGradient
|
||||
data backendbase.Gradient
|
||||
}
|
||||
|
||||
// RadialGradient is a gradient with any number of
|
||||
|
@ -18,146 +26,123 @@ type LinearGradient struct {
|
|||
// be drawn such that each point on the gradient
|
||||
// will correspond to a circle
|
||||
type RadialGradient struct {
|
||||
gradient
|
||||
radFrom, radTo float64
|
||||
}
|
||||
|
||||
type gradient struct {
|
||||
from, to vec
|
||||
stops []gradientStop
|
||||
tex uint32
|
||||
cv *Canvas
|
||||
from, to backendbase.Vec
|
||||
radFrom float64
|
||||
radTo float64
|
||||
created bool
|
||||
loaded bool
|
||||
deleted bool
|
||||
opaque bool
|
||||
grad backendbase.RadialGradient
|
||||
data backendbase.Gradient
|
||||
}
|
||||
|
||||
type gradientStop struct {
|
||||
pos float64
|
||||
color glColor
|
||||
}
|
||||
|
||||
// NewLinearGradient creates a new linear gradient with
|
||||
// CreateLinearGradient creates a new linear gradient with
|
||||
// the coordinates from where to where the gradient
|
||||
// will apply on the canvas
|
||||
func NewLinearGradient(x0, y0, x1, y1 float64) *LinearGradient {
|
||||
if gli == nil {
|
||||
panic("LoadGL must be called before gradients can be created")
|
||||
func (cv *Canvas) CreateLinearGradient(x0, y0, x1, y1 float64) *LinearGradient {
|
||||
lg := &LinearGradient{
|
||||
cv: cv,
|
||||
opaque: true,
|
||||
from: backendbase.Vec{x0, y0},
|
||||
to: backendbase.Vec{x1, y1},
|
||||
data: make(backendbase.Gradient, 0, 20),
|
||||
}
|
||||
lg := &LinearGradient{gradient: gradient{from: vec{x0, y0}, to: vec{x1, y1}, opaque: true}}
|
||||
gli.GenTextures(1, &lg.tex)
|
||||
gli.ActiveTexture(gl_TEXTURE0)
|
||||
gli.BindTexture(gl_TEXTURE_2D, lg.tex)
|
||||
gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, gl_LINEAR)
|
||||
gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, gl_LINEAR)
|
||||
gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_S, gl_CLAMP_TO_EDGE)
|
||||
gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_T, gl_CLAMP_TO_EDGE)
|
||||
runtime.SetFinalizer(lg, func(lg *LinearGradient) {
|
||||
glChan <- func() {
|
||||
gli.DeleteTextures(1, &lg.tex)
|
||||
}
|
||||
runtime.SetFinalizer(lg, func(*LinearGradient) {
|
||||
lg.grad.Delete()
|
||||
})
|
||||
return lg
|
||||
}
|
||||
|
||||
// NewRadialGradient creates a new linear gradient with
|
||||
// CreateRadialGradient creates a new radial gradient with
|
||||
// the coordinates and the radii for two circles. The
|
||||
// gradient will apply from the first to the second
|
||||
// circle
|
||||
func NewRadialGradient(x0, y0, r0, x1, y1, r1 float64) *RadialGradient {
|
||||
if gli == nil {
|
||||
panic("LoadGL must be called before gradients can be created")
|
||||
func (cv *Canvas) CreateRadialGradient(x0, y0, r0, x1, y1, r1 float64) *RadialGradient {
|
||||
rg := &RadialGradient{
|
||||
cv: cv,
|
||||
opaque: true,
|
||||
from: backendbase.Vec{x0, y0},
|
||||
to: backendbase.Vec{x1, y1},
|
||||
radFrom: r0,
|
||||
radTo: r1,
|
||||
data: make(backendbase.Gradient, 0, 20),
|
||||
}
|
||||
rg := &RadialGradient{gradient: gradient{from: vec{x0, y0}, to: vec{x1, y1}, opaque: true}, radFrom: r0, radTo: r1}
|
||||
gli.GenTextures(1, &rg.tex)
|
||||
gli.ActiveTexture(gl_TEXTURE0)
|
||||
gli.BindTexture(gl_TEXTURE_2D, rg.tex)
|
||||
gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, gl_LINEAR)
|
||||
gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, gl_LINEAR)
|
||||
gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_S, gl_CLAMP_TO_EDGE)
|
||||
gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_T, gl_CLAMP_TO_EDGE)
|
||||
runtime.SetFinalizer(rg, func(rg *RadialGradient) {
|
||||
glChan <- func() {
|
||||
gli.DeleteTextures(1, &rg.tex)
|
||||
}
|
||||
runtime.SetFinalizer(rg, func(*RadialGradient) {
|
||||
rg.grad.Delete()
|
||||
})
|
||||
return rg
|
||||
}
|
||||
|
||||
// Delete explicitly deletes the gradient
|
||||
func (g *gradient) Delete() {
|
||||
gli.DeleteTextures(1, &g.tex)
|
||||
g.deleted = true
|
||||
}
|
||||
|
||||
func (g *gradient) load() {
|
||||
if g.loaded {
|
||||
func (lg *LinearGradient) load() {
|
||||
if lg.loaded || len(lg.data) < 1 {
|
||||
return
|
||||
}
|
||||
|
||||
gli.ActiveTexture(gl_TEXTURE0)
|
||||
gli.BindTexture(gl_TEXTURE_2D, g.tex)
|
||||
var pixels [2048 * 4]byte
|
||||
pp := 0
|
||||
for i := 0; i < 2048; i++ {
|
||||
c := g.colorAt(float64(i) / 2047)
|
||||
pixels[pp] = byte(math.Floor(c.r*255 + 0.5))
|
||||
pixels[pp+1] = byte(math.Floor(c.g*255 + 0.5))
|
||||
pixels[pp+2] = byte(math.Floor(c.b*255 + 0.5))
|
||||
pixels[pp+3] = byte(math.Floor(c.a*255 + 0.5))
|
||||
pp += 4
|
||||
if !lg.created {
|
||||
lg.grad = lg.cv.b.LoadLinearGradient(lg.data)
|
||||
} else {
|
||||
lg.grad.Replace(lg.data)
|
||||
}
|
||||
gli.TexImage2D(gl_TEXTURE_2D, 0, gl_RGBA, 2048, 1, 0, gl_RGBA, gl_UNSIGNED_BYTE, gli.Ptr(&pixels[0]))
|
||||
g.loaded = true
|
||||
lg.created = true
|
||||
lg.loaded = true
|
||||
}
|
||||
|
||||
func (g *gradient) colorAt(pos float64) glColor {
|
||||
if len(g.stops) == 0 {
|
||||
return glColor{}
|
||||
} else if len(g.stops) == 1 {
|
||||
return g.stops[0].color
|
||||
func (rg *RadialGradient) load() {
|
||||
if rg.loaded || len(rg.data) < 1 {
|
||||
return
|
||||
}
|
||||
beforeIdx, afterIdx := -1, -1
|
||||
for i, stop := range g.stops {
|
||||
if stop.pos > pos {
|
||||
afterIdx = i
|
||||
break
|
||||
}
|
||||
beforeIdx = i
|
||||
|
||||
if !rg.created {
|
||||
rg.grad = rg.cv.b.LoadRadialGradient(rg.data)
|
||||
} else {
|
||||
rg.grad.Replace(rg.data)
|
||||
}
|
||||
if beforeIdx == -1 {
|
||||
return g.stops[0].color
|
||||
} else if afterIdx == -1 {
|
||||
return g.stops[len(g.stops)-1].color
|
||||
}
|
||||
before, after := g.stops[beforeIdx], g.stops[afterIdx]
|
||||
p := (pos - before.pos) / (after.pos - before.pos)
|
||||
var c glColor
|
||||
c.r = (after.color.r-before.color.r)*p + before.color.r
|
||||
c.g = (after.color.g-before.color.g)*p + before.color.g
|
||||
c.b = (after.color.b-before.color.b)*p + before.color.b
|
||||
c.a = (after.color.a-before.color.a)*p + before.color.a
|
||||
return c
|
||||
rg.created = true
|
||||
rg.loaded = true
|
||||
}
|
||||
|
||||
// AddColorStop adds a color stop to the gradient. The stops
|
||||
// don't have to be added in order, they are sorted into the
|
||||
// right place
|
||||
func (g *gradient) AddColorStop(pos float64, color ...interface{}) {
|
||||
c, _ := parseColor(color...)
|
||||
if c.a < 1 {
|
||||
g.opaque = false
|
||||
func (lg *LinearGradient) AddColorStop(pos float64, stopColor ...interface{}) {
|
||||
var c color.RGBA
|
||||
lg.data, c = addColorStop(lg.data, pos, stopColor...)
|
||||
if c.A < 255 {
|
||||
lg.opaque = false
|
||||
}
|
||||
insert := len(g.stops)
|
||||
for i, stop := range g.stops {
|
||||
if stop.pos > pos {
|
||||
lg.loaded = false
|
||||
}
|
||||
|
||||
// AddColorStop adds a color stop to the gradient. The stops
|
||||
// don't have to be added in order, they are sorted into the
|
||||
// right place
|
||||
func (rg *RadialGradient) AddColorStop(pos float64, stopColor ...interface{}) {
|
||||
var c color.RGBA
|
||||
rg.data, c = addColorStop(rg.data, pos, stopColor...)
|
||||
if c.A < 255 {
|
||||
rg.opaque = false
|
||||
}
|
||||
rg.loaded = false
|
||||
}
|
||||
|
||||
func addColorStop(
|
||||
stops backendbase.Gradient,
|
||||
pos float64,
|
||||
stopColor ...interface{},
|
||||
) (backendbase.Gradient, color.RGBA) {
|
||||
c, _ := parseColor(stopColor...)
|
||||
insert := len(stops)
|
||||
for i, stop := range stops {
|
||||
if stop.Pos > pos {
|
||||
insert = i
|
||||
break
|
||||
}
|
||||
}
|
||||
g.stops = append(g.stops, gradientStop{})
|
||||
if insert < len(g.stops)-1 {
|
||||
copy(g.stops[insert+1:], g.stops[insert:len(g.stops)-1])
|
||||
stops = append(stops, backendbase.GradientStop{})
|
||||
if insert < len(stops)-1 {
|
||||
copy(stops[insert+1:], stops[insert:len(stops)-1])
|
||||
}
|
||||
g.stops[insert] = gradientStop{pos: pos, color: c}
|
||||
g.loaded = false
|
||||
stops[insert] = backendbase.GradientStop{Pos: pos, Color: c}
|
||||
return stops, c
|
||||
}
|
||||
|
|
96
imagedata.go
|
@ -1,96 +0,0 @@
|
|||
package canvas
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var imageBufTex uint32
|
||||
var imageBuf []byte
|
||||
|
||||
// GetImageData returns an RGBA image of the currently displayed image. The
|
||||
// alpha channel is always opaque
|
||||
func (cv *Canvas) GetImageData(x, y, w, h int) *image.RGBA {
|
||||
cv.activate()
|
||||
|
||||
if x < 0 {
|
||||
w += x
|
||||
x = 0
|
||||
}
|
||||
if y < 0 {
|
||||
h += y
|
||||
y = 0
|
||||
}
|
||||
if w > cv.w {
|
||||
w = cv.w
|
||||
}
|
||||
if h > cv.h {
|
||||
h = cv.h
|
||||
}
|
||||
if len(imageBuf) < w*h*3 {
|
||||
imageBuf = make([]byte, w*h*3)
|
||||
}
|
||||
|
||||
gli.ReadPixels(int32(x), int32(y), int32(w), int32(h), gl_RGB, gl_UNSIGNED_BYTE, gli.Ptr(&imageBuf[0]))
|
||||
rgba := image.NewRGBA(image.Rect(x, y, x+w, y+h))
|
||||
bp := 0
|
||||
for cy := y; cy < y+h; cy++ {
|
||||
for cx := x; cx < x+w; cx++ {
|
||||
rgba.SetRGBA(cx, y+h-1-cy, color.RGBA{R: imageBuf[bp], G: imageBuf[bp+1], B: imageBuf[bp+2], A: 255})
|
||||
bp += 3
|
||||
}
|
||||
}
|
||||
return rgba
|
||||
}
|
||||
|
||||
// PutImageData puts the given image at the given x/y coordinates
|
||||
func (cv *Canvas) PutImageData(img *image.RGBA, x, y int) {
|
||||
cv.activate()
|
||||
|
||||
gli.ActiveTexture(gl_TEXTURE0)
|
||||
if imageBufTex == 0 {
|
||||
gli.GenTextures(1, &imageBufTex)
|
||||
gli.BindTexture(gl_TEXTURE_2D, imageBufTex)
|
||||
gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, gl_LINEAR)
|
||||
gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, gl_LINEAR)
|
||||
gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_S, gl_CLAMP_TO_EDGE)
|
||||
gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_T, gl_CLAMP_TO_EDGE)
|
||||
} else {
|
||||
gli.BindTexture(gl_TEXTURE_2D, imageBufTex)
|
||||
}
|
||||
|
||||
w, h := img.Bounds().Dx(), img.Bounds().Dy()
|
||||
|
||||
if img.Stride == img.Bounds().Dx()*4 {
|
||||
gli.TexImage2D(gl_TEXTURE_2D, 0, gl_RGBA, int32(w), int32(h), 0, gl_RGBA, gl_UNSIGNED_BYTE, gli.Ptr(&img.Pix[0]))
|
||||
} else {
|
||||
data := make([]uint8, 0, w*h*4)
|
||||
for cy := 0; cy < h; cy++ {
|
||||
start := cy * img.Stride
|
||||
end := start + w*4
|
||||
data = append(data, img.Pix[start:end]...)
|
||||
}
|
||||
gli.TexImage2D(gl_TEXTURE_2D, 0, gl_RGBA, int32(w), int32(h), 0, gl_RGBA, gl_UNSIGNED_BYTE, gli.Ptr(&data[0]))
|
||||
}
|
||||
|
||||
dx, dy := float32(x), float32(y)
|
||||
dw, dh := float32(w), float32(h)
|
||||
|
||||
gli.BindBuffer(gl_ARRAY_BUFFER, buf)
|
||||
data := [16]float32{dx, dy, dx + dw, dy, dx + dw, dy + dh, dx, dy + dh,
|
||||
0, 0, 1, 0, 1, 1, 0, 1}
|
||||
gli.BufferData(gl_ARRAY_BUFFER, len(data)*4, unsafe.Pointer(&data[0]), gl_STREAM_DRAW)
|
||||
|
||||
gli.UseProgram(ir.id)
|
||||
gli.Uniform1i(ir.image, 0)
|
||||
gli.Uniform2f(ir.canvasSize, float32(cv.fw), float32(cv.fh))
|
||||
gli.Uniform1f(ir.globalAlpha, 1)
|
||||
gli.VertexAttribPointer(ir.vertex, 2, gl_FLOAT, false, 0, 0)
|
||||
gli.VertexAttribPointer(ir.texCoord, 2, gl_FLOAT, false, 0, 8*4)
|
||||
gli.EnableVertexAttribArray(ir.vertex)
|
||||
gli.EnableVertexAttribArray(ir.texCoord)
|
||||
gli.DrawArrays(gl_TRIANGLE_FAN, 0, 4)
|
||||
gli.DisableVertexAttribArray(ir.vertex)
|
||||
gli.DisableVertexAttribArray(ir.texCoord)
|
||||
}
|
453
images.go
|
@ -5,262 +5,153 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"unsafe"
|
||||
"time"
|
||||
|
||||
"git.mstar.dev/mstar/canvas/backend/backendbase"
|
||||
)
|
||||
|
||||
// Image represents a loaded image that can be used in various drawing functions
|
||||
// Image is a type holding information on an image loaded with the LoadImage
|
||||
// function
|
||||
type Image struct {
|
||||
w, h int
|
||||
tex uint32
|
||||
deleted bool
|
||||
opaque bool
|
||||
src interface{}
|
||||
cv *Canvas
|
||||
img backendbase.Image
|
||||
deleted bool
|
||||
lastUsed time.Time
|
||||
}
|
||||
|
||||
var images = make(map[interface{}]*Image)
|
||||
|
||||
// LoadImage loads an image. The src parameter can be either an image from the
|
||||
// standard image package, a byte slice that will be loaded, or a file name
|
||||
// string. If you want the canvas package to load the image, make sure you
|
||||
// import the required format packages
|
||||
func LoadImage(src interface{}) (*Image, error) {
|
||||
if gli == nil {
|
||||
panic("LoadGL must be called before images can be loaded")
|
||||
}
|
||||
|
||||
var tex uint32
|
||||
gli.GenTextures(1, &tex)
|
||||
gli.ActiveTexture(gl_TEXTURE0)
|
||||
gli.BindTexture(gl_TEXTURE_2D, tex)
|
||||
if src == nil {
|
||||
return &Image{tex: tex}, nil
|
||||
}
|
||||
|
||||
img, err := loadImage(src, tex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
runtime.SetFinalizer(img, func(img *Image) {
|
||||
if !img.deleted {
|
||||
glChan <- func() {
|
||||
gli.DeleteTextures(1, &img.tex)
|
||||
}
|
||||
func (cv *Canvas) LoadImage(src interface{}) (*Image, error) {
|
||||
var reload *Image
|
||||
if img, ok := src.(*Image); ok {
|
||||
if img.cv != cv {
|
||||
panic("image loaded with different canvas")
|
||||
}
|
||||
})
|
||||
|
||||
return img, nil
|
||||
}
|
||||
|
||||
func loadImage(src interface{}, tex uint32) (*Image, error) {
|
||||
var img *Image
|
||||
var err error
|
||||
if img.deleted {
|
||||
reload = img
|
||||
src = img.src
|
||||
} else {
|
||||
img.lastUsed = time.Now()
|
||||
return img, nil
|
||||
}
|
||||
} else if _, ok := src.([]byte); !ok {
|
||||
if img, ok := cv.images[src]; ok {
|
||||
img.lastUsed = time.Now()
|
||||
return img, nil
|
||||
}
|
||||
}
|
||||
cv.reduceCache(Performance.CacheSize, 0)
|
||||
var srcImg image.Image
|
||||
switch v := src.(type) {
|
||||
case *image.RGBA:
|
||||
img, err = loadImageRGBA(v, tex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case *image.Gray:
|
||||
img, err = loadImageGray(v, tex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case image.Image:
|
||||
img, err = loadImageConverted(v, tex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
srcImg = v
|
||||
case string:
|
||||
data, err := ioutil.ReadFile(v)
|
||||
data, err := os.ReadFile(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
srcImg, _, err := image.Decode(bytes.NewReader(data))
|
||||
srcImg, _, err = image.Decode(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return loadImage(srcImg, tex)
|
||||
case []byte:
|
||||
srcImg, _, err := image.Decode(bytes.NewReader(v))
|
||||
var err error
|
||||
srcImg, _, err = image.Decode(bytes.NewReader(v))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return loadImage(srcImg, tex)
|
||||
case *Canvas:
|
||||
w, h := cv.b.Size()
|
||||
src = cv.GetImageData(0, 0, w, h)
|
||||
default:
|
||||
return nil, errors.New("Unsupported source type")
|
||||
}
|
||||
return img, nil
|
||||
backendImg, err := cv.b.LoadImage(srcImg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cvimg := &Image{cv: cv, img: backendImg, lastUsed: time.Now(), src: src}
|
||||
if reload != nil {
|
||||
*reload = *cvimg
|
||||
return reload, nil
|
||||
}
|
||||
if _, ok := src.([]byte); !ok {
|
||||
cv.images[src] = cvimg
|
||||
}
|
||||
return cvimg, nil
|
||||
}
|
||||
|
||||
func getImage(src interface{}) *Image {
|
||||
if img, ok := images[src]; ok {
|
||||
return img
|
||||
}
|
||||
switch v := src.(type) {
|
||||
case *Image:
|
||||
return v
|
||||
case image.Image:
|
||||
img, err := LoadImage(v)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error loading image: %v\n", err)
|
||||
images[src] = nil
|
||||
return nil
|
||||
func (cv *Canvas) getImage(src interface{}) *Image {
|
||||
if cv2, ok := src.(*Canvas); ok {
|
||||
if !cv.b.CanUseAsImage(cv2.b) {
|
||||
w, h := cv2.Size()
|
||||
return cv.getImage(cv2.GetImageData(0, 0, w, h))
|
||||
}
|
||||
images[v] = img
|
||||
return img
|
||||
case string:
|
||||
img, err := LoadImage(v)
|
||||
if err != nil {
|
||||
bimg := cv2.b.AsImage()
|
||||
if bimg == nil {
|
||||
w, h := cv2.Size()
|
||||
return cv.getImage(cv2.GetImageData(0, 0, w, h))
|
||||
}
|
||||
return &Image{cv: cv, img: bimg}
|
||||
}
|
||||
|
||||
img, err := cv.LoadImage(src)
|
||||
if err != nil {
|
||||
cv.images[src] = nil
|
||||
switch v := src.(type) {
|
||||
case image.Image:
|
||||
fmt.Fprintf(os.Stderr, "Error loading image: %v\n", err)
|
||||
case string:
|
||||
if strings.Contains(strings.ToLower(err.Error()), "format") {
|
||||
fmt.Fprintf(os.Stderr, "Error loading image %s: %v\nIt may be necessary to import the appropriate decoder, e.g.\nimport _ \"image/jpeg\"\n", v, err)
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, "Error loading image %s: %v\n", v, err)
|
||||
}
|
||||
images[src] = nil
|
||||
return nil
|
||||
}
|
||||
images[v] = img
|
||||
return img
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "Unknown image type: %T\n", src)
|
||||
images[src] = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadImageRGBA(src *image.RGBA, tex uint32) (*Image, error) {
|
||||
img := &Image{tex: tex, w: src.Bounds().Dx(), h: src.Bounds().Dy(), opaque: true}
|
||||
|
||||
checkOpaque:
|
||||
for y := 0; y < img.h; y++ {
|
||||
off := src.PixOffset(0, y) + 3
|
||||
for x := 0; x < img.w; x++ {
|
||||
if src.Pix[off] < 255 {
|
||||
img.opaque = false
|
||||
break checkOpaque
|
||||
}
|
||||
off += 4
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "Failed to load image: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, gl_LINEAR_MIPMAP_LINEAR)
|
||||
gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, gl_LINEAR)
|
||||
gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_S, gl_CLAMP_TO_EDGE)
|
||||
gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_T, gl_CLAMP_TO_EDGE)
|
||||
if err := glError(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if src.Stride == img.w*4 {
|
||||
gli.TexImage2D(gl_TEXTURE_2D, 0, gl_RGBA, int32(img.w), int32(img.h), 0, gl_RGBA, gl_UNSIGNED_BYTE, gli.Ptr(&src.Pix[0]))
|
||||
} else {
|
||||
data := make([]uint8, 0, img.w*img.h*4)
|
||||
for y := 0; y < img.h; y++ {
|
||||
start := y * src.Stride
|
||||
end := start + img.w*4
|
||||
data = append(data, src.Pix[start:end]...)
|
||||
}
|
||||
gli.TexImage2D(gl_TEXTURE_2D, 0, gl_RGBA, int32(img.w), int32(img.h), 0, gl_RGBA, gl_UNSIGNED_BYTE, gli.Ptr(&data[0]))
|
||||
}
|
||||
if err := glError(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gli.GenerateMipmap(gl_TEXTURE_2D)
|
||||
if err := glError(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return img, nil
|
||||
}
|
||||
|
||||
func loadImageGray(src *image.Gray, tex uint32) (*Image, error) {
|
||||
img := &Image{tex: tex, w: src.Bounds().Dx(), h: src.Bounds().Dy()}
|
||||
gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, gl_LINEAR_MIPMAP_LINEAR)
|
||||
gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, gl_LINEAR)
|
||||
gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_S, gl_CLAMP_TO_EDGE)
|
||||
gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_T, gl_CLAMP_TO_EDGE)
|
||||
if err := glError(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if src.Stride == img.w {
|
||||
gli.TexImage2D(gl_TEXTURE_2D, 0, gl_RED, int32(img.w), int32(img.h), 0, gl_RED, gl_UNSIGNED_BYTE, gli.Ptr(&src.Pix[0]))
|
||||
} else {
|
||||
data := make([]uint8, 0, img.w*img.h)
|
||||
for y := 0; y < img.h; y++ {
|
||||
start := y * src.Stride
|
||||
end := start + img.w
|
||||
data = append(data, src.Pix[start:end]...)
|
||||
}
|
||||
gli.TexImage2D(gl_TEXTURE_2D, 0, gl_RED, int32(img.w), int32(img.h), 0, gl_RED, gl_UNSIGNED_BYTE, gli.Ptr(&data[0]))
|
||||
}
|
||||
if err := glError(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gli.GenerateMipmap(gl_TEXTURE_2D)
|
||||
if err := glError(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return img, nil
|
||||
}
|
||||
|
||||
func loadImageConverted(src image.Image, tex uint32) (*Image, error) {
|
||||
img := &Image{tex: tex, w: src.Bounds().Dx(), h: src.Bounds().Dy(), opaque: true}
|
||||
gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, gl_LINEAR_MIPMAP_LINEAR)
|
||||
gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, gl_LINEAR)
|
||||
gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_S, gl_CLAMP_TO_EDGE)
|
||||
gli.TexParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_T, gl_CLAMP_TO_EDGE)
|
||||
if err := glError(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := make([]uint8, 0, img.w*img.h*4)
|
||||
for y := 0; y < img.h; y++ {
|
||||
for x := 0; x < img.w; x++ {
|
||||
ir, ig, ib, ia := src.At(x, y).RGBA()
|
||||
r, g, b, a := uint8(ir>>8), uint8(ig>>8), uint8(ib>>8), uint8(ia>>8)
|
||||
data = append(data, r, g, b, a)
|
||||
if a < 255 {
|
||||
img.opaque = false
|
||||
}
|
||||
}
|
||||
}
|
||||
gli.TexImage2D(gl_TEXTURE_2D, 0, gl_RGBA, int32(img.w), int32(img.h), 0, gl_RGBA, gl_UNSIGNED_BYTE, gli.Ptr(&data[0]))
|
||||
if err := glError(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gli.GenerateMipmap(gl_TEXTURE_2D)
|
||||
if err := glError(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return img, nil
|
||||
return img
|
||||
}
|
||||
|
||||
// Width returns the width of the image
|
||||
func (img *Image) Width() int { return img.w }
|
||||
func (img *Image) Width() int { return img.img.Width() }
|
||||
|
||||
// Height returns the height of the image
|
||||
func (img *Image) Height() int { return img.h }
|
||||
func (img *Image) Height() int { return img.img.Height() }
|
||||
|
||||
// Size returns the width and height of the image
|
||||
func (img *Image) Size() (int, int) { return img.w, img.h }
|
||||
func (img *Image) Size() (int, int) { return img.img.Size() }
|
||||
|
||||
// Delete deletes the image from memory. Any draw calls with a deleted image
|
||||
// will not do anything
|
||||
// Delete deletes the image from memory
|
||||
func (img *Image) Delete() {
|
||||
gli.DeleteTextures(1, &img.tex)
|
||||
if img == nil || img.deleted {
|
||||
return
|
||||
}
|
||||
img.deleted = true
|
||||
img.img.Delete()
|
||||
delete(img.cv.images, img.src)
|
||||
}
|
||||
|
||||
// Replace replaces the image with the new one
|
||||
func (img *Image) Replace(src interface{}) {
|
||||
gli.ActiveTexture(gl_TEXTURE0)
|
||||
gli.BindTexture(gl_TEXTURE_2D, img.tex)
|
||||
newImg, err := loadImage(src, img.tex)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error replacing image: %v\n", err)
|
||||
return
|
||||
func (img *Image) Replace(src interface{}) error {
|
||||
if img.src == src {
|
||||
if origImg, ok := img.src.(image.Image); ok {
|
||||
img.img.Replace(origImg)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
*img = *newImg
|
||||
newImg, err := img.cv.LoadImage(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
img.img = newImg.img
|
||||
return nil
|
||||
}
|
||||
|
||||
// DrawImage draws the given image to the given coordinates. The image
|
||||
|
@ -269,34 +160,22 @@ func (img *Image) Replace(src interface{}) {
|
|||
// loaded image with the same name parameter.
|
||||
//
|
||||
// The coordinates must be one of the following:
|
||||
// DrawImage("image", dx, dy)
|
||||
// DrawImage("image", dx, dy, dw, dh)
|
||||
// DrawImage("image", sx, sy, sw, sh, dx, dy, dw, dh)
|
||||
//
|
||||
// DrawImage("image", dx, dy)
|
||||
// DrawImage("image", dx, dy, dw, dh)
|
||||
// DrawImage("image", sx, sy, sw, sh, dx, dy, dw, dh)
|
||||
//
|
||||
// Where dx/dy/dw/dh are the destination coordinates and sx/sy/sw/sh are the
|
||||
// source coordinates
|
||||
func (cv *Canvas) DrawImage(image interface{}, coords ...float64) {
|
||||
var img *Image
|
||||
var flip bool
|
||||
if cv2, ok := image.(*Canvas); ok && cv2.offscreen {
|
||||
img = &cv2.offscrImg
|
||||
flip = true
|
||||
} else {
|
||||
img = getImage(image)
|
||||
}
|
||||
|
||||
img := cv.getImage(image)
|
||||
if img == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if img.deleted {
|
||||
return
|
||||
}
|
||||
|
||||
cv.activate()
|
||||
|
||||
var sx, sy, sw, sh, dx, dy, dw, dh float64
|
||||
sw, sh = float64(img.w), float64(img.h)
|
||||
dw, dh = float64(img.w), float64(img.h)
|
||||
sw, sh = float64(img.Width()), float64(img.Height())
|
||||
dw, dh = float64(img.Width()), float64(img.Height())
|
||||
if len(coords) == 2 {
|
||||
dx, dy = coords[0], coords[1]
|
||||
} else if len(coords) == 4 {
|
||||
|
@ -309,60 +188,78 @@ func (cv *Canvas) DrawImage(image interface{}, coords ...float64) {
|
|||
dw, dh = coords[6], coords[7]
|
||||
}
|
||||
|
||||
if flip {
|
||||
dy += dh
|
||||
dh = -dh
|
||||
}
|
||||
var data [4]backendbase.Vec
|
||||
data[0] = cv.tf(backendbase.Vec{dx, dy})
|
||||
data[1] = cv.tf(backendbase.Vec{dx, dy + dh})
|
||||
data[2] = cv.tf(backendbase.Vec{dx + dw, dy + dh})
|
||||
data[3] = cv.tf(backendbase.Vec{dx + dw, dy})
|
||||
|
||||
sx /= float64(img.w)
|
||||
sy /= float64(img.h)
|
||||
sw /= float64(img.w)
|
||||
sh /= float64(img.h)
|
||||
cv.drawShadow(data[:], nil, false)
|
||||
|
||||
p0 := cv.tf(vec{dx, dy})
|
||||
p1 := cv.tf(vec{dx, dy + dh})
|
||||
p2 := cv.tf(vec{dx + dw, dy + dh})
|
||||
p3 := cv.tf(vec{dx + dw, dy})
|
||||
|
||||
if cv.state.shadowColor.a != 0 {
|
||||
tris := [24]float32{
|
||||
0, 0,
|
||||
float32(cv.fw), 0,
|
||||
float32(cv.fw), float32(cv.fh),
|
||||
0, 0,
|
||||
float32(cv.fw), float32(cv.fh),
|
||||
0, float32(cv.fh),
|
||||
float32(p0[0]), float32(p0[1]),
|
||||
float32(p3[0]), float32(p3[1]),
|
||||
float32(p2[0]), float32(p2[1]),
|
||||
float32(p0[0]), float32(p0[1]),
|
||||
float32(p2[0]), float32(p2[1]),
|
||||
float32(p1[0]), float32(p1[1]),
|
||||
}
|
||||
cv.drawShadow(tris[:])
|
||||
}
|
||||
|
||||
gli.StencilFunc(gl_EQUAL, 0, 0xFF)
|
||||
|
||||
gli.BindBuffer(gl_ARRAY_BUFFER, buf)
|
||||
data := [16]float32{float32(p0[0]), float32(p0[1]), float32(p1[0]), float32(p1[1]), float32(p2[0]), float32(p2[1]), float32(p3[0]), float32(p3[1]),
|
||||
float32(sx), float32(sy), float32(sx), float32(sy + sh), float32(sx + sw), float32(sy + sh), float32(sx + sw), float32(sy)}
|
||||
gli.BufferData(gl_ARRAY_BUFFER, len(data)*4, unsafe.Pointer(&data[0]), gl_STREAM_DRAW)
|
||||
|
||||
gli.ActiveTexture(gl_TEXTURE0)
|
||||
gli.BindTexture(gl_TEXTURE_2D, img.tex)
|
||||
|
||||
gli.UseProgram(ir.id)
|
||||
gli.Uniform1i(ir.image, 0)
|
||||
gli.Uniform2f(ir.canvasSize, float32(cv.fw), float32(cv.fh))
|
||||
gli.Uniform1f(ir.globalAlpha, float32(cv.state.globalAlpha))
|
||||
gli.VertexAttribPointer(ir.vertex, 2, gl_FLOAT, false, 0, 0)
|
||||
gli.VertexAttribPointer(ir.texCoord, 2, gl_FLOAT, false, 0, 8*4)
|
||||
gli.EnableVertexAttribArray(ir.vertex)
|
||||
gli.EnableVertexAttribArray(ir.texCoord)
|
||||
gli.DrawArrays(gl_TRIANGLE_FAN, 0, 4)
|
||||
gli.DisableVertexAttribArray(ir.vertex)
|
||||
gli.DisableVertexAttribArray(ir.texCoord)
|
||||
|
||||
gli.StencilFunc(gl_ALWAYS, 0, 0xFF)
|
||||
cv.b.DrawImage(img.img, sx, sy, sw, sh, data, cv.state.globalAlpha)
|
||||
}
|
||||
|
||||
// GetImageData returns an RGBA image of the current image
|
||||
func (cv *Canvas) GetImageData(x, y, w, h int) *image.RGBA {
|
||||
return cv.b.GetImageData(x, y, w, h)
|
||||
}
|
||||
|
||||
// PutImageData puts the given image at the given x/y coordinates
|
||||
func (cv *Canvas) PutImageData(img *image.RGBA, x, y int) {
|
||||
cv.b.PutImageData(img, x, y)
|
||||
}
|
||||
|
||||
// ImagePattern is an image pattern that can be used for any
|
||||
// fill call
|
||||
type ImagePattern struct {
|
||||
cv *Canvas
|
||||
img *Image
|
||||
tf backendbase.Mat
|
||||
rep imagePatternRepeat
|
||||
ip backendbase.ImagePattern
|
||||
}
|
||||
|
||||
type imagePatternRepeat uint8
|
||||
|
||||
// Image pattern repeat constants
|
||||
const (
|
||||
Repeat imagePatternRepeat = imagePatternRepeat(backendbase.Repeat)
|
||||
RepeatX = imagePatternRepeat(backendbase.RepeatX)
|
||||
RepeatY = imagePatternRepeat(backendbase.RepeatY)
|
||||
NoRepeat = imagePatternRepeat(backendbase.NoRepeat)
|
||||
)
|
||||
|
||||
func (ip *ImagePattern) data(tf backendbase.Mat) backendbase.ImagePatternData {
|
||||
m := tf.Invert().Mul(ip.tf.Invert())
|
||||
return backendbase.ImagePatternData{
|
||||
Image: ip.img.img,
|
||||
Transform: [9]float64{
|
||||
m[0], m[2], m[4],
|
||||
m[1], m[3], m[5],
|
||||
0, 0, 1,
|
||||
},
|
||||
Repeat: backendbase.ImagePatternRepeat(ip.rep),
|
||||
}
|
||||
}
|
||||
|
||||
// SetTransform changes the transformation of the image pattern
|
||||
// to the given matrix. The matrix is a 3x3 matrix, but three
|
||||
// of the values are always identity values
|
||||
func (ip *ImagePattern) SetTransform(tf [6]float64) {
|
||||
ip.tf = backendbase.Mat(tf)
|
||||
}
|
||||
|
||||
// CreatePattern creates a new image pattern with the specified
|
||||
// image and repetition
|
||||
func (cv *Canvas) CreatePattern(src interface{}, repeat imagePatternRepeat) *ImagePattern {
|
||||
ip := &ImagePattern{
|
||||
cv: cv,
|
||||
img: cv.getImage(src),
|
||||
rep: repeat,
|
||||
tf: backendbase.Mat{1, 0, 0, 1, 0, 0},
|
||||
}
|
||||
if ip.img != nil {
|
||||
ip.ip = cv.b.LoadImagePattern(ip.data(cv.state.transform))
|
||||
}
|
||||
return ip
|
||||
}
|
||||
|
|
1120
made_shaders.go
312
make_shaders.go
|
@ -1,312 +0,0 @@
|
|||
//+build ignore
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// find the go files in the current directory
|
||||
pkg, err := build.Default.ImportDir(".", 0)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not process directory: %s", err)
|
||||
}
|
||||
|
||||
vsMap := make(map[string]string)
|
||||
fsMap := make(map[string]string)
|
||||
|
||||
// go through each file and find const raw string literals with
|
||||
fset := token.NewFileSet()
|
||||
for _, goFile := range pkg.GoFiles {
|
||||
parsedFile, err := parser.ParseFile(fset, goFile, nil, 0)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to parse file %s: %s", goFile, err)
|
||||
}
|
||||
|
||||
for _, obj := range parsedFile.Scope.Objects {
|
||||
isVS := strings.HasSuffix(obj.Name, "VS")
|
||||
isFS := strings.HasSuffix(obj.Name, "FS")
|
||||
var shaderCode string
|
||||
if isVS || isFS {
|
||||
if value, ok := obj.Decl.(*ast.ValueSpec); ok && len(value.Values) == 1 {
|
||||
if lit, ok := value.Values[0].(*ast.BasicLit); ok {
|
||||
shaderCode = lit.Value
|
||||
}
|
||||
}
|
||||
}
|
||||
if shaderCode != "" {
|
||||
baseName := obj.Name[:len(obj.Name)-2]
|
||||
if isVS {
|
||||
vsMap[baseName] = shaderCode
|
||||
} else if isFS {
|
||||
fsMap[baseName] = shaderCode
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for name := range vsMap {
|
||||
if _, ok := fsMap[name]; !ok {
|
||||
log.Println("Warning: Vertex shader with no corresponding fragment shader (" + name + ")")
|
||||
delete(vsMap, name)
|
||||
continue
|
||||
}
|
||||
}
|
||||
for name := range fsMap {
|
||||
if _, ok := vsMap[name]; !ok {
|
||||
log.Println("Warning: Fragment shader with no corresponding vertex shader (" + name + ")")
|
||||
delete(fsMap, name)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
goCode := &bytes.Buffer{}
|
||||
buildCodeHeader(goCode)
|
||||
|
||||
for name, vs := range vsMap {
|
||||
fs := fsMap[name]
|
||||
|
||||
vs = vs[1 : len(vs)-1]
|
||||
fs = fs[1 : len(fs)-1]
|
||||
|
||||
inputs := shaderFindInputVariables(vs + "\n" + fs)
|
||||
|
||||
buildCode(goCode, name, inputs)
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile("made_shaders.go", goCode.Bytes(), 0777)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not write made_shaders.go: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
type ShaderInput struct {
|
||||
Name string
|
||||
IsAttrib bool
|
||||
IsUniform bool
|
||||
}
|
||||
|
||||
func shaderFindInputVariables(source string) []ShaderInput {
|
||||
inputs := make([]ShaderInput, 0, 10)
|
||||
|
||||
varDefSplitter := regexp.MustCompile("[ \t\r\n,]+")
|
||||
lines := shaderGetTopLevelLines(source)
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if len(line) == 0 || line[0] == '#' {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(line, "{") {
|
||||
break
|
||||
}
|
||||
parts := varDefSplitter.Split(line, -1)
|
||||
isAttrib := parts[0] == "attribute"
|
||||
isUniform := parts[0] == "uniform"
|
||||
if !isAttrib && !isUniform {
|
||||
continue
|
||||
}
|
||||
for _, part := range parts[2:] {
|
||||
if idx := strings.IndexByte(part, '['); idx >= 0 {
|
||||
part = part[:idx]
|
||||
}
|
||||
inputs = append(inputs, ShaderInput{
|
||||
Name: part,
|
||||
IsAttrib: isAttrib,
|
||||
IsUniform: isUniform})
|
||||
}
|
||||
}
|
||||
return inputs
|
||||
}
|
||||
|
||||
func shaderGetTopLevelLines(source string) []string {
|
||||
sourceBytes := []byte(source)
|
||||
l := len(sourceBytes)
|
||||
if l == 0 {
|
||||
return make([]string, 0)
|
||||
}
|
||||
|
||||
var inPrecompiledStatement, inLineComment, inBlockComment, inString, stringEscape, lastWasWhitespace bool
|
||||
curlyBraceDepth := 0
|
||||
|
||||
topLevelLines := make([]string, 0, 100)
|
||||
currentLine := make([]byte, 0, 1000)
|
||||
|
||||
var c0, c1 byte = ' ', ' '
|
||||
for i := 0; i < l; i++ {
|
||||
c1 = sourceBytes[i]
|
||||
isWhitespace := unicode.IsSpace(rune(c1))
|
||||
if !inBlockComment && !inString && c0 == '/' && c1 == '/' {
|
||||
inLineComment = true
|
||||
if len(currentLine) > 0 {
|
||||
currentLine = currentLine[:len(currentLine)-1]
|
||||
}
|
||||
} else if !inBlockComment && !inString && c1 == '#' {
|
||||
inPrecompiledStatement = true
|
||||
} else if !inBlockComment && !inLineComment && !inPrecompiledStatement && !inString && c0 == '/' && c1 == '*' {
|
||||
inBlockComment = true
|
||||
if len(currentLine) > 0 {
|
||||
currentLine = currentLine[:len(currentLine)-1]
|
||||
}
|
||||
} else if !inBlockComment && !inString && !inLineComment && !inPrecompiledStatement && c1 == '"' {
|
||||
inString = true
|
||||
} else if inString && !stringEscape && c1 == '\\' {
|
||||
stringEscape = true
|
||||
} else if inString && stringEscape {
|
||||
stringEscape = false
|
||||
} else if inString && !stringEscape && c1 == '"' {
|
||||
inString = false
|
||||
} else if !inBlockComment && !inLineComment && !inPrecompiledStatement && !inString && c1 == '{' {
|
||||
if curlyBraceDepth == 0 {
|
||||
topLevelLines = append(topLevelLines, string(currentLine))
|
||||
currentLine = currentLine[:0]
|
||||
}
|
||||
curlyBraceDepth++
|
||||
}
|
||||
if !inBlockComment && !inLineComment && !inPrecompiledStatement && !inString && curlyBraceDepth == 0 {
|
||||
if c1 == ';' {
|
||||
topLevelLines = append(topLevelLines, string(currentLine))
|
||||
currentLine = currentLine[:0]
|
||||
} else if !isWhitespace {
|
||||
currentLine = append(currentLine, c1)
|
||||
} else if !lastWasWhitespace {
|
||||
currentLine = append(currentLine, ' ')
|
||||
}
|
||||
}
|
||||
if !inBlockComment && !inLineComment && !inPrecompiledStatement && !inString && c1 == '}' {
|
||||
curlyBraceDepth--
|
||||
} else if (inLineComment || inPrecompiledStatement) && (c1 == '\r' || c1 == '\n') {
|
||||
inLineComment = false
|
||||
inPrecompiledStatement = false
|
||||
} else if inBlockComment && c0 == '*' && c1 == '/' {
|
||||
inBlockComment = false
|
||||
}
|
||||
|
||||
lastWasWhitespace = isWhitespace
|
||||
c0 = c1
|
||||
}
|
||||
|
||||
topLevelLines = append(topLevelLines, string(currentLine))
|
||||
|
||||
return topLevelLines
|
||||
}
|
||||
|
||||
const compilePart = `
|
||||
{
|
||||
SHADER_VAR = gli.CreateShader(gl_SHADER_TYPE)
|
||||
gli.ShaderSource(SHADER_VAR, SHADER_SRC)
|
||||
gli.CompileShader(SHADER_VAR)
|
||||
|
||||
shLog := gli.GetShaderInfoLog(SHADER_VAR)
|
||||
if len(shLog) > 0 {
|
||||
fmt.Printf("SHADER_TYPE compilation log for SHADER_SRC:\n\n%s\n", shLog)
|
||||
}
|
||||
|
||||
var status int32
|
||||
gli.GetShaderiv(SHADER_VAR, gl_COMPILE_STATUS, &status)
|
||||
if status != gl_TRUE {
|
||||
gli.DeleteShader(SHADER_VAR)
|
||||
return nil, errors.New("Error compiling GL_SHADER_TYPE shader part for SHADER_SRC")
|
||||
}
|
||||
if glErr := gli.GetError(); glErr != gl_NO_ERROR {
|
||||
return nil, errors.New("error compiling shader part for SHADER_SRC, glError: " + fmt.Sprint(glErr))
|
||||
}
|
||||
}
|
||||
`
|
||||
const linkPart = `
|
||||
{
|
||||
program = gli.CreateProgram()
|
||||
gli.AttachShader(program, vs)
|
||||
gli.AttachShader(program, fs)
|
||||
gli.LinkProgram(program)
|
||||
|
||||
shLog := gli.GetProgramInfoLog(program)
|
||||
if len(shLog) > 0 {
|
||||
fmt.Printf("Shader link log for SHADER_SRC:\n\n%s\n", shLog)
|
||||
}
|
||||
|
||||
var status int32
|
||||
gli.GetProgramiv(program, gl_LINK_STATUS, &status)
|
||||
if status != gl_TRUE {
|
||||
gli.DeleteShader(vs)
|
||||
gli.DeleteShader(fs)
|
||||
return nil, errors.New("error linking shader for SHADER_SRC")
|
||||
}
|
||||
if glErr := gli.GetError(); glErr != gl_NO_ERROR {
|
||||
return nil, errors.New("error linking shader for SHADER_SRC, glError: " + fmt.Sprint(glErr))
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
func capitalize(s string) string {
|
||||
return strings.ToUpper(s[:1]) + s[1:]
|
||||
}
|
||||
|
||||
func buildCodeHeader(buf *bytes.Buffer) {
|
||||
fmt.Fprint(buf, "package canvas\n\n")
|
||||
fmt.Fprint(buf, "import (\n")
|
||||
fmt.Fprint(buf, "\t\"errors\"\n")
|
||||
fmt.Fprint(buf, "\t\"fmt\"\n")
|
||||
fmt.Fprint(buf, ")\n\n")
|
||||
}
|
||||
|
||||
func buildCode(buf *bytes.Buffer, baseName string, inputs []ShaderInput) {
|
||||
shaderName := baseName + "Shader"
|
||||
vsName := baseName + "VS"
|
||||
fsName := baseName + "FS"
|
||||
|
||||
fmt.Fprintf(buf, "type %s struct {\n", shaderName)
|
||||
fmt.Fprint(buf, "\tid uint32\n")
|
||||
for _, input := range inputs {
|
||||
if input.IsAttrib {
|
||||
fmt.Fprintf(buf, "\t%s uint32\n", input.Name)
|
||||
} else if input.IsUniform {
|
||||
fmt.Fprintf(buf, "\t%s int32\n", input.Name)
|
||||
}
|
||||
}
|
||||
fmt.Fprint(buf, "}\n\n")
|
||||
fmt.Fprintf(buf, "func load%s() (*%s, error) {\n", capitalize(shaderName), shaderName)
|
||||
fmt.Fprint(buf, "\tvar vs, fs, program uint32\n")
|
||||
|
||||
var part string
|
||||
|
||||
part = strings.Replace(compilePart, "SHADER_SRC", vsName, -1)
|
||||
part = strings.Replace(part, "SHADER_TYPE", "VERTEX_SHADER", -1)
|
||||
part = strings.Replace(part, "SHADER_VAR", "vs", -1)
|
||||
fmt.Fprint(buf, part)
|
||||
|
||||
part = strings.Replace(compilePart, "SHADER_SRC", fsName, -1)
|
||||
part = strings.Replace(part, "SHADER_TYPE", "FRAGMENT_SHADER", -1)
|
||||
part = strings.Replace(part, "SHADER_VAR", "fs", -1)
|
||||
fmt.Fprint(buf, part)
|
||||
|
||||
part = strings.Replace(linkPart, "SHADER_SRC", fsName, -1)
|
||||
fmt.Fprint(buf, part)
|
||||
|
||||
fmt.Fprint(buf, "\n")
|
||||
fmt.Fprintf(buf, "\tresult := &%s{}\n", shaderName)
|
||||
fmt.Fprint(buf, "\tresult.id = program\n")
|
||||
|
||||
for _, input := range inputs {
|
||||
if input.IsAttrib {
|
||||
fmt.Fprintf(buf, "\tresult.%s = uint32(gli.GetAttribLocation(program, \"%s\"))\n", input.Name, input.Name)
|
||||
} else if input.IsUniform {
|
||||
fmt.Fprintf(buf, "\tresult.%s = gli.GetUniformLocation(program, \"%s\")\n", input.Name, input.Name)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprint(buf, "\n")
|
||||
|
||||
fmt.Fprint(buf, "\treturn result, nil\n")
|
||||
fmt.Fprint(buf, "}\n")
|
||||
}
|
141
math.go
|
@ -1,141 +0,0 @@
|
|||
package canvas
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
|
||||
type vec [2]float64
|
||||
|
||||
func (v vec) String() string {
|
||||
return fmt.Sprintf("[%f,%f]", v[0], v[1])
|
||||
}
|
||||
|
||||
func (v1 vec) add(v2 vec) vec {
|
||||
return vec{v1[0] + v2[0], v1[1] + v2[1]}
|
||||
}
|
||||
|
||||
func (v1 vec) sub(v2 vec) vec {
|
||||
return vec{v1[0] - v2[0], v1[1] - v2[1]}
|
||||
}
|
||||
|
||||
func (v1 vec) mul(v2 vec) vec {
|
||||
return vec{v1[0] * v2[0], v1[1] * v2[1]}
|
||||
}
|
||||
|
||||
func (v vec) mulf(f float64) vec {
|
||||
return vec{v[0] * f, v[1] * f}
|
||||
}
|
||||
|
||||
func (v vec) mulMat(m mat) (vec, float64) {
|
||||
return vec{
|
||||
m[0]*v[0] + m[3]*v[1] + m[6],
|
||||
m[1]*v[0] + m[4]*v[1] + m[7]},
|
||||
m[2]*v[0] + m[5]*v[1] + m[8]
|
||||
}
|
||||
|
||||
func (v1 vec) div(v2 vec) vec {
|
||||
return vec{v1[0] / v2[0], v1[1] / v2[1]}
|
||||
}
|
||||
|
||||
func (v vec) divf(f float64) vec {
|
||||
return vec{v[0] / f, v[1] / f}
|
||||
}
|
||||
|
||||
func (v1 vec) dot(v2 vec) float64 {
|
||||
return v1[0]*v2[0] + v1[1]*v2[1]
|
||||
}
|
||||
|
||||
func (v vec) len() float64 {
|
||||
return math.Sqrt(v[0]*v[0] + v[1]*v[1])
|
||||
}
|
||||
|
||||
func (v vec) lenSqr() float64 {
|
||||
return v[0]*v[0] + v[1]*v[1]
|
||||
}
|
||||
|
||||
func (v vec) norm() vec {
|
||||
return v.mulf(1.0 / v.len())
|
||||
}
|
||||
|
||||
func (v vec) atan2() float64 {
|
||||
return math.Atan2(v[1], v[0])
|
||||
}
|
||||
|
||||
func (v vec) angle() float64 {
|
||||
return math.Pi*0.5 - math.Atan2(v[1], v[0])
|
||||
}
|
||||
|
||||
func (v vec) angleTo(v2 vec) float64 {
|
||||
return math.Acos(v.norm().dot(v2.norm()))
|
||||
}
|
||||
|
||||
type mat [9]float64
|
||||
|
||||
func (m *mat) String() string {
|
||||
return fmt.Sprintf("[%f,%f,%f,\n %f,%f,%f,\n %f,%f,%f,]", m[0], m[3], m[6], m[1], m[4], m[7], m[2], m[5], m[8])
|
||||
}
|
||||
|
||||
func matIdentity() mat {
|
||||
return mat{
|
||||
1, 0, 0,
|
||||
0, 1, 0,
|
||||
0, 0, 1}
|
||||
}
|
||||
|
||||
func matTranslate(v vec) mat {
|
||||
return mat{
|
||||
1, 0, 0,
|
||||
0, 1, 0,
|
||||
v[0], v[1], 1}
|
||||
}
|
||||
|
||||
func matScale(v vec) mat {
|
||||
return mat{
|
||||
v[0], 0, 0,
|
||||
0, v[1], 0,
|
||||
0, 0, 1}
|
||||
}
|
||||
|
||||
func matRotate(radians float64) mat {
|
||||
s, c := math.Sincos(radians)
|
||||
return mat{
|
||||
c, s, 0,
|
||||
-s, c, 0,
|
||||
0, 0, 1}
|
||||
}
|
||||
|
||||
func (m1 mat) mul(m2 mat) mat {
|
||||
return mat{
|
||||
m1[0]*m2[0] + m1[1]*m2[3] + m1[2]*m2[6],
|
||||
m1[0]*m2[1] + m1[1]*m2[4] + m1[2]*m2[7],
|
||||
m1[0]*m2[2] + m1[1]*m2[5] + m1[2]*m2[8],
|
||||
m1[3]*m2[0] + m1[4]*m2[3] + m1[5]*m2[6],
|
||||
m1[3]*m2[1] + m1[4]*m2[4] + m1[5]*m2[7],
|
||||
m1[3]*m2[2] + m1[4]*m2[5] + m1[5]*m2[8],
|
||||
m1[6]*m2[0] + m1[7]*m2[3] + m1[8]*m2[6],
|
||||
m1[6]*m2[1] + m1[7]*m2[4] + m1[8]*m2[7],
|
||||
m1[6]*m2[2] + m1[7]*m2[5] + m1[8]*m2[8]}
|
||||
}
|
||||
|
||||
func (m mat) invert() mat {
|
||||
var identity float64 = 1.0 / (m[0]*m[4]*m[8] + m[3]*m[7]*m[2] + m[6]*m[1]*m[5] - m[6]*m[4]*m[2] - m[3]*m[1]*m[8] - m[0]*m[7]*m[5])
|
||||
|
||||
return mat{
|
||||
(m[4]*m[8] - m[5]*m[7]) * identity,
|
||||
(m[2]*m[7] - m[1]*m[8]) * identity,
|
||||
(m[1]*m[5] - m[2]*m[4]) * identity,
|
||||
(m[5]*m[6] - m[3]*m[8]) * identity,
|
||||
(m[0]*m[8] - m[2]*m[6]) * identity,
|
||||
(m[2]*m[3] - m[0]*m[5]) * identity,
|
||||
(m[3]*m[7] - m[4]*m[6]) * identity,
|
||||
(m[1]*m[6] - m[0]*m[7]) * identity,
|
||||
(m[0]*m[4] - m[1]*m[3]) * identity}
|
||||
}
|
||||
|
||||
func (m mat) f32() [9]float32 {
|
||||
return [9]float32{
|
||||
float32(m[0]), float32(m[1]), float32(m[2]),
|
||||
float32(m[3]), float32(m[4]), float32(m[5]),
|
||||
float32(m[6]), float32(m[7]), float32(m[8])}
|
||||
}
|
|
@ -1,855 +0,0 @@
|
|||
package canvas
|
||||
|
||||
import "unsafe"
|
||||
|
||||
const (
|
||||
gl_ACTIVE_ATTRIBUTES = 0x8B89
|
||||
gl_ACTIVE_ATTRIBUTE_MAX_LENGTH = 0x8B8A
|
||||
gl_ACTIVE_TEXTURE = 0x84E0
|
||||
gl_ACTIVE_UNIFORMS = 0x8B86
|
||||
gl_ACTIVE_UNIFORM_BLOCKS = 0x8A36
|
||||
gl_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH = 0x8A35
|
||||
gl_ACTIVE_UNIFORM_MAX_LENGTH = 0x8B87
|
||||
gl_ALIASED_LINE_WIDTH_RANGE = 0x846E
|
||||
gl_ALPHA = 0x1906
|
||||
gl_ALREADY_SIGNALED = 0x911A
|
||||
gl_ALWAYS = 0x0207
|
||||
gl_AND = 0x1501
|
||||
gl_AND_INVERTED = 0x1504
|
||||
gl_AND_REVERSE = 0x1502
|
||||
gl_ARRAY_BUFFER = 0x8892
|
||||
gl_ARRAY_BUFFER_BINDING = 0x8894
|
||||
gl_ATTACHED_SHADERS = 0x8B85
|
||||
gl_BACK = 0x0405
|
||||
gl_BACK_LEFT = 0x0402
|
||||
gl_BACK_RIGHT = 0x0403
|
||||
gl_BGR = 0x80E0
|
||||
gl_BGRA = 0x80E1
|
||||
gl_BGRA_INTEGER = 0x8D9B
|
||||
gl_BGR_INTEGER = 0x8D9A
|
||||
gl_BLEND = 0x0BE2
|
||||
gl_BLEND_DST = 0x0BE0
|
||||
gl_BLEND_DST_ALPHA = 0x80CA
|
||||
gl_BLEND_DST_RGB = 0x80C8
|
||||
gl_BLEND_EQUATION_ALPHA = 0x883D
|
||||
gl_BLEND_EQUATION_RGB = 0x8009
|
||||
gl_BLEND_SRC = 0x0BE1
|
||||
gl_BLEND_SRC_ALPHA = 0x80CB
|
||||
gl_BLEND_SRC_RGB = 0x80C9
|
||||
gl_BLUE = 0x1905
|
||||
gl_BLUE_INTEGER = 0x8D96
|
||||
gl_BOOL = 0x8B56
|
||||
gl_BOOL_VEC2 = 0x8B57
|
||||
gl_BOOL_VEC3 = 0x8B58
|
||||
gl_BOOL_VEC4 = 0x8B59
|
||||
gl_BUFFER_ACCESS = 0x88BB
|
||||
gl_BUFFER_ACCESS_FLAGS = 0x911F
|
||||
gl_BUFFER_MAPPED = 0x88BC
|
||||
gl_BUFFER_MAP_LENGTH = 0x9120
|
||||
gl_BUFFER_MAP_OFFSET = 0x9121
|
||||
gl_BUFFER_MAP_POINTER = 0x88BD
|
||||
gl_BUFFER_SIZE = 0x8764
|
||||
gl_BUFFER_USAGE = 0x8765
|
||||
gl_BYTE = 0x1400
|
||||
gl_CCW = 0x0901
|
||||
gl_CLAMP_READ_COLOR = 0x891C
|
||||
gl_CLAMP_TO_BORDER = 0x812D
|
||||
gl_CLAMP_TO_EDGE = 0x812F
|
||||
gl_CLEAR = 0x1500
|
||||
gl_CLIP_DISTANCE0 = 0x3000
|
||||
gl_CLIP_DISTANCE1 = 0x3001
|
||||
gl_CLIP_DISTANCE2 = 0x3002
|
||||
gl_CLIP_DISTANCE3 = 0x3003
|
||||
gl_CLIP_DISTANCE4 = 0x3004
|
||||
gl_CLIP_DISTANCE5 = 0x3005
|
||||
gl_CLIP_DISTANCE6 = 0x3006
|
||||
gl_CLIP_DISTANCE7 = 0x3007
|
||||
gl_COLOR = 0x1800
|
||||
gl_COLOR_ATTACHMENT0 = 0x8CE0
|
||||
gl_COLOR_ATTACHMENT1 = 0x8CE1
|
||||
gl_COLOR_ATTACHMENT10 = 0x8CEA
|
||||
gl_COLOR_ATTACHMENT11 = 0x8CEB
|
||||
gl_COLOR_ATTACHMENT12 = 0x8CEC
|
||||
gl_COLOR_ATTACHMENT13 = 0x8CED
|
||||
gl_COLOR_ATTACHMENT14 = 0x8CEE
|
||||
gl_COLOR_ATTACHMENT15 = 0x8CEF
|
||||
gl_COLOR_ATTACHMENT2 = 0x8CE2
|
||||
gl_COLOR_ATTACHMENT3 = 0x8CE3
|
||||
gl_COLOR_ATTACHMENT4 = 0x8CE4
|
||||
gl_COLOR_ATTACHMENT5 = 0x8CE5
|
||||
gl_COLOR_ATTACHMENT6 = 0x8CE6
|
||||
gl_COLOR_ATTACHMENT7 = 0x8CE7
|
||||
gl_COLOR_ATTACHMENT8 = 0x8CE8
|
||||
gl_COLOR_ATTACHMENT9 = 0x8CE9
|
||||
gl_COLOR_BUFFER_BIT = 0x00004000
|
||||
gl_COLOR_CLEAR_VALUE = 0x0C22
|
||||
gl_COLOR_LOGIC_OP = 0x0BF2
|
||||
gl_COLOR_WRITEMASK = 0x0C23
|
||||
gl_COMPARE_REF_TO_TEXTURE = 0x884E
|
||||
gl_COMPILE_STATUS = 0x8B81
|
||||
gl_COMPRESSED_RED = 0x8225
|
||||
gl_COMPRESSED_RED_RGTC1 = 0x8DBB
|
||||
gl_COMPRESSED_RG = 0x8226
|
||||
gl_COMPRESSED_RGB = 0x84ED
|
||||
gl_COMPRESSED_RGBA = 0x84EE
|
||||
gl_COMPRESSED_RG_RGTC2 = 0x8DBD
|
||||
gl_COMPRESSED_SIGNED_RED_RGTC1 = 0x8DBC
|
||||
gl_COMPRESSED_SIGNED_RG_RGTC2 = 0x8DBE
|
||||
gl_COMPRESSED_SRGB = 0x8C48
|
||||
gl_COMPRESSED_SRGB_ALPHA = 0x8C49
|
||||
gl_COMPRESSED_TEXTURE_FORMATS = 0x86A3
|
||||
gl_CONDITION_SATISFIED = 0x911C
|
||||
gl_CONSTANT_ALPHA = 0x8003
|
||||
gl_CONSTANT_COLOR = 0x8001
|
||||
gl_CONTEXT_COMPATIBILITY_PROFILE_BIT = 0x00000002
|
||||
gl_CONTEXT_CORE_PROFILE_BIT = 0x00000001
|
||||
gl_CONTEXT_FLAGS = 0x821E
|
||||
gl_CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT = 0x00000001
|
||||
gl_CONTEXT_PROFILE_MASK = 0x9126
|
||||
gl_COPY = 0x1503
|
||||
gl_COPY_INVERTED = 0x150C
|
||||
gl_COPY_READ_BUFFER = 0x8F36
|
||||
gl_COPY_WRITE_BUFFER = 0x8F37
|
||||
gl_CULL_FACE = 0x0B44
|
||||
gl_CULL_FACE_MODE = 0x0B45
|
||||
gl_CURRENT_PROGRAM = 0x8B8D
|
||||
gl_CURRENT_QUERY = 0x8865
|
||||
gl_CURRENT_VERTEX_ATTRIB = 0x8626
|
||||
gl_CW = 0x0900
|
||||
gl_DECR = 0x1E03
|
||||
gl_DECR_WRAP = 0x8508
|
||||
gl_DELETE_STATUS = 0x8B80
|
||||
gl_DEPTH = 0x1801
|
||||
gl_DEPTH24_STENCIL8 = 0x88F0
|
||||
gl_DEPTH32F_STENCIL8 = 0x8CAD
|
||||
gl_DEPTH_ATTACHMENT = 0x8D00
|
||||
gl_DEPTH_BUFFER_BIT = 0x00000100
|
||||
gl_DEPTH_CLAMP = 0x864F
|
||||
gl_DEPTH_CLEAR_VALUE = 0x0B73
|
||||
gl_DEPTH_COMPONENT = 0x1902
|
||||
gl_DEPTH_COMPONENT16 = 0x81A5
|
||||
gl_DEPTH_COMPONENT24 = 0x81A6
|
||||
gl_DEPTH_COMPONENT32 = 0x81A7
|
||||
gl_DEPTH_COMPONENT32F = 0x8CAC
|
||||
gl_DEPTH_FUNC = 0x0B74
|
||||
gl_DEPTH_RANGE = 0x0B70
|
||||
gl_DEPTH_STENCIL = 0x84F9
|
||||
gl_DEPTH_STENCIL_ATTACHMENT = 0x821A
|
||||
gl_DEPTH_TEST = 0x0B71
|
||||
gl_DEPTH_WRITEMASK = 0x0B72
|
||||
gl_DITHER = 0x0BD0
|
||||
gl_DONT_CARE = 0x1100
|
||||
gl_DOUBLE = 0x140A
|
||||
gl_DOUBLEBUFFER = 0x0C32
|
||||
gl_DRAW_BUFFER = 0x0C01
|
||||
gl_DRAW_BUFFER0 = 0x8825
|
||||
gl_DRAW_BUFFER1 = 0x8826
|
||||
gl_DRAW_BUFFER10 = 0x882F
|
||||
gl_DRAW_BUFFER11 = 0x8830
|
||||
gl_DRAW_BUFFER12 = 0x8831
|
||||
gl_DRAW_BUFFER13 = 0x8832
|
||||
gl_DRAW_BUFFER14 = 0x8833
|
||||
gl_DRAW_BUFFER15 = 0x8834
|
||||
gl_DRAW_BUFFER2 = 0x8827
|
||||
gl_DRAW_BUFFER3 = 0x8828
|
||||
gl_DRAW_BUFFER4 = 0x8829
|
||||
gl_DRAW_BUFFER5 = 0x882A
|
||||
gl_DRAW_BUFFER6 = 0x882B
|
||||
gl_DRAW_BUFFER7 = 0x882C
|
||||
gl_DRAW_BUFFER8 = 0x882D
|
||||
gl_DRAW_BUFFER9 = 0x882E
|
||||
gl_DRAW_FRAMEBUFFER = 0x8CA9
|
||||
gl_DRAW_FRAMEBUFFER_BINDING = 0x8CA6
|
||||
gl_DST_ALPHA = 0x0304
|
||||
gl_DST_COLOR = 0x0306
|
||||
gl_DYNAMIC_COPY = 0x88EA
|
||||
gl_DYNAMIC_DRAW = 0x88E8
|
||||
gl_DYNAMIC_READ = 0x88E9
|
||||
gl_ELEMENT_ARRAY_BUFFER = 0x8893
|
||||
gl_ELEMENT_ARRAY_BUFFER_BINDING = 0x8895
|
||||
gl_EQUAL = 0x0202
|
||||
gl_EQUIV = 0x1509
|
||||
gl_EXTENSIONS = 0x1F03
|
||||
gl_FALSE = 0
|
||||
gl_FASTEST = 0x1101
|
||||
gl_FILL = 0x1B02
|
||||
gl_FIRST_VERTEX_CONVENTION = 0x8E4D
|
||||
gl_FIXED_ONLY = 0x891D
|
||||
gl_FLOAT = 0x1406
|
||||
gl_FLOAT_32_UNSIGNED_INT_24_8_REV = 0x8DAD
|
||||
gl_FLOAT_MAT2 = 0x8B5A
|
||||
gl_FLOAT_MAT2x3 = 0x8B65
|
||||
gl_FLOAT_MAT2x4 = 0x8B66
|
||||
gl_FLOAT_MAT3 = 0x8B5B
|
||||
gl_FLOAT_MAT3x2 = 0x8B67
|
||||
gl_FLOAT_MAT3x4 = 0x8B68
|
||||
gl_FLOAT_MAT4 = 0x8B5C
|
||||
gl_FLOAT_MAT4x2 = 0x8B69
|
||||
gl_FLOAT_MAT4x3 = 0x8B6A
|
||||
gl_FLOAT_VEC2 = 0x8B50
|
||||
gl_FLOAT_VEC3 = 0x8B51
|
||||
gl_FLOAT_VEC4 = 0x8B52
|
||||
gl_FRAGMENT_SHADER = 0x8B30
|
||||
gl_FRAGMENT_SHADER_DERIVATIVE_HINT = 0x8B8B
|
||||
gl_FRAMEBUFFER = 0x8D40
|
||||
gl_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE = 0x8215
|
||||
gl_FRAMEBUFFER_ATTACHMENT_BLUE_SIZE = 0x8214
|
||||
gl_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING = 0x8210
|
||||
gl_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE = 0x8211
|
||||
gl_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE = 0x8216
|
||||
gl_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE = 0x8213
|
||||
gl_FRAMEBUFFER_ATTACHMENT_LAYERED = 0x8DA7
|
||||
gl_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME = 0x8CD1
|
||||
gl_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE = 0x8CD0
|
||||
gl_FRAMEBUFFER_ATTACHMENT_RED_SIZE = 0x8212
|
||||
gl_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE = 0x8217
|
||||
gl_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE = 0x8CD3
|
||||
gl_FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER = 0x8CD4
|
||||
gl_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL = 0x8CD2
|
||||
gl_FRAMEBUFFER_BINDING = 0x8CA6
|
||||
gl_FRAMEBUFFER_COMPLETE = 0x8CD5
|
||||
gl_FRAMEBUFFER_DEFAULT = 0x8218
|
||||
gl_FRAMEBUFFER_INCOMPLETE_ATTACHMENT = 0x8CD6
|
||||
gl_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER = 0x8CDB
|
||||
gl_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS = 0x8DA8
|
||||
gl_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = 0x8CD7
|
||||
gl_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE = 0x8D56
|
||||
gl_FRAMEBUFFER_INCOMPLETE_READ_BUFFER = 0x8CDC
|
||||
gl_FRAMEBUFFER_SRGB = 0x8DB9
|
||||
gl_FRAMEBUFFER_UNDEFINED = 0x8219
|
||||
gl_FRAMEBUFFER_UNSUPPORTED = 0x8CDD
|
||||
gl_FRONT = 0x0404
|
||||
gl_FRONT_AND_BACK = 0x0408
|
||||
gl_FRONT_FACE = 0x0B46
|
||||
gl_FRONT_LEFT = 0x0400
|
||||
gl_FRONT_RIGHT = 0x0401
|
||||
gl_FUNC_ADD = 0x8006
|
||||
gl_FUNC_REVERSE_SUBTRACT = 0x800B
|
||||
gl_FUNC_SUBTRACT = 0x800A
|
||||
gl_GEOMETRY_INPUT_TYPE = 0x8917
|
||||
gl_GEOMETRY_OUTPUT_TYPE = 0x8918
|
||||
gl_GEOMETRY_SHADER = 0x8DD9
|
||||
gl_GEOMETRY_VERTICES_OUT = 0x8916
|
||||
gl_GEQUAL = 0x0206
|
||||
gl_GREATER = 0x0204
|
||||
gl_GREEN = 0x1904
|
||||
gl_GREEN_INTEGER = 0x8D95
|
||||
gl_HALF_FLOAT = 0x140B
|
||||
gl_INCR = 0x1E02
|
||||
gl_INCR_WRAP = 0x8507
|
||||
gl_INFO_LOG_LENGTH = 0x8B84
|
||||
gl_INT = 0x1404
|
||||
gl_INTERLEAVED_ATTRIBS = 0x8C8C
|
||||
gl_INT_SAMPLER_1D = 0x8DC9
|
||||
gl_INT_SAMPLER_1D_ARRAY = 0x8DCE
|
||||
gl_INT_SAMPLER_2D = 0x8DCA
|
||||
gl_INT_SAMPLER_2D_ARRAY = 0x8DCF
|
||||
gl_INT_SAMPLER_2D_MULTISAMPLE = 0x9109
|
||||
gl_INT_SAMPLER_2D_MULTISAMPLE_ARRAY = 0x910C
|
||||
gl_INT_SAMPLER_2D_RECT = 0x8DCD
|
||||
gl_INT_SAMPLER_3D = 0x8DCB
|
||||
gl_INT_SAMPLER_BUFFER = 0x8DD0
|
||||
gl_INT_SAMPLER_CUBE = 0x8DCC
|
||||
gl_INT_VEC2 = 0x8B53
|
||||
gl_INT_VEC3 = 0x8B54
|
||||
gl_INT_VEC4 = 0x8B55
|
||||
gl_INVALID_ENUM = 0x0500
|
||||
gl_INVALID_FRAMEBUFFER_OPERATION = 0x0506
|
||||
gl_INVALID_INDEX = 0xFFFFFFFF
|
||||
gl_INVALID_OPERATION = 0x0502
|
||||
gl_INVALID_VALUE = 0x0501
|
||||
gl_INVERT = 0x150A
|
||||
gl_KEEP = 0x1E00
|
||||
gl_LAST_VERTEX_CONVENTION = 0x8E4E
|
||||
gl_LEFT = 0x0406
|
||||
gl_LEQUAL = 0x0203
|
||||
gl_LESS = 0x0201
|
||||
gl_LINE = 0x1B01
|
||||
gl_LINEAR = 0x2601
|
||||
gl_LINEAR_MIPMAP_LINEAR = 0x2703
|
||||
gl_LINEAR_MIPMAP_NEAREST = 0x2701
|
||||
gl_LINES = 0x0001
|
||||
gl_LINES_ADJACENCY = 0x000A
|
||||
gl_LINE_LOOP = 0x0002
|
||||
gl_LINE_SMOOTH = 0x0B20
|
||||
gl_LINE_SMOOTH_HINT = 0x0C52
|
||||
gl_LINE_STRIP = 0x0003
|
||||
gl_LINE_STRIP_ADJACENCY = 0x000B
|
||||
gl_LINE_WIDTH = 0x0B21
|
||||
gl_LINE_WIDTH_GRANULARITY = 0x0B23
|
||||
gl_LINE_WIDTH_RANGE = 0x0B22
|
||||
gl_LINK_STATUS = 0x8B82
|
||||
gl_LOGIC_OP_MODE = 0x0BF0
|
||||
gl_LOWER_LEFT = 0x8CA1
|
||||
gl_MAJOR_VERSION = 0x821B
|
||||
gl_MAP_FLUSH_EXPLICIT_BIT = 0x0010
|
||||
gl_MAP_INVALIDATE_BUFFER_BIT = 0x0008
|
||||
gl_MAP_INVALIDATE_RANGE_BIT = 0x0004
|
||||
gl_MAP_READ_BIT = 0x0001
|
||||
gl_MAP_UNSYNCHRONIZED_BIT = 0x0020
|
||||
gl_MAP_WRITE_BIT = 0x0002
|
||||
gl_MAX = 0x8008
|
||||
gl_MAX_3D_TEXTURE_SIZE = 0x8073
|
||||
gl_MAX_ARRAY_TEXTURE_LAYERS = 0x88FF
|
||||
gl_MAX_CLIP_DISTANCES = 0x0D32
|
||||
gl_MAX_COLOR_ATTACHMENTS = 0x8CDF
|
||||
gl_MAX_COLOR_TEXTURE_SAMPLES = 0x910E
|
||||
gl_MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS = 0x8A33
|
||||
gl_MAX_COMBINED_GEOMETRY_UNIFORM_COMPONENTS = 0x8A32
|
||||
gl_MAX_COMBINED_TEXTURE_IMAGE_UNITS = 0x8B4D
|
||||
gl_MAX_COMBINED_UNIFORM_BLOCKS = 0x8A2E
|
||||
gl_MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS = 0x8A31
|
||||
gl_MAX_CUBE_MAP_TEXTURE_SIZE = 0x851C
|
||||
gl_MAX_DEPTH_TEXTURE_SAMPLES = 0x910F
|
||||
gl_MAX_DRAW_BUFFERS = 0x8824
|
||||
gl_MAX_ELEMENTS_INDICES = 0x80E9
|
||||
gl_MAX_ELEMENTS_VERTICES = 0x80E8
|
||||
gl_MAX_FRAGMENT_INPUT_COMPONENTS = 0x9125
|
||||
gl_MAX_FRAGMENT_UNIFORM_BLOCKS = 0x8A2D
|
||||
gl_MAX_FRAGMENT_UNIFORM_COMPONENTS = 0x8B49
|
||||
gl_MAX_GEOMETRY_INPUT_COMPONENTS = 0x9123
|
||||
gl_MAX_GEOMETRY_OUTPUT_COMPONENTS = 0x9124
|
||||
gl_MAX_GEOMETRY_OUTPUT_VERTICES = 0x8DE0
|
||||
gl_MAX_GEOMETRY_TEXTURE_IMAGE_UNITS = 0x8C29
|
||||
gl_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS = 0x8DE1
|
||||
gl_MAX_GEOMETRY_UNIFORM_BLOCKS = 0x8A2C
|
||||
gl_MAX_GEOMETRY_UNIFORM_COMPONENTS = 0x8DDF
|
||||
gl_MAX_INTEGER_SAMPLES = 0x9110
|
||||
gl_MAX_PROGRAM_TEXEL_OFFSET = 0x8905
|
||||
gl_MAX_RECTANGLE_TEXTURE_SIZE = 0x84F8
|
||||
gl_MAX_RENDERBUFFER_SIZE = 0x84E8
|
||||
gl_MAX_SAMPLES = 0x8D57
|
||||
gl_MAX_SAMPLE_MASK_WORDS = 0x8E59
|
||||
gl_MAX_SERVER_WAIT_TIMEOUT = 0x9111
|
||||
gl_MAX_TEXTURE_BUFFER_SIZE = 0x8C2B
|
||||
gl_MAX_TEXTURE_IMAGE_UNITS = 0x8872
|
||||
gl_MAX_TEXTURE_LOD_BIAS = 0x84FD
|
||||
gl_MAX_TEXTURE_SIZE = 0x0D33
|
||||
gl_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS = 0x8C8A
|
||||
gl_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS = 0x8C8B
|
||||
gl_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS = 0x8C80
|
||||
gl_MAX_UNIFORM_BLOCK_SIZE = 0x8A30
|
||||
gl_MAX_UNIFORM_BUFFER_BINDINGS = 0x8A2F
|
||||
gl_MAX_VARYING_COMPONENTS = 0x8B4B
|
||||
gl_MAX_VARYING_FLOATS = 0x8B4B
|
||||
gl_MAX_VERTEX_ATTRIBS = 0x8869
|
||||
gl_MAX_VERTEX_OUTPUT_COMPONENTS = 0x9122
|
||||
gl_MAX_VERTEX_TEXTURE_IMAGE_UNITS = 0x8B4C
|
||||
gl_MAX_VERTEX_UNIFORM_BLOCKS = 0x8A2B
|
||||
gl_MAX_VERTEX_UNIFORM_COMPONENTS = 0x8B4A
|
||||
gl_MAX_VIEWPORT_DIMS = 0x0D3A
|
||||
gl_MIN = 0x8007
|
||||
gl_MINOR_VERSION = 0x821C
|
||||
gl_MIN_PROGRAM_TEXEL_OFFSET = 0x8904
|
||||
gl_MIRRORED_REPEAT = 0x8370
|
||||
gl_MULTISAMPLE = 0x809D
|
||||
gl_NAND = 0x150E
|
||||
gl_NEAREST = 0x2600
|
||||
gl_NEAREST_MIPMAP_LINEAR = 0x2702
|
||||
gl_NEAREST_MIPMAP_NEAREST = 0x2700
|
||||
gl_NEVER = 0x0200
|
||||
gl_NICEST = 0x1102
|
||||
gl_NONE = 0
|
||||
gl_NOOP = 0x1505
|
||||
gl_NOR = 0x1508
|
||||
gl_NOTEQUAL = 0x0205
|
||||
gl_NO_ERROR = 0
|
||||
gl_NUM_COMPRESSED_TEXTURE_FORMATS = 0x86A2
|
||||
gl_NUM_EXTENSIONS = 0x821D
|
||||
gl_OBJECT_TYPE = 0x9112
|
||||
gl_ONE = 1
|
||||
gl_ONE_MINUS_CONSTANT_ALPHA = 0x8004
|
||||
gl_ONE_MINUS_CONSTANT_COLOR = 0x8002
|
||||
gl_ONE_MINUS_DST_ALPHA = 0x0305
|
||||
gl_ONE_MINUS_DST_COLOR = 0x0307
|
||||
gl_ONE_MINUS_SRC_ALPHA = 0x0303
|
||||
gl_ONE_MINUS_SRC_COLOR = 0x0301
|
||||
gl_OR = 0x1507
|
||||
gl_OR_INVERTED = 0x150D
|
||||
gl_OR_REVERSE = 0x150B
|
||||
gl_OUT_OF_MEMORY = 0x0505
|
||||
gl_PACK_ALIGNMENT = 0x0D05
|
||||
gl_PACK_IMAGE_HEIGHT = 0x806C
|
||||
gl_PACK_LSB_FIRST = 0x0D01
|
||||
gl_PACK_ROW_LENGTH = 0x0D02
|
||||
gl_PACK_SKIP_IMAGES = 0x806B
|
||||
gl_PACK_SKIP_PIXELS = 0x0D04
|
||||
gl_PACK_SKIP_ROWS = 0x0D03
|
||||
gl_PACK_SWAP_BYTES = 0x0D00
|
||||
gl_PIXEL_PACK_BUFFER = 0x88EB
|
||||
gl_PIXEL_PACK_BUFFER_BINDING = 0x88ED
|
||||
gl_PIXEL_UNPACK_BUFFER = 0x88EC
|
||||
gl_PIXEL_UNPACK_BUFFER_BINDING = 0x88EF
|
||||
gl_POINT = 0x1B00
|
||||
gl_POINTS = 0x0000
|
||||
gl_POINT_FADE_THRESHOLD_SIZE = 0x8128
|
||||
gl_POINT_SIZE = 0x0B11
|
||||
gl_POINT_SIZE_GRANULARITY = 0x0B13
|
||||
gl_POINT_SIZE_RANGE = 0x0B12
|
||||
gl_POINT_SPRITE_COORD_ORIGIN = 0x8CA0
|
||||
gl_POLYGON_MODE = 0x0B40
|
||||
gl_POLYGON_OFFSET_FACTOR = 0x8038
|
||||
gl_POLYGON_OFFSET_FILL = 0x8037
|
||||
gl_POLYGON_OFFSET_LINE = 0x2A02
|
||||
gl_POLYGON_OFFSET_POINT = 0x2A01
|
||||
gl_POLYGON_OFFSET_UNITS = 0x2A00
|
||||
gl_POLYGON_SMOOTH = 0x0B41
|
||||
gl_POLYGON_SMOOTH_HINT = 0x0C53
|
||||
gl_PRIMITIVES_GENERATED = 0x8C87
|
||||
gl_PRIMITIVE_RESTART = 0x8F9D
|
||||
gl_PRIMITIVE_RESTART_INDEX = 0x8F9E
|
||||
gl_PROGRAM_POINT_SIZE = 0x8642
|
||||
gl_PROVOKING_VERTEX = 0x8E4F
|
||||
gl_PROXY_TEXTURE_1D = 0x8063
|
||||
gl_PROXY_TEXTURE_1D_ARRAY = 0x8C19
|
||||
gl_PROXY_TEXTURE_2D = 0x8064
|
||||
gl_PROXY_TEXTURE_2D_ARRAY = 0x8C1B
|
||||
gl_PROXY_TEXTURE_2D_MULTISAMPLE = 0x9101
|
||||
gl_PROXY_TEXTURE_2D_MULTISAMPLE_ARRAY = 0x9103
|
||||
gl_PROXY_TEXTURE_3D = 0x8070
|
||||
gl_PROXY_TEXTURE_CUBE_MAP = 0x851B
|
||||
gl_PROXY_TEXTURE_RECTANGLE = 0x84F7
|
||||
gl_QUADS_FOLLOW_PROVOKING_VERTEX_CONVENTION = 0x8E4C
|
||||
gl_QUERY_BY_REGION_NO_WAIT = 0x8E16
|
||||
gl_QUERY_BY_REGION_WAIT = 0x8E15
|
||||
gl_QUERY_COUNTER_BITS = 0x8864
|
||||
gl_QUERY_NO_WAIT = 0x8E14
|
||||
gl_QUERY_RESULT = 0x8866
|
||||
gl_QUERY_RESULT_AVAILABLE = 0x8867
|
||||
gl_QUERY_WAIT = 0x8E13
|
||||
gl_R11F_G11F_B10F = 0x8C3A
|
||||
gl_R16 = 0x822A
|
||||
gl_R16F = 0x822D
|
||||
gl_R16I = 0x8233
|
||||
gl_R16UI = 0x8234
|
||||
gl_R16_SNORM = 0x8F98
|
||||
gl_R32F = 0x822E
|
||||
gl_R32I = 0x8235
|
||||
gl_R32UI = 0x8236
|
||||
gl_R3_G3_B2 = 0x2A10
|
||||
gl_R8 = 0x8229
|
||||
gl_R8I = 0x8231
|
||||
gl_R8UI = 0x8232
|
||||
gl_R8_SNORM = 0x8F94
|
||||
gl_RASTERIZER_DISCARD = 0x8C89
|
||||
gl_READ_BUFFER = 0x0C02
|
||||
gl_READ_FRAMEBUFFER = 0x8CA8
|
||||
gl_READ_FRAMEBUFFER_BINDING = 0x8CAA
|
||||
gl_READ_ONLY = 0x88B8
|
||||
gl_READ_WRITE = 0x88BA
|
||||
gl_RED = 0x1903
|
||||
gl_RED_INTEGER = 0x8D94
|
||||
gl_RENDERBUFFER = 0x8D41
|
||||
gl_RENDERBUFFER_ALPHA_SIZE = 0x8D53
|
||||
gl_RENDERBUFFER_BINDING = 0x8CA7
|
||||
gl_RENDERBUFFER_BLUE_SIZE = 0x8D52
|
||||
gl_RENDERBUFFER_DEPTH_SIZE = 0x8D54
|
||||
gl_RENDERBUFFER_GREEN_SIZE = 0x8D51
|
||||
gl_RENDERBUFFER_HEIGHT = 0x8D43
|
||||
gl_RENDERBUFFER_INTERNAL_FORMAT = 0x8D44
|
||||
gl_RENDERBUFFER_RED_SIZE = 0x8D50
|
||||
gl_RENDERBUFFER_SAMPLES = 0x8CAB
|
||||
gl_RENDERBUFFER_STENCIL_SIZE = 0x8D55
|
||||
gl_RENDERBUFFER_WIDTH = 0x8D42
|
||||
gl_RENDERER = 0x1F01
|
||||
gl_REPEAT = 0x2901
|
||||
gl_REPLACE = 0x1E01
|
||||
gl_RG = 0x8227
|
||||
gl_RG16 = 0x822C
|
||||
gl_RG16F = 0x822F
|
||||
gl_RG16I = 0x8239
|
||||
gl_RG16UI = 0x823A
|
||||
gl_RG16_SNORM = 0x8F99
|
||||
gl_RG32F = 0x8230
|
||||
gl_RG32I = 0x823B
|
||||
gl_RG32UI = 0x823C
|
||||
gl_RG8 = 0x822B
|
||||
gl_RG8I = 0x8237
|
||||
gl_RG8UI = 0x8238
|
||||
gl_RG8_SNORM = 0x8F95
|
||||
gl_RGB = 0x1907
|
||||
gl_RGB10 = 0x8052
|
||||
gl_RGB10_A2 = 0x8059
|
||||
gl_RGB12 = 0x8053
|
||||
gl_RGB16 = 0x8054
|
||||
gl_RGB16F = 0x881B
|
||||
gl_RGB16I = 0x8D89
|
||||
gl_RGB16UI = 0x8D77
|
||||
gl_RGB16_SNORM = 0x8F9A
|
||||
gl_RGB32F = 0x8815
|
||||
gl_RGB32I = 0x8D83
|
||||
gl_RGB32UI = 0x8D71
|
||||
gl_RGB4 = 0x804F
|
||||
gl_RGB5 = 0x8050
|
||||
gl_RGB5_A1 = 0x8057
|
||||
gl_RGB8 = 0x8051
|
||||
gl_RGB8I = 0x8D8F
|
||||
gl_RGB8UI = 0x8D7D
|
||||
gl_RGB8_SNORM = 0x8F96
|
||||
gl_RGB9_E5 = 0x8C3D
|
||||
gl_RGBA = 0x1908
|
||||
gl_RGBA12 = 0x805A
|
||||
gl_RGBA16 = 0x805B
|
||||
gl_RGBA16F = 0x881A
|
||||
gl_RGBA16I = 0x8D88
|
||||
gl_RGBA16UI = 0x8D76
|
||||
gl_RGBA16_SNORM = 0x8F9B
|
||||
gl_RGBA2 = 0x8055
|
||||
gl_RGBA32F = 0x8814
|
||||
gl_RGBA32I = 0x8D82
|
||||
gl_RGBA32UI = 0x8D70
|
||||
gl_RGBA4 = 0x8056
|
||||
gl_RGBA8 = 0x8058
|
||||
gl_RGBA8I = 0x8D8E
|
||||
gl_RGBA8UI = 0x8D7C
|
||||
gl_RGBA8_SNORM = 0x8F97
|
||||
gl_RGBA_INTEGER = 0x8D99
|
||||
gl_RGB_INTEGER = 0x8D98
|
||||
gl_RG_INTEGER = 0x8228
|
||||
gl_RIGHT = 0x0407
|
||||
gl_SAMPLER_1D = 0x8B5D
|
||||
gl_SAMPLER_1D_ARRAY = 0x8DC0
|
||||
gl_SAMPLER_1D_ARRAY_SHADOW = 0x8DC3
|
||||
gl_SAMPLER_1D_SHADOW = 0x8B61
|
||||
gl_SAMPLER_2D = 0x8B5E
|
||||
gl_SAMPLER_2D_ARRAY = 0x8DC1
|
||||
gl_SAMPLER_2D_ARRAY_SHADOW = 0x8DC4
|
||||
gl_SAMPLER_2D_MULTISAMPLE = 0x9108
|
||||
gl_SAMPLER_2D_MULTISAMPLE_ARRAY = 0x910B
|
||||
gl_SAMPLER_2D_RECT = 0x8B63
|
||||
gl_SAMPLER_2D_RECT_SHADOW = 0x8B64
|
||||
gl_SAMPLER_2D_SHADOW = 0x8B62
|
||||
gl_SAMPLER_3D = 0x8B5F
|
||||
gl_SAMPLER_BUFFER = 0x8DC2
|
||||
gl_SAMPLER_CUBE = 0x8B60
|
||||
gl_SAMPLER_CUBE_SHADOW = 0x8DC5
|
||||
gl_SAMPLES = 0x80A9
|
||||
gl_SAMPLES_PASSED = 0x8914
|
||||
gl_SAMPLE_ALPHA_TO_COVERAGE = 0x809E
|
||||
gl_SAMPLE_ALPHA_TO_ONE = 0x809F
|
||||
gl_SAMPLE_BUFFERS = 0x80A8
|
||||
gl_SAMPLE_COVERAGE = 0x80A0
|
||||
gl_SAMPLE_COVERAGE_INVERT = 0x80AB
|
||||
gl_SAMPLE_COVERAGE_VALUE = 0x80AA
|
||||
gl_SAMPLE_MASK = 0x8E51
|
||||
gl_SAMPLE_MASK_VALUE = 0x8E52
|
||||
gl_SAMPLE_POSITION = 0x8E50
|
||||
gl_SCISSOR_BOX = 0x0C10
|
||||
gl_SCISSOR_TEST = 0x0C11
|
||||
gl_SEPARATE_ATTRIBS = 0x8C8D
|
||||
gl_SET = 0x150F
|
||||
gl_SHADER_SOURCE_LENGTH = 0x8B88
|
||||
gl_SHADER_TYPE = 0x8B4F
|
||||
gl_SHADING_LANGUAGE_VERSION = 0x8B8C
|
||||
gl_SHORT = 0x1402
|
||||
gl_SIGNALED = 0x9119
|
||||
gl_SIGNED_NORMALIZED = 0x8F9C
|
||||
gl_SMOOTH_LINE_WIDTH_GRANULARITY = 0x0B23
|
||||
gl_SMOOTH_LINE_WIDTH_RANGE = 0x0B22
|
||||
gl_SMOOTH_POINT_SIZE_GRANULARITY = 0x0B13
|
||||
gl_SMOOTH_POINT_SIZE_RANGE = 0x0B12
|
||||
gl_SRC1_ALPHA = 0x8589
|
||||
gl_SRC_ALPHA = 0x0302
|
||||
gl_SRC_ALPHA_SATURATE = 0x0308
|
||||
gl_SRC_COLOR = 0x0300
|
||||
gl_SRGB = 0x8C40
|
||||
gl_SRGB8 = 0x8C41
|
||||
gl_SRGB8_ALPHA8 = 0x8C43
|
||||
gl_SRGB_ALPHA = 0x8C42
|
||||
gl_STATIC_COPY = 0x88E6
|
||||
gl_STATIC_DRAW = 0x88E4
|
||||
gl_STATIC_READ = 0x88E5
|
||||
gl_STENCIL = 0x1802
|
||||
gl_STENCIL_ATTACHMENT = 0x8D20
|
||||
gl_STENCIL_BACK_FAIL = 0x8801
|
||||
gl_STENCIL_BACK_FUNC = 0x8800
|
||||
gl_STENCIL_BACK_PASS_DEPTH_FAIL = 0x8802
|
||||
gl_STENCIL_BACK_PASS_DEPTH_PASS = 0x8803
|
||||
gl_STENCIL_BACK_REF = 0x8CA3
|
||||
gl_STENCIL_BACK_VALUE_MASK = 0x8CA4
|
||||
gl_STENCIL_BACK_WRITEMASK = 0x8CA5
|
||||
gl_STENCIL_BUFFER_BIT = 0x00000400
|
||||
gl_STENCIL_CLEAR_VALUE = 0x0B91
|
||||
gl_STENCIL_FAIL = 0x0B94
|
||||
gl_STENCIL_FUNC = 0x0B92
|
||||
gl_STENCIL_INDEX = 0x1901
|
||||
gl_STENCIL_INDEX1 = 0x8D46
|
||||
gl_STENCIL_INDEX16 = 0x8D49
|
||||
gl_STENCIL_INDEX4 = 0x8D47
|
||||
gl_STENCIL_INDEX8 = 0x8D48
|
||||
gl_STENCIL_PASS_DEPTH_FAIL = 0x0B95
|
||||
gl_STENCIL_PASS_DEPTH_PASS = 0x0B96
|
||||
gl_STENCIL_REF = 0x0B97
|
||||
gl_STENCIL_TEST = 0x0B90
|
||||
gl_STENCIL_VALUE_MASK = 0x0B93
|
||||
gl_STENCIL_WRITEMASK = 0x0B98
|
||||
gl_STEREO = 0x0C33
|
||||
gl_STREAM_COPY = 0x88E2
|
||||
gl_STREAM_DRAW = 0x88E0
|
||||
gl_STREAM_READ = 0x88E1
|
||||
gl_SUBPIXEL_BITS = 0x0D50
|
||||
gl_SYNC_CONDITION = 0x9113
|
||||
gl_SYNC_FENCE = 0x9116
|
||||
gl_SYNC_FLAGS = 0x9115
|
||||
gl_SYNC_FLUSH_COMMANDS_BIT = 0x00000001
|
||||
gl_SYNC_GPU_COMMANDS_COMPLETE = 0x9117
|
||||
gl_SYNC_STATUS = 0x9114
|
||||
gl_TEXTURE = 0x1702
|
||||
gl_TEXTURE0 = 0x84C0
|
||||
gl_TEXTURE1 = 0x84C1
|
||||
gl_TEXTURE10 = 0x84CA
|
||||
gl_TEXTURE11 = 0x84CB
|
||||
gl_TEXTURE12 = 0x84CC
|
||||
gl_TEXTURE13 = 0x84CD
|
||||
gl_TEXTURE14 = 0x84CE
|
||||
gl_TEXTURE15 = 0x84CF
|
||||
gl_TEXTURE16 = 0x84D0
|
||||
gl_TEXTURE17 = 0x84D1
|
||||
gl_TEXTURE18 = 0x84D2
|
||||
gl_TEXTURE19 = 0x84D3
|
||||
gl_TEXTURE2 = 0x84C2
|
||||
gl_TEXTURE20 = 0x84D4
|
||||
gl_TEXTURE21 = 0x84D5
|
||||
gl_TEXTURE22 = 0x84D6
|
||||
gl_TEXTURE23 = 0x84D7
|
||||
gl_TEXTURE24 = 0x84D8
|
||||
gl_TEXTURE25 = 0x84D9
|
||||
gl_TEXTURE26 = 0x84DA
|
||||
gl_TEXTURE27 = 0x84DB
|
||||
gl_TEXTURE28 = 0x84DC
|
||||
gl_TEXTURE29 = 0x84DD
|
||||
gl_TEXTURE3 = 0x84C3
|
||||
gl_TEXTURE30 = 0x84DE
|
||||
gl_TEXTURE31 = 0x84DF
|
||||
gl_TEXTURE4 = 0x84C4
|
||||
gl_TEXTURE5 = 0x84C5
|
||||
gl_TEXTURE6 = 0x84C6
|
||||
gl_TEXTURE7 = 0x84C7
|
||||
gl_TEXTURE8 = 0x84C8
|
||||
gl_TEXTURE9 = 0x84C9
|
||||
gl_TEXTURE_1D = 0x0DE0
|
||||
gl_TEXTURE_1D_ARRAY = 0x8C18
|
||||
gl_TEXTURE_2D = 0x0DE1
|
||||
gl_TEXTURE_2D_ARRAY = 0x8C1A
|
||||
gl_TEXTURE_2D_MULTISAMPLE = 0x9100
|
||||
gl_TEXTURE_2D_MULTISAMPLE_ARRAY = 0x9102
|
||||
gl_TEXTURE_3D = 0x806F
|
||||
gl_TEXTURE_ALPHA_SIZE = 0x805F
|
||||
gl_TEXTURE_ALPHA_TYPE = 0x8C13
|
||||
gl_TEXTURE_BASE_LEVEL = 0x813C
|
||||
gl_TEXTURE_BINDING_1D = 0x8068
|
||||
gl_TEXTURE_BINDING_1D_ARRAY = 0x8C1C
|
||||
gl_TEXTURE_BINDING_2D = 0x8069
|
||||
gl_TEXTURE_BINDING_2D_ARRAY = 0x8C1D
|
||||
gl_TEXTURE_BINDING_2D_MULTISAMPLE = 0x9104
|
||||
gl_TEXTURE_BINDING_2D_MULTISAMPLE_ARRAY = 0x9105
|
||||
gl_TEXTURE_BINDING_3D = 0x806A
|
||||
gl_TEXTURE_BINDING_BUFFER = 0x8C2C
|
||||
gl_TEXTURE_BINDING_CUBE_MAP = 0x8514
|
||||
gl_TEXTURE_BINDING_RECTANGLE = 0x84F6
|
||||
gl_TEXTURE_BLUE_SIZE = 0x805E
|
||||
gl_TEXTURE_BLUE_TYPE = 0x8C12
|
||||
gl_TEXTURE_BORDER_COLOR = 0x1004
|
||||
gl_TEXTURE_BUFFER = 0x8C2A
|
||||
gl_TEXTURE_BUFFER_DATA_STORE_BINDING = 0x8C2D
|
||||
gl_TEXTURE_COMPARE_FUNC = 0x884D
|
||||
gl_TEXTURE_COMPARE_MODE = 0x884C
|
||||
gl_TEXTURE_COMPRESSED = 0x86A1
|
||||
gl_TEXTURE_COMPRESSED_IMAGE_SIZE = 0x86A0
|
||||
gl_TEXTURE_COMPRESSION_HINT = 0x84EF
|
||||
gl_TEXTURE_CUBE_MAP = 0x8513
|
||||
gl_TEXTURE_CUBE_MAP_NEGATIVE_X = 0x8516
|
||||
gl_TEXTURE_CUBE_MAP_NEGATIVE_Y = 0x8518
|
||||
gl_TEXTURE_CUBE_MAP_NEGATIVE_Z = 0x851A
|
||||
gl_TEXTURE_CUBE_MAP_POSITIVE_X = 0x8515
|
||||
gl_TEXTURE_CUBE_MAP_POSITIVE_Y = 0x8517
|
||||
gl_TEXTURE_CUBE_MAP_POSITIVE_Z = 0x8519
|
||||
gl_TEXTURE_CUBE_MAP_SEAMLESS = 0x884F
|
||||
gl_TEXTURE_DEPTH = 0x8071
|
||||
gl_TEXTURE_DEPTH_SIZE = 0x884A
|
||||
gl_TEXTURE_DEPTH_TYPE = 0x8C16
|
||||
gl_TEXTURE_FIXED_SAMPLE_LOCATIONS = 0x9107
|
||||
gl_TEXTURE_GREEN_SIZE = 0x805D
|
||||
gl_TEXTURE_GREEN_TYPE = 0x8C11
|
||||
gl_TEXTURE_HEIGHT = 0x1001
|
||||
gl_TEXTURE_INTERNAL_FORMAT = 0x1003
|
||||
gl_TEXTURE_LOD_BIAS = 0x8501
|
||||
gl_TEXTURE_MAG_FILTER = 0x2800
|
||||
gl_TEXTURE_MAX_LEVEL = 0x813D
|
||||
gl_TEXTURE_MAX_LOD = 0x813B
|
||||
gl_TEXTURE_MIN_FILTER = 0x2801
|
||||
gl_TEXTURE_MIN_LOD = 0x813A
|
||||
gl_TEXTURE_RECTANGLE = 0x84F5
|
||||
gl_TEXTURE_RED_SIZE = 0x805C
|
||||
gl_TEXTURE_RED_TYPE = 0x8C10
|
||||
gl_TEXTURE_SAMPLES = 0x9106
|
||||
gl_TEXTURE_SHARED_SIZE = 0x8C3F
|
||||
gl_TEXTURE_STENCIL_SIZE = 0x88F1
|
||||
gl_TEXTURE_WIDTH = 0x1000
|
||||
gl_TEXTURE_WRAP_R = 0x8072
|
||||
gl_TEXTURE_WRAP_S = 0x2802
|
||||
gl_TEXTURE_WRAP_T = 0x2803
|
||||
gl_TIMEOUT_EXPIRED = 0x911B
|
||||
gl_TIMEOUT_IGNORED = 0xFFFFFFFFFFFFFFFF
|
||||
gl_TRANSFORM_FEEDBACK_BUFFER = 0x8C8E
|
||||
gl_TRANSFORM_FEEDBACK_BUFFER_BINDING = 0x8C8F
|
||||
gl_TRANSFORM_FEEDBACK_BUFFER_MODE = 0x8C7F
|
||||
gl_TRANSFORM_FEEDBACK_BUFFER_SIZE = 0x8C85
|
||||
gl_TRANSFORM_FEEDBACK_BUFFER_START = 0x8C84
|
||||
gl_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN = 0x8C88
|
||||
gl_TRANSFORM_FEEDBACK_VARYINGS = 0x8C83
|
||||
gl_TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH = 0x8C76
|
||||
gl_TRIANGLES = 0x0004
|
||||
gl_TRIANGLES_ADJACENCY = 0x000C
|
||||
gl_TRIANGLE_FAN = 0x0006
|
||||
gl_TRIANGLE_STRIP = 0x0005
|
||||
gl_TRIANGLE_STRIP_ADJACENCY = 0x000D
|
||||
gl_TRUE = 1
|
||||
gl_UNIFORM_ARRAY_STRIDE = 0x8A3C
|
||||
gl_UNIFORM_BLOCK_ACTIVE_UNIFORMS = 0x8A42
|
||||
gl_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES = 0x8A43
|
||||
gl_UNIFORM_BLOCK_BINDING = 0x8A3F
|
||||
gl_UNIFORM_BLOCK_DATA_SIZE = 0x8A40
|
||||
gl_UNIFORM_BLOCK_INDEX = 0x8A3A
|
||||
gl_UNIFORM_BLOCK_NAME_LENGTH = 0x8A41
|
||||
gl_UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER = 0x8A46
|
||||
gl_UNIFORM_BLOCK_REFERENCED_BY_GEOMETRY_SHADER = 0x8A45
|
||||
gl_UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER = 0x8A44
|
||||
gl_UNIFORM_BUFFER = 0x8A11
|
||||
gl_UNIFORM_BUFFER_BINDING = 0x8A28
|
||||
gl_UNIFORM_BUFFER_OFFSET_ALIGNMENT = 0x8A34
|
||||
gl_UNIFORM_BUFFER_SIZE = 0x8A2A
|
||||
gl_UNIFORM_BUFFER_START = 0x8A29
|
||||
gl_UNIFORM_IS_ROW_MAJOR = 0x8A3E
|
||||
gl_UNIFORM_MATRIX_STRIDE = 0x8A3D
|
||||
gl_UNIFORM_NAME_LENGTH = 0x8A39
|
||||
gl_UNIFORM_OFFSET = 0x8A3B
|
||||
gl_UNIFORM_SIZE = 0x8A38
|
||||
gl_UNIFORM_TYPE = 0x8A37
|
||||
gl_UNPACK_ALIGNMENT = 0x0CF5
|
||||
gl_UNPACK_IMAGE_HEIGHT = 0x806E
|
||||
gl_UNPACK_LSB_FIRST = 0x0CF1
|
||||
gl_UNPACK_ROW_LENGTH = 0x0CF2
|
||||
gl_UNPACK_SKIP_IMAGES = 0x806D
|
||||
gl_UNPACK_SKIP_PIXELS = 0x0CF4
|
||||
gl_UNPACK_SKIP_ROWS = 0x0CF3
|
||||
gl_UNPACK_SWAP_BYTES = 0x0CF0
|
||||
gl_UNSIGNALED = 0x9118
|
||||
gl_UNSIGNED_BYTE = 0x1401
|
||||
gl_UNSIGNED_BYTE_2_3_3_REV = 0x8362
|
||||
gl_UNSIGNED_BYTE_3_3_2 = 0x8032
|
||||
gl_UNSIGNED_INT = 0x1405
|
||||
gl_UNSIGNED_INT_10F_11F_11F_REV = 0x8C3B
|
||||
gl_UNSIGNED_INT_10_10_10_2 = 0x8036
|
||||
gl_UNSIGNED_INT_24_8 = 0x84FA
|
||||
gl_UNSIGNED_INT_2_10_10_10_REV = 0x8368
|
||||
gl_UNSIGNED_INT_5_9_9_9_REV = 0x8C3E
|
||||
gl_UNSIGNED_INT_8_8_8_8 = 0x8035
|
||||
gl_UNSIGNED_INT_8_8_8_8_REV = 0x8367
|
||||
gl_UNSIGNED_INT_SAMPLER_1D = 0x8DD1
|
||||
gl_UNSIGNED_INT_SAMPLER_1D_ARRAY = 0x8DD6
|
||||
gl_UNSIGNED_INT_SAMPLER_2D = 0x8DD2
|
||||
gl_UNSIGNED_INT_SAMPLER_2D_ARRAY = 0x8DD7
|
||||
gl_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE = 0x910A
|
||||
gl_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY = 0x910D
|
||||
gl_UNSIGNED_INT_SAMPLER_2D_RECT = 0x8DD5
|
||||
gl_UNSIGNED_INT_SAMPLER_3D = 0x8DD3
|
||||
gl_UNSIGNED_INT_SAMPLER_BUFFER = 0x8DD8
|
||||
gl_UNSIGNED_INT_SAMPLER_CUBE = 0x8DD4
|
||||
gl_UNSIGNED_INT_VEC2 = 0x8DC6
|
||||
gl_UNSIGNED_INT_VEC3 = 0x8DC7
|
||||
gl_UNSIGNED_INT_VEC4 = 0x8DC8
|
||||
gl_UNSIGNED_NORMALIZED = 0x8C17
|
||||
gl_UNSIGNED_SHORT = 0x1403
|
||||
gl_UNSIGNED_SHORT_1_5_5_5_REV = 0x8366
|
||||
gl_UNSIGNED_SHORT_4_4_4_4 = 0x8033
|
||||
gl_UNSIGNED_SHORT_4_4_4_4_REV = 0x8365
|
||||
gl_UNSIGNED_SHORT_5_5_5_1 = 0x8034
|
||||
gl_UNSIGNED_SHORT_5_6_5 = 0x8363
|
||||
gl_UNSIGNED_SHORT_5_6_5_REV = 0x8364
|
||||
gl_UPPER_LEFT = 0x8CA2
|
||||
gl_VALIDATE_STATUS = 0x8B83
|
||||
gl_VENDOR = 0x1F00
|
||||
gl_VERSION = 0x1F02
|
||||
gl_VERTEX_ARRAY_BINDING = 0x85B5
|
||||
gl_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING = 0x889F
|
||||
gl_VERTEX_ATTRIB_ARRAY_ENABLED = 0x8622
|
||||
gl_VERTEX_ATTRIB_ARRAY_INTEGER = 0x88FD
|
||||
gl_VERTEX_ATTRIB_ARRAY_NORMALIZED = 0x886A
|
||||
gl_VERTEX_ATTRIB_ARRAY_POINTER = 0x8645
|
||||
gl_VERTEX_ATTRIB_ARRAY_SIZE = 0x8623
|
||||
gl_VERTEX_ATTRIB_ARRAY_STRIDE = 0x8624
|
||||
gl_VERTEX_ATTRIB_ARRAY_TYPE = 0x8625
|
||||
gl_VERTEX_PROGRAM_POINT_SIZE = 0x8642
|
||||
gl_VERTEX_SHADER = 0x8B31
|
||||
gl_VIEWPORT = 0x0BA2
|
||||
gl_WAIT_FAILED = 0x911D
|
||||
gl_WRITE_ONLY = 0x88B9
|
||||
gl_XOR = 0x1506
|
||||
gl_ZERO = 0
|
||||
)
|
||||
|
||||
// The GL interface is used to make this package independent of any particular
|
||||
// OpenGL implementation. The goglimpl subpackage conatins an implementation of
|
||||
// this interface based on Go-GL v3.2
|
||||
type GL interface {
|
||||
Ptr(data interface{}) unsafe.Pointer
|
||||
ActiveTexture(texture uint32)
|
||||
AttachShader(program uint32, shader uint32)
|
||||
BindBuffer(target uint32, buffer uint32)
|
||||
BindFramebuffer(target uint32, framebuffer uint32)
|
||||
BindRenderbuffer(target uint32, renderbuffer uint32)
|
||||
BindTexture(target uint32, texture uint32)
|
||||
BlendFunc(sfactor uint32, dfactor uint32)
|
||||
BufferData(target uint32, size int, data unsafe.Pointer, usage uint32)
|
||||
CheckFramebufferStatus(target uint32) uint32
|
||||
Clear(mask uint32)
|
||||
ClearColor(red float32, green float32, blue float32, alpha float32)
|
||||
ColorMask(red bool, green bool, blue bool, alpha bool)
|
||||
CompileShader(shader uint32)
|
||||
CreateProgram() uint32
|
||||
CreateShader(xtype uint32) uint32
|
||||
DeleteShader(shader uint32)
|
||||
DeleteFramebuffers(n int32, framebuffers *uint32)
|
||||
DeleteRenderbuffers(n int32, renderbuffers *uint32)
|
||||
DeleteTextures(n int32, textures *uint32)
|
||||
Disable(cap uint32)
|
||||
DisableVertexAttribArray(index uint32)
|
||||
DrawArrays(mode uint32, first int32, count int32)
|
||||
Enable(cap uint32)
|
||||
EnableVertexAttribArray(index uint32)
|
||||
FramebufferRenderbuffer(target uint32, attachment uint32, renderbuffertarget uint32, renderbuffer uint32)
|
||||
FramebufferTexture(target uint32, attachment uint32, texture uint32, level int32)
|
||||
GenBuffers(n int32, buffers *uint32)
|
||||
GenFramebuffers(n int32, framebuffers *uint32)
|
||||
GenRenderbuffers(n int32, renderbuffers *uint32)
|
||||
GenTextures(n int32, textures *uint32)
|
||||
GenerateMipmap(target uint32)
|
||||
GetAttribLocation(program uint32, name string) int32
|
||||
GetError() uint32
|
||||
GetProgramInfoLog(program uint32) string
|
||||
GetProgramiv(program uint32, pname uint32, params *int32)
|
||||
GetShaderInfoLog(shader uint32) string
|
||||
GetShaderiv(shader uint32, pname uint32, params *int32)
|
||||
GetUniformLocation(program uint32, name string) int32
|
||||
LinkProgram(program uint32)
|
||||
ReadPixels(x int32, y int32, width int32, height int32, format uint32, xtype uint32, pixels unsafe.Pointer)
|
||||
RenderbufferStorage(target uint32, internalformat uint32, width int32, height int32)
|
||||
Scissor(x int32, y int32, width int32, height int32)
|
||||
ShaderSource(shader uint32, source string)
|
||||
StencilFunc(xfunc uint32, ref int32, mask uint32)
|
||||
StencilMask(mask uint32)
|
||||
StencilOp(fail uint32, zfail uint32, zpass uint32)
|
||||
TexImage2D(target uint32, level int32, internalformat int32, width int32, height int32, border int32, format uint32, xtype uint32, pixels unsafe.Pointer)
|
||||
TexParameteri(target uint32, pname uint32, param int32)
|
||||
TexSubImage2D(target uint32, level int32, xoffset int32, yoffset int32, width int32, height int32, format uint32, xtype uint32, pixels unsafe.Pointer)
|
||||
Uniform1f(location int32, v0 float32)
|
||||
Uniform1fv(location int32, count int32, v *float32)
|
||||
Uniform1i(location int32, v0 int32)
|
||||
Uniform2f(location int32, v0 float32, v1 float32)
|
||||
Uniform4f(location int32, v0 float32, v1 float32, v2 float32, v3 float32)
|
||||
UniformMatrix3fv(location int32, count int32, transpose bool, value *float32)
|
||||
UseProgram(program uint32)
|
||||
VertexAttribPointer(index uint32, size int32, xtype uint32, normalized bool, stride int32, offset uint32)
|
||||
Viewport(x int32, y int32, width int32, height int32)
|
||||
}
|
503
path2d.go
Normal file
|
@ -0,0 +1,503 @@
|
|||
package canvas
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"git.mstar.dev/mstar/canvas/backend/backendbase"
|
||||
)
|
||||
|
||||
// Path2D is a type that holds a predefined path which can be drawn
|
||||
// with a single call
|
||||
type Path2D struct {
|
||||
cv *Canvas
|
||||
p []pathPoint
|
||||
move backendbase.Vec
|
||||
cwSum float64
|
||||
|
||||
standalone bool
|
||||
fillCache []backendbase.Vec
|
||||
|
||||
noSelfIntersection bool
|
||||
}
|
||||
|
||||
type pathPoint struct {
|
||||
pos backendbase.Vec
|
||||
next backendbase.Vec
|
||||
flags pathPointFlag
|
||||
}
|
||||
|
||||
type pathPointFlag uint8
|
||||
|
||||
const (
|
||||
pathMove pathPointFlag = 1 << iota
|
||||
pathAttach
|
||||
pathIsRect
|
||||
pathIsConvex
|
||||
pathIsClockwise
|
||||
pathSelfIntersects
|
||||
)
|
||||
|
||||
// NewPath2D creates a new Path2D and returns it
|
||||
func (cv *Canvas) NewPath2D() *Path2D {
|
||||
return &Path2D{cv: cv, p: make([]pathPoint, 0, 20), standalone: true}
|
||||
}
|
||||
|
||||
func (p *Path2D) clearCache() {
|
||||
p.fillCache = nil
|
||||
}
|
||||
|
||||
// func (p *Path2D) AddPath(p2 *Path2D) {
|
||||
// }
|
||||
|
||||
// MoveTo (see equivalent function on canvas type)
|
||||
func (p *Path2D) MoveTo(x, y float64) {
|
||||
if len(p.p) > 0 && isSamePoint(p.p[len(p.p)-1].pos, backendbase.Vec{x, y}, 0.1) {
|
||||
return
|
||||
}
|
||||
p.clearCache()
|
||||
p.p = append(p.p, pathPoint{pos: backendbase.Vec{x, y}, flags: pathMove | pathIsConvex})
|
||||
p.cwSum = 0
|
||||
p.move = backendbase.Vec{x, y}
|
||||
}
|
||||
|
||||
// LineTo (see equivalent function on canvas type)
|
||||
func (p *Path2D) LineTo(x, y float64) {
|
||||
p.lineTo(x, y, true)
|
||||
}
|
||||
|
||||
func (p *Path2D) lineTo(x, y float64, checkSelfIntersection bool) {
|
||||
count := len(p.p)
|
||||
if count > 0 && isSamePoint(p.p[len(p.p)-1].pos, backendbase.Vec{x, y}, 0.1) {
|
||||
return
|
||||
}
|
||||
p.clearCache()
|
||||
if count == 0 {
|
||||
p.MoveTo(x, y)
|
||||
return
|
||||
}
|
||||
prev := &p.p[count-1]
|
||||
prev.next = backendbase.Vec{x, y}
|
||||
prev.flags |= pathAttach
|
||||
p.p = append(p.p, pathPoint{pos: backendbase.Vec{x, y}})
|
||||
newp := &p.p[count]
|
||||
|
||||
if prev.flags&pathIsConvex > 0 {
|
||||
px, py := prev.pos[0], prev.pos[1]
|
||||
p.cwSum += (x - px) * (y + py)
|
||||
cwTotal := p.cwSum
|
||||
cwTotal += (p.move[0] - x) * (p.move[1] + y)
|
||||
if cwTotal <= 0 {
|
||||
newp.flags |= pathIsClockwise
|
||||
}
|
||||
}
|
||||
|
||||
if len(p.p) < 4 || Performance.AssumeConvex {
|
||||
newp.flags |= pathIsConvex
|
||||
} else if prev.flags&pathIsConvex > 0 {
|
||||
prev2 := &p.p[count-2]
|
||||
cw := (prev.flags & pathIsClockwise) > 0
|
||||
|
||||
ln := prev.pos.Sub(prev2.pos)
|
||||
lo := backendbase.Vec{ln[1], -ln[0]}
|
||||
dot := newp.pos.Sub(prev2.pos).Dot(lo)
|
||||
|
||||
if (cw && dot <= 0) || (!cw && dot >= 0) {
|
||||
newp.flags |= pathIsConvex
|
||||
}
|
||||
}
|
||||
|
||||
csi := checkSelfIntersection && !Performance.IgnoreSelfIntersections && !p.noSelfIntersection
|
||||
|
||||
if prev.flags&pathSelfIntersects > 0 {
|
||||
newp.flags |= pathSelfIntersects
|
||||
} else if newp.flags&pathIsConvex == 0 && newp.flags&pathSelfIntersects == 0 && csi {
|
||||
|
||||
cuts := false
|
||||
var cutPoint backendbase.Vec
|
||||
b0, b1 := prev.pos, backendbase.Vec{x, y}
|
||||
for i := 1; i < count; i++ {
|
||||
a0, a1 := p.p[i-1].pos, p.p[i].pos
|
||||
var r1, r2 float64
|
||||
cutPoint, r1, r2 = lineIntersection(a0, a1, b0, b1)
|
||||
if r1 > 0 && r1 < 1 && r2 > 0 && r2 < 1 {
|
||||
cuts = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if cuts && !isSamePoint(cutPoint, backendbase.Vec{x, y}, samePointTolerance) {
|
||||
newp.flags |= pathSelfIntersects
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Arc (see equivalent function on canvas type)
|
||||
func (p *Path2D) Arc(x, y, radius, startAngle, endAngle float64, anticlockwise bool) {
|
||||
p.arc(x, y, radius, startAngle, endAngle, anticlockwise, backendbase.MatIdentity, true)
|
||||
}
|
||||
|
||||
func (p *Path2D) arc(
|
||||
x, y, radius, startAngle, endAngle float64,
|
||||
anticlockwise bool,
|
||||
m backendbase.Mat,
|
||||
ident bool,
|
||||
) {
|
||||
checkSelfIntersection := len(p.p) > 0
|
||||
|
||||
lastWasMove := len(p.p) == 0 || p.p[len(p.p)-1].flags&pathMove != 0
|
||||
|
||||
if endAngle == startAngle {
|
||||
s, c := math.Sincos(endAngle)
|
||||
pt := backendbase.Vec{x + radius*c, y + radius*s}
|
||||
if !ident {
|
||||
pt = pt.MulMat(m)
|
||||
}
|
||||
p.lineTo(pt[0], pt[1], checkSelfIntersection)
|
||||
|
||||
if lastWasMove {
|
||||
p.p[len(p.p)-1].flags |= pathIsConvex
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if !anticlockwise && endAngle < startAngle {
|
||||
endAngle = startAngle + (2*math.Pi - math.Mod(startAngle-endAngle, math.Pi*2))
|
||||
} else if anticlockwise && endAngle > startAngle {
|
||||
endAngle = startAngle - (2*math.Pi - math.Mod(endAngle-startAngle, math.Pi*2))
|
||||
}
|
||||
|
||||
if !anticlockwise {
|
||||
diff := endAngle - startAngle
|
||||
if diff >= math.Pi*4 {
|
||||
diff = math.Mod(diff, math.Pi*2) + math.Pi*2
|
||||
endAngle = startAngle + diff
|
||||
}
|
||||
} else {
|
||||
diff := startAngle - endAngle
|
||||
if diff >= math.Pi*4 {
|
||||
diff = math.Mod(diff, math.Pi*2) + math.Pi*2
|
||||
endAngle = startAngle - diff
|
||||
}
|
||||
}
|
||||
|
||||
const step = math.Pi * 2 / 90
|
||||
if !anticlockwise {
|
||||
for a := startAngle; a < endAngle; a += step {
|
||||
s, c := math.Sincos(a)
|
||||
pt := backendbase.Vec{x + radius*c, y + radius*s}
|
||||
if !ident {
|
||||
pt = pt.MulMat(m)
|
||||
}
|
||||
p.lineTo(pt[0], pt[1], checkSelfIntersection)
|
||||
}
|
||||
} else {
|
||||
for a := startAngle; a > endAngle; a -= step {
|
||||
s, c := math.Sincos(a)
|
||||
pt := backendbase.Vec{x + radius*c, y + radius*s}
|
||||
if !ident {
|
||||
pt = pt.MulMat(m)
|
||||
}
|
||||
p.lineTo(pt[0], pt[1], checkSelfIntersection)
|
||||
}
|
||||
}
|
||||
s, c := math.Sincos(endAngle)
|
||||
pt := backendbase.Vec{x + radius*c, y + radius*s}
|
||||
if !ident {
|
||||
pt = pt.MulMat(m)
|
||||
}
|
||||
p.lineTo(pt[0], pt[1], checkSelfIntersection)
|
||||
|
||||
if lastWasMove {
|
||||
p.p[len(p.p)-1].flags |= pathIsConvex
|
||||
}
|
||||
}
|
||||
|
||||
// ArcTo (see equivalent function on canvas type)
|
||||
func (p *Path2D) ArcTo(x1, y1, x2, y2, radius float64) {
|
||||
p.arcTo(x1, y1, x2, y2, radius, backendbase.MatIdentity, true)
|
||||
}
|
||||
|
||||
func (p *Path2D) arcTo(x1, y1, x2, y2, radius float64, m backendbase.Mat, ident bool) {
|
||||
if len(p.p) == 0 {
|
||||
return
|
||||
}
|
||||
p0, p1, p2 := p.p[len(p.p)-1].pos, backendbase.Vec{x1, y1}, backendbase.Vec{x2, y2}
|
||||
if !ident {
|
||||
p0 = p0.MulMat(m.Invert())
|
||||
}
|
||||
v0, v1 := p0.Sub(p1).Norm(), p2.Sub(p1).Norm()
|
||||
angle := math.Acos(v0.Dot(v1))
|
||||
// should be in the range [0-pi]. if parallel, use a straight line
|
||||
if angle <= 0 || angle >= math.Pi {
|
||||
p.LineTo(x2, y2)
|
||||
return
|
||||
}
|
||||
// cv0 and cv1 are vectors that point to the center of the circle
|
||||
cv0 := backendbase.Vec{-v0[1], v0[0]}
|
||||
cv1 := backendbase.Vec{v1[1], -v1[0]}
|
||||
x := cv1.Sub(cv0).Div(v0.Sub(v1))[0] * radius
|
||||
if x < 0 {
|
||||
cv0 = cv0.Mulf(-1)
|
||||
cv1 = cv1.Mulf(-1)
|
||||
}
|
||||
center := p1.Add(v0.Mulf(math.Abs(x))).Add(cv0.Mulf(radius))
|
||||
a0, a1 := cv0.Mulf(-1).Atan2(), cv1.Mulf(-1).Atan2()
|
||||
if x > 0 {
|
||||
if a1-a0 > 0 {
|
||||
a0 += math.Pi * 2
|
||||
}
|
||||
} else {
|
||||
if a0-a1 > 0 {
|
||||
a1 += math.Pi * 2
|
||||
}
|
||||
}
|
||||
p.arc(center[0], center[1], radius, a0, a1, x > 0, m, ident)
|
||||
}
|
||||
|
||||
// QuadraticCurveTo (see equivalent function on canvas type)
|
||||
func (p *Path2D) QuadraticCurveTo(x1, y1, x2, y2 float64) {
|
||||
if len(p.p) == 0 {
|
||||
return
|
||||
}
|
||||
p0 := p.p[len(p.p)-1].pos
|
||||
p1 := backendbase.Vec{x1, y1}
|
||||
p2 := backendbase.Vec{x2, y2}
|
||||
v0 := p1.Sub(p0)
|
||||
v1 := p2.Sub(p1)
|
||||
|
||||
const step = 0.01
|
||||
|
||||
for r := 0.0; r < 1; r += step {
|
||||
i0 := v0.Mulf(r).Add(p0)
|
||||
i1 := v1.Mulf(r).Add(p1)
|
||||
pt := i1.Sub(i0).Mulf(r).Add(i0)
|
||||
p.LineTo(pt[0], pt[1])
|
||||
}
|
||||
p.LineTo(x2, y2)
|
||||
}
|
||||
|
||||
// BezierCurveTo (see equivalent function on canvas type)
|
||||
func (p *Path2D) BezierCurveTo(x1, y1, x2, y2, x3, y3 float64) {
|
||||
if len(p.p) == 0 {
|
||||
return
|
||||
}
|
||||
p0 := p.p[len(p.p)-1].pos
|
||||
p1 := backendbase.Vec{x1, y1}
|
||||
p2 := backendbase.Vec{x2, y2}
|
||||
p3 := backendbase.Vec{x3, y3}
|
||||
v0 := p1.Sub(p0)
|
||||
v1 := p2.Sub(p1)
|
||||
v2 := p3.Sub(p2)
|
||||
|
||||
const step = 0.01
|
||||
|
||||
for r := 0.0; r < 1; r += step {
|
||||
i0 := v0.Mulf(r).Add(p0)
|
||||
i1 := v1.Mulf(r).Add(p1)
|
||||
i2 := v2.Mulf(r).Add(p2)
|
||||
iv0 := i1.Sub(i0)
|
||||
iv1 := i2.Sub(i1)
|
||||
j0 := iv0.Mulf(r).Add(i0)
|
||||
j1 := iv1.Mulf(r).Add(i1)
|
||||
pt := j1.Sub(j0).Mulf(r).Add(j0)
|
||||
p.LineTo(pt[0], pt[1])
|
||||
}
|
||||
p.LineTo(x3, y3)
|
||||
}
|
||||
|
||||
// Ellipse (see equivalent function on canvas type)
|
||||
func (p *Path2D) Ellipse(
|
||||
x, y, radiusX, radiusY, rotation, startAngle, endAngle float64,
|
||||
anticlockwise bool,
|
||||
) {
|
||||
checkSelfIntersection := len(p.p) > 0
|
||||
|
||||
rs, rc := math.Sincos(rotation)
|
||||
|
||||
lastWasMove := len(p.p) == 0 || p.p[len(p.p)-1].flags&pathMove != 0
|
||||
|
||||
if endAngle == startAngle {
|
||||
s, c := math.Sincos(endAngle)
|
||||
rx, ry := radiusX*c, radiusY*s
|
||||
rx, ry = rx*rc-ry*rs, rx*rs+ry*rc
|
||||
p.lineTo(x+rx, y+ry, checkSelfIntersection)
|
||||
|
||||
if lastWasMove {
|
||||
p.p[len(p.p)-1].flags |= pathIsConvex
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if !anticlockwise && endAngle < startAngle {
|
||||
endAngle = startAngle + (2*math.Pi - math.Mod(startAngle-endAngle, math.Pi*2))
|
||||
} else if anticlockwise && endAngle > startAngle {
|
||||
endAngle = startAngle - (2*math.Pi - math.Mod(endAngle-startAngle, math.Pi*2))
|
||||
}
|
||||
|
||||
if !anticlockwise {
|
||||
diff := endAngle - startAngle
|
||||
if diff >= math.Pi*4 {
|
||||
diff = math.Mod(diff, math.Pi*2) + math.Pi*2
|
||||
endAngle = startAngle + diff
|
||||
}
|
||||
} else {
|
||||
diff := startAngle - endAngle
|
||||
if diff >= math.Pi*4 {
|
||||
diff = math.Mod(diff, math.Pi*2) + math.Pi*2
|
||||
endAngle = startAngle - diff
|
||||
}
|
||||
}
|
||||
|
||||
const step = math.Pi * 2 / 90
|
||||
if !anticlockwise {
|
||||
for a := startAngle; a < endAngle; a += step {
|
||||
s, c := math.Sincos(a)
|
||||
rx, ry := radiusX*c, radiusY*s
|
||||
rx, ry = rx*rc-ry*rs, rx*rs+ry*rc
|
||||
p.lineTo(x+rx, y+ry, checkSelfIntersection)
|
||||
}
|
||||
} else {
|
||||
for a := startAngle; a > endAngle; a -= step {
|
||||
s, c := math.Sincos(a)
|
||||
rx, ry := radiusX*c, radiusY*s
|
||||
rx, ry = rx*rc-ry*rs, rx*rs+ry*rc
|
||||
p.lineTo(x+rx, y+ry, checkSelfIntersection)
|
||||
}
|
||||
}
|
||||
s, c := math.Sincos(endAngle)
|
||||
rx, ry := radiusX*c, radiusY*s
|
||||
rx, ry = rx*rc-ry*rs, rx*rs+ry*rc
|
||||
p.lineTo(x+rx, y+ry, checkSelfIntersection)
|
||||
|
||||
if lastWasMove {
|
||||
p.p[len(p.p)-1].flags |= pathIsConvex
|
||||
}
|
||||
}
|
||||
|
||||
// ClosePath (see equivalent function on canvas type)
|
||||
func (p *Path2D) ClosePath() {
|
||||
if len(p.p) < 2 {
|
||||
return
|
||||
}
|
||||
closeIdx := 0
|
||||
for i := len(p.p) - 1; i >= 0; i-- {
|
||||
if p.p[i].flags&pathMove != 0 {
|
||||
closeIdx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if !isSamePoint(p.p[len(p.p)-1].pos, p.p[0].pos, 0.1) {
|
||||
p.LineTo(p.p[closeIdx].pos[0], p.p[closeIdx].pos[1])
|
||||
}
|
||||
p.p[len(p.p)-1].next = p.p[closeIdx].next
|
||||
p.p[len(p.p)-1].flags |= pathAttach
|
||||
}
|
||||
|
||||
// Rect (see equivalent function on canvas type)
|
||||
func (p *Path2D) Rect(x, y, w, h float64) {
|
||||
lastWasMove := len(p.p) == 0 || p.p[len(p.p)-1].flags&pathMove != 0
|
||||
p.MoveTo(x, y)
|
||||
p.LineTo(x+w, y)
|
||||
p.LineTo(x+w, y+h)
|
||||
p.LineTo(x, y+h)
|
||||
p.LineTo(x, y)
|
||||
if lastWasMove {
|
||||
p.p[len(p.p)-1].flags |= pathIsRect
|
||||
p.p[len(p.p)-1].flags |= pathIsConvex
|
||||
}
|
||||
}
|
||||
|
||||
func runSubPaths(path []pathPoint, close bool, fn func(subPath []pathPoint) bool) {
|
||||
start := 0
|
||||
for i, p := range path {
|
||||
if p.flags&pathMove == 0 {
|
||||
continue
|
||||
}
|
||||
if i >= start+3 {
|
||||
if runSubPath(path[start:i], close, fn) {
|
||||
return
|
||||
}
|
||||
}
|
||||
start = i
|
||||
}
|
||||
if len(path) >= start+3 {
|
||||
runSubPath(path[start:], close, fn)
|
||||
}
|
||||
}
|
||||
|
||||
func runSubPath(path []pathPoint, close bool, fn func(subPath []pathPoint) bool) bool {
|
||||
if !close || path[0].pos == path[len(path)-1].pos {
|
||||
return fn(path)
|
||||
}
|
||||
|
||||
var buf [64]pathPoint
|
||||
path2 := Path2D{
|
||||
p: append(buf[:0], path...),
|
||||
move: path[0].pos,
|
||||
}
|
||||
path2.lineTo(path[0].pos[0], path[0].pos[1], true)
|
||||
return fn(path2.p)
|
||||
}
|
||||
|
||||
type pathRule uint8
|
||||
|
||||
// Path rule constants. See https://en.wikipedia.org/wiki/Nonzero-rule
|
||||
// and https://en.wikipedia.org/wiki/Even%E2%80%93odd_rule
|
||||
const (
|
||||
NonZero pathRule = iota
|
||||
EvenOdd
|
||||
)
|
||||
|
||||
// IsPointInPath returns true if the point is in the path according
|
||||
// to the given rule
|
||||
func (p *Path2D) IsPointInPath(x, y float64, rule pathRule) bool {
|
||||
inside := false
|
||||
runSubPaths(p.p, false, func(sp []pathPoint) bool {
|
||||
num := 0
|
||||
prev := sp[len(sp)-1].pos
|
||||
for _, pt := range sp {
|
||||
r, dir := pointIsRightOfLine(prev, pt.pos, backendbase.Vec{x, y})
|
||||
prev = pt.pos
|
||||
if !r {
|
||||
continue
|
||||
}
|
||||
if dir {
|
||||
num++
|
||||
} else {
|
||||
num--
|
||||
}
|
||||
}
|
||||
|
||||
if rule == NonZero {
|
||||
inside = num != 0
|
||||
} else {
|
||||
inside = num%2 == 0
|
||||
}
|
||||
|
||||
return inside
|
||||
})
|
||||
return inside
|
||||
}
|
||||
|
||||
// IsPointInStroke returns true if the point is in the stroke
|
||||
func (p *Path2D) IsPointInStroke(x, y float64) bool {
|
||||
if len(p.p) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
var triBuf [500]backendbase.Vec
|
||||
tris := p.cv.strokeTris(p, p.cv.state.transform, backendbase.Mat{}, false, triBuf[:0])
|
||||
|
||||
pt := backendbase.Vec{x, y}
|
||||
|
||||
for i := 0; i < len(tris); i += 3 {
|
||||
a := backendbase.Vec{tris[i][0], tris[i][1]}
|
||||
b := backendbase.Vec{tris[i+1][0], tris[i+1][1]}
|
||||
c := backendbase.Vec{tris[i+2][0], tris[i+2][1]}
|
||||
if triangleContainsPoint(a, b, c, pt) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
|
@ -1,216 +0,0 @@
|
|||
package sdlcanvas
|
||||
|
||||
import "github.com/veandco/go-sdl2/sdl"
|
||||
|
||||
var keyNameMap [262]string
|
||||
var keyRuneMap [262]rune
|
||||
|
||||
func init() {
|
||||
keyNameMap[sdl.SCANCODE_ESCAPE] = "Escape"
|
||||
keyNameMap[sdl.SCANCODE_0] = "Digit0"
|
||||
keyNameMap[sdl.SCANCODE_1] = "Digit1"
|
||||
keyNameMap[sdl.SCANCODE_2] = "Digit2"
|
||||
keyNameMap[sdl.SCANCODE_3] = "Digit3"
|
||||
keyNameMap[sdl.SCANCODE_4] = "Digit4"
|
||||
keyNameMap[sdl.SCANCODE_5] = "Digit5"
|
||||
keyNameMap[sdl.SCANCODE_6] = "Digit6"
|
||||
keyNameMap[sdl.SCANCODE_7] = "Digit7"
|
||||
keyNameMap[sdl.SCANCODE_8] = "Digit8"
|
||||
keyNameMap[sdl.SCANCODE_9] = "Digit9"
|
||||
keyNameMap[sdl.SCANCODE_MINUS] = "Minus"
|
||||
keyNameMap[sdl.SCANCODE_EQUALS] = "Equal"
|
||||
keyNameMap[sdl.SCANCODE_BACKSPACE] = "Backspace"
|
||||
keyNameMap[sdl.SCANCODE_TAB] = "Tab"
|
||||
keyNameMap[sdl.SCANCODE_Q] = "KeyQ"
|
||||
keyNameMap[sdl.SCANCODE_W] = "KeyW"
|
||||
keyNameMap[sdl.SCANCODE_E] = "KeyE"
|
||||
keyNameMap[sdl.SCANCODE_R] = "KeyR"
|
||||
keyNameMap[sdl.SCANCODE_T] = "KeyT"
|
||||
keyNameMap[sdl.SCANCODE_Y] = "KeyY"
|
||||
keyNameMap[sdl.SCANCODE_U] = "KeyU"
|
||||
keyNameMap[sdl.SCANCODE_I] = "KeyI"
|
||||
keyNameMap[sdl.SCANCODE_O] = "KeyO"
|
||||
keyNameMap[sdl.SCANCODE_P] = "KeyP"
|
||||
keyNameMap[sdl.SCANCODE_LEFTBRACKET] = "BracketLeft"
|
||||
keyNameMap[sdl.SCANCODE_RIGHTBRACKET] = "BracketRight"
|
||||
keyNameMap[sdl.SCANCODE_RETURN] = "Enter"
|
||||
keyNameMap[sdl.SCANCODE_LCTRL] = "ControlLeft"
|
||||
keyNameMap[sdl.SCANCODE_A] = "KeyA"
|
||||
keyNameMap[sdl.SCANCODE_S] = "KeyS"
|
||||
keyNameMap[sdl.SCANCODE_D] = "KeyD"
|
||||
keyNameMap[sdl.SCANCODE_F] = "KeyF"
|
||||
keyNameMap[sdl.SCANCODE_G] = "KeyG"
|
||||
keyNameMap[sdl.SCANCODE_H] = "KeyH"
|
||||
keyNameMap[sdl.SCANCODE_J] = "KeyJ"
|
||||
keyNameMap[sdl.SCANCODE_K] = "KeyK"
|
||||
keyNameMap[sdl.SCANCODE_L] = "KeyL"
|
||||
keyNameMap[sdl.SCANCODE_SEMICOLON] = "Semicolon"
|
||||
keyNameMap[sdl.SCANCODE_APOSTROPHE] = "Quote"
|
||||
keyNameMap[sdl.SCANCODE_GRAVE] = "Backquote"
|
||||
keyNameMap[sdl.SCANCODE_LSHIFT] = "ShiftLeft"
|
||||
keyNameMap[sdl.SCANCODE_BACKSLASH] = "Backslash"
|
||||
keyNameMap[sdl.SCANCODE_Z] = "KeyZ"
|
||||
keyNameMap[sdl.SCANCODE_X] = "KeyX"
|
||||
keyNameMap[sdl.SCANCODE_C] = "KeyC"
|
||||
keyNameMap[sdl.SCANCODE_V] = "KeyV"
|
||||
keyNameMap[sdl.SCANCODE_B] = "KeyB"
|
||||
keyNameMap[sdl.SCANCODE_N] = "KeyN"
|
||||
keyNameMap[sdl.SCANCODE_M] = "KeyM"
|
||||
keyNameMap[sdl.SCANCODE_COMMA] = "Comma"
|
||||
keyNameMap[sdl.SCANCODE_PERIOD] = "Period"
|
||||
keyNameMap[sdl.SCANCODE_SLASH] = "Slash"
|
||||
keyNameMap[sdl.SCANCODE_RSHIFT] = "RightShift"
|
||||
keyNameMap[sdl.SCANCODE_KP_MULTIPLY] = "NumpadMultiply"
|
||||
keyNameMap[sdl.SCANCODE_LALT] = "AltLeft"
|
||||
keyNameMap[sdl.SCANCODE_SPACE] = "Space"
|
||||
keyNameMap[sdl.SCANCODE_CAPSLOCK] = "CapsLock"
|
||||
keyNameMap[sdl.SCANCODE_F1] = "F1"
|
||||
keyNameMap[sdl.SCANCODE_F2] = "F2"
|
||||
keyNameMap[sdl.SCANCODE_F3] = "F3"
|
||||
keyNameMap[sdl.SCANCODE_F4] = "F4"
|
||||
keyNameMap[sdl.SCANCODE_F5] = "F5"
|
||||
keyNameMap[sdl.SCANCODE_F6] = "F6"
|
||||
keyNameMap[sdl.SCANCODE_F7] = "F7"
|
||||
keyNameMap[sdl.SCANCODE_F8] = "F8"
|
||||
keyNameMap[sdl.SCANCODE_F9] = "F9"
|
||||
keyNameMap[sdl.SCANCODE_F10] = "F10"
|
||||
keyNameMap[sdl.SCANCODE_PAUSE] = "Pause"
|
||||
keyNameMap[sdl.SCANCODE_SCROLLLOCK] = "ScrollLock"
|
||||
keyNameMap[sdl.SCANCODE_KP_7] = "Numpad7"
|
||||
keyNameMap[sdl.SCANCODE_KP_8] = "Numpad8"
|
||||
keyNameMap[sdl.SCANCODE_KP_9] = "Numpad9"
|
||||
keyNameMap[sdl.SCANCODE_KP_MINUS] = "NumpadSubtract"
|
||||
keyNameMap[sdl.SCANCODE_KP_4] = "Numpad4"
|
||||
keyNameMap[sdl.SCANCODE_KP_5] = "Numpad5"
|
||||
keyNameMap[sdl.SCANCODE_KP_6] = "Numpad6"
|
||||
keyNameMap[sdl.SCANCODE_KP_PLUS] = "NumpadAdd"
|
||||
keyNameMap[sdl.SCANCODE_KP_1] = "Numpad1"
|
||||
keyNameMap[sdl.SCANCODE_KP_2] = "Numpad2"
|
||||
keyNameMap[sdl.SCANCODE_KP_3] = "Numpad3"
|
||||
keyNameMap[sdl.SCANCODE_KP_0] = "Numpad0"
|
||||
keyNameMap[sdl.SCANCODE_KP_DECIMAL] = "NumpadDecimal"
|
||||
keyNameMap[sdl.SCANCODE_PRINTSCREEN] = "PrintScreen"
|
||||
keyNameMap[sdl.SCANCODE_NONUSBACKSLASH] = "IntlBackslash"
|
||||
keyNameMap[sdl.SCANCODE_F11] = "F11"
|
||||
keyNameMap[sdl.SCANCODE_F12] = "F12"
|
||||
keyNameMap[sdl.SCANCODE_KP_EQUALS] = "NumpadEqual"
|
||||
keyNameMap[sdl.SCANCODE_F13] = "F13"
|
||||
keyNameMap[sdl.SCANCODE_F14] = "F14"
|
||||
keyNameMap[sdl.SCANCODE_F15] = "F15"
|
||||
keyNameMap[sdl.SCANCODE_F16] = "F16"
|
||||
keyNameMap[sdl.SCANCODE_F17] = "F17"
|
||||
keyNameMap[sdl.SCANCODE_F18] = "F18"
|
||||
keyNameMap[sdl.SCANCODE_F19] = "F19"
|
||||
keyNameMap[sdl.SCANCODE_UNDO] = "Undo"
|
||||
keyNameMap[sdl.SCANCODE_PASTE] = "Paste"
|
||||
keyNameMap[sdl.SCANCODE_AUDIOPREV] = "MediaTrackPrevious"
|
||||
keyNameMap[sdl.SCANCODE_CUT] = "Cut"
|
||||
keyNameMap[sdl.SCANCODE_COPY] = "Copy"
|
||||
keyNameMap[sdl.SCANCODE_AUDIONEXT] = "MediaTrackNext"
|
||||
keyNameMap[sdl.SCANCODE_KP_ENTER] = "NumpadEnter"
|
||||
keyNameMap[sdl.SCANCODE_RCTRL] = "ControlRight"
|
||||
keyNameMap[sdl.SCANCODE_MUTE] = "AudioVolumeMute"
|
||||
keyNameMap[sdl.SCANCODE_AUDIOPLAY] = "MediaPlayPause"
|
||||
keyNameMap[sdl.SCANCODE_AUDIOSTOP] = "MediaStop"
|
||||
keyNameMap[sdl.SCANCODE_VOLUMEDOWN] = "AudioVolumeDown"
|
||||
keyNameMap[sdl.SCANCODE_VOLUMEUP] = "AudioVolumeUp"
|
||||
keyNameMap[sdl.SCANCODE_KP_DIVIDE] = "NumpadDivide"
|
||||
keyNameMap[sdl.SCANCODE_RALT] = "AltRight"
|
||||
keyNameMap[sdl.SCANCODE_HELP] = "Help"
|
||||
keyNameMap[sdl.SCANCODE_HOME] = "Home"
|
||||
keyNameMap[sdl.SCANCODE_UP] = "ArrowUp"
|
||||
keyNameMap[sdl.SCANCODE_PAGEUP] = "PageUp"
|
||||
keyNameMap[sdl.SCANCODE_LEFT] = "ArrowLeft"
|
||||
keyNameMap[sdl.SCANCODE_RIGHT] = "ArrowRight"
|
||||
keyNameMap[sdl.SCANCODE_END] = "End"
|
||||
keyNameMap[sdl.SCANCODE_DOWN] = "ArrowDown"
|
||||
keyNameMap[sdl.SCANCODE_INSERT] = "Insert"
|
||||
keyNameMap[sdl.SCANCODE_DELETE] = "Delete"
|
||||
keyNameMap[sdl.SCANCODE_APPLICATION] = "ContextMenu"
|
||||
|
||||
keyRuneMap[sdl.SCANCODE_0] = '0'
|
||||
keyRuneMap[sdl.SCANCODE_1] = '1'
|
||||
keyRuneMap[sdl.SCANCODE_2] = '2'
|
||||
keyRuneMap[sdl.SCANCODE_3] = '3'
|
||||
keyRuneMap[sdl.SCANCODE_4] = '4'
|
||||
keyRuneMap[sdl.SCANCODE_5] = '5'
|
||||
keyRuneMap[sdl.SCANCODE_6] = '6'
|
||||
keyRuneMap[sdl.SCANCODE_7] = '7'
|
||||
keyRuneMap[sdl.SCANCODE_8] = '8'
|
||||
keyRuneMap[sdl.SCANCODE_9] = '9'
|
||||
keyRuneMap[sdl.SCANCODE_MINUS] = '-'
|
||||
keyRuneMap[sdl.SCANCODE_EQUALS] = '='
|
||||
keyRuneMap[sdl.SCANCODE_TAB] = '\t'
|
||||
keyRuneMap[sdl.SCANCODE_Q] = 'Q'
|
||||
keyRuneMap[sdl.SCANCODE_W] = 'W'
|
||||
keyRuneMap[sdl.SCANCODE_E] = 'E'
|
||||
keyRuneMap[sdl.SCANCODE_R] = 'R'
|
||||
keyRuneMap[sdl.SCANCODE_T] = 'T'
|
||||
keyRuneMap[sdl.SCANCODE_Y] = 'Y'
|
||||
keyRuneMap[sdl.SCANCODE_U] = 'U'
|
||||
keyRuneMap[sdl.SCANCODE_I] = 'I'
|
||||
keyRuneMap[sdl.SCANCODE_O] = 'O'
|
||||
keyRuneMap[sdl.SCANCODE_P] = 'P'
|
||||
keyRuneMap[sdl.SCANCODE_LEFTBRACKET] = '['
|
||||
keyRuneMap[sdl.SCANCODE_RIGHTBRACKET] = ']'
|
||||
keyRuneMap[sdl.SCANCODE_RETURN] = '\n'
|
||||
keyRuneMap[sdl.SCANCODE_A] = 'A'
|
||||
keyRuneMap[sdl.SCANCODE_S] = 'S'
|
||||
keyRuneMap[sdl.SCANCODE_D] = 'D'
|
||||
keyRuneMap[sdl.SCANCODE_F] = 'F'
|
||||
keyRuneMap[sdl.SCANCODE_G] = 'G'
|
||||
keyRuneMap[sdl.SCANCODE_H] = 'H'
|
||||
keyRuneMap[sdl.SCANCODE_J] = 'J'
|
||||
keyRuneMap[sdl.SCANCODE_K] = 'K'
|
||||
keyRuneMap[sdl.SCANCODE_L] = 'L'
|
||||
keyRuneMap[sdl.SCANCODE_SEMICOLON] = ';'
|
||||
keyRuneMap[sdl.SCANCODE_APOSTROPHE] = '\''
|
||||
keyRuneMap[sdl.SCANCODE_GRAVE] = '`'
|
||||
keyRuneMap[sdl.SCANCODE_BACKSLASH] = '\\'
|
||||
keyRuneMap[sdl.SCANCODE_Z] = 'Z'
|
||||
keyRuneMap[sdl.SCANCODE_X] = 'X'
|
||||
keyRuneMap[sdl.SCANCODE_C] = 'C'
|
||||
keyRuneMap[sdl.SCANCODE_V] = 'V'
|
||||
keyRuneMap[sdl.SCANCODE_B] = 'B'
|
||||
keyRuneMap[sdl.SCANCODE_N] = 'N'
|
||||
keyRuneMap[sdl.SCANCODE_M] = 'M'
|
||||
keyRuneMap[sdl.SCANCODE_COMMA] = ','
|
||||
keyRuneMap[sdl.SCANCODE_PERIOD] = '.'
|
||||
keyRuneMap[sdl.SCANCODE_SLASH] = '/'
|
||||
keyRuneMap[sdl.SCANCODE_KP_MULTIPLY] = '*'
|
||||
keyRuneMap[sdl.SCANCODE_SPACE] = ' '
|
||||
keyRuneMap[sdl.SCANCODE_KP_7] = '7'
|
||||
keyRuneMap[sdl.SCANCODE_KP_8] = '8'
|
||||
keyRuneMap[sdl.SCANCODE_KP_9] = '9'
|
||||
keyRuneMap[sdl.SCANCODE_KP_MINUS] = '-'
|
||||
keyRuneMap[sdl.SCANCODE_KP_4] = '4'
|
||||
keyRuneMap[sdl.SCANCODE_KP_5] = '5'
|
||||
keyRuneMap[sdl.SCANCODE_KP_6] = '6'
|
||||
keyRuneMap[sdl.SCANCODE_KP_PLUS] = '+'
|
||||
keyRuneMap[sdl.SCANCODE_KP_1] = '1'
|
||||
keyRuneMap[sdl.SCANCODE_KP_2] = '2'
|
||||
keyRuneMap[sdl.SCANCODE_KP_3] = '3'
|
||||
keyRuneMap[sdl.SCANCODE_KP_0] = '0'
|
||||
keyRuneMap[sdl.SCANCODE_KP_DECIMAL] = '.'
|
||||
keyRuneMap[sdl.SCANCODE_KP_EQUALS] = '='
|
||||
keyRuneMap[sdl.SCANCODE_KP_ENTER] = '\n'
|
||||
keyRuneMap[sdl.SCANCODE_KP_DIVIDE] = '/'
|
||||
}
|
||||
|
||||
func keyName(s sdl.Scancode) string {
|
||||
if int(s) >= len(keyNameMap) {
|
||||
return "Unidentified"
|
||||
}
|
||||
name := keyNameMap[s]
|
||||
if name == "" {
|
||||
return "Unidentified"
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
func keyRune(s sdl.Scancode) rune {
|
||||
if int(s) >= len(keyNameMap) {
|
||||
return 0
|
||||
}
|
||||
return keyRuneMap[s]
|
||||
}
|
|
@ -1,258 +0,0 @@
|
|||
package sdlcanvas
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
_ "image/gif" // Imported here so that applications based on this package support these formats by default
|
||||
_ "image/jpeg"
|
||||
_ "image/png"
|
||||
"runtime"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/go-gl/gl/v3.2-core/gl"
|
||||
"github.com/tfriedel6/canvas"
|
||||
"github.com/tfriedel6/canvas/glimpl/gogl"
|
||||
"github.com/veandco/go-sdl2/sdl"
|
||||
)
|
||||
|
||||
// Window represents the opened window with GL context. The Mouse* and Key*
|
||||
// functions can be set for callbacks
|
||||
type Window struct {
|
||||
Window *sdl.Window
|
||||
WindowID uint32
|
||||
GLContext sdl.GLContext
|
||||
canvas *canvas.Canvas
|
||||
frameTimes [10]time.Time
|
||||
frameIndex int
|
||||
frameCount int
|
||||
fps float32
|
||||
close bool
|
||||
events []sdl.Event
|
||||
Event func(event sdl.Event)
|
||||
MouseDown func(button, x, y int)
|
||||
MouseMove func(x, y int)
|
||||
MouseUp func(button, x, y int)
|
||||
MouseWheel func(x, y int)
|
||||
KeyDown func(scancode int, rn rune, name string)
|
||||
KeyUp func(scancode int, rn rune, name string)
|
||||
KeyChar func(rn rune)
|
||||
SizeChange func(w, h int)
|
||||
}
|
||||
|
||||
// CreateWindow creates a window using SDL and initializes the OpenGL context
|
||||
func CreateWindow(w, h int, title string) (*Window, *canvas.Canvas, error) {
|
||||
runtime.LockOSThread()
|
||||
|
||||
// init SDL
|
||||
err := sdl.Init(sdl.INIT_VIDEO)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("Error initializing SDL: %v", err)
|
||||
}
|
||||
|
||||
sdl.GLSetAttribute(sdl.GL_RED_SIZE, 8)
|
||||
sdl.GLSetAttribute(sdl.GL_GREEN_SIZE, 8)
|
||||
sdl.GLSetAttribute(sdl.GL_BLUE_SIZE, 8)
|
||||
sdl.GLSetAttribute(sdl.GL_ALPHA_SIZE, 8)
|
||||
sdl.GLSetAttribute(sdl.GL_DEPTH_SIZE, 0)
|
||||
sdl.GLSetAttribute(sdl.GL_STENCIL_SIZE, 8)
|
||||
sdl.GLSetAttribute(sdl.GL_DOUBLEBUFFER, 1)
|
||||
sdl.GLSetAttribute(sdl.GL_MULTISAMPLEBUFFERS, 1)
|
||||
sdl.GLSetAttribute(sdl.GL_MULTISAMPLESAMPLES, 4)
|
||||
|
||||
// create window
|
||||
window, err := sdl.CreateWindow(title, sdl.WINDOWPOS_CENTERED, sdl.WINDOWPOS_CENTERED, int32(w), int32(h), sdl.WINDOW_RESIZABLE|sdl.WINDOW_OPENGL)
|
||||
if err != nil {
|
||||
sdl.GLSetAttribute(sdl.GL_MULTISAMPLEBUFFERS, 0)
|
||||
sdl.GLSetAttribute(sdl.GL_MULTISAMPLESAMPLES, 0)
|
||||
window, err = sdl.CreateWindow(title, sdl.WINDOWPOS_CENTERED, sdl.WINDOWPOS_CENTERED, int32(w), int32(h), sdl.WINDOW_RESIZABLE|sdl.WINDOW_OPENGL)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("Error creating window: %v", err)
|
||||
}
|
||||
}
|
||||
windowID, err := window.GetID()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("Error getting window ID: %v", err)
|
||||
}
|
||||
|
||||
// create GL context
|
||||
glContext, err := window.GLCreateContext()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("Error creating GL context: %v", err)
|
||||
}
|
||||
|
||||
// init GL
|
||||
err = gl.Init()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("Error initializing GL: %v", err)
|
||||
}
|
||||
|
||||
sdl.GLSetSwapInterval(1)
|
||||
gl.Enable(gl.MULTISAMPLE)
|
||||
|
||||
err = canvas.LoadGL(glimplgogl.GLImpl{})
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("Error loading canvas GL assets: %v", err)
|
||||
}
|
||||
|
||||
cv := canvas.New(0, 0, w, h)
|
||||
wnd := &Window{
|
||||
Window: window,
|
||||
WindowID: windowID,
|
||||
GLContext: glContext,
|
||||
canvas: cv,
|
||||
events: make([]sdl.Event, 0, 100),
|
||||
}
|
||||
|
||||
return wnd, cv, nil
|
||||
}
|
||||
|
||||
// Destroy destroys the GL context and the window
|
||||
func (wnd *Window) Destroy() {
|
||||
sdl.GLDeleteContext(wnd.GLContext)
|
||||
wnd.Window.Destroy()
|
||||
}
|
||||
|
||||
// FPS returns the frames per second (averaged over 10 frames)
|
||||
func (wnd *Window) FPS() float32 {
|
||||
return wnd.fps
|
||||
}
|
||||
|
||||
// Close can be used to end a call to MainLoop
|
||||
func (wnd *Window) Close() {
|
||||
wnd.close = true
|
||||
}
|
||||
|
||||
// StartFrame handles events and gets the window ready for rendering
|
||||
func (wnd *Window) StartFrame() error {
|
||||
err := wnd.Window.GLMakeCurrent(wnd.GLContext)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
wnd.events = wnd.events[:0]
|
||||
for {
|
||||
event := sdl.PollEvent()
|
||||
if event == nil {
|
||||
break
|
||||
}
|
||||
|
||||
handled := false
|
||||
switch e := event.(type) {
|
||||
case *sdl.MouseButtonEvent:
|
||||
if e.Type == sdl.MOUSEBUTTONDOWN {
|
||||
if wnd.MouseDown != nil {
|
||||
wnd.MouseDown(int(e.Button), int(e.X), int(e.Y))
|
||||
handled = true
|
||||
}
|
||||
} else if e.Type == sdl.MOUSEBUTTONUP {
|
||||
if wnd.MouseUp != nil {
|
||||
wnd.MouseUp(int(e.Button), int(e.X), int(e.Y))
|
||||
handled = true
|
||||
}
|
||||
}
|
||||
case *sdl.MouseMotionEvent:
|
||||
if wnd.MouseMove != nil {
|
||||
wnd.MouseMove(int(e.X), int(e.Y))
|
||||
handled = true
|
||||
}
|
||||
case *sdl.MouseWheelEvent:
|
||||
if wnd.MouseWheel != nil {
|
||||
wnd.MouseWheel(int(e.X), int(e.Y))
|
||||
handled = true
|
||||
}
|
||||
case *sdl.KeyboardEvent:
|
||||
if e.Type == sdl.KEYDOWN {
|
||||
if wnd.KeyDown != nil {
|
||||
wnd.KeyDown(int(e.Keysym.Scancode), keyRune(e.Keysym.Scancode), keyName(e.Keysym.Scancode))
|
||||
handled = true
|
||||
}
|
||||
} else if e.Type == sdl.KEYUP {
|
||||
if wnd.KeyUp != nil {
|
||||
wnd.KeyUp(int(e.Keysym.Scancode), keyRune(e.Keysym.Scancode), keyName(e.Keysym.Scancode))
|
||||
handled = true
|
||||
}
|
||||
}
|
||||
case *sdl.TextInputEvent:
|
||||
if wnd.KeyChar != nil {
|
||||
rn, _ := utf8.DecodeRune(e.Text[:])
|
||||
wnd.KeyChar(rn)
|
||||
handled = true
|
||||
}
|
||||
case *sdl.WindowEvent:
|
||||
if e.WindowID == wnd.WindowID {
|
||||
if e.Event == sdl.WINDOWEVENT_SIZE_CHANGED {
|
||||
if wnd.SizeChange != nil {
|
||||
wnd.SizeChange(int(e.Data1), int(e.Data2))
|
||||
handled = true
|
||||
} else {
|
||||
wnd.canvas.SetBounds(0, 0, int(e.Data1), int(e.Data2))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !handled && wnd.Event != nil {
|
||||
wnd.Event(event)
|
||||
handled = true
|
||||
}
|
||||
|
||||
if !handled {
|
||||
wnd.events = append(wnd.events, event)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// FinishFrame updates the FPS count and displays the frame
|
||||
func (wnd *Window) FinishFrame() {
|
||||
now := time.Now()
|
||||
wnd.frameTimes[wnd.frameIndex] = now
|
||||
wnd.frameIndex++
|
||||
wnd.frameIndex %= len(wnd.frameTimes)
|
||||
if wnd.frameCount < len(wnd.frameTimes) {
|
||||
wnd.frameCount++
|
||||
} else {
|
||||
diff := now.Sub(wnd.frameTimes[wnd.frameIndex]).Seconds()
|
||||
wnd.fps = float32(wnd.frameCount-1) / float32(diff)
|
||||
}
|
||||
|
||||
wnd.Window.GLSwap()
|
||||
}
|
||||
|
||||
// MainLoop runs a main loop and calls run on every frame
|
||||
func (wnd *Window) MainLoop(run func()) {
|
||||
// main loop
|
||||
for !wnd.close {
|
||||
err := wnd.StartFrame()
|
||||
if err != nil {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, event := range wnd.events {
|
||||
switch e := event.(type) {
|
||||
case *sdl.WindowEvent:
|
||||
if e.Event == sdl.WINDOWEVENT_CLOSE {
|
||||
wnd.close = true
|
||||
}
|
||||
case *sdl.QuitEvent:
|
||||
wnd.close = true
|
||||
case *sdl.KeyboardEvent:
|
||||
if e.Type == sdl.KEYDOWN && e.Keysym.Scancode == sdl.SCANCODE_ESCAPE {
|
||||
wnd.close = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
run()
|
||||
|
||||
wnd.FinishFrame()
|
||||
}
|
||||
}
|
||||
|
||||
// Size returns the current width and height of the window
|
||||
func (wnd *Window) Size() (int, int) {
|
||||
w, h := wnd.Window.GetSize()
|
||||
return int(w), int(h)
|
||||
}
|
373
shaders.go
|
@ -1,373 +0,0 @@
|
|||
package canvas
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var imageVS = `
|
||||
attribute vec2 vertex, texCoord;
|
||||
uniform vec2 canvasSize;
|
||||
varying vec2 v_texCoord;
|
||||
void main() {
|
||||
v_texCoord = texCoord;
|
||||
vec2 glp = vertex * 2.0 / canvasSize - 1.0;
|
||||
gl_Position = vec4(glp.x, -glp.y, 0.0, 1.0);
|
||||
}`
|
||||
var imageFS = `
|
||||
#ifdef GL_ES
|
||||
precision mediump float;
|
||||
#endif
|
||||
varying vec2 v_texCoord;
|
||||
uniform sampler2D image;
|
||||
uniform float globalAlpha;
|
||||
void main() {
|
||||
vec4 col = texture2D(image, v_texCoord);
|
||||
col.a *= globalAlpha;
|
||||
gl_FragColor = col;
|
||||
}`
|
||||
|
||||
var solidVS = `
|
||||
attribute vec2 vertex;
|
||||
uniform vec2 canvasSize;
|
||||
void main() {
|
||||
vec2 glp = vertex * 2.0 / canvasSize - 1.0;
|
||||
gl_Position = vec4(glp.x, -glp.y, 0.0, 1.0);
|
||||
}`
|
||||
var solidFS = `
|
||||
#ifdef GL_ES
|
||||
precision mediump float;
|
||||
#endif
|
||||
uniform vec4 color;
|
||||
uniform float globalAlpha;
|
||||
void main() {
|
||||
vec4 col = color;
|
||||
col.a *= globalAlpha;
|
||||
gl_FragColor = col;
|
||||
}`
|
||||
|
||||
var linearGradientVS = `
|
||||
attribute vec2 vertex;
|
||||
uniform vec2 canvasSize;
|
||||
varying vec2 v_cp;
|
||||
void main() {
|
||||
v_cp = vertex;
|
||||
vec2 glp = vertex * 2.0 / canvasSize - 1.0;
|
||||
gl_Position = vec4(glp.x, -glp.y, 0.0, 1.0);
|
||||
}`
|
||||
var linearGradientFS = `
|
||||
#ifdef GL_ES
|
||||
precision mediump float;
|
||||
#endif
|
||||
varying vec2 v_cp;
|
||||
uniform mat3 invmat;
|
||||
uniform sampler2D gradient;
|
||||
uniform vec2 from, dir;
|
||||
uniform float len;
|
||||
uniform float globalAlpha;
|
||||
void main() {
|
||||
vec3 untf = vec3(v_cp, 1.0) * invmat;
|
||||
vec2 v = untf.xy - from;
|
||||
float r = dot(v, dir) / len;
|
||||
r = clamp(r, 0.0, 1.0);
|
||||
vec4 col = texture2D(gradient, vec2(r, 0.0));
|
||||
col.a *= globalAlpha;
|
||||
gl_FragColor = col;
|
||||
}`
|
||||
|
||||
var radialGradientVS = `
|
||||
attribute vec2 vertex;
|
||||
uniform vec2 canvasSize;
|
||||
varying vec2 v_cp;
|
||||
void main() {
|
||||
v_cp = vertex;
|
||||
vec2 glp = vertex * 2.0 / canvasSize - 1.0;
|
||||
gl_Position = vec4(glp.x, -glp.y, 0.0, 1.0);
|
||||
}`
|
||||
var radialGradientFS = `
|
||||
#ifdef GL_ES
|
||||
precision mediump float;
|
||||
#endif
|
||||
varying vec2 v_cp;
|
||||
uniform mat3 invmat;
|
||||
uniform sampler2D gradient;
|
||||
uniform vec2 from, to, dir;
|
||||
uniform float radFrom, radTo;
|
||||
uniform float len;
|
||||
uniform float globalAlpha;
|
||||
bool isNaN(float v) {
|
||||
return v < 0.0 || 0.0 < v || v == 0.0 ? false : true;
|
||||
}
|
||||
void main() {
|
||||
vec3 untf = vec3(v_cp, 1.0) * invmat;
|
||||
float o_a = 0.5 * sqrt(
|
||||
pow(-2.0*from.x*from.x+2.0*from.x*to.x+2.0*from.x*untf.x-2.0*to.x*untf.x-2.0*from.y*from.y+2.0*from.y*to.y+2.0*from.y*untf.y-2.0*to.y*untf.y+2.0*radFrom*radFrom-2.0*radFrom*radTo, 2.0)
|
||||
-4.0*(from.x*from.x-2.0*from.x*untf.x+untf.x*untf.x+from.y*from.y-2.0*from.y*untf.y+untf.y*untf.y-radFrom*radFrom)
|
||||
*(from.x*from.x-2.0*from.x*to.x+to.x*to.x+from.y*from.y-2.0*from.y*to.y+to.y*to.y-radFrom*radFrom+2.0*radFrom*radTo-radTo*radTo)
|
||||
);
|
||||
float o_b = (from.x*from.x-from.x*to.x-from.x*untf.x+to.x*untf.x+from.y*from.y-from.y*to.y-from.y*untf.y+to.y*untf.y-radFrom*radFrom+radFrom*radTo);
|
||||
float o_c = (from.x*from.x-2.0*from.x*to.x+to.x*to.x+from.y*from.y-2.0*from.y*to.y+to.y*to.y-radFrom*radFrom+2.0*radFrom*radTo-radTo*radTo);
|
||||
float o1 = (-o_a + o_b) / o_c;
|
||||
float o2 = (o_a + o_b) / o_c;
|
||||
if (isNaN(o1) && isNaN(o2)) {
|
||||
gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);
|
||||
return;
|
||||
}
|
||||
float o = max(o1, o2);
|
||||
//float r = radFrom + o * (radTo - radFrom);
|
||||
vec4 col = texture2D(gradient, vec2(o, 0.0));
|
||||
col.a *= globalAlpha;
|
||||
gl_FragColor = col;
|
||||
}`
|
||||
|
||||
var imagePatternVS = `
|
||||
attribute vec2 vertex;
|
||||
uniform vec2 canvasSize;
|
||||
varying vec2 v_cp;
|
||||
void main() {
|
||||
v_cp = vertex;
|
||||
vec2 glp = vertex * 2.0 / canvasSize - 1.0;
|
||||
gl_Position = vec4(glp.x, -glp.y, 0.0, 1.0);
|
||||
}`
|
||||
var imagePatternFS = `
|
||||
#ifdef GL_ES
|
||||
precision mediump float;
|
||||
#endif
|
||||
varying vec2 v_cp;
|
||||
uniform vec2 imageSize;
|
||||
uniform mat3 invmat;
|
||||
uniform sampler2D image;
|
||||
uniform float globalAlpha;
|
||||
void main() {
|
||||
vec3 untf = vec3(v_cp, 1.0) * invmat;
|
||||
vec4 col = texture2D(image, mod(untf.xy / imageSize, 1.0));
|
||||
col.a *= globalAlpha;
|
||||
gl_FragColor = col;
|
||||
}`
|
||||
|
||||
var solidAlphaVS = `
|
||||
attribute vec2 vertex, alphaTexCoord;
|
||||
uniform vec2 canvasSize;
|
||||
varying vec2 v_atc;
|
||||
void main() {
|
||||
v_atc = alphaTexCoord;
|
||||
vec2 glp = vertex * 2.0 / canvasSize - 1.0;
|
||||
gl_Position = vec4(glp.x, -glp.y, 0.0, 1.0);
|
||||
}`
|
||||
var solidAlphaFS = `
|
||||
#ifdef GL_ES
|
||||
precision mediump float;
|
||||
#endif
|
||||
varying vec2 v_atc;
|
||||
uniform vec4 color;
|
||||
uniform sampler2D alphaTex;
|
||||
uniform float globalAlpha;
|
||||
void main() {
|
||||
vec4 col = color;
|
||||
col.a *= texture2D(alphaTex, v_atc).a * globalAlpha;
|
||||
gl_FragColor = col;
|
||||
}`
|
||||
|
||||
var linearGradientAlphaVS = `
|
||||
attribute vec2 vertex, alphaTexCoord;
|
||||
uniform vec2 canvasSize;
|
||||
varying vec2 v_cp;
|
||||
varying vec2 v_atc;
|
||||
void main() {
|
||||
v_cp = vertex;
|
||||
v_atc = alphaTexCoord;
|
||||
vec2 glp = vertex * 2.0 / canvasSize - 1.0;
|
||||
gl_Position = vec4(glp.x, -glp.y, 0.0, 1.0);
|
||||
}`
|
||||
var linearGradientAlphaFS = `
|
||||
#ifdef GL_ES
|
||||
precision mediump float;
|
||||
#endif
|
||||
varying vec2 v_cp;
|
||||
varying vec2 v_atc;
|
||||
varying vec2 v_texCoord;
|
||||
uniform mat3 invmat;
|
||||
uniform sampler2D gradient;
|
||||
uniform vec2 from, dir;
|
||||
uniform float len;
|
||||
uniform sampler2D alphaTex;
|
||||
uniform float globalAlpha;
|
||||
void main() {
|
||||
vec3 untf = vec3(v_cp, 1.0) * invmat;
|
||||
vec2 v = untf.xy - from;
|
||||
float r = dot(v, dir) / len;
|
||||
r = clamp(r, 0.0, 1.0);
|
||||
vec4 col = texture2D(gradient, vec2(r, 0.0));
|
||||
col.a *= texture2D(alphaTex, v_atc).a * globalAlpha;
|
||||
gl_FragColor = col;
|
||||
}`
|
||||
|
||||
var radialGradientAlphaVS = `
|
||||
attribute vec2 vertex, alphaTexCoord;
|
||||
uniform vec2 canvasSize;
|
||||
varying vec2 v_cp;
|
||||
varying vec2 v_atc;
|
||||
void main() {
|
||||
v_cp = vertex;
|
||||
v_atc = alphaTexCoord;
|
||||
vec2 glp = vertex * 2.0 / canvasSize - 1.0;
|
||||
gl_Position = vec4(glp.x, -glp.y, 0.0, 1.0);
|
||||
}`
|
||||
var radialGradientAlphaFS = `
|
||||
#ifdef GL_ES
|
||||
precision mediump float;
|
||||
#endif
|
||||
varying vec2 v_cp;
|
||||
varying vec2 v_atc;
|
||||
uniform mat3 invmat;
|
||||
uniform sampler2D gradient;
|
||||
uniform vec2 from, to, dir;
|
||||
uniform float radFrom, radTo;
|
||||
uniform float len;
|
||||
uniform sampler2D alphaTex;
|
||||
uniform float globalAlpha;
|
||||
bool isNaN(float v) {
|
||||
return v < 0.0 || 0.0 < v || v == 0.0 ? false : true;
|
||||
}
|
||||
void main() {
|
||||
vec3 untf = vec3(v_cp, 1.0) * invmat;
|
||||
float o_a = 0.5 * sqrt(
|
||||
pow(-2.0*from.x*from.x+2.0*from.x*to.x+2.0*from.x*untf.x-2.0*to.x*untf.x-2.0*from.y*from.y+2.0*from.y*to.y+2.0*from.y*untf.y-2.0*to.y*untf.y+2.0*radFrom*radFrom-2.0*radFrom*radTo, 2.0)
|
||||
-4.0*(from.x*from.x-2.0*from.x*untf.x+untf.x*untf.x+from.y*from.y-2.0*from.y*untf.y+untf.y*untf.y-radFrom*radFrom)
|
||||
*(from.x*from.x-2.0*from.x*to.x+to.x*to.x+from.y*from.y-2.0*from.y*to.y+to.y*to.y-radFrom*radFrom+2.0*radFrom*radTo-radTo*radTo)
|
||||
);
|
||||
float o_b = (from.x*from.x-from.x*to.x-from.x*untf.x+to.x*untf.x+from.y*from.y-from.y*to.y-from.y*untf.y+to.y*untf.y-radFrom*radFrom+radFrom*radTo);
|
||||
float o_c = (from.x*from.x-2.0*from.x*to.x+to.x*to.x+from.y*from.y-2.0*from.y*to.y+to.y*to.y-radFrom*radFrom+2.0*radFrom*radTo-radTo*radTo);
|
||||
float o1 = (-o_a + o_b) / o_c;
|
||||
float o2 = (o_a + o_b) / o_c;
|
||||
if (isNaN(o1) && isNaN(o2)) {
|
||||
gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);
|
||||
return;
|
||||
}
|
||||
float o = max(o1, o2);
|
||||
float r = radFrom + o * (radTo - radFrom);
|
||||
vec4 col = texture2D(gradient, vec2(o, 0.0));
|
||||
col.a *= texture2D(alphaTex, v_atc).a * globalAlpha;
|
||||
gl_FragColor = col;
|
||||
}`
|
||||
|
||||
var imagePatternAlphaVS = `
|
||||
attribute vec2 vertex, alphaTexCoord;
|
||||
uniform vec2 canvasSize;
|
||||
varying vec2 v_cp;
|
||||
varying vec2 v_atc;
|
||||
void main() {
|
||||
v_cp = vertex;
|
||||
v_atc = alphaTexCoord;
|
||||
vec2 glp = vertex * 2.0 / canvasSize - 1.0;
|
||||
gl_Position = vec4(glp.x, -glp.y, 0.0, 1.0);
|
||||
}`
|
||||
var imagePatternAlphaFS = `
|
||||
#ifdef GL_ES
|
||||
precision mediump float;
|
||||
#endif
|
||||
varying vec2 v_cp;
|
||||
varying vec2 v_atc;
|
||||
uniform vec2 imageSize;
|
||||
uniform mat3 invmat;
|
||||
uniform sampler2D image;
|
||||
uniform sampler2D alphaTex;
|
||||
uniform float globalAlpha;
|
||||
void main() {
|
||||
vec3 untf = vec3(v_cp, 1.0) * invmat;
|
||||
vec4 col = texture2D(image, mod(untf.xy / imageSize, 1.0));
|
||||
col.a *= texture2D(alphaTex, v_atc).a * globalAlpha;
|
||||
gl_FragColor = col;
|
||||
}`
|
||||
|
||||
var gaussian15VS = `
|
||||
attribute vec2 vertex, texCoord;
|
||||
uniform vec2 canvasSize;
|
||||
varying vec2 v_texCoord;
|
||||
void main() {
|
||||
v_texCoord = texCoord;
|
||||
vec2 glp = vertex * 2.0 / canvasSize - 1.0;
|
||||
gl_Position = vec4(glp.x, -glp.y, 0.0, 1.0);
|
||||
}`
|
||||
var gaussian15FS = `
|
||||
#ifdef GL_ES
|
||||
precision mediump float;
|
||||
#endif
|
||||
varying vec2 v_texCoord;
|
||||
uniform vec2 kernelScale;
|
||||
uniform sampler2D image;
|
||||
uniform float kernel[15];
|
||||
void main() {
|
||||
vec4 color = vec4(0.0, 0.0, 0.0, 0.0);
|
||||
_SUM_
|
||||
gl_FragColor = color;
|
||||
}`
|
||||
|
||||
var gaussian63VS = `
|
||||
attribute vec2 vertex, texCoord;
|
||||
uniform vec2 canvasSize;
|
||||
varying vec2 v_texCoord;
|
||||
void main() {
|
||||
v_texCoord = texCoord;
|
||||
vec2 glp = vertex * 2.0 / canvasSize - 1.0;
|
||||
gl_Position = vec4(glp.x, -glp.y, 0.0, 1.0);
|
||||
}`
|
||||
var gaussian63FS = `
|
||||
#ifdef GL_ES
|
||||
precision mediump float;
|
||||
#endif
|
||||
varying vec2 v_texCoord;
|
||||
uniform vec2 kernelScale;
|
||||
uniform sampler2D image;
|
||||
uniform float kernel[63];
|
||||
void main() {
|
||||
vec4 color = vec4(0.0, 0.0, 0.0, 0.0);
|
||||
_SUM_
|
||||
gl_FragColor = color;
|
||||
}`
|
||||
|
||||
var gaussian127VS = `
|
||||
attribute vec2 vertex, texCoord;
|
||||
uniform vec2 canvasSize;
|
||||
varying vec2 v_texCoord;
|
||||
void main() {
|
||||
v_texCoord = texCoord;
|
||||
vec2 glp = vertex * 2.0 / canvasSize - 1.0;
|
||||
gl_Position = vec4(glp.x, -glp.y, 0.0, 1.0);
|
||||
}`
|
||||
var gaussian127FS = `
|
||||
#ifdef GL_ES
|
||||
precision mediump float;
|
||||
#endif
|
||||
varying vec2 v_texCoord;
|
||||
uniform vec2 kernelScale;
|
||||
uniform sampler2D image;
|
||||
uniform float kernel[127];
|
||||
void main() {
|
||||
vec4 color = vec4(0.0, 0.0, 0.0, 0.0);
|
||||
_SUM_
|
||||
gl_FragColor = color;
|
||||
}`
|
||||
|
||||
func init() {
|
||||
fstr := "\tcolor += texture2D(image, v_texCoord + vec2(%.1f * kernelScale.x, %.1f * kernelScale.y)) * kernel[%d];\n"
|
||||
bb := bytes.Buffer{}
|
||||
for i := 0; i < 127; i++ {
|
||||
off := float64(i) - 63
|
||||
fmt.Fprintf(&bb, fstr, off, off, i)
|
||||
}
|
||||
gaussian127FS = strings.Replace(gaussian127FS, "_SUM_", bb.String(), -1)
|
||||
bb.Reset()
|
||||
for i := 0; i < 63; i++ {
|
||||
off := float64(i) - 31
|
||||
fmt.Fprintf(&bb, fstr, off, off, i)
|
||||
}
|
||||
gaussian63FS = strings.Replace(gaussian63FS, "_SUM_", bb.String(), -1)
|
||||
bb.Reset()
|
||||
for i := 0; i < 15; i++ {
|
||||
off := float64(i) - 7
|
||||
fmt.Fprintf(&bb, fstr, off, off, i)
|
||||
}
|
||||
gaussian15FS = strings.Replace(gaussian15FS, "_SUM_", bb.String(), -1)
|
||||
}
|
230
shadows.go
|
@ -3,215 +3,41 @@ package canvas
|
|||
import (
|
||||
"image"
|
||||
"math"
|
||||
"unsafe"
|
||||
|
||||
"git.mstar.dev/mstar/canvas/backend/backendbase"
|
||||
)
|
||||
|
||||
func (cv *Canvas) drawShadow(tris []float32) {
|
||||
if len(tris) == 0 || cv.state.shadowColor.a == 0 {
|
||||
func (cv *Canvas) drawShadow(pts []backendbase.Vec, mask *image.Alpha, canOverlap bool) {
|
||||
if cv.state.shadowColor.A == 0 {
|
||||
return
|
||||
}
|
||||
if cv.state.shadowOffsetX == 0 && cv.state.shadowOffsetY == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if cv.state.shadowBlur > 0 {
|
||||
offscr1.alpha = true
|
||||
cv.enableTextureRenderTarget(&offscr1)
|
||||
gli.ClearColor(0, 0, 0, 0)
|
||||
gli.Clear(gl_COLOR_BUFFER_BIT | gl_STENCIL_BUFFER_BIT)
|
||||
if cv.shadowBuf == nil || cap(cv.shadowBuf) < len(pts) {
|
||||
cv.shadowBuf = make([]backendbase.Vec, 0, len(pts)+1000)
|
||||
}
|
||||
cv.shadowBuf = cv.shadowBuf[:0]
|
||||
|
||||
for _, pt := range pts {
|
||||
cv.shadowBuf = append(cv.shadowBuf, backendbase.Vec{
|
||||
pt[0] + cv.state.shadowOffsetX,
|
||||
pt[1] + cv.state.shadowOffsetY,
|
||||
})
|
||||
}
|
||||
|
||||
ox, oy := float32(cv.state.shadowOffsetX), float32(cv.state.shadowOffsetY)
|
||||
|
||||
count := len(tris)
|
||||
for i := 12; i < count; i += 2 {
|
||||
tris[i] += ox
|
||||
tris[i+1] += oy
|
||||
}
|
||||
|
||||
gli.BindBuffer(gl_ARRAY_BUFFER, shadowBuf)
|
||||
gli.BufferData(gl_ARRAY_BUFFER, len(tris)*4, unsafe.Pointer(&tris[0]), gl_STREAM_DRAW)
|
||||
|
||||
gli.ColorMask(false, false, false, false)
|
||||
gli.StencilFunc(gl_ALWAYS, 1, 0xFF)
|
||||
gli.StencilOp(gl_REPLACE, gl_REPLACE, gl_REPLACE)
|
||||
gli.StencilMask(0x01)
|
||||
|
||||
gli.UseProgram(sr.id)
|
||||
gli.Uniform4f(sr.color, 0, 0, 0, 0)
|
||||
gli.Uniform2f(sr.canvasSize, float32(cv.fw), float32(cv.fh))
|
||||
|
||||
gli.EnableVertexAttribArray(sr.vertex)
|
||||
gli.VertexAttribPointer(sr.vertex, 2, gl_FLOAT, false, 0, 0)
|
||||
gli.DrawArrays(gl_TRIANGLES, 6, int32(len(tris)/2-6))
|
||||
gli.DisableVertexAttribArray(sr.vertex)
|
||||
|
||||
gli.ColorMask(true, true, true, true)
|
||||
|
||||
gli.StencilFunc(gl_EQUAL, 1, 0xFF)
|
||||
|
||||
var style drawStyle
|
||||
style.color = cv.state.shadowColor
|
||||
|
||||
vertex := cv.useShader(&style)
|
||||
gli.EnableVertexAttribArray(vertex)
|
||||
gli.VertexAttribPointer(vertex, 2, gl_FLOAT, false, 0, 0)
|
||||
gli.DrawArrays(gl_TRIANGLES, 0, 6)
|
||||
gli.DisableVertexAttribArray(vertex)
|
||||
|
||||
gli.StencilOp(gl_KEEP, gl_KEEP, gl_KEEP)
|
||||
gli.StencilFunc(gl_ALWAYS, 0, 0xFF)
|
||||
|
||||
gli.Clear(gl_STENCIL_BUFFER_BIT)
|
||||
gli.StencilMask(0xFF)
|
||||
|
||||
if cv.state.shadowBlur > 0 {
|
||||
cv.drawBlurredShadow()
|
||||
}
|
||||
}
|
||||
|
||||
func (cv *Canvas) drawTextShadow(offset image.Point, strWidth, strHeight int, x, y float64) {
|
||||
x += cv.state.shadowOffsetX
|
||||
y += cv.state.shadowOffsetY
|
||||
|
||||
if cv.state.shadowBlur > 0 {
|
||||
offscr1.alpha = true
|
||||
cv.enableTextureRenderTarget(&offscr1)
|
||||
gli.ClearColor(0, 0, 0, 0)
|
||||
gli.Clear(gl_COLOR_BUFFER_BIT | gl_STENCIL_BUFFER_BIT)
|
||||
}
|
||||
|
||||
gli.StencilFunc(gl_EQUAL, 0, 0xFF)
|
||||
|
||||
gli.BindBuffer(gl_ARRAY_BUFFER, buf)
|
||||
|
||||
var style drawStyle
|
||||
style.color = cv.state.shadowColor
|
||||
|
||||
vertex, alphaTexCoord := cv.useAlphaShader(&style, 1)
|
||||
|
||||
gli.EnableVertexAttribArray(vertex)
|
||||
gli.EnableVertexAttribArray(alphaTexCoord)
|
||||
|
||||
p0 := cv.tf(vec{float64(offset.X) + x, float64(offset.Y) + y})
|
||||
p1 := cv.tf(vec{float64(offset.X) + x, float64(offset.Y+strHeight) + y})
|
||||
p2 := cv.tf(vec{float64(offset.X+strWidth) + x, float64(offset.Y+strHeight) + y})
|
||||
p3 := cv.tf(vec{float64(offset.X+strWidth) + x, float64(offset.Y) + y})
|
||||
|
||||
tw := float64(strWidth) / alphaTexSize
|
||||
th := float64(strHeight) / alphaTexSize
|
||||
data := [16]float32{float32(p0[0]), float32(p0[1]), float32(p1[0]), float32(p1[1]), float32(p2[0]), float32(p2[1]), float32(p3[0]), float32(p3[1]),
|
||||
0, 1, 0, float32(1 - th), float32(tw), float32(1 - th), float32(tw), 1}
|
||||
gli.BufferData(gl_ARRAY_BUFFER, len(data)*4, unsafe.Pointer(&data[0]), gl_STREAM_DRAW)
|
||||
|
||||
gli.VertexAttribPointer(vertex, 2, gl_FLOAT, false, 0, 0)
|
||||
gli.VertexAttribPointer(alphaTexCoord, 2, gl_FLOAT, false, 0, 8*4)
|
||||
gli.DrawArrays(gl_TRIANGLE_FAN, 0, 4)
|
||||
|
||||
gli.DisableVertexAttribArray(vertex)
|
||||
gli.DisableVertexAttribArray(alphaTexCoord)
|
||||
|
||||
gli.ActiveTexture(gl_TEXTURE0)
|
||||
|
||||
gli.StencilFunc(gl_ALWAYS, 0, 0xFF)
|
||||
|
||||
if cv.state.shadowBlur > 0 {
|
||||
cv.drawBlurredShadow()
|
||||
}
|
||||
}
|
||||
|
||||
func (cv *Canvas) drawBlurredShadow() {
|
||||
gli.BlendFunc(gl_ONE, gl_ONE_MINUS_SRC_ALPHA)
|
||||
|
||||
var kernel []float32
|
||||
var kernelBuf [255]float32
|
||||
var gs *gaussianShader
|
||||
if cv.state.shadowBlur < 3 {
|
||||
gs = gauss15r
|
||||
kernel = kernelBuf[:15]
|
||||
} else if cv.state.shadowBlur < 12 {
|
||||
gs = gauss63r
|
||||
kernel = kernelBuf[:63]
|
||||
color := cv.state.shadowColor
|
||||
color.A = uint8(math.Round(((float64(color.A) / 255.0) * cv.state.globalAlpha) * 255.0))
|
||||
style := backendbase.FillStyle{Color: color, Blur: cv.state.shadowBlur}
|
||||
if mask != nil {
|
||||
if len(cv.shadowBuf) != 4 {
|
||||
panic("invalid number of points to fill with mask, must be 4")
|
||||
}
|
||||
var quad [4]backendbase.Vec
|
||||
copy(quad[:], cv.shadowBuf)
|
||||
cv.b.FillImageMask(&style, mask, quad)
|
||||
} else {
|
||||
gs = gauss127r
|
||||
kernel = kernelBuf[:127]
|
||||
}
|
||||
|
||||
gaussianKernel(cv.state.shadowBlur, kernel)
|
||||
|
||||
offscr2.alpha = true
|
||||
cv.enableTextureRenderTarget(&offscr2)
|
||||
gli.ClearColor(0, 0, 0, 0)
|
||||
gli.Clear(gl_COLOR_BUFFER_BIT | gl_STENCIL_BUFFER_BIT)
|
||||
|
||||
gli.StencilFunc(gl_EQUAL, 0, 0xFF)
|
||||
|
||||
gli.BindBuffer(gl_ARRAY_BUFFER, shadowBuf)
|
||||
data := [16]float32{0, 0, 0, float32(cv.h), float32(cv.w), float32(cv.h), float32(cv.w), 0, 0, 0, 0, 1, 1, 1, 1, 0}
|
||||
gli.BufferData(gl_ARRAY_BUFFER, len(data)*4, unsafe.Pointer(&data[0]), gl_STREAM_DRAW)
|
||||
|
||||
gli.ActiveTexture(gl_TEXTURE0)
|
||||
gli.BindTexture(gl_TEXTURE_2D, offscr1.tex)
|
||||
|
||||
gli.UseProgram(gs.id)
|
||||
gli.Uniform1i(gs.image, 0)
|
||||
gli.Uniform2f(gs.canvasSize, float32(cv.fw), float32(cv.fh))
|
||||
gli.Uniform2f(gs.kernelScale, 1.0/float32(cv.fw), 0.0)
|
||||
gli.Uniform1fv(gs.kernel, int32(len(kernel)), &kernel[0])
|
||||
gli.VertexAttribPointer(gs.vertex, 2, gl_FLOAT, false, 0, 0)
|
||||
gli.VertexAttribPointer(gs.texCoord, 2, gl_FLOAT, false, 0, 8*4)
|
||||
gli.EnableVertexAttribArray(gs.vertex)
|
||||
gli.EnableVertexAttribArray(gs.texCoord)
|
||||
gli.DrawArrays(gl_TRIANGLE_FAN, 0, 4)
|
||||
gli.DisableVertexAttribArray(gs.vertex)
|
||||
gli.DisableVertexAttribArray(gs.texCoord)
|
||||
|
||||
gli.StencilFunc(gl_ALWAYS, 0, 0xFF)
|
||||
|
||||
cv.disableTextureRenderTarget()
|
||||
|
||||
gli.StencilFunc(gl_EQUAL, 0, 0xFF)
|
||||
|
||||
gli.BindBuffer(gl_ARRAY_BUFFER, shadowBuf)
|
||||
data = [16]float32{0, 0, 0, float32(cv.h), float32(cv.w), float32(cv.h), float32(cv.w), 0, 0, 0, 0, 1, 1, 1, 1, 0}
|
||||
gli.BufferData(gl_ARRAY_BUFFER, len(data)*4, unsafe.Pointer(&data[0]), gl_STREAM_DRAW)
|
||||
|
||||
gli.ActiveTexture(gl_TEXTURE0)
|
||||
gli.BindTexture(gl_TEXTURE_2D, offscr2.tex)
|
||||
|
||||
gli.UseProgram(gs.id)
|
||||
gli.Uniform1i(gs.image, 0)
|
||||
gli.Uniform2f(gs.canvasSize, float32(cv.fw), float32(cv.fh))
|
||||
gli.Uniform2f(gs.kernelScale, 0.0, 1.0/float32(cv.fh))
|
||||
gli.Uniform1fv(gs.kernel, int32(len(kernel)), &kernel[0])
|
||||
gli.VertexAttribPointer(gs.vertex, 2, gl_FLOAT, false, 0, 0)
|
||||
gli.VertexAttribPointer(gs.texCoord, 2, gl_FLOAT, false, 0, 8*4)
|
||||
gli.EnableVertexAttribArray(gs.vertex)
|
||||
gli.EnableVertexAttribArray(gs.texCoord)
|
||||
gli.DrawArrays(gl_TRIANGLE_FAN, 0, 4)
|
||||
gli.DisableVertexAttribArray(gs.vertex)
|
||||
gli.DisableVertexAttribArray(gs.texCoord)
|
||||
|
||||
gli.StencilFunc(gl_ALWAYS, 0, 0xFF)
|
||||
|
||||
gli.BlendFunc(gl_SRC_ALPHA, gl_ONE_MINUS_SRC_ALPHA)
|
||||
}
|
||||
|
||||
func gaussianKernel(stddev float64, target []float32) {
|
||||
stddevSqr := stddev * stddev
|
||||
center := float64(len(target) / 2)
|
||||
factor := 1.0 / math.Sqrt(2*math.Pi*stddevSqr)
|
||||
for i := range target {
|
||||
x := float64(i) - center
|
||||
target[i] = float32(factor * math.Pow(math.E, -x*x/(2*stddevSqr)))
|
||||
}
|
||||
// normalizeKernel(target)
|
||||
}
|
||||
|
||||
func normalizeKernel(kernel []float32) {
|
||||
var sum float32
|
||||
for _, v := range kernel {
|
||||
sum += v
|
||||
}
|
||||
factor := 1.0 / sum
|
||||
for i := range kernel {
|
||||
kernel[i] *= factor
|
||||
cv.b.Fill(&style, cv.shadowBuf, backendbase.MatIdentity, canOverlap)
|
||||
}
|
||||
}
|
||||
|
|
BIN
testdata/Alpha.png
vendored
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
BIN
testdata/Convex.png
vendored
Executable file
After Width: | Height: | Size: 392 B |
BIN
testdata/ConvexSelfIntersecting.png
vendored
Executable file
After Width: | Height: | Size: 415 B |
BIN
testdata/Curves.png
vendored
Before Width: | Height: | Size: 641 B After Width: | Height: | Size: 651 B |
BIN
testdata/FillHammer.png
vendored
Executable file
After Width: | Height: | Size: 336 B |
BIN
testdata/Gradient.png
vendored
Executable file
After Width: | Height: | Size: 2.3 KiB |
BIN
testdata/Image.png
vendored
Executable file
After Width: | Height: | Size: 14 KiB |