diff --git a/internal/buffered/image.go b/internal/buffered/image.go index 9c08f918b..1f75d1e91 100644 --- a/internal/buffered/image.go +++ b/internal/buffered/image.go @@ -142,16 +142,26 @@ func (i *Image) At(x, y int) (r, g, b, a byte, err error) { panic("buffered: the command queue is not available yet at At") } + if x < 0 || y < 0 || x >= i.width || y >= i.height { + return 0, 0, 0, 0, nil + } + // If there are pixels or pending fillling that needs to be resolved, use this rather than resolving. // Resolving them needs to access GPU and is expensive (#1137). if i.hasFill { return i.fillColor.R, i.fillColor.G, i.fillColor.B, i.fillColor.A, nil } - if i.pixels != nil { - idx := i.width*y + x - return i.pixels[4*idx], i.pixels[4*idx+1], i.pixels[4*idx+2], i.pixels[4*idx+3], nil + + if i.pixels == nil { + pix, err := i.img.Pixels(0, 0, i.width, i.height) + if err != nil { + return 0, 0, 0, 0, err + } + i.pixels = pix } - return i.img.At(x, y) + + idx := i.width*y + x + return i.pixels[4*idx], i.pixels[4*idx+1], i.pixels[4*idx+2], i.pixels[4*idx+3], nil } func (i *Image) Dump(name string, blackbg bool) error { @@ -209,22 +219,9 @@ func (i *Image) ReplacePixels(pix []byte, x, y, width, height int) error { // TODO: Can we use (*restorable.Image).ReplacePixels? if i.pixels == nil { - pix := make([]byte, 4*i.width*i.height) - idx := 0 - img := i.img - sw, sh := i.width, i.height - for j := 0; j < sh; j++ { - for i := 0; i < sw; i++ { - r, g, b, a, err := img.At(i, j) - if err != nil { - return err - } - pix[4*idx] = r - pix[4*idx+1] = g - pix[4*idx+2] = b - pix[4*idx+3] = a - idx++ - } + pix, err := i.img.Pixels(0, 0, i.width, i.height) + if err != nil { + return err } i.pixels = pix } diff --git a/internal/mipmap/mipmap.go b/internal/mipmap/mipmap.go index 41e1737ad..42f51120a 100644 --- a/internal/mipmap/mipmap.go +++ b/internal/mipmap/mipmap.go @@ -92,8 +92,8 @@ func (m *Mipmap) ReplacePixels(pix []byte) { m.disposeMipmaps() } -func (m *Mipmap) At(x, y int) (r, g, b, a byte, err error) { - return m.orig.At(x, y) +func (m *Mipmap) Pixels(x, y, width, height int) ([]byte, error) { + return m.orig.Pixels(x, y, width, height) } func (m *Mipmap) DrawImage(src *Mipmap, bounds image.Rectangle, geom GeoM, colorm *affine.ColorM, mode driver.CompositeMode, filter driver.Filter) { diff --git a/internal/shareable/image.go b/internal/shareable/image.go index f23b8d704..6670b0fba 100644 --- a/internal/shareable/image.go +++ b/internal/shareable/image.go @@ -376,11 +376,26 @@ func (i *Image) replacePixels(p []byte) { i.backend.restorable.ReplacePixels(p, x, y, w, h) } -func (i *Image) At(x, y int) (byte, byte, byte, byte, error) { +func (img *Image) Pixels(x, y, width, height int) ([]byte, error) { backendsM.Lock() - r, g, b, a, err := i.at(x, y) - backendsM.Unlock() - return r, g, b, a, err + defer backendsM.Unlock() + + bs := make([]byte, 4*width*height) + idx := 0 + for j := y; j < y+height; j++ { + for i := x; i < x+width; i++ { + r, g, b, a, err := img.at(i, j) + if err != nil { + return nil, err + } + bs[4*idx] = r + bs[4*idx+1] = g + bs[4*idx+2] = b + bs[4*idx+3] = a + idx++ + } + } + return bs, nil } func (i *Image) at(x, y int) (byte, byte, byte, byte, error) { diff --git a/internal/shareable/image_test.go b/internal/shareable/image_test.go index 8b6da8ba1..0cef272b6 100644 --- a/internal/shareable/image_test.go +++ b/internal/shareable/image_test.go @@ -95,12 +95,16 @@ func TestEnsureNotShared(t *testing.T) { t.Errorf("got: %v, want: %v", got, want) } + pix, err := img4.Pixels(0, 0, size, size) + if err != nil { + t.Fatal(err) + } for j := 0; j < size; j++ { for i := 0; i < size; i++ { - r, g, b, a, err := img4.At(i, j) - if err != nil { - t.Fatal(err) - } + r := pix[4*(size*j+i)] + g := pix[4*(size*j+i)+1] + b := pix[4*(size*j+i)+2] + a := pix[4*(size*j+i)+3] got := color.RGBA{r, g, b, a} var want color.RGBA if i < dx0 || dx1 <= i || j < dy0 || dy1 <= j { @@ -108,7 +112,7 @@ func TestEnsureNotShared(t *testing.T) { want = color.RGBA{c, c, c, c} } if got != want { - t.Errorf("img4.At(%d, %d): got: %v, want: %v", i, j, got, want) + t.Errorf("at(%d, %d): got: %v, want: %v", i, j, got, want) } } } @@ -174,13 +178,17 @@ func TestReshared(t *testing.T) { t.Fatal(err) } + pix, err := img1.Pixels(0, 0, size, size) + if err != nil { + t.Fatal(err) + } for j := 0; j < size; j++ { for i := 0; i < size; i++ { want := color.RGBA{byte(i + j), byte(i + j), byte(i + j), byte(i + j)} - r, g, b, a, err := img1.At(i, j) - if err != nil { - t.Fatal(err) - } + r := pix[4*(size*j+i)] + g := pix[4*(size*j+i)+1] + b := pix[4*(size*j+i)+2] + a := pix[4*(size*j+i)+3] got := color.RGBA{r, g, b, a} if got != want { t.Errorf("got: %v, want: %v", got, want) @@ -193,13 +201,17 @@ func TestReshared(t *testing.T) { t.Errorf("got: %v, want: %v", got, want) } + pix, err = img1.Pixels(0, 0, size, size) + if err != nil { + t.Fatal(err) + } for j := 0; j < size; j++ { for i := 0; i < size; i++ { want := color.RGBA{byte(i + j), byte(i + j), byte(i + j), byte(i + j)} - r, g, b, a, err := img1.At(i, j) - if err != nil { - t.Fatal(err) - } + r := pix[4*(size*j+i)] + g := pix[4*(size*j+i)+1] + b := pix[4*(size*j+i)+2] + a := pix[4*(size*j+i)+3] got := color.RGBA{r, g, b, a} if got != want { t.Errorf("got: %v, want: %v", got, want) @@ -247,32 +259,40 @@ func TestExtend(t *testing.T) { // Ensure to allocate img1.ReplacePixels(p1) + pix0, err := img0.Pixels(0, 0, w0, h0) + if err != nil { + t.Fatal(err) + } for j := 0; j < h0; j++ { for i := 0; i < w0; i++ { - r, g, b, a, err := img0.At(i, j) - if err != nil { - t.Fatal(err) - } + r := pix0[4*(w0*j+i)] + g := pix0[4*(w0*j+i)+1] + b := pix0[4*(w0*j+i)+2] + a := pix0[4*(w0*j+i)+3] got := color.RGBA{r, g, b, a} c := byte(i + w0*j) want := color.RGBA{c, c, c, c} if got != want { - t.Errorf("img0.At(%d, %d): got: %v, want: %v", i, j, got, want) + t.Errorf("at(%d, %d): got: %v, want: %v", i, j, got, want) } } } + pix1, err := img1.Pixels(0, 0, w1, h1) + if err != nil { + t.Fatal(err) + } for j := 0; j < h1; j++ { for i := 0; i < w1; i++ { - r, g, b, a, err := img1.At(i, j) - if err != nil { - t.Fatal(err) - } + r := pix1[4*(w1*j+i)] + g := pix1[4*(w1*j+i)+1] + b := pix1[4*(w1*j+i)+2] + a := pix1[4*(w1*j+i)+3] got := color.RGBA{r, g, b, a} c := byte(i + w1*j) want := color.RGBA{c, c, c, c} if got != want { - t.Errorf("img1.At(%d, %d): got: %v, want: %v", i, j, got, want) + t.Errorf("at(%d, %d): got: %v, want: %v", i, j, got, want) } } } @@ -302,17 +322,21 @@ func TestReplacePixelsAfterDrawTriangles(t *testing.T) { dst.DrawTriangles(src, vs, is, nil, driver.CompositeModeCopy, driver.FilterNearest, driver.AddressClampToZero) dst.ReplacePixels(pix) + pix, err := dst.Pixels(0, 0, w, h) + if err != nil { + t.Fatal(err) + } for j := 0; j < h; j++ { for i := 0; i < w; i++ { - r, g, b, a, err := dst.At(i, j) - if err != nil { - t.Fatal(err) - } + r := pix[4*(w*j+i)] + g := pix[4*(w*j+i)+1] + b := pix[4*(w*j+i)+2] + a := pix[4*(w*j+i)+3] got := color.RGBA{r, g, b, a} c := byte(i + w*j) want := color.RGBA{c, c, c, c} if got != want { - t.Errorf("dst.At(%d, %d): got %v, want: %v", i, j, got, want) + t.Errorf("at(%d, %d): got %v, want: %v", i, j, got, want) } } } @@ -339,17 +363,19 @@ func TestSmallImages(t *testing.T) { is := graphics.QuadIndices() dst.DrawTriangles(src, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressClampToZero) + pix, err := dst.Pixels(0, 0, w, h) + if err != nil { + t.Fatal(err) + } for j := 0; j < h; j++ { for i := 0; i < w; i++ { - r, _, _, a, err := dst.At(i, j) - if err != nil { - t.Fatal(err) - } + r := pix[4*(w*j+i)] + a := pix[4*(w*j+i)+3] if got, want := r, byte(0xff); got != want { - t.Errorf("At(%d, %d) red: got: %d, want: %d", i, j, got, want) + t.Errorf("at(%d, %d) red: got: %d, want: %d", i, j, got, want) } if got, want := a, byte(0xff); got != want { - t.Errorf("At(%d, %d) alpha: got: %d, want: %d", i, j, got, want) + t.Errorf("at(%d, %d) alpha: got: %d, want: %d", i, j, got, want) } } } @@ -360,7 +386,9 @@ func TestLongImages(t *testing.T) { const w, h = 1, 6 src := NewImage(w, h, false) defer src.MarkDisposed() - dst := NewImage(256, 256, false) + + const dstW, dstH = 256, 256 + dst := NewImage(dstW, dstH, false) defer dst.MarkDisposed() pix := make([]byte, 4*w*h) @@ -377,17 +405,19 @@ func TestLongImages(t *testing.T) { is := graphics.QuadIndices() dst.DrawTriangles(src, vs, is, nil, driver.CompositeModeSourceOver, driver.FilterNearest, driver.AddressClampToZero) + pix, err := dst.Pixels(0, 0, dstW, dstH) + if err != nil { + t.Fatal(err) + } for j := 0; j < h; j++ { for i := 0; i < w*scale; i++ { - r, _, _, a, err := dst.At(i, j) - if err != nil { - t.Fatal(err) - } + r := pix[4*(dstW*j+i)] + a := pix[4*(dstW*j+i)+3] if got, want := r, byte(0xff); got != want { - t.Errorf("At(%d, %d) red: got: %d, want: %d", i, j, got, want) + t.Errorf("at(%d, %d) red: got: %d, want: %d", i, j, got, want) } if got, want := a, byte(0xff); got != want { - t.Errorf("At(%d, %d) alpha: got: %d, want: %d", i, j, got, want) + t.Errorf("at(%d, %d) alpha: got: %d, want: %d", i, j, got, want) } } }