Wayback Machinekoobas.hobune.stream
May JUN Jul
Previous capture 13 Next capture
2021 2022 2023
1 capture
13 Jun 22 - 13 Jun 22
sparklines
Close Help
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
  • Asset Store
  • Get Unity

UNITY ACCOUNT

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account
  • Blog
  • Forums
  • Answers
  • Evangelists
  • User Groups
  • Beta Program
  • Advisory Panel

Navigation

  • Home
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
    • Blog
    • Forums
    • Answers
    • Evangelists
    • User Groups
    • Beta Program
    • Advisory Panel

Unity account

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account

Language

  • Chinese
  • Spanish
  • Japanese
  • Korean
  • Portuguese
  • Ask a question
  • Spaces
    • Default
    • Help Room
    • META
    • Moderators
    • Topics
    • Questions
    • Users
    • Badges
  • Home /
avatar image
1
Question by MD_Reptile · Nov 16, 2013 at 08:35 AM · spritetexture2dslicetexture-atlassprite sheets

Coding my own auto-slicer, getting "islands" of pixels at runtime?

I want to take a sprite sheet image like this:

alt text

And determine through code at runtime where there are non-connected shapes (after getting information from the texture2d with getpixels32), like in this case there would be 4 separate shapes for those three dudes and the sword thing (lets call em "islands" cuz it sounds cool)... and in turn I want to find the bounds of these islands of pixels (a 2d box which surrounds their extents) so I can determine where to slice the sprites up at. After doing that magic I could have auto sliced sprite sheets! and without having unity pro that would sure be helpful.

alt text

Could this be done somehow by determining the alpha areas that completely separate the "islands" of pixels that represent each of the four objects? I keep thinking of ways to do it, but I am drawing a blank here - I mean I know I can check the alpha of each pixel, and by that I know where there are no pixels... but how can I check where there are some kind of connected shapes, then decide the borders around them in a square?

Comment
Add comment
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users

1 Reply

· Add your reply
  • Sort: 
avatar image
3
Best Answer

Answer by Seregon · Nov 16, 2013 at 09:42 PM

A relatively straight forward way of doing this (if not necessarily the most efficient), is to flood each shape to see if they're connected. This is a lot easier to explain with code:

