silverrulezz
silverrulezz

Reputation: 107

Get and save bitmap from HWND (8, 4, 1 bits-per-pixel - black image)

I write this simple code for saving bitmap to file (*.bmp) from HWND.

#include <stdio.h>
#include <windows.h>

bool captureAndSave(const HWND& hWnd, int nBitCount, const char* szFilePath)
{
    if(!szFilePath || !strlen(szFilePath))
    {
        printf("bad function arguments\n");
        return false;
    }

    //calculate the number of color indexes in the color table
    int nColorTableEntries = -1;
    switch(nBitCount) 
    {
        case 1:
            nColorTableEntries = 2;
            break;
        case 4:
            nColorTableEntries = 16;
            break;
        case 8:
            nColorTableEntries = 256;
            break;
        case 16:
        case 24:
        case 32:
            nColorTableEntries = 0;
            break;
        default:
            nColorTableEntries = -1;
            break;
    }

    if(nColorTableEntries == -1)
    {
        printf("bad bits-per-pixel argument\n");
        return false;
    }

    HDC hDC = GetDC(hWnd);
    HDC hMemDC = CreateCompatibleDC(hDC);

    int nWidth = 0;
    int nHeight = 0;

    if(hWnd != HWND_DESKTOP)
    {
        RECT rect;
        GetClientRect(hWnd, &rect);
        nWidth = rect.right - rect.left;
        nHeight = rect.bottom - rect.top;
    }
    else
    {
        nWidth = ::GetSystemMetrics(SM_CXSCREEN);
        nHeight = ::GetSystemMetrics(SM_CYSCREEN);
    }


    HBITMAP hBMP = CreateCompatibleBitmap(hDC, nWidth, nHeight);
    SelectObject(hMemDC, hBMP);
    BitBlt(hMemDC, 0, 0, nWidth, nHeight, hDC, 0, 0, SRCCOPY);

    int nStructLength = sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * nColorTableEntries;
    LPBITMAPINFOHEADER lpBitmapInfoHeader = (LPBITMAPINFOHEADER)new char[nStructLength];
    ::ZeroMemory(lpBitmapInfoHeader, nStructLength);

    lpBitmapInfoHeader->biSize = sizeof(BITMAPINFOHEADER);
    lpBitmapInfoHeader->biWidth = nWidth;
    lpBitmapInfoHeader->biHeight = nHeight;
    lpBitmapInfoHeader->biPlanes = 1;
    lpBitmapInfoHeader->biBitCount = nBitCount;
    lpBitmapInfoHeader->biCompression = BI_RGB;
    lpBitmapInfoHeader->biXPelsPerMeter = 0;
    lpBitmapInfoHeader->biYPelsPerMeter = 0;
    lpBitmapInfoHeader->biClrUsed = nColorTableEntries;
    lpBitmapInfoHeader->biClrImportant = nColorTableEntries;

    DWORD dwBytes = ((DWORD) nWidth * nBitCount) / 32;
    if(((DWORD) nWidth * nBitCount) % 32) {
        dwBytes++;
    }
    dwBytes *= 4;

    DWORD dwSizeImage = dwBytes * nHeight;
    lpBitmapInfoHeader->biSizeImage = dwSizeImage;

    LPBYTE lpDibBits = 0;
    HBITMAP hBitmap = ::CreateDIBSection(hMemDC, (LPBITMAPINFO)lpBitmapInfoHeader, DIB_RGB_COLORS,  (void**)&lpDibBits, NULL, 0);
    SelectObject(hMemDC, hBitmap);
    BitBlt(hMemDC, 0, 0, nWidth, nHeight, hDC, 0, 0, SRCCOPY);
    ReleaseDC(hWnd, hDC);

    BITMAPFILEHEADER bmfh;
    bmfh.bfType = 0x4d42;  // 'BM'
    int nHeaderSize = sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * nColorTableEntries;
    bmfh.bfSize = 0;
    bmfh.bfReserved1 = bmfh.bfReserved2 = 0;
    bmfh.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * nColorTableEntries;    

    FILE *pFile = 0;
    fopen_s(&pFile, szFilePath, "wb");
    if(!pFile)
    {
        ::DeleteObject(hBMP);
        ::DeleteObject(hBitmap);
        delete[]lpBitmapInfoHeader;
        printf("can not open file\n");
        return false;   
    }

    DWORD nColorTableSize = 0;
    if (nBitCount != 24)
        nColorTableSize = (1 << nBitCount) * sizeof(RGBQUAD);
    else
        nColorTableSize = 0;


    fwrite(&bmfh, sizeof(BITMAPFILEHEADER), 1, pFile);
    fwrite(lpBitmapInfoHeader, nHeaderSize,1,pFile);

    if(nBitCount < 16)
    {
        int nBytesWritten = 0;
        RGBQUAD *rgbTable = new RGBQUAD[nColorTableEntries * sizeof(RGBQUAD)];
        //fill RGBQUAD table and write it in file
        for(int i = 0; i < nColorTableEntries; ++i)
        {
            rgbTable[i].rgbRed = rgbTable[i].rgbGreen = rgbTable[i].rgbBlue = i;
            rgbTable[i].rgbReserved = 0;

            fwrite(&rgbTable[i], sizeof(RGBQUAD), 1, pFile);
        }
        delete[]rgbTable;

        /*
        RGBQUAD rgb;
        for (DWORD i = 0; i < nColorTableEntries ; i++)
        {
            rgb.rgbBlue = rgb.rgbGreen = rgb.rgbRed = (BYTE)(i*(255/(nColorTableEntries-1)));
            nBytesWritten = fwrite(&rgb, 1, sizeof(rgb), pFile);
            if (nBytesWritten != sizeof(rgb)) 
            {
                printf("error while writing rgb header\n");
                fclose(pFile);

                ::DeleteObject(hBMP);
                ::DeleteObject(hBitmap);
                delete[]lpBitmapInfoHeader;

                return false;
            }
        }
        */
    }

    fwrite(lpDibBits, dwSizeImage, 1, pFile);

    fclose(pFile);

    ::DeleteObject(hBMP);
    ::DeleteObject(hBitmap);
    delete[]lpBitmapInfoHeader;
}

