もっと詳しく

C#でスクリーン(画面)キャプチャする方法は、Windows標準機能からフリーソフトまで数多く出回っています。

しかし、実際に使ってみると、自分が求める使い方が出来ない、もうすこしこんな機能が欲しいなど、なかなかピッタリとニーズが合うものが見つかりません。

そこで、オリジナルの画面キャプチャアプリを作ることにしたのですが、その時に調べた内容をご紹介したいと思います。

コピペで簡単に使えるようにクラス化してみたので、とりあえずC#からキャプチャが撮りたいという方は、是非コピペしてお使いください。

ちなみに、WindowsForm、WPF共通です。

キャプチャの方法

スクリーンキャプチャを行う手順は次の通りです。

  • キャプチャイメージを格納するBitmapを作成
  • 作成したBitmapを引数としてGraphicsオブジェクトを作成
  • CopyFromScreen で Bitmap にキャプチャイメージを格納

画面の指定区画をキャプチャする

以上の事を踏まえて指定した区画のキャプチャ画像を返してくれるサンプルを作ってみました。

引数に区画の座標(X1,Y1,X2,Y2)を渡すと、その範囲のキャプチャ画像をBitmap として返してくれます。

public Bitmap Capture(int x1,int y1,int x2,int y2)
{
    var rect = new Rectangle(x1, y1, x2 - x1, y2 - y1);
    var bmp = new Bitmap(rect.Width,rect.Height);
    var graphics = Graphics.FromImage(bmp);
    graphics.CopyFromScreen(rect.X, rect.Y, 0, 0, rect.Size);
    
    return bmp;
}

CopyFromScreenの引数は、左から 始点X、始点Y、転送先のオフセットX、転送先のオフセットY、区画のサイズを指定します。

スクリーン(画面)全体をキャプチャする

画面全体をキャプチャしたい場合、 WindowsFromの場合は System.Windows.Forms.Screen を、WPFの場合は SystemParameters を参照することでで、スクリーンの縦と横のサイズを取得できますので、これを CopyFromScreen に渡します。

WindowsFormの場合 Screen.PrimaryScreen.Bounds.Width
Screen.PrimaryScreen.Bounds.Height
WPFの場合 SystemParameters.FullPrimaryScreenWidth
SystemParameters.FullPrimaryScreenHeight

先ほど紹介した Capture 関数を使うのであれば、以下の様に記述可能です。

//WindowsFormのサンプル。WPFでも System.Windows.Forms を参照すれば同じ記述が可能。
Capture(0, 0, Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height)

アクティブウィンドウをキャプチャする

アクティブウィンドウをキャプチャする場合、アクティブウィンドウの座標とサイズを取得する必要がありますが、これは .NET 標準では用意されていません。

そのため、User32.DLL と dwmapi.dell をインポートして、直接WindowsのAPIを呼び出すことになります。

[DllImport("user32.dll")]
extern static IntPtr GetForegroundWindow();

[DllImport("dwmapi.dll")]
extern static int DwmGetWindowAttribute(IntPtr hWnd, int dwAttribute, out WindowRect rect, int cbAttribute);

そして、実際にアクティブウィンドウの座標を取得するには、次のようになります。

GetForegroundWindow()でアクティブウィンドウのハンドルを取得し、DwmGetWindowAttributeで座標を取得しています。

DWMWA_EXTENDED_FRAME_BOUNDS はウィンドウの境界情報を取得するための定数で、WindowRectは、Windows API から座標を受け取るための構造体ですが、詳細はサンプルソースに記載しているので、そちらをご確認下さい。

// アクティブウィンドウを取得
IntPtr window_handle = GetForegroundWindow();

//ウィンドウ表示エリアの座標を取得
DwmGetWindowAttribute(window_handle, DWMWA_EXTENDED_FRAME_BOUNDS, out WindowRect wr, Marshal.SizeOf(typeof(WindowRect)));

マルチモニタでのキャプチャ

パソコンに接続されているモニタの情報は、System.Windows.Forms.Screen で取得可能です。

一方、WPFの場合は残念ながら接続されているスクリーン情報を取得する方法がありません。

したがって、WPFでも System.Windows.Forms.Screen を使う必要があります。

画面の指定区画をキャプチャする

マルチモニタの場合、あたかも1つの巨大なモニタのように扱われます。

下記は3台のモニタが接続されている例ですが、真ん中のモニタの左上座標げ(0,0) となっており、左側はマイナス座標(-1980,0)が割り当てられます。

Windowsのディスプレイ設定において、プライマリ座標が(0,0)となり、これより左はマイナス、右はプラスの座標が割り当てられます。

例えば、1がプライマリ、2がサブモニタだった場合、プライマリより左側に設置されたモニタの(0,0)~(100,100) の区間をキャプチャしたい場合、次の様に記述します。

Capture(-1980, 0, -1880,100)

指定したスクリーン(画面)全体をキャプチャする

パソコンに接続されているモニタは、System.Windows.Forms.Screen に実装されている AllScreens プロパティで取得できます。

Screen[] screens = System.Windows.Forms.Screen.AllScreens;

この配列からキャプチャしたいモニタを指定し、Bounds.Height,Bouns.Width を取得すれば、任意のモニタの画面全体がキャプチャ可能です。

下記は、配列0番目のスクリーンに対して、画面全体をキャプチャするサンプルです。

Screen[] screens = System.Windows.Forms.Screen.AllScreens;
Capture(0, 0, screens[0].Bounds.Width, screens[0].Bounds.Height)

ちなみに、Primary メソッドを参照することで、どれがプライマリなのかを知ることができます。

プライマリの場合は true 、そうでない場合は false になっています。

すべてのスクリーン全体をキャプチャする

