In this post, I outline a basic GDI video window class. GDI is inefficient for rendering video, but it’s also very convinient when writing simple tools on Windows. I find myself doing it often enough that it makes sense to do a brain-dump here. This is only an outline, but it should get you over the biggest hurdles.
Outline
- Video Window Class Definition
- Creating and Destroying the Window
- The Window Thread
- The Message Pump
- Rendering a Video Frame
Things to note:
- You will need a thread in your window class to “pump” window events.
- GDI does NOT do any color conversion, so you will need to convert each frame into the same format as the display (always RGB, usually 24 or 32 bits).
Video Window Class Definition
class VideoWindow
{
public:
...
private:
static DWORD _ThreadMain(void*);
static LRESULT WINAPI _MsgProc(HWND, UINT, WPARAM, LPARAM);
CRITICAL_SECTION _lok;
HANDLE _thread;
HWND _hWnd;
HDC _hDC;
HDC _hFrameDC;
HFONT _hFont;
int _bpp; // bit depth
volatile bool _exit;
int _x, _y, _w, _h; // Window dimensions
int _imgWidth, _imgHeight, _imgLength; // Frame dimensions
unsigned char *_imgBits;
};
Creating and Destroying the Window
void VideoWindow::Create(int x, int y, int w, int h)
{
EnterCriticalSection(&_lok);
_x = x;
_y = y;
_w = w;
_h = h;
_hWnd = 0;
_exit = false;
_thread = CreateThread( NULL, NULL, (LPTHREAD_START_ROUTINE)_ThreadMain,
this, NULL, 0 );
LeaveCriticalSection(&_lok);
// Wait for window to be created. Cheap, but it works.
while (!_hWnd) {
::Sleep(100);
}
}
void VideoWindow::Destroy()
{
// Safe to call from the destructor.
EnterCriticalSection(&_lok);
if(_hWnd)
{
_exit = true;
LeaveCriticalSection(&_lok);
WaitForSingleObject( _thread, INFINITE );
CloseHandle(_thread);
EnterCriticalSection(&_lok);
DestroyWindow(_hWnd);
_hWnd = 0;
}
if (_hFont) ::DeleteObject(_hFont);
if (_hFrameDC) ::DeleteDC(_hFrameDC);
if (_hDC) ::DeleteDC(_hDC);
LeaveCriticalSection(&_lok);
}
The Window Thread
DWORD VideoWindow::_ThreadMain(void* ctx)
{
VideoWindow *p = (VideoWindow*)ctx;
EnterCriticalSection(&p->_lok);
LPCSTR className = "VideoWndClass";
LPCSTR windowName = "MyWindowText";
WNDCLASSEX wcex = {
sizeof(WNDCLASSEX),
CS_OWNDC | CS_HREDRAW | CS_VREDRAW,
_MsgProc,
0L,
0L,
NULL,
NULL,
NULL,
(HBRUSH)GetStockObject(BLACK_BRUSH),
NULL,
className,
NULL
};
RegisterClassEx(&wcex);
p->_hWnd = CreateWindow(className, windowName, WS_OVERLAPPEDWINDOW,
p->_x, p->_y, p->_w, p->_h, ::GetDesktopWindow(), NULL, NULL, NULL);
p->_hDC = ::GetDC(p->_hWnd);
p->_bpp = ::GetDeviceCaps(p->_hDC, BITSPIXEL);
p->_hFrameDC = ::CreateCompatibleDC(p->_hDC);
p->_hFont = ::CreateFont(18, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE,
ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
ANTIALIASED_QUALITY, DEFAULT_PITCH | FF_DONTCARE, "Arial");
ShowWindow(p->_hWnd,SW_SHOW);
UpdateWindow(p->_hWnd);
while( !p->_exit )
{
MSG msg;
if(PeekMessage(&msg,p->_hWnd,0,0,PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
LeaveCriticalSection(&p->_lok);
Sleep(50); // A better way to do this is to use a message
EnterCriticalSection(&p->_lok);
}
LeaveCriticalSection(&p->_lok);
return 0;
}
The Message Pump
LRESULT WINAPI VideoWindow::_MsgProc(HWND hWnd, UINT msg, WPARAM w, LPARAM l)
{
if (msg == WM_PAINT && l)
{
VideoWindow *p = (VideoWindow*)l;
EnterCriticalSection(&p->_lok);
HGDIOBJ oldFont = ::SelectObject(p->_hDC, p->_hFont);
COLORREF oldFtColor = ::SetTextColor(p->_hDC, RGB(255,255,255));
COLORREF oldBkColor = ::SetBkColor(p->_hDC, RGB(64,64,64));
// Render image
//
// NOTE: This assumes you've already coverted the image to the proper
// RGB bit-depth (_bpp) and copied the data into _imageBits.
//
HBITMAP hBMP = ::CreateCompatibleBitmap(p->_hDC, p->_imgWidth,
p->_imgHeight);
::SetBitmapBits(hBMP, p->_imgLength, p->_imgBits);
HGDIOBJ oldBMP = ::SelectObject(p->_hFrameDC, hBMP);
::BitBlt(p->_hDC, 0, 0, p->_imgWidth, p->_imgHeight, p->_hFrameDC,
0, 0, SRCCOPY);
::SelectObject(p->_hFrameDC, oldBMP);
// Draw some text over the image
RECT rect;
::SetRectEmpty(&rect);
::DrawText(p->_hDC, p->_imageText, -1, &rect, DT_CALCRECT);
::DrawText(p->_hDC, p->_imageText, -1, &rect, DT_CENTER | DT_VCENTER);
if (hBMP) ::DeleteObject(hBMP);
LeaveCriticalSection(&p->_lok);
}
return DefWindowProc(hWnd, msg, w, l);
}
Rendering a Video Frame
void VideoWindow::Render(const unsigned char* frame, int w, int h, int stride)
{
// Convert frame to the proper RGB bit depth to match _bpp and copy it
// into _imgBits buffer. Update _imgWidth, _imgHeight, and _imgLength as
// necessary.
::PostMessage(_hWnd, WM_PAINT, 0, (LPARAM)this);
}

