给定一个 HBITMAP,有什么方法可以查明它是否包含 alpha 通道?

Given an HBITMAP, is there any way to find out, whether it contains an alpha channel?

为了改进 this answer,我正在寻找一种方法来确定通过 HBITMAP 引用的位图是否包含 alpha 通道。

我明白了,我可以调用 GetObject, and retrieve a 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 只处理 pre-multiplied alpha。这意味着,对于每个像素 A >= max(R, G, B)。因此,如果您愿意扫描所有像素,则可以排除一堆 24 位图像。如果该条件适用于所有像素 如果任何 A 是 non-zero,您几乎可以肯定有一个 alpha 通道(或损坏的图像)。

基本上,唯一不确定的是 all-transparent 图像与 all-black 图像,两者都由所有通道设置为零的像素组成。也许在这种情况下进行有根据的猜测就足够了。我猜是的,因为拥有 32 位 DIB 部分是一个非常强的信号。如果你有一个 32 位 device-dependent 位图,那么它没有 alpha,如果你有一个没有 alpha 的 device-independent 位图,你可能会使用每像素 24 位来保存 space.

一些更详细的位图信息 header 可以告诉您是否为 alpha 通道保留了位。例如,请参阅 BITMAPV5HEADER,它有一个掩码指示哪些位是 alpha 通道(尽管文档说了一些矛盾的事情)。 BITMAPV4HEADER 也是如此。不幸的是,我认为没有办法从 HBITMAP 中获取此版本的 header。 (而且我确定那里有 alpha-enabled 位图文件没有正确设置这些字段。)

GDI 不处理 alpha 通道是 well-known(AlphaBlend 除外,它不采用 HBITMAP 但将选定的通道访问到内存 DC 中)。有一些用户 APIs,比如 UpdateLayeredWindow 可以处理带有 alpha 通道的图像,但是,像 AlphaBlend 一样,将位图数据从选定的信息中提取到内存 DC 中。 LoadImage,如果传递了正确的标志,将在加载要由 HBITMAP 访问的 DIB 时保留 alpha 通道。
ImageList_Add,如果使用适当的标志创建图像列表,它确实采用 HBITMAP,将保留和 alpha 通道。然而,在所有这些情况下,调用者必须知道位图数据包含正确的 alpha 数据并为 API 设置正确的标志。这表明无法从位图句柄中轻松获得信息。

如果您有权访问从中加载图像的位图资源或文件,则可以查看原始 header 是否使用 BI_BITFIELDS 并指定了 alpha 通道,但是您在所有情况下都无法从 HBITMAP 获取 header。 (还有人担心 header 没有正确填写。)

可以通过GetObject函数间接获取。隐藏在文档中的是一条注释:

If hgdiobj is a handle to a bitmap created by calling CreateDIBSection, and the specified buffer is large enough, the GetObject function returns a DIBSECTION structure. In addition, the bmBits member of the BITMAP structure contained within the DIBSECTION will contain a pointer to the bitmap's bit values.

If hgdiobj is a handle to a bitmap created by any other means, GetObject returns only the width, height, and color format information of the bitmap. You can obtain the bitmap's bit values by calling the GetDIBits or GetBitmapBits function.

(强调我的)

换句话说:如果你试图解码它是一个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
}