C# - Captura de la imagen del cursor del ratón


ANTECEDENTES

MI PROBLEMA

  • El código funciona bien cuando el cursor del ratón es el puntero normal o el icono de la mano - el ratón se representa correctamente en el captura de pantalla
  • Sin embargo, cuando el cursor del ratón se cambia al punto de inserción (el cursor "I-beam"), por ejemplo, escribiendo en el BLOC de NOTAS, el código no funciona, el resultado es que obtengo una imagen débil del cursor, como una versión muy translúcida (gris) en lugar del blanco y blanco que cabría esperar.

MI PREGUNTA

  • ¿Cómo puedo capturar la imagen del cursor del ratón cuando la imagen es una de estas imágenes de tipo "I-beam"
  • NOTA: Si hace clic en el original artículo alguien ofrece una sugerencia-no funciona

FUENTE

Esto es del artículo original.

    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;
    }
Author: namenlos, 2009-05-28

6 answers

Aunque no puedo explicar exactamente por qué sucede esto, creo que puedo mostrar cómo evitarlo.

La estructura ICONINFO contiene dos miembros, hbmMask y hbmColor, que contienen la máscara y los mapas de bits de color, respectivamente, para el cursor (consulte la página MSDN para ICONINFO para la documentación oficial).

Cuando se llama a GetIconInfo () para el cursor predeterminado, la estructura ICONINFO contiene tanto máscara válida como mapas de bits de color, como se muestra a continuación (Nota: el borde rojo se ha añadido a mostrar claramente los límites de la imagen):

Mapa de Bits de Máscara de Cursor Predeterminado imagen de mapa de bits de máscara de cursor predeterminada http://img4.imageshack.us/img4/1108/arrowmask.png

Mapa de Bits de Color del Cursor Predeterminado imagen de mapa de bits de color del cursor predeterminado http://img191.imageshack.us/img191/7680/arrowcolor.png

Cuando Windows dibuja el cursor predeterminado, el mapa de bits de máscara se aplica primero con una operación Y ráster, luego el mapa de bits de color se aplica con un ráster XOR operación. Esto resulta en un cursor opaco y un fondo transparente.

Cuando se llama a Geiconinfo () para el cursor I-Beam, sin embargo, la estructura ICONINFO solo contiene un mapa de bits de máscara válido, y ningún mapa de bits de color, como se muestra a continuación (Nota: de nuevo, se ha agregado el borde rojo para mostrar claramente los límites de la imagen):

Mapa de Bits de Máscara de Cursor I-Beam imagen de mapa de bits de la máscara de cursor ibeam http://img14.imageshack.us/img14/6025/ibeammask.png

Según ICONINFO documentación, el cursor I-Beam es entonces un cursor monocromo. La mitad superior del mapa de bits de la máscara es la máscara Y, y la mitad inferior del mapa de bits de la máscara es el mapa de bits XOR. Cuando Windows dibuja el cursor I-Beam, la mitad superior de este mapa de bits se dibuja primero sobre el escritorio con una operación Y ráster. La mitad inferior del mapa de bits se dibuja sobre la parte superior con una operación ráster XOR. En pantalla, el cursor aparecerá como el inverso del contenido detrás de él.

Uno de los comentarios para el artículo original que ha vinculado menciona esto. En el escritorio, dado que las operaciones ráster se aplican sobre el contenido del escritorio, el cursor aparecerá correcto. Sin embargo, cuando la imagen se dibuja sin fondo, como en el código publicado, las operaciones de trama que Windows realiza dan como resultado una imagen desvanecida.

Dicho esto, este método CaptureCursor() actualizado manejará cursores tanto en color como monocromáticos, proporcionando una imagen de cursor negra cuando el cursor es monocromo.

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

Hay algunos problemas con el código que pueden o no ser un problema.

  1. La comprobación de un cursor monocromo simplemente prueba si la altura es el doble de la anchura. Si bien esto parece lógico, la documentación ICONINFO no exige que solo un cursor monocromo esté definido por esto.
  2. Probablemente hay una mejor manera de renderizar el cursor que la combinación BitBlt() - BitBlt() - MakeTransparent() de llamadas a métodos que usé.
 25
Author: Tarsier,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-10-22 05:21:14
[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;
}
 10
Author: Dimitar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-01-31 10:42:23

Aquí hay una versión modificada de la respuesta de Dimitar (usando DrawIconEx) que funcionó para mí en múltiples pantallas:

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;
    }
}
 3
Author: johv,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-09-23 17:09:19

Su descripción de una versión translúcida 'gris' del cursor I-beam me hace preguntarme si está encontrando un problema con el escalado de la imagen o la mala posición del cursor.

Una de las personas que publican en ese sitio proporcionó un enlace (roto) a un informe con un comportamiento peculiar que he rastreado: http://www.efg2.com/Lab/Graphics/CursorOverlay.htm

Los ejemplos en esa página no están en C# , pero el autor de la solución codeproject puede haber estado haciendo algo similar y sé que he jodido mi escalado cuando uso el objeto gráfico en muchas ocasiones:

En cualquier evento ImageMouseDown una vez se carga la imagen, el CusorBitmap es dibujado con transparencia en la parte superior de la mapa de bits usando el Lienzo.Método de dibujar. Nota algunos ajustes de coordenadas (reajuste) son necesarios en caso de que el mapa de bits se extiende para caber en el TImage.

 2
Author: nvuono,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-05-29 20:24:42

Basado en las otras respuestas hice una versión sin todo el material de la API de Windows (para la parte monocromática) porque las soluciones no funcionaban para todos los cursores monocromáticos. Creo el cursor a partir de la máscara combinando las dos partes de la máscara.

Mi solución:

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
Author: Daniel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-06-02 08:38:03

Esta es la versión parcheada con todas las correcciones para los errores presentados en esta página:

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

Esta versión funciona con:

  1. Cursores I-Beam.
  2. Cursores negros.
  3. Cursores normales.
  4. Cursores invertidos.

Ver trabajando, aquí: https://github.com/NickeManarin/ScreenToGif/blob/master/ScreenToGif/Util/Native.cs#L991

 1
Author: Nicke Manarin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-06-13 23:50:51