Creating a Color Cursor from a Bitmap

Figure 1: Transition of a color bitmap to a cursor

Environment: Win32

This article focuses on creating a color cursor from a HBITMAP. First, it explains the steps that Windows performs to display a cursor on the screen and how can we create the necessary information that Windows needs to create our cursor. Then, it explains the steps needed to convert a color HBITMAP to an HCURSOR. Finally, it shows a utility class that converts HBITMAP to HCURSOR.

How Does Windows Display a Cursor?

In Windows, transparency of the cursor is achieved by the use of two masks. One is called the AND mask and the other is called the XOR mask. To display a cursor in the screen, the system first performs a logical AND operation on the screen with the AND mask. In this process, the pixels in the screen corresponding to the 1 bits in the AND mask remain unchanged and the pixels corresponding to the 0 bits in the AND mask become modified. Then, the system will perform a logical XOR operation on the screen with the XOR mask. In this process, the pixels on the screen corresponding to the 0 bits in the XOR mask remain unchanged and the pixels corresponding to the non-0 bits get modified.

Figure 2: A sample color bitmap to be converted as a cursor

Now, let's try to realize the above cursor to its AND/XOR masks so that the system can display the cursor using these masks. First, let us create the AND mask. The above cursor contains a red colored rectangle in the center. So, all the other pixels should be transparent. Assuming that the size of the cursor is 8*8 and the size of the rectangle is 4*4, we shall define the AND mask as shown below.

1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1
1 1 0 0 0 0 1 1
1 1 0 0 0 0 1 1
1 1 0 0 0 0 1 1
1 1 0 0 0 0 1 1
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1

Figure 3: AND mask for the sample color bitmap in Figure 2

In the above AND mask, the bits corresponding to the red rectangle are 0 and the rest of the bits are 1. This is because we need only the red rectangle to be displayed as the cursor and the rest of the area should be transparent. When the system performs a logical AND operation of this mask to the screen, the pixels in the screen corresponding to the red rectangle become modified and the rest remain unchanged.

Now, let us create the XOR mask for our cursor. Because we need to display the red rectangle as a cursor on the screen and the rest as transparent, we need to make the bits in the XOR mask correspond to the red rectangle as Red (RGB (255,0,0)) and the rest as 0.

0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 R R R R 0 0
0 0 R R R R 0 0
0 0 R R R R 0 0
0 0 R R R R 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0

Figure 4: XOR mask for the sample color bitmap in Figure 2

The R in the above XOR mask represents RGB (255,0,0). In other words, it's the red color. When the system performs a logical XOR of this XOR mask to the screen, the R pixels are updated on the screen and the pixels corresponding to the 0 bits remain unchanged.

So, finally, after performing the logical AND of the AND mask, followed by the logical XOR of the XOR mask to the screen, the screen under our cursor region looks like what is shown below.

S S S S S S S S
S S S S S S S S
S S R R R R S S
S S R R R R S S
S S R R R R S S
S S R R R R S S
S S S S S S S S
S S S S S S S S

Figure 5: State of the screen area under the cursor after applying AND and XOR masks

The S represents the original screen pixels and the R represents the red color pixels.

Converting HBITMAP to HCURSOR

Now, let us try to create these AND/XOR masks from a HBITMAP. The following code fragment will do it for you.

