github.com/SkycoinProject/gomobile@v0.0.0-20190312151609-d3739f865fa6/exp/sprite/portable/affine_test.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 6 7 import ( 8 "image" 9 "image/color" 10 "image/draw" 11 "image/png" 12 "io/ioutil" 13 "math" 14 "os" 15 "runtime" 16 "testing" 17 18 "golang.org/x/mobile/event/size" 19 "golang.org/x/mobile/exp/f32" 20 "golang.org/x/mobile/geom" 21 ) 22 23 func TestAffine(t *testing.T) { 24 if runtime.GOOS == "android" { 25 t.Skip("testdata not available on Android") 26 } 27 f, err := os.Open("../../../testdata/testpattern.png") 28 if err != nil { 29 t.Fatal(err) 30 } 31 defer f.Close() 32 srcOrig, _, err := image.Decode(f) 33 if err != nil { 34 t.Fatal(err) 35 } 36 src := image.NewRGBA(srcOrig.Bounds()) 37 draw.Draw(src, src.Rect, srcOrig, srcOrig.Bounds().Min, draw.Src) 38 39 const ( 40 pixW = 100 41 pixH = 100 42 ptW = geom.Pt(50) 43 ptH = geom.Pt(50) 44 ) 45 sz := size.Event{ 46 WidthPx: pixW, 47 HeightPx: pixH, 48 WidthPt: ptW, 49 HeightPt: ptH, 50 PixelsPerPt: float32(pixW) / float32(ptW), 51 } 52 53 got := image.NewRGBA(image.Rect(0, 0, pixW, pixH)) 54 blue := image.NewUniform(color.RGBA{B: 0xff, A: 0xff}) 55 draw.Draw(got, got.Bounds(), blue, image.Point{}, draw.Src) 56 57 b := src.Bounds() 58 b.Min.X += 10 59 b.Max.Y /= 2 60 61 var a f32.Affine 62 a.Identity() 63 a.Scale(&a, sz.PixelsPerPt, sz.PixelsPerPt) 64 a.Translate(&a, 0, 24) 65 a.Rotate(&a, float32(math.Asin(12./20))) 66 // See commentary in the render method defined in portable.go. 67 a.Scale(&a, 40/float32(b.Dx()), 20/float32(b.Dy())) 68 a.Inverse(&a) 69 70 affine(got, src, b, nil, &a, draw.Over) 71 72 ptTopLeft := geom.Point{0, 24} 73 ptBottomRight := geom.Point{12 + 32, 16} 74 75 drawCross(got, 0, 0) 76 drawCross(got, int(ptTopLeft.X.Px(sz.PixelsPerPt)), int(ptTopLeft.Y.Px(sz.PixelsPerPt))) 77 drawCross(got, int(ptBottomRight.X.Px(sz.PixelsPerPt)), int(ptBottomRight.Y.Px(sz.PixelsPerPt))) 78 drawCross(got, pixW-1, pixH-1) 79 80 const wantPath = "../../../testdata/testpattern-window.png" 81 f, err = os.Open(wantPath) 82 if err != nil { 83 t.Fatal(err) 84 } 85 defer f.Close() 86 wantSrc, _, err := image.Decode(f) 87 if err != nil { 88 t.Fatal(err) 89 } 90 want, ok := wantSrc.(*image.RGBA) 91 if !ok { 92 b := wantSrc.Bounds() 93 want = image.NewRGBA(b) 94 draw.Draw(want, b, wantSrc, b.Min, draw.Src) 95 } 96 97 if !imageEq(got, want) { 98 gotPath, err := writeTempPNG("testpattern-window-got", got) 99 if err != nil { 100 t.Fatal(err) 101 } 102 t.Errorf("got\n%s\nwant\n%s", gotPath, wantPath) 103 } 104 } 105 106 func TestAffineMask(t *testing.T) { 107 if runtime.GOOS == "android" { 108 t.Skip("testdata not available on Android") 109 } 110 f, err := os.Open("../../../testdata/testpattern.png") 111 if err != nil { 112 t.Fatal(err) 113 } 114 defer f.Close() 115 srcOrig, _, err := image.Decode(f) 116 if err != nil { 117 t.Fatal(err) 118 } 119 b := srcOrig.Bounds() 120 src := image.NewRGBA(b) 121 draw.Draw(src, src.Rect, srcOrig, b.Min, draw.Src) 122 mask := image.NewAlpha(b) 123 for y := b.Min.Y; y < b.Max.Y; y++ { 124 for x := b.Min.X; x < b.Max.X; x++ { 125 mask.Set(x, y, color.Alpha{A: uint8(x - b.Min.X)}) 126 } 127 } 128 want := image.NewRGBA(b) 129 draw.DrawMask(want, want.Rect, src, b.Min, mask, b.Min, draw.Src) 130 131 a := new(f32.Affine) 132 a.Identity() 133 got := image.NewRGBA(b) 134 affine(got, src, b, mask, a, draw.Src) 135 136 if !imageEq(got, want) { 137 gotPath, err := writeTempPNG("testpattern-mask-got", got) 138 if err != nil { 139 t.Fatal(err) 140 } 141 wantPath, err := writeTempPNG("testpattern-mask-want", want) 142 if err != nil { 143 t.Fatal(err) 144 } 145 t.Errorf("got\n%s\nwant\n%s", gotPath, wantPath) 146 } 147 } 148 149 func writeTempPNG(prefix string, m image.Image) (string, error) { 150 f, err := ioutil.TempFile("", prefix+"-") 151 if err != nil { 152 return "", err 153 } 154 f.Close() 155 path := f.Name() + ".png" 156 f, err = os.Create(path) 157 if err != nil { 158 return "", err 159 } 160 if err := png.Encode(f, m); err != nil { 161 f.Close() 162 return "", err 163 } 164 return path, f.Close() 165 } 166 167 func drawCross(m *image.RGBA, x, y int) { 168 c := color.RGBA{0xff, 0, 0, 0xff} // red 169 m.SetRGBA(x+0, y-2, c) 170 m.SetRGBA(x+0, y-1, c) 171 m.SetRGBA(x-2, y+0, c) 172 m.SetRGBA(x-1, y+0, c) 173 m.SetRGBA(x+0, y+0, c) 174 m.SetRGBA(x+1, y+0, c) 175 m.SetRGBA(x+2, y+0, c) 176 m.SetRGBA(x+0, y+1, c) 177 m.SetRGBA(x+0, y+2, c) 178 } 179 180 func eqEpsilon(x, y uint8) bool { 181 const epsilon = 9 182 return x-y < epsilon || y-x < epsilon 183 } 184 185 func colorEq(c0, c1 color.RGBA) bool { 186 return eqEpsilon(c0.R, c1.R) && eqEpsilon(c0.G, c1.G) && eqEpsilon(c0.B, c1.B) && eqEpsilon(c0.A, c1.A) 187 } 188 189 func imageEq(m0, m1 *image.RGBA) bool { 190 b0 := m0.Bounds() 191 b1 := m1.Bounds() 192 if b0 != b1 { 193 return false 194 } 195 badPx := 0 196 for y := b0.Min.Y; y < b0.Max.Y; y++ { 197 for x := b0.Min.X; x < b0.Max.X; x++ { 198 c0, c1 := m0.At(x, y).(color.RGBA), m1.At(x, y).(color.RGBA) 199 if !colorEq(c0, c1) { 200 badPx++ 201 } 202 } 203 } 204 badFrac := float64(badPx) / float64(b0.Dx()*b0.Dy()) 205 return badFrac < 0.01 206 }