Overclock.net › Forums › Software, Programming and Coding › Coding and Programming › Application Programming › Hey, I want animations too! - A quick tutorial on buffered paint animation.
New Posts  All Forums:Forum Nav:

Hey, I want animations too! - A quick tutorial on buffered paint animation.

post #1 of 3
Thread Starter 
I am not really seeing this being used much out in the wild, and so I wanted to write about this thing that was introduced into visual styles (uxtheme) in Windows Vista - the buffered paint engine.

Essentially the buffered paint engine provides some really easy ways to get double buffering happening in your normal WM_PAINT (Win32), OnPaint (MFC, .NET) type painting model. But this thread is about a specific component of the buffered paint engine - the animations. The buffered paint engine was basically just a foundation for the quick and dirty animation that you can do to give your application that extra finishing touch. Most developers are happy with a simple instant switch to an on/off state (say for example, hover or normal states on a button), but there are times where the extra polish just makes things that bit more smoother and provides a better UX! The animations simply provide linear fade transitions. If you were looking for more advanced animation, this probably isn't what you want, but instead the new Animation Manager introduced with Windows 7.

Let's jump in shall we? OK, we'll be dealing with some new APIs that are part of the buffered paint engine, these are:

BufferedPaintInit
BufferedPaintUnInit
BeginBufferedAnimation
EndBufferedAnimation
BufferedPaintRenderAnimation
BufferedPaintStopAllAnimations

So what do we need (other than these APIs to get started)? Well for this example, all we will need is a window! For the sake of simplicity we won't actually use bitmaps to do the painting, we'll just draw a letter to the screen every time the user presses space.
I'll assume you've already set up your application with a window and window procedure. You've also got a message loop going on the thread, haven't you? I'll show the example in pure C, but a bit of P/Invoke and it is easily usable in .NET too!

Setting it all up
First we'll need to include a couple of headers from the Win32 API:
Code:
#include <windowsx.h>
#include <uxtheme.h>

We'll need to know the state of the current drawing, so we'll add two variables to our code:
Code:
TCHAR g_currentState = L'0';
TCHAR g_drawnState;

These will allow us to know what to paint when we come around to doing the animation (which you will see a bit further on...).

We'll need some forward declarations, too:
Code:
BOOL OnCreate( HWND hwnd, LPCREATESTRUCT wParam );
void OnDestroy( HWND hwnd );
void OnChar( HWND hwnd, TCHAR ch, int cRepeats );
void OnPaint( HWND hwnd );
void PaintContent( HWND hwnd, HDC hdc, LPRECT rc, TCHAR state );

We'll need to hook up a notification when the user presses the spacebar so we can animate the change in state:
Code:
// Add to code
void OnChar( HWND hwnd, TCHAR ch, int cRepeats )
{
        switch ( ch )
        {
        case ' ':
                g_currentState = g_drawnState;
                g_currentState++;

                if ( g_currentState > L'~' )
                        g_currentState = L'!';

                InvalidateRect(hwnd, NULL, TRUE ); /* InvalidateRect gets our window to repaint (i.e. our OnPaint function gets called right away) */
                break;
        }
}

We'll also need to modify our window procedure with our message handlers:
Code:
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
        switch (message)
        {
                HANDLE_MSG(hWnd, WM_CREATE, OnCreate);
                HANDLE_MSG(hWnd, WM_PAINT, OnPaint);
                HANDLE_MSG(hWnd, WM_CHAR, OnChar);
                HANDLE_MSG(hWnd, WM_DESTROY, OnDestroy);

        default:
                return DefWindowProc(hWnd, message, wParam, lParam);
        }

        return 0;
}

BufferedPaintInit & BufferedPaintUnInit

These two functions are optional, however they are recommended to be used. For each thread, the buffered paint engine will maintain a cache of bitmaps that it can re-use each time you need to paint your window. This means that it doesn't need to reach down into GDI every time to allocate a bitmap to render to. By calling BufferedPaintInit in your WM_CREATE and BufferedPaintUnInit in your WM_DESTROY, you tell the engine that someone is actively using the buffered paint APIs on the thread and to keep the cache alive. When the last call to BufferedPaintUnInit occurs, the engine throws the cache away.
Code:
// Add to code
BOOL OnCreate( HWND hwnd, LPCREATESTRUCT wParam )
{
        BufferedPaintInit();
        return TRUE;
}

void OnDestroy( HWND hwnd )
{
        BufferedPaintUnInit();
        BufferedPaintStopAllAnimations(hwnd); /* Stops any animations that are in progress so that the buffered paint engine isn't trying to animate a window that isn't there! */
        PostQuitMessage(0);
}

