Extracting Texture Data

Introduction

It's no surprise how often people want to be able to pull out raw pixel information from a Texture2D object. There are lots of good reasons for doing this. For example, if you want to save a screenshot to a file, or make some tweaks to a texture. Perhaps you even want to give your players a way to edit an image. Not unlike the feature in Animal Crossing, where you get to custom paint Blanca's face. Of course, all of that is a lot more work than I can cover in this single tutorial, but we will tackle the first step: pulling out color information from a Texture2D.

Extracting the Data

The first step is to actually pull out the raw image data. The simplest way to do this is with the Texture2D's GetData method. There are a couple of overloads for this method, but we'll start with the simplest one.

Let's start off by assuming we have a Texture2D object. You perhaps loaded this through the content pipeline (the normal way) or maybe you made it from rendering the screen to a texture, or perhaps another way.

Texture2D texture = Content.Load<Texture2D>("textureName");

You need a place to store the raw texture data. Now, you actually have a lot of choices here. There are lots of formats that you can store color data in. As a "side quest" to this tutorial, you might want to investigate the members of the PackedVector namespace, since you can use most of those, depending on your texture's image format. But in reality, in most cases, we'll be able to use the Color struct, which is probably the most convenient way of storing our raw color data.

So step #2 is to define an array of Color structs to store the raw color data that we'll be retrieving.

Color[] rawData = new Color[texture.Width * texture.Height];

A couple of things to note here: this is a 1-dimensional array, even though the texture will be 2D (we'll deal with that in a minute) and we're going to need to know the total number of pixels that we're retrieving. If we're getting the entire picture, this will be the texture's width multiplied by the texture's height. (In a minute, we'll also look at pulling out just a small piece of the full texture.)

Finally, step #3 is to actually grab the texture data:

texture.GetData<Color>(rawData);

Note that this is a generic method (hence the "<Color>" part). Whatever type we're trying to retrieve, we put in here. Since we've got an array of type Color, we put Color in here. (If you followed the earlier side quest, and wanted to try another type, like Byte4, you would simply put that in here instead.)

This will copy the texture data into our rawData array.

We've got the data we were looking for!

Extracting a Region of the Data

You may also want to retrieve only a part of the data. That's also fairly easy to do, using a different overload of the GetData method. Of course, we'll also need to use a different size for our rawData array.

The simplest way to tackle this is by defining a rectangle with the position and size that we want to extract.

int x = 10;
int y = 10;
int width = 40;
int height = 40;
Rectangle extractRegion = new Rectangle(x, y, width, height);

Using this, we can then construct the correct size of the rawData array, where things will get stored:

Color[] rawData = new Color[width * height];

We then call GetData using a different overload than before. This requires a few more parameters, including the rectangle we just built:

texture.GetData<Color>(0, extractRegion, rawData, 0, width * height);

The first parameter is the mipmap level. That's a discussion for another day, but basically, mipmap level 0 is the full image. (Higher mipmap levels represent smaller, subsampled images.) We then pass in the rectangle that we want to extract from, and our buffer (rawData) to store the data in.

The last two parameters indicate what chunk of the requested region to actually extract. We want all of it, so we start at zero, and get width * height (one per pixel in the entire rectangular area) total elements.

This results in an array, just like before, containing only a specific region of the original Texture2D object.

Converting to a Grid

There's one final task that we should look at before wrapping up. While what we've discussed has made it possible to grab raw image data, it would definitely be convenient if we had it in a 2D grid, instead of a 1D array. This would make it so we can grab the 10th pixel on the 7th row much more easily.

To perform this task, it will be important to note that the data we get back is in "row-major order". This means that the elements in the array store the entire first row first (starting from left to right) then the second row, then the third, and so on until you reach the last row.

This is contrasted with column-major order, where the entire first column (usually starting at the top) is first, then the second column, and so on.

It's important to know that so that you can figure out how to organize the one-dimensional array into a 2D array.

The following code will make this conversion:

// Note that this stores the pixel's row in the first index, and the pixel's column in the second,
// with this setup.
Color[,] rawDataAsGrid = new Color[height, width];
for (int row = 0; row < height; row++)
{
    for (int column = 0; column < width; column++)
    {
        // Assumes row major ordering of the array.
        rawDataAsGrid[row, column] = rawData[row * width + column];
    }
}

And now you're free to ask for individual pixel colors:

Color color = rawDataAsGrid[7, 10]; // 10th pixel on the 7th row

Troubleshooting.png Having problems with this tutorial? Try the troubleshooting page!