C# - 捕获鼠标光标图像

nam*_*los 30 c# mouse icons screenshot cursor

背景

我的问题

  • 当鼠标光标是普通指针或手形图标时,代码工作正常 - 鼠标在屏幕截图上正确呈现
  • 但是,当鼠标光标变为插入点("I-beam"光标)时 - 例如键入NOTEPAD - 然后代码不起作用 - 结果是我得到了一个模糊的光标图像 - 就像一个非常半透明(灰色)的版本,而不是空白和白色的预期.

我的问题

  • 当图像是这些"I-beam"类型图像之一时,如何捕获鼠标光标图像
  • 注意:如果您点击原始文章,有人提出建议 - 它不起作用

资源

这是来自原始文章.

    static Bitmap CaptureCursor(ref int x, ref int y)
    {
        Bitmap bmp;
        IntPtr hicon;
        Win32Stuff.CURSORINFO ci = new Win32Stuff.CURSORINFO();
        Win32Stuff.ICONINFO icInfo;
        ci.cbSize = Marshal.SizeOf(ci);
        if (Win32Stuff.GetCursorInfo(out ci))
        {
            if (ci.flags == Win32Stuff.CURSOR_SHOWING)
            {
                hicon = Win32Stuff.CopyIcon(ci.hCursor);
                if (Win32Stuff.GetIconInfo(hicon, out icInfo))
                {
                    x = ci.ptScreenPos.x - ((int)icInfo.xHotspot);
                    y = ci.ptScreenPos.y - ((int)icInfo.yHotspot);

                    Icon ic = Icon.FromHandle(hicon);
                    bmp = ic.ToBitmap(); 
                    return bmp;
                }
            }
        }

        return null;
    }
Run Code Online (Sandbox Code Playgroud)

Tar*_*ier 29

虽然我无法解释为什么会发生这种情况,但我想我可以展示如何解决这个问题.

ICONINFO结构包含两个成员hbmMask和hbmColor,它们分别包含光标的掩码和颜色位图(有关官方文档,请参阅ICONINFO的MSDN页面).

当您为默认光标调用GetIconInfo()时,ICONINFO结构包含有效的掩码和颜色位图,如下所示(注意:已添加红色边框以清楚地显示图像边界):

默认光标掩码位图 默认光标掩码位图图像http://img4.imageshack.us/img4/1108/arrowmask.png

默认光标颜色位图 默认光标颜色位图图像http://img191.imageshack.us/img191/7680/arrowcolor.png

当Windows绘制默认光标时,首先使用AND光栅操作应用蒙版位图,然后使用XOR光栅操作应用颜色位图.这导致不透明的光标和透明背景.

但是,当你为I-Beam光标调用GetIconInfo()时,ICONINFO结构只包含一个有效的掩码位图,而没有颜色位图,如下所示(注意:再次添加红色边框以清楚地显示图像边界) ):

I-Beam光标掩码位图 ibeam光标掩码位图图像http://img14.imageshack.us/img14/6025/ibeammask.png

根据ICONINFO文档,I-Beam光标是单色光标.掩码位图的上半部分是AND掩码,掩码位图的下半部分是XOR位图.当Windows绘制I-Beam光标时,首先使用AND光栅操作在桌面上绘制此位图的上半部分.然后使用XOR光栅操作在顶部绘制位图的下半部分.屏幕,光标将显示为其背后内容的反转.

您链接的原始文章的评论之一提到了这一点.在桌面上,由于光栅操作应用于桌面内容,因此光标将显示正确.但是,如果图像是在没有背景的情况下绘制的,就像在发布的代码中一样,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();
}
Run Code Online (Sandbox Code Playgroud)

代码存在一些问题,可能是也可能不是问题.

  1. 检查单色光标只是测试高度是否是宽度的两倍.虽然这似乎合乎逻辑,但ICONINFO文档并未强制要求仅定义单色光标.
  2. 可能有一种更好的方法来渲染光标,即我使用的方法调用的BitBlt() - BitBlt() - MakeTransparent()组合.

  • 您还可以使用DrawIcon(hDC,x,y,hIcon)直接将光标绘制到目标DC,如果您只想获取捕获鼠标光标的屏幕截图 (3认同)

小智 10

[StructLayout(LayoutKind.Sequential)]
struct CURSORINFO
{
    public Int32 cbSize;
    public Int32 flags;
    public IntPtr hCursor;
    public POINTAPI ptScreenPos;
}

[StructLayout(LayoutKind.Sequential)]
struct POINTAPI
{
    public int x;
    public int y;
}

[DllImport("user32.dll")]
static extern bool GetCursorInfo(out CURSORINFO pci);

[DllImport("user32.dll")]
static extern bool DrawIcon(IntPtr hDC, int X, int Y, IntPtr hIcon);

const Int32 CURSOR_SHOWING = 0x00000001;

public static Bitmap CaptureScreen(bool CaptureMouse)
{
    Bitmap result = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height, PixelFormat.Format24bppRgb);

    try
    {
        using (Graphics g = Graphics.FromImage(result))
        {
            g.CopyFromScreen(0, 0, 0, 0, Screen.PrimaryScreen.Bounds.Size, CopyPixelOperation.SourceCopy);

            if (CaptureMouse)
            {
                CURSORINFO pci;
                pci.cbSize = System.Runtime.InteropServices.Marshal.SizeOf(typeof(CURSORINFO));

                if (GetCursorInfo(out pci))
                {
                    if (pci.flags == CURSOR_SHOWING)
                    {
                        DrawIcon(g.GetHdc(), pci.ptScreenPos.x, pci.ptScreenPos.y, pci.hCursor);
                        g.ReleaseHdc();
                    }
                }
            }
        }
    }
    catch
    {
        result = null;
    }

    return result;
}
Run Code Online (Sandbox Code Playgroud)