imagedd/sprite/sprite.go

267 lines
6.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package sprite
import (
"image"
"math"
)
type Sprite struct {
Name string
Position image.Point
Images []image.Image
CurrentFrame int
Index int
}
func (s *Sprite) SetImage(img *image.RGBA) {
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) {
// 创建旋转矩阵
sin, cos := math.Sin(angle), math.Cos(angle)
// 获取原始图像的边界
bounds := s.Images[s.CurrentFrame].Bounds()
width, height := bounds.Dx(), bounds.Dy()
centerX, centerY := width/2, height/2
// 计算旋转后图像的边界
corners := [4][2]int{
{0 - centerX, 0 - centerY},
{width - centerX, 0 - centerY},
{0 - centerX, height - centerY},
{width - centerX, height - centerY},
}
// 计算旋转后的四个角点位置
minX, minY, maxX, maxY := 0, 0, 0, 0
for i, p := range corners {
// 应用旋转
rotX := int(cos*float64(p[0]) - sin*float64(p[1]))
rotY := int(sin*float64(p[0]) + cos*float64(p[1]))
// 更新边界
if i == 0 || rotX < minX {
minX = rotX
}
if i == 0 || rotY < minY {
minY = rotY
}
if i == 0 || rotX > maxX {
maxX = rotX
}
if i == 0 || rotY > maxY {
maxY = rotY
}
}
// 创建新图像
newWidth, newHeight := maxX-minX+1, maxY-minY+1
newImage := image.NewRGBA(image.Rect(0, 0, newWidth, newHeight))
newCenterX, newCenterY := newWidth/2, newHeight/2
// 预计算矩阵常量
invSin, invCos := math.Sin(-angle), math.Cos(-angle)
// 使用查找表存储sin/cos值避免重复计算
srcXOrigin := make([]int, newWidth)
srcYOrigin := make([]int, newHeight)
// 预计算X坐标的变换
for x := 0; x < newWidth; x++ {
srcXOrigin[x] = centerX
}
// 预计算Y坐标的变换
for y := 0; y < newHeight; y++ {
srcYOrigin[y] = centerY
}
// 批量处理像素
for y := 0; y < newHeight; y++ {
srcY := y - newCenterY
for x := 0; x < newWidth; x++ {
srcX := x - newCenterX
// 应用逆向旋转矩阵
origX := int(invCos*float64(srcX)-invSin*float64(srcY)) + centerX
origY := int(invSin*float64(srcX)+invCos*float64(srcY)) + centerY
// 检查是否在原图范围内
if origX >= 0 && origX < width && origY >= 0 && origY < height {
newImage.Set(x, y, s.Images[s.CurrentFrame].At(origX, origY))
}
}
}
s.Images[s.CurrentFrame] = newImage
}
func (s *Sprite) Move(x, y int) {
s.Position.X += x
s.Position.Y += y
}
func (s *Sprite) Project(projectMatrix *ProjectMatrix) {
bounds := s.Images[s.CurrentFrame].Bounds()
// 计算四个角点投影后的位置
minX, minY, maxX, maxY := 0, 0, 0, 0
// 预先计算所有角点的投影,一次性确定边界
corners := [4]image.Point{
{bounds.Min.X, bounds.Min.Y},
{bounds.Max.X, bounds.Min.Y},
{bounds.Min.X, bounds.Max.Y},
{bounds.Max.X, bounds.Max.Y},
}
for i, corner := range corners {
projected := projectMatrix.ProjectPoint(corner)
if i == 0 {
minX, minY = projected.X, projected.Y
maxX, maxY = projected.X, projected.Y
} else {
if projected.X < minX {
minX = projected.X
}
if projected.Y < minY {
minY = projected.Y
}
if projected.X > maxX {
maxX = projected.X
}
if projected.Y > maxY {
maxY = projected.Y
}
}
}
// 创建新图像
newWidth, newHeight := maxX-minX+1, maxY-minY+1
newImage := image.NewRGBA(image.Rect(0, 0, newWidth, newHeight))
// 计算逆矩阵
det := projectMatrix.Matrix[0][0]*projectMatrix.Matrix[1][1] -
projectMatrix.Matrix[0][1]*projectMatrix.Matrix[1][0]
if math.Abs(det) < 1e-6 {
// 矩阵接近不可逆
return
}
invDet := 1.0 / det
invMatrix := [2][2]float64{
{projectMatrix.Matrix[1][1] * invDet, -projectMatrix.Matrix[0][1] * invDet},
{-projectMatrix.Matrix[1][0] * invDet, projectMatrix.Matrix[0][0] * invDet},
}
// 预计算变换常量
m00, m01 := invMatrix[0][0], invMatrix[0][1]
m10, m11 := invMatrix[1][0], invMatrix[1][1]
// 使用直接内存访问优化像素设置
dstRGBA := newImage.Pix
srcImg, srcRGBA, _ := getImageBytes(s.Images[s.CurrentFrame])
// 批量处理像素
for y := 0; y < newHeight; y++ {
srcY := float64(y + minY)
for x := 0; x < newWidth; x++ {
srcX := float64(x + minX)
// 使用逆矩阵计算
origX := int(m00*srcX + m01*srcY)
origY := int(m10*srcX + m11*srcY)
// 检查边界
if origX >= bounds.Min.X && origX < bounds.Max.X &&
origY >= bounds.Min.Y && origY < bounds.Max.Y {
// 设置像素 - 直接使用indexing会更快
if srcRGBA != nil {
// 如果源图像是RGBA格式直接复制像素
dstIdx := (y*newImage.Stride + x*4)
srcIdx := ((origY-bounds.Min.Y)*srcImg.Stride + (origX-bounds.Min.X)*4)
dstRGBA[dstIdx] = srcRGBA[srcIdx] // R
dstRGBA[dstIdx+1] = srcRGBA[srcIdx+1] // G
dstRGBA[dstIdx+2] = srcRGBA[srcIdx+2] // B
dstRGBA[dstIdx+3] = srcRGBA[srcIdx+3] // A
} else {
// 否则使用通用方法
newImage.Set(x, y, s.Images[s.CurrentFrame].At(origX, origY))
}
}
}
}
s.Images[s.CurrentFrame] = newImage
}
func (s *Sprite) DrawLine(line *Line) {
// 使用Line.ToSprite方法在当前精灵上绘制线条
line.AddToSprite(s)
}
// getImageBytes 尝试提取图像的原始字节数据以加速访问
func getImageBytes(img image.Image) (*image.RGBA, []uint8, bool) {
if rgba, ok := img.(*image.RGBA); ok {
return rgba, rgba.Pix, true
}
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)
}