0

I'm trying to use a PictureBox to create an animation in a Windows Forms app (C#) by drawing on the Bitmap using Graphics. Unfortunately, it looks like I am leaking memory somewhere or allocating too much based on this error:

An unhandled exception of type 'System.OutOfMemoryException' occurred in System.Drawing.dll
Additional information: Out of memory.

This is the source code. Desired behavior is a popup with a glitchy animation.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace AnimationApp
{
    public partial class Form1 : Form
{
    Timer timer = new Timer();
    Form1 form1;
    //Bitmap bmp;
    public Form1()
    {
        InitializeComponent();
        form1 = this;
        form1.TransparencyKey = Color.White;
        FormBorderStyle = FormBorderStyle.None;
WindowState = FormWindowState.Maximized;

        pictureBox1.Left = 0;
        pictureBox1.Top = 0;
        pictureBox1.Width = Screen.PrimaryScreen.Bounds.Width;
        pictureBox1.Height = Screen.PrimaryScreen.Bounds.Height;
        timer.Enabled = true;
        timer.Interval = 10;
        timer.Tick += timer_tick;

    }
    Random rand = new Random();
    int partsection = 0;
    int waitint = 0;
    int ran1 = 0;
    int ran2 = 0;
    int intensifies = 0;
    public void timer_tick(object e, EventArgs ea)
    {
        Bitmap bmp;
        bmp = new Bitmap(pictureBox1.Width, pictureBox1.Height);

        using (Graphics g = Graphics.FromImage(bmp))
        {
            if (partsection == -1)
            {

            }
           
            else if (partsection<300)
            {
                if (waitint == 0)
                {

                    ran1 = rand.Next(0, pictureBox1.Width);
                            ran2 = rand.Next(0, pictureBox1.Width);
                    waitint = 10;
                }
                else
                {
                    waitint--;
                }

                g.FillRectangle(new SolidBrush(Color.Green), (float)ran1, (float)0, 3, pictureBox1.Height);
                g.FillRectangle(new SolidBrush(Color.DarkGreen), (float)ran2, (float)0, 3, pictureBox1.Height);

                partsection++;
            }
            else if (partsection < 1000)
            {
                if (intensifies < 255)
                {
                    intensifies++;

                }
                else
                {

                }
                g.FillRectangle(new SolidBrush(Color.FromArgb(intensifies,Color.Black)), (float)0, (float)0, pictureBox1.Width, pictureBox1.Height);
            }

        }
        pictureBox1.Image = bmp;
     
    }
}
}

Thank you for any help.

pepperjack
  • 673
  • 6
  • 20
  • You're leaking `SolidBrush`es, that's for sure. You need to dispose of them. The thing I'm not sure about is if you'd also need to dispose explicitly of the old `pictureBox1.Image` image. 100 ticks per seconds is also pretty excessive... – Pierre-Luc Pineault Dec 18 '15 at 03:05
  • What code do I need to change? Sorry, I have only been coding in c# for about half a year. – pepperjack Dec 18 '15 at 03:15
  • Your `SolidBrush` should be either in `using` statements like your `Graphics` or be declared on a separate line so you can call `Dispose` on em. Or even better, declare them at class level and reuse the same brushes instead of creating a new one each time. For the `pictureBox`, you can look at [this answer](http://stackoverflow.com/a/2809026/2316200). And you can change your `Tick` to 33 for about 30FPS, which should be fluid enough and less taxing on the hardware. Not sure about the picturebox though. – Pierre-Luc Pineault Dec 18 '15 at 03:18
  • Ok, so like SolidBrush sb1 = new SolidBrush(); //Use Code sb1.Dispose(); – pepperjack Dec 18 '15 at 03:24
  • Ok, I tried this on all the solid brush code, the error takes a little longer to activate but still activates. – pepperjack Dec 18 '15 at 03:42
  • Is there a specific reason why you can't draw directly on the form? And how big is your screen? Tried it on my 1440x900 and it doesn't throw `OutOfMemoryException` exception. And I'm not sure if GDI leak is the problem here also, Task Manager showed only ~40 GDI object... max. – IronGeek Dec 18 '15 at 04:02
  • 1
    Change the code to use fixed screen resolution of 3840x2160, and it does throw `OutOfMemoryException ` for me. So apparently it does leak, albeit due to late triggered GC (see Ian answer below). Adding `if (pictureBox1.Image != null) pictureBox1.Image.Dispose();` right before `pictureBox1.Image = bmp;` would probably solve the out of memory issue... – IronGeek Dec 18 '15 at 05:44
  • @Pierre-LucPineault I was wondering about the same thing earlier this week. Apparently the answer is _yes_, the `PictureBox` setter does not dispose the image on its own: http://stackoverflow.com/questions/2613286/if-i-replace-an-image-in-a-picturebox-control-should-i-dispose-the-original-ima – CompuChip Dec 18 '15 at 08:28
  • Thank you @IronGeek, that solved my issue. – pepperjack Dec 19 '15 at 00:18

2 Answers2

2

This happens because some of the resources you use are unmanaged and you do not put it under using block or any alternative way to dispose your unmanaged resource after use. The memory is leaked due to that.

For instance, these lines,

g.FillRectangle(new SolidBrush(Color.Green), (float)ran1, (float)0, 3, pictureBox1.Height);
g.FillRectangle(new SolidBrush(Color.DarkGreen), (float)ran2, (float)0, 3, pictureBox1.Height);

Can be changed to

using (SolidBrush greenBrush = new SolidBrush(Color.Green))
using (SolidBrush darkGreenBrush = new SolidBrush(Color.DarkGreen)) {
    g.FillRectangle(greenBrush , (float)ran1, (float)0, 3, pictureBox1.Height);
    g.FillRectangle(darkGreenBrush, (float)ran2, (float)0, 3, pictureBox1.Height);
}

And similarly, this line

g.FillRectangle(new SolidBrush(Color.FromArgb(intensifies,Color.Black)), (float)0, (float)0, pictureBox1.Width, pictureBox1.Height);

To this

using (SolidBrush blackBrush = new SolidBrush(Color.FromArgb(intensifies, Color.Black)))
    g.FillRectangle(blackBrush, (float)0, (float)0, pictureBox1.Width, pictureBox1.Height);

Another advise is you should check the unmanaged resources on System.Drawing. Since there might be more of the unmanaged resources which you show but are not pointed out - or - you do not put in your question but are actually in the other parts of your codes.

Here is more of unmanaged resources:

Here is another explanation for the possibility of Bitmap causes the leak (due to small-sized managed Bitmap class wrapper, out of memory can be caused because the Garbage Collector is late-triggered), see explanation by Mr. Hans Passant

Community
  • 1
  • 1
Ian
  • 30,182
  • 19
  • 69
  • 107
1

Your bitmap needs to be declared as an instance variable of the form.

EDIT

Specifically, uncomment your declaration of the Bitmap in the class instance variables. Next, add the following code:

    protected override void OnResize(EventArgs e)
    {
        pictureBox1.Left = 0;
        pictureBox1.Top = 0;
        pictureBox1.Width = Screen.PrimaryScreen.Bounds.Width;
        pictureBox1.Height = Screen.PrimaryScreen.Bounds.Height;
        bmp = new Bitmap(pictureBox1.Width, pictureBox1.Height);
        base.OnResize(e);
    }

Why? Because you need to resize your picture box when the form resizes, or the user changes the screen resolution. Now, the Bitmap is only declared a single time. I ran the code for several minutes with no ill effects.

BTW, the pictureBox1 resizing code was removed from the constructor.

The Sharp Ninja
  • 1,041
  • 9
  • 18