WinAPI/GDI: 如何使用 GetDIBits() 为位图合成颜色 table?
WinAPI/GDI: How to use GetDIBits() to get color table synthesized for a bitmap?
我发现很难理解下面摘自 MSDN 站点的 GetDIBits()
函数的摘录:
If lpvBits is NULL and the bit count member of BITMAPINFO is
initialized to zero, GetDIBits fills in a BITMAPINFOHEADER
structure or BITMAPCOREHEADER without the color table. This
technique can be used to query bitmap attributes.
问题一:"the bit count member of BITMAPINFO"是什么意思?意思是some_bmi.bmiHeader.biBitCount
?
问题2:"GetDIBits fills in a BITMAPINFOHEADER structure or BITMAPCOREHEADER without the color table"是什么意思?有什么颜色table可以填充那些结构? None 其中似乎有一个与颜色有关的成员 table。这是关于数组 some_bmi.bmiColors
?
问题3:有没有办法使用GetDIBits()
获取位图的颜色table(即数组映射索引到颜色)?
编辑:
从目前的评论来看,将问题分解成更小的部分似乎没有效果。我会尝试另一种方式。
开头我从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
初始化为位图的位深度?
我尝试按如下方式测试问题 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);
}
获得颜色 table 的最简单方法是使用 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
这应该可行;然而,正如文档中所述,这只会填充信息 header,而不是颜色 table。要获得颜色 table,您必须再次调用 GetDIBits
并为 dib 位分配足够的空间。
此外 DIB_PAL_COLORS
将 return 调色板索引数组(我不确定你能用它做什么)。您可能想要使用 DIB_RGB_COLORS
标志,当颜色 table 存在时,它将 return 实际颜色。
尝试使用从文件加载的位图:
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
数组相同的值。
BITMAPINFO
和 BITMAPINFOHEADER
之间可能混淆:
以上结构声明如下:
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
将 return 一个兼容的位图(如果你没有指定 LR_CREATEDIBSECTION
)并且你可以马上使用它。
尽管接受的答案涵盖了细节,但这更像是对 OP 中问题的直接回答。
假设函数调用是GetDIBits(hdc, hbmp, uStartScan, cScanLines, lpvBits, lpbi, uUsage)
;
问题一:
没错。 "The bit count member of BITMAPINFO"指的是lpbi->bmiHeader.biBitCount
.
问题2:
当调用 GetDIBits()
以获取 DIB 位时(即使用非空 lpvBits
和适当初始化的 lpbi->bmiHeader
),lpbi->bmiColors
也由颜色 table 的函数(如果位深度小于 24 bpp)。
不幸的是,这在函数的文档中并不清楚。考虑到这一点,引用部分的意思是,当 lpvBits
为 NULL 且 lpbi->bmiHeader.biBitCount
为零时,函数仅填充 lpbi->bmiHeader
, 不修改lpbi->bimColor
(与调用函数获取DIB位时相反)。
问题3:
您可以在 lpbi->bmiColors
中通过使用非空 lpvBits
和适当初始化的 lpbi->bmiHeader
。 IOW,当您像往常一样调用函数获取 DIB 位时,它也会填充 lpbi->bmiColors
。
编辑部分的问题:
If lpvBits is NULL and lpvBits->bmiHeader.biBitCount
is initialized to
zero, GetDIBits() fills in lpbi->bmiHeader
only and lpbi->bmiColors
is
not modified.
Is this the correct way to understand it?
是的,没错。
And if so, is there a way to get GetDIBits() to fill in
lpbi->bmiColors
, such as initializing lpvBits->bmiHeader.biBitCount
to
bit-depth of the bitmap?
是的,有一种方法可以使函数获得 return 颜色 table,但正如在对 Q2 的回答中所解释的那样,将 lpvBits->bmiHeader.biBitCount
初始化为位图的位深度光靠自己是不够的。 lpvBits->bmiHeader
的所有成员都必须适当地初始化并且 lpvBits
必须是非空的。这与调用函数获取DIB位基本相同。
我发现很难理解下面摘自 MSDN 站点的 GetDIBits()
函数的摘录:
If lpvBits is NULL and the bit count member of BITMAPINFO is initialized to zero, GetDIBits fills in a BITMAPINFOHEADER structure or BITMAPCOREHEADER without the color table. This technique can be used to query bitmap attributes.
问题一:"the bit count member of BITMAPINFO"是什么意思?意思是some_bmi.bmiHeader.biBitCount
?
问题2:"GetDIBits fills in a BITMAPINFOHEADER structure or BITMAPCOREHEADER without the color table"是什么意思?有什么颜色table可以填充那些结构? None 其中似乎有一个与颜色有关的成员 table。这是关于数组 some_bmi.bmiColors
?
问题3:有没有办法使用GetDIBits()
获取位图的颜色table(即数组映射索引到颜色)?
编辑:
从目前的评论来看,将问题分解成更小的部分似乎没有效果。我会尝试另一种方式。
开头我从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
初始化为位图的位深度?
我尝试按如下方式测试问题 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);
}
获得颜色 table 的最简单方法是使用 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
这应该可行;然而,正如文档中所述,这只会填充信息 header,而不是颜色 table。要获得颜色 table,您必须再次调用 GetDIBits
并为 dib 位分配足够的空间。
此外 DIB_PAL_COLORS
将 return 调色板索引数组(我不确定你能用它做什么)。您可能想要使用 DIB_RGB_COLORS
标志,当颜色 table 存在时,它将 return 实际颜色。
尝试使用从文件加载的位图:
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
数组相同的值。
BITMAPINFO
和 BITMAPINFOHEADER
之间可能混淆:
以上结构声明如下:
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
将 return 一个兼容的位图(如果你没有指定 LR_CREATEDIBSECTION
)并且你可以马上使用它。
尽管接受的答案涵盖了细节,但这更像是对 OP 中问题的直接回答。
假设函数调用是GetDIBits(hdc, hbmp, uStartScan, cScanLines, lpvBits, lpbi, uUsage)
;
问题一:
没错。 "The bit count member of BITMAPINFO"指的是lpbi->bmiHeader.biBitCount
.
问题2:
当调用 GetDIBits()
以获取 DIB 位时(即使用非空 lpvBits
和适当初始化的 lpbi->bmiHeader
),lpbi->bmiColors
也由颜色 table 的函数(如果位深度小于 24 bpp)。
不幸的是,这在函数的文档中并不清楚。考虑到这一点,引用部分的意思是,当 lpvBits
为 NULL 且 lpbi->bmiHeader.biBitCount
为零时,函数仅填充 lpbi->bmiHeader
, 不修改lpbi->bimColor
(与调用函数获取DIB位时相反)。
问题3:
您可以在 lpbi->bmiColors
中通过使用非空 lpvBits
和适当初始化的 lpbi->bmiHeader
。 IOW,当您像往常一样调用函数获取 DIB 位时,它也会填充 lpbi->bmiColors
。
编辑部分的问题:
If lpvBits is NULL and
lpvBits->bmiHeader.biBitCount
is initialized to zero, GetDIBits() fills inlpbi->bmiHeader
only andlpbi->bmiColors
is not modified.Is this the correct way to understand it?
是的,没错。
And if so, is there a way to get GetDIBits() to fill in
lpbi->bmiColors
, such as initializinglpvBits->bmiHeader.biBitCount
to bit-depth of the bitmap?
是的,有一种方法可以使函数获得 return 颜色 table,但正如在对 Q2 的回答中所解释的那样,将 lpvBits->bmiHeader.biBitCount
初始化为位图的位深度光靠自己是不够的。 lpvBits->bmiHeader
的所有成员都必须适当地初始化并且 lpvBits
必须是非空的。这与调用函数获取DIB位基本相同。