BufferedPaintRenderAnimation, BeginBufferedAnimation & EndBufferedAnimation

Now that we've got most of the boring stuff taken care off, we can finally dive into our OnPaint to do the buffered painting + animations:
Code:
void OnPaint( HWND hwnd )
{
        PAINTSTRUCT ps;
        BeginPaint(hwnd, &ps);

        /* 
         * BufferedPaintRenderAnimation will return TRUE if this paint call is a result of WM_PAINT fired by the engine. The animation works by the buffered paint engine
         * sending WM_PAINTS into your window. What we do here is detect if we are inside one of those WM_PAINTs. If we are, then we skip our code and say
         * we're done here - somebody else did the painting for me! If not, then we continue to pain our content to render the animation.
         */
        if ( !BufferedPaintRenderAnimation( hwnd, ps.hdc))
        {
                RECT rc;
                BP_ANIMATIONPARAMS params = {0};
                GetClientRect( hwnd, &rc );

                params.cbSize     = sizeof(params);
                params.style      = BPAS_LINEAR; /* Linear transition */
                params.dwDuration = g_currentState == g_drawnState ? 0 : 1000; /* If the state hasn't changed, then set duration to zero. The engine doesn't perform any animation if this is zero */

                HDC hdcFrom;
                HDC hdcTo;

                /*
                 * BeginBufferedAnimation will give us a handle to two device contexts: A DC to render the previous state and a DC to render the current state.
                 * These are essentially key frames. hdcFrom is the DC to paint the initial state of the animation. hdcTo is the DC to paint the final state of the animation.
                 *
                 * hdcFrom will be NULL on the first call, only the final state is to be painted. 
                 */
                HANIMATIONBUFFER hBuffer = BeginBufferedAnimation( hwnd, ps.hdc, &rc, BPBF_COMPATIBLEBITMAP, NULL, &params, &hdcFrom, &hdcTo );

                if ( hBuffer )
                {
                        if ( hdcFrom )
                                PaintContent( hwnd, hdcFrom, &rc, g_drawnState );

                        if ( hdcTo )
                                PaintContent( hwnd, hdcTo, &rc, g_currentState );

                        /* Here we update the drawn state to the current state*/
                        g_drawnState = g_currentState;
                        EndBufferedAnimation( hBuffer, TRUE );
                }
        }

        EndPaint(hwnd, &ps);
}

We also have a helper function that does the actual painting. This function simply draws the current state (character) 10 times down a vertical line.
Code:
void PaintContent( HWND hwnd, HDC hdc, LPRECT rc, TCHAR state )
{
        COLORREF prevText = SetTextColor( hdc, GetSysColor( COLOR_WINDOWTEXT ) );
        COLORREF prevBack = SetBkColor( hdc, GetSysColor( COLOR_WINDOW ) );
        HFONT prevFont    = SelectFont( hdc, GetStockFont( SYSTEM_FONT ) );

        RECT rcClient;
        GetClientRect( hwnd, &rcClient );
        FillRect( hdc, &rcClient, GetSysColorBrush( COLOR_WINDOW ) ); /* Fill the background */

        /* A little bit of tweaking here */
        int cyRow = ( rcClient.bottom - rcClient.top ) / 22 * 2;
        if ( cyRow < 2 )
                cyRow = 2;

        int i;
        for ( i = 0; i < 10; ++i )
        {
                int offset = ( ( state * 2 ) % cyRow );
                ExtTextOutW(hdc, 20, cyRow * i + offset, 0, NULL, &state, 1, NULL );
        }

        SelectFont( hdc, prevFont );
        SetBkColor( hdc, prevBack );
        SetTextColor( hdc, prevText );
}

Finishing up
In order to use this code sample, start with a Win32 application project in Visual Studio and make the following change to the window class registration:
Code:
wcex.hbrBackground   = NULL;

This stops flickering - because we are double buffering we do not need to waste time erasing the background every WM_PAINT call, so we set the background brush to the NULL brush, or you can also handle WM_ERASEBKGND and return TRUE.
Also, remember to add uxtheme.lib to the linker.

After that is done, you should be good to go!
Edited by tompsonn - 8/8/12 at 9:26am
My System
(30 items)
 
