Transparent Bitmap - True Mask Method

This code shows you how to draw a transparent bitmap using memory rather than directly using the screen. This is important as intensive bitmap operations are slow due to the number of bits that are affected, and using the screen makes things even worse; some flicker results. The visual flickering can be eliminated by using memory bitmaps:

  1. Copy the section of the screen that is to be affected to a memory bitmap.
  2. Carry out the bitmap operation on the memory bitmap instead of on the screen.
  3. Copy the memory bitmap back to the screen.

The result is that only one blt affects the screen, so there is no flicker. We may be using two more blt operations, but the operation will probably be perceived as quicker as there is no flicker.

N.B. Some device memory blts are faster than blts that have to access the screen.

MSDN

I had written the source code provided many months ago now for a project I was working on. When I decided to submit it to CodeGuru I had to look at MSDN again to refresh my memory and help me construct a decent explanation for the technique that I use.

One article in particular is called 'Bitmaps with Transparency by Ron Gery (Microsoft Developer Network Technology Group)'. His explanation of transparent blts is excellent, and I have used this as my reference for constructing the following brief explanation. If you are still a bit unclear I recommend you read some of the articles that helped this one along...

The True Mask Method

True mask blting does not need any modification on the part of the source bitmap to be useful. The masked blt involves a three-step process and a mask that has all transparent pixels set to 1 and all opaque pixels set to 0:

  1. XOR the source bitmap onto the destination (BitBlt with SRCINVERT). This looks a bit funny, but the second XOR restores the destination to its original state.
  2. Masking operation. When the mask is ANDed to the destination (BitBlt with SRCAND), all of the transparent pixels leave the destination pixels unchanged, while the opaque pixels set the destination to black. Now the destination has a blacked-out image of the opaque part of the source and an XORed image of itself in the transparent part.
  3. XOR the source to the destination (BitBlt with SRCINVERT). The transparent pixels are restored to their original state, and the opaque pixels are copied directly from the source.

How do I use it?

The function takes four parameters:

  1. The device context that you wish to display the bitmap in,
  2. The horizontal position you wish to draw from,
  3. the vertical position you wish to draw from,
  4. and the colour in the bitmap that is considered to be transparent (as a COLORREF).

pBitmap->DrawTransparent(pCDC, xPos, yPos, crTrans);

Before I start getting emails on the subject, I should point out that the techniques described in this article specifically target displays and may not necessarily work on some printer devices.

Source Code

void CCISBitmap::DrawTransparent(CDC * pDC, int x, int y, COLORREF crColour)
{
	COLORREF crOldBack = pDC->SetBkColor(m_crWhite);
	COLORREF crOldText = pDC->SetTextColor(m_crBlack);
	CDC dcImage, dcTrans;

	// Create two memory dcs for the image and the mask
	dcImage.CreateCompatibleDC(pDC);
	dcTrans.CreateCompatibleDC(pDC);

	// Select the image into the appropriate dc
	CBitmap* pOldBitmapImage = dcImage.SelectObject(this);

	// Create the mask bitmap
	CBitmap bitmapTrans;
	int nWidth = Width();
	int nHeight = Height();
	bitmapTrans.CreateBitmap(nWidth, nHeight, 1, 1, NULL);

	// Select the mask bitmap into the appropriate dc
	CBitmap* pOldBitmapTrans = dcTrans.SelectObject(&bitmapTrans);

	// Build mask based on transparent colour
	dcImage.SetBkColor(crColour);
	dcTrans.BitBlt(0, 0, nWidth, nHeight, &dcImage, 0, 0, SRCCOPY);

	// Do the work - True Mask method - cool if not actual display
	pDC->BitBlt(x, y, nWidth, nHeight, &dcImage, 0, 0, SRCINVERT);
	pDC->BitBlt(x, y, nWidth, nHeight, &dcTrans, 0, 0, SRCAND);
	pDC->BitBlt(x, y, nWidth, nHeight, &dcImage, 0, 0, SRCINVERT);

	// Restore settings
	dcImage.SelectObject(pOldBitmapImage);
	dcTrans.SelectObject(pOldBitmapTrans);
	pDC->SetBkColor(crOldBack);
	pDC->SetTextColor(crOldText);
}


