Bitmap to Texture2D problem with colors

I have a small problem with converting a bitmap to a Texture2D. The resulted image of the conversion has the red channel switched with the blue channel :/ I don't know why, because the pixel formats are the same. If someone can help me I will be very happy :)

      System.Drawing.Image image =
System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(image);
Texture2D mapTexture = new Texture2D(Screen.Game.GraphicsDevice, bitmap.Width, bitmap.Height,false,SurfaceFormat.Color);

System.Drawing.Imaging.BitmapData data =
bitmap.LockBits(new System.Drawing.Rectangle(
0, 0, bitmap.Width, bitmap.Height),

byte[] bytes = new byte[data.Height * data.Width*4];

System.Runtime.InteropServices.Marshal.Copy(data.Scan0, bytes, 0, bytes.Length);

mapTexture.SetData<byte>(bytes, 0, data.Height * data.Width * 4);

bitmap.UnlockBits(data);
bitmap.Dispose();
image.Dispose();


What you are seeing is not unusual. Image data is often stored as either BGR or RGB. (Plus there's ARGB vs RGBA and also premultiplication to take into consideration.)

XNA's SurfaceFormat.Color is ARGB.

For XNA 4 you generally want premultiplied alpha (this has more to do with what blend settings you use or need to use). If your image is entirely opaque it won't matter.

I'm pretty sure that the problem in your code arises from your use of LockBits. While LockBits gives you an opportunity to select a pixel format, it doesn't actually perform any conversion. So you've basically told it that a ABGR image is ARGB - you've lied to it! (I believe ABGR is a common format for Windows bitmaps.)

The solution is, of course, to swap the Red and Blue bytes in your temporary buffer before you set them on the XNA texture.

Here's a method i figured out, that is fast and swaps the red and blue fast also:

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

• This code is wrong. First you are modifying the original bitmap itself, and copying with Marshal.Copy is useless since you are already going through all pixels manually. See my correction. – trethaller May 20 '12 at 21:27
    public static Texture2D ConvertToTexture(System.Drawing.Bitmap b, GraphicsDevice graphicsDevice)
{
Texture2D tx = null;
using (MemoryStream s = new MemoryStream())
{
b.Save(s, System.Drawing.Imaging.ImageFormat.Png);
s.Seek(0, SeekOrigin.Begin);
tx = Texture2D.FromStream(graphicsDevice, s);
}
return tx;
}


This works:

// Assuming:
uint[] m_PixelsBuffer;
System.Drawing.Bitmap m_Bitmap;

unsafe {
System.Drawing.Imaging.BitmapData origdata =
m_Bitmap.LockBits(new System.Drawing.Rectangle(0, 0, m_Bitmap.Width, m_Bitmap.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, m_Bitmap.PixelFormat);
uint* byteData = (uint*)origdata.Scan0;
for (int i = 0; i < m_PixelsBuffer.Length; i++) {
m_PixelsBuffer[i] = (byteData[i] & 0x000000ff) << 16 | (byteData[i] & 0x0000FF00) | (byteData[i] & 0x00FF0000) >> 16 | (byteData[i] & 0xFF000000);
}
m_Bitmap.UnlockBits(origdata);
}
texture.SetData(m_PixelsBuffer);