31

バックグラウンド

  • 私はスクリーンキャプチャアプリケーションを書いています
  • 私のコードは、このプロジェクトから派生したものです: http://www.codeproject.com/KB/cs/DesktopCaptureWithMouse.aspx?display=Print
  • コードはマウスカーソルもキャプチャすることに注意してください(これは私にとって望ましいことです)

私の問題

  • マウス カーソルが通常のポインターまたは手のアイコンの場合、コードは正常に動作します。マウスはスクリーンショットで正しくレンダリングされます。
  • ただし、マウス カーソルが挿入ポイント (「I ビーム」カーソル) に変更されると (たとえば、NOTEPAD で入力すると)、コードが機能しなくなります。その結果、カーソルのかすかなイメージが表示されます。期待される空白と白の代わりに、非常に半透明(灰色)のバージョンです。

私の質問

  • 画像がこれらの「Iビーム」タイプの画像の1つである場合、マウスカーソル画像をキャプチャするにはどうすればよいですか
  • 注:元の記事をクリックすると、誰かが提案を提供します - それは機能しません

ソース

これは元の記事からです。

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

6 に答える 6

30

なぜこれが起こるのか正確に説明することはできませんが、それを回避する方法を示すことができると思います.

ICONINFO 構造体には、カーソルのマスク ビットマップとカラー ビットマップをそれぞれ含む hbmMask と hbmColor の 2 つのメンバーが含まれています (公式ドキュメントについては、ICONINFOの MSDN ページを参照してください)。

デフォルト カーソルに対して GetIconInfo() を呼び出すと、以下に示すように、ICONINFO 構造体に有効なマスク ビットマップとカラー ビットマップの両方が含まれます (注: 画像の境界を明確に示すために赤い境界線が追加されています)。

デフォルトのカーソル マスク ビットマップ デフォルトのカーソル マスク ビットマップ イメージ

デフォルトのカーソル カラー ビットマップ デフォルトのカーソル カラーのビットマップ イメージ

Windows が既定のカーソルを描画するとき、マスク ビットマップは最初に AND ラスター操作で適用され、次にカラー ビットマップが XOR ラスター操作で適用されます。これにより、カーソルが不透明になり、背景が透明になります。

ただし、I ビーム カーソルに対して GetIconInfo() を呼び出すと、以下に示すように、ICONINFO 構造体には有効なマスク ビットマップのみが含まれ、カラー ビットマップは含まれません (注: ここでも、画像の境界を明確に示すために赤い境界線が追加されています):

I ビーム カーソル マスク ビットマップ ibeam カーソル マスク ビットマップ イメージ

ICONINFO のドキュメントによると、I ビーム カーソルはモノクロ カーソルです。マスク ビットマップの上半分は AND マスクで、マスク ビットマップの下半分は XOR ビットマップです。Windows が I ビーム カーソルを描画するとき、最初にこのビットマップの上半分が AND ラスター操作でデスクトップ上に描画されます。ビットマップの下半分は、XOR ラスター操作で上に描画されます。画面上では、カーソルはその背後にあるコンテンツの反転として表示されます。

リンクした元の記事のコメントの1つに、これについて言及されています。デスクトップでは、ラスター操作がデスクトップ コンテンツに適用されるため、カーソルは正しく表示されます。ただし、投稿されたコードのように、画像が背景なしで描画されると、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. モノクロ カーソルのチェックは、高さが幅の 2 倍であるかどうかを単純にテストします。これは論理的に思えますが、ICONINFO のドキュメントでは、これによってモノクロ カーソルのみを定義することは義務付けられていません。
  2. 私が使用したメソッド呼び出しの BitBlt() - BitBlt() - MakeTransparent() の組み合わせよりも、カーソルをレンダリングするためのより良い方法がおそらくあるでしょう。
于 2009-06-20T02:54:40.753 に答える
10
[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;
}
于 2012-01-31T10:42:23.187 に答える
4

これは、複数の画面で機能する 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;
    }
}
于 2013-09-23T17:09:19.180 に答える
2

Iビームカーソルの半透明の「灰色」バージョンについての説明から、画像のスケーリングやカーソルの位置のずれに問題が発生しているのではないかと思います。

そのサイトに投稿している人の1人が、私が追跡した独特の動作を含むレポートへの(壊れた)リンクを提供しました:http ://www.efg2.com/Lab/Graphics/CursorOverlay.htm

そのページの例はC#ではありませんが、codeprojectソリューションの作成者が同様のことを行っている可能性があり、グラフィックスオブジェクトをさまざまな場面で使用するときにスケーリングを台無しにしたことはわかっています。

ImageMouseDownイベントでは、画像が読み込まれると、Canvas.Drawメソッドを使用して、ビットマップの上に透明度を使用してCusorBitmapが描画されます。ビットマップがTImageに収まるように引き伸ばされる場合は、いくつかの座標調整(再スケーリング)が必要であることに注意してください。

于 2009-05-29T20:24:42.250 に答える
2

これは、このページに示されているバグのすべての修正を含む、パッチが適用されたバージョンです。

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

于 2016-11-19T21:32:48.483 に答える