5.3. Taking Advantage of the Alpha Channel

If we have a display with an alpha channel (that usually means 32 bits per pixel) the code of the previous section becomes much simpler. The code of Listing B - part 1 must be repeated, but the code of parts 2 to 5 is much simpler.

	// Listing C - part 1 same as Listing B - part 1
	// Listing C - part 2 replaces Listing B - part 2 to part 5
	b.SetBitmapBits(m_spot.Width()* m_spot.Height()*4, m_bits);
	CDC mdc;
	mdc.CreateCompatibleDC(&cdc);
	mdc.SelectObject(&b);
	BLENDFUNCTION v = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA };
	cdc.AlphaBlend(point.x-m_rad, point.y-m_rad,
		m_spot.Width(), m_spot.Height(),
		&mdc, 0, 0, m_spot.Width(), m_spot.Height(), v);

Of course this code is quite specific to MFC (and only for its versions after 2000) and in particular the method AlphaBlend and its last argument. The values of the BLENDFUNCTION structure are fixed with the exception of the third member. The value 255 indicates that alpha is different for each pixel. Otherwise the value of the third member is taken as that of alpha. While we have replaced about 20 lines of code by 6, we have done so at the expense of limiting the platforms of the program to those that support an alpha channel.

5.4. Bitmaps with Transparency using Masks

If the bitmap has parts that are either completely transparent or completely opaque, we have another option that does not reply on a blending formula such as that given in Section 3.1.

Most graphics devices provide the opportunity to select a logical operation for combining the source bitmap with the contents of the destination. The problem with bitmaps that contain transparent parts is that we want to use a replacement operation for the non-transparent parts and do not thing for the transparent parts. A common way is to define a binary mask that has the same dimensions as the bitmap and it is 1 for the transparent parts and 0 for the rest. Furthermore we set the pixels of the transparent part in the original bitmap to 0. If we first display (using a BitBlt call) the mask using an AND operation, then the pixels that correspond to the non-transparent parts of the bitmap will take the value 0 and the rest will remain unchanged (think of this is a multiplication between the mask and what is already on the display). If we display the original bitmap next using and OR operation, the parts withe pixels with value 0 will not affect the display and the rest will replace what is already on the display, achieving the desired effect.

Here is an implementation of this idea using MFC and based on Chris Becke's tutorial on bitmaps that is part of Microsoft's MVPS (Most Valuable Professional Site). Recent versions of MFC have a MaskBlt() method that can be used to display transparent bitmaps but there is a warning that "not all devices support" the method and, furthermore, it requires the passing of an already constucted mark bitmap. The construction of the mask is by no means trivial and the MVPS method constructs that bitmap as well. (This is actually where most the code lies.) In this description I assume that the object to be displayed has four members, the first three needed for its display and the fourth chosen by the user.

  HBITMAP m_Pix;
  HBITMAP m_PixMask;
  CSize m_PixDim;
  CPoint m_PixPos;
The initialization code deals with the creation of the transparent bitmap where ResID is the resource number of the bitmap, for example IDB_BITMAP1.
#define  SelectBitmap(dc,bm)  ((HBITMAP)SelectObject((dc),(HGDIOBJ)(bm)))

