twitchapon-anim

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

image.go (22274B)


      1 // Copyright 2014 Hajime Hoshi
      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 ebiten
     16 
     17 import (
     18 	"fmt"
     19 	"image"
     20 	"image/color"
     21 
     22 	"github.com/hajimehoshi/ebiten/v2/internal/driver"
     23 	"github.com/hajimehoshi/ebiten/v2/internal/graphics"
     24 	"github.com/hajimehoshi/ebiten/v2/internal/mipmap"
     25 )
     26 
     27 // panicOnErrorAtImageAt indicates whether (*Image).At panics on an error or not.
     28 // This value is set only on testing.
     29 var panicOnErrorAtImageAt bool
     30 
     31 // Image represents a rectangle set of pixels.
     32 // The pixel format is alpha-premultiplied RGBA.
     33 // Image implements image.Image and draw.Image.
     34 type Image struct {
     35 	// addr holds self to check copying.
     36 	// See strings.Builder for similar examples.
     37 	addr *Image
     38 
     39 	mipmap *mipmap.Mipmap
     40 
     41 	bounds   image.Rectangle
     42 	original *Image
     43 }
     44 
     45 func (i *Image) copyCheck() {
     46 	if i.addr != i {
     47 		panic("ebiten: illegal use of non-zero Image copied by value")
     48 	}
     49 }
     50 
     51 // Size returns the size of the image.
     52 func (i *Image) Size() (width, height int) {
     53 	s := i.Bounds().Size()
     54 	return s.X, s.Y
     55 }
     56 
     57 func (i *Image) isDisposed() bool {
     58 	return i.mipmap == nil
     59 }
     60 
     61 func (i *Image) isSubImage() bool {
     62 	return i.original != nil
     63 }
     64 
     65 // Clear resets the pixels of the image into 0.
     66 //
     67 // When the image is disposed, Clear does nothing.
     68 func (i *Image) Clear() {
     69 	i.Fill(color.Transparent)
     70 }
     71 
     72 // Fill fills the image with a solid color.
     73 //
     74 // When the image is disposed, Fill does nothing.
     75 func (i *Image) Fill(clr color.Color) {
     76 	i.copyCheck()
     77 
     78 	if i.isDisposed() {
     79 		return
     80 	}
     81 
     82 	// TODO: Implement this.
     83 	if i.isSubImage() {
     84 		panic("ebiten: rendering to a sub-image is not implemented (Fill)")
     85 	}
     86 
     87 	i.mipmap.Fill(color.RGBAModel.Convert(clr).(color.RGBA))
     88 }
     89 
     90 func canSkipMipmap(geom GeoM, filter driver.Filter) bool {
     91 	det := geom.det2x2()
     92 	if filter == driver.FilterNearest && det <= 1.001 {
     93 		return true
     94 	}
     95 	if filter == driver.FilterLinear && det >= 0.999 {
     96 		return true
     97 	}
     98 	return false
     99 }
    100 
    101 // DrawImageOptions represents options for DrawImage.
    102 type DrawImageOptions struct {
    103 	// GeoM is a geometry matrix to draw.
    104 	// The default (zero) value is identity, which draws the image at (0, 0).
    105 	GeoM GeoM
    106 
    107 	// ColorM is a color matrix to draw.
    108 	// The default (zero) value is identity, which doesn't change any color.
    109 	ColorM ColorM
    110 
    111 	// CompositeMode is a composite mode to draw.
    112 	// The default (zero) value is regular alpha blending.
    113 	CompositeMode CompositeMode
    114 
    115 	// Filter is a type of texture filter.
    116 	// The default (zero) value is FilterNearest.
    117 	Filter Filter
    118 }
    119 
    120 // DrawImage draws the given image on the image i.
    121 //
    122 // DrawImage accepts the options. For details, see the document of
    123 // DrawImageOptions.
    124 //
    125 // For drawing, the pixels of the argument image at the time of this call is
    126 // adopted. Even if the argument image is mutated after this call, the drawing
    127 // result is never affected.
    128 //
    129 // When the image i is disposed, DrawImage does nothing.
    130 // When the given image img is disposed, DrawImage panics.
    131 //
    132 // When the given image is as same as i, DrawImage panics.
    133 //
    134 // DrawImage works more efficiently as batches
    135 // when the successive calls of DrawImages satisfy the below conditions:
    136 //
    137 //   * All render targets are same (A in A.DrawImage(B, op))
    138 //   * Either all ColorM element values are same or all the ColorM have only
    139 //      diagonal ('scale') elements
    140 //     * If only (*ColorM).Scale is applied to a ColorM, the ColorM has only
    141 //       diagonal elements. The other ColorM functions might modify the other
    142 //       elements.
    143 //   * All CompositeMode values are same
    144 //   * All Filter values are same
    145 //
    146 // Even when all the above conditions are satisfied, multiple draw commands can
    147 // be used in really rare cases. Ebiten images usually share an internal
    148 // automatic texture atlas, but when you consume the atlas, or you create a huge
    149 // image, those images cannot be on the same texture atlas. In this case, draw
    150 // commands are separated. The texture atlas size is 4096x4096 so far. Another
    151 // case is when you use an offscreen as a render source. An offscreen doesn't
    152 // share the texture atlas with high probability.
    153 //
    154 // For more performance tips, see https://ebiten.org/documents/performancetips.html
    155 func (i *Image) DrawImage(img *Image, options *DrawImageOptions) {
    156 	i.copyCheck()
    157 
    158 	if img.isDisposed() {
    159 		panic("ebiten: the given image to DrawImage must not be disposed")
    160 	}
    161 	if i.isDisposed() {
    162 		return
    163 	}
    164 
    165 	// TODO: Implement this.
    166 	if i.isSubImage() {
    167 		panic("ebiten: render to a sub-image is not implemented (DrawImage)")
    168 	}
    169 
    170 	// Calculate vertices before locking because the user can do anything in
    171 	// options.ImageParts interface without deadlock (e.g. Call Image functions).
    172 	if options == nil {
    173 		options = &DrawImageOptions{}
    174 	}
    175 
    176 	bounds := img.Bounds()
    177 	mode := driver.CompositeMode(options.CompositeMode)
    178 	filter := driver.Filter(options.Filter)
    179 
    180 	a, b, c, d, tx, ty := options.GeoM.elements32()
    181 
    182 	sx0 := float32(bounds.Min.X)
    183 	sy0 := float32(bounds.Min.Y)
    184 	sx1 := float32(bounds.Max.X)
    185 	sy1 := float32(bounds.Max.Y)
    186 	vs := graphics.QuadVertices(sx0, sy0, sx1, sy1, a, b, c, d, tx, ty, 1, 1, 1, 1, filter == driver.FilterScreen)
    187 	is := graphics.QuadIndices()
    188 
    189 	srcs := [graphics.ShaderImageNum]*mipmap.Mipmap{img.mipmap}
    190 	i.mipmap.DrawTriangles(srcs, vs, is, options.ColorM.impl, mode, filter, driver.AddressUnsafe, driver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, canSkipMipmap(options.GeoM, filter))
    191 }
    192 
    193 // Vertex represents a vertex passed to DrawTriangles.
    194 type Vertex struct {
    195 	// DstX and DstY represents a point on a destination image.
    196 	DstX float32
    197 	DstY float32
    198 
    199 	// SrcX and SrcY represents a point on a source image.
    200 	// Be careful that SrcX/SrcY coordinates are on the image's bounds.
    201 	// This means that a left-upper point of a sub-image might not be (0, 0).
    202 	SrcX float32
    203 	SrcY float32
    204 
    205 	// ColorR/ColorG/ColorB/ColorA represents color scaling values.
    206 	// 1 means the original source image color is used.
    207 	// 0 means a transparent color is used.
    208 	ColorR float32
    209 	ColorG float32
    210 	ColorB float32
    211 	ColorA float32
    212 }
    213 
    214 // Address represents a sampler address mode.
    215 type Address int
    216 
    217 const (
    218 	// AddressUnsafe means there is no guarantee when the texture coodinates are out of range.
    219 	AddressUnsafe Address = Address(driver.AddressUnsafe)
    220 
    221 	// AddressClampToZero means that out-of-range texture coordinates return 0 (transparent).
    222 	AddressClampToZero Address = Address(driver.AddressClampToZero)
    223 
    224 	// AddressRepeat means that texture coordinates wrap to the other side of the texture.
    225 	AddressRepeat Address = Address(driver.AddressRepeat)
    226 )
    227 
    228 // DrawTrianglesOptions represents options for DrawTriangles.
    229 type DrawTrianglesOptions struct {
    230 	// ColorM is a color matrix to draw.
    231 	// The default (zero) value is identity, which doesn't change any color.
    232 	// ColorM is applied before vertex color scale is applied.
    233 	//
    234 	// If Shader is not nil, ColorM is ignored.
    235 	ColorM ColorM
    236 
    237 	// CompositeMode is a composite mode to draw.
    238 	// The default (zero) value is regular alpha blending.
    239 	CompositeMode CompositeMode
    240 
    241 	// Filter is a type of texture filter.
    242 	// The default (zero) value is FilterNearest.
    243 	Filter Filter
    244 
    245 	// Address is a sampler address mode.
    246 	// The default (zero) value is AddressUnsafe.
    247 	Address Address
    248 }
    249 
    250 // MaxIndicesNum is the maximum number of indices for DrawTriangles.
    251 const MaxIndicesNum = graphics.IndicesNum
    252 
    253 // DrawTriangles draws triangles with the specified vertices and their indices.
    254 //
    255 // If len(indices) is not multiple of 3, DrawTriangles panics.
    256 //
    257 // If len(indices) is more than MaxIndicesNum, DrawTriangles panics.
    258 //
    259 // The rule in which DrawTriangles works effectively is same as DrawImage's.
    260 //
    261 // When the given image is disposed, DrawTriangles panics.
    262 //
    263 // When the image i is disposed, DrawTriangles does nothing.
    264 func (i *Image) DrawTriangles(vertices []Vertex, indices []uint16, img *Image, options *DrawTrianglesOptions) {
    265 	i.copyCheck()
    266 
    267 	if img != nil && img.isDisposed() {
    268 		panic("ebiten: the given image to DrawTriangles must not be disposed")
    269 	}
    270 	if i.isDisposed() {
    271 		return
    272 	}
    273 
    274 	if i.isSubImage() {
    275 		panic("ebiten: render to a sub-image is not implemented (DrawTriangles)")
    276 	}
    277 
    278 	if len(indices)%3 != 0 {
    279 		panic("ebiten: len(indices) % 3 must be 0")
    280 	}
    281 	if len(indices) > MaxIndicesNum {
    282 		panic("ebiten: len(indices) must be <= MaxIndicesNum")
    283 	}
    284 	// TODO: Check the maximum value of indices and len(vertices)?
    285 
    286 	if options == nil {
    287 		options = &DrawTrianglesOptions{}
    288 	}
    289 
    290 	mode := driver.CompositeMode(options.CompositeMode)
    291 
    292 	address := driver.Address(options.Address)
    293 	var sr driver.Region
    294 	if address != driver.AddressUnsafe {
    295 		b := img.Bounds()
    296 		sr = driver.Region{
    297 			X:      float32(b.Min.X),
    298 			Y:      float32(b.Min.Y),
    299 			Width:  float32(b.Dx()),
    300 			Height: float32(b.Dy()),
    301 		}
    302 	}
    303 
    304 	filter := driver.Filter(options.Filter)
    305 
    306 	vs := make([]float32, len(vertices)*graphics.VertexFloatNum)
    307 	for i, v := range vertices {
    308 		vs[i*graphics.VertexFloatNum] = v.DstX
    309 		vs[i*graphics.VertexFloatNum+1] = v.DstY
    310 		vs[i*graphics.VertexFloatNum+2] = v.SrcX
    311 		vs[i*graphics.VertexFloatNum+3] = v.SrcY
    312 		vs[i*graphics.VertexFloatNum+4] = v.ColorR
    313 		vs[i*graphics.VertexFloatNum+5] = v.ColorG
    314 		vs[i*graphics.VertexFloatNum+6] = v.ColorB
    315 		vs[i*graphics.VertexFloatNum+7] = v.ColorA
    316 	}
    317 	is := make([]uint16, len(indices))
    318 	copy(is, indices)
    319 
    320 	srcs := [graphics.ShaderImageNum]*mipmap.Mipmap{img.mipmap}
    321 
    322 	i.mipmap.DrawTriangles(srcs, vs, is, options.ColorM.impl, mode, filter, address, sr, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, false)
    323 }
    324 
    325 // DrawTrianglesShaderOptions represents options for DrawTrianglesShader.
    326 //
    327 // This API is experimental.
    328 type DrawTrianglesShaderOptions struct {
    329 	// CompositeMode is a composite mode to draw.
    330 	// The default (zero) value is regular alpha blending.
    331 	CompositeMode CompositeMode
    332 
    333 	// Uniforms is a set of uniform variables for the shader.
    334 	// The keys are the names of the uniform variables.
    335 	// The values must be float or []float.
    336 	// If the uniform variable type is an array, a vector or a matrix,
    337 	// you have to specify linearly flattened values as a slice.
    338 	// For example, if the uniform variable type is [4]vec4, the number of the slice values will be 16.
    339 	Uniforms map[string]interface{}
    340 
    341 	// Images is a set of the source images.
    342 	// All the image must be the same size.
    343 	Images [4]*Image
    344 }
    345 
    346 func init() {
    347 	var op DrawTrianglesShaderOptions
    348 	if got, want := len(op.Images), graphics.ShaderImageNum; got != want {
    349 		panic(fmt.Sprintf("ebiten: len((DrawTrianglesShaderOptions{}).Images) must be %d but %d", want, got))
    350 	}
    351 }
    352 
    353 // DrawTrianglesShader draws triangles with the specified vertices and their indices with the specified shader.
    354 //
    355 // For the details about the shader, see https://ebiten.org/documents/shader.html.
    356 //
    357 // If len(indices) is not multiple of 3, DrawTrianglesShader panics.
    358 //
    359 // If len(indices) is more than MaxIndicesNum, DrawTrianglesShader panics.
    360 //
    361 // When a specified image is non-nil and is disposed, DrawTrianglesShader panics.
    362 //
    363 // When the image i is disposed, DrawTrianglesShader does nothing.
    364 //
    365 // This API is experimental.
    366 func (i *Image) DrawTrianglesShader(vertices []Vertex, indices []uint16, shader *Shader, options *DrawTrianglesShaderOptions) {
    367 	i.copyCheck()
    368 
    369 	if i.isDisposed() {
    370 		return
    371 	}
    372 
    373 	if i.isSubImage() {
    374 		panic("ebiten: render to a sub-image is not implemented (DrawTrianglesShader)")
    375 	}
    376 
    377 	if len(indices)%3 != 0 {
    378 		panic("ebiten: len(indices) % 3 must be 0")
    379 	}
    380 	if len(indices) > MaxIndicesNum {
    381 		panic("ebiten: len(indices) must be <= MaxIndicesNum")
    382 	}
    383 	// TODO: Check the maximum value of indices and len(vertices)?
    384 
    385 	if options == nil {
    386 		options = &DrawTrianglesShaderOptions{}
    387 	}
    388 
    389 	mode := driver.CompositeMode(options.CompositeMode)
    390 
    391 	vs := make([]float32, len(vertices)*graphics.VertexFloatNum)
    392 	for i, v := range vertices {
    393 		vs[i*graphics.VertexFloatNum] = v.DstX
    394 		vs[i*graphics.VertexFloatNum+1] = v.DstY
    395 		vs[i*graphics.VertexFloatNum+2] = v.SrcX
    396 		vs[i*graphics.VertexFloatNum+3] = v.SrcY
    397 		vs[i*graphics.VertexFloatNum+4] = v.ColorR
    398 		vs[i*graphics.VertexFloatNum+5] = v.ColorG
    399 		vs[i*graphics.VertexFloatNum+6] = v.ColorB
    400 		vs[i*graphics.VertexFloatNum+7] = v.ColorA
    401 	}
    402 	is := make([]uint16, len(indices))
    403 	copy(is, indices)
    404 
    405 	var imgs [graphics.ShaderImageNum]*mipmap.Mipmap
    406 	var imgw, imgh int
    407 	for i, img := range options.Images {
    408 		if img == nil {
    409 			continue
    410 		}
    411 		if img.isDisposed() {
    412 			panic("ebiten: the given image to DrawRectShader must not be disposed")
    413 		}
    414 		if i == 0 {
    415 			imgw, imgh = img.Size()
    416 		} else {
    417 			// TODO: Check imgw > 0 && imgh > 0
    418 			if w, h := img.Size(); imgw != w || imgh != h {
    419 				panic("ebiten: all the source images must be the same size with the rectangle")
    420 			}
    421 		}
    422 		imgs[i] = img.mipmap
    423 	}
    424 
    425 	var sx, sy float32
    426 	if options.Images[0] != nil {
    427 		b := options.Images[0].Bounds()
    428 		sx = float32(b.Min.X)
    429 		sy = float32(b.Min.Y)
    430 	}
    431 
    432 	var sr driver.Region
    433 	if img := options.Images[0]; img != nil {
    434 		b := img.Bounds()
    435 		sr = driver.Region{
    436 			X:      float32(b.Min.X),
    437 			Y:      float32(b.Min.Y),
    438 			Width:  float32(b.Dx()),
    439 			Height: float32(b.Dy()),
    440 		}
    441 	}
    442 
    443 	var offsets [graphics.ShaderImageNum - 1][2]float32
    444 	for i, img := range options.Images[1:] {
    445 		if img == nil {
    446 			continue
    447 		}
    448 		b := img.Bounds()
    449 		offsets[i][0] = -sx + float32(b.Min.X)
    450 		offsets[i][1] = -sy + float32(b.Min.Y)
    451 	}
    452 
    453 	us := shader.convertUniforms(options.Uniforms)
    454 	i.mipmap.DrawTriangles(imgs, vs, is, nil, mode, driver.FilterNearest, driver.AddressUnsafe, sr, offsets, shader.shader, us, false)
    455 }
    456 
    457 // DrawRectShaderOptions represents options for DrawRectShader.
    458 //
    459 // This API is experimental.
    460 type DrawRectShaderOptions struct {
    461 	// GeoM is a geometry matrix to draw.
    462 	// The default (zero) value is identity, which draws the rectangle at (0, 0).
    463 	GeoM GeoM
    464 
    465 	// CompositeMode is a composite mode to draw.
    466 	// The default (zero) value is regular alpha blending.
    467 	CompositeMode CompositeMode
    468 
    469 	// Uniforms is a set of uniform variables for the shader.
    470 	// The keys are the names of the uniform variables.
    471 	// The values must be float or []float.
    472 	// If the uniform variable type is an array, a vector or a matrix,
    473 	// you have to specify linearly flattened values as a slice.
    474 	// For example, if the uniform variable type is [4]vec4, the number of the slice values will be 16.
    475 	Uniforms map[string]interface{}
    476 
    477 	// Images is a set of the source images.
    478 	// All the image must be the same size with the rectangle.
    479 	Images [4]*Image
    480 }
    481 
    482 func init() {
    483 	var op DrawRectShaderOptions
    484 	if got, want := len(op.Images), graphics.ShaderImageNum; got != want {
    485 		panic(fmt.Sprintf("ebiten: len((DrawRectShaderOptions{}).Images) must be %d but %d", want, got))
    486 	}
    487 }
    488 
    489 // DrawRectShader draws a rectangle with the specified width and height with the specified shader.
    490 //
    491 // For the details about the shader, see https://ebiten.org/documents/shader.html.
    492 //
    493 // When one of the specified image is non-nil and is disposed, DrawRectShader panics.
    494 //
    495 // When the image i is disposed, DrawRectShader does nothing.
    496 //
    497 // This API is experimental.
    498 func (i *Image) DrawRectShader(width, height int, shader *Shader, options *DrawRectShaderOptions) {
    499 	i.copyCheck()
    500 
    501 	if i.isDisposed() {
    502 		return
    503 	}
    504 
    505 	// TODO: Implement this.
    506 	if i.isSubImage() {
    507 		panic("ebiten: rendering to a sub-image is not implemented (DrawRectShader)")
    508 	}
    509 
    510 	if options == nil {
    511 		options = &DrawRectShaderOptions{}
    512 	}
    513 
    514 	mode := driver.CompositeMode(options.CompositeMode)
    515 
    516 	var imgs [graphics.ShaderImageNum]*mipmap.Mipmap
    517 	for i, img := range options.Images {
    518 		if img == nil {
    519 			continue
    520 		}
    521 		if img.isDisposed() {
    522 			panic("ebiten: the given image to DrawRectShader must not be disposed")
    523 		}
    524 		if w, h := img.Size(); width != w || height != h {
    525 			panic("ebiten: all the source images must be the same size with the rectangle")
    526 		}
    527 		imgs[i] = img.mipmap
    528 	}
    529 
    530 	var sx, sy float32
    531 	if options.Images[0] != nil {
    532 		b := options.Images[0].Bounds()
    533 		sx = float32(b.Min.X)
    534 		sy = float32(b.Min.Y)
    535 	}
    536 
    537 	a, b, c, d, tx, ty := options.GeoM.elements32()
    538 	vs := graphics.QuadVertices(sx, sy, sx+float32(width), sy+float32(height), a, b, c, d, tx, ty, 1, 1, 1, 1, false)
    539 	is := graphics.QuadIndices()
    540 
    541 	var sr driver.Region
    542 	if img := options.Images[0]; img != nil {
    543 		b := img.Bounds()
    544 		sr = driver.Region{
    545 			X:      float32(b.Min.X),
    546 			Y:      float32(b.Min.Y),
    547 			Width:  float32(b.Dx()),
    548 			Height: float32(b.Dy()),
    549 		}
    550 	}
    551 
    552 	var offsets [graphics.ShaderImageNum - 1][2]float32
    553 	for i, img := range options.Images[1:] {
    554 		if img == nil {
    555 			continue
    556 		}
    557 		b := img.Bounds()
    558 		offsets[i][0] = -sx + float32(b.Min.X)
    559 		offsets[i][1] = -sy + float32(b.Min.Y)
    560 	}
    561 
    562 	us := shader.convertUniforms(options.Uniforms)
    563 	i.mipmap.DrawTriangles(imgs, vs, is, nil, mode, driver.FilterNearest, driver.AddressUnsafe, sr, offsets, shader.shader, us, canSkipMipmap(options.GeoM, driver.FilterNearest))
    564 }
    565 
    566 // SubImage returns an image representing the portion of the image p visible through r.
    567 // The returned value shares pixels with the original image.
    568 //
    569 // The returned value is always *ebiten.Image.
    570 //
    571 // If the image is disposed, SubImage returns nil.
    572 //
    573 // In the current Ebiten implementation, SubImage is available only as a rendering source.
    574 func (i *Image) SubImage(r image.Rectangle) image.Image {
    575 	i.copyCheck()
    576 	if i.isDisposed() {
    577 		return nil
    578 	}
    579 
    580 	r = r.Intersect(i.Bounds())
    581 	// Need to check Empty explicitly. See the standard image package implementations.
    582 	if r.Empty() {
    583 		r = image.ZR
    584 	}
    585 
    586 	// Keep the original image's reference not to dispose that by GC.
    587 	var orig = i
    588 	if i.isSubImage() {
    589 		orig = i.original
    590 	}
    591 
    592 	img := &Image{
    593 		mipmap:   i.mipmap,
    594 		bounds:   r,
    595 		original: orig,
    596 	}
    597 	img.addr = img
    598 
    599 	return img
    600 }
    601 
    602 // Bounds returns the bounds of the image.
    603 func (i *Image) Bounds() image.Rectangle {
    604 	if i.isDisposed() {
    605 		panic("ebiten: the image is already disposed")
    606 	}
    607 	return i.bounds
    608 }
    609 
    610 // ColorModel returns the color model of the image.
    611 func (i *Image) ColorModel() color.Model {
    612 	return color.RGBAModel
    613 }
    614 
    615 // At returns the color of the image at (x, y).
    616 //
    617 // At loads pixels from GPU to system memory if necessary, which means that At can be slow.
    618 //
    619 // At always returns a transparent color if the image is disposed.
    620 //
    621 // Note that an important logic should not rely on values returned by At, since
    622 // the returned values can include very slight differences between some machines.
    623 //
    624 // At can't be called outside the main loop (ebiten.Run's updating function) starts.
    625 func (i *Image) At(x, y int) color.Color {
    626 	if i.isDisposed() {
    627 		return color.RGBA{}
    628 	}
    629 	if !image.Pt(x, y).In(i.Bounds()) {
    630 		return color.RGBA{}
    631 	}
    632 	pix, err := i.mipmap.Pixels(x, y, 1, 1)
    633 	if err != nil {
    634 		if panicOnErrorAtImageAt {
    635 			panic(err)
    636 		}
    637 		theUIContext.setError(err)
    638 		return color.RGBA{}
    639 	}
    640 	return color.RGBA{pix[0], pix[1], pix[2], pix[3]}
    641 }
    642 
    643 // Set sets the color at (x, y).
    644 //
    645 // Set loads pixels from GPU to system memory if necessary, which means that Set can be slow.
    646 //
    647 // In the current implementation, successive calls of Set invokes loading pixels at most once, so this is efficient.
    648 //
    649 // If the image is disposed, Set does nothing.
    650 func (i *Image) Set(x, y int, clr color.Color) {
    651 	i.copyCheck()
    652 	if i.isDisposed() {
    653 		return
    654 	}
    655 	if !image.Pt(x, y).In(i.Bounds()) {
    656 		return
    657 	}
    658 	if i.isSubImage() {
    659 		i = i.original
    660 	}
    661 
    662 	r, g, b, a := clr.RGBA()
    663 	pix := []byte{byte(r >> 8), byte(g >> 8), byte(b >> 8), byte(a >> 8)}
    664 	if err := i.mipmap.ReplacePixels(pix, x, y, 1, 1); err != nil {
    665 		theUIContext.setError(err)
    666 	}
    667 }
    668 
    669 // Dispose disposes the image data.
    670 // After disposing, most of image functions do nothing and returns meaningless values.
    671 //
    672 // Calling Dispose is not mandatory. GC automatically collects internal resources that no objects refer to.
    673 // However, calling Dispose explicitly is helpful if memory usage matters.
    674 //
    675 // When the image is disposed, Dipose does nothing.
    676 func (i *Image) Dispose() {
    677 	i.copyCheck()
    678 
    679 	if i.isDisposed() {
    680 		return
    681 	}
    682 	if i.isSubImage() {
    683 		return
    684 	}
    685 	i.mipmap.MarkDisposed()
    686 	i.mipmap = nil
    687 }
    688 
    689 // ReplacePixels replaces the pixels of the image with p.
    690 //
    691 // The given p must represent RGBA pre-multiplied alpha values.
    692 // len(pix) must equal to 4 * (bounds width) * (bounds height).
    693 //
    694 // ReplacePixels works on a sub-image.
    695 //
    696 // When len(pix) is not appropriate, ReplacePixels panics.
    697 //
    698 // When the image is disposed, ReplacePixels does nothing.
    699 func (i *Image) ReplacePixels(pixels []byte) {
    700 	i.copyCheck()
    701 
    702 	if i.isDisposed() {
    703 		return
    704 	}
    705 	r := i.Bounds()
    706 
    707 	// Do not need to copy pixels here.
    708 	// * In internal/mipmap, pixels are copied when necessary.
    709 	// * In internal/shareable, pixels are copied to make its paddings.
    710 	if err := i.mipmap.ReplacePixels(pixels, r.Min.X, r.Min.Y, r.Dx(), r.Dy()); err != nil {
    711 		theUIContext.setError(err)
    712 	}
    713 }
    714 
    715 // NewImage returns an empty image.
    716 //
    717 // If width or height is less than 1 or more than device-dependent maximum size, NewImage panics.
    718 func NewImage(width, height int) *Image {
    719 	i := &Image{
    720 		mipmap: mipmap.New(width, height),
    721 		bounds: image.Rect(0, 0, width, height),
    722 	}
    723 	i.addr = i
    724 	return i
    725 }
    726 
    727 // NewImageFromImage creates a new image with the given image (source).
    728 //
    729 // If source's width or height is less than 1 or more than device-dependent maximum size, NewImageFromImage panics.
    730 func NewImageFromImage(source image.Image) *Image {
    731 	size := source.Bounds().Size()
    732 
    733 	width, height := size.X, size.Y
    734 
    735 	i := &Image{
    736 		mipmap: mipmap.New(width, height),
    737 		bounds: image.Rect(0, 0, width, height),
    738 	}
    739 	i.addr = i
    740 
    741 	i.ReplacePixels(imageToBytes(source))
    742 	return i
    743 }
    744 
    745 func newScreenFramebufferImage(width, height int) *Image {
    746 	i := &Image{
    747 		mipmap: mipmap.NewScreenFramebufferMipmap(width, height),
    748 		bounds: image.Rect(0, 0, width, height),
    749 	}
    750 	i.addr = i
    751 	return i
    752 }