R
Richard Lewis Haggard
I'm having drawing performance issues in an application. What's the best way
to draw a large number of small bitmaps onto the client area?
The application with the drawing issues can be downloaded from
http://www.haggard-and-associates.com/Products/h&a_pocket_spider_solitaire.htm
Here's the situation: the application has 104 cards. Each card has a single
graphic (heart, spade, club, diamond) and a number (1-K).
Here's how it's working now: I'm using the standard double buffering
approach to get around the flashing problems. The erase background event has
been intercepted and does nothing. Each card maintains a copy of its image
so that it was not necessary to construct it each time. At Paint event time,
a bitmap of the clip rectangle size is created. A graphics object is created
to contain the bitmap. Those cards that intersect the clip rectangle are
drawn into the bitmap. Once all of the cards have been drawn, the bitmap is
copied to the device's display surface. At the end of the Paint, the clip
rectangle bitmap and its graphics object are disposed.
The performance is acceptable but there is significant delay. In a non.NET
application, I know that this could be done so that the delay is
imperceptable. Can .NET operate as efficiently or is the .NET overhead so
much greater that this performance hit is just something that we're just
going to have to live with? Or is there a better way to draw graphics?
There are two areas that are potential time sinks. First of all, during the
paint, a new bitmap is created and disposed of with each Paint event. How
much time is eaten up by creation of the bitmap? Would it make a sifnificant
difference to create a memory bitmap at the start of time and just hang onto
it until end of program or would that be a resource hog?
Secondly, this code has a lot small object creations. Is this important? How
many CPU cycles get chewed up with a "new Rectangle(xxxx)" call? Is this
significant?
Here's the code from the main form that receives the Paint event-
private void frmMain_Paint(object sender,
System.Windows.Forms.PaintEventArgs e)
{
Graphics dc;
Bitmap bm = null;
// Create and initialize a drawing surface that is the same size as the
refresh area.
Rectangle rc = e.ClipRectangle;
bm = new Bitmap( rc.Width, rc.Height );
dc = Graphics.FromImage( bm );
dc.FillRectangle( new SolidBrush( Color.Green ), 0, 0, rc.Width,
rc.Height );
// Let all of the various stacks draw their information onto this
surface.
// These routines pass the graphics object that contains the
// clip rectangle bitmap down to those card objects that intersect the
clip rectangle
m_topStacks.Paint( dc, e.ClipRectangle );
m_doneStacks.Paint( dc, e.ClipRectangle );
m_dealStacks.Paint( dc, e.ClipRectangle );
if ( m_bDrag )
m_dragStack.Paint( dc, rc, m_dragStack.Rect, true );
// Transfer the image from the drawing surface to the screen.
e.Graphics.DrawImage( bm, rc, 0, 0, rc.Width, rc.Height,
System.Drawing.GraphicsUnit.Pixel,
new System.Drawing.Imaging.ImageAttributes() );
// Graphics are a special case. The garbage collector doesn't react fast
// enough to handle it so we have to push the resource release up in
priority.
bm.Dispose();
dc.Dispose();
}
Here's the code down at the card leve that is responsible for doing the card
drawing -
public void Paint( Graphics e, Rectangle ClipRectangle )
{
// <Clipped code>
// The code here checks to see if the back bitmap exists.
// If not, creates the back bitmap.
// All cards share the same back bitmap.
// <Clipped code>
// The code clipped from here checks to see if the front card
// bitmap exists. If not, a front bitmap is created.
// m_rcRect is the card's Rectangle member that describe's the card's
screen location.
// Since the card is being drawn into the cliprectangle's bitmap, not
the screen, the coordinates
// must be adjusted to be relative to the clip rectangle.
Rectangle rcDst = new Rectangle( m_rcRect.X-ClipRectangle.X,
m_rcRect.Y-ClipRectangle.Y, m_rcRect.Width, m_rcRect.Height );
if ( m_bUp )
e.DrawImage( m_bmFace, rcDst.Left, rcDst.Top );
else
e.DrawImage( m_bmBack, rcDst.Left, rcDst.Top );
}
to draw a large number of small bitmaps onto the client area?
The application with the drawing issues can be downloaded from
http://www.haggard-and-associates.com/Products/h&a_pocket_spider_solitaire.htm
Here's the situation: the application has 104 cards. Each card has a single
graphic (heart, spade, club, diamond) and a number (1-K).
Here's how it's working now: I'm using the standard double buffering
approach to get around the flashing problems. The erase background event has
been intercepted and does nothing. Each card maintains a copy of its image
so that it was not necessary to construct it each time. At Paint event time,
a bitmap of the clip rectangle size is created. A graphics object is created
to contain the bitmap. Those cards that intersect the clip rectangle are
drawn into the bitmap. Once all of the cards have been drawn, the bitmap is
copied to the device's display surface. At the end of the Paint, the clip
rectangle bitmap and its graphics object are disposed.
The performance is acceptable but there is significant delay. In a non.NET
application, I know that this could be done so that the delay is
imperceptable. Can .NET operate as efficiently or is the .NET overhead so
much greater that this performance hit is just something that we're just
going to have to live with? Or is there a better way to draw graphics?
There are two areas that are potential time sinks. First of all, during the
paint, a new bitmap is created and disposed of with each Paint event. How
much time is eaten up by creation of the bitmap? Would it make a sifnificant
difference to create a memory bitmap at the start of time and just hang onto
it until end of program or would that be a resource hog?
Secondly, this code has a lot small object creations. Is this important? How
many CPU cycles get chewed up with a "new Rectangle(xxxx)" call? Is this
significant?
Here's the code from the main form that receives the Paint event-
private void frmMain_Paint(object sender,
System.Windows.Forms.PaintEventArgs e)
{
Graphics dc;
Bitmap bm = null;
// Create and initialize a drawing surface that is the same size as the
refresh area.
Rectangle rc = e.ClipRectangle;
bm = new Bitmap( rc.Width, rc.Height );
dc = Graphics.FromImage( bm );
dc.FillRectangle( new SolidBrush( Color.Green ), 0, 0, rc.Width,
rc.Height );
// Let all of the various stacks draw their information onto this
surface.
// These routines pass the graphics object that contains the
// clip rectangle bitmap down to those card objects that intersect the
clip rectangle
m_topStacks.Paint( dc, e.ClipRectangle );
m_doneStacks.Paint( dc, e.ClipRectangle );
m_dealStacks.Paint( dc, e.ClipRectangle );
if ( m_bDrag )
m_dragStack.Paint( dc, rc, m_dragStack.Rect, true );
// Transfer the image from the drawing surface to the screen.
e.Graphics.DrawImage( bm, rc, 0, 0, rc.Width, rc.Height,
System.Drawing.GraphicsUnit.Pixel,
new System.Drawing.Imaging.ImageAttributes() );
// Graphics are a special case. The garbage collector doesn't react fast
// enough to handle it so we have to push the resource release up in
priority.
bm.Dispose();
dc.Dispose();
}
Here's the code down at the card leve that is responsible for doing the card
drawing -
public void Paint( Graphics e, Rectangle ClipRectangle )
{
// <Clipped code>
// The code here checks to see if the back bitmap exists.
// If not, creates the back bitmap.
// All cards share the same back bitmap.
// <Clipped code>
// The code clipped from here checks to see if the front card
// bitmap exists. If not, a front bitmap is created.
// m_rcRect is the card's Rectangle member that describe's the card's
screen location.
// Since the card is being drawn into the cliprectangle's bitmap, not
the screen, the coordinates
// must be adjusted to be relative to the clip rectangle.
Rectangle rcDst = new Rectangle( m_rcRect.X-ClipRectangle.X,
m_rcRect.Y-ClipRectangle.Y, m_rcRect.Width, m_rcRect.Height );
if ( m_bUp )
e.DrawImage( m_bmFace, rcDst.Left, rcDst.Top );
else
e.DrawImage( m_bmBack, rcDst.Left, rcDst.Top );
}