C# – 捕获鼠标光标图像

背景

  • 我正在编写一个屏幕截图应用程序
  • 我的代码基于派生自这个项目: http : //www.codeproject.com/KB/cs/DesktopCaptureWithMouse.aspx?display=Print
  • 请注意,代码也捕获鼠标光标(这是我所期望的)

我的问题

  • 当鼠标光标是普通指针或手形图标时,代码可以正常工作 – 鼠标在屏幕截图上正确显示
  • 但是,当鼠标光标改变为插入点(“I-beam”光标) – 例如inputNOTEPAD时 – 代码不起作用 – 结果是我得到了一个微弱的光标图像 – 就像非常半透明(灰色)的版本,而不是空白和白色的预期。

我的问题

  • 当图像是这些“工字型”图像之一时,如何捕获鼠标光标图像
  • 注意:如果您点击原始文章有人提供了一个build议 – 这是行不通的

资源

这是从原来的文章。

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; } 

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

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(); } 

代码有一些问题可能会或可能不会成为问题。

  1. 单色光标的检查只是testing高度是宽度的两倍。 虽然这看起来合乎逻辑,但ICONINFO文档并没有强制规定只有一个单色光标由此定义。
  2. 有可能是一个更好的方法来渲染游标的BitBlt() – BitBlt() – MakeTransparent()方法调用的组合。
 [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; } 

这里是Dimitar的反应(使用DrawIconEx)的修改版本,在多个屏幕上为我工作:

 public class ScreenCapturePInvoke { [StructLayout(LayoutKind.Sequential)] private struct CURSORINFO { public Int32 cbSize; public Int32 flags; public IntPtr hCursor; public POINTAPI ptScreenPos; } [StructLayout(LayoutKind.Sequential)] private struct POINTAPI { public int x; public int y; } [DllImport("user32.dll")] private static extern bool GetCursorInfo(out CURSORINFO pci); [DllImport("user32.dll", SetLastError = true)] static extern bool DrawIconEx(IntPtr hdc, int xLeft, int yTop, IntPtr hIcon, int cxWidth, int cyHeight, int istepIfAniCur, IntPtr hbrFlickerFreeDraw, int diFlags); private const Int32 CURSOR_SHOWING = 0x0001; private const Int32 DI_NORMAL = 0x0003; public static Bitmap CaptureFullScreen(bool captureMouse) { var allBounds = Screen.AllScreens.Select(s => s.Bounds).ToArray(); Rectangle bounds = Rectangle.FromLTRB(allBounds.Min(b => b.Left), allBounds.Min(b => b.Top), allBounds.Max(b => b.Right), allBounds.Max(b => b.Bottom)); var bitmap = CaptureScreen(bounds, captureMouse); return bitmap; } public static Bitmap CapturePrimaryScreen(bool captureMouse) { Rectangle bounds = Screen.PrimaryScreen.Bounds; var bitmap = CaptureScreen(bounds, captureMouse); return bitmap; } public static Bitmap CaptureScreen(Rectangle bounds, bool captureMouse) { Bitmap result = new Bitmap(bounds.Width, bounds.Height); try { using (Graphics g = Graphics.FromImage(result)) { g.CopyFromScreen(bounds.Location, Point.Empty, bounds.Size); if (captureMouse) { CURSORINFO pci; pci.cbSize = Marshal.SizeOf(typeof (CURSORINFO)); if (GetCursorInfo(out pci)) { if (pci.flags == CURSOR_SHOWING) { var hdc = g.GetHdc(); DrawIconEx(hdc, pci.ptScreenPos.x-bounds.X, pci.ptScreenPos.y-bounds.Y, pci.hCursor, 0, 0, 0, IntPtr.Zero, DI_NORMAL); g.ReleaseHdc(); } } } } } catch { result = null; } return result; } } 

您对I型光标的半透明“灰色”版本的描述使我怀疑是否遇到图像缩放或光标错位的问题。

其中一个在该网站上发布的人提供了一个具有特殊行为的(断开)链接,我已经追踪到: http : //www.efg2.com/Lab/Graphics/CursorOverlay.htm

该页面上的例子不是在C#中,但codeproject解决scheme的作者可能已经做了类似的事情,我知道我在我自己的大量occassions上使用graphics对象时,缩小了我的缩放比例:

在任何ImageMouseDown事件中,一旦图像被加载,CusorBitmap就会使用Canvas.Draw方法在位图顶部绘制透明度。 注意一些坐标调整(缩放比例)是需要的情况下位图被拉伸以适应TImage。

