7

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?

IInspectable
  • 46,945
  • 8
  • 85
  • 181
  • Do we have any API functions that support alpha channel with `HBITMAP`s? Without the API, the bit layout does not make much sense. The existing way to define bit layout is [`BITMAPV4HEADER`](https://msdn.microsoft.com/en-us/library/dd183380) but it's used with functions like `StretchDIBits` which take bitmap data arguments, not bitmap handles. That is, when you are dealing with old school bitmap handles, it is as simple as assumed that no alpha channel is there and bit layout is fixed. – Roman R. Feb 12 '18 at 21:11
  • 1
    @RomanR. - *Do we have any API functions that support alpha channel with HBITMAP* - `AlphaBlend` with [`AC_SRC_ALPHA `](https://msdn.microsoft.com/en-us/library/windows/desktop/dd183393(v=vs.85).aspx) – RbMm Feb 12 '18 at 21:17
  • @RbMm: `AlphaBlend` is a sort of exception because it's a late arrival among the functions. Then it's fixed to take 32-bit RGB and I would expect that it does not care whether that alpha channel even exists: I think it's just assumed to be there and the fourth byte is treated as alpha. That is, `AlphaBlend` is a patch to offer at least some alpha/transparency where transparency was not supposed to be present in first place. – Roman R. Feb 12 '18 at 21:20
  • Also, I think it's woth quoting related comment from [this Q](https://stackoverflow.com/questions/47056848/win32-gdi-alphablend-not-using-constant-alpha-value-correctly): "GDI is strictly 24bpp and does not support alpha. AlphaBlend() was a hack to use a bitmap with transparency anyway, but the blend result is 24bpp. This kind of code went out of style almost 20 years ago, please use gdiplus.h" – Roman R. Feb 12 '18 at 21:24
  • @RomanR. - anyway AlphaBlend perfect display 32bpp with aplha. not once use it – RbMm Feb 12 '18 at 21:26
  • @RbMm: This is correct, but this is not what is asked in the question. If `AlphaBlend` treats 8 of 32 bits as alpha regardless of whether it's indeed alpha channel of the bitmap, it's one thing. However the question asks whether the bitmap was created with alpha channel intentionally in first place, and what bits are alpha. – Roman R. Feb 12 '18 at 21:28
  • @RomanR.: `AlphaBlend` is one of the API calls that got me puzzled. It takes an `HBITMAP` (by way of a device context), but I don't know, whether it *knows*, that the bitmap contains an alpha channel, or simply assumes one as part of its operation, regardless of its existence. – IInspectable Feb 12 '18 at 21:29
  • @IInspectable - this we say to api - [`AlphaFormat`](https://msdn.microsoft.com/en-us/library/windows/desktop/dd183393(v=vs.85).aspx) - *This member controls the way the source and destination bitmaps are interpreted* . `AC_SRC_ALPHA` - *This flag is set when the bitmap has an Alpha channel* – RbMm Feb 12 '18 at 21:34
  • `AlphaBlend` when we use `AC_SRC_ALPHA` simply call `GetObject` and check that this is 32bpp image. if no - return error, if yes - use high 8 bits as alpha – RbMm Feb 12 '18 at 21:58
  • @RomanR.: Actually, the sheer existence of `AlphaBlend` is a strong indication, that GDI does not support (or know about) alpha channels. Otherwise, [`SetStretchBltMode`](https://msdn.microsoft.com/en-us/library/dd145089.aspx) could have been extended to support alpha blending, without introducing a new API call. It appears, that (given an `HBITMAP`) you cannot determine the existence of an alpha channel. You have to know. It is an input, which can be as subtle as choosing between `StretchBlt` and `AlphaBlend`. – IInspectable Feb 12 '18 at 22:14
  • 3
    Assuming the only supported alpha format is ARGB, one could use heuristics to determine whether an alpha channel is present. Iterate over all pixels, if we find one where the A byte is > 0, we could assume it is an actual alpha channel. This fails for all-transparent / all-black images of course. – zett42 Feb 12 '18 at 22:17
  • @RbMm: That's helpful, actually. If `AlphaBlend` doesn't do any more validation than checking for a *potential* alpha channel, that information is likely not available. – IInspectable Feb 12 '18 at 22:17
  • Well, that was my point in previous comments. Back to your original WIC part of the question, I'd expect that WIC importing from `HBITMAP` follows the same assumptions: just in case of 32-bit HBITMAP it might reinterpret the A part as alpha (with a fixed bit layout), premultiplied alpha or ignore that. – Roman R. Feb 12 '18 at 22:17
  • @zett42: The `BITMAP` structure returned by `GetObject` does not hold a valid pointer to the actual bitmap data. You'd have to call `GetDIBits` (and friends) to get to that. I'm trying to avoid the extraneous copy/conversion. All the more, since that has the potential for false negatives as well as false positives. I'm really looking for a reliable solution, but it appears, that that does not exist. It looks like I'm going to have to add more inputs. – IInspectable Feb 12 '18 at 22:24
  • 2
    "Do we have any API functions that support alpha channel with HBITMAPs?" Yes, besides AlphaBlend, there are also APIs for ImageLists that can add 32-bit alpha-enabled HBITMAPs. But those all require passing extra flags to say, that, yes this HBITMAP has alpha. – Adrian McCarthy Feb 12 '18 at 23:35
  • @IInspectable - you know the answer to this question: Simply because we both know that at no point when creating a bitmap is there an opportunity to identify the bitmap as containing alpha - Other than being careful to ensure the bitmap is 32bpp. – Chris Becke Feb 13 '18 at 08:06
  • 2
    @ChrisBecke: [CreateDIBitmap](https://msdn.microsoft.com/en-us/library/dd183491.aspx) takes a [BITMAPV5HEADER](https://msdn.microsoft.com/en-us/library/dd183381.aspx) that is capable of describing existence and layout of an alpha channel. – IInspectable Feb 13 '18 at 13:07
  • @zett42 You have any idea how to get the image data of a device independent bitmap? GetDiBits can only get the image data of a compatible bitmap. I actually tried. And there is no function in the Bitmap reference that promises to do this. Except GetPixel which should be sub optimal and maybe not even give me the alpha channell. – user13947194 Jan 18 '23 at 16:03
  • @user13947194 Use 2nd code sample above to get a `DIBSECTION`. Then you have a read/write pointer to the raw pixel data in `.dsBm.bmBits`. – zett42 Jan 18 '23 at 17:36

2 Answers2

4

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

Adrian McCarthy
  • 45,555
  • 16
  • 123
  • 175
  • _"you'd probably use 24 bits per pixel to save space"_ -- When saving to disk, yes. But in memory, RGB images are usually represented by 32 bpp, even if they don't contain an alpha channel, mostly for performance reasons. So the all-black vs. all-transparent case keeps being uncertain. – zett42 Feb 13 '18 at 17:26
  • @zett42: In-memory RGB images are often 32-bit when they are device-dependent bitmaps (because the hardware likes 32-bit alignment). Device-independent bitmaps are often (but not always) represented in memory as they are stored on disk. But, yes, you cannot with certainty distinguish between all-black vs. all-transparent (even if you have access to the original files). Intent can only be inferred from how they're used. – Adrian McCarthy Feb 13 '18 at 17:42
  • 1
    In essence: Bitmaps support alpha channels by coincidence, using otherwise unused space in the memory layout. If you want to use that data, you are going to have to tell the system, i.e. *you* have to know. I guess, that answers my question. While the information on trying to guess might be useful to others, it isn't immediately applicable in my scenario. – IInspectable Feb 16 '18 at 10:45
1

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
}
Ian Boyd
  • 246,734
  • 253
  • 869
  • 1,219
  • I'm not sure this addresses the question that was asked. This determines, whether any given `HBITMAP` refers to a `BITMAP` or a `DIBSECTION`. Being a `DIBSECTION` is required for a bitmap to have an alpha channel, but it isn't sufficient. The code really just determines, what's in the question already. Or am I missing something here? – IInspectable Sep 12 '18 at 15:06