表示しているマウスカーソル画像を取得表示してみた、WinAPIとWPF
0.1秒ごとにマウスカーソル画像をいくつかの方法で取得して表示している
WinAPIを使ってカーソルのハンドルやカーソル画像のハンドルを取得して
WPF(.NET)の
System.Windows.Interop.Imaging.CreateBitmapSourceFromHIcon
System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap
を使ってBitmapSourceを作成している
マウスカーソルのハンドルから画像作成
[DllImport("user32.dll")]
private static extern IntPtr GetCursor();
GetCursor()で取得したハンドルを
Imaging.CreateBitmapSourceFromHIconにわたす
例えば
Imaging.CreateBitmapSourceFromHIcon(GetCursor(), Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
これだけでカーソル画像が取得できる、けど
形が違う、タイミングによっては同じになることもあるけど、当てにならない
アイコンの情報を取得する
マウスカーソルのハンドルをGetIconInfoに渡すと、いろいろ情報が得られる
その中の、マスク画像のハンドルhbmMaskと、カラー画像?のハンドルhbmColorを
System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmaに渡すとBitmapSourceが得られた
でも、これも形が違う
GetCursorInfo()を使う
いろいろカーソルの情報が得られる
その中のhCursorはカーソルのハンドルは、さっきのGetCursor()で得られるカーソルのハンドルとは別のようで、結果は
表示されているカーソルと同じのが取得できた!
さらに
エクセルのシート上での十字形のカーソルも正しく取得できた
いいね
自作した大きめなマウスカーソルも取得できている
0.1秒ごとの取得だから少しラグがあるけど、アニメーションするカーソルも取得できてたのは意外だった
マスクが必要なカーソル?
テキストエディタとかテキスト編集中のI型カーソルは残念ながら取得できていない、影も形もない完全透明画像が取得されている
それっぽいのが取得できたのは
GetCursorInfo()で得たマウスカーソルのハンドルを、GetIconInfo()に渡して得られるマスク画像のハンドルから作成する方法だった
白背景に白画像はわかりにくかったので
32x64ピクセルの縦長の画像の下半分にマウスカーソルが表示されている
マスク画像ってのは他の画像と重ね合わせて使うみたいなんだけどねえ、まだわかってない
今回試したことでわかったのは
GetCursorInfoを使えばいい
これで取得できないカーソルはGetCursorInfoとIconInfoで得られるマスク画像を使ってなんとかする
参照したところ
C# - Capturing the Mouse cursor image - Stack Overflow
https://stackoverflow.com/questions/918990/c-sharp-capturing-the-mouse-cursor-image
c# - j_C#_j-Mousecursorイメージのキャプチャ
https://python5.com/q/ukmbkppc
C# - マウスカーソルイメージのキャプチャ
https://www.366service.com/jp/qa/24498664dd5f63c42b15614906b07ff2
このあたりを見ると、マスク画像の上半分をAND、下半分をXORで使うとあって、その処理はAPIのBitBlt()を使っている
API 関数解説
https://www.tokovalue.jp/function/BitBlt.htm
こちらを見ると、BitBltにはオプションがあって、SRCERASEでAND演算、SRCINVERTでXOR演算できるみたいなんだけど、BitBltはDCとかビットマップオブジェクトとか、よくわからんから普通の画像処理としてWPFで行いたいんだよねえ
カーソルと重なった部分の色
ここを拡大してみる
文字がない部分は白が黒に変化している、つまり色が反転している、これはわかる、白地に白のカーソルのままじゃ見分けがつかない
一方、文字の紫の部分は緑色になっている
それぞれのRGB値を確認したら
167, 70, 255 紫
88, 185, 000 緑
だった
この辺のマスク処理は次回
ScreenToGifでスクショ
今まであるの気づかなかった
メニューのホームのコピーをクリック、もしくはCtrl+C押すと、表示しているフレームの画像がクリップボードに格納されるってあったんだけど、実際にはクリップボードには格納されずに、ScreenToGifのClipboardフォルダに1枚毎別々のフォルダが作成されてそこに保存された
…1ファイルごとに1フォルダはあかんやろwどっかに設定があるのか新しいバージョンでは直ってるのかな、でもマウスカーソルつきのスクリーンショットが撮れるのはとても便利
ベンチマークの結果
普通だ!
ステキ!!?な環境
- Windows 10 Home
- Visual Studio 2019 Community
- C#、WPF、.NET Core 3.1
- Excel 2007
2020WPF/20201120_マウスカーソル画像取得
github.com
MainWindow.xaml
<Window x:Class="_20201120_マウスカーソル画像取得.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:_20201120_マウスカーソル画像取得" mc:Ignorable="d" Title="MainWindow" Height="380" Width="300"> <Grid UseLayoutRounding="True"> <Grid.Resources> <Style TargetType="StackPanel"> <Setter Property="Margin" Value="8,2,8,10"/> <Setter Property="Background" Value="LightSteelBlue"/> <Setter Property="Orientation" Value="Horizontal"/> </Style> <Style TargetType="Image"> <Setter Property="Stretch" Value="None"/> </Style> <Style TargetType="Border"> <Setter Property="BorderBrush" Value="SteelBlue"/> <Setter Property="BorderThickness" Value="1"/> </Style> </Grid.Resources> <StackPanel Orientation="Vertical"> <StackPanel> <Border> <Image x:Name="MyImageCursor"/> </Border> <TextBlock Text="Cursor"/> </StackPanel> <StackPanel> <Border> <Image x:Name="MyImageCursorMask"/> </Border> <TextBlock Text="CursorMask"/> </StackPanel> <StackPanel> <Border> <Image x:Name="MyImageCursorColor"/> </Border> <TextBlock Text="CursorColor"/> </StackPanel> <StackPanel> <Border> <Image x:Name="MyImageCursorInfo"/> </Border> <TextBlock Text="CursorInfo"/> </StackPanel> <StackPanel Orientation="Horizontal"> <Border> <Image x:Name="MyImageCursorInfoMask"/> </Border> <TextBlock Text="CursorInfoMask"/> </StackPanel> <StackPanel> <Border> <Image x:Name="MyImageCursorInfoColor"/> </Border> <TextBlock Text="CursorInfoColor"/> </StackPanel> </StackPanel> </Grid> </Window>
MainWindow.xaml.cs
using System; using System.Windows; using System.Windows.Media.Imaging; using System.Runtime.InteropServices;//APIのインポートで使っている using System.Windows.Interop;//Imaging.CreateBitmapSourceFromHBitmapで使っている using System.Windows.Threading;//DispatcherTimerで使っている namespace _20201120_マウスカーソル画像取得 { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { #region WindowsAPI^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //座標取得用 private struct POINT { public int X; public int Y; } #region マウスカーソル系API //マウスカーソル関係 [DllImport("user32.dll")] private static extern IntPtr GetCursor(); [DllImport("user32.dll")] private static extern bool GetCursorPos(out POINT lpPoint); [DllImport("user32.dll")] private static extern IntPtr DrawIcon(IntPtr hDC, int x, int y, IntPtr hIcon); [DllImport("user32.dll")] private static extern IntPtr DrawIconEx(IntPtr hDC, int x, int y, IntPtr hIcon, int cxWidth, int cyWidth, int istepIfAniCur, IntPtr hbrFlickerFreeDraw, int diFlags); private const int DI_DEFAULTSIZE = 0x0008;//cxWidth cyWidthが0に指定されている場合に規定サイズで描画する private const int DI_NORMAL = 0x0003;//通常はこれを指定する、IMAGEとMASKの組み合わせ private const int DI_IMAGE = 0x0002;//画像を使用して描画 private const int DI_MASK = 0x0001;//マスクを使用して描画 private const int DI_COMPAT = 0x0004;//このフラグは無視の意味 private const int DI_NOMIRROR = 0x0010;//ミラーリングされていないアイコンとし描画される [DllImport("user32.dll")] private static extern bool GetIconInfo(IntPtr hIcon, out ICONINFO pIconInfo); struct ICONINFO { public bool fIcon; public int xHotspot; public int yHotspot; public IntPtr hbmMask; public IntPtr hbmColor; } [DllImport("user32.dll")] private static extern bool GetCursorInfo(out CURSORINFO pci); [StructLayout(LayoutKind.Sequential)] struct CURSORINFO { public int cbSize; public int flags; public IntPtr hCursor; public POINT ptScreenPos; } [DllImport("user32.dll")] private static extern IntPtr CopyIcon(IntPtr hIcon); [DllImport("user32.dll")] private static extern bool DestroyIcon(IntPtr hIcon);//CopyIcon使ったあとに使う #endregion マウスカーソル系 #endregion コピペ呪文ここまで^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //タイマー用 private DispatcherTimer MyTimer; public MainWindow() { InitializeComponent(); //タイマー初期化 MyTimer = new DispatcherTimer(); MyTimer.Interval = new TimeSpan(0, 0, 0, 0, 100);//0.1秒間隔 MyTimer.Tick += MyTimer_Tick; MyTimer.Start(); //アプリ終了時、タイマーストップ this.Closing += (s, e) => { MyTimer.Stop(); }; } private void MyTimer_Tick(object sender, EventArgs e) { //カーソル画像 //CURSORINFO:カーソルの表示の有無、カーソルのハンドル、座標 //ICONINFO:trueでアイコン falseでカーソルの識別、ホットスポット座標、アイコンマスク画像のハンドル、アイコンカラー画像のハンドル //GetCursorから取得 IntPtr hCursor = GetCursor(); MyImageCursor.Source = FromIconHandle(hCursor); //GetCursor+GetIconInfoから取得 GetIconInfo(hCursor, out ICONINFO iconInfo); MyImageCursorMask.Source = FromBitmapHandle(iconInfo.hbmMask); MyImageCursorColor.Source = FromBitmapHandle(iconInfo.hbmColor); //GetCursorInfoを使って取得 var cInfo = new CURSORINFO(); cInfo.cbSize = Marshal.SizeOf(typeof(CURSORINFO)); GetCursorInfo(out cInfo); MyImageCursorInfo.Source = FromIconHandle(cInfo.hCursor); //GetCursorInfo+GetIconInfoから取得 GetIconInfo(cInfo.hCursor, out ICONINFO corsorInfoIconInfo); MyImageCursorInfoMask.Source = FromBitmapHandle(corsorInfoIconInfo.hbmMask); MyImageCursorInfoColor.Source = FromBitmapHandle(corsorInfoIconInfo.hbmColor); } //カーソルやアイコンのハンドルからBitmapSourceを適当に作成 //5~10分くらい経つと「値が期待された範囲内にありません」エラーが出ることがあるのでtry使った //原因がわからん private BitmapSource FromIconHandle(IntPtr hIcon) { BitmapSource source = null; if (hIcon == IntPtr.Zero) return source; try { source = Imaging.CreateBitmapSourceFromHIcon(hIcon, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions()); } catch (Exception e) { MyTimer.Stop(); Console.WriteLine(e.Message); System.Threading.Thread.Sleep(1000); MyTimer.Start(); } return source; } //Bitmapのハンドルから適当にBitmapSourceを作成 private BitmapSource FromBitmapHandle(IntPtr hBitmap) { //hbmColorは存在しないことがあるので、そのときはnull BitmapSource source = null; if (hBitmap == IntPtr.Zero) return source; try { source = Imaging.CreateBitmapSourceFromHBitmap(hBitmap, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions()); } catch (Exception e) { MyTimer.Stop(); Console.WriteLine(e); System.Threading.Thread.Sleep(1000); MyTimer.Start(); } return source; //return Imaging.CreateBitmapSourceFromHBitmap(hBitmap, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions()); } } }
関連記事
次回は3日後
前回は実質一昨日