从HWND获取并保存位图(每像素8,4,1位 - 黑色图像)

时间:2012-07-22 14:49:53

标签: c++ windows image

我编写了这个简单的代码,用于将位图从HWND保存到文件(* .bmp)。

#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;
}

正确保存图像,每像素32,24和16位。但是,对于每像素8,4和1位图像,仅包含黑色像素。

请告诉我我做错了什么。

2 个答案:

答案 0 :(得分:1)

对于 8/4/1 位图像(索引图像),必须将 RGBQUAD 表写入文件,因为原始位图数据不是颜色,而是指向< strong> RQBQUAD 表。

答案 1 :(得分:1)

当GDI将图像复制到索引曲面时,需要将源上的颜色映射到目标上可用的颜色。

除非您在目标DC中创建并选择调色板,否则GDI将不知道可用的颜色,并将使用默认调色板映射颜色,默认调色板将仅定义黑白。

这可能涉及很多 - 理想情况下,您需要扫描源图像,创建所有颜色的地图及其频率,并使用它来计算理想的调色板。

或者,只需使用CreateHalfTonePalette

在您的情况下,您正在使用所需的位深度进入DIBSection,因此您需要在执行blit之前初始化DIBSections颜色表。 iirc,当对DIB部分进行blitting时,使用DIBSections颜色表设置比在DC中选择HPALETTE更重要 - 但您可以使用CreateHalfTonePalette创建的调色板,并提取生成的颜色表。