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 }