Capturar pantalla Windows API

En este tutorial usaremos la Windows API para desarrollar una aplicación C/C++ que nos permitirá hacer una captura de pantalla, esta captura la almacenaremos en disco utilizando en formato de archivo de imagen BMP.

Primero creamos un HBITMAP que almacenará la imagen en memoria, este debe ser compatible con el DC de la pantalla, la función CreateDC(“DISPLAY”, NULL, NULL, NULL); obtiene el contexto de dispositivo de la pantalla, usamos la función CreateCompatibleBitmap(hdc, w, h); para crear un bitmap compatible.

Transferir pixeles de la pantalla al bitmap con la función BitBlt().

BOOL BitBlt(HDC hdcDest, int nXDest, int nYDest, int nWidth, int nHeight, HDC hdcSrc, int nXSrc, int nYSrc, DWORD dwRop);

Esta función realiza una transferencia de un bloque de datos de colores correspondiente a un rectángulo de pixeles desde el contexto de dispositivo de origen especificado al contexto de dispositivo destino.

El DC de destino lo llamamos hdcCompatible y lo asociamos al HBITMAP por lo que al transferir los pixeles estos se almacenarán en la memoria del bitmap, por ultimo usaremos la función GetBitmapBits() para obtener el puntero a los pixeles previamente mencionados.

VOID OnScreenshot(HWND hWnd)
{
    HDC hdcScreen = CreateDC(L"DISPLAY", NULL, NULL, NULL);
    HDC hdcCompatible = CreateCompatibleDC(hdcScreen);

    HBITMAP hbmScreen = CreateCompatibleBitmap(hdcScreen,
                        GetDeviceCaps(hdcScreen, HORZRES),
                        GetDeviceCaps(hdcScreen, VERTRES));

    SelectObject(hdcCompatible, hbmScreen);

    BITMAP bmp;
    GetObject(hbmScreen, sizeof(BITMAP), &bmp);

    UINT size_data = bmp.bmWidthBytes * bmp.bmHeight;
    BYTE* data = new BYTE[size_data];

    BitBlt(hdcCompatible, 0, 0, bmp.bmWidth, bmp.bmHeight, hdcScreen, 0, 0, SRCCOPY);

    GetBitmapBits(hbmScreen, size_data, data);

    SaveImage(data, bmp);

    delete[] data;
}

Para almacenar la imagen en disco usaremos WIC (Windows Imaging Component), el formato a utilizar será BMP por simplicidad, aunque WIC soporta otros, como: JPEG, PNG, TIFF, etc., una vez creados e inicializado todos los componentes necesarios almacenaremos los pixeles obtenidos previamente del siguiente modo:

hr = piBitmapFrame->WritePixels(bmp.bmHeight, bmp.bmWidthBytes, bmp.bmWidthBytes * bmp.bmHeight, data);

Antes de utilizar WIC debemos inicializar COM, CoInitialize(NULL);

HRESULT SaveImage(BYTE* data, BITMAP bmp)
{
    IWICImagingFactory* piFactory = NULL;
    IWICBitmapEncoder *piEncoder = NULL;
    IWICBitmapFrameEncode *piBitmapFrame = NULL;
    IWICStream *piStream = NULL;
    IPropertyBag2 *pPropertybag = NULL;

    static unsigned int frame_count = 0;

    HRESULT hr = CoCreateInstance(
        CLSID_WICImagingFactory,
        NULL,
        CLSCTX_INPROC_SERVER,
        IID_IWICImagingFactory,
        reinterpret_cast<void **>(&piFactory));

    if (SUCCEEDED(hr))
    {
        hr = piFactory->CreateStream(&piStream);
    }

    if (SUCCEEDED(hr))
    {
        wchar_t txt[128];
        wsprintf(txt, L"images/frame_%d.bmp", frame_count++);
        hr = piStream->InitializeFromFilename(txt, GENERIC_WRITE);
    }

    if (SUCCEEDED(hr))
    {
        hr = piFactory->CreateEncoder(GUID_ContainerFormatBmp, NULL, &piEncoder);
    }

    if (SUCCEEDED(hr))
    {
        hr = piEncoder->Initialize(piStream, WICBitmapEncoderNoCache);
    }

    if (SUCCEEDED(hr))
    {
        hr = piEncoder->CreateNewFrame(&piBitmapFrame, &pPropertybag);
    }

    if (SUCCEEDED(hr))
    {
        hr = piBitmapFrame->Initialize(pPropertybag);
    }

    if (SUCCEEDED(hr))
    {
        hr = piBitmapFrame->SetSize(bmp.bmWidth, bmp.bmHeight);
    }

    if (SUCCEEDED(hr))
    {
        WICPixelFormatGUID formatGUID = GUID_WICPixelFormat32bppBGR;
        hr = piBitmapFrame->SetPixelFormat(&formatGUID);
    }

    if (SUCCEEDED(hr))
    {
        hr = piBitmapFrame->WritePixels(bmp.bmHeight, bmp.bmWidthBytes, bmp.bmWidthBytes * bmp.bmHeight, data);
    }

    if (SUCCEEDED(hr))
    {
        hr = piBitmapFrame->Commit();
    }

    if (SUCCEEDED(hr))
    {
        hr = piEncoder->Commit();
    }

    if (piBitmapFrame)
        piBitmapFrame->Release();

    if (piEncoder)
        piEncoder->Release();

    if (piStream)
        piStream->Release();

    if (piFactory)
        piFactory->Release();

    return hr;
}

La GUI de esta aplicación consta de tres botones, el primero permite hacer una captura de pantalla y la almacena en disco en formato bmp, el segundo inicia una captura sucesiva de imágenes que luego podemos convertir en video, el último botón detiene la captura. 

win32 botones

El botón Iniciar Captura inicia un temporizador, este llama a la función de captura de pantalla cada cierto tiempo, usamos setTimer() para crear e iniciar el temporizador y KillTimer() para detenerlo y destruirlo.

case WM_COMMAND:
    switch (LOWORD(wParam))
    {
    case BTN_IMAGECAPTURA:
        OnScreenshot(hWnd);
        break;
    case BTN_START_VIDEOCAPTURA:
        SetTimer(hWnd, SCREENSHOT_TIMER, 1000 / 30, (TIMERPROC)NULL);
        break;
    case BTN_STOP_VIDEOCAPTURA:
        KillTimer(hWnd, SCREENSHOT_TIMER);
        break;
    }
    return 0;

Cada vez que ha transcurrido el intervalo de tiempo definido, 1000 / 30 ms,  se produce el mensaje WM_TIMER, obtenemos el identificador, si es SCREENSHOT_TIMER capturamos la pantalla. 

case WM_TIMER:
    switch (wParam)
    {
    case SCREENSHOT_TIMER:
        OnScreenshot(hWnd);
        break;
    }
return 0;

Proyecto en GitHub: Captura de pantalla C/C++

Comentarios

Entradas populares de este blog

Conectar SQL Server con Java

Entrenar OpenCV en Detección de Objetos

Procesamiento de imágenes en OpenCV

Acceso a la webcam con OpenCV

Conociendo la clase cv::Mat de OpenCV