restorable: Bug fix: Do not include emptyImage into the dependency graph

Instead, add baseColor to Pixels and use it when restoring.

Fixes #928.
This commit is contained in:
Hajime Hoshi 2019-08-27 07:05:08 +09:00
parent 76330c492c
commit 22c31da6c1
3 changed files with 56 additions and 17 deletions

View File

@ -25,11 +25,17 @@ import (
) )
type Pixels struct { type Pixels struct {
baseColor color.RGBA
rectToPixels *rectToPixels rectToPixels *rectToPixels
} }
// Apply applies the Pixels state to the given image especially for restoring. // Apply applies the Pixels state to the given image especially for restoring.
func (p *Pixels) Apply(img *graphicscommand.Image) { func (p *Pixels) Apply(img *graphicscommand.Image) {
// Pixels doesn't clear the image. This is a caller's responsibility.
if p.baseColor != (color.RGBA{}) {
fillImage(img, p.baseColor)
}
if p.rectToPixels == nil { if p.rectToPixels == nil {
return return
} }
@ -58,7 +64,7 @@ func (p *Pixels) At(i, j int) (byte, byte, byte, byte) {
return r, g, b, a return r, g, b, a
} }
} }
return 0, 0, 0, 0 return p.baseColor.R, p.baseColor.G, p.baseColor.B, p.baseColor.A
} }
// drawTrianglesHistoryItem is an item for history of draw-image commands. // drawTrianglesHistoryItem is an item for history of draw-image commands.
@ -163,6 +169,9 @@ func (i *Image) Extend(width, height int) *Image {
newImg := NewImage(width, height) newImg := NewImage(width, height)
i.basePixels.Apply(newImg.image) i.basePixels.Apply(newImg.image)
if i.basePixels.baseColor != (color.RGBA{}) {
panic("restorable: baseColor must be empty at Extend")
}
newImg.basePixels = i.basePixels newImg.basePixels = i.basePixels
i.Dispose() i.Dispose()
@ -233,20 +242,29 @@ func (i *Image) clear() {
} }
// Fill fills the specified part of the image with a solid color. // Fill fills the specified part of the image with a solid color.
func (i *Image) Fill(clr color.Color, x, y, width, height int) { func (i *Image) Fill(clr color.Color) {
// If all the pixels will be changed, reset the information for restoring. i.basePixels = Pixels{
if w, h := i.Size(); x == 0 && y == 0 && width == w && height == h { baseColor: color.RGBAModel.Convert(clr).(color.RGBA),
i.basePixels = Pixels{} }
i.drawTrianglesHistory = nil i.drawTrianglesHistory = nil
i.stale = false i.stale = false
// Do not call i.DrawTriangles as emptyImage is special (#928).
// baseColor is updated instead.
fillImage(i.image, i.basePixels.baseColor)
}
func fillImage(i *graphicscommand.Image, clr color.RGBA) {
if i == emptyImage.image {
panic("restorable: fillImage cannot be called on emptyImage")
} }
var rf, gf, bf, af float32 var rf, gf, bf, af float32
if r, g, b, a := clr.RGBA(); a > 0 { if clr.A > 0 {
rf = float32(r) / float32(a) rf = float32(clr.R) / float32(clr.A)
gf = float32(g) / float32(a) gf = float32(clr.G) / float32(clr.A)
bf = float32(b) / float32(a) bf = float32(clr.B) / float32(clr.A)
af = float32(a) / 0xffff af = float32(clr.A) / 0xff
} }
// TODO: Use the previous composite mode if possible. // TODO: Use the previous composite mode if possible.
@ -255,15 +273,16 @@ func (i *Image) Fill(clr color.Color, x, y, width, height int) {
compositemode = driver.CompositeModeCopy compositemode = driver.CompositeModeCopy
} }
dw, dh := width, height // TODO: Integrate with clearColor
dw, dh := i.InternalSize()
sw, sh := emptyImage.Size() sw, sh := emptyImage.Size()
vs := make([]float32, 4*graphics.VertexFloatNum) vs := make([]float32, 4*graphics.VertexFloatNum)
graphics.PutQuadVertices(vs, emptyImage, 0, 0, sw, sh, graphics.PutQuadVertices(vs, emptyImage, 0, 0, sw, sh,
float32(dw)/float32(sw), 0, 0, float32(dh)/float32(sh), float32(x), float32(y), float32(dw)/float32(sw), 0, 0, float32(dh)/float32(sh), 0, 0,
rf, gf, bf, af) rf, gf, bf, af)
is := graphics.QuadIndices() is := graphics.QuadIndices()
i.DrawTriangles(emptyImage, vs, is, nil, compositemode, driver.FilterNearest, driver.AddressClampToZero) i.DrawTriangles(emptyImage.image, vs, is, nil, compositemode, driver.FilterNearest, driver.AddressClampToZero)
} }
func (i *Image) IsVolatile() bool { func (i *Image) IsVolatile() bool {

View File

@ -760,3 +760,23 @@ func TestClearPixels(t *testing.T) {
// After clearing, the regions will be available again. // 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), 0, 0, 8, 4)
} }
func TestFill(t *testing.T) {
const w, h = 16, 16
img := NewImage(w, h)
img.Fill(color.RGBA{0xff, 0, 0, 0xff})
ResolveStaleImages()
if err := RestoreIfNeeded(); err != nil {
t.Fatal(err)
}
for j := 0; j < h; j++ {
for i := 0; i < w; i++ {
r, g, b, a := img.At(i, j)
got := color.RGBA{r, g, b, a}
want := color.RGBA{0xff, 0, 0, 0xff}
if got != want {
t.Errorf("img.At(%d, %d): got: %v, want: %v", i, j, got, want)
}
}
}
}

View File

@ -332,8 +332,8 @@ func (i *Image) Fill(clr color.Color) {
i.ensureNotShared() i.ensureNotShared()
x, y, width, height := i.region() // As *restorable.Image is an independent image, it is fine to fill the entire image.
i.backend.restorable.Fill(clr, x, y, width, height) i.backend.restorable.Fill(clr)
i.nonUpdatedCount = 0 i.nonUpdatedCount = 0
delete(imagesToMakeShared, i) delete(imagesToMakeShared, i)