Transparency and AlphaBlending
- by TechTwaddle
In this post we'll look at the AlphaBlend() api and how it can be used for semi-transparent blitting. AlphaBlend() takes a source device context and a destination device context (DC) and combines the bits in such a way that it gives a transparent effect. Follow the links for the msdn documentation.
So lets take a image like,
and AlphaBlend() it on our window. The code to do so is below, (under the WM_PAINT message of WndProc)
HBITMAP hBitmap=NULL, hBitmapOld=NULL;
HDC hMemDC=NULL;
BLENDFUNCTION bf;
hdc = BeginPaint(hWnd, &ps);
hMemDC = CreateCompatibleDC(hdc);
hBitmap = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP1));
hBitmapOld = SelectObject(hMemDC, hBitmap);
bf.BlendOp = AC_SRC_OVER;
bf.BlendFlags = 0;
bf.SourceConstantAlpha = 80; //transparency value between 0-255
bf.AlphaFormat = 0;
AlphaBlend(hdc, 0, 25, 240, 100, hMemDC, 0, 0, 240, 100, bf);
SelectObject(hMemDC, hBitmapOld);
DeleteDC(hMemDC);
DeleteObject(hBitmap);
EndPaint(hWnd, &ps);
The code above creates a memory DC (hMemDC) using CreateCompatibleDC(), loads a bitmap onto the memory DC and AlphaBlends it on the device DC (hdc), with a transparency value of 80. The result is:
Pretty simple till now. Now lets try to do something a little more exciting. Lets get two images involved, each overlapping the other, giving a better demonstration of transparency. I am also going to add a few buttons so that the user can increase or decrease the transparency by clicking on the buttons. Since this is the first time I played around with GDI apis, I ran into something that everybody runs into sometime or the other, flickering. When clicking the buttons the images would flicker a lot, I figured out why and used something called double buffering to avoid flickering. We will look at both my first implementation and the second implementation just to give the concept a little more depth and perspective.
A few pre-conditions before I dive into the code:
- hBitmap and hBitmap2 are handles to the two images obtained using LoadBitmap(), these variables are global and are initialized under WM_CREATE
- The two buttons in the application are labeled Opaque++ (make more opaque, less transparent) and Opaque-- (make less opaque, more transparent)
- DrawPics(HWND hWnd, int step=0); is the function called to draw the images on the screen. This is called from under WM_PAINT and also when the buttons are clicked. When Opaque++ is clicked the 'step' value passed to DrawPics() is +20 and when Opaque-- is clicked the 'step' value is -20. The default value of 'step' is 0
Now lets take a look at my first implementation:
//this funciton causes flicker, cos it draws directly to screen several times
void DrawPics(HWND hWnd, int step)
{
HDC hdc=NULL, hMemDC=NULL;
BLENDFUNCTION bf;
static UINT32 transparency = 100;
//no point in drawing when transparency is 0 and user clicks Opaque--
if (transparency == 0 && step < 0)
return;
//no point in drawing when transparency is 240 (opaque) and user clicks Opaque++
if (transparency == 240 && step > 0)
return;
hdc = GetDC(hWnd);
if (!hdc)
return;
//create a memory DC
hMemDC = CreateCompatibleDC(hdc);
if (!hMemDC)
{
ReleaseDC(hWnd, hdc);
return;
}
//while increasing transparency, clear the contents of screen
if (step < 0)
{
RECT rect = {0, 0, 240, 200};
FillRect(hdc, &rect, (HBRUSH)GetStockObject(WHITE_BRUSH));
}
SelectObject(hMemDC, hBitmap2);
BitBlt(hdc, 0, 25, 240, 100, hMemDC, 0, 0, SRCCOPY);
SelectObject(hMemDC, hBitmap);
transparency += step;
if (transparency >= 240)
transparency = 240;
if (transparency <= 0)
transparency = 0;
bf.BlendOp = AC_SRC_OVER;
bf.BlendFlags = 0;
bf.SourceConstantAlpha = transparency;
bf.AlphaFormat = 0;
AlphaBlend(hdc, 0, 75, 240, 100, hMemDC, 0, 0, 240, 100, bf);
DeleteDC(hMemDC);
ReleaseDC(hWnd, hdc);
}
In the code above, we first get the window DC using GetDC() and create a memory DC using CreateCompatibleDC(). Then we select hBitmap2 onto the memory DC and Blt it on the window DC (hdc). Next, we select the other image, hBitmap, onto memory DC and AlphaBlend() it over window DC. As I told you before, this implementation causes flickering because it draws directly on the screen (hdc) several times. The video below shows what happens when the buttons were clicked rapidly:
Well, the video recording tool I use captures only 15 frames per second and so the flickering is not visible in the video. So you're gonna have to trust me on this, it flickers (;
To solve this problem we make sure that the drawing to the screen happens only once and to do that we create an additional memory DC, hTempDC. We perform all our drawing on this memory DC and finally when it is ready we Blt hTempDC on hdc, and the images are displayed in one go. Here is the code for our new DrawPics() function:
//no flicker
void DrawPics(HWND hWnd, int step)
{
HDC hdc=NULL, hMemDC=NULL, hTempDC=NULL;
BLENDFUNCTION bf;
HBITMAP hBitmapTemp=NULL, hBitmapOld=NULL;
static UINT32 transparency = 100;
//no point in drawing when transparency is 0 and user clicks Opaque--
if (transparency == 0 && step < 0)
return;
//no point in drawing when transparency is 240 (opaque) and user clicks Opaque++
if (transparency == 240 && step > 0)
return;
hdc = GetDC(hWnd);
if (!hdc)
return;
hMemDC = CreateCompatibleDC(hdc);
hTempDC = CreateCompatibleDC(hdc);
hBitmapTemp = CreateCompatibleBitmap(hdc, 240, 150);
hBitmapOld = (HBITMAP)SelectObject(hTempDC, hBitmapTemp);
if (!hMemDC)
{
ReleaseDC(hWnd, hdc);
return;
}
//while increasing transparency, clear the contents
if (step < 0)
{
RECT rect = {0, 0, 240, 150};
FillRect(hTempDC, &rect, (HBRUSH)GetStockObject(WHITE_BRUSH));
}
SelectObject(hMemDC, hBitmap2);
//Blt hBitmap2 directly to hTempDC
BitBlt(hTempDC, 0, 0, 240, 100, hMemDC, 0, 0, SRCCOPY);
SelectObject(hMemDC, hBitmap);
transparency += step;
if (transparency >= 240)
transparency = 240;
if (transparency <= 0)
transparency = 0;
bf.BlendOp = AC_SRC_OVER;
bf.BlendFlags = 0;
bf.SourceConstantAlpha = transparency;
bf.AlphaFormat = 0;
AlphaBlend(hTempDC, 0, 50, 240, 100, hMemDC, 0, 0, 240, 100, bf);
//now hTempDC is ready, blt it directly on hdc
BitBlt(hdc, 0, 25, 240, 150, hTempDC, 0, 0, SRCCOPY);
SelectObject(hTempDC, hBitmapOld);
DeleteObject(hBitmapTemp);
DeleteDC(hMemDC);
DeleteDC(hTempDC);
ReleaseDC(hWnd, hdc);
}
This function is very similar to the first version, except for the use of hTempDC. Another point to note is the use of CreateCompatibleBitmap(). When a memory device context is created using CreateCompatibleDC(), the context is exactly one monochrome pixel high and one monochrome pixel wide. So in order for us to draw anything onto hTempDC, we first have to set a bitmap on it. We use CreateCompatibleBitmap() to create a bitmap of required dimension (240x150 above), and then select this bitmap onto hTempDC. Think of it as utilizing an extra canvas, drawing everything on the canvas and finally transferring the contents to the display in one scoop. And with this version the flickering is gone, video follows:
If you want the entire solutions source code then leave a message, I will share the code over SkyDrive.