Compare commits

...

1 commit

Author SHA1 Message Date
fb1b1c09ec Fix module naming 2025-04-10 14:12:26 +02:00
25 changed files with 221 additions and 2129 deletions

View file

@ -5,7 +5,7 @@ import (
"image/color" "image/color"
"math" "math"
"github.com/tfriedel6/canvas/backend/backendbase" "git.mstar.dev/mstar/canvas/backend/backendbase"
) )
func (b *SoftwareBackend) Clear(pts [4]backendbase.Vec) { 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) ffn := fillFunc(style)
var triBuf [500]backendbase.Vec 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) ffn := fillFunc(style)
mw := float64(mask.Bounds().Dx()) 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 { if lg := style.LinearGradient; lg != nil {
lg := lg.(*LinearGradient) lg := lg.(*LinearGradient)
from := backendbase.Vec{style.Gradient.X0, style.Gradient.Y0} 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]) dirlen := math.Sqrt(dir[0]*dir[0] + dir[1]*dir[1])
dir[0] /= dirlen dir[0] /= dirlen
dir[1] /= dirlen dir[1] /= dirlen

View file

@ -5,7 +5,7 @@ import (
"image/color" "image/color"
"math" "math"
"github.com/tfriedel6/canvas/backend/backendbase" "git.mstar.dev/mstar/canvas/backend/backendbase"
) )
type Image struct { type Image struct {
@ -43,7 +43,12 @@ func halveImage(img image.Image) (*image.RGBA, int, int) {
return rimg, w, h 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) simg := dimg.(*Image)
if simg.deleted { if simg.deleted {
return return
@ -130,7 +135,9 @@ type ImagePattern struct {
data backendbase.ImagePatternData data backendbase.ImagePatternData
} }
func (b *SoftwareBackend) LoadImagePattern(data backendbase.ImagePatternData) backendbase.ImagePattern { func (b *SoftwareBackend) LoadImagePattern(
data backendbase.ImagePatternData,
) backendbase.ImagePattern {
return &ImagePattern{ return &ImagePattern{
data: data, data: data,
} }

View file

@ -4,7 +4,7 @@ import (
"image" "image"
"image/draw" "image/draw"
"github.com/tfriedel6/canvas/backend/backendbase" "git.mstar.dev/mstar/canvas/backend/backendbase"
) )
type SoftwareBackend struct { type SoftwareBackend struct {

View file

@ -4,7 +4,7 @@ import (
"image/color" "image/color"
"math" "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) { func triangleLR(tri []backendbase.Vec, y float64) (l, r float64, outside bool) {
@ -96,7 +96,12 @@ type msaaPixel struct {
tx, ty float64 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) msaaStep := 1.0 / float64(msaaLevel+1)
minY := int(math.Floor(math.Min(math.Min(tri[0][1], tri[1][1]), tri[2][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 sx := float64(x) + msaaStep*0.5
for stepx := 0; stepx <= msaaLevel; stepx++ { for stepx := 0; stepx <= msaaLevel; stepx++ {
if sx >= l[stepy] && sx < r[stepy] { 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 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)) { 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])))) minY := int(
maxY := int(math.Ceil(math.Max(math.Max(quad[0][1], quad[1][1]), math.Max(quad[2][1], quad[3][1])))) 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 { if minY < 0 {
minY = 0 minY = 0
} else if minY >= b.h { } 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) 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])))) minY := int(
maxY := int(math.Ceil(math.Max(math.Max(quad[0][1], quad[1][1]), math.Max(quad[2][1], quad[3][1])))) 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 { if minY < 0 {
minY = 0 minY = 0
} else if minY >= b.h { } 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] 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 sx += msaaStep
} }
@ -402,7 +433,10 @@ func (b *SoftwareBackend) fillQuadMSAA(quad [4]backendbase.Vec, msaaLevel int, m
return msaaPixels 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() b.clearStencil()
if b.MSAA > 0 { 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) samples := (b.MSAA + 1) * (b.MSAA + 1)
for i, px := range msaaPixels { 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 continue
} }
b.stencil.SetAlpha(px.ix, px.iy, color.Alpha{A: 255}) 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) { iterateTriangles(pts[:], func(tri []backendbase.Vec) {
b.fillTriangleNoAA(tri, func(x, y int) { b.fillTriangleNoAA(tri, func(x, y int) {
if b.clip.AlphaAt(x, y).A == 0 { 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 var msaaPixelBuf [500]msaaPixel
msaaPixels := msaaPixelBuf[:0] msaaPixels := msaaPixelBuf[:0]
@ -530,7 +572,8 @@ func (b *SoftwareBackend) fillTrianglesMSAA(pts []backendbase.Vec, msaaLevel int
samples := (msaaLevel + 1) * (msaaLevel + 1) samples := (msaaLevel + 1) * (msaaLevel + 1)
for i, px := range msaaPixels { 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 continue
} }
b.stencil.SetAlpha(px.ix, px.iy, color.Alpha{A: 255}) b.stencil.SetAlpha(px.ix, px.iy, color.Alpha{A: 255})

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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
}

View file

@ -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:])
}

View file

@ -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)
}

View file

@ -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 }

View file

@ -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)
}
}

