zorldo

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

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 }