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