鉴于HBITMAP,有没有办法找出它是否包含alpha通道?

时间:2018-02-12 20:04:50

标签: winapi gdi

为了改进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通道(以及哪些位用于它),或者这些信息根本不可用?

2 个答案:

答案 0 :(得分:3)

你无法明确地知道,但如果你愿意迭代像素,你可以做出良好的教育猜测..

(暂时忽略带有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获取该标头在所有情况下。 (并且仍然存在没有正确填写标题的担忧。)

答案 1 :(得分:0)

您可以使用GetObject函数间接获取它。在文档中隐藏了一条注释:

  

如果 hgdiobj 是通过调用CreateDIBSection创建的位图的句柄,并且指定的缓冲区足够大,则 GetObject 函数将返回{{3 }} 结构体。此外, DIBSECTION 中包含的DIBSECTION结构的 bmBits 成员将包含一个指向位图位值的指针。

     

如果 hgdiobj 是通过任何其他方式创建的位图的句柄,则 GetObject 仅返回位图的宽度,高度和颜色格式信息。 您可以通过调用BITMAPGetDIBits函数来获取位图的位值。

(重点是我的)

换句话说:如果您尝试解码它是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作为分区,则 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
}