View file

@ -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
}

View file

@ -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
}

View file

@ -9,9 +9,10 @@ import (
"time" "time"
"github.com/golang/freetype/truetype" "github.com/golang/freetype/truetype"
"github.com/tfriedel6/canvas/backend/backendbase"
"golang.org/x/image/font" "golang.org/x/image/font"
"golang.org/x/image/math/fixed" "golang.org/x/image/math/fixed"
"git.mstar.dev/mstar/canvas/backend/backendbase"
) )
//go:generate go run make_shaders.go //go:generate go run make_shaders.go
@ -460,7 +461,13 @@ func (cv *Canvas) IsPointInStroke(x, y float64) bool {
} }
var triBuf [500]backendbase.Vec 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} pt := backendbase.Vec{x, y}

View file

@ -4,7 +4,7 @@ import (
"math" "math"
"sort" "sort"
"github.com/tfriedel6/canvas/backend/backendbase" "git.mstar.dev/mstar/canvas/backend/backendbase"
) )
// Go port of https://github.com/mapbox/earcut.hpp // Go port of https://github.com/mapbox/earcut.hpp
@ -313,7 +313,8 @@ func (ec *earcut) cureLocalIntersections(start *node) *node {
b := p.next.next b := p.next.next
// a self-intersection where edge (v[i-1],v[i]) intersects (v[i+1],v[i+2]) // 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) ec.indices = append(ec.indices, a.i, p.i, b.i)
// remove two nodes involved // 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) // check if a diagonal between two polygon nodes is valid (lies in polygon interior)
func (ec *earcut) isValidDiagonal(a, b *node) bool { 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.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.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 (ec.equals(a, b) && ec.area(a.prev, a, a.next) > 0 && ec.area(b.prev, b, b.next) > 0)) // special zero-length case

View file

@ -5,8 +5,8 @@ import (
"math" "math"
"os" "os"
"github.com/tfriedel6/canvas" "git.mstar.dev/mstar/canvas"
"github.com/tfriedel6/canvas/backend/softwarebackend" "git.mstar.dev/mstar/canvas/backend/softwarebackend"
) )
func main() { func main() {

8
go.mod
View file

@ -1,14 +1,8 @@
module github.com/tfriedel6/canvas module git.mstar.dev/mstar/canvas
require ( 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/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/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 go 1.13

41
go.sum
View file

@ -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 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= 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 h1:U7GPaoQyQmX+CBRWXKrvRzWTbd+slqeSh8uARsIyhAw=
golang.org/x/image v0.0.0-20200801110659-972c09e46d76/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 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/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=

View file

@ -4,7 +4,7 @@ import (
"image/color" "image/color"
"runtime" "runtime"
"github.com/tfriedel6/canvas/backend/backendbase" "git.mstar.dev/mstar/canvas/backend/backendbase"
) )
// LinearGradient is a gradient with any number of // LinearGradient is a gradient with any number of
@ -126,7 +126,11 @@ func (rg *RadialGradient) AddColorStop(pos float64, stopColor ...interface{}) {
rg.loaded = false 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...) c, _ := parseColor(stopColor...)
insert := len(stops) insert := len(stops)
for i, stop := range stops { for i, stop := range stops {

View file

@ -5,12 +5,11 @@ import (
"errors" "errors"
"fmt" "fmt"
"image" "image"
"io/ioutil"
"os" "os"
"strings" "strings"
"time" "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 // 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: case image.Image:
srcImg = v srcImg = v
case string: case string:
data, err := ioutil.ReadFile(v) data, err := os.ReadFile(v)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -161,9 +160,11 @@ func (img *Image) Replace(src interface{}) error {
// loaded image with the same name parameter. // loaded image with the same name parameter.
// //
// The coordinates must be one of the following: // The coordinates must be one of the following:
// DrawImage("image", dx, dy) //
// DrawImage("image", dx, dy, dw, dh) // DrawImage("image", dx, dy)
// DrawImage("image", sx, sy, sw, sh, dx, dy, dw, dh) // 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 // Where dx/dy/dw/dh are the destination coordinates and sx/sy/sw/sh are the
// source coordinates // source coordinates
func (cv *Canvas) DrawImage(image interface{}, coords ...float64) { func (cv *Canvas) DrawImage(image interface{}, coords ...float64) {

View file

@ -3,7 +3,7 @@ package canvas
import ( import (
"math" "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 // 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) 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 checkSelfIntersection := len(p.p) > 0
lastWasMove := len(p.p) == 0 || p.p[len(p.p)-1].flags&pathMove != 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) // 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 checkSelfIntersection := len(p.p) > 0
rs, rc := math.Sincos(rotation) rs, rc := math.Sincos(rotation)

View file

@ -3,7 +3,7 @@ package canvas
import ( import (
"math" "math"
"github.com/tfriedel6/canvas/backend/backendbase" "git.mstar.dev/mstar/canvas/backend/backendbase"
) )
// BeginPath clears the current path and starts a new one // 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 // rotation is the rotation of the ellipse in radians, startAngle and endAngle
// are angles in radians, and anticlockwise means that the line is added // are angles in radians, and anticlockwise means that the line is added
// anticlockwise // 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}) tf := cv.tf(backendbase.Vec{x, y})
ax, ay := math.Sincos(startAngle) ax, ay := math.Sincos(startAngle)
startAngle2 := backendbase.Vec{ay, ax}.MulMat2(cv.state.transform.Mat2()).Atan2() 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) 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 { if len(path.p) == 0 {
return target 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 { if p.flags&pathAttach != 0 && cv.state.lineWidth > 1 {
target = cv.lineJoint(p0, p1, p.next, lp0, lp1, lp2, lp3, tf, target) 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 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() v2 := p1.Sub(p2).Norm()
v3 := backendbase.Vec{v2[1], -v2[0]}.Mulf(cv.state.lineWidth * 0.5) 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 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 step := 6 / radius
if step > 0.8 { if step > 0.8 {
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) va := a1.Sub(a0)
vb := b1.Sub(b0) 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)) return backendbase.Vec{}, float64(math.Inf(1)), float64(math.Inf(1))
} }
d := va[1]*vb[0] - va[0]*vb[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) 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] last := path[len(path)-1]
if last.flags&pathIsConvex != 0 { if last.flags&pathIsConvex != 0 {
p0, p1 := path[0].pos.MulMat(mat), path[1].pos.MulMat(mat) p0, p1 := path[0].pos.MulMat(mat), path[1].pos.MulMat(mat)

View file

@ -4,7 +4,7 @@ import (
"image" "image"
"math" "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) { func (cv *Canvas) drawShadow(pts []backendbase.Vec, mask *image.Alpha, canOverlap bool) {

64
text.go
View file

@ -5,7 +5,6 @@ import (
"fmt" "fmt"
"image" "image"
"image/draw" "image/draw"
"io/ioutil"
"math" "math"
"os" "os"
"time" "time"
@ -13,9 +12,10 @@ import (
"github.com/golang/freetype" "github.com/golang/freetype"
"github.com/golang/freetype/truetype" "github.com/golang/freetype/truetype"
"github.com/tfriedel6/canvas/backend/backendbase"
"golang.org/x/image/font" "golang.org/x/image/font"
"golang.org/x/image/math/fixed" "golang.org/x/image/math/fixed"
"git.mstar.dev/mstar/canvas/backend/backendbase"
) )
// Font is a loaded font that can be passed to the // 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: case *truetype.Font:
f = &Font{font: v} f = &Font{font: v}
case string: case string:
data, err := ioutil.ReadFile(v) data, err := os.ReadFile(v)
if err != nil { if err != nil {
return nil, err 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 // 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 var size int
for size = 2; size < alphaTexSize; size *= 2 { for size = 2; size < alphaTexSize; size *= 2 {
if size >= strWidth && size >= strHeight { if size >= strWidth && size >= strHeight {
@ -229,10 +230,27 @@ func (cv *Canvas) FillText(str string, x, y float64) {
// render textImage to the screen // render textImage to the screen
var pts [4]backendbase.Vec var pts [4]backendbase.Vec
pts[0] = cv.tf(backendbase.Vec{float64(textOffset.X)/scale + x, float64(textOffset.Y)/scale + y}) pts[0] = cv.tf(
pts[1] = cv.tf(backendbase.Vec{float64(textOffset.X)/scale + x, float64(textOffset.Y)/scale + float64(strHeight)/scale + y}) backendbase.Vec{float64(textOffset.X)/scale + x, float64(textOffset.Y)/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[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) 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 // measure rendered text size
var p fixed.Point26_6 var p fixed.Point26_6
prev, hasPrev := truetype.Index(0), false 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() w, h := cv.b.Size()
fw, fh := float64(w), float64(h) fw, fh := float64(w), float64(h)
p0 := cv.tf(backendbase.Vec{float64(bounds.Min.X)/scale + curX, float64(strMinY)/scale + *y}) p0 := cv.tf(
p1 := cv.tf(backendbase.Vec{float64(bounds.Min.X)/scale + curX, float64(strMaxY)/scale + *y}) backendbase.Vec{float64(bounds.Min.X)/scale + curX, float64(strMinY)/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}) 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) && 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[1] >= 0 || p1[1] >= 0 || p2[1] >= 0 || p3[1] >= 0) &&
(p0[0] < fw || p1[0] < fw || p2[0] < fw || p3[0] < fw) && (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 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) path.MoveTo(float64(p0.X)*scale, -float64(p0.Y)*scale)
for _, p := range others { for _, p := range others {
on := p.Flags&0x01 != 0 on := p.Flags&0x01 != 0

View file

@ -4,7 +4,7 @@ import (
"math" "math"
"sort" "sort"
"github.com/tfriedel6/canvas/backend/backendbase" "git.mstar.dev/mstar/canvas/backend/backendbase"
) )
const samePointTolerance = 1e-20 const samePointTolerance = 1e-20
@ -97,7 +97,11 @@ func polygonContainsPoint(polygon []backendbase.Vec, p backendbase.Vec) bool {
return count%2 == 1 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 { if path[0].pos == path[len(path)-1].pos {
path = path[:len(path)-1] path = path[:len(path)-1]
} }