github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/mobile/gl/glutil/glimage_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 // +build darwin linux,!android 6 7 // TODO(crawshaw): Run tests on other OSs when more contexts are supported. 8 9 package glutil 10 11 import ( 12 "image" 13 "image/color" 14 "image/draw" 15 "image/png" 16 "io/ioutil" 17 "os" 18 "testing" 19 20 "golang.org/x/mobile/geom" 21 "golang.org/x/mobile/gl" 22 ) 23 24 func TestImage(t *testing.T) { 25 // GL testing strategy: 26 // 1. Create an offscreen framebuffer object. 27 // 2. Configure framebuffer to render to a GL texture. 28 // 3. Run test code: use glimage to draw testdata. 29 // 4. Copy GL texture back into system memory. 30 // 5. Compare to a pre-computed image. 31 32 f, err := os.Open("../../testdata/testpattern.png") 33 if err != nil { 34 t.Fatal(err) 35 } 36 defer f.Close() 37 src, _, err := image.Decode(f) 38 if err != nil { 39 t.Fatal(err) 40 } 41 42 ctxGL := createContext() 43 defer ctxGL.destroy() 44 45 const ( 46 pixW = 100 47 pixH = 100 48 ptW = geom.Pt(50) 49 ptH = geom.Pt(50) 50 ) 51 geom.PixelsPerPt = float32(pixW) / float32(ptW) 52 geom.Width = ptW 53 geom.Height = ptH 54 55 fBuf := gl.GenFramebuffer() 56 gl.BindFramebuffer(gl.FRAMEBUFFER, fBuf) 57 colorBuf := gl.GenRenderbuffer() 58 gl.BindRenderbuffer(gl.RENDERBUFFER, colorBuf) 59 // https://www.khronos.org/opengles/sdk/docs/man/xhtml/glRenderbufferStorage.xml 60 // says that the internalFormat "must be one of the following symbolic constants: 61 // GL_RGBA4, GL_RGB565, GL_RGB5_A1, GL_DEPTH_COMPONENT16, or GL_STENCIL_INDEX8". 62 gl.RenderbufferStorage(gl.RENDERBUFFER, gl.RGB565, pixW, pixH) 63 gl.FramebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, colorBuf) 64 65 if status := gl.CheckFramebufferStatus(gl.FRAMEBUFFER); status != gl.FRAMEBUFFER_COMPLETE { 66 t.Fatalf("framebuffer create failed: %v", status) 67 } 68 69 gl.ClearColor(0, 0, 1, 1) // blue 70 gl.Clear(gl.COLOR_BUFFER_BIT) 71 gl.Viewport(0, 0, pixW, pixH) 72 73 m := NewImage(src.Bounds().Dx(), src.Bounds().Dy()) 74 b := m.RGBA.Bounds() 75 draw.Draw(m.RGBA, b, src, src.Bounds().Min, draw.Src) 76 m.Upload() 77 b.Min.X += 10 78 b.Max.Y /= 2 79 80 // All-integer right-angled triangles offsetting the 81 // box: 24-32-40, 12-16-20. 82 ptTopLeft := geom.Point{0, 24} 83 ptTopRight := geom.Point{32, 0} 84 ptBottomLeft := geom.Point{12, 24 + 16} 85 ptBottomRight := geom.Point{12 + 32, 16} 86 m.Draw(ptTopLeft, ptTopRight, ptBottomLeft, b) 87 88 // For unknown reasons, a windowless OpenGL context renders upside- 89 // down. That is, a quad covering the initial viewport spans: 90 // 91 // (-1, -1) ( 1, -1) 92 // (-1, 1) ( 1, 1) 93 // 94 // To avoid modifying live code for tests, we flip the rows 95 // recovered from the renderbuffer. We are not the first: 96 // 97 // http://lists.apple.com/archives/mac-opengl/2010/Jun/msg00080.html 98 got := image.NewRGBA(image.Rect(0, 0, pixW, pixH)) 99 upsideDownPix := make([]byte, len(got.Pix)) 100 gl.ReadPixels(upsideDownPix, 0, 0, pixW, pixH, gl.RGBA, gl.UNSIGNED_BYTE) 101 for y := 0; y < pixH; y++ { 102 i0 := (pixH - 1 - y) * got.Stride 103 i1 := i0 + pixW*4 104 copy(got.Pix[y*got.Stride:], upsideDownPix[i0:i1]) 105 } 106 107 drawCross(got, 0, 0) 108 drawCross(got, int(ptTopLeft.X.Px()), int(ptTopLeft.Y.Px())) 109 drawCross(got, int(ptBottomRight.X.Px()), int(ptBottomRight.Y.Px())) 110 drawCross(got, pixW-1, pixH-1) 111 112 const wantPath = "../../testdata/testpattern-window.png" 113 f, err = os.Open(wantPath) 114 if err != nil { 115 t.Fatal(err) 116 } 117 defer f.Close() 118 wantSrc, _, err := image.Decode(f) 119 if err != nil { 120 t.Fatal(err) 121 } 122 want, ok := wantSrc.(*image.RGBA) 123 if !ok { 124 b := wantSrc.Bounds() 125 want = image.NewRGBA(b) 126 draw.Draw(want, b, wantSrc, b.Min, draw.Src) 127 } 128 129 if !imageEq(got, want) { 130 // Write out the image we got. 131 f, err = ioutil.TempFile("", "testpattern-window-got") 132 if err != nil { 133 t.Fatal(err) 134 } 135 f.Close() 136 gotPath := f.Name() + ".png" 137 f, err = os.Create(gotPath) 138 if err != nil { 139 t.Fatal(err) 140 } 141 if err := png.Encode(f, got); err != nil { 142 t.Fatal(err) 143 } 144 if err := f.Close(); err != nil { 145 t.Fatal(err) 146 } 147 t.Errorf("got\n%s\nwant\n%s", gotPath, wantPath) 148 } 149 } 150 151 func drawCross(m *image.RGBA, x, y int) { 152 c := color.RGBA{0xff, 0, 0, 0xff} // red 153 m.SetRGBA(x+0, y-2, c) 154 m.SetRGBA(x+0, y-1, c) 155 m.SetRGBA(x-2, y+0, c) 156 m.SetRGBA(x-1, y+0, c) 157 m.SetRGBA(x+0, y+0, c) 158 m.SetRGBA(x+1, y+0, c) 159 m.SetRGBA(x+2, y+0, c) 160 m.SetRGBA(x+0, y+1, c) 161 m.SetRGBA(x+0, y+2, c) 162 } 163 164 func eqEpsilon(x, y uint8) bool { 165 const epsilon = 9 166 return x-y < epsilon || y-x < epsilon 167 } 168 169 func colorEq(c0, c1 color.RGBA) bool { 170 return eqEpsilon(c0.R, c1.R) && eqEpsilon(c0.G, c1.G) && eqEpsilon(c0.B, c1.B) && eqEpsilon(c0.A, c1.A) 171 } 172 173 func imageEq(m0, m1 *image.RGBA) bool { 174 b0 := m0.Bounds() 175 b1 := m1.Bounds() 176 if b0 != b1 { 177 return false 178 } 179 badPx := 0 180 for y := b0.Min.Y; y < b0.Max.Y; y++ { 181 for x := b0.Min.X; x < b0.Max.X; x++ { 182 c0, c1 := m0.At(x, y).(color.RGBA), m1.At(x, y).(color.RGBA) 183 if !colorEq(c0, c1) { 184 badPx++ 185 } 186 } 187 } 188 badFrac := float64(badPx) / float64(b0.Dx()*b0.Dy()) 189 return badFrac < 0.01 190 }