Windowsのスクリーンキャプチャーツールを.NETで作ってみた

## 概要

Windows標準のスクリーンキャプチャーツールは、ピクセル単位での領域選択ができない。
いろいろなツールは良いものばかりだけど自分には機能が使いこなせない。
古いツールでは Aero の有効なウィンドウのキャプチャが綺麗にできない。
それならということで、.NET でスクリーンキャプチャーツールを作ってみました。

![20190611190750168.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283306/96e59ac6-ea23-7155-086b-3f2f7b164897.png)

## 環境

* Windows 10 x64 1809
* Visual Studio 2019 Professional
* Power Shell 6 x64
* Git for Windows x64
* .NET Framework 4.7.2 / C# 7.3 / Windows Application

## ソースコード

GitHubにあります。
https://github.com/kerobot/GamenShot.git

できるだけ Win API を利用しないようにしましたが、少々利用しています。
かなり久しぶりにハンドルとか利用したので、実装は怪しいです。
ご利用は自由ですが自己責任でお願いします。

例えばこんな感じ。Bitmap オブジェクトは呼び出し元で using ステートメントを使ってます。
Graphics オブジェクトも using ステートメントを使えるか…。

```csharp
private Bitmap CaptureActiveWindow()
{
    IntPtr hWnd = IntPtr.Zero;
    IntPtr windowDC = IntPtr.Zero;
    Graphics graphics = null;
    IntPtr hDC = IntPtr.Zero;
    try
    {
        // アクティブウィンドウのデバイスコンテキストを取得
        hWnd = NativeAPIUtility.GetForegroundWindow();
        windowDC = NativeAPIUtility.GetWindowDC(hWnd);
        // ウィンドウサイズを取得
        NativeAPIUtility.RECT rect = new NativeAPIUtility.RECT();
        // TODO:クラシックモードを考慮していないことに注意（Aaro有効を前提）
        NativeAPIUtility.DwmGetWindowAttribute(hWnd, (int)NativeAPIUtility.DwmWindowAttribute.DWMWA_EXTENDED_FRAME_BOUNDS, out var bounds, Marshal.SizeOf(typeof(NativeAPIUtility.RECT)));
        NativeAPIUtility.GetWindowRect(hWnd, ref rect);
        // Bitmapの作成
        Bitmap bmp = new Bitmap(bounds.right - bounds.left, bounds.bottom - bounds.top);
        // Graphicsの作成
        graphics = Graphics.FromImage(bmp);
        // Graphicsのデバイスコンテキストを取得
        hDC = graphics.GetHdc();
        // Bitmapに画像をコピー
        NativeAPIUtility.BitBlt(hDC, 0, 0, bmp.Width, bmp.Height, windowDC, bounds.left - rect.left, bounds.top - rect.top, NativeAPIUtility.SRCCOPY);
        return bmp;
    }
    finally
    {
        if (hDC != IntPtr.Zero)
        {
            graphics.ReleaseHdc(hDC);
        }
        if (graphics != null)
        {
            graphics.Dispose();
        }
        if (windowDC != IntPtr.Zero)
        {
            NativeAPIUtility.ReleaseDC(hWnd, windowDC);
        }
    }
}
```

## 実行ファイル

GitHub の Releases の Assets にあります。
https://github.com/kerobot/GamenShot/releases

バージョンはまだ0.1としています。
.NET Framework 4.7.2がインストールされていない環境では、実行時にエラーとなります。

## 参考情報

こちらの情報をフックの実装に利用させて頂きました。
[C#にてマウスとキーボードを操りし者](https://qiita.com/exliko/items/3135e4413a6da067b35d)

## 機能概要

1. 実行するとタスクトレイに常駐してキー入力をフック
2. タスクトレイのメニューかキーボードショートカットでキャプチャ開始
3. デスクトップかアクティブウィンドウか矩形領域をキャプチャ実行
4. 矩形領域ではキーボードの上下左右キーでピクセル単位の範囲指定
5. キャプチャした画像はクリップボードかPNGかBMPへ保存

## これからやりたいこと

* 画像保存時の減色（画像サイズ縮小）
    Word とか Excel に画像を貼り付けるときにできるだけ画像のサイズを小さくしたい。

* 画像保存時の画像フォーマット増加
    画像処理系の実装は楽しいので、いろいろなフォーマットを試してみたい。

* キーボードショートカットの任意設定　など
    複雑にしすぎない程度の設定機能の追加やカーソルを表示してキャプチャの ON/OFF とか。

## 反省点や課題

* 画像をいったん PNG 形式でストリームに保存してからクリップボードにセットしているのが心苦しい。
    メディアンカットなど、減色による画像サイズの縮小を試してみる。

~~* ラバーバンドの描画を XOR 演算でやろうとしたけど、どうしても描画色が反転しない。
    SetROP2 で描画モードに R2_NOT とか指定して、前回の描画を今回の描画で上書きしたのに線が消えなかった。
    XOR 演算を利用した描画が定石だと思っていたけど、環境の問題かしら？~~

~~* Win32API ではなく、ControlPaint.DrawReversibleFrame を使ってもうまく描画できない。
    そもそもの使い方が間違っているのかも、あらためて挑戦してみたい。~~

2019/06/14更新

* ControlPaint.DrawReversibleFrameを利用して、前回の描画を今回の描画で上書きしてラバーバンドの描画ができた。

2019/06/19追記

* ラバーバンド描画がうまくできる場合と、うまくできない（移動前の線が消去されない）場合がある。今の時点で分かったことは、Intel系GPUやAMD系GPU(Radeon)だとうまくいくけど、nVidia系GPUではうまくいかない…。