Elsaの技術日記(徒然なるままに)

主に自分で作ったアプリとかの報告・日記を記載

MENU

特定のアプリに限定したスクリーン録画を試みる

先日、OpenCVSharpを用いて画面全体でのスクリーン録画を試してみました。
elsammit-beginnerblg.hatenablog.com

ですがこの方法ですと、
特定のアプリが映っている領域のみ録画したい場合には無駄な領域も写ってしまいますね。
後で余計な領域を削除するのも大変なので、今回は特定のアプリケーションの領域のみを録画する方法をまとめたいと思います!!



■前提条件

アプリケーション領域に限定したスクリーン録画の方法をまとめるにあたり、
前回の画面全体のスクリーン録画で用いたコードを流用します。

このため本ブログではxamlコードの掲載は割愛します。
必要に応じて前回のブログをご参照ください。

■アプリケーションの座標やサイズ取得方法

アプリケーションの領域に限定したスクリーン録画をまとめる前に、
アプリケーション領域を特定するための座標やサイズを取得する方法をまとめます。

アプリケーションの座標やサイズ取得するにあたり、
Windows APIの1つであるuser32.dllを用います。
user32.dllを用いるにあたり、

using System.Runtime.InteropServices;

を定義ください。

さらに、user32.dllのGetWindowRect()APIを用いるためあらかじめ、
こちらのコードも定義しておきます。

        [DllImport("user32.dll")]
        private static extern bool GetWindowRect(IntPtr hwnd, out RECT lpRect);

        [StructLayout(LayoutKind.Sequential)]
        private struct RECT
        {
            public int left;
            public int top;
            public int right;
            public int bottom;
        }

GetWindowRectは該当するウィンドウハンドルに対する座標情報を返すAPIになります。
引数ですが、
 ・IntPtr hwnd:ウィンドウハンドル
 ・RECT lpRect:ウィンドウハンドルに対する座標情報
になります。

ここまでがアプリケーションの座標や領域の情報を取得するための前準備。
では実際に情報を取得する関数を作成していきます。
コードはこちら。

private bool GetAppPosition(ref RECT rect)
{
    bool flag = false;
    string appName = "notepad"; //ここに実行ファイル名(拡張子なし)を記入する.

    try
    {
        var mainWindowHandle = System.Diagnostics.Process.GetProcessesByName(appName)[0].MainWindowHandle;
        flag = GetWindowRect(mainWindowHandle, out rect);
    }
    catch
    {
        flag = false;
    }

    return flag;
}

appName変数に記入したアプリケーション名(実行ファイル名)の座標を取得するコードになります。
さらに、アプリケーション名が存在しない場合や領域取得できなかった場合にエラーとするためにOK/NGを示すflagを返すようにしております。

System.Diagnostics.Process.GetProcessesByName(appName)は、
引数に与えたアプリケーション名を元に全てのプロセス情報を返すAPIになっております。
今回は取得できたプロセス情報のうち1つ目に見つけられたプロセス情報のみを抽出した上で、
プロセス情報のうち、ウィンドウハンドル情報をmainWindowHandle 変数に代入しています。

var mainWindowHandle = System.Diagnostics.Process.GetProcessesByName(appName)[0].MainWindowHandle;

後は先ほど用意した、
GetWindowRect(mainWindowHandle, out rect);
でウィンドウハンドルから座標情報を取得して終了です。

■特定アプリの領域を切り出してスクリーン録画してみる

では本題の特定アプリの領域を切り出してスクリーン録画を行う方法をまとめます。
といっても座標情報を先ほどのコードで得られるので後は領域指定して録画するのみになります。

全体のコードはこちら。

using System;
using System.Windows;
using System.Windows.Media.Imaging;
using System.Drawing;
using System.Windows.Interop;
using System.Threading;
using OpenCvSharp;
using OpenCvSharp.Extensions;
using System.Runtime.InteropServices;

