从位图创建彩色光标






4.96/5 (25投票s)
2003年10月15日
4分钟阅读

137204

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 中,光标尺寸存在一些限制。因此,始终传递标准尺寸的位图是安全的。否则,结果可能是不可预测的。