Drawing a Bitmap Transparently by Zafir Anjum

This is a similar article on this subject which uses a slightly different technique often referred to as 'The Black Source Method'.

The True Mask method requires no additional work: The mask is built and the source needs no manipulation. The three blts do cause on-screen flicker, but there are only three of them.

The Black Source method requires additional work on the source bitmap: the transparent bits need to be made black. The on-screen flashing is less noticeable with this method, and once the source is set-up with black in the correct places, transparency looks very good. For bitmaps as small as icons, transparency is achieved very smoothly. (This mechanism is the one used by Windows to display icons on the screen.)

Direct Transparent Blts

Some device drivers support transparent blts directly. You can determine this using the C1_TRANSPARENT bit of the CAPS1 capability word returned by the GetDeviceCaps function. A special background mode, NEWTRANSPARENT, indicates that subsequent blts are transparent blts. The current background colour of the destination is the transparent colour. When this capability is available on the driver, the basic transparent blt operation can be performed as follows:

// Only attempt this if device supports functionality.
if(GetDeviceCaps(hdcDest, CAPS1) & C1_TRANSPARENT)
{
   // Special transparency background mode
      oldMode = SetBkMode(hdcDest, NEWTRANSPARENT);
      rgbBk = SetBkColor(hdcDest, rgbTransparent);
   // Actual blt is a simple source copy; transparency is automatic.
      BitBlt(hdcDest, x, y, dx, dy, hdcSrc, x0, y0, SRCCOPY);
      SetBkColor(hdcDest, rgbBk);
      SetBkMode(hdcDest, oldMode);
}

This code has been compiled and built with Microsoft Visual C++ Version 5.0 SP3.

Download source - 1.31 KB



