Implement image.Image at ebiten.Image (#35)

This commit is contained in:
Hajime Hoshi 2014-12-23 01:26:44 +09:00
parent 095c3ca380
commit cd9efd3932
6 changed files with 102 additions and 30 deletions

View File

@ -19,6 +19,7 @@ package ebitenutil
import ( import (
"github.com/hajimehoshi/ebiten" "github.com/hajimehoshi/ebiten"
"image" "image"
"image/png"
"os" "os"
) )
@ -38,3 +39,15 @@ func NewImageFromFile(path string, filter ebiten.Filter) (*ebiten.Image, image.I
} }
return img2, img, err return img2, img, err
} }
func SaveImageAsPNG(path string, img *ebiten.Image) error {
file, err := os.Create(path)
if err != nil {
return err
}
defer file.Close()
if err := png.Encode(file, img); err != nil {
return err
}
return nil
}

View File

@ -29,31 +29,31 @@ const (
screenHeight = 240 screenHeight = 240
) )
type Game struct { var (
count int count int
tmpRenderTarget *ebiten.Image tmpRenderTarget *ebiten.Image
ebitenImage *ebiten.Image ebitenImage *ebiten.Image
} saved bool
)
func (g *Game) Update(r *ebiten.Image) error { func Update(r *ebiten.Image) error {
g.count++ count++
g.count %= 600 count %= 600
diff := float64(g.count) * 0.2 diff := float64(count) * 0.2
switch { switch {
case 480 < g.count: case 480 < count:
diff = 0 diff = 0
case 240 < g.count: case 240 < count:
diff = float64(480-g.count) * 0.2 diff = float64(480-count) * 0.2
} }
_ = diff
if err := g.tmpRenderTarget.Clear(); err != nil { if err := tmpRenderTarget.Clear(); err != nil {
return err return err
} }
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
geo := ebiten.TranslateGeometry(15+float64(i)*(diff), 20) geo := ebiten.TranslateGeometry(15+float64(i)*(diff), 20)
clr := ebiten.ScaleColor(1.0, 1.0, 1.0, 0.5) clr := ebiten.ScaleColor(1.0, 1.0, 1.0, 0.5)
if err := ebiten.DrawWholeImage(g.tmpRenderTarget, g.ebitenImage, geo, clr); err != nil { if err := ebiten.DrawWholeImage(tmpRenderTarget, ebitenImage, geo, clr); err != nil {
return err return err
} }
} }
@ -62,25 +62,32 @@ func (g *Game) Update(r *ebiten.Image) error {
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
geo := ebiten.TranslateGeometry(0, float64(i)*(diff)) geo := ebiten.TranslateGeometry(0, float64(i)*(diff))
clr := ebiten.ColorMatrixI() clr := ebiten.ColorMatrixI()
if err := ebiten.DrawWholeImage(r, g.tmpRenderTarget, geo, clr); err != nil { if err := ebiten.DrawWholeImage(r, tmpRenderTarget, geo, clr); err != nil {
return err return err
} }
} }
if !saved && ebiten.IsKeyPressed(ebiten.KeySpace) {
if err := ebitenutil.SaveImageAsPNG("out.png", r); err != nil {
return err
}
saved = true
}
return nil return nil
} }
func main() { func main() {
g := new(Game)
var err error var err error
g.ebitenImage, _, err = ebitenutil.NewImageFromFile("images/ebiten.png", ebiten.FilterNearest) ebitenImage, _, err = ebitenutil.NewImageFromFile("images/ebiten.png", ebiten.FilterNearest)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
g.tmpRenderTarget, err = ebiten.NewImage(screenWidth, screenHeight, ebiten.FilterNearest) tmpRenderTarget, err = ebiten.NewImage(screenWidth, screenHeight, ebiten.FilterNearest)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
if err := ebiten.Run(g.Update, screenWidth, screenHeight, 2, "Alpha Blending (Ebiten Demo)"); err != nil { if err := ebiten.Run(Update, screenWidth, screenHeight, 2, "Alpha Blending (Ebiten Demo)"); err != nil {
log.Fatal(err) log.Fatal(err)
} }
} }

View File