EDIT - This is now code for a complete component, see comments below for further explanation. For the original code, skip to the CutSpriteSheet function below.

 using UnityEngine;
 using System.Collections.Generic;
 using System.Linq;
 using System;
 using System.IO;
 
 public class SpriteSheetCutter : MonoBehaviour
 {
     /// <summary>
     /// An sprite sheet from which to cut
     /// </summary>
     public Texture2D inputTex;
     /// <summary>
     /// The background color to ignore
     /// </summary>
     public Color backGroundColor = Color.clear;
 
     // Use this for initialization
     void Start()
     {
 
         List<int[]> Islands = CutSpriteSheet(inputTex);
         Texture2D DisplayTex = HighlightIslands(inputTex, Islands);
 
         // Render the sprite sheet with highlighting:
         MeshRenderer Mr = GetComponent<MeshRenderer>();
         Mr.material.mainTexture = DisplayTex;
         DisplayTex.wrapMode = TextureWrapMode.Clamp;
         DisplayTex.Apply(true);
 
         // Extract sprites as individual textures:
         Texture2D[] Sprites = GetSprites(inputTex, Islands);
         Directory.CreateDirectory(Application.dataPath + "/sprites/");
         for (int i = 0; i < Sprites.Length; i++)
         {
             byte[] bytes = Sprites[i].EncodeToPNG();
             FileStream file = File.Open(Application.dataPath + "/sprites/sprite" + i + ".png", FileMode.Create);
             BinaryWriter bw = new BinaryWriter(file);
             bw.Write(bytes);
             file.Close();
         }
     }
 
     /// <summary>
     /// Extract sprites from sheet as individual textures
     /// </summary>
     Texture2D[] GetSprites(Texture2D input, List<int[]> Islands)
     {
         Texture2D[] output = new Texture2D[Islands.Count];
 
         int i = 0;
         foreach (int[] coords in Islands)
         {
             int width = coords[2] - coords[0];
             int height = coords[3] - coords[1];
 
             Texture2D sprite = new Texture2D(width, height);
             Color[] pix = input.GetPixels(coords[0], coords[1], width, height);
 
             sprite.SetPixels(pix);
             sprite.Apply(true);
 
             output[i++] = sprite;
         }
 
         return output;
     }
 
     /// <summary>
     /// Highlight each sprite with a red border
     /// </summary>
     Texture2D HighlightIslands(Texture2D Sprites, List<int[]> Islands)
     {
         Color[] pix = Sprites.GetPixels();
         int width = Sprites.width;
         int height = Sprites.height;
 
         for (int x = 0; x < width; x++)
             for (int y = 0; y < height; y++)
             {
                 foreach (int[] coords in Islands)
                 {
                     if ((x == coords[0] || x == coords[2]) &&
                         (y >= coords[1] && y <= coords[3]))
                         pix[x + width * y] = Color.red;
 
                     if ((y == coords[1] || y == coords[3]) &&
                         (x >= coords[0] && x <= coords[2]))
                         pix[x + width * y] = Color.red;
                 }
             }
 
         Texture2D ret = new Texture2D(width, height);
         ret.SetPixels(pix);
         ret.Apply();
 
         return ret;
     }
 
     /// <summary>
     /// Find contiguous islands of pixels in a sprite sheet.
     /// </summary>
     /// <returns>
     /// A list of coordinates for each sprite, in the form:  [x_min, y_min, x_max, y_max]
     /// </returns>
     List<int[]> CutSpriteSheet(Texture2D Sprites)
     {
         // Get pixels, width and height from texture:
         Color[] pix = Sprites.GetPixels();
         int width = Sprites.width;
         int height = Sprites.height;
 
         // Create a new array which identifies which island each pixel belongs to.
         int[] Islands = new int[pix.Length];
 
         // Each pixel starts as it's own island, unless it's transparent, in which case we set it to -1
         for (int i = 0; i < Islands.Length; i++)
         {
             if (pix[i] == backGroundColor)
                 Islands[i] = -1;
             else
                 Islands[i] = i;
         }
 
         // For simplicity, we'll convert this to a 2d array
         int[,] Islands2d = new int[width, height];
         for (int i = 0; i < Islands.Length; i++)
         {
             int x = i % width;
             int y = (i - x) / width;
             Islands2d[x, y] = Islands[i];
         }
 
         // Now we spread each island
         bool Changed = true;
         while (Changed)
         {
             // If no changes are made this loop, we're done
             Changed = false;
 
             // For each pixel
             for (int x = 0; x < width; x++)
             {
                 for (int y = 0; y < height; y++)
                 {
                     // If this pixel is transparent, do nothing and continue to the next pixel
                     if (Islands2d[x, y] == -1)
                         continue;
 
                     // For each pixel neighbouring that pixel
                     for (int i = -1; i <= 1; i++)
                     {
                         // Check the neighbouring pixel is within bounds
                         if ((x + i) < 0 || (x + i) >= width)
                             continue;
 
                         for (int j = -1; j <= 1; j++)
                         {
                             // Check the neighbouring pixel is within bounds
                             if ((y + j) < 0 || (y + j) >= height)
                                 continue;
 
                             // If this and the neighbouring pixel are not in the same island, join them
                             if (Islands2d[x, y] > Islands2d[x + i, y + j] && Islands2d[x + i, y + j] != -1)
                             {
                                 Islands2d[x, y] = Islands2d[x + i, y + j];
 
                                 Changed = true;
                             }
                         }
                     }
                 }
             }
         }
 
         // Now all connected islands of points should have the same number (ID)
         // We get the number of each island
         List<int> Island_IDs = new List<int>();
         for (int x = 0; x < width; x++)
         {
             for (int y = 0; y < height; y++)
             {
                 if (Islands2d[x, y] != -1 && !Island_IDs.Contains(Islands2d[x, y]))
                     Island_IDs.Add(Islands2d[x, y]);
             }
         }
 
         List<int[]> output = new List<int[]>();
 
         // For each ID, get the upper and lower bounds of that island's coordinates
         for (int i = 0; i < Island_IDs.Count; i++)
         {
             int x_min = int.MaxValue;
             int x_max = int.MinValue;
             int y_min = int.MaxValue;
             int y_max = int.MinValue;
 
             for (int x = 0; x < width; x++)
             {
                 for (int y = 0; y < height; y++)
                 {
                     // If this point belongs to this island ID
                     if (Islands2d[x, y] == Island_IDs[i])
                     {  // Check if its coordinates extend the bounds of this island:
                         x_min = x < x_min ? x : x_min;
                         x_max = x > x_max ? x : x_max;
                         y_min = y < y_min ? y : y_min;
                         y_max = y > y_max ? y : y_max;
                     }
                 }
             }
 
             // What you do with this information now is up to you, for now I'll print it to the console:
             print("-----");
             print("Island ID: " + Island_IDs[i]);
             print("Top left corner:     " + x_min + ", " + y_min);
             print("Bottom right corner: " + x_max + ", " + y_max);
 
             // Also output as an array of coordinates
             output.Add(new int[] { x_min, y_min, x_max, y_max });
         }
 
         return output;
     }
 }

