// 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 "golang.org/x/mobile/exp/sprite/portable" import ( "image" "image/draw" xdraw "golang.org/x/image/draw" "golang.org/x/image/math/f64" "golang.org/x/mobile/event/config" "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) Unload() { panic("TODO") } 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, cfg config.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 cfg.PixelsPerPt to do this. e.absTransforms = append(e.absTransforms[:0], f32.Affine{ {cfg.PixelsPerPt, 0, 0}, {0, cfg.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 // cfg.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] } // 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, xdraw.Op(op), &xdraw.Options{ SrcMask: mask, }) }