Revert "ebiten: Make ebiten.Image and buffered.Image 1:1"

This reverts commit 620981a09a.

Fixes #1218
Updates #896
This commit is contained in:
Hajime Hoshi 2020-06-28 00:36:13 +09:00
parent 52df43b7ab
commit 36515eb1f5
3 changed files with 46 additions and 236 deletions

196
image.go
View File

@ -37,11 +37,8 @@ type Image struct {
bounds image.Rectangle
original *Image
subs map[image.Rectangle]*Image
filter Filter
disposed bool
}
func (i *Image) copyCheck() {
@ -57,11 +54,7 @@ func (i *Image) Size() (width, height int) {
}
func (i *Image) isDisposed() bool {
return i.disposed
}
func (i *Image) isEmpty() bool {
return i.bounds.Dx() == 0 || i.bounds.Dy() == 0
return i.buffered == nil
}
func (i *Image) isSubImage() bool {
@ -89,18 +82,13 @@ func (i *Image) Fill(clr color.Color) error {
if i.isDisposed() {
return nil
}
if i.isEmpty() {
return nil
}
// TODO: Implement this.
if i.isSubImage() {
panic("ebiten: render to a subimage is not implemented (Fill)")
}
i.desyncSubImages()
i.buffered.Fill(color.RGBAModel.Convert(clr).(color.RGBA))
return nil
}
@ -147,27 +135,15 @@ func (i *Image) DrawImage(img *Image, options *DrawImageOptions) error {
if img.isDisposed() {
panic("ebiten: the given image to DrawImage must not be disposed")
}
if img.isEmpty() {
return nil
}
if i.isDisposed() {
return nil
}
if i.isEmpty() {
return nil
}
// TODO: Implement this.
if i.isSubImage() {
panic("ebiten: render to a subimage is not implemented (drawImage)")
}
if err := img.syncWithOriginalIfNeeded(); err != nil {
theUIContext.setError(err)
return nil
}
i.desyncSubImages()
// Calculate vertices before locking because the user can do anything in
// options.ImageParts interface without deadlock (e.g. Call Image functions).
if options == nil {
@ -222,9 +198,7 @@ func (i *Image) DrawImage(img *Image, options *DrawImageOptions) error {
}
a, b, c, d, tx, ty := geom.elements32()
w, h := img.Size()
i.buffered.DrawImage(img.buffered, image.Rect(0, 0, w, h), a, b, c, d, tx, ty, options.ColorM.impl, mode, filter)
i.buffered.DrawImage(img.buffered, img.Bounds(), a, b, c, d, tx, ty, options.ColorM.impl, mode, filter)
return nil
}
@ -306,12 +280,10 @@ func (i *Image) DrawTriangles(vertices []Vertex, indices []uint16, img *Image, o
if img.isDisposed() {
panic("ebiten: the given image to DrawTriangles must not be disposed")
}
if img.isEmpty() {
return
}
if i.isDisposed() {
return
}
if i.isSubImage() {
panic("ebiten: render to a subimage is not implemented (DrawTriangles)")
}
@ -324,12 +296,6 @@ func (i *Image) DrawTriangles(vertices []Vertex, indices []uint16, img *Image, o
}
// TODO: Check the maximum value of indices and len(vertices)?
if err := img.syncWithOriginalIfNeeded(); err != nil {
theUIContext.setError(err)
return
}
i.desyncSubImages()
if options == nil {
options = &DrawTrianglesOptions{}
}
@ -343,19 +309,18 @@ func (i *Image) DrawTriangles(vertices []Vertex, indices []uint16, img *Image, o
filter = driver.Filter(img.filter)
}
w, h := img.Size()
bx0 := float32(0)
by0 := float32(0)
bx1 := float32(w)
by1 := float32(h)
b := img.Bounds()
bx0 := float32(b.Min.X)
by0 := float32(b.Min.Y)
bx1 := float32(b.Max.X)
by1 := float32(b.Max.Y)
dx, dy := float32(img.Bounds().Min.X), float32(img.Bounds().Min.Y)
vs := make([]float32, len(vertices)*graphics.VertexFloatNum)
for i, v := range vertices {
vs[i*graphics.VertexFloatNum] = v.DstX
vs[i*graphics.VertexFloatNum+1] = v.DstY
vs[i*graphics.VertexFloatNum+2] = v.SrcX - dx
vs[i*graphics.VertexFloatNum+3] = v.SrcY - dy
vs[i*graphics.VertexFloatNum+2] = v.SrcX
vs[i*graphics.VertexFloatNum+3] = v.SrcY
vs[i*graphics.VertexFloatNum+4] = bx0
vs[i*graphics.VertexFloatNum+5] = by0
vs[i*graphics.VertexFloatNum+6] = bx1
@ -395,8 +360,6 @@ func (i *Image) DrawTrianglesWithShader(vertices []Vertex, indices []uint16, sha
panic("ebiten: len(indices) must be <= MaxIndicesNum")
}
i.desyncSubImages()
if options == nil {
options = &DrawTrianglesWithShaderOptions{}
}
@ -411,25 +374,16 @@ func (i *Image) DrawTrianglesWithShader(vertices []Vertex, indices []uint16, sha
if v.isDisposed() {
panic("ebiten: the given image to DrawTriangles must not be disposed")
}
if v.isEmpty() {
// TODO: Fix this
panic("ebiten: zero-sized image for DrawTrianglesWithShader is not implemented so far")
}
if err := v.syncWithOriginalIfNeeded(); err != nil {
theUIContext.setError(err)
return
}
us = append(us, v.buffered)
if firstImage == nil {
firstImage = v
} else {
w, h := v.Size()
b := v.Bounds()
us = append(us, []float32{
0,
0,
float32(w),
float32(h),
float32(b.Min.X),
float32(b.Min.Y),
float32(b.Max.X),
float32(b.Max.Y),
})
}
default:
@ -441,24 +395,21 @@ func (i *Image) DrawTrianglesWithShader(vertices []Vertex, indices []uint16, sha
// The actual value is set at graphicscommand package.
us = append([]interface{}{[]float32{0, 0}}, us...)
var dx, dy, bx0, by0, bx1, by1 float32
var bx0, by0, bx1, by1 float32
if firstImage != nil {
dx = float32(firstImage.Bounds().Min.X)
dy = float32(firstImage.Bounds().Min.Y)
w, h := firstImage.Size()
bx0 = float32(0)
by0 = float32(0)
bx1 = float32(w)
by1 = float32(h)
b := firstImage.Bounds()
bx0 = float32(b.Min.X)
by0 = float32(b.Min.Y)
bx1 = float32(b.Max.X)
by1 = float32(b.Max.Y)
}
vs := make([]float32, len(vertices)*graphics.VertexFloatNum)
for i, v := range vertices {
vs[i*graphics.VertexFloatNum] = v.DstX
vs[i*graphics.VertexFloatNum+1] = v.DstY
vs[i*graphics.VertexFloatNum+2] = v.SrcX - dx
vs[i*graphics.VertexFloatNum+3] = v.SrcY - dy
vs[i*graphics.VertexFloatNum+2] = v.SrcX
vs[i*graphics.VertexFloatNum+3] = v.SrcY
vs[i*graphics.VertexFloatNum+4] = bx0
vs[i*graphics.VertexFloatNum+5] = by0
vs[i*graphics.VertexFloatNum+6] = bx1
@ -488,8 +439,6 @@ func (i *Image) SubImage(r image.Rectangle) image.Image {
return nil
}
// TODO: Check that SubImage cannot be called on a screen image.
r = r.Intersect(i.Bounds())
// Need to check Empty explicitly. See the standard image package implementations.
if r.Empty() {
@ -502,53 +451,17 @@ func (i *Image) SubImage(r image.Rectangle) image.Image {
orig = i.original
}
if sub, ok := orig.subs[r]; ok {
return sub
}
// In the initial state, the image doesn't have its own pixels.
// Sync with its original image when necessary.
img := &Image{
buffered: i.buffered,
filter: i.filter,
bounds: r,
original: orig,
}
img.addr = img
orig.subs[img.Bounds()] = img
return img
}
func (i *Image) desyncSubImages() {
if i.isSubImage() {
panic("ebiten: desyncSubImages must be called on an original image")
}
for _, s := range i.subs {
if s.buffered == nil {
continue
}
s.buffered.MarkDisposed()
s.buffered = nil
}
}
func (i *Image) syncWithOriginalIfNeeded() error {
if !i.isSubImage() {
return nil
}
if i.buffered != nil {
return nil
}
x, y, width, height := i.bounds.Min.X, i.bounds.Min.Y, i.bounds.Dx(), i.bounds.Dy()
i.buffered = buffered.NewImage(width, height, false)
if err := i.buffered.CopyPixels(i.original.buffered, x, y, width, height); err != nil {
return err
}
return nil
}
// Bounds returns the bounds of the image.
func (i *Image) Bounds() image.Rectangle {
if i.isDisposed() {
@ -579,11 +492,6 @@ func (i *Image) At(x, y int) color.Color {
if !image.Pt(x, y).In(i.Bounds()) {
return color.RGBA{}
}
if i.isSubImage() {
return i.original.At(x, y)
}
pix, err := i.buffered.Pixels(x, y, 1, 1)
if err != nil {
theUIContext.setError(err)
@ -607,27 +515,20 @@ func (i *Image) Set(x, y int, clr color.Color) {
if !image.Pt(x, y).In(i.Bounds()) {
return
}
if i.isSubImage() {
i.original.Set(x, y, clr)
return
i = i.original
}
r, g, b, a := clr.RGBA()
pix := []byte{byte(r >> 8), byte(g >> 8), byte(b >> 8), byte(a >> 8)}
if err := i.buffered.ReplacePixels(pix, x, y, 1, 1); err != nil {
theUIContext.setError(err)
return
}
i.desyncSubImages()
}
// Dispose disposes the image data.
// After disposing, most of image functions do nothing and returns meaningless values.
//
// If the callee is a sub-image, Dispose disposes only the callee.
// If the callee is not a sub-image, Dispose also diposes all its related sub-images.
//
// Calling Dispose is not mandatory. GC automatically collects internal resources that no objects refer to.
// However, calling Dispose explicitly is helpful if memory usage matters.
//
@ -640,25 +541,11 @@ func (i *Image) Dispose() error {
if i.isDisposed() {
return nil
}
// Get the bound before the image is disposed.
b := i.Bounds()
if i.buffered != nil {
i.buffered.MarkDisposed()
i.buffered = nil
}
i.disposed = true
// If a sub-image is disposed, dispose only this image.
if i.isSubImage() {
delete(i.original.subs, b)
return nil
}
for _, s := range i.subs {
s.Dispose()
}
i.buffered.MarkDisposed()
i.buffered = nil
return nil
}
@ -675,35 +562,16 @@ func (i *Image) Dispose() error {
//
// ReplacePixels always returns nil as of 1.5.0-alpha.
func (i *Image) ReplacePixels(pix []byte) error {
// TODO: This is not very efficient. Fix this.
if i.isSubImage() {
i.original.replacePixels(pix, i.Bounds().Min.X, i.Bounds().Min.Y, i.Bounds().Dx(), i.Bounds().Dy())
return nil
}
w, h := i.Size()
i.replacePixels(pix, 0, 0, w, h)
return nil
}
func (i *Image) replacePixels(pix []byte, x, y, width, height int) {
i.copyCheck()
if i.isDisposed() {
return
return nil
}
if i.isEmpty() {
return
}
if i.isSubImage() {
panic("ebiten: replacePixels must be called on an original image")
}
if err := i.buffered.ReplacePixels(pix, x, y, width, height); err != nil {
r := i.Bounds()
if err := i.buffered.ReplacePixels(pix, r.Min.X, r.Min.Y, r.Dx(), r.Dy()); err != nil {
theUIContext.setError(err)
return
}
i.desyncSubImages()
return nil
}
// A DrawImageOptions represents options to render an image on an image.
@ -759,7 +627,6 @@ func newImage(width, height int, filter Filter, volatile bool) *Image {
buffered: buffered.NewImage(width, height, volatile),
filter: filter,
bounds: image.Rect(0, 0, width, height),
subs: map[image.Rectangle]*Image{},
}
i.addr = i
return i
@ -782,7 +649,6 @@ func NewImageFromImage(source image.Image, filter Filter) (*Image, error) {
buffered: buffered.NewImage(width, height, false),
filter: filter,
bounds: image.Rect(0, 0, width, height),
subs: map[image.Rectangle]*Image{},
}
i.addr = i

View File

@ -26,7 +26,6 @@ import (
"testing"
. "github.com/hajimehoshi/ebiten"
"github.com/hajimehoshi/ebiten/ebitenutil"
"github.com/hajimehoshi/ebiten/examples/resources/images"
"github.com/hajimehoshi/ebiten/internal/graphics"
t "github.com/hajimehoshi/ebiten/internal/testing"
@ -548,18 +547,18 @@ func TestImageClear(t *testing.T) {
// Issue #317, #558, #724
func TestImageEdge(t *testing.T) {
// TODO: This test is not so meaningful after #1218. Do we remove this?
if isGopherJS() {
t.Skip("too slow on GopherJS")
return
}
const (
img0Width = 16
img0Height = 16
img0InnerWidth = 10
img0InnerHeight = 10
img0OffsetWidth = (img0Width - img0InnerWidth) / 2
img0OffsetHeight = (img0Height - img0InnerHeight) / 2
img0Width = 10
img0Height = 10
img0InnerWidth = 10
img0InnerHeight = 10
img1Width = 32
img1Height = 32
@ -569,19 +568,10 @@ func TestImageEdge(t *testing.T) {
for j := 0; j < img0Height; j++ {
for i := 0; i < img0Width; i++ {
idx := 4 * (i + j*img0Width)
switch {
case img0OffsetWidth <= i && i < img0Width-img0OffsetWidth &&
img0OffsetHeight <= j && j < img0Height-img0OffsetHeight:
pixels[idx] = 0xff
pixels[idx+1] = 0
pixels[idx+2] = 0
pixels[idx+3] = 0xff
default:
pixels[idx] = 0
pixels[idx+1] = 0xff
pixels[idx+2] = 0
pixels[idx+3] = 0xff
}
pixels[idx] = 0xff
pixels[idx+1] = 0
pixels[idx+2] = 0
pixels[idx+3] = 0xff
}
}
img0.ReplacePixels(pixels)
@ -598,15 +588,13 @@ func TestImageEdge(t *testing.T) {
angles = append(angles, float64(a)/4096*2*math.Pi)
}
img0Sub := img0.SubImage(image.Rect(img0OffsetWidth, img0OffsetHeight, img0Width-img0OffsetWidth, img0Height-img0OffsetHeight)).(*Image)
for _, s := range []float64{1, 0.5, 0.25} {
for _, f := range []Filter{FilterNearest, FilterLinear} {
for _, a := range angles {
for _, testDrawTriangles := range []bool{false, true} {
img1.Clear()
w, h := img0Sub.Size()
b := img0Sub.Bounds()
w, h := img0.Size()
b := img0.Bounds()
var geo GeoM
geo.Translate(-float64(w)/2, -float64(h)/2)
geo.Scale(s, s)
@ -616,7 +604,7 @@ func TestImageEdge(t *testing.T) {
op := &DrawImageOptions{}
op.GeoM = geo
op.Filter = f
img1.DrawImage(img0Sub, op)
img1.DrawImage(img0, op)
} else {
op := &DrawTrianglesOptions{}
dx0, dy0 := geo.Apply(0, 0)
@ -667,7 +655,7 @@ func TestImageEdge(t *testing.T) {
}
is := graphics.QuadIndices()
op.Filter = f
img1.DrawTriangles(vs, is, img0Sub, op)
img1.DrawTriangles(vs, is, img0, op)
}
allTransparent := true
for j := 0; j < img1Height; j++ {
@ -763,30 +751,6 @@ func TestImageLinearGradiation(t *testing.T) {
}
}
func TestImageLinearEdges(t *testing.T) {
src, _ := NewImage(32, 32, FilterDefault)
dst, _ := NewImage(64, 64, FilterDefault)
src.Fill(color.RGBA{0, 0xff, 0, 0xff})
ebitenutil.DrawRect(src, 8, 8, 16, 16, color.RGBA{0xff, 0, 0, 0xff})
op := &DrawImageOptions{}
op.GeoM.Translate(8, 8)
op.GeoM.Scale(2, 2)
op.Filter = FilterLinear
dst.DrawImage(src.SubImage(image.Rect(8, 8, 24, 24)).(*Image), op)
for j := 0; j < 64; j++ {
for i := 0; i < 64; i++ {
c := dst.At(i, j).(color.RGBA)
got := c.G
want := uint8(0)
if abs(int(c.G)-int(want)) > 1 {
t.Errorf("dst At(%d, %d).G: got %#v, want: %#v", i, j, got, want)
}
}
}
}
func TestImageOutside(t *testing.T) {
src, _ := NewImage(5, 10, FilterNearest) // internal texture size is 8x16.
dst, _ := NewImage(4, 4, FilterNearest)

View File

@ -198,9 +198,7 @@ func (i *Image) ReplacePixels(pix []byte, x, y, width, height int) error {
i.invalidatePendingPixels()
// Don't call (*mipmap.Mipmap).ReplacePixels here. Let's defer it to reduce GPU operations as much as
// posssible. This is a necessary optimization for sub-images: as sub-images are actually used and,
// have to allocate their region on a texture atlas, while their original image doesn't have to
// allocate its region on a texture atlas (#896).
// posssible.
copied := make([]byte, len(pix))
copy(copied, pix)
i.pixels = copied
@ -229,24 +227,6 @@ func (i *Image) replacePendingPixels(pix []byte, x, y, width, height int) {
i.needsToResolvePixels = true
}
func (i *Image) CopyPixels(img *Image, x, y, width, height int) error {
if tryAddDelayedCommand(func(obj interface{}) error {
i.CopyPixels(img, x, y, width, height)
return nil
}, nil) {
return nil
}
pix, err := img.Pixels(x, y, width, height)
if err != nil {
return err
}
if err := i.ReplacePixels(pix, 0, 0, width, height); err != nil {
return err
}
return nil
}
func (i *Image) DrawImage(src *Image, bounds image.Rectangle, a, b, c, d, tx, ty float32, colorm *affine.ColorM, mode driver.CompositeMode, filter driver.Filter) {
if i == src {
panic("buffered: Image.DrawImage: src must be different from the receiver")