Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is my c# paint method running out of memory?

I'm new to c#, and trying to learn by writing some simple apps to get familiar with the syntax and .NET library. The most recent miniproject I took on is a polar clock like the one found here.

One of the problems I noticed early on was that the app would constantly "flicker", which really took away from the presentation, so I read online about how to implement a double buffer, which eliminated this problem, but may or may not have something to do with the problem. Here is my onPaint method; it is called every 33ms (~30 FPS) by a timer control. Most of the rest of the app is simply handlers for dragging the app (since it is frameless and has a transparent background), exiting on double-click, etc.

    protected override void OnPaint(PaintEventArgs e) {
        DateTime now = DateTime.Now;

        float secondAngle = now.Second / 60F;
        secondAngle += (now.Millisecond / 1000F) * (1F / 60F);

        float minuteAngle = now.Minute / 60F;
        minuteAngle += secondAngle / 60F;

        float hourAngle = now.Hour / 24F;
        hourAngle += minuteAngle / 60F;

        float dayOfYearAngle = now.DayOfYear / (365F + (now.Year % 4 == 0 ? 1F : 0F));
        dayOfYearAngle += hourAngle / 24F;

        float dayOfWeekAngle = (float)(now.DayOfWeek + 1) / 7F;
        dayOfWeekAngle += hourAngle / 24F;

        float dayOfMonthAngle = (float)now.Day / (float)DateTime.DaysInMonth(now.Year, now.Month);
        dayOfMonthAngle += hourAngle / 24F;

        float monthAngle = now.Month / 12F;
        monthAngle += dayOfMonthAngle / (float)DateTime.DaysInMonth(now.Year, now.Month);

        float currentPos = brushWidth / 2F;

        float[] angles = {
            secondAngle, minuteAngle,
            hourAngle, dayOfYearAngle,
            dayOfWeekAngle, dayOfMonthAngle,
            monthAngle
        };

        SolidBrush DateInfo = new SolidBrush(Color.Black);
        SolidBrush background = new SolidBrush(Color.Gray);
        Pen lineColor = new Pen(Color.Blue, brushWidth);
        Font DateFont = new Font("Arial", 12);

        if (_backBuffer == null) {
            _backBuffer = new Bitmap(this.Width, this.Height);
        }

        Graphics g = Graphics.FromImage(_backBuffer);
        g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;

        try {                
            g.Clear(Color.White);
            if (_mouseIsOver) {
                g.FillEllipse(background, new Rectangle(0, 0, this.Width, this.Height));
            }
            foreach (float angle in angles) {
                g.DrawArc(
                    lineColor,
                    currentPos, currentPos,
                    this.Height - currentPos * 2, this.Width - currentPos * 2,
                    startAngle, angle * 360F
                );

                currentPos += brushWidth + spaceStep;
            }

            // Text - Seconds

            g.DrawString(String.Format("{0:D2} s", now.Second), DateFont, DateInfo, new PointF(115F, 0F));
            g.DrawString(String.Format("{0:D2} m", now.Minute), DateFont, DateInfo, new PointF(115F, 20F));
            g.DrawString(String.Format("{0:D2} h", now.Hour), DateFont, DateInfo, new PointF(115F, 40F));
            g.DrawString(String.Format("{0:D3}", now.DayOfYear), DateFont, DateInfo, new PointF(115F, 60F));
            g.DrawString(now.ToString("ddd"), DateFont, DateInfo, new PointF(115F, 80F));
            g.DrawString(String.Format("{0:D2} d", now.Day), DateFont, DateInfo, new PointF(115F, 100F));
            g.DrawString(now.ToString("MMM"), DateFont, DateInfo, new PointF(115F, 120F));
            g.DrawString(now.ToString("yyyy"), DateFont, DateInfo, new PointF(115F, 140F));

            e.Graphics.DrawImageUnscaled(_backBuffer, 0, 0);
        }
        finally {
            g.Dispose();
            DateInfo.Dispose();
            background.Dispose();
            DateFont.Dispose();
            lineColor.Dispose();
        }
        //base.OnPaint(e);
    }

    protected override void OnPaintBackground(PaintEventArgs e) {
        //base.OnPaintBackground(e);
    }

    protected override void OnResize(EventArgs e) {
        if (_backBuffer != null) {
            _backBuffer.Dispose();
            _backBuffer = null;
        }
        base.OnResize(e);
    }

I thought by disposing of everything at the end of the method I'd be safe, but it doesn't seem to help. Furthermore, the interval between run-time and the OutOfMemoryException isn't constant; once it happened only a few seconds in, but usually it takes a minute or two. Here are some class-wide variable declarations.

    private Bitmap _backBuffer;

    private float startAngle = -91F;
    private float brushWidth = 14;
    private float spaceStep = 6;

And a screenshot (edit: screenshot links to a view with some code present):

Screenshot
(source: ggot.org)

EDIT: Stacktrace!

System.OutOfMemoryException: Out of memory.
   at System.Drawing.Graphics.CheckErrorStatus(Int32 status)
   at System.Drawing.Graphics.DrawArc(Pen pen, Single x, Single y, Single width, Single height, Single startAngle, Single sweepAngle)
   at PolarClock.clockActual.OnPaint(PaintEventArgs e) in C:\Redacted\PolarClock\clockActual.cs:line 111
   at System.Windows.Forms.Control.PaintWithErrorHandling(PaintEventArgs e, Int16 layer, Boolean disposeEventArgs)
   at System.Windows.Forms.Control.WmPaint(Message& m)
   at System.Windows.Forms.Control.WndProc(Message& m)
   at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
   at System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)

Seems to be the same line it crashed on last time, the main drawArc inside the loop.

like image 889
Dereleased Avatar asked Nov 29 '22 05:11

Dereleased


2 Answers

Just for anyone else, finding this page via Google:

A possible cause of System.OutOfMemoryException if you use System.Drawing.DrawArc could also be a bug if you try to print small angles.

For angles < 1 this bug occured several times in my code.

See also:

http://connect.microsoft.com/VisualStudio/feedback/details/121532/drawarc-out-of-memory-exception-on-small-arcs

like image 88
buhlara Avatar answered Dec 21 '22 21:12

buhlara


Make sure that you dispose Pen and Brush objects as well and use using blocks to make sure that you dispose objects even if there are exceptions.

As a side note: avoid recreating and disposing _backBuffer each time you draw. Either catch resize event and dispose _backBuffer there or just check if _backBuffer has correct dimensions on each Paint event and dispose and recreate if dimensions does not match.

like image 41
Andreas Paulsson Avatar answered Dec 21 '22 23:12

Andreas Paulsson