refactor: 修改精灵板构造函数,移除宽高参数,使用实际内容确定大小。优化图像处理逻辑,更新测试用例以适应新接口。

This commit is contained in:
lixiangwuxian 2025-05-10 02:54:25 +08:00
parent 6cd5efa192
commit 1346911f86
4 changed files with 121 additions and 39 deletions

View File

@ -76,8 +76,8 @@ type Circle struct {
Color color.Color
}
// ToSprite 在精灵图上绘制圆形,保留原有的图像
func (c *Circle) ToSprite(sprite *Sprite) {
// AddToSprite 在精灵图上绘制圆形,保留原有的图像
func (c *Circle) AddToSprite(sprite *Sprite) {
// 检查精灵是否已有图像
if sprite.Image == nil {
// 如果没有图像,创建一个新图像
@ -94,10 +94,6 @@ func (c *Circle) ToSprite(sprite *Sprite) {
// 获取当前图像大小和位置
bounds := sprite.Image.Bounds()
// 计算圆心相对于精灵图像的位置
relCenterX := c.Center.X - sprite.Position.X
relCenterY := c.Center.Y - sprite.Position.Y
// 直接使用原图像,不再扩展
var newImage *image.RGBA
newImage, _ = sprite.Image.(*image.RGBA)
@ -109,7 +105,7 @@ func (c *Circle) ToSprite(sprite *Sprite) {
// 在新图像上绘制圆
// 超出图像边界的部分将被自动丢弃
drawhelper.DrawCircleOnImage(newImage, relCenterX, relCenterY, c.Radius, c.Color)
drawhelper.DrawCircleOnImage(newImage, c.Center.X, c.Center.Y, c.Radius, c.Color)
// 更新精灵图像
sprite.Image = newImage

View File

@ -77,7 +77,7 @@ func TestLineToSprite(t *testing.T) {
if !redPixelFound {
// 如果没找到,保存图像用于调试
board := NewNamedSpriteBoard(100, 100)
board := NewNamedSpriteBoard()
board.AddSprite(sprite)
board.SaveToPng("test_line1.png")
t.Errorf("未找到红色线条像素")
@ -138,14 +138,14 @@ func TestLineToSprite(t *testing.T) {
if !bluePixelFound {
// 保存图像用于调试
board := NewNamedSpriteBoard(100, 100)
board := NewNamedSpriteBoard()
board.AddSprite(sprite)
board.SaveToPng("test_line2.png")
t.Errorf("未找到蓝色线条像素")
}
// 总是保存最终图像以便查看结果
board := NewNamedSpriteBoard(100, 100)
board := NewNamedSpriteBoard()
board.AddSprite(sprite)
board.SaveToPng("test_line_final.png")
}
@ -167,7 +167,7 @@ func TestCircleToSprite(t *testing.T) {
}
// 在精灵上绘制圆
circle.ToSprite(sprite)
circle.AddToSprite(sprite)
// 验证精灵现在有了图像
if sprite.Image == nil {
@ -208,7 +208,7 @@ func TestCircleToSprite(t *testing.T) {
// 如果找到了绿色像素,则测试通过
if len(foundPoints) == 0 {
// 保存图像用于调试
board := NewNamedSpriteBoard(100, 100)
board := NewNamedSpriteBoard()
board.AddSprite(sprite)
board.SaveToPng("test_circle1.png")
t.Errorf("未找到绿色圆形像素")
@ -233,7 +233,7 @@ func TestCircleToSprite(t *testing.T) {
}
// 在已有精灵上绘制第二个圆
circle2.ToSprite(sprite)
circle2.AddToSprite(sprite)
// 检查图像是否扩展
newBounds := sprite.Image.Bounds()
@ -269,7 +269,7 @@ func TestCircleToSprite(t *testing.T) {
if !yellowPixelFound {
// 保存图像用于调试
board := NewNamedSpriteBoard(100, 100)
board := NewNamedSpriteBoard()
board.AddSprite(sprite)
board.SaveToPng("test_circle2.png")
t.Errorf("未找到黄色圆形像素")

View File

@ -35,8 +35,6 @@ type IndexGroup struct {
// NamedSpriteBoard 提供按名称哈希查找,按索引排序的精灵图管理
type NamedSpriteBoard struct {
Width int
Height int
Sprites []*Sprite
// nameMap 用于O(1)的按名称查找键为Name值为精灵指针
nameMap map[string]*Sprite
@ -50,10 +48,8 @@ type NamedSpriteBoard struct {
}
// NewNamedSpriteBoard 创建一个新的NamedSpriteBoard
func NewNamedSpriteBoard(width, height int) *NamedSpriteBoard {
func NewNamedSpriteBoard() *NamedSpriteBoard {
return &NamedSpriteBoard{
Width: width,
Height: height,
Sprites: make([]*Sprite, 0),
nameMap: make(map[string]*Sprite),
indexMap: make(map[int]*IndexGroup),
@ -462,32 +458,122 @@ func (b *NamedSpriteBoard) updateSpriteArray() {
// RenderToImage 将精灵板渲染为图像
func (b *NamedSpriteBoard) RenderToImage() *image.RGBA {
// 创建画布
img := image.NewRGBA(image.Rect(0, 0, b.Width, b.Height))
// 计算所有精灵图覆盖的最大区域
var minX, minY, maxX, maxY int
// 按索引排序(已经在链表中排好)遍历所有精灵组
// 初始化为最大/最小可能值,以便于后续比较
initialized := false
// 遍历所有精灵计算边界
currentGroup := b.indexHead
for currentGroup != nil {
// 在每个索引组中,按名称排序(已经在链表中排好)遍历所有精灵
currentNode := currentGroup.Head
for currentNode != nil {
sprite := currentNode.Sprite
if sprite.Image != nil {
srcBounds := sprite.Image.Bounds()
spriteMinX := sprite.Position.X
spriteMinY := sprite.Position.Y
spriteMaxX := sprite.Position.X + srcBounds.Dx()
spriteMaxY := sprite.Position.Y + srcBounds.Dy()
// 首次初始化边界值或更新边界
if !initialized {
minX, minY = spriteMinX, spriteMinY
maxX, maxY = spriteMaxX, spriteMaxY
initialized = true
} else {
// 更新边界值
if spriteMinX < minX {
minX = spriteMinX
}
if spriteMinY < minY {
minY = spriteMinY
}
if spriteMaxX > maxX {
maxX = spriteMaxX
}
if spriteMaxY > maxY {
maxY = spriteMaxY
}
}
}
currentNode = currentNode.Next
}
currentGroup = currentGroup.Next
}
// 如果没有精灵图,则使用默认大小
if !initialized {
minX, minY = 0, 0
maxX, maxY = 1, 1
}
// 确保有最小尺寸
if maxX <= minX {
maxX = minX + 1
}
if maxY <= minY {
maxY = minY + 1
}
// 计算画布尺寸,保留负坐标空间
width := maxX - minX
height := maxY - minY
// 创建画布,大小由精灵图决定
img := image.NewRGBA(image.Rect(0, 0, width, height))
// 按索引排序遍历所有精灵组
currentGroup = b.indexHead
for currentGroup != nil {
currentNode := currentGroup.Head
for currentNode != nil {
sprite := currentNode.Sprite
if sprite.Image != nil {
// 获取精灵的边界
bounds := sprite.Image.Bounds()
srcBounds := sprite.Image.Bounds()
// 计算目标区域
min := image.Point{
X: sprite.Position.X,
Y: sprite.Position.Y,
}
max := image.Point{
X: sprite.Position.X + bounds.Dx(),
Y: sprite.Position.Y + bounds.Dy(),
// 计算原始目标区域相对于minX, minY偏移
dstMinX := sprite.Position.X - minX
dstMinY := sprite.Position.Y - minY
dstMaxX := dstMinX + srcBounds.Dx()
dstMaxY := dstMinY + srcBounds.Dy()
// 计算源图像的裁剪区域(如果精灵部分在画布外)
srcMinX := srcBounds.Min.X
srcMinY := srcBounds.Min.Y
// 处理左边界超出
if dstMinX < 0 {
srcMinX -= dstMinX // 向右移动源图像起点
dstMinX = 0
}
// 绘制精灵到画布上
draw.Draw(img, image.Rectangle{Min: min, Max: max}, sprite.Image, bounds.Min, draw.Over)
// 处理上边界超出
if dstMinY < 0 {
srcMinY -= dstMinY // 向下移动源图像起点
dstMinY = 0
}
// 处理右边界超出
if dstMaxX > width {
dstMaxX = width
}
// 处理下边界超出
if dstMaxY > height {
dstMaxY = height
}
// 如果裁剪后的区域有效宽度和高度都大于0
if dstMinX < dstMaxX && dstMinY < dstMaxY {
dstRect := image.Rect(dstMinX, dstMinY, dstMaxX, dstMaxY)
srcPt := image.Point{X: srcMinX, Y: srcMinY}
// 绘制裁剪后的精灵到画布上
draw.Draw(img, dstRect, sprite.Image, srcPt, draw.Over)
}
}
// 移动到下一个精灵

View File

@ -9,7 +9,7 @@ import (
func TestNamedSpriteBoard(t *testing.T) {
// 创建测试板
board := NewNamedSpriteBoard(100, 100)
board := NewNamedSpriteBoard()
// 测试添加精灵
sprite1 := &Sprite{
@ -186,7 +186,7 @@ func TestNamedSpriteBoard(t *testing.T) {
// 性能测试
func BenchmarkNamedSpriteBoardAdd(b *testing.B) {
board := NewNamedSpriteBoard(1000, 1000)
board := NewNamedSpriteBoard()
img := image.NewRGBA(image.Rect(0, 0, 10, 10))
b.ResetTimer()
@ -203,7 +203,7 @@ func BenchmarkNamedSpriteBoardAdd(b *testing.B) {
func BenchmarkNamedSpriteBoardGetByName(b *testing.B) {
// 准备测试数据
board := NewNamedSpriteBoard(1000, 1000)
board := NewNamedSpriteBoard()
img := image.NewRGBA(image.Rect(0, 0, 10, 10))
names := make([]string, 1000)
@ -227,7 +227,7 @@ func BenchmarkNamedSpriteBoardGetByName(b *testing.B) {
func BenchmarkNamedSpriteBoardGetByIndex(b *testing.B) {
// 准备测试数据
board := NewNamedSpriteBoard(1000, 1000)
board := NewNamedSpriteBoard()
img := image.NewRGBA(image.Rect(0, 0, 10, 10))
for i := 0; i < 1000; i++ {
@ -248,7 +248,7 @@ func BenchmarkNamedSpriteBoardGetByIndex(b *testing.B) {
func TestNamedSpriteBoardRenderToImage(t *testing.T) {
// 创建测试板
board := NewNamedSpriteBoard(200, 200)
board := NewNamedSpriteBoard()
// 创建几个具有不同索引的彩色精灵
// 索引值小的精灵会被先绘制,索引值大的精灵会覆盖在上面