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  }