I had made an app in C# that will perform screen capture continuously and display it in a PictureBox using timer. After running for a few seconds, there was an ArgumentException.
Below is the code and the line that has the ArgumentException
private void timer1_Tick(object sender, EventArgs e)
{
Rectangle bounds = Screen.GetBounds(Point.Empty);
Graphics graphics;
Bitmap bitmap = new Bitmap(bounds.Width, bounds.Height);
using (graphics = Graphics.FromImage(bitmap))
{
graphics.CopyFromScreen(0, 0, 0, 0, new Size(bounds.Width , bounds.Height )); // ArgumentException
pictureBox1.Image = bitmap;
pictureBox1.SizeMode = PictureBoxSizeMode.StretchImage;
}
}
Besides that, I had notices that an alert saying low memory from Windows after running the app for a few seconds.
Any tips on resolving this problem?
You keep setting a new bitmap to the picturebox, and the previous bitmap is never disposed. After a while, the system runs short of GDI handles and/or memory (running your code, I consumed one gig of memory in under 15 seconds).
You can simply reuse your existing bitmap:
Rectangle bounds = Screen.GetBounds(Point.Empty);
Image bitmap = pictureBox1.Image ?? new Bitmap(bounds.Width, bounds.Height);
using (Graphics graphics = Graphics.FromImage(bitmap))
{
graphics.CopyFromScreen(0, 0, 0, 0, new Size(bounds.Width, bounds.Height));
if (pictureBox1.Image == null)
{
pictureBox1.Image = bitmap;
}
else
{
pictureBox1.Refresh();
}
}
You also don't have to reset pictureBox1.SizeMode on each iteration.
Alternatively, you can dispose the previous bitmap manually:
Rectangle bounds = Screen.GetBounds(Point.Empty);
Bitmap bitmap = new Bitmap(bounds.Width, bounds.Height);
using (Graphics graphics = Graphics.FromImage(bitmap))
{
graphics.CopyFromScreen(0, 0, 0, 0, new Size(bounds.Width, bounds.Height));
using (Image prev_bitmap = pictureBox1.Image)
{
pictureBox1.Image = bitmap;
}
}
pictureBox1.Image = bitmap;
Yes, your program won't last long when you frequently update the picture box. The Bitmap class is the singular .NET class where IDisposable can't easily be ignored. It is like an iceberg, bitmaps can use massive amounts of unmanaged memory but very little managed memory. You must dispose bitmaps when you no longer use them to prevent them from consuming all available unmanaged memory for their pixel data. The garbage collector tends to hide that problem but it can't do so when it doesn't run frequently enough. And the managed portion of Bitmap is too small to trigger a collection often enough. Fix:
if (pictureBox1.Image != null) pictureBox1.Image.Dispose();
pictureBox1.Image = bitmap;
The following KB can help understanding this issue:
Bitmap and Image constructor dependencies
GDI+, and therefore the System.Drawing namespace, may defer the
decoding of raw image bits until the bits are required by the image.
Additionally, even after the image has been decoded, GDI+ may
determine that it is more efficient to discard the memory for a large
Bitmap and to re-decode later. Therefore, GDI+ must have access to the
source bits for the image for the life of the Bitmap or the Image
object.
To retain access to the source bits, GDI+ locks any source file, and
forces the application to maintain the life of any source stream, for
the life of the Bitmap or the Image object.
One has to figure out when is the object ready for disposal.
Related
I am drawing images in a C# Winforms panel with:
private void DrawPanel_Paint(object sender, PaintEventArgs e)
{
DrawOnPanel(e.Graphics);
}
The called method takes an existing image from my resources (myImage), gives it to another method which resizes the image and returns the resized image so it can be drawn.
public static void DrawOnPanel(Graphics g)
{
var _resizedImage = ResizeImage(Resources.myImage);
g.DrawImage(_resizedImage, destX, destY);
// ... several other images resized & manipulated then drawn
}
The resize image function is:
public Bitmap ResizeImage(Bitmap image)
{
int scale = 3;
var destImage= new Bitmap(image.Width * scale, image.Height * scale);
destImage.SetResolution(image.HorizontalResolution, image.VerticalResolution);
using (var graphics = Graphics.FromImage(destImage))
{
graphics.CompositingMode = CompositingMode.SourceCopy;
graphics.CompositingQuality = CompositingQuality.HighSpeed;
graphics.InterpolationMode = InterpolationMode.NearestNeighbor;
graphics.SmoothingMode = SmoothingMode.HighSpeed;
graphics.PixelOffsetMode = PixelOffsetMode.None;
using var wrapMode = new ImageAttributes();
wrapMode.SetWrapMode(WrapMode.TileFlipXY);
graphics.DrawImage(image, destRect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, wrapMode);
}
return destImage;
}
The program keeps calling DrawPanel.Invalidate() in its loop.
I am detecting a memory leak each time DrawPanel.Invalidate() is called. The memory consumption is rising steadily until the GC takes care of it. While this isn't a game breaking problem, I'm still wondering where and how should I dispose of my objects in the above code.
I tried using using var _resizedImage = ResizeImage(Resources.myImage); in the above DrawOnPanel method but the program returns an error System.ArgumentException: 'Parameter is not valid.'. If I remove using there is no error.
Every time that ResizeImage is called, you create a new Bitmap. This Bitmap should be Disposed as soon as you know that it is not needed anymore. It seems that you don't need the resized image after you've drawn it on the Graphics. Therefore I suggest the following change:
public static void DrawOnPanel(Graphics g)
{
using (Image_resizedImage = ResizeImage(Resources.myImage))
{
g.DrawImage(_resizedImage, destX, destY);
}
// resizedImage is Disposed
// ... several other images resized & manipulated then drawn
}
Room for improvement
Your current design will create a new Resized Bitmap every time DrawOnPanel is called. It seems to me that most of time that Resources.myImage is resized the resized Bitmap will be the same. Why don't you just remember it until the parameters that influence the resized Bitmap are changed?
If I look at the code it seems that you always create the same resized image from an original image. So you could even put all your resized images into one Dictionary for fast lookup:
// get all Image resources that you will resize and put them in a Dictionary
// Key original Bitmap, Value: resized Bitmap
private Dictionary<Bitmap, BitMap> images = this.FetchImages()
.ToDictionary(
// Parameter keySelector: key is original image
image => image,
// Parameter valueSelector: value is the resized image
imgage => ResizeImage(original));
Displaying the resized images will now be much faster: only a Lookup.
public static void DrawOnPanel(Graphics g)
{
var _resizedImage = this.Images[Resources.myImage];
g.DrawImage(_resizedImage, destX, destY);
// ... several other images resized & manipulated then drawn
}
Don't forget to Dispose the bitmaps when your Form is Closed, or at its latest when your Form is Disposed:
protected override void Dispose(bool disposing)
{
if (disposing)
{
foreach (var resizedImage in images.Values)
resizedImage.Dispose();
}
}
I am using this code to take screen shot every second with timer, but it seems to be filling up my memory (about 30 MB each take)... This is the code:
Bitmap bmpScreenshot = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height, PixelFormat.Format32bppArgb);
Graphics gfxScreenshot = Graphics.FromImage(bmpScreenshot);
gfxScreenshot.CopyFromScreen(136, 93, 9, 0, new Size(1088-391, 1039-65), CopyPixelOperation.SourceCopy);
return bmpScreenshot;
Is there a way to clear memory?
You need to dispose of things properly by calling Dispose or wrapping with the using() statement which calls it for you, for example:
Bitmap bmpScreenshot = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height, PixelFormat.Format32bppArgb);
using(Graphics gfxScreenshot = Graphics.FromImage(bmpScreenshot)) {
gfxScreenshot.CopyFromScreen(136, 93, 9, 0, new Size(1088-391, 1039-65), CopyPixelOperation.SourceCopy);
}
return bmpScreenshot;
You should also dispose of the returned Bitmap instance once you're done with it.
I met this problem, too.
var g = CreateGraphics();
using (var brush = new SolidBrush(Color.Red))
{
Point point = new Point(0,0);
Rectangle sourceRect = new Rectangle(100,100,100,100);
var invoker = new MethodInvoker(() =>
{
// The following line will filling up the memory.
g.CopyFromScreen(new Point(0,0), sourceRect.Location, sourceRect.Size);
});
do
{
BeginInvoke(invoker);
} while (!mre.WaitOne(1));
}
It means the 'CopyFromScreen' method has some things that are not disposed.
But I have no idea how to dispose them.
Even through 'Diagnositics Tools' of VS2017, I can see the heap usage and point to ExecutionContext -> Queue, the Queue is going larger and larger, and mostly, the size of each item in the Queue is 44 bytes. Of cause, GC will collect them after a while, but it still grow up quickly.
Bitmap and Graphics implement IDisposable interface. To free unmanaged resources you should call Dispose method or wrap both objects with using statement.
For my program I need a method which takes a screenshot every half a minute. I googled and came up with this method:
public static Bitmap CaptureScreen()
{
Bitmap BMP = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height,
System.Drawing.Imaging.PixelFormat.Format32bppArgb);
Graphics GFX = Graphics.FromImage(BMP);
GFX.CopyFromScreen(Screen.PrimaryScreen.Bounds.X, Seen.PrimaryScreen.Bounds.Y, 0, 0, Screen.PrimaryScreen.Bounds.Size, CopyPixelOperation.SourceCopy);
return BMP;
}
Well all works fine for the first 200 uses of the method or so. Then the function crashes at CopyFromScreen and it says that it caused an invalid argument exception. I'm slightly confused why that is because the parameters don't change.
Could it be that the function itself just has a bug? If so are there any alternatives to take a screenshot?
Probably two separate failures to dispose. Both Graphics and Image / Bitmap implement IDisposable, so the "obvious" of the two is here:
using(Graphics GFX = Graphics.FromImage(BMP)) {
GFX.CopyFromScreen(Screen.PrimaryScreen.Bounds.X,Seen.PrimaryScreen.Bounds.Y,
0, 0, Screen.PrimaryScreen.Bounds.Size, CopyPixelOperation.SourceCopy);
}
return BMP;
However: the caller of your method should also be using the result of CaptureScreen (to release the bitmap's GDI+ handle), i.e.
using(var screen = CaptureScreen()) {
// some work here
}
I've looked through a few pages of similar inquiries, implemented most of the suggestions, but can't seem to find anything that's worked so far. Hopefully I'm not overlooking something glaringly obvious.
Right, so I'm using AForge.net to capture an image. It provides an event, which is triggered for each new frame received, which in my code looks like this:
private void videoSourcePlayer_NewFrame(object sender, ref Bitmap image)
{
framesRecieved++;
try
{
if (!stopCapturing)
{
if (pictureBox1.Image != null)
{
pictureBox1.Image.Dispose();
}
pictureBox1.Image = image.Clone(new Rectangle(0, 0, image.Width, image.Height), image.PixelFormat);
}
}
catch { }
finally { GC.Collect(); }
}
Memory usage is very stable so long as the window remains stationary, but as soon as I grab the window form and start moving it about, memory usage keeps going up. The reason I've been led to believe it might be related to the picturebox, is because as soon as I turn the "stopCapturing" bool to true, memory stops rising, even if I'm moving the window around the screen. "stopCapturing" is not used for anything else, and the event continues triggering as normal, the only difference is the image being displayed in the picturebox. I'm at a loss as to the cause, so any help would be appreciated.
PS: Not sure if it's related, but my workstation has 2 screens.
Bitmap.Clone() does a shallow copy, the actual bytes are still owned by the caller, so this could potentially cause all kind of troubles.
You need to do a deep copy.
For example, the AForge way:
Bitmap bmp = AForge.Imaging.Image.Clone(image);
Or the GDI+ way (could also use lockbits, etc. for better perfs):
Bitmap bmp = new Bitmap(image.Width, image.Height, image.PixelFormat);
Graphics g = Graphics.FromImage(bmp);
g.DrawImageUnscaled(image, Point.Empty);
I'm wondering why you're cloning the image at all. It strikes me that you should only allocate a new image when either pictureBox1.Image is null or when the dimensions or pixel format of the image change:
private bool BitmapChanged(Bitmap old, Bitmap new)
{
return old == null || old.PixelFormat != new.PixelFormat ||
old.Width != new.Width || old.Height != new.Height;
}
private Bitmap MakeSimilarBitmap(Bitmap source)
{
Bitmap copy = new Bitmap(source.Width, source.Height, source.PixelFormat);
return copy;
}
private void DrawOnto(Image im, Bitmap source)
{
using (Graphics g = Graphics.FromImage(im)) {
g.DrawImage(source, 0, 0);
}
}
then when you get a frame, you'll do something like this:
Image im = BitmapChanged(pictureBox1.Image as Bitmap, srcBmp) ?
MakeSimilarBitmap(image) : pictureBox1.Image;
DrawOnto(im, srcBmp);
bool same = im == pictureBox1.Image;
if (same)
pictureBox1.Invalidate();
else {
Image old = pictureBox1.Image;
pictureBox1.Image = im;
old.Dispose();
}
Is it possible to load a picture from memory (byte[] or stream or Bitmap) without saving it to disk?
This is the code I use to turn the byte[] array into a Bitmap:
unsafe
{
fixed (byte* ptr = Misc.ConvertFromUInt32Array(image))
{
Bitmap bmp = new Bitmap(200, 64, 800, PixelFormat.Format32bppRgb, new IntPtr(ptr));
bmp.RotateFlip(RotateFlipType.Rotate180FlipX);
bmp.MakeTransparent(Color.Black);
bmp.Save("test.bmp");
}
}
Instead of using Bmp.save(), can I put the Bitmap in the picture box on my form?
Have you tried this?
pictureBox.Image = bmp;
I had some code resembling the accepted answer that caused a memory leak. Problem is that when you set the picture box image to the bitmap, you're still referring to the bitmap, rather than creating a copy. If you need to set the image multiple times you need to make sure you're disposing all the old bitmaps.
This is for anyone who's looking to clone a bitmap to an image box. Try this:
if (pictureBox.Image != null) pictureBox.Image.Dispose();
pictureBox.Image = myBitmap.Clone(
new Rectangle(0, 0, myBitmap.Width, myBitmap.Height),
System.Drawing.Imaging.PixelFormat.DontCare);
If you are working with C++ programming language, it can be done like this:
void backGroundImage()
{
Image^ back = gcnew Bitmap("C:\\Users\\User\\Documents\\image.bmp");
pictureBox1->BackGroundImage = back;
};
Then you can call backGroundImage when you need to load a bitmap.