122 lines
2.7 KiB
Go
122 lines
2.7 KiB
Go
package skin_renderer
|
||
|
||
import (
|
||
"bytes"
|
||
"image"
|
||
"image/png"
|
||
)
|
||
|
||
// CapeRenderer 披风渲染器
|
||
type CapeRenderer struct{}
|
||
|
||
// NewCapeRenderer 创建披风渲染器
|
||
func NewCapeRenderer() *CapeRenderer {
|
||
return &CapeRenderer{}
|
||
}
|
||
|
||
// Render 渲染披风
|
||
// 披风纹理布局:
|
||
// - 正面: (1, 1) 到 (11, 17) - 10x16 像素
|
||
// - 背面: (12, 1) 到 (22, 17) - 10x16 像素
|
||
func (r *CapeRenderer) Render(capeData []byte, height int) (image.Image, error) {
|
||
// 解码披风图像
|
||
img, err := png.Decode(bytes.NewReader(capeData))
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
bounds := img.Bounds()
|
||
srcWidth := bounds.Dx()
|
||
srcHeight := bounds.Dy()
|
||
|
||
// 披风纹理可能是 64x32 或 22x17
|
||
// 标准披风正面区域
|
||
var frontX, frontY, frontW, frontH int
|
||
|
||
if srcWidth >= 64 && srcHeight >= 32 {
|
||
// 64x32 格式(Minecraft 1.8+)
|
||
// 正面: (1, 1) 到 (11, 17)
|
||
frontX = 1
|
||
frontY = 1
|
||
frontW = 10
|
||
frontH = 16
|
||
} else if srcWidth >= 22 && srcHeight >= 17 {
|
||
// 22x17 格式(旧版)
|
||
frontX = 1
|
||
frontY = 1
|
||
frontW = 10
|
||
frontH = 16
|
||
} else {
|
||
// 未知格式,直接缩放整个图像
|
||
return resizeImageBilinear(img, height*srcWidth/srcHeight, height), nil
|
||
}
|
||
|
||
// 提取正面区域
|
||
front := image.NewRGBA(image.Rect(0, 0, frontW, frontH))
|
||
for y := 0; y < frontH; y++ {
|
||
for x := 0; x < frontW; x++ {
|
||
front.Set(x, y, img.At(bounds.Min.X+frontX+x, bounds.Min.Y+frontY+y))
|
||
}
|
||
}
|
||
|
||
// 计算输出尺寸,保持宽高比
|
||
outputWidth := height * frontW / frontH
|
||
if outputWidth < 1 {
|
||
outputWidth = 1
|
||
}
|
||
|
||
// 使用最近邻缩放保持像素风格
|
||
return scaleNearest(front, outputWidth, height), nil
|
||
}
|
||
|
||
// scaleNearest 最近邻缩放
|
||
func scaleNearest(src image.Image, width, height int) *image.RGBA {
|
||
bounds := src.Bounds()
|
||
srcW := bounds.Dx()
|
||
srcH := bounds.Dy()
|
||
|
||
dst := image.NewRGBA(image.Rect(0, 0, width, height))
|
||
|
||
for y := 0; y < height; y++ {
|
||
for x := 0; x < width; x++ {
|
||
srcX := bounds.Min.X + x*srcW/width
|
||
srcY := bounds.Min.Y + y*srcH/height
|
||
dst.Set(x, y, src.At(srcX, srcY))
|
||
}
|
||
}
|
||
|
||
return dst
|
||
}
|
||
|
||
// resizeImageBilinear 双线性插值缩放
|
||
func resizeImageBilinear(src image.Image, width, height int) *image.RGBA {
|
||
bounds := src.Bounds()
|
||
srcW := float64(bounds.Dx())
|
||
srcH := float64(bounds.Dy())
|
||
|
||
dst := image.NewRGBA(image.Rect(0, 0, width, height))
|
||
|
||
for y := 0; y < height; y++ {
|
||
for x := 0; x < width; x++ {
|
||
// 计算源图像中的位置
|
||
srcX := float64(x) * srcW / float64(width)
|
||
srcY := float64(y) * srcH / float64(height)
|
||
|
||
// 简单的最近邻(可以改进为双线性)
|
||
ix := int(srcX)
|
||
iy := int(srcY)
|
||
|
||
if ix >= bounds.Dx() {
|
||
ix = bounds.Dx() - 1
|
||
}
|
||
if iy >= bounds.Dy() {
|
||
iy = bounds.Dy() - 1
|
||
}
|
||
|
||
dst.Set(x, y, src.At(bounds.Min.X+ix, bounds.Min.Y+iy))
|
||
}
|
||
}
|
||
|
||
return dst
|
||
}
|