zorldo

Goofing around with Ebiten
git clone git://bsandro.tech/zorldo
Log | Files | Refs | README

image.go (26280B)


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