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