Why does my C # drawing method run out of memory?

I am new to c # and trying to learn by writing some simple applications to familiarize myself with the syntax and the .NET library. The most recent mini-project I took is a polar watch similar to those found here .

One of the problems that I noticed in the early stages was that the application constantly β€œflickered”, which really removed from the presentation, so I read on the Internet about how to implement a double buffer that fixed this problem, but might have or not have something to do with the problem. Here is my onPaint method; it is called every 33 ms (~ 30 FPS) using a timer. Most of the rest of the application is just handlers for dragging and dropping the application (since it is frameless and with a transparent background), double-click exit, 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 that getting rid of everything at the end of the method, I would be safe, but it does not seem to help. In addition, the interval between runtime and OutOfMemoryException is not constant; once it happened in just a few seconds, but it usually takes a minute or two. Here are some class variable declarations.

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

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

Screen shot
(source: ggot.org )

UPDATE: 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) 

This seems to be the same line in which it crashed the last time, the main drawArc inside the loop.

+4
source share
6 answers

Just for someone else, finding this page via Google:

A possible reason for a System.OutOfMemoryException exception if you use System.Drawing.DrawArc could also be an error if you try to print small angles.

For angles <1, this error occurred several times in my code.

See also:

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

+6
source

Make sure that you also delete Pen and Brush objects and use blocks to make sure that you delete objects, even if there are exceptions.

As a side note: Avoid recreating and deleting _backBuffer every time you draw. Either catch the resize event, either delete _backBuffer there, or just check if _backBuffer have the correct sizes for each Paint event and are deleted and recreated if the sizes do not match.

+8
source

I did not find anything terrible in your code. Can you specify the exact line on which an OutOfMemoryException occurs?

Just for you to know, it really took me several months to understand: OutOfMemoryException does not mean out of memory. ;-) This happens in GDI + when something just went wrong (shows a bad coding style inside GDI, IMHO), for example , you tried to load an invalid image or image with an invalid pixel format, etc.

+3
source

Not quite the answer to the question, but a possible solution:

You do not have to create a new bitmap every time. Just clean it every time you draw a new frame.

However, when resizing, you must create a new bitmap.

+2
source

Why do you need a new bitmap every time you want something drawn with OnPaint ?! You need exactly 1. Try something like this:

 private Bitmap _backBuffer = new Bitmap(this.Width, this.Height); protected override void OnPaint(PaintEventArgs e) { Graphics g = Graphics.FromImage(_backBuffer); //Clear back buffer with white color... g.Clear(Color.White); //Draw all new stuff... } 
+1
source

This is not the answer to your question, and maybe there is a good reason why you do it this way (I could learn something), but why first create bitmaps, draw a bitmap and then draw a bitmap in the form? Wouldn't it be more efficient to draw directly on the form? Something about this:

 protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); //_backBuffer = new Bitmap(this.Width, this.Height); Graphics g = Graphics.FromImage(_backBuffer); //Rest of your code //e.Graphics.DrawImageUnscaled(_backBuffer, 0, 0); //g.Dispose(); //e.Dispose(); //base.OnPaint(e); //_backBuffer.Dispose(); //_backBuffer = null; } 

Also according to MSDN

When overriding OnPaint in a derived class, be sure to call the OnPaint method of the base class so that registered delegates receive the event.

0
source

All Articles