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