Windows GDI object HBITMAP
does not contain any flags that mark it as having a valid alpha channel.
It is possible to obtain some information about it through the function GetObject()
which can return the BITMAP
structure. This structure contains the bmBitsPixel
field that can be used to eliminate bitmap formats that are less than 32bpp
and thus cannot contain the alpha channel.
When bmBitsPixel==32
the format of the bitmap can be 0RGB
or ARGB
and as far as I know, the only way to distinguish between these two cases is checking ALL pixel values for the presence of non-zero Alpha value (please correct me if I am wrong). Even if 1 pixel has a non-zero Alpha value, Windows treats it as an ARGB
bitmap.
This determination has profound consequences for ICON
handling and the behavior of some function such as, but not limited to, the DrawIcon()
and DrawIconEx()
. These functions ignore or account for the monochrome mask (ICONINFO.hbmMask
) that contains the transparency data for the entire icon, depending on some flags and whether the format of the icon's color bitmap is 0RGB
or ARGB
.
The code below performs the classification of a DDB bitmap as 0RGB
or ARGB
by shrinking it to 1x1px
and testing for non-zero Alpha value.
Q: Is there a more optimal way to perform such classification ?
Note: I heard that bringing DDB's pixel data to User Mode is costly.
BOOL HasAlpha(HBITMAP hBmp)
{
BOOL res = FALSE;
BITMAP bmp = {0};
GetObject(hBmp, sizeof(BITMAP), &bmp);
if (bmp.bmBitsPixel == 32)
{
HDC hDCsrc = CreateCompatibleDC(0);
HBITMAP hBmpOldSrc = (HBITMAP)SelectObject(hDCsrc, hBmp);
HDC hDCdst = CreateCompatibleDC(hDCsrc);
HBITMAP hBmpDst = CreateCompatibleBitmap(hDCsrc, 1, 1);
HBITMAP hBmpOldDst = (HBITMAP)SelectObject(hDCdst, hBmpDst);
PDWORD pPixel;
SetStretchBltMode(hDCdst, STRETCH_ORSCANS); //STRETCH_ANDSCANS
StretchBlt(hDCdst, 0, 0, 1, 1, hDCsrc, 0, 0, bmp.bmWidth, bmp.bmHeight, SRCCOPY);
if (pPixel = (PDWORD)GetBMBits(hDCdst, DIB_RGB_COLORS))
{
if (*pPixel & 0xFF000000)
res = TRUE;
_aligned_free(pPixel);
}
SelectObject(hDCsrc, hBmpOldSrc);
DeleteObject(SelectObject(hDCdst, hBmpOldDst));
DeleteDC(hDCsrc);
DeleteDC(hDCdst);
}
return res;
}
LPVOID GetBMBits(HDC hDC, UINT usage) //When done must free the result with _aligned_free(). Also, usage = DIB_RGB_COLORS or DIB_PAL_COLORS
{
LPVOID pMem = NULL;
BITMAPINFO BmpInf[2]; BmpInf[0].bmiHeader.biBitCount = 0; BmpInf[0].bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
HBITMAP hBmp = HBITMAP(SelectObject(hDC, CreateCompatibleBitmap(hDC, 0, 0))); //Push out the bitmap that was in DC
if (GetDIBits(hDC, hBmp, 0, 1, NULL, &BmpInf[0], usage))
{
if (pMem = _aligned_malloc(BmpInf[0].bmiHeader.biSizeImage, sizeof(DWORD)))
{
if (!GetDIBits(hDC, hBmp, 0, BmpInf[0].bmiHeader.biHeight, pMem, &BmpInf[0], usage))
{
_aligned_free(pMem);
pMem = NULL;
}
}
}
DeleteObject(SelectObject(hDC, hBmp));
return pMem;
}
if you have HBITMAP
as input code can be next
BOOL HasAlpha(PBYTE pvBits, ULONG cb)
{
pvBits += 3;// pointer to alpha byte
cb >>= 2;
do
{
if (*pvBits)
{
break;
}
} while (pvBits += 4, --cb);
return cb;
}
BOOL HasAlpha(HBITMAP hBmp)
{
BITMAP bm;
if (GetObject(hBmp, sizeof(bm), &bm) &&
1 == bm.bmPlanes &&
32 == bm.bmBitsPixel)
{
ULONG cb = bm.bmWidthBytes * bm.bmHeight;
if (bm.bmBits)
{
return HasAlpha((PBYTE)bm.bmBits, cb);
}
if (bm.bmBits = LocalAlloc(LMEM_FIXED, cb))
{
if (cb = GetBitmapBits(hBmp, cb, bm.bmBits))
{
cb = HasAlpha((PBYTE)bm.bmBits, cb);
}
LocalFree(bm.bmBits);
return cb;
}
}
return FALSE;
}
as noted Simon Mourier some system DLLs (ExplorerFrame, shell32, Taskbar, twinui.pcshell, Windows.Internal.Shell.Broker, Windows.Storage, Windows.UI.CredDialogController) have internal function:
HRESULT _DoesBitmapHaveAlpha(_In_ HBITMAP hbmp, _Out_ bool * pHave);
which have similar implementation:
first call GetObject
, then GetDC(0)
and GetDIBits
. It does not check for BITMAP::bmBits
which can be already not 0, so we not need call GetDIBits
. I use GetBitmapBits
instead GetDIBits
. then function check only alpha byte from bmBits
and decide that alpha exist if some alpha byte not 0. I bit optimize my implementation, after I look for system code:
was
BOOL HasAlpha(PULONG pvBits, ULONG cb)
{
cb >>= 2;
do
{
if (*pvBits++ >> 24)
{
break;
}
} while (--cb);
return cb;
}
and I replace it with:
BOOL HasAlpha(PBYTE pvBits, ULONG cb)
{
pvBits += 3;// pointer to alpha byte
cb >>= 2;
do
{
if (*pvBits)
{
break;
}
} while (pvBits += 4, --cb);
return cb;
}
sense here - no more need >> 24
on DWORD
or & 0XFF000000
- we just read single appha byte, instead ARGB dword.
but my code stop loop after just found first not 0 alpha, when system implementation always iterate over all image bytes unconditionally ( which is not the best)
the _DoesBitmapHaveAlpha
is not exported, so we can not call it from any dll.
it always called from another not exported API:
HRESULT CreateBitmapFromIconWithAlpha(_In_ HICON hi, _In_ int size, _Out_ HBITMAP * pbmp);
which can be called from several locations, as example
HRESULT _CreateBitmapFromSystemImageListWithAlpha(
int iIndex, //= SHMapPIDLToSystemImageListIndex
int dwFlags,
int size,
HBITMAP *);
HRESULT CShellItem::_GetIcon(SIZE s, int dwFlags, bool b, HBITMAP *);
virtual HRESULT CShellItem::GetSharedBitmap(SIZE s, int dwFlags, ISharedBitmap * *);