The fastest Mogre.Image or PixelBox => System.Drawing.Bitmap

victorporof

30-04-2011 14:02:25

I've been searching everywhere for this, and could not find anything even close to efficient. Best thing was here: MogreImageToBitmap, but I guess having an O(n^2) speed is very bad, especially when someone has to do this every frame.

Long story short, here's the code:


[DllImport("kernel32.dll", EntryPoint = "RtlMoveMemory")]
static extern void CopyMemory(IntPtr Destination, IntPtr Source, uint Length);

public void Handle(PixelBox pixels)
{
unsafe
{
Bitmap bmp = new Bitmap((int)pixels.box.Width, (int)pixels.box.Height, PixelFormat.Format24bppRgb);
BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.WriteOnly, bmp.PixelFormat);
CopyMemory(bmpData.Scan0, pixels.data, pixels.box.Width * pixels.box.Height * 3);

bmp.UnlockBits(bmpData);

//do stuff with the bitmap object
//ex: bmp.Save("test.bmp");
}
}


Now let's say you want to convert your RenderWindow contents to a Bitmap:


public void Grab(ref RenderWindow window)
{
PixelFormat pf = PixelFormat.PF_R8G8B8;
var bytes = new byte[(int)(window.Width * window.Height * PixelUtil.GetNumElemBytes(pf))];

unsafe
{
fixed (byte* bytePtr = bytes)
{
var pixels = new PixelBox(window.Width, window.Height, 1, pf, new IntPtr(bytePtr));
window.CopyContentsToMemory(pixels);

Handle(pixels);
}
}
}


And with a Mogre Image:


public void Grab(ref RenderWindow window)
{
PixelFormat pf = PixelFormat.PF_R8G8B8;
var bytes = new byte[(int)(window.Width * window.Height * PixelUtil.GetNumElemBytes(pf))];

unsafe
{
fixed (byte* bytePtr = bytes)
{
var pixels = new PixelBox(window.Width, window.Height, 1, pf, new IntPtr(bytePtr));
window.CopyContentsToMemory(pixels);

using (var image = new Image())
{
image.LoadDynamicImage((byte*)pixels.data.ToPointer(), pixels.box.Width, pixels.box.Height, pf);
Handle(image.GetPixelBox());
}
}
}
}

Meharin

30-04-2011 21:23:45

I've been searching everywhere for this, and could not find anything even close to efficient. Best thing was here: MogreImageToBitmap, but I guess having an O(n^2) speed is very bad, especially when someone has to do this every frame.


What are you trying to accomplish?

I avoid an inefficient copy from GPU to CPU back to GPU (as long as D3D 9Ex is used) by using the D3DImage approach: https://bitbucket.org/JaredThirsk/mogreinwpf, http://msdn.microsoft.com/en-us/library ... image.aspx

victorporof

01-05-2011 09:21:44

I did not have to go from GPU -> CPU -> GPU. That would have been easy :)

It's a bit random, but I had to generate a .swf animation using each frame from the rendering. Almost every lib I found required either loading an image from disk, or using any object type of System.Drawing. Saving each frame to disk and loading it again was by far out of discussion, so converting whatever thing window.CopyContentsToMemory() gave me was the only option. On a side note, rendering to a texture, and then converting the TexturePtr to a System.Drawing was also slower.

Meharin

02-05-2011 23:49:40

Ah, I hope you find something!
(I'm not sure what you mean by n^2, but I would think it would be impossible to avoid n^2, or rather length*width*depth, when copying from GPU to CPU memory.)
[Update: oh now I understand that you are sharing what you found!]

victorporof

03-05-2011 15:46:27

Ah, I hope you find something!
Um.. I already did :)

I'm not sure what you mean by n^2, but I would think it would be impossible to avoid n^2, or rather length*width*depth, when copying from GPU to CPU memory.
Avoiding nested loops and doing binary copy of data instead of setting pixels manually does the trick.

Basically, instead of doing this (clearly n^2):

for (int x = 0; x < bmp.Width; x++)
{
for (int y = 0; y < bmp.Height; y++)
{
Mogre.ColourValue color = img.GetColourAt(x, y, 0);
bmp.SetPixel(x, y, Color.FromArgb((int)color.GetAsARGB()));
}
}

..you do this:

CopyMemory(bmpData.Scan0, pixels.data, pixels.box.Width * pixels.box.Height * 3);

..from the code in the first post ;)

thatnerdyguy

03-05-2011 17:25:15

I looks like you are doing 2 memcpy operations there. You could just provide the bitmap as the location for the pixelbox to blit its contents. I do this:

// Lock the buffers and move out the data
using (HardwarePixelBufferSharedPtr pixelBuffer = rtt.GetBuffer())
{
System.Drawing.Imaging.BitmapData bmpData = bmp.LockBits(new System.Drawing.Rectangle(0, 0, sizeX, sizeY),
System.Drawing.Imaging.ImageLockMode.WriteOnly, bmpPixelFormat);

try
{
PixelBox pb = new PixelBox((uint)sizeX, (uint)sizeY, 1, texturePixelFormat, bmpData.Scan0);
pixelBuffer.BlitToMemory(pb);
}
catch (Exception)
{
}

bmp.UnlockBits(bmpData);
}

Meharin

03-05-2011 20:28:34

Ah, I hope you find something!
Um.. I already did :)


Doh, that's what I get for not reading the code. Makes sense now... Thanks for sharing your solution!

victorporof

06-05-2011 02:45:27

You could just provide the bitmap as the location for the pixelbox to blit its contents.
That looks quite interesting! I guess you'd then have to render to texture first.