gpu/headless: make Screenshot take an input image to tranfer into

When extracting headless.Window's content via screenshots,
it can be useful to keep reusing the same image for output,
as well as specify which area of the Window is to be
extracted.
The updated Screenshot method does this by using the supplied
image.

API change: users must pass an existing image to Window.Screenshot.

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>
This commit is contained in:
Pierre Curto
2021-12-15 13:22:21 +01:00
committed by Elias Naur
parent 0c7b0b1d0b
commit af63c089f6
7 changed files with 34 additions and 32 deletions
+4 -7
View File
@@ -10,6 +10,7 @@ import (
"hash/maphash" "hash/maphash"
"image" "image"
"image/color" "image/color"
"image/draw"
"image/png" "image/png"
"io/ioutil" "io/ioutil"
"math" "math"
@@ -648,17 +649,13 @@ func (g *compute) frame(target RenderTarget) error {
func (g *compute) dumpAtlases() { func (g *compute) dumpAtlases() {
for i, a := range g.atlases { for i, a := range g.atlases {
dump, err := driver.DownloadImage(g.ctx, a.image, image.Rectangle{Max: a.size}) dump := image.NewRGBA(image.Rectangle{Max: a.size})
err := driver.DownloadImage(g.ctx, a.image, dump)
if err != nil { if err != nil {
panic(err) panic(err)
} }
nrgba := image.NewNRGBA(dump.Bounds()) nrgba := image.NewNRGBA(dump.Bounds())
bnd := dump.Bounds() draw.Draw(nrgba, image.Rectangle{}, dump, image.Point{}, draw.Src)
for x := bnd.Min.X; x < bnd.Max.X; x++ {
for y := bnd.Min.Y; y < bnd.Max.Y; y++ {
nrgba.SetNRGBA(x, y, f32color.RGBAToNRGBA(dump.RGBAAt(x, y)))
}
}
var buf bytes.Buffer var buf bytes.Buffer
if err := png.Encode(&buf, nrgba); err != nil { if err := png.Encode(&buf, nrgba); err != nil {
panic(err) panic(err)
+2 -1
View File
@@ -185,7 +185,8 @@ func newDriver(t *testing.T) driver.Device {
} }
func screenshot(t *testing.T, d driver.Device, fbo driver.Texture, size image.Point) *image.RGBA { func screenshot(t *testing.T, d driver.Device, fbo driver.Texture, size image.Point) *image.RGBA {
img, err := driver.DownloadImage(d, fbo, image.Rectangle{Max: size}) img := image.NewRGBA(image.Rectangle{Max: size})
err := driver.DownloadImage(d, fbo, img)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
+10 -12
View File
@@ -122,7 +122,12 @@ func (w *Window) Release() {
} }
} }
// Frame replace the window content and state with the // Size returns the window size.
func (w *Window) Size() image.Point {
return w.size
}
// Frame replaces the window content and state with the
// operation list. // operation list.
func (w *Window) Frame(frame *op.Ops) error { func (w *Window) Frame(frame *op.Ops) error {
return contextDo(w.ctx, func() error { return contextDo(w.ctx, func() error {
@@ -131,18 +136,11 @@ func (w *Window) Frame(frame *op.Ops) error {
}) })
} }
// Screenshot returns an image with the content of the window. // Screenshot transfers the Window content at origin img.Rect.Min to img.
func (w *Window) Screenshot() (*image.RGBA, error) { func (w *Window) Screenshot(img *image.RGBA) error {
var img *image.RGBA return contextDo(w.ctx, func() error {
err := contextDo(w.ctx, func() error { return driver.DownloadImage(w.dev, w.fboTex, img)
var err error
img, err = driver.DownloadImage(w.dev, w.fboTex, image.Rectangle{Max: w.size})
return err
}) })
if err != nil {
return nil, err
}
return img, nil
} }
func contextDo(ctx context, f func() error) error { func contextDo(ctx context, f func() error) error {
+6 -3
View File
@@ -28,7 +28,8 @@ func TestHeadless(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
img, err := w.Screenshot() img := image.NewRGBA(image.Rectangle{Max: w.Size()})
err := w.Screenshot(img)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -69,7 +70,8 @@ func TestClipping(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
img, err := w.Screenshot() img := image.NewRGBA(image.Rectangle{Max: w.Size()})
err := w.Screenshot(img)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -108,7 +110,8 @@ func TestDepth(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
img, err := w.Screenshot() img := image.NewRGBA(image.Rectangle{Max: w.Size()})
err := w.Screenshot(img)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
+4 -4
View File
@@ -199,17 +199,17 @@ func (f Features) Has(feats Features) bool {
return f&feats == feats return f&feats == feats
} }
func DownloadImage(d Device, t Texture, r image.Rectangle) (*image.RGBA, error) { func DownloadImage(d Device, t Texture, img *image.RGBA) error {
img := image.NewRGBA(r) r := img.Bounds()
if err := t.ReadPixels(r, img.Pix, img.Stride); err != nil { if err := t.ReadPixels(r, img.Pix, img.Stride); err != nil {
return nil, err return err
} }
if d.Caps().BottomLeftOrigin { if d.Caps().BottomLeftOrigin {
// OpenGL origin is in the lower-left corner. Flip the image to // OpenGL origin is in the lower-left corner. Flip the image to
// match. // match.
flipImageY(r.Dx()*4, r.Dy(), img.Pix) flipImageY(r.Dx()*4, r.Dy(), img.Pix)
} }
return img, nil return nil
} }
func flipImageY(stride, height int, pixels []byte) { func flipImageY(stride, height int, pixels []byte) {
+2 -1
View File
@@ -47,7 +47,8 @@ func resetOps(gtx layout.Context) {
func finishBenchmark(b *testing.B, w *headless.Window) { func finishBenchmark(b *testing.B, w *headless.Window) {
b.StopTimer() b.StopTimer()
if *dumpImages { if *dumpImages {
img, err := w.Screenshot() img := image.NewRGBA(image.Rectangle{Max: w.Size()})
err := w.Screenshot(img)
w.Release() w.Release()
if err != nil { if err != nil {
b.Error(err) b.Error(err)
+6 -4
View File
@@ -59,7 +59,7 @@ func buildSquares(size int) paint.ImageOp {
return paint.NewImageOp(im) return paint.NewImageOp(im)
} }
func drawImage(t *testing.T, size int, ops *op.Ops, draw func(o *op.Ops)) (im *image.RGBA, err error) { func drawImage(t *testing.T, size int, ops *op.Ops, draw func(o *op.Ops)) (*image.RGBA, error) {
sz := image.Point{X: size, Y: size} sz := image.Point{X: size, Y: size}
w := newWindow(t, sz.X, sz.Y) w := newWindow(t, sz.X, sz.Y)
defer w.Release() defer w.Release()
@@ -67,7 +67,9 @@ func drawImage(t *testing.T, size int, ops *op.Ops, draw func(o *op.Ops)) (im *i
if err := w.Frame(ops); err != nil { if err := w.Frame(ops); err != nil {
return nil, err return nil, err
} }
return w.Screenshot() img := image.NewRGBA(image.Rectangle{Max: sz})
err := w.Screenshot(img)
return img, err
} }
func run(t *testing.T, f func(o *op.Ops), c func(r result)) { func run(t *testing.T, f func(o *op.Ops), c func(r result)) {
@@ -106,7 +108,6 @@ type frameT struct {
func multiRun(t *testing.T, frames ...frameT) { func multiRun(t *testing.T, frames ...frameT) {
// draw a few times and check that it is correct each time, to // draw a few times and check that it is correct each time, to
// ensure any caching effects still generate the correct images. // ensure any caching effects still generate the correct images.
var img *image.RGBA
var err error var err error
sz := image.Point{X: 128, Y: 128} sz := image.Point{X: 128, Y: 128}
w := newWindow(t, sz.X, sz.Y) w := newWindow(t, sz.X, sz.Y)
@@ -119,7 +120,8 @@ func multiRun(t *testing.T, frames ...frameT) {
t.Errorf("rendering failed: %v", err) t.Errorf("rendering failed: %v", err)
continue continue
} }
img, err = w.Screenshot() img := image.NewRGBA(image.Rectangle{Max: sz})
err = w.Screenshot(img)
if err != nil { if err != nil {
t.Errorf("screenshot failed: %v", err) t.Errorf("screenshot failed: %v", err)
continue continue