为了改善this answer,我正在寻找一种方法来确定通过HBITMAP
引用的位图是否包含alpha通道。
我明白,我可以调用GetObject
,并检索BITMAP
结构:
BITMAP bm = { 0 };
::GetObject(hbitmap, sizeof(bm), &bm);
但这只能得到存储像素颜色所需的位数。它没有告诉我,实际使用了哪些位,或者它们与各个通道的关系。例如,16bpp位图可以编码5-6-5 BGR图像,或1-5-5-5 ABGR图像。同样,32bpp位图可以存储ABGR或xBGR数据。
我可以更进一步,并探测DIBSECTION(如果可用):
bool is_dib = false;
BITMAP bm = { 0 };
DIBSECTION ds = { 0 };
if ( sizeof(ds) == ::GetObject(hbitmap, sizeof(ds), &ds ) {
is_dib = true;
} else {
::GetObject(hbitmap, sizeof(bm), &bm );
}
虽然这可以消除16bpp情况(使用dsBitfields
成员)的歧义,但在32bpp图像的情况下仍然无法确定是否存在alpha通道。
有没有办法找出,通过HBITMAP
引用的位图是否包含alpha通道(以及哪些位用于它),或者这些信息是否根本不可用?
你无法明确地知道,但如果你愿意迭代像素,你可以做出良好的教育猜测。
(现在无论如何,忽略带有1位alpha通道的16位颜色。)
对于存在alpha通道,位图必须(但不充分)是DIB部分并且每像素具有32位。如问题中所述,您可以检查这些要求。
我们也知道Windows仅处理预乘的alpha。这意味着,对于每个像素A >= max(R, G, B)
。因此,如果您愿意扫描所有像素,则可以排除一堆24位图像。如果该条件适用于所有像素,并且如果任何A的非零,则几乎肯定会有alpha通道(或损坏的图像)。
基本上,唯一的不确定性是全透明图像与全黑图像,两者都包括所有通道都设置为零的像素。也许在这种情况下采取有根据的猜测是足够的。我猜是的,因为拥有32位DIB部分是一个非常强大的信号。如果你有一个32位设备相关的位图,那么它没有alpha,如果你有一个没有alpha的设备无关位图,你可能会使用每像素24位来节省空间。
一些更详细的位图信息标题可以告诉您是否为alpha通道保留了位。例如,请参阅BITMAPV5HEADER,它有一个掩码,指示哪些位是alpha通道(尽管文档说明了一些相互矛盾的事情)。同样适用于BITMAPV4HEADER。不幸的是,我认为没有办法从HBITMAP获得此版本的标题。 (而且我确定那里有支持alpha的位图文件没有正确设置这些字段。)
众所周知,GDI不处理alpha通道(AlphaBlend除外,它不接受HBITMAP,而是访问一个选择的内存DC)。有一些用户API,如UpdateLayeredWindow,可以处理带有alpha通道的图像,但是,像AlphaBlend一样,将位图数据从所选信息中取出到内存DC中。如果传递了正确的标志,LoadImage将在加载要由HBITMAP访问的DIB时保留alpha通道。 如果使用适当的标志创建图像列表,则采用HBITMAP的ImageList_Add将保留和alpha通道。但是,在所有这些情况下,调用者必须知道位图数据包含正确的alpha数据并为API设置正确的标志。这表明位图句柄不容易获得信息。
如果您可以访问从中加载图像的位图资源或文件,则可以查看原始标头是否使用BI_BITFIELDS并指定了alpha通道,但在所有情况下都无法从HBITMAP获取该标头。 (并且仍然存在标题未正确填写的担忧。)
您可以使用GetObject函数间接获取它。隐藏在文档中的是一个注释:
如果hgdiobj是通过调用CreateDIBSection创建的位图的句柄,并且指定的缓冲区足够大,则GetObject函数返回DIBSECTION结构。此外,DIBSECTION中包含的BITMAP结构的bmBits成员将包含指向位图位值的指针。
如果hgdiobj是通过任何其他方式创建的位图的句柄,则GetObject仅返回位图的宽度,高度和颜色格式信息。您可以通过调用GetDIBits或GetBitmapBits函数来获取位图的位值。
(强调我的)
换句话说:如果你试图解码它是一个DIBSECTION,但它只是一个BITMAP
dibSection.BitmapInfoHeader
不会更新。 (例如,保留为零)
记住BITMAP和DIBSECTION的不同之处很有帮助:
| BITMAP | DIBSECTION |
|------------------------|--------------------------|
| bmType: Longint | bmType: Longint |
| bmWidth: Longint | bmWidth: Longint |
| bmHeight: Longint | bmHeight: Longint |
| bmWidthBytes: Longint | bmWidthBytes: Longint |
| bmPlanes: Word | bmPlanes: Word |
| bmBitsPixel: Word | bmBitsPixel: Word |
| bmBits: Pointer | bmBits: Pointer |
| | |
| |BITMAPINFOHEADER | <-- will remain unchanged for BITMAPs
| | biSize: DWORD |
| | biWidth: Longint |
| | biHeight: Longint |
| | biPlanes: Word |
| | biBitCount: Word |
| | biCompression: DWORD |
| | biSizeImage: DWORD |
| | biXPelsPerMeter: Longint |
| | biYPelsPerMeter: Longint |
| | biClrUsed: DWORD |
| | biClrImportant: DWORD |
| | |
| | dsBitfields: DWORD[3] |
| | dshSection: HANDLE |
| | dsOffset: DWORD |
如果您尝试将BITMAP作为DIBSECTION,则GetObject函数将不会填充BITMAPINFOHEADER的任何字段。
因此,检查所有这些值是否为空,如果是,那么:您知道它不是DIBSECTION(并且必须是BITMAP)。
function IsDibSection(bmp: HBITMAP): Boolean
{
Result := True; //assume that it is a DIBSECTION.
var ds: DIBSECTION = Default(DIBSECTION); //initialize everything to zeros
var res: Integer;
//Try to decode hbitmap as a DIBSECTION
res := GetObject(bmp, sizeof(ds), ref ds);
if (res = 0)
ThrowLastWin32Error();
//If the bitmap actually was a BITMAP (and not a DIBSECTION),
//then BitmapInfoHeader values will remain zeros
if ((ds.Bmih.biSize = 0)
and (ds.Bmih.biWidth = 0)
and (ds.Bmih.biHeight= 0)
and (ds.Bmih.biPlanes= 0)
and (ds.Bmih.biBitCount= 0)
and (ds.Bmih.biCompression= 0)
and (ds.Bmih.biSizeImage= 0)
and (ds.Bmih.biXPelsPerMeter= 0)
and (ds.Bmih.biYPelsPerMeter= 0)
and (ds.Bmih.biClrUsed= 0)
and (ds.Bmih.biClrImportant= 0))
Result := False; //it's not a dibsection
}