![]() |
Multimedia »
GDI+ »
General
Intermediate
Drawing Speed in GDI+By Alex FrThis article examines GDI+ drawing speed |
VC6Win2K, WTL, GDI+, Dev
|
|
Advanced Search |
|
|
|
||||||||||||||||
GDI+ gives developers many advanced graphic functions and looks more friendly than GDI. I wanted to answer the question 'how fast is GDI+?' and 'what can we do to get smooth and fast redrawing?'.
To estimate drawing speed I checked the "Show window contents while dragging" in the Control Panel - Display Properties dialog, Effects tag. I then ran the program which looks like this:

I made two tests
Redrawing should be both smooth and fast.
For the demo project I used Windows Template Library (WTL) which is available in the Microsoft Platform SDK. WTL is my preferable framework for writing small Windows programs without MFC. To compile the demo project add the WTL Include directory to the list of Visual C++ Include directories. For GDI+ use the Microsoft SDK Include\Prerelease and Lib\Prerelease directories.
The Drawing code is very simple - horizontal and vertical lines on white background. The Window doesn't erase the background itself and the client area is filled using GDI+. To get the exact time of redrawing I use my class CTimeCounter which prints to the Debug stream the execution time of the various program sections.
I made four demo projects - from step 1 (drawing directly to window) to step 4 (using cashed bitmap). All projects are created using the WTL Application Wizard, SDI application without View class.
For each step I write the execution time. It depends on many factors, such as operating system, computer and graphic card speed, program configuration, window size etc., but I believe the proportions between execution speed on various steps will be the same in any case.
class CMainFrame : public CFrameWindowImpl<CMainFrame>,Execution time in Output window:
public CUpdateUI<CMainFrame>, public CMessageFilter, public CIdleHandler { // ... LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/,
BOOL& /*bHandled*/) { // Initialize GDI+ Gdiplus::GdiplusStartupInput gdiplusStartupInput; Gdiplus::GdiplusStartup(&m_gdiplusToken, &gdiplusStartupInput, NULL); // ... } // release GDI+ LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { Gdiplus::GdiplusShutdown(m_gdiplusToken); return 0; } // cause to redraw all client area while resizing LRESULT OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { Invalidate(); return 0; } // don't redraw background LRESULT OnEraseBkgnd(UINT uMsg, WPARAM wParam, LPARAM lParam,
BOOL& bHandled) { return 0; } // draw lines LRESULT OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { SHOW_TIME_START(t, _T("OnPaint")); // use CTimeCounter to get
// execution time CPaintDC dc(m_hWnd); Graphics graphics(dc.m_hDC); RECT rect; GetClientRect(&rect); int nWidth = rect.right - rect.left + 1; int nHeight = rect.bottom - rect.top + 1; // fill background SolidBrush solidBrush(Color(255, 255, 255)); graphics.FillRectangle(&solidBrush, 0, 0, nWidth, nHeight); Pen pen(Color(255, 0, 0, 255)); int nStep, i; int nLines = 20; // draw horizontal lines nStep = nHeight / nLines; for ( i = 0; i < nLines; i++ ) { graphics.DrawLine(&pen, 0, nStep*i, nWidth, nStep*i); } // draw vertical lines nStep = nWidth / nLines; for ( i = 0; i < nLines; i++ ) { graphics.DrawLine(&pen, nStep*i, 0, nStep*i, nHeight); } SHOW_TIME_END(t); // TRACE execution time here return 0; } protected: ULONG_PTR m_gdiplusToken; };
LRESULT OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
SHOW_TIME_START(t, _T("OnPaint"));
CPaintDC dc(m_hWnd);
RECT rect;
GetClientRect(&rect);
int nWidth = rect.right - rect.left + 1;
int nHeight = rect.bottom - rect.top + 1;
Graphics graphics(dc.m_hDC);
SHOW_TIME_START(t1, _T("Prepare off-screen bitmap"));
// memory bitmap
Bitmap* pMemBitmap = new Bitmap(nWidth, nHeight);
// offscreen graphics
Graphics* pMemGraphics = Graphics::FromImage(pMemBitmap);
// draw to memory bitmap
Draw(pMemGraphics, nWidth, nHeight);
SHOW_TIME_END(t1);
// draw from memory bitmap to window
graphics.DrawImage(pMemBitmap, 0, 0);
delete pMemGraphics;
delete pMemBitmap;
SHOW_TIME_END(t);
return 0;
}
// draw lines to Graphics
void Draw(Graphics* pGraphics, int nWidth, int nHeight)
{
// fill background
SolidBrush solidBrush(Color(255, 255, 255));
pGraphics->FillRectangle(&solidBrush, 0, 0, nWidth, nHeight);
Pen pen(Color(255, 0, 0, 255));
int nStep, i;
int nLines = 20;
// draw horizontal lines
nStep = nHeight / nLines;
for ( i = 0; i < nLines; i++ )
{
pGraphics->DrawLine(&pen, 0, nStep*i, nWidth, nStep*i);
}
// draw vertical lines
nStep = nWidth / nLines;
for ( i = 0; i < nLines; i++ )
{
pGraphics->DrawLine(&pen, nStep*i, 0, nStep*i, nHeight);
}
}
Execution time in the Output window:
Preparing the off-screen bitmap is part of the drawing. That means window redrawing takes 130 ms, 40 ms of them is preparing the off-screen bitmap.
In this step there are two redrawing times. Full redrawing is done when the window is resizing. Redrawing without resizing takes less time, because the off-screen graphics with the required size is ready for use.
CMainFrame()
{
m_pMemBitmap = NULL;
m_pMemGraphics = NULL;
}
// clean-up operations
LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
// release off-screen bitmap and graphics
if ( m_pMemGraphics )
delete m_pMemGraphics;
if ( m_pMemBitmap )
delete m_pMemBitmap;
// ...
}
// cause to redraw all window client area while resizing
LRESULT OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
Invalidate();
SetDirty();
return 0;
}
// cause to create offscreen graphics when window will be redrawn
void SetDirty()
{
if ( m_pMemGraphics )
{
delete m_pMemGraphics;
m_pMemGraphics = NULL;
}
if ( m_pMemBitmap )
{
delete m_pMemBitmap;
m_pMemBitmap = NULL;
}
}
// redraw window
LRESULT OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
SHOW_TIME_START(t, _T("OnPaint"));
CPaintDC dc(m_hWnd);
RECT rect;
GetClientRect(&rect);
int nWidth = rect.right - rect.left + 1;
int nHeight = rect.bottom - rect.top + 1;
Graphics graphics(dc.m_hDC);
if ( ! m_pMemBitmap )
{
SHOW_TIME_START(t1, _T("Prepare off-screen bitmap"));
CreateOffScreeenGraphics(nWidth, nHeight);
SHOW_TIME_END(t1);
}
// draw from memory bitmap to window
graphics.DrawImage(m_pMemBitmap, 0, 0);
SHOW_TIME_END(t);
return 0;
}
// create off-screen graphics and draw to it
void CreateOffScreeenGraphics(int nWidth, int nHeight)
{
// Create off-screen bitmap
m_pMemBitmap = new Bitmap(nWidth, nHeight);
// Create off-screen graphics
m_pMemGraphics = Graphics::FromImage(m_pMemBitmap);
// draw to off-screen graphics
Draw(m_pMemGraphics, nWidth, nHeight);
}
// draw lines to Graphics
void Draw(Graphics* pGraphics, int nWidth, int nHeight)
{
// ...
}
protected:
ULONG_PTR m_gdiplusToken;
Bitmap* m_pMemBitmap;
Graphics* m_pMemGraphics;
// ...
Execution time in the Output window
While using a cashed bitmap we should be ready for the rare case when the Display Properties are changed. The Program checks the return value of DrawCachedBitmap function and creates the CachedBitmap object again if necessary.
CMainFrame()
{
m_pCachedBitmap = NULL;
// ...
}
// clean-up operations
LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
if ( m_pCachedBitmap )
delete m_pCachedBitmap;
// ...
}
// redraw window
LRESULT OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
SHOW_TIME_START(t, _T("OnPaint"));
CPaintDC dc(m_hWnd);
RECT rect;
GetClientRect(&rect);
int nWidth = rect.right - rect.left + 1;
int nHeight = rect.bottom - rect.top + 1;
Graphics graphics(dc.m_hDC);
if ( ! m_pMemBitmap )
{
SHOW_TIME_START(t1, _T("Prepare off-screen bitmap"));
CreateOffScreeenGraphics(nWidth, nHeight, &graphics);
SHOW_TIME_END(t1);
}
// draw from cached bitmap to window
if ( graphics.DrawCachedBitmap(m_pCachedBitmap, 0, 0) != Ok )
{
// make bitmap again (display parameters are changed)
SetDirty();
CreateOffScreeenGraphics(nWidth, nHeight, &graphics);
graphics.DrawCachedBitmap(m_pCachedBitmap, 0, 0);
}
SHOW_TIME_END(t);
return 0;
}
// create off-screen graphics and draw to it
void CreateOffScreeenGraphics(int nWidth, int nHeight, Graphics* pGraphics)
{
// Create off-screen bitmap
m_pMemBitmap = new Bitmap(nWidth, nHeight);
// Create off-screen graphics
m_pMemGraphics = Graphics::FromImage(m_pMemBitmap);
// draw to off-screen graphics
Draw(m_pMemGraphics, nWidth, nHeight);
// Create cashed bitmap
m_pCachedBitmap = new CachedBitmap(m_pMemBitmap, pGraphics);
}
// draw lines to Graphics
void Draw(Graphics* pGraphics, int nWidth, int nHeight)
{
// ...
}
// cause to create offscreen graphics when window will be redrawn
void SetDirty()
{
// ...
if ( m_pCachedBitmap )
{
delete m_pCachedBitmap;
m_pCachedBitmap = NULL;
}
}
protected:
CachedBitmap* m_pCachedBitmap;
// ...
Execution time in Output window:
Using CashedBitmap in GDI+ allows to get acceptable drawing speed. Maybe in Windows XP results will be better.
GetClipBox, which allows to minimize drawing operations;
GetBitmapBits and SetBitmapBits which are very useful in image processing. Does anyone has some idea about this?
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 9 Aug 2001 Editor: Chris Maunder |
Copyright 2001 by Alex Fr Everything else Copyright © CodeProject, 1999-2009 Web11 | Advertise on the Code Project |