feat!: 支持动图
This commit is contained in:
parent
30425af13e
commit
2cf9da0896
94
main.go
94
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{
|
||||
|
24
sprite/2d.go
24
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 {
|
||||
|
@ -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
90
sprite/image_loader.go
Normal 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
|
||||
}
|
@ -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()
|
||||
|
@ -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,
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user