"Zeus"
(13 items)
 
 
CPUMotherboardGraphicsRAM
Intel Core i5 2500K (4.5ghz @ 1.320v) Gigabyte Z68X-UD3R-B3 MSI R7970 Lightning Corsair 16GB (4x4GB) 
Hard DriveHard DriveHard DriveHard Drive
Plextor PX-256M5S 256GB Crucial M4 128GB Hitachi HDS721010CLA332 Hitachi HDS723020BLA642 
Hard DriveHard DriveHard DriveOptical Drive
Hitachi HDS723020BLA642 Hitachi HUA722010CLA330 WDC WD10EARS-00Z5B1 TSSTcorp CDDVDW SH-S223B 
CoolingCoolingOSMonitor
Phanteks PH-TC14PE with TY-140's Lamptron FCv5 (x2) Windows 7 Ultimate 64-bit Dell U2412M 
MonitorMonitorMonitorKeyboard
Dell U2412M Dell U2212HM Dell U2212HM Ducky DK9087 G2 Pro 
PowerCaseMouseMouse Pad
Corsair AX-750 Corsair Obsidian 650D Microsoft IntelliMouse Optical  XTRAC Ripper XXL 
AudioAudioAudioAudio
Westone W3 IEMs RE-272 IEMs Shure SE-215 IEMs Schiit Bifrost DAC 
AudioAudio
Schiit Asgard 2 amp HiVi Swan M50W 2.1 
CPUMotherboardGraphicsRAM
Intel Core i7 950 GA-X58-UD3R Radeon HD 5450  24GB Corsair @ 1333mhz 
Hard DriveOSPowerCase
4x WD Cavair Red 1TB in RAID 0 Windows Server 2008 R2 x64 Corsair HX-520 LianLi LanCool 
  hide details  
Reply
My System
(30 items)
 
"Zeus"
(13 items)
 
 
CPUMotherboardGraphicsRAM
Intel Core i5 2500K (4.5ghz @ 1.320v) Gigabyte Z68X-UD3R-B3 MSI R7970 Lightning Corsair 16GB (4x4GB) 
Hard DriveHard DriveHard DriveHard Drive
Plextor PX-256M5S 256GB Crucial M4 128GB Hitachi HDS721010CLA332 Hitachi HDS723020BLA642 
Hard DriveHard DriveHard DriveOptical Drive
Hitachi HDS723020BLA642 Hitachi HUA722010CLA330 WDC WD10EARS-00Z5B1 TSSTcorp CDDVDW SH-S223B 
CoolingCoolingOSMonitor
Phanteks PH-TC14PE with TY-140's Lamptron FCv5 (x2) Windows 7 Ultimate 64-bit Dell U2412M 
MonitorMonitorMonitorKeyboard
Dell U2412M Dell U2212HM Dell U2212HM Ducky DK9087 G2 Pro 
PowerCaseMouseMouse Pad
Corsair AX-750 Corsair Obsidian 650D Microsoft IntelliMouse Optical  XTRAC Ripper XXL 
AudioAudioAudioAudio
Westone W3 IEMs RE-272 IEMs Shure SE-215 IEMs Schiit Bifrost DAC 
AudioAudio
Schiit Asgard 2 amp HiVi Swan M50W 2.1 
CPUMotherboardGraphicsRAM
Intel Core i7 950 GA-X58-UD3R Radeon HD 5450  24GB Corsair @ 1333mhz 
Hard DriveOSPowerCase
4x WD Cavair Red 1TB in RAID 0 Windows Server 2008 R2 x64 Corsair HX-520 LianLi LanCool 
  hide details  
Reply
post #2 of 3
subbed for later read:)
R.E.D (dead)
(5 items)
 
Wife's D3 machine
(13 items)
 
 
CPUGraphicsRAMHard Drive
Core 2 Duo P8400 Nvidia 9600M GT 2x2Gb Hynx DDR2 WD Scorpio Blue 
OS
Windows 7 x64 Professional 
CPUMotherboardGraphicsRAM
Intel Pentium G840 DualCore SB 2.8Ghz AsRock H61M-VS Inno3D 8800 GTS 320 Kingston HyperX 1600 4Gb 
Hard DriveOptical DriveCoolingOS
WD Scorpio Blue 250Gb LiteOn DVD-RW stock Win 7 Home Premium 
MonitorKeyboardPowerCase
Acer 19" 1440x900 Logitech MX3200 beQuiet 300W MS-Tech CA-0130 Black 
Mouse
Logitech MX300 Laser 
  hide details  
Reply
R.E.D (dead)
(5 items)
 
