graphics: Forbid copying Image objects

This commit is contained in:
Hajime Hoshi 2018-02-25 21:54:35 +09:00
parent b88b86e0e7
commit 94c4cc35ab
2 changed files with 55 additions and 6 deletions

View File

@ -47,8 +47,25 @@ func init() {
// //
// Functions of Image never returns error as of 1.5.0-alpha, and error values are always nil. // Functions of Image never returns error as of 1.5.0-alpha, and error values are always nil.
type Image struct { type Image struct {
// addr holds self to check copying.
// See strings.Builder for similar examples.
addr *Image
restorable *restorable.Image restorable *restorable.Image
filter Filter
filter Filter
}
func (i *Image) copyCheck() {
if i.addr == nil {
// As it is OK that an image is allocated at heap,
// 'noespace' function like strings.noescape is not needed.
i.addr = i
return
}
if i.addr != i {
panic("ebiten: illegal use of non-zero Image copied by value")
}
} }
// Size returns the size of the image. // Size returns the size of the image.
@ -62,6 +79,7 @@ func (i *Image) Size() (width, height int) {
// //
// Clear always returns nil as of 1.5.0-alpha. // Clear always returns nil as of 1.5.0-alpha.
func (i *Image) Clear() error { func (i *Image) Clear() error {
i.copyCheck()
i.fill(0, 0, 0, 0) i.fill(0, 0, 0, 0)
return nil return nil
} }
@ -72,6 +90,7 @@ func (i *Image) Clear() error {
// //
// Fill always returns nil as of 1.5.0-alpha. // Fill always returns nil as of 1.5.0-alpha.
func (i *Image) Fill(clr color.Color) error { func (i *Image) Fill(clr color.Color) error {
i.copyCheck()
r, g, b, a := clr.RGBA() r, g, b, a := clr.RGBA()
i.fill(uint8(r>>8), uint8(g>>8), uint8(b>>8), uint8(a>>8)) i.fill(uint8(r>>8), uint8(g>>8), uint8(b>>8), uint8(a>>8))
return nil return nil
@ -123,6 +142,7 @@ func (i *Image) fill(r, g, b, a uint8) {
// //
// DrawImage always returns nil as of 1.5.0-alpha. // DrawImage always returns nil as of 1.5.0-alpha.
func (i *Image) DrawImage(img *Image, options *DrawImageOptions) error { func (i *Image) DrawImage(img *Image, options *DrawImageOptions) error {
i.copyCheck()
if i == img { if i == img {
panic("ebiten: Image.DrawImage: img must be different from the receiver") panic("ebiten: Image.DrawImage: img must be different from the receiver")
} }
@ -230,6 +250,7 @@ func (i *Image) At(x, y int) color.Color {
// //
// Dipose always return nil as of 1.5.0-alpha. // Dipose always return nil as of 1.5.0-alpha.
func (i *Image) Dispose() error { func (i *Image) Dispose() error {
i.copyCheck()
if i.restorable == nil { if i.restorable == nil {
return nil return nil
} }
@ -251,6 +272,7 @@ func (i *Image) Dispose() error {
// //
// ReplacePixels always returns nil as of 1.5.0-alpha. // ReplacePixels always returns nil as of 1.5.0-alpha.
func (i *Image) ReplacePixels(p []byte) error { func (i *Image) ReplacePixels(p []byte) error {
i.copyCheck()
if i.restorable == nil { if i.restorable == nil {
return nil return nil
} }
@ -317,7 +339,10 @@ type DrawImageOptions struct {
func NewImage(width, height int, filter Filter) (*Image, error) { func NewImage(width, height int, filter Filter) (*Image, error) {
checkSize(width, height) checkSize(width, height)
r := restorable.NewImage(width, height, false) r := restorable.NewImage(width, height, false)
i := &Image{r, filter} i := &Image{
restorable: r,
filter: filter,
}
i.fill(0, 0, 0, 0) i.fill(0, 0, 0, 0)
runtime.SetFinalizer(i, (*Image).Dispose) runtime.SetFinalizer(i, (*Image).Dispose)
return i, nil return i, nil
@ -327,7 +352,10 @@ func NewImage(width, height int, filter Filter) (*Image, error) {
func newImageWithoutInit(width, height int) *Image { func newImageWithoutInit(width, height int) *Image {
checkSize(width, height) checkSize(width, height)
r := restorable.NewImage(width, height, false) r := restorable.NewImage(width, height, false)
i := &Image{r, FilterDefault} i := &Image{
restorable: r,
filter: FilterDefault,
}
runtime.SetFinalizer(i, (*Image).Dispose) runtime.SetFinalizer(i, (*Image).Dispose)
return i return i
} }
@ -350,7 +378,10 @@ func newImageWithoutInit(width, height int) *Image {
func newVolatileImage(width, height int, filter Filter) *Image { func newVolatileImage(width, height int, filter Filter) *Image {
checkSize(width, height) checkSize(width, height)
r := restorable.NewImage(width, height, true) r := restorable.NewImage(width, height, true)
i := &Image{r, filter} i := &Image{
restorable: r,
filter: filter,
}
i.fill(0, 0, 0, 0) i.fill(0, 0, 0, 0)
runtime.SetFinalizer(i, (*Image).Dispose) runtime.SetFinalizer(i, (*Image).Dispose)
return i return i
@ -368,7 +399,10 @@ func NewImageFromImage(source image.Image, filter Filter) (*Image, error) {
size := source.Bounds().Size() size := source.Bounds().Size()
checkSize(size.X, size.Y) checkSize(size.X, size.Y)
r := restorable.NewImageFromImage(source) r := restorable.NewImageFromImage(source)
i := &Image{r, filter} i := &Image{
restorable: r,
filter: filter,
}
runtime.SetFinalizer(i, (*Image).Dispose) runtime.SetFinalizer(i, (*Image).Dispose)
return i, nil return i, nil
} }
@ -376,7 +410,10 @@ func NewImageFromImage(source image.Image, filter Filter) (*Image, error) {
func newImageWithScreenFramebuffer(width, height, framebufferWidth, framebufferHeight int) *Image { func newImageWithScreenFramebuffer(width, height, framebufferWidth, framebufferHeight int) *Image {
checkSize(width, height) checkSize(width, height)
r := restorable.NewScreenFramebufferImage(width, height, framebufferWidth, framebufferHeight) r := restorable.NewScreenFramebufferImage(width, height, framebufferWidth, framebufferHeight)
i := &Image{r, FilterDefault} i := &Image{
restorable: r,
filter: FilterDefault,
}
runtime.SetFinalizer(i, (*Image).Dispose) runtime.SetFinalizer(i, (*Image).Dispose)
return i return i
} }

View File

@ -744,3 +744,15 @@ func TestImageSize4096(t *testing.T) {
} }
} }
} }
func TestImageCopy(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Errorf("copying image and using it should panic")
}
}()
img0, _ := NewImage(256, 256, FilterDefault)
img1 := *img0
img1.Fill(color.Transparent)
}