Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

winapi

gdi

In an attempt to improve this answer, I am looking for a way to determine, whether a bitmap referenced through an HBITMAP contains an alpha channel.

I understand, that I can call GetObject, and retrieve a BITMAP structure:

BITMAP bm = { 0 };
::GetObject(hbitmap, sizeof(bm), &bm);

But that only gets me the number of bits required to store the color of a pixel. It doesn't tell me, which bits are actually used, or how they relate to the individual channels. For example, a 16bpp bitmap could encode a 5-6-5 BGR image, or a 1-5-5-5 ABGR image. Likewise, a 32bpp bitmap could store ABGR or xBGR data.

I could take it a step further, and probe for a DIBSECTION instead (if available):

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 );
}

While this can disambiguate the 16bpp case (using the dsBitfields member), it still fails to determine existence of an alpha channel in the case of a 32bpp image.

Is there any way to find out, whether a bitmap referenced through an HBITMAP contains an alpha channel (and which bit(s) are used for it), or is this information simply not available?

like image 488
IInspectable Avatar asked Feb 12 '18 20:02

IInspectable


2 Answers

You cannot know definitively, but you can make a good educated guess if you're willing to iterate through the pixels..

(Ignoring the 16-bit color with 1-bit alpha channels, for now anyway.)

For there to be an alpha channel, it's necessary (but not sufficient) for the bitmap to be a DIB section and to have 32 bits per pixel. As noted in the question, you can check for those requirements.

We also know that Windows deals only with pre-multiplied alpha. That means, for every pixel A >= max(R, G, B). So, if you're willing to scan all the pixels, you can rule out a bunch of 24-bit images. If that condition holds for all pixels and if any of the A's are non-zero, you almost certainly have an alpha channel (or a corrupted image).

Basically, the only uncertainty left is the all-transparent image versus the all-black image, both of which consist of pixels with all channels set to zero. Perhaps it's sufficient to take an educated guess in that case. I would guess yes, since having a 32-bit DIB section is a pretty strong signal. If you had a 32-bit device-dependent bitmap, then it doesn't have alpha, and if you had a device-independent bitmap without alpha, you'd probably use 24 bits per pixel to save space.

Some of the more detailed bitmap info headers can tell you if bits are reserved for the alpha channel. For example, see BITMAPV5HEADER, which has a mask indicating which bits are the alpha channel (though the documentation says some contradictory things). Likewise for the BITMAPV4HEADER. Unfortunately, I don't think there's a way to get this version of the header from an HBITMAP. (And I'm certain there are alpha-enabled bitmap files out there that don't set these fields correctly.)

It's well-known that GDI doesn't handle alpha channels (with the exception of AlphaBlend, which doesn't take an HBITMAP but accesses one selected into a memory DC). There are user APIs, like UpdateLayeredWindow that work with images with alpha channels, but, like AlphaBlend, take the bitmap data from the information selected into a memory DC. LoadImage, if passed the correct flags, will preserve the alpha channel when loading a DIB to be accessed by HBITMAP.
ImageList_Add, which does take an HBITMAP, will preserve and alpha channel if the image list is created with the proper flags. In all these cases, however, the caller must know that the bitmap data contains proper alpha data and set the right flags for the API. This suggests that the information is not readily available from the bitmap handle.

If you have access to the bitmap resource or file that the image was loaded from, it is possible to see if the original header uses BI_BITFIELDS and has an alpha channel specified, but you can't get to that header from the HBITMAP in all cases. (And there's remains the concern that the header isn't filled out correctly.)

like image 196
Adrian McCarthy Avatar answered Oct 26 '22 02:10

Adrian McCarthy


You can get it indirectly by using GetObject function. Tucked away in the documentation is a note:

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.

(emphasis mine)

In other words: if you try to decode it is a DIBSECTION, but it's only a BITMAP, then

dibSection.BitmapInfoHeader

will not be updated. (e.g. left as zeros)

It's helpful to remember how a BITMAP and a DIBSECTION differ:

| 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          |

If you try to get a BITMAP as a DIBSECTION, the GetObject function will not fill in any fields of the BITMAPINFOHEADER.

So check if all those values are empty, and if so: you know it's not a DIBSECTION (and must be a BITMAP).

Sample

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
}
like image 20
Ian Boyd Avatar answered Oct 26 '22 02:10

Ian Boyd