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