I am trying to write a function that takes a System.Drawing.Bitmap, and spits out a Monogame Texture2D as quickly as possible.
I've managed to make it work using GetPixel on the bitmap, which is a no-brainer, but it's far too slow, of course.
I dug around and discovered a much faster technique for quickly accessing the pixel values using LockBits and Marshal.Copy. The problem is, it works perfectly on some images, while others end up looking like this: Example Image
From what I've been able to gather, this has something to do with not properly compensating for Stride, but I am new to directly working with image data at this level, and I am completely lost. What am I doing wrong here that leads to this greyscale-ish slanted result?
When this works, which it does for most images, it is very fast and exactly what I want. If it matters, I'm exclusively going to be handling pretty typical JPG/PNG images off the web.
public static Texture2D FromBitmap(MagickImage input, GraphicsDevice graphicsDevice)
{
var bounds = new Rectangle(0, 0, input.Width, input.Height);
var bitmap = input.ToBitmap();
var output = new Texture2D(graphicsDevice, bitmap.Width, bitmap.Height);
var outputArray = new Color[bitmap.Width * bitmap.Height];
var bitmapBits = bitmap.LockBits(bounds, ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
var bitmapDepth = Image.GetPixelFormatSize(bitmapBits.PixelFormat) / 8;
var bitmapBuffer = new byte[bitmapBits.Stride * bitmapBits.Height];
Marshal.Copy(bitmapBits.Scan0, bitmapBuffer, 0, bitmapBuffer.Length);
for (var y = 0; y < bitmap.Height; y++)
{
for (var x = 0; x < bitmap.Width; x++)
{
var arrayPoint = x + (y * bitmap.Width);
outputArray[arrayPoint].R = bitmapBuffer[(arrayPoint * bitmapDepth) + 2];
outputArray[arrayPoint].G = bitmapBuffer[(arrayPoint * bitmapDepth) + 1];
outputArray[arrayPoint].B = bitmapBuffer[(arrayPoint * bitmapDepth) + 0];
}
}
output.SetData(outputArray);
return output;
}
}
Edit: I had a simple mistake in pointing to the arrayPoint within outputArray, a leftover from a previous attempt. Correcting this + rewriting the arrayPoint definition fixed it. The below works:
var arrayPoint = (y * bitmapBits.Stride) + (x * bitmapDepth);
outputArray[(y * bitmap.Width) + x].R = bitmapBuffer[arrayPoint + 2];
outputArray[(y * bitmap.Width) + x].G = bitmapBuffer[arrayPoint + 1];
outputArray[(y * bitmap.Width) + x].B = bitmapBuffer[arrayPoint + 0];
You're ignoring the stride
, and that means that some images will work and some won't.
Stride is NOT bitmap.Width * bitmapDepth.
The stride is the width of a single row of pixels (a scan line), rounded up to a four-byte boundary.
So you always have to increase your arrayPoint by stride for each Row, and not some manual calculation.