image.go (6494B)
1 // Copyright 2016 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 graphicscommand 16 17 import ( 18 "fmt" 19 "image" 20 "os" 21 "sort" 22 "strings" 23 24 "github.com/hajimehoshi/ebiten/v2/internal/affine" 25 "github.com/hajimehoshi/ebiten/v2/internal/debug" 26 "github.com/hajimehoshi/ebiten/v2/internal/driver" 27 "github.com/hajimehoshi/ebiten/v2/internal/graphics" 28 "github.com/hajimehoshi/ebiten/v2/internal/png" 29 ) 30 31 // Image represents an image that is implemented with OpenGL. 32 type Image struct { 33 image driver.Image 34 width int 35 height int 36 internalWidth int 37 internalHeight int 38 screen bool 39 40 // id is an indentifier for the image. This is used only when dummping the information. 41 // 42 // This is duplicated with driver.Image's ID, but this id is still necessary because this image might not 43 // have its driver.Image. 44 id int 45 46 bufferedRP []*driver.ReplacePixelsArgs 47 } 48 49 var nextID = 1 50 51 func genNextID() int { 52 id := nextID 53 nextID++ 54 return id 55 } 56 57 // NewImage returns a new image. 58 // 59 // Note that the image is not initialized yet. 60 func NewImage(width, height int) *Image { 61 i := &Image{ 62 width: width, 63 height: height, 64 id: genNextID(), 65 } 66 c := &newImageCommand{ 67 result: i, 68 width: width, 69 height: height, 70 } 71 theCommandQueue.Enqueue(c) 72 return i 73 } 74 75 func NewScreenFramebufferImage(width, height int) *Image { 76 i := &Image{ 77 width: width, 78 height: height, 79 screen: true, 80 id: genNextID(), 81 } 82 c := &newScreenFramebufferImageCommand{ 83 result: i, 84 width: width, 85 height: height, 86 } 87 theCommandQueue.Enqueue(c) 88 return i 89 } 90 91 func (i *Image) resolveBufferedReplacePixels() { 92 if len(i.bufferedRP) == 0 { 93 return 94 } 95 c := &replacePixelsCommand{ 96 dst: i, 97 args: i.bufferedRP, 98 } 99 theCommandQueue.Enqueue(c) 100 i.bufferedRP = nil 101 } 102 103 func (i *Image) Dispose() { 104 c := &disposeImageCommand{ 105 target: i, 106 } 107 theCommandQueue.Enqueue(c) 108 } 109 110 func (i *Image) InternalSize() (int, int) { 111 if i.screen { 112 return i.width, i.height 113 } 114 if i.internalWidth == 0 { 115 i.internalWidth = graphics.InternalImageSize(i.width) 116 } 117 if i.internalHeight == 0 { 118 i.internalHeight = graphics.InternalImageSize(i.height) 119 } 120 return i.internalWidth, i.internalHeight 121 } 122 123 // DrawTriangles draws triangles with the given image. 124 // 125 // The vertex floats are: 126 // 127 // 0: Destination X in pixels 128 // 1: Destination Y in pixels 129 // 2: Source X in pixels (not texels!) 130 // 3: Source Y in pixels 131 // 4: Color R [0.0-1.0] 132 // 5: Color G 133 // 6: Color B 134 // 7: Color Y 135 // 136 // src and shader are exclusive and only either is non-nil. 137 // 138 // The elements that index is in between 2 and 7 are used for the source images. 139 // The source image is 1) src argument if non-nil, or 2) an image value in the uniform variables if it exists. 140 // If there are multiple images in the uniform variables, the smallest ID's value is adopted. 141 // 142 // If the source image is not specified, i.e., src is nil and there is no image in the uniform variables, the 143 // elements for the source image are not used. 144 func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, offsets [graphics.ShaderImageNum - 1][2]float32, vertices []float32, indices []uint16, clr affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, shader *Shader, uniforms []interface{}, evenOdd bool) { 145 if shader == nil { 146 // Fast path for rendering without a shader (#1355). 147 img := srcs[0] 148 if img.screen { 149 panic("graphicscommand: the screen image cannot be the rendering source") 150 } 151 img.resolveBufferedReplacePixels() 152 } else { 153 for _, src := range srcs { 154 if src == nil { 155 continue 156 } 157 if src.screen { 158 panic("graphicscommand: the screen image cannot be the rendering source") 159 } 160 src.resolveBufferedReplacePixels() 161 } 162 } 163 i.resolveBufferedReplacePixels() 164 165 theCommandQueue.EnqueueDrawTrianglesCommand(i, srcs, offsets, vertices, indices, clr, mode, filter, address, dstRegion, srcRegion, shader, uniforms, evenOdd) 166 } 167 168 // Pixels returns the image's pixels. 169 // Pixels might return nil when OpenGL error happens. 170 func (i *Image) Pixels() ([]byte, error) { 171 i.resolveBufferedReplacePixels() 172 c := &pixelsCommand{ 173 img: i, 174 } 175 theCommandQueue.Enqueue(c) 176 if err := theCommandQueue.Flush(); err != nil { 177 return nil, err 178 } 179 return c.result, nil 180 } 181 182 func (i *Image) ReplacePixels(pixels []byte, x, y, width, height int) { 183 i.bufferedRP = append(i.bufferedRP, &driver.ReplacePixelsArgs{ 184 Pixels: pixels, 185 X: x, 186 Y: y, 187 Width: width, 188 Height: height, 189 }) 190 } 191 192 func (i *Image) IsInvalidated() bool { 193 if i.screen { 194 // The screen image might not have a texture, and in this case it is impossible to detect whether 195 // the image is invalidated or not. 196 panic("graphicscommand: IsInvalidated cannot be called on the screen image") 197 } 198 199 // i.image can be nil before initializing. 200 if i.image == nil { 201 return false 202 } 203 return i.image.IsInvalidated() 204 } 205 206 // Dump dumps the image to the specified path. 207 // In the path, '*' is replaced with the image's ID. 208 // 209 // If blackbg is true, any alpha values in the dumped image will be 255. 210 // 211 // This is for testing usage. 212 func (i *Image) Dump(path string, blackbg bool, rect image.Rectangle) error { 213 // Screen image cannot be dumped. 214 if i.screen { 215 return nil 216 } 217 218 path = strings.ReplaceAll(path, "*", fmt.Sprintf("%d", i.id)) 219 f, err := os.Create(path) 220 if err != nil { 221 return err 222 } 223 defer f.Close() 224 225 pix, err := i.Pixels() 226 if err != nil { 227 return err 228 } 229 230 if blackbg { 231 for i := 0; i < len(pix)/4; i++ { 232 pix[4*i+3] = 0xff 233 } 234 } 235 236 if err := png.Encode(f, (&image.RGBA{ 237 Pix: pix, 238 Stride: 4 * i.width, 239 Rect: image.Rect(0, 0, i.width, i.height), 240 }).SubImage(rect)); err != nil { 241 return err 242 } 243 return nil 244 } 245 246 func LogImagesInfo(images []*Image) { 247 sort.Slice(images, func(a, b int) bool { 248 return images[a].id < images[b].id 249 }) 250 for _, i := range images { 251 w, h := i.InternalSize() 252 debug.Logf(" %d: (%d, %d)\n", i.id, w, h) 253 } 254 }