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

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

MENU

WPFでスクリーンショットや画面操作動画表示

今回はC#スクリーンショットや画面をリアルタイムで表示する方法をまとめたいと思います。
最終的にはC#で画面録画を実現するまでをまとめたいと思いますが、
今回は画面をWPFのImageに表示するまでをまとめていきます。



■前提条件

今回画面レイアウトですが、こちらの通り
ImageタグとButtonタグのみの構成とします。
用意したボタンをトリガにスクリーンショットや操作映像をImageに表示させてみます。
ボタン押下時に実行される関数名は、「Button_Click」としております。

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Image x:Name="ImgCap" HorizontalAlignment="Left" Height="343" VerticalAlignment="Top" Width="525" Margin="62,34,0,0"/>
        <Button Content="Button" HorizontalAlignment="Left" Height="54" Margin="656,351,0,0" VerticalAlignment="Top" Width="113" Click="Button_Click"/>
    </Grid>
</Window>

スクリーンショット実現方法

まずはWPFで画面のスクリーンショットです。
ボタンを押下した時にスクリーンショットを画面に表示するコードをまとめます。
コードはこちら。

using System;
using System.Windows;
using System.Windows.Media.Imaging;
using System.Drawing;
using System.Windows.Interop;

namespace WpfApp1
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : System.Windows.Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            ImgCap.Source = CopyScreen();
        }
        private static BitmapSource CopyScreen()
        {
            using (var bmtpScreen = new System.Drawing.Bitmap(
                (int)SystemParameters.PrimaryScreenWidth,
                (int)SystemParameters.PrimaryScreenHeight,
                System.Drawing.Imaging.PixelFormat.Format32bppArgb))
            {
                using (var bmpFromImg = Graphics.FromImage(bmtpScreen))
                {
                    bmpFromImg.CopyFromScreen(0, 0, 0, 0, bmtpScreen.Size);
                    
                    return Imaging.CreateBitmapSourceFromHBitmap(
                        bmtpScreen.GetHbitmap(),
                        IntPtr.Zero,
                        Int32Rect.Empty,
                        BitmapSizeOptions.FromEmptyOptions());
                }
            }
        }
    }
}

スクリーンショットを取得している関数ですが、

private static BitmapSource CopyScreen()
{
    using (var bmtpScreen = new System.Drawing.Bitmap(
        (int)SystemParameters.PrimaryScreenWidth,
        (int)SystemParameters.PrimaryScreenHeight,
        System.Drawing.Imaging.PixelFormat.Format32bppArgb))
    {
        using (var bmpFromImg = Graphics.FromImage(bmtpScreen))
        {
            bmpFromImg.CopyFromScreen(0, 0, 0, 0, bmtpScreen.Size);

            return Imaging.CreateBitmapSourceFromHBitmap(
                bmtpScreen.GetHbitmap(),
                IntPtr.Zero,
                Int32Rect.Empty,
                BitmapSizeOptions.FromEmptyOptions());
        }
    }
}

になります。
まず、

bmtpScreen = new System.Drawing.Bitmap

にて新規でDrawing.Bitmap型の変数を生成します。
その後、

var bmpFromImg = Graphics.FromImage(bmtpScreen)

でGraphicsに変換後、

bmpFromImg.CopyFromScreen(0, 0, 0, 0, bmtpScreen.Size);

Windows画面のスクリーンショット結果を取得します。
CopyFromScreen関数ですが、
引数はそれぞれ、
第1引数:転送元の四角形の左上隅の点の x 座標
第2引数:転送元の四角形の左上隅の点の y 座標
第3引数:転送先の四角形の左上隅の点の x 座標
第4引数:転送先の四角形の左上隅の点の y 座標
第5引数:転送される領域のサイズ
になります。
※参考:https://docs.microsoft.com/ja-jp/dotnet/api/system.drawing.graphics.copyfromscreen?view=dotnet-plat-ext-6.0

■動画化してみる

先ほどはボタン押下した際にその瞬間のスクリーンショットを表示するのみでした。
つまり静止画が表示しております。
今度は、ボタン押下後の画面を動画としてリアルタイム表示させる方法をまとめたいと思います。
コードはこちら。

using System;
using System.Windows;
using System.Windows.Media.Imaging;
using System.Drawing;
using System.Windows.Interop;
using System.Threading;

namespace WpfApp1
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : System.Windows.Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

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

        private void CaptureMovieAsync()
        {
            for (int i = 0; i < 1000; i++)
            {
                using (var screenBmp = new System.Drawing.Bitmap(
                    (int)SystemParameters.PrimaryScreenWidth,
                    (int)SystemParameters.PrimaryScreenHeight,
                    System.Drawing.Imaging.PixelFormat.Format32bppArgb))
                {
                    using (var bmpGraphics = Graphics.FromImage(screenBmp))
                    {
                        bmpGraphics.CopyFromScreen(0, 0, 0, 0, screenBmp.Size);

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

今回のコードですが、100秒間画面をリアルタイムで表示させるコードになります。

やっていることは、
ボタン押下時にスレッド生成の上、動作実行し、
スレッド内で先ほどのスクリーンショットを連続で実行している
のみ。です。

スレッド側でオブジェクトの更新を行う必要があるので、
Dispatcher.Invokeを用いております。

■(補足)Dispatcher.Invokeとは?

WPFでは、メインスレッドのみオブジェクトにアクセスでき、別スレッドからオブジェクトにアクセスすることが出来ません。
理由ですが、
メインスレッドで所有するオブジェクトを別スレッドからアクセスできない
ためです。

そこで、別スレッドからアクセスするために
Dispatcher.Invoke
を用います。
このDispatcherはキューを管理するクラスになっており、
メインスレッドに対して、操作したいオブジェクトや処理をキューに格納し、
受け取ったメインスレッドは順次処理を実行しているイメージになります。

■最後に

今回はWPFスクリーンショットや画面をリアルタイム動画で表示する方法でまとめました。
次は表示したスクリーンショットや動画を保存・録画する方法についてまとめたいと思います。