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 onto. // // Canvas supports TrianglesPosition, TrianglesColor and TrianglesTexture. type Canvas struct { f *pixelgl.Frame s *pixelgl.Shader copyVs *pixelgl.VertexSlice smooth bool drawTd TrianglesDrawer 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.drawTd = TrianglesDrawer{Triangles: &TrianglesData{ {Position: V(-1, -1), Color: NRGBA{1, 1, 1, 1}, Texture: V(0, 0)}, {Position: V(1, -1), Color: NRGBA{1, 1, 1, 1}, Texture: V(1, 0)}, {Position: V(1, 1), Color: NRGBA{1, 1, 1, 1}, Texture: V(1, 1)}, {Position: V(-1, -1), Color: NRGBA{1, 1, 1, 1}, Texture: V(0, 0)}, {Position: V(1, 1), Color: NRGBA{1, 1, 1, 1}, Texture: V(1, 1)}, {Position: V(-1, 1), Color: NRGBA{1, 1, 1, 1}, Texture: V(0, 1)}, }} 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 fit 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 (but it // might be beneficial to your code to do so). func (c *Canvas) Content() *Picture { tex := c.f.Texture() return &Picture{ texture: tex, bounds: R(0, 0, float64(tex.Width()), float64(tex.Height())), } } // Clear fills the whole Canvas with one 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() }) } // Draw draws the content of the Canvas onto another Target. If no transform is applied, the content // is fully stretched to fit the Target. func (c *Canvas) Draw(t Target) { t.SetPicture(c.Content()) c.drawTd.Draw(t) } // 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)); } } `