虽然我无法准确解释为什么会发生这种情况,但我想我可以展示如何解决它。
ICONINFO 结构包含两个成员 hbmMask 和 hbmColor,它们分别包含光标的掩码和颜色位图(请参阅 MSDN 页面了解ICONINFO获取官方文档)。
当您为默认光标调用 GetIconInfo() 时,ICONINFO 结构包含有效的掩码和颜色位图,如下所示(注意:已添加红色边框以清晰显示图像边界):
Default Cursor Mask Bitmap
Default Cursor Color Bitmap
当Windows绘制默认光标时,首先对掩码位图应用AND光栅操作,然后对颜色位图应用XOR光栅操作。这会产生不透明的光标和透明的背景。
但是,当您为 I-Beam 光标调用 GetIconInfo() 时,ICONINFO 结构仅包含有效的掩码位图,而没有颜色位图,如下所示(注意:再次添加红色边框以清楚地显示图像边界):
I-Beam Cursor Mask Bitmap
根据 ICONINFO 文档,I-Beam 光标是单色光标。掩码位图的上半部分是 AND 掩码,掩码位图的下半部分是 XOR 位图。当 Windows 绘制 I-Beam 光标时,首先使用 AND 光栅操作在桌面上绘制该位图的上半部分。然后通过 XOR 光栅操作将位图的下半部分绘制在顶部。在屏幕上,光标将与其后面的内容相反地显示。
中的一个comments您链接的原始文章提到了这一点。在桌面上,由于光栅操作应用于桌面内容,因此光标将显示正确。但是,当图像没有背景绘制时(如您发布的代码中所示),Windows 执行的光栅操作会导致图像褪色。
也就是说,这个更新的 CaptureCursor() 方法将处理彩色和单色光标,当光标为单色时提供纯黑色光标图像。
static Bitmap CaptureCursor(ref int x, ref int y)
{
Win32Stuff.CURSORINFO cursorInfo = new Win32Stuff.CURSORINFO();
cursorInfo.cbSize = Marshal.SizeOf(cursorInfo);
if (!Win32Stuff.GetCursorInfo(out cursorInfo))
return null;
if (cursorInfo.flags != Win32Stuff.CURSOR_SHOWING)
return null;
IntPtr hicon = Win32Stuff.CopyIcon(cursorInfo.hCursor);
if (hicon == IntPtr.Zero)
return null;
Win32Stuff.ICONINFO iconInfo;
if (!Win32Stuff.GetIconInfo(hicon, out iconInfo))
return null;
x = cursorInfo.ptScreenPos.x - ((int)iconInfo.xHotspot);
y = cursorInfo.ptScreenPos.y - ((int)iconInfo.yHotspot);
using (Bitmap maskBitmap = Bitmap.FromHbitmap(iconInfo.hbmMask))
{
// Is this a monochrome cursor?
if (maskBitmap.Height == maskBitmap.Width * 2)
{
Bitmap resultBitmap = new Bitmap(maskBitmap.Width, maskBitmap.Width);
Graphics desktopGraphics = Graphics.FromHwnd(Win32Stuff.GetDesktopWindow());
IntPtr desktopHdc = desktopGraphics.GetHdc();
IntPtr maskHdc = Win32Stuff.CreateCompatibleDC(desktopHdc);
IntPtr oldPtr = Win32Stuff.SelectObject(maskHdc, maskBitmap.GetHbitmap());
using (Graphics resultGraphics = Graphics.FromImage(resultBitmap))
{
IntPtr resultHdc = resultGraphics.GetHdc();
// These two operation will result in a black cursor over a white background.
// Later in the code, a call to MakeTransparent() will get rid of the white background.
Win32Stuff.BitBlt(resultHdc, 0, 0, 32, 32, maskHdc, 0, 32, Win32Stuff.TernaryRasterOperations.SRCCOPY);
Win32Stuff.BitBlt(resultHdc, 0, 0, 32, 32, maskHdc, 0, 0, Win32Stuff.TernaryRasterOperations.SRCINVERT);
resultGraphics.ReleaseHdc(resultHdc);
}
IntPtr newPtr = Win32Stuff.SelectObject(maskHdc, oldPtr);
Win32Stuff.DeleteObject(newPtr);
Win32Stuff.DeleteDC(maskHdc);
desktopGraphics.ReleaseHdc(desktopHdc);
// Remove the white background from the BitBlt calls,
// resulting in a black cursor over a transparent background.
resultBitmap.MakeTransparent(Color.White);
return resultBitmap;
}
}
Icon icon = Icon.FromHandle(hicon);
return icon.ToBitmap();
}
代码中存在一些问题,这些问题可能是问题,也可能不是问题。
- 对单色光标的检查只是测试高度是否是宽度的两倍。虽然这看起来合乎逻辑,但 ICONINFO 文档并不强制要求仅由此定义单色光标。
- 可能有一种更好的方法来呈现光标,即我使用的 BitBlt() - BitBlt() - MakeTransparent() 方法调用组合。