Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove x,y,w,h from PixMap.Copy, Merge and Foreach #123

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion devtools/internal/inspector/spr.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func (m *sprSelectionMode) update(spr *Spr) {
}

func (m *sprSelectionMode) draw() {
pi.SprSheet().Copy(0, 0, pi.SprSheet().Width(), pi.SprSheet().Height(), pi.Scr(), m.camera.X, m.camera.Y)
pi.SprSheet().Copy(pi.Scr(), m.camera.X, m.camera.Y)

mouseX, mouseY := pi.MousePos.X, pi.MousePos.Y

Expand Down
9 changes: 6 additions & 3 deletions examples/pixmap/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,23 @@ func main() {
pi.Load(resources)

// copy from sprite-sheet to sprite-sheet:
pi.SprSheet().Copy(10, 0, 100, 100, pi.SprSheet(), 0, 0)
src := pi.SprSheet().WithClip(10, 0, 100, 100)
src.Copy(pi.SprSheet(), 0, 0)

// draw a filled rectangle directly to sprite-sheet:
pi.SprSheet().RectFill(60, 30, 70, 40, 7)

// merge from sprite-sheet to screen using custom merge function, which merges two lines
pi.SprSheet().Merge(-1, -1, 103, 70, pi.Scr(), -1, -1, func(dst, src []byte) {
src = pi.SprSheet().WithClip(-1, -1, 103, 70)
src.Merge(pi.Scr(), -1, -1, func(dst, src []byte) {
for x := 0; x < len(dst); x++ {
dst[x] += pi.Pal[src[x]] + 1
}
})

// update each line in a loop:
pi.Scr().Foreach(10, 10, 16, 16, func(x, y int, line []byte) {
src = pi.Scr().WithClip(10, 10, 16, 16)
src.Foreach(func(x, y int, line []byte) {
for i := 0; i < len(line); i++ {
line[i] = byte(i)
}
Expand Down
6 changes: 3 additions & 3 deletions internal/bench/pixmap_bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func BenchmarkPixMapPointer(b *testing.B) {
func BenchmarkCopy(b *testing.B) {
runBenchmarks(b, func(res Resolution) {
for i := 0; i < 100; i++ {
pi.SprSheet().Copy(0, 0, 16, 16, pi.Scr(), 16, 16) // 2x times faster than SprSize
pi.SprSheet().WithClip(0, 0, 16, 16).Copy(pi.Scr(), 16, 16) // 2x times faster than SprSize
}
})
}
Expand All @@ -38,7 +38,7 @@ func SrcAtop(dst, src []byte) { copy(dst, src) }
func BenchmarkMerge(b *testing.B) {
runBenchmarks(b, func(res Resolution) {
for i := 0; i < 100; i++ {
pi.SprSheet().Merge(0, 0, 16, 16, pi.Scr(), 16, 16, SrcAtop) // FAST! NOT AS FAST AS COPY BUT THE PERF IS GREAT!
pi.SprSheet().WithClip(0, 0, 16, 16).Merge(pi.Scr(), 16, 16, SrcAtop) // FAST! NOT AS FAST AS COPY BUT THE PERF IS GREAT!
}
})
}
Expand All @@ -47,7 +47,7 @@ func BenchmarkForeach(b *testing.B) {
src := make([]byte, 16)
runBenchmarks(b, func(res Resolution) {
for i := 0; i < 100; i++ {
pi.Scr().Foreach(0, 0, 16, 16, func(x, y int, dst []byte) { copy(dst, src) })
pi.Scr().WithClip(0, 0, 16, 16).Foreach(func(x, y int, dst []byte) { copy(dst, src) })
}
})
}
9 changes: 5 additions & 4 deletions internal/fuzz/pixmap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ func FuzzPixMap_Pointer(f *testing.F) {
func FuzzPixMap_Foreach(f *testing.F) {
pixMap := pi.NewPixMap(2, 3)
f.Fuzz(func(t *testing.T, x, y, w, h, dstX, dstY int) {
pixMap.Foreach(x, y, w, h, func(x, y int, dst []byte) {
src := pixMap.WithClip(x, y, w, h)
src.Foreach(func(x, y int, dst []byte) {
dst[0] = byte(x + y)
})
})
Expand All @@ -33,7 +34,7 @@ func FuzzPixMap_Copy_Src_Bigger(f *testing.F) {
dst := pi.NewPixMap(2, 3)

f.Fuzz(func(t *testing.T, x, y, w, h, dstX, dstY int) {
src.Copy(x, y, w, h, dst, dstX, dstY)
src.WithClip(x, y, w, h).Copy(dst, dstX, dstY)
})
}

Expand All @@ -42,7 +43,7 @@ func FuzzPixMap_Copy_Dst_Bigger(f *testing.F) {
dst := pi.NewPixMap(5, 4)

f.Fuzz(func(t *testing.T, x, y, w, h, dstX, dstY int) {
src.Copy(x, y, w, h, dst, dstX, dstY)
src.WithClip(x, y, w, h).Copy(dst, dstX, dstY)
})
}

Expand All @@ -51,7 +52,7 @@ func FuzzPixMap_Merge(f *testing.F) {
dst := pi.NewPixMap(4, 3)

f.Fuzz(func(t *testing.T, x, y, w, h, dstX, dstY int) {
src.Merge(x, y, w, h, dst, dstX, dstY, func(dst, src []byte) {
src.WithClip(x, y, w, h).Merge(dst, dstX, dstY, func(dst, src []byte) {
copy(dst, src)
})
})
Expand Down
25 changes: 15 additions & 10 deletions pixmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,9 +226,9 @@ type Pointer struct {
RemainingLines int
}

// Copy copies the region specified by x, y, w, h into dst PixMap at dstX,dstY position.
func (p PixMap) Copy(x, y, w, h int, dst PixMap, dstX, dstY int) {
dstPtr, srcPtr := p.pointersForCopy(x, y, w, h, dst, dstX, dstY)
// Copy copies the clipped pixmap into dst PixMap at dstX,dstY position.
func (p PixMap) Copy(dst PixMap, dstX, dstY int) {
dstPtr, srcPtr := p.pointersForCopy(dst, dstX, dstY)

remainingLines := MinInt(dstPtr.RemainingLines, srcPtr.RemainingLines)

Expand All @@ -247,8 +247,8 @@ func (p PixMap) Copy(x, y, w, h int, dst PixMap, dstX, dstY int) {
}

// Merge merges destination with source by running merge operation for each destination line.
func (p PixMap) Merge(x, y, w, h int, dst PixMap, dstX, dstY int, merge func(dst, src []byte)) {
dstPtr, srcPtr := p.pointersForCopy(x, y, w, h, dst, dstX, dstY)
func (p PixMap) Merge(dst PixMap, dstX, dstY int, merge func(dst, src []byte)) {
dstPtr, srcPtr := p.pointersForCopy(dst, dstX, dstY)

remainingLines := MinInt(dstPtr.RemainingLines, srcPtr.RemainingLines)

Expand All @@ -266,14 +266,19 @@ func (p PixMap) Merge(x, y, w, h int, dst PixMap, dstX, dstY int, merge func(dst
}
}

// Foreach runs the update function on PixMap fragment specified by x, y, w and h.
// Foreach runs the update function on clipped PixMap.
//
// The update function accepts entire line to increase the performance.
func (p PixMap) Foreach(x, y, w, h int, update func(x, y int, dst []byte)) {
func (p PixMap) Foreach(update func(x, y int, dst []byte)) {
if update == nil {
return
}

x := p.clip.X
y := p.clip.Y
w := p.clip.W
h := p.clip.H

ptr, ok := p.Pointer(x, y, w, h)
if !ok {
return
Expand Down Expand Up @@ -334,9 +339,9 @@ func (p PixMap) lineOfColor(col byte, length int) []byte {
return line
}

func (p PixMap) pointersForCopy(srcX int, srcY int, w int, h int, dst PixMap, dstX int, dstY int) (Pointer, Pointer) {
dstPtr, _ := dst.Pointer(dstX, dstY, w, h)
srcPtr, _ := p.Pointer(srcX, srcY, w, h)
func (p PixMap) pointersForCopy(dst PixMap, dstX int, dstY int) (Pointer, Pointer) {
dstPtr, _ := dst.Pointer(dstX, dstY, p.clip.W, p.clip.H)
srcPtr, _ := p.Pointer(p.clip.X, p.clip.Y, p.clip.W, p.clip.H)

// both maps must be moved by the same DeltaX and DeltaY
if srcPtr.DeltaX > dstPtr.DeltaX {
Expand Down
38 changes: 24 additions & 14 deletions pixmap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -351,14 +351,15 @@ func TestPixMap_Pointer(t *testing.T) {
func TestPixMap_Foreach(t *testing.T) {
t.Run("should not do anything when update function is nil", func(t *testing.T) {
pixMap := pi.NewPixMap(2, 2)
pixMap.Foreach(0, 0, 2, 2, nil)
pixMap.Foreach(nil)
})

t.Run("should run update line number of times", func(t *testing.T) {
t.Run("inside clipping range", func(t *testing.T) {
pixMap := pi.NewPixMap(1, 3)
timesRun := 0
pixMap.Foreach(0, 0, 1, 2, func(x, y int, dst []byte) {
src := pixMap.WithClip(0, 0, 1, 2)
src.Foreach(func(x, y int, dst []byte) {
timesRun++
})
assert.Equal(t, 2, timesRun)
Expand All @@ -367,7 +368,8 @@ func TestPixMap_Foreach(t *testing.T) {
t.Run("outside clipping range", func(t *testing.T) {
pixMap := pi.NewPixMap(1, 3)
timesRun := 0
pixMap.Foreach(0, 0, 1, 4, func(x, y int, dst []byte) {
src := pixMap.WithClip(0, 0, 1, 4)
src.Foreach(func(x, y int, dst []byte) {
timesRun++
})
assert.Equal(t, 3, timesRun)
Expand All @@ -380,15 +382,17 @@ func TestPixMap_Foreach(t *testing.T) {

t.Run("x=0", func(t *testing.T) {
var lines [][]byte
pixMap.Foreach(0, 0, 2, 3, func(x, y int, dst []byte) {
src := pixMap.WithClip(0, 0, 2, 3)
src.Foreach(func(x, y int, dst []byte) {
lines = append(lines, dst)
})
assert.Equal(t, [][]byte{{0, 1}, {3, 4}, {6, 7}}, lines)
})

t.Run("x=1", func(t *testing.T) {
var lines [][]byte
pixMap.Foreach(1, 0, 2, 3, func(x, y int, dst []byte) {
src := pixMap.WithClip(1, 0, 2, 3)
src.Foreach(func(x, y int, dst []byte) {
lines = append(lines, dst)
})
assert.Equal(t, [][]byte{{1, 2}, {4, 5}, {7, 8}}, lines)
Expand All @@ -401,15 +405,17 @@ func TestPixMap_Foreach(t *testing.T) {

t.Run("inside clipping range", func(t *testing.T) {
var coords []pi.Position
pixMap.Foreach(1, 1, 2, 2, func(x, y int, dst []byte) {
src := pixMap.WithClip(1, 1, 2, 2)
src.Foreach(func(x, y int, dst []byte) {
coords = append(coords, pi.Position{X: x, Y: y})
})
assert.Equal(t, []pi.Position{{X: 1, Y: 1}, {X: 1, Y: 2}}, coords)
})

t.Run("outside clipping range", func(t *testing.T) {
var coords []pi.Position
pixMap.Foreach(-1, -1, 3, 3, func(x, y int, dst []byte) {
src := pixMap.WithClip(-1, -1, 3, 3)
src.Foreach(func(x, y int, dst []byte) {
coords = append(coords, pi.Position{X: x, Y: y})
})
assert.Equal(t, []pi.Position{{X: 0, Y: 0}, {X: 0, Y: 1}}, coords)
Expand All @@ -422,14 +428,16 @@ func TestPixMap_Foreach(t *testing.T) {
update := func(x, y int, dst []byte) {
executed = true
}
pixMap.Foreach(0, 0, 0, 1, update) // width = 0
src := pixMap.WithClip(0, 0, 0, 1) // width = 0
src.Foreach(update)
assert.False(t, executed)
})

t.Run("should update pixels", func(t *testing.T) {
pixMap := pi.NewPixMap(2, 3)
i := byte(1)
pixMap.Foreach(0, 0, 2, 3, func(x, y int, dst []byte) {
src := pixMap.WithClip(0, 0, 2, 3)
src.Foreach(func(x, y int, dst []byte) {
dst[0] = i
dst[1] = i + 1
i += 2
Expand All @@ -442,7 +450,7 @@ func TestPixMap_Copy(t *testing.T) {
testPixMapCopy(t, pi.PixMap.Copy)
}

func testPixMapCopy(t *testing.T, merge func(pi.PixMap, int, int, int, int, pi.PixMap, int, int)) {
func testPixMapCopy(t *testing.T, merge func(pi.PixMap, pi.PixMap, int, int)) {
t.Run("src bigger than dst", func(t *testing.T) {
src := pi.NewPixMapWithPix([]byte{
1, 2, 3,
Expand Down Expand Up @@ -488,7 +496,8 @@ func testPixMapCopy(t *testing.T, merge func(pi.PixMap, int, int, int, int, pi.P
for name, test := range tests {
t.Run(name, func(t *testing.T) {
dst := pi.NewPixMap(dstWidth, dstHeight)
merge(src, test.x, test.y, test.w, test.h, dst, test.dstX, test.dstY)
source := src.WithClip(test.x, test.y, test.w, test.h)
merge(source, dst, test.dstX, test.dstY)
assert.Equal(t, test.expected, dst.Pix())
})
}
Expand Down Expand Up @@ -517,16 +526,17 @@ func testPixMapCopy(t *testing.T, merge func(pi.PixMap, int, int, int, int, pi.P
for name, test := range tests {
t.Run(name, func(t *testing.T) {
dst := pi.NewPixMap(dstWidth, dstHeight)
merge(src, test.x, test.y, test.w, test.h, dst, test.dstX, test.dstY)
source := src.WithClip(test.x, test.y, test.w, test.h)
merge(source, dst, test.dstX, test.dstY)
assert.Equal(t, test.expected, dst.Pix())
})
}
})
}

func TestPixMap_Merge(t *testing.T) {
testPixMapCopy(t, func(src pi.PixMap, x int, y int, w int, h int, dst pi.PixMap, dstX int, dstY int) {
src.Merge(x, y, w, h, dst, dstX, dstY, func(dst, src []byte) {
testPixMapCopy(t, func(src pi.PixMap, dst pi.PixMap, dstX int, dstY int) {
src.Merge(dst, dstX, dstY, func(dst, src []byte) {
copy(dst, src)
})
})
Expand Down