WinAPI / GDI:如何使用GetDIBits()来获取位图合成的颜色表?

时间:2017-10-04 10:15:31

标签: c++ winapi gdi

我发现很难理解MSDN网站GetDIBits()功能下面的摘录:

  

如果 lpvBits NULL BITMAPINFO 的位数成员是   初始化为零, GetDIBits 填写 BITMAPINFOHEADER   没有颜色表的结构或 BITMAPCOREHEADER 。这个   技术可用于查询位图属性。

问题1:“BITMAPINFO的位数成员”是什么意思?这是some_bmi.bmiHeader.biBitCount吗?

问题2:“GetDIBits填充BITMAPINFOHEADER结构或没有颜色表的BITMAPCOREHEADER”是什么意思?填充这些结构的是什么颜色表?它们似乎都没有与颜色表相关的成员。这是关于数组some_bmi.bmiColors吗?

问题3:有没有办法使用GetDIBits()来获取位图的颜色表(即数组映射索引到颜色)?

编辑:

从目前为止的评论来看,将问题分解为较小的部分似乎无效。我会以另一种方式尝试。

这是我从一开始引用MSDN的部分所理解的:

假设函数调用是GetDIBits(hdc, hbmp, uStartScan, cScanLines, lpvBits, lpbi, uUsage);如果 lpvBits 为NULL并且lpvBits->bmiHeader.biBitCount初始化为零,则GetDIBits()仅填充lpbi->bmiHeader并且不会修改lpbi->bmiColors

这是理解它的正确方法吗?如果是这样,有没有办法让GetDIBits()填充lpbi->bmiColors,例如将lpvBits->bmiHeader.biBitCount初始化为位图的位深度?

我尝试按如下方式测试Question-1的假设,但在这种情况下GetDIBits()失败:

void test(HWND hWnd) {
    // get a memory DC
    HDC hdc = GetDC(hWnd);
    HDC hdcmem = CreateCompatibleDC(hdc); // has 1x1 mono bitmap selected 
                                          // into it initially by default
    // select a 16x16 mono bmp into it
    const int bmp_h = 16, bmp_w = 16;
    const int bits_per_px = 1;
    HBITMAP hbmp = CreateCompatibleBitmap(hdcmem, bmp_h, bmp_w); // 16x16 mono bitmap
    HGDIOBJ hOldBmp = SelectObject(hdcmem, hbmp);

    // initialize BITMAPINFO ptr
    // (make sure to allocate a buffer large enough for 2 RGBQUADs 
    // in case color table is retured by GetDIBits() call)
    const int bmi_buf_sz =
        sizeof(BITMAPINFO) + sizeof(RGBQUAD) * (1 << bits_per_px); // 2 + 1(extra) RGBQUADs allocated for pbmi->bimColors
    BYTE* p_bmi_buf = new BYTE[bmi_buf_sz];
    BITMAPINFO* pbmi = reinterpret_cast<BITMAPINFO*>(p_bmi_buf);
    ZeroMemory(pbmi, bmi_buf_sz);

    // populate BITMAPINFO
    pbmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    pbmi->bmiHeader.biBitCount = 1; // set to 1 just to see if GetDIBits()
                                    // fills in pbmi->bmiColors too
                                   // (when set to 0, only pbmi->bmiHeader is filled)
    if(!GetDIBits(hdcmem, hbmp,
                  0, (UINT)bmp_h,
                  NULL, pbmi, DIB_PAL_COLORS)) {
        MessageBox(hWnd, L"GetDIBits() failed!", NULL, MB_OK);
    }

    // clean-up
    delete[] p_bmi_buf;
    SelectObject(hdcmem, hOldBmp); // push hbmp out
    DeleteObject(hbmp);
    DeleteDC(hdcmem);
    ReleaseDC(hWnd, hdc);
}

2 个答案:

答案 0 :(得分:1)

获取颜色表的最简单方法是使用GetDibColorTable

HDC memdc = CreateCompatibleDC(NULL);
HBITMAP oldbmp = (HBITMAP)SelectObject(memdc, hbitmap);
int ncolors = 1 << bm.bmBitsPixel;
std::vector<RGBQUAD> rgb(ncolors);
if(ncolors == GetDIBColorTable(memdc, 0, ncolors, &rgb[0]))
{
    //success!
}
SelectObject(memdc, oldbmp);
DeleteDC(memdc);

回到你的问题:GetDIBits期望pbmi包含全部零(bmiHeader.biSize成员除外)。所以pbmi->bmiHeader.biBitCount应为零。

//pbmi->bmiHeader.biBitCount = 1; <<= comment out this line

这应该有用;但是,正如文档中所述,这只会填充信息标题,而不是颜色表。要获取颜色表,您必须再次调用GetDIBits并为dib位分配足够的数据。

