moved image loading and drawing into backend
This commit is contained in:
parent
7cef867541
commit
476dbac6e2
10 changed files with 406 additions and 320 deletions
|
@ -1,12 +1,36 @@
|
||||||
package backendbase
|
package backendbase
|
||||||
|
|
||||||
import "image/color"
|
import (
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
)
|
||||||
|
|
||||||
type Style struct {
|
// Backend is used by the canvas to actually do the final
|
||||||
Color color.RGBA
|
// drawing. This enables the backend to be implemented by
|
||||||
GlobalAlpha float64
|
// various methods (OpenGL, but also other APIs or software)
|
||||||
Blur float64
|
type Backend interface {
|
||||||
|
ClearRect(x, y, w, h int)
|
||||||
|
Clear(pts [4][2]float64)
|
||||||
|
Fill(style *FillStyle, pts [][2]float64)
|
||||||
|
LoadImage(img image.Image) (Image, error)
|
||||||
|
DrawImage(dimg Image, sx, sy, sw, sh, dx, dy, dw, dh float64, alpha float64)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FillStyle is the color and other details on how to fill
|
||||||
|
type FillStyle struct {
|
||||||
|
Color color.RGBA
|
||||||
|
Blur float64
|
||||||
// radialGradient *RadialGradient
|
// radialGradient *RadialGradient
|
||||||
// linearGradient *LinearGradient
|
// linearGradient *LinearGradient
|
||||||
// image *Image
|
// image *Image
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Image interface {
|
||||||
|
Width() int
|
||||||
|
Height() int
|
||||||
|
Size() (w, h int)
|
||||||
|
Delete()
|
||||||
|
IsDeleted() bool
|
||||||
|
Replace(src image.Image) error
|
||||||
|
IsOpaque() bool
|
||||||
|
}
|
||||||
|
|
|
@ -45,7 +45,7 @@ func (b *GoGLBackend) Clear(pts [4][2]float64) {
|
||||||
gl.Enable(gl.BLEND)
|
gl.Enable(gl.BLEND)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *GoGLBackend) Fill(style *backendbase.Style, pts [][2]float64) {
|
func (b *GoGLBackend) Fill(style *backendbase.FillStyle, pts [][2]float64) {
|
||||||
if style.Blur > 0 {
|
if style.Blur > 0 {
|
||||||
b.offscr1.alpha = true
|
b.offscr1.alpha = true
|
||||||
b.enableTextureRenderTarget(&b.offscr1)
|
b.enableTextureRenderTarget(&b.offscr1)
|
||||||
|
@ -71,7 +71,7 @@ func (b *GoGLBackend) Fill(style *backendbase.Style, pts [][2]float64) {
|
||||||
gl.BindBuffer(gl.ARRAY_BUFFER, b.buf)
|
gl.BindBuffer(gl.ARRAY_BUFFER, b.buf)
|
||||||
gl.BufferData(gl.ARRAY_BUFFER, len(b.ptsBuf)*4, unsafe.Pointer(&b.ptsBuf[0]), gl.STREAM_DRAW)
|
gl.BufferData(gl.ARRAY_BUFFER, len(b.ptsBuf)*4, unsafe.Pointer(&b.ptsBuf[0]), gl.STREAM_DRAW)
|
||||||
|
|
||||||
if style.GlobalAlpha >= 1 && style.Color.A >= 255 {
|
if style.Color.A >= 255 {
|
||||||
vertex := b.useShader(style)
|
vertex := b.useShader(style)
|
||||||
|
|
||||||
gl.EnableVertexAttribArray(vertex)
|
gl.EnableVertexAttribArray(vertex)
|
||||||
|
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"image/color"
|
"image/color"
|
||||||
|
|
||||||
"github.com/go-gl/gl/v3.2-core/gl"
|
"github.com/go-gl/gl/v3.2-core/gl"
|
||||||
"github.com/tfriedel6/canvas"
|
|
||||||
"github.com/tfriedel6/canvas/backend/backendbase"
|
"github.com/tfriedel6/canvas/backend/backendbase"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -46,7 +45,7 @@ type offscreenBuffer struct {
|
||||||
alpha bool
|
alpha bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(x, y, w, h int) (canvas.Backend, error) {
|
func New(x, y, w, h int) (backendbase.Backend, error) {
|
||||||
err := gl.Init()
|
err := gl.Init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -219,6 +218,23 @@ func glError() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Activate makes this GL backend active and sets the viewport. Only
|
||||||
|
// needs to be called if any other GL code changes the viewport
|
||||||
|
func (b *GoGLBackend) Activate() {
|
||||||
|
// if b.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 {
|
||||||
|
gl.Viewport(int32(b.x), int32(b.y), int32(b.w), int32(b.h))
|
||||||
|
b.disableTextureRenderTarget()
|
||||||
|
// }
|
||||||
|
// b.applyScissor()
|
||||||
|
gl.Clear(gl.STENCIL_BUFFER_BIT)
|
||||||
|
}
|
||||||
|
|
||||||
type glColor struct {
|
type glColor struct {
|
||||||
r, g, b, a float64
|
r, g, b, a float64
|
||||||
}
|
}
|
||||||
|
@ -232,7 +248,7 @@ func colorGoToGL(c color.RGBA) glColor {
|
||||||
return glc
|
return glc
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *GoGLBackend) useShader(style *backendbase.Style) (vertexLoc uint32) {
|
func (b *GoGLBackend) useShader(style *backendbase.FillStyle) (vertexLoc uint32) {
|
||||||
// if lg := style.LinearGradient; lg != nil {
|
// if lg := style.LinearGradient; lg != nil {
|
||||||
// lg.load()
|
// lg.load()
|
||||||
// gl.ActiveTexture(gl.TEXTURE0)
|
// gl.ActiveTexture(gl.TEXTURE0)
|
||||||
|
@ -293,11 +309,11 @@ func (b *GoGLBackend) useShader(style *backendbase.Style) (vertexLoc uint32) {
|
||||||
gl.Uniform2f(b.sr.CanvasSize, float32(b.fw), float32(b.fh))
|
gl.Uniform2f(b.sr.CanvasSize, float32(b.fw), float32(b.fh))
|
||||||
c := colorGoToGL(style.Color)
|
c := colorGoToGL(style.Color)
|
||||||
gl.Uniform4f(b.sr.Color, float32(c.r), float32(c.g), float32(c.b), float32(c.a))
|
gl.Uniform4f(b.sr.Color, float32(c.r), float32(c.g), float32(c.b), float32(c.a))
|
||||||
gl.Uniform1f(b.sr.GlobalAlpha, float32(style.GlobalAlpha))
|
gl.Uniform1f(b.sr.GlobalAlpha, 1)
|
||||||
return b.sr.Vertex
|
return b.sr.Vertex
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *GoGLBackend) useAlphaShader(style *backendbase.Style, alphaTexSlot int32) (vertexLoc, alphaTexCoordLoc uint32) {
|
func (b *GoGLBackend) useAlphaShader(style *backendbase.FillStyle, alphaTexSlot int32) (vertexLoc, alphaTexCoordLoc uint32) {
|
||||||
// if lg := style.LinearGradient; lg != nil {
|
// if lg := style.LinearGradient; lg != nil {
|
||||||
// lg.load()
|
// lg.load()
|
||||||
// gl.ActiveTexture(gl.TEXTURE0)
|
// gl.ActiveTexture(gl.TEXTURE0)
|
||||||
|
@ -362,7 +378,7 @@ func (b *GoGLBackend) useAlphaShader(style *backendbase.Style, alphaTexSlot int3
|
||||||
c := colorGoToGL(style.Color)
|
c := colorGoToGL(style.Color)
|
||||||
gl.Uniform4f(b.sar.Color, float32(c.r), float32(c.g), float32(c.b), float32(c.a))
|
gl.Uniform4f(b.sar.Color, float32(c.r), float32(c.g), float32(c.b), float32(c.a))
|
||||||
gl.Uniform1i(b.sar.AlphaTex, alphaTexSlot)
|
gl.Uniform1i(b.sar.AlphaTex, alphaTexSlot)
|
||||||
gl.Uniform1f(b.sar.GlobalAlpha, float32(style.GlobalAlpha))
|
gl.Uniform1f(b.sar.GlobalAlpha, 1)
|
||||||
return b.sar.Vertex, b.sar.AlphaTexCoord
|
return b.sar.Vertex, b.sar.AlphaTexCoord
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
250
backend/gogl/images.go
Normal file
250
backend/gogl/images.go
Normal file
|
@ -0,0 +1,250 @@
|
||||||
|
package goglbackend
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"image"
|
||||||
|
"runtime"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/go-gl/gl/v3.2-core/gl"
|
||||||
|
"github.com/tfriedel6/canvas/backend/backendbase"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Image represents a loaded image that can be used in various drawing functions
|
||||||
|
type Image struct {
|
||||||
|
w, h int
|
||||||
|
tex uint32
|
||||||
|
deleted bool
|
||||||
|
opaque bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *GoGLBackend) LoadImage(src image.Image) (backendbase.Image, error) {
|
||||||
|
var tex uint32
|
||||||
|
gl.GenTextures(1, &tex)
|
||||||
|
gl.ActiveTexture(gl.TEXTURE0)
|
||||||
|
gl.BindTexture(gl.TEXTURE_2D, tex)
|
||||||
|
if src == nil {
|
||||||
|
return &Image{tex: tex}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
img, err := loadImage(src, tex)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
runtime.SetFinalizer(img, func(img *Image) {
|
||||||
|
if !img.deleted {
|
||||||
|
b.glChan <- func() {
|
||||||
|
gl.DeleteTextures(1, &img.tex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return img, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadImage(src image.Image, tex uint32) (*Image, error) {
|
||||||
|
var img *Image
|
||||||
|
var err error
|
||||||
|
switch v := src.(type) {
|
||||||
|
case *image.RGBA:
|
||||||
|
img, err = loadImageRGBA(v, tex)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
case *image.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
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, errors.New("Unsupported source type")
|
||||||
|
}
|
||||||
|
return img, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadImageRGBA(src *image.RGBA, tex uint32) (*Image, error) {
|
||||||
|
img := &Image{tex: tex, w: src.Bounds().Dx(), h: src.Bounds().Dy(), 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR)
|
||||||
|
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
|
||||||
|
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
|
||||||
|
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
|
||||||
|
if err := glError(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if src.Stride == img.w*4 {
|
||||||
|
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, int32(img.w), int32(img.h), 0, gl.RGBA, gl.UNSIGNED_BYTE, gl.Ptr(&src.Pix[0]))
|
||||||
|
} else {
|
||||||
|
data := make([]uint8, 0, img.w*img.h*4)
|
||||||
|
for y := 0; y < img.h; y++ {
|
||||||
|
start := y * src.Stride
|
||||||
|
end := start + img.w*4
|
||||||
|
data = append(data, src.Pix[start:end]...)
|
||||||
|
}
|
||||||
|
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, int32(img.w), int32(img.h), 0, gl.RGBA, gl.UNSIGNED_BYTE, gl.Ptr(&data[0]))
|
||||||
|
}
|
||||||
|
if err := glError(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
gl.GenerateMipmap(gl.TEXTURE_2D)
|
||||||
|
if err := glError(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return img, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadImageGray(src *image.Gray, tex uint32) (*Image, error) {
|
||||||
|
img := &Image{tex: tex, w: src.Bounds().Dx(), h: src.Bounds().Dy()}
|
||||||
|
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR)
|
||||||
|
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
|
||||||
|
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
|
||||||
|
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
|
||||||
|
if err := glError(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if src.Stride == img.w {
|
||||||
|
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RED, int32(img.w), int32(img.h), 0, gl.RED, gl.UNSIGNED_BYTE, gl.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]...)
|
||||||
|
}
|
||||||
|
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RED, int32(img.w), int32(img.h), 0, gl.RED, gl.UNSIGNED_BYTE, gl.Ptr(&data[0]))
|
||||||
|
}
|
||||||
|
if err := glError(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
gl.GenerateMipmap(gl.TEXTURE_2D)
|
||||||
|
if err := glError(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return img, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadImageConverted(src image.Image, tex uint32) (*Image, error) {
|
||||||
|
img := &Image{tex: tex, w: src.Bounds().Dx(), h: src.Bounds().Dy(), opaque: true}
|
||||||
|
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR)
|
||||||
|
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
|
||||||
|
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
|
||||||
|
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
|
||||||
|
if err := glError(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
data := make([]uint8, 0, img.w*img.h*4)
|
||||||
|
for y := 0; y < img.h; y++ {
|
||||||
|
for x := 0; x < img.w; x++ {
|
||||||
|
ir, ig, ib, ia := src.At(x, y).RGBA()
|
||||||
|
r, g, b, a := uint8(ir>>8), uint8(ig>>8), uint8(ib>>8), uint8(ia>>8)
|
||||||
|
data = append(data, r, g, b, a)
|
||||||
|
if a < 255 {
|
||||||
|
img.opaque = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, int32(img.w), int32(img.h), 0, gl.RGBA, gl.UNSIGNED_BYTE, gl.Ptr(&data[0]))
|
||||||
|
if err := glError(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
gl.GenerateMipmap(gl.TEXTURE_2D)
|
||||||
|
if err := glError(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return img, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Width returns the width of the image
|
||||||
|
func (img *Image) Width() int { return img.w }
|
||||||
|
|
||||||
|
// Height returns the height of the image
|
||||||
|
func (img *Image) Height() int { return img.h }
|
||||||
|
|
||||||
|
// Size returns the width and height of the image
|
||||||
|
func (img *Image) Size() (int, int) { return img.w, img.h }
|
||||||
|
|
||||||
|
// Delete deletes the image from memory. Any draw calls
|
||||||
|
// with a deleted image will not do anything
|
||||||
|
func (img *Image) Delete() {
|
||||||
|
gl.DeleteTextures(1, &img.tex)
|
||||||
|
img.deleted = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsDeleted returns true if the Delete function has been
|
||||||
|
// called on this image
|
||||||
|
func (img *Image) IsDeleted() bool { return img.deleted }
|
||||||
|
|
||||||
|
// Replace replaces the image with the new one
|
||||||
|
func (img *Image) Replace(src image.Image) error {
|
||||||
|
gl.ActiveTexture(gl.TEXTURE0)
|
||||||
|
gl.BindTexture(gl.TEXTURE_2D, img.tex)
|
||||||
|
newImg, err := loadImage(src, img.tex)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*img = *newImg
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsOpaque returns true if all pixels in the image
|
||||||
|
// have a full alpha value
|
||||||
|
func (img *Image) IsOpaque() bool { return img.opaque }
|
||||||
|
|
||||||
|
func (b *GoGLBackend) DrawImage(dimg backendbase.Image, sx, sy, sw, sh, dx, dy, dw, dh float64, alpha float64) {
|
||||||
|
img := dimg.(*Image)
|
||||||
|
|
||||||
|
sx /= float64(img.w)
|
||||||
|
sy /= float64(img.h)
|
||||||
|
sw /= float64(img.w)
|
||||||
|
sh /= float64(img.h)
|
||||||
|
|
||||||
|
gl.StencilFunc(gl.EQUAL, 0, 0xFF)
|
||||||
|
|
||||||
|
gl.BindBuffer(gl.ARRAY_BUFFER, b.buf)
|
||||||
|
data := [16]float32{
|
||||||
|
float32(dx), float32(dy),
|
||||||
|
float32(dx), float32(dy + dh),
|
||||||
|
float32(dx + dw), float32(dy + dh),
|
||||||
|
float32(dx + dw), float32(dy),
|
||||||
|
float32(sx), float32(sy),
|
||||||
|
float32(sx), float32(sy + sh),
|
||||||
|
float32(sx + sw), float32(sy + sh),
|
||||||
|
float32(sx + sw), float32(sy),
|
||||||
|
}
|
||||||
|
gl.BufferData(gl.ARRAY_BUFFER, len(data)*4, unsafe.Pointer(&data[0]), gl.STREAM_DRAW)
|
||||||
|
|
||||||
|
gl.ActiveTexture(gl.TEXTURE0)
|
||||||
|
gl.BindTexture(gl.TEXTURE_2D, img.tex)
|
||||||
|
|
||||||
|
gl.UseProgram(b.ir.ID)
|
||||||
|
gl.Uniform1i(b.ir.Image, 0)
|
||||||
|
gl.Uniform2f(b.ir.CanvasSize, float32(b.fw), float32(b.fh))
|
||||||
|
gl.Uniform1f(b.ir.GlobalAlpha, float32(alpha))
|
||||||
|
gl.VertexAttribPointer(b.ir.Vertex, 2, gl.FLOAT, false, 0, nil)
|
||||||
|
gl.VertexAttribPointer(b.ir.TexCoord, 2, gl.FLOAT, false, 0, gl.PtrOffset(8*4))
|
||||||
|
gl.EnableVertexAttribArray(b.ir.Vertex)
|
||||||
|
gl.EnableVertexAttribArray(b.ir.TexCoord)
|
||||||
|
gl.DrawArrays(gl.TRIANGLE_FAN, 0, 4)
|
||||||
|
gl.DisableVertexAttribArray(b.ir.Vertex)
|
||||||
|
gl.DisableVertexAttribArray(b.ir.TexCoord)
|
||||||
|
|
||||||
|
gl.StencilFunc(gl.ALWAYS, 0, 0xFF)
|
||||||
|
}
|
95
canvas.go
95
canvas.go
|
@ -18,7 +18,7 @@ import (
|
||||||
// Canvas represents an area on the viewport on which to draw
|
// Canvas represents an area on the viewport on which to draw
|
||||||
// using a set of functions very similar to the HTML5 canvas
|
// using a set of functions very similar to the HTML5 canvas
|
||||||
type Canvas struct {
|
type Canvas struct {
|
||||||
b Backend
|
b backendbase.Backend
|
||||||
|
|
||||||
x, y, w, h int
|
x, y, w, h int
|
||||||
fx, fy, fw, fh float64
|
fx, fy, fw, fh float64
|
||||||
|
@ -34,16 +34,9 @@ type Canvas struct {
|
||||||
offscrBuf offscreenBuffer
|
offscrBuf offscreenBuffer
|
||||||
offscrImg Image
|
offscrImg Image
|
||||||
|
|
||||||
shadowBuf [][2]float64
|
images map[interface{}]*Image
|
||||||
}
|
|
||||||
|
|
||||||
// Backend is used by the canvas to actually do the final
|
shadowBuf [][2]float64
|
||||||
// drawing. This enables the backend to be implemented by
|
|
||||||
// various methods (OpenGL, but also other APIs or software)
|
|
||||||
type Backend interface {
|
|
||||||
ClearRect(x, y, w, h int)
|
|
||||||
Clear(pts [4][2]float64)
|
|
||||||
Fill(style *backendbase.Style, pts [][2]float64)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type drawState struct {
|
type drawState struct {
|
||||||
|
@ -149,11 +142,15 @@ var Performance = struct {
|
||||||
// While all functions on the canvas use the top left point as
|
// While all functions on the canvas use the top left point as
|
||||||
// the origin, since GL uses the bottom left coordinate, the
|
// the origin, since GL uses the bottom left coordinate, the
|
||||||
// coordinates given here also use the bottom left as origin
|
// coordinates given here also use the bottom left as origin
|
||||||
func New(backend Backend, x, y, w, h int) *Canvas {
|
func New(backend backendbase.Backend, x, y, w, h int) *Canvas {
|
||||||
if gli == nil {
|
if gli == nil {
|
||||||
panic("LoadGL must be called before a canvas can be created")
|
panic("LoadGL must be called before a canvas can be created")
|
||||||
}
|
}
|
||||||
cv := &Canvas{b: backend, stateStack: make([]drawState, 0, 20)}
|
cv := &Canvas{
|
||||||
|
b: backend,
|
||||||
|
stateStack: make([]drawState, 0, 20),
|
||||||
|
images: make(map[interface{}]*Image),
|
||||||
|
}
|
||||||
cv.SetBounds(x, y, w, h)
|
cv.SetBounds(x, y, w, h)
|
||||||
cv.state.lineWidth = 1
|
cv.state.lineWidth = 1
|
||||||
cv.state.lineAlpha = 1
|
cv.state.lineAlpha = 1
|
||||||
|
@ -169,7 +166,7 @@ func New(backend Backend, x, y, w, h int) *Canvas {
|
||||||
// does not render directly to the screen but renders to a
|
// does not render directly to the screen but renders to a
|
||||||
// texture instead. If alpha is set to true, the offscreen
|
// texture instead. If alpha is set to true, the offscreen
|
||||||
// canvas will have an alpha channel
|
// canvas will have an alpha channel
|
||||||
func NewOffscreen(backend Backend, w, h int, alpha bool) *Canvas {
|
func NewOffscreen(backend backendbase.Backend, w, h int, alpha bool) *Canvas {
|
||||||
cv := New(backend, 0, 0, w, h)
|
cv := New(backend, 0, 0, w, h)
|
||||||
cv.offscreen = true
|
cv.offscreen = true
|
||||||
cv.offscrBuf.alpha = alpha
|
cv.offscrBuf.alpha = alpha
|
||||||
|
@ -217,9 +214,9 @@ func (cv *Canvas) Activate() {
|
||||||
if cv.offscreen {
|
if cv.offscreen {
|
||||||
gli.Viewport(0, 0, int32(cv.w), int32(cv.h))
|
gli.Viewport(0, 0, int32(cv.w), int32(cv.h))
|
||||||
cv.enableTextureRenderTarget(&cv.offscrBuf)
|
cv.enableTextureRenderTarget(&cv.offscrBuf)
|
||||||
cv.offscrImg.w = cv.offscrBuf.w
|
// cv.offscrImg.w = cv.offscrBuf.w
|
||||||
cv.offscrImg.h = cv.offscrBuf.h
|
// cv.offscrImg.h = cv.offscrBuf.h
|
||||||
cv.offscrImg.tex = cv.offscrBuf.tex
|
// cv.offscrImg.tex = cv.offscrBuf.tex
|
||||||
} else {
|
} else {
|
||||||
gli.Viewport(int32(cv.x), int32(cv.y), int32(cv.w), int32(cv.h))
|
gli.Viewport(int32(cv.x), int32(cv.y), int32(cv.w), int32(cv.h))
|
||||||
cv.disableTextureRenderTarget()
|
cv.disableTextureRenderTarget()
|
||||||
|
@ -456,7 +453,7 @@ func glError() error {
|
||||||
// the range 0-255, 3 or 4 float values for RGB(A) in the range 0-1, hex strings
|
// 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"
|
// in the format "#AABBCC", "#AABBCCDD", "#ABC", or "#ABCD"
|
||||||
func (cv *Canvas) SetFillStyle(value ...interface{}) {
|
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.
|
// SetStrokeStyle sets the color, gradient, or image for any line drawing calls.
|
||||||
|
@ -464,10 +461,10 @@ 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,
|
// 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"
|
// hex strings in the format "#AABBCC", "#AABBCCDD", "#ABC", or "#ABCD"
|
||||||
func (cv *Canvas) SetStrokeStyle(value ...interface{}) {
|
func (cv *Canvas) SetStrokeStyle(value ...interface{}) {
|
||||||
cv.state.stroke = parseStyle(value...)
|
cv.state.stroke = cv.parseStyle(value...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseStyle(value ...interface{}) drawStyle {
|
func (cv *Canvas) parseStyle(value ...interface{}) drawStyle {
|
||||||
var style drawStyle
|
var style drawStyle
|
||||||
if len(value) == 1 {
|
if len(value) == 1 {
|
||||||
switch v := value[0].(type) {
|
switch v := value[0].(type) {
|
||||||
|
@ -485,7 +482,7 @@ func parseStyle(value ...interface{}) drawStyle {
|
||||||
} else if len(value) == 1 {
|
} else if len(value) == 1 {
|
||||||
switch v := value[0].(type) {
|
switch v := value[0].(type) {
|
||||||
case *Image, string:
|
case *Image, string:
|
||||||
style.image = getImage(v)
|
style.image = cv.getImage(v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return style
|
return style
|
||||||
|
@ -499,16 +496,16 @@ func (s *drawStyle) isOpaque() bool {
|
||||||
return rg.opaque
|
return rg.opaque
|
||||||
}
|
}
|
||||||
if img := s.image; img != nil {
|
if img := s.image; img != nil {
|
||||||
return img.opaque
|
return img.img.IsOpaque()
|
||||||
}
|
}
|
||||||
return s.color.A >= 255
|
return s.color.A >= 255
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cv *Canvas) backendStyle(s *drawStyle, alpha float64) backendbase.Style {
|
func (cv *Canvas) backendFillStyle(s *drawStyle, alpha float64) backendbase.FillStyle {
|
||||||
return backendbase.Style{
|
col := s.color
|
||||||
Color: s.color,
|
finalAlpha := (float64(s.color.A) / 255) * alpha * cv.state.globalAlpha
|
||||||
GlobalAlpha: cv.state.globalAlpha * alpha,
|
col.A = uint8(finalAlpha * 255)
|
||||||
}
|
return backendbase.FillStyle{Color: col}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cv *Canvas) useShader(style *drawStyle) (vertexLoc uint32) {
|
func (cv *Canvas) useShader(style *drawStyle) (vertexLoc uint32) {
|
||||||
|
@ -551,16 +548,18 @@ func (cv *Canvas) useShader(style *drawStyle) (vertexLoc uint32) {
|
||||||
gli.Uniform1f(rgr.globalAlpha, float32(cv.state.globalAlpha))
|
gli.Uniform1f(rgr.globalAlpha, float32(cv.state.globalAlpha))
|
||||||
return rgr.vertex
|
return rgr.vertex
|
||||||
}
|
}
|
||||||
if img := style.image; img != nil {
|
// if img := style.image; img != nil {
|
||||||
gli.UseProgram(ipr.id)
|
// gli.UseProgram(ipr.id)
|
||||||
gli.ActiveTexture(gl_TEXTURE0)
|
// gli.ActiveTexture(gl_TEXTURE0)
|
||||||
gli.BindTexture(gl_TEXTURE_2D, img.tex)
|
// gli.BindTexture(gl_TEXTURE_2D, img.tex)
|
||||||
gli.Uniform2f(ipr.canvasSize, float32(cv.fw), float32(cv.fh))
|
// gli.Uniform2f(ipr.canvasSize, float32(cv.fw), float32(cv.fh))
|
||||||
gli.Uniform2f(ipr.imageSize, float32(img.w), float32(img.h))
|
// inv := cv.state.transform.invert().f32()
|
||||||
gli.Uniform1i(ipr.image, 0)
|
// gli.UniformMatrix3fv(ipr.invmat, 1, false, &inv[0])
|
||||||
gli.Uniform1f(ipr.globalAlpha, float32(cv.state.globalAlpha))
|
// gli.Uniform2f(ipr.imageSize, float32(img.w), float32(img.h))
|
||||||
return ipr.vertex
|
// gli.Uniform1i(ipr.image, 0)
|
||||||
}
|
// gli.Uniform1f(ipr.globalAlpha, float32(cv.state.globalAlpha))
|
||||||
|
// return ipr.vertex
|
||||||
|
// }
|
||||||
|
|
||||||
gli.UseProgram(sr.id)
|
gli.UseProgram(sr.id)
|
||||||
gli.Uniform2f(sr.canvasSize, float32(cv.fw), float32(cv.fh))
|
gli.Uniform2f(sr.canvasSize, float32(cv.fw), float32(cv.fh))
|
||||||
|
@ -612,17 +611,19 @@ func (cv *Canvas) useAlphaShader(style *drawStyle, alphaTexSlot int32) (vertexLo
|
||||||
gli.Uniform1f(rgar.globalAlpha, float32(cv.state.globalAlpha))
|
gli.Uniform1f(rgar.globalAlpha, float32(cv.state.globalAlpha))
|
||||||
return rgar.vertex, rgar.alphaTexCoord
|
return rgar.vertex, rgar.alphaTexCoord
|
||||||
}
|
}
|
||||||
if img := style.image; img != nil {
|
// if img := style.image; img != nil {
|
||||||
gli.UseProgram(ipar.id)
|
// gli.UseProgram(ipar.id)
|
||||||
gli.ActiveTexture(gl_TEXTURE0)
|
// gli.ActiveTexture(gl_TEXTURE0)
|
||||||
gli.BindTexture(gl_TEXTURE_2D, img.tex)
|
// gli.BindTexture(gl_TEXTURE_2D, img.tex)
|
||||||
gli.Uniform2f(ipar.canvasSize, float32(cv.fw), float32(cv.fh))
|
// gli.Uniform2f(ipar.canvasSize, float32(cv.fw), float32(cv.fh))
|
||||||
gli.Uniform2f(ipar.imageSize, float32(img.w), float32(img.h))
|
// inv := cv.state.transform.invert().f32()
|
||||||
gli.Uniform1i(ipar.image, 0)
|
// gli.UniformMatrix3fv(ipar.invmat, 1, false, &inv[0])
|
||||||
gli.Uniform1i(ipar.alphaTex, alphaTexSlot)
|
// gli.Uniform2f(ipar.imageSize, float32(img.w), float32(img.h))
|
||||||
gli.Uniform1f(ipar.globalAlpha, float32(cv.state.globalAlpha))
|
// gli.Uniform1i(ipar.image, 0)
|
||||||
return ipar.vertex, ipar.alphaTexCoord
|
// gli.Uniform1i(ipar.alphaTex, alphaTexSlot)
|
||||||
}
|
// gli.Uniform1f(ipar.globalAlpha, float32(cv.state.globalAlpha))
|
||||||
|
// return ipar.vertex, ipar.alphaTexCoord
|
||||||
|
// }
|
||||||
|
|
||||||
gli.UseProgram(sar.id)
|
gli.UseProgram(sar.id)
|
||||||
gli.Uniform2f(sar.canvasSize, float32(cv.fw), float32(cv.fh))
|
gli.Uniform2f(sar.canvasSize, float32(cv.fw), float32(cv.fh))
|
||||||
|
|
|
@ -17,15 +17,13 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func run(t *testing.T, fn func(cv *canvas.Canvas)) {
|
func run(t *testing.T, fn func(cv *canvas.Canvas)) {
|
||||||
wnd, cv2, err := sdlcanvas.CreateWindow(100, 100, "test")
|
wnd, cv, err := sdlcanvas.CreateWindow(100, 100, "test")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to crete window: %v", err)
|
t.Fatalf("Failed to crete window: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer wnd.Destroy()
|
defer wnd.Destroy()
|
||||||
|
|
||||||
cv := canvas.NewOffscreen(wnd.Backend, 100, 100, false)
|
|
||||||
|
|
||||||
gl.Disable(gl.MULTISAMPLE)
|
gl.Disable(gl.MULTISAMPLE)
|
||||||
|
|
||||||
wnd.StartFrame()
|
wnd.StartFrame()
|
||||||
|
@ -34,9 +32,6 @@ func run(t *testing.T, fn func(cv *canvas.Canvas)) {
|
||||||
fn(cv)
|
fn(cv)
|
||||||
img := cv.GetImageData(0, 0, 100, 100)
|
img := cv.GetImageData(0, 0, 100, 100)
|
||||||
|
|
||||||
cv2.DrawImage(cv)
|
|
||||||
img2 := cv2.GetImageData(0, 0, 100, 100)
|
|
||||||
|
|
||||||
caller, _, _, ok := runtime.Caller(1)
|
caller, _, _, ok := runtime.Caller(1)
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatal("Failed to get caller")
|
t.Fatal("Failed to get caller")
|
||||||
|
@ -87,16 +82,9 @@ func run(t *testing.T, fn func(cv *canvas.Canvas)) {
|
||||||
for y := 0; y < 100; y++ {
|
for y := 0; y < 100; y++ {
|
||||||
for x := 0; x < 100; x++ {
|
for x := 0; x < 100; x++ {
|
||||||
r1, g1, b1, a1 := img.At(x, y).RGBA()
|
r1, g1, b1, a1 := img.At(x, y).RGBA()
|
||||||
r2, g2, b2, a2 := img2.At(x, y).RGBA()
|
r2, g2, b2, a2 := refImg.At(x, y).RGBA()
|
||||||
r3, g3, b3, a3 := refImg.At(x, y).RGBA()
|
if r1 != r2 || g1 != g2 || b1 != b2 || a1 != a2 {
|
||||||
if r1 != r3 || g1 != g3 || b1 != b3 || a1 != a3 {
|
|
||||||
writeImage(img, fmt.Sprintf("testdata/%s_fail.png", callerFuncName))
|
writeImage(img, fmt.Sprintf("testdata/%s_fail.png", callerFuncName))
|
||||||
t.Error("onscreen canvas failed")
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
if r2 != r3 || g2 != g3 || b2 != b3 || a2 != a3 {
|
|
||||||
writeImage(img2, fmt.Sprintf("testdata/%s_fail.png", callerFuncName))
|
|
||||||
t.Error("offscreen canvas failed")
|
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
286
images.go
286
images.go
|
@ -7,260 +7,108 @@ import (
|
||||||
"image"
|
"image"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
|
||||||
"strings"
|
"strings"
|
||||||
"unsafe"
|
|
||||||
|
"github.com/tfriedel6/canvas/backend/backendbase"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Image represents a loaded image that can be used in various drawing functions
|
|
||||||
type Image struct {
|
type Image struct {
|
||||||
w, h int
|
cv *Canvas
|
||||||
tex uint32
|
img backendbase.Image
|
||||||
deleted bool
|
|
||||||
opaque bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var images = make(map[interface{}]*Image)
|
|
||||||
|
|
||||||
// LoadImage loads an image. The src parameter can be either an image from the
|
// LoadImage loads an image. The src parameter can be either an image from the
|
||||||
// standard image package, a byte slice that will be loaded, or a file name
|
// standard image package, a byte slice that will be loaded, or a file name
|
||||||
// string. If you want the canvas package to load the image, make sure you
|
// string. If you want the canvas package to load the image, make sure you
|
||||||
// import the required format packages
|
// import the required format packages
|
||||||
func LoadImage(src interface{}) (*Image, error) {
|
func (cv *Canvas) LoadImage(src interface{}) (*Image, error) {
|
||||||
if gli == nil {
|
var srcImg image.Image
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return img, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadImage(src interface{}, tex uint32) (*Image, error) {
|
|
||||||
var img *Image
|
|
||||||
var err error
|
|
||||||
switch v := src.(type) {
|
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:
|
case image.Image:
|
||||||
img, err = loadImageConverted(v, tex)
|
srcImg = v
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
case string:
|
case string:
|
||||||
data, err := ioutil.ReadFile(v)
|
data, err := ioutil.ReadFile(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
srcImg, _, err := image.Decode(bytes.NewReader(data))
|
srcImg, _, err = image.Decode(bytes.NewReader(data))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return loadImage(srcImg, tex)
|
|
||||||
case []byte:
|
case []byte:
|
||||||
srcImg, _, err := image.Decode(bytes.NewReader(v))
|
var err error
|
||||||
|
srcImg, _, err = image.Decode(bytes.NewReader(v))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return loadImage(srcImg, tex)
|
case *Canvas:
|
||||||
|
src = cv.GetImageData(0, 0, cv.Width(), cv.Height())
|
||||||
default:
|
default:
|
||||||
return nil, errors.New("Unsupported source type")
|
return nil, errors.New("Unsupported source type")
|
||||||
}
|
}
|
||||||
return img, nil
|
backendImg, err := cv.b.LoadImage(srcImg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Image{cv: cv, img: backendImg}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getImage(src interface{}) *Image {
|
func (cv *Canvas) getImage(src interface{}) *Image {
|
||||||
if img, ok := images[src]; ok {
|
if img, ok := cv.images[src]; ok {
|
||||||
return img
|
return img
|
||||||
}
|
}
|
||||||
switch v := src.(type) {
|
switch v := src.(type) {
|
||||||
case *Image:
|
case *Image:
|
||||||
return v
|
return v
|
||||||
case image.Image:
|
case image.Image:
|
||||||
img, err := LoadImage(v)
|
img, err := cv.LoadImage(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Error loading image: %v\n", err)
|
fmt.Fprintf(os.Stderr, "Error loading image: %v\n", err)
|
||||||
images[src] = nil
|
cv.images[src] = nil
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
images[v] = img
|
cv.images[v] = img
|
||||||
return img
|
return img
|
||||||
case string:
|
case string:
|
||||||
img, err := LoadImage(v)
|
img, err := cv.LoadImage(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(strings.ToLower(err.Error()), "format") {
|
if strings.Contains(strings.ToLower(err.Error()), "format") {
|
||||||
fmt.Fprintf(os.Stderr, "Error loading image %s: %v\nIt may be necessary to import the appropriate decoder, e.g.\nimport _ \"image/jpeg\"\n", v, err)
|
fmt.Fprintf(os.Stderr, "Error loading image %s: %v\nIt may be necessary to import the appropriate decoder, e.g.\nimport _ \"image/jpeg\"\n", v, err)
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprintf(os.Stderr, "Error loading image %s: %v\n", v, err)
|
fmt.Fprintf(os.Stderr, "Error loading image %s: %v\n", v, err)
|
||||||
}
|
}
|
||||||
images[src] = nil
|
cv.images[src] = nil
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
images[v] = img
|
cv.images[v] = img
|
||||||
return img
|
return img
|
||||||
}
|
}
|
||||||
fmt.Fprintf(os.Stderr, "Unknown image type: %T\n", src)
|
fmt.Fprintf(os.Stderr, "Unknown image type: %T\n", src)
|
||||||
images[src] = nil
|
cv.images[src] = nil
|
||||||
return 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// Width returns the width of the image
|
// 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
|
// 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
|
// 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
|
// Delete deletes the image from memory. Any draw calls with a deleted image
|
||||||
// will not do anything
|
// will not do anything
|
||||||
func (img *Image) Delete() {
|
func (img *Image) Delete() { img.img.Delete() }
|
||||||
gli.DeleteTextures(1, &img.tex)
|
|
||||||
img.deleted = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace replaces the image with the new one
|
// Replace replaces the image with the new one
|
||||||
func (img *Image) Replace(src interface{}) {
|
func (img *Image) Replace(src interface{}) error {
|
||||||
gli.ActiveTexture(gl_TEXTURE0)
|
newImg, err := img.cv.LoadImage(src)
|
||||||
gli.BindTexture(gl_TEXTURE_2D, img.tex)
|
|
||||||
newImg, err := loadImage(src, img.tex)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Error replacing image: %v\n", err)
|
return err
|
||||||
return
|
|
||||||
}
|
}
|
||||||
*img = *newImg
|
img.img = newImg.img
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DrawImage draws the given image to the given coordinates. The image
|
// DrawImage draws the given image to the given coordinates. The image
|
||||||
|
@ -281,22 +129,22 @@ func (cv *Canvas) DrawImage(image interface{}, coords ...float64) {
|
||||||
img = &cv2.offscrImg
|
img = &cv2.offscrImg
|
||||||
flip = true
|
flip = true
|
||||||
} else {
|
} else {
|
||||||
img = getImage(image)
|
img = cv.getImage(image)
|
||||||
}
|
}
|
||||||
|
|
||||||
if img == nil {
|
if img == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if img.deleted {
|
if img.img.IsDeleted() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cv.activate()
|
cv.activate()
|
||||||
|
|
||||||
var sx, sy, sw, sh, dx, dy, dw, dh float64
|
var sx, sy, sw, sh, dx, dy, dw, dh float64
|
||||||
sw, sh = float64(img.w), float64(img.h)
|
sw, sh = float64(img.Width()), float64(img.Height())
|
||||||
dw, dh = float64(img.w), float64(img.h)
|
dw, dh = float64(img.Width()), float64(img.Height())
|
||||||
if len(coords) == 2 {
|
if len(coords) == 2 {
|
||||||
dx, dy = coords[0], coords[1]
|
dx, dy = coords[0], coords[1]
|
||||||
} else if len(coords) == 4 {
|
} else if len(coords) == 4 {
|
||||||
|
@ -314,55 +162,13 @@ func (cv *Canvas) DrawImage(image interface{}, coords ...float64) {
|
||||||
dh = -dh
|
dh = -dh
|
||||||
}
|
}
|
||||||
|
|
||||||
sx /= float64(img.w)
|
var data [4][2]float64
|
||||||
sy /= float64(img.h)
|
data[0] = cv.tf(vec{dx, dy})
|
||||||
sw /= float64(img.w)
|
data[1] = cv.tf(vec{dx, dy + dh})
|
||||||
sh /= float64(img.h)
|
data[2] = cv.tf(vec{dx + dw, dy + dh})
|
||||||
|
data[3] = cv.tf(vec{dx + dw, dy})
|
||||||
|
|
||||||
p0 := cv.tf(vec{dx, dy})
|
cv.drawShadow2(data[:])
|
||||||
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 {
|
cv.b.DrawImage(img.img, sx, sy, sw, sh, dx, dy, dw, dh, cv.state.globalAlpha)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
6
paths.go
6
paths.go
|
@ -158,7 +158,7 @@ func (cv *Canvas) strokePath(path *Path2D) {
|
||||||
|
|
||||||
cv.drawShadow2(tris)
|
cv.drawShadow2(tris)
|
||||||
|
|
||||||
stl := cv.backendStyle(&cv.state.stroke, 1)
|
stl := cv.backendFillStyle(&cv.state.stroke, 1)
|
||||||
cv.b.Fill(&stl, tris)
|
cv.b.Fill(&stl, tris)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -352,7 +352,7 @@ func (cv *Canvas) FillPath(path *Path2D) {
|
||||||
|
|
||||||
cv.drawShadow2(tris)
|
cv.drawShadow2(tris)
|
||||||
|
|
||||||
stl := cv.backendStyle(&cv.state.fill, 1)
|
stl := cv.backendFillStyle(&cv.state.fill, 1)
|
||||||
cv.b.Fill(&stl, tris)
|
cv.b.Fill(&stl, tris)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -528,7 +528,7 @@ func (cv *Canvas) FillRect(x, y, w, h float64) {
|
||||||
|
|
||||||
cv.drawShadow2(data[:])
|
cv.drawShadow2(data[:])
|
||||||
|
|
||||||
stl := cv.backendStyle(&cv.state.fill, 1)
|
stl := cv.backendFillStyle(&cv.state.fill, 1)
|
||||||
cv.b.Fill(&stl, data[:])
|
cv.b.Fill(&stl, data[:])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
|
|
||||||
"github.com/go-gl/gl/v3.2-core/gl"
|
"github.com/go-gl/gl/v3.2-core/gl"
|
||||||
"github.com/tfriedel6/canvas"
|
"github.com/tfriedel6/canvas"
|
||||||
|
"github.com/tfriedel6/canvas/backend/backendbase"
|
||||||
"github.com/tfriedel6/canvas/backend/gogl"
|
"github.com/tfriedel6/canvas/backend/gogl"
|
||||||
"github.com/tfriedel6/canvas/glimpl/gogl"
|
"github.com/tfriedel6/canvas/glimpl/gogl"
|
||||||
"github.com/veandco/go-sdl2/sdl"
|
"github.com/veandco/go-sdl2/sdl"
|
||||||
|
@ -23,7 +24,7 @@ type Window struct {
|
||||||
Window *sdl.Window
|
Window *sdl.Window
|
||||||
WindowID uint32
|
WindowID uint32
|
||||||
GLContext sdl.GLContext
|
GLContext sdl.GLContext
|
||||||
Backend canvas.Backend
|
Backend backendbase.Backend
|
||||||
canvas *canvas.Canvas
|
canvas *canvas.Canvas
|
||||||
frameTimes [10]time.Time
|
frameTimes [10]time.Time
|
||||||
frameIndex int
|
frameIndex int
|
||||||
|
|
|
@ -28,7 +28,7 @@ func (cv *Canvas) drawShadow2(pts [][2]float64) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
style := backendbase.Style{Color: cv.state.shadowColor, GlobalAlpha: 1, Blur: cv.state.shadowBlur}
|
style := backendbase.FillStyle{Color: cv.state.shadowColor, Blur: cv.state.shadowBlur}
|
||||||
cv.b.Fill(&style, cv.shadowBuf)
|
cv.b.Fill(&style, cv.shadowBuf)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue