Continuous creation of bitmaps leads to memory leak - c#

I have a thread that continuously generates bitmaps and takes a screenshot of another program's window. Now, I have a pictureBox on my form, and that's constantly being updated with the bitmaps generated. Here's the code I have in the thread:
Bitmap bitmap = null;
while (true)
{
if (listBoxIndex != -1)
{
Rectangle rect = windowRects[listBoxIndex];
bitmap = new Bitmap(rect.Width, rect.Height);
Graphics g = Graphics.FromImage(bitmap);
IntPtr hdc = g.GetHdc();
PrintWindow(windows[listBoxIndex], hdc, 0);
pictureBox1.Image = bitmap;
g.ReleaseHdc(hdc);
}
}
As you can see, this leads to a memory leak, because of the continuous call to new Bitmap(rect.Width, rect.Height). I've tried adding "bitmap.Dispose()" to the bottom of the while loop, but that leads to the pictureBox's image also being disposed, which makes a giant red X in place of the actual image. Is there any way I can dispose of "bitmap" without disposing of the pictureBox image?

You're also "leaking" the Graphics object. Try this:
while (true)
{
if (listBoxIndex != -1)
{
Rectangle rect = windowRects[listBoxIndex];
Bitmap bitmap = new Bitmap(rect.Width, rect.Height);
using (Graphics g = Graphics.FromImage(bitmap))
{
IntPtr hdc = g.GetHdc();
try
{
PrintWindow(windows[listBoxIndex], hdc, 0);
}
finally
{
g.ReleaseHdc(hdc);
}
}
if (pictureBox1.Image != null)
{
pictureBox1.Image.Dispose();
}
pictureBox1.Image = bitmap;
}
}

The answered example has a leak with Graphics g
after g.ReleaseHdc(..);
Remember to dipose the graphics variable
as for example:
g.Dispose();

Related

IntPtr - Memory & GDI leak [C#]

The Problem:
When taking Screenshots of a Screen (in a loop) I get a RAM and GDI leak.
private Bitmap GetSS(int ScreenWidth, int ScreenHeight, int ScreenWidthCut, int ScreenHeightCut)
{
int ScreenLocWidth = Screen.PrimaryScreen.Bounds.Width - ScreenWidth;
int ScreenLocHeight = Screen.PrimaryScreen.Bounds.Height - ScreenHeight;
IntPtr dc1 = CreateDC("DISPLAY", null, null, (IntPtr)null);
//Create the DC of the display
Graphics g1 = Graphics.FromHdc(dc1);
//Create a new Graphics object from the handle of a specified device
Bitmap MyImage = new Bitmap(ScreenWidthCut, ScreenHeightCut, g1);
//Create a Bitmap object of the same size according to the screen size
Graphics g2 = Graphics.FromImage(MyImage);
//Get the handle of the screen
IntPtr dc3 = g1.GetHdc();
//Get the handle of the bitmap
IntPtr dc2 = g2.GetHdc();
BitBlt(dc2, 0, 0, ScreenWidth, ScreenHeight, dc3, ScreenLocWidth, ScreenLocHeight,
(int)CopyPixelOperation.SourceCopy | (int)CopyPixelOperation.CaptureBlt);
g1.ReleaseHdc(dc3);
//Release the screen handle
g2.ReleaseHdc(dc2);
//Release the bitmap handle
DeleteObject(dc1);
DeleteObject(dc2);
DeleteObject(dc3);
return MyImage;
}
Debugging gave me these lines which are potentially causing the leak.
//Get the handle of the screen
IntPtr dc3 = g1.GetHdc();
//Get the handle of the bitmap
IntPtr dc2 = g2.GetHdc();
With the following I am trying to release and delete the objects created, with no effect.
g1.ReleaseHdc(dc3);
//Release the screen handle
g2.ReleaseHdc(dc2);
//Release the bitmap handle
DeleteObject(dc1);
DeleteObject(dc2);
DeleteObject(dc3);
I found a solution using the GarbageCollector. That works! No more memory nor GDI leak. I simply call
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
after I call "GetSS".
But I would like to understand why releasing and deleting the objects manually doesn't work, I want to avoid using the GarbageCollector at all if possible.
EDIT: This is how I call GetSS
while (startLoc.x == 0)
{
using (Bitmap imgScene = GetSS(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height, Screen.PrimaryScreen.Bounds.Width, (int)(Screen.PrimaryScreen.Bounds.Height * 0.20)))
{
//the stuff I do with the image is commented out for testing purposes, this is not causing th leak
}
Thread.Sleep(10);
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
}
And this is for deleting the Object:
[System.Runtime.InteropServices.DllImport("gdi32.dll")]
public static extern bool DeleteObject(IntPtr hObject);
Stay healthy everyone.
If a forced GC solves the problem, it is probably due to some finalizer kicking in and freeing memory. That hints that it might be some disposable object not being disposed. The Graphics class are IDisposable, so they should be inside a using statement to ensure disposal. The bitmap seem to be correctly disposed outside the function.
This suggest that the corresponding function for CreateDC is DeleteDC.
I might also recommend releasing all resources inside finally-statements, to ensure they are disposed even if some exception occur.
You are missing using blocks, and also DeleteObject should be DeleteDC, which should also be in a finally.
Also, dc3 is not necessary as you have that already in dc1.
private Bitmap GetSS(int ScreenWidth, int ScreenHeight, int ScreenWidthCut, int ScreenHeightCut)
{
int ScreenLocWidth = Screen.PrimaryScreen.Bounds.Width - ScreenWidth;
int ScreenLocHeight = Screen.PrimaryScreen.Bounds.Height - ScreenHeight;
Bitmap MyImage;
IntPtr dc1 = IntPtr.Zero;
IntPtr dc2 = IntPtr.Zero;
try
{
dc1 = CreateDC("DISPLAY", null, null, (IntPtr)null);
//Create the DC of the display
//Create a Bitmap object of the same size according to the screen size
using (Graphics g1 = Graphics.FromHdc(dc1))
{
MyImage = new Bitmap(ScreenWidthCut, ScreenHeightCut, g1);
using (Graphics g2 = Graphics.FromImage(MyImage))
{
//Get the handle of the bitmap
dc2 = g2.GetHdc();
BitBlt(dc2, 0, 0, ScreenWidth, ScreenHeight, dc1, ScreenLocWidth, ScreenLocHeight,
(int)CopyPixelOperation.SourceCopy | (int)CopyPixelOperation.CaptureBlt);
}
}
}
catch
{
MyImage?.Dispose();
throw;
}
finally
{
//Release the bitmap handle
if (dc1 != IntPtr.Zero)
DeleteObject(dc1);
if (dc2 != IntPtr.Zero)
g2.ReleaseHdc(dc2);
}
return MyImage;
}
Don't forget that the image you return also must be disposed at some point.

split image to multiple image from the bottom up

I have a picture I want to print, but it's too big for one page
so i have decided to split it into multiple images
i have tried a method, but now im using this (Talha Irfan answer)
i also tried the other solutions there but those didnt worked as well
(ex. bm.Clone(rec, bm.PixelFormat);)
and here's my code(this is on non-form class)
Bitmap bm = new Bitmap(frmPrint.Width, frmPrint.Height);
Rectangle rec = new Rectangle(0, 200, 576, 300);
Bitmap bitmap = cropImg(bm, rec);
frmPrint.DrawToBitmap(bitmap, rec);
frmPrint._img = bitmap;
frmPrint.setImage();
and setImage function(on some form)
public void setImage()
{
pictureBox3.BackgroundImage = _img;
this.ShowDialog();
}
and cropImg is the same as cropAtRect
the below shows the original image (on the left)
the wanted result in the blue rectangle
and the actual result on the right
PS
my actual image size is (height = 698, wifht = 576)
Edit - as suggested below
on non-form class
Rectangle cropRect = new Rectangle(0, 0, 576, 698);
Bitmap target = new Bitmap(cropRect.Width, cropRect.Height, bm.PixelFormat);
frmPrint.setImage(bm, target, cropRect);
target.Dispose();
at form class
public void setImage(Bitmap src, Bitmap target, Rectangle cropRect)
{
pictureBox3.Visible = false;
using (Graphics g = Graphics.FromImage(target))
{
g.DrawImage(src, new Rectangle(pictureBox3.Location.X, pictureBox3.Location.Y, target.Width, target.Height),
cropRect,
GraphicsUnit.Pixel);
}
this.ShowDialog();
}
Control.DrawToBitmap will always try to draw the whole control or form and will always start at the top. The parameter:
targetBounds
Type: System.Drawing.Rectangle
The bounds within which the control is rendered.
as the name implies, sets the target, not the source rectangle. Hence the white space above your result.
Move the line before cropping with a rectangle that holds the full area, maybe like this:
DrawToBitmap(bm, ClientRectangle);
and then crop the lower part as before..
Note that the cropping trick from your link will not work for DrawToBitmap; using a rectangle with a negative offset will cause a parameter exception.
Btw: to safely dispose of a Bitmap in a PictureBox use this:
Bitmap dummy = (Bitmap )somePictureBox.Image;
somePictureBox.Image = null;
if (dummy != null) dummy.Dispose;
And, indeed, the answer by ChrisJJ in the link leaks the Graphics object.
Update:
Since you seem to have lost control over the various changes and suggestions, here is the minimal code change from the original post:
Bitmap bm = new Bitmap(frmPrint.ClientWidth, frmPrint.ClientHeight);
DrawToBitmap(bm, frmPrint.ClientRectangle);
Rectangle rec = new Rectangle(0, 200, 576, 300);
Bitmap bitmap = cropImg(bm, rec);
frmPrint._img = bitmap;
frmPrint.setImage();
With:
public void setImage()
{
Bitmap dummy = pictureBox3.BackgroundImage;
pictureBox3.BackgroundImage = null;
if (dummy != bnull) dummy.Dispose();
pictureBox3.BackgroundImage = _img;
this.ShowDialog();
}
In the cropImg function add a g.Dispose before returning.

How to save graphics created on a PictureBox?

In c# and Visual Studio Windows forms I have loaded an image into a picture box (pictureBox2) and then cropped it and show in another picture box (pictureBox3).
Now I want to save what is inside pictureBox3 as an image file.
How can I do this?
private void crop_bttn_Click(object sender, EventArgs e)
{
Image crop = GetCopyImage("grayScale.jpg");
pictureBox2.Image = crop;
Bitmap sourceBitmap = new Bitmap(pictureBox2.Image,
pictureBox2.Width, pictureBox2.Height);
Graphics g = pictureBox3.CreateGraphics();
g.DrawImage(sourceBitmap, new Rectangle(0, 0,
pictureBox3.Width, pictureBox3.Height), rectCropArea, GraphicsUnit.Pixel);
sourceBitmap.Dispose();
}
Never use control.CreateGraphics! Either draw into a Bitmap bmp using a Graphics g = Graphics.FromImage(bmp) or in the Paint event of a control, using the e.Graphics parameter..
Here is a cropping code that draws into a new Bitmap and that makes use of your controls etc but changes a few things:
It uses a Graphics object that is created from a new Bitmap
It make use of using clauses to make sure it won't leak
It takes the size of the pictureBox3.ClientSize so it won't include any borders..
private void crop_bttn_Click(object sender, EventArgs e)
{
Image crop = GetCopyImage("grayScale.jpg");
pictureBox2.Image = crop;
Bitmap targetBitmap = new Bitmap(pictureBox3.ClientSize.Width,
pictureBox3.ClientSize.Height);
using (Bitmap sourceBitmap = new Bitmap(pictureBox2.Image,
pictureBox2.ClientSize.Width, pictureBox2.ClientSize.Height))
{
using (Graphics g = Graphics.FromImage(targetBitmap))
{
g.DrawImage(sourceBitmap, new Rectangle(0, 0,
pictureBox3.ClientSize.Width, pictureBox3.ClientSize.Height),
rectCropArea, GraphicsUnit.Pixel);
}
}
if (pictureBox3.Image != null) pictureBox3.Image.Dispose();
pictureBox3.Image = targetBitmap;
targetBitmap.Save(somename, someFormat);
}
The alternative would be to..:
move all your code to the Paint event
replace the Graphics g = pictureBox3.CreateGraphics(); be Graphics g = e.Graphics;
insert these two lines to the click event:
Bitmap targetBitmap = new Bitmap(pictureBox3.ClientSize.Width,
pictureBox3.ClientSize.Height);
pictureBox3.DrawToBitmap(targetBitmap, pictureBox3.ClientRectangle);
The method PictureBox.CreateGraphics() should not be used unless you know what you are doing because it can cause some not-so-obvious problems. For example, in you scenario, the image in pictureBox3 will disappear when you minimize or resize the window.
A better way is to draw to a bitmap, which you also can save:
var croppedImage = new Bitmap(pictureBox3.Width, pictureBox3.Height);
var g = Graphics.FromImage(croppedImage);
g.DrawImage(crop, new Point(0, 0), rectCropArea, GraphicsUnit.Pixel);
g.Dispose();
//Now you can save the bitmap
croppedImage.Save(...);
pictureBox3.Image = croppedImage;
Btw, please use more reasonable variable names, especially for pictureBox1..3.

c# bitmap into picturebox

`
public Bitmap catchFullScreen()
{ Bitmap r = new Bitmap(SystemInformation.VirtualScreen.Width ,SystemInformation.VirtualScreen.Height);
Rectangle bounds = new Rectangle (0,0,SystemInformation.VirtualScreen.Width ,SystemInformation.VirtualScreen.Height);
using (Bitmap bitmap = new Bitmap(SystemInformation.VirtualScreen.Width ,SystemInformation.VirtualScreen.Height))
{
using (Graphics g = Graphics.FromImage(bitmap))
{
g.CopyFromScreen(Point.Empty, Point.Empty, bounds.Size);
r = bitmap;
pictureBox1.Image = r;
pictureBox1.Update();
pictureBox1.Refresh();
}
pictureBox2.Image = r; // breakpoint 1
pictureBox2.Update(); // breakpoint 2
pictureBox2.Refresh();
}
pictureBox3.Image = r;
pictureBox3.Update();
pictureBox3.Refresh();
return r;
}
`
Here is my capture screenshot, but something strange is going on, picturebox1 and 2 is able to capture , but picturebox3 does not.
further more, breakpoint1 works but breakpoint2 never arrives,
Why cant i use this bitmap after i am outside the using routines??
more important it wont return r?
suggestions please!
Bitmap is a class, that is a reference type. When you dispose bitmap you also dispose your r. If you want to continue using r while disposing bitmap, consider using something like Bitmap.Clone.

Printing only the active window in C#

I want to print only the active window from a C# application. Using CopyFromScreen() does not work as it takes a screenshot and the window can be partially hidden by other transient windows appearing. So I tried PrintWindow():
private void printDocument1_PrintPage(object sender, PrintPageEventArgs e)
{
e.Graphics.PageScale = 10.0f;
Graphics g = e.Graphics;
PrintWindow(this.Handle, g.GetHdc(), 0);
}
This produces a tiny stamp-sized printout of the window regardless of which PageScale I use.
Any ideas?
EDIT
This does the trick:
private void printDocument1_PrintPage(object sender, PrintPageEventArgs e)
{
Graphics myGraphics = CreateGraphics();
var bitmap = new Bitmap(Width, Height, myGraphics);
DrawToBitmap(bitmap, new Rectangle(0, 0, bitmap.Width, bitmap.Height));
e.Graphics.DrawImage(bitmap, 0, 0);
}
Some googling around, and I found: Copying content from a hidden or clipped window in XP?
It seems you will need to prepare a bitmap for storing the page:
// Takes a snapshot of the window hwnd, stored in the memory device context hdcMem
HDC hdc = GetWindowDC(hwnd);
if (hdc)
{
HDC hdcMem = CreateCompatibleDC(hdc);
if (hdcMem)
{
RECT rc;
GetWindowRect(hwnd, &rc);
HBITMAP hbitmap = CreateCompatibleBitmap(hdc, RECTWIDTH(rc), RECTHEIGHT(rc));
if (hbitmap)
{
SelectObject(hdcMem, hbitmap);
PrintWindow(hwnd, hdcMem, 0);
DeleteObject(hbitmap);
}
DeleteObject(hdcMem);
}
ReleaseDC(hwnd, hdc);
}

Categories

Resources