A few notes:

- This code is untested, I'm testing it now and will edit with any corrections. - I'm assuming that the top left corner of a texture is (0, 0), I may have this wrong. - This code is horrifically inefficient, in the interest of being readable. If needed I'll write a more efficient version and post later. - If a sprite consists of two areas of unconnected pixels, this method will treat each area as a separate sprite. You could possibly fix this by testing when sprites overlap.

Let me know if this works/helps, and if I need to explain anything better.

EDIT - see comments below for complete explanation

Comment
Add comment · Show 12 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image MD_Reptile · Nov 16, 2013 at 09:53 PM 0
Share

this is an epic answer haha - give me a few hours to test it out and see what I come up with, but man that is definitely far above and beyond what I expected anyone to answer :P so major thanks for effort!

avatar image Seregon · Nov 16, 2013 at 10:05 PM 0
Share

No worries, looked like a nice challenge. Still testing it myself, and will update in a little while.

avatar image Seregon · Nov 16, 2013 at 10:13 PM 0
Share

Tested it, and it works fine. I used your picture above, and tweaked the script a little to cut out white ins$$anonymous$$d of transparent pixels: testing script

unityanswers1.png (91.9 kB)
avatar image Seregon · Nov 16, 2013 at 11:47 PM 1
Share

@$$anonymous$$D_Reptile Was just about to post back with exactly that. I've updated the code in the answer above to a complete script (was too long to post in a comment). It includes the original function (with a few tweaks), the function for drawing the red borders above, and a new function for cutting out the individual sprites.

That should be enough code to work from, but just in case I'll explain how you'd do what you asked. I don't actually work with sprites much, and don't know if your using a particular library, so I'm not sure the exact arguments your looking for, but you can construct the rectangle bounds like this (near the end of the CuteSpriteSheet function):

 Rect spriteRect = new Rect(x_$$anonymous$$, y_$$anonymous$$, (x_max - x_$$anonymous$$), (y_max - y_$$anonymous$$));
 
avatar image tanoshimi · Jul 26, 2016 at 08:08 PM 1
Share

Great answer. In case anyone else comes here from the future like me, line 181 should read:

  for (int y = 0; y < height; y++)

:)

avatar image Seregon tanoshimi · Jul 27, 2016 at 10:11 AM 0
Share

Fixed - thank you.

Show more comments

Your answer

Hint: You can notify a user about this post by typing @username

Up to 2 attachments (including images) can be used with a maximum of 524.3 kB each and 1.0 MB total.

Follow this Question

Answers Answers and Comments

19 People are following this question.

avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image

Related Questions

How do I access a texture slice's rect via scripting? 1 Answer

Slicing sliced sprite via script 0 Answers

Sprite Rendering Cost Calculation 1 Answer

Lines appear between sprites from sprite sheets 7 Answers

Cast Texture2d to Sprite 4 Answers


Enterprise
Social Q&A

Social
Subscribe on YouTube social-youtube Follow on LinkedIn social-linkedin Follow on Twitter social-twitter Follow on Facebook social-facebook Follow on Instagram social-instagram

Footer

  • Purchase
    • Products
    • Subscription
    • Asset Store
    • Unity Gear
    • Resellers
  • Education
    • Students
    • Educators
    • Certification
    • Learn
    • Center of Excellence
  • Download
    • Unity
    • Beta Program
  • Unity Labs
    • Labs
    • Publications
  • Resources
    • Learn platform
    • Community
    • Documentation
    • Unity QA
    • FAQ
    • Services Status
    • Connect
  • About Unity
    • About Us
    • Blog
    • Events
    • Careers
    • Contact
    • Press
    • Partners
    • Affiliates
    • Security
Copyright © 2020 Unity Technologies
  • Legal
  • Privacy Policy
  • Cookies
  • Do Not Sell My Personal Information
  • Cookies Settings
"Unity", Unity logos, and other Unity trademarks are trademarks or registered trademarks of Unity Technologies or its affiliates in the U.S. and elsewhere (more info here). Other names or brands are trademarks of their respective owners.
  • Anonymous
  • Sign in
  • Create
  • Ask a question
  • Spaces
  • Default
  • Help Room
  • META
  • Moderators
  • Explore
  • Topics
  • Questions
  • Users
  • Badges