Guest User

Untitled

a guest
Jan 23rd, 2020
83
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. package main
  2.  
  3. import (
  4.     "container/list"
  5.     "encoding/json"
  6.     "flag"
  7.     "fmt"
  8.     "image"
  9.     "image/color"
  10.     "image/png"
  11.     _ "image/png"
  12.     "log"
  13.     "math"
  14.     "os"
  15.     "sort"
  16.     "strings"
  17.     "time"
  18. )
  19.  
  20. // ResultItem is returned type.
  21. // Position is the coords to the top left corner of the sprite.
  22. // Center is the coords to the center of the sprite.
  23. // Size is sprite size.
  24. type ResultItem struct {
  25.     Position image.Point `json:"position"`
  26.     Center   image.Point `json:"center"`
  27.     Size     image.Point `json:"size"`
  28. }
  29.  
  30. const maxUint = ^uint(0)
  31. const minUint = 0
  32. const maxInt = int(maxUint >> 1)
  33. const minInt = -maxInt - 1
  34.  
  35. func main() {
  36.     inputDirFlag := flag.String("inputDir", fmt.Sprintf(".%cinput%c", os.PathSeparator, os.PathSeparator), "Specify input directory")
  37.     outputDirFlag := flag.String("outputdir", fmt.Sprintf(".%coutput%c", os.PathSeparator, os.PathSeparator), "Specify output directory")
  38.     flag.Parse()
  39.  
  40.     inputDir := *inputDirFlag
  41.     outputDir := *outputDirFlag
  42.  
  43.     start := time.Now()
  44.     successCount, errorsCount := 0, 0
  45.     filesToProcess := 0
  46.     resultChan := make(chan bool)
  47.  
  48.     // Removes output directory.
  49.     err := os.RemoveAll(outputDir)
  50.     if err != nil {
  51.         log.Fatal(err)
  52.     }
  53.  
  54.     // Creates empty output directory.
  55.     err = os.MkdirAll(outputDir, 0755)
  56.     if err != nil {
  57.         log.Fatal(err)
  58.     }
  59.  
  60.     // Opens input directory.
  61.     dir, err := os.Open(inputDir)
  62.     if err != nil {
  63.         log.Fatal(err)
  64.     }
  65.     defer dir.Close()
  66.  
  67.     // Read all files in input directory.
  68.     files, err := dir.Readdir(-1)
  69.     if err != nil {
  70.         log.Fatal(err)
  71.     }
  72.  
  73.     // For every .png file, executes the process in a separated goroutine.
  74.     for _, file := range files {
  75.  
  76.         if file.IsDir() || !strings.HasSuffix(file.Name(), ".png") {
  77.             continue
  78.         }
  79.  
  80.         filesToProcess++
  81.         go processFile(inputDir, strings.Split(file.Name(), ".png")[0], outputDir, resultChan)
  82.     }
  83.  
  84.     // Waits for all process.
  85.     for i := 0; i < filesToProcess; i++ {
  86.         res := <-resultChan
  87.         if res {
  88.             successCount++
  89.         } else {
  90.             errorsCount++
  91.         }
  92.     }
  93.  
  94.     log.Printf("Success = %d, Errors = %d, Time = %s\n", successCount, errorsCount, time.Since(start))
  95. }
  96.  
  97. // processFile process spritesheet by its filename.
  98. // The filename paremeter should not contain extension.
  99. func processFile(inputDir, filename, outputDir string, result chan<- bool) {
  100.  
  101.     // Process sprite sheet file.
  102.     errorOnProcess := createSpriteSheetInfo(inputDir, filename, outputDir)
  103.     if errorOnProcess == nil {
  104.         result <- true
  105.         return
  106.     }
  107.  
  108.     // If an error has occurred, creates a json file with the message.
  109.     outputErrorFileJSON, err := os.Create(outputDir + filename + "-error.json")
  110.     if err != nil {
  111.         log.Println(err)
  112.         result <- false
  113.         return
  114.     }
  115.     defer outputErrorFileJSON.Close()
  116.  
  117.     _, err = outputErrorFileJSON.WriteString(fmt.Sprintf("{ \"error\": \"%s\" }", errorOnProcess.Error()))
  118.     if err != nil {
  119.         log.Println(err)
  120.         result <- false
  121.         return
  122.     }
  123.  
  124.     result <- false
  125. }
  126.  
  127. // createSpriteSheetInfo creates the sprite sheet info json file.
  128. func createSpriteSheetInfo(inputDir, filename, outputDir string) error {
  129.     start := time.Now()
  130.  
  131.     // Reads sprite sheet image.
  132.     inputFileImg, err := os.Open(inputDir + filename + ".png")
  133.     if err != nil {
  134.         return err
  135.     }
  136.     defer inputFileImg.Close()
  137.  
  138.     inputImg, _, err := image.Decode(inputFileImg)
  139.     if err != nil {
  140.         return err
  141.     }
  142.  
  143.     size := inputImg.Bounds().Size()
  144.     visited := make([]bool, size.X*size.Y)
  145.     backgroundColor := inputImg.At(0, 0)
  146.     noColor := color.RGBA{0, 0, 0, 0}
  147.     result := list.List{}
  148.     outputImg := image.NewRGBA(image.Rect(0, 0, size.X, size.Y))
  149.     outputImgBox := image.NewRGBA(image.Rect(0, 0, size.X, size.Y))
  150.  
  151.     // Process all pixels.
  152.     for y := 0; y < size.Y; y++ {
  153.         for x := 0; x < size.X; x++ {
  154.  
  155.             // Pixel is a background color.
  156.             if inputImg.At(x, y) == backgroundColor {
  157.                 continue
  158.             }
  159.  
  160.             // Pixel already visited.
  161.             if visited[x+y*size.X] {
  162.                 continue
  163.             }
  164.  
  165.             minX, maxX, minY, maxY := maxInt, minInt, maxInt, minInt
  166.             queue := list.List{}
  167.             queue.PushBack(image.Point{X: x, Y: y})
  168.             visited[x+y*size.X] = true
  169.  
  170.             // Processing a sprite.
  171.             for queue.Len() > 0 {
  172.  
  173.                 elem := queue.Remove(queue.Front()).(image.Point)
  174.                 outputImg.Set(elem.X, elem.Y, inputImg.At(elem.X, elem.Y))
  175.  
  176.                 if elem.X < minX {
  177.                     minX = elem.X
  178.                 }
  179.  
  180.                 if elem.X > maxX {
  181.                     maxX = elem.X
  182.                 }
  183.  
  184.                 if elem.Y < minY {
  185.                     minY = elem.Y
  186.                 }
  187.  
  188.                 if elem.Y > maxY {
  189.                     maxY = elem.Y
  190.                 }
  191.  
  192.                 for yOff := elem.Y - 1; yOff <= elem.Y+1; yOff++ {
  193.                     for xOff := elem.X - 1; xOff <= elem.X+1; xOff++ {
  194.  
  195.                         // Pixel is a background color.
  196.                         if inputImg.At(xOff, yOff) == backgroundColor {
  197.                             continue
  198.                         }
  199.  
  200.                         // Pixel already visited
  201.                         if visited[xOff+yOff*size.X] {
  202.                             continue
  203.                         }
  204.  
  205.                         queue.PushBack(image.Point{X: xOff, Y: yOff})
  206.                         visited[xOff+yOff*size.X] = true
  207.                     }
  208.                 }
  209.             }
  210.  
  211.             // Marks and checks if this pixel is already taken by other sprite.
  212.             for y := minY; y <= maxY; y++ {
  213.                 for x := minX; x <= maxX; x++ {
  214.                     if outputImgBox.At(x, y) != noColor {
  215.                         return fmt.Errorf("The pixel x = %d, y = %d has already processed", x, y)
  216.                     }
  217.                     outputImgBox.SetRGBA(x, y, color.RGBA{1, 1, 1, 1})
  218.                 }
  219.             }
  220.  
  221.             result.PushBack(ResultItem{
  222.                 Position: image.Point{X: minX, Y: minY},
  223.                 Center:   image.Point{X: minX + (maxX-minX)/2.0, Y: minY + (maxY-minY)/2.0},
  224.                 Size:     image.Point{X: maxX - minX, Y: maxY - minY}})
  225.         }
  226.     }
  227.  
  228.     // Order results by row and then by x-coords.
  229.     resultInSlice, err := orderResults(&result)
  230.     if err != nil {
  231.         return err
  232.     }
  233.  
  234.     // Serializes result into json format.
  235.     data, err := json.MarshalIndent(resultInSlice, "", "\t")
  236.     if err != nil {
  237.         return err
  238.     }
  239.  
  240.     // Creates json file.
  241.     outputFileJSON, err := os.Create(outputDir + filename + ".json")
  242.     if err != nil {
  243.         return err
  244.     }
  245.     defer outputFileJSON.Close()
  246.  
  247.     _, err = outputFileJSON.Write(data)
  248.     if err != nil {
  249.         return err
  250.     }
  251.  
  252.     // Creates image file.
  253.     outputFileImg, err := os.Create(outputDir + filename + ".png")
  254.     if err != nil {
  255.         return err
  256.     }
  257.     defer outputFileImg.Close()
  258.     png.Encode(outputFileImg, outputImg)
  259.  
  260.     log.Printf("%d sprites found in %s.png file, time = %s\n", len(resultInSlice), filename, time.Since(start))
  261.     return nil
  262. }
  263.  
  264. // orderResults orders results by row and its x-coord.
  265. // This function clears listToOrder parameter.
  266. func orderResults(listToOrder *list.List) ([]ResultItem, error) {
  267.     result := make([]ResultItem, listToOrder.Len())
  268.  
  269.     index := 0
  270.     for listToOrder.Len() > 0 {
  271.  
  272.         minY, maxY := 0, listToOrder.Front().Value.(ResultItem).Position.Y
  273.         row := make([]ResultItem, 0)
  274.  
  275.         // Adds on a single array elements that are in the same row.
  276.         for {
  277.  
  278.             if listToOrder.Len() <= 0 {
  279.                 break
  280.             }
  281.  
  282.             elem, ok := listToOrder.Remove(listToOrder.Front()).(ResultItem)
  283.             if !ok {
  284.                 return nil, fmt.Errorf("Elem is not a ResultItem")
  285.             }
  286.  
  287.             if minY > elem.Position.Y+elem.Size.Y || maxY < elem.Position.Y {
  288.                 listToOrder.PushFront(elem)
  289.                 break
  290.             }
  291.  
  292.             maxY = int(math.Max(float64(maxY), float64(elem.Position.Y+elem.Size.Y)))
  293.             row = append(row, elem)
  294.         }
  295.  
  296.         // Orders sprites in a row by its offsetX coords.
  297.         sort.Slice(row, func(i, j int) bool {
  298.             return row[i].Position.X < row[j].Position.X
  299.         })
  300.  
  301.         // Appends to the current result.
  302.         // Also checks if sprites are overlapped on same row.
  303.         for _, item1 := range row {
  304.  
  305.             for _, item2 := range row {
  306.  
  307.                 if item1 == item2 {
  308.                     continue
  309.                 }
  310.  
  311.                 if item1.Position.X <= item2.Position.X+item2.Size.X &&
  312.                     item1.Position.X+item1.Size.X >= item2.Position.X {
  313.                     return nil, fmt.Errorf("Sprite (offsetX = %d, offsetY = %d, width = %d, height = %d)"+
  314.                         " is overlapped with (offsetX = %d, offsetY = %d, width = %d, height = %d)",
  315.                         item1.Position.X, item1.Position.Y, item1.Size.X, item1.Size.Y,
  316.                         item2.Position.X, item2.Position.Y, item2.Size.X, item2.Size.Y)
  317.                 }
  318.  
  319.             }
  320.  
  321.             result[index] = item1
  322.             index++
  323.         }
  324.     }
  325.  
  326.     return result, nil
  327. }
RAW Paste Data