2014-09-27 03:10:15 +10:00
|
|
|
// 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"
|
2014-11-01 07:48:58 -04:00
|
|
|
"image/png"
|
|
|
|
"io/ioutil"
|
|
|
|
"math"
|
|
|
|
"os"
|
2014-09-27 03:10:15 +10:00
|
|
|
"testing"
|
2014-11-01 07:48:58 -04:00
|
|
|
|
app: use one thread for both GL and other UI C code.
This change will break Darwin. I have only built and tested this on
desktop linux and Android linux. A follow-up CL will fix Darwin.
Currently, OpenGL gets its own thread, and UI C code (e.g. the Android
event loop, or the X11 event loop) gets its own thread. This relies on
multiple system-provided UI-related C libraries working nicely together,
even when running on different threads. Keeping all the C code on the
one thread seems more sound.
As side-effects:
- In package app/debug, DrawFPS now takes an explicit Config.
- In package app, some callbacks now take an explicit Config.
- In package exp/sprite, Render now takes an explicit Config.
- In package event, there are new events (Config, Draw, Lifecycle),
and an event filter mechanism to replace multiple app Callbacks.
- In package geom, the deprecated Width, Height and PixelsPerPt global
variables were removed in favor of an event.Config that is
explicitly passed around (and does not require mutex-locking).
Converting a geom.Pt to pixels now requires passing a pixelsPerPt.
- In package gl, the Do, Start and Stop functions are removed, as well
as the need to call Start in its own goroutine. There is no longer a
separate GL thread. Instead, package app explicitly performs any GL
work (gl.DoWork) when some is available (gl.WorkAvailable).
- In package gl/glutil, Image.Draw now takes an explicit Config.
Callbacks are no longer executed on 'the UI thread'.
Changing the app programming model from callbacks to events (since a
channel of events works with select) will be a follow-up change.
Change-Id: Id9865cd9ee1c45a98c613e9021a63c17226a64b1
Reviewed-on: https://go-review.googlesource.com/11351
Reviewed-by: David Crawshaw <crawshaw@golang.org>
2015-06-23 16:41:48 +10:00
|
|
|
"golang.org/x/mobile/event"
|
2014-11-10 08:55:57 +11:00
|
|
|
"golang.org/x/mobile/f32"
|
|
|
|
"golang.org/x/mobile/geom"
|
2014-09-27 03:10:15 +10:00
|
|
|
)
|
|
|
|
|
|
|
|
func TestAffine(t *testing.T) {
|
2015-06-24 18:27:18 +10:00
|
|
|
f, err := os.Open("../../../testdata/testpattern.png")
|
2014-11-01 07:48:58 -04:00
|
|
|
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)
|
|
|
|
)
|
app: use one thread for both GL and other UI C code.
This change will break Darwin. I have only built and tested this on
desktop linux and Android linux. A follow-up CL will fix Darwin.
Currently, OpenGL gets its own thread, and UI C code (e.g. the Android
event loop, or the X11 event loop) gets its own thread. This relies on
multiple system-provided UI-related C libraries working nicely together,
even when running on different threads. Keeping all the C code on the
one thread seems more sound.
As side-effects:
- In package app/debug, DrawFPS now takes an explicit Config.
- In package app, some callbacks now take an explicit Config.
- In package exp/sprite, Render now takes an explicit Config.
- In package event, there are new events (Config, Draw, Lifecycle),
and an event filter mechanism to replace multiple app Callbacks.
- In package geom, the deprecated Width, Height and PixelsPerPt global
variables were removed in favor of an event.Config that is
explicitly passed around (and does not require mutex-locking).
Converting a geom.Pt to pixels now requires passing a pixelsPerPt.
- In package gl, the Do, Start and Stop functions are removed, as well
as the need to call Start in its own goroutine. There is no longer a
separate GL thread. Instead, package app explicitly performs any GL
work (gl.DoWork) when some is available (gl.WorkAvailable).
- In package gl/glutil, Image.Draw now takes an explicit Config.
Callbacks are no longer executed on 'the UI thread'.
Changing the app programming model from callbacks to events (since a
channel of events works with select) will be a follow-up change.
Change-Id: Id9865cd9ee1c45a98c613e9021a63c17226a64b1
Reviewed-on: https://go-review.googlesource.com/11351
Reviewed-by: David Crawshaw <crawshaw@golang.org>
2015-06-23 16:41:48 +10:00
|
|
|
cfg := event.Config{
|
|
|
|
Width: ptW,
|
|
|
|
Height: ptH,
|
|
|
|
PixelsPerPt: float32(pixW) / float32(ptW),
|
|
|
|
}
|
2014-11-01 07:48:58 -04:00
|
|
|
|
|
|
|
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()
|
app: use one thread for both GL and other UI C code.
This change will break Darwin. I have only built and tested this on
desktop linux and Android linux. A follow-up CL will fix Darwin.
Currently, OpenGL gets its own thread, and UI C code (e.g. the Android
event loop, or the X11 event loop) gets its own thread. This relies on
multiple system-provided UI-related C libraries working nicely together,
even when running on different threads. Keeping all the C code on the
one thread seems more sound.
As side-effects:
- In package app/debug, DrawFPS now takes an explicit Config.
- In package app, some callbacks now take an explicit Config.
- In package exp/sprite, Render now takes an explicit Config.
- In package event, there are new events (Config, Draw, Lifecycle),
and an event filter mechanism to replace multiple app Callbacks.
- In package geom, the deprecated Width, Height and PixelsPerPt global
variables were removed in favor of an event.Config that is
explicitly passed around (and does not require mutex-locking).
Converting a geom.Pt to pixels now requires passing a pixelsPerPt.
- In package gl, the Do, Start and Stop functions are removed, as well
as the need to call Start in its own goroutine. There is no longer a
separate GL thread. Instead, package app explicitly performs any GL
work (gl.DoWork) when some is available (gl.WorkAvailable).
- In package gl/glutil, Image.Draw now takes an explicit Config.
Callbacks are no longer executed on 'the UI thread'.
Changing the app programming model from callbacks to events (since a
channel of events works with select) will be a follow-up change.
Change-Id: Id9865cd9ee1c45a98c613e9021a63c17226a64b1
Reviewed-on: https://go-review.googlesource.com/11351
Reviewed-by: David Crawshaw <crawshaw@golang.org>
2015-06-23 16:41:48 +10:00
|
|
|
a.Scale(&a, cfg.PixelsPerPt, cfg.PixelsPerPt)
|
2014-11-01 07:48:58 -04:00
|
|
|
a.Translate(&a, 0, 24)
|
|
|
|
a.Rotate(&a, float32(math.Asin(12./20)))
|
|
|
|
// See commentary in the render method defined in portable.go.
|
2014-11-11 20:46:47 -05:00
|
|
|
a.Scale(&a, 40/float32(b.Dx()), 20/float32(b.Dy()))
|
2014-11-01 07:48:58 -04:00
|
|
|
a.Inverse(&a)
|
|
|
|
|
2014-11-11 20:46:47 -05:00
|
|
|
affine(got, src, b, nil, &a, draw.Over)
|
2014-11-01 07:48:58 -04:00
|
|
|
|
|
|
|
ptTopLeft := geom.Point{0, 24}
|
|
|
|
ptBottomRight := geom.Point{12 + 32, 16}
|
|
|
|
|
|
|
|
drawCross(got, 0, 0)
|
app: use one thread for both GL and other UI C code.
This change will break Darwin. I have only built and tested this on
desktop linux and Android linux. A follow-up CL will fix Darwin.
Currently, OpenGL gets its own thread, and UI C code (e.g. the Android
event loop, or the X11 event loop) gets its own thread. This relies on
multiple system-provided UI-related C libraries working nicely together,
even when running on different threads. Keeping all the C code on the
one thread seems more sound.
As side-effects:
- In package app/debug, DrawFPS now takes an explicit Config.
- In package app, some callbacks now take an explicit Config.
- In package exp/sprite, Render now takes an explicit Config.
- In package event, there are new events (Config, Draw, Lifecycle),
and an event filter mechanism to replace multiple app Callbacks.
- In package geom, the deprecated Width, Height and PixelsPerPt global
variables were removed in favor of an event.Config that is
explicitly passed around (and does not require mutex-locking).
Converting a geom.Pt to pixels now requires passing a pixelsPerPt.
- In package gl, the Do, Start and Stop functions are removed, as well
as the need to call Start in its own goroutine. There is no longer a
separate GL thread. Instead, package app explicitly performs any GL
work (gl.DoWork) when some is available (gl.WorkAvailable).
- In package gl/glutil, Image.Draw now takes an explicit Config.
Callbacks are no longer executed on 'the UI thread'.
Changing the app programming model from callbacks to events (since a
channel of events works with select) will be a follow-up change.
Change-Id: Id9865cd9ee1c45a98c613e9021a63c17226a64b1
Reviewed-on: https://go-review.googlesource.com/11351
Reviewed-by: David Crawshaw <crawshaw@golang.org>
2015-06-23 16:41:48 +10:00
|
|
|
drawCross(got, int(ptTopLeft.X.Px(cfg.PixelsPerPt)), int(ptTopLeft.Y.Px(cfg.PixelsPerPt)))
|
|
|
|
drawCross(got, int(ptBottomRight.X.Px(cfg.PixelsPerPt)), int(ptBottomRight.Y.Px(cfg.PixelsPerPt)))
|
2014-11-01 07:48:58 -04:00
|
|
|
drawCross(got, pixW-1, pixH-1)
|
|
|
|
|
2015-06-24 18:27:18 +10:00
|
|
|
const wantPath = "../../../testdata/testpattern-window.png"
|
2014-11-01 07:48:58 -04:00
|
|
|
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) {
|
2014-11-04 08:51:09 -05:00
|
|
|
gotPath, err := writeTempPNG("testpattern-window-got", got)
|
2014-11-01 07:48:58 -04:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2014-11-04 08:51:09 -05:00
|
|
|
t.Errorf("got\n%s\nwant\n%s", gotPath, wantPath)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestAffineMask(t *testing.T) {
|
2015-06-24 18:27:18 +10:00
|
|
|
f, err := os.Open("../../../testdata/testpattern.png")
|
2014-11-04 08:51:09 -05:00
|
|
|
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)})
|
2014-11-01 07:48:58 -04:00
|
|
|
}
|
2014-11-04 08:51:09 -05:00
|
|
|
}
|
|
|
|
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)
|
2014-11-11 20:46:47 -05:00
|
|
|
affine(got, src, b, mask, a, draw.Src)
|
2014-11-04 08:51:09 -05:00
|
|
|
|
|
|
|
if !imageEq(got, want) {
|
|
|
|
gotPath, err := writeTempPNG("testpattern-mask-got", got)
|
|
|
|
if err != nil {
|
2014-11-01 07:48:58 -04:00
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2014-11-04 08:51:09 -05:00
|
|
|
wantPath, err := writeTempPNG("testpattern-mask-want", want)
|
|
|
|
if err != nil {
|
2014-11-01 07:48:58 -04:00
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
t.Errorf("got\n%s\nwant\n%s", gotPath, wantPath)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-11-04 08:51:09 -05:00
|
|
|
func writeTempPNG(prefix string, m image.Image) (string, error) {
|
|
|
|
f, err := ioutil.TempFile("", 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()
|
|
|
|
}
|
|
|
|
|
2014-11-01 07:48:58 -04:00
|
|
|
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 {
|
2014-11-03 10:31:19 +11:00
|
|
|
const epsilon = 9
|
2014-11-01 07:48:58 -04:00
|
|
|
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++
|
2014-09-27 03:10:15 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-11-01 07:48:58 -04:00
|
|
|
badFrac := float64(badPx) / float64(b0.Dx()*b0.Dy())
|
|
|
|
return badFrac < 0.01
|
2014-09-27 03:10:15 +10:00
|
|
|
}
|