Wo997
Wo997

Reputation: 103

C++ screen capture - how to read the bitmap?

I wanted the program (written in c++) to read the pixels from the screen, but the response I get seems to be messy, the variable start indicates the position of the mouse. What is the correct way to do it? I used the GetPixel() which worked fine, but I need a whole bitmap for the efficiency. Here is the code:

#include <Windows.h>
#include <iostream>
#include <math.h>
#include <stdio.h>
using namespace std;

int nScreenWidth;
int nScreenHeight;

HBITMAP GetScreenBmp(HDC hdc) {
    HDC hCaptureDC  = CreateCompatibleDC(hdc);
    HBITMAP hBitmap = CreateCompatibleBitmap(hdc, nScreenWidth, nScreenHeight);
    HGDIOBJ hOld = SelectObject(hCaptureDC, hBitmap);
    BOOL bOK = BitBlt(hCaptureDC,0,0,nScreenWidth, nScreenHeight, hdc,0,0,SRCCOPY|CAPTUREBLT);

    SelectObject(hCaptureDC, hOld); // always select the previously selected object once done
    DeleteDC(hCaptureDC);
    return hBitmap;
}

int main() {
    nScreenWidth = GetSystemMetrics(SM_CXSCREEN);
    nScreenHeight = GetSystemMetrics(SM_CYSCREEN);
    HBITMAP hBitmap;

    int times = 0;
    while (!GetAsyncKeyState(VK_SPACE) && times<1000)
    {
        times++;

        POINT p;
        GetCursorPos(&p);

        HDC hdc = GetDC(0);

        hBitmap = GetScreenBmp(hdc);

        BITMAPINFO MyBMInfo = {0};
        MyBMInfo.bmiHeader.biSize = sizeof(MyBMInfo.bmiHeader);

        if(0 == GetDIBits(hdc, hBitmap, 0, 0, NULL, &MyBMInfo, DIB_RGB_COLORS)) {
            cout << "error" << endl;
        }

        BYTE* lpPixels = new BYTE[MyBMInfo.bmiHeader.biSizeImage];

        MyBMInfo.bmiHeader.biCompression = BI_RGB;

        if(0 == GetDIBits(hdc, hBitmap, 0, MyBMInfo.bmiHeader.biHeight, (LPVOID)lpPixels, &MyBMInfo, DIB_RGB_COLORS)) {
            cout << "error2" << endl;
        }
        //**HERE** - position is wrong?
        int start = (p.y*nScreenWidth+p.x)*4;
        for(int i = start; i < start + 4; i+=4)
        {
            cout << "R:" << (int)lpPixels[i+2] << " G:" << (int)lpPixels[i+1] << " B:" << (int)lpPixels[i] << endl;
        }

        ReleaseDC(NULL, hdc);
        delete[] lpPixels;

        Sleep(1000);
    }
    DeleteObject(hBitmap);

    return 0;
}

Upvotes: 2

Views: 1964

Answers (1)

Barmak Shemirani
Barmak Shemirani

Reputation: 31599

You are forcing the compression to be BI_RGB, you might as well set the first 6 values and call GetDIBits only once. And since the bitmap height starts from bottom to top you have to supply negative height for BITMAPINFOHEADER otherwise read from bottom to top.

Make sure the process is DPI aware. Easiest way (but not the preferred way) is to call SetProcessDPIAware(). For each hBitmap allocation call DeleteObject(hBitmap)

int main()
{
    SetProcessDPIAware();
    int width = GetSystemMetrics(SM_CXSCREEN);
    int height = GetSystemMetrics(SM_CYSCREEN);
    while(!GetAsyncKeyState(VK_SPACE))
    {
        HDC hdc = GetDC(0);
        POINT p;
        GetCursorPos(&p);

        HDC memdc = CreateCompatibleDC(hdc);
        HBITMAP hbitmap = CreateCompatibleBitmap(hdc, width, height);
        HGDIOBJ oldbmp = SelectObject(memdc, hbitmap);
        BitBlt(memdc, 0, 0, width, height, hdc, 0, 0, SRCCOPY | CAPTUREBLT);
        SelectObject(memdc, oldbmp);
        DeleteDC(memdc);

        //use GetPixel for testing
        COLORREF c = GetPixel(hdc, p.x, p.y);
        printf("%02X%02X%02X\n", GetRValue(c), GetGValue(c), GetBValue(c));

        BITMAPINFO bi = { 0 };
        bi.bmiHeader.biSize = sizeof(bi.bmiHeader);
        bi.bmiHeader.biWidth = width;
        bi.bmiHeader.biHeight = -height;
        bi.bmiHeader.biBitCount = 32; //32-bit bitmap
        bi.bmiHeader.biPlanes = 1;
        bi.bmiHeader.biCompression = BI_RGB;

        //allocate 4 bytes per pixel for 32-bit
        BYTE* lpPixels = new BYTE[height * width * 4]; 
        if(0 != GetDIBits(hdc, hbitmap, 0, height, lpPixels,
            &bi, DIB_RGB_COLORS))
        {
            int i = (p.y * width + p.x) * 4;
            printf("%02X%02X%02X\n\n", 
                        lpPixels[i + 2], lpPixels[i + 1], lpPixels[i + 0]);
        }
        DeleteObject(hbitmap);
        ReleaseDC(NULL, hdc);
        delete[] lpPixels;
        Sleep(1000);
    }
    return 0;
}

Upvotes: 3

Related Questions