未来の自分へ知識貯金

ゆーきのエンジニアブログ

約4分 C#Visual Studioプログラミング

【C#】画面の一部をスクリーンショットする

【C#】画面の一部をスクリーンショットする

デスクトップ上の領域をスクリーンショットするコードを記載します。

Windows 標準の Snipping Tool を使えば手動でのキャプチャはできますが、「ボタンひとつで決まった領域を撮りたい」「撮った画像をそのままアプリ内で処理したい」となると自作したくなります。私の場合は、画面の一部を切り取って OCR(文字認識)にかけるツールを作ったときにこのコードを使いました。

結論のコード

using System.Drawing;

public Bitmap CaptureScreen(Rectangle screenRect)
{
    Bitmap bmp = new Bitmap(screenRect.Size.Width, screenRect.Size.Height);
    using (Graphics g = Graphics.FromImage(bmp))
    {
        g.CopyFromScreen(screenRect.Location, new Point(0, 0), bmp.Size);
    }
    return bmp;
}

呼び出し側はこうなります。画面左上から 800×600 の領域を撮って PNG に保存する例です。

Rectangle rect = new Rectangle(0, 0, 800, 600);
using (Bitmap bmp = CaptureScreen(rect))
{
    bmp.Save(@"C:\temp\capture.png", System.Drawing.Imaging.ImageFormat.Png);
}

実行すると、指定した領域がそのまま画像ファイルになります。Windows フォームアプリなら参照の追加なしで動きます(System.Drawing は最初から使える状態です)。

コードの解説

やっていることは 3 ステップです。

  1. new Bitmap(width, height) で、キャプチャ結果の入れ物になる空の画像を作る
  2. Graphics.FromImage(bmp) で、その画像に対する「描画ペン」にあたる Graphics を取得する
  3. CopyFromScreen で、画面の内容を画像へ転写する

CopyFromScreen の引数は次の意味です。

引数意味
第1引数 screenRect.Location画面側のコピー元の左上座標
第2引数 new Point(0, 0)画像側の貼り付け先の左上座標(普通は 0,0 でよい)
第3引数 bmp.Sizeコピーするサイズ

Graphics は使い終わったら解放が必要なので using で囲みます。戻り値の BitmapIDisposable なので、呼び出し側で使い終わったら usingDispose() で解放してください。タイマーで連続キャプチャするようなツールだと、ここを忘れるとメモリ使用量がどんどん増えていきます。

全画面を撮りたいとき

領域ではなく画面全体を撮りたい場合は、Screen クラスから画面サイズを取得して同じメソッドに渡すだけです。

// プライマリモニタ全体
Bitmap bmp = CaptureScreen(Screen.PrimaryScreen.Bounds);

// マルチモニタ環境で全モニタをまとめて撮る
Bitmap all = CaptureScreen(SystemInformation.VirtualScreen);

クリップボードにコピーする

ファイル保存ではなく、撮った画像をそのまま他のアプリに貼り付けたいときは Clipboard.SetImage が便利です。

using (Bitmap bmp = CaptureScreen(rect))
{
    Clipboard.SetImage(bmp);
}

これで Ctrl+V で Excel やペイントに貼り付けられる状態になります。

ハマりポイント:高DPI環境で座標がずれる

このコードで一番ハマりやすいのが、ディスプレイの拡大率が 100% 以外(125%・150% など)に設定されている環境で、撮りたい場所とずれた領域がキャプチャされるという現象です。

原因は、アプリが「DPI 非対応」として動いていると、Windows が座標やサイズを拡大率で読み替えてしまうためです。たとえば拡大率 150% の環境では、コード上の 800px が実際の画面では 1200px 相当として扱われ、キャプチャ範囲が想定とずれます。

対処としては、アプリを DPI 対応(DPI-Aware)として宣言するのが確実です。プロジェクトに app.manifest を追加し、次のコメントアウトを外します。

<application xmlns="urn:schemas-microsoft-com:asm.v3">
  <windowsSettings>
    <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
  </windowsSettings>
</application>

これでコード上の座標と実際の画面座標が 1:1 になり、CopyFromScreen が見たままの範囲を撮ってくれるようになります。「自分の PC では動くのに、別の PC だとずれる」というときは、まず拡大率の設定を疑ってみてください。

応用:範囲選択と組み合わせてキャプチャツールにする

以前の記事と組み合わせると、「ユーザーがマウスで選んだ領域をキャプチャする」という Snipping Tool 風の動きが作れます。

範囲指定フォームで取得した Rectangle を、そのまま今回の CaptureScreen に渡すだけです。撮った Bitmap を OCR にかけたり、画像比較に使ったりと、ここから先はアイデア次第で広げられます。

まとめ

  • 画面の一部を撮るには Bitmap + Graphics.CopyFromScreen を使う
  • GraphicsBitmapIDisposable なので using で解放する(連続キャプチャでは特に注意)
  • 全画面は Screen.PrimaryScreen.Bounds、マルチモニタは SystemInformation.VirtualScreen
  • 高DPI環境で座標がずれるときは app.manifest で DPI-Aware を宣言する(最重要)

コード自体は数行ですが、DPI の件は知らないと「環境によって動きが違う」という嫌なバグになりがちです。同じところで詰まった方の参考になれば幸いです。

この記事をシェアする

ゆーき
ゆーき

プログラマー経験10年以上。好きなプログラム言語はC#。使える言語はC++, Python他。好きな開発環境はVisual Studio。

プロフィールを見る →