Reputation: 9662
Under Win32, it is a common technique to generate a monochrome bitmask from a bitmap for transparency use by doing the following:
SetBkColor(hdcSource, clrTransparency);
VERIFY(BitBlt(hdcMask, 0, 0, bm.bmWidth, bm.bmHeight, hdcSource, 0, 0, SRCCOPY));
This assumes that hdcSource is a memory DC holding the source image, and hdcMask is a memory DC holding a monochrome bitmap of the same size (so both are 32x32, but the source is 4 bit color, while the target is 1bit monochrome).
However, this seems to fail for me when the source is 32 bit color + alpha. Instead of getting a monochrome bitmap in hdcMask, I get a mask that is all black. No bits get set to white (1). Whereas this works for the 4bit color source.
My search-foo is failing, as I cannot seem to find any references to this particular problem.
I have isolated that this is indeed the issue in my code: i.e. if I use a source bitmap that is 16 color (4bit), it works; if I use a 32 bit image, it produces the all-black mask.
Is there an alternate method I should be using in the case of 32 bit color images? Is there an issue with the alpha channel that overrides the normal behavior of the above technique?
Thanks for any help you may have to offer!
ADDENDUM: I am still unable to find a technique that creates a valid monochrome bitmap for my GDI+ produced source bitmap.
I have somewhat alleviated my particular issue by simply not generating a monochrome bitmask at all, and instead I'm using TransparentBlt(), which seems to get it right (but I don't know what they're doing internally that's any different that allows them to correctly mask the image).
It might be useful to have a really good, working function:
HBITMAP CreateTransparencyMask(HDC hdc, HBITMAP hSource, COLORREF crTransparency);
Where it always creates a valid transparency mask, regardless of the color depth of hSource.
Upvotes: 10
Views: 5634
Reputation: 2125
Yes, you can construct a monochrome mask out of an Alpha channel in a 32bpp bitmap with GDI calls only, and without scanning the individual pixels. See the code below:
HBITMAP Alpha2Mask(HBITMAP hBmpAlpha)
{
HBITMAP hBmpMask = 0;
BITMAP bmp;
GetObject(hBmpAlpha, sizeof(BITMAP), &bmp);
if (bmp.bmBitsPixel == 32)
{
HDC hDCsrc = CreateCompatibleDC(0);
HBITMAP hBmpOldSrc = (HBITMAP)SelectObject(hDCsrc, hBmpAlpha);
HDC hDCdst = CreateCompatibleDC(hDCsrc);
HBITMAP hBmpDst = CreateCompatibleBitmap(hDCsrc, bmp.bmWidth, bmp.bmHeight);
HBITMAP hBmpOldDst = (HBITMAP)SelectObject(hDCdst, hBmpDst);
TransparentBlt(hDCdst, 0, 0, bmp.bmWidth, bmp.bmHeight, hDCsrc, 0, 0, bmp.bmWidth, bmp.bmHeight, RGB(0, 0, 0)); //Copy the pixel RGB volues and set Alpha to zero
BitBlt(hDCdst, 0, 0, bmp.bmWidth, bmp.bmHeight, hDCsrc, 0, 0, SRCINVERT); // Set all pixels to black but leave alpha channel unchanged.
HBITMAP hBmpSrc = CreateCompatibleBitmap(hDCsrc, bmp.bmWidth, bmp.bmHeight);
SelectObject(hDCsrc, hBmpSrc); // Pushes out hBmpAlpha
SetDCBrushColor(hDCsrc, RGB(0, 0, 0));
BitBlt(hDCsrc, 0, 0, bmp.bmWidth, bmp.bmHeight, 0, 0, 0, PATCOPY); //Set all pixels to white and leave the alpha transparent
BLENDFUNCTION bfn; bfn.BlendOp = AC_SRC_OVER; bfn.BlendFlags = 0; bfn.SourceConstantAlpha = 255; bfn.AlphaFormat = AC_SRC_ALPHA;
AlphaBlend(hDCsrc, 0, 0, bmp.bmWidth, bmp.bmHeight, hDCdst, 0, 0, bmp.bmWidth, bmp.bmHeight, bfn); //After this, the hBmpSrc contains the per-pixel Alpha conveted to grayscale RGB.
hBmpMask = CreateBitmap(bmp.bmWidth, bmp.bmHeight, 1, 1, NULL);
SelectObject(hDCdst, hBmpMask); //Reuse the hDCdst as the final output DC. Push out the hBmpDst
SetBkColor(hDCsrc, RGB(0xFF, 0xFF, 0xFF)); //Set the background to white
BitBlt(hDCdst, 0, 0, bmp.bmWidth, bmp.bmHeight, hDCsrc, 0, 0, SRCCOPY); //All pixels equal to the background color end up white in the hBmpMask (meaning "transparent") while everythig else ends up black (meaning "opaque").
DeleteObject(SelectObject(hDCsrc, hBmpOldSrc)); //Push out and delete hBmpSrc containing the Alpha converted to grayscale
hBmpMask = (HBITMAP)SelectObject(hDCdst, hBmpOldDst); //Push out hBmpMask
DeleteDC(hDCsrc);
DeleteDC(hDCdst);
}
return hBmpMask; //Return the monochromatic (1bpp) mask
}
If you do not mind the hBmpAlpha
getting clobbered then you can use the functions below, that do the same job, but in-place.
HBITMAP Alpha2Mask_inplace(HBITMAP hBmpAlpha)
{
HBITMAP hBmpMask = 0;
BITMAP bmp, bmp_dbg;;
GetObject(hBmpAlpha, sizeof(BITMAP), &bmp);
if (bmp.bmBitsPixel == 32)
{
HDC hDCsrc = CreateCompatibleDC(0);
HBITMAP hBmpOldSrc = (HBITMAP)SelectObject(hDCsrc, hBmpAlpha);
AndRGBArect(hDCsrc, 0, 0, bmp.bmWidth, bmp.bmHeight, { 0,0,0,0xFF }); //Set all pixels to black but leave alpha chanel unchanged. Destroys hBmpAlpha.
HDC hDCdest = CreateCompatibleDC(hDCsrc);
HBITMAP hBmpDest = CreateCompatibleBitmap(hDCsrc, bmp.bmWidth, bmp.bmHeight);
HBITMAP hBmpOldDst = (HBITMAP)SelectObject(hDCdest, hBmpDest);
SelectObject(hDCdest, GetStockObject(WHITE_BRUSH));
BitBlt(hDCdest, 0, 0, bmp.bmWidth, bmp.bmHeight, 0, 0, 0, PATCOPY); //Set all pixels to white and leave the alpha transparent
BLENDFUNCTION bfn; bfn.BlendOp = AC_SRC_OVER; bfn.BlendFlags = 0; bfn.SourceConstantAlpha = 255; bfn.AlphaFormat = AC_SRC_ALPHA;
AlphaBlend(hDCdest, 0, 0, bmp.bmWidth, bmp.bmHeight, hDCsrc, 0, 0, bmp.bmWidth, bmp.bmHeight, bfn); //After this, the hBmpDest contains the per-pixel Alpha conveted to grayscale RGB.
hBmpMask = CreateBitmap(bmp.bmWidth, bmp.bmHeight, 1, 1, NULL);
SelectObject(hDCsrc, hBmpMask); //Reuse the hDCsrc as the final output DC (a deceptive name now). Push out the hBmpAlpha
SetBkColor(hDCdest, RGB(0xFF, 0xFF, 0xFF)); //Set the background to white
BitBlt(hDCsrc, 0, 0, bmp.bmWidth, bmp.bmHeight, hDCdest, 0, 0, SRCCOPY); //All pixels equal to the background color end up white in the hBmpMask (meaning "transparent") while everythig else ends up black (meaning "opaque").
DeleteObject(SelectObject(hDCdest, hBmpOldDst)); //Push out and delete hBmpDest
hBmpMask = (HBITMAP)SelectObject(hDCsrc, hBmpOldSrc); //Push out hBmpMask
DeleteDC(hDCdest);
DeleteDC(hDCsrc);
}
return hBmpMask; //Return the monochromatic (1bpp) mask
}
The function Alpha2Mask_inplace()
requires the helper function below:
void AndRGBArect(HDC hdc, int x, int y, int cx, int cy, RGBQUAD rgba)
{
BITMAPINFO bi = {};
bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bi.bmiHeader.biWidth = 1;
bi.bmiHeader.biHeight = 1;
bi.bmiHeader.biPlanes = 1;
bi.bmiHeader.biBitCount = 32;
bi.bmiHeader.biCompression = BI_RGB; // BI_ALPHABITFIELDS;
StretchDIBits(hdc, x, y, cx, cy, 0, 0, 1, 1, &rgba, &bi, DIB_RGB_COLORS, SRCAND);
}
Upvotes: 0
Reputation: 1385
The method that worked for me was to convert the bitmap from 32 bit to 24 bit first.
1. CreateCompatibleDC
2. CreateDIBSection with 24 as the biBitCount.
3. SelectObject
4. BitBlt from 32bit DC to 24 bit. This removes alpha.
5. BitBlt from 24 bit DC to the monochrome DC works as expected.
On my machine this executes faster than the double loop from Ujjwal's answer.
Upvotes: 2
Reputation: 4998
Can do :)
As pointed out by 'Chris Becke' above, GDI can compare only if the reserved Alpha channel is zero.
The HBITMAP got from BITMAP::GetHBITMAP() returns an HBITMAP with Alpha Channel all set to 0xFF.
This must be 0x00 for SetBkColor() Comparison to work.
Hence, the Soln: Loop through each pixel and set the Alpha Component to Zero.
Bitmap img(L"X.bmp");
HBITMAP hBM;
img.GetHBITMAP(Color::White, &hBM);
BITMAP bm;
GetObject(g_hbmBall, sizeof(BITMAP), &bm);
for(UINT i = 0, n = -1; i < bm.bmHeight; i++)
for(UINT j = 0; j < bm.bmWidth; j++)
{
n += 4; // Once per Pixel of 4 Bytes
((LPBYTE)bm.bmBits)[n] = 0;
}
// Now SetBkColor and BitBlt will work as expected
Upvotes: 3
Reputation: 48021
An alternate method would be to scan the pixels yourself and generate and monochrome bitmap based on the source color (or source alpha versus a threshold).
Note that if you're using GDI+, then, depending on the operation, the pixels may have been antialiased, resulting in none of them being an exact match for your "transparent" color.
Upvotes: 0
Reputation: 36101
You can't do it if there is an alpha channel. COLORREF's use the top 8 bits for a number of purposes, including specifying wether or not the lower 3 bytes are a color table index into the current palette, or a RGB triplet. As such you can't specify anything except 0x00 in the upper byte of clrTransparency.
If you have an alpha bitmap then, to GDI that remains "unaware" of the alpha channel, theres no sane way to actually compare a 24bit BkColor with 32bit pixels in the bitmap.
I would expect GDI to treat the alpha channel in 32bpp bitmaps as "Reserved", and only successfully compare pixels where the reserved channel is zero. i.e. your mask color must be fully transparent anyway to have a chance of succeeding. (and, if youve made a legitimate premultiplied bitmap, that implies the RGV values would be zero too, rather constraining your choice of mask colors :P)
Upvotes: 5