twitchapon-anim

Basic Twitchapon Receiver/Visuals
git clone git://bsandro.tech/twitchapon-anim
Log | Files | Refs | README | LICENSE

image.go (19197B)


      1 // Copyright 2016 The Ebiten Authors
      2 //
      3 // Licensed under the Apache License, Version 2.0 (the "License");
      4 // you may not use this file except in compliance with the License.
      5 // You may obtain a copy of the License at
      6 //
      7 //     http://www.apache.org/licenses/LICENSE-2.0
      8 //
      9 // Unless required by applicable law or agreed to in writing, software
     10 // distributed under the License is distributed on an "AS IS" BASIS,
     11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     12 // See the License for the specific language governing permissions and
     13 // limitations under the License.
     14 
     15 package restorable
     16 
     17 import (
     18 	"fmt"
     19 	"image/color"
     20 
     21 	"github.com/hajimehoshi/ebiten/v2/internal/affine"
     22 	"github.com/hajimehoshi/ebiten/v2/internal/driver"
     23 	"github.com/hajimehoshi/ebiten/v2/internal/graphics"
     24 	"github.com/hajimehoshi/ebiten/v2/internal/graphicscommand"
     25 )
     26 
     27 type Pixels struct {
     28 	baseColor    color.RGBA
     29 	rectToPixels *rectToPixels
     30 }
     31 
     32 // Apply applies the Pixels state to the given image especially for restoring.
     33 func (p *Pixels) Apply(img *graphicscommand.Image) {
     34 	// Pixels doesn't clear the image. This is a caller's responsibility.
     35 	if p.baseColor != (color.RGBA{}) {
     36 		fillImage(img, p.baseColor)
     37 	}
     38 
     39 	if p.rectToPixels == nil {
     40 		return
     41 	}
     42 	p.rectToPixels.apply(img)
     43 }
     44 
     45 func (p *Pixels) AddOrReplace(pix []byte, x, y, width, height int) {
     46 	if p.rectToPixels == nil {
     47 		p.rectToPixels = &rectToPixels{}
     48 	}
     49 	p.rectToPixels.addOrReplace(pix, x, y, width, height)
     50 }
     51 
     52 func (p *Pixels) Remove(x, y, width, height int) {
     53 	// Note that we don't care whether the region is actually removed or not here. There is an actual case that
     54 	// the region is allocated but nothing is rendered. See TestDisposeImmediately at shareable package.
     55 	if p.rectToPixels == nil {
     56 		return
     57 	}
     58 	p.rectToPixels.remove(x, y, width, height)
     59 }
     60 
     61 func (p *Pixels) At(i, j int) (byte, byte, byte, byte) {
     62 	if p.rectToPixels != nil {
     63 		if r, g, b, a, ok := p.rectToPixels.at(i, j); ok {
     64 			return r, g, b, a
     65 		}
     66 	}
     67 	return p.baseColor.R, p.baseColor.G, p.baseColor.B, p.baseColor.A
     68 }
     69 
     70 // drawTrianglesHistoryItem is an item for history of draw-image commands.
     71 type drawTrianglesHistoryItem struct {
     72 	images       [graphics.ShaderImageNum]*Image
     73 	offsets      [graphics.ShaderImageNum - 1][2]float32
     74 	vertices     []float32
     75 	indices      []uint16
     76 	colorm       *affine.ColorM
     77 	mode         driver.CompositeMode
     78 	filter       driver.Filter
     79 	address      driver.Address
     80 	sourceRegion driver.Region
     81 	shader       *Shader
     82 	uniforms     []interface{}
     83 }
     84 
     85 // Image represents an image that can be restored when GL context is lost.
     86 type Image struct {
     87 	image *graphicscommand.Image
     88 
     89 	width  int
     90 	height int
     91 
     92 	basePixels Pixels
     93 
     94 	// drawTrianglesHistory is a set of draw-image commands.
     95 	// TODO: This should be merged with the similar command queue in package graphics (#433).
     96 	drawTrianglesHistory []*drawTrianglesHistoryItem
     97 
     98 	// stale indicates whether the image needs to be synced with GPU as soon as possible.
     99 	stale bool
    100 
    101 	// volatile indicates whether the image is cleared whenever a frame starts.
    102 	volatile bool
    103 
    104 	// screen indicates whether the image is used as an actual screen.
    105 	screen bool
    106 
    107 	// priority indicates whether the image is restored in high priority when context-lost happens.
    108 	priority bool
    109 }
    110 
    111 var emptyImage *Image
    112 
    113 func init() {
    114 	// Use a big-enough image as an rendering source. By enlarging with x128, this can reach to 16384.
    115 	// See #907 for details.
    116 	const w, h = 128, 128
    117 	emptyImage = &Image{
    118 		image:    graphicscommand.NewImage(w, h),
    119 		width:    w,
    120 		height:   h,
    121 		priority: true,
    122 	}
    123 	pix := make([]byte, 4*w*h)
    124 	for i := range pix {
    125 		pix[i] = 0xff
    126 	}
    127 
    128 	// As emptyImage is the source at clearImage, initialize this with ReplacePixels, not clearImage.
    129 	// This operation is also important when restoring emptyImage.
    130 	emptyImage.ReplacePixels(pix, 0, 0, w, h)
    131 	theImages.add(emptyImage)
    132 }
    133 
    134 // NewImage creates an empty image with the given size.
    135 //
    136 // The returned image is cleared.
    137 //
    138 // Note that Dispose is not called automatically.
    139 func NewImage(width, height int) *Image {
    140 	i := &Image{
    141 		image:  graphicscommand.NewImage(width, height),
    142 		width:  width,
    143 		height: height,
    144 	}
    145 	fillImage(i.image, color.RGBA{})
    146 	theImages.add(i)
    147 	return i
    148 }
    149 
    150 // SetVolatile sets the volatile state of the image.
    151 //
    152 // Regular non-volatile images need to record drawing history or read its pixels from GPU if necessary so that all
    153 // the images can be restored automatically from the context lost. However, such recording the drawing history or
    154 // reading pixels from GPU are expensive operations. Volatile images can skip such oprations, but the image content
    155 // is cleared every frame instead.
    156 func (i *Image) SetVolatile(volatile bool) {
    157 	changed := i.volatile != volatile
    158 	i.volatile = volatile
    159 	if changed {
    160 		i.makeStale()
    161 	}
    162 }
    163 
    164 // Extend extends the image by the given size.
    165 // Extend creates a new image with the given size and copies the pixels of the given source image.
    166 // Extend disposes itself after its call.
    167 //
    168 // If the given size (width and height) is smaller than the source image, ExtendImage panics.
    169 //
    170 // The image must be ReplacePixels-only image. Extend panics when Fill or DrawTriangles are applied on the image.
    171 //
    172 // Extend panics when the image is stale.
    173 func (i *Image) Extend(width, height int) *Image {
    174 	if i.width > width || i.height > height {
    175 		panic(fmt.Sprintf("restorable: the original size (%d, %d) cannot be extended to (%d, %d)", i.width, i.height, width, height))
    176 	}
    177 
    178 	if i.stale {
    179 		panic("restorable: Extend at a stale image is forbidden")
    180 	}
    181 
    182 	if len(i.drawTrianglesHistory) > 0 {
    183 		panic("restorable: Extend after DrawTriangles is forbidden")
    184 	}
    185 
    186 	newImg := NewImage(width, height)
    187 	newImg.SetVolatile(i.volatile)
    188 	i.basePixels.Apply(newImg.image)
    189 
    190 	if i.basePixels.baseColor != (color.RGBA{}) {
    191 		panic("restorable: baseColor must be empty at Extend")
    192 	}
    193 	newImg.basePixels = i.basePixels
    194 
    195 	i.Dispose()
    196 
    197 	return newImg
    198 }
    199 
    200 // NewScreenFramebufferImage creates a special image that framebuffer is one for the screen.
    201 //
    202 // The returned image is cleared.
    203 //
    204 // Note that Dispose is not called automatically.
    205 func NewScreenFramebufferImage(width, height int) *Image {
    206 	i := &Image{
    207 		image:  graphicscommand.NewScreenFramebufferImage(width, height),
    208 		width:  width,
    209 		height: height,
    210 		screen: true,
    211 	}
    212 	fillImage(i.image, color.RGBA{})
    213 	theImages.add(i)
    214 	return i
    215 }
    216 
    217 // quadVertices returns vertices to render a quad. These values are passed to graphicscommand.Image.
    218 func quadVertices(dx0, dy0, dx1, dy1, sx0, sy0, sx1, sy1, cr, cg, cb, ca float32) []float32 {
    219 	return []float32{
    220 		dx0, dy0, sx0, sy0, cr, cg, cb, ca,
    221 		dx1, dy0, sx1, sy0, cr, cg, cb, ca,
    222 		dx0, dy1, sx0, sy1, cr, cg, cb, ca,
    223 		dx1, dy1, sx1, sy1, cr, cg, cb, ca,
    224 	}
    225 }
    226 
    227 // Fill fills the specified part of the image with a solid color.
    228 func (i *Image) Fill(clr color.RGBA) {
    229 	theImages.makeStaleIfDependingOn(i)
    230 	i.basePixels = Pixels{
    231 		baseColor: clr,
    232 	}
    233 	i.drawTrianglesHistory = nil
    234 	i.stale = false
    235 
    236 	// Do not call i.DrawTriangles as emptyImage is special (#928).
    237 	// baseColor is updated instead.
    238 	fillImage(i.image, i.basePixels.baseColor)
    239 }
    240 
    241 func fillImage(i *graphicscommand.Image, clr color.RGBA) {
    242 	if i == emptyImage.image {
    243 		panic("restorable: fillImage cannot be called on emptyImage")
    244 	}
    245 
    246 	var rf, gf, bf, af float32
    247 	if clr.A > 0 {
    248 		rf = float32(clr.R) / float32(clr.A)
    249 		gf = float32(clr.G) / float32(clr.A)
    250 		bf = float32(clr.B) / float32(clr.A)
    251 		af = float32(clr.A) / 0xff
    252 	}
    253 
    254 	// TODO: Use the previous composite mode if possible.
    255 	compositemode := driver.CompositeModeSourceOver
    256 	switch {
    257 	case af == 0.0:
    258 		compositemode = driver.CompositeModeClear
    259 	case af < 1.0:
    260 		compositemode = driver.CompositeModeCopy
    261 	}
    262 
    263 	// This needs to use 'InternalSize' to render the whole region, or edges are unexpectedly cleared on some
    264 	// devices.
    265 	//
    266 	// TODO: Can we unexport InternalSize()?
    267 	dw, dh := i.InternalSize()
    268 	sw, sh := emptyImage.image.InternalSize()
    269 	// Add 1 pixels for paddings.
    270 	vs := quadVertices(0, 0, float32(dw), float32(dh), 1, 1, float32(sw-1), float32(sh-1), rf, gf, bf, af)
    271 	is := graphics.QuadIndices()
    272 	srcs := [graphics.ShaderImageNum]*graphicscommand.Image{emptyImage.image}
    273 	var offsets [graphics.ShaderImageNum - 1][2]float32
    274 	i.DrawTriangles(srcs, offsets, vs, is, nil, compositemode, driver.FilterNearest, driver.AddressUnsafe, driver.Region{}, nil, nil)
    275 }
    276 
    277 // BasePixelsForTesting returns the image's basePixels for testing.
    278 func (i *Image) BasePixelsForTesting() *Pixels {
    279 	return &i.basePixels
    280 }
    281 
    282 // makeStale makes the image stale.
    283 func (i *Image) makeStale() {
    284 	i.basePixels = Pixels{}
    285 	i.drawTrianglesHistory = nil
    286 	i.stale = true
    287 
    288 	// Don't have to call makeStale recursively here.
    289 	// Restoring is done after topological sorting is done.
    290 	// If an image depends on another stale image, this means that
    291 	// the former image can be restored from the latest state of the latter image.
    292 }
    293 
    294 // ClearPixels clears the specified region by ReplacePixels.
    295 func (i *Image) ClearPixels(x, y, width, height int) {
    296 	i.ReplacePixels(nil, x, y, width, height)
    297 }
    298 
    299 // ReplacePixels replaces the image pixels with the given pixels slice.
    300 //
    301 // ReplacePixels for a part is forbidden if the image is rendered with DrawTriangles or Fill.
    302 func (i *Image) ReplacePixels(pixels []byte, x, y, width, height int) {
    303 	if width <= 0 || height <= 0 {
    304 		panic("restorable: width/height must be positive")
    305 	}
    306 	w, h := i.width, i.height
    307 	if x < 0 || y < 0 || w <= x || h <= y || x+width <= 0 || y+height <= 0 || w < x+width || h < y+height {
    308 		panic(fmt.Sprintf("restorable: out of range x: %d, y: %d, width: %d, height: %d", x, y, width, height))
    309 	}
    310 
    311 	// TODO: Avoid making other images stale if possible. (#514)
    312 	// For this purpuse, images should remember which part of that is used for DrawTriangles.
    313 	theImages.makeStaleIfDependingOn(i)
    314 
    315 	if pixels != nil {
    316 		i.image.ReplacePixels(pixels, x, y, width, height)
    317 	} else {
    318 		// TODO: When pixels == nil, we don't have to care the pixel state there. In such cases, the image
    319 		// accepts only ReplacePixels and not Fill or DrawTriangles.
    320 		// TODO: Separate Image struct into two: images for only-ReplacePixels, and the others.
    321 		i.image.ReplacePixels(make([]byte, 4*width*height), x, y, width, height)
    322 	}
    323 
    324 	if x == 0 && y == 0 && width == w && height == h {
    325 		if pixels != nil {
    326 			i.basePixels.AddOrReplace(pixels, 0, 0, w, h)
    327 		} else {
    328 			i.basePixels.Remove(0, 0, w, h)
    329 		}
    330 		i.drawTrianglesHistory = nil
    331 		i.stale = false
    332 		return
    333 	}
    334 
    335 	// It looked like ReplacePixels on a part of image deletes other region that are rendered by DrawTriangles
    336 	// (#593, #758).
    337 
    338 	if len(i.drawTrianglesHistory) > 0 {
    339 		panic("restorable: ReplacePixels for a part after DrawTriangles is forbidden")
    340 	}
    341 
    342 	if i.stale {
    343 		// TODO: panic here?
    344 		return
    345 	}
    346 
    347 	if pixels != nil {
    348 		i.basePixels.AddOrReplace(pixels, x, y, width, height)
    349 	} else {
    350 		i.basePixels.Remove(x, y, width, height)
    351 	}
    352 }
    353 
    354 // DrawTriangles draws triangles with the given image.
    355 //
    356 // The vertex floats are:
    357 //
    358 //   0: Destination X in pixels
    359 //   1: Destination Y in pixels
    360 //   2: Source X in pixels (not texels!)
    361 //   3: Source Y in pixels
    362 //   4: Color R [0.0-1.0]
    363 //   5: Color G
    364 //   6: Color B
    365 //   7: Color Y
    366 func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, offsets [graphics.ShaderImageNum - 1][2]float32, vertices []float32, indices []uint16, colorm *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, sourceRegion driver.Region, shader *Shader, uniforms []interface{}) {
    367 	if i.priority {
    368 		panic("restorable: DrawTriangles cannot be called on a priority image")
    369 	}
    370 	if len(vertices) == 0 {
    371 		return
    372 	}
    373 	theImages.makeStaleIfDependingOn(i)
    374 
    375 	// TODO: Add tests to confirm this logic.
    376 	var srcstale bool
    377 	for _, src := range srcs {
    378 		if src == nil {
    379 			continue
    380 		}
    381 		if src.stale || src.volatile {
    382 			srcstale = true
    383 			break
    384 		}
    385 	}
    386 
    387 	if srcstale || i.screen || !needsRestoring() || i.volatile {
    388 		i.makeStale()
    389 	} else {
    390 		i.appendDrawTrianglesHistory(srcs, offsets, vertices, indices, colorm, mode, filter, address, sourceRegion, shader, uniforms)
    391 	}
    392 
    393 	var s *graphicscommand.Shader
    394 	var imgs [graphics.ShaderImageNum]*graphicscommand.Image
    395 	if shader == nil {
    396 		// Fast path for rendering without a shader (#1355).
    397 		imgs[0] = srcs[0].image
    398 	} else {
    399 		for i, src := range srcs {
    400 			if src == nil {
    401 				continue
    402 			}
    403 			imgs[i] = src.image
    404 		}
    405 		s = shader.shader
    406 	}
    407 	i.image.DrawTriangles(imgs, offsets, vertices, indices, colorm, mode, filter, address, sourceRegion, s, uniforms)
    408 }
    409 
    410 // appendDrawTrianglesHistory appends a draw-image history item to the image.
    411 func (i *Image) appendDrawTrianglesHistory(srcs [graphics.ShaderImageNum]*Image, offsets [graphics.ShaderImageNum - 1][2]float32, vertices []float32, indices []uint16, colorm *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, sourceRegion driver.Region, shader *Shader, uniforms []interface{}) {
    412 	if i.stale || i.volatile || i.screen {
    413 		return
    414 	}
    415 	// TODO: Would it be possible to merge draw image history items?
    416 	const maxDrawTrianglesHistoryNum = 1024
    417 	if len(i.drawTrianglesHistory)+1 > maxDrawTrianglesHistoryNum {
    418 		i.makeStale()
    419 		return
    420 	}
    421 	// All images must be resolved and not stale each after frame.
    422 	// So we don't have to care if image is stale or not here.
    423 
    424 	vs := make([]float32, len(vertices))
    425 	copy(vs, vertices)
    426 	is := make([]uint16, len(indices))
    427 	copy(is, indices)
    428 
    429 	item := &drawTrianglesHistoryItem{
    430 		images:       srcs,
    431 		offsets:      offsets,
    432 		vertices:     vs,
    433 		indices:      is,
    434 		colorm:       colorm,
    435 		mode:         mode,
    436 		filter:       filter,
    437 		address:      address,
    438 		sourceRegion: sourceRegion,
    439 		shader:       shader,
    440 		uniforms:     uniforms,
    441 	}
    442 	i.drawTrianglesHistory = append(i.drawTrianglesHistory, item)
    443 }
    444 
    445 func (i *Image) readPixelsFromGPUIfNeeded() error {
    446 	if len(i.drawTrianglesHistory) > 0 || i.stale {
    447 		if err := graphicscommand.FlushCommands(); err != nil {
    448 			return err
    449 		}
    450 		if err := i.readPixelsFromGPU(); err != nil {
    451 			return err
    452 		}
    453 		i.drawTrianglesHistory = nil
    454 		i.stale = false
    455 	}
    456 	return nil
    457 }
    458 
    459 // At returns a color value at (x, y).
    460 //
    461 // Note that this must not be called until context is available.
    462 func (i *Image) At(x, y int) (byte, byte, byte, byte, error) {
    463 	if x < 0 || y < 0 || i.width <= x || i.height <= y {
    464 		return 0, 0, 0, 0, nil
    465 	}
    466 
    467 	if err := i.readPixelsFromGPUIfNeeded(); err != nil {
    468 		return 0, 0, 0, 0, err
    469 	}
    470 
    471 	r, g, b, a := i.basePixels.At(x, y)
    472 	return r, g, b, a, nil
    473 }
    474 
    475 // makeStaleIfDependingOn makes the image stale if the image depends on target.
    476 func (i *Image) makeStaleIfDependingOn(target *Image) {
    477 	if i.stale {
    478 		return
    479 	}
    480 	if i.dependsOn(target) {
    481 		i.makeStale()
    482 	}
    483 }
    484 
    485 // makeStaleIfDependingOnShader makes the image stale if the image depends on shader.
    486 func (i *Image) makeStaleIfDependingOnShader(shader *Shader) {
    487 	if i.stale {
    488 		return
    489 	}
    490 	if i.dependsOnShader(shader) {
    491 		i.makeStale()
    492 	}
    493 }
    494 
    495 // readPixelsFromGPU reads the pixels from GPU and resolves the image's 'stale' state.
    496 func (i *Image) readPixelsFromGPU() error {
    497 	pix, err := i.image.Pixels()
    498 	if err != nil {
    499 		return err
    500 	}
    501 	i.basePixels = Pixels{}
    502 	i.basePixels.AddOrReplace(pix, 0, 0, i.width, i.height)
    503 	i.drawTrianglesHistory = nil
    504 	i.stale = false
    505 	return nil
    506 }
    507 
    508 // resolveStale resolves the image's 'stale' state.
    509 func (i *Image) resolveStale() error {
    510 	if !needsRestoring() {
    511 		return nil
    512 	}
    513 
    514 	if i.volatile {
    515 		return nil
    516 	}
    517 	if i.screen {
    518 		return nil
    519 	}
    520 	if !i.stale {
    521 		return nil
    522 	}
    523 	return i.readPixelsFromGPU()
    524 }
    525 
    526 // dependsOn reports whether the image depends on target.
    527 func (i *Image) dependsOn(target *Image) bool {
    528 	for _, c := range i.drawTrianglesHistory {
    529 		for _, img := range c.images {
    530 			if img == nil {
    531 				continue
    532 			}
    533 			if img == target {
    534 				return true
    535 			}
    536 		}
    537 	}
    538 	return false
    539 }
    540 
    541 // dependsOnShader reports whether the image depends on shader.
    542 func (i *Image) dependsOnShader(shader *Shader) bool {
    543 	for _, c := range i.drawTrianglesHistory {
    544 		if c.shader == shader {
    545 			return true
    546 		}
    547 	}
    548 	return false
    549 }
    550 
    551 // dependingImages returns all images that is depended by the image.
    552 func (i *Image) dependingImages() map[*Image]struct{} {
    553 	r := map[*Image]struct{}{}
    554 	for _, c := range i.drawTrianglesHistory {
    555 		for _, img := range c.images {
    556 			if img == nil {
    557 				continue
    558 			}
    559 			r[img] = struct{}{}
    560 		}
    561 	}
    562 	return r
    563 }
    564 
    565 // hasDependency returns a boolean value indicating whether the image depends on another image.
    566 func (i *Image) hasDependency() bool {
    567 	if i.stale {
    568 		return false
    569 	}
    570 	return len(i.drawTrianglesHistory) > 0
    571 }
    572 
    573 // Restore restores *graphicscommand.Image from the pixels using its state.
    574 func (i *Image) restore() error {
    575 	w, h := i.width, i.height
    576 	// Do not dispose the image here. The image should be already disposed.
    577 
    578 	if i.screen {
    579 		// The screen image should also be recreated because framebuffer might
    580 		// be changed.
    581 		i.image = graphicscommand.NewScreenFramebufferImage(w, h)
    582 		i.basePixels = Pixels{}
    583 		i.drawTrianglesHistory = nil
    584 		i.stale = false
    585 		return nil
    586 	}
    587 	if i.volatile {
    588 		i.image = graphicscommand.NewImage(w, h)
    589 		fillImage(i.image, color.RGBA{})
    590 		return nil
    591 	}
    592 	if i.stale {
    593 		panic("restorable: pixels must not be stale when restoring")
    594 	}
    595 
    596 	gimg := graphicscommand.NewImage(w, h)
    597 	// Clear the image explicitly.
    598 	if i != emptyImage {
    599 		// As fillImage uses emptyImage, fillImage cannot be called on emptyImage.
    600 		// It is OK to skip this since emptyImage has its entire pixel information.
    601 		fillImage(gimg, color.RGBA{})
    602 	}
    603 	i.basePixels.Apply(gimg)
    604 
    605 	for _, c := range i.drawTrianglesHistory {
    606 		var s *graphicscommand.Shader
    607 		if c.shader != nil {
    608 			s = c.shader.shader
    609 		}
    610 
    611 		var imgs [graphics.ShaderImageNum]*graphicscommand.Image
    612 		for i, img := range c.images {
    613 			if img == nil {
    614 				continue
    615 			}
    616 			if img.hasDependency() {
    617 				panic("restorable: all dependencies must be already resolved but not")
    618 			}
    619 			imgs[i] = img.image
    620 		}
    621 		gimg.DrawTriangles(imgs, c.offsets, c.vertices, c.indices, c.colorm, c.mode, c.filter, c.address, c.sourceRegion, s, c.uniforms)
    622 	}
    623 
    624 	if len(i.drawTrianglesHistory) > 0 {
    625 		i.basePixels = Pixels{}
    626 		pix, err := gimg.Pixels()
    627 		if err != nil {
    628 			return err
    629 		}
    630 		i.basePixels.AddOrReplace(pix, 0, 0, w, h)
    631 	}
    632 
    633 	i.image = gimg
    634 	i.drawTrianglesHistory = nil
    635 	i.stale = false
    636 	return nil
    637 }
    638 
    639 // Dispose disposes the image.
    640 //
    641 // After disposing, calling the function of the image causes unexpected results.
    642 func (i *Image) Dispose() {
    643 	theImages.remove(i)
    644 	i.image.Dispose()
    645 	i.image = nil
    646 	i.basePixels = Pixels{}
    647 	i.drawTrianglesHistory = nil
    648 	i.stale = false
    649 }
    650 
    651 // isInvalidated returns a boolean value indicating whether the image is invalidated.
    652 //
    653 // If an image is invalidated, GL context is lost and all the images should be restored asap.
    654 func (i *Image) isInvalidated() (bool, error) {
    655 	// FlushCommands is required because c.offscreen.impl might not have an actual texture.
    656 	if err := graphicscommand.FlushCommands(); err != nil {
    657 		return false, err
    658 	}
    659 	return i.image.IsInvalidated(), nil
    660 }
    661 
    662 func (i *Image) Dump(path string, blackbg bool) error {
    663 	return i.image.Dump(path, blackbg)
    664 }