Search code examples
cpointersmemory-leaksbitmapgdi

Raw/Direct acess on pixels data in a BITMAPINFO (HBITMAP)


I'm trying to figure out how do I acess raw pixel information from screen. So far I've been capturing the screen to a HBITMAP, populating a BITMAPINFO then creating a pointer of this BITMAPINFO variable to read directly from the memory. I know that the header must be "removed" from the file, so I advance the pointer directly to the bitmap data (adding sizeof(MyBMInfo2->bmiHeader) to my pointer offset). I also know that this bitmap is inversed/topdown, the first pixels are on the end of the raw data. I need to figure out how do I extract R G and B bytes from a given X and Y in the image, and that's what I'm being incapable of doing. So I asking you sirs a light, a snippet or any hint that can help my dirty attempt to recreate Bitmap.GetPixel(x,y) from gdi32 (so slow, I need a better one).

A snippet of my source code so far:

...
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);

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

// Get the MyBMInfo structure from the bitmap
if(0 == GetDIBits(hdc, hBitmap, 0, 0, NULL, &MyBMInfo, DIB_RGB_COLORS)) {
    printf("error\n");
}

BITMAPINFO* MyBMInfo2 = &MyBMInfo;
BYTE* bitmapBits=(BYTE*)MyBMInfo2+sizeof(MyBMInfo2->bmiHeader);
//So... how do I acess X and Y RGB bytes now? xD
...

By the way.... There are any other more direct way to do this without throwing a memory protection fault? Or... another faster way at all? Thank you.

--Edit--

Using Barmak's code I've figured out how do I access X and Y rgb based on current cursor position, here follows the source code:

#include <windows.h>

int main()
{
    HWND hwnd = GetDesktopWindow();
    RECT rc;
    GetClientRect(hwnd, &rc);
    int width = rc.right;
    int height = rc.bottom;
    if (width < 1 || height < 1)
    {
        OutputDebugStringA("error\n");
        return 0;
    }

    HDC hdc = GetDC(hwnd);

    HDC hCaptureDC = CreateCompatibleDC(hdc);
    HBITMAP hBitmap = CreateCompatibleBitmap(hdc, width, height);
    HGDIOBJ hOld = SelectObject(hCaptureDC, hBitmap);
    BitBlt(hCaptureDC, 0, 0, width, height, hdc, 0, 0, SRCCOPY | CAPTUREBLT);
    SelectObject(hCaptureDC, hOld);

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

    BITMAPINFOHEADER bmpInfoHeader = { sizeof(BITMAPINFOHEADER) };
    bmpInfoHeader.biWidth = width;
    bmpInfoHeader.biHeight = height;
    bmpInfoHeader.biPlanes = 1;
    bmpInfoHeader.biBitCount = 32;

    DWORD size = ((width * bmpInfoHeader.biBitCount + 31) / 32) * 4 * height;
    BYTE *bits = malloc(size);

    if (GetDIBits(hdc, hBitmap, 0, height, bits, (BITMAPINFO*)&bmpInfoHeader, DIB_RGB_COLORS))
    {
        OutputDebugStringA("success\n");
        //you can use bits here

        //access bits from upper-left to lower-right corner
        POINT p;
        GetCursorPos(&p);
        int x = p.x;
        int y = p.y;

        int col = x;
        int row = height - y - 1;
        int index = (row * width + col) * 4;

        BYTE b = bits[index + 0];
        BYTE g = bits[index + 1];
        BYTE r = bits[index + 2];

        printf("r:%i g:%i b:%i \n",r,g,b);


    }
    else
    {
        OutputDebugStringA("error\n");
    }

    free(bits);

    DeleteDC(hCaptureDC);
    DeleteObject(hBitmap);
    ReleaseDC(hwnd, hdc);

    return 0;
}

Thank you very much Mr Shemirani. To who downvoted my answer, please, consider the withdrawn of it please? :) Peace.


Solution

  • GetDibBits requires a buffer to receive the bits. You have to allocate the buffer, then delete when no longer needed.

    #include <windows.h>
    
    int WINAPI wWinMain(HINSTANCE, HINSTANCE, LPTSTR, int)
    {
        HWND hwnd = GetDesktopWindow();
        RECT rc;
        GetClientRect(hwnd, &rc);
        int width = rc.right;
        int height = rc.bottom;
        if (width < 1 || height < 1)
        {
            OutputDebugStringA("error\n");
            return 0;
        }
    
        HDC hdc = GetDC(hwnd);
    
        HDC hCaptureDC = CreateCompatibleDC(hdc);
        HBITMAP hBitmap = CreateCompatibleBitmap(hdc, width, height);
        HGDIOBJ hOld = SelectObject(hCaptureDC, hBitmap);
        BitBlt(hCaptureDC, 0, 0, width, height, hdc, 0, 0, SRCCOPY | CAPTUREBLT);
        SelectObject(hCaptureDC, hOld);
    
        BITMAPINFO MyBMInfo = { 0 };
        MyBMInfo.bmiHeader.biSize = sizeof(MyBMInfo.bmiHeader);
    
        BITMAPINFOHEADER bmpInfoHeader = { sizeof(BITMAPINFOHEADER) };
        bmpInfoHeader.biWidth = width;
        bmpInfoHeader.biHeight = height;
        bmpInfoHeader.biPlanes = 1;
        bmpInfoHeader.biBitCount = 32;
    
        DWORD size = ((width * bmpInfoHeader.biBitCount + 31) / 32) * 4 * height;
        BYTE *bits = malloc(size);
    
        if (GetDIBits(hdc, hBitmap, 0, height, bits, (BITMAPINFO*)&bmpInfoHeader, DIB_RGB_COLORS))
        {
            OutputDebugStringA("success\n");
            //you can use bits here
    
            //access bits from lower-left to upper-right corner
            for (int row = 0; row < height; row++)
            {
                for (int col = 0; col < width; col++)
                {
                    //for 32 bit image only:
                    int index = (row * width + col) * 4;
    
                    BYTE blue  = bits[index + 0];
                    BYTE green = bits[index + 1];
                    BYTE red   = bits[index + 2];
                }
            }
    
            //access bits from upper-left to lower-right corner
            for (int y = 0; y < height; y++)
            {
                for (int x = 0; x < width; x++)
                {
                    int col = x;
                    int row = height - y - 1;
                    int index = (row * width + col) * 4;
    
                    BYTE b = bits[index + 0];
                    BYTE g = bits[index + 1];
                    BYTE r = bits[index + 2];
                }
            }
    
        }
        else
        {
            OutputDebugStringA("error\n");
        }
    
        free(bits);
    
        DeleteDC(hCaptureDC);
        DeleteObject(hBitmap);
        ReleaseDC(hwnd, hdc);
    
        return 0;
    }