Search code examples
c#.netwinformsbitmappaintevent

Using Bitmap in Bouncing Balls


I have a WinForm application 'Bouncing Balls' , and I need to paint the balls on a bitmap and present the bitmap on this form.

I have a plusButton that adds new ball, and i'm saving each new ball in a list.

Now, the Form_Paint method is telling to each ball to draw himself, it works fine until there are a lot of balls and the all application become very slow..

Here is my Code:

The paint method of the form code:

 private void Form1_Paint(object sender, PaintEventArgs e)
 {
     ballsArray.drawImage(bmp,e, ClientRectangle);
 }

NOTE: ballsArray is from type AllBalls, this is a class that wraps the ball methods, inside his c'tor i'm creating a list that keeps each ball. the bmp, is created when the form is loading - on Form_Load() method.

The drawImage of ballsArray code:

 public void drawImage(Bitmap bmp,PaintEventArgs e, Rectangle r)
 {
     foreach (Ball b in allBalls)
     {
         b.drawImage(bmp,e, r);
     }
 }

The drawImage of Ball code:

  public void drawImage(Bitmap bmp, PaintEventArgs e, Rectangle r)
  {
      using (Graphics g = Graphics.FromImage(bmp))
      {
          e.Graphics.FillEllipse(brush, ballLocation);
          g.DrawImage(bmp, 0, 0);
      }
  }

NOTE: ballLocation is a rectangle that represent the location of the ball in each step of movement..

So what I'm doing wrong? What causing the application to be slowly?

I have a constraint to draw everything on the bitmap and present it on the form. I'm also passing the bitmap that I create when the form is loading, because I need to draw each on it.


Solution

  • Some basic techniques to make this fast:

    • Don't double-buffer yourself and especially don't double-buffer twice. The double-buffering you get by setting the form's DoubleBuffer property to true is superior to most any double-buffering you'd do yourself. The buffer is highly optimized to work efficiently with your video adapter's settings. So completely drop your bmp variable and draw to the e.Graphics you got from the Paint event handler argument.

    • You are not using the passed r argument. Possibly intended to support clipping invisible balls. The one you want to pass is e.ClipRectangle, you can skip painting balls that are completely outside of this rectangle. While that's an optimization, it isn't one that's commonly useful when you use the Aero theme and you do get inconsistent redraw rates so you might want to skip that one.

    • It isn't very clear why you use both Graphics.FillEllipse and Graphics.DrawImage when you draw the ball. The image ought to overlap the circle so just remove FillEllipse.

    • Pay a lot of attention to the Bitmap object that stores the ball graphic. First thing you want to make sure is that it is drawn with the exact size of the image so it doesn't have to be rescaled. Rescaling is very expensive. While you don't have any rescaling in your DrawImage() call, you will still get it if the resolution of the bitmap is not the same as the resolution of your video adapter. The next step will solve that

    • The pixel format of the ball bitmap is very important. You want one that permits copying the bitmap straight to video memory without any format conversion. On any modern machine, that format is PixelFormat.Format32bppPArgb. The difference is enormous, it draws ten times faster than any of the other ones. You won't get this format from an image resource you added, you'll have to create that bitmap when your program starts up. Check this answer for the required code.

    You ought to be able to render at least 15 times faster when you follow these guidelines. If that's still enough then you do need to turn to DirectX, it has the unbeatable advantage of being able to store the ball graphic in video memory so you don't get the expensive blt from main memory to video memory.