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