0

I'm trying to copy the contents of an OpenGL (using OpenTK) viewport into a C# Bitmap. I'm using this code:

BitmapData data = bmp.LockBits(Rectangle.FromLTRB(0, 0, bmp.Width, bmp.Height), ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb);
GL.ReadPixels(0, 0, bmp.Width, bmp.Height, PixelFormat.Bgr, PixelType.UnsignedByte, data.Scan0);
bmp.UnlockBits(data);

I've seen code similar to this all over the internet and for the most part, it works fine. But in obscure circumstances, when the width of the bitmap is one a few particular values, it doesn't. The data variable comes back with a stride that does not match the Width and the result is an image heavily skewed to the left.

Has anyone else noticed this behavior? Is it something I'm doing, or is it there some workaround that I need to know about?

edit: Per Fiddler's advice, I've amended my code to:

BitmapData data = bmp.LockBits(dest, ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb);
GL.PushClientAttrib(ClientAttribMask.ClientPixelStoreBit);
GL.PixelStore(PixelStoreParameter.PackAlignment, 4);
GL.ReadPixels(0, 0, bmp.Width, bmp.Height, PixelFormat.Bgr, PixelType.UnsignedByte, data.Scan0);
GL.PopClientAttrib();
bmp.UnlockBits(data);

Doesn't seem to make a difference. Have tried values of 1,2,4, and 8 for PixelStore, and also have tried UnpackAlignment with same values. Could there be another setting at play here?

TrespassersW
  • 403
  • 7
  • 14
  • Are you using OpenTK? Tao? – Magus Feb 11 '14 at 20:10
  • As far as I can tell, this is the correct behavior of LockBits. It is not guaranteed to return a contiguous blocks of data. And ReadPixels requires that the block be contiguous. So it looks to me like all the places that recommend this code are wrong. Or at least, are advocating code that only works most of the time. I haven't been able to find anyone recommending a different technique or acknowledging that this code may not work, though. Makes me wonder if I'm missing something. – TrespassersW Feb 13 '14 at 19:42
  • `LockBits` returns a pointer to a block of memory with a shape defined by its parameters. OpenGL can cope with any shape returned by `LockBits`, but it has to be instructed about its layout. – The Fiddler Mar 09 '14 at 17:35

1 Answers1

2

Check out the Pixel Transfer in the OpenGL wiki, especially the "Pixel layout" section towards the end.

Your Bitmap must have a layout and alignment that matches the current OpenGL pixel transfer parameters. If your Bitmap size is a multiple of four, it will match the default pixel transfer parameters so it will appear to work. If you call GL.PixelStore or you use an odd-sized Bitmap, then the transferred data will be invalid - causing the behavior you observed.

This is a common mistake in OpenGL.

The Fiddler
  • 2,726
  • 22
  • 28
  • Thanks, Fiddler. What you say makes sense, except that from what I can tell, OpenGL defaults to aligning to 4 bytes. C# Bitmap.LockBits also defaults to 4 byte alignment. So it seems like they should already match up. Further, adding GL.PixelStore and passing every possible value for PackAlignment (and UnpackAlignment, just to be sure) doesn't help, but only makes the problem worse. – TrespassersW Mar 11 '14 at 18:48
  • It would really help if you could create a small test case that reproduces this issue. You mentioned that this only occurs on specific sizes - what do they have in common? Finally, I'd try using `PixelFormat.Format32bppArgb` and `PixelFormat.Bgra` to see if that makes any difference. – The Fiddler Mar 11 '14 at 21:42
  • I've temporarily solved my problem by forcing the bitmap width to always be a multiple of 4. This isn't an optimal solution, though, and I'd really like to understand what I'm doing wrong, so I'll put together a minimal test case. Might take me a day or two to get to it. Thanks for your help and suggestions. – TrespassersW Mar 11 '14 at 22:09