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