diff --git a/graphicscontext.go b/graphicscontext.go index 8af0bafe5..0e3d8ff33 100644 --- a/graphicscontext.go +++ b/graphicscontext.go @@ -75,18 +75,6 @@ func (c *graphicsContext) initializeIfNeeded() error { return nil } -func drawWithFittingScale(dst *Image, src *Image, filter Filter) { - wd, hd := dst.Size() - ws, hs := src.Size() - sw := float64(wd) / float64(ws) - sh := float64(hd) / float64(hs) - op := &DrawImageOptions{} - op.GeoM.Scale(sw, sh) - op.CompositeMode = CompositeModeCopy - op.Filter = filter - _ = dst.DrawImage(src, op) -} - func (c *graphicsContext) Update(afterFrameUpdate func()) error { updateCount := clock.Update() @@ -105,7 +93,7 @@ func (c *graphicsContext) Update(afterFrameUpdate func()) error { afterFrameUpdate() } if 0 < updateCount { - drawWithFittingScale(c.screen, c.offscreen, filterScreen) + drawWithFittingScale(c.screen, c.offscreen, nil, filterScreen) } if err := restorable.ResolveStaleImages(); err != nil { diff --git a/image.go b/image.go index 6dcc8016b..4fd13568d 100644 --- a/image.go +++ b/image.go @@ -26,6 +26,36 @@ import ( "github.com/hajimehoshi/ebiten/internal/restorable" ) +// emptyImage is an empty image used for filling other images with a uniform color. +// +// Do not call Fill or Clear on emptyImage or the program causes infinite recursion. +var emptyImage *Image + +func init() { + const ( + w = 16 + h = 16 + ) + emptyImage = newImageWithoutInit(w, h) + pix := make([]uint8, w*h*4) + _ = emptyImage.ReplacePixels(pix) +} + +func drawWithFittingScale(dst *Image, src *Image, colorM *ColorM, filter Filter) { + wd, hd := dst.Size() + ws, hs := src.Size() + sw := float64(wd) / float64(ws) + sh := float64(hd) / float64(hs) + op := &DrawImageOptions{} + op.GeoM.Scale(sw, sh) + if colorM != nil { + op.ColorM = *colorM + } + op.CompositeMode = CompositeModeCopy + op.Filter = filter + _ = dst.DrawImage(src, op) +} + // Image represents a rectangle set of pixels. // The pixel format is alpha-premultiplied RGBA. // Image implements image.Image. @@ -47,7 +77,7 @@ func (i *Image) Size() (width, height int) { // // Clear always returns nil as of 1.5.0-alpha. func (i *Image) Clear() error { - i.restorable.Fill(0, 0, 0, 0) + i.fill(0, 0, 0, 0) return nil } @@ -58,10 +88,23 @@ func (i *Image) Clear() error { // Fill always returns nil as of 1.5.0-alpha. func (i *Image) Fill(clr color.Color) error { r, g, b, a := clr.RGBA() - i.restorable.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 } +func (i *Image) fill(r, g, b, a uint8) { + var c *ColorM + if a > 0 { + c = &ColorM{} + rf := float64(r) / float64(a) + gf := float64(g) / float64(a) + bf := float64(b) / float64(a) + af := float64(a) / 0xff + c.Translate(rf, gf, bf, af) + } + drawWithFittingScale(i, emptyImage, c, FilterNearest) +} + // DrawImage draws the given image on the image i. // // DrawImage accepts the options. For details, see the document of DrawImageOptions. @@ -283,12 +326,21 @@ type DrawImageOptions struct { func NewImage(width, height int, filter Filter) (*Image, error) { checkSize(width, height) r := restorable.NewImage(width, height, false) - r.Fill(0, 0, 0, 0) i := &Image{r, filter} + i.fill(0, 0, 0, 0) runtime.SetFinalizer(i, (*Image).Dispose) return i, nil } +// newImageWithoutInit creates an empty image without initialization. +func newImageWithoutInit(width, height int) *Image { + checkSize(width, height) + r := restorable.NewImage(width, height, false) + i := &Image{r, FilterDefault} + runtime.SetFinalizer(i, (*Image).Dispose) + return i +} + // newVolatileImage returns an empty 'volatile' image. // A volatile image is always cleared at the start of a frame. // @@ -307,8 +359,8 @@ func NewImage(width, height int, filter Filter) (*Image, error) { func newVolatileImage(width, height int, filter Filter) *Image { checkSize(width, height) r := restorable.NewImage(width, height, true) - r.Fill(0, 0, 0, 0) i := &Image{r, filter} + i.fill(0, 0, 0, 0) runtime.SetFinalizer(i, (*Image).Dispose) return i }