Wife's D3 machine
(13 items)
 
 
CPUGraphicsRAMHard Drive
Core 2 Duo P8400 Nvidia 9600M GT 2x2Gb Hynx DDR2 WD Scorpio Blue 
OS
Windows 7 x64 Professional 
CPUMotherboardGraphicsRAM
Intel Pentium G840 DualCore SB 2.8Ghz AsRock H61M-VS Inno3D 8800 GTS 320 Kingston HyperX 1600 4Gb 
Hard DriveOptical DriveCoolingOS
WD Scorpio Blue 250Gb LiteOn DVD-RW stock Win 7 Home Premium 
MonitorKeyboardPowerCase
Acer 19" 1440x900 Logitech MX3200 beQuiet 300W MS-Tech CA-0130 Black 
Mouse
Logitech MX300 Laser 
  hide details  
Reply
post #3 of 3
Thread Starter 
Quote:
Originally Posted by ronnin426850 View Post

subbed for later read:)

It is pretty much done now - have added comments and things like that.
If anything needs explaining more, do tell smile.gif
My System
(30 items)
 
"Zeus"
(13 items)
 
 
CPUMotherboardGraphicsRAM
Intel Core i5 2500K (4.5ghz @ 1.320v) Gigabyte Z68X-UD3R-B3 MSI R7970 Lightning Corsair 16GB (4x4GB) 
Hard DriveHard DriveHard DriveHard Drive
Plextor PX-256M5S 256GB Crucial M4 128GB Hitachi HDS721010CLA332 Hitachi HDS723020BLA642 
Hard DriveHard DriveHard DriveOptical Drive
Hitachi HDS723020BLA642 Hitachi HUA722010CLA330 WDC WD10EARS-00Z5B1 TSSTcorp CDDVDW SH-S223B 
CoolingCoolingOSMonitor
Phanteks PH-TC14PE with TY-140's Lamptron FCv5 (x2) Windows 7 Ultimate 64-bit Dell U2412M 
MonitorMonitorMonitorKeyboard
Dell U2412M Dell U2212HM Dell U2212HM Ducky DK9087 G2 Pro 
PowerCaseMouseMouse Pad
Corsair AX-750 Corsair Obsidian 650D Microsoft IntelliMouse Optical  XTRAC Ripper XXL 
AudioAudioAudioAudio
Westone W3 IEMs RE-272 IEMs Shure SE-215 IEMs Schiit Bifrost DAC 
AudioAudio
Schiit Asgard 2 amp HiVi Swan M50W 2.1 
CPUMotherboardGraphicsRAM
Intel Core i7 950 GA-X58-UD3R Radeon HD 5450  24GB Corsair @ 1333mhz 
Hard DriveOSPowerCase
4x WD Cavair Red 1TB in RAID 0 Windows Server 2008 R2 x64 Corsair HX-520 LianLi LanCool 
  hide details  
Reply
My System
(30 items)
 
"Zeus"
(13 items)
 
 
CPUMotherboardGraphicsRAM
Intel Core i5 2500K (4.5ghz @ 1.320v) Gigabyte Z68X-UD3R-B3 MSI R7970 Lightning Corsair 16GB (4x4GB) 
Hard DriveHard DriveHard DriveHard Drive
Plextor PX-256M5S 256GB Crucial M4 128GB Hitachi HDS721010CLA332 Hitachi HDS723020BLA642 
Hard DriveHard DriveHard DriveOptical Drive
Hitachi HDS723020BLA642 Hitachi HUA722010CLA330 WDC WD10EARS-00Z5B1 TSSTcorp CDDVDW SH-S223B 
CoolingCoolingOSMonitor
Phanteks PH-TC14PE with TY-140's Lamptron FCv5 (x2) Windows 7 Ultimate 64-bit Dell U2412M 
MonitorMonitorMonitorKeyboard
Dell U2412M Dell U2212HM Dell U2212HM Ducky DK9087 G2 Pro 
PowerCaseMouseMouse Pad
Corsair AX-750 Corsair Obsidian 650D Microsoft IntelliMouse Optical  XTRAC Ripper XXL 
AudioAudioAudioAudio
Westone W3 IEMs RE-272 IEMs Shure SE-215 IEMs Schiit Bifrost DAC 
AudioAudio
Schiit Asgard 2 amp HiVi Swan M50W 2.1 
CPUMotherboardGraphicsRAM
Intel Core i7 950 GA-X58-UD3R Radeon HD 5450  24GB Corsair @ 1333mhz 
Hard DriveOSPowerCase
4x WD Cavair Red 1TB in RAID 0 Windows Server 2008 R2 x64 Corsair HX-520 LianLi LanCool 
  hide details  
Reply
New Posts  All Forums:Forum Nav:
  Return Home
  Back to Forum: Application Programming
Overclock.net › Forums › Software, Programming and Coding › Coding and Programming › Application Programming › Hey, I want animations too! - A quick tutorial on buffered paint animation.