此外DIB_PAL_COLORS将返回调色板索引数组(我不知道你可以用它做什么)。您可能希望使用DIB_RGB_COLORS标志,当存在颜色表时,该标志将返回实际颜色。

尝试使用从文件加载的位图:

HBITMAP hbitmap = (HBITMAP)LoadImage(0, L"8bit.bmp",
    IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION | LR_LOADFROMFILE);

BITMAP bm;
GetObject(hbitmap, sizeof(bm), &bm);

//don't continue for hi color bitmaps
if(bm.bmBitsPixel > 8) return;

int ncolors = 1 << bm.bmBitsPixel;
HDC memdc = CreateCompatibleDC(NULL);
int bmpinfo_size = sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * ncolors;
std::vector<BYTE> buf(bmpinfo_size);
BITMAPINFO* bmpinfo = (BITMAPINFO*)buf.data();
bmpinfo->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
if(!GetDIBits(memdc, hbitmap, 0, bm.bmHeight, NULL, bmpinfo, DIB_RGB_COLORS))
{
    DWORD err = GetLastError();
    //...
}

int dibsize = ((bm.bmWidth * bm.bmBitsPixel + 31) / 32) * 4 * bm.bmHeight;
std::vector<BYTE> dib(dibsize);
if(!GetDIBits(memdc, hbitmap, 0, (UINT)bm.bmHeight, &dib[0], bmpinfo, DIB_RGB_COLORS))
{
    DWORD err = GetLastError();
    //...
}

现在bmpinfo->bmiColors应包含与前面显示的rgb数组相同的值。

<小时/> BITMAPINFOBITMAPINFOHEADER之间可能存在混淆:

以上结构声明如下:

typedef struct tagBITMAPINFO {
    BITMAPINFOHEADER    bmiHeader;
    RGBQUAD             bmiColors[1];
} BITMAPINFO, FAR *LPBITMAPINFO, *PBITMAPINFO;

typedef struct tagBITMAPINFOHEADER{
        DWORD      biSize;
        LONG       biWidth;
        LONG       biHeight;
        WORD       biPlanes;
        WORD       biBitCount;
        DWORD      biCompression;
        DWORD      biSizeImage;
        LONG       biXPelsPerMeter;
        LONG       biYPelsPerMeter;
        DWORD      biClrUsed;
        DWORD      biClrImportant;
} BITMAPINFOHEADER, FAR *LPBITMAPINFOHEADER, *PBITMAPINFOHEADER;

因此BITMAPINFO没有biBitCount成员。但它确实有bmiHeader.biBitCount成员。

当您声明BITMAPINFOHEADER变量时,您必须设置biSize成员(这是Windows的版本控制理念)。当您声明BITMAPINFO变量时,您必须确保BITMAPINFOHEADER已被处理。

请注意,大多数情况下您不必担心调色板。例如,LoadImage将返回兼容的位图(如果您未指定LR_CREATEDIBSECTION),您可以立即使用它。

答案 1 :(得分:1)

虽然接受的答案涵盖了详细信息,但这更直接地回答了OP中的问题。

假设函数调用是GetDIBits(hdc, hbmp, uStartScan, cScanLines, lpvBits, lpbi, uUsage);

问题-1:

这是正确的。 &#34; BITMAPINFO的位数成员&#34;是指lpbi->bmiHeader.biBitCount

问题-2:

当调用GetDIBits()来获取DIB位(即使用非空lpvBits和适当初始化的lpbi->bmiHeader)时,lpbi->bmiColors也被函数填充颜色表(如果位深度小于24 bpp)。 不幸的是,这个功能的文档中并不清楚。考虑到这一点,引用部分的含义是,当lpvBits为NULL且lpbi->bmiHeader.biBitCount为零时,该函数仅填充lpbi->bmiHeader ,并执行< strong>不修改lpbi->bimColor(而不是在调用函数来获取DIB位时)。

问题-3:

您可以通过使用非空lpbi->bmiColors和适当初始化的lpvBits调用lpbi->bmiHeader来获取函数以返回颜色表(对于8 bbp或更低的位图) 。 IOW,当您像往常一样调用函数来获取DIB位时,它也会填充lpbi->bmiColors

编辑部分的问题:

  

如果 lpvBits 为NULL且lpvBits->bmiHeader.biBitCount初始化为   零,GetDIBits()仅填充lpbi->bmiHeaderlpbi->bmiColors填充   没有修改。

     

这是理解它的正确方法吗?

是的,这是正确的。

  

如果是这样,有没有办法让GetDIBits()填写   lpbi->bmiColors,例如初始化lpvBits->bmiHeader.biBitCount   位图的位深度?

是的,有一种方法可以让函数返回颜色表,但正如在Q2的回答中所解释的那样,仅仅将lpvBits->bmiHeader.biBitCount初始化为位图的位深度是不够的。必须正确初始化lpvBits->bmiHeader的所有成员,lpvBits必须为非空。这与调用函数获取DIB位基本相同。