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.
|
// Image represents a rectangle set of pixels.
|
||||||
// The pixel format is alpha-premultiplied RGBA.
|
// 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.
|
// Functions of Image never returns error as of 1.5.0-alpha, and error values are always nil.
|
||||||
type Image struct {
|
type Image struct {
|
||||||
@ -131,6 +131,8 @@ type Image struct {
|
|||||||
bounds *image.Rectangle
|
bounds *image.Rectangle
|
||||||
original *Image
|
original *Image
|
||||||
|
|
||||||
|
pixelsToSet map[int]color.RGBA
|
||||||
|
|
||||||
filter Filter
|
filter Filter
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -180,6 +182,8 @@ func (i *Image) Fill(clr color.Color) error {
|
|||||||
panic("render to a subimage is not implemented")
|
panic("render to a subimage is not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
i.resolvePixelsToSet(false)
|
||||||
|
|
||||||
r16, g16, b16, a16 := clr.RGBA()
|
r16, g16, b16, a16 := clr.RGBA()
|
||||||
r, g, b, a := uint8(r16>>8), uint8(g16>>8), uint8(b16>>8), uint8(a16>>8)
|
r, g, b, a := uint8(r16>>8), uint8(g16>>8), uint8(b16>>8), uint8(a16>>8)
|
||||||
i.mipmap.original().Fill(r, g, b, a)
|
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")
|
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
|
// Calculate vertices before locking because the user can do anything in
|
||||||
// options.ImageParts interface without deadlock (e.g. Call Image functions).
|
// options.ImageParts interface without deadlock (e.g. Call Image functions).
|
||||||
if options == nil {
|
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")
|
panic("render to a subimage is not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
img.resolvePixelsToSet(true)
|
||||||
|
i.resolvePixelsToSet(true)
|
||||||
|
|
||||||
if len(indices)%3 != 0 {
|
if len(indices)%3 != 0 {
|
||||||
panic("ebiten: len(indices) % 3 must be 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) {
|
if i.bounds != nil && !image.Pt(x, y).In(*i.bounds) {
|
||||||
return color.RGBA{}
|
return color.RGBA{}
|
||||||
}
|
}
|
||||||
|
i.resolvePixelsToSet(true)
|
||||||
return i.mipmap.original().At(x, y)
|
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 disposes the image data. After disposing, most of image functions do nothing and returns meaningless values.
|
||||||
//
|
//
|
||||||
// Dispose is useful to save memory.
|
// Dispose is useful to save memory.
|
||||||
@ -549,6 +626,7 @@ func (i *Image) Dispose() error {
|
|||||||
if !i.isSubimage() {
|
if !i.isSubimage() {
|
||||||
i.mipmap.dispose()
|
i.mipmap.dispose()
|
||||||
}
|
}
|
||||||
|
i.resolvePixelsToSet(false)
|
||||||
runtime.SetFinalizer(i, nil)
|
runtime.SetFinalizer(i, nil)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -573,6 +651,7 @@ func (i *Image) ReplacePixels(p []byte) error {
|
|||||||
if i.isSubimage() {
|
if i.isSubimage() {
|
||||||
panic("render to a subimage is not implemented")
|
panic("render to a subimage is not implemented")
|
||||||
}
|
}
|
||||||
|
i.resolvePixelsToSet(false)
|
||||||
s := i.Bounds().Size()
|
s := i.Bounds().Size()
|
||||||
if l := 4 * s.X * s.Y; len(p) != l {
|
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))
|
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
|
const w, h = 256, 256
|
||||||
img, _ := NewImage(w, h, FilterDefault)
|
img, _ := NewImage(w, h, FilterDefault)
|
||||||
img.ReplacePixels(make([]byte, 4*w*h))
|
img.ReplacePixels(make([]byte, 4*w*h))
|
||||||
@ -1353,3 +1353,97 @@ func TestReplacePixelsAfterClear(t *testing.T) {
|
|||||||
|
|
||||||
// The test passes if this doesn't crash.
|
// 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