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