mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-25 19:28:57 +01:00
restorable: Add restorable.Pixels
This commit is contained in:
parent
93d0b0dd09
commit
6930176fbb
@ -17,12 +17,67 @@ package restorable
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"image/color"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/internal/affine"
|
||||
"github.com/hajimehoshi/ebiten/internal/graphics"
|
||||
"github.com/hajimehoshi/ebiten/internal/graphicscommand"
|
||||
)
|
||||
|
||||
type Pixels struct {
|
||||
pixels []byte
|
||||
|
||||
length int
|
||||
|
||||
// color is used only when pixels == nil
|
||||
color color.RGBA
|
||||
}
|
||||
|
||||
func (p *Pixels) ensurePixels() {
|
||||
if p.pixels != nil {
|
||||
return
|
||||
}
|
||||
p.pixels = make([]byte, p.length)
|
||||
if p.color.A == 0 {
|
||||
return
|
||||
}
|
||||
for i := 0; i < p.length/4; i++ {
|
||||
p.pixels[4*i] = p.color.R
|
||||
p.pixels[4*i+1] = p.color.G
|
||||
p.pixels[4*i+2] = p.color.B
|
||||
p.pixels[4*i+3] = p.color.A
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Pixels) CopyFrom(pix []byte, from int) {
|
||||
p.ensurePixels()
|
||||
copy(p.pixels[from:from+len(pix)], pix)
|
||||
}
|
||||
|
||||
func (p *Pixels) At(i int) byte {
|
||||
if i < 0 || p.length <= i {
|
||||
panic(fmt.Sprintf("restorable: index out of range: %d for length: %d", i, p.length))
|
||||
}
|
||||
if p.pixels == nil {
|
||||
switch i % 4 {
|
||||
case 0:
|
||||
return p.color.R
|
||||
case 1:
|
||||
return p.color.G
|
||||
case 2:
|
||||
return p.color.B
|
||||
case 3:
|
||||
return p.color.A
|
||||
}
|
||||
}
|
||||
return p.pixels[i]
|
||||
}
|
||||
|
||||
func (p *Pixels) Slice() []byte {
|
||||
p.ensurePixels()
|
||||
return p.pixels
|
||||
}
|
||||
|
||||
// drawImageHistoryItem is an item for history of draw-image commands.
|
||||
type drawImageHistoryItem struct {
|
||||
image *Image
|
||||
@ -38,7 +93,7 @@ type drawImageHistoryItem struct {
|
||||
type Image struct {
|
||||
image *graphicscommand.Image
|
||||
|
||||
basePixels []byte
|
||||
basePixels *Pixels
|
||||
|
||||
// drawImageHistory is a set of draw-image commands.
|
||||
// TODO: This should be merged with the similar command queue in package graphics (#433).
|
||||
@ -150,19 +205,9 @@ func (i *Image) fill(r, g, b, a uint8) {
|
||||
}
|
||||
i.image.DrawImage(emptyImage.image, vs, is, nil, c, graphics.FilterNearest, graphics.AddressClampToZero)
|
||||
|
||||
if a == 0 {
|
||||
i.basePixels = nil
|
||||
} else {
|
||||
// TODO: Add baseColor?
|
||||
if i.basePixels == nil {
|
||||
i.basePixels = make([]byte, 4*w*h)
|
||||
}
|
||||
for idx := 0; idx < w*h; idx++ {
|
||||
i.basePixels[4*idx] = r
|
||||
i.basePixels[4*idx+1] = g
|
||||
i.basePixels[4*idx+2] = b
|
||||
i.basePixels[4*idx+3] = a
|
||||
}
|
||||
i.basePixels = &Pixels{
|
||||
color: color.RGBA{r, g, b, a},
|
||||
length: 4 * w * h,
|
||||
}
|
||||
i.drawImageHistory = nil
|
||||
i.stale = false
|
||||
@ -173,7 +218,7 @@ func (i *Image) IsVolatile() bool {
|
||||
}
|
||||
|
||||
// BasePixelsForTesting returns the image's basePixels for testing.
|
||||
func (i *Image) BasePixelsForTesting() []byte {
|
||||
func (i *Image) BasePixelsForTesting() *Pixels {
|
||||
return i.basePixels
|
||||
}
|
||||
|
||||
@ -244,9 +289,11 @@ func (i *Image) ReplacePixels(pixels []byte, x, y, width, height int) {
|
||||
if x == 0 && y == 0 && width == w && height == h {
|
||||
if pixels != nil {
|
||||
if i.basePixels == nil {
|
||||
i.basePixels = make([]byte, 4*w*h)
|
||||
i.basePixels = &Pixels{
|
||||
length: 4 * w * h,
|
||||
}
|
||||
}
|
||||
copy(i.basePixels, pixels)
|
||||
i.basePixels.CopyFrom(pixels, 0)
|
||||
} else {
|
||||
// If basePixels is nil, the restored pixels are cleared.
|
||||
// See restore() implementation.
|
||||
@ -268,16 +315,18 @@ func (i *Image) ReplacePixels(pixels []byte, x, y, width, height int) {
|
||||
idx := 4 * (y*w + x)
|
||||
if pixels != nil {
|
||||
if i.basePixels == nil {
|
||||
i.basePixels = make([]byte, 4*w*h)
|
||||
i.basePixels = &Pixels{
|
||||
length: 4 * w * h,
|
||||
}
|
||||
}
|
||||
for j := 0; j < height; j++ {
|
||||
copy(i.basePixels[idx:idx+4*width], pixels[4*j*width:4*(j+1)*width])
|
||||
i.basePixels.CopyFrom(pixels[4*j*width:4*(j+1)*width], idx)
|
||||
idx += 4 * w
|
||||
}
|
||||
} else if i.basePixels != nil {
|
||||
zeros := make([]byte, 4*width)
|
||||
for j := 0; j < height; j++ {
|
||||
copy(i.basePixels[idx:idx+4*width], zeros)
|
||||
i.basePixels.CopyFrom(zeros, idx)
|
||||
idx += 4 * w
|
||||
}
|
||||
}
|
||||
@ -351,7 +400,7 @@ func (i *Image) At(x, y int) (byte, byte, byte, byte) {
|
||||
}
|
||||
|
||||
idx := 4*x + 4*y*w
|
||||
return i.basePixels[idx], i.basePixels[idx+1], i.basePixels[idx+2], i.basePixels[idx+3]
|
||||
return i.basePixels.At(idx), i.basePixels.At(idx + 1), i.basePixels.At(idx + 2), i.basePixels.At(idx + 3)
|
||||
}
|
||||
|
||||
// makeStaleIfDependingOn makes the image stale if the image depends on target.
|
||||
@ -366,7 +415,10 @@ func (i *Image) makeStaleIfDependingOn(target *Image) {
|
||||
|
||||
// readPixelsFromGPU reads the pixels from GPU and resolves the image's 'stale' state.
|
||||
func (i *Image) readPixelsFromGPU() {
|
||||
i.basePixels = i.image.Pixels()
|
||||
i.basePixels = &Pixels{
|
||||
pixels: i.image.Pixels(),
|
||||
length: len(i.image.Pixels()),
|
||||
}
|
||||
i.drawImageHistory = nil
|
||||
i.stale = false
|
||||
}
|
||||
@ -440,7 +492,7 @@ func (i *Image) restore() error {
|
||||
|
||||
gimg := graphicscommand.NewImage(w, h)
|
||||
if i.basePixels != nil {
|
||||
gimg.ReplacePixels(i.basePixels, 0, 0, w, h)
|
||||
gimg.ReplacePixels(i.basePixels.Slice(), 0, 0, w, h)
|
||||
} else {
|
||||
// Clear the image explicitly.
|
||||
pix := make([]uint8, w*h*4)
|
||||
@ -454,7 +506,10 @@ func (i *Image) restore() error {
|
||||
}
|
||||
i.image = gimg
|
||||
|
||||
i.basePixels = gimg.Pixels()
|
||||
i.basePixels = &Pixels{
|
||||
pixels: gimg.Pixels(),
|
||||
length: len(gimg.Pixels()),
|
||||
}
|
||||
i.drawImageHistory = nil
|
||||
i.stale = false
|
||||
return nil
|
||||
|
@ -44,9 +44,9 @@ func TestMain(m *testing.M) {
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
func byteSliceToColor(b []byte, index int) color.RGBA {
|
||||
func pixelsToColor(p *Pixels, index int) color.RGBA {
|
||||
i := index * 4
|
||||
return color.RGBA{b[i], b[i+1], b[i+2], b[i+3]}
|
||||
return color.RGBA{p.At(i), p.At(i + 1), p.At(i + 2), p.At(i + 3)}
|
||||
}
|
||||
|
||||
func abs(x int) int {
|
||||
@ -68,30 +68,18 @@ func sameColors(c1, c2 color.RGBA, delta int) bool {
|
||||
abs(int(c1.A)-int(c2.A)) <= delta
|
||||
}
|
||||
|
||||
func fill(img *Image, r, g, b, a uint8) {
|
||||
w, h := img.Size()
|
||||
pix := make([]uint8, w*h*4)
|
||||
for i := 0; i < w*h; i++ {
|
||||
pix[4*i] = r
|
||||
pix[4*i+1] = g
|
||||
pix[4*i+2] = b
|
||||
pix[4*i+3] = a
|
||||
}
|
||||
img.ReplacePixels(pix, 0, 0, w, h)
|
||||
}
|
||||
|
||||
func TestRestore(t *testing.T) {
|
||||
img0 := NewImage(1, 1)
|
||||
defer img0.Dispose()
|
||||
|
||||
clr0 := color.RGBA{0x00, 0x00, 0x00, 0xff}
|
||||
fill(img0, clr0.R, clr0.G, clr0.B, clr0.A)
|
||||
img0.Fill(clr0.R, clr0.G, clr0.B, clr0.A)
|
||||
ResolveStaleImages()
|
||||
if err := Restore(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := clr0
|
||||
got := byteSliceToColor(img0.BasePixelsForTesting(), 0)
|
||||
got := pixelsToColor(img0.BasePixelsForTesting(), 0)
|
||||
if !sameColors(got, want, 1) {
|
||||
t.Errorf("got %v, want %v", got, want)
|
||||
}
|
||||
@ -110,7 +98,7 @@ func TestRestoreWithoutDraw(t *testing.T) {
|
||||
|
||||
for i := 0; i < 1024*1024; i++ {
|
||||
want := color.RGBA{0x00, 0x00, 0x00, 0x00}
|
||||
got := byteSliceToColor(img0.BasePixelsForTesting(), i)
|
||||
got := pixelsToColor(img0.BasePixelsForTesting(), i)
|
||||
if !sameColors(got, want, 0) {
|
||||
t.Errorf("got %v, want %v", got, want)
|
||||
}
|
||||
@ -130,7 +118,7 @@ func TestRestoreChain(t *testing.T) {
|
||||
}
|
||||
}()
|
||||
clr := color.RGBA{0x00, 0x00, 0x00, 0xff}
|
||||
fill(imgs[0], clr.R, clr.G, clr.B, clr.A)
|
||||
imgs[0].Fill(clr.R, clr.G, clr.B, clr.A)
|
||||
for i := 0; i < num-1; i++ {
|
||||
w, h := imgs[i].Size()
|
||||
vs := graphics.QuadVertices(w, h, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1)
|
||||
@ -143,7 +131,7 @@ func TestRestoreChain(t *testing.T) {
|
||||
}
|
||||
want := clr
|
||||
for i, img := range imgs {
|
||||
got := byteSliceToColor(img.BasePixelsForTesting(), 0)
|
||||
got := pixelsToColor(img.BasePixelsForTesting(), 0)
|
||||
if !sameColors(got, want, 1) {
|
||||
t.Errorf("%d: got %v, want %v", i, got, want)
|
||||
}
|
||||
@ -168,11 +156,11 @@ func TestRestoreChain2(t *testing.T) {
|
||||
}()
|
||||
|
||||
clr0 := color.RGBA{0xff, 0x00, 0x00, 0xff}
|
||||
fill(imgs[0], clr0.R, clr0.G, clr0.B, clr0.A)
|
||||
imgs[0].Fill(clr0.R, clr0.G, clr0.B, clr0.A)
|
||||
clr7 := color.RGBA{0x00, 0xff, 0x00, 0xff}
|
||||
fill(imgs[7], clr7.R, clr7.G, clr7.B, clr7.A)
|
||||
imgs[7].Fill(clr7.R, clr7.G, clr7.B, clr7.A)
|
||||
clr8 := color.RGBA{0x00, 0x00, 0xff, 0xff}
|
||||
fill(imgs[8], clr8.R, clr8.G, clr8.B, clr8.A)
|
||||
imgs[8].Fill(clr8.R, clr8.G, clr8.B, clr8.A)
|
||||
|
||||
vs := graphics.QuadVertices(w, h, 0, 0, w, h, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1)
|
||||
is := graphics.QuadIndices()
|
||||
@ -191,7 +179,7 @@ func TestRestoreChain2(t *testing.T) {
|
||||
if i == 8 || i == 9 {
|
||||
want = clr7
|
||||
}
|
||||
got := byteSliceToColor(img.BasePixelsForTesting(), 0)
|
||||
got := pixelsToColor(img.BasePixelsForTesting(), 0)
|
||||
if !sameColors(got, want, 1) {
|
||||
t.Errorf("%d: got %v, want %v", i, got, want)
|
||||
}
|
||||
@ -215,12 +203,12 @@ func TestRestoreOverrideSource(t *testing.T) {
|
||||
}()
|
||||
clr0 := color.RGBA{0x00, 0x00, 0x00, 0xff}
|
||||
clr1 := color.RGBA{0x00, 0x00, 0x01, 0xff}
|
||||
fill(img1, clr0.R, clr0.G, clr0.B, clr0.A)
|
||||
img1.Fill(clr0.R, clr0.G, clr0.B, clr0.A)
|
||||
vs := graphics.QuadVertices(w, h, 0, 0, w, h, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1)
|
||||
is := graphics.QuadIndices()
|
||||
img2.DrawImage(img1, vs, is, nil, graphics.CompositeModeSourceOver, graphics.FilterNearest, graphics.AddressClampToZero)
|
||||
img3.DrawImage(img2, vs, is, nil, graphics.CompositeModeSourceOver, graphics.FilterNearest, graphics.AddressClampToZero)
|
||||
fill(img0, clr1.R, clr1.G, clr1.B, clr1.A)
|
||||
img0.Fill(clr1.R, clr1.G, clr1.B, clr1.A)
|
||||
img1.DrawImage(img0, vs, is, nil, graphics.CompositeModeSourceOver, graphics.FilterNearest, graphics.AddressClampToZero)
|
||||
ResolveStaleImages()
|
||||
if err := Restore(); err != nil {
|
||||
@ -234,22 +222,22 @@ func TestRestoreOverrideSource(t *testing.T) {
|
||||
{
|
||||
"0",
|
||||
clr1,
|
||||
byteSliceToColor(img0.BasePixelsForTesting(), 0),
|
||||
pixelsToColor(img0.BasePixelsForTesting(), 0),
|
||||
},
|
||||
{
|
||||
"1",
|
||||
clr1,
|
||||
byteSliceToColor(img1.BasePixelsForTesting(), 0),
|
||||
pixelsToColor(img1.BasePixelsForTesting(), 0),
|
||||
},
|
||||
{
|
||||
"2",
|
||||
clr0,
|
||||
byteSliceToColor(img2.BasePixelsForTesting(), 0),
|
||||
pixelsToColor(img2.BasePixelsForTesting(), 0),
|
||||
},
|
||||
{
|
||||
"3",
|
||||
clr0,
|
||||
byteSliceToColor(img3.BasePixelsForTesting(), 0),
|
||||
pixelsToColor(img3.BasePixelsForTesting(), 0),
|
||||
},
|
||||
}
|
||||
for _, c := range testCases {
|
||||
@ -371,7 +359,7 @@ func TestRestoreComplexGraph(t *testing.T) {
|
||||
if c.out[i] == '*' {
|
||||
want = color.RGBA{0xff, 0xff, 0xff, 0xff}
|
||||
}
|
||||
got := byteSliceToColor(c.image.BasePixelsForTesting(), i)
|
||||
got := pixelsToColor(c.image.BasePixelsForTesting(), i)
|
||||
if !sameColors(got, want, 1) {
|
||||
t.Errorf("%s[%d]: got %v, want %v", c.name, i, got, want)
|
||||
}
|
||||
@ -433,7 +421,7 @@ func TestRestoreRecursive(t *testing.T) {
|
||||
if c.out[i] == '*' {
|
||||
want = color.RGBA{0xff, 0xff, 0xff, 0xff}
|
||||
}
|
||||
got := byteSliceToColor(c.image.BasePixelsForTesting(), i)
|
||||
got := pixelsToColor(c.image.BasePixelsForTesting(), i)
|
||||
if !sameColors(got, want, 1) {
|
||||
t.Errorf("%s[%d]: got %v, want %v", c.name, i, got, want)
|
||||
}
|
||||
@ -601,7 +589,7 @@ func TestClear(t *testing.T) {
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
got := byteSliceToColor(img.BasePixelsForTesting(), c.Index)
|
||||
got := pixelsToColor(img.BasePixelsForTesting(), c.Index)
|
||||
want := c.Want
|
||||
if got != want {
|
||||
t.Errorf("base pixel [%d]: got %v, want %v", c.Index, got, want)
|
||||
@ -634,7 +622,7 @@ func TestReplacePixelsOnly(t *testing.T) {
|
||||
case i%5 == 0:
|
||||
want = color.RGBA{1, 2, 3, 4}
|
||||
}
|
||||
got := byteSliceToColor(img0.BasePixelsForTesting(), i)
|
||||
got := pixelsToColor(img0.BasePixelsForTesting(), i)
|
||||
if !sameColors(got, want, 0) {
|
||||
t.Errorf("got %v, want %v", got, want)
|
||||
}
|
||||
@ -645,7 +633,7 @@ func TestReplacePixelsOnly(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := color.RGBA{1, 2, 3, 4}
|
||||
got := byteSliceToColor(img1.BasePixelsForTesting(), 0)
|
||||
got := pixelsToColor(img1.BasePixelsForTesting(), 0)
|
||||
if !sameColors(got, want, 0) {
|
||||
t.Errorf("got %v, want %v", got, want)
|
||||
}
|
||||
@ -664,7 +652,7 @@ func TestReadPixelsFromVolatileImage(t *testing.T) {
|
||||
dst.ReplacePixels(make([]byte, 4*w*h), 0, 0, w, h)
|
||||
|
||||
// Second, draw src to dst. If the implementation is correct, dst becomes stale.
|
||||
fill(src, 0xff, 0xff, 0xff, 0xff)
|
||||
src.Fill(0xff, 0xff, 0xff, 0xff)
|
||||
vs := graphics.QuadVertices(w, h, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1)
|
||||
is := graphics.QuadIndices()
|
||||
dst.DrawImage(src, vs, is, nil, graphics.CompositeModeCopy, graphics.FilterNearest, graphics.AddressClampToZero)
|
||||
|
Loading…
Reference in New Issue
Block a user