Friday, October 10, 2008

Converting System.Drawing.Bitmap to XNA Texture2D

For those of you who visit this blog for art or writing topics, this is your fair warning: this post will hold no interest for you.

For those independent XNA game developers who find this post via Google, I hope I can help with a problem I've seen talked about in a few places on the 'net.

In the game I am currently working on, I have a need to load a bunch of bitmaps into memory, and then I need to turn those bitmaps into texture objects. I'm using a blend of GDI+ and XNA in my application, you see. Previously, when I was using MDX in conjunction with GDI+, this was no problem because there was a direct conversion available. Since XNA is dual-targeted at both the 360 and the Windows platforms, there isn't a conversion available.

On various forums I've seen solutions batted about relating to doing a per-pixel copy of the images from one format to the other (often using Bitmap.GetPixel, which is horribly slow -- you're much better off using Bitmap.LockBits, but even that is not nearly ideal).

The solution I have is simply relating to memory streams, since a Bitmap can be saved to a stream, and a Texture2D can be loaded from a stream. This approach might seem like a waste of memory, but it's the most processor-efficient way to do this. My game is able to process several dozen 28x28 images in under two seconds using this approach. The trick is to do the conversions just in little bits, as you need the images, rather than doing them all up front (which would take forever, and really give the garbage collector fits). I leave that part up to you. Here's the C# code for the actual conversion, which is quite simple:
Bitmap b = new Bitmap( nameOfFile );
Texture2D tx = null;
using ( MemoryStream s = new MemoryStream() )
{
b.Save( s, System.Drawing.Imaging.ImageFormat.Png );
s.Seek( 0, SeekOrigin.Begin ); //must do this, or error is thrown in next line
tx = Texture2D.FromFile( GraphicsDevice, s );
}

That's all there is to it!


(Added point of interest: It seems that XNA is unable to load GIF files -- presumably a licensing thing, knowing GIF -- but of course regular .NET is able to load those just fine. Using this sort of code provides a way for you to load GIFs or any other format that .NET supports but that XNA does not into XNA Texture2D objects. This is handy for me, because at present my project has... uh... just over 8,500 GIF files in it.)

9 comments:

Stephen Parrish said...

I love it when you talk dirty.

Seriously, thanks for all the computer nerd geek help you've given me this past year or so. You da man.

Christopher M. Park said...

Thanks, Stephen. Glad I could help!

a thankful user said...

Thank you very much, saved a lot of time

Do you know how to convert a ".spritefont"-file to a SpriteFont object? I am trying to include all Content-files into a single EXEcutable using Resources because a single file is easier to distribute.

Thanks in advance.

Christopher M. Park said...

Glad that was helpful! I'm not familiar with spritefont files, though.

Jaska said...

Hi guys, i figured out even faster method, which also switches the red and blue bytes because in xna 4.0 they have changed the format from bgra to rgba.. Here you go:

private Texture2D GetTexture(GraphicsDevice dev, System.Drawing.Bitmap bmp)
{
int[] imgData = new int[bmp.Width * bmp.Height];
Texture2D texture = new Texture2D(dev, bmp.Width, bmp.Height);

unsafe
{
// lock bitmap
System.Drawing.Imaging.BitmapData origdata =
bmp.LockBits(new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, bmp.PixelFormat);

uint* byteData = (uint*)origdata.Scan0;

// Switch bgra -> rgba
for (int i = 0; i < imgData.Length; i++)
{
byteData[i] = (byteData[i] & 0x000000ff) << 16 | (byteData[i] & 0x0000FF00) | (byteData[i] & 0x00FF0000) >> 16 | (byteData[i] & 0xFF000000);
}

// copy data
System.Runtime.InteropServices.Marshal.Copy(origdata.Scan0, imgData, 0, bmp.Width * bmp.Height);

byteData = null;

// unlock bitmap
bmp.UnlockBits(origdata);
}

texture.SetData(imgData);

return texture;
}

Anonymous said...

Jaska. How do i do this for 64bit os?

Anonymous said...

Many thanks ! :)

Anonymous said...

Don't works for me. Error says:
Attempt to read or write protected memory .... (sorry,error translated from french). Is it related to 64 bits OS ?

Christopher M. Park said...

Probably not, as I had a 64bit OS even back that far. Most likely something has changed in .net or xna since 2008. I haven't used xna since mid 2008 (I quickly moved to SlimDX and then later Unity 3D), so unfortunately I'm not likely to be much help.