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 }