namespace WpfApp1
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : System.Windows.Window
    {
        [DllImport("user32.dll")]
        private static extern bool GetWindowRect(IntPtr hwnd, out RECT lpRect);

        [StructLayout(LayoutKind.Sequential)]
        private struct RECT
        {
            public int left;
            public int top;
            public int right;
            public int bottom;
        }


        public MainWindow()
        {
            InitializeComponent();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            Thread thread = new Thread(new ThreadStart(() =>
            {
                CaptureMovieAsync();
            }));
            thread.Start();
        }

        private void CaptureMovieAsync()
        {
            using (var writer = new VideoWriter("test.wmv", FourCC.WMV3, 5, new OpenCvSharp.Size((int)SystemParameters.PrimaryScreenWidth, (int)SystemParameters.PrimaryScreenHeight)))
            {


                for (int i = 0; i < 100; i++)
                {
                    RECT rect = new RECT();
                    bool flag = GetAppPosition(ref rect);
                    int width = rect.right - rect.left;
                    int height = rect.bottom - rect.top;

                    using (var screenBmp = new System.Drawing.Bitmap(
                        width,
                        height,
                        System.Drawing.Imaging.PixelFormat.Format32bppArgb))
                    {
                        using (var bmpGraphics = Graphics.FromImage(screenBmp))
                        {
                            bmpGraphics.CopyFromScreen(rect.left, rect.top, 0, 0, screenBmp.Size);

                            Dispatcher.Invoke((Action)(() =>
                            {
                                ImgCap.Source = Imaging.CreateBitmapSourceFromHBitmap(
                                screenBmp.GetHbitmap(),
                                IntPtr.Zero,
                                Int32Rect.Empty,
                                BitmapSizeOptions.FromEmptyOptions());

                                Mat mat = BitmapConverter.ToMat(screenBmp).CvtColor(ColorConversionCodes.RGB2BGR);
                                Cv2.CvtColor(mat, mat, ColorConversionCodes.BGR2RGB);
                                Cv2.Resize(mat, mat, new OpenCvSharp.Size((int)SystemParameters.PrimaryScreenWidth, (int)SystemParameters.PrimaryScreenHeight));
                                writer.Write(mat);
                            }));
                        }
                    }
                    Thread.Sleep(100);
                }
            }    
        }

        private bool GetAppPosition(ref RECT rect)
        {
            bool flag = false;
            string appName = "notepad"; //ここに実行ファイル名(拡張子なし)を記入する.

            try
            {
                var mainWindowHandle = System.Diagnostics.Process.GetProcessesByName(appName)[0].MainWindowHandle;
                flag = GetWindowRect(mainWindowHandle, out rect);
            }
            catch
            {
                flag = false;
            }

            return flag;
        }
    }
}

追加・変更したコードはこちらのみ。

 for (int i = 0; i < 100; i++)
                {
                    RECT rect = new RECT();
                    bool flag = GetAppPosition(ref rect);
                    int width = rect.right - rect.left;
                    int height = rect.bottom - rect.top;

                    using (var screenBmp = new System.Drawing.Bitmap(
                        width,
                        height,
                        System.Drawing.Imaging.PixelFormat.Format32bppArgb))
                    {
                           using (var bmpGraphics = Graphics.FromImage(screenBmp))
                        {
                            bmpGraphics.CopyFromScreen(rect.left, rect.top, 0, 0, screenBmp.Size);

実施していることは、
GetAppPosition()関数にて取得できたRECT情報からアプリケーションのサイズを算出。
算出した結果をscreenBmp定義時にサイズ情報として入力かつ、
CopyFromScreen()関数にて取得するスクリーンショットの領域をアプリケーションの領域に指定しているのみになります。

アプリケーションの座標さえ手に入ってしまえば後はスクリーンショットとして取得する領域を合わせるだけですね。

■最後に

今回は特定のアプリケーションに限定したスクリーン録画を試してみました。
最近スクリーンショット取得すること多いので利用していきたいと思います。