Compare commits
1 commit
Author | SHA1 | Date | |
---|---|---|---|
fb1b1c09ec |
25 changed files with 221 additions and 2129 deletions
|
@ -5,7 +5,7 @@ import (
|
|||
"image/color"
|
||||
"math"
|
||||
|
||||
"github.com/tfriedel6/canvas/backend/backendbase"
|
||||
"git.mstar.dev/mstar/canvas/backend/backendbase"
|
||||
)
|
||||
|
||||
func (b *SoftwareBackend) Clear(pts [4]backendbase.Vec) {
|
||||
|
@ -19,7 +19,12 @@ func (b *SoftwareBackend) Clear(pts [4]backendbase.Vec) {
|
|||
})
|
||||
}
|
||||
|
||||
func (b *SoftwareBackend) Fill(style *backendbase.FillStyle, pts []backendbase.Vec, tf backendbase.Mat, canOverlap bool) {
|
||||
func (b *SoftwareBackend) Fill(
|
||||
style *backendbase.FillStyle,
|
||||
pts []backendbase.Vec,
|
||||
tf backendbase.Mat,
|
||||
canOverlap bool,
|
||||
) {
|
||||
ffn := fillFunc(style)
|
||||
|
||||
var triBuf [500]backendbase.Vec
|
||||
|
@ -44,7 +49,11 @@ func (b *SoftwareBackend) Fill(style *backendbase.FillStyle, pts []backendbase.V
|
|||
}
|
||||
}
|
||||
|
||||
func (b *SoftwareBackend) FillImageMask(style *backendbase.FillStyle, mask *image.Alpha, pts [4]backendbase.Vec) {
|
||||
func (b *SoftwareBackend) FillImageMask(
|
||||
style *backendbase.FillStyle,
|
||||
mask *image.Alpha,
|
||||
pts [4]backendbase.Vec,
|
||||
) {
|
||||
ffn := fillFunc(style)
|
||||
|
||||
mw := float64(mask.Bounds().Dx())
|
||||
|
@ -65,7 +74,10 @@ func fillFunc(style *backendbase.FillStyle) func(x, y float64) color.RGBA {
|
|||
if lg := style.LinearGradient; lg != nil {
|
||||
lg := lg.(*LinearGradient)
|
||||
from := backendbase.Vec{style.Gradient.X0, style.Gradient.Y0}
|
||||
dir := backendbase.Vec{style.Gradient.X1 - style.Gradient.X0, style.Gradient.Y1 - style.Gradient.Y0}
|
||||
dir := backendbase.Vec{
|
||||
style.Gradient.X1 - style.Gradient.X0,
|
||||
style.Gradient.Y1 - style.Gradient.Y0,
|
||||
}
|
||||
dirlen := math.Sqrt(dir[0]*dir[0] + dir[1]*dir[1])
|
||||
dir[0] /= dirlen
|
||||
dir[1] /= dirlen
|
||||
|
|
|
@ -5,7 +5,7 @@ import (
|
|||
"image/color"
|
||||
"math"
|
||||
|
||||
"github.com/tfriedel6/canvas/backend/backendbase"
|
||||
"git.mstar.dev/mstar/canvas/backend/backendbase"
|
||||
)
|
||||
|
||||
type Image struct {
|
||||
|
@ -43,7 +43,12 @@ func halveImage(img image.Image) (*image.RGBA, int, int) {
|
|||
return rimg, w, h
|
||||
}
|
||||
|
||||
func (b *SoftwareBackend) DrawImage(dimg backendbase.Image, sx, sy, sw, sh float64, pts [4]backendbase.Vec, alpha float64) {
|
||||
func (b *SoftwareBackend) DrawImage(
|
||||
dimg backendbase.Image,
|
||||
sx, sy, sw, sh float64,
|
||||
pts [4]backendbase.Vec,
|
||||
alpha float64,
|
||||
) {
|
||||
simg := dimg.(*Image)
|
||||
if simg.deleted {
|
||||
return
|
||||
|
@ -130,7 +135,9 @@ type ImagePattern struct {
|
|||
data backendbase.ImagePatternData
|
||||
}
|
||||
|
||||
func (b *SoftwareBackend) LoadImagePattern(data backendbase.ImagePatternData) backendbase.ImagePattern {
|
||||
func (b *SoftwareBackend) LoadImagePattern(
|
||||
data backendbase.ImagePatternData,
|
||||
) backendbase.ImagePattern {
|
||||
return &ImagePattern{
|
||||
data: data,
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import (
|
|||
"image"
|
||||
"image/draw"
|
||||
|
||||
"github.com/tfriedel6/canvas/backend/backendbase"
|
||||
"git.mstar.dev/mstar/canvas/backend/backendbase"
|
||||
)
|
||||
|
||||
type SoftwareBackend struct {
|
||||
|
|
|
@ -4,7 +4,7 @@ import (
|
|||
"image/color"
|
||||
"math"
|
||||
|
||||
"github.com/tfriedel6/canvas/backend/backendbase"
|
||||
"git.mstar.dev/mstar/canvas/backend/backendbase"
|
||||
)
|
||||
|
||||
func triangleLR(tri []backendbase.Vec, y float64) (l, r float64, outside bool) {
|
||||
|
@ -96,7 +96,12 @@ type msaaPixel struct {
|
|||
tx, ty float64
|
||||
}
|
||||
|
||||
func (b *SoftwareBackend) fillTriangleMSAA(tri []backendbase.Vec, msaaLevel int, msaaPixels []msaaPixel, fn func(x, y int)) []msaaPixel {
|
||||
func (b *SoftwareBackend) fillTriangleMSAA(
|
||||
tri []backendbase.Vec,
|
||||
msaaLevel int,
|
||||
msaaPixels []msaaPixel,
|
||||
fn func(x, y int),
|
||||
) []msaaPixel {
|
||||
msaaStep := 1.0 / float64(msaaLevel+1)
|
||||
|
||||
minY := int(math.Floor(math.Min(math.Min(tri[0][1], tri[1][1]), tri[2][1])))
|
||||
|
@ -175,7 +180,10 @@ func (b *SoftwareBackend) fillTriangleMSAA(tri []backendbase.Vec, msaaLevel int,
|
|||
sx := float64(x) + msaaStep*0.5
|
||||
for stepx := 0; stepx <= msaaLevel; stepx++ {
|
||||
if sx >= l[stepy] && sx < r[stepy] {
|
||||
msaaPixels = addMSAAPixel(msaaPixels, msaaPixel{ix: x, iy: y, fx: sx, fy: sy})
|
||||
msaaPixels = addMSAAPixel(
|
||||
msaaPixels,
|
||||
msaaPixel{ix: x, iy: y, fx: sx, fy: sy},
|
||||
)
|
||||
}
|
||||
sx += msaaStep
|
||||
}
|
||||
|
@ -203,8 +211,12 @@ func quadArea(quad [4]backendbase.Vec) float64 {
|
|||
}
|
||||
|
||||
func (b *SoftwareBackend) fillQuadNoAA(quad [4]backendbase.Vec, fn func(x, y int, tx, ty float64)) {
|
||||
minY := int(math.Floor(math.Min(math.Min(quad[0][1], quad[1][1]), math.Min(quad[2][1], quad[3][1]))))
|
||||
maxY := int(math.Ceil(math.Max(math.Max(quad[0][1], quad[1][1]), math.Max(quad[2][1], quad[3][1]))))
|
||||
minY := int(
|
||||
math.Floor(math.Min(math.Min(quad[0][1], quad[1][1]), math.Min(quad[2][1], quad[3][1]))),
|
||||
)
|
||||
maxY := int(
|
||||
math.Ceil(math.Max(math.Max(quad[0][1], quad[1][1]), math.Max(quad[2][1], quad[3][1]))),
|
||||
)
|
||||
if minY < 0 {
|
||||
minY = 0
|
||||
} else if minY >= b.h {
|
||||
|
@ -272,11 +284,20 @@ func (b *SoftwareBackend) fillQuadNoAA(quad [4]backendbase.Vec, fn func(x, y int
|
|||
}
|
||||
}
|
||||
|
||||
func (b *SoftwareBackend) fillQuadMSAA(quad [4]backendbase.Vec, msaaLevel int, msaaPixels []msaaPixel, fn func(x, y int, tx, ty float64)) []msaaPixel {
|
||||
func (b *SoftwareBackend) fillQuadMSAA(
|
||||
quad [4]backendbase.Vec,
|
||||
msaaLevel int,
|
||||
msaaPixels []msaaPixel,
|
||||
fn func(x, y int, tx, ty float64),
|
||||
) []msaaPixel {
|
||||
msaaStep := 1.0 / float64(msaaLevel+1)
|
||||
|
||||
minY := int(math.Floor(math.Min(math.Min(quad[0][1], quad[1][1]), math.Min(quad[2][1], quad[3][1]))))
|
||||
maxY := int(math.Ceil(math.Max(math.Max(quad[0][1], quad[1][1]), math.Max(quad[2][1], quad[3][1]))))
|
||||
minY := int(
|
||||
math.Floor(math.Min(math.Min(quad[0][1], quad[1][1]), math.Min(quad[2][1], quad[3][1]))),
|
||||
)
|
||||
maxY := int(
|
||||
math.Ceil(math.Max(math.Max(quad[0][1], quad[1][1]), math.Max(quad[2][1], quad[3][1]))),
|
||||
)
|
||||
if minY < 0 {
|
||||
minY = 0
|
||||
} else if minY >= b.h {
|
||||
|
@ -390,7 +411,17 @@ func (b *SoftwareBackend) fillQuadMSAA(quad [4]backendbase.Vec, msaaLevel int, m
|
|||
ty = (tfy - topv[1]*tx) / leftv[1]
|
||||
}
|
||||
|
||||
msaaPixels = addMSAAPixel(msaaPixels, msaaPixel{ix: x, iy: y, fx: sx, fy: sy, tx: tx / topLen, ty: ty / leftLen})
|
||||
msaaPixels = addMSAAPixel(
|
||||
msaaPixels,
|
||||
msaaPixel{
|
||||
ix: x,
|
||||
iy: y,
|
||||
fx: sx,
|
||||
fy: sy,
|
||||
tx: tx / topLen,
|
||||
ty: ty / leftLen,
|
||||
},
|
||||
)
|
||||
}
|
||||
sx += msaaStep
|
||||
}
|
||||
|
@ -402,7 +433,10 @@ func (b *SoftwareBackend) fillQuadMSAA(quad [4]backendbase.Vec, msaaLevel int, m
|
|||
return msaaPixels
|
||||
}
|
||||
|
||||
func (b *SoftwareBackend) fillQuad(pts [4]backendbase.Vec, fn func(x, y, tx, ty float64) color.RGBA) {
|
||||
func (b *SoftwareBackend) fillQuad(
|
||||
pts [4]backendbase.Vec,
|
||||
fn func(x, y, tx, ty float64) color.RGBA,
|
||||
) {
|
||||
b.clearStencil()
|
||||
|
||||
if b.MSAA > 0 {
|
||||
|
@ -426,7 +460,8 @@ func (b *SoftwareBackend) fillQuad(pts [4]backendbase.Vec, fn func(x, y, tx, ty
|
|||
samples := (b.MSAA + 1) * (b.MSAA + 1)
|
||||
|
||||
for i, px := range msaaPixels {
|
||||
if px.ix < 0 || b.clip.AlphaAt(px.ix, px.iy).A == 0 || b.stencil.AlphaAt(px.ix, px.iy).A > 0 {
|
||||
if px.ix < 0 || b.clip.AlphaAt(px.ix, px.iy).A == 0 ||
|
||||
b.stencil.AlphaAt(px.ix, px.iy).A > 0 {
|
||||
continue
|
||||
}
|
||||
b.stencil.SetAlpha(px.ix, px.iy, color.Alpha{A: 255})
|
||||
|
@ -489,7 +524,10 @@ func iterateTriangles(pts []backendbase.Vec, fn func(tri []backendbase.Vec)) {
|
|||
}
|
||||
}
|
||||
|
||||
func (b *SoftwareBackend) fillTrianglesNoAA(pts []backendbase.Vec, fn func(x, y float64) color.RGBA) {
|
||||
func (b *SoftwareBackend) fillTrianglesNoAA(
|
||||
pts []backendbase.Vec,
|
||||
fn func(x, y float64) color.RGBA,
|
||||
) {
|
||||
iterateTriangles(pts[:], func(tri []backendbase.Vec) {
|
||||
b.fillTriangleNoAA(tri, func(x, y int) {
|
||||
if b.clip.AlphaAt(x, y).A == 0 {
|
||||
|
@ -507,7 +545,11 @@ func (b *SoftwareBackend) fillTrianglesNoAA(pts []backendbase.Vec, fn func(x, y
|
|||
})
|
||||
}
|
||||
|
||||
func (b *SoftwareBackend) fillTrianglesMSAA(pts []backendbase.Vec, msaaLevel int, fn func(x, y float64) color.RGBA) {
|
||||
func (b *SoftwareBackend) fillTrianglesMSAA(
|
||||
pts []backendbase.Vec,
|
||||
msaaLevel int,
|
||||
fn func(x, y float64) color.RGBA,
|
||||
) {
|
||||
var msaaPixelBuf [500]msaaPixel
|
||||
msaaPixels := msaaPixelBuf[:0]
|
||||
|
||||
|
@ -530,7 +572,8 @@ func (b *SoftwareBackend) fillTrianglesMSAA(pts []backendbase.Vec, msaaLevel int
|
|||
samples := (msaaLevel + 1) * (msaaLevel + 1)
|
||||
|
||||
for i, px := range msaaPixels {
|
||||
if px.ix < 0 || b.clip.AlphaAt(px.ix, px.iy).A == 0 || b.stencil.AlphaAt(px.ix, px.iy).A > 0 {
|
||||
if px.ix < 0 || b.clip.AlphaAt(px.ix, px.iy).A == 0 ||
|
||||
b.stencil.AlphaAt(px.ix, px.iy).A > 0 {
|
||||
continue
|
||||
}
|
||||
b.stencil.SetAlpha(px.ix, px.iy, color.Alpha{A: 255})
|
||||
|
|
|
@ -1,77 +0,0 @@
|
|||
package xmobilebackend
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/tfriedel6/canvas/backend/backendbase"
|
||||
"golang.org/x/mobile/gl"
|
||||
)
|
||||
|
||||
func (b *XMobileBackend) ClearClip() {
|
||||
b.activate()
|
||||
|
||||
b.glctx.StencilMask(0xFF)
|
||||
b.glctx.Clear(gl.STENCIL_BUFFER_BIT)
|
||||
}
|
||||
|
||||
func (b *XMobileBackend) Clip(pts []backendbase.Vec) {
|
||||
b.activate()
|
||||
|
||||
b.ptsBuf = b.ptsBuf[:0]
|
||||
b.ptsBuf = append(b.ptsBuf,
|
||||
0, 0,
|
||||
0, float32(b.fh),
|
||||
float32(b.fw), float32(b.fh),
|
||||
float32(b.fw), 0)
|
||||
for _, pt := range pts {
|
||||
b.ptsBuf = append(b.ptsBuf, float32(pt[0]), float32(pt[1]))
|
||||
}
|
||||
|
||||
mode := gl.Enum(gl.TRIANGLES)
|
||||
if len(pts) == 4 {
|
||||
mode = gl.TRIANGLE_FAN
|
||||
}
|
||||
|
||||
b.glctx.BindBuffer(gl.ARRAY_BUFFER, b.buf)
|
||||
b.glctx.BufferData(gl.ARRAY_BUFFER, byteSlice(unsafe.Pointer(&b.ptsBuf[0]), len(b.ptsBuf)*4), gl.STREAM_DRAW)
|
||||
b.glctx.VertexAttribPointer(b.shd.Vertex, 2, gl.FLOAT, false, 0, 0)
|
||||
b.glctx.VertexAttribPointer(b.shd.TexCoord, 2, gl.FLOAT, false, 0, 0)
|
||||
|
||||
b.glctx.UseProgram(b.shd.ID)
|
||||
b.glctx.Uniform4f(b.shd.Color, 1, 1, 1, 1)
|
||||
b.glctx.Uniform2f(b.shd.CanvasSize, float32(b.fw), float32(b.fh))
|
||||
b.glctx.UniformMatrix3fv(b.shd.Matrix, mat3identity[:])
|
||||
b.glctx.Uniform1f(b.shd.GlobalAlpha, 1)
|
||||
b.glctx.Uniform1i(b.shd.UseAlphaTex, 0)
|
||||
b.glctx.Uniform1i(b.shd.Func, shdFuncSolid)
|
||||
b.glctx.EnableVertexAttribArray(b.shd.Vertex)
|
||||
b.glctx.EnableVertexAttribArray(b.shd.TexCoord)
|
||||
|
||||
b.glctx.ColorMask(false, false, false, false)
|
||||
|
||||
// set bit 2 in the stencil buffer in the given shape
|
||||
b.glctx.StencilMask(0x04)
|
||||
b.glctx.StencilFunc(gl.ALWAYS, 4, 0)
|
||||
b.glctx.StencilOp(gl.REPLACE, gl.REPLACE, gl.REPLACE)
|
||||
b.glctx.DrawArrays(mode, 4, len(pts))
|
||||
|
||||
// on entire screen, where neither bit 1 or 2 are set, invert bit 1
|
||||
b.glctx.StencilMask(0x02)
|
||||
b.glctx.StencilFunc(gl.EQUAL, 0, 0x06)
|
||||
b.glctx.StencilOp(gl.KEEP, gl.INVERT, gl.INVERT)
|
||||
b.glctx.DrawArrays(gl.TRIANGLE_FAN, 0, 4)
|
||||
|
||||
// on entire screen, clear bit 2
|
||||
b.glctx.StencilMask(0x04)
|
||||
b.glctx.StencilFunc(gl.ALWAYS, 0, 0)
|
||||
b.glctx.StencilOp(gl.ZERO, gl.ZERO, gl.ZERO)
|
||||
b.glctx.DrawArrays(gl.TRIANGLE_FAN, 0, 4)
|
||||
|
||||
b.glctx.DisableVertexAttribArray(b.shd.Vertex)
|
||||
b.glctx.DisableVertexAttribArray(b.shd.TexCoord)
|
||||
|
||||
b.glctx.ColorMask(true, true, true, true)
|
||||
b.glctx.StencilOp(gl.KEEP, gl.KEEP, gl.KEEP)
|
||||
b.glctx.StencilMask(0xFF)
|
||||
b.glctx.StencilFunc(gl.EQUAL, 0, 0xFF)
|
||||
}
|
|
@ -1,339 +0,0 @@
|
|||
package xmobilebackend
|
||||
|
||||
import (
|
||||
"image"
|
||||
"math"
|
||||
"unsafe"
|
||||
|
||||
"github.com/tfriedel6/canvas/backend/backendbase"
|
||||
"golang.org/x/mobile/gl"
|
||||
)
|
||||
|
||||
func (b *XMobileBackend) Clear(pts [4]backendbase.Vec) {
|
||||
b.activate()
|
||||
|
||||
// first check if the four points are aligned to form a nice rectangle, which can be more easily
|
||||
// cleared using glScissor and glClear
|
||||
aligned := pts[0][0] == pts[1][0] && pts[2][0] == pts[3][0] && pts[0][1] == pts[3][1] && pts[1][1] == pts[2][1]
|
||||
if !aligned {
|
||||
aligned = pts[0][0] == pts[3][0] && pts[1][0] == pts[2][0] && pts[0][1] == pts[1][1] && pts[2][1] == pts[3][1]
|
||||
}
|
||||
if aligned {
|
||||
minX := math.Floor(math.Min(pts[0][0], pts[2][0]))
|
||||
maxX := math.Ceil(math.Max(pts[0][0], pts[2][0]))
|
||||
minY := math.Floor(math.Min(pts[0][1], pts[2][1]))
|
||||
maxY := math.Ceil(math.Max(pts[0][1], pts[2][1]))
|
||||
b.clearRect(int(minX), int(minY), int(maxX)-int(minX), int(maxY)-int(minY))
|
||||
return
|
||||
}
|
||||
|
||||
data := [8]float32{
|
||||
float32(pts[0][0]), float32(pts[0][1]),
|
||||
float32(pts[1][0]), float32(pts[1][1]),
|
||||
float32(pts[2][0]), float32(pts[2][1]),
|
||||
float32(pts[3][0]), float32(pts[3][1])}
|
||||
|
||||
b.glctx.UseProgram(b.shd.ID)
|
||||
b.glctx.Uniform2f(b.shd.CanvasSize, float32(b.fw), float32(b.fh))
|
||||
b.glctx.UniformMatrix3fv(b.shd.Matrix, mat3identity[:])
|
||||
b.glctx.Uniform4f(b.shd.Color, 0, 0, 0, 0)
|
||||
b.glctx.Uniform1f(b.shd.GlobalAlpha, 1)
|
||||
b.glctx.Uniform1i(b.shd.UseAlphaTex, 0)
|
||||
b.glctx.Uniform1i(b.shd.Func, shdFuncSolid)
|
||||
|
||||
b.glctx.Disable(gl.BLEND)
|
||||
|
||||
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.StencilFunc(gl.EQUAL, 0, 0xFF)
|
||||
|
||||
b.glctx.VertexAttribPointer(b.shd.Vertex, 2, gl.FLOAT, false, 0, 0)
|
||||
b.glctx.VertexAttribPointer(b.shd.TexCoord, 2, gl.FLOAT, false, 0, 0)
|
||||
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)
|
||||
|
||||
b.glctx.Enable(gl.BLEND)
|
||||
}
|
||||
|
||||
func (b *XMobileBackend) clearRect(x, y, w, h int) {
|
||||
b.glctx.Enable(gl.SCISSOR_TEST)
|
||||
|
||||
var box [4]int32
|
||||
b.glctx.GetIntegerv(box[:], gl.SCISSOR_BOX)
|
||||
|
||||
b.glctx.Scissor(int32(x), int32(b.h-y-h), int32(w), int32(h))
|
||||
b.glctx.ClearColor(0, 0, 0, 0)
|
||||
b.glctx.Clear(gl.COLOR_BUFFER_BIT)
|
||||
b.glctx.Scissor(box[0], box[1], box[2], box[3])
|
||||
|
||||
b.glctx.Disable(gl.SCISSOR_TEST)
|
||||
}
|
||||
|
||||
func extent(pts []backendbase.Vec) (min, max backendbase.Vec) {
|
||||
max[0] = -math.MaxFloat64
|
||||
max[1] = -math.MaxFloat64
|
||||
min[0] = math.MaxFloat64
|
||||
min[1] = math.MaxFloat64
|
||||
for _, v := range pts {
|
||||
min[0] = math.Min(min[0], v[0])
|
||||
min[1] = math.Min(min[1], v[1])
|
||||
max[0] = math.Max(max[0], v[0])
|
||||
max[1] = math.Max(max[1], v[1])
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (b *XMobileBackend) Fill(style *backendbase.FillStyle, pts []backendbase.Vec, tf backendbase.Mat, canOverlap bool) {
|
||||
b.activate()
|
||||
|
||||
if style.Blur > 0 {
|
||||
b.offscr1.alpha = true
|
||||
b.enableTextureRenderTarget(&b.offscr1)
|
||||
b.glctx.ClearColor(0, 0, 0, 0)
|
||||
b.glctx.Clear(gl.COLOR_BUFFER_BIT | gl.STENCIL_BUFFER_BIT)
|
||||
}
|
||||
|
||||
b.ptsBuf = b.ptsBuf[:0]
|
||||
min, max := extent(pts)
|
||||
b.ptsBuf = append(b.ptsBuf,
|
||||
float32(min[0]), float32(min[1]),
|
||||
float32(min[0]), float32(max[1]),
|
||||
float32(max[0]), float32(max[1]),
|
||||
float32(max[0]), float32(min[1]))
|
||||
for _, pt := range pts {
|
||||
b.ptsBuf = append(b.ptsBuf, float32(pt[0]), float32(pt[1]))
|
||||
}
|
||||
|
||||
mode := gl.Enum(gl.TRIANGLES)
|
||||
if len(pts) == 4 {
|
||||
mode = gl.TRIANGLE_FAN
|
||||
}
|
||||
|
||||
b.glctx.BindBuffer(gl.ARRAY_BUFFER, b.buf)
|
||||
b.glctx.BufferData(gl.ARRAY_BUFFER, byteSlice(unsafe.Pointer(&b.ptsBuf[0]), len(b.ptsBuf)*4), gl.STREAM_DRAW)
|
||||
|
||||
if !canOverlap || style.Color.A >= 255 {
|
||||
vertex, _ := b.useShader(style, mat3(tf), false, 0)
|
||||
|
||||
b.glctx.StencilFunc(gl.EQUAL, 0, 0xFF)
|
||||
b.glctx.EnableVertexAttribArray(vertex)
|
||||
b.glctx.EnableVertexAttribArray(b.shd.TexCoord)
|
||||
b.glctx.VertexAttribPointer(vertex, 2, gl.FLOAT, false, 0, 0)
|
||||
b.glctx.VertexAttribPointer(b.shd.TexCoord, 2, gl.FLOAT, false, 0, 0)
|
||||
b.glctx.DrawArrays(mode, 4, len(pts))
|
||||
b.glctx.DisableVertexAttribArray(vertex)
|
||||
b.glctx.DisableVertexAttribArray(b.shd.TexCoord)
|
||||
b.glctx.StencilFunc(gl.ALWAYS, 0, 0xFF)
|
||||
} else {
|
||||
b.glctx.ColorMask(false, false, false, false)
|
||||
b.glctx.StencilFunc(gl.ALWAYS, 1, 0xFF)
|
||||
b.glctx.StencilOp(gl.REPLACE, gl.REPLACE, gl.REPLACE)
|
||||
b.glctx.StencilMask(0x01)
|
||||
|
||||
b.glctx.UseProgram(b.shd.ID)
|
||||
b.glctx.Uniform4f(b.shd.Color, 0, 0, 0, 0)
|
||||
b.glctx.Uniform2f(b.shd.CanvasSize, float32(b.fw), float32(b.fh))
|
||||
m3 := mat3(tf)
|
||||
b.glctx.UniformMatrix3fv(b.shd.Matrix, m3[:])
|
||||
b.glctx.Uniform1f(b.shd.GlobalAlpha, 1)
|
||||
b.glctx.Uniform1i(b.shd.UseAlphaTex, 0)
|
||||
b.glctx.Uniform1i(b.shd.Func, shdFuncSolid)
|
||||
|
||||
b.glctx.EnableVertexAttribArray(b.shd.Vertex)
|
||||
b.glctx.EnableVertexAttribArray(b.shd.TexCoord)
|
||||
b.glctx.VertexAttribPointer(b.shd.Vertex, 2, gl.FLOAT, false, 0, 0)
|
||||
b.glctx.VertexAttribPointer(b.shd.TexCoord, 2, gl.FLOAT, false, 0, 0)
|
||||
b.glctx.DrawArrays(mode, 4, len(pts))
|
||||
b.glctx.DisableVertexAttribArray(b.shd.Vertex)
|
||||
b.glctx.DisableVertexAttribArray(b.shd.TexCoord)
|
||||
|
||||
b.glctx.ColorMask(true, true, true, true)
|
||||
|
||||
b.glctx.StencilFunc(gl.EQUAL, 1, 0xFF)
|
||||
|
||||
vertex, _ := b.useShader(style, mat3identity, false, 0)
|
||||
b.glctx.EnableVertexAttribArray(vertex)
|
||||
b.glctx.EnableVertexAttribArray(b.shd.TexCoord)
|
||||
b.glctx.VertexAttribPointer(vertex, 2, gl.FLOAT, false, 0, 0)
|
||||
b.glctx.VertexAttribPointer(b.shd.TexCoord, 2, gl.FLOAT, false, 0, 0)
|
||||
|
||||
b.glctx.DrawArrays(gl.TRIANGLE_FAN, 0, 4)
|
||||
b.glctx.DisableVertexAttribArray(vertex)
|
||||
b.glctx.DisableVertexAttribArray(b.shd.TexCoord)
|
||||
|
||||
b.glctx.StencilOp(gl.KEEP, gl.KEEP, gl.KEEP)
|
||||
b.glctx.StencilFunc(gl.ALWAYS, 0, 0xFF)
|
||||
|
||||
b.glctx.Clear(gl.STENCIL_BUFFER_BIT)
|
||||
b.glctx.StencilMask(0xFF)
|
||||
}
|
||||
|
||||
if style.Blur > 0 {
|
||||
b.drawBlurred(style.Blur, min, max)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *XMobileBackend) FillImageMask(style *backendbase.FillStyle, mask *image.Alpha, pts [4]backendbase.Vec) {
|
||||
b.activate()
|
||||
|
||||
w, h := mask.Rect.Dx(), mask.Rect.Dy()
|
||||
|
||||
b.glctx.ActiveTexture(gl.TEXTURE1)
|
||||
b.glctx.BindTexture(gl.TEXTURE_2D, b.alphaTex)
|
||||
b.glctx.TexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, mask.Stride, h, gl.ALPHA, gl.UNSIGNED_BYTE, mask.Pix[0:])
|
||||
if w < alphaTexSize {
|
||||
b.glctx.TexSubImage2D(gl.TEXTURE_2D, 0, w, 0, 1, h, gl.ALPHA, gl.UNSIGNED_BYTE, zeroes[0:])
|
||||
}
|
||||
if h < alphaTexSize {
|
||||
b.glctx.TexSubImage2D(gl.TEXTURE_2D, 0, 0, h, w, 1, gl.ALPHA, gl.UNSIGNED_BYTE, zeroes[0:])
|
||||
}
|
||||
|
||||
if style.Blur > 0 {
|
||||
b.offscr1.alpha = true
|
||||
b.enableTextureRenderTarget(&b.offscr1)
|
||||
b.glctx.ClearColor(0, 0, 0, 0)
|
||||
b.glctx.Clear(gl.COLOR_BUFFER_BIT | gl.STENCIL_BUFFER_BIT)
|
||||
}
|
||||
|
||||
b.glctx.StencilFunc(gl.EQUAL, 0, 0xFF)
|
||||
|
||||
b.glctx.BindBuffer(gl.ARRAY_BUFFER, b.buf)
|
||||
|
||||
vertex, alphaTexCoord := b.useShader(style, mat3identity, true, 1)
|
||||
|
||||
b.glctx.EnableVertexAttribArray(vertex)
|
||||
b.glctx.EnableVertexAttribArray(alphaTexCoord)
|
||||
|
||||
tw := float64(w) / alphaTexSize
|
||||
th := float64(h) / alphaTexSize
|
||||
var buf [16]float32
|
||||
data := buf[:0]
|
||||
for _, pt := range pts {
|
||||
data = append(data, float32(math.Round(pt[0])), float32(math.Round(pt[1])))
|
||||
}
|
||||
data = append(data, 0, 0, 0, float32(th), float32(tw), float32(th), float32(tw), 0)
|
||||
|
||||
b.glctx.BufferData(gl.ARRAY_BUFFER, byteSlice(unsafe.Pointer(&data[0]), len(data)*4), gl.STREAM_DRAW)
|
||||
|
||||
b.glctx.VertexAttribPointer(vertex, 2, gl.FLOAT, false, 0, 0)
|
||||
b.glctx.VertexAttribPointer(alphaTexCoord, 2, gl.FLOAT, false, 0, 8*4)
|
||||
b.glctx.DrawArrays(gl.TRIANGLE_FAN, 0, 4)
|
||||
|
||||
b.glctx.DisableVertexAttribArray(vertex)
|
||||
b.glctx.DisableVertexAttribArray(alphaTexCoord)
|
||||
|
||||
b.glctx.ActiveTexture(gl.TEXTURE1)
|
||||
b.glctx.BindTexture(gl.TEXTURE_2D, b.alphaTex)
|
||||
|
||||
b.glctx.StencilFunc(gl.ALWAYS, 0, 0xFF)
|
||||
|
||||
b.glctx.ActiveTexture(gl.TEXTURE0)
|
||||
|
||||
if style.Blur > 0 {
|
||||
min, max := extent(pts[:])
|
||||
b.drawBlurred(style.Blur, min, max)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *XMobileBackend) drawBlurred(size float64, min, max backendbase.Vec) {
|
||||
b.offscr1.alpha = true
|
||||
b.offscr2.alpha = true
|
||||
|
||||
// calculate box blur size
|
||||
fsize := math.Max(1, math.Floor(size))
|
||||
sizea := int(fsize)
|
||||
sizeb := sizea
|
||||
sizec := sizea
|
||||
if size-fsize > 0.333333333 {
|
||||
sizeb++
|
||||
}
|
||||
if size-fsize > 0.666666666 {
|
||||
sizec++
|
||||
}
|
||||
|
||||
min[0] -= fsize * 3
|
||||
min[1] -= fsize * 3
|
||||
max[0] += fsize * 3
|
||||
max[1] += fsize * 3
|
||||
min[0] = math.Max(0.0, math.Min(b.fw, min[0]))
|
||||
min[1] = math.Max(0.0, math.Min(b.fh, min[1]))
|
||||
max[0] = math.Max(0.0, math.Min(b.fw, max[0]))
|
||||
max[1] = math.Max(0.0, math.Min(b.fh, max[1]))
|
||||
|
||||
b.glctx.BindBuffer(gl.ARRAY_BUFFER, b.shadowBuf)
|
||||
data := [16]float32{
|
||||
float32(min[0]), float32(min[1]),
|
||||
float32(min[0]), float32(max[1]),
|
||||
float32(max[0]), float32(max[1]),
|
||||
float32(max[0]), float32(min[1]),
|
||||
float32(min[0] / b.fw), 1 - float32(min[1]/b.fh),
|
||||
float32(min[0] / b.fw), 1 - float32(max[1]/b.fh),
|
||||
float32(max[0] / b.fw), 1 - float32(max[1]/b.fh),
|
||||
float32(max[0] / b.fw), 1 - float32(min[1]/b.fh),
|
||||
}
|
||||
b.glctx.BufferData(gl.ARRAY_BUFFER, byteSlice(unsafe.Pointer(&data[0]), len(data)*4), gl.STREAM_DRAW)
|
||||
|
||||
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.UniformMatrix3fv(b.shd.Matrix, mat3identity[:])
|
||||
b.glctx.Uniform1i(b.shd.UseAlphaTex, 0)
|
||||
b.glctx.Uniform1i(b.shd.Func, shdFuncBoxBlur)
|
||||
|
||||
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.Disable(gl.BLEND)
|
||||
|
||||
b.glctx.ActiveTexture(gl.TEXTURE0)
|
||||
|
||||
b.glctx.ClearColor(0, 0, 0, 0)
|
||||
|
||||
b.enableTextureRenderTarget(&b.offscr2)
|
||||
b.glctx.BindTexture(gl.TEXTURE_2D, b.offscr1.tex)
|
||||
b.glctx.Clear(gl.COLOR_BUFFER_BIT | gl.STENCIL_BUFFER_BIT)
|
||||
b.box3(sizea, 0, false)
|
||||
b.enableTextureRenderTarget(&b.offscr1)
|
||||
b.glctx.BindTexture(gl.TEXTURE_2D, b.offscr2.tex)
|
||||
b.box3(sizeb, -0.5, false)
|
||||
b.enableTextureRenderTarget(&b.offscr2)
|
||||
b.glctx.BindTexture(gl.TEXTURE_2D, b.offscr1.tex)
|
||||
b.box3(sizec, 0, false)
|
||||
b.enableTextureRenderTarget(&b.offscr1)
|
||||
b.glctx.BindTexture(gl.TEXTURE_2D, b.offscr2.tex)
|
||||
b.box3(sizea, 0, true)
|
||||
b.enableTextureRenderTarget(&b.offscr2)
|
||||
b.glctx.BindTexture(gl.TEXTURE_2D, b.offscr1.tex)
|
||||
b.box3(sizeb, -0.5, true)
|
||||
b.glctx.Enable(gl.BLEND)
|
||||
b.glctx.BlendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA)
|
||||
b.disableTextureRenderTarget()
|
||||
b.glctx.BindTexture(gl.TEXTURE_2D, b.offscr2.tex)
|
||||
b.box3(sizec, 0, true)
|
||||
|
||||
b.glctx.DisableVertexAttribArray(b.shd.Vertex)
|
||||
b.glctx.DisableVertexAttribArray(b.shd.TexCoord)
|
||||
|
||||
b.glctx.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
|
||||
}
|
||||
|
||||
func (b *XMobileBackend) box3(size int, offset float32, vertical bool) {
|
||||
b.glctx.Uniform1i(b.shd.BoxSize, size)
|
||||
if vertical {
|
||||
b.glctx.Uniform1i(b.shd.BoxVertical, 1)
|
||||
b.glctx.Uniform1f(b.shd.BoxScale, 1/float32(b.fh))
|
||||
} else {
|
||||
b.glctx.Uniform1i(b.shd.BoxVertical, 0)
|
||||
b.glctx.Uniform1f(b.shd.BoxScale, 1/float32(b.fw))
|
||||
}
|
||||
b.glctx.Uniform1f(b.shd.BoxOffset, offset)
|
||||
b.glctx.DrawArrays(gl.TRIANGLE_FAN, 0, 4)
|
||||
}
|
|
@ -1,439 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func main() {
|
||||
{ // make sure we are in the right directory
|
||||
dir, err := os.Getwd()
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to get working directory: %v", err)
|
||||
}
|
||||
d1 := filepath.Base(dir)
|
||||
d2 := filepath.Base(filepath.Dir(dir))
|
||||
if d2 != "backend" || d1 != "xmobilebackend" {
|
||||
log.Fatalln("This must be run in the backend/xmobilebackend directory")
|
||||
}
|
||||
}
|
||||
|
||||
{ // delete existing files
|
||||
fis, err := ioutil.ReadDir(".")
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to read current dir: %v", err)
|
||||
}
|
||||
|
||||
for _, fi := range fis {
|
||||
if fi.IsDir() || fi.Name() == "shader.go" {
|
||||
continue
|
||||
}
|
||||
err = os.Remove(fi.Name())
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to delete file %s: %v", fi.Name(), err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{ // copy gogl files
|
||||
fis, err := ioutil.ReadDir("../goglbackend")
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to read dir ../goglbackend: %v", err)
|
||||
}
|
||||
|
||||
for _, fi := range fis {
|
||||
if !strings.HasSuffix(fi.Name(), ".go") || fi.Name() == "shader.go" {
|
||||
continue
|
||||
}
|
||||
path := filepath.Join("../goglbackend", fi.Name())
|
||||
data, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to read file %s: %v", path, err)
|
||||
}
|
||||
|
||||
filename, rewritten := rewrite(fi.Name(), string(data))
|
||||
|
||||
err = ioutil.WriteFile(filename, ([]byte)(rewritten), 0777)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to write file %s: %v", fi.Name(), err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err := exec.Command("go", "fmt").Run()
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to run go fmt: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func rewrite(filename, src string) (string, string) {
|
||||
src = strings.Replace(src, `package goglbackend`, `package xmobilebackend`, 1)
|
||||
src = strings.Replace(src, `"github.com/tfriedel6/canvas/backend/goglbackend/gl"`, `"golang.org/x/mobile/gl"`, 1)
|
||||
src = strings.Replace(src, "\tgl.", "\tb.glctx.", -1)
|
||||
src = strings.Replace(src, "GoGLBackend", "XMobileBackend", -1)
|
||||
src = strings.Replace(src, "uint32(gl.TRIANGLES)", "gl.Enum(gl.TRIANGLES)", -1)
|
||||
|
||||
src = strings.Replace(src, `func (g *gradient) Delete() {`,
|
||||
`func (g *gradient) Delete() {
|
||||
b := g.b`, -1)
|
||||
src = strings.Replace(src, `func (g *gradient) load(stops backendbase.Gradient) {`,
|
||||
`func (g *gradient) load(stops backendbase.Gradient) {
|
||||
b := g.b`, -1)
|
||||
src = strings.Replace(src, `func (img *Image) Delete() {`,
|
||||
`func (img *Image) Delete() {
|
||||
b := img.b`, -1)
|
||||
src = strings.Replace(src, `func (img *Image) Replace(src image.Image) error {`,
|
||||
`func (img *Image) Replace(src image.Image) error {
|
||||
b := img.b`, -1)
|
||||
|
||||
src = strings.Replace(src, `imageBufTex == 0`, `imageBufTex.Value == 0`, -1)
|
||||
|
||||
src = strings.Replace(src,
|
||||
`loadImage(src image.Image, tex uint32)`,
|
||||
`loadImage(b *XMobileBackend, src image.Image, tex gl.Texture)`, -1)
|
||||
src = strings.Replace(src,
|
||||
`loadImageRGBA(src *image.RGBA, tex uint32)`,
|
||||
`loadImageRGBA(b *XMobileBackend, src *image.RGBA, tex gl.Texture)`, -1)
|
||||
src = strings.Replace(src,
|
||||
`loadImageGray(src *image.Gray, tex uint32)`,
|
||||
`loadImageGray(b *XMobileBackend, src *image.Gray, tex gl.Texture)`, -1)
|
||||
src = strings.Replace(src,
|
||||
`loadImageConverted(src image.Image, tex uint32)`,
|
||||
`loadImageConverted(b *XMobileBackend, src image.Image, tex gl.Texture)`, -1)
|
||||
|
||||
src = strings.Replace(src,
|
||||
`func loadShader(vs, fs string, sp *shaderProgram) error {`,
|
||||
`func loadShader(b *XMobileBackend, vs, fs string, sp *shaderProgram) error {
|
||||
sp.b = b`, -1)
|
||||
|
||||
src = strings.Replace(src, `func glError() error {
|
||||
glErr := gl.GetError()
|
||||
`, `func glError(b *XMobileBackend) error {
|
||||
glErr := b.glctx.GetError()
|
||||
`, -1)
|
||||
src = rewriteCalls(src, "glError", func(params []string) string {
|
||||
return "glError(b)"
|
||||
})
|
||||
|
||||
src = regexp.MustCompile(`[ \t]+tex[ ]+uint32`).ReplaceAllString(src, "\ttex gl.Texture")
|
||||
|
||||
src = rewriteCalls(src, "b.glctx.BufferData", func(params []string) string {
|
||||
return "b.glctx.BufferData(" + params[0] + ", byteSlice(" + params[2] + ", " + params[1] + "), " + params[3] + ")"
|
||||
})
|
||||
src = rewriteCalls(src, "b.glctx.VertexAttribPointer", func(params []string) string {
|
||||
params[5] = strings.Replace(params[5], "gl.PtrOffset(", "", 1)
|
||||
params[5] = strings.Replace(params[5], ")", "", 1)
|
||||
params[5] = strings.Replace(params[5], "nil", "0", 1)
|
||||
return "b.glctx.VertexAttribPointer(" + strings.Join(params, ",") + ")"
|
||||
})
|
||||
src = rewriteCalls(src, "b.glctx.DrawArrays", func(params []string) string {
|
||||
if strings.HasPrefix(params[2], "int32(") {
|
||||
params[2] = params[2][6 : len(params[2])-1]
|
||||
}
|
||||
return "b.glctx.DrawArrays(" + strings.Join(params, ",") + ")"
|
||||
})
|
||||
src = rewriteCalls(src, "b.glctx.Uniform1fv", func(params []string) string {
|
||||
params[2] = params[2][1 : len(params[2])-3]
|
||||
return "b.glctx.Uniform1fv(" + params[0] + ", " + params[2] + ")"
|
||||
})
|
||||
src = rewriteCalls(src, "b.glctx.Uniform1i", func(params []string) string {
|
||||
if strings.HasPrefix(params[1], "int32(") {
|
||||
params[1] = params[1][6 : len(params[1])-1]
|
||||
}
|
||||
return "b.glctx.Uniform1i(" + strings.Join(params, ",") + ")"
|
||||
})
|
||||
src = rewriteCalls(src, "b.glctx.UniformMatrix3fv", func(params []string) string {
|
||||
return "b.glctx.UniformMatrix3fv(" + params[0] + ", " + params[3][1:len(params[3])-3] + "[:])"
|
||||
})
|
||||
src = rewriteCalls(src, "b.glctx.TexImage2D", func(params []string) string {
|
||||
params = append(params[:5], params[6:]...)
|
||||
for i, param := range params {
|
||||
if strings.HasPrefix(param, "int32(") {
|
||||
params[i] = param[6 : len(param)-1]
|
||||
} else if strings.HasPrefix(param, "gl.Ptr(") {
|
||||
params[i] = param[8:len(param)-2] + ":]"
|
||||
}
|
||||
}
|
||||
return "b.glctx.TexImage2D(" + strings.Join(params, ", ") + ")"
|
||||
})
|
||||
src = rewriteCalls(src, "b.glctx.TexSubImage2D", func(params []string) string {
|
||||
for i, param := range params {
|
||||
if strings.HasPrefix(param, "int32(") {
|
||||
params[i] = param[6 : len(param)-1]
|
||||
} else if strings.HasPrefix(param, "gl.Ptr(") {
|
||||
params[i] = param[8:len(param)-2] + ":]"
|
||||
}
|
||||
}
|
||||
return "b.glctx.TexSubImage2D(" + strings.Join(params, ", ") + ")"
|
||||
})
|
||||
src = rewriteCalls(src, "b.glctx.GetIntegerv", func(params []string) string {
|
||||
return "b.glctx.GetIntegerv(" + params[1][1:len(params[1])-2] + ":], " + params[0] + ")"
|
||||
})
|
||||
src = rewriteCalls(src, "b.glctx.GenTextures", func(params []string) string {
|
||||
return params[1][1:] + " = b.glctx.CreateTexture()"
|
||||
})
|
||||
src = rewriteCalls(src, "b.glctx.DeleteTextures", func(params []string) string {
|
||||
return "b.glctx.DeleteTexture(" + params[1][1:] + ")"
|
||||
})
|
||||
src = rewriteCalls(src, "b.glctx.GenBuffers", func(params []string) string {
|
||||
return params[1][1:] + " = b.glctx.CreateBuffer()"
|
||||
})
|
||||
src = rewriteCalls(src, "b.glctx.GenFramebuffers", func(params []string) string {
|
||||
return params[1][1:] + " = b.glctx.CreateFramebuffer()"
|
||||
})
|
||||
src = rewriteCalls(src, "b.glctx.DeleteFramebuffers", func(params []string) string {
|
||||
return "b.glctx.DeleteFramebuffer(" + params[1][1:] + ")"
|
||||
})
|
||||
src = rewriteCalls(src, "b.glctx.GenRenderbuffers", func(params []string) string {
|
||||
return params[1][1:] + " = b.glctx.CreateRenderbuffer()"
|
||||
})
|
||||
src = rewriteCalls(src, "b.glctx.DeleteRenderbuffers", func(params []string) string {
|
||||
return "b.glctx.DeleteRenderbuffer(" + params[1][1:] + ")"
|
||||
})
|
||||
src = rewriteCalls(src, "b.glctx.RenderbufferStorage", func(params []string) string {
|
||||
for i, param := range params {
|
||||
if strings.HasPrefix(param, "int32(") {
|
||||
params[i] = param[6 : len(param)-1]
|
||||
}
|
||||
}
|
||||
return "b.glctx.RenderbufferStorage(" + strings.Join(params, ", ") + ")"
|
||||
})
|
||||
src = rewriteCalls(src, "gl.CheckFramebufferStatus", func(params []string) string {
|
||||
return "b.glctx.CheckFramebufferStatus(" + strings.Join(params, ", ") + ")"
|
||||
})
|
||||
src = rewriteCalls(src, "b.glctx.BindFramebuffer", func(params []string) string {
|
||||
if params[1] == "0" {
|
||||
params[1] = "gl.Framebuffer{Value: 0}"
|
||||
}
|
||||
return "b.glctx.BindFramebuffer(" + strings.Join(params, ", ") + ")"
|
||||
})
|
||||
src = rewriteCalls(src, "b.glctx.ReadPixels", func(params []string) string {
|
||||
for i, param := range params {
|
||||
if strings.HasPrefix(param, "int32(") {
|
||||
params[i] = param[6 : len(param)-1]
|
||||
} else if strings.HasPrefix(param, "gl.Ptr(") {
|
||||
params[i] = param[8:len(param)-2] + ":]"
|
||||
} else if len(param) >= 5 && param[:3] == "vp[" {
|
||||
params[i] = fmt.Sprintf("int(%s)", param)
|
||||
}
|
||||
}
|
||||
return "b.glctx.ReadPixels(" + params[6] + ", " + strings.Join(params[:len(params)-1], ", ") + ")"
|
||||
})
|
||||
src = rewriteCalls(src, "b.glctx.Viewport", func(params []string) string {
|
||||
for i, param := range params {
|
||||
if strings.HasPrefix(param, "int32(") {
|
||||
params[i] = param[6 : len(param)-1]
|
||||
}
|
||||
}
|
||||
return "b.glctx.Viewport(" + strings.Join(params, ", ") + ")"
|
||||
})
|
||||
src = rewriteCalls(src, "loadImage", func(params []string) string {
|
||||
return "loadImage(b, " + strings.Join(params, ", ") + ")"
|
||||
})
|
||||
src = rewriteCalls(src, "loadImageRGBA", func(params []string) string {
|
||||
return "loadImageRGBA(b, " + strings.Join(params, ", ") + ")"
|
||||
})
|
||||
src = rewriteCalls(src, "loadImageGray", func(params []string) string {
|
||||
return "loadImageGray(b, " + strings.Join(params, ", ") + ")"
|
||||
})
|
||||
src = rewriteCalls(src, "loadImageConverted", func(params []string) string {
|
||||
return "loadImageConverted(b, " + strings.Join(params, ", ") + ")"
|
||||
})
|
||||
src = rewriteCalls(src, "loadShader", func(params []string) string {
|
||||
return "loadShader(b, " + strings.Join(params, ", ") + ")"
|
||||
})
|
||||
src = strings.ReplaceAll(src, "if tex == 0 {", "if tex.Value == 0 {")
|
||||
|
||||
if filename == "gogl.go" {
|
||||
filename = "xmobile.go"
|
||||
src = rewriteMain(src)
|
||||
} else if filename == "shaders.go" {
|
||||
src = rewriteShaders(src)
|
||||
}
|
||||
|
||||
return filename, src
|
||||
}
|
||||
|
||||
func rewriteMain(src string) string {
|
||||
src = strings.Replace(src, "type GLContext struct {\n",
|
||||
"type GLContext struct {\n\tglctx gl.Context\n\n", 1)
|
||||
src = strings.Replace(src, "ctx := &GLContext{\n",
|
||||
"ctx := &GLContext{\n\t\tglctx: glctx,\n\n", 1)
|
||||
src = strings.Replace(src, "\tb.glctx.GetError() // clear error state\n",
|
||||
"\tb := &XMobileBackend{GLContext: ctx}\n\n\tb.glctx.GetError() // clear error state\n\n", 1)
|
||||
src = strings.Replace(src, "type XMobileBackend struct {\n",
|
||||
"type XMobileBackend struct {\n", 1)
|
||||
src = strings.Replace(src, "func NewGLContext() (*GLContext, error) {",
|
||||
"func NewGLContext(glctx gl.Context) (*GLContext, error) {", 1)
|
||||
src = strings.Replace(src, "TextureID uint32", "TextureID gl.Texture", 1)
|
||||
|
||||
src = strings.Replace(src,
|
||||
` err := gl.Init()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
`, ` var err error
|
||||
|
||||
`, 1)
|
||||
|
||||
src = strings.Replace(src,
|
||||
`// New returns a new canvas backend. x, y, w, h define the target
|
||||
// rectangle in the window. ctx is a GLContext created with
|
||||
// NewGLContext, but can be nil for a default one. It makes sense
|
||||
// to pass one in when using for example an onscreen and an
|
||||
// offscreen backend using the same GL context.
|
||||
`, `// New returns a new canvas backend. x, y, w, h define the target
|
||||
// rectangle in the window. ctx is a GLContext created with
|
||||
// NewGLContext
|
||||
`, 1)
|
||||
src = strings.Replace(src,
|
||||
`// NewOffscreen returns a new offscreen canvas backend. w, h define
|
||||
// the size of the offscreen texture. ctx is a GLContext created
|
||||
// with NewGLContext, but can be nil for a default one. It makes
|
||||
// sense to pass one in when using for example an onscreen and an
|
||||
// offscreen backend using the same GL context.
|
||||
`, `// NewOffscreen returns a new offscreen canvas backend. w, h define
|
||||
// the size of the offscreen texture. ctx is a GLContext created
|
||||
// with NewGLContext
|
||||
`, 1)
|
||||
src = strings.Replace(src,
|
||||
` if ctx == nil {
|
||||
var err error
|
||||
ctx, err = NewGLContext()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
`, "", 1)
|
||||
|
||||
src = strings.Replace(src,
|
||||
` buf uint32
|
||||
shadowBuf uint32
|
||||
alphaTex uint32
|
||||
`,
|
||||
` buf gl.Buffer
|
||||
shadowBuf gl.Buffer
|
||||
alphaTex gl.Texture
|
||||
`, 1)
|
||||
|
||||
src = strings.Replace(src, `imageBufTex uint32`, `imageBufTex gl.Texture`, 1)
|
||||
|
||||
src = strings.Replace(src,
|
||||
`type offscreenBuffer struct {
|
||||
tex gl.Texture
|
||||
w int
|
||||
h int
|
||||
renderStencilBuf uint32
|
||||
frameBuf uint32
|
||||
alpha bool
|
||||
}
|
||||
`,
|
||||
`type offscreenBuffer struct {
|
||||
tex gl.Texture
|
||||
w int
|
||||
h int
|
||||
renderStencilBuf gl.Renderbuffer
|
||||
frameBuf gl.Framebuffer
|
||||
alpha bool
|
||||
}
|
||||
`, 1)
|
||||
|
||||
src = src + `
|
||||
func byteSlice(ptr unsafe.Pointer, size int) []byte {
|
||||
var buf []byte
|
||||
sh := (*reflect.SliceHeader)(unsafe.Pointer(&buf))
|
||||
sh.Cap = size
|
||||
sh.Len = size
|
||||
sh.Data = uintptr(ptr)
|
||||
return buf
|
||||
}
|
||||
`
|
||||
src = strings.Replace(src, "import (\n",
|
||||
`import (
|
||||
"unsafe"
|
||||
"reflect"
|
||||
`, 1)
|
||||
src = strings.Replace(src, "vertexLoc uint32", "vertexLoc gl.Attrib", 1)
|
||||
src = strings.Replace(src, "alphaTexSlot int32", "alphaTexSlot int", 1)
|
||||
src = strings.Replace(src, "vertexLoc, alphaTexCoordLoc uint32", "vertexLoc, alphaTexCoordLoc gl.Attrib", 1)
|
||||
|
||||
return src
|
||||
}
|
||||
|
||||
func rewriteShaders(src string) string {
|
||||
src = strings.Replace(src,
|
||||
`package xmobilebackend
|
||||
`,
|
||||
`package xmobilebackend
|
||||
|
||||
import (
|
||||
"golang.org/x/mobile/gl"
|
||||
)
|
||||
`, 1)
|
||||
|
||||
src = strings.Replace(src, "uint32", "gl.Attrib", -1)
|
||||
src = strings.Replace(src, "int32", "gl.Uniform", -1)
|
||||
src = strings.Replace(src, "shdFuncSolid gl.Uniform", "shdFuncSolid int", -1)
|
||||
|
||||
return src
|
||||
}
|
||||
|
||||
func rewriteCalls(src, funcName string, fn func([]string) string) string {
|
||||
rewritten := ""
|
||||
pos := 0
|
||||
for {
|
||||
idx := strings.Index(src[pos:], funcName)
|
||||
if idx == -1 {
|
||||
rewritten += src[pos:]
|
||||
break
|
||||
}
|
||||
idx += pos
|
||||
rewritten += src[pos:idx]
|
||||
parenStart := idx + len(funcName)
|
||||
if idx > 5 && src[idx-5:idx] == "func " {
|
||||
rewritten += src[idx:parenStart]
|
||||
pos = parenStart
|
||||
continue
|
||||
}
|
||||
if src[parenStart] != '(' {
|
||||
rewritten += src[idx:parenStart]
|
||||
pos = parenStart
|
||||
continue
|
||||
}
|
||||
|
||||
params := make([]string, 0, 10)
|
||||
|
||||
parenDepth := 0
|
||||
paramStart := 0
|
||||
paramsStr := src[parenStart+1:]
|
||||
paramloop:
|
||||
for i, rn := range paramsStr {
|
||||
switch rn {
|
||||
case '(':
|
||||
parenDepth++
|
||||
case ')':
|
||||
parenDepth--
|
||||
if parenDepth == -1 {
|
||||
params = append(params, strings.TrimSpace(paramsStr[paramStart:i]))
|
||||
pos = parenStart + i + 2
|
||||
break paramloop
|
||||
}
|
||||
case ',':
|
||||
params = append(params, strings.TrimSpace(paramsStr[paramStart:i]))
|
||||
paramStart = i + 1
|
||||
}
|
||||
}
|
||||
|
||||
rewritten += fn(params)
|
||||
}
|
||||
|
||||
return rewritten
|
||||
}
|
|
@ -1,92 +0,0 @@
|
|||
package xmobilebackend
|
||||
|
||||
import (
|
||||
"github.com/tfriedel6/canvas/backend/backendbase"
|
||||
"golang.org/x/mobile/gl"
|
||||
)
|
||||
|
||||
// LinearGradient is a gradient with any number of
|
||||
// stops and any number of colors. The gradient will
|
||||
// be drawn such that each point on the gradient
|
||||
// will correspond to a straight line
|
||||
type LinearGradient struct {
|
||||
gradient
|
||||
}
|
||||
|
||||
// RadialGradient is a gradient with any number of
|
||||
// stops and any number of colors. The gradient will
|
||||
// be drawn such that each point on the gradient
|
||||
// will correspond to a circle
|
||||
type RadialGradient struct {
|
||||
gradient
|
||||
}
|
||||
|
||||
type gradient struct {
|
||||
b *XMobileBackend
|
||||
tex gl.Texture
|
||||
}
|
||||
|
||||
func (b *XMobileBackend) LoadLinearGradient(data backendbase.Gradient) backendbase.LinearGradient {
|
||||
b.activate()
|
||||
|
||||
lg := &LinearGradient{
|
||||
gradient: gradient{b: b},
|
||||
}
|
||||
lg.tex = b.glctx.CreateTexture()
|
||||
b.glctx.ActiveTexture(gl.TEXTURE0)
|
||||
b.glctx.BindTexture(gl.TEXTURE_2D, lg.tex)
|
||||
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.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)
|
||||
lg.load(data)
|
||||
return lg
|
||||
}
|
||||
|
||||
func (b *XMobileBackend) LoadRadialGradient(data backendbase.Gradient) backendbase.RadialGradient {
|
||||
b.activate()
|
||||
|
||||
rg := &RadialGradient{
|
||||
gradient: gradient{b: b},
|
||||
}
|
||||
rg.tex = b.glctx.CreateTexture()
|
||||
b.glctx.ActiveTexture(gl.TEXTURE0)
|
||||
b.glctx.BindTexture(gl.TEXTURE_2D, rg.tex)
|
||||
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.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)
|
||||
rg.load(data)
|
||||
return rg
|
||||
}
|
||||
|
||||
// Delete explicitly deletes the gradient
|
||||
func (g *gradient) Delete() {
|
||||
b := g.b
|
||||
g.b.activate()
|
||||
|
||||
b.glctx.DeleteTexture(g.tex)
|
||||
}
|
||||
|
||||
func (lg *LinearGradient) Replace(data backendbase.Gradient) { lg.load(data) }
|
||||
func (rg *RadialGradient) Replace(data backendbase.Gradient) { rg.load(data) }
|
||||
|
||||
func (g *gradient) load(stops backendbase.Gradient) {
|
||||
b := g.b
|
||||
g.b.activate()
|
||||
|
||||
b.glctx.ActiveTexture(gl.TEXTURE0)
|
||||
b.glctx.BindTexture(gl.TEXTURE_2D, g.tex)
|
||||
var pixels [2048 * 4]byte
|
||||
pp := 0
|
||||
for i := 0; i < 2048; i++ {
|
||||
c := stops.ColorAt(float64(i) / 2047)
|
||||
pixels[pp] = c.R
|
||||
pixels[pp+1] = c.G
|
||||
pixels[pp+2] = c.B
|
||||
pixels[pp+3] = c.A
|
||||
pp += 4
|
||||
}
|
||||
|
||||
b.glctx.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 2048, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixels[0:])
|
||||
}
|
|
@ -1,102 +0,0 @@
|
|||
package xmobilebackend
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/mobile/gl"
|
||||
)
|
||||
|
||||
// GetImageData returns an RGBA image of the current image
|
||||
func (b *XMobileBackend) GetImageData(x, y, w, h int) *image.RGBA {
|
||||
b.activate()
|
||||
|
||||
if x < 0 {
|
||||
w += x
|
||||
x = 0
|
||||
}
|
||||
if y < 0 {
|
||||
h += y
|
||||
y = 0
|
||||
}
|
||||
if w > b.w {
|
||||
w = b.w
|
||||
}
|
||||
if h > b.h {
|
||||
h = b.h
|
||||
}
|
||||
|
||||
var vp [4]int32
|
||||
b.glctx.GetIntegerv(vp[:], gl.VIEWPORT)
|
||||
|
||||
size := int(vp[2] * vp[3] * 3)
|
||||
if len(b.imageBuf) < size {
|
||||
b.imageBuf = make([]byte, size)
|
||||
}
|
||||
b.glctx.ReadPixels(b.imageBuf[0:], int(vp[0]), int(vp[1]), int(vp[2]), int(vp[3]), gl.RGB, gl.UNSIGNED_BYTE)
|
||||
|
||||
rgba := image.NewRGBA(image.Rect(x, y, x+w, y+h))
|
||||
for cy := y; cy < y+h; cy++ {
|
||||
bp := (int(vp[3])-h+cy)*int(vp[2])*3 + x*3
|
||||
for cx := x; cx < x+w; cx++ {
|
||||
rgba.SetRGBA(cx, y+h-1-cy, color.RGBA{R: b.imageBuf[bp], G: b.imageBuf[bp+1], B: b.imageBuf[bp+2], A: 255})
|
||||
bp += 3
|
||||
}
|
||||
}
|
||||
return rgba
|
||||
}
|
||||
|
||||
// PutImageData puts the given image at the given x/y coordinates
|
||||
func (b *XMobileBackend) PutImageData(img *image.RGBA, x, y int) {
|
||||
b.activate()
|
||||
|
||||
b.glctx.ActiveTexture(gl.TEXTURE0)
|
||||
if b.imageBufTex.Value == 0 {
|
||||
b.imageBufTex = b.glctx.CreateTexture()
|
||||
b.glctx.BindTexture(gl.TEXTURE_2D, b.imageBufTex)
|
||||
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.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)
|
||||
} else {
|
||||
b.glctx.BindTexture(gl.TEXTURE_2D, b.imageBufTex)
|
||||
}
|
||||
|
||||
w, h := img.Bounds().Dx(), img.Bounds().Dy()
|
||||
|
||||
if img.Stride == img.Bounds().Dx()*4 {
|
||||
b.glctx.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, w, h, gl.RGBA, gl.UNSIGNED_BYTE, img.Pix[0:])
|
||||
} else {
|
||||
data := make([]uint8, 0, w*h*4)
|
||||
for cy := 0; cy < h; cy++ {
|
||||
start := cy * img.Stride
|
||||
end := start + w*4
|
||||
data = append(data, img.Pix[start:end]...)
|
||||
}
|
||||
b.glctx.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, w, h, gl.RGBA, gl.UNSIGNED_BYTE, data[0:])
|
||||
}
|
||||
|
||||
dx, dy := float32(x), float32(y)
|
||||
dw, dh := float32(w), float32(h)
|
||||
|
||||
b.glctx.BindBuffer(gl.ARRAY_BUFFER, b.buf)
|
||||
data := [16]float32{dx, dy, dx + dw, dy, dx + dw, dy + dh, dx, dy + dh,
|
||||
0, 0, 1, 0, 1, 1, 0, 1}
|
||||
b.glctx.BufferData(gl.ARRAY_BUFFER, byteSlice(unsafe.Pointer(&data[0]), len(data)*4), gl.STREAM_DRAW)
|
||||
|
||||
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.UniformMatrix3fv(b.shd.Matrix, mat3identity[:])
|
||||
b.glctx.Uniform1f(b.shd.GlobalAlpha, 1)
|
||||
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)
|
||||
}
|
|
@ -1,223 +0,0 @@
|
|||
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]backendbase.Vec, 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.UniformMatrix3fv(b.shd.Matrix, mat3identity[:])
|
||||
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 }
|
|
@ -1,180 +0,0 @@
|
|||
package xmobilebackend
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"golang.org/x/mobile/gl"
|
||||
)
|
||||
|
||||
type shaderProgram struct {
|
||||
b *XMobileBackend
|
||||
ID gl.Program
|
||||
vs, fs gl.Shader
|
||||
|
||||
attribs map[string]gl.Attrib
|
||||
uniforms map[string]gl.Uniform
|
||||
}
|
||||
|
||||
func loadShader(b *XMobileBackend, vs, fs string, sp *shaderProgram) error {
|
||||
sp.b = b
|
||||
glError(b) // clear the current error
|
||||
|
||||
// compile vertex shader
|
||||
{
|
||||
sp.vs = b.glctx.CreateShader(gl.VERTEX_SHADER)
|
||||
b.glctx.ShaderSource(sp.vs, vs)
|
||||
b.glctx.CompileShader(sp.vs)
|
||||
|
||||
status := b.glctx.GetShaderi(sp.vs, gl.COMPILE_STATUS)
|
||||
if status != gl.TRUE {
|
||||
clog := b.glctx.GetShaderInfoLog(sp.vs)
|
||||
b.glctx.DeleteShader(sp.vs)
|
||||
return fmt.Errorf("failed to compile vertex shader:\n\n%s", clog)
|
||||
}
|
||||
if err := glError(b); err != nil {
|
||||
return fmt.Errorf("gl error after compiling vertex shader: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// compile fragment shader
|
||||
{
|
||||
sp.fs = b.glctx.CreateShader(gl.FRAGMENT_SHADER)
|
||||
b.glctx.ShaderSource(sp.fs, fs)
|
||||
b.glctx.CompileShader(sp.fs)
|
||||
|
||||
status := b.glctx.GetShaderi(sp.fs, gl.COMPILE_STATUS)
|
||||
if status != gl.TRUE {
|
||||
clog := b.glctx.GetShaderInfoLog(sp.fs)
|
||||
b.glctx.DeleteShader(sp.fs)
|
||||
return fmt.Errorf("failed to compile fragment shader:\n\n%s", clog)
|
||||
}
|
||||
if err := glError(b); err != nil {
|
||||
return fmt.Errorf("gl error after compiling fragment shader: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// link shader program
|
||||
{
|
||||
sp.ID = b.glctx.CreateProgram()
|
||||
b.glctx.AttachShader(sp.ID, sp.vs)
|
||||
b.glctx.AttachShader(sp.ID, sp.fs)
|
||||
b.glctx.LinkProgram(sp.ID)
|
||||
|
||||
status := b.glctx.GetProgrami(sp.ID, gl.LINK_STATUS)
|
||||
if status != gl.TRUE {
|
||||
clog := b.glctx.GetProgramInfoLog(sp.ID)
|
||||
b.glctx.DeleteProgram(sp.ID)
|
||||
b.glctx.DeleteShader(sp.vs)
|
||||
b.glctx.DeleteShader(sp.fs)
|
||||
return fmt.Errorf("failed to link shader program:\n\n%s", clog)
|
||||
}
|
||||
if err := glError(b); err != nil {
|
||||
return fmt.Errorf("gl error after linking shader: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
b.glctx.UseProgram(sp.ID)
|
||||
// load the attributes
|
||||
count := b.glctx.GetProgrami(sp.ID, gl.ACTIVE_ATTRIBUTES)
|
||||
sp.attribs = make(map[string]gl.Attrib, int(count))
|
||||
for i := 0; i < count; i++ {
|
||||
name, _, _ := b.glctx.GetActiveAttrib(sp.ID, uint32(i))
|
||||
sp.attribs[name] = b.glctx.GetAttribLocation(sp.ID, name)
|
||||
}
|
||||
|
||||
// load the uniforms
|
||||
count = b.glctx.GetProgrami(sp.ID, gl.ACTIVE_UNIFORMS)
|
||||
sp.uniforms = make(map[string]gl.Uniform, int(count))
|
||||
for i := 0; i < count; i++ {
|
||||
name, _, _ := b.glctx.GetActiveUniform(sp.ID, uint32(i))
|
||||
sp.uniforms[name] = b.glctx.GetUniformLocation(sp.ID, name)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sp *shaderProgram) use() {
|
||||
sp.b.glctx.UseProgram(sp.ID)
|
||||
}
|
||||
|
||||
func (sp *shaderProgram) delete() {
|
||||
sp.b.glctx.DeleteProgram(sp.ID)
|
||||
sp.b.glctx.DeleteShader(sp.vs)
|
||||
sp.b.glctx.DeleteShader(sp.fs)
|
||||
}
|
||||
|
||||
func (sp *shaderProgram) loadLocations(target interface{}) error {
|
||||
val := reflect.ValueOf(target)
|
||||
if val.Kind() != reflect.Ptr {
|
||||
panic("target must be a pointer to a struct")
|
||||
}
|
||||
val = val.Elem()
|
||||
if val.Kind() != reflect.Struct {
|
||||
panic("target must be a pointer to a struct")
|
||||
}
|
||||
|
||||
sp.b.glctx.UseProgram(sp.ID)
|
||||
|
||||
var errs strings.Builder
|
||||
|
||||
for name, loc := range sp.attribs {
|
||||
field := val.FieldByName(sp.structName(name))
|
||||
if field == (reflect.Value{}) {
|
||||
fmt.Fprintf(&errs, "field for attribute \"%s\" not found; ", name)
|
||||
} else if field.Type() != reflect.TypeOf(gl.Attrib{}) {
|
||||
fmt.Fprintf(&errs, "field for attribute \"%s\" must have type gl.Attrib; ", name)
|
||||
} else {
|
||||
field.Set(reflect.ValueOf(loc))
|
||||
}
|
||||
}
|
||||
|
||||
for name, loc := range sp.uniforms {
|
||||
field := val.FieldByName(sp.structName(name))
|
||||
if field == (reflect.Value{}) {
|
||||
fmt.Fprintf(&errs, "field for uniform \"%s\" not found; ", name)
|
||||
} else if field.Type() != reflect.TypeOf(gl.Uniform{}) {
|
||||
fmt.Fprintf(&errs, "field for uniform \"%s\" must have type gl.Uniform; ", name)
|
||||
} else {
|
||||
field.Set(reflect.ValueOf(loc))
|
||||
}
|
||||
}
|
||||
|
||||
if errs.Len() > 0 {
|
||||
return errors.New(strings.TrimSpace(errs.String()))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sp *shaderProgram) structName(name string) string {
|
||||
rn, sz := utf8.DecodeRuneInString(name)
|
||||
name = fmt.Sprintf("%c%s", unicode.ToUpper(rn), name[sz:])
|
||||
idx := strings.IndexByte(name, '[')
|
||||
if idx > 0 {
|
||||
name = name[:idx]
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
func (sp *shaderProgram) mustLoadLocations(target interface{}) {
|
||||
err := sp.loadLocations(target)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (sp *shaderProgram) enableAllVertexAttribArrays() {
|
||||
for _, loc := range sp.attribs {
|
||||
sp.b.glctx.EnableVertexAttribArray(loc)
|
||||
}
|
||||
}
|
||||
|
||||
func (sp *shaderProgram) disableAllVertexAttribArrays() {
|
||||
for _, loc := range sp.attribs {
|
||||
sp.b.glctx.DisableVertexAttribArray(loc)
|
||||
}
|
||||
}
|
|
@ -1,162 +0,0 @@
|
|||
package xmobilebackend
|
||||
|
||||
import (
|
||||
"golang.org/x/mobile/gl"
|
||||
)
|
||||
|
||||
var unifiedVS = `
|
||||
attribute vec2 vertex, texCoord;
|
||||
|
||||
uniform vec2 canvasSize;
|
||||
uniform mat3 matrix;
|
||||
|
||||
varying vec2 v_cp, v_tc;
|
||||
|
||||
void main() {
|
||||
v_tc = texCoord;
|
||||
vec3 v = matrix * vec3(vertex.xy, 1.0);
|
||||
vec2 tf = v.xy / v.z;
|
||||
v_cp = tf;
|
||||
vec2 glp = tf * 2.0 / canvasSize - 1.0;
|
||||
gl_Position = vec4(glp.x, -glp.y, 0.0, 1.0);
|
||||
}
|
||||
`
|
||||
var unifiedFS = `
|
||||
#ifdef GL_ES
|
||||
precision mediump float;
|
||||
#endif
|
||||
|
||||
varying vec2 v_cp, v_tc;
|
||||
|
||||
uniform int func;
|
||||
|
||||
uniform vec4 color;
|
||||
uniform float globalAlpha;
|
||||
|
||||
uniform sampler2D gradient;
|
||||
uniform vec2 from, dir, to;
|
||||
uniform float len, radFrom, radTo;
|
||||
|
||||
uniform vec2 imageSize;
|
||||
uniform sampler2D image;
|
||||
uniform mat3 imageTransform;
|
||||
uniform vec2 repeat;
|
||||
|
||||
uniform bool useAlphaTex;
|
||||
uniform sampler2D alphaTex;
|
||||
|
||||
uniform int boxSize;
|
||||
uniform bool boxVertical;
|
||||
uniform float boxScale;
|
||||
uniform float boxOffset;
|
||||
|
||||
bool isNaN(float v) {
|
||||
return v < 0.0 || 0.0 < v || v == 0.0 ? false : true;
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec4 col = color;
|
||||
|
||||
if (func == 5) {
|
||||
vec4 sum = vec4(0.0);
|
||||
if (boxVertical) {
|
||||
vec2 start = v_tc - vec2(0.0, (float(boxSize) * 0.5 + boxOffset) * boxScale);
|
||||
for (int i=0; i <= boxSize; i++) {
|
||||
sum += texture2D(image, start + vec2(0.0, float(i) * boxScale));
|
||||
}
|
||||
} else {
|
||||
vec2 start = v_tc - vec2((float(boxSize) * 0.5 + boxOffset) * boxScale, 0.0);
|
||||
for (int i=0; i <= boxSize; i++) {
|
||||
sum += texture2D(image, start + vec2(float(i) * boxScale, 0.0));
|
||||
}
|
||||
}
|
||||
gl_FragColor = sum / float(boxSize+1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (func == 1) {
|
||||
vec2 v = v_cp - from;
|
||||
float r = dot(v, dir) / len;
|
||||
r = clamp(r, 0.0, 1.0);
|
||||
col = texture2D(gradient, vec2(r, 0.0));
|
||||
} else if (func == 2) {
|
||||
float o_a = 0.5 * sqrt(
|
||||
pow(-2.0*from.x*from.x+2.0*from.x*to.x+2.0*from.x*v_cp.x-2.0*to.x*v_cp.x-2.0*from.y*from.y+2.0*from.y*to.y+2.0*from.y*v_cp.y-2.0*to.y*v_cp.y+2.0*radFrom*radFrom-2.0*radFrom*radTo, 2.0)
|
||||
-4.0*(from.x*from.x-2.0*from.x*v_cp.x+v_cp.x*v_cp.x+from.y*from.y-2.0*from.y*v_cp.y+v_cp.y*v_cp.y-radFrom*radFrom)
|
||||
*(from.x*from.x-2.0*from.x*to.x+to.x*to.x+from.y*from.y-2.0*from.y*to.y+to.y*to.y-radFrom*radFrom+2.0*radFrom*radTo-radTo*radTo)
|
||||
);
|
||||
float o_b = (from.x*from.x-from.x*to.x-from.x*v_cp.x+to.x*v_cp.x+from.y*from.y-from.y*to.y-from.y*v_cp.y+to.y*v_cp.y-radFrom*radFrom+radFrom*radTo);
|
||||
float o_c = (from.x*from.x-2.0*from.x*to.x+to.x*to.x+from.y*from.y-2.0*from.y*to.y+to.y*to.y-radFrom*radFrom+2.0*radFrom*radTo-radTo*radTo);
|
||||
float o1 = (-o_a + o_b) / o_c;
|
||||
float o2 = (o_a + o_b) / o_c;
|
||||
if (isNaN(o1) && isNaN(o2)) {
|
||||
gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);
|
||||
return;
|
||||
}
|
||||
float o = max(o1, o2);
|
||||
o = clamp(o, 0.0, 1.0);
|
||||
col = texture2D(gradient, vec2(o, 0.0));
|
||||
} else if (func == 3) {
|
||||
vec3 tfpt = vec3(v_cp, 1.0) * imageTransform;
|
||||
vec2 imgpt = tfpt.xy / imageSize;
|
||||
col = texture2D(image, mod(imgpt, 1.0));
|
||||
if (imgpt.x < 0.0 || imgpt.x > 1.0) {
|
||||
col *= repeat.x;
|
||||
}
|
||||
if (imgpt.y < 0.0 || imgpt.y > 1.0) {
|
||||
col *= repeat.y;
|
||||
}
|
||||
} else if (func == 4) {
|
||||
col = texture2D(image, v_tc);
|
||||
}
|
||||
|
||||
if (useAlphaTex) {
|
||||
col.a *= texture2D(alphaTex, v_tc).a * globalAlpha;
|
||||
} else {
|
||||
col.a *= globalAlpha;
|
||||
}
|
||||
|
||||
gl_FragColor = col;
|
||||
}
|
||||
`
|
||||
|
||||
const (
|
||||
shdFuncSolid int = iota
|
||||
shdFuncLinearGradient
|
||||
shdFuncRadialGradient
|
||||
shdFuncImagePattern
|
||||
shdFuncImage
|
||||
shdFuncBoxBlur
|
||||
)
|
||||
|
||||
type unifiedShader struct {
|
||||
shaderProgram
|
||||
|
||||
Vertex gl.Attrib
|
||||
TexCoord gl.Attrib
|
||||
|
||||
CanvasSize gl.Uniform
|
||||
Matrix gl.Uniform
|
||||
Color gl.Uniform
|
||||
GlobalAlpha gl.Uniform
|
||||
|
||||
Func gl.Uniform
|
||||
|
||||
UseAlphaTex gl.Uniform
|
||||
AlphaTex gl.Uniform
|
||||
|
||||
Gradient gl.Uniform
|
||||
From, To, Dir gl.Uniform
|
||||
Len gl.Uniform
|
||||
RadFrom, RadTo gl.Uniform
|
||||
|
||||
ImageSize gl.Uniform
|
||||
Image gl.Uniform
|
||||
ImageTransform gl.Uniform
|
||||
Repeat gl.Uniform
|
||||
|
||||
BoxSize gl.Uniform
|
||||
BoxVertical gl.Uniform
|
||||
BoxScale gl.Uniform
|
||||
BoxOffset gl.Uniform
|
||||
}
|
|
@ -1,402 +0,0 @@
|
|||
package xmobilebackend
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"unsafe"
|
||||
|
||||
"github.com/tfriedel6/canvas/backend/backendbase"
|
||||
"golang.org/x/mobile/gl"
|
||||
)
|
||||
|
||||
const alphaTexSize = 2048
|
||||
|
||||
var zeroes [alphaTexSize]byte
|
||||
|
||||
// GLContext is a context that contains all the
|
||||
// shaders and buffers necessary for rendering
|
||||
type GLContext struct {
|
||||
glctx gl.Context
|
||||
|
||||
buf gl.Buffer
|
||||
shadowBuf gl.Buffer
|
||||
alphaTex gl.Texture
|
||||
|
||||
shd unifiedShader
|
||||
|
||||
offscr1 offscreenBuffer
|
||||
offscr2 offscreenBuffer
|
||||
|
||||
imageBufTex gl.Texture
|
||||
imageBuf []byte
|
||||
|
||||
ptsBuf []float32
|
||||
}
|
||||
|
||||
// NewGLContext creates all the necessary GL resources,
|
||||
// like shaders and buffers
|
||||
func NewGLContext(glctx gl.Context) (*GLContext, error) {
|
||||
ctx := &GLContext{
|
||||
glctx: glctx,
|
||||
|
||||
ptsBuf: make([]float32, 0, 4096),
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
b := &XMobileBackend{GLContext: ctx}
|
||||
|
||||
b.glctx.GetError() // clear error state
|
||||
|
||||
err = loadShader(b, unifiedVS, unifiedFS, &ctx.shd.shaderProgram)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ctx.shd.shaderProgram.mustLoadLocations(&ctx.shd)
|
||||
if err = glError(b); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ctx.buf = b.glctx.CreateBuffer()
|
||||
if err = glError(b); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ctx.shadowBuf = b.glctx.CreateBuffer()
|
||||
if err = glError(b); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
b.glctx.ActiveTexture(gl.TEXTURE0)
|
||||
ctx.alphaTex = b.glctx.CreateTexture()
|
||||
b.glctx.BindTexture(gl.TEXTURE_2D, ctx.alphaTex)
|
||||
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
|
||||
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
|
||||
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)
|
||||
b.glctx.TexImage2D(gl.TEXTURE_2D, 0, gl.ALPHA, alphaTexSize, alphaTexSize, gl.ALPHA, gl.UNSIGNED_BYTE, nil)
|
||||
// todo should use gl.RED on OpenGL, gl.ALPHA on OpenGL ES
|
||||
|
||||
b.glctx.Enable(gl.BLEND)
|
||||
b.glctx.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
|
||||
b.glctx.Enable(gl.STENCIL_TEST)
|
||||
b.glctx.StencilMask(0xFF)
|
||||
b.glctx.Clear(gl.STENCIL_BUFFER_BIT)
|
||||
b.glctx.StencilOp(gl.KEEP, gl.KEEP, gl.KEEP)
|
||||
b.glctx.StencilFunc(gl.EQUAL, 0, 0xFF)
|
||||
|
||||
b.glctx.Disable(gl.SCISSOR_TEST)
|
||||
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
// XMobileBackend is a canvas backend using Go-GL
|
||||
type XMobileBackend struct {
|
||||
x, y, w, h int
|
||||
fx, fy, fw, fh float64
|
||||
|
||||
*GLContext
|
||||
|
||||
activateFn func()
|
||||
disableTextureRenderTarget func()
|
||||
}
|
||||
|
||||
type offscreenBuffer struct {
|
||||
tex gl.Texture
|
||||
w int
|
||||
h int
|
||||
renderStencilBuf gl.Renderbuffer
|
||||
frameBuf gl.Framebuffer
|
||||
alpha bool
|
||||
}
|
||||
|
||||
// New returns a new canvas backend. x, y, w, h define the target
|
||||
// rectangle in the window. ctx is a GLContext created with
|
||||
// NewGLContext
|
||||
func New(x, y, w, h int, ctx *GLContext) (*XMobileBackend, error) {
|
||||
b := &XMobileBackend{
|
||||
w: w,
|
||||
h: h,
|
||||
fw: float64(w),
|
||||
fh: float64(h),
|
||||
GLContext: ctx,
|
||||
}
|
||||
|
||||
b.activateFn = func() {
|
||||
b.glctx.BindFramebuffer(gl.FRAMEBUFFER, gl.Framebuffer{Value: 0})
|
||||
b.glctx.Viewport(b.x, b.y, b.w, b.h)
|
||||
// todo reapply clipping since another application may have used the stencil buffer
|
||||
}
|
||||
b.disableTextureRenderTarget = func() {
|
||||
b.glctx.BindFramebuffer(gl.FRAMEBUFFER, gl.Framebuffer{Value: 0})
|
||||
b.glctx.Viewport(b.x, b.y, b.w, b.h)
|
||||
}
|
||||
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// XMobileBackendOffscreen is a canvas backend using an offscreen
|
||||
// texture
|
||||
type XMobileBackendOffscreen struct {
|
||||
XMobileBackend
|
||||
|
||||
TextureID gl.Texture
|
||||
|
||||
offscrBuf offscreenBuffer
|
||||
offscrImg Image
|
||||
}
|
||||
|
||||
// NewOffscreen returns a new offscreen canvas backend. w, h define
|
||||
// the size of the offscreen texture. ctx is a GLContext created
|
||||
// with NewGLContext
|
||||
func NewOffscreen(w, h int, alpha bool, ctx *GLContext) (*XMobileBackendOffscreen, error) {
|
||||
b, err := New(0, 0, w, h, ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bo := &XMobileBackendOffscreen{XMobileBackend: *b}
|
||||
bo.offscrBuf.alpha = alpha
|
||||
bo.offscrImg.flip = true
|
||||
|
||||
bo.activateFn = func() {
|
||||
bo.enableTextureRenderTarget(&bo.offscrBuf)
|
||||
b.glctx.Viewport(0, 0, bo.w, bo.h)
|
||||
bo.offscrImg.w = bo.offscrBuf.w
|
||||
bo.offscrImg.h = bo.offscrBuf.h
|
||||
bo.offscrImg.tex = bo.offscrBuf.tex
|
||||
bo.TextureID = bo.offscrBuf.tex
|
||||
}
|
||||
bo.disableTextureRenderTarget = func() {
|
||||
bo.enableTextureRenderTarget(&bo.offscrBuf)
|
||||
}
|
||||
|
||||
return bo, nil
|
||||
}
|
||||
|
||||
// SetBounds updates the bounds of the canvas. This would
|
||||
// usually be called for example when the window is resized
|
||||
func (b *XMobileBackend) SetBounds(x, y, w, h int) {
|
||||
b.x, b.y = x, y
|
||||
b.fx, b.fy = float64(x), float64(y)
|
||||
b.w, b.h = w, h
|
||||
b.fw, b.fh = float64(w), float64(h)
|
||||
if b == activeContext {
|
||||
b.glctx.Viewport(0, 0, b.w, b.h)
|
||||
b.glctx.Clear(gl.STENCIL_BUFFER_BIT)
|
||||
}
|
||||
}
|
||||
|
||||
// SetSize updates the size of the offscreen texture
|
||||
func (b *XMobileBackendOffscreen) SetSize(w, h int) {
|
||||
b.XMobileBackend.SetBounds(0, 0, w, h)
|
||||
b.offscrImg.w = b.offscrBuf.w
|
||||
b.offscrImg.h = b.offscrBuf.h
|
||||
}
|
||||
|
||||
// Size returns the size of the window or offscreen
|
||||
// texture
|
||||
func (b *XMobileBackend) Size() (int, int) {
|
||||
return b.w, b.h
|
||||
}
|
||||
|
||||
func glError(b *XMobileBackend) error {
|
||||
glErr := b.glctx.GetError()
|
||||
if glErr != gl.NO_ERROR {
|
||||
return fmt.Errorf("GL Error: %x", glErr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Activate only needs to be called if there is other
|
||||
// code also using the GL state
|
||||
func (b *XMobileBackend) Activate() {
|
||||
b.activate()
|
||||
}
|
||||
|
||||
var activeContext *XMobileBackend
|
||||
|
||||
func (b *XMobileBackend) activate() {
|
||||
if activeContext != b {
|
||||
activeContext = b
|
||||
b.activateFn()
|
||||
}
|
||||
}
|
||||
|
||||
// Delete deletes the offscreen texture. After calling this
|
||||
// the backend can no longer be used
|
||||
func (b *XMobileBackendOffscreen) Delete() {
|
||||
b.glctx.DeleteTexture(b.offscrBuf.tex)
|
||||
b.glctx.DeleteFramebuffer(b.offscrBuf.frameBuf)
|
||||
b.glctx.DeleteRenderbuffer(b.offscrBuf.renderStencilBuf)
|
||||
}
|
||||
|
||||
// CanUseAsImage returns true if the given backend can be
|
||||
// directly used by this backend to avoid a conversion.
|
||||
// Used internally
|
||||
func (b *XMobileBackend) CanUseAsImage(b2 backendbase.Backend) bool {
|
||||
_, ok := b2.(*XMobileBackendOffscreen)
|
||||
return ok
|
||||
}
|
||||
|
||||
// AsImage returns nil, since this backend cannot be directly
|
||||
// used as an image. Used internally
|
||||
func (b *XMobileBackend) AsImage() backendbase.Image {
|
||||
return nil
|
||||
}
|
||||
|
||||
// AsImage returns an implementation of the Image interface
|
||||
// that can be used to render this offscreen texture
|
||||
// directly. Used internally
|
||||
func (b *XMobileBackendOffscreen) AsImage() backendbase.Image {
|
||||
return &b.offscrImg
|
||||
}
|
||||
|
||||
func (b *XMobileBackend) useShader(style *backendbase.FillStyle, tf [9]float32, useAlpha bool, alphaTexSlot int) (vertexLoc, alphaTexCoordLoc gl.Attrib) {
|
||||
b.glctx.UseProgram(b.shd.ID)
|
||||
b.glctx.Uniform2f(b.shd.CanvasSize, float32(b.fw), float32(b.fh))
|
||||
b.glctx.UniformMatrix3fv(b.shd.Matrix, tf[:])
|
||||
if useAlpha {
|
||||
b.glctx.Uniform1i(b.shd.UseAlphaTex, 1)
|
||||
b.glctx.Uniform1i(b.shd.AlphaTex, alphaTexSlot)
|
||||
} else {
|
||||
b.glctx.Uniform1i(b.shd.UseAlphaTex, 0)
|
||||
}
|
||||
b.glctx.Uniform1f(b.shd.GlobalAlpha, float32(style.Color.A)/255)
|
||||
|
||||
if lg := style.LinearGradient; lg != nil {
|
||||
lg := lg.(*LinearGradient)
|
||||
b.glctx.ActiveTexture(gl.TEXTURE0)
|
||||
b.glctx.BindTexture(gl.TEXTURE_2D, lg.tex)
|
||||
from := backendbase.Vec{style.Gradient.X0, style.Gradient.Y0}
|
||||
to := backendbase.Vec{style.Gradient.X1, style.Gradient.Y1}
|
||||
dir := to.Sub(from)
|
||||
length := dir.Len()
|
||||
dir = dir.Mulf(1 / length)
|
||||
b.glctx.Uniform2f(b.shd.From, float32(from[0]), float32(from[1]))
|
||||
b.glctx.Uniform2f(b.shd.Dir, float32(dir[0]), float32(dir[1]))
|
||||
b.glctx.Uniform1f(b.shd.Len, float32(length))
|
||||
b.glctx.Uniform1i(b.shd.Gradient, 0)
|
||||
b.glctx.Uniform1i(b.shd.Func, shdFuncLinearGradient)
|
||||
return b.shd.Vertex, b.shd.TexCoord
|
||||
}
|
||||
if rg := style.RadialGradient; rg != nil {
|
||||
rg := rg.(*RadialGradient)
|
||||
b.glctx.ActiveTexture(gl.TEXTURE0)
|
||||
b.glctx.BindTexture(gl.TEXTURE_2D, rg.tex)
|
||||
b.glctx.Uniform2f(b.shd.From, float32(style.Gradient.X0), float32(style.Gradient.Y0))
|
||||
b.glctx.Uniform2f(b.shd.To, float32(style.Gradient.X1), float32(style.Gradient.Y1))
|
||||
b.glctx.Uniform1f(b.shd.RadFrom, float32(style.Gradient.RadFrom))
|
||||
b.glctx.Uniform1f(b.shd.RadTo, float32(style.Gradient.RadTo))
|
||||
b.glctx.Uniform1i(b.shd.Gradient, 0)
|
||||
b.glctx.Uniform1i(b.shd.Func, shdFuncRadialGradient)
|
||||
return b.shd.Vertex, b.shd.TexCoord
|
||||
}
|
||||
if ip := style.ImagePattern; ip != nil {
|
||||
ipd := ip.(*ImagePattern).data
|
||||
img := ipd.Image.(*Image)
|
||||
b.glctx.ActiveTexture(gl.TEXTURE0)
|
||||
b.glctx.BindTexture(gl.TEXTURE_2D, img.tex)
|
||||
b.glctx.Uniform2f(b.shd.ImageSize, float32(img.w), float32(img.h))
|
||||
b.glctx.Uniform1i(b.shd.Image, 0)
|
||||
var f32mat [9]float32
|
||||
for i, v := range ipd.Transform {
|
||||
f32mat[i] = float32(v)
|
||||
}
|
||||
b.glctx.UniformMatrix3fv(b.shd.ImageTransform, f32mat[:])
|
||||
switch ipd.Repeat {
|
||||
case backendbase.Repeat:
|
||||
b.glctx.Uniform2f(b.shd.Repeat, 1, 1)
|
||||
case backendbase.RepeatX:
|
||||
b.glctx.Uniform2f(b.shd.Repeat, 1, 0)
|
||||
case backendbase.RepeatY:
|
||||
b.glctx.Uniform2f(b.shd.Repeat, 0, 1)
|
||||
case backendbase.NoRepeat:
|
||||
b.glctx.Uniform2f(b.shd.Repeat, 0, 0)
|
||||
}
|
||||
b.glctx.Uniform1i(b.shd.Func, shdFuncImagePattern)
|
||||
return b.shd.Vertex, b.shd.TexCoord
|
||||
}
|
||||
|
||||
cr := float32(style.Color.R) / 255
|
||||
cg := float32(style.Color.G) / 255
|
||||
cb := float32(style.Color.B) / 255
|
||||
ca := float32(style.Color.A) / 255
|
||||
b.glctx.Uniform4f(b.shd.Color, cr, cg, cb, ca)
|
||||
b.glctx.Uniform1f(b.shd.GlobalAlpha, 1)
|
||||
b.glctx.Uniform1i(b.shd.Func, shdFuncSolid)
|
||||
return b.shd.Vertex, b.shd.TexCoord
|
||||
}
|
||||
|
||||
func (b *XMobileBackend) enableTextureRenderTarget(offscr *offscreenBuffer) {
|
||||
if offscr.w == b.w && offscr.h == b.h {
|
||||
b.glctx.BindFramebuffer(gl.FRAMEBUFFER, offscr.frameBuf)
|
||||
return
|
||||
}
|
||||
|
||||
if b.w == 0 || b.h == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if offscr.w != 0 && offscr.h != 0 {
|
||||
b.glctx.DeleteTexture(offscr.tex)
|
||||
b.glctx.DeleteFramebuffer(offscr.frameBuf)
|
||||
b.glctx.DeleteRenderbuffer(offscr.renderStencilBuf)
|
||||
}
|
||||
offscr.w = b.w
|
||||
offscr.h = b.h
|
||||
|
||||
b.glctx.ActiveTexture(gl.TEXTURE0)
|
||||
offscr.tex = b.glctx.CreateTexture()
|
||||
b.glctx.BindTexture(gl.TEXTURE_2D, offscr.tex)
|
||||
// todo do non-power-of-two textures work everywhere?
|
||||
if offscr.alpha {
|
||||
b.glctx.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, b.w, b.h, gl.RGBA, gl.UNSIGNED_BYTE, nil)
|
||||
} else {
|
||||
b.glctx.TexImage2D(gl.TEXTURE_2D, 0, gl.RGB, b.w, b.h, gl.RGB, gl.UNSIGNED_BYTE, nil)
|
||||
}
|
||||
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
|
||||
b.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
|
||||
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)
|
||||
|
||||
offscr.frameBuf = b.glctx.CreateFramebuffer()
|
||||
b.glctx.BindFramebuffer(gl.FRAMEBUFFER, offscr.frameBuf)
|
||||
|
||||
offscr.renderStencilBuf = b.glctx.CreateRenderbuffer()
|
||||
b.glctx.BindRenderbuffer(gl.RENDERBUFFER, offscr.renderStencilBuf)
|
||||
b.glctx.RenderbufferStorage(gl.RENDERBUFFER, gl.STENCIL_INDEX8, b.w, b.h)
|
||||
b.glctx.FramebufferRenderbuffer(gl.FRAMEBUFFER, gl.STENCIL_ATTACHMENT, gl.RENDERBUFFER, offscr.renderStencilBuf)
|
||||
|
||||
b.glctx.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, offscr.tex, 0)
|
||||
|
||||
if err := b.glctx.CheckFramebufferStatus(gl.FRAMEBUFFER); err != gl.FRAMEBUFFER_COMPLETE {
|
||||
// todo this should maybe not panic
|
||||
panic(fmt.Sprintf("Failed to set up framebuffer for offscreen texture: %x", err))
|
||||
}
|
||||
|
||||
b.glctx.Clear(gl.COLOR_BUFFER_BIT | gl.STENCIL_BUFFER_BIT)
|
||||
}
|
||||
|
||||
func mat3(m backendbase.Mat) (m3 [9]float32) {
|
||||
m3[0] = float32(m[0])
|
||||
m3[1] = float32(m[1])
|
||||
m3[2] = 0
|
||||
m3[3] = float32(m[2])
|
||||
m3[4] = float32(m[3])
|
||||
m3[5] = 0
|
||||
m3[6] = float32(m[4])
|
||||
m3[7] = float32(m[5])
|
||||
m3[8] = 1
|
||||
return
|
||||
}
|
||||
|
||||
var mat3identity = [9]float32{1, 0, 0, 0, 1, 0, 0, 0, 1}
|
||||
|
||||
func byteSlice(ptr unsafe.Pointer, size int) []byte {
|
||||
var buf []byte
|
||||
sh := (*reflect.SliceHeader)(unsafe.Pointer(&buf))
|
||||
sh.Cap = size
|
||||
sh.Len = size
|
||||
sh.Data = uintptr(ptr)
|
||||
return buf
|
||||
}
|
11
canvas.go
11
canvas.go
|
@ -9,9 +9,10 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/golang/freetype/truetype"
|
||||
"github.com/tfriedel6/canvas/backend/backendbase"
|
||||
"golang.org/x/image/font"
|
||||
"golang.org/x/image/math/fixed"
|
||||
|
||||
"git.mstar.dev/mstar/canvas/backend/backendbase"
|
||||
)
|
||||
|
||||
//go:generate go run make_shaders.go
|
||||
|
@ -460,7 +461,13 @@ func (cv *Canvas) IsPointInStroke(x, y float64) bool {
|
|||
}
|
||||
|
||||
var triBuf [500]backendbase.Vec
|
||||
tris := cv.strokeTris(&cv.path, cv.state.transform, cv.state.transform.Invert(), true, triBuf[:0])
|
||||
tris := cv.strokeTris(
|
||||
&cv.path,
|
||||
cv.state.transform,
|
||||
cv.state.transform.Invert(),
|
||||
true,
|
||||
triBuf[:0],
|
||||
)
|
||||
|
||||
pt := backendbase.Vec{x, y}
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import (
|
|||
"math"
|
||||
"sort"
|
||||
|
||||
"github.com/tfriedel6/canvas/backend/backendbase"
|
||||
"git.mstar.dev/mstar/canvas/backend/backendbase"
|
||||
)
|
||||
|
||||
// Go port of https://github.com/mapbox/earcut.hpp
|
||||
|
@ -313,7 +313,8 @@ func (ec *earcut) cureLocalIntersections(start *node) *node {
|
|||
b := p.next.next
|
||||
|
||||
// a self-intersection where edge (v[i-1],v[i]) intersects (v[i+1],v[i+2])
|
||||
if !ec.equals(a, b) && ec.intersects(a, p, p.next, b) && ec.locallyInside(a, b) && ec.locallyInside(b, a) {
|
||||
if !ec.equals(a, b) && ec.intersects(a, p, p.next, b) && ec.locallyInside(a, b) &&
|
||||
ec.locallyInside(b, a) {
|
||||
ec.indices = append(ec.indices, a.i, p.i, b.i)
|
||||
|
||||
// remove two nodes involved
|
||||
|
@ -633,7 +634,8 @@ func (ec *earcut) pointInTriangle(ax, ay, bx, by, cx, cy, px, py float64) bool {
|
|||
|
||||
// check if a diagonal between two polygon nodes is valid (lies in polygon interior)
|
||||
func (ec *earcut) isValidDiagonal(a, b *node) bool {
|
||||
return a.next.i != b.i && a.prev.i != b.i && !ec.intersectsPolygon(a, b) && // dones't intersect other edges
|
||||
return a.next.i != b.i && a.prev.i != b.i &&
|
||||
!ec.intersectsPolygon(a, b) && // dones't intersect other edges
|
||||
((ec.locallyInside(a, b) && ec.locallyInside(b, a) && ec.middleInside(a, b) && // locally visible
|
||||
(ec.area(a.prev, a, b.prev) != 0.0 || ec.area(a, b.prev, b) != 0.0)) || // does not create opposite-facing sectors
|
||||
(ec.equals(a, b) && ec.area(a.prev, a, a.next) > 0 && ec.area(b.prev, b, b.next) > 0)) // special zero-length case
|
||||
|
|
|
@ -5,8 +5,8 @@ import (
|
|||
"math"
|
||||
"os"
|
||||
|
||||
"github.com/tfriedel6/canvas"
|
||||
"github.com/tfriedel6/canvas/backend/softwarebackend"
|
||||
"git.mstar.dev/mstar/canvas"
|
||||
"git.mstar.dev/mstar/canvas/backend/softwarebackend"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
|
8
go.mod
8
go.mod
|
@ -1,14 +1,8 @@
|
|||
module github.com/tfriedel6/canvas
|
||||
module git.mstar.dev/mstar/canvas
|
||||
|
||||
require (
|
||||
github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200707082815-5321531c36a2
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
|
||||
github.com/veandco/go-sdl2 v0.4.4
|
||||
golang.org/x/exp v0.0.0-20200513190911-00229845015e
|
||||
golang.org/x/image v0.0.0-20200801110659-972c09e46d76
|
||||
golang.org/x/mobile v0.0.0-20200801112145-973feb4309de
|
||||
golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed // indirect
|
||||
)
|
||||
|
||||
go 1.13
|
||||
|
|
41
go.sum
41
go.sum
|
@ -1,46 +1,5 @@
|
|||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200707082815-5321531c36a2 h1:Ac1OEHHkbAZ6EUnJahF0GKcU0FjPc/V8F1DvjhKngFE=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200707082815-5321531c36a2/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
github.com/veandco/go-sdl2 v0.4.4 h1:coOJGftOdvNvGoUIZmm4XD+ZRQF4mg9ZVHmH3/42zFQ=
|
||||
github.com/veandco/go-sdl2 v0.4.4/go.mod h1:FB+kTpX9YTE+urhYiClnRzpOXbiWgaU3+5F2AB78DPg=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
|
||||
golang.org/x/exp v0.0.0-20200513190911-00229845015e h1:rMqLP+9XLy+LdbCXHjJHAmTfXCr93W7oruWA6Hq1Alc=
|
||||
golang.org/x/exp v0.0.0-20200513190911-00229845015e/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20200801110659-972c09e46d76 h1:U7GPaoQyQmX+CBRWXKrvRzWTbd+slqeSh8uARsIyhAw=
|
||||
golang.org/x/image v0.0.0-20200801110659-972c09e46d76/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mobile v0.0.0-20200801112145-973feb4309de h1:OVJ6QQUBAesB8CZijKDSsXX7xYVtUhrkY0gwMfbi4p4=
|
||||
golang.org/x/mobile v0.0.0-20200801112145-973feb4309de/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed h1:WBkVNH1zd9jg/dK4HCM4lNANnmd12EHC9z+LmcCG4ns=
|
||||
golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
|
|
@ -4,7 +4,7 @@ import (
|
|||
"image/color"
|
||||
"runtime"
|
||||
|
||||
"github.com/tfriedel6/canvas/backend/backendbase"
|
||||
"git.mstar.dev/mstar/canvas/backend/backendbase"
|
||||
)
|
||||
|
||||
// LinearGradient is a gradient with any number of
|
||||
|
@ -126,7 +126,11 @@ func (rg *RadialGradient) AddColorStop(pos float64, stopColor ...interface{}) {
|
|||
rg.loaded = false
|
||||
}
|
||||
|
||||
func addColorStop(stops backendbase.Gradient, pos float64, stopColor ...interface{}) (backendbase.Gradient, color.RGBA) {
|
||||
func addColorStop(
|
||||
stops backendbase.Gradient,
|
||||
pos float64,
|
||||
stopColor ...interface{},
|
||||
) (backendbase.Gradient, color.RGBA) {
|
||||
c, _ := parseColor(stopColor...)
|
||||
insert := len(stops)
|
||||
for i, stop := range stops {
|
||||
|
|
13
images.go
13
images.go
|
@ -5,12 +5,11 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/tfriedel6/canvas/backend/backendbase"
|
||||
"git.mstar.dev/mstar/canvas/backend/backendbase"
|
||||
)
|
||||
|
||||
// Image is a type holding information on an image loaded with the LoadImage
|
||||
|
@ -52,7 +51,7 @@ func (cv *Canvas) LoadImage(src interface{}) (*Image, error) {
|
|||
case image.Image:
|
||||
srcImg = v
|
||||
case string:
|
||||
data, err := ioutil.ReadFile(v)
|
||||
data, err := os.ReadFile(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -161,9 +160,11 @@ func (img *Image) Replace(src interface{}) error {
|
|||
// loaded image with the same name parameter.
|
||||
//
|
||||
// The coordinates must be one of the following:
|
||||
// DrawImage("image", dx, dy)
|
||||
// DrawImage("image", dx, dy, dw, dh)
|
||||
// DrawImage("image", sx, sy, sw, sh, dx, dy, dw, dh)
|
||||
//
|
||||
// DrawImage("image", dx, dy)
|
||||
// DrawImage("image", dx, dy, dw, dh)
|
||||
// DrawImage("image", sx, sy, sw, sh, dx, dy, dw, dh)
|
||||
//
|
||||
// Where dx/dy/dw/dh are the destination coordinates and sx/sy/sw/sh are the
|
||||
// source coordinates
|
||||
func (cv *Canvas) DrawImage(image interface{}, coords ...float64) {
|
||||
|
|
14
path2d.go
14
path2d.go
|
@ -3,7 +3,7 @@ package canvas
|
|||
import (
|
||||
"math"
|
||||
|
||||
"github.com/tfriedel6/canvas/backend/backendbase"
|
||||
"git.mstar.dev/mstar/canvas/backend/backendbase"
|
||||
)
|
||||
|
||||
// Path2D is a type that holds a predefined path which can be drawn
|
||||
|
@ -135,7 +135,12 @@ func (p *Path2D) Arc(x, y, radius, startAngle, endAngle float64, anticlockwise b
|
|||
p.arc(x, y, radius, startAngle, endAngle, anticlockwise, backendbase.MatIdentity, true)
|
||||
}
|
||||
|
||||
func (p *Path2D) arc(x, y, radius, startAngle, endAngle float64, anticlockwise bool, m backendbase.Mat, ident bool) {
|
||||
func (p *Path2D) arc(
|
||||
x, y, radius, startAngle, endAngle float64,
|
||||
anticlockwise bool,
|
||||
m backendbase.Mat,
|
||||
ident bool,
|
||||
) {
|
||||
checkSelfIntersection := len(p.p) > 0
|
||||
|
||||
lastWasMove := len(p.p) == 0 || p.p[len(p.p)-1].flags&pathMove != 0
|
||||
|
@ -301,7 +306,10 @@ func (p *Path2D) BezierCurveTo(x1, y1, x2, y2, x3, y3 float64) {
|
|||
}
|
||||
|
||||
// Ellipse (see equivalent function on canvas type)
|
||||
func (p *Path2D) Ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle float64, anticlockwise bool) {
|
||||
func (p *Path2D) Ellipse(
|
||||
x, y, radiusX, radiusY, rotation, startAngle, endAngle float64,
|
||||
anticlockwise bool,
|
||||
) {
|
||||
checkSelfIntersection := len(p.p) > 0
|
||||
|
||||
rs, rc := math.Sincos(rotation)
|
||||
|
|
47
paths.go
47
paths.go
|
@ -3,7 +3,7 @@ package canvas
|
|||
import (
|
||||
"math"
|
||||
|
||||
"github.com/tfriedel6/canvas/backend/backendbase"
|
||||
"git.mstar.dev/mstar/canvas/backend/backendbase"
|
||||
)
|
||||
|
||||
// BeginPath clears the current path and starts a new one
|
||||
|
@ -70,7 +70,10 @@ func (cv *Canvas) BezierCurveTo(x1, y1, x2, y2, x3, y3 float64) {
|
|||
// rotation is the rotation of the ellipse in radians, startAngle and endAngle
|
||||
// are angles in radians, and anticlockwise means that the line is added
|
||||
// anticlockwise
|
||||
func (cv *Canvas) Ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle float64, anticlockwise bool) {
|
||||
func (cv *Canvas) Ellipse(
|
||||
x, y, radiusX, radiusY, rotation, startAngle, endAngle float64,
|
||||
anticlockwise bool,
|
||||
) {
|
||||
tf := cv.tf(backendbase.Vec{x, y})
|
||||
ax, ay := math.Sincos(startAngle)
|
||||
startAngle2 := backendbase.Vec{ay, ax}.MulMat2(cv.state.transform.Mat2()).Atan2()
|
||||
|
@ -113,7 +116,13 @@ func (cv *Canvas) strokePath(path *Path2D, tf backendbase.Mat, inv backendbase.M
|
|||
cv.b.Fill(&stl, tris, backendbase.MatIdentity, true)
|
||||
}
|
||||
|
||||
func (cv *Canvas) strokeTris(path *Path2D, tf backendbase.Mat, inv backendbase.Mat, doInv bool, target []backendbase.Vec) []backendbase.Vec {
|
||||
func (cv *Canvas) strokeTris(
|
||||
path *Path2D,
|
||||
tf backendbase.Mat,
|
||||
inv backendbase.Mat,
|
||||
doInv bool,
|
||||
target []backendbase.Vec,
|
||||
) []backendbase.Vec {
|
||||
if len(path.p) == 0 {
|
||||
return target
|
||||
}
|
||||
|
@ -180,7 +189,15 @@ func (cv *Canvas) strokeTris(path *Path2D, tf backendbase.Mat, inv backendbase.M
|
|||
}
|
||||
}
|
||||
|
||||
target = append(target, lp0.MulMat(tf), lp1.MulMat(tf), lp3.MulMat(tf), lp0.MulMat(tf), lp3.MulMat(tf), lp2.MulMat(tf))
|
||||
target = append(
|
||||
target,
|
||||
lp0.MulMat(tf),
|
||||
lp1.MulMat(tf),
|
||||
lp3.MulMat(tf),
|
||||
lp0.MulMat(tf),
|
||||
lp3.MulMat(tf),
|
||||
lp2.MulMat(tf),
|
||||
)
|
||||
|
||||
if p.flags&pathAttach != 0 && cv.state.lineWidth > 1 {
|
||||
target = cv.lineJoint(p0, p1, p.next, lp0, lp1, lp2, lp3, tf, target)
|
||||
|
@ -249,7 +266,11 @@ func (cv *Canvas) applyLineDash(path []pathPoint) []pathPoint {
|
|||
return path2
|
||||
}
|
||||
|
||||
func (cv *Canvas) lineJoint(p0, p1, p2, l0p0, l0p1, l0p2, l0p3 backendbase.Vec, tf backendbase.Mat, tris []backendbase.Vec) []backendbase.Vec {
|
||||
func (cv *Canvas) lineJoint(
|
||||
p0, p1, p2, l0p0, l0p1, l0p2, l0p3 backendbase.Vec,
|
||||
tf backendbase.Mat,
|
||||
tris []backendbase.Vec,
|
||||
) []backendbase.Vec {
|
||||
v2 := p1.Sub(p2).Norm()
|
||||
v3 := backendbase.Vec{v2[1], -v2[0]}.Mulf(cv.state.lineWidth * 0.5)
|
||||
|
||||
|
@ -316,7 +337,12 @@ func (cv *Canvas) lineJoint(p0, p1, p2, l0p0, l0p1, l0p2, l0p3 backendbase.Vec,
|
|||
return tris
|
||||
}
|
||||
|
||||
func (cv *Canvas) addCircleTris(center backendbase.Vec, radius float64, tf backendbase.Mat, tris []backendbase.Vec) []backendbase.Vec {
|
||||
func (cv *Canvas) addCircleTris(
|
||||
center backendbase.Vec,
|
||||
radius float64,
|
||||
tf backendbase.Mat,
|
||||
tris []backendbase.Vec,
|
||||
) []backendbase.Vec {
|
||||
step := 6 / radius
|
||||
if step > 0.8 {
|
||||
step = 0.8
|
||||
|
@ -338,7 +364,8 @@ func lineIntersection(a0, a1, b0, b1 backendbase.Vec) (backendbase.Vec, float64,
|
|||
va := a1.Sub(a0)
|
||||
vb := b1.Sub(b0)
|
||||
|
||||
if (va[0] == 0 && vb[0] == 0) || (va[1] == 0 && vb[1] == 0) || (va[0] == 0 && va[1] == 0) || (vb[0] == 0 && vb[1] == 0) {
|
||||
if (va[0] == 0 && vb[0] == 0) || (va[1] == 0 && vb[1] == 0) || (va[0] == 0 && va[1] == 0) ||
|
||||
(vb[0] == 0 && vb[1] == 0) {
|
||||
return backendbase.Vec{}, float64(math.Inf(1)), float64(math.Inf(1))
|
||||
}
|
||||
d := va[1]*vb[0] - va[0]*vb[1]
|
||||
|
@ -410,7 +437,11 @@ func (cv *Canvas) fillPath(path *Path2D, tf backendbase.Mat) {
|
|||
cv.b.Fill(&stl, tris, tf, false)
|
||||
}
|
||||
|
||||
func appendSubPathTriangles(tris []backendbase.Vec, mat backendbase.Mat, path []pathPoint) []backendbase.Vec {
|
||||
func appendSubPathTriangles(
|
||||
tris []backendbase.Vec,
|
||||
mat backendbase.Mat,
|
||||
path []pathPoint,
|
||||
) []backendbase.Vec {
|
||||
last := path[len(path)-1]
|
||||
if last.flags&pathIsConvex != 0 {
|
||||
p0, p1 := path[0].pos.MulMat(mat), path[1].pos.MulMat(mat)
|
||||
|
|
|
@ -4,7 +4,7 @@ import (
|
|||
"image"
|
||||
"math"
|
||||
|
||||
"github.com/tfriedel6/canvas/backend/backendbase"
|
||||
"git.mstar.dev/mstar/canvas/backend/backendbase"
|
||||
)
|
||||
|
||||
func (cv *Canvas) drawShadow(pts []backendbase.Vec, mask *image.Alpha, canOverlap bool) {
|
||||
|
|
64
text.go
64
text.go
|
@ -5,7 +5,6 @@ import (
|
|||
"fmt"
|
||||
"image"
|
||||
"image/draw"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"os"
|
||||
"time"
|
||||
|
@ -13,9 +12,10 @@ import (
|
|||
|
||||
"github.com/golang/freetype"
|
||||
"github.com/golang/freetype/truetype"
|
||||
"github.com/tfriedel6/canvas/backend/backendbase"
|
||||
"golang.org/x/image/font"
|
||||
"golang.org/x/image/math/fixed"
|
||||
|
||||
"git.mstar.dev/mstar/canvas/backend/backendbase"
|
||||
)
|
||||
|
||||
// Font is a loaded font that can be passed to the
|
||||
|
@ -84,7 +84,7 @@ func (cv *Canvas) LoadFont(src interface{}) (*Font, error) {
|
|||
case *truetype.Font:
|
||||
f = &Font{font: v}
|
||||
case string:
|
||||
data, err := ioutil.ReadFile(v)
|
||||
data, err := os.ReadFile(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -174,7 +174,8 @@ func (cv *Canvas) FillText(str string, x, y float64) {
|
|||
}
|
||||
|
||||
// make sure textImage is large enough for the rendered string
|
||||
if textImage == nil || textImage.Bounds().Dx() < strWidth || textImage.Bounds().Dy() < strHeight {
|
||||
if textImage == nil || textImage.Bounds().Dx() < strWidth ||
|
||||
textImage.Bounds().Dy() < strHeight {
|
||||
var size int
|
||||
for size = 2; size < alphaTexSize; size *= 2 {
|
||||
if size >= strWidth && size >= strHeight {
|
||||
|
@ -229,10 +230,27 @@ func (cv *Canvas) FillText(str string, x, y float64) {
|
|||
|
||||
// render textImage to the screen
|
||||
var pts [4]backendbase.Vec
|
||||
pts[0] = cv.tf(backendbase.Vec{float64(textOffset.X)/scale + x, float64(textOffset.Y)/scale + y})
|
||||
pts[1] = cv.tf(backendbase.Vec{float64(textOffset.X)/scale + x, float64(textOffset.Y)/scale + float64(strHeight)/scale + y})
|
||||
pts[2] = cv.tf(backendbase.Vec{float64(textOffset.X)/scale + float64(strWidth)/scale + x, float64(textOffset.Y)/scale + float64(strHeight)/scale + y})
|
||||
pts[3] = cv.tf(backendbase.Vec{float64(textOffset.X)/scale + float64(strWidth)/scale + x, float64(textOffset.Y)/scale + y})
|
||||
pts[0] = cv.tf(
|
||||
backendbase.Vec{float64(textOffset.X)/scale + x, float64(textOffset.Y)/scale + y},
|
||||
)
|
||||
pts[1] = cv.tf(
|
||||
backendbase.Vec{
|
||||
float64(textOffset.X)/scale + x,
|
||||
float64(textOffset.Y)/scale + float64(strHeight)/scale + y,
|
||||
},
|
||||
)
|
||||
pts[2] = cv.tf(
|
||||
backendbase.Vec{
|
||||
float64(textOffset.X)/scale + float64(strWidth)/scale + x,
|
||||
float64(textOffset.Y)/scale + float64(strHeight)/scale + y,
|
||||
},
|
||||
)
|
||||
pts[3] = cv.tf(
|
||||
backendbase.Vec{
|
||||
float64(textOffset.X)/scale + float64(strWidth)/scale + x,
|
||||
float64(textOffset.Y)/scale + y,
|
||||
},
|
||||
)
|
||||
|
||||
mask := textImage.SubImage(image.Rect(0, 0, strWidth, strHeight)).(*image.Alpha)
|
||||
|
||||
|
@ -335,7 +353,12 @@ func (cv *Canvas) StrokeText(str string, x, y float64) {
|
|||
|
||||
}
|
||||
|
||||
func (cv *Canvas) measureTextRendering(str string, x, y *float64, frc *frContext, scale float64) (int, int, image.Point, string) {
|
||||
func (cv *Canvas) measureTextRendering(
|
||||
str string,
|
||||
x, y *float64,
|
||||
frc *frContext,
|
||||
scale float64,
|
||||
) (int, int, image.Point, string) {
|
||||
// measure rendered text size
|
||||
var p fixed.Point26_6
|
||||
prev, hasPrev := truetype.Index(0), false
|
||||
|
@ -423,10 +446,18 @@ func (cv *Canvas) measureTextRendering(str string, x, y *float64, frc *frContext
|
|||
w, h := cv.b.Size()
|
||||
fw, fh := float64(w), float64(h)
|
||||
|
||||
p0 := cv.tf(backendbase.Vec{float64(bounds.Min.X)/scale + curX, float64(strMinY)/scale + *y})
|
||||
p1 := cv.tf(backendbase.Vec{float64(bounds.Min.X)/scale + curX, float64(strMaxY)/scale + *y})
|
||||
p2 := cv.tf(backendbase.Vec{float64(bounds.Max.X)/scale + curX, float64(strMaxY)/scale + *y})
|
||||
p3 := cv.tf(backendbase.Vec{float64(bounds.Max.X)/scale + curX, float64(strMinY)/scale + *y})
|
||||
p0 := cv.tf(
|
||||
backendbase.Vec{float64(bounds.Min.X)/scale + curX, float64(strMinY)/scale + *y},
|
||||
)
|
||||
p1 := cv.tf(
|
||||
backendbase.Vec{float64(bounds.Min.X)/scale + curX, float64(strMaxY)/scale + *y},
|
||||
)
|
||||
p2 := cv.tf(
|
||||
backendbase.Vec{float64(bounds.Max.X)/scale + curX, float64(strMaxY)/scale + *y},
|
||||
)
|
||||
p3 := cv.tf(
|
||||
backendbase.Vec{float64(bounds.Max.X)/scale + curX, float64(strMinY)/scale + *y},
|
||||
)
|
||||
inside := (p0[0] >= 0 || p1[0] >= 0 || p2[0] >= 0 || p3[0] >= 0) &&
|
||||
(p0[1] >= 0 || p1[1] >= 0 || p2[1] >= 0 || p3[1] >= 0) &&
|
||||
(p0[0] < fw || p1[0] < fw || p2[0] < fw || p3[0] < fw) &&
|
||||
|
@ -628,7 +659,12 @@ func (cv *Canvas) runeTris(rn rune) []backendbase.Vec {
|
|||
}
|
||||
|
||||
p0, on0 := start, true
|
||||
path := &Path2D{cv: cv, p: make([]pathPoint, 0, 50), standalone: true, noSelfIntersection: true}
|
||||
path := &Path2D{
|
||||
cv: cv,
|
||||
p: make([]pathPoint, 0, 50),
|
||||
standalone: true,
|
||||
noSelfIntersection: true,
|
||||
}
|
||||
path.MoveTo(float64(p0.X)*scale, -float64(p0.Y)*scale)
|
||||
for _, p := range others {
|
||||
on := p.Flags&0x01 != 0
|
||||
|
|
|
@ -4,7 +4,7 @@ import (
|
|||
"math"
|
||||
"sort"
|
||||
|
||||
"github.com/tfriedel6/canvas/backend/backendbase"
|
||||
"git.mstar.dev/mstar/canvas/backend/backendbase"
|
||||
)
|
||||
|
||||
const samePointTolerance = 1e-20
|
||||
|
@ -97,7 +97,11 @@ func polygonContainsPoint(polygon []backendbase.Vec, p backendbase.Vec) bool {
|
|||
return count%2 == 1
|
||||
}
|
||||
|
||||
func triangulatePath(path []pathPoint, mat backendbase.Mat, target []backendbase.Vec) []backendbase.Vec {
|
||||
func triangulatePath(
|
||||
path []pathPoint,
|
||||
mat backendbase.Mat,
|
||||
target []backendbase.Vec,
|
||||
) []backendbase.Vec {
|
||||
if path[0].pos == path[len(path)-1].pos {
|
||||
path = path[:len(path)-1]
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue