mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-11-10 04:57:26 +01:00
graphics: Implement (*Image).Set
This commit is contained in:
parent
ddecc1c602
commit
128e99b6af
81
image.go
81
image.go
@ -116,7 +116,7 @@ func (m *mipmap) disposeMipmaps() {
|
||||
|
||||
// Image represents a rectangle set of pixels.
|
||||
// The pixel format is alpha-premultiplied RGBA.
|
||||
// Image implements image.Image.
|
||||
// Image implements image.Image and draw.Image.
|
||||
//
|
||||
// Functions of Image never returns error as of 1.5.0-alpha, and error values are always nil.
|
||||
type Image struct {
|
||||
@ -131,6 +131,8 @@ type Image struct {
|
||||
bounds *image.Rectangle
|
||||
original *Image
|
||||
|
||||
pixelsToSet map[int]color.RGBA
|
||||
|
||||
filter Filter
|
||||
}
|
||||
|
||||
@ -180,6 +182,8 @@ func (i *Image) Fill(clr color.Color) error {
|
||||
panic("render to a subimage is not implemented")
|
||||
}
|
||||
|
||||
i.resolvePixelsToSet(false)
|
||||
|
||||
r16, g16, b16, a16 := clr.RGBA()
|
||||
r, g, b, a := uint8(r16>>8), uint8(g16>>8), uint8(b16>>8), uint8(a16>>8)
|
||||
i.mipmap.original().Fill(r, g, b, a)
|
||||
@ -243,6 +247,9 @@ func (i *Image) drawImage(img *Image, options *DrawImageOptions) {
|
||||
panic("render to a subimage is not implemented")
|
||||
}
|
||||
|
||||
img.resolvePixelsToSet(true)
|
||||
i.resolvePixelsToSet(true)
|
||||
|
||||
// Calculate vertices before locking because the user can do anything in
|
||||
// options.ImageParts interface without deadlock (e.g. Call Image functions).
|
||||
if options == nil {
|
||||
@ -428,6 +435,9 @@ func (i *Image) DrawTriangles(vertices []Vertex, indices []uint16, img *Image, o
|
||||
panic("render to a subimage is not implemented")
|
||||
}
|
||||
|
||||
img.resolvePixelsToSet(true)
|
||||
i.resolvePixelsToSet(true)
|
||||
|
||||
if len(indices)%3 != 0 {
|
||||
panic("ebiten: len(indices) % 3 must be 0")
|
||||
}
|
||||
@ -531,9 +541,76 @@ func (i *Image) At(x, y int) color.Color {
|
||||
if i.bounds != nil && !image.Pt(x, y).In(*i.bounds) {
|
||||
return color.RGBA{}
|
||||
}
|
||||
i.resolvePixelsToSet(true)
|
||||
return i.mipmap.original().At(x, y)
|
||||
}
|
||||
|
||||
// Set sets the color at (x, y).
|
||||
//
|
||||
// Set loads pixels from GPU to system memory if necessary, which means that Set can be slow.
|
||||
//
|
||||
// Set can't be called before the main loop (ebiten.Run) starts.
|
||||
//
|
||||
// If the image is disposed, Set does nothing.
|
||||
func (i *Image) Set(x, y int, clr color.Color) {
|
||||
i.copyCheck()
|
||||
if i.isDisposed() {
|
||||
return
|
||||
}
|
||||
if i.bounds != nil && !image.Pt(x, y).In(*i.bounds) {
|
||||
return
|
||||
}
|
||||
if i.isSubimage() {
|
||||
i = i.original
|
||||
}
|
||||
|
||||
if i.pixelsToSet == nil {
|
||||
i.pixelsToSet = map[int]color.RGBA{}
|
||||
}
|
||||
r, g, b, a := clr.RGBA()
|
||||
w, _ := i.Size()
|
||||
i.pixelsToSet[x+y*w] = color.RGBA{
|
||||
byte(r >> 8),
|
||||
byte(g >> 8),
|
||||
byte(b >> 8),
|
||||
byte(a >> 8),
|
||||
}
|
||||
}
|
||||
|
||||
func (img *Image) resolvePixelsToSet(draw bool) {
|
||||
if img.isSubimage() {
|
||||
img = img.original
|
||||
}
|
||||
|
||||
if img.pixelsToSet == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !draw {
|
||||
img.pixelsToSet = nil
|
||||
return
|
||||
}
|
||||
|
||||
w, h := img.Size()
|
||||
pix := make([]byte, 4*w*h)
|
||||
idx := 0
|
||||
for j := 0; j < h; j++ {
|
||||
for i := 0; i < w; i++ {
|
||||
c, ok := img.pixelsToSet[idx]
|
||||
if !ok {
|
||||
c = img.mipmap.original().At(i, j)
|
||||
}
|
||||
pix[4*idx] = c.R
|
||||
pix[4*idx+1] = c.G
|
||||
pix[4*idx+2] = c.B
|
||||
pix[4*idx+3] = c.A
|
||||
idx++
|
||||
}
|
||||
}
|
||||
img.ReplacePixels(pix)
|
||||
img.pixelsToSet = nil
|
||||
}
|
||||
|
||||
// Dispose disposes the image data. After disposing, most of image functions do nothing and returns meaningless values.
|
||||
//
|
||||
// Dispose is useful to save memory.
|
||||
@ -549,6 +626,7 @@ func (i *Image) Dispose() error {
|
||||
if !i.isSubimage() {
|
||||
i.mipmap.dispose()
|
||||
}
|
||||
i.resolvePixelsToSet(false)
|
||||
runtime.SetFinalizer(i, nil)
|
||||
return nil
|
||||
}
|
||||
@ -573,6 +651,7 @@ func (i *Image) ReplacePixels(p []byte) error {
|
||||
if i.isSubimage() {
|
||||
panic("render to a subimage is not implemented")
|
||||
}
|
||||
i.resolvePixelsToSet(false)
|
||||
s := i.Bounds().Size()
|
||||
if l := 4 * s.X * s.Y; len(p) != l {
|
||||
panic(fmt.Sprintf("ebiten: len(p) was %d but must be %d", len(p), l))
|
||||
|
@ -1340,7 +1340,7 @@ func TestImageAddressRepeat(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestReplacePixelsAfterClear(t *testing.T) {
|
||||
func TestImageReplacePixelsAfterClear(t *testing.T) {
|
||||
const w, h = 256, 256
|
||||
img, _ := NewImage(w, h, FilterDefault)
|
||||
img.ReplacePixels(make([]byte, 4*w*h))
|
||||
@ -1353,3 +1353,97 @@ func TestReplacePixelsAfterClear(t *testing.T) {
|
||||
|
||||
// The test passes if this doesn't crash.
|
||||
}
|
||||
|
||||
func TestImageSet(t *testing.T) {
|
||||
type Pt struct {
|
||||
X, Y int
|
||||
}
|
||||
|
||||
const w, h = 16, 16
|
||||
img, _ := NewImage(w, h, FilterDefault)
|
||||
colors := map[Pt]color.RGBA{
|
||||
{1, 2}: {3, 4, 5, 6},
|
||||
{7, 8}: {9, 10, 11, 12},
|
||||
{13, 14}: {15, 16, 17, 18},
|
||||
}
|
||||
|
||||
for p, c := range colors {
|
||||
img.Set(p.X, p.Y, c)
|
||||
}
|
||||
|
||||
for j := 0; j < h; j++ {
|
||||
for i := 0; i < w; i++ {
|
||||
got := img.At(i, j).(color.RGBA)
|
||||
var want color.RGBA
|
||||
if c, ok := colors[Pt{i, j}]; ok {
|
||||
want = c
|
||||
}
|
||||
if got != want {
|
||||
t.Errorf("img.At(%d, %d): got: %v, want: %v", i, j, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestImageSetAndDraw(t *testing.T) {
|
||||
type Pt struct {
|
||||
X, Y int
|
||||
}
|
||||
|
||||
const w, h = 16, 16
|
||||
src, _ := NewImage(w, h, FilterDefault)
|
||||
dst, _ := NewImage(w, h, FilterDefault)
|
||||
colors := map[Pt]color.RGBA{
|
||||
{1, 2}: {3, 4, 5, 6},
|
||||
{7, 8}: {9, 10, 11, 12},
|
||||
{13, 14}: {15, 16, 17, 18},
|
||||
}
|
||||
for p, c := range colors {
|
||||
src.Set(p.X, p.Y, c)
|
||||
dst.Set(p.X+1, p.Y+1, c)
|
||||
}
|
||||
|
||||
dst.DrawImage(src, nil)
|
||||
for j := 0; j < h; j++ {
|
||||
for i := 0; i < w; i++ {
|
||||
got := dst.At(i, j).(color.RGBA)
|
||||
var want color.RGBA
|
||||
if c, ok := colors[Pt{i, j}]; ok {
|
||||
want = c
|
||||
}
|
||||
if c, ok := colors[Pt{i - 1, j - 1}]; ok {
|
||||
want = c
|
||||
}
|
||||
if got != want {
|
||||
t.Errorf("img.At(%d, %d): got: %v, want: %v", i, j, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
src.Clear()
|
||||
dst.Clear()
|
||||
for p, c := range colors {
|
||||
src.Set(p.X, p.Y, c)
|
||||
dst.Set(p.X+1, p.Y+1, c)
|
||||
}
|
||||
op := &DrawImageOptions{}
|
||||
op.GeoM.Translate(2, 2)
|
||||
dst.DrawImage(src.SubImage(image.Rect(2, 2, w-2, h-2)).(*Image), op)
|
||||
for j := 0; j < h; j++ {
|
||||
for i := 0; i < w; i++ {
|
||||
got := dst.At(i, j).(color.RGBA)
|
||||
var want color.RGBA
|
||||
if 2 <= i && 2 <= j && i < w-2 && j < h-2 {
|
||||
if c, ok := colors[Pt{i, j}]; ok {
|
||||
want = c
|
||||
}
|
||||
}
|
||||
if c, ok := colors[Pt{i - 1, j - 1}]; ok {
|
||||
want = c
|
||||
}
|
||||
if got != want {
|
||||
t.Errorf("img.At(%d, %d): got: %v, want: %v", i, j, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user