System.Windows.Forms.Screen.AllScreens を使って全てのスクリーンの座標を取得し、そこから左上(最小座標)と右下(最大座標)を求めることで、1枚の画像にキャプチャできます。

public Bitmap GetAllScreen()
{
    var rect = new Rectangle(0, 0, 0, 0);

    // プライマリスクリーン全体の座標を取得
    foreach (var screen in GetScreenList())
    {
        rect.X = Math.Min(rect.X, screen.Bounds.X);
        rect.Y = Math.Min(rect.Y, screen.Bounds.Y);
        rect.Width = Math.Max(rect.Width, screen.Bounds.Right);
        rect.Height = Math.Max(rect.Height, screen.Bounds.Bottom);
    }
    return Capture(rect.X, rect.Y, rect.Width,rect.Height);
}

アクティブウィンドウをキャプチャする

アクティブウィンドウに関しては、マルチモニタのどこに表示されていても関係ありません。

プライマリと全く同じで、次の記述でキャプチャできます。

// アクティブウィンドウを取得
IntPtr window_handle = GetForegroundWindow();

//ウィンドウ表示エリアの座標を取得
DwmGetWindowAttribute(window_handle, DWMWA_EXTENDED_FRAME_BOUNDS, out WindowRect wr, Marshal.SizeOf(typeof(WindowRect)));

サンプルソース(クラス化したもの)

これまでの内容を元に、キャプチャを簡単に行えるようクラス化したサンプルソースは次のようになります。

使い方はコメントを見ていただくと分かると思うので割愛します。

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Windows;

namespace WinCapture
{
    public class ScreenCapture
    {
        // ウィンドウの境界情報を取得するための定数
        int DWMWA_EXTENDED_FRAME_BOUNDS = 9;

        [StructLayout(LayoutKind.Sequential)]
        private struct WindowRect
        {
            public int X1;
            public int Y1;
            public int X2;
            public int Y2;
        }

        [DllImport("user32.dll")]
        extern static IntPtr GetForegroundWindow();

        [DllImport("dwmapi.dll")]
        extern static int DwmGetWindowAttribute(IntPtr hWnd, int dwAttribute, out WindowRect rect, int cbAttribute);

        /// <summary>
        /// 全てのスクリーンを取得
        /// </summary>
        /// <returns></returns>
        public Screen[] GetScreenList()
        {
            return System.Windows.Forms.Screen.AllScreens;
        }

        /// <summary>
        /// 指定した区画をキャプチャする
        /// </summary>
        /// <param name="x1"></param>
        /// <param name="y1"></param>
        /// <param name="x2"></param>
        /// <param name="y2"></param>
        /// <returns></returns>
        public Bitmap Capture(int x1,int y1,int x2,int y2)
        {
            var rect = new Rectangle(x1, y1, x2 - x1, y2 - y1);
            var bmp = new Bitmap(rect.Width,rect.Height);
            var graphics = Graphics.FromImage(bmp);
            graphics.CopyFromScreen(rect.X, rect.Y, 0, 0, rect.Size);
            
            return bmp;
        } 

        /// <summary>
        /// マルチスクリーン全体をキャプチャする
        /// </summary>
        /// <returns></returns>
        public Bitmap GetAllScreen()
        {
            var rect = new Rectangle(0, 0, 0, 0);

            // プライマリスクリーン全体の座標を取得
            foreach (var screen in GetScreenList())
            {
                //Console.WriteLine(s.Bounds);
                rect.X = Math.Min(rect.X, screen.Bounds.X);
                rect.Y = Math.Min(rect.Y, screen.Bounds.Y);
                rect.Width = Math.Max(rect.Width, screen.Bounds.Right);
                rect.Height = Math.Max(rect.Height, screen.Bounds.Bottom);
            }
            return Capture(rect.X, rect.Y, rect.Width,rect.Height);
        }

        /// <summary>
        /// プライマリスクリーン全体をキャプチャする
        /// </summary>
        /// <returns></returns>
        public Bitmap GetPrymaryScreen()
        {
            // プライマリスクリーン全体の座標を取得
            return Capture(0, 0, Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height);
        }

        /// <summary>
        /// 指定したスクリーン全体をキャプチャする
        /// </summary>
        /// <param name="screen"></param>
        /// <returns></returns>
        public Bitmap GetScreen(Screen screen)
        {
            // プライマリスクリーン全体の座標を取得
            return Capture(0, 0, screen.Bounds.Width, screen.Bounds.Height);
        }

        /// <summary>
        /// アクティブウィンドウをキャプチャする
        /// </summary>
        /// <returns></returns>
        public Bitmap GetActiveWindow()
        {
            // アクティブウィンドウを取得
            IntPtr window_handle = GetForegroundWindow();

            //ウィンドウ表示エリアの座標を取得
            DwmGetWindowAttribute(window_handle, DWMWA_EXTENDED_FRAME_BOUNDS, out WindowRect wr, Marshal.SizeOf(typeof(WindowRect)));

            //キャプチャの実行
            return Capture(wr.X1, wr.Y1, wr.X2, wr.Y2);
        }
    }
}

まとめ

今回は、C#でスクリーン(画面)キャプチャする方法について、具体的なサンプルプログラムを用いて解説致しました。

マルチモニタの場合であっても、プログラムから扱う場合は1つの巨大なモニタが接続されているイメージで扱えます。

プライマリより左側のモニタはX軸がマイナスになりますが、そのポイントさえ押さえておけば、マルチモニタ構成であっても任意の範囲をキャプチャすることができます。

画面キャプチャのフリーソフトは数多く出回っていますが、もし自分のニーズに合わずに自作を考えた場合、是非この記事を参考にしてください。