From cb63550d38d4b7305c5760c80845eb5083af9135 Mon Sep 17 00:00:00 2001 From: flga Date: Fri, 26 Apr 2019 14:07:45 +0100 Subject: [PATCH 1/2] sdl: Improve pixel fetching api --- sdl/render.go | 53 +++++++++++++++++++++++++++++ sdl/render_test.go | 84 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 137 insertions(+) diff --git a/sdl/render.go b/sdl/render.go index 1769da69..ea998b1a 100644 --- a/sdl/render.go +++ b/sdl/render.go @@ -64,6 +64,7 @@ static inline void * SDL_RenderGetMetalCommandEncoder(SDL_Renderer *renderer) */ import "C" import ( + "bytes" "reflect" "unsafe" ) @@ -688,6 +689,58 @@ func (renderer *Renderer) ReadPixels(rect *Rect, format uint32, pixels unsafe.Po C.int(pitch)))) } +// GetPixels returns the pixel data of the current rendering target. +// It just wraps ReadPixels in a neater api, as such it has the same implications. +func (renderer *Renderer) GetPixels(rect *Rect, format uint32) ([]byte, error) { + // rect A pointer to the rectangle to read, or NULL for the entire render target. + // format The desired format of the pixel data, or 0 to use the format of the rendering target + // pixels A pointer to be filled in with the pixel data + // pitch The pitch of the pixels parameter. + + if format == 0 || rect == nil { + targetFormat, _, targetWidth, targetHeight, err := renderer.GetRenderTarget().Query() + if err != nil { + return nil, err + } + + if format == 0 { + format = targetFormat + } + if rect == nil { + rect = &Rect{ + W: targetWidth, + H: targetHeight, + } + } + } + + bpp := int32(BytesPerPixel(format)) + + padding := []byte("overflow detector") + paddingLen := int32(len(padding)) + + dataLen := rect.W * rect.H * bpp + buffer := make([]byte, dataLen+paddingLen) + + for i := int32(0); i < paddingLen; i++ { + buffer[dataLen+i] = padding[i] + } + + errCode := C.SDL_RenderReadPixels( + renderer.cptr(), + rect.cptr(), + C.Uint32(format), + unsafe.Pointer(&buffer[0]), + C.int(rect.W*bpp), + ) + + if !bytes.Equal(buffer[dataLen:], padding) { + panic("overflow detected") + } + + return buffer[:dataLen], errorFromInt(int(errCode)) +} + // Present updates the screen with any rendering performed since the previous call. // (https://wiki.libsdl.org/SDL_RenderPresent) func (renderer *Renderer) Present() { diff --git a/sdl/render_test.go b/sdl/render_test.go index 242c67ef..b5344552 100644 --- a/sdl/render_test.go +++ b/sdl/render_test.go @@ -1,7 +1,10 @@ package sdl import ( + "bytes" "errors" + "image/color" + "reflect" "testing" ) @@ -87,3 +90,84 @@ func TestRenderIntegerScale(t *testing.T) { } }) } + +func TestGetPixels(t *testing.T) { + repeat := bytes.Repeat + line := func(a []byte, b []byte, len int) []byte { + return append(repeat(a, len), repeat(b, len)...) + } + concat := func(a []byte, b []byte) []byte { + return append(a, b...) + } + rgba := func(c color.RGBA) []byte { + return []byte{c.R, c.G, c.B, c.A} + } + bgra := func(c color.RGBA) []byte { + return []byte{c.A, c.R, c.G, c.B} + } + drawRect := func(renderer *Renderer, rect *Rect, c color.RGBA) { + if err := renderer.SetDrawColor(c.R, c.G, c.B, c.A); err != nil { + t.Fatalf("unable to set draw color: %s", err) + } + if err := renderer.FillRect(rect); err != nil { + t.Fatalf("unable to fill rect: %s", err) + } + } + + _, r, err := CreateWindowAndRenderer(50, 50, 0) + if err != nil { + t.Fatalf("unable to create renderer: %s", err) + } + + c1, r1 := color.RGBA{151, 237, 158, 255}, &Rect{X: 0, Y: 0, W: 25, H: 25} + c2, r2 := color.RGBA{151, 213, 237, 255}, &Rect{X: 25, Y: 0, W: 25, H: 25} + c3, r3 := color.RGBA{224, 151, 237, 255}, &Rect{X: 0, Y: 25, W: 25, H: 25} + c4, r4 := color.RGBA{237, 151, 151, 255}, &Rect{X: 25, Y: 25, W: 25, H: 25} + + drawRect(r, r1, c1) + drawRect(r, r2, c2) + drawRect(r, r3, c3) + drawRect(r, r4, c4) + + tests := []struct { + name string + rect *Rect + fmt uint32 + want []byte + }{ + {"r1 ABGR8888", r1, PIXELFORMAT_ABGR8888, repeat(rgba(c1), 25*25)}, + {"r2 ABGR8888", r2, PIXELFORMAT_ABGR8888, repeat(rgba(c2), 25*25)}, + {"r3 ABGR8888", r3, PIXELFORMAT_ABGR8888, repeat(rgba(c3), 25*25)}, + {"r4 ABGR8888", r4, PIXELFORMAT_ABGR8888, repeat(rgba(c4), 25*25)}, + + {"r1 + r2 ABGR8888", &Rect{X: 0, Y: 0, W: 50, H: 25}, PIXELFORMAT_ABGR8888, repeat(line(rgba(c1), rgba(c2), 25), 25)}, + {"r3 + r4 ABGR8888", &Rect{X: 0, Y: 25, W: 50, H: 25}, PIXELFORMAT_ABGR8888, repeat(line(rgba(c3), rgba(c4), 25), 25)}, + {"r1 + r3 ABGR8888", &Rect{X: 0, Y: 0, W: 25, H: 50}, PIXELFORMAT_ABGR8888, concat(repeat(rgba(c1), 25*25), repeat(rgba(c3), 25*25))}, + {"r2 + r4 ABGR8888", &Rect{X: 25, Y: 0, W: 25, H: 50}, PIXELFORMAT_ABGR8888, concat(repeat(rgba(c2), 25*25), repeat(rgba(c4), 25*25))}, + + {"full ABGR8888", &Rect{X: 0, Y: 0, W: 50, H: 50}, PIXELFORMAT_ABGR8888, concat(repeat(line(rgba(c1), rgba(c2), 25), 25), repeat(line(rgba(c3), rgba(c4), 25), 25))}, + + {"r1 BGRA8888", r1, PIXELFORMAT_BGRA8888, repeat(bgra(c1), 25*25)}, + {"r2 BGRA8888", r2, PIXELFORMAT_BGRA8888, repeat(bgra(c2), 25*25)}, + {"r3 BGRA8888", r3, PIXELFORMAT_BGRA8888, repeat(bgra(c3), 25*25)}, + {"r4 BGRA8888", r4, PIXELFORMAT_BGRA8888, repeat(bgra(c4), 25*25)}, + + {"r1 + r2 ABGR8888", &Rect{X: 0, Y: 0, W: 50, H: 25}, PIXELFORMAT_BGRA8888, repeat(line(bgra(c1), bgra(c2), 25), 25)}, + {"r3 + r4 ABGR8888", &Rect{X: 0, Y: 25, W: 50, H: 25}, PIXELFORMAT_BGRA8888, repeat(line(bgra(c3), bgra(c4), 25), 25)}, + {"r1 + r3 ABGR8888", &Rect{X: 0, Y: 0, W: 25, H: 50}, PIXELFORMAT_BGRA8888, concat(repeat(bgra(c1), 25*25), repeat(bgra(c3), 25*25))}, + {"r2 + r4 ABGR8888", &Rect{X: 25, Y: 0, W: 25, H: 50}, PIXELFORMAT_BGRA8888, concat(repeat(bgra(c2), 25*25), repeat(bgra(c4), 25*25))}, + + {"full ABGR8888", &Rect{X: 0, Y: 0, W: 50, H: 50}, PIXELFORMAT_BGRA8888, concat(repeat(line(bgra(c1), bgra(c2), 25), 25), repeat(line(bgra(c3), bgra(c4), 25), 25))}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := r.GetPixels(tt.rect, tt.fmt) + if err != nil { + t.Fatalf("unable to get pixels for r1: %s", err) + } + if !reflect.DeepEqual(got, tt.want) { + t.Fatalf("wanted %v got: %v", tt.want, got) + } + }) + } +} From c6aab4d12dee1cb820f6253140034d74c2b4fe0d Mon Sep 17 00:00:00 2001 From: flga Date: Sat, 27 Apr 2019 07:54:36 +0100 Subject: [PATCH 2/2] sdl: implement RGBA pixel fetching tests --- sdl/render_test.go | 200 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 151 insertions(+), 49 deletions(-) diff --git a/sdl/render_test.go b/sdl/render_test.go index b5344552..5bb21dd8 100644 --- a/sdl/render_test.go +++ b/sdl/render_test.go @@ -1,11 +1,11 @@ package sdl import ( - "bytes" "errors" "image/color" "reflect" "testing" + "unsafe" ) func TestRenderIntegerScale(t *testing.T) { @@ -92,81 +92,183 @@ func TestRenderIntegerScale(t *testing.T) { } func TestGetPixels(t *testing.T) { - repeat := bytes.Repeat - line := func(a []byte, b []byte, len int) []byte { - return append(repeat(a, len), repeat(b, len)...) - } - concat := func(a []byte, b []byte) []byte { - return append(a, b...) + srcFormat := uint32(PIXELFORMAT_RGBA32) + s, err := CreateRGBSurfaceWithFormat(0, 50, 50, 0, srcFormat) + if err != nil { + t.Fatalf("unable to create surface: %s", err) } - rgba := func(c color.RGBA) []byte { - return []byte{c.R, c.G, c.B, c.A} + defer s.Free() + + r, err := CreateSoftwareRenderer(s) + if err != nil { + t.Fatalf("unable to create renderer: %s", err) } - bgra := func(c color.RGBA) []byte { - return []byte{c.A, c.R, c.G, c.B} + defer r.Destroy() + + type quadrant struct { + c color.RGBA + r *Rect + pitch int + pixels []byte } - drawRect := func(renderer *Renderer, rect *Rect, c color.RGBA) { - if err := renderer.SetDrawColor(c.R, c.G, c.B, c.A); err != nil { - t.Fatalf("unable to set draw color: %s", err) + + newQuad := func(c color.RGBA, r *Rect) quadrant { + s, err := CreateRGBSurfaceWithFormat(0, int32(r.W), int32(r.H), 8, srcFormat) + if err != nil { + t.Fatalf("unable to create rgba surface: %s", err) } - if err := renderer.FillRect(rect); err != nil { + defer s.Free() + + if err := s.FillRect(nil, MapRGBA(s.Format, c.R, c.G, c.B, c.A)); err != nil { t.Fatalf("unable to fill rect: %s", err) } + + src := s.Pixels() + q := quadrant{ + c: c, + r: r, + pitch: int(s.Pitch), + pixels: make([]byte, len(src)), + } + copy(q.pixels, src) + return q } - _, r, err := CreateWindowAndRenderer(50, 50, 0) - if err != nil { - t.Fatalf("unable to create renderer: %s", err) + conv := func(q quadrant, format uint32) []byte { + ret := make([]byte, int(q.r.W*q.r.H)*BytesPerPixel(format)) + if err := ConvertPixels( + q.r.W, + q.r.H, + srcFormat, + unsafe.Pointer(&q.pixels[0]), + q.pitch, + format, + unsafe.Pointer(&ret[0]), + int(q.r.W)*BytesPerPixel(format), + ); err != nil { + t.Fatalf("unable to convert pixels to format %s: %s", GetPixelFormatName(uint(format)), err) + } + + return ret + } + + mergeH := func(q1, q2 quadrant, format uint32) []byte { + a := conv(q1, format) + b := conv(q2, format) + pitch := int(q1.r.W) * BytesPerPixel(format) + + ret := make([]byte, 0, len(a)+len(b)) + rows := len(a) / pitch + for i := 0; i < rows; i++ { + ret = append(ret, a[:pitch]...) + ret = append(ret, b[:pitch]...) + a = a[pitch:] + b = b[pitch:] + } + return ret + } + + mergeV := func(q1, q2 quadrant, format uint32) []byte { + a := conv(q1, format) + b := conv(q2, format) + return append(a, b...) } - c1, r1 := color.RGBA{151, 237, 158, 255}, &Rect{X: 0, Y: 0, W: 25, H: 25} - c2, r2 := color.RGBA{151, 213, 237, 255}, &Rect{X: 25, Y: 0, W: 25, H: 25} - c3, r3 := color.RGBA{224, 151, 237, 255}, &Rect{X: 0, Y: 25, W: 25, H: 25} - c4, r4 := color.RGBA{237, 151, 151, 255}, &Rect{X: 25, Y: 25, W: 25, H: 25} + merge := func(q1, q2, q3, q4 quadrant, format uint32) []byte { + a := mergeH(q1, q2, format) + b := mergeH(q3, q4, format) - drawRect(r, r1, c1) - drawRect(r, r2, c2) - drawRect(r, r3, c3) - drawRect(r, r4, c4) + return append(a, b...) + } - tests := []struct { + q1 := newQuad(color.RGBA{151, 237, 158, 255}, &Rect{X: 0, Y: 0, W: 25, H: 25}) + q2 := newQuad(color.RGBA{151, 213, 237, 255}, &Rect{X: 25, Y: 0, W: 25, H: 25}) + q3 := newQuad(color.RGBA{224, 151, 237, 255}, &Rect{X: 0, Y: 25, W: 25, H: 25}) + q4 := newQuad(color.RGBA{237, 151, 151, 255}, &Rect{X: 25, Y: 25, W: 25, H: 25}) + + type test struct { name string rect *Rect fmt uint32 want []byte - }{ - {"r1 ABGR8888", r1, PIXELFORMAT_ABGR8888, repeat(rgba(c1), 25*25)}, - {"r2 ABGR8888", r2, PIXELFORMAT_ABGR8888, repeat(rgba(c2), 25*25)}, - {"r3 ABGR8888", r3, PIXELFORMAT_ABGR8888, repeat(rgba(c3), 25*25)}, - {"r4 ABGR8888", r4, PIXELFORMAT_ABGR8888, repeat(rgba(c4), 25*25)}, + } - {"r1 + r2 ABGR8888", &Rect{X: 0, Y: 0, W: 50, H: 25}, PIXELFORMAT_ABGR8888, repeat(line(rgba(c1), rgba(c2), 25), 25)}, - {"r3 + r4 ABGR8888", &Rect{X: 0, Y: 25, W: 50, H: 25}, PIXELFORMAT_ABGR8888, repeat(line(rgba(c3), rgba(c4), 25), 25)}, - {"r1 + r3 ABGR8888", &Rect{X: 0, Y: 0, W: 25, H: 50}, PIXELFORMAT_ABGR8888, concat(repeat(rgba(c1), 25*25), repeat(rgba(c3), 25*25))}, - {"r2 + r4 ABGR8888", &Rect{X: 25, Y: 0, W: 25, H: 50}, PIXELFORMAT_ABGR8888, concat(repeat(rgba(c2), 25*25), repeat(rgba(c4), 25*25))}, + block := func(format uint32) []test { + name := GetPixelFormatName(uint(format)) + return []test{ + {"q1 " + name, q1.r, format, conv(q1, format)}, + {"q2 " + name, q2.r, format, conv(q2, format)}, + {"q3 " + name, q3.r, format, conv(q3, format)}, + {"q4 " + name, q4.r, format, conv(q4, format)}, + {"q1 + q2 " + name, &Rect{X: 0, Y: 0, W: 50, H: 25}, format, mergeH(q1, q2, format)}, + {"q3 + q4 " + name, &Rect{X: 0, Y: 25, W: 50, H: 25}, format, mergeH(q3, q4, format)}, + {"q1 + q3 " + name, &Rect{X: 0, Y: 0, W: 25, H: 50}, format, mergeV(q1, q3, format)}, + {"q2 + q4 " + name, &Rect{X: 25, Y: 0, W: 25, H: 50}, format, mergeV(q2, q4, format)}, + {"full " + name, &Rect{X: 0, Y: 0, W: 50, H: 50}, format, merge(q1, q2, q3, q4, format)}, + } + } - {"full ABGR8888", &Rect{X: 0, Y: 0, W: 50, H: 50}, PIXELFORMAT_ABGR8888, concat(repeat(line(rgba(c1), rgba(c2), 25), 25), repeat(line(rgba(c3), rgba(c4), 25), 25))}, + for _, q := range []quadrant{q1, q2, q3, q4} { + if err := r.SetDrawColor(q.c.R, q.c.G, q.c.B, q.c.A); err != nil { + t.Fatalf("unable to set draw color: %s", err) + } + if err := r.FillRect(q.r); err != nil { + t.Fatalf("unable to fill rect: %s", err) + } + } - {"r1 BGRA8888", r1, PIXELFORMAT_BGRA8888, repeat(bgra(c1), 25*25)}, - {"r2 BGRA8888", r2, PIXELFORMAT_BGRA8888, repeat(bgra(c2), 25*25)}, - {"r3 BGRA8888", r3, PIXELFORMAT_BGRA8888, repeat(bgra(c3), 25*25)}, - {"r4 BGRA8888", r4, PIXELFORMAT_BGRA8888, repeat(bgra(c4), 25*25)}, + var tests []test + // tests = append(tests, block(PIXELFORMAT_UNKNOWN)...) + // tests = append(tests, block(PIXELFORMAT_INDEX1LSB)...) + // tests = append(tests, block(PIXELFORMAT_INDEX1MSB)...) + // tests = append(tests, block(PIXELFORMAT_INDEX4LSB)...) + // tests = append(tests, block(PIXELFORMAT_INDEX4MSB)...) + // tests = append(tests, block(PIXELFORMAT_INDEX8)...) - {"r1 + r2 ABGR8888", &Rect{X: 0, Y: 0, W: 50, H: 25}, PIXELFORMAT_BGRA8888, repeat(line(bgra(c1), bgra(c2), 25), 25)}, - {"r3 + r4 ABGR8888", &Rect{X: 0, Y: 25, W: 50, H: 25}, PIXELFORMAT_BGRA8888, repeat(line(bgra(c3), bgra(c4), 25), 25)}, - {"r1 + r3 ABGR8888", &Rect{X: 0, Y: 0, W: 25, H: 50}, PIXELFORMAT_BGRA8888, concat(repeat(bgra(c1), 25*25), repeat(bgra(c3), 25*25))}, - {"r2 + r4 ABGR8888", &Rect{X: 25, Y: 0, W: 25, H: 50}, PIXELFORMAT_BGRA8888, concat(repeat(bgra(c2), 25*25), repeat(bgra(c4), 25*25))}, + tests = append(tests, block(PIXELFORMAT_RGB332)...) // ok + tests = append(tests, block(PIXELFORMAT_RGB444)...) // ok + tests = append(tests, block(PIXELFORMAT_RGB555)...) // ok + tests = append(tests, block(PIXELFORMAT_BGR555)...) // ok + tests = append(tests, block(PIXELFORMAT_ARGB4444)...) // ok + tests = append(tests, block(PIXELFORMAT_RGBA4444)...) // ok + tests = append(tests, block(PIXELFORMAT_ABGR4444)...) // ok + tests = append(tests, block(PIXELFORMAT_BGRA4444)...) // ok + tests = append(tests, block(PIXELFORMAT_ARGB1555)...) // ok + tests = append(tests, block(PIXELFORMAT_RGBA5551)...) // ok + tests = append(tests, block(PIXELFORMAT_ABGR1555)...) // ok + tests = append(tests, block(PIXELFORMAT_BGRA5551)...) // ok + tests = append(tests, block(PIXELFORMAT_RGB565)...) // ok + tests = append(tests, block(PIXELFORMAT_BGR565)...) // ok + tests = append(tests, block(uint32(PIXELFORMAT_RGB24))...) // ok + tests = append(tests, block(uint32(PIXELFORMAT_BGR24))...) // ok + tests = append(tests, block(uint32(PIXELFORMAT_RGBA32))...) // ok + tests = append(tests, block(uint32(PIXELFORMAT_ARGB32))...) // ok + tests = append(tests, block(uint32(PIXELFORMAT_BGRA32))...) // ok + tests = append(tests, block(uint32(PIXELFORMAT_ABGR32))...) // ok + tests = append(tests, block(PIXELFORMAT_RGB888)...) // ok + tests = append(tests, block(PIXELFORMAT_RGBX8888)...) // ok + tests = append(tests, block(PIXELFORMAT_BGR888)...) // ok + tests = append(tests, block(PIXELFORMAT_BGRX8888)...) // ok + tests = append(tests, block(PIXELFORMAT_ARGB8888)...) // ok + tests = append(tests, block(PIXELFORMAT_RGBA8888)...) // ok + tests = append(tests, block(PIXELFORMAT_ABGR8888)...) // ok + tests = append(tests, block(PIXELFORMAT_BGRA8888)...) // ok + // tests = append(tests, block(PIXELFORMAT_ARGB2101010)...) // fail + + // tests = append(tests, block(PIXELFORMAT_YV12)...) + // tests = append(tests, block(PIXELFORMAT_IYUV)...) + // tests = append(tests, block(PIXELFORMAT_YUY2)...) + // tests = append(tests, block(PIXELFORMAT_UYVY)...) + // tests = append(tests, block(PIXELFORMAT_YVYU)...) - {"full ABGR8888", &Rect{X: 0, Y: 0, W: 50, H: 50}, PIXELFORMAT_BGRA8888, concat(repeat(line(bgra(c1), bgra(c2), 25), 25), repeat(line(bgra(c3), bgra(c4), 25), 25))}, - } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := r.GetPixels(tt.rect, tt.fmt) if err != nil { - t.Fatalf("unable to get pixels for r1: %s", err) + t.Fatalf("unable to get pixels: %s", err) } if !reflect.DeepEqual(got, tt.want) { - t.Fatalf("wanted %v got: %v", tt.want, got) + t.Fatalf("want %v len(%d), got %v len(%d)", tt.want, len(tt.want), got, len(got)) } }) }