Comments

  • The perfect solution

    Posted by Legacy on 01/12/2004 12:00am

    Originally posted by: Marcus OBrien

    This requires other functions defined elsewhere on this site. viz. WriteDIB, DDBToDIB
    
    

    It takes the current view and maps a specified colour
    to a transparent bitmap. This can then be changed to
    any other colour , good for preparing a bitmap to be printed (ie knock the background colour out to white, thus reducing ink usage).

    E.g. this will change any colour on the screen view
    from black to white, so if you have a black background
    and want to map it white for printing.....

    NOTE haven't cleaned up DCs yet will post if there
    is any think to do.


    void DumpScreenView(CWnd *pwd, const CString &NameOfBitmapFile)
    {
    CBitmap bitmap;
    CClientDC dc(pwd);
    CDC memDC;
    CRect rect;

    COLORREF CRefBackground = 0xffffff; //set this to your background colour in RGB values

    memDC.CreateCompatibleDC(&dc);
    pwd->GetClientRect(rect);
    bitmap.CreateCompatibleBitmap(&dc, rect.Width(),rect.Height() );

    CBitmap* pOldBitmap = memDC.SelectObject(&bitmap);
    memDC.BitBlt(0, 0, rect.Width(),rect.Height(), &dc, 0, 0, SRCCOPY);

    // Create logical palette if device support a palette
    CPalette pal;
    if( dc.GetDeviceCaps(RASTERCAPS) & RC_PALETTE )
    {
    UINT nSize = sizeof(LOGPALETTE) + (sizeof(PALETTEENTRY) * 256);
    LOGPALETTE *pLP = (LOGPALETTE *) new BYTE[nSize];
    pLP->palVersion = 0x300;
    pLP->palNumEntries =
    GetSystemPaletteEntries( dc, 0, 255, pLP->palPalEntry );
    pal.CreatePalette( pLP );
    delete[] pLP;
    }

    memDC.SelectObject(pOldBitmap);

    CImageList imageList;
    imageList.Create(rect.Width(),rect.Height(), ILC_COLOR|ILC_MASK, 1, 1);
    imageList.Add(&bitmap, CRefBackground);

    CPoint ptLocation(0,0);
    memDC.SelectObject(bitmap);
    //change background colour to white
    imageList.SetBkColor(RGB(255,255,255));
    imageList.Draw(&memDC, 0, ptLocation, ILD_NORMAL);
    memDC.BitBlt(0, 0, rect.Width(),rect.Height(), &memDC, 0, 0, SRCCOPY);
    memDC.SelectObject(pOldBitmap);

    HANDLE hDIB = DDBToDIB( bitmap, BI_RGB, &pal );
    if( hDIB == NULL )
    return false;

    CString PathName("C:\\Whereever");
    NameOfBitmapFile = PathName + "\\" + NameOfBitmapFile + ".bmp";

    CFileFind Folder;
    if (!Folder.FindFile(PathName))
    {
    if (!CreateDirectory( PathName, NULL))
    {
    CString Message("Failed To Create The Screen View Folder. \nPlease make sure the following path is accessible " + PathName);
    AfxMessageBox(Message);
    return false;
    }
    }
    WriteDIB(ViewName , hDIB );
    GlobalFree( hDIB );
    return true;
    }

    Reply
  • windows bitmap

    Posted by Legacy on 12/03/2003 12:00am

    Originally posted by: azaj17

    1. what is windows bitmaps ?

    2. how windows bitmaps function ?

    3. where the windows bitmaps useses ?

    4. what is important the windows bitmaps ?

    Reply
  • Transparent Bitmap - Performance issues + Direct Transparent Blt woes

    Posted by Legacy on 11/21/2003 12:00am

    Originally posted by: keesh

    Paul & Egon & Feng & Hayduk,
    
    

    Thanks for your excellent work in the Transparent Bitmap arena.

    I have employed a similar method from
    Windows Graphics Programming: Win32 GDI and DirectDraw, ISBN 0-13-086985-6, by Yuan, Feng, www.fengyuan.com.

    The problem is that the creation of the mask is very, very slow on certain laptop platforms
    (Dell Latitude C840, Pentium IV, NVIDIA GeForce4 440 Go and
    Gateway Solo, Pentium III, ATI Rage Mobility-P AGP).

    After profiling, the line that takes all of the time (0.5+ seconds in high resolution modes(1280x1024, 32bpp) is::
    //Converting between these two(2) DCs is making the
    //following call very, very slow?
    //If you first select a one-bit HBITMAP into the device
    //context before you draw an image into it, every pixel
    //that corresponds to the background color will be drawn
    //white; the rest will be black.
    BOOL rslt = BitBlt(m_hMemDC, 0, 0, m_nMaskWidth, m_nMaskHeight, hDC, nX, nY, SRCCOPY);

    This appears to be due to the "1-bit bitmap" BitBlt conversion process? For user interaction (i.e. drawing
    true transparent overlay measurements, the performance is unacceptable).
    Any ideas on how to create the overlay mask without using the 1-bit bitmap trick?

    Also, Regarding Direct Transparent Blts--
    under VC++ 7.1 (.NET 1.1) I could not find 'C1_TRANSPARENT' or 'CAPS1' defined anywhere
    and therefore could not call GetDeviceCaps with these arguments.
    It may be available in some DDK using a device GetDeviceCaps alternative?
    if(GetDeviceCaps(hdcDest, CAPS1) & C1_TRANSPARENT)
    This would be great if many graphic drivers supported this, but my tests seemed to indicate otherwise.

    thx in adv (for any advice)
    e.-

    ******************************************
    keesh
    Software Development Engineer
    (Alchemist of the Millennium)

    Contact:
    keesh@ieee.org
    ******************************************

    Life Plan:
    To improve the quality of life for all mankind through better pattern recognition techniques.


    Reply
  • (NON-MFC) DrawTransparent

    Posted by Legacy on 10/14/2003 12:00am

    Originally posted by: Chris Brown

    I got the code for the MFC Transparent bitmap drawing by Paul Reynolds, and modified it to use the VS.NET Platform SDK functions.  Works for drawing to the desktop window too! just call it like this:
    
    

    DrawTransparent(GetDesktopWindow(), ...);

    Source Code:

    void DrawTransparent(HDC hdc, int x, int y, HBITMAP
    hBitmap, COLORREF crColour)
    {
    COLORREF crOldBack = SetBkColor(hdc, RGB(255, 255, 255));
    COLORREF crOldText = SetTextColor(hdc, RGB(0, 0, 0));
    HDC dcImage, dcTrans;

    // Create two memory dcs for the image and the mask
    dcImage=CreateCompatibleDC(hdc);
    dcTrans=CreateCompatibleDC(hdc);

    // Select the image into the appropriate dc
    HBITMAP pOldBitmapImage = (HBITMAP)SelectObject(dcImage, hBitmap);

    // Create the mask bitmap
    BITMAP bitmap;
    GetObject(hBitmap, sizeof(BITMAP), &bitmap);
    HBITMAP bitmapTrans=CreateBitmap(bitmap.bmWidth, bitmap.bmHeight, 1, 1, NULL);

    // Select the mask bitmap into the appropriate dc
    HBITMAP pOldBitmapTrans = (HBITMAP)SelectObject(dcTrans, bitmapTrans);

    // Build mask based on transparent colour
    SetBkColor(dcImage, crColour);
    BitBlt(dcTrans, 0, 0, bitmap.bmWidth, bitmap.bmHeight, dcImage, 0, 0, SRCCOPY);

    // Do the work - True Mask method - cool if not actual display
    BitBlt(hdc, x, y, bitmap.bmWidth, bitmap.bmHeight, dcImage, 0, 0, SRCINVERT);
    BitBlt(hdc, x, y, bitmap.bmWidth, bitmap.bmHeight, dcTrans, 0, 0, SRCAND);
    BitBlt(hdc, x, y, bitmap.bmWidth, bitmap.bmHeight, dcImage, 0, 0, SRCINVERT);

    // Restore settings
    SelectObject(dcImage, pOldBitmapImage);
    SelectObject(dcTrans, pOldBitmapTrans);
    SetBkColor(hdc, crOldBack);
    SetTextColor(hdc, crOldText);
    }

    Reply
  • Problems drawing some icons...

    Posted by Legacy on 10/03/2003 12:00am

    Originally posted by: Chris

    Hi
    
    

    I'm having problems drawing some icons in that the background colour seems to get very slightly changed. The change is so minimal (e.g. RGB(0,255,0) becomes RGB(4,255,4) as to be almost invisible to the naked eye but, as I'm relying on the background colour to be constant it's causing me problems.

    The problem is easy to recreate, as follows:


    -Add a command button to a vbform
    -Add the following to the Command1_Click() event:

    Dim hIcon As Long

    ExtractIconEx "netshell.dll", 0, ByVal 0, hIcon, 1
    DrawIconEx Me.hDC, 0, 0, hIcon, 0, 0, 0, 0, DI_NORMAL

    -Add the following to the delcares section:

    Private Const DI_NORMAL = &H3
    Private Declare Function ExtractIconEx Lib "shell32.dll" Alias "ExtractIconExA" (ByVal lpszFile As String, ByVal nIconIndex As Long, phiconLarge As Long, phiconSmall As Long, ByVal nIcons As Long) As Long
    Private Declare Function DrawIconEx Lib "user32" (ByVal hDC As Long, ByVal xLeft As Long, ByVal yTop As Long, ByVal hIcon As Long, ByVal cxWidth As Long, ByVal cyWidth As Long, ByVal istepIfAniCur As Long, ByVal hbrFlickerFreeDraw As Long, ByVal diFlags As Long) As Long


    Run the app and click the button. Take a snapshot of the window that appears and scrutenize it in a paint app of your choice. You should see that there's a slighly alteration in the colouring around the icon's background.

    Note that the icon you use is significant and that this only seems to have started with Windows2000.

    The same problem occurs if I try to use "black source" or "two bitblts only" methods mentioned here.

    Help would be appreciated!

    Chris

    Reply
  • Here the code for Stretch Transparent Bitmap (2)

    Posted by Legacy on 10/01/2003 12:00am

    Originally posted by: Doo-Yeoun Cho

    this is compatible with this CCISBitmap
    
    

    --------------------------------------------------
    void CCISBitmap::DrawTransparent(CDC * pDC, int x, int y, int width, int height, COLORREF crColour)
    {
    COLORREF crOldBack = pDC->SetBkColor(m_crWhite);
    COLORREF crOldText = pDC->SetTextColor(m_crBlack);
    CDC dcImage, dcTrans;

    // Create two memory dcs for the image and the mask
    dcImage.CreateCompatibleDC(pDC);
    dcTrans.CreateCompatibleDC(pDC);

    // Select the image into the appropriate dc
    CBitmap* pOldBitmapImage = dcImage.SelectObject(this);

    // Create the mask bitmap
    CBitmap bitmapTrans;
    int nWidth = Width();
    int nHeight = Height();
    bitmapTrans.CreateBitmap(width, height, 1, 1, NULL);

    // Select the mask bitmap into the appropriate dc
    CBitmap* pOldBitmapTrans = dcTrans.SelectObject(&bitmapTrans);

    // Build mask based on transparent colour
    dcImage.SetBkColor(crColour);
    dcTrans.StretchBlt(0, 0, width, height, &dcImage, 0, 0, nWidth, nHeight, SRCCOPY);

    // Do the work - True Mask method - cool if not actual display
    pDC->StretchBlt(x, y, width, height, &dcImage, 0, 0, nWidth, nHeight, SRCINVERT);
    pDC->BitBlt(x, y, width, height, &dcTrans, 0, 0, SRCAND);
    pDC->StretchBlt(x, y, width, height, &dcImage, 0, 0, nWidth, nHeight, SRCINVERT);

    // Restore settings
    dcImage.SelectObject(pOldBitmapImage);
    dcTrans.SelectObject(pOldBitmapTrans);
    pDC->SetBkColor(crOldBack);
    pDC->SetTextColor(crOldText);
    }

    Reply
  • Here the code for Stretch Transparent Bitmap

    Posted by Legacy on 06/27/2003 12:00am

    Originally posted by: Michel Lajeunesse

    void DrawTransparent(HDC hDC, int x, int y, int iDestWidth, int iDestHeight, HBITMAP hBmpSrc, COLORREF crColour)
    
    {
    COLORREF crOldBack = SetBkColor(hDC, RGB(255,255,255));
    COLORREF crOldText = SetTextColor(hDC, RGB(0,0,0));
    HDC hdcImage, // Final destination image DC
    hdcTrans; // Mask DC
    HDC hdcStretch, // The strectched source bitmap DC
    hdcSrc; // The source bitmap DC
    HBITMAP hBmpStretch, // Final destination image bitmap attach to hdcStretch
    hBmpTrans; // Mask bitmap attach to hdcTrans

    HBITMAP hOldSrcBmp, // Old DC bitmap that must be restored at end
    hOldBmpStretch,
    hOldBmpImage,
    hOldBmpTrans;
    BITMAP bmpInfo;


    // Create two memory dcs for the image and the mask
    hdcImage = CreateCompatibleDC( hDC );
    hdcTrans = CreateCompatibleDC( hDC );

    // Get Bmp source info
    GetObject(hBmpSrc, sizeof(BITMAP), &bmpInfo);

    // If we Stretch, we must create a compatible DC & bitmap then draw
    // the stretched source image into the compatible DC. Then, the bitmap
    // of the compatible DC will be used as source bitmap to hdcImage.
    // Else, associate directly bmp source to hdcImage.
    if(iDestWidth != bmpInfo.bmWidth || iDestHeight != bmpInfo.bmHeight)
    {
    hdcSrc = CreateCompatibleDC( hDC );
    hOldSrcBmp = (HBITMAP)SelectObject(hdcSrc, hBmpSrc);

    hdcStretch = CreateCompatibleDC( hDC );
    hBmpStretch = CreateCompatibleBitmap(hDC, iDestWidth, iDestHeight);

    hOldBmpStretch = (HBITMAP)SelectObject(hdcStretch, hBmpStretch);
    StretchBlt(hdcStretch, 0, 0, iDestWidth, iDestHeight, hdcSrc, 0, 0, bmpInfo.bmWidth, bmpInfo.bmHeight, SRCCOPY);
    SelectObject(hdcStretch, hOldBmpStretch);

    // Select the image into the appropriate dc
    hOldBmpImage = (HBITMAP)SelectObject(hdcImage, hBmpStretch);
    }
    else
    {
    // Select the image into the appropriate dc
    hOldBmpImage = (HBITMAP)SelectObject(hdcImage, hBmpSrc);
    }

    // Create the mask bitmap
    hBmpTrans = CreateBitmap(iDestWidth, iDestHeight, 1, 1, NULL);

    // Select the mask bitmap into the appropriate dc
    hOldBmpTrans = (HBITMAP)SelectObject(hdcTrans, hBmpTrans);

    // Build mask based on transparent colour
    SetBkColor(hdcImage, crColour);
    BitBlt(hdcTrans, 0, 0, iDestWidth, iDestHeight, hdcImage, 0, 0, SRCCOPY);

    // Do the work - True Mask method - cool if not actual display
    BitBlt(hDC, x, y, iDestWidth, iDestHeight, hdcImage, 0, 0, SRCINVERT);
    BitBlt(hDC, x, y, iDestWidth, iDestHeight, hdcTrans, 0, 0, SRCAND);
    BitBlt(hDC, x, y, iDestWidth, iDestHeight, hdcImage, 0, 0, SRCINVERT);

    // Restore settings
    SelectObject(hdcImage, hOldBmpImage);
    SelectObject(hdcTrans, hOldBmpTrans);
    SetBkColor(hDC, crOldBack);
    SetTextColor(hDC, crOldText);

    if(iDestWidth != bmpInfo.bmWidth || iDestHeight != bmpInfo.bmHeight)
    {
    SelectObject(hdcSrc, hOldSrcBmp);

    DeleteObject( hBmpStretch );
    DeleteDC( hdcSrc );
    DeleteDC( hdcStretch );
    }
    }

    Reply
  • How to stretch a bitmap in memory?

    Posted by Legacy on 08/30/2002 12:00am

    Originally posted by: gothing

    A bitmap can be draw or stretch on DC. Now I don't need to display it to screen, I stretch it in memory, how to do?

    Reply
  • How do I draw on top of a bitmap?

    Posted by Legacy on 08/12/2002 12:00am

    Originally posted by: Rainey

    In my appication I have a symbol which I have drawn using the CPaintDC class. I need to be able to display this symbol on top of a bitmap. When I try this, the bitmap is drawn ontop of the symbol so that it cannot be seen. Is the a way of reversing the z-order so that the symbol is drawn after the bitmap and therefore on top of it?

    Reply
  • You only need two blits - not three IF...

    Posted by Legacy on 08/01/2002 12:00am

    Originally posted by: David Billen


    ...if your bitmap has zeros for the pixels you want masked. This is acceptable in most cases because you have a bitmap with one corresponding mask. The only time it wouldn't be possible is to use a single mask with different bitmaps for cookie cutter operations.

    The mask should have the bits you want masked off as white, and bits you want to keep as black.

    Just AND the mask, (SRCAND), then OR the bitmap, (SRCPAINT).

    Reply
  • Loading, Please Wait ...

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • IBM Worklight is a mobile application development platform that lets you extend your business to mobile devices. It is designed to provide an open, comprehensive platform to build, run and manage HTML5, hybrid and native mobile apps.

  • In this webinar, IDC featured speaker Steve Conway, Vice President of High Performance Computing, will present an update on the global x86 HPC cluster market. The presentation will include IDC's five-year forecast for the medium- to large-scale technical computing and data analysis emerging markets by systems, processors and application middleware. Cray's featured speaker, John Lee, Vice President of Cray Cluster Advanced Technology Systems, will present the new Cray® CS400™ cluster series based on …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds