go.mobile/sprite: replace Sheet/Texture with Texture/SubTex
LGTM=nigeltao R=nigeltao CC=golang-codereviews https://golang.org/cl/172050044
This commit is contained in:
parent
50b4d219bf
commit
10b4dcd412
|
@ -21,29 +21,35 @@ type node struct {
|
|||
relTransform f32.Affine
|
||||
}
|
||||
|
||||
type sheet struct {
|
||||
type texture struct {
|
||||
glImage *glutil.Image
|
||||
b image.Rectangle
|
||||
}
|
||||
|
||||
type texture struct {
|
||||
sheet sprite.Sheet
|
||||
b image.Rectangle
|
||||
func (t *texture) Bounds() (w, h int) { return t.b.Dx(), t.b.Dy() }
|
||||
|
||||
func (t *texture) Download(r image.Rectangle, dst draw.Image) {
|
||||
panic("TODO")
|
||||
}
|
||||
|
||||
func (t *texture) Upload(r image.Rectangle, src image.Image) {
|
||||
draw.Draw(t.glImage.RGBA, r, src, src.Bounds().Min, draw.Src)
|
||||
t.glImage.Upload()
|
||||
}
|
||||
|
||||
func (t *texture) Unload() {
|
||||
panic("TODO")
|
||||
}
|
||||
|
||||
func Engine() sprite.Engine {
|
||||
return &engine{
|
||||
nodes: []*node{nil},
|
||||
sheets: []sheet{{}},
|
||||
textures: []texture{{}},
|
||||
nodes: []*node{nil},
|
||||
}
|
||||
}
|
||||
|
||||
type engine struct {
|
||||
glImages map[sprite.Texture]*glutil.Image
|
||||
nodes []*node
|
||||
sheets []sheet
|
||||
textures []texture
|
||||
|
||||
absTransforms []f32.Affine
|
||||
}
|
||||
|
@ -63,41 +69,20 @@ func (e *engine) Unregister(n *sprite.Node) {
|
|||
panic("todo")
|
||||
}
|
||||
|
||||
func (e *engine) LoadSheet(a image.Image) (sprite.Sheet, error) {
|
||||
b := a.Bounds()
|
||||
glImage := glutil.NewImage(b.Dx(), b.Dy())
|
||||
draw.Draw(glImage.RGBA, glImage.Bounds(), a, b.Min, draw.Src)
|
||||
glImage.Upload()
|
||||
func (e *engine) LoadTexture(src image.Image) (sprite.Texture, error) {
|
||||
b := src.Bounds()
|
||||
t := &texture{glutil.NewImage(b.Dx(), b.Dy()), b}
|
||||
t.Upload(b, src)
|
||||
// TODO: set "glImage.Pix = nil"?? We don't need the CPU-side image any more.
|
||||
e.sheets = append(e.sheets, sheet{
|
||||
glImage: glImage,
|
||||
b: b,
|
||||
})
|
||||
return sprite.Sheet(len(e.sheets) - 1), nil
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func (e *engine) LoadTexture(s sprite.Sheet, bounds image.Rectangle) (sprite.Texture, error) {
|
||||
e.textures = append(e.textures, texture{
|
||||
sheet: s,
|
||||
b: bounds,
|
||||
})
|
||||
return sprite.Texture(len(e.textures) - 1), nil
|
||||
}
|
||||
|
||||
func (e *engine) UnloadSheet(s sprite.Sheet) error {
|
||||
panic("todo")
|
||||
}
|
||||
|
||||
func (e *engine) UnloadTexture(x sprite.Texture) error {
|
||||
panic("todo")
|
||||
}
|
||||
|
||||
func (e *engine) SetTexture(n *sprite.Node, t clock.Time, x sprite.Texture) {
|
||||
func (e *engine) SetSubTex(n *sprite.Node, x sprite.SubTex) {
|
||||
n.EngineFields.Dirty = true // TODO: do we need to propagate dirtiness up/down the tree?
|
||||
n.EngineFields.Texture = x
|
||||
n.EngineFields.SubTex = x
|
||||
}
|
||||
|
||||
func (e *engine) SetTransform(n *sprite.Node, t clock.Time, m f32.Affine) {
|
||||
func (e *engine) SetTransform(n *sprite.Node, m f32.Affine) {
|
||||
n.EngineFields.Dirty = true // TODO: do we need to propagate dirtiness up/down the tree?
|
||||
e.nodes[n.EngineFields.Index].relTransform = m
|
||||
}
|
||||
|
@ -125,24 +110,22 @@ func (e *engine) render(n *sprite.Node, t clock.Time) {
|
|||
m.Mul(&e.absTransforms[len(e.absTransforms)-1], rel)
|
||||
e.absTransforms = append(e.absTransforms, m)
|
||||
|
||||
if x := e.textures[n.EngineFields.Texture]; x.sheet != 0 {
|
||||
if 0 < x.sheet && int(x.sheet) < len(e.sheets) {
|
||||
e.sheets[x.sheet].glImage.Draw(
|
||||
geom.Point{
|
||||
geom.Pt(m[0][2]),
|
||||
geom.Pt(m[1][2]),
|
||||
},
|
||||
geom.Point{
|
||||
geom.Pt(m[0][2] + m[0][0]),
|
||||
geom.Pt(m[1][2] + m[1][0]),
|
||||
},
|
||||
geom.Point{
|
||||
geom.Pt(m[0][2] + m[0][1]),
|
||||
geom.Pt(m[1][2] + m[1][1]),
|
||||
},
|
||||
x.b,
|
||||
)
|
||||
}
|
||||
if x := n.EngineFields.SubTex; x.T != nil {
|
||||
x.T.(*texture).glImage.Draw(
|
||||
geom.Point{
|
||||
geom.Pt(m[0][2]),
|
||||
geom.Pt(m[1][2]),
|
||||
},
|
||||
geom.Point{
|
||||
geom.Pt(m[0][2] + m[0][0]),
|
||||
geom.Pt(m[1][2] + m[1][0]),
|
||||
},
|
||||
geom.Point{
|
||||
geom.Pt(m[0][2] + m[0][1]),
|
||||
geom.Pt(m[1][2] + m[1][1]),
|
||||
},
|
||||
x.R,
|
||||
)
|
||||
}
|
||||
|
||||
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||
|
|
|
@ -25,12 +25,11 @@ import (
|
|||
//
|
||||
// will produce a dst that is half the size of src. To perform a
|
||||
// traditional affine transform, use the inverse of the affine matrix.
|
||||
func affine(dst *image.RGBA, src image.Image, a *f32.Affine, mask image.Image, op draw.Op) {
|
||||
srcb := src.Bounds()
|
||||
func affine(dst *image.RGBA, src image.Image, srcb image.Rectangle, mask image.Image, a *f32.Affine, op draw.Op) {
|
||||
b := dst.Bounds()
|
||||
var maskb image.Rectangle
|
||||
if mask != nil {
|
||||
maskb = mask.Bounds()
|
||||
maskb = mask.Bounds().Add(srcb.Min)
|
||||
}
|
||||
|
||||
for y := b.Min.Y; y < b.Max.Y; y++ {
|
||||
|
|
|
@ -48,7 +48,6 @@ func TestAffine(t *testing.T) {
|
|||
b := src.Bounds()
|
||||
b.Min.X += 10
|
||||
b.Max.Y /= 2
|
||||
src = src.SubImage(b).(*image.RGBA)
|
||||
|
||||
var a f32.Affine
|
||||
a.Identity()
|
||||
|
@ -56,10 +55,10 @@ func TestAffine(t *testing.T) {
|
|||
a.Translate(&a, 0, 24)
|
||||
a.Rotate(&a, float32(math.Asin(12./20)))
|
||||
// See commentary in the render method defined in portable.go.
|
||||
a.Scale(&a, 40/float32(src.Rect.Dx()), 20/float32(src.Rect.Dy()))
|
||||
a.Scale(&a, 40/float32(b.Dx()), 20/float32(b.Dy()))
|
||||
a.Inverse(&a)
|
||||
|
||||
affine(got, src, &a, nil, draw.Over)
|
||||
affine(got, src, b, nil, &a, draw.Over)
|
||||
|
||||
ptTopLeft := geom.Point{0, 24}
|
||||
ptBottomRight := geom.Point{12 + 32, 16}
|
||||
|
@ -120,7 +119,7 @@ func TestAffineMask(t *testing.T) {
|
|||
a := new(f32.Affine)
|
||||
a.Identity()
|
||||
got := image.NewRGBA(b)
|
||||
affine(got, src, a, mask, draw.Src)
|
||||
affine(got, src, b, mask, a, draw.Src)
|
||||
|
||||
if !imageEq(got, want) {
|
||||
gotPath, err := writeTempPNG("testpattern-mask-got", got)
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
package portable
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"image/draw"
|
||||
|
||||
|
@ -23,10 +22,8 @@ import (
|
|||
// Engine builds a sprite Engine that renders onto dst.
|
||||
func Engine(dst *image.RGBA) sprite.Engine {
|
||||
return &engine{
|
||||
dst: dst,
|
||||
nodes: []*node{nil},
|
||||
sheets: []*image.RGBA{nil},
|
||||
textures: []*image.RGBA{nil},
|
||||
dst: dst,
|
||||
nodes: []*node{nil},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -35,11 +32,28 @@ type node struct {
|
|||
relTransform f32.Affine
|
||||
}
|
||||
|
||||
type texture struct {
|
||||
m *image.RGBA
|
||||
}
|
||||
|
||||
func (t *texture) Bounds() (w, h int) {
|
||||
b := t.m.Bounds()
|
||||
return b.Dx(), b.Dy()
|
||||
}
|
||||
|
||||
func (t *texture) Download(r image.Rectangle, dst draw.Image) {
|
||||
draw.Draw(dst, r, t.m, t.m.Bounds().Min, draw.Src)
|
||||
}
|
||||
|
||||
func (t *texture) Upload(r image.Rectangle, src image.Image) {
|
||||
draw.Draw(t.m, r, src, src.Bounds().Min, draw.Src)
|
||||
}
|
||||
|
||||
func (t *texture) Unload() { panic("TODO") }
|
||||
|
||||
type engine struct {
|
||||
dst *image.RGBA
|
||||
nodes []*node
|
||||
sheets []*image.RGBA
|
||||
textures []*image.RGBA
|
||||
absTransforms []f32.Affine
|
||||
}
|
||||
|
||||
|
@ -59,39 +73,21 @@ func (e *engine) Unregister(n *sprite.Node) {
|
|||
panic("todo")
|
||||
}
|
||||
|
||||
func (e *engine) LoadSheet(a image.Image) (sprite.Sheet, error) {
|
||||
rgba, ok := a.(*image.RGBA)
|
||||
if !ok {
|
||||
b := a.Bounds()
|
||||
rgba = image.NewRGBA(b)
|
||||
draw.Draw(rgba, b, a, b.Min, draw.Src)
|
||||
}
|
||||
e.sheets = append(e.sheets, rgba)
|
||||
return sprite.Sheet(len(e.sheets) - 1), nil
|
||||
func (e *engine) LoadTexture(m image.Image) (sprite.Texture, error) {
|
||||
b := m.Bounds()
|
||||
w, h := b.Dx(), b.Dy()
|
||||
|
||||
t := &texture{m: image.NewRGBA(image.Rect(0, 0, w, h))}
|
||||
t.Upload(b, m)
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func (e *engine) LoadTexture(s sprite.Sheet, bounds image.Rectangle) (sprite.Texture, error) {
|
||||
if s < 0 || len(e.sheets) <= int(s) {
|
||||
return 0, fmt.Errorf("portable: LoadTexture: sheet %d is out of bounds", s)
|
||||
}
|
||||
e.textures = append(e.textures, e.sheets[s].SubImage(bounds).(*image.RGBA))
|
||||
return sprite.Texture(len(e.textures) - 1), nil
|
||||
}
|
||||
|
||||
func (e *engine) UnloadSheet(s sprite.Sheet) error {
|
||||
panic("todo")
|
||||
}
|
||||
|
||||
func (e *engine) UnloadTexture(x sprite.Texture) error {
|
||||
panic("todo")
|
||||
}
|
||||
|
||||
func (e *engine) SetTexture(n *sprite.Node, t clock.Time, x sprite.Texture) {
|
||||
func (e *engine) SetSubTex(n *sprite.Node, x sprite.SubTex) {
|
||||
n.EngineFields.Dirty = true // TODO: do we need to propagate dirtiness up/down the tree?
|
||||
n.EngineFields.Texture = x
|
||||
n.EngineFields.SubTex = x
|
||||
}
|
||||
|
||||
func (e *engine) SetTransform(n *sprite.Node, t clock.Time, m f32.Affine) {
|
||||
func (e *engine) SetTransform(n *sprite.Node, m f32.Affine) {
|
||||
n.EngineFields.Dirty = true // TODO: do we need to propagate dirtiness up/down the tree?
|
||||
e.nodes[n.EngineFields.Index].relTransform = m
|
||||
}
|
||||
|
@ -122,9 +118,7 @@ func (e *engine) render(n *sprite.Node, t clock.Time) {
|
|||
m.Mul(&e.absTransforms[len(e.absTransforms)-1], rel)
|
||||
e.absTransforms = append(e.absTransforms, m)
|
||||
|
||||
if x := e.textures[n.EngineFields.Texture]; x != nil {
|
||||
b := x.Bounds()
|
||||
|
||||
if x := n.EngineFields.SubTex; x.T != nil {
|
||||
// Affine transforms work in geom.Pt, which is entirely
|
||||
// independent of the number of pixels in a texture. A texture
|
||||
// of any image.Rectangle bounds rendered with
|
||||
|
@ -135,10 +129,12 @@ func (e *engine) render(n *sprite.Node, t clock.Time) {
|
|||
// by the pixel width and height, reducing the texture to
|
||||
// (1px, 1px) of the destination image. Multiplying by
|
||||
// geom.PixelsPerPt, done in Render above, makes it (1pt, 1pt).
|
||||
m.Scale(&m, 1/float32(b.Dx()), 1/float32(b.Dy()))
|
||||
|
||||
m.Inverse(&m) // See the documentation on the affine function.
|
||||
affine(e.dst, x, &m, nil, draw.Over)
|
||||
dx, dy := x.R.Dx(), x.R.Dy()
|
||||
if dx > 0 && dy > 0 {
|
||||
m.Scale(&m, 1/float32(dx), 1/float32(dy))
|
||||
m.Inverse(&m) // See the documentation on the affine function.
|
||||
affine(e.dst, x.T.(*texture).m, x.R, nil, &m, draw.Over)
|
||||
}
|
||||
}
|
||||
|
||||
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||
|
|
|
@ -26,6 +26,7 @@ package sprite
|
|||
|
||||
import (
|
||||
"image"
|
||||
"image/draw"
|
||||
|
||||
"golang.org/x/mobile/f32"
|
||||
"golang.org/x/mobile/sprite/clock"
|
||||
|
@ -35,21 +36,26 @@ type Arranger interface {
|
|||
Arrange(e Engine, n *Node, t clock.Time)
|
||||
}
|
||||
|
||||
type Sheet int32
|
||||
type Texture interface {
|
||||
Bounds() (w, h int)
|
||||
Download(r image.Rectangle, dst draw.Image)
|
||||
Upload(r image.Rectangle, src image.Image)
|
||||
Unload()
|
||||
}
|
||||
|
||||
type Texture int32
|
||||
type SubTex struct {
|
||||
T Texture
|
||||
R image.Rectangle
|
||||
}
|
||||
|
||||
type Engine interface {
|
||||
Register(n *Node)
|
||||
Unregister(n *Node)
|
||||
|
||||
LoadSheet(a image.Image) (Sheet, error)
|
||||
LoadTexture(s Sheet, bounds image.Rectangle) (Texture, error)
|
||||
UnloadSheet(s Sheet) error
|
||||
UnloadTexture(x Texture) error
|
||||
LoadTexture(a image.Image) (Texture, error)
|
||||
|
||||
SetTexture(n *Node, t clock.Time, x Texture)
|
||||
SetTransform(n *Node, t clock.Time, m f32.Affine) // sets transform relative to parent.
|
||||
SetSubTex(n *Node, x SubTex)
|
||||
SetTransform(n *Node, m f32.Affine) // sets transform relative to parent.
|
||||
|
||||
Render(scene *Node, t clock.Time)
|
||||
}
|
||||
|
@ -65,9 +71,9 @@ type Node struct {
|
|||
// in other packages.
|
||||
EngineFields struct {
|
||||
// TODO: separate TexDirty and TransformDirty bits?
|
||||
Dirty bool
|
||||
Index int32
|
||||
Texture Texture
|
||||
Dirty bool
|
||||
Index int32
|
||||
SubTex SubTex
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue