Search code examples
c++bitmapgdi+gdi

Converting Color Bitmap to Grayscale in C++


Need to convert a color bitmap to a grey scale. Here is the approach:

        HDC             hdcWindow = GetDC(hWnd);
        HBITMAP hDIBBitmap;
        {
            // Create a compatible DC which is used in a BitBlt from the window DC
            HDC hdcMemDC = CreateCompatibleDC(hdcWindow);
            SelectObject(hdcMemDC, bmHatch);

            BITMAPINFO bmi;
            memset(&bmi, 0, sizeof(BITMAPINFO));
            bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
            bmi.bmiHeader.biWidth = bm.bmWidth;
            bmi.bmiHeader.biHeight = bm.bmHeight; // top-down
            bmi.bmiHeader.biPlanes = bm.bmPlanes;
            bmi.bmiHeader.biBitCount = bm.bmBitsPixel;
            UINT* pBits;
            HBITMAP hDIBBitmap = CreateDIBSection(hdcMemDC, &bmi, DIB_RGB_COLORS, (void**)&pBits, NULL, NULL);
            for (int i = 0; i < bm.bmWidth; i++) {
                for (int j = 0; j < bm.bmHeight; j++) {
                    UINT val = pBits[i + j * bm.bmWidth];
                    if (val != 0)
                    {
                        COLORREF clr = val;
                        UINT newVal = (GetRValue(clr) + GetBValue(clr) + GetGValue(clr)) / 3;
                        pBits[i + j * bm.bmWidth] = newVal;
                    }
                }
            }
            SelectObject(hdcMemDC, hDIBBitmap);

            // draw content of memory bitmap in the window
            BitBlt(hdcWindow, 0, 0, bm.bmWidth, bm.bmHeight, hdcMemDC, 0, 0, SRCCOPY);

            DeleteObject(hDIBBitmap);
            DeleteDC(hdcMemDC);
        }
        ReleaseDC(hWnd, hdcWindow);

In the above code sample, the input bitmap is given by bm which is a Bitmap instance.

Created a compatible DC. Loaded the bitmap into using the selectObject statement. Then wanted to change the bits by creating the DIB section for traversing the bitmap values. After changing the values, the hDIBBitmap is selected and then finally drawing using the BitBlt function.

When I comment out the following line, I can see the original bitmap rendered correctly: SelectObject(hdcMemDC, hDIBBitmap);

What I observed was the pBits is always 0 and hence the transformed gray scale image is not being rendered and instead getting a black picture.

Please advise on what is wrong with this approach.


Solution

  • CreateDIBSection is creating a blank bitmap with that usage. None of the bits from the original bitmap are used. If you paint it you get a black bitmap.

    If you comment out SelectObject(hdcMemDC, hDIBBitmap) then this new hDIBBitmap is ignored as well. You print the original bitmap which was selected in device context earlier: SelectObject(hdcMemDC, bmHatch)

    To convert HBITMAP to gray scale use the following code. Note this will not work for palette bitmaps.

    Converting 24-bit or 32-bit HBITMAP to grayscale:

    void grayscale(HBITMAP hbitmap)
    {
        BITMAP bm;
        GetObject(hbitmap, sizeof(bm), &bm);
        if(bm.bmBitsPixel < 24)
        {
            DebugBreak();
            return;
        }
    
        HDC hdc = GetDC(HWND_DESKTOP);
        DWORD size = ((bm.bmWidth * bm.bmBitsPixel + 31) / 32) * 4 * bm.bmHeight;
    
        BITMAPINFO bmi
        {sizeof(BITMAPINFOHEADER),bm.bmWidth,bm.bmHeight,1,bm.bmBitsPixel,BI_RGB,size};
    
        int stride = bm.bmWidth + (bm.bmWidth * bm.bmBitsPixel / 8) % 4;
        BYTE *bits = new BYTE[size];
        GetDIBits(hdc, hbitmap, 0, bm.bmHeight, bits, &bmi, DIB_RGB_COLORS);
        for(int y = 0; y < bm.bmHeight; y++) {
            for(int x = 0; x < stride; x++) {
                int i = (x + y * stride) * bm.bmBitsPixel / 8;
                BYTE gray = BYTE(0.1 * bits[i+0] + 0.6 * bits[i+1] + 0.3 * bits[i+2]);
                bits[i+0] = bits[i+1] = bits[i+2] = gray;
            }
        }
    
        SetDIBits(hdc, hbitmap, 0, bm.bmHeight, bits, &bmi, DIB_RGB_COLORS);
        ReleaseDC(HWND_DESKTOP, hdc);
        delete[]bits;
    }
    

    Usage

    //convert to grayscale
    //this should be called once
    grayscale(hbitmap);
    
    //paint image
    HDC memdc = CreateCompatibleDC(hdc);
    HBITMAP oldbmp = SelectObject(memdc, hbitmap);
    BITMAP bm;
    GetObject(hbitmap, sizeof(bm), &bm);
    BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, memdc, 0, 0, SRCCOPY);
    ...