Click here to Skip to main content
Click here to Skip to main content

Drawing Speed in GDI+

, 9 Aug 2001
Rate this:
Please Sign up or sign in to vote.
This article examines GDI+ drawing speed

Introduction

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:

Sample Image - GDIPlusSpeed.jpg

I made two tests

  1. resize window
  2. move some other window over tested window (for example, About box).

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.

Code fragments and execution time

Step 1. Drawing directly to window

class CMainFrame : public CFrameWindowImpl<CMainFrame>,
        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;
};

Execution time in Output window:
  • OnPaint 300 ms

Step 2 - Using off-screen graphics

    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:
  • OnPaint 130 ms
  • Prepare off-screen bitmap 40 ms

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.

Step 3 - Using off-screen graphics as class member

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
  • OnPaint 130 ms
  • Prepare off-screen bitmap 40 ms
  • Without resizing (draw from off-screen graphics to screen): OnPaint 70 ms

Step 4 - using CachedBitmap

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:
  • OnPaint 130 ms
  • Prepare off-screen bitmap 110 ms
  • Without resizing (draw from cached bitmap to screen): OnPaint 20 ms

Conclusion

Using CashedBitmap in GDI+ allows to get acceptable drawing speed. Maybe in Windows XP results will be better.

Notes

I didn't find in GDI+ Windows API functions like:
  • GetClipBox, which allows to minimize drawing operations;
  • GetBitmapBits and SetBitmapBits which are very useful in image processing.

Does anyone has some idea about this?

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Alex Fr
Software Developer
Israel Israel
No Biography provided

Comments and Discussions

 
GeneralMy vote of 5 Pinmembermanoj kumar choubey20-Feb-12 20:34 
Generallaggard GDI+ PinmemberSeungwoo Oh3-May-08 16:42 
Generalwhere is &quot;atlapp.h&quot; PinmemberBrillianxe26-Dec-04 19:21 
GeneralRe: where is &quot;atlapp.h&quot; PinmemberBrillianxe26-Dec-04 19:34 
GeneralRe: where is &quot;atlapp.h&quot; PinsussAnonymous28-Jan-05 10:45 
QuestionStep 2's OnPaint time is slower than Step1? PinmemberKi C Jung15-Nov-04 10:52 
AnswerRe: Step 2's OnPaint time is slower than Step1? Pinmemberinshua17-Jul-07 15:59 
GeneralWorking with Controls PinsussAnonymous4-Aug-04 5:13 
GeneralResolution problem with offscreen method PinmemberDjauh22-Jun-04 3:45 
GeneralRe: Resolution problem with offscreen method PinmemberJorgen E.6-Jul-04 13:33 
GeneralRe: Resolution problem with offscreen method PinsussAnonymous4-Aug-04 5:01 
GeneralProgram Crash PinmemberJorgen E.23-May-04 6:12 
GeneralRe: Program Crash PinmemberAlex Farber23-May-04 20:05 
GeneralRe: Program Crash PinmemberJorgen E.24-May-04 8:53 
QuestionCreating Graphics Object Only Once ? PinsussAnonymous21-Apr-04 11:06 
AnswerRe: Creating Graphics Object Only Once ? PinmemberAlex Farber21-Apr-04 18:14 
Generalimage processing Pinsussprit14-Apr-03 7:21 
GeneralErasing drawing objects.. Pinmemberbaba25-Nov-02 18:12 
Questionwhere atlres.h ?? Pinmemberghj197617-Nov-02 19:08 
GeneralProblems with SelectObject PinsussPeter Schuhmann27-Jul-02 3:27 
GeneralOdd behaviour PinmemberBox202011-Jul-02 3:02 
GeneralRe: Odd behaviour PinmemberAlex Farber13-Jul-02 19:04 
GeneralRe: Odd behaviour PinsussAnonymous12-Jun-03 8:49 
GeneralGDI performance PinmemberAnonymous1-May-02 7:20 
GeneralRe: loading bitmap in CachedBitmap gobbles memory PinmemberAlex Farber7-Mar-02 2:38 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140721.1 | Last Updated 10 Aug 2001
Article Copyright 2001 by Alex Fr
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid