go-opengl-pixel/canvas.go

265 lines
6.0 KiB
Go

package pixel
import (
"image/color"
"github.com/faiface/mainthread"
"github.com/faiface/pixel/pixelgl"
"github.com/go-gl/mathgl/mgl32"
"github.com/pkg/errors"
)
// Canvas is basically a Picture that you can draw on.
//
// Canvas supports TrianglesPosition, TrianglesColor and TrianglesTexture.
type Canvas struct {
f *pixelgl.Frame
s *pixelgl.Shader
copyVs *pixelgl.VertexSlice
smooth bool
pic *Picture
mat mgl32.Mat3
col mgl32.Vec4
bnd mgl32.Vec4
}
// NewCanvas creates a new fully transparent Canvas with specified dimensions in pixels.
func NewCanvas(width, height float64, smooth bool) *Canvas {
c := &Canvas{smooth: smooth}
mainthread.Call(func() {
var err error
c.f = pixelgl.NewFrame(int(width), int(height), smooth)
c.s, err = pixelgl.NewShader(
canvasVertexFormat,
canvasUniformFormat,
canvasVertexShader,
canvasFragmentShader,
)
if err != nil {
panic(errors.Wrap(err, "failed to create canvas"))
}
c.copyVs = pixelgl.MakeVertexSlice(c.s, 6, 6)
c.copyVs.Begin()
c.copyVs.SetVertexData([]float32{
-1, -1, 1, 1, 1, 1, 0, 0,
1, -1, 1, 1, 1, 1, 1, 0,
1, 1, 1, 1, 1, 1, 1, 1,
-1, -1, 1, 1, 1, 1, 0, 0,
1, 1, 1, 1, 1, 1, 1, 1,
-1, 1, 1, 1, 1, 1, 0, 1,
})
c.copyVs.End()
})
c.pic = nil
c.mat = mgl32.Ident3()
c.col = mgl32.Vec4{1, 1, 1, 1}
c.bnd = mgl32.Vec4{0, 0, 1, 1}
return c
}
// SetSize resizes the Canvas. The original content will be stretched to the new size.
func (c *Canvas) SetSize(width, height float64) {
if V(width, height) == V(c.Size()) {
return
}
mainthread.Call(func() {
oldF := c.f
c.f = pixelgl.NewFrame(int(width), int(height), c.smooth)
c.f.Begin()
c.s.Begin()
c.s.SetUniformAttr(canvasTransformMat3, mgl32.Ident3())
c.s.SetUniformAttr(canvasMaskColorVec4, mgl32.Vec4{1, 1, 1, 1})
c.s.SetUniformAttr(canvasBoundsVec4, mgl32.Vec4{0, 0, 1, 1})
oldF.Texture().Begin()
c.copyVs.Begin()
c.copyVs.Draw()
c.copyVs.End()
oldF.Texture().End()
c.s.End()
c.f.End()
})
}
// Size returns the width and the height of the Canvas in pixels.
func (c *Canvas) Size() (width, height float64) {
return float64(c.f.Width()), float64(c.f.Height())
}
// Content returns a Picture that contains the content of this Canvas. The returned Picture changes
// as you draw onto the Canvas, so there is no real need to call this method more than once.
func (c *Canvas) Content() *Picture {
tex := c.f.Texture()
return &Picture{
texture: tex,
bounds: R(0, 0, float64(tex.Width()), float64(tex.Height())),
}
}
// Clear fill the whole Canvas with on specified color.
func (c *Canvas) Clear(col color.Color) {
mainthread.CallNonBlock(func() {
c.f.Begin()
col := NRGBAModel.Convert(col).(NRGBA)
pixelgl.Clear(float32(col.R), float32(col.G), float32(col.B), float32(col.A))
c.f.End()
})
}
// MakeTriangles returns Triangles that draw onto this Canvas.
func (c *Canvas) MakeTriangles(t Triangles) Triangles {
tpcs := NewGLTriangles(c.s, t).(trianglesPositionColorTexture)
return &canvasTriangles{
c: c,
trianglesPositionColorTexture: tpcs,
}
}
// SetPicture sets a Picture that will be used in further draw operations.
//
// This does not set the Picture that this Canvas draws onto, don't confuse it.
func (c *Canvas) SetPicture(p *Picture) {
if p != nil {
min := pictureBounds(p, V(0, 0))
max := pictureBounds(p, V(1, 1))
c.bnd = mgl32.Vec4{
float32(min.X()), float32(min.Y()),
float32(max.X()), float32(max.Y()),
}
}
c.pic = p
}
// SetTransform sets the transformations used in further draw operations.
func (c *Canvas) SetTransform(t ...Transform) {
c.mat = transformToMat(t...)
}
// SetMaskColor sets the mask color used in further draw operations.
func (c *Canvas) SetMaskColor(col color.Color) {
if col == nil {
col = NRGBA{1, 1, 1, 1}
}
nrgba := NRGBAModel.Convert(col).(NRGBA)
r := float32(nrgba.R)
g := float32(nrgba.G)
b := float32(nrgba.B)
a := float32(nrgba.A)
c.col = mgl32.Vec4{r, g, b, a}
}
type trianglesPositionColorTexture interface {
Triangles
Position(i int) Vec
Color(i int) NRGBA
Texture(i int) Vec
}
type canvasTriangles struct {
c *Canvas
trianglesPositionColorTexture
}
func (ct *canvasTriangles) Draw() {
// avoid possible race condition
pic := ct.c.pic
mat := ct.c.mat
col := ct.c.col
bnd := ct.c.bnd
mainthread.CallNonBlock(func() {
ct.c.f.Begin()
ct.c.s.Begin()
ct.c.s.SetUniformAttr(canvasTransformMat3, mat)
ct.c.s.SetUniformAttr(canvasMaskColorVec4, col)
ct.c.s.SetUniformAttr(canvasBoundsVec4, bnd)
if pic != nil {
pic.Texture().Begin()
ct.trianglesPositionColorTexture.Draw()
pic.Texture().End()
} else {
ct.trianglesPositionColorTexture.Draw()
}
ct.c.s.End()
ct.c.f.End()
})
}
const (
canvasPositionVec2 int = iota
canvasColorVec4
canvasTextureVec2
)
var canvasVertexFormat = pixelgl.AttrFormat{
canvasPositionVec2: {Name: "position", Type: pixelgl.Vec2},
canvasColorVec4: {Name: "color", Type: pixelgl.Vec4},
canvasTextureVec2: {Name: "texture", Type: pixelgl.Vec2},
}
const (
canvasMaskColorVec4 int = iota
canvasTransformMat3
canvasBoundsVec4
)
var canvasUniformFormat = pixelgl.AttrFormat{
{Name: "maskColor", Type: pixelgl.Vec4},
{Name: "transform", Type: pixelgl.Mat3},
{Name: "bounds", Type: pixelgl.Vec4},
}
var canvasVertexShader = `
#version 330 core
in vec2 position;
in vec4 color;
in vec2 texture;
out vec4 Color;
out vec2 Texture;
uniform mat3 transform;
void main() {
gl_Position = vec4((transform * vec3(position.x, position.y, 1.0)).xy, 0.0, 1.0);
Color = color;
Texture = texture;
}
`
var canvasFragmentShader = `
#version 330 core
in vec4 Color;
in vec2 Texture;
out vec4 color;
uniform vec4 maskColor;
uniform vec4 bounds;
uniform sampler2D tex;
void main() {
vec2 boundsMin = bounds.xy;
vec2 boundsMax = bounds.zw;
if (Texture == vec2(-1, -1)) {
color = maskColor * Color;
} else {
float tx = boundsMin.x * (1 - Texture.x) + boundsMax.x * Texture.x;
float ty = boundsMin.y * (1 - Texture.y) + boundsMax.y * Texture.y;
color = maskColor * Color * texture(tex, vec2(tx, ty));
}
}
`