Jump to content
  • Advertisement
Sign in to follow this  
DrGUI

[.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.

If you intended to correct an error in the post then please contact us.

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 this post


Link to post
Share on other sites
Advertisement
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 this post


Link to post
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 this post


Link to post
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 string

Surface 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 this post


Link to post
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 this post


Link to post
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 this post


Link to post
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:
Free Image Hosting at www.ImageShack.us

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 this post


Link to post
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 this post


Link to post
Share on other sites
Image.Save saves bitmaps, but not Pngs! I shouldn't be getting these sort of exceptions from the .NET framework.
Free Image Hosting at www.ImageShack.us

And I got this one yesterday from trying to save as a memory bitmap:
Free Image Hosting at www.ImageShack.us

I really need somewhere to report this!

Share this post


Link to post
Share on other sites
Quote:
Original post by DrGUI
Image.Save saves bitmaps, but not Pngs! I shouldn't be getting these sort of exceptions from the .NET framework.
Free Image Hosting at www.ImageShack.us

And I got this one yesterday from trying to save as a memory bitmap:
Free Image Hosting at www.ImageShack.us

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.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

Participate in the game development conversation and more when you create an account on GameDev.net!

Sign me up!