# [.net] Copying Bitmaps to big Bitmap

This topic is 4514 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

## Recommended Posts

Hi Could some kind soul help me speed up my bitmap copying code with some unsafe code please? To create a high-resolution screenshot made from 2x2 1280x1024 viewports, that's 5098880 pixels, it took 9 minutes 30 seconds. Only about 9000 pixels a second, not very impressive. At least I don't have to do it by hand anymore, but that's way too slow! (Frames only take 0.05s so negligible, and I actually do the bitmap copying between submitting the render and taking the screenshot to avoid stalling the CPU waiting for the GPU to finish)
private void CopyBitmapToBitmapOffset(System.Drawing.Bitmap lastBitmap,
System.Drawing.Bitmap screenShotBMP, int lastX, int lastY)
{
for (int y = 0; y < lastBitmap.Height; y++)
{
for (int x = 0; x < lastBitmap.Width; x++)
{
screenShotBMP.SetPixel(x + lastX, y + lastY, lastBitmap.GetPixel(x, y));
}
System.Diagnostics.Debug.WriteLine("CopyBitmapToBitmapOffset, copied line " + y);
}
}


I've been googling and someone (who could not use unsafe code as they had to use VB) said 'I have already overcome the slowness of GetPixel/SetPixel by using Marshaling.' Although that's probably not as good, I would be interested. Thanks a lot!!!

##### Share on other sites
btw I found this, but that is for copying a whole bitmap...I'm sure my offset complicates things a bit and I haven't done this before. I'll learn a great deal from your answer I'm sure.

edit: I found the format for you, 32bpp ARGB.

##### Share on other sites
I pieced together the following...I'm not sure if it works but I'm posting before I destroy my computer [lol]

Can someone check through it please?

Cheers

