Auto Slicing

This week we started to test the first tool’s features that we will use to develop our engine for Action-RPG. The auto slicing of an image in multiple sprites by analysis of pixels.

Firstly, we ran each pixel in the image, from left to right, ignoring transparent pixels, at end of each line, we move on to the next, and starting again from the left. Upon encountering a non-transparent pixel, we resorted to another part of the code that is responsible for returning a Rectangle object containing the information of the position of the sprite image.

private GraphicsDeviceManager _graphics;
private SpriteBatch _spriteBatch;

private readonly List _sources;
private Texture2D _texture;
private Color[] _pixelColours;

public Game1()
{
    _graphics = new GraphicsDeviceManager(this)
    {
        PreferredBackBufferWidth = 800,
        PreferredBackBufferHeight = 600
    };
    Content.RootDirectory = "Content";
    _sources = new List();
}

protected override void LoadContent()
{
    _spriteBatch = new SpriteBatch(GraphicsDevice);

    // http://www.hoshi.trinitymugen.net/dcuseitem.png
    _texture = Content.Load("item");
    _pixelColours = _texture.GetPixels();

    GenerateSources();
}

private void GenerateSources()
{
    for (var y = 0; y < _texture.Height; y++)
    {
        for (var x = 0; x < _texture.Width; x++) { if (_pixelColours.IsTransparent(x, y, _texture.Width)) continue; if (_sources.Any(r => r.Contains(new Point(x, y)))) continue;

            _sources.Add(new SourceRectangle(_texture, _pixelColours, x, y).Generate());
        }
    }
}

In this class we received what position was found that pixel and the texture data. The Generate() method is where the magic happens.
Using the concept of Flood Fill, we added in a queue the starting pixel point (x, y) with the structure Point.
The code is repeated while there are queue items.
At each iteration that to pass the validations we added in a queued the pixels that are around.

public class SourceRectangle
{
    private readonly Texture2D _texture;
    private readonly Color[] _pixelColours;
    private readonly int _x;
    private readonly int _y;
    private readonly Queue _queue;
    private readonly HashSet _points;

    public SourceRectangle(Texture2D texture, Color[] pixelColours, int x, int y)
    {
        _texture = texture;
        _pixelColours = pixelColours;
        _x = x;
        _y = y;
        _queue = new Queue();
        _points = new HashSet();
    }

    public Rectangle Generate()
    {
        _queue.Enqueue(new Point(_x, _y));
        while (_queue.Any())
        {
            var p = _queue.Dequeue();

            if (_points.Contains(p))
                continue;

            if (p.X < 0 || p.Y < 0 || p.X >= _texture.Width || p.Y >= _texture.Height)
                continue;

            if (_pixelColours.IsTransparent(p.X, p.Y, _texture.Width))
                continue;

            _points.Add(p);

            // UP
            _queue.Enqueue(new Point(p.X, p.Y + 1));
            // UP-RIGHT
            _queue.Enqueue(new Point(p.X + 1, p.Y + 1));
            // RIGHT
            _queue.Enqueue(new Point(p.X + 1, p.Y));
            // RIGHT-DOWN
            _queue.Enqueue(new Point(p.X + 1, p.Y - 1));
            // DOWN
            _queue.Enqueue(new Point(p.X, p.Y - 1));
            // DOWN-LEFT
            _queue.Enqueue(new Point(p.X - 1, p.Y - 1));
            // LEFT
            _queue.Enqueue(new Point(p.X - 1, p.Y));
            // LEFT-UP
            _queue.Enqueue(new Point(p.X - 1, p.Y + 1));
        }

        var left = _points.Min(r => r.X);
        var top = _points.Min(r => r.Y);
        var right = _points.Max(r => r.X);
        var bottom = _points.Max(r => r.Y);
        var width = right - left + 1;
        var height = bottom - top + 1;

        return new Rectangle(left, top, width, height);
    }
}

The main idea is to add all non-transparent points that it are interconnected in a HashSet<Point>, because is not needed store repeated pixels, and finally use Linq to query the extremes and then return the data.

With a 2048×2048 image this code takes an average of 48 seconds to find 798 objects.

corte automatico

 

project in github