WinAPI/GDI:如何使用 GetDIBits() 为位图合成颜色表?

Posted

技术标签:

【中文标题】WinAPI/GDI:如何使用 GetDIBits() 为位图合成颜色表?【英文标题】:WinAPI/GDI: How to use GetDIBits() to get color table synthesized for a bitmap? 【发布时间】:2018-03-15 16:48:10 【问题描述】:

我很难理解下面来自 MSDN 网站GetDIBits() 函数的摘录:

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

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

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

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


编辑:

从目前的 cmets 来看,将问题分解成更小的部分似乎没有效果。我会尝试另一种方式。

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

假设函数调用为GetDIBits(hdc, hbmp, uStartScan, cScanLines, lpvBits, lpbi, uUsage);如果 lpvBits 为 NULL 且 lpvBits->bmiHeader.biBitCount 初始化为零,GetDIBits() 仅填写 lpbi->bmiHeaderlpbi->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);

【问题讨论】:

所有这些在 MSDN 中都有详尽的解释。见Bitmaps。 @IInspectable :我已经阅读了整个“位图”部分以及几乎所有关于 GDI 的一般 MSDN 文档,但我仍然无法理解 OP 中引用的部分。我对 GDI 很陌生,而且我总是可能错过了一些东西。如果您能指出与我的问题相关的更具体的部分,我将不胜感激。 例如,对颜色表进行了非常详细的解释。 1-、2-、4- 和 8-bpp 位图具有颜色表,紧跟在 BITMAPINFO 结构之后。 16 和更高的 bpp 位图没有颜色表。这回答了问题 2。链接中也回答了其他问题。也许你只需要再读一遍。 @IInspectable :我认为我已经掌握了这些部分。我担心可能是我的 OP 不够清楚。我的问题与引用部分的一般含义无关。只是在这些情况下它们对我来说没有意义。例如,我知道“颜色表”是什么,或者当它存在于位图中时它在哪里。我只是不明白“在没有颜色表的情况下填写BITMAPINFOHEADER 结构”是什么意思。 BITMAPINFOHEADER 不包含颜色表。它存储在BITMAPINFObmiColors 数组成员中。 正在BITMAPINFO 结构传递给GetDIBits 【参考方案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-&gt;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-&gt;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),您可以立即使用它。

【讨论】:

终于一切都清楚了,谢谢。现在我看到了我所有误解的关键点是我认为调用GetDIBits() 时使用BITMAPINFO 结构的完全初始化的bmiHeader 成员仅填充lpvBits,而实际上它也填充bmiColors 成员。我只是希望在文档中清楚。 是的,我也花了一个小时才弄清楚文档。 使用GetDIBitslpvBits 设置为NULL 将填充BITMAPINFO。但我认为最好手动填写BITMAPINFO。通常你可以从BITMAPGetObject得到所有必要的BITMAPINFO值...【参考方案2】:

虽然接受的答案涵盖了细节,但这更像是对 OP 中问题的直接回答。

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

问题 1:

没错。 “BITMAPINFO的位数成员”指的是lpbi-&gt;bmiHeader.biBitCount

问题 2:

当调用GetDIBits() 以获取DIB 位(即具有非空lpvBits 和适当初始化的lpbi-&gt;bmiHeader)时,lpbi-&gt;bmiColors 也被颜色表的函数填充(如果位深度为低于 24 bpp)。 不幸的是,这在函数的文档中并不清楚。考虑到这一点,引用部分的意思是,当 lpvBits 为 NULL 且 lpbi-&gt;bmiHeader.biBitCount 为零时,该函数填充lpbi-&gt;bmiHeader > 修改lpbi-&gt;bimColor(而不是在调用函数以获取 DIB 位时)。

问题 3:

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

编辑部分的问题:

如果 lpvBits 为 NULL 并且 lpvBits-&gt;bmiHeader.biBitCount 被初始化为 零,GetDIBits() 仅填写 lpbi-&gt;bmiHeaderlpbi-&gt;bmiColors 是 未修改。

这样理解正确吗?

是的,没错。

如果是这样,有没有办法让 GetDIBits() 填写 lpbi-&gt;bmiColors,比如初始化lpvBits-&gt;bmiHeader.biBitCount为 位图的位深?

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

【讨论】:

以上是关于WinAPI/GDI:如何使用 GetDIBits() 为位图合成颜色表?的主要内容,如果未能解决你的问题,请参考以下文章

处理 GetDIBits() 返回的像素缓冲区的正确方法是啥?

使用 Winsock、GetDIBits 和 SetDiBits 进行位图传输 [关闭]

GetDIBits 返回兼容位图的无效颜色数组

GetDIBits 遍历位图 获取像素的颜色值(RGB)

GetDIBits 遍历位图 获取像素的颜色值(RGB)

GetDIBits 返回所有值为 0 的数组