mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-24 18:58:54 +01:00
internal/restorable: simplify the implementation of rect
Allow overlapped regions at pixelsRecords.
This removes the members `last*` for caching. These were introduced
at 7e7751bd43
, and apparently these
are no longer needed.
This commit is contained in:
parent
ea81d4abf4
commit
c1a0d83f8d
@ -25,38 +25,38 @@ import (
|
||||
)
|
||||
|
||||
type Pixels struct {
|
||||
rectToPixels *rectToPixels
|
||||
pixelsRecords *pixelsRecords
|
||||
}
|
||||
|
||||
// Apply applies the Pixels state to the given image especially for restoring.
|
||||
func (p *Pixels) Apply(img *graphicscommand.Image) {
|
||||
// Pixels doesn't clear the image. This is a caller's responsibility.
|
||||
|
||||
if p.rectToPixels == nil {
|
||||
if p.pixelsRecords == nil {
|
||||
return
|
||||
}
|
||||
p.rectToPixels.apply(img)
|
||||
p.pixelsRecords.apply(img)
|
||||
}
|
||||
|
||||
func (p *Pixels) AddOrReplace(pix []byte, x, y, width, height int) {
|
||||
if p.rectToPixels == nil {
|
||||
p.rectToPixels = &rectToPixels{}
|
||||
if p.pixelsRecords == nil {
|
||||
p.pixelsRecords = &pixelsRecords{}
|
||||
}
|
||||
p.rectToPixels.addOrReplace(pix, x, y, width, height)
|
||||
p.pixelsRecords.addOrReplace(pix, x, y, width, height)
|
||||
}
|
||||
|
||||
func (p *Pixels) Remove(x, y, width, height int) {
|
||||
func (p *Pixels) Clear(x, y, width, height int) {
|
||||
// Note that we don't care whether the region is actually removed or not here. There is an actual case that
|
||||
// the region is allocated but nothing is rendered. See TestDisposeImmediately at shareable package.
|
||||
if p.rectToPixels == nil {
|
||||
if p.pixelsRecords == nil {
|
||||
return
|
||||
}
|
||||
p.rectToPixels.remove(x, y, width, height)
|
||||
p.pixelsRecords.clear(x, y, width, height)
|
||||
}
|
||||
|
||||
func (p *Pixels) At(i, j int) (byte, byte, byte, byte) {
|
||||
if p.rectToPixels != nil {
|
||||
if r, g, b, a, ok := p.rectToPixels.at(i, j); ok {
|
||||
if p.pixelsRecords != nil {
|
||||
if r, g, b, a, ok := p.pixelsRecords.at(i, j); ok {
|
||||
return r, g, b, a
|
||||
}
|
||||
}
|
||||
@ -325,7 +325,7 @@ func (i *Image) ReplacePixels(pixels []byte, x, y, width, height int) {
|
||||
copy(copiedPixels, pixels)
|
||||
i.basePixels.AddOrReplace(copiedPixels, 0, 0, w, h)
|
||||
} else {
|
||||
i.basePixels.Remove(0, 0, w, h)
|
||||
i.basePixels.Clear(0, 0, w, h)
|
||||
}
|
||||
i.clearDrawTrianglesHistory()
|
||||
i.stale = false
|
||||
@ -349,7 +349,7 @@ func (i *Image) ReplacePixels(pixels []byte, x, y, width, height int) {
|
||||
copy(copiedPixels, pixels)
|
||||
i.basePixels.AddOrReplace(copiedPixels, x, y, width, height)
|
||||
} else {
|
||||
i.basePixels.Remove(x, y, width, height)
|
||||
i.basePixels.Clear(x, y, width, height)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -918,3 +918,88 @@ func TestMutateSlices(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestOverlappedPixels(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
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
dst.ReplacePixels(pix0, 0, 0, 2, 2)
|
||||
dst.ReplacePixels(pix1, 1, 1, 2, 2)
|
||||
|
||||
wantColors := []color.RGBA{
|
||||
{0xff, 0, 0, 0xff},
|
||||
{0xff, 0, 0, 0xff},
|
||||
{0, 0, 0, 0},
|
||||
|
||||
{0xff, 0, 0, 0xff},
|
||||
{0, 0xff, 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, 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,14 +21,32 @@ import (
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/graphicscommand"
|
||||
)
|
||||
|
||||
type rectToPixels struct {
|
||||
m map[image.Rectangle][]byte
|
||||
|
||||
lastR image.Rectangle
|
||||
lastPix []byte
|
||||
type pixelsRecord struct {
|
||||
rect image.Rectangle
|
||||
pix []byte
|
||||
}
|
||||
|
||||
func (rtp *rectToPixels) addOrReplace(pixels []byte, x, y, width, height int) {
|
||||
func (p *pixelsRecord) ClearIfOverlapped(rect image.Rectangle) {
|
||||
r := p.rect.Intersect(rect)
|
||||
ox := r.Min.X - p.rect.Min.X
|
||||
oy := r.Min.Y - p.rect.Min.Y
|
||||
w := p.rect.Dx()
|
||||
for j := 0; j < r.Dy(); j++ {
|
||||
for i := 0; i < r.Dx(); i++ {
|
||||
idx := 4 * ((oy+j)*w + ox + i)
|
||||
p.pix[idx] = 0
|
||||
p.pix[idx+1] = 0
|
||||
p.pix[idx+2] = 0
|
||||
p.pix[idx+3] = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type pixelsRecords struct {
|
||||
records []*pixelsRecord
|
||||
}
|
||||
|
||||
func (pr *pixelsRecords) addOrReplace(pixels []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 {
|
||||
@ -37,83 +55,68 @@ func (rtp *rectToPixels) addOrReplace(pixels []byte, x, y, width, height int) {
|
||||
panic(msg)
|
||||
}
|
||||
|
||||
if rtp.m == nil {
|
||||
rtp.m = map[image.Rectangle][]byte{}
|
||||
}
|
||||
|
||||
newr := image.Rect(x, y, x+width, y+height)
|
||||
for r := range rtp.m {
|
||||
if r == newr {
|
||||
// Replace the region.
|
||||
rtp.m[r] = pixels
|
||||
if r == rtp.lastR {
|
||||
rtp.lastPix = pixels
|
||||
}
|
||||
return
|
||||
}
|
||||
if r.Overlaps(newr) {
|
||||
panic(fmt.Sprintf("restorable: region (%#v) conflicted with the other region (%#v)", newr, r))
|
||||
// Remove or update the duplicated records first.
|
||||
rect := image.Rect(x, y, x+width, y+height)
|
||||
var n int
|
||||
for _, r := range pr.records {
|
||||
if r.rect.In(rect) {
|
||||
continue
|
||||
}
|
||||
pr.records[n] = r
|
||||
n++
|
||||
}
|
||||
for i := n; i < len(pr.records); i++ {
|
||||
pr.records[i] = nil
|
||||
}
|
||||
pr.records = pr.records[:n]
|
||||
|
||||
// Add the region.
|
||||
rtp.m[newr] = pixels
|
||||
if newr == rtp.lastR {
|
||||
rtp.lastPix = pixels
|
||||
}
|
||||
// Add the new record.
|
||||
pr.records = append(pr.records, &pixelsRecord{
|
||||
rect: rect,
|
||||
pix: pixels,
|
||||
})
|
||||
}
|
||||
|
||||
func (rtp *rectToPixels) remove(x, y, width, height int) {
|
||||
if rtp.m == nil {
|
||||
return
|
||||
}
|
||||
|
||||
func (pr *pixelsRecords) clear(x, y, width, height int) {
|
||||
newr := image.Rect(x, y, x+width, y+height)
|
||||
for r := range rtp.m {
|
||||
if r == newr {
|
||||
delete(rtp.m, r)
|
||||
return
|
||||
var n int
|
||||
for _, r := range pr.records {
|
||||
if r.rect.In(newr) {
|
||||
continue
|
||||
}
|
||||
r.ClearIfOverlapped(newr)
|
||||
pr.records[n] = r
|
||||
n++
|
||||
}
|
||||
for i := n; i < len(pr.records); i++ {
|
||||
pr.records[i] = nil
|
||||
}
|
||||
pr.records = pr.records[:n]
|
||||
}
|
||||
|
||||
func (rtp *rectToPixels) at(i, j int) (byte, byte, byte, byte, bool) {
|
||||
if rtp.m == nil {
|
||||
func (pr *pixelsRecords) at(i, j int) (byte, byte, byte, byte, bool) {
|
||||
var record *pixelsRecord
|
||||
pt := image.Pt(i, j)
|
||||
// Traverse the slice in the reversed order.
|
||||
for i := len(pr.records) - 1; i >= 0; i-- {
|
||||
p := pr.records[i]
|
||||
if pt.In(p.rect) {
|
||||
record = p
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if record == nil {
|
||||
return 0, 0, 0, 0, false
|
||||
}
|
||||
|
||||
var pix []byte
|
||||
|
||||
var r image.Rectangle
|
||||
var found bool
|
||||
if pt := image.Pt(i, j); pt.In(rtp.lastR) {
|
||||
r = rtp.lastR
|
||||
found = true
|
||||
pix = rtp.lastPix
|
||||
} else {
|
||||
for rr := range rtp.m {
|
||||
if pt.In(rr) {
|
||||
r = rr
|
||||
found = true
|
||||
rtp.lastR = rr
|
||||
pix = rtp.m[rr]
|
||||
rtp.lastPix = pix
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return 0, 0, 0, 0, false
|
||||
}
|
||||
|
||||
idx := 4 * ((j-r.Min.Y)*r.Dx() + (i - r.Min.X))
|
||||
return pix[idx], pix[idx+1], pix[idx+2], pix[idx+3], true
|
||||
idx := 4 * ((j-record.rect.Min.Y)*record.rect.Dx() + (i - record.rect.Min.X))
|
||||
return record.pix[idx], record.pix[idx+1], record.pix[idx+2], record.pix[idx+3], true
|
||||
}
|
||||
|
||||
func (rtp *rectToPixels) apply(img *graphicscommand.Image) {
|
||||
func (pr *pixelsRecords) apply(img *graphicscommand.Image) {
|
||||
// TODO: Isn't this too heavy? Can we merge the operations?
|
||||
for r, pix := range rtp.m {
|
||||
img.ReplacePixels(pix, r.Min.X, r.Min.Y, r.Dx(), r.Dy())
|
||||
for _, r := range pr.records {
|
||||
img.ReplacePixels(r.pix, r.rect.Min.X, r.rect.Min.Y, r.rect.Dx(), r.rect.Dy())
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user