Skip to content

Commit 41575cf

Browse files
committed
Implement Palette Swapping
Add a new Pal, PalDisplay and PalReset functions. Pal replaces color with another one for all subsequent drawings (it is changing the so-called draw palette). PalDisplay replaces color with another one for the whole screen at the end of a frame (it is changing the so-called display palette). PalReset resets all swapped colors for all palettes.
1 parent a860e8a commit 41575cf

File tree

9 files changed

+189
-9
lines changed

9 files changed

+189
-9
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ See also [examples](examples) directory and [documentation](https://pkg.go.dev/g
5555
* [x] drawing sprites and pixels with camera and clipping support
5656
* [x] add the ability to directly access pixels on the screen and sprite-sheet
5757
* [x] palette transparency
58-
* [ ] palette swapping
58+
* [x] palette swapping
5959
* [ ] printing text on the screen
6060
* [ ] drawing shapes
6161
* [ ] lines, rectangles, circles, ovals

ebitengine.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ func (e *ebitengineGame) replaceScreenPixels(screen *ebiten.Image) {
6969

7070
offset := 0
7171
for _, col := range ScreenData {
72-
rgb := Palette[col]
72+
rgb := Palette[displayPalette[col]]
7373
e.screenDataRGBA[offset] = rgb.R
7474
e.screenDataRGBA[offset+1] = rgb.G
7575
e.screenDataRGBA[offset+2] = rgb.B

examples/hello/main.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"github.com/elgopher/pi"
88
)
99

10-
// Go compiler will automatically add sprite sheet to binary
1110
//go:embed sprite-sheet.png
1211
var resources embed.FS
1312

examples/pal/main.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Example showing practical use of palette swapping. Drawing same sprite with different palette
2+
// generates tens of different sprites.
3+
package main
4+
5+
import (
6+
"embed"
7+
8+
"github.com/elgopher/pi"
9+
)
10+
11+
//go:embed sprite-sheet.png
12+
var resources embed.FS
13+
14+
var (
15+
eyeColors = [...]byte{0, 1, 3, 4, 12}
16+
skinColors = [...]byte{7, 5, 15}
17+
hairColors = [...]byte{0, 4, 5, 6, 7, 9, 10}
18+
mouthColors = [...]byte{2, 8}
19+
)
20+
21+
const (
22+
eyes = 1 // color number of eyes in sprite-sheet
23+
skin = 7
24+
mouth = 8
25+
hair = 5
26+
)
27+
28+
func draw() {
29+
pi.Cls()
30+
x, y := 0, 0
31+
32+
for _, eyeColor := range eyeColors {
33+
pi.Pal(eyes, eyeColor)
34+
35+
for _, skinColor := range skinColors {
36+
pi.Pal(skin, skinColor)
37+
38+
for _, hairColor := range hairColors {
39+
pi.Pal(hair, hairColor)
40+
41+
for _, mouthColor := range mouthColors {
42+
pi.Pal(mouth, mouthColor)
43+
// draw the sprite with swapped colors:
44+
pi.Spr(0, x, y)
45+
46+
x += 8
47+
if x >= 128 {
48+
// go to next line
49+
x = 0
50+
y += 8
51+
}
52+
}
53+
}
54+
}
55+
}
56+
}
57+
58+
func main() {
59+
pi.Resources = resources
60+
pi.Draw = draw
61+
pi.RunOrPanic()
62+
}

examples/pal/sprite-sheet.png

196 Bytes
Loading

pi.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ func Boot() error {
125125
Clip(0, 0, scrWidth, scrHeight)
126126
Camera(0, 0)
127127
PaltReset()
128+
PalReset()
128129

129130
return nil
130131
}

screen.go

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ func Pset(x, y int) {
7474
return
7575
}
7676

77-
ScreenData[y*scrWidth+x] = Color
77+
ScreenData[y*scrWidth+x] = drawPalette[Color]
7878
}
7979

8080
// Pget gets a pixel color on the screen.
@@ -266,7 +266,7 @@ func SprSizeFlip(n, x, y int, w, h float64, flipX, flipY bool) {
266266
continue
267267
}
268268

269-
ScreenData[screenOffset+j] = col
269+
ScreenData[screenOffset+j] = drawPalette[col]
270270
}
271271
screenOffset += scrWidth
272272
spriteSheetOffset += spriteSheetStep
@@ -289,3 +289,37 @@ var defaultTransparency = [256]bool{true}
289289
func PaltReset() {
290290
colorIsTransparent = defaultTransparency
291291
}
292+
293+
var drawPalette [256]byte
294+
295+
// Pal replaces color with another one for all subsequent drawings (it is changing
296+
// the so-called draw palette).
297+
//
298+
// Affected functions are Pset, Spr, SprSize and SprSizeFlip.
299+
func Pal(color byte, replacementColor byte) {
300+
drawPalette[color] = replacementColor
301+
}
302+
303+
var displayPalette [256]byte
304+
305+
// PalDisplay replaces color with another one for the whole screen at the end of a frame
306+
// (it is changing the so-called display palette).
307+
func PalDisplay(color byte, replacementColor byte) {
308+
displayPalette[color] = replacementColor
309+
}
310+
311+
// PalSecondary
312+
313+
var notSwappedPalette [256]byte
314+
315+
func init() {
316+
for i := 0; i < 256; i++ {
317+
notSwappedPalette[i] = byte(i)
318+
}
319+
}
320+
321+
// PalReset resets all swapped colors for all palettes.
322+
func PalReset() {
323+
drawPalette = notSwappedPalette
324+
displayPalette = notSwappedPalette
325+
}

