forked from golang/mobile
/
portable.go
193 lines (166 loc) · 5.73 KB
/
portable.go
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 "github.com/TimaSlipko/gomobile/exp/sprite/portable"
import (
"image"
"image/draw"
"github.com/TimaSlipko/gomobile/event/size"
"github.com/TimaSlipko/gomobile/exp/f32"
"github.com/TimaSlipko/gomobile/exp/sprite"
"github.com/TimaSlipko/gomobile/exp/sprite/clock"
xdraw "golang.org/x/image/draw"
"golang.org/x/image/math/f64"
)
// 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,
})
}