diff --git a/graphics.go b/graphics.go index 86a85c3b7..cd4ae47ae 100644 --- a/graphics.go +++ b/graphics.go @@ -19,10 +19,13 @@ import ( "github.com/hajimehoshi/ebiten/internal/opengl" ) -// Filter represents the type of filter to be used when an image is maginified or minified. +// Filter represents the type of texture filter to be used when an image is maginified or minified. type Filter int const ( + // FilterDefault represents the defualt filter. + FilterDefault Filter = Filter(graphics.FilterDefault) + // FilterNearest represents nearest (crisp-edged) filter FilterNearest Filter = Filter(graphics.FilterNearest) diff --git a/image.go b/image.go index 7633b55f4..979b9120a 100644 --- a/image.go +++ b/image.go @@ -33,6 +33,7 @@ import ( // Functions of Image never returns error as of 1.5.0-alpha, and error values are always nil. type Image struct { restorable *restorable.Image + filter Filter } // Size returns the size of the image. @@ -82,6 +83,7 @@ func (i *Image) Fill(clr color.Color) error { // * All render sources are same (B in A.DrawImage(B, op)) // * All ColorM values are same // * All CompositeMode values are same +// * All Filter values are same // // For more performance tips, see https://github.com/hajimehoshi/ebiten/wiki/Performance-Tips. // @@ -144,7 +146,15 @@ func (i *Image) DrawImage(img *Image, options *DrawImageOptions) error { return nil } mode := opengl.CompositeMode(options.CompositeMode) - i.restorable.DrawImage(img.restorable, vs, &options.ColorM.impl, mode) + + filter := graphics.FilterNearest + if options.Filter != FilterDefault { + filter = graphics.Filter(options.Filter) + } else if img.filter != FilterDefault { + filter = graphics.Filter(img.filter) + } + + i.restorable.DrawImage(img.restorable, vs, &options.ColorM.impl, mode, filter) return nil } @@ -243,6 +253,15 @@ type DrawImageOptions struct { // The default (zero) value is regular alpha blending. CompositeMode CompositeMode + // Filter is a type of texture filter. + // The default (zero) value is FilterDefault. + // + // If both Filter specified at NewImage* and DrawImageOptions are FilterDefault, + // FilterNearest is used. + // If either is FilterDefault and the other is not, the latter is used. + // Otherwise, Filter specified at DrawImageOptions is used. + Filter Filter + // Deprecated (as of 1.5.0-alpha): Use SourceRect instead. ImageParts ImageParts @@ -254,12 +273,15 @@ type DrawImageOptions struct { // // If width or height is less than 1 or more than MaxImageSize, NewImage panics. // +// filter argument is just for backward compatibility. +// If you are not sure, specify FilterDefault. +// // Error returned by NewImage is always nil as of 1.5.0-alpha. func NewImage(width, height int, filter Filter) (*Image, error) { checkSize(width, height) - r := restorable.NewImage(width, height, graphics.Filter(filter), false) + r := restorable.NewImage(width, height, false) r.Fill(0, 0, 0, 0) - i := &Image{r} + i := &Image{r, filter} runtime.SetFinalizer(i, (*Image).Dispose) return i, nil } @@ -281,9 +303,9 @@ func NewImage(width, height int, filter Filter) (*Image, error) { // Error returned by newVolatileImage is always nil as of 1.5.0-alpha. func newVolatileImage(width, height int, filter Filter) *Image { checkSize(width, height) - r := restorable.NewImage(width, height, graphics.Filter(filter), true) + r := restorable.NewImage(width, height, true) r.Fill(0, 0, 0, 0) - i := &Image{r} + i := &Image{r, filter} runtime.SetFinalizer(i, (*Image).Dispose) return i } @@ -292,12 +314,15 @@ func newVolatileImage(width, height int, filter Filter) *Image { // // If source's width or height is less than 1 or more than MaxImageSize, NewImageFromImage panics. // +// filter argument is just for backward compatibility. +// If you are not sure, specify FilterDefault. +// // Error returned by NewImageFromImage is always nil as of 1.5.0-alpha. func NewImageFromImage(source image.Image, filter Filter) (*Image, error) { size := source.Bounds().Size() checkSize(size.X, size.Y) - r := restorable.NewImageFromImage(source, graphics.Filter(filter)) - i := &Image{r} + r := restorable.NewImageFromImage(source) + i := &Image{r, filter} runtime.SetFinalizer(i, (*Image).Dispose) return i, nil } @@ -305,7 +330,7 @@ func NewImageFromImage(source image.Image, filter Filter) (*Image, error) { func newImageWithScreenFramebuffer(width, height int, offsetX, offsetY float64) *Image { checkSize(width, height) r := restorable.NewScreenFramebufferImage(width, height, offsetX, offsetY) - i := &Image{r} + i := &Image{r, FilterDefault} runtime.SetFinalizer(i, (*Image).Dispose) return i } diff --git a/internal/graphics/command.go b/internal/graphics/command.go index e04c447f6..507682bf6 100644 --- a/internal/graphics/command.go +++ b/internal/graphics/command.go @@ -71,13 +71,13 @@ func (q *commandQueue) appendVertices(vertices []float32) { } // EnqueueDrawImageCommand enqueues a drawing-image command. -func (q *commandQueue) EnqueueDrawImageCommand(dst, src *Image, vertices []float32, clr *affine.ColorM, mode opengl.CompositeMode) { +func (q *commandQueue) EnqueueDrawImageCommand(dst, src *Image, vertices []float32, clr *affine.ColorM, mode opengl.CompositeMode, filter Filter) { // Avoid defer for performance q.m.Lock() q.appendVertices(vertices) if 0 < len(q.commands) { if c, ok := q.commands[len(q.commands)-1].(*drawImageCommand); ok { - if c.canMerge(dst, src, clr, mode) { + if c.canMerge(dst, src, clr, mode, filter) { c.verticesNum += len(vertices) q.m.Unlock() return @@ -90,6 +90,7 @@ func (q *commandQueue) EnqueueDrawImageCommand(dst, src *Image, vertices []float verticesNum: len(vertices), color: *clr, mode: mode, + filter: filter, } q.commands = append(q.commands, c) q.m.Unlock() @@ -225,6 +226,7 @@ type drawImageCommand struct { verticesNum int color affine.ColorM mode opengl.CompositeMode + filter Filter } // QuadVertexSizeInBytes returns the size in bytes of vertices for a quadrangle. @@ -251,7 +253,7 @@ func (c *drawImageCommand) Exec(indexOffsetInBytes int) error { sh = emath.NextPowerOf2Int(sh) _, dh := c.dst.Size() proj := f.projectionMatrix(dh) - theOpenGLState.useProgram(proj, c.src.texture.native, sw, sh, c.color, c.src.texture.filter) + theOpenGLState.useProgram(proj, c.src.texture.native, sw, sh, c.color, c.filter) // TODO: We should call glBindBuffer here? // The buffer is already bound at begin() but it is counterintuitive. opengl.GetContext().DrawElements(opengl.Triangles, 6*n, indexOffsetInBytes) @@ -279,7 +281,7 @@ func (c *drawImageCommand) split(quadsNum int) [2]*drawImageCommand { // canMerge returns a boolean value indicating whether the other drawImageCommand can be merged // with the drawImageCommand c. -func (c *drawImageCommand) canMerge(dst, src *Image, clr *affine.ColorM, mode opengl.CompositeMode) bool { +func (c *drawImageCommand) canMerge(dst, src *Image, clr *affine.ColorM, mode opengl.CompositeMode, filter Filter) bool { if c.dst != dst { return false } @@ -292,6 +294,9 @@ func (c *drawImageCommand) canMerge(dst, src *Image, clr *affine.ColorM, mode op if c.mode != mode { return false } + if c.filter != filter { + return false + } return true } @@ -351,7 +356,6 @@ func (c *disposeCommand) Exec(indexOffsetInBytes int) error { type newImageFromImageCommand struct { result *Image img *image.RGBA - filter Filter } // Exec executes the newImageFromImageCommand. @@ -373,7 +377,6 @@ func (c *newImageFromImageCommand) Exec(indexOffsetInBytes int) error { } c.result.texture = &texture{ native: native, - filter: c.filter, } return nil } @@ -383,7 +386,6 @@ type newImageCommand struct { result *Image width int height int - filter Filter } // Exec executes a newImageCommand. @@ -402,7 +404,6 @@ func (c *newImageCommand) Exec(indexOffsetInBytes int) error { } c.result.texture = &texture{ native: native, - filter: c.filter, } return nil } diff --git a/internal/graphics/image.go b/internal/graphics/image.go index ccb68d4ba..b4e90f1ac 100644 --- a/internal/graphics/image.go +++ b/internal/graphics/image.go @@ -33,7 +33,7 @@ type Image struct { // MaxImageSize is the maximum of width/height of an image. const MaxImageSize = defaultViewportSize -func NewImage(width, height int, filter Filter) *Image { +func NewImage(width, height int) *Image { i := &Image{ width: width, height: height, @@ -42,13 +42,12 @@ func NewImage(width, height int, filter Filter) *Image { result: i, width: width, height: height, - filter: filter, } theCommandQueue.Enqueue(c) return i } -func NewImageFromImage(img *image.RGBA, width, height int, filter Filter) *Image { +func NewImageFromImage(img *image.RGBA, width, height int) *Image { i := &Image{ width: width, height: height, @@ -56,7 +55,6 @@ func NewImageFromImage(img *image.RGBA, width, height int, filter Filter) *Image c := &newImageFromImageCommand{ result: i, img: img, - filter: filter, } theCommandQueue.Enqueue(c) return i @@ -100,8 +98,8 @@ func (i *Image) Fill(r, g, b, a uint8) { theCommandQueue.Enqueue(c) } -func (i *Image) DrawImage(src *Image, vertices []float32, clr *affine.ColorM, mode opengl.CompositeMode) { - theCommandQueue.EnqueueDrawImageCommand(i, src, vertices, clr, mode) +func (i *Image) DrawImage(src *Image, vertices []float32, clr *affine.ColorM, mode opengl.CompositeMode, filter Filter) { + theCommandQueue.EnqueueDrawImageCommand(i, src, vertices, clr, mode, filter) } func (i *Image) Pixels() ([]byte, error) { diff --git a/internal/graphics/texture.go b/internal/graphics/texture.go index b52146f32..0e2197827 100644 --- a/internal/graphics/texture.go +++ b/internal/graphics/texture.go @@ -21,7 +21,7 @@ import ( type Filter int const ( - FilterNone Filter = iota + FilterDefault Filter = iota FilterNearest FilterLinear ) @@ -29,5 +29,4 @@ const ( // texture represents OpenGL's texture. type texture struct { native opengl.Texture - filter Filter } diff --git a/internal/restorable/image.go b/internal/restorable/image.go index fbf571490..4e73a26af 100644 --- a/internal/restorable/image.go +++ b/internal/restorable/image.go @@ -40,11 +40,12 @@ type drawImageHistoryItem struct { vertices []float32 colorm affine.ColorM mode opengl.CompositeMode + filter graphics.Filter } // canMerge returns a boolean value indicating whether the drawImageHistoryItem d // can be merged with the given conditions. -func (d *drawImageHistoryItem) canMerge(image *Image, colorm *affine.ColorM, mode opengl.CompositeMode) bool { +func (d *drawImageHistoryItem) canMerge(image *Image, colorm *affine.ColorM, mode opengl.CompositeMode, filter graphics.Filter) bool { if d.image != image { return false } @@ -54,13 +55,15 @@ func (d *drawImageHistoryItem) canMerge(image *Image, colorm *affine.ColorM, mod if d.mode != mode { return false } + if d.filter != filter { + return false + } return true } // Image represents an image that can be restored when GL context is lost. type Image struct { - image *graphics.Image - filter graphics.Filter + image *graphics.Image // baseImage and baseColor are exclusive. basePixels []byte @@ -83,11 +86,10 @@ type Image struct { offsetY float64 } -// NewImage creates an empty image with the given size and filter. -func NewImage(width, height int, filter graphics.Filter, volatile bool) *Image { +// NewImage creates an empty image with the given size. +func NewImage(width, height int, volatile bool) *Image { i := &Image{ - image: graphics.NewImage(width, height, filter), - filter: filter, + image: graphics.NewImage(width, height), volatile: volatile, } theImages.add(i) @@ -96,7 +98,7 @@ func NewImage(width, height int, filter graphics.Filter, volatile bool) *Image { } // NewImageFromImage creates an image with source image. -func NewImageFromImage(source image.Image, filter graphics.Filter) *Image { +func NewImageFromImage(source image.Image) *Image { size := source.Bounds().Size() width, height := size.X, size.Y rgbaImg := CopyImage(source) @@ -106,9 +108,8 @@ func NewImageFromImage(source image.Image, filter graphics.Filter) *Image { copy(p[j*w2*4:(j+1)*w2*4], rgbaImg.Pix[j*rgbaImg.Stride:]) } i := &Image{ - image: graphics.NewImageFromImage(rgbaImg, width, height, filter), + image: graphics.NewImageFromImage(rgbaImg, width, height), basePixels: p, - filter: filter, } theImages.add(i) runtime.SetFinalizer(i, (*Image).Dispose) @@ -183,24 +184,24 @@ func (i *Image) ReplacePixels(pixels []byte) { } // DrawImage draws a given image img to the image. -func (i *Image) DrawImage(img *Image, vertices []float32, colorm *affine.ColorM, mode opengl.CompositeMode) { +func (i *Image) DrawImage(img *Image, vertices []float32, colorm *affine.ColorM, mode opengl.CompositeMode, filter graphics.Filter) { theImages.makeStaleIfDependingOn(i) if img.stale || img.volatile || !IsRestoringEnabled() { i.makeStale() } else { - i.appendDrawImageHistory(img, vertices, colorm, mode) + i.appendDrawImageHistory(img, vertices, colorm, mode, filter) } - i.image.DrawImage(img.image, vertices, colorm, mode) + i.image.DrawImage(img.image, vertices, colorm, mode, filter) } // appendDrawImageHistory appends a draw-image history item to the image. -func (i *Image) appendDrawImageHistory(image *Image, vertices []float32, colorm *affine.ColorM, mode opengl.CompositeMode) { +func (i *Image) appendDrawImageHistory(image *Image, vertices []float32, colorm *affine.ColorM, mode opengl.CompositeMode, filter graphics.Filter) { if i.stale || i.volatile { return } if len(i.drawImageHistory) > 0 { last := i.drawImageHistory[len(i.drawImageHistory)-1] - if last.canMerge(image, colorm, mode) { + if last.canMerge(image, colorm, mode, filter) { last.vertices = append(last.vertices, vertices...) return } @@ -217,6 +218,7 @@ func (i *Image) appendDrawImageHistory(image *Image, vertices []float32, colorm vertices: vertices, colorm: *colorm, mode: mode, + filter: filter, } i.drawImageHistory = append(i.drawImageHistory, item) } @@ -318,7 +320,7 @@ func (i *Image) restore() error { return nil } if i.volatile { - i.image = graphics.NewImage(w, h, i.filter) + i.image = graphics.NewImage(w, h) i.basePixels = nil i.baseColor = color.RGBA{} i.drawImageHistory = nil @@ -336,7 +338,7 @@ func (i *Image) restore() error { copy(img.Pix[j*img.Stride:], i.basePixels[j*w2*4:(j+1)*w2*4]) } } - gimg := graphics.NewImageFromImage(img, w, h, i.filter) + gimg := graphics.NewImageFromImage(img, w, h) if i.baseColor != (color.RGBA{}) { if i.basePixels != nil { panic("not reached") @@ -348,7 +350,7 @@ func (i *Image) restore() error { if c.image.hasDependency() { panic("not reached") } - gimg.DrawImage(c.image.image, c.vertices, &c.colorm, c.mode) + gimg.DrawImage(c.image.image, c.vertices, &c.colorm, c.mode, c.filter) } i.image = gimg diff --git a/internal/restorable/images_test.go b/internal/restorable/images_test.go index 88bc2a6d0..4f6621ede 100644 --- a/internal/restorable/images_test.go +++ b/internal/restorable/images_test.go @@ -67,7 +67,7 @@ func sameColors(c1, c2 color.RGBA, delta int) bool { } func TestRestore(t *testing.T) { - img0 := NewImage(1, 1, graphics.FilterNearest, false) + img0 := NewImage(1, 1, false) // Clear images explicitly. // In this 'restorable' layer, reused texture might not be cleared. img0.Fill(0, 0, 0, 0) @@ -109,7 +109,7 @@ func TestRestoreChain(t *testing.T) { const num = 10 imgs := []*Image{} for i := 0; i < num; i++ { - img := NewImage(1, 1, graphics.FilterNearest, false) + img := NewImage(1, 1, false) img.Fill(0, 0, 0, 0) imgs = append(imgs, img) } @@ -121,7 +121,7 @@ func TestRestoreChain(t *testing.T) { clr := color.RGBA{0x00, 0x00, 0x00, 0xff} imgs[0].Fill(clr.R, clr.G, clr.B, clr.A) for i := 0; i < num-1; i++ { - imgs[i+1].DrawImage(imgs[i], vertices(1, 1, 0, 0), &affine.ColorM{}, opengl.CompositeModeSourceOver) + imgs[i+1].DrawImage(imgs[i], vertices(1, 1, 0, 0), &affine.ColorM{}, opengl.CompositeModeSourceOver, graphics.FilterNearest) } if err := ResolveStaleImages(); err != nil { t.Fatal(err) @@ -139,13 +139,13 @@ func TestRestoreChain(t *testing.T) { } func TestRestoreOverrideSource(t *testing.T) { - img0 := NewImage(1, 1, graphics.FilterNearest, false) + img0 := NewImage(1, 1, false) img0.Fill(0, 0, 0, 0) - img1 := NewImage(1, 1, graphics.FilterNearest, false) + img1 := NewImage(1, 1, false) img1.Fill(0, 0, 0, 0) - img2 := NewImage(1, 1, graphics.FilterNearest, false) + img2 := NewImage(1, 1, false) img2.Fill(0, 0, 0, 0) - img3 := NewImage(1, 1, graphics.FilterNearest, false) + img3 := NewImage(1, 1, false) img3.Fill(0, 0, 0, 0) defer func() { img3.Dispose() @@ -156,10 +156,10 @@ func TestRestoreOverrideSource(t *testing.T) { clr0 := color.RGBA{0x00, 0x00, 0x00, 0xff} clr1 := color.RGBA{0x00, 0x00, 0x01, 0xff} img1.Fill(clr0.R, clr0.G, clr0.B, clr0.A) - img2.DrawImage(img1, vertices(1, 1, 0, 0), &affine.ColorM{}, opengl.CompositeModeSourceOver) - img3.DrawImage(img2, vertices(1, 1, 0, 0), &affine.ColorM{}, opengl.CompositeModeSourceOver) + img2.DrawImage(img1, vertices(1, 1, 0, 0), &affine.ColorM{}, opengl.CompositeModeSourceOver, graphics.FilterNearest) + img3.DrawImage(img2, vertices(1, 1, 0, 0), &affine.ColorM{}, opengl.CompositeModeSourceOver, graphics.FilterNearest) img0.Fill(clr1.R, clr1.G, clr1.B, clr1.A) - img1.DrawImage(img0, vertices(1, 1, 0, 0), &affine.ColorM{}, opengl.CompositeModeSourceOver) + img1.DrawImage(img0, vertices(1, 1, 0, 0), &affine.ColorM{}, opengl.CompositeModeSourceOver, graphics.FilterNearest) if err := ResolveStaleImages(); err != nil { t.Fatal(err) } @@ -214,18 +214,18 @@ func TestRestoreComplexGraph(t *testing.T) { base.Pix[1] = 0xff base.Pix[2] = 0xff base.Pix[3] = 0xff - img0 := NewImageFromImage(base, graphics.FilterNearest) - img1 := NewImageFromImage(base, graphics.FilterNearest) - img2 := NewImageFromImage(base, graphics.FilterNearest) - img3 := NewImage(4, 1, graphics.FilterNearest, false) + img0 := NewImageFromImage(base) + img1 := NewImageFromImage(base) + img2 := NewImageFromImage(base) + img3 := NewImage(4, 1, false) img3.Fill(0, 0, 0, 0) - img4 := NewImage(4, 1, graphics.FilterNearest, false) + img4 := NewImage(4, 1, false) img4.Fill(0, 0, 0, 0) - img5 := NewImage(4, 1, graphics.FilterNearest, false) + img5 := NewImage(4, 1, false) img5.Fill(0, 0, 0, 0) - img6 := NewImage(4, 1, graphics.FilterNearest, false) + img6 := NewImage(4, 1, false) img6.Fill(0, 0, 0, 0) - img7 := NewImage(4, 1, graphics.FilterNearest, false) + img7 := NewImage(4, 1, false) img7.Fill(0, 0, 0, 0) defer func() { img7.Dispose() @@ -237,15 +237,15 @@ func TestRestoreComplexGraph(t *testing.T) { img1.Dispose() img0.Dispose() }() - img3.DrawImage(img0, vertices(4, 1, 0, 0), &affine.ColorM{}, opengl.CompositeModeSourceOver) - img3.DrawImage(img1, vertices(4, 1, 1, 0), &affine.ColorM{}, opengl.CompositeModeSourceOver) - img4.DrawImage(img1, vertices(4, 1, 1, 0), &affine.ColorM{}, opengl.CompositeModeSourceOver) - img4.DrawImage(img2, vertices(4, 1, 2, 0), &affine.ColorM{}, opengl.CompositeModeSourceOver) - img5.DrawImage(img3, vertices(4, 1, 0, 0), &affine.ColorM{}, opengl.CompositeModeSourceOver) - img6.DrawImage(img3, vertices(4, 1, 0, 0), &affine.ColorM{}, opengl.CompositeModeSourceOver) - img6.DrawImage(img4, vertices(4, 1, 1, 0), &affine.ColorM{}, opengl.CompositeModeSourceOver) - img7.DrawImage(img2, vertices(4, 1, 0, 0), &affine.ColorM{}, opengl.CompositeModeSourceOver) - img7.DrawImage(img3, vertices(4, 1, 2, 0), &affine.ColorM{}, opengl.CompositeModeSourceOver) + img3.DrawImage(img0, vertices(4, 1, 0, 0), &affine.ColorM{}, opengl.CompositeModeSourceOver, graphics.FilterNearest) + img3.DrawImage(img1, vertices(4, 1, 1, 0), &affine.ColorM{}, opengl.CompositeModeSourceOver, graphics.FilterNearest) + img4.DrawImage(img1, vertices(4, 1, 1, 0), &affine.ColorM{}, opengl.CompositeModeSourceOver, graphics.FilterNearest) + img4.DrawImage(img2, vertices(4, 1, 2, 0), &affine.ColorM{}, opengl.CompositeModeSourceOver, graphics.FilterNearest) + img5.DrawImage(img3, vertices(4, 1, 0, 0), &affine.ColorM{}, opengl.CompositeModeSourceOver, graphics.FilterNearest) + img6.DrawImage(img3, vertices(4, 1, 0, 0), &affine.ColorM{}, opengl.CompositeModeSourceOver, graphics.FilterNearest) + img6.DrawImage(img4, vertices(4, 1, 1, 0), &affine.ColorM{}, opengl.CompositeModeSourceOver, graphics.FilterNearest) + img7.DrawImage(img2, vertices(4, 1, 0, 0), &affine.ColorM{}, opengl.CompositeModeSourceOver, graphics.FilterNearest) + img7.DrawImage(img3, vertices(4, 1, 2, 0), &affine.ColorM{}, opengl.CompositeModeSourceOver, graphics.FilterNearest) if err := ResolveStaleImages(); err != nil { t.Fatal(err) } @@ -318,15 +318,15 @@ func TestRestoreRecursive(t *testing.T) { base.Pix[1] = 0xff base.Pix[2] = 0xff base.Pix[3] = 0xff - img0 := NewImageFromImage(base, graphics.FilterNearest) - img1 := NewImage(4, 1, graphics.FilterNearest, false) + img0 := NewImageFromImage(base) + img1 := NewImage(4, 1, false) img1.Fill(0, 0, 0, 0) defer func() { img1.Dispose() img0.Dispose() }() - img1.DrawImage(img0, vertices(4, 1, 1, 0), &affine.ColorM{}, opengl.CompositeModeSourceOver) - img0.DrawImage(img1, vertices(4, 1, 1, 0), &affine.ColorM{}, opengl.CompositeModeSourceOver) + img1.DrawImage(img0, vertices(4, 1, 1, 0), &affine.ColorM{}, opengl.CompositeModeSourceOver, graphics.FilterNearest) + img0.DrawImage(img1, vertices(4, 1, 1, 0), &affine.ColorM{}, opengl.CompositeModeSourceOver, graphics.FilterNearest) if err := ResolveStaleImages(); err != nil { t.Fatal(err) }