Skip to content
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
Binary file added _examples/stretchtest/gamepad.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
43 changes: 43 additions & 0 deletions _examples/stretchtest/main.go
Original file line number Diff line number Diff line change
@@ -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()
}
Binary file added internal/test/stretch/sprite-1x1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added internal/test/stretch/sprite-2x1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added internal/test/stretch/sprite-3x3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added internal/test/stretch/sprite-3x4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added internal/test/stretch/sprite-3x5.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added internal/test/stretch/sprite-3x6.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added internal/test/stretch/sprite-4x3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added internal/test/stretch/sprite-5x3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added internal/test/stretch/sprite-6x3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added internal/test/stretch/sprite-6x6.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added internal/test/stretch/sprite.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
41 changes: 28 additions & 13 deletions sprite.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
}
}
Expand Down
88 changes: 88 additions & 0 deletions sprite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion surface_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

Expand Down