Flicker-free text scrolling with Double buffering in Windows
If you’ve tried scrolling text over an image in a Win32 application, you’ve probably seen flickering. The text doesn’t draw cleanly over the background. This happens because Windows repaints the background and then the text separately, and your eye catches the gap between them.
Double buffering fixes this by drawing everything to a memory buffer first, then copying the completed image to the screen in one operation.
The Approach
- Create a memory buffer
- Draw the background image to the buffer
- Draw the scrolling text to the buffer
- Copy the buffer to the screen
- Free resources
First, calculate how much space the text needs:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Counts the number of lines in scroll text
int TextLen = lstrlen(Text);
int nLines = 0;
for(int i=0;i<TextLen;i++) {
if(Text[i]=='\n')
nLines++;
}
// Gets the size of total scroll texts
SIZE sizeAboutText;
HDC hDC = GetDC(hWnd);
HDC hDCMem = CreateCompatibleDC(hDC);
SelectObject(hDCMem, hFont);
GetTextExtentPoint32A(hDCMem, Text, TextLen, &sizeAboutText);
// Calculates the needed size for given scroll text
GetClientRect(hWnd, &rcClient);
rcText.bottom += (sizeAboutText.cy * (nLines+3))+rcClient.bottom;
rcText.top = rcClient.bottom;
rcText.right = rcClient.right;
rcText.left = rcClient.left;
Get the background image dimensions:
1
2
3
4
5
6
// Loads the bitmap, get its CX,CY and get compatible device context
hSkin = LoadBitmap(hIns, MAKEINTRESOURCE(IDB_BITMAP1));
BITMAP bm = {0};
GetObject( hSkin, sizeof(bm), &bm );
cx = bm.bmWidth;
cy = bm.bmHeight;
The WM_PAINT Handler
Here’s where the double buffering happens:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
case WM_PAINT:
{
PAINTSTRUCT ps;
if(BeginPaint(hWnd,&ps)) {
//Creating double buffer
HDC hdcMem = CreateCompatibleDC(ps.hdc);
int ndcmem = SaveDC(hdcMem);
HBITMAP hbmMem = CreateCompatibleBitmap(ps.hdc, cx, cy);
SelectObject(hdcMem, hbmMem);
//-------------------------------------------------------
// Copy background bitmap into double buffer
BitBlt(hdcMem, 0, 0, cx, cy, hdcBackground, 0, 0, SRCCOPY);
//---------------------------------------------------------
// Draw the text
SelectObject(hdcMem, hFont);
SetTextColor(hdcMem, CLR);
SetBkMode(hdcMem, TRANSPARENT);
DrawText(hdcMem, Text, -1, &rcText, DT_CENTER | DT_TOP | DT_NOPREFIX | DT_NOCLIP);
//-----------------------------------------------------------------------------
// Copy double buffer to screen
BitBlt(ps.hdc, 0, 0, cx, cy, hdcMem, 0, 0, SRCCOPY);
//--------------------------------------------------
// Clean up
RestoreDC(hdcMem, ndcmem);
DeleteObject(hbmMem);
DeleteDC(hdcMem);
EndPaint(hWnd, &ps);
//--------------------
} else {
MessageBox(hWnd, "Can not start painting!", "Error", MB_ICONERROR);
}
}
return TRUE;
CreateCompatibleDC creates a memory device context. CreateCompatibleBitmap creates a bitmap to draw on. After selecting it with SelectObject, the buffer is ready.
BitBlt copies the background image to the buffer. Then DrawText draws the text on top. Since both operations happen in memory, the user sees nothing yet.
The second BitBlt copies the completed buffer to the screen in one operation. No flicker.
Finally, clean up the resources. Always restore and delete device contexts you create.
You can download the complete source code here