C# - захват изображения курсора мыши

ФОН

МОЯ ПРОБЛЕМА

  • Код работает нормально, когда курсор мыши является обычным указателем или значком руки - мышь отображается правильно на скриншоте
  • Однако, когда курсор мыши изменяется на точку вставки (курсор "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;
    }

6 ответов

Решение

Хотя я не могу точно объяснить, почему это происходит, я думаю, что могу показать, как это обойти.

Структура ICONINFO содержит два члена, hbmMask и hbmColor, которые содержат соответственно маску и цветные растровые изображения для курсора (официальную документацию см. На странице MSDN для ICONINFO).

Когда вы вызываете GetIconInfo() для курсора по умолчанию, структура ICONINFO содержит как действительные маски, так и цветовые растровые изображения, как показано ниже (Примечание. Красная граница была добавлена ​​для четкого отображения границ изображения):

Растровое изображение маски курсора по умолчанию Растровое изображение маски курсора по умолчанию http://img4.imageshack.us/img4/1108/arrowmask.png

Цвет курсора по умолчанию для курсора Цвет изображения по http://img191.imageshack.us/img191/7680/arrowcolor.png

Когда Windows рисует курсор по умолчанию, растровое изображение маски сначала применяется с растровой операцией AND, а затем растровое изображение цвета применяется с растровой операцией XOR. Это приводит к непрозрачному курсору и прозрачному фону.

Однако, когда вы вызываете GetIconInfo() для курсора I-Beam, структура ICONINFO содержит только действительное растровое изображение маски и не содержит растровое растровое изображение, как показано ниже (Примечание: снова была добавлена ​​красная граница, чтобы четко показать границы изображения):

Растровая маска курсора I-Beam 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. Проверка монохромного курсора просто проверяет, равна ли высота двойной ширине. Хотя это кажется логичным, документация 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;
}

Вот модифицированная версия ответа Димитара (с использованием 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;
    }
}

Основываясь на других ответах, я сделал версию без всего 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();
}

Это исправленная версия со всеми исправлениями ошибок, представленных на этой странице:

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-Beam курсоры.
  2. Черные курсоры.
  3. Обычные курсоры.
  4. Перевернутые курсоры.

Смотрите работу здесь: https://github.com/NickeManarin/ScreenToGif/blob/master/ScreenToGif/Util/Native.cs#L991

Ваше описание полупрозрачной "серой" версии двутаврового курсора заставляет меня задуматься, сталкивались ли вы с проблемой масштабирования изображения или неправильного расположения курсора.

Один из людей, размещающих на этом сайте, предоставил (неработающую) ссылку на отчет со странным поведением, которое я отслеживал по адресу: http://www.efg2.com/Lab/Graphics/CursorOverlay.htm

Примеры на этой странице не на C#, но автор решения codeproject, возможно, делал нечто подобное, и я знаю, что я испортил свое масштабирование при использовании графического объекта во многих случаях сам:

В любом событии ImageMouseDown после загрузки изображения CusorBitmap рисуется с прозрачностью поверх растрового изображения, используя метод Canvas.Draw. Обратите внимание, что некоторые корректировки координат (изменение масштаба) необходимы в том случае, если растровое изображение растянуто до размера TImage.

Другие вопросы по тегам