feat!: 支持动图

This commit is contained in:
lixiangwuxian 2025-05-13 19:51:10 +08:00
parent 30425af13e
commit 2cf9da0896
7 changed files with 250 additions and 99 deletions

94
main.go
View File

@ -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{

View File

@ -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 {

View File

@ -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

90
sprite/image_loader.go Normal file
View File

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

View File

@ -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()

View File

@ -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,
}

View File

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