Adding upstream version 0.0~git20250520.a1d9079+dfsg.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
590ac7ff5f
commit
20149b7f3a
456 changed files with 70406 additions and 0 deletions
205
exp/sprite/portable/affine_test.go
Normal file
205
exp/sprite/portable/affine_test.go
Normal file
|
@ -0,0 +1,205 @@
|
|||
// 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
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
"image/draw"
|
||||
"image/png"
|
||||
"math"
|
||||
"os"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/mobile/event/size"
|
||||
"golang.org/x/mobile/exp/f32"
|
||||
"golang.org/x/mobile/geom"
|
||||
)
|
||||
|
||||
func TestAffine(t *testing.T) {
|
||||
if runtime.GOOS == "android" || runtime.GOOS == "ios" {
|
||||
t.Skipf("testdata not available on %s", runtime.GOOS)
|
||||
}
|
||||
f, err := os.Open("../../../testdata/testpattern.png")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
srcOrig, _, err := image.Decode(f)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
src := image.NewRGBA(srcOrig.Bounds())
|
||||
draw.Draw(src, src.Rect, srcOrig, srcOrig.Bounds().Min, draw.Src)
|
||||
|
||||
const (
|
||||
pixW = 100
|
||||
pixH = 100
|
||||
ptW = geom.Pt(50)
|
||||
ptH = geom.Pt(50)
|
||||
)
|
||||
sz := size.Event{
|
||||
WidthPx: pixW,
|
||||
HeightPx: pixH,
|
||||
WidthPt: ptW,
|
||||
HeightPt: ptH,
|
||||
PixelsPerPt: float32(pixW) / float32(ptW),
|
||||
}
|
||||
|
||||
got := image.NewRGBA(image.Rect(0, 0, pixW, pixH))
|
||||
blue := image.NewUniform(color.RGBA{B: 0xff, A: 0xff})
|
||||
draw.Draw(got, got.Bounds(), blue, image.Point{}, draw.Src)
|
||||
|
||||
b := src.Bounds()
|
||||
b.Min.X += 10
|
||||
b.Max.Y /= 2
|
||||
|
||||
var a f32.Affine
|
||||
a.Identity()
|
||||
a.Scale(&a, sz.PixelsPerPt, sz.PixelsPerPt)
|
||||
a.Translate(&a, 0, 24)
|
||||
a.Rotate(&a, float32(math.Asin(12./20)))
|
||||
// See commentary in the render method defined in portable.go.
|
||||
a.Scale(&a, 40/float32(b.Dx()), 20/float32(b.Dy()))
|
||||
a.Inverse(&a)
|
||||
|
||||
affine(got, src, b, nil, &a, draw.Over)
|
||||
|
||||
ptTopLeft := geom.Point{0, 24}
|
||||
ptBottomRight := geom.Point{12 + 32, 16}
|
||||
|
||||
drawCross(got, 0, 0)
|
||||
drawCross(got, int(ptTopLeft.X.Px(sz.PixelsPerPt)), int(ptTopLeft.Y.Px(sz.PixelsPerPt)))
|
||||
drawCross(got, int(ptBottomRight.X.Px(sz.PixelsPerPt)), int(ptBottomRight.Y.Px(sz.PixelsPerPt)))
|
||||
drawCross(got, pixW-1, pixH-1)
|
||||
|
||||
const wantPath = "../../../testdata/testpattern-window.png"
|
||||
f, err = os.Open(wantPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
wantSrc, _, err := image.Decode(f)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want, ok := wantSrc.(*image.RGBA)
|
||||
if !ok {
|
||||
b := wantSrc.Bounds()
|
||||
want = image.NewRGBA(b)
|
||||
draw.Draw(want, b, wantSrc, b.Min, draw.Src)
|
||||
}
|
||||
|
||||
if !imageEq(got, want) {
|
||||
gotPath, err := writeTempPNG("testpattern-window-got", got)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Errorf("got\n%s\nwant\n%s", gotPath, wantPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAffineMask(t *testing.T) {
|
||||
if runtime.GOOS == "android" || runtime.GOOS == "ios" {
|
||||
t.Skipf("testdata not available on %s", runtime.GOOS)
|
||||
}
|
||||
f, err := os.Open("../../../testdata/testpattern.png")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
srcOrig, _, err := image.Decode(f)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
b := srcOrig.Bounds()
|
||||
src := image.NewRGBA(b)
|
||||
draw.Draw(src, src.Rect, srcOrig, b.Min, draw.Src)
|
||||
mask := image.NewAlpha(b)
|
||||
for y := b.Min.Y; y < b.Max.Y; y++ {
|
||||
for x := b.Min.X; x < b.Max.X; x++ {
|
||||
mask.Set(x, y, color.Alpha{A: uint8(x - b.Min.X)})
|
||||
}
|
||||
}
|
||||
want := image.NewRGBA(b)
|
||||
draw.DrawMask(want, want.Rect, src, b.Min, mask, b.Min, draw.Src)
|
||||
|
||||
a := new(f32.Affine)
|
||||
a.Identity()
|
||||
got := image.NewRGBA(b)
|
||||
affine(got, src, b, mask, a, draw.Src)
|
||||
|
||||
if !imageEq(got, want) {
|
||||
gotPath, err := writeTempPNG("testpattern-mask-got", got)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
wantPath, err := writeTempPNG("testpattern-mask-want", want)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Errorf("got\n%s\nwant\n%s", gotPath, wantPath)
|
||||
}
|
||||
}
|
||||
|
||||
func writeTempPNG(prefix string, m image.Image) (string, error) {
|
||||
f, err := os.CreateTemp("", prefix+"-")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
f.Close()
|
||||
path := f.Name() + ".png"
|
||||
f, err = os.Create(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := png.Encode(f, m); err != nil {
|
||||
f.Close()
|
||||
return "", err
|
||||
}
|
||||
return path, f.Close()
|
||||
}
|
||||
|
||||
func drawCross(m *image.RGBA, x, y int) {
|
||||
c := color.RGBA{0xff, 0, 0, 0xff} // red
|
||||
m.SetRGBA(x+0, y-2, c)
|
||||
m.SetRGBA(x+0, y-1, c)
|
||||
m.SetRGBA(x-2, y+0, c)
|
||||
m.SetRGBA(x-1, y+0, c)
|
||||
m.SetRGBA(x+0, y+0, c)
|
||||
m.SetRGBA(x+1, y+0, c)
|
||||
m.SetRGBA(x+2, y+0, c)
|
||||
m.SetRGBA(x+0, y+1, c)
|
||||
m.SetRGBA(x+0, y+2, c)
|
||||
}
|
||||
|
||||
func eqEpsilon(x, y uint8) bool {
|
||||
const epsilon = 9
|
||||
return x-y < epsilon || y-x < epsilon
|
||||
}
|
||||
|
||||
func colorEq(c0, c1 color.RGBA) bool {
|
||||
return eqEpsilon(c0.R, c1.R) && eqEpsilon(c0.G, c1.G) && eqEpsilon(c0.B, c1.B) && eqEpsilon(c0.A, c1.A)
|
||||
}
|
||||
|
||||
func imageEq(m0, m1 *image.RGBA) bool {
|
||||
b0 := m0.Bounds()
|
||||
b1 := m1.Bounds()
|
||||
if b0 != b1 {
|
||||
return false
|
||||
}
|
||||
badPx := 0
|
||||
for y := b0.Min.Y; y < b0.Max.Y; y++ {
|
||||
for x := b0.Min.X; x < b0.Max.X; x++ {
|
||||
c0, c1 := m0.At(x, y).(color.RGBA), m1.At(x, y).(color.RGBA)
|
||||
if !colorEq(c0, c1) {
|
||||
badPx++
|
||||
}
|
||||
}
|
||||
}
|
||||
badFrac := float64(badPx) / float64(b0.Dx()*b0.Dy())
|
||||
return badFrac < 0.01
|
||||
}
|
193
exp/sprite/portable/portable.go
Normal file
193
exp/sprite/portable/portable.go
Normal file
|
@ -0,0 +1,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,
|
||||
})
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue