Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- package main
- import (
- "container/list"
- "encoding/json"
- "flag"
- "fmt"
- "image"
- "image/color"
- "image/png"
- _ "image/png"
- "log"
- "math"
- "os"
- "sort"
- "strings"
- "time"
- )
- // ResultItem is returned type.
- // Position is the coords to the top left corner of the sprite.
- // Center is the coords to the center of the sprite.
- // Size is sprite size.
- type ResultItem struct {
- Position image.Point `json:"position"`
- Center image.Point `json:"center"`
- Size image.Point `json:"size"`
- }
- const maxUint = ^uint(0)
- const minUint = 0
- const maxInt = int(maxUint >> 1)
- const minInt = -maxInt - 1
- func main() {
- inputDirFlag := flag.String("inputDir", fmt.Sprintf(".%cinput%c", os.PathSeparator, os.PathSeparator), "Specify input directory")
- outputDirFlag := flag.String("outputdir", fmt.Sprintf(".%coutput%c", os.PathSeparator, os.PathSeparator), "Specify output directory")
- flag.Parse()
- inputDir := *inputDirFlag
- outputDir := *outputDirFlag
- start := time.Now()
- successCount, errorsCount := 0, 0
- filesToProcess := 0
- resultChan := make(chan bool)
- // Removes output directory.
- err := os.RemoveAll(outputDir)
- if err != nil {
- log.Fatal(err)
- }
- // Creates empty output directory.
- err = os.MkdirAll(outputDir, 0755)
- if err != nil {
- log.Fatal(err)
- }
- // Opens input directory.
- dir, err := os.Open(inputDir)
- if err != nil {
- log.Fatal(err)
- }
- defer dir.Close()
- // Read all files in input directory.
- files, err := dir.Readdir(-1)
- if err != nil {
- log.Fatal(err)
- }
- // For every .png file, executes the process in a separated goroutine.
- for _, file := range files {
- if file.IsDir() || !strings.HasSuffix(file.Name(), ".png") {
- continue
- }
- filesToProcess++
- go processFile(inputDir, strings.Split(file.Name(), ".png")[0], outputDir, resultChan)
- }
- // Waits for all process.
- for i := 0; i < filesToProcess; i++ {
- res := <-resultChan
- if res {
- successCount++
- } else {
- errorsCount++
- }
- }
- log.Printf("Success = %d, Errors = %d, Time = %s\n", successCount, errorsCount, time.Since(start))
- }
- // processFile process spritesheet by its filename.
- // The filename paremeter should not contain extension.
- func processFile(inputDir, filename, outputDir string, result chan<- bool) {
- // Process sprite sheet file.
- errorOnProcess := createSpriteSheetInfo(inputDir, filename, outputDir)
- if errorOnProcess == nil {
- result <- true
- return
- }
- // If an error has occurred, creates a json file with the message.
- outputErrorFileJSON, err := os.Create(outputDir + filename + "-error.json")
- if err != nil {
- log.Println(err)
- result <- false
- return
- }
- defer outputErrorFileJSON.Close()
- _, err = outputErrorFileJSON.WriteString(fmt.Sprintf("{ \"error\": \"%s\" }", errorOnProcess.Error()))
- if err != nil {
- log.Println(err)
- result <- false
- return
- }
- result <- false
- }
- // createSpriteSheetInfo creates the sprite sheet info json file.
- func createSpriteSheetInfo(inputDir, filename, outputDir string) error {
- start := time.Now()
- // Reads sprite sheet image.
- inputFileImg, err := os.Open(inputDir + filename + ".png")
- if err != nil {
- return err
- }
- defer inputFileImg.Close()
- inputImg, _, err := image.Decode(inputFileImg)
- if err != nil {
- return err
- }
- size := inputImg.Bounds().Size()
- visited := make([]bool, size.X*size.Y)
- backgroundColor := inputImg.At(0, 0)
- noColor := color.RGBA{0, 0, 0, 0}
- result := list.List{}
- outputImg := image.NewRGBA(image.Rect(0, 0, size.X, size.Y))
- outputImgBox := image.NewRGBA(image.Rect(0, 0, size.X, size.Y))
- // Process all pixels.
- for y := 0; y < size.Y; y++ {
- for x := 0; x < size.X; x++ {
- // Pixel is a background color.
- if inputImg.At(x, y) == backgroundColor {
- continue
- }
- // Pixel already visited.
- if visited[x+y*size.X] {
- continue
- }
- minX, maxX, minY, maxY := maxInt, minInt, maxInt, minInt
- queue := list.List{}
- queue.PushBack(image.Point{X: x, Y: y})
- visited[x+y*size.X] = true
- // Processing a sprite.
- for queue.Len() > 0 {
- elem := queue.Remove(queue.Front()).(image.Point)
- outputImg.Set(elem.X, elem.Y, inputImg.At(elem.X, elem.Y))
- if elem.X < minX {
- minX = elem.X
- }
- if elem.X > maxX {
- maxX = elem.X
- }
- if elem.Y < minY {
- minY = elem.Y
- }
- if elem.Y > maxY {
- maxY = elem.Y
- }
- for yOff := elem.Y - 1; yOff <= elem.Y+1; yOff++ {
- for xOff := elem.X - 1; xOff <= elem.X+1; xOff++ {
- // Pixel is a background color.
- if inputImg.At(xOff, yOff) == backgroundColor {
- continue
- }
- // Pixel already visited
- if visited[xOff+yOff*size.X] {
- continue
- }
- queue.PushBack(image.Point{X: xOff, Y: yOff})
- visited[xOff+yOff*size.X] = true
- }
- }
- }
- // Marks and checks if this pixel is already taken by other sprite.
- for y := minY; y <= maxY; y++ {
- for x := minX; x <= maxX; x++ {
- if outputImgBox.At(x, y) != noColor {
- return fmt.Errorf("The pixel x = %d, y = %d has already processed", x, y)
- }
- outputImgBox.SetRGBA(x, y, color.RGBA{1, 1, 1, 1})
- }
- }
- result.PushBack(ResultItem{
- Position: image.Point{X: minX, Y: minY},
- Center: image.Point{X: minX + (maxX-minX)/2.0, Y: minY + (maxY-minY)/2.0},
- Size: image.Point{X: maxX - minX, Y: maxY - minY}})
- }
- }
- // Order results by row and then by x-coords.
- resultInSlice, err := orderResults(&result)
- if err != nil {
- return err
- }
- // Serializes result into json format.
- data, err := json.MarshalIndent(resultInSlice, "", "\t")
- if err != nil {
- return err
- }
- // Creates json file.
- outputFileJSON, err := os.Create(outputDir + filename + ".json")
- if err != nil {
- return err
- }
- defer outputFileJSON.Close()
- _, err = outputFileJSON.Write(data)
- if err != nil {
- return err
- }
- // Creates image file.
- outputFileImg, err := os.Create(outputDir + filename + ".png")
- if err != nil {
- return err
- }
- defer outputFileImg.Close()
- png.Encode(outputFileImg, outputImg)
- log.Printf("%d sprites found in %s.png file, time = %s\n", len(resultInSlice), filename, time.Since(start))
- return nil
- }
- // orderResults orders results by row and its x-coord.
- // This function clears listToOrder parameter.
- func orderResults(listToOrder *list.List) ([]ResultItem, error) {
- result := make([]ResultItem, listToOrder.Len())
- index := 0
- for listToOrder.Len() > 0 {
- minY, maxY := 0, listToOrder.Front().Value.(ResultItem).Position.Y
- row := make([]ResultItem, 0)
- // Adds on a single array elements that are in the same row.
- for {
- if listToOrder.Len() <= 0 {
- break
- }
- elem, ok := listToOrder.Remove(listToOrder.Front()).(ResultItem)
- if !ok {
- return nil, fmt.Errorf("Elem is not a ResultItem")
- }
- if minY > elem.Position.Y+elem.Size.Y || maxY < elem.Position.Y {
- listToOrder.PushFront(elem)
- break
- }
- maxY = int(math.Max(float64(maxY), float64(elem.Position.Y+elem.Size.Y)))
- row = append(row, elem)
- }
- // Orders sprites in a row by its offsetX coords.
- sort.Slice(row, func(i, j int) bool {
- return row[i].Position.X < row[j].Position.X
- })
- // Appends to the current result.
- // Also checks if sprites are overlapped on same row.
- for _, item1 := range row {
- for _, item2 := range row {
- if item1 == item2 {
- continue
- }
- if item1.Position.X <= item2.Position.X+item2.Size.X &&
- item1.Position.X+item1.Size.X >= item2.Position.X {
- return nil, fmt.Errorf("Sprite (offsetX = %d, offsetY = %d, width = %d, height = %d)"+
- " is overlapped with (offsetX = %d, offsetY = %d, width = %d, height = %d)",
- item1.Position.X, item1.Position.Y, item1.Size.X, item1.Size.Y,
- item2.Position.X, item2.Position.Y, item2.Size.X, item2.Size.Y)
- }
- }
- result[index] = item1
- index++
- }
- }
- return result, nil
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement