From 5c79b8641214c863c923f777d5688d5c60df3717 Mon Sep 17 00:00:00 2001 From: Hajime Hoshi Date: Mon, 21 Mar 2022 02:08:37 +0900 Subject: [PATCH] internal/restorable: add a new parameter specifying a mask for ReplacePixels --- internal/atlas/image.go | 5 +- internal/restorable/image.go | 18 +-- internal/restorable/images_test.go | 200 +++++++++++++++++++++++++---- internal/restorable/rect.go | 65 +++++++++- internal/restorable/shader_test.go | 10 +- 5 files changed, 250 insertions(+), 48 deletions(-) diff --git a/internal/atlas/image.go b/internal/atlas/image.go index cd697b9e2..b5a0de103 100644 --- a/internal/atlas/image.go +++ b/internal/atlas/image.go @@ -563,7 +563,7 @@ func (i *Image) replacePixels(pix []byte) { px, py, pw, ph := i.regionWithPadding() if pix == nil { - i.backend.restorable.ReplacePixels(nil, px, py, pw, ph) + i.backend.restorable.ReplacePixels(nil, nil, px, py, pw, ph) return } @@ -598,7 +598,8 @@ func (i *Image) replacePixels(pix []byte) { copy(pixb[4*((j+paddingSize)*pw+paddingSize):], pix[4*j*ow:4*(j+1)*ow]) } - i.backend.restorable.ReplacePixels(pixb, px, py, pw, ph) + // TODO: Specify a mask if needed. + i.backend.restorable.ReplacePixels(pixb, nil, px, py, pw, ph) } func (img *Image) Pixels(graphicsDriver graphicsdriver.Graphics) ([]byte, error) { diff --git a/internal/restorable/image.go b/internal/restorable/image.go index 82e3b5af2..13b8c5873 100644 --- a/internal/restorable/image.go +++ b/internal/restorable/image.go @@ -38,11 +38,11 @@ func (p *Pixels) Apply(img *graphicscommand.Image) { p.pixelsRecords.apply(img) } -func (p *Pixels) AddOrReplace(pix []byte, x, y, width, height int) { +func (p *Pixels) AddOrReplace(pix []byte, mask []byte, x, y, width, height int) { if p.pixelsRecords == nil { p.pixelsRecords = &pixelsRecords{} } - p.pixelsRecords.addOrReplace(pix, x, y, width, height) + p.pixelsRecords.addOrReplace(pix, mask, x, y, width, height) } func (p *Pixels) Clear(x, y, width, height int) { @@ -130,7 +130,7 @@ func ensureEmptyImage() *Image { // As emptyImage is the source at clearImage, initialize this with ReplacePixels, not clearImage. // This operation is also important when restoring emptyImage. - emptyImage.ReplacePixels(pix, 0, 0, w, h) + emptyImage.ReplacePixels(pix, nil, 0, 0, w, h) theImages.add(emptyImage) return emptyImage } @@ -284,13 +284,13 @@ func (i *Image) makeStale() { // ClearPixels clears the specified region by ReplacePixels. func (i *Image) ClearPixels(x, y, width, height int) { - i.ReplacePixels(nil, x, y, width, height) + i.ReplacePixels(nil, nil, x, y, width, height) } // ReplacePixels replaces the image pixels with the given pixels slice. // // The specified region must not be overlapped with other regions by ReplacePixels. -func (i *Image) ReplacePixels(pixels []byte, x, y, width, height int) { +func (i *Image) ReplacePixels(pixels []byte, mask []byte, x, y, width, height int) { if width <= 0 || height <= 0 { panic("restorable: width/height must be positive") } @@ -323,7 +323,7 @@ func (i *Image) ReplacePixels(pixels []byte, x, y, width, height int) { // This function is responsible to copy this. copiedPixels := make([]byte, len(pixels)) copy(copiedPixels, pixels) - i.basePixels.AddOrReplace(copiedPixels, 0, 0, w, h) + i.basePixels.AddOrReplace(copiedPixels, mask, 0, 0, w, h) } else { i.basePixels.Clear(0, 0, w, h) } @@ -347,7 +347,7 @@ func (i *Image) ReplacePixels(pixels []byte, x, y, width, height int) { // This function is responsible to copy this. copiedPixels := make([]byte, len(pixels)) copy(copiedPixels, pixels) - i.basePixels.AddOrReplace(copiedPixels, x, y, width, height) + i.basePixels.AddOrReplace(copiedPixels, mask, x, y, width, height) } else { i.basePixels.Clear(x, y, width, height) } @@ -502,7 +502,7 @@ func (i *Image) readPixelsFromGPU(graphicsDriver graphicsdriver.Graphics) error return err } i.basePixels = Pixels{} - i.basePixels.AddOrReplace(pix, 0, 0, i.width, i.height) + i.basePixels.AddOrReplace(pix, nil, 0, 0, i.width, i.height) i.clearDrawTrianglesHistory() i.stale = false return nil @@ -630,7 +630,7 @@ func (i *Image) restore(graphicsDriver graphicsdriver.Graphics) error { if err := gimg.ReadPixels(graphicsDriver, pix); err != nil { return err } - i.basePixels.AddOrReplace(pix, 0, 0, w, h) + i.basePixels.AddOrReplace(pix, nil, 0, 0, w, h) } i.image = gimg diff --git a/internal/restorable/images_test.go b/internal/restorable/images_test.go index f832033bf..ba51bb377 100644 --- a/internal/restorable/images_test.go +++ b/internal/restorable/images_test.go @@ -61,7 +61,7 @@ func TestRestore(t *testing.T) { defer img0.Dispose() clr0 := color.RGBA{0x00, 0x00, 0x00, 0xff} - img0.ReplacePixels([]byte{clr0.R, clr0.G, clr0.B, clr0.A}, 0, 0, 1, 1) + img0.ReplacePixels([]byte{clr0.R, clr0.G, clr0.B, clr0.A}, nil, 0, 0, 1, 1) if err := restorable.ResolveStaleImages(ui.GraphicsDriverForTesting()); err != nil { t.Fatal(err) } @@ -129,7 +129,7 @@ func TestRestoreChain(t *testing.T) { } }() clr := color.RGBA{0x00, 0x00, 0x00, 0xff} - imgs[0].ReplacePixels([]byte{clr.R, clr.G, clr.B, clr.A}, 0, 0, 1, 1) + imgs[0].ReplacePixels([]byte{clr.R, clr.G, clr.B, clr.A}, nil, 0, 0, 1, 1) for i := 0; i < num-1; i++ { vs := quadVertices(1, 1, 0, 0) is := graphics.QuadIndices() @@ -174,11 +174,11 @@ func TestRestoreChain2(t *testing.T) { }() clr0 := color.RGBA{0xff, 0x00, 0x00, 0xff} - imgs[0].ReplacePixels([]byte{clr0.R, clr0.G, clr0.B, clr0.A}, 0, 0, w, h) + imgs[0].ReplacePixels([]byte{clr0.R, clr0.G, clr0.B, clr0.A}, nil, 0, 0, w, h) clr7 := color.RGBA{0x00, 0xff, 0x00, 0xff} - imgs[7].ReplacePixels([]byte{clr7.R, clr7.G, clr7.B, clr7.A}, 0, 0, w, h) + imgs[7].ReplacePixels([]byte{clr7.R, clr7.G, clr7.B, clr7.A}, nil, 0, 0, w, h) clr8 := color.RGBA{0x00, 0x00, 0xff, 0xff} - imgs[8].ReplacePixels([]byte{clr8.R, clr8.G, clr8.B, clr8.A}, 0, 0, w, h) + imgs[8].ReplacePixels([]byte{clr8.R, clr8.G, clr8.B, clr8.A}, nil, 0, 0, w, h) is := graphics.QuadIndices() dr := graphicsdriver.Region{ @@ -228,7 +228,7 @@ func TestRestoreOverrideSource(t *testing.T) { }() clr0 := color.RGBA{0x00, 0x00, 0x00, 0xff} clr1 := color.RGBA{0x00, 0x00, 0x01, 0xff} - img1.ReplacePixels([]byte{clr0.R, clr0.G, clr0.B, clr0.A}, 0, 0, w, h) + img1.ReplacePixels([]byte{clr0.R, clr0.G, clr0.B, clr0.A}, nil, 0, 0, w, h) is := graphics.QuadIndices() dr := graphicsdriver.Region{ X: 0, @@ -238,7 +238,7 @@ func TestRestoreOverrideSource(t *testing.T) { } img2.DrawTriangles([graphics.ShaderImageNum]*restorable.Image{img1}, [graphics.ShaderImageNum - 1][2]float32{}, quadVertices(w, h, 0, 0), is, affine.ColorMIdentity{}, graphicsdriver.CompositeModeSourceOver, graphicsdriver.FilterNearest, graphicsdriver.AddressUnsafe, dr, graphicsdriver.Region{}, nil, nil, false) img3.DrawTriangles([graphics.ShaderImageNum]*restorable.Image{img2}, [graphics.ShaderImageNum - 1][2]float32{}, quadVertices(w, h, 0, 0), is, affine.ColorMIdentity{}, graphicsdriver.CompositeModeSourceOver, graphicsdriver.FilterNearest, graphicsdriver.AddressUnsafe, dr, graphicsdriver.Region{}, nil, nil, false) - img0.ReplacePixels([]byte{clr1.R, clr1.G, clr1.B, clr1.A}, 0, 0, w, h) + img0.ReplacePixels([]byte{clr1.R, clr1.G, clr1.B, clr1.A}, nil, 0, 0, w, h) img1.DrawTriangles([graphics.ShaderImageNum]*restorable.Image{img0}, [graphics.ShaderImageNum - 1][2]float32{}, quadVertices(w, h, 0, 0), is, affine.ColorMIdentity{}, graphicsdriver.CompositeModeSourceOver, graphicsdriver.FilterNearest, graphicsdriver.AddressUnsafe, dr, graphicsdriver.Region{}, nil, nil, false) if err := restorable.ResolveStaleImages(ui.GraphicsDriverForTesting()); err != nil { t.Fatal(err) @@ -411,7 +411,7 @@ func TestRestoreComplexGraph(t *testing.T) { func newImageFromImage(rgba *image.RGBA) *restorable.Image { s := rgba.Bounds().Size() img := restorable.NewImage(s.X, s.Y) - img.ReplacePixels(rgba.Pix, 0, 0, s.X, s.Y) + img.ReplacePixels(rgba.Pix, nil, 0, 0, s.X, s.Y) return img } @@ -485,7 +485,7 @@ func TestReplacePixels(t *testing.T) { for i := range pix { pix[i] = 0xff } - img.ReplacePixels(pix, 5, 7, 4, 4) + img.ReplacePixels(pix, nil, 5, 7, 4, 4) // Check the region (5, 7)-(9, 11). Outside state is indeterministic. for j := 7; j < 11; j++ { for i := 5; i < 9; i++ { @@ -541,7 +541,7 @@ func TestDrawTrianglesAndReplacePixels(t *testing.T) { Height: 1, } img1.DrawTriangles([graphics.ShaderImageNum]*restorable.Image{img0}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, affine.ColorMIdentity{}, graphicsdriver.CompositeModeCopy, graphicsdriver.FilterNearest, graphicsdriver.AddressUnsafe, dr, graphicsdriver.Region{}, nil, nil, false) - img1.ReplacePixels([]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, 0, 0, 2, 1) + img1.ReplacePixels([]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, nil, 0, 0, 2, 1) if err := restorable.ResolveStaleImages(ui.GraphicsDriverForTesting()); err != nil { t.Fatal(err) @@ -612,7 +612,7 @@ func TestReplacePixelsPart(t *testing.T) { img := restorable.NewImage(4, 4) // This doesn't make the image stale. Its base pixels are available. - img.ReplacePixels(pix, 1, 1, 2, 2) + img.ReplacePixels(pix, nil, 1, 1, 2, 2) cases := []struct { i int @@ -687,7 +687,7 @@ func TestReplacePixelsOnly(t *testing.T) { defer img1.Dispose() for i := 0; i < w*h; i += 5 { - img0.ReplacePixels([]byte{1, 2, 3, 4}, i%w, i/w, 1, 1) + img0.ReplacePixels([]byte{1, 2, 3, 4}, nil, i%w, i/w, 1, 1) } vs := quadVertices(1, 1, 0, 0) @@ -699,7 +699,7 @@ func TestReplacePixelsOnly(t *testing.T) { Height: 1, } img1.DrawTriangles([graphics.ShaderImageNum]*restorable.Image{img0}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, affine.ColorMIdentity{}, graphicsdriver.CompositeModeCopy, graphicsdriver.FilterNearest, graphicsdriver.AddressUnsafe, dr, graphicsdriver.Region{}, nil, nil, false) - img0.ReplacePixels([]byte{5, 6, 7, 8}, 0, 0, 1, 1) + img0.ReplacePixels([]byte{5, 6, 7, 8}, nil, 0, 0, 1, 1) // BasePixelsForTesting is available without GPU accessing. for j := 0; j < h; j++ { @@ -742,14 +742,14 @@ func TestReadPixelsFromVolatileImage(t *testing.T) { src := restorable.NewImage(w, h) // First, make sure that dst has pixels - dst.ReplacePixels(make([]byte, 4*w*h), 0, 0, w, h) + dst.ReplacePixels(make([]byte, 4*w*h), nil, 0, 0, w, h) // Second, draw src to dst. If the implementation is correct, dst becomes stale. pix := make([]byte, 4*w*h) for i := range pix { pix[i] = 0xff } - src.ReplacePixels(pix, 0, 0, w, h) + src.ReplacePixels(pix, nil, 0, 0, w, h) vs := quadVertices(1, 1, 0, 0) is := graphics.QuadIndices() dr := graphicsdriver.Region{ @@ -786,7 +786,7 @@ func TestAllowReplacePixelsAfterDrawTriangles(t *testing.T) { Height: h, } dst.DrawTriangles([graphics.ShaderImageNum]*restorable.Image{src}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, affine.ColorMIdentity{}, graphicsdriver.CompositeModeSourceOver, graphicsdriver.FilterNearest, graphicsdriver.AddressUnsafe, dr, graphicsdriver.Region{}, nil, nil, false) - dst.ReplacePixels(make([]byte, 4*w*h), 0, 0, w, h) + dst.ReplacePixels(make([]byte, 4*w*h), nil, 0, 0, w, h) // ReplacePixels for a whole image doesn't panic. } @@ -810,7 +810,7 @@ func TestDisallowReplacePixelsForPartAfterDrawTriangles(t *testing.T) { Height: h, } dst.DrawTriangles([graphics.ShaderImageNum]*restorable.Image{src}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, affine.ColorMIdentity{}, graphicsdriver.CompositeModeSourceOver, graphicsdriver.FilterNearest, graphicsdriver.AddressUnsafe, dr, graphicsdriver.Region{}, nil, nil, false) - dst.ReplacePixels(make([]byte, 4), 0, 0, 1, 1) + dst.ReplacePixels(make([]byte, 4), nil, 0, 0, 1, 1) } func TestExtend(t *testing.T) { @@ -832,7 +832,7 @@ func TestExtend(t *testing.T) { } } - orig.ReplacePixels(pix, 0, 0, w, h) + orig.ReplacePixels(pix, nil, 0, 0, w, h) extended := orig.Extend(w*2, h*2) // After this, orig is already disposed. for j := 0; j < h*2; j++ { @@ -855,13 +855,13 @@ func TestExtend(t *testing.T) { func TestClearPixels(t *testing.T) { const w, h = 16, 16 img := restorable.NewImage(w, h) - img.ReplacePixels(make([]byte, 4*4*4), 0, 0, 4, 4) - img.ReplacePixels(make([]byte, 4*4*4), 4, 0, 4, 4) + img.ReplacePixels(make([]byte, 4*4*4), nil, 0, 0, 4, 4) + img.ReplacePixels(make([]byte, 4*4*4), nil, 4, 0, 4, 4) img.ClearPixels(0, 0, 4, 4) img.ClearPixels(4, 0, 4, 4) // After clearing, the regions will be available again. - img.ReplacePixels(make([]byte, 4*8*4), 0, 0, 8, 4) + img.ReplacePixels(make([]byte, 4*8*4), nil, 0, 0, 8, 4) } func TestMutateSlices(t *testing.T) { @@ -875,7 +875,7 @@ func TestMutateSlices(t *testing.T) { pix[4*i+2] = byte(i) pix[4*i+3] = 0xff } - src.ReplacePixels(pix, 0, 0, w, h) + src.ReplacePixels(pix, nil, 0, 0, w, h) vs := quadVertices(w, h, 0, 0) is := make([]uint16, len(graphics.QuadIndices())) @@ -932,7 +932,7 @@ func TestOverlappedPixels(t *testing.T) { pix0[idx+3] = 0xff } } - dst.ReplacePixels(pix0, 0, 0, 2, 2) + dst.ReplacePixels(pix0, nil, 0, 0, 2, 2) pix1 := make([]byte, 4*2*2) for j := 0; j < 2; j++ { @@ -944,7 +944,7 @@ func TestOverlappedPixels(t *testing.T) { pix1[idx+3] = 0xff } } - dst.ReplacePixels(pix1, 1, 1, 2, 2) + dst.ReplacePixels(pix1, nil, 1, 1, 2, 2) wantColors := []color.RGBA{ {0xff, 0, 0, 0xff}, @@ -973,7 +973,7 @@ func TestOverlappedPixels(t *testing.T) { } } - dst.ReplacePixels(nil, 1, 0, 2, 2) + dst.ReplacePixels(nil, nil, 1, 0, 2, 2) wantColors = []color.RGBA{ {0xff, 0, 0, 0xff}, @@ -1012,7 +1012,7 @@ func TestOverlappedPixels(t *testing.T) { pix2[idx+3] = 0xff } } - dst.ReplacePixels(pix2, 1, 1, 2, 2) + dst.ReplacePixels(pix2, nil, 1, 1, 2, 2) wantColors = []color.RGBA{ {0xff, 0, 0, 0xff}, @@ -1062,3 +1062,151 @@ func TestOverlappedPixels(t *testing.T) { } } } + +func TestReplacePixelsWithMask(t *testing.T) { + dst := restorable.NewImage(3, 3) + + pix0 := make([]byte, 4*2*2) + for j := 0; j < 2; j++ { + for i := 0; i < 2; i++ { + idx := 4 * (j*2 + i) + pix0[idx] = 0xff + pix0[idx+1] = 0 + pix0[idx+2] = 0 + pix0[idx+3] = 0xff + } + } + dst.ReplacePixels(pix0, nil, 0, 0, 2, 2) + + pix1 := make([]byte, 4*2*2) + for j := 0; j < 2; j++ { + for i := 0; i < 2; i++ { + idx := 4 * (j*2 + i) + pix1[idx] = 0 + pix1[idx+1] = 0xff + pix1[idx+2] = 0 + pix1[idx+3] = 0xff + } + } + + mask1 := []byte{0b00001110} + dst.ReplacePixels(pix1, mask1, 1, 1, 2, 2) + + wantColors := []color.RGBA{ + {0xff, 0, 0, 0xff}, + {0xff, 0, 0, 0xff}, + {0, 0, 0, 0}, + + {0xff, 0, 0, 0xff}, + {0xff, 0, 0, 0xff}, + {0, 0xff, 0, 0xff}, + + {0, 0, 0, 0}, + {0, 0xff, 0, 0xff}, + {0, 0xff, 0, 0xff}, + } + for j := 0; j < 3; j++ { + for i := 0; i < 3; i++ { + r, g, b, a, err := dst.At(ui.GraphicsDriverForTesting(), i, j) + if err != nil { + t.Fatal(err) + } + got := color.RGBA{r, g, b, a} + want := wantColors[3*j+i] + if got != want { + t.Errorf("color at (%d, %d): got %v, want: %v", i, j, got, want) + } + } + } + + dst.ReplacePixels(nil, nil, 1, 0, 2, 2) + + wantColors = []color.RGBA{ + {0xff, 0, 0, 0xff}, + {0, 0, 0, 0}, + {0, 0, 0, 0}, + + {0xff, 0, 0, 0xff}, + {0, 0, 0, 0}, + {0, 0, 0, 0}, + + {0, 0, 0, 0}, + {0, 0xff, 0, 0xff}, + {0, 0xff, 0, 0xff}, + } + for j := 0; j < 3; j++ { + for i := 0; i < 3; i++ { + r, g, b, a, err := dst.At(ui.GraphicsDriverForTesting(), i, j) + if err != nil { + t.Fatal(err) + } + got := color.RGBA{r, g, b, a} + want := wantColors[3*j+i] + if got != want { + t.Errorf("color at (%d, %d): got %v, want: %v", i, j, got, want) + } + } + } + + // Update the same region with pix1/mask1. + pix2 := make([]byte, 4*2*2) + for j := 0; j < 2; j++ { + for i := 0; i < 2; i++ { + idx := 4 * (j*2 + i) + pix2[idx] = 0 + pix2[idx+1] = 0 + pix2[idx+2] = 0xff + pix2[idx+3] = 0xff + } + } + mask2 := []byte{0b00000111} + dst.ReplacePixels(pix2, mask2, 1, 1, 2, 2) + + wantColors = []color.RGBA{ + {0xff, 0, 0, 0xff}, + {0, 0, 0, 0}, + {0, 0, 0, 0}, + + {0xff, 0, 0, 0xff}, + {0, 0, 0xff, 0xff}, + {0, 0, 0xff, 0xff}, + + {0, 0, 0, 0}, + {0, 0, 0xff, 0xff}, + {0, 0xff, 0, 0xff}, + } + for j := 0; j < 3; j++ { + for i := 0; i < 3; i++ { + r, g, b, a, err := dst.At(ui.GraphicsDriverForTesting(), i, j) + if err != nil { + t.Fatal(err) + } + got := color.RGBA{r, g, b, a} + want := wantColors[3*j+i] + if got != want { + t.Errorf("color at (%d, %d): got %v, want: %v", i, j, got, want) + } + } + } + + if err := restorable.ResolveStaleImages(ui.GraphicsDriverForTesting()); err != nil { + t.Fatal(err) + } + if err := restorable.RestoreIfNeeded(ui.GraphicsDriverForTesting()); err != nil { + t.Fatal(err) + } + + for j := 0; j < 3; j++ { + for i := 0; i < 3; i++ { + r, g, b, a, err := dst.At(ui.GraphicsDriverForTesting(), i, j) + if err != nil { + t.Fatal(err) + } + got := color.RGBA{r, g, b, a} + want := wantColors[3*j+i] + if got != want { + t.Errorf("color at (%d, %d): got %v, want: %v", i, j, got, want) + } + } + } +} diff --git a/internal/restorable/rect.go b/internal/restorable/rect.go index cabc8ab7d..9fee2c810 100644 --- a/internal/restorable/rect.go +++ b/internal/restorable/rect.go @@ -24,6 +24,7 @@ import ( type pixelsRecord struct { rect image.Rectangle pix []byte + mask []byte } func (p *pixelsRecord) clearIfOverlapped(rect image.Rectangle) { @@ -42,20 +43,48 @@ func (p *pixelsRecord) clearIfOverlapped(rect image.Rectangle) { } } +func (p *pixelsRecord) merge(pix []byte, mask []byte) { + if len(p.pix) != len(pix) { + panic(fmt.Sprintf("restorable: len(p.pix) (%d) and len(pix) (%d) must match but not", len(p.pix), len(pix))) + } + + if mask == nil { + p.pix = pix + return + } + + if p.mask == nil { + p.mask = mask + } + if len(p.mask) != len(mask) { + panic(fmt.Sprintf("restorable: len(p.mask) (%d) and len(mask) (%d) must match but not", len(p.mask), len(mask))) + } + + for i := 0; i < len(p.pix)/4; i++ { + if mask[i/8]>>(i%8)&1 == 0 { + continue + } + p.mask[i/8] |= 1 << (i % 8) + copy(p.pix[4*i:4*(i+1)], pix[4*i:4*(i+1)]) + } +} + func (p *pixelsRecord) at(x, y int) (r, g, b, a byte, ok bool) { if !image.Pt(x, y).In(p.rect) { return 0, 0, 0, 0, false } - - idx := 4 * ((y-p.rect.Min.Y)*p.rect.Dx() + (x - p.rect.Min.X)) - return p.pix[idx], p.pix[idx+1], p.pix[idx+2], p.pix[idx+3], true + idx := ((y-p.rect.Min.Y)*p.rect.Dx() + (x - p.rect.Min.X)) + if p.mask != nil && p.mask[idx/8]>>(idx%8)&1 == 0 { + return 0, 0, 0, 0, false + } + return p.pix[4*idx], p.pix[4*idx+1], p.pix[4*idx+2], p.pix[4*idx+3], true } type pixelsRecords struct { records []*pixelsRecord } -func (pr *pixelsRecords) addOrReplace(pixels []byte, x, y, width, height int) { +func (pr *pixelsRecords) addOrReplace(pixels []byte, mask []byte, x, y, width, height int) { if len(pixels) != 4*width*height { msg := fmt.Sprintf("restorable: len(pixels) must be 4*%d*%d = %d but %d", width, height, 4*width*height, len(pixels)) if pixels == nil { @@ -64,8 +93,32 @@ func (pr *pixelsRecords) addOrReplace(pixels []byte, x, y, width, height int) { panic(msg) } - // Remove or update the duplicated records first. rect := image.Rect(x, y, x+width, y+height) + if mask != nil { + // If a mask is specified, try merging the records. + var merged bool + for idx := len(pr.records) - 1; idx >= 0; idx-- { + if r := pr.records[idx]; r.rect.Overlaps(rect) { + if r.rect == rect { + r.merge(pixels, mask) + merged = true + } + // If there is an overlap with other regions in the existing records, + // give up modifying them and just add a record. + break + } + } + if !merged { + pr.records = append(pr.records, &pixelsRecord{ + rect: rect, + pix: pixels, + mask: mask, + }) + } + return + } + + // Remove or update the duplicated records first. var n int for _, r := range pr.records { if r.rect.In(rect) { @@ -118,6 +171,6 @@ func (pr *pixelsRecords) at(i, j int) (r, g, b, a byte, ok bool) { func (pr *pixelsRecords) apply(img *graphicscommand.Image) { // TODO: Isn't this too heavy? Can we merge the operations? for _, r := range pr.records { - img.ReplacePixels(r.pix, nil, r.rect.Min.X, r.rect.Min.Y, r.rect.Dx(), r.rect.Dy()) + img.ReplacePixels(r.pix, r.mask, r.rect.Min.X, r.rect.Min.Y, r.rect.Dx(), r.rect.Dy()) } } diff --git a/internal/restorable/shader_test.go b/internal/restorable/shader_test.go index 92e5b0699..77214a837 100644 --- a/internal/restorable/shader_test.go +++ b/internal/restorable/shader_test.go @@ -96,7 +96,7 @@ func TestShaderChain(t *testing.T) { imgs = append(imgs, img) } - imgs[0].ReplacePixels([]byte{0xff, 0, 0, 0xff}, 0, 0, 1, 1) + imgs[0].ReplacePixels([]byte{0xff, 0, 0, 0xff}, nil, 0, 0, 1, 1) ir := etesting.ShaderProgramImages(needsInvertY(), 1) s := restorable.NewShader(&ir) @@ -131,9 +131,9 @@ func TestShaderMultipleSources(t *testing.T) { for i := range srcs { srcs[i] = restorable.NewImage(1, 1) } - srcs[0].ReplacePixels([]byte{0x40, 0, 0, 0xff}, 0, 0, 1, 1) - srcs[1].ReplacePixels([]byte{0, 0x80, 0, 0xff}, 0, 0, 1, 1) - srcs[2].ReplacePixels([]byte{0, 0, 0xc0, 0xff}, 0, 0, 1, 1) + srcs[0].ReplacePixels([]byte{0x40, 0, 0, 0xff}, nil, 0, 0, 1, 1) + srcs[1].ReplacePixels([]byte{0, 0x80, 0, 0xff}, nil, 0, 0, 1, 1) + srcs[2].ReplacePixels([]byte{0, 0, 0xc0, 0xff}, nil, 0, 0, 1, 1) dst := restorable.NewImage(1, 1) @@ -171,7 +171,7 @@ func TestShaderMultipleSourcesOnOneTexture(t *testing.T) { 0x40, 0, 0, 0xff, 0, 0x80, 0, 0xff, 0, 0, 0xc0, 0xff, - }, 0, 0, 3, 1) + }, nil, 0, 0, 3, 1) srcs := [graphics.ShaderImageNum]*restorable.Image{src, src, src} dst := restorable.NewImage(1, 1)