graphics: Bug fix: now ebiten.Image can be passed to NewImageFromImage (#213)

This commit is contained in:
Hajime Hoshi 2016-05-12 11:06:12 +09:00
parent 15d48703fc
commit 9d569e3e49
4 changed files with 75 additions and 66 deletions

113
image.go
View File

@ -19,6 +19,7 @@ import (
"fmt" "fmt"
"image" "image"
"image/color" "image/color"
"image/draw"
"runtime" "runtime"
"sync" "sync"
@ -26,24 +27,46 @@ import (
"github.com/hajimehoshi/ebiten/internal/graphics/opengl" "github.com/hajimehoshi/ebiten/internal/graphics/opengl"
) )
var imageM sync.Mutex
var ( var (
lazyImageProcesses = []func() error{} imageM sync.Mutex
) )
func execDelayedImageProcesses() error { type delayedImageTasks struct {
imageM.Lock() tasks []func() error
defer imageM.Unlock() m sync.Mutex
for _, f := range lazyImageProcesses { execCalled bool
}
var theDelayedImageTasks = &delayedImageTasks{
tasks: []func() error{},
}
func (t *delayedImageTasks) add(f func() error) bool {
t.m.Lock()
defer t.m.Unlock()
if t.execCalled {
return false
}
t.tasks = append(t.tasks, f)
return true
}
func (t *delayedImageTasks) exec() error {
t.m.Lock()
defer t.m.Unlock()
t.execCalled = true
for _, f := range t.tasks {
if err := f(); err != nil { if err := f(); err != nil {
return err return err
} }
} }
lazyImageProcesses = nil
return nil return nil
} }
func (t *delayedImageTasks) isExecCalled() bool {
t.execCalled
}
// Image represents an image. // Image represents an image.
// The pixel format is alpha-premultiplied. // The pixel format is alpha-premultiplied.
// Image implements image.Image. // Image implements image.Image.
@ -67,8 +90,6 @@ func (i *Image) Size() (width, height int) {
// //
// This function is concurrent-safe. // This function is concurrent-safe.
func (i *Image) Clear() (err error) { func (i *Image) Clear() (err error) {
imageM.Lock()
defer imageM.Unlock()
return i.clear() return i.clear()
} }
@ -80,22 +101,21 @@ func (i *Image) clear() (err error) {
// //
// This function is concurrent-safe. // This function is concurrent-safe.
func (i *Image) Fill(clr color.Color) (err error) { func (i *Image) Fill(clr color.Color) (err error) {
imageM.Lock()
defer imageM.Unlock()
return i.fill(clr) return i.fill(clr)
} }
func (i *Image) fill(clr color.Color) (err error) { func (i *Image) fill(clr color.Color) (err error) {
f := func() error { f := func() error {
imageM.Lock()
defer imageM.Unlock()
if i.isDisposed() { if i.isDisposed() {
return errors.New("ebiten: image is already disposed") return errors.New("ebiten: image is already disposed")
} }
i.pixels = nil i.pixels = nil
return i.framebuffer.Fill(glContext, clr) return i.framebuffer.Fill(glContext, clr)
} }
if lazyImageProcesses != nil { if theDelayedImageTasks.add(f) {
lazyImageProcesses = append(lazyImageProcesses, f) return nil
return
} }
return f() return f()
@ -142,12 +162,12 @@ func (i *Image) DrawImage(image *Image, options *DrawImageOptions) (err error) {
return nil return nil
} }
imageM.Lock()
defer imageM.Unlock()
if i == image { if i == image {
return errors.New("ebiten: Image.DrawImage: image should be different from the receiver") return errors.New("ebiten: Image.DrawImage: image should be different from the receiver")
} }
f := func() error { f := func() error {
imageM.Lock()
defer imageM.Unlock()
if i.isDisposed() { if i.isDisposed() {
return errors.New("ebiten: image is already disposed") return errors.New("ebiten: image is already disposed")
} }
@ -155,8 +175,7 @@ func (i *Image) DrawImage(image *Image, options *DrawImageOptions) (err error) {
m := opengl.CompositeMode(options.CompositeMode) m := opengl.CompositeMode(options.CompositeMode)
return i.framebuffer.DrawTexture(glContext, image.texture, vertices[:16*n], &options.GeoM, &options.ColorM, m) return i.framebuffer.DrawTexture(glContext, image.texture, vertices[:16*n], &options.GeoM, &options.ColorM, m)
} }
if lazyImageProcesses != nil { if theDelayedImageTasks.add(f) {
lazyImageProcesses = append(lazyImageProcesses, f)
return nil return nil
} }
return f() return f()
@ -187,7 +206,7 @@ func (i *Image) At(x, y int) color.Color {
// TODO: What if At is called internaly (like from image parts?) // TODO: What if At is called internaly (like from image parts?)
imageM.Lock() imageM.Lock()
defer imageM.Unlock() defer imageM.Unlock()
if lazyImageProcesses != nil { if !theDelayedImageTasks.isExecCalled() {
panic("ebiten: At can't be called when the GL context is not initialized (this panic happens as of version 1.4.0-alpha)") panic("ebiten: At can't be called when the GL context is not initialized (this panic happens as of version 1.4.0-alpha)")
} }
if i.isDisposed() { if i.isDisposed() {
@ -213,9 +232,9 @@ func (i *Image) At(x, y int) color.Color {
// //
// This function is concurrent-safe. // This function is concurrent-safe.
func (i *Image) Dispose() error { func (i *Image) Dispose() error {
f := func() error {
imageM.Lock() imageM.Lock()
defer imageM.Unlock() defer imageM.Unlock()
f := func() error {
if i.isDisposed() { if i.isDisposed() {
return errors.New("ebiten: image is already disposed") return errors.New("ebiten: image is already disposed")
} }
@ -236,8 +255,8 @@ func (i *Image) Dispose() error {
runtime.SetFinalizer(i, nil) runtime.SetFinalizer(i, nil)
return nil return nil
} }
if lazyImageProcesses != nil {
lazyImageProcesses = append(lazyImageProcesses, f) if theDelayedImageTasks.add(f) {
return nil return nil
} }
return f() return f()
@ -255,12 +274,12 @@ func (i *Image) isDisposed() bool {
// //
// This function is concurrent-safe. // This function is concurrent-safe.
func (i *Image) ReplacePixels(p []uint8) error { func (i *Image) ReplacePixels(p []uint8) error {
imageM.Lock()
defer imageM.Unlock()
if l := 4 * i.width * i.height; len(p) != l { if l := 4 * i.width * i.height; len(p) != l {
return fmt.Errorf("ebiten: p's length must be %d", l) return fmt.Errorf("ebiten: p's length must be %d", l)
} }
f := func() error { f := func() error {
imageM.Lock()
defer imageM.Unlock()
// Don't set i.pixels here because i.pixels is used not every time. // Don't set i.pixels here because i.pixels is used not every time.
i.pixels = nil i.pixels = nil
if i.isDisposed() { if i.isDisposed() {
@ -268,8 +287,7 @@ func (i *Image) ReplacePixels(p []uint8) error {
} }
return i.framebuffer.ReplacePixels(glContext, i.texture, p) return i.framebuffer.ReplacePixels(glContext, i.texture, p)
} }
if lazyImageProcesses != nil { if theDelayedImageTasks.add(f) {
lazyImageProcesses = append(lazyImageProcesses, f)
return nil return nil
} }
return f() return f()
@ -295,13 +313,13 @@ type DrawImageOptions struct {
// //
// This function is concurrent-safe. // This function is concurrent-safe.
func NewImage(width, height int, filter Filter) (*Image, error) { func NewImage(width, height int, filter Filter) (*Image, error) {
imageM.Lock()
defer imageM.Unlock()
image := &Image{ image := &Image{
width: width, width: width,
height: height, height: height,
} }
f := func() error { f := func() error {
imageM.Lock()
defer imageM.Unlock()
texture, err := graphics.NewTexture(glContext, width, height, glFilter(glContext, filter)) texture, err := graphics.NewTexture(glContext, width, height, glFilter(glContext, filter))
if err != nil { if err != nil {
return err return err
@ -319,8 +337,7 @@ func NewImage(width, height int, filter Filter) (*Image, error) {
} }
return nil return nil
} }
if lazyImageProcesses != nil { if theDelayedImageTasks.add(f) {
lazyImageProcesses = append(lazyImageProcesses, f)
return image, nil return image, nil
} }
if err := f(); err != nil { if err := f(); err != nil {
@ -338,21 +355,24 @@ func NewImage(width, height int, filter Filter) (*Image, error) {
// //
// This function is concurrent-safe. // This function is concurrent-safe.
func NewImageFromImage(img image.Image, filter Filter) (*Image, error) { func NewImageFromImage(img image.Image, filter Filter) (*Image, error) {
// Can't call (*ebiten.Image).At here because of the lock.
if _, ok := img.(*Image); ok {
return nil, errors.New("ebiten: NewImageFromImage can't take *ebiten.Image")
}
imageM.Lock()
defer imageM.Unlock()
size := img.Bounds().Size() size := img.Bounds().Size()
w, h := size.X, size.Y w, h := size.X, size.Y
image := &Image{ eimg := &Image{
width: w, width: w,
height: h, height: h,
} }
f := func() error { f := func() error {
texture, err := graphics.NewTextureFromImage(glContext, img, glFilter(glContext, filter)) // Don't lock while manipulating an image.Image interface.
rgbaImg, ok := img.(*image.RGBA)
if !ok {
origImg := img
newImg := image.NewRGBA(origImg.Bounds())
draw.Draw(newImg, newImg.Bounds(), origImg, origImg.Bounds().Min, draw.Src)
rgbaImg = newImg
}
imageM.Lock()
defer imageM.Unlock()
texture, err := graphics.NewTextureFromImage(glContext, rgbaImg, glFilter(glContext, filter))
if err != nil { if err != nil {
return err return err
} }
@ -361,17 +381,16 @@ func NewImageFromImage(img image.Image, filter Filter) (*Image, error) {
// TODO: texture should be removed here? // TODO: texture should be removed here?
return err return err
} }
image.framebuffer = framebuffer eimg.framebuffer = framebuffer
image.texture = texture eimg.texture = texture
runtime.SetFinalizer(image, (*Image).Dispose) runtime.SetFinalizer(eimg, (*Image).Dispose)
return nil return nil
} }
if lazyImageProcesses != nil { if theDelayedImageTasks.add(f) {
lazyImageProcesses = append(lazyImageProcesses, f) return eimg, nil
return image, nil
} }
if err := f(); err != nil { if err := f(); err != nil {
return nil, err return nil, err
} }
return image, nil return eimg, nil
} }

View File

@ -71,14 +71,3 @@ func TestImageDispose(t *testing.T) {
t.Errorf("img.Dipose() returns error: %v", err) t.Errorf("img.Dipose() returns error: %v", err)
} }
} }
func TestNewImageFromEbitenImage(t *testing.T) {
img, _, err := openEbitenImage("testdata/ebiten.png")
if err != nil {
t.Fatal(err)
return
}
if _, err := NewImageFromImage(img, FilterNearest); err == nil {
t.Errorf("NewImageFromImage with an *ebiten.Image must return an error")
}
}

View File

@ -16,12 +16,13 @@ package graphics
import ( import (
"errors" "errors"
"github.com/hajimehoshi/ebiten/internal/graphics/opengl"
"image" "image"
"image/draw" "image/draw"
"github.com/hajimehoshi/ebiten/internal/graphics/opengl"
) )
func adjustImageForTexture(img image.Image) *image.RGBA { func adjustImageForTexture(img *image.RGBA) *image.RGBA {
width, height := img.Bounds().Size().X, img.Bounds().Size().Y width, height := img.Bounds().Size().X, img.Bounds().Size().Y
adjustedImageBounds := image.Rectangle{ adjustedImageBounds := image.Rectangle{
image.ZP, image.ZP,
@ -30,8 +31,8 @@ func adjustImageForTexture(img image.Image) *image.RGBA {
int(NextPowerOf2Int32(int32(height))), int(NextPowerOf2Int32(int32(height))),
}, },
} }
if adjustedImage, ok := img.(*image.RGBA); ok && img.Bounds() == adjustedImageBounds { if img.Bounds() == adjustedImageBounds {
return adjustedImage return img
} }
adjustedImage := image.NewRGBA(adjustedImageBounds) adjustedImage := image.NewRGBA(adjustedImageBounds)
@ -39,7 +40,7 @@ func adjustImageForTexture(img image.Image) *image.RGBA {
image.ZP, image.ZP,
img.Bounds().Size(), img.Bounds().Size(),
} }
draw.Draw(adjustedImage, dstBounds, img, image.ZP, draw.Src) draw.Draw(adjustedImage, dstBounds, img, img.Bounds().Min, draw.Src)
return adjustedImage return adjustedImage
} }
@ -69,7 +70,7 @@ func NewTexture(c *opengl.Context, width, height int, filter opengl.Filter) (*Te
return &Texture{native, width, height}, nil return &Texture{native, width, height}, nil
} }
func NewTextureFromImage(c *opengl.Context, img image.Image, filter opengl.Filter) (*Texture, error) { func NewTextureFromImage(c *opengl.Context, img *image.RGBA, filter opengl.Filter) (*Texture, error) {
origSize := img.Bounds().Size() origSize := img.Bounds().Size()
if origSize.X < 4 { if origSize.X < 4 {
return nil, errors.New("width must be equal or more than 4.") return nil, errors.New("width must be equal or more than 4.")

2
run.go
View File

@ -189,7 +189,7 @@ func run(f func(*Image) error, width, height, scale int, title string) error {
return err return err
} }
if err := execDelayedImageProcesses(); err != nil { if err := theDelayedImageTasks.exec(); err != nil {
return err return err
} }