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?

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*.EncodeToPNG();*

FileStream file = File.Open(Application.dataPath + “/sprites/sprite” + i + “.png”, FileMode.Create);
BinaryWriter bw = new BinaryWriter(file);
bw.Write(bytes);
file.Close();
}
}

///


/// Extract sprites from sheet as individual textures
///

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;
}

///


/// Highlight each sprite with a red border
///

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;
}

///


/// Find contiguous islands of pixels in a sprite sheet.
///

///
/// A list of coordinates for each sprite, in the form: [x_min, y_min, x_max, y_max]
///
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 == backGroundColor)
Islands = -1;
else
Islands = 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*;*
}

// 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 Island_IDs = new List();
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*)
_{ // 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*);*
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