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) }