tejashwi.io

Technology explored

author
Tejashwi Kalp Taru
Engineer, Tinkerer, Blogger
Reading time about 5 minutes

Flicker-free text scrolling with Double buffering in Windows


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

  1. Create a memory buffer
  2. Draw the background image to the buffer
  3. Draw the scrolling text to the buffer
  4. Copy the buffer to the screen
  5. 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

comments powered by Disqus