void CColorCursor::GetMaskBitmaps(HBITMAP hSourceBitmap,
                                  COLORREF clrTransparent,
                                  HBITMAP &hAndMaskBitmap,
                                  HBITMAP &hXorMaskBitmap)
{
  HDC hDC        = ::GetDC(NULL);
  HDC hMainDC    = ::CreateCompatibleDC(hDC);
  HDC hAndMaskDC = ::CreateCompatibleDC(hDC);
  HDC hXorMaskDC = ::CreateCompatibleDC(hDC);

  //Get the dimensions of the source bitmap
  BITMAP bm;
  ::GetObject(hSourceBitmap,sizeof(BITMAP),&bm);


  hAndMaskBitmap = ::CreateCompatibleBitmap(hDC,bm.bmWidth,
                                            bm.bmHeight);
  hXorMaskBitmap = ::CreateCompatibleBitmap(hDC,bm.bmWidth,
                                            bm.bmHeight);

  //Select the bitmaps to DC
  HBITMAP hOldMainBitmap    = (HBITMAP)::
                              SelectObject(hMainDC,hSourceBitmap);
  HBITMAP hOldAndMaskBitmap = (HBITMAP)::
                              SelectObject(hAndMaskDC,
                                           hAndMaskBitmap);
  HBITMAP hOldXorMaskBitmap = (HBITMAP)::
                              SelectObject(hXorMaskDC,
                                           hXorMaskBitmap);

  //Scan each pixel of the souce bitmap and create the masks
  COLORREF MainBitPixel;
  for(int x=0;x&ltbm.bmWidth;++x)
  {
    for(int y=0;y&ltbm.bmHeight;++y)
    {
      MainBitPixel = ::GetPixel(hMainDC,x,y);
      if(MainBitPixel == clrTransparent)
      {
        ::SetPixel(hAndMaskDC,x,y,RGB(255,255,255));
        ::SetPixel(hXorMaskDC,x,y,RGB(0,0,0));
      }
      else
      {
        ::SetPixel(hAndMaskDC,x,y,RGB(0,0,0));
        ::SetPixel(hXorMaskDC,x,y,MainBitPixel);
      }
    }
  }

  ::SelectObject(hMainDC,hOldMainBitmap);
  ::SelectObject(hAndMaskDC,hOldAndMaskBitmap);
  ::SelectObject(hXorMaskDC,hOldXorMaskBitmap);

  ::DeleteDC(hXorMaskDC);
  ::DeleteDC(hAndMaskDC);
  ::DeleteDC(hMainDC);

  ::ReleaseDC(NULL,hDC);
}

The above code creates two memory DC and two memory bitmaps for the AND/XOR masks. Then, it examines the source bitmap pixels and creates the masks as we have explained in the theory part.

Now, what we need is to use these masks and create a cursor using the well-known CreateIconIndirect() SDK call as shown below.

ICONINFO iconinfo       = {0};
iconinfo.fIcon          = FALSE;
iconinfo.xHotspot       = 0;
iconinfo.yHotspot       = 0;
iconinfo.hbmMask        = hAndMask;
iconinfo.hbmColor       = hXorMask;

HCURSOR hCursor         = ::CreateIconIndirect(&iconinfo);

That's it. We have successfully created a color cursor from a bitmap.

Using the Code

It is always better to create a utility class for doing these things for us. So, I created one called CColorCursor. It has the following interfaces.

static void GetMaskBitmaps(HBITMAP hSourceBitmap,
                           COLORREF clrTransparent,
                           HBITMAP &hAndMaskBitmap,
                           HBITMAP &hXorMaskBitmap);
static HCURSOR CreateCursorFromBitmap(HBITMAP hSourceBitmap,
                                      COLORREF clrTransparent,
                                      DWORD xHotspot,
                                      DWORD yHotspot);

The first interface is called from the second one to create the masks. The first one is also made public because we can use it to get an idea of what is happening inside. I used the first interface in my test application to display the AND/XOR mask as shown in the first figure and the second one to create a cursor directly.

Now, we are approaching towards the end of this article. I will finish it by showing the usage of this utility class.

#include "ColorCursor.h"

....

HBITMAP hSourceBitmap  = c.
HCURSOR hCursor = CColorCursor::
                  CreateCursorFromBitmap(hSourceBitmap,RGB(0,0,0),
                                         0,0);

A Word of Caution

The utility class explained above will try to create the cursor in the same size of the input source bitmap. But in Windows, there are some limitations for the size of the cursor. So, it is always safer to pass bitmaps having a standard size. Otherwise, the result may be unpredictable.

Downloads

Download demo project - 37 Kb
Download source - 2 Kb