Files
galaxy-game/client/world/drawer_test.go
T
2026-03-07 17:01:22 +02:00

279 lines
5.9 KiB
Go

package world
import (
"image"
"image/color"
"testing"
"github.com/fogleman/gg"
"github.com/stretchr/testify/require"
)
func hasAnyNonTransparentPixel(img image.Image) bool {
b := img.Bounds()
for y := b.Min.Y; y < b.Max.Y; y++ {
for x := b.Min.X; x < b.Max.X; x++ {
_, _, _, a := img.At(x, y).RGBA()
if a != 0 {
return true
}
}
}
return false
}
func pixelHasAlpha(img image.Image, x, y int) bool {
_, _, _, a := img.At(x, y).RGBA()
return a != 0
}
func TestGGDrawerStrokeSequenceProducesPixels(t *testing.T) {
t.Parallel()
dc := gg.NewContext(32, 32)
drawer := &GGDrawer{DC: dc}
drawer.SetStrokeColor(color.RGBA{R: 255, A: 255})
drawer.SetLineWidth(2)
drawer.SetDash(4, 2)
drawer.SetDashOffset(1)
drawer.AddLine(4, 16, 28, 16)
drawer.Stroke()
require.True(t, hasAnyNonTransparentPixel(dc.Image()))
}
func TestGGDrawerFillSequenceProducesPixels(t *testing.T) {
t.Parallel()
dc := gg.NewContext(32, 32)
drawer := &GGDrawer{DC: dc}
drawer.SetFillColor(color.RGBA{G: 255, A: 255})
drawer.AddCircle(16, 16, 6)
drawer.Fill()
require.True(t, pixelHasAlpha(dc.Image(), 16, 16))
}
func TestGGDrawerPointSequenceProducesPixels(t *testing.T) {
t.Parallel()
dc := gg.NewContext(32, 32)
drawer := &GGDrawer{DC: dc}
drawer.SetFillColor(color.RGBA{B: 255, A: 255})
drawer.AddPoint(16, 16, 3)
drawer.Fill()
require.True(t, pixelHasAlpha(dc.Image(), 16, 16))
}
func TestGGDrawerClipRectLimitsDrawing(t *testing.T) {
t.Parallel()
dc := gg.NewContext(32, 32)
drawer := &GGDrawer{DC: dc}
drawer.Save()
drawer.ClipRect(0, 0, 10, 32)
drawer.SetFillColor(color.RGBA{B: 255, A: 255})
drawer.AddCircle(15, 16, 10)
drawer.Fill()
drawer.Restore()
img := dc.Image()
require.True(t, pixelHasAlpha(img, 5, 16))
require.False(t, pixelHasAlpha(img, 15, 16))
}
func TestGGDrawerResetClipClearsClip(t *testing.T) {
t.Parallel()
dc := gg.NewContext(32, 32)
drawer := &GGDrawer{DC: dc}
drawer.ClipRect(0, 0, 10, 32)
drawer.ResetClip()
drawer.SetFillColor(color.RGBA{R: 255, G: 255, A: 255})
drawer.AddCircle(15, 16, 10)
drawer.Fill()
require.True(t, pixelHasAlpha(dc.Image(), 15, 16))
}
func TestGGDrawerClearRectTo_FillsBackground(t *testing.T) {
t.Parallel()
dc := gg.NewContext(10, 10)
dr := &GGDrawer{DC: dc}
// Draw something to ensure we overwrite non-background.
dr.SetFillColor(color.RGBA{R: 255, A: 255})
dr.AddCircle(5, 5, 5)
dr.Fill()
bg := color.RGBA{A: 255} // black
dr.ClearRectTo(1, 1, 2, 2, bg)
img := dc.Image()
r, g, b, a := img.At(1, 1).RGBA()
require.Equal(t, uint32(0), r)
require.Equal(t, uint32(0), g)
require.Equal(t, uint32(0), b)
require.Equal(t, uint32(0xffff), a)
// Pixel outside cleared rect should still have non-zero alpha.
_, _, _, a2 := img.At(5, 5).RGBA()
require.NotEqual(t, uint32(0), a2)
}
func TestGGDrawerSaveRestoreRestoresClipState(t *testing.T) {
t.Parallel()
dc := gg.NewContext(32, 32)
drawer := &GGDrawer{DC: dc}
drawer.Save()
drawer.ClipRect(0, 0, 10, 32)
drawer.Restore()
drawer.SetFillColor(color.RGBA{R: 255, A: 255})
drawer.AddCircle(15, 16, 10)
drawer.Fill()
require.True(t, pixelHasAlpha(dc.Image(), 15, 16))
}
func TestGGDrawerNestedSaveRestoreRestoresOuterClip(t *testing.T) {
t.Parallel()
dc := gg.NewContext(32, 32)
drawer := &GGDrawer{DC: dc}
drawer.ClipRect(0, 0, 20, 32)
drawer.Save()
drawer.ClipRect(0, 0, 10, 32)
drawer.Restore()
drawer.SetFillColor(color.RGBA{R: 255, G: 255, A: 255})
drawer.AddCircle(15, 16, 10)
drawer.Fill()
img := dc.Image()
require.True(t, pixelHasAlpha(img, 15, 16))
require.False(t, pixelHasAlpha(img, 25, 16))
}
func TestFakePrimitiveDrawerRecordsCommandsAndState(t *testing.T) {
t.Parallel()
d := &fakePrimitiveDrawer{}
d.Save()
d.ClipRect(1, 2, 30, 40)
d.SetStrokeColor(color.RGBA{R: 10, G: 20, B: 30, A: 255})
d.SetFillColor(color.RGBA{R: 40, G: 50, B: 60, A: 255})
d.SetLineWidth(3)
d.SetDash(5, 6)
d.SetDashOffset(7)
d.AddLine(10, 11, 12, 13)
d.Stroke()
d.Restore()
requireDrawerCommandNames(t, d,
"Save",
"ClipRect",
"SetStrokeColor",
"SetFillColor",
"SetLineWidth",
"SetDash",
"SetDashOffset",
"AddLine",
"Stroke",
"Restore",
)
cmd := requireDrawerSingleCommand(t, d, "AddLine")
requireCommandArgs(t, cmd, 10, 11, 12, 13)
requireCommandLineWidth(t, cmd, 3)
requireCommandDashes(t, cmd, 5, 6)
requireCommandDashOffset(t, cmd, 7)
requireCommandClipRects(t, cmd, fakeClipRect{X: 1, Y: 2, W: 30, H: 40})
require.Equal(t, color.RGBA{R: 10, G: 20, B: 30, A: 255}, cmd.StrokeColor)
require.Equal(t, color.RGBA{R: 40, G: 50, B: 60, A: 255}, cmd.FillColor)
}
func TestFakePrimitiveDrawerRestoreWithoutSavePanics(t *testing.T) {
t.Parallel()
d := &fakePrimitiveDrawer{}
require.Panics(t, func() {
d.Restore()
})
}
func TestFakePrimitiveDrawerSaveRestoreRestoresState(t *testing.T) {
t.Parallel()
d := &fakePrimitiveDrawer{}
d.SetLineWidth(1)
d.Save()
d.SetLineWidth(9)
d.ClipRect(1, 2, 3, 4)
d.Restore()
state := d.CurrentState()
require.Equal(t, 1.0, state.LineWidth)
require.Empty(t, state.Clips)
require.Equal(t, 0, d.SaveDepth())
}
func TestFakePrimitiveDrawerResetClipClearsOnlyClipState(t *testing.T) {
t.Parallel()
d := &fakePrimitiveDrawer{}
d.SetLineWidth(4)
d.ClipRect(1, 2, 3, 4)
d.ResetClip()
state := d.CurrentState()
require.Equal(t, 4.0, state.LineWidth)
require.Empty(t, state.Clips)
}
func TestGGDrawerCopyShift_ShiftsPixels(t *testing.T) {
t.Parallel()
dc := gg.NewContext(10, 10)
drawer := &GGDrawer{DC: dc}
// Draw a single filled point at (1,1).
drawer.SetFillColor(color.RGBA{R: 255, A: 255})
drawer.AddPoint(1, 1, 1)
drawer.Fill()
// Shift image right by 2 and down by 3.
drawer.CopyShift(2, 3)
img := dc.Image()
// The old pixel near (1,1) should now be present near (3,4).
// We check alpha only to avoid depending on exact blending.
_, _, _, a := img.At(3, 4).RGBA()
require.NotEqual(t, uint32(0), a)
// A pixel in the newly exposed top-left area should be transparent.
_, _, _, a2 := img.At(0, 0).RGBA()
require.Equal(t, uint32(0), a2)
}