maptile/util/sprite/sprite.go

205 lines
4.5 KiB
Go

package sprite
import (
"fmt"
"image"
"image/color"
"image/png"
"os"
"strings"
"github.com/pkg/errors"
"github.com/srwiley/oksvg"
"github.com/srwiley/rasterx"
"golang.org/x/image/draw"
)
type Direction int
const (
Horizontal = iota
Vertical
)
func (d Direction) String() string {
switch d {
case Horizontal:
return "Horizontal"
case Vertical:
return "Vertical"
default:
return "Unknown"
}
}
type Image struct {
Name string
Width, Height int
X, Y int
}
type Sprite struct {
x, y int
padding int
direction Direction
standard int
count int
Images []Image
}
func NewSprite(padding int, direct int, count int, standard int) (*Sprite, error) {
if Direction(direct).String() == "Unknown" {
return nil, errors.Errorf("direction %d is unknown", direct)
}
if count <= 0 {
return nil, errors.Errorf("count %d need to max than 0", count)
}
if standard <= 0 {
return nil, errors.Errorf("standard %d need to max than 0", standard)
}
return &Sprite{
x: 0,
y: 0,
padding: padding,
direction: Direction(direct),
count: count,
standard: standard,
Images: []Image{},
}, nil
}
func (s *Sprite) MergeImages(infos []ImageInfo) (image.Image, error) {
width, height := 0, 0
max := 0
spritesheet := image.NewRGBA(image.Rect(0, 0, 0, 0))
for i, info := range infos {
img, err := info.ReadInfo()
if err != nil {
return nil, err
}
x, y := 0, 0
imgWidth, imgHeight := img.Bounds().Dx(), img.Bounds().Dy()
perCount := (i + 1) / s.count
nowCount := i % s.count
if s.direction == Horizontal {
scale := float64(s.standard) / float64(imgHeight)
imgWidth = int(float64(imgWidth) * scale)
imgHeight = s.standard
if perCount == 0 {
height = nowCount * (s.standard + s.padding*2)
} else {
height = s.count * (s.standard + s.padding*2)
}
y = (s.standard+2*s.padding)*nowCount + s.padding
if nowCount == 0 {
x = width + s.padding
max = imgWidth
width = width + imgWidth + s.padding*2
} else {
if imgWidth > max {
width = width + (imgWidth - max)
max = imgWidth
}
x = width - max - s.padding
}
}
if s.direction == Vertical {
scale := float64(s.standard) / float64(imgWidth)
imgHeight = int(float64(imgHeight) * scale)
imgWidth = s.standard
if perCount == 0 {
width = (nowCount + 1) * (s.standard + s.padding*2)
} else {
width = s.count * (s.standard + s.padding*2)
}
x = (s.standard+2*s.padding)*nowCount + s.padding
if nowCount == 0 {
y = height + s.padding
max = imgHeight
height = height + imgHeight + s.padding*2
} else {
if imgHeight > max {
height = height + (imgHeight - max)
max = imgHeight
}
y = height - max - s.padding
}
}
newspritesheet := image.NewRGBA(image.Rect(0, 0, width, height))
if spritesheet.Bounds().Dx() > 0 && spritesheet.Bounds().Dy() > 0 {
draw.Draw(newspritesheet, spritesheet.Bounds(), spritesheet, image.Point{}, draw.Src)
}
draw.ApproxBiLinear.Scale(newspritesheet, image.Rect(x, y, x+imgWidth, y+imgHeight), img, img.Bounds(), draw.Src, nil)
spritesheet = newspritesheet
s.Images = append(s.Images, Image{
Name: info.Name,
Width: imgWidth,
Height: imgHeight,
X: x,
Y: y,
})
fmt.Println(info.Name, imgWidth, imgHeight, x, y)
}
return spritesheet, nil
}
type ImageInfo struct {
Name, Path string
}
func (i *ImageInfo) ReadInfo() (image.Image, error) {
if strings.HasSuffix(i.Path, ".png") {
pngFile, err := os.Open(i.Path)
if err != nil {
return nil, err
}
defer pngFile.Close()
pngImg, err := png.Decode(pngFile)
if err != nil {
return nil, err
}
return pngImg, nil
}
if strings.HasSuffix(i.Path, ".svg") {
svgFile, err := os.Open(i.Path)
if err != nil {
return nil, err
}
defer svgFile.Close()
svgInfo, err := oksvg.ReadIconStream(svgFile, oksvg.WarnErrorMode)
if err != nil {
return nil, err
}
svgImg := image.NewRGBA(image.Rect(int(svgInfo.ViewBox.X), int(svgInfo.ViewBox.Y), int(svgInfo.ViewBox.W), int(svgInfo.ViewBox.H)))
draw.Draw(svgImg, svgImg.Bounds(), &image.Uniform{color.Transparent}, image.Point{}, draw.Src)
svgInfo.SetTarget(0, 0, float64(svgInfo.ViewBox.W), float64(svgInfo.ViewBox.H))
scaner := rasterx.NewScannerGV(int(svgInfo.ViewBox.W), int(svgInfo.ViewBox.H), svgImg, svgImg.Bounds())
raster := rasterx.NewDasher(int(svgInfo.ViewBox.W), int(svgInfo.ViewBox.H), scaner)
svgInfo.Draw(raster, 1.0)
return svgImg, nil
}
return nil, nil
}