代码之家  ›  专栏  ›  技术社区  ›  namenlos

C-捕获鼠标光标图像

  •  30
  • namenlos  · 技术社区  · 16 年前

    背景

    我的问题

    • 当鼠标光标是普通指针或手图标时,代码工作正常-鼠标在屏幕截图上呈现正确
    • 然而,当鼠标光标被改变为插入点(“i-束”光标)时,例如在记事本中键入代码,那么代码就不起作用了,结果是,我得到光标的模糊图像,就像它的半透明(灰色)版本,而不是空白的和白色的。

    我的问题

    • 当图像是这些“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;
        }
    
    6 回复  |  直到 8 年前
        1
  •  25
  •   caesay    9 年前

    虽然我不能确切地解释为什么会发生这种情况,但我想我可以展示如何绕过它。

    iconinfo结构包含两个成员hbmmask和hbmcolor,分别包含光标的掩码和颜色位图(请参见msdn页 ICONINFO 官方文件)。

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

    默认光标蒙板位图 default cursor mask bitmap image http://img4.imageshack.us/img4/1108/arrowmask.png

    默认光标颜色位图 default cursor color bitmap image http://img191.imageshack.us/img191/7680/arrowcolor.png

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

    但是,当您为i-beam光标调用geticoninfo()时,iconinfo结构只包含有效的遮罩位图,而不包含颜色位图,如下所示(注意:再次添加红色边框以清楚地显示图像边界):

    工字梁光标蒙板位图 ibeam cursor mask bitmap image http://img14.imageshack.us/img14/6025/ibeammask.png

    根据iconinfo文档,i-beam光标是单色光标。遮罩位图的上半部分是和遮罩,而遮罩位图的下半部分是XOR位图。当Windows绘制工字梁光标时,该位图的上半部分首先通过“和光栅”操作绘制在桌面上。然后,使用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();
    }
    

    代码有一些问题,可能是或不是问题。

    1. 检查单色光标只需测试高度是否是宽度的两倍。虽然这看起来很合乎逻辑,但ICONINFO文档并不要求仅定义单色光标。
    2. 可能有更好的方法来呈现我使用的方法调用的bitblt()-bitblt()-makeTransparent()组合的光标。
        2
  •  10
  •   Dimitar    13 年前
    [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;
    }
    
        3
  •  3
  •   johv Chris    12 年前

    下面是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;
        }
    }
    
        4
  •  2
  •   nvuono    16 年前

    您对半透明的“灰色”i-beam光标的描述让我怀疑您是否遇到了图像缩放或光标位置错误的问题。

    在那个网站上发布的一个人提供了一个(断开的)链接,指向一个具有特殊行为的报告,我跟踪到: http://www.efg2.com/Lab/Graphics/CursorOverlay.htm

    该页面上的示例不在C中,但代码项目解决方案的作者可能正在做类似的事情,我知道在很多场合使用图形对象时,我自己都搞砸了缩放比例:

    在任何ImageMousedown事件中, 图像已加载,CusOrbitmap是 在 使用canvas.draw方法进行位图。 注意一些坐标调整 (重新缩放)是必要的,以防 位图被拉伸以适应 TImage。

        5
  •  2
  •   Daniel    8 年前

    基于其他答案,我制作了一个没有所有Windows API材料的版本(对于单色部分),因为解决方案不适用于所有单色光标。我通过组合两个遮罩部分从遮罩创建光标。

    我的解决方案:

    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();
    }
    
        6
  •  1
  •   Vladimír Adamovský    8 年前

    这是修补版本,包含此页面上显示的所有错误修复:

    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. I-光束光标。
    2. 黑色游标。
    3. 正常游标。
    4. 反向光标。

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

    推荐文章