int main(int argc, char **argv)
{
    captureAndSave(HWND_DESKTOP, 1,  "1.bmp");
    captureAndSave(HWND_DESKTOP, 4,  "4.bmp");
    captureAndSave(HWND_DESKTOP, 8,  "8.bmp");
    captureAndSave(HWND_DESKTOP, 16, "16.bmp");
    captureAndSave(HWND_DESKTOP, 24, "24.bmp");
    captureAndSave(HWND_DESKTOP, 32, "32.bmp");

    return 0;
}

Images are saved properly for 32, 24 and 16 bits per pixel. But for 8, 4 and 1 bits per pixel image contains only black pixels.

Please tell me what I'm doing wrong.

Upvotes: 0

Views: 3757

Answers (2)

Chris Becke
Chris Becke

Reputation: 36016

When GDI copies images to an indexed surface, it needs to map the colors it has on the source to colors available on the destination.

Until, and unless, you create and select a palette into the destination DC, GDI won't know what colors are available, and will map the colors using the default palette, which will define black and white only.

The could be quite a lot involved in this - ideally you'd want to scan the source image, create a map of all the colors used and their freqencies and use that to compute an ideal palette.

Or, just use CreateHalfTonePalette.

In your case, you are blitting onto a DIBSection with the required bit depth, so you need to initialize the DIBSections color-table before performing the blit. iirc, When blitting to a DIB Section, having the DIBSections color table setup is more important than selecting an HPALETTE into the DC - but you can use the palette created by CreateHalfTonePalette, and extract the resulting color table.

Upvotes: 1

Xearinox
Xearinox

Reputation: 3234

For 8/4/1 bit images, which are indexed images, must write RGBQUAD table to file, because raw bitmap data is not colors, but indexes to RQBQUAD table.

Upvotes: 1

Related Questions