internal/restorable: add a new parameter specifying a mask for ReplacePixels

This commit is contained in:
Hajime Hoshi 2022-03-21 02:08:37 +09:00
parent e55dbbf3cd
commit 5c79b86412
5 changed files with 250 additions and 48 deletions

View File

@ -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) {

View File

@ -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

View File

@ -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)
}
}
}
}

View File

@ -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())
}
}

View File

@ -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)