3
\$\begingroup\$

I would like to split a large texture into a one dimensional array of smaller textures. How should I do this?

\$\endgroup\$
1
  • 1
    \$\begingroup\$ Usually you want to avoid splitting up textures - generally you will want to combine textures as much as possible for performance reasons. bearcdp's answer has you covered. \$\endgroup\$ Apr 25, 2011 at 7:14

2 Answers 2

7
\$\begingroup\$

Are you talking about 2D or 3D Textures?

When you're drawing 2d graphics with SpriteBatch.Draw(), you have the option to set a source and destination rectangle. To keep track of a collection of smaller textures, you'd have an array/list/whatever of Rectangle objects that represent the source rectangle of your loaded Texture.

See MSDN's documentation for SpriteBatch: http://msdn.microsoft.com/en-us/library/ff433987.aspx

I haven't done any 3D in XNA, but I presume there's a similar mechanism.

\$\endgroup\$
1
\$\begingroup\$

Here's a method that should do what you need:

    /// <summary>
    /// Splits a texture into an array of smaller textures of the specified size.
    /// </summary>
    /// <param name="original">The texture to be split into smaller textures</param>
    /// <param name="partWidth">The width of each of the smaller textures that will be contained in the returned array.</param>
    /// <param name="partHeight">The height of each of the smaller textures that will be contained in the returned array.</param>
    public Texture2D[] Split(Texture2D original, int partWidth, int partHeight, out int xCount, out int yCount)
    {
        yCount = original.Height / partHeight + (partHeight % original.Height == 0 ? 0 : 1);//The number of textures in each horizontal row
        xCount = original.Height / partHeight + (partHeight % original.Height == 0 ? 0 : 1);//The number of textures in each vertical column
        Texture2D[] r = new Texture2D[xCount * yCount];//Number of parts = (area of original) / (area of each part).
        int dataPerPart = partWidth * partHeight;//Number of pixels in each of the split parts

        //Get the pixel data from the original texture:
        Color[] originalData = new Color[original.Width * original.Height];
        original.GetData<Color>(originalData);

        int index = 0;
        for (int y = 0; y < yCount * partHeight; y += partHeight)
            for (int x = 0; x < xCount * partWidth; x += partWidth)
            {
                //The texture at coordinate {x, y} from the top-left of the original texture
                Texture2D part = new Texture2D(original.GraphicsDevice, partWidth, partHeight);
                //The data for part
                Color[] partData = new Color[dataPerPart];

                //Fill the part data with colors from the original texture
                for (int py = 0; py < partHeight; py++)
                    for (int px = 0; px < partWidth; px++)
                    {
                        int partIndex = px + py * partWidth;
                        //If a part goes outside of the source texture, then fill the overlapping part with Color.Transparent
                        if (y + py >= original.Height || x + px >= original.Width)
                            partData[partIndex] = Color.Transparent;
                        else
                            partData[partIndex] = originalData[(x + px) + (y + py) * original.Width];
                    }

                //Fill the part with the extracted data
                part.SetData<Color>(partData);
                //Stick the part in the return array:                    
                r[index++] = part;
            }
        //Return the array of parts.
        return r;
    }

And here's an example of how to use it:

    protected override void LoadContent()
    {
        //Create a new SpriteBatch, which can be used to draw textures.
        spriteBatch = new SpriteBatch(GraphicsDevice);

        Texture2D tex = Content.Load<Texture2D>("MyTexture");
        parts = Split(tex, 10, 10, out xCount, out yCount);
    }

    Texture2D[] parts;
    int
        xCount,
        yCount,
        partWidth = 10;
    protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.Clear(Color.CornflowerBlue);

        spriteBatch.Begin(SpriteSortMode.Texture, BlendState.Opaque);
        for (int y = 0; y < yCount; y++)
            for (int x = 0; x < xCount; x++)
                spriteBatch.Draw(parts[x + y * xCount], new Rectangle(10 + x * 40 + x * 10, 10 + y * 40 + y * 10, 40, 40), Color.White);
        spriteBatch.End();

        base.Draw(gameTime);
    }

