65.9K
CodeProject 正在变化。 阅读更多。
Home

从位图创建彩色光标

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.96/5 (25投票s)

2003年10月15日

4分钟阅读

viewsIcon

137204

downloadIcon

3968

一篇关于如何将彩色 HBITMAP 转换为 HCURSOR 的文章


图 1:彩色位图转换为光标

引言

本文着重于如何使用 HBITMAP 创建彩色光标。首先,它解释了 Windows 在屏幕上显示光标的步骤,以及如何创建 Windows 创建光标所需的必要信息。然后,它解释了将彩色 HBITMAP 转换为 HCURSOR 所需的步骤。最后,它展示了一个将 HBITMAP 转换为 HCURSOR 的实用类。

Windows 如何显示光标?

在 Windows 中,光标的透明度是通过使用两个掩码来实现的。一个称为 AND 掩码,另一个称为 XOR 掩码。为了在屏幕上显示光标,系统首先对屏幕执行与 AND 掩码的逻辑 AND 操作。在此过程中,屏幕上与 AND 掩码中的 1 位对应的像素保持不变,与 AND 掩码中的 0 位对应的像素将被修改。然后,系统将对屏幕执行与 XOR 掩码的逻辑 XOR 操作。在此过程中,屏幕上与 XOR 掩码中的 0 位对应的像素保持不变,与非 0 位对应的像素将被修改。


图 2:将要转换为光标的示例彩色位图

现在,让我们尝试将上述光标实现为其 AND/XOR 掩码,以便系统可以使用这些掩码来显示光标。首先,让我们创建 AND 掩码。上面的光标中心有一个红色的矩形。因此,所有其他像素都应该是透明的。假设光标的大小是 8*8,矩形的大小是 4*4,我们将 AND 掩码定义如下。

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
图 3:图 2 中示例彩色位图的 AND 掩码

在上面的 AND 掩码中,与红色矩形对应的位是 0,其余位是 1。这是因为我们只需要显示红色矩形作为光标,其余区域应该是透明的。当系统将此掩码与屏幕执行逻辑 AND 操作时,屏幕上与红色矩形对应的像素将被修改,其余像素保持不变。

现在,让我们为我们的光标创建 XOR 掩码。由于我们需要将红色矩形作为光标显示在屏幕上,其余部分为透明,因此我们需要将 XOR 掩码中与红色矩形对应的位设置为红色(RGB (255,0,0)),其余位设置为 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
图 4:图 2 中示例彩色位图的 XOR 掩码

上面的 XOR 掩码中的 R 代表 RGB (255,0,0)。即红色。当系统将此 XOR 掩码与屏幕执行逻辑 XOR 操作时,屏幕上的 R 像素将被更新,与 0 位对应的像素将保持不变。

因此,最终,在对 AND 掩码执行逻辑 AND 操作,然后对 XOR 掩码执行逻辑 XOR 操作到屏幕后,我们光标区域下的屏幕外观如下所示。

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
图 5:应用 AND 和 XOR 掩码后光标下屏幕区域的状态

其中 S 代表原始屏幕像素,R 代表红色像素。

HBITMAP 转换为 HCURSOR

现在,让我们尝试从 HBITMAP 创建这些 AND/XOR 掩码。以下代码片段将为您完成此操作。

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<bm.bmWidth;++x)
  {
    for(int y=0;y<bm.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);
}

上面的代码创建了两个内存 DC 和两个内存位图来存储 AND/XOR 掩码。然后,它检查源位图的像素,并按照理论部分解释的方式创建掩码。

现在,我们需要使用这些掩码,并通过使用众所周知的 CreateIconIndirect() SDK 调用来创建光标,如下所示。

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

HCURSOR hCursor = ::CreateIconIndirect(&iconinfo);

就是这样。我们已经成功地从位图创建了一个彩色光标。

使用代码

最好创建一个实用类来为我们完成这些工作。所以我创建了一个名为 CColorCursor 的类,它具有以下接口。

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

第一个接口由第二个接口调用以创建掩码。第一个接口也设置为公共的,因为我们可以使用它来了解内部发生了什么。我在我的测试应用程序中使用了第一个接口来显示 AND/XOR 掩码,如第一个图所示,并使用第二个接口直接创建光标。

现在我们接近本文的结尾。我将通过展示这个实用类的用法来结束它。

#include "ColorCursor.h"

....

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

注意事项

上面解释的实用类将尝试以输入源位图相同的尺寸创建光标。但在 Windows 中,光标尺寸存在一些限制。因此,始终传递标准尺寸的位图是安全的。否则,结果可能是不可预测的。

© . All rights reserved.