C # - захват изображения курсора мыши - PullRequest
30 голосов
/ 28 мая 2009

ФОН

МОЯ ПРОБЛЕМА

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

28 голосов
/ 20 июня 2009

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

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

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

Растровое изображение маски курсора по умолчанию default cursor mask bitmap image

Цветовая карта курсора по умолчанию default cursor color bitmap image

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

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

Растровое изображение маски курсора I-Beam ibeam cursor mask bitmap image

Согласно документации 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 ().
10 голосов
/ 31 января 2012
[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 голосов
/ 23 сентября 2013

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

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

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

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

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

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

1 голос
/ 20 ноября 2016

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

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

...