From 2cf9da0896124fcbc86f9a5edd31ed97d13ea538 Mon Sep 17 00:00:00 2001 From: lixiangwuxian Date: Tue, 13 May 2025 19:51:10 +0800 Subject: [PATCH] =?UTF-8?q?feat!:=20=E6=94=AF=E6=8C=81=E5=8A=A8=E5=9B=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.go | 94 ++++++++++++++++--------------- sprite/2d.go | 24 ++++---- sprite/2d_test.go | 27 ++++----- sprite/image_loader.go | 90 +++++++++++++++++++++++++++++ sprite/named_sprite_board.go | 17 +++--- sprite/named_sprite_board_test.go | 20 +++---- sprite/sprite.go | 77 +++++++++++++++++++++---- 7 files changed, 250 insertions(+), 99 deletions(-) create mode 100644 sprite/image_loader.go diff --git a/main.go b/main.go index 6da44ba..bf3afe2 100644 --- a/main.go +++ b/main.go @@ -1,16 +1,12 @@ package main import ( - "bytes" "image" "image/color" - "image/png" "log" - "math" - "os" - "git.lxtend.com/lixiangwuxian/imagedd/font2img" "git.lxtend.com/lixiangwuxian/imagedd/sprite" + "git.lxtend.com/lixiangwuxian/imagedd/text2img" ) func main() { @@ -23,63 +19,70 @@ func main() { } } board.AddSprite(&sprite.Sprite{ - Name: "background", - Image: whiteBackground, - Index: 0, - Position: image.Point{X: 0, Y: 0}, + Name: "background", + Images: []image.Image{whiteBackground}, + CurrentFrame: 0, + Index: 0, + Position: image.Point{X: 0, Y: 0}, }) } { wordSprite := &sprite.Sprite{ - Name: "word", - Index: 5, - Position: image.Point{X: -30, Y: 30}, + Name: "word", + Images: []image.Image{}, + CurrentFrame: 0, + Index: 5, + Position: image.Point{X: -30, Y: 30}, } - img, err := font2img.RenderCharToImage(nil, '🤣', 40, color.Black) + img, err := text2img.RenderCharToImage(nil, '🤣', 40, color.Black) if err != nil { log.Fatal(err) } - wordSprite.Image = img + wordSprite.AddFrame(img) board.AddSprite(wordSprite) } { textSprite := &sprite.Sprite{ - Name: "text", - Index: 5, - Position: image.Point{X: 10, Y: 12}, + Name: "text", + Images: []image.Image{}, + CurrentFrame: 0, + Index: 5, + Position: image.Point{X: 10, Y: 12}, } - img, err := font2img.RenderTextToTrimmedImage(nil, "ddjdgj测试\n测试📧测✌试测\n试🥳🧁🍰\n🎁🎂🎈🎺🎉🎊\n📧🧿🌶adadadada🔋😂❤😍🤣😊🥺\n🙏💕😭😘👍\n😅👏测试测试", 12, color.Black, 0, 0) + img, err := text2img.RenderTextToTrimmedImage(nil, "ddjdgj测试\n测试📧测✌试测\n试🥳🧁🍰\n🎁🎂🎈🎺🎉🎊\n📧🧿🌶adadadada🔋😂❤😍🤣😊🥺\n🙏💕😭😘👍\n😅👏测试测试", 12, color.Black, 0, 0) if err != nil { log.Fatal(err) } - textSprite.Image = img + textSprite.AddFrame(img) board.AddSprite(textSprite) } - { - faceBytes, err := os.ReadFile("face.png") - if err != nil { - log.Fatal(err) - } - faceImage, err := png.Decode(bytes.NewReader(faceBytes)) - if err != nil { - log.Fatal(err) - } - faceSprite := &sprite.Sprite{ - Name: "face", - Image: faceImage, - Index: 1, - Position: image.Point{X: -100, Y: -100}, - } - faceSprite.Rotate(math.Pi / 2) - board.AddSprite(faceSprite) - } + // { + // faceBytes, err := os.ReadFile("face.png") + // if err != nil { + // log.Fatal(err) + // } + // faceImage, err := png.Decode(bytes.NewReader(faceBytes)) + // if err != nil { + // log.Fatal(err) + // } + // faceSprite := &sprite.Sprite{ + // Name: "face", + // Images: []image.Image{faceImage}, + // CurrentFrame: 0, + // Index: 1, + // Position: image.Point{X: -100, Y: -100}, + // } + // faceSprite.Rotate(math.Pi / 2) + // board.AddSprite(faceSprite) + // } { rect := image.NewRGBA(image.Rect(0, 0, 101, 101)) rectSprite := &sprite.Sprite{ - Name: "rect", - Image: rect, - Index: 20, - Position: image.Point{X: 20, Y: 20}, + Name: "rect", + Images: []image.Image{rect}, + CurrentFrame: 0, + Index: 20, + Position: image.Point{X: 20, Y: 20}, } lineL := &sprite.Line{ Start: image.Point{X: 0, Y: 100}, @@ -106,10 +109,11 @@ func main() { } { circleSprite := &sprite.Sprite{ - Name: "circle", - Image: image.NewRGBA(image.Rect(0, 0, 101, 101)), - Index: 3, - Position: image.Point{X: 30, Y: 20}, + Name: "circle", + Images: []image.Image{image.NewRGBA(image.Rect(0, 0, 101, 101))}, + CurrentFrame: 0, + Index: 3, + Position: image.Point{X: 30, Y: 20}, } for r := 15.0; r < 45.0; r += 0.01 { circle := &sprite.Circle{ diff --git a/sprite/2d.go b/sprite/2d.go index a497565..7411d1d 100644 --- a/sprite/2d.go +++ b/sprite/2d.go @@ -21,7 +21,7 @@ func (l *Line) AddToSprite(sprite *Sprite) { // l.Start.X, l.Start.Y, l.End.X, l.End.Y, l.Width) // 检查精灵是否已有图像 - if sprite.Image == nil { + if sprite.Images[sprite.CurrentFrame] == nil { // 如果没有图像,创建一个新图像 width := l.End.X - l.Start.X + 1 height := l.End.Y - l.Start.Y + 1 @@ -32,25 +32,25 @@ func (l *Line) AddToSprite(sprite *Sprite) { height = 1 } img := image.NewRGBA(image.Rect(0, 0, width, height)) - sprite.Image = img + sprite.Images[sprite.CurrentFrame] = img sprite.Position = l.Start // log.Printf("创建新图像: 大小(%d,%d) 位置(%d,%d)", // width, height, sprite.Position.X, sprite.Position.Y) } // 获取当前图像大小和位置 - bounds := sprite.Image.Bounds() + bounds := sprite.Images[sprite.CurrentFrame].Bounds() // width, height := bounds.Dx(), bounds.Dy() // log.Printf("当前图像: 大小(%d,%d) 位置(%d,%d)", // width, height, sprite.Position.X, sprite.Position.Y) // 直接使用原图像,不再扩展 var newImage *image.RGBA - newImage, _ = sprite.Image.(*image.RGBA) + newImage, _ = sprite.Images[sprite.CurrentFrame].(*image.RGBA) if newImage == nil { // 如果原图像不是RGBA,转换为RGBA newImage = image.NewRGBA(bounds) - draw.Draw(newImage, bounds, sprite.Image, bounds.Min, draw.Src) + draw.Draw(newImage, bounds, sprite.Images[sprite.CurrentFrame], bounds.Min, draw.Src) // log.Printf("转换图像为RGBA格式") } @@ -65,7 +65,7 @@ func (l *Line) AddToSprite(sprite *Sprite) { } // 更新精灵图像 - sprite.Image = newImage + sprite.Images[sprite.CurrentFrame] = newImage // log.Printf("线条绘制完成") } @@ -78,11 +78,11 @@ type Circle struct { // AddToSprite 在精灵图上绘制圆形,保留原有的图像 func (c *Circle) AddToSprite(sprite *Sprite) { // 检查精灵是否已有图像 - if sprite.Image == nil { + if sprite.Images[sprite.CurrentFrame] == nil { // 如果没有图像,创建一个新图像 size := int(c.Radius * 2) img := image.NewRGBA(image.Rect(0, 0, size, size)) - sprite.Image = img + sprite.Images[sprite.CurrentFrame] = img // 设置精灵位置为圆心减去半径 sprite.Position = image.Point{ X: c.Center.X - int(c.Radius), @@ -91,15 +91,15 @@ func (c *Circle) AddToSprite(sprite *Sprite) { } // 获取当前图像大小和位置 - bounds := sprite.Image.Bounds() + bounds := sprite.Images[sprite.CurrentFrame].Bounds() // 直接使用原图像,不再扩展 var newImage *image.RGBA - newImage, _ = sprite.Image.(*image.RGBA) + newImage, _ = sprite.Images[sprite.CurrentFrame].(*image.RGBA) if newImage == nil { // 如果原图像不是RGBA,转换为RGBA newImage = image.NewRGBA(bounds) - draw.Draw(newImage, bounds, sprite.Image, bounds.Min, draw.Src) + draw.Draw(newImage, bounds, sprite.Images[sprite.CurrentFrame], bounds.Min, draw.Src) } // 在新图像上绘制圆 @@ -107,7 +107,7 @@ func (c *Circle) AddToSprite(sprite *Sprite) { drawhelper.DrawCircleOnImage(newImage, c.Center.X, c.Center.Y, c.Radius, c.Color) // 更新精灵图像 - sprite.Image = newImage + sprite.Images[sprite.CurrentFrame] = newImage } type ProjectMatrix struct { diff --git a/sprite/2d_test.go b/sprite/2d_test.go index 5256787..86555fe 100644 --- a/sprite/2d_test.go +++ b/sprite/2d_test.go @@ -10,9 +10,10 @@ import ( func TestLineToSprite(t *testing.T) { // 创建一个新的空白精灵 sprite := &Sprite{ - Name: "test_line", - Position: image.Point{X: 10, Y: 10}, - Index: 1, + Name: "test_line", + Position: image.Point{X: 10, Y: 10}, + CurrentFrame: 0, + Index: 1, } // 定义一条线 @@ -27,7 +28,7 @@ func TestLineToSprite(t *testing.T) { line.AddToSprite(sprite) // 验证精灵现在有了图像 - if sprite.Image == nil { + if sprite.Images[sprite.CurrentFrame] == nil { t.Fatalf("精灵图像为空") } @@ -38,7 +39,7 @@ func TestLineToSprite(t *testing.T) { } // 检查图像大小 - bounds := sprite.Image.Bounds() + bounds := sprite.Images[sprite.CurrentFrame].Bounds() t.Logf("图像大小:%dx%d", bounds.Dx(), bounds.Dy()) if bounds.Dx() < 41 || bounds.Dy() < 21 { t.Errorf("图像大小错误:期望至少(41x21),实际(%dx%d)", @@ -52,7 +53,7 @@ func TestLineToSprite(t *testing.T) { var redPixels []image.Point for y := 0; y < bounds.Dy(); y++ { for x := 0; x < bounds.Dx(); x++ { - pixelColor := sprite.Image.At(x, y) + pixelColor := sprite.Images[sprite.CurrentFrame].At(x, y) r, g, b, _ := pixelColor.RGBA() if r>>8 > 200 && g>>8 < 50 && b>>8 < 50 { redPixelFound = true @@ -99,7 +100,7 @@ func TestLineToSprite(t *testing.T) { sprite.DrawLine(line2) // 检查图像是否扩展以容纳新线 - newBounds := sprite.Image.Bounds() + newBounds := sprite.Images[sprite.CurrentFrame].Bounds() t.Logf("扩展后图像大小:%dx%d", newBounds.Dx(), newBounds.Dy()) if newBounds.Dx() <= initialWidth || newBounds.Dy() <= initialHeight { t.Errorf("图像未扩展:原始(%dx%d),现在(%dx%d)", @@ -113,7 +114,7 @@ func TestLineToSprite(t *testing.T) { var bluePixels []image.Point for y := 0; y < newBounds.Dy(); y++ { for x := 0; x < newBounds.Dx(); x++ { - pixelColor := sprite.Image.At(x, y) + pixelColor := sprite.Images[sprite.CurrentFrame].At(x, y) r, g, b, _ := pixelColor.RGBA() if r>>8 < 50 && g>>8 < 50 && b>>8 > 200 { bluePixelFound = true @@ -170,7 +171,7 @@ func TestCircleToSprite(t *testing.T) { circle.AddToSprite(sprite) // 验证精灵现在有了图像 - if sprite.Image == nil { + if sprite.Images[sprite.CurrentFrame] == nil { t.Fatalf("精灵图像为空") } @@ -181,7 +182,7 @@ func TestCircleToSprite(t *testing.T) { } // 检查图像大小 - bounds := sprite.Image.Bounds() + bounds := sprite.Images[sprite.CurrentFrame].Bounds() t.Logf("图像大小:%dx%d", bounds.Dx(), bounds.Dy()) // 圆的相对坐标是 (30-20, 30-20) 即 (10,10) 中心 @@ -191,7 +192,7 @@ func TestCircleToSprite(t *testing.T) { // 在图像中寻找绿色像素 for y := 0; y < bounds.Dy(); y++ { for x := 0; x < bounds.Dx(); x++ { - pixelColor := sprite.Image.At(x, y) + pixelColor := sprite.Images[sprite.CurrentFrame].At(x, y) r, g, b, _ := pixelColor.RGBA() if r>>8 < 50 && g>>8 > 200 && b>>8 < 50 { foundPoints = append(foundPoints, image.Point{X: x, Y: y}) @@ -236,7 +237,7 @@ func TestCircleToSprite(t *testing.T) { circle2.AddToSprite(sprite) // 检查图像是否扩展 - newBounds := sprite.Image.Bounds() + newBounds := sprite.Images[sprite.CurrentFrame].Bounds() t.Logf("扩展后图像大小:%dx%d", newBounds.Dx(), newBounds.Dy()) // 检查图像是否扩展了 @@ -252,7 +253,7 @@ func TestCircleToSprite(t *testing.T) { // 在图像中寻找黄色像素 for y := 0; y < newBounds.Dy(); y++ { for x := 0; x < newBounds.Dx(); x++ { - pixelColor := sprite.Image.At(x, y) + pixelColor := sprite.Images[sprite.CurrentFrame].At(x, y) r, g, b, _ := pixelColor.RGBA() if r>>8 > 200 && g>>8 > 200 && b>>8 < 50 { yellowPixelFound = true diff --git a/sprite/image_loader.go b/sprite/image_loader.go new file mode 100644 index 0000000..62f1705 --- /dev/null +++ b/sprite/image_loader.go @@ -0,0 +1,90 @@ +package sprite + +import ( + "bytes" + "image" + "image/gif" + _ "image/jpeg" // 注册JPEG解码器 + _ "image/png" // 注册PNG解码器 + "io" + "os" +) + +// LoadImageFile 从文件加载图像,支持PNG、JPEG、GIF和静态WebP格式 +func LoadImageFile(filePath string) ([]image.Image, error) { + // 读取文件内容 + fileData, err := os.ReadFile(filePath) + if err != nil { + return nil, err + } + + return LoadImageData(fileData) +} + +// LoadImageData 从字节数据加载图像,支持PNG、JPEG、GIF和静态WebP格式 +func LoadImageData(fileData []byte) ([]image.Image, error) { + // 创建Reader + reader := bytes.NewReader(fileData) + + // 尝试解码为GIF + if isGIF(fileData) { + return decodeGIF(reader) + } + + // 尝试使用通用图像解码器(处理PNG、JPEG等) + img, _, err := image.Decode(bytes.NewReader(fileData)) + if err != nil { + return nil, err + } + + // 返回单帧图像 + return []image.Image{img}, nil +} + +// isGIF 检查数据是否为GIF格式 +func isGIF(data []byte) bool { + return len(data) > 3 && data[0] == 'G' && data[1] == 'I' && data[2] == 'F' +} + +// decodeGIF 解码GIF文件为图像帧序列 +func decodeGIF(reader io.Reader) ([]image.Image, error) { + // 解码GIF + gifImg, err := gif.DecodeAll(reader) + if err != nil { + return nil, err + } + + // 检查帧数量 + frameCount := len(gifImg.Image) + if frameCount == 0 { + return nil, nil + } + + // 提取所有帧 + images := make([]image.Image, frameCount) + for i, frame := range gifImg.Image { + images[i] = frame + } + + return images, nil +} + +// LoadSpriteFromFile 从文件创建一个精灵 +func LoadSpriteFromFile(name string, filePath string) (*Sprite, error) { + // 加载图像 + images, err := LoadImageFile(filePath) + if err != nil { + return nil, err + } + + // 创建精灵 + sprite := &Sprite{ + Name: name, + Images: images, + CurrentFrame: 0, + Position: image.Point{X: 0, Y: 0}, + Index: 0, + } + + return sprite, nil +} diff --git a/sprite/named_sprite_board.go b/sprite/named_sprite_board.go index 00689cf..9e55177 100644 --- a/sprite/named_sprite_board.go +++ b/sprite/named_sprite_board.go @@ -470,8 +470,9 @@ func (b *NamedSpriteBoard) RenderToImage() *image.RGBA { currentNode := currentGroup.Head for currentNode != nil { sprite := currentNode.Sprite - if sprite.Image != nil { - srcBounds := sprite.Image.Bounds() + currentImage := sprite.GetCurrentImage() + if currentImage != nil { + srcBounds := currentImage.Bounds() spriteMinX := sprite.Position.X spriteMinY := sprite.Position.Y spriteMaxX := sprite.Position.X + srcBounds.Dx() @@ -530,9 +531,10 @@ func (b *NamedSpriteBoard) RenderToImage() *image.RGBA { currentNode := currentGroup.Head for currentNode != nil { sprite := currentNode.Sprite - if sprite.Image != nil { + currentImage := sprite.GetCurrentImage() + if currentImage != nil { // 获取精灵的边界 - srcBounds := sprite.Image.Bounds() + srcBounds := currentImage.Bounds() // 计算原始目标区域(相对于minX, minY偏移) dstMinX := sprite.Position.X - minX @@ -572,7 +574,7 @@ func (b *NamedSpriteBoard) RenderToImage() *image.RGBA { srcPt := image.Point{X: srcMinX, Y: srcMinY} // 绘制裁剪后的精灵到画布上 - draw.Draw(img, dstRect, sprite.Image, srcPt, draw.Over) + draw.Draw(img, dstRect, currentImage, srcPt, draw.Over) } } @@ -599,8 +601,9 @@ func (b *NamedSpriteBoard) GetRenderBounds() (minX, minY, maxX, maxY int) { currentNode := currentGroup.Head for currentNode != nil { sprite := currentNode.Sprite - if sprite.Image != nil { - srcBounds := sprite.Image.Bounds() + currentImage := sprite.GetCurrentImage() + if currentImage != nil { + srcBounds := currentImage.Bounds() spriteMinX := sprite.Position.X spriteMinY := sprite.Position.Y spriteMaxX := sprite.Position.X + srcBounds.Dx() diff --git a/sprite/named_sprite_board_test.go b/sprite/named_sprite_board_test.go index f680957..182a6cd 100644 --- a/sprite/named_sprite_board_test.go +++ b/sprite/named_sprite_board_test.go @@ -15,21 +15,21 @@ func TestNamedSpriteBoard(t *testing.T) { sprite1 := &Sprite{ Name: "sprite1", Position: image.Point{X: 10, Y: 10}, - Image: image.NewRGBA(image.Rect(0, 0, 10, 10)), + Images: []image.Image{image.NewRGBA(image.Rect(0, 0, 10, 10))}, Index: 5, } sprite2 := &Sprite{ Name: "sprite2", Position: image.Point{X: 20, Y: 20}, - Image: image.NewRGBA(image.Rect(0, 0, 10, 10)), + Images: []image.Image{image.NewRGBA(image.Rect(0, 0, 10, 10))}, Index: 3, } sprite3 := &Sprite{ Name: "sprite3", Position: image.Point{X: 30, Y: 30}, - Image: image.NewRGBA(image.Rect(0, 0, 10, 10)), + Images: []image.Image{image.NewRGBA(image.Rect(0, 0, 10, 10))}, Index: 8, } @@ -88,7 +88,7 @@ func TestNamedSpriteBoard(t *testing.T) { sprite4 := &Sprite{ Name: "sprite4", Position: image.Point{X: 40, Y: 40}, - Image: image.NewRGBA(image.Rect(0, 0, 10, 10)), + Images: []image.Image{image.NewRGBA(image.Rect(0, 0, 10, 10))}, Index: 5, // 与sprite1相同的索引 } board.AddSprite(sprite4) @@ -194,7 +194,7 @@ func BenchmarkNamedSpriteBoardAdd(b *testing.B) { sprite := &Sprite{ Name: "sprite" + strconv.Itoa(i), Position: image.Point{X: i % 100, Y: i / 100}, - Image: img, + Images: []image.Image{img}, Index: i % 100, // 使用100个不同的索引,模拟多个精灵共享索引 } board.AddSprite(sprite) @@ -213,7 +213,7 @@ func BenchmarkNamedSpriteBoardGetByName(b *testing.B) { sprite := &Sprite{ Name: name, Position: image.Point{X: i % 100, Y: i / 100}, - Image: img, + Images: []image.Image{img}, Index: i % 100, } board.AddSprite(sprite) @@ -234,7 +234,7 @@ func BenchmarkNamedSpriteBoardGetByIndex(b *testing.B) { sprite := &Sprite{ Name: "sprite" + strconv.Itoa(i), Position: image.Point{X: i % 100, Y: i / 100}, - Image: img, + Images: []image.Image{img}, Index: i % 100, } board.AddSprite(sprite) @@ -263,7 +263,7 @@ func TestNamedSpriteBoardRenderToImage(t *testing.T) { redSprite := &Sprite{ Name: "red", Position: image.Point{X: 50, Y: 50}, - Image: redImg, + Images: []image.Image{redImg}, Index: 1, } @@ -277,7 +277,7 @@ func TestNamedSpriteBoardRenderToImage(t *testing.T) { greenSprite := &Sprite{ Name: "green", Position: image.Point{X: 75, Y: 75}, - Image: greenImg, + Images: []image.Image{greenImg}, Index: 2, } @@ -291,7 +291,7 @@ func TestNamedSpriteBoardRenderToImage(t *testing.T) { blueSprite := &Sprite{ Name: "blue", Position: image.Point{X: 85, Y: 85}, - Image: blueImg, + Images: []image.Image{blueImg}, Index: 2, } diff --git a/sprite/sprite.go b/sprite/sprite.go index 8220674..e950291 100644 --- a/sprite/sprite.go +++ b/sprite/sprite.go @@ -6,14 +6,20 @@ import ( ) type Sprite struct { - Name string - Position image.Point - Image image.Image - Index int + Name string + Position image.Point + Images []image.Image + CurrentFrame int + Index int } func (s *Sprite) SetImage(img *image.RGBA) { - s.Image = img + if len(s.Images) == 0 { + s.Images = []image.Image{img} + s.CurrentFrame = 0 + } else { + s.Images[s.CurrentFrame] = img + } } func (s *Sprite) Rotate(angle float64) { @@ -21,7 +27,7 @@ func (s *Sprite) Rotate(angle float64) { sin, cos := math.Sin(angle), math.Cos(angle) // 获取原始图像的边界 - bounds := s.Image.Bounds() + bounds := s.Images[s.CurrentFrame].Bounds() width, height := bounds.Dx(), bounds.Dy() centerX, centerY := width/2, height/2 @@ -90,12 +96,12 @@ func (s *Sprite) Rotate(angle float64) { // 检查是否在原图范围内 if origX >= 0 && origX < width && origY >= 0 && origY < height { - newImage.Set(x, y, s.Image.At(origX, origY)) + newImage.Set(x, y, s.Images[s.CurrentFrame].At(origX, origY)) } } } - s.Image = newImage + s.Images[s.CurrentFrame] = newImage } func (s *Sprite) Move(x, y int) { @@ -104,7 +110,7 @@ func (s *Sprite) Move(x, y int) { } func (s *Sprite) Project(projectMatrix *ProjectMatrix) { - bounds := s.Image.Bounds() + bounds := s.Images[s.CurrentFrame].Bounds() // 计算四个角点投影后的位置 minX, minY, maxX, maxY := 0, 0, 0, 0 @@ -162,7 +168,7 @@ func (s *Sprite) Project(projectMatrix *ProjectMatrix) { // 使用直接内存访问优化像素设置 dstRGBA := newImage.Pix - srcImg, srcRGBA, _ := getImageBytes(s.Image) + srcImg, srcRGBA, _ := getImageBytes(s.Images[s.CurrentFrame]) // 批量处理像素 for y := 0; y < newHeight; y++ { @@ -190,13 +196,13 @@ func (s *Sprite) Project(projectMatrix *ProjectMatrix) { dstRGBA[dstIdx+3] = srcRGBA[srcIdx+3] // A } else { // 否则使用通用方法 - newImage.Set(x, y, s.Image.At(origX, origY)) + newImage.Set(x, y, s.Images[s.CurrentFrame].At(origX, origY)) } } } } - s.Image = newImage + s.Images[s.CurrentFrame] = newImage } func (s *Sprite) DrawLine(line *Line) { @@ -211,3 +217,50 @@ func getImageBytes(img image.Image) (*image.RGBA, []uint8, bool) { } return nil, nil, false } + +// AddFrame 添加一个新的图像帧 +func (s *Sprite) AddFrame(img image.Image) { + s.Images = append(s.Images, img) +} + +// SetFrames 设置所有图像帧 +func (s *Sprite) SetFrames(images []image.Image) { + s.Images = images + s.CurrentFrame = 0 +} + +// NextFrame 切换到下一帧 +func (s *Sprite) NextFrame() { + if len(s.Images) > 1 { + s.CurrentFrame = (s.CurrentFrame + 1) % len(s.Images) + } +} + +// SetFrame 设置当前帧 +func (s *Sprite) SetFrame(frame int) { + if len(s.Images) > 0 && frame >= 0 && frame < len(s.Images) { + s.CurrentFrame = frame + } +} + +// GetCurrentImage 获取当前帧图像 +func (s *Sprite) GetCurrentImage() image.Image { + if len(s.Images) == 0 { + return nil + } + return s.Images[s.CurrentFrame] +} + +func (s *Sprite) GetFrame(index int) image.Image { + if index < 0 { + return s.Images[0] + } else if index >= len(s.Images) { + return s.Images[len(s.Images)-1] + } + return s.Images[index] +} + +// FrameCount 返回图像帧数量 +func (s *Sprite) FrameCount() int { + return len(s.Images) +}