Sign in to follow this  

[.net] Copying Bitmaps to big Bitmap

This topic is 4351 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
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[i], dx, dy);
dx += subImages[i].Width;
if(dx >= screenShotBMP.Width)
{
dy += subImages[i].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
Quote:
Original post by capn_midnight
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.


That's nice to know :p
Maybe the images are just too big for the codec to handle? EDIT: it had that error again on a bmp approx 250x350 so maybe not. My system must be fxord! Jpeg doesn't seem to work either.
But if my system got fxord when I have a good firewall and don't install lots of random programs on it, how fxord must end users' computers be!!

Share this post


Link to post
Share on other sites
Quote:
Original post by DrGUI
Quote:
Original post by capn_midnight
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.


That's nice to know :p
Maybe the images are just too big for the codec to handle? EDIT: it had that error again on a bmp approx 250x350 so maybe not. My system must be fxord! Jpeg doesn't seem to work either.
But if my system got fxord when I have a good firewall and don't install lots of random programs on it, how fxord must end users' computers be!!


it's not necessarily a virus. It actually sounds like the GDI+ dll is corrupt, and that could be just random bit rot. Try running the system repair from your OS disk (winxp, win2k, whatever). There is also a patch for GDI+ that was a really big deal quite a few months back and even brand new computers don't ship with it. So, you might want to run windows update and see if the "GDI+ detection tool" comes up.

Share this post


Link to post
Share on other sites
Hey, I fixed it; rates for your post.

I assumed that once you created a bitmap from a stream you could dispose the stream, but evidently not. I can't help thinking that I would have solved this problem much earlier if the error message had been better... I guess the bitmap holds on to the stream as a backing store or something.


/////OLD/////
//Load a bitmap from the stream and save again in new format
System.Drawing.Bitmap bitmap;
using (bitmapStream)
{
bitmap = new System.Drawing.Bitmap(bitmapStream);
}
//Save bitmap
SaveBitmap(bitmap);


/////NEW/////
//Load a bitmap from the stream and save again in new format
using (bitmapStream)
{
System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(bitmapStream);

//Save bitmap (using block has to encompass this else GDI+ error)
SaveBitmap(bitmap);
}


Thanks for all your help!

Share this post


Link to post
Share on other sites
One simple solution: don't use .Width and .Height in the for loop condition. These actually are incredibly slow. Just make width and height ints first, and use those in the condition.

Share this post


Link to post
Share on other sites
Quote:
Original post by NexusOne
One simple solution: don't use .Width and .Height in the for loop condition. These actually are incredibly slow. Just make width and height ints first, and use those in the condition.


Thanks for your reply, I assume you are talking about my first solution.
Yes, I see what you mean now; there I was assuming that they would inline to a simple memory fetch and it's actually an interop to GDI+!

public int get_Width()
{
int num1;
int num2 = SafeNativeMethods.Gdip.GdipGetImageWidth(new HandleRef(this, this.nativeImage), out num1);
if (num2 != 0)
{
throw SafeNativeMethods.Gdip.StatusException(num2);
}
return num1;
}

Also, GDI+ interop in each GetPixel and SetPixel, little wonder the capn's DrawImage approach was much faster!

Cheers NexusOne

Share this post


Link to post
Share on other sites

This topic is 4351 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.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this