diff --git a/_examples/stretchtest/gamepad.png b/_examples/stretchtest/gamepad.png new file mode 100644 index 0000000..43fb08d Binary files /dev/null and b/_examples/stretchtest/gamepad.png differ diff --git a/_examples/stretchtest/main.go b/_examples/stretchtest/main.go new file mode 100644 index 0000000..f726986 --- /dev/null +++ b/_examples/stretchtest/main.go @@ -0,0 +1,43 @@ +// Copyright 2025 Jacek Olszak +// This code is licensed under MIT license (see LICENSE for details) + +package main + +import ( + _ "embed" + "github.com/elgopher/pi" + "github.com/elgopher/pi/piebiten" + "github.com/elgopher/pi/pikey" + "github.com/elgopher/pi/piscope" +) + +//go:embed "gamepad.png" +var gamepadPNG []byte + +func main() { + pi.SetScreenSize(100, 60) + pi.Palette = pi.DecodePalette(gamepadPNG) + canvas := pi.DecodeCanvas(gamepadPNG) + spr := pi.SpriteFrom(canvas, 58, 26, 9, 9) + piscope.Start() + posx := 0 + posy := 0 + pi.Update = func() { + if pikey.Duration(pikey.Left) > 0 { + posx-- + } + if pikey.Duration(pikey.Right) > 0 { + posx++ + } + if pikey.Duration(pikey.Up) > 0 { + posy-- + } + if pikey.Duration(pikey.Down) > 0 { + posy++ + } + } + pi.Draw = func() { + pi.DrawSprite(spr, posx, posy) + } + piebiten.Run() +} diff --git a/internal/test/stretch/sprite-1x1.png b/internal/test/stretch/sprite-1x1.png new file mode 100644 index 0000000..ce898bb Binary files /dev/null and b/internal/test/stretch/sprite-1x1.png differ diff --git a/internal/test/stretch/sprite-2x1.png b/internal/test/stretch/sprite-2x1.png new file mode 100644 index 0000000..7495513 Binary files /dev/null and b/internal/test/stretch/sprite-2x1.png differ diff --git a/internal/test/stretch/sprite-3x3.png b/internal/test/stretch/sprite-3x3.png new file mode 100644 index 0000000..3264241 Binary files /dev/null and b/internal/test/stretch/sprite-3x3.png differ diff --git a/internal/test/stretch/sprite-3x4.png b/internal/test/stretch/sprite-3x4.png new file mode 100644 index 0000000..93b066d Binary files /dev/null and b/internal/test/stretch/sprite-3x4.png differ diff --git a/internal/test/stretch/sprite-3x5.png b/internal/test/stretch/sprite-3x5.png new file mode 100644 index 0000000..f3d5d2b Binary files /dev/null and b/internal/test/stretch/sprite-3x5.png differ diff --git a/internal/test/stretch/sprite-3x6.png b/internal/test/stretch/sprite-3x6.png new file mode 100644 index 0000000..c8e35fb Binary files /dev/null and b/internal/test/stretch/sprite-3x6.png differ diff --git a/internal/test/stretch/sprite-4x3.png b/internal/test/stretch/sprite-4x3.png new file mode 100644 index 0000000..2825b64 Binary files /dev/null and b/internal/test/stretch/sprite-4x3.png differ diff --git a/internal/test/stretch/sprite-5x3.png b/internal/test/stretch/sprite-5x3.png new file mode 100644 index 0000000..b87d52a Binary files /dev/null and b/internal/test/stretch/sprite-5x3.png differ diff --git a/internal/test/stretch/sprite-6x3.png b/internal/test/stretch/sprite-6x3.png new file mode 100644 index 0000000..c983631 Binary files /dev/null and b/internal/test/stretch/sprite-6x3.png differ diff --git a/internal/test/stretch/sprite-6x6.png b/internal/test/stretch/sprite-6x6.png new file mode 100644 index 0000000..e17effd Binary files /dev/null and b/internal/test/stretch/sprite-6x6.png differ diff --git a/internal/test/stretch/sprite.png b/internal/test/stretch/sprite.png new file mode 100644 index 0000000..a973bea Binary files /dev/null and b/internal/test/stretch/sprite.png differ diff --git a/sprite.go b/sprite.go index 4773799..a6c78fc 100644 --- a/sprite.go +++ b/sprite.go @@ -5,6 +5,7 @@ package pi import ( "fmt" + "github.com/elgopher/pi/pimath" ) // DrawSprite draws the given sprite at (dx, dy) on the current draw target. @@ -72,31 +73,45 @@ func Stretch(sprite Sprite, dx, dy, dw, dh int) { targetStride := drawTarget.width - int(dst.W) srcSource := sprite.Source - if sprite.FlipY { - src.Y += float64(dh-1) * stepY - stepY *= -1 - } + stepXAbs := src.W / dst.W + stepYAbs := src.H / dst.H + // start sampling from half-step offsets if sprite.FlipX { - src.X += float64(dw-1) * stepX - stepX *= -1 + src.X += src.W - stepXAbs/2 + stepXAbs = -stepXAbs + } else { + src.X += stepXAbs / 2 } - srcX, srcY := src.X, src.Y + if sprite.FlipY { + src.Y += src.H - stepYAbs/2 + stepYAbs = -stepYAbs + } else { + src.Y += stepYAbs / 2 + } + + srcY := src.Y + + srcMaxX := int(src.X + src.W) + srcMaxY := int(src.Y + src.H) for line := 0.0; line < dst.H; line++ { - srcLineIdx := int(srcY) * srcSource.width // multiplication, but only once per line, so it's not a performance problem + syIndex := pimath.Clamp(int(srcY), 0, srcMaxY-1) + srcLineIdx := syIndex * srcSource.width + + srcX := src.X + for cell := 0; cell < int(dst.W); cell++ { + sxIndex := pimath.Clamp(int(srcX), 0, srcMaxX-1) - for cell := 0.0; cell < dst.W; cell++ { - sourceColor := srcSource.data[srcLineIdx+int(srcX)] & ReadMask + sourceColor := srcSource.data[srcLineIdx+sxIndex] & ReadMask targetColor := drawTarget.data[targetIdx] & TargetMask drawTarget.data[targetIdx] = ColorTables[(sourceColor|targetColor)>>6][sourceColor&(MaxColors-1)][targetColor&(MaxColors-1)] - srcX += stepX + srcX += stepXAbs targetIdx++ } - srcX = src.X - srcY += stepY + srcY += stepYAbs targetIdx += targetStride } } diff --git a/sprite_test.go b/sprite_test.go index e34acd3..3d289f1 100644 --- a/sprite_test.go +++ b/sprite_test.go @@ -4,12 +4,100 @@ package pi_test import ( + _ "embed" + "github.com/elgopher/pi/pitest" "testing" "github.com/elgopher/pi" ) +var ( + //go:embed internal/test/stretch/sprite.png + spritePNG []byte + //go:embed internal/test/stretch/sprite-3x3.png + sprite3x3PNG []byte + //go:embed internal/test/stretch/sprite-6x6.png + sprite6x6PNG []byte + //go:embed internal/test/stretch/sprite-6x3.png + sprite6x3PNG []byte + //go:embed internal/test/stretch/sprite-3x6.png + sprite3x6PNG []byte + //go:embed internal/test/stretch/sprite-4x3.png + sprite4x3PNG []byte + //go:embed internal/test/stretch/sprite-5x3.png + sprite5x3PNG []byte + //go:embed internal/test/stretch/sprite-3x4.png + sprite3x4PNG []byte + //go:embed internal/test/stretch/sprite-3x5.png + sprite3x5PNG []byte + //go:embed internal/test/stretch/sprite-1x1.png + sprite1x1PNG []byte + //go:embed internal/test/stretch/sprite-2x1.png + sprite2x1PNG []byte +) + func TestStretch(t *testing.T) { + t.Run("inside screen", func(t *testing.T) { + tests := map[string]struct { + dw, dh int + png []byte + }{ + "3x3": { + dw: 3, dh: 3, + png: sprite3x3PNG, + }, + "6x6": { + dw: 6, dh: 6, + png: sprite6x6PNG, + }, + "6x3": { + dw: 6, dh: 3, + png: sprite6x3PNG, + }, + "3x6": { + dw: 3, dh: 6, + png: sprite3x6PNG, + }, + "4x3": { + dw: 4, dh: 3, + png: sprite4x3PNG, + }, + "5x3": { + dw: 5, dh: 3, + png: sprite5x3PNG, + }, + "3x4": { + dw: 3, dh: 4, + png: sprite3x4PNG, + }, + "3x5": { + dw: 3, dh: 5, + png: sprite3x5PNG, + }, + "1x1": { + dw: 1, dh: 1, + png: sprite1x1PNG, + }, + "2x1": { + dw: 2, dh: 1, + png: sprite2x1PNG, + }, + } + for testName, testCase := range tests { + t.Run(testName, func(t *testing.T) { + pi.SetScreenSize(8, 8) + pi.Cls() + pi.Palette = pi.DecodePalette(spritePNG) + sprite := pi.SpriteFrom(pi.DecodeCanvas(spritePNG), 1, 1, 3, 3) + // when + pi.Stretch(sprite, 1, 1, testCase.dw, testCase.dh) + // then + expected := pi.DecodeCanvas(testCase.png) + pitest.AssertSurfaceEqual(t, expected, pi.Screen()) + }) + } + }) + // temporary test dst := pi.NewCanvas(16, 16) pi.SetDrawTarget(dst) diff --git a/surface_test.go b/surface_test.go index 97734da..92a12da 100644 --- a/surface_test.go +++ b/surface_test.go @@ -375,7 +375,7 @@ func BenchmarkDrawCanvas(b *testing.B) { src.Clear(7) for b.Loop() { - pi.DrawCanvas(src, 130, 130) + pi.DrawCanvas(src, 130, 130) // 2256 ns/op } }