@ -21,6 +21,7 @@ import (
"github.com/hajimehoshi/ebiten/internal" "github.com/hajimehoshi/ebiten/internal"
"github.com/hajimehoshi/ebiten/internal/opengl" "github.com/hajimehoshi/ebiten/internal/opengl"
"github.com/hajimehoshi/ebiten/internal/opengl/internal/shader" "github.com/hajimehoshi/ebiten/internal/opengl/internal/shader"
"image"
"image/color" "image/color"
) )
@ -61,12 +62,8 @@ func (i *innerImage) drawImage(image *innerImage, parts []ImagePart, geo Geometr
} }
w, h := image.texture.Size() w, h := image.texture.Size()
quads := textureQuads(parts, w, h) quads := textureQuads(parts, w, h)
targetNativeTexture := gl.Texture(0)
if i.texture != nil {
targetNativeTexture = i.texture.Native()
}
projectionMatrix := i.framebuffer.ProjectionMatrix() projectionMatrix := i.framebuffer.ProjectionMatrix()
shader.DrawTexture(image.texture.Native(), targetNativeTexture, projectionMatrix, quads, &geo, &color) shader.DrawTexture(image.texture.Native(), projectionMatrix, quads, &geo, &color)
return nil return nil
} }
@ -104,6 +101,7 @@ type syncer interface {
type Image struct { type Image struct {
syncer syncer syncer syncer
inner *innerImage inner *innerImage
pixels []uint8
} }
// Size returns the size of the image. // Size returns the size of the image.
@ -113,6 +111,7 @@ func (i *Image) Size() (width, height int) {
// Clear resets the pixels of the image into 0. // Clear resets the pixels of the image into 0.
func (i *Image) Clear() (err error) { func (i *Image) Clear() (err error) {
i.pixels = nil
i.syncer.Sync(func() { i.syncer.Sync(func() {
err = i.inner.Clear() err = i.inner.Clear()
}) })
@ -121,6 +120,7 @@ func (i *Image) Clear() (err error) {
// Fill fills the image with a solid color. // Fill fills the image with a solid color.
func (i *Image) Fill(clr color.Color) (err error) { func (i *Image) Fill(clr color.Color) (err error) {
i.pixels = nil
i.syncer.Sync(func() { i.syncer.Sync(func() {
err = i.inner.Fill(clr) err = i.inner.Fill(clr)
}) })
@ -133,8 +133,39 @@ func (i *Image) DrawImage(image *Image, parts []ImagePart, geo GeometryMatrix, c
} }
func (i *Image) drawImage(image *innerImage, parts []ImagePart, geo GeometryMatrix, color ColorMatrix) (err error) { func (i *Image) drawImage(image *innerImage, parts []ImagePart, geo GeometryMatrix, color ColorMatrix) (err error) {
i.pixels = nil
i.syncer.Sync(func() { i.syncer.Sync(func() {
err = i.inner.drawImage(image, parts, geo, color) err = i.inner.drawImage(image, parts, geo, color)
}) })
return return
} }
// Bounds returns the bounds of the image.
func (i *Image) Bounds() image.Rectangle {
w, h := i.inner.size()
return image.Rect(0, 0, w, h)
}
// ColorModel returns the color model of the image.
func (i *Image) ColorModel() color.Model {
return color.RGBAModel
}
// At returns the color of the image at (x, y).
// This method may read pixels from GPU to VRAM and be slow.
func (i *Image) At(x, y int) color.Color {
if i.pixels == nil {
i.syncer.Sync(func() {
var err error
i.pixels, err = i.inner.texture.Pixels()
if err != nil {
panic(err)
}
})
}
w, _ := i.inner.size()
w = internal.NextPowerOf2Int(w)
idx := 4*x + 4*y*w
r, g, b, a := i.pixels[idx], i.pixels[idx+1], i.pixels[idx+2], i.pixels[idx+3]
return color.RGBA{r, g, b, a}
}

View File

@ -38,7 +38,7 @@ type Matrix interface {
} }
// TODO: Use VBO // TODO: Use VBO
func DrawTexture(native gl.Texture, target gl.Texture, projectionMatrix [4][4]float64, quads []TextureQuad, geo Matrix, color Matrix) { func DrawTexture(native gl.Texture, projectionMatrix [4][4]float64, quads []TextureQuad, geo Matrix, color Matrix) {
once.Do(func() { once.Do(func() {
initialize() initialize()
}) })
@ -52,11 +52,6 @@ func DrawTexture(native gl.Texture, target gl.Texture, projectionMatrix [4][4]fl
gl.ActiveTexture(gl.TEXTURE0) gl.ActiveTexture(gl.TEXTURE0)
native.Bind(gl.TEXTURE_2D) native.Bind(gl.TEXTURE_2D)
if 0 < target {
gl.ActiveTexture(gl.TEXTURE1)
target.Bind(gl.TEXTURE_2D)
}
vertexAttrLocation := getAttributeLocation(program, "vertex") vertexAttrLocation := getAttributeLocation(program, "vertex")
texCoordAttrLocation := getAttributeLocation(program, "tex_coord") texCoordAttrLocation := getAttributeLocation(program, "tex_coord")

View File

@ -17,6 +17,8 @@ limitations under the License.
package opengl package opengl
import ( import (
"errors"
"fmt"
"github.com/go-gl/gl" "github.com/go-gl/gl"
"github.com/hajimehoshi/ebiten/internal" "github.com/hajimehoshi/ebiten/internal"
"image" "image"
@ -79,12 +81,24 @@ func createNativeTexture(textureWidth, textureHeight int, pixels []uint8, filter
func NewTexture(width, height int, filter int) (*Texture, error) { func NewTexture(width, height int, filter int) (*Texture, error) {
w := internal.NextPowerOf2Int(width) w := internal.NextPowerOf2Int(width)
h := internal.NextPowerOf2Int(height) h := internal.NextPowerOf2Int(height)
if w < 4 {
return nil, errors.New("width must be equal or more than 4.")
}
if h < 4 {
return nil, errors.New("height must be equal or more than 4.")
}
native := createNativeTexture(w, h, nil, filter) native := createNativeTexture(w, h, nil, filter)
return &Texture{native, width, height}, nil return &Texture{native, width, height}, nil
} }
func NewTextureFromImage(img image.Image, filter int) (*Texture, error) { func NewTextureFromImage(img image.Image, filter int) (*Texture, error) {
origSize := img.Bounds().Size() origSize := img.Bounds().Size()
if origSize.X < 4 {
return nil, errors.New("width must be equal or more than 4.")
}
if origSize.Y < 4 {
return nil, errors.New("height must be equal or more than 4.")
}
adjustedImage := adjustImageForTexture(img) adjustedImage := adjustImageForTexture(img)
size := adjustedImage.Bounds().Size() size := adjustedImage.Bounds().Size()
native := createNativeTexture(size.X, size.Y, adjustedImage.Pix, filter) native := createNativeTexture(size.X, size.Y, adjustedImage.Pix, filter)
@ -94,3 +108,15 @@ func NewTextureFromImage(img image.Image, filter int) (*Texture, error) {
func (t *Texture) Dispose() { func (t *Texture) Dispose() {
t.native.Delete() t.native.Delete()
} }
func (t *Texture) Pixels() ([]uint8, error) {
w, h := internal.NextPowerOf2Int(t.width), internal.NextPowerOf2Int(t.height)
pixels := make([]uint8, 4*w*h)
t.native.Bind(gl.TEXTURE_2D)
gl.GetTexImage(gl.TEXTURE_2D, 0, gl.RGBA, gl.UNSIGNED_BYTE, pixels)
if e := gl.GetError(); e != gl.NO_ERROR {
// TODO: Use glu.ErrorString
return nil, errors.New(fmt.Sprintf("gl error: %d", e))
}
return pixels, nil
}

4
ui.go
View File

@ -148,7 +148,7 @@ func (u *ui) newImageFromImage(img image.Image, filter int) (*Image, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &Image{u, innerImage}, nil return &Image{syncer: u, inner: innerImage}, nil
} }
func (u *ui) newImage(width, height int, filter int) (*Image, error) { func (u *ui) newImage(width, height int, filter int) (*Image, error) {
@ -166,7 +166,7 @@ func (u *ui) newImage(width, height int, filter int) (*Image, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &Image{u, innerImage}, nil return &Image{syncer: u, inner: innerImage}, nil
} }
func (u *ui) run() { func (u *ui) run() {