未来の自分へ知識貯金

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

約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 で囲みます。戻り値の Bitmap も IDisposable なので、呼び出し側で使い終わったら using か Dispose() で解放してください。タイマーで連続キャプチャするようなツールだと、ここを忘れるとメモリ使用量がどんどん増えていきます。

全画面を撮りたいとき

領域ではなく画面全体を撮りたい場合は、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 を使う
  • Graphics も Bitmap も IDisposable なので using で解放する(連続キャプチャでは特に注意)
  • 全画面は Screen.PrimaryScreen.Bounds、マルチモニタは SystemInformation.VirtualScreen
  • 高DPI環境で座標がずれるときは app.manifest で DPI-Aware を宣言する(最重要)

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

この記事をシェアする

ゆーき
ゆーき

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

プロフィールを見る →