Flicker Free Drawing In C#

Environment: .NET C#, Win2K, Windows XP

Introduction

Flicker free animated drawing had been a very hot issue with Win32 and MFC. Many excellent articles are available to explain the techniques to get a flicker free animated effect. As many of the reader know that most popular technique has been to use off-screen DC (device context) to do the entire complex drawing and then copying this off-screen DC to the screen DC directly. This technique is also known as double buffering.

C# is projected by Microsoft as the future for C++ programmers. So like many other C++ programmers, I used some of my spare time to play around with C#.

A few days back I was trying to write an application in C# to simulate an analog clock. After establishing a base frame work and seeing my clock work (with flicker of course) I was excited to use the old double buffering technique to let my clock animate smoothly. But my first dilemma was not finding functions like CreateCompatibleDC, CreateCompatibleBitmap and SelectObject etc. I started to search around MSDN and study the Graphics class. After some research I was able to find two ways to produce smooth animated effects. These techniques I will explain below.

Double Buffering Technique The Old Way

I was glad to know that there was a work around in C# to use the old Win32 techniques for smooth animation. Although one cannot find direct implementation for functions like CreateCompatibleDC, CreateCompatibleBitmap and SelectObject, but there is an indirect way to use these functions for your GDI+ device context. The idea is to let C# know that you will be using some functions from an unmanaged dll. You can import a function that is exported by dll using the DllImport attribute. The detailed documentation for DllImport can be found in .NET documentation. In short with the help of DllImport we can tell the compiler that we will be using the specified function from the specified dll. For example,
[DllImport("msvcrt.dll")] 
public static extern int puts(string c); 

This declaration will declare the function named puts with static and extern attributes and the actual implementation of this function will be imported from msvcrt.dll. I used DllImport to import all the necessary functions from gdi32.dll. To keep thing managed, I declared a separate class to import all such functions. Code below shows the actual implementation for this class

/// <summary>
/// Summary description for Win32Support.
/// Win32Support is a wrapper class that imports all the necessary
/// functions that are used in old double-buffering technique
/// for smooth animation.
/// </summary>
public class Win32Support
{
  /// <summary>
  /// Enumeration to be used for those Win32 function that return BOOL
  /// </summary>
  public enum Bool 
  {
    False= 0,
    True
  };

  /// <summary>
  /// Enumeration for the raster operations used in BitBlt.
  /// In C++ these are actually #define. But to use these
  /// constants with C#, a new enumeration type is defined.
  /// </summary>
  public enum TernaryRasterOperations
  {
    SRCCOPY     = 0x00CC0020, /* dest = source                   */
    SRCPAINT    = 0x00EE0086, /* dest = source OR dest           */
    SRCAND      = 0x008800C6, /* dest = source AND dest          */
    SRCINVERT   = 0x00660046, /* dest = source XOR dest          */
    SRCERASE    = 0x00440328, /* dest = source AND (NOT dest )   */
    NOTSRCCOPY  = 0x00330008, /* dest = (NOT source)             */
    NOTSRCERASE = 0x001100A6, /* dest = (NOT src) AND (NOT dest) */
    MERGECOPY   = 0x00C000CA, /* dest = (source AND pattern)     */
    MERGEPAINT  = 0x00BB0226, /* dest = (NOT source) OR dest     */
    PATCOPY     = 0x00F00021, /* dest = pattern                  */
    PATPAINT    = 0x00FB0A09, /* dest = DPSnoo                   */
    PATINVERT   = 0x005A0049, /* dest = pattern XOR dest         */
    DSTINVERT   = 0x00550009, /* dest = (NOT dest)               */
    BLACKNESS   = 0x00000042, /* dest = BLACK                    */
    WHITENESS   = 0x00FF0062, /* dest = WHITE                    */
  };

  /// <summary>
  /// CreateCompatibleDC
  /// </summary>
  [DllImport("gdi32.dll", ExactSpelling=true, SetLastError=true)]
  public static extern IntPtr CreateCompatibleDC(IntPtr hDC);

  /// <summary>
  /// DeleteDC
  /// </summary>
  [DllImport("gdi32.dll", ExactSpelling=true, SetLastError=true)]
  public static extern Bool DeleteDC(IntPtr hdc);

  /// <summary>
  /// SelectObject
  /// </summary>
  [DllImport("gdi32.dll", ExactSpelling=true)]
  public static extern IntPtr SelectObject( IntPtr hDC,
                                            IntPtr hObject);

  /// <summary>
  /// DeleteObject
  /// </summary>
  [DllImport("gdi32.dll", ExactSpelling=true, SetLastError=true)]
  public static extern Bool DeleteObject(IntPtr hObject);

  /// <summary>
  /// CreateCompatibleBitmap
  /// </summary>
  [DllImport("gdi32.dll",
             ExactSpelling=true,
             SetLastError=true)]
  public static extern IntPtr CreateCompatibleBitmap(IntPtr hObject,
                                                     int width,
                                                     int height);

  /// <summary>
  /// BitBlt
  /// </summary>
  [DllImport("gdi32.dll", ExactSpelling=true, SetLastError=true)]
  public static extern Bool BitBlt(IntPtr hObject,
                                   int nXDest,
                                   int nYDest,
                                   int nWidth,
                                   int nHeight,
                                   IntPtr hObjSource,
                                   int nXSrc,
                                   int nYSrc,
                                   TernaryRasterOperations dwRop);
}

Now I can use this Win32Support class to use my old techniques. The code snippet below shows how to create a memory DC with help of Win32Support from within your Form class.

Graphics memDC; 
Bitmap memBmp; 
memBmp = new Bitmap(this.Width, this.Height); 

Graphics clientDC = this.CreateGraphics(); 
IntPtr hdc = clientDC.GetHdc(); 
IntPtr memdc = Win32Support.CreateCompatibleDC(hdc); 
Win32Support.SelectObject(memdc, memBmp.GetHbitmap()); 
memDC = Graphics.FromHdc(memdc); clientDC.ReleaseHdc(hdc); 

One important point to note here is that every call to the function Graphic.GetHdc on some DC must be paired with the call to Graphic.ReleaseHdc. this is what MSDN has to say about this issue:

"Calls to the GetHdc and ReleaseHdc methods must appear in pairs. During the scope of a GetHdc- ReleaseHdc method pair, you usually make only calls to GDI functions. Calls in that scope made to GDI+ methods of the Graphics object that produced the hdc parameter fail with an ObjectBusy error. Also, GDI+ ignores any state changes made to the Graphics object of the hdc parameter in subsequent operations."
\

Once you have memDC, you can use it for off screen drawing and then we will use BitBlt to copy the contents of memDC to actual screen DC.

Graphics clientDC = this.CreateGraphics(); 
// do drawing in memDC 
// do drawing in memDC 
// do drawing in memDC 
IntPtr hdc = clientDC.GetHdc(); 
IntPtr hMemdc = memDC.GetHdc(); 
// transfer the bits from memDC to clientDC 
Win32Support.BitBlt(hdc,
                    0, 0,
                    this.Width, this.Height,
                    hMemdc,
                    0, 0, 
                    Win32Support.TernaryRasterOperations.SRCCOPY);
clientDC.ReleaseHdc(hdc); 
memDC.ReleaseHdc(hMemdc); 

This will have dramatic effect on the animation that you have been trying to produce.

Sample application uses this technique when you click the "Offscreen Drawing Using BitBlt" radio button.

Double Buffering Technique The .NET Way

Luckily we can achieve the same goal without any direct help from Win32 API. Image rendering in .NET is much simple and efficient than MFC. There are two functions in Graphics class to render your Image object on screen, these are DrawImage and DrawImageUnscaled. What makes these functions important is the fact that .NET always uses BitBlt in background to render the image on DC. So if we are able to do our off-screen drawing in an Image object, we can use these functions to render this object directly to DC and have the smooth animated effects.

The technique is same, but the way to implement it differs a little bit. In the code fragment below, we are using a Bitmap object to do our off-screen drawing. In order to draw on some Image object, it must be attached to a Graphics object. We can create a new Graphics object from an Image object using the static member function, of Graphics, named FromImage. Once we get a Graphics object from some Image object, any drawing done on this Graphics object will actually be changing the Image.

Bitmap offScreenBmp; 
Graphics offScreenDC; 
offScreenBmp = new Bitmap(this.Width, this.Height); 
offScreenDC = Graphics.FromImage(offScreenBmp); 

Provided that we have created offScreenDC as per shown in example above, we can implement the double buffering technique as per the code fragment below.

Graphics clientDC = this.CreateGraphics(); 
// do drawing in offScreenDC 
// do drawing in offScreenDC 
// do drawing in offScreenDC 
clientDC.DrawImage(offScreenBmp, 0, 0); 

I will recommend this technique as it does not involve any call to Unmanaged code and is simpler in nature.

Sample application uses this technique when you click the "Offscreen Drawing Using Image" radio button.

Downloads

Download demo project - 30 Kb


Comments

  • Double Buffering Technique The .NET Way

    Posted by anuragkrsinha on 03/13/2009 01:12am

    Good Article

    Reply
  • Double Buffering Technique The .NET Way

    Posted by imekon on 11/24/2006 09:03am

    This uses GDI+, which is slow as its not accelerated.

    Reply
  • Double buffering... yet another way

    Posted by glattetre on 07/27/2004 05:30am

    I found a simpler solution for getting double buffering that was even easier to implement:  
    
    Add the following style in the forms constructor:
    base.SetStyle
      (
        ControlStyles.DoubleBuffer | 
        ControlStyles.UserPaint |  
        ControlStyles.AllPaintingInWmPaint,true
      );
    
    It seamed to me like the performance was better than I archived using the mechanisms you described in "The .NET way".  I think this was caused by the fact that the Off-screen surface was in the computers memory in your example and hopefully in the display card memory as an off-screen surface.  This would increase the blitting performance from back to front buffer significantly.

    • worked but failed

      Posted by sudhir_kr on 05/18/2006 07:23am

      I added the above mentioned line in my form's constructor and it really worked fine........But when i tried to scroll(axis scroll of my graph) my graph still flickers, any help ???? I am desperetely seeking the solution.... Thanks in advance Sudhir

      Reply
    Reply
  • Remember to call Dispose()

    Posted by ralfoide on 05/15/2004 11:36pm

    Very good article, thank you!
    
    For the pure .Net way, remember to call Dispose()
    on your Graphics and Bitmap instances.
    Simply letting the garbage collector remove them
    won't be enough.
    This is especially important for "temporary"
    Graphics context or Bitmaps that are created in a
    loop, otherwise you may notice your app behaving
    erratically after a while when resources get low
    (I've actually only started to notice that bug
    once I ported my game to Pocket PC using the .Net
    Compact framework.)
    
    All .Net classes mapping GDI+ inherit IDisposable
    and must be disposed properly says the
    documentation.

    Reply
  • Works like magic. Thanks!

    Posted by Legacy on 06/18/2003 12:00am

    Originally posted by: miro

    Worked fast
    not a single problem
    thanks man

    Reply
  • Paining on the desktop

    Posted by Legacy on 04/17/2003 12:00am

    Originally posted by: David Bishop

    How can I paint directly onto the windows desktop?
    
    

    I'm trying to port a Delphi/Pascal ap to C#, and I can't seem to make sence of it.


    Shouldn't this put the desktop handle into the Graphics object?

    Graphics g = Graphics.FromHdc(GetDesktopHandle());


    And, shouldn't I be able to use:

    g.DrawRectangle(new Pen(Color.Red, 3), 0, 0, 200, 100);

    to draw a red box on the screen?

    (I'm guessing a "Graphics" object is similar to the TCanvas object available in Delphi).

    What am I missing?

    Dave

    Reply
  • addition for multithreaded apps...

    Posted by Legacy on 04/01/2003 12:00am

    Originally posted by: tomw

    hi, I used this in a UserControl for a pretty high intensive gfx app (an oscillascope - yes, I know...) anyway... I was getting bizzare 'object in use elsewhere errors' until I realised that if you get a hdc using these classes you MUST place a monitor lock onto it until you're finished otherwise any old random windows-ism can frick with your device contect. FYI..

    btw, nice code otherwise!

    Reply
  • Problem bitblt'ing from an axWebBrowser control

    Posted by Legacy on 10/02/2002 12:00am

    Originally posted by: Tim Beauchamp

    I have inconsistent results getting an image from an AxWebBrowser control.
    
    

    It works as expected if it the form it is on is activated and it is not
    covered by any
    other form or control, but if I have another control over the top of it or
    if another application
    is covering it, it captures the front forms or windows. If it is half off
    screen, half of the
    image is missing.

    Ideally, what I would like to do is to capture it in a hidden window and
    show the image
    in another control that can be scaled and manipulated.

    I am perplexed.

    Below is a code sample.

    Thanks in advance for insight into this problem

    Tim Beauchamp
    Sr. Software Developer
    Iteration Software, Inc.


    /// <summary>
    /// method CaptureBrowserImage
    /// </summary>
    private Image CaptureBrowserImage()
    {
    Image image;

    Graphics grContainer = axWebBrowser.CreateGraphics();

    try
    {
    image = new Bitmap( axWebBrowser.ClientRectangle.Width,
    axWebBrowser.ClientRectangle.Height, grContainer );
    }
    catch
    {
    return null;
    }

    Graphics grImage = Graphics.FromImage(image);

    IntPtr hdcContainer = grContainer.GetHdc( );
    IntPtr hdcImage = grImage.GetHdc( );
    BitBlt( hdcImage, 0, 0, this.ClientRectangle.Width,
    this.ClientRectangle.Height,
    hdcContainer, 0, 0, 13369376);

    grContainer.ReleaseHdc( hdcContainer );
    grImage.ReleaseHdc(hdcImage);
    return image;

    }

    Reply
  • how to draw off the form (on the screen)

    Posted by Legacy on 09/27/2002 12:00am

    Originally posted by: Jason Arnold

    I am trying to emulate the way windows drags a window using only the border. I have tried all kinds of stuff, one of which actually works. (Using 4 forms declared in the class and moving them around with the mouse events) But that is slow an painful to watch the flicker. Is there any way to just draw all over the screen, and not be limited to the form?

    -Jason

    Reply
  • Faster offscreens the .NET way

    Posted by Legacy on 07/30/2002 12:00am

    Originally posted by: Angus Graham

    You can make your blit much faster by constructing the offscreen in the resolution as the screen:

    In your code just change the line

    offScreenBmp = new Bitmap(this.Width, this.Height);

    to

    offScreenBmp = new Bitmap(this.Width, this.Height, this.CreateGraphics());

    I got a 100% speed increase from doing this.

    Reply
  • Loading, Please Wait ...

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: May 7, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT This eSeminar will explore three popular games engines and how they empower developers to create exciting, graphically rich, and high-performance games for Android® on Intel® Architecture. Join us for a deep dive as experts describe the features, tools, and common challenges using Marmalade, App Game Kit, and Havok game engines, as well as a discussion of the pros and cons of each engine and how they fit into your development …

  • Instead of only managing projects organizations do need to manage value! "Doing the right things" and "doing things right" are the essential ingredients for successful software and systems delivery. Unfortunately, with distributed delivery spanning multiple disciplines, geographies and time zones, many organizations struggle with teams working in silos, broken lines of communication, lack of collaboration, inadequate traceability, and poor project visibility. This often results in organizations "doing the …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds