c++winapibitblt

How to display a pixel vector on screen?


I create a screenshot from the desktop and receive a vector with the pixel data, but I am having problems when trying to draw the pixel data on my screen.

CODE TO CREATE SCREENSHOT SNIPPET

std::vector<RGBQUAD> recvScreenSnippet(int x, int y, int width, int height) {
    std::vector<RGBQUAD> v_screen_data(width*height);

    HDC screen_dc = GetDC(NULL);
    HDC mem_dc = CreateCompatibleDC(NULL);
    HBITMAP hBmp = CreateCompatibleBitmap(screen_dc, width, height);
    auto oldBmp = SelectObject(mem_dc, hBmp);

    // 32 bit & Upside Down headers
    BITMAPINFO bmi{};
    bmi.bmiHeader.biBitCount = 32;
    bmi.bmiHeader.biCompression = BI_RGB;
    bmi.bmiHeader.biPlanes = 1;
    bmi.bmiHeader.biHeight = height;
    bmi.bmiHeader.biWidth = width;
    bmi.bmiHeader.biSize = sizeof(BITMAPINFO);

    // Receive pixel data from hdc
    BitBlt(mem_dc, 0, 0, width, height, screen_dc, x, y, SRCCOPY);
    GetDIBits(mem_dc, hBmp, 0, height, &v_screen_data[0], &bmi, DIB_RGB_COLORS);

    // Cleanup
    SelectObject(mem_dc, oldBmp);
    DeleteObject(hBmp);
    DeleteDC(mem_dc);
    ReleaseDC(0, screen_dc);

    return v_screen_data;
}

CODE TO PAINT THE PIXEL DATA ON MY SCREEN:

void setScreenSnippet(int x, int y, int width, int height, std::vector<RGBQUAD> &v_pixel_data) {
    HDC screen_dc = GetDC(0);
    HDC mem_dc = CreateCompatibleDC(0);

    // 32 bit & Upside Down headers
    BITMAPINFO bmi{};
    bmi.bmiHeader.biBitCount = 32;
    bmi.bmiHeader.biCompression = BI_RGB;
    bmi.bmiHeader.biPlanes = 1;
    bmi.bmiHeader.biHeight = height;
    bmi.bmiHeader.biWidth = width;
    bmi.bmiHeader.biSize = sizeof(BITMAPINFO);

    // Create bitmap
    HBITMAP bitmap = CreateCompatibleBitmap(mem_dc,width,height);

    // Store pixel data in bitmap
    SetDIBits(mem_dc, bitmap, 0, height, &v_pixel_data[0], &bmi, DIB_RGB_COLORS);

    // Select bitmap data into mem_dc
    SelectObject(mem_dc, bitmap);

    while (true)
    {
        BitBlt(screen_dc, x, y, width, height, mem_dc, 0, 0, SRCCOPY);
        Sleep(1);
    }
    
    // Cleanup
    SelectObject(mem_dc, bitmap);
    DeleteObject(bitmap);
    ReleaseDC(0, screen_dc);
}

HOW I AM CALLING IT

int main()
{   
    Sleep(5000);
    std::vector<RGBQUAD> v_pixel_data = recvScreenSnippet(600,600,400,400);
    setScreenSnippet(100, 600, 400, 400, v_pixel_data);

    system("pause"), exit(1);
}

The issue is that the painted screenshot is black on the top, which means that something is not working.

This is what it looks like

What am I doing wrong?


Solution

  • First of all, I have to apologize that I have used C++ only in bare metal projects where no standard library functions (std::...) were used, so my knowledge of these functions is very limited.

    However, let's look at the following code:

    std::vector<int> example1, example2;
    
    example1[20] = 1;               /* Line 2 */
    
    int * pExample = &example2[0];  /* Line 3 */
    pExample[300] = 2;              /* Line 4 */
    

    Not knowing much about the std::vector data type, I cannot tell you what "line 2" is doing; maybe the size of the vector example1 is changed so it has at least 21 elements.

    However, I can definitely tell you what happens in "lines 3 and 4":

    In line 3, a pointer to the element [0] of the vector is generated.

    In line 4, an operation on the pointer (but not on the vector) is performed. This means that the compiler does not know that this operation accesses an element in the example2 vector.

    For this reason, the compiler definitely does not re-size the vector in this line.

    If the vector example2 was already larger than 300 elements, "line 4" will write to example2[300]; if it was smaller than 301 elements, this line of code will write to anywhere in the RAM and may crash your program because of overwriting important data (such as the return addresses on the stack).

    For this reason, you must ensure that example2 is already larger than 300 elements before accessing pExample[300].

    What does this have to do with your problem?

    Windows API functions perform pointer operations, not vector operations.

    This means: GetDIBits() internally works like this:

    ... GetDIBits(..., RGBQUAD * pExample, ...)
    {
        ...
        pExample[0] = ...;
        pExample[1] = ...;
        ...
        pExample[width * height - 1] = ...;
        ...
    }
    

    For this reason, you must ensure that the vector v_pixel_data already has a size of at least width * height elements before you call GetDIBits().

    The issue is that the painted screenshot is black on the top, which means that something is not working.

    Windows stores the bottom pixels first and the top pixels last.

    Maybe, your vector was large enough to hold a part of the image but not the full image.