private void CopyBitmapToBitmapOffset(System.Drawing.Bitmap sourceBitmap,                                      System.Drawing.Bitmap destinationBitmap, int lastX, int lastY){	System.Diagnostics.Debug.Assert(sourceBitmap.PixelFormat == System.Drawing.Imaging.PixelFormat.Format32bppArgb);	System.Diagnostics.Debug.Assert(destinationBitmap.PixelFormat == System.Drawing.Imaging.PixelFormat.Format32bppArgb);	//Calculate the rectangles to lock	System.Drawing.Rectangle sourceRectangle = new System.Drawing.Rectangle(0, 0, sourceBitmap.Width,	                                                                              sourceBitmap.Height);	System.Drawing.Rectangle destinationRectangle = new System.Drawing.Rectangle(lastX, lastY, sourceBitmap.Width,	                                                                                          sourceBitmap.Height);	//Declare BitmapData here so finalizers unlock the bitmaps	System.Drawing.Imaging.BitmapData sourceData = null;	System.Drawing.Imaging.BitmapData destinationData = null;	try 	{		//Lock source		sourceData = sourceBitmap.LockBits(sourceRectangle, System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);		try 		{			//Lock destination			destinationData = destinationBitmap.LockBits(destinationRectangle, System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);			int sizeX = sourceBitmap.Width;			int sizeY = sourceBitmap.Height;			unsafe			{				// Get some pointers to the data in memory				byte* pSourceRow = (byte*)sourceData.Scan0.ToPointer();				byte* pDestinationRow = (byte*)destinationData.Scan0.ToPointer();				System.Int32* pSourcePixel;				System.Int32* pDestinationPixel;				// Loop through each row				for (int y = 0; y < sizeY; y++)				{					// Set the current pixels to the start of the row					pSourcePixel = (System.Int32*)pSourceRow;					pDestinationPixel = (System.Int32*)pDestinationRow;					// Loop through each column					for (int x = 0; x < sizeX; x++, pSourcePixel++, pDestinationPixel++)					{						*((int*)pDestinationPixel) = *((int*)pSourcePixel);					}					// Increment the row pointers by					// the byte length of a row (Stride)					pSourceRow += sourceData.Stride;					pDestinationRow += destinationData.Stride;				}			}		} 		finally 		{			if (destinationData != null)				destinationBitmap.UnlockBits(destinationData);		}	} 	finally 	{		if (sourceData != null)			sourceBitmap.UnlockBits(sourceData);	}}

##### Share on other sites
Well, if you don't mind using libraries then let me suggestion SDL.NET

I actually made a program for this type of thing (combining tiles to make a larger bitmap)

For your program it wouldn't be very difficult, especially if you know it is going to be from 2x2 1280x1024 viewports.

using SdlDotNet;string [,] filename = new string[2,2];// TODO; put the filenames in the stringSurface largeBitmap = new Surface(1280 * 2, 1024 * 2);for (int x = 0; x < 2; x++){   for (int y = 0; y < 2; y++)   {      Surface smallerBitmap = new Surface(filename[x,y]);      largeBitmap.Blit(smallerBitmap, new Point(x * 1280, y * 1024));   }}largeBitmap.Update();largeBitmap.Bitmap.Save("new filename.bmp", System.Drawing.Imaging.ImageFormat.Bmp); // or you could pick any supported filetype (Bmp, Emf, Emif, Gif, Jpeg, Png, Tiff, Wmf)

This shouldn't take more than a second to do.

-edit- fixed code.

##### Share on other sites
what's wrong with:

private void CopyBitmapToBitmapOffset(System.Drawing.Bitmap lastBitmap,                                      System.Drawing.Bitmap screenShotBMP, int lastX, int lastY){    System.Drawing.Graphics g = System.Drawing.Graphics.CreateFromImage(screenShotBMP);    g.DrawImageUnscaled(lastBitmap, lastX, lastY);}

You may have to save the reference to the Graphics object, I think each call to the CreateFromImage method wipes out the bitmap. So, it would be something like this:
private void SomeOtherMethod(System.Drawing.Bitmap[] subImages, System.Drawing.Bitmap screenShotBMP){    System.Drawing.Graphics screenShotGraphics = System.Drawing.Graphics.CreateFromImage(screenShotBMP);    int dx = 0, dy = 0;    for(int i = 0; i < subImages.length; ++i)    {        screenShotGraphics.DrawImageUnscaled(subImages, dx, dy);        dx += subImages.Width;        if(dx >= screenShotBMP.Width)        {            dy += subImages.Height;            dx = 0;        }    }}

It makes a few assumptions, like, all the images are the same size, and all the images will fit perfectly in the screenshot. It may not be the absolute fastest, but it should be pretty quick and it keeps you all-managed.

##### Share on other sites
Looping through each pixel is probably the worst solution you can have. The Capn has the right solution going. The Graphics class is probably the best way to go. What other APIs are you using?

##### Share on other sites
Thanks for the replies! (++)

I'm just using .NET 2.0 and DirectX 9 (december), so I'd prefer to not have to use SDL.

I like the capn's method, unfortunately DrawImageUnscaled seems to be scaling the sections (ironic huh?). The output bitmap is the correct size but the sections are clipped. Also, if I measure the diameter of the yellow spheres on screen then again in the screenshot filling the screen, I get 4cm vs 5.5cm. Have I convinced you now that it is scaling?

I even tried setting the PageUnit to Pixel to make sure I was using the correct units,
screenShotGraphics.PageUnit = System.Drawing.GraphicsUnit.Pixel;screenShotGraphics.DrawImageUnscaled(lastBitmap, lastX, lastY, lastBitmap.Width, lastBitmap.Height);

Here's a png of the result:

The unsafe method I posted did work very well, however I would prefer a managed solution. But yay I can take screen shots in a few seconds rather than minutes! I suppose I could always disable high-res screenshots in the release version, but I would like to get this working.

Thanks again for your help

##### Share on other sites
Hi

I was going to try using DrawImage, scaling the images by half to investigate how much they were being scaled, but to my surprise,
screenShotGraphics.DrawImage(lastBitmap, lastX, lastY, lastBitmap.Width, lastBitmap.Height);
works!
Of course, no scaling is being done as I pass it the bitmap's width and height, so DrawImageUnscaled should work.

EDIT: I took a peek in Reflector, DrawImageUnscaled just redirects to DrawImage(Image, int, int). Therefore that has the same scaling problem.
The speed difference isn't noticable so I could live with that...hopefully it optimizes a scale factor of 1 away anyway.

Could someone give me the email address to report DrawImage(int, int), bclfeedback does not exist any more.

Cheers

##### Share on other sites
Image.Save saves bitmaps, but not Pngs! I shouldn't be getting these sort of exceptions from the .NET framework.

And I got this one yesterday from trying to save as a memory bitmap:

I really need somewhere to report this!

##### Share on other sites
Quote:
 Original post by DrGUIImage.Save saves bitmaps, but not Pngs! I shouldn't be getting these sort of exceptions from the .NET framework.And I got this one yesterday from trying to save as a memory bitmap:I really need somewhere to report this!

sounds like something fxord with your system. I use the Save method with PNGs all the time. I even have a little program that I wrote for making 1% sized png thumbnails.

• 9
• 9
• 13
• 41
• 15