BOOL CBitmapObject::InitPlayer(int ResID, CPoint origin)
{
  // using MVPS method (gdi02)
  m_Pix = (HBITMAP)::LoadImageA(AfxGetResourceHandle(),
    MAKEINTRESOURCE(ResID), IMAGE_BITMAP, 0, 0, 0);
  ASSERT(m_Pix != NULL);
  BITMAP bm;
  GetObject(m_Pix, sizeof(bm), &bm);
  m_PixDim.SetSize(bm.bmWidth, bm.bmHeight);
  m_PixMask = CreateBitmap(bm.bmWidth, bm.bmHeight, 1, 1, NULL);
  // copied from MVPS
  HDC hdcSrc = CreateCompatibleDC(NULL);
  HDC hdcDst = CreateCompatibleDC(NULL);

  HBITMAP hbmSrcT = SelectBitmap(hdcSrc, m_Pix);
  HBITMAP hbmDstT = SelectBitmap(hdcDst, m_PixMask);
  // set background color to that of upper left corner of m_Pix
  COLORREF clrTopLeft = GetPixel(hdcSrc,0,0);
  COLORREF clrSaveBk  = SetBkColor(hdcSrc,clrTopLeft);//1
  // This call sets up the mask bitmap.
  BitBlt(hdcDst, 0, 0, bm.bmWidth, bm.bmHeight, hdcSrc, 0, 0, SRCCOPY);//2
  COLORREF clrSaveDstText = SetTextColor(hdcSrc, RGB(255, 255, 255));//3
  SetBkColor(hdcSrc, RGB(0,0,0));
  BitBlt(hdcSrc, 0, 0, bm.bmWidth, bm.bmHeight, hdcDst, 0, 0, SRCAND);//4
  // cleanup
  SetBkColor(hdcSrc,clrSaveBk);
  SelectBitmap(hdcSrc,hbmSrcT);
  SelectBitmap(hdcDst,hbmDstT);
  DeleteDC(hdcSrc);
  DeleteDC(hdcDst);

  m_PixPos.x = origin.x;
  return TRUE;
}

What the above code does is detect the color of the top left pixel of the bitmap and assume that all pixels of that color should be transparent. m_PixMask is created as one bit map (the arguments 1, 1 mean one plane, one bit) and the original bitmap is copied upon it. Because the background color was set to that of the top left corner (statement 1), statement 2 will map pixels with that color to 1 in the 1-bit bitmap and the all the rest will map to 0. (The background "color" of a bitmap is white, and is stored as binary 0. This is historical with bitmaps of letters were defined with 1 for black and 0 for white, which of course the opposite of what we expect on the basis of brightness.) Statement 3 sets the foreground to white and the following statement sets the background to black. (We use the "text" functions because in reality they deal with any 1-bit bitmap besides text.) Statement 4 performs a logical AND between the pixels of the mask and the original thus making all pixels with the bacround color equal to 0. If the background was 0 to start with, steps 3-4 are not needed.

The code for drawing the bitmap that is shown below. First we perform an AND operation between the mask and the image. Wherever the mask is 0 the destination is set to 0, otherwise is left alone. In essence, we open a "hole" where the nontransparent part of the bitmap will go. Next we copy the color bitmap using the SRCPAINT mode that performs a logical OR operation. Since the destination pixels had been zeroed before we copy the color part. Ans since the transparent pixels had been set to 0 before, they are left untouched!

void CBitmapObject::Draw(CDC *pDC, CPoint spot)
{
  // compute m_PixPos from spot. spot is usually the mouse position
  HDC hdcMem = CreateCompatibleDC(NULL);
  CDC dcMemory;
  dcMemory.Attach(hdcMem);
  HBITMAP hbmT = SelectBitmap(hdcMem, m_PixMask);
  pDC->BitBlt(m_PixPos.x, m_PixPos.y, m_PixDim.cx, m_PixDim.cy,
    &dcMemory, 0, 0, SRCAND);
  SelectBitmap(hdcMem, m_Pix);
  pDC->BitBlt(m_PixPos.x, m_PixPos.y, m_PixDim.cx, m_PixDim.cy,
    &dcMemory, 0, 0, SRCPAINT);
  SelectBitmap(hdcMem, hbmT);
  DeleteDC(hdcMem);
}

If we were going to use the MaskBlt() method of CDC the code would have been

// WARNING: UNTESTED CODE
void CBitmapObject::Draw(CDC *pDC, CPoint spot)
{
  // compute m_PixPos from spot. spot is usually the mouse position
  // ....
  pDC->BitBlt(m_PixPos.x, m_PixPos.y, m_PixDim.cx, m_PixDim.cy,
    &dcMemory, 0, 0, &m_PixMask, 0, 0, MAKEROP4(SRCAND, SRCPAINT);
   ... 
}
Previous Section Next Section