screen_bench_test.go

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,12 +80,44 @@ func BenchmarkSprSizeFlip(b *testing.B) {
8080
})
8181
}
8282

83+
func BenchmarkPalt(b *testing.B) {
84+
runBenchmarks(b, func(res Resolution) {
85+
for i := 0; i < 100; i++ { // Palt is too fast
86+
pi.Palt(byte(i), true)
87+
}
88+
})
89+
}
90+
8391
func BenchmarkPaltReset(b *testing.B) {
84-
for i := 0; i < b.N; i++ {
85-
runBenchmarks(b, func(res Resolution) {
92+
runBenchmarks(b, func(res Resolution) {
93+
for i := 0; i < 100; i++ { // PaltReset is too fast
8694
pi.PaltReset()
87-
})
88-
}
95+
}
96+
})
97+
}
98+
99+
func BenchmarkPal(b *testing.B) {
100+
runBenchmarks(b, func(res Resolution) {
101+
for i := 0; i < 100; i++ { // Pal is too fast
102+
pi.Pal(byte(i), byte(i+1))
103+
}
104+
})
105+
}
106+
107+
func BenchmarkPalDisplay(b *testing.B) {
108+
runBenchmarks(b, func(res Resolution) {
109+
for i := 0; i < 100; i++ { // PalDisplay is too fast
110+
pi.PalDisplay(byte(i), byte(i+1))
111+
}
112+
})
113+
}
114+
115+
func BenchmarkPalReset(b *testing.B) {
116+
runBenchmarks(b, func(res Resolution) {
117+
for i := 0; i < 100; i++ { // PalReset is too fast
118+
pi.PalReset()
119+
}
120+
})
89121
}
90122

91123
type Resolution struct{ W, H int }

screen_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,31 @@ func TestPset(t *testing.T) {
158158
})
159159
}
160160
})
161+
162+
t.Run("should draw swapped color", func(t *testing.T) {
163+
pi.ScreenWidth = 1
164+
pi.ScreenHeight = 1
165+
pi.BootOrPanic()
166+
pi.Color = 1
167+
pi.Pal(1, 2)
168+
// when
169+
pi.Pset(0, 0)
170+
// then
171+
assert.Equal(t, []byte{2}, pi.ScreenData)
172+
})
173+
174+
t.Run("should draw original color after PalReset", func(t *testing.T) {
175+
pi.ScreenWidth = 1
176+
pi.ScreenHeight = 1
177+
pi.BootOrPanic()
178+
pi.Color = 1
179+
pi.Pal(1, 2)
180+
pi.PalReset()
181+
// when
182+
pi.Pset(0, 0)
183+
// then
184+
assert.Equal(t, []byte{1}, pi.ScreenData)
185+
})
161186
}
162187

163188
func TestPget(t *testing.T) {
@@ -478,6 +503,33 @@ func testSpr(t *testing.T, spr func(spriteNo int, x int, y int)) {
478503
expectedScreen := decodePNG(t, "internal/testimage/spr_1_on_top_of_2_trans_50.png")
479504
assert.Equal(t, expectedScreen.Pixels, pi.ScreenData)
480505
})
506+
507+
t.Run("should swap color", func(t *testing.T) {
508+
pi.SpriteSheetWidth, pi.SpriteSheetHeight = 8, 8
509+
pi.ScreenWidth, pi.ScreenHeight = 8, 8
510+
pi.BootOrPanic()
511+
const originalColor byte = 7
512+
const replacementColor byte = 15
513+
pi.Sset(5, 5, originalColor)
514+
pi.Pal(originalColor, replacementColor)
515+
// when
516+
spr(0, 0, 0)
517+
// then
518+
actual := pi.Pget(5, 5)
519+
assert.Equal(t, replacementColor, actual)
520+
})
521+
522+
t.Run("should draw original color after reset", func(t *testing.T) {
523+
boot(8, 8, spriteSheet16x16)
524+
expectedScreen := decodePNG(t, "internal/testimage/spr_0_at_00.png")
525+
// when
526+
pi.Pal(1, 3)
527+
pi.Pal(28, 30)
528+
pi.PalReset()
529+
spr(0, 0, 0)
530+
// then
531+
assert.Equal(t, expectedScreen.Pixels, pi.ScreenData)
532+
})
481533
}
482534

483535
func TestSprSize(t *testing.T) {

0 commit comments

Comments
 (0)