Rendering Video with GDI (Example)

On April 20, 2011, in Code Monkey, by Tom

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

  1. Video Window Class Definition
  2. Creating and Destroying the Window
  3. The Window Thread
  4. The Message Pump
  5. 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);
}

Tagged with:  

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>