Please realize that this is not quite the most efficient way to do what you want, but it is very maintainable and easy to understand (I hope).

If you're looking for more efficiency, and have some experience with XNA, I would recommend using RenderTargets1. Just set each texture "part" in your array as a render target, then draw the original large texture, offset by the position of the part you're drawing. Be aware that this method requires your XNA project to target the Hi-Def profile 99% of the time, which is why I couldn't give you a working sample with that method (my current computer doesn't support the Hi-Def profile). I have used RenderTargets to do this exact thing in the past, though, so it's possible. It's also much easier2 now than in previous versions of XNA, thanks to the RenderTarget changes in XNA Game Studio 4.03.

Please feel free to comment if you need any clarification or more help :)

Links:

  1. http://msdn.microsoft.com/en-us/library/microsoft.xna.framework.graphics.rendertarget%28v=xnagamestudio.31%29.aspx
  2. https://web.archive.org/web/20150920191915/http://blogs.msdn.com/b/shawnhar/archive/2007/02/04/xna-rendertarget-semantics.aspx
  3. https://web.archive.org/web/20150919060328/http://blogs.msdn.com/b/shawnhar/archive/2010/03/26/rendertarget-changes-in-xna-game-studio-4-0.aspx
\$\endgroup\$
5
  • \$\begingroup\$ Actually, I suspect reading the data directly out of the texture that is already CPU-side will be faster than pulling it back off the GPU from a render target. Because you'd do this at load-time, rather than per-frame, you can afford to be a little bit more relaxed about performance anyway. Additionally Texture2D.GetData has an overload where you can specify a source rectangle - this is probably preferable to to the additional copy required to perform the crop yourself. \$\endgroup\$ Apr 25, 2011 at 7:20
  • \$\begingroup\$ @Andrew Whether the GPU or the CPU is faster depends on the computer. On mine, the GPU is much faster than the CPU, so using rendertargets would be much faster. With a faster CPU, though, you're absolutely right. As for rendering at load-time, I've hit problems trying to access the graphics device outside of the game's Draw method (using older hardware and Reach profile), so this varies per computer. Correct me if I'm wrong, but I thought that overload threw exceptions if the clip is out of the texture's bounds. My sample fills the overlap areas with black, which I thought would be preferable. \$\endgroup\$ Apr 25, 2011 at 17:37
  • \$\begingroup\$ @Hoffmann : You can use render targets just fine in the reach profile. Also, you can access the graphics device inside LoadContent (You need to have a graphics device to load textures, after all.) @Andrew : With Hoffmann's rendertarget approach the texture data never has to leave VRAM, so it should be at least as fast. \$\endgroup\$
    – Blecki
    Apr 25, 2011 at 22:44
  • \$\begingroup\$ @Blecki : Yes, you can technically use render targets in Reach profile, but that doesn't mean each individual graphics device behaves the way it should. For example, with my computer's GPU, the targets aren't drawn on until the draw frame after they're set via GraphicsDevice.SetRenderTarget for whatever reason. When I tried using render targets for the sample I provided, this required the splitting to be done per-frame, which, as @Andrew said, is expensive compared to pixel copying at load-time. \$\endgroup\$ Apr 25, 2011 at 23:34
  • \$\begingroup\$ @Blecki: True - if you keep them as RenderTarget2D then leaving them on the GPU will be faster. There are advantages to having them as plain ol' Texture2D, though - see this question. @Hoffmann The point I was getting at was that the CPU-GPU transfer dominates the performance of both the CPU and the GPU themselves. Both can perform a crop operation extremely quickly. Although, by copying each pixel with a bounds check, your method is a lot slower than what is possible. \$\endgroup\$ Apr 26, 2011 at 4:52

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .