Using the Windows Vista/Windows 7 Built-In Buffered Animation API

This article is an extension to my “Using Windows Vista Built-In Double Buffering” article. That article explained how to use the built-in support for buffered painting in Windows Vista and later. A related API is called the Buffered Animation API. This API makes it easy to make animations without flickering(*). This article will explain how to use this Buffered Animation API.

This article comes with an example application to illustrate the Windows Vista buffered animation technique. The example will draw a new colored random rectangle in the window each time you press the spacebar. The new rectangle will smoothly fade onto the window. The buffered animation is used for this fading effect. Because you will be working with random colored rectangles, the following structure is introduced:

// Store a rectangle and its color
struct structARectangle
{
   RECT rc;
   COLORREF clr;
};

Two vectors are created to store rectangles:

// Stores our rectangles currently on the screen
vector<structARectangle> g_vecRectanglesOld;
// Stores new set of rectangles to which we want to animate
vector<structARectangle> g_vecRectanglesNew;

The OLD vector contains all the rectangles that are currently visible on the screen. The NEW vector contains all the current rectangles plus a new rectangle that has been added by pressing the spacebar. The purpose is to fade this new rectangle onto the screen. When you press the spacebar, you add a new random rectangle as follows:

case WM_KEYUP:
   if (wParam == VK_SPACE)
   {
      RECT rcClient;
      GetClientRect(hWnd, &rcClient);
      structARectangle s;
      // Create random color
      s.clr = RGB(GETRANDOM(0,255),GETRANDOM(0,255),
         GETRANDOM(0,255));
      // Create random rectangle
      s.rc.left = GETRANDOM(0, rcClient.right);
      s.rc.right = GETRANDOM(0, rcClient.right);
      s.rc.top = GETRANDOM(0, rcClient.bottom);
      s.rc.bottom = GETRANDOM(0, rcClient.bottom);
      // Store rectangle in NEW vector
      g_vecRectanglesNew.push_back(s);
      // Trigger a redraw of the window
      InvalidateRect(hWnd, NULL, TRUE);
   }
   break;

I created the following rendering function to render rectangles to the screen. This function will be used in your WM_PAINT handler.

// Render the given vector of rectangles to the given device
// context
void RenderWindow(HDC hdc, RECT& rcClient,
   vector<structARectangle>& vec)
{
   // Fill the entire background with a color
   HBRUSH hBrushWhite = (HBRUSH)GetStockObject(WHITE_BRUSH);
   FillRect(hdc, &rcClient, hBrushWhite);

   // Render all rectangles in the give vector
   for (vector<structARectangle>::const_iterator citer =
      vec.begin();
      citer != vec.end(); ++citer)
   {
      HBRUSH hBrush = CreateSolidBrush((*citer).clr);
      FillRect(hdc, &(*citer).rc, hBrush);
      DeleteObject(hBrush);
   }

   DrawText(hdc, L"Press the spacebar to add a colored rectangle",
      -1, &rcClient, DT_TOP | DT_LEFT);
}

There is not a single animation line in the above rendering function. It gets a device context and a vector of rectangles. All the rectangles in the vector are drawn to the given device context. That’s it.

Before you can use any buffered animation, you need to initialize the new API. The first thing you need to do is to include the uxtheme header file and link with the uxtheme library. This can be done as follows:

#include <uxtheme.h>
#pragma comment(lib, "uxtheme.lib")

Before you use any buffered animation function, it is highly recommended that you call BufferedPaintInit. BufferedPaintInit should be called once in every thread that wants to use buffered animation. It will set up some internal buffers that will be reused with every buffered animation operation. Every call to BufferedPaintInit should be matched with a call to BufferedPaintUnInit. When you do not call BufferedPaintInit, it might be that buffered animation will still work but you will have degraded performance because some internal data structures will be created and destroyed every time you use buffered animation. A good idea is to call BufferedPaintInit in your WM_CREATE handler and to call BufferedPaintUnInit in your WM_NCDESTROY handler, as can be seen below.

This is done in the following message handlers:

case WM_CREATE:
   // Initialize buffered animation
   BufferedPaintInit();
   break;
case WM_NCDESTROY:
   // Stop all animation and uninitialize the API
   BufferedPaintStopAllAnimations(hWnd);
   BufferedPaintUnInit();
   break;
case WM_SIZE:
   // Stop all animation
   BufferedPaintStopAllAnimations(hWnd);
   break;

Now, you are finally ready to write your WM_PAINT handler to use the buffered animation API. The handler looks as follows:

case WM_PAINT:
   {
      RECT rcClient;
      GetClientRect(hWnd, &rcClient);
      hdc = BeginPaint(hWnd, &ps);

      // See if this paint was generated by a soft-fade
      // animation
      // by the buffered animation API itself.
      if (!BufferedPaintRenderAnimation(hWnd, hdc))
      {
         BP_ANIMATIONPARAMS animParams = {0};
         animParams.cbSize = sizeof(animParams);
         animParams.style = BPAS_LINEAR;

         // Check if animation is needed.
         // If not, set dwDuration to 0.
         animParams.dwDuration =
            (g_vecRectanglesNew.size() !=
               g_vecRectanglesOld.size() ? 2000 : 0);

         // Start the buffered animation.
         HDC hdcFrom, hdcTo;
         HANIMATIONBUFFER hbpAnimation =
            BeginBufferedAnimation(hWnd, hdc, &rcClient,
            BPBF_COMPATIBLEBITMAP, NULL, &animParams,
               &hdcFrom, &hdcTo);
         if (hbpAnimation)
         {
            if (hdcFrom)
            {
               // Render all the OLD rectangles.
               RenderWindow(hdcFrom, rcClient, g_vecRectanglesOld);
            }
            if (hdcTo)
            {
               // Render all the NEW rectangles.
               RenderWindow(hdcTo, rcClient, g_vecRectanglesNew);
            }

            g_vecRectanglesOld = g_vecRectanglesNew;
            EndBufferedAnimation(hbpAnimation, TRUE);
         }
         else
         {
            g_vecRectanglesOld = g_vecRectanglesNew;
            RenderWindow(hdc, rcClient, g_vecRectanglesOld);
         }
      }

      EndPaint(hWnd, &ps);
   }
   break;

BeginBufferedAnimation works by generating multiple WM_PAINT messages to draw intermediate frames based on a timer. In response to each WM_PAINT message, you first call BufferedPaintRenderAnimation. BufferedPaintRenderAnimation will return TRUE when it painted an intermediate frame, in which case you don’t have to render anything anymore. In case BufferedPaintRenderAnimation returned FALSE, you have to draw something. First, you check whether the OLD and the NEW vector of rectangles are equal. If they are equal, you don’t need any animation and thus set the dwDuration to 0, telling BeginBufferedAnimation that you don’t need any animation. After that, you call BeginBufferedAnimation, which will set up two device contexts to which you draw the correct vector of rectangles.

With the call to BeginBufferedAnimation, you start your buffered animation. The fourth parameter to this function defines the type of off-screen buffer that will be created. The following options are available (from MSDN):

  • BPBF_COMPATIBLEBITMAP: Compatible bitmap. The number of bits per pixel is based on the color format of the device associated with the HDC specified with BeginBufferedPaint—typically, this is the display device.
  • BPBF_DIB: Bottom-up device-independent bitmap. The origin of the bitmap is the lower-left corner. Uses 32 bits per pixel.
  • BPBF_TOPDOWNDIB:Top-down device-independent bitmap. The origin of the bitmap is the upper-left corner. Uses 32 bits per pixel.
  • BPBF_TOPDOWNMONODIB: Top-down, monochrome, device-independent bitmap. Uses 1 bit per pixel.

Setting this to, for example, BPBF_DIB will ensure you always have a 32 bit off-screen buffer, so you can always draw in 32 bit regardless of your current monitor settings.

The fifth parameter to BeginBufferedAnimation is a pointer to a BP_PAINTPARAMS structure. This can be NULL if you don’t need it. Because the buffered animation technique will keep off-screen buffers alive and will reuse them in future buffered painting calls, it is important to remember that the contents of the off-screen buffer is not erased by default. So, either you fill the off-screen buffer yourself with a certain color to erase the previous drawing (as I did in my example above), or you can use the BP_PAINTPARAMS structure to force BeginBufferedAnimation to clear the off-screen buffer with ARGB = {0,0,0,0}. You also can change clipping behavior on the off-screen buffer and specify blending parameters. The blending parameters are used to control blending operations when EndBufferedAnimation copies the contents of the off-screen buffer to the destination.

To prevent flickering, it is also important to handle WM_ERASEBKGND as follows:

case WM_ERASEBKGND:
   return 1;

The example that comes with this article shows the buffered animation in action. Run the application and press the spacebar to add a new colored random rectangle on the screen. The new rectangle will fade onto the screen. Because buffered animation is only available since Windows Vista, the example application will not run on Windows XP or lower.

(*) Note: While writing this article, I found an issue with the DWM in Windows Vista. After discussing with someone from Microsoft, it seems the buffered animation API is relying on some buffering from the DWM; however, on Windows Vista this is causing some flickering. They also told me that the buffered animation was not designed for big or full-screen animations but rather for small animations like fading buttons. To reduce the flickering as much as possible, only use the buffered animation on small rectangles and use a short animation interval, for example 500 milliseconds, which is more than enough for GUI related animations anyway.

The good news is that I tested the application on Windows 7 Beta and it works without any flickering. So, it seems that the DWM issue has been fixed on Windows 7.

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read