这是修补版本,其中包含针对此页面上显示的错误的所有修补程序:

 public static Bitmap CaptureImageCursor(ref Point point) { try { var cursorInfo = new CursorInfo(); cursorInfo.cbSize = Marshal.SizeOf(cursorInfo); if (!GetCursorInfo(out cursorInfo)) return null; if (cursorInfo.flags != CursorShowing) return null; var hicon = CopyIcon(cursorInfo.hCursor); if (hicon == IntPtr.Zero) return null; Iconinfo iconInfo; if (!GetIconInfo(hicon, out iconInfo)) { DestroyIcon(hicon); return null; } point.X = cursorInfo.ptScreenPos.X - iconInfo.xHotspot; point.Y = cursorInfo.ptScreenPos.Y - iconInfo.yHotspot; using (var maskBitmap = Image.FromHbitmap(iconInfo.hbmMask)) { //Is this a monochrome cursor? if (maskBitmap.Height == maskBitmap.Width * 2 && iconInfo.hbmColor == IntPtr.Zero) { var final = new Bitmap(maskBitmap.Width, maskBitmap.Width); var hDesktop = GetDesktopWindow(); var dcDesktop = GetWindowDC(hDesktop); using (var resultGraphics = Graphics.FromImage(final)) { var resultHdc = resultGraphics.GetHdc(); BitBlt(resultHdc, 0, 0, final.Width, final.Height, dcDesktop, (int)point.X + 3, (int)point.Y + 3, CopyPixelOperation.SourceCopy); DrawIconEx(resultHdc, 0, 0, cursorInfo.hCursor, 0, 0, 0, IntPtr.Zero, 0x0003); //TODO: I have to try removing the background of this cursor capture. //Native.BitBlt(resultHdc, 0, 0, final.Width, final.Height, dcDesktop, (int)point.X + 3, (int)point.Y + 3, Native.CopyPixelOperation.SourceErase); resultGraphics.ReleaseHdc(resultHdc); ReleaseDC(hDesktop, dcDesktop); } DeleteObject(iconInfo.hbmMask); DeleteDC(dcDesktop); DestroyIcon(hicon); return final; } DeleteObject(iconInfo.hbmColor); DeleteObject(iconInfo.hbmMask); DestroyIcon(hicon); } var icon = Icon.FromHandle(hicon); return icon.ToBitmap(); } catch (Exception ex) { //You should catch exception with your method here. //LogWriter.Log(ex, "Impossible to get the cursor."); } return null; } 

此版本适用于:

  1. 我光束游标。
  2. 黑色的游标。
  3. 普通游标。
  4. 倒置的游标。

看到工作,在这里: https : //github.com/NickeManarin/ScreenToGif/blob/master/ScreenToGif/Util/Native.cs#L991

基于其他答案,我做了一个版本没有所有的Windows API的东西(单色部分),因为解决scheme不适用于所有的单色游标。 我通过组合两个蒙版部分从蒙版创build光标。

我的解决scheme

 Bitmap CaptureCursor(ref Point position) { CURSORINFO cursorInfo = new CURSORINFO(); cursorInfo.cbSize = Marshal.SizeOf(cursorInfo); if (!GetCursorInfo(out cursorInfo)) return null; if (cursorInfo.flags != CURSOR_SHOWING) return null; IntPtr hicon = CopyIcon(cursorInfo.hCursor); if (hicon == IntPtr.Zero) return null; ICONINFO iconInfo; if (!GetIconInfo(hicon, out iconInfo)) return null; position.X = cursorInfo.ptScreenPos.x - iconInfo.xHotspot; position.Y = cursorInfo.ptScreenPos.y - iconInfo.yHotspot; using (Bitmap maskBitmap = Bitmap.FromHbitmap(iconInfo.hbmMask)) { // check for monochrome cursor if (maskBitmap.Height == maskBitmap.Width * 2) { Bitmap cursor = new Bitmap(32, 32, PixelFormat.Format32bppArgb); Color BLACK = Color.FromArgb(255, 0, 0, 0); //cannot compare Color.Black because of different names Color WHITE = Color.FromArgb(255, 255, 255, 255); //cannot compare Color.White because of different names for (int y = 0; y < 32; y++) { for (int x = 0; x < 32; x++) { Color maskPixel = maskBitmap.GetPixel(x, y); Color cursorPixel = maskBitmap.GetPixel(x, y + 32); if (maskPixel == WHITE && cursorPixel == BLACK) { cursor.SetPixel(x, y, Color.Transparent); } else if (maskPixel == BLACK) { cursor.SetPixel(x, y, cursorPixel); } else { cursor.SetPixel(x, y, cursorPixel == BLACK ? WHITE : BLACK); } } } return cursor; } } Icon icon = Icon.FromHandle(hicon); return icon.ToBitmap(); }