Newer
Older
"github.com/faiface/mainthread"
"github.com/faiface/pixel"
"github.com/go-gl/mathgl/mgl32"
"github.com/pkg/errors"
)
// Canvas is an off-screen rectangular BasicTarget that you can draw onto.
// It supports TrianglesPosition, TrianglesColor, TrianglesPicture and PictureColor.
mat mgl32.Mat3
col mgl32.Vec4
pixels []uint8
dirty bool
bounds pixel.Rect
orig *Canvas
// NewCanvas creates a new empty, fully transparent Canvas with given bounds. If the smooth flag
// set, then stretched Pictures will be smoothed and will not be drawn pixely onto this Canvas.
func NewCanvas(bounds pixel.Rect, smooth bool) *Canvas {
c := &Canvas{
smooth: smooth,
mat: mgl32.Ident3(),
col: mgl32.Vec4{1, 1, 1, 1},
}
mainthread.Call(func() {
var err error
canvasVertexFormat,
canvasUniformFormat,
canvasVertexShader,
canvasFragmentShader,
)
if err != nil {
panic(errors.Wrap(err, "failed to create Canvas, there's a bug in the shader"))
// MakeTriangles creates a specialized copy of the supplied Triangles that draws onto this Canvas.
//
// TrianglesPosition, TrianglesColor and TrianglesPicture are supported.
func (c *Canvas) MakeTriangles(t pixel.Triangles) pixel.TargetTriangles {
return &canvasTriangles{
GLTriangles: NewGLTriangles(c.s, t),
c: c,
// MakePicture create a specialized copy of the supplied Picture that draws onto this Canvas.
//
// PictureColor is supported.
func (c *Canvas) MakePicture(p pixel.Picture) pixel.TargetPicture {
if ccp, ok := p.(*canvasCanvasPicture); ok {
tp := new(canvasCanvasPicture)
*tp = *ccp
tp.dst = c
return tp
}
if canvas, ok := p.(*Canvas); ok {
return &canvasCanvasPicture{
src: canvas,
dst: c,
bounds: c.bounds,
}
}
pixels := make([]uint8, 4*bw*bh)
if p, ok := p.(pixel.PictureColor); ok {
for y := 0; y < bh; y++ {
for x := 0; x < bw; x++ {
at := pixel.V(
math.Max(float64(bx+x), bounds.Pos.X()),
math.Max(float64(by+y), bounds.Pos.Y()),
)
color := p.Color(at)
pixels[(y*bw+x)*4+0] = uint8(color.R * 255)
pixels[(y*bw+x)*4+1] = uint8(color.G * 255)
pixels[(y*bw+x)*4+2] = uint8(color.B * 255)
pixels[(y*bw+x)*4+3] = uint8(color.A * 255)
}
}
var tex *glhf.Texture
mainthread.Call(func() {
tex = glhf.NewTexture(bw, bh, c.smooth, pixels)
tex: tex,
borders: pixel.R(
float64(bx), float64(by),
float64(bw), float64(bh),
),
// SetMatrix sets a Matrix that every point will be projected by.
func (c *Canvas) SetMatrix(m pixel.Matrix) {
for i := range m {
c.mat[i] = float32(m[i])
}
// SetColorMask sets a color that every color in triangles or a picture will be multiplied by.
func (c *Canvas) SetColorMask(col color.Color) {
nrgba := pixel.NRGBA{R: 1, G: 1, B: 1, A: 1}
if col != nil {
nrgba = pixel.NRGBAModel.Convert(col).(pixel.NRGBA)
}
c.col = mgl32.Vec4{
float32(nrgba.R),
float32(nrgba.G),
float32(nrgba.B),
float32(nrgba.A),
}
// SetBounds resizes the Canvas to the new bounds. Old content will be preserved.
//
// If this Canvas was created using Slice-ing, then the relation between this Canvas and it's
// Original is unspecified (but Original will always return valid stuff).
func (c *Canvas) SetBounds(bounds pixel.Rect) {
if c.Bounds() == bounds {
return
}
mainthread.Call(func() {
oldF := c.f
c.f = glhf.NewFrame(w, h, c.smooth)
// preserve old content
if oldF != nil {
relBounds := c.bounds
relBounds.Pos -= bounds.Pos
oldF.Blit(
c.f,
ox, oy, ox+ow, oy+oh,
ox, oy, ox+ow, oy+oh,
)
}
})
c.orig = c // detach from the Original
c.dirty = true
// Bounds returns the rectangular bounds of the Canvas.
func (c *Canvas) Bounds() pixel.Rect {
return c.bounds
// SetSmooth sets whether the stretched Pictures drawn onto this Canvas should be drawn smooth or
// pixely.
func (c *Canvas) SetSmooth(smooth bool) {
c.smooth = smooth
// Smooth returns whether the stretched Pictures drawn onto this Canvas are set to be drawn smooth
// or pixely.
func (c *Canvas) Smooth() bool {
return c.smooth
// must be manually called inside mainthread
func (c *Canvas) setGlhfBounds() {
bounds := c.bounds
bounds.Pos -= c.orig.bounds.Pos
bx, by, bw, bh := intBounds(bounds)
glhf.Bounds(bx, by, bw, bh)
}
// Clear fill the whole Canvas with a single color.
func (c *Canvas) Clear(color color.Color) {
nrgba := pixel.NRGBAModel.Convert(color).(pixel.NRGBA)
// color masking
nrgba = nrgba.Mul(pixel.NRGBA{
R: float64(c.col[0]),
G: float64(c.col[1]),
B: float64(c.col[2]),
A: float64(c.col[3]),
})
c.f.Begin()
glhf.Clear(
float32(nrgba.R),
float32(nrgba.G),
float32(nrgba.B),
float32(nrgba.A),
)
c.f.End()
})
// Slice returns a sub-Canvas with the specified Bounds.
//
// The returned value is *Canvas, the type of the return value is a general pixel.Picture just so
// that Canvas implements pixel.Picture interface.
func (c *Canvas) Slice(bounds pixel.Rect) pixel.Picture {
sc := new(Canvas)
*sc = *c
sc.bounds = bounds
return sc
}
// Original returns the most original Canvas that this Canvas was created from using Slice-ing.
//
// The returned value is *Canvas, the type of the return value is a general pixel.Picture just so
// that Canvas implements pixel.Picture interface.
func (c *Canvas) Original() pixel.Picture {
return c.orig
}
// Color returns the color of the pixel over the given position inside the Canvas.
func (c *Canvas) Color(at pixel.Vec) pixel.NRGBA {
if c.orig.dirty {
mainthread.Call(func() {
tex := c.f.Texture()
tex.Begin()
c.orig.pixels = tex.Pixels(0, 0, tex.Width(), tex.Height())
tex.End()
})
c.orig.dirty = false
}
if !c.bounds.Contains(at) {
return pixel.NRGBA{}
}
bx, by, bw, _ := intBounds(c.orig.bounds)
x, y := int(at.X())-bx, int(at.Y())-by
off := y*bw + x
return pixel.NRGBA{
R: float64(c.orig.pixels[off*4+0]) / 255,
G: float64(c.orig.pixels[off*4+1]) / 255,
B: float64(c.orig.pixels[off*4+2]) / 255,
A: float64(c.orig.pixels[off*4+3]) / 255,
}
}
type canvasTriangles struct {
func (ct *canvasTriangles) draw(tex *glhf.Texture, borders, bounds pixel.Rect) {
// save the current state vars to avoid race condition
mat := ct.c.mat
col := ct.c.col
mainthread.CallNonBlock(func() {
ct.c.f.Begin()
ct.c.s.Begin()
ct.c.s.SetUniformAttr(canvasBounds, mgl32.Vec4{
float32(ct.c.bounds.X()),
float32(ct.c.bounds.Y()),
float32(ct.c.bounds.W()),
float32(ct.c.bounds.H()),
ct.c.s.SetUniformAttr(canvasTransform, mat)
ct.c.s.SetUniformAttr(canvasColorMask, col)
ct.vs.Begin()
ct.vs.Draw()
ct.vs.End()
ct.c.s.SetUniformAttr(canvasTexBorders, mgl32.Vec4{
float32(borders.X()),
float32(borders.Y()),
float32(borders.W()),
float32(borders.H()),
})
ct.c.s.SetUniformAttr(canvasTexBounds, mgl32.Vec4{
float32(bounds.X()),
float32(bounds.Y()),
float32(bounds.W()),
float32(bounds.H()),
if tex.Smooth() != ct.c.smooth {
tex.SetSmooth(ct.c.smooth)
}
ct.vs.Begin()
ct.vs.Draw()
ct.vs.End()
}
ct.c.s.End()
ct.c.f.End()
})
}
ct.draw(nil, pixel.Rect{}, pixel.Rect{})
tex *glhf.Texture
borders pixel.Rect
bounds pixel.Rect
}
func (cp *canvasPicture) Bounds() pixel.Rect {
return cp.bounds
}
func (cp *canvasPicture) Slice(r pixel.Rect) pixel.Picture {
sp := new(canvasPicture)
*sp = *cp
sp.bounds = r
return sp
func (cp *canvasPicture) Draw(t pixel.TargetTriangles) {
ct := t.(*canvasTriangles)
panic(fmt.Errorf("%T.Draw: TargetTriangles generated by different Canvas", cp))
}
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
ct.draw(cp.tex, cp.borders, cp.bounds)
}
type canvasCanvasPicture struct {
src, dst *Canvas
bounds pixel.Rect
orig *canvasCanvasPicture
}
func (ccp *canvasCanvasPicture) Bounds() pixel.Rect {
return ccp.bounds
}
func (ccp *canvasCanvasPicture) Slice(r pixel.Rect) pixel.Picture {
sp := new(canvasCanvasPicture)
*sp = *ccp
sp.bounds = r
return sp
}
func (ccp *canvasCanvasPicture) Original() pixel.Picture {
return ccp.orig
}
func (ccp *canvasCanvasPicture) Draw(t pixel.TargetTriangles) {
ct := t.(*canvasTriangles)
if ccp.dst != ct.c {
panic(fmt.Errorf("%T.Draw: TargetTriangles generated by different Canvas", ccp))
}
ct.draw(ccp.src.f.Texture(), ccp.src.orig.bounds, ccp.bounds)
canvasPosition int = iota
canvasColor
canvasTexture
canvasIntensity
canvasPosition: {Name: "position", Type: glhf.Vec2},
canvasColor: {Name: "color", Type: glhf.Vec4},
canvasTexture: {Name: "texture", Type: glhf.Vec2},
canvasIntensity: {Name: "intensity", Type: glhf.Float},
canvasTransform int = iota
canvasColorMask
canvasTransform: {Name: "transform", Type: glhf.Mat3},
canvasColorMask: {Name: "colorMask", Type: glhf.Vec4},
canvasBounds: {Name: "bounds", Type: glhf.Vec4},
canvasTexBorders: {Name: "texBorders", Type: glhf.Vec4},
canvasTexBounds: {Name: "texBounds", Type: glhf.Vec4},
}
var canvasVertexShader = `
#version 330 core
in vec2 position;
in vec4 color;
in vec2 texture;
out vec4 Color;
out vec2 Texture;
uniform mat3 transform;
vec2 transPos = (transform * vec3(position, 1.0)).xy;
vec2 normPos = (transPos - bounds.xy) / (bounds.zw) * 2 - vec2(1, 1);
Color = color;
Texture = texture;
}
`
var canvasFragmentShader = `
#version 330 core
in vec4 Color;
in vec2 Texture;
uniform sampler2D tex;
void main() {
if (Intensity == 0) {
color = colorMask * Color;
color = vec4(0, 0, 0, 0);
color += (1 - Intensity) * colorMask * Color;
float bx = texBounds.x;
float by = texBounds.y;
float bw = texBounds.z;
float bh = texBounds.w;
if (bx <= Texture.x && Texture.x <= bx + bw && by <= Texture.y && Texture.y <= by + bh) {
vec2 t = (Texture - texBorders.xy) / texBorders.zw;
color += Intensity * colorMask * Color * texture(tex, t);
}