1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193
|
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package portable implements a sprite Engine using the image package.
//
// It is intended to serve as a reference implementation for testing
// other sprite Engines written against OpenGL, or other more exotic
// modern hardware interfaces.
package portable
import (
"image"
"image/draw"
xdraw "golang.org/x/image/draw"
"golang.org/x/image/math/f64"
"golang.org/x/mobile/event/size"
"golang.org/x/mobile/exp/f32"
"golang.org/x/mobile/exp/sprite"
"golang.org/x/mobile/exp/sprite/clock"
)
// Engine builds a sprite Engine that renders onto dst.
func Engine(dst *image.RGBA) sprite.Engine {
return &engine{
dst: dst,
nodes: []*node{nil},
}
}
type node struct {
// TODO: move this into package sprite as Node.EngineFields.RelTransform??
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) Release() {}
type engine struct {
dst *image.RGBA
nodes []*node
absTransforms []f32.Affine
}
func (e *engine) Register(n *sprite.Node) {
if n.EngineFields.Index != 0 {
panic("portable: sprite.Node already registered")
}
o := &node{}
o.relTransform.Identity()
e.nodes = append(e.nodes, o)
n.EngineFields.Index = int32(len(e.nodes) - 1)
}
func (e *engine) Unregister(n *sprite.Node) {
panic("todo")
}
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) SetSubTex(n *sprite.Node, x sprite.SubTex) {
n.EngineFields.Dirty = true // TODO: do we need to propagate dirtiness up/down the tree?
n.EngineFields.SubTex = x
}
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
}
func (e *engine) Render(scene *sprite.Node, t clock.Time, sz size.Event) {
// Affine transforms are done in geom.Pt. When finally drawing
// the geom.Pt onto an image.Image we need to convert to system
// pixels. We scale by sz.PixelsPerPt to do this.
e.absTransforms = append(e.absTransforms[:0], f32.Affine{
{sz.PixelsPerPt, 0, 0},
{0, sz.PixelsPerPt, 0},
})
e.render(scene, t)
}
func (e *engine) render(n *sprite.Node, t clock.Time) {
if n.EngineFields.Index == 0 {
panic("portable: sprite.Node not registered")
}
if n.Arranger != nil {
n.Arranger.Arrange(e, n, t)
}
// Push absTransforms.
// TODO: cache absolute transforms and use EngineFields.Dirty?
rel := &e.nodes[n.EngineFields.Index].relTransform
m := f32.Affine{}
m.Mul(&e.absTransforms[len(e.absTransforms)-1], rel)
e.absTransforms = append(e.absTransforms, m)
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
//
// Affine{{1, 0, 0}, {0, 1, 0}}
//
// should have the dimensions (1pt, 1pt). To do this we divide
// by the pixel width and height, reducing the texture to
// (1px, 1px) of the destination image. Multiplying by
// sz.PixelsPerPt, done in Render above, makes it (1pt, 1pt).
dx, dy := x.R.Dx(), x.R.Dy()
if dx > 0 && dy > 0 {
m.Scale(&m, 1/float32(dx), 1/float32(dy))
// TODO(nigeltao): delete the double-inverse: one here and one
// inside func affine.
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 {
e.render(c, t)
}
// Pop absTransforms.
e.absTransforms = e.absTransforms[:len(e.absTransforms)-1]
}
func (e *engine) Release() {}
// affine draws each pixel of dst using bilinear interpolation of the
// affine-transformed position in src. This is equivalent to:
//
// for each (x,y) in dst:
// dst(x,y) = bilinear interpolation of src(a*(x,y))
//
// While this is the simpler implementation, it can be counter-
// intuitive as an affine transformation is usually described in terms
// of the source, not the destination. For example, a scale transform
//
// Affine{{2, 0, 0}, {0, 2, 0}}
//
// 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, srcb image.Rectangle, mask image.Image, a *f32.Affine, op draw.Op) {
// For legacy compatibility reasons, the matrix a transforms from dst-space
// to src-space. The golang.org/x/image/draw package's matrices transform
// from src-space to dst-space, so we invert (and adjust for different
// origins).
i := *a
i[0][2] += float32(srcb.Min.X)
i[1][2] += float32(srcb.Min.Y)
i.Inverse(&i)
i[0][2] += float32(dst.Rect.Min.X)
i[1][2] += float32(dst.Rect.Min.Y)
m := f64.Aff3{
float64(i[0][0]),
float64(i[0][1]),
float64(i[0][2]),
float64(i[1][0]),
float64(i[1][1]),
float64(i[1][2]),
}
// TODO(nigeltao): is the caller or callee responsible for detecting
// transforms that are simple copies or scales, for which there are faster
// implementations in the xdraw package.
xdraw.ApproxBiLinear.Transform(dst, m, src, srcb, op, &xdraw.Options{
SrcMask: mask,
})
}
|