From af63c089f63cdf07bb1ffad1a6106f705162a5a4 Mon Sep 17 00:00:00 2001 From: Pierre Curto Date: Wed, 15 Dec 2021 13:22:21 +0100 Subject: [PATCH] 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 --- gpu/compute.go | 11 ++++------- gpu/headless/driver_test.go | 3 ++- gpu/headless/headless.go | 22 ++++++++++------------ gpu/headless/headless_test.go | 9 ++++++--- gpu/internal/driver/driver.go | 8 ++++---- gpu/internal/rendertest/bench_test.go | 3 ++- gpu/internal/rendertest/util_test.go | 10 ++++++---- 7 files changed, 34 insertions(+), 32 deletions(-) diff --git a/gpu/compute.go b/gpu/compute.go index ce06add5..ee6e127c 100644 --- a/gpu/compute.go +++ b/gpu/compute.go @@ -10,6 +10,7 @@ import ( "hash/maphash" "image" "image/color" + "image/draw" "image/png" "io/ioutil" "math" @@ -648,17 +649,13 @@ func (g *compute) frame(target RenderTarget) error { func (g *compute) dumpAtlases() { 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 { panic(err) } nrgba := image.NewNRGBA(dump.Bounds()) - bnd := dump.Bounds() - 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))) - } - } + draw.Draw(nrgba, image.Rectangle{}, dump, image.Point{}, draw.Src) var buf bytes.Buffer if err := png.Encode(&buf, nrgba); err != nil { panic(err) diff --git a/gpu/headless/driver_test.go b/gpu/headless/driver_test.go index 34e4d530..dbfb253a 100644 --- a/gpu/headless/driver_test.go +++ b/gpu/headless/driver_test.go @@ -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 { - 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 { t.Fatal(err) } diff --git a/gpu/headless/headless.go b/gpu/headless/headless.go index 78353cec..22d86b1e 100644 --- a/gpu/headless/headless.go +++ b/gpu/headless/headless.go @@ -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. func (w *Window) Frame(frame *op.Ops) 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. -func (w *Window) Screenshot() (*image.RGBA, error) { - var img *image.RGBA - err := contextDo(w.ctx, func() error { - var err error - img, err = driver.DownloadImage(w.dev, w.fboTex, image.Rectangle{Max: w.size}) - return err +// Screenshot transfers the Window content at origin img.Rect.Min to img. +func (w *Window) Screenshot(img *image.RGBA) error { + return contextDo(w.ctx, func() error { + return driver.DownloadImage(w.dev, w.fboTex, img) }) - if err != nil { - return nil, err - } - return img, nil } func contextDo(ctx context, f func() error) error { diff --git a/gpu/headless/headless_test.go b/gpu/headless/headless_test.go index 2e016394..6c6cb10c 100644 --- a/gpu/headless/headless_test.go +++ b/gpu/headless/headless_test.go @@ -28,7 +28,8 @@ func TestHeadless(t *testing.T) { t.Fatal(err) } - img, err := w.Screenshot() + img := image.NewRGBA(image.Rectangle{Max: w.Size()}) + err := w.Screenshot(img) if err != nil { t.Fatal(err) } @@ -69,7 +70,8 @@ func TestClipping(t *testing.T) { t.Fatal(err) } - img, err := w.Screenshot() + img := image.NewRGBA(image.Rectangle{Max: w.Size()}) + err := w.Screenshot(img) if err != nil { t.Fatal(err) } @@ -108,7 +110,8 @@ func TestDepth(t *testing.T) { t.Fatal(err) } - img, err := w.Screenshot() + img := image.NewRGBA(image.Rectangle{Max: w.Size()}) + err := w.Screenshot(img) if err != nil { t.Fatal(err) } diff --git a/gpu/internal/driver/driver.go b/gpu/internal/driver/driver.go index 69791443..58cb89bd 100644 --- a/gpu/internal/driver/driver.go +++ b/gpu/internal/driver/driver.go @@ -199,17 +199,17 @@ func (f Features) Has(feats Features) bool { return f&feats == feats } -func DownloadImage(d Device, t Texture, r image.Rectangle) (*image.RGBA, error) { - img := image.NewRGBA(r) +func DownloadImage(d Device, t Texture, img *image.RGBA) error { + r := img.Bounds() if err := t.ReadPixels(r, img.Pix, img.Stride); err != nil { - return nil, err + return err } if d.Caps().BottomLeftOrigin { // OpenGL origin is in the lower-left corner. Flip the image to // match. flipImageY(r.Dx()*4, r.Dy(), img.Pix) } - return img, nil + return nil } func flipImageY(stride, height int, pixels []byte) { diff --git a/gpu/internal/rendertest/bench_test.go b/gpu/internal/rendertest/bench_test.go index dd333aeb..73366d38 100644 --- a/gpu/internal/rendertest/bench_test.go +++ b/gpu/internal/rendertest/bench_test.go @@ -47,7 +47,8 @@ func resetOps(gtx layout.Context) { func finishBenchmark(b *testing.B, w *headless.Window) { b.StopTimer() if *dumpImages { - img, err := w.Screenshot() + img := image.NewRGBA(image.Rectangle{Max: w.Size()}) + err := w.Screenshot(img) w.Release() if err != nil { b.Error(err) diff --git a/gpu/internal/rendertest/util_test.go b/gpu/internal/rendertest/util_test.go index b56188de..6f3f688c 100644 --- a/gpu/internal/rendertest/util_test.go +++ b/gpu/internal/rendertest/util_test.go @@ -59,7 +59,7 @@ func buildSquares(size int) paint.ImageOp { 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} w := newWindow(t, sz.X, sz.Y) 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 { 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)) { @@ -106,7 +108,6 @@ type frameT struct { func multiRun(t *testing.T, frames ...frameT) { // draw a few times and check that it is correct each time, to // ensure any caching effects still generate the correct images. - var img *image.RGBA var err error sz := image.Point{X: 128, Y: 128} w := newWindow(t, sz.X, sz.Y) @@ -119,7 +120,8 @@ func multiRun(t *testing.T, frames ...frameT) { t.Errorf("rendering failed: %v", err) continue } - img, err = w.Screenshot() + img := image.NewRGBA(image.Rectangle{Max: sz}) + err = w.Screenshot(img) if err != nil { t.Errorf("screenshot failed: %v", err) continue