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

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

MENU

WPFでディスクトップ上の指定したエリアのスクリーン録画を行ってみた

自分でカスタマイズしながらスクリーンショットアプリが欲しくて色々試してきました。

今回はこちらで紹介したウィンドウ全体を表示、録画する方法をまとめました。
elsammit-beginnerblg.hatenablog.com

また、Rctangleで描画する方法もまとめました。
elsammit-beginnerblg.hatenablog.com

今回はこちらのディスクトップウィンドウのスクリーン録画する方法と、
Rectangleを描画する方法を組み合わせて、、、
Rectangleで描画した部分を切り出してスクリーン録画する方法をまとめたいと思います。
これで画面上の好きなエリアのみを録画することが出来るようになります。



■完成形イメージ

アプリの完成形ですが、
こちらのように中央にディスクトップウィンドウを表示させ、
マウスで指定エリア指定した後に録画ボタンを押すと指定したエリアを切り出して録画をする。
といった形になります。

ソースコードについて

ソースコードはこちらに公開しております。
コード全体を確認したい方はこちらをご参考ください。
github.com

■画面デザイン(xaml)

画面デザインですが、こちらのように作成しました。
CanvasとImageを同じ大きさ位置で表示するように作成し、Imageに対してBorderで枠線を引いております。
Canvas(& Image)のサイズですが、
Height:720px
Width:1280px
としております。
後はボタンが用意されているのみのシンプルな画面になります。

<Window x:Name="ScreenCapture" 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="ScreenCaptureツール" Height="792.901" Width="1485.972" Closing="CloseWindow" Loaded="WindowLoad"
        ResizeMode="NoResize" Icon="DispIcon.jpeg" Background="#FF5D5D5D">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Canvas x:Name="RectArea" HorizontalAlignment="Left" Margin="17,18,0,0" VerticalAlignment="Top" Height="720"  Width="1280" MouseLeftButtonDown="MouseLeftBtnDwn" MouseMove="MouseMoving" MouseLeftButtonUp="MouseLeftBtnUp" Panel.ZIndex="1" Background="Transparent" OpacityMask="Gray"/>
        <Border BorderBrush="White" BorderThickness="1" HorizontalAlignment="Left" Height="720" Margin="17,18,0,0" VerticalAlignment="Top" Width="1280">
            <Image x:Name="ImgCap" HorizontalAlignment="Left" Height="720" VerticalAlignment="Top" Width="1280" Margin="0,0,0,0"/>
        </Border>
        <Button x:Name="StartButton" Content="録画開始" HorizontalAlignment="Left" Height="74" Margin="1317,659,0,0" VerticalAlignment="Top" Width="136" Click="Button_Click" RenderTransformOrigin="1.155,1.833" Panel.ZIndex="2" FontSize="24">
            <Button.RenderTransform>
                <TransformGroup>
                    <ScaleTransform/>
                    <SkewTransform AngleX="-2.969"/>
                    <RotateTransform/>
                    <TranslateTransform X="-4.567"/>
                </TransformGroup>
            </Button.RenderTransform>
        </Button>
        <Border x:Name="RecBlock" BorderBrush="Black" BorderThickness="1" HorizontalAlignment="Left" Height="56" Margin="1332,32,0,0" VerticalAlignment="Top" Width="122" CornerRadius="30" Background="#FFFB4646" RenderTransformOrigin="-6.375,10.312">
            <TextBlock TextWrapping="Wrap" Text="REC" Margin="19,11" TextAlignment="Center" Foreground="White" FontSize="24"/>
        </Border>
        <Label x:Name="RecTimer" Content="00:00" HorizontalAlignment="Left" Margin="1358,109,0,0" VerticalAlignment="Top" Height="40" Width="76" FontSize="24" Foreground="White"/>
    </Grid>
</Window>

■指定したエリアに限定した録画方法

指定したエリアに限定して録画をするコードはこちらになります。
ここで、screenBmpはウィンドウ全体のBitmapイメージであり、rectが自身で指定したRectangleのエリアになります。

        private bool WriteVideo(bool isStartRec, Bitmap screenBmp, RECT rect)
        {
            m_recordData = rect;

            int capWidth = m_recordData.right - m_recordData.left;
            int capHeight = m_recordData.bottom - m_recordData.top;
         
            if (capHeight <= 0 || capWidth <= 0)
            {
                Console.WriteLine(" size Error");
                return false;
            }
            System.Drawing.Rectangle rectBuf = new System.Drawing.Rectangle(rect.left, rect.top,
                        capWidth, capHeight);
            Bitmap bmp = screenBmp.Clone(rectBuf, screenBmp.PixelFormat);
            Mat mat = BitmapConverter.ToMat(bmp).CvtColor(ColorConversionCodes.RGB2BGR);
            if (isStartRec)
            {
                Cv2.CvtColor(mat, mat, ColorConversionCodes.BGR2RGB);
                Cv2.Resize(mat, mat, new OpenCvSharp.Size(capWidth, capHeight));
                writer.Write(mat);
            }
            return true;
        }

実施していることですが、

System.Drawing.Rectangle rectBuf = new System.Drawing.Rectangle(rect.left, rect.top,
                        capWidth, capHeight);

で切り出し領域のRectangleデータを作成し、

Bitmap bmp = screenBmp.Clone(rectBuf, screenBmp.PixelFormat);

にてウィンドウ全体から切り出すために作成したRectangleで切り出しを行い、
最後はMat型の変数に変換しOpenCVのWrite関数でデータ書き込みを実施しております。

ウィンドウの切り出しですが、下記のようなことを実施しているイメージです。

■指定したエリアの切り出し原理

先ほど記載した通り、指定した座標やRectangleのサイズを指定すればBitmapイメージの切り出しが可能です。
では、座標やRectangleのサイズはCanvas上でマウスで指定したエリアを利用すればよいのでしょうか?

答えはNoです。
Canvasのサイズと実際のウィンドウサイズは異なりますので、座標やRectangleのサイズをそのまま利用するとおかしな位置が切り出されることになります。

このため、
Canvasのサイズと実際のウィンドウサイズの比率で座標やRectangleのサイズを変更させる必要があります。

指定エリアの比率変更のコードはこちらになります。

        public bool DrawRectangle(System.Windows.Point point, double canvasWidth, double canvasHeight,
            ref Position position, ref System.Windows.Shapes.Rectangle rectangle)
        {

            bool ret = false;

            rectangle.Stroke = new SolidColorBrush(Colors.Red);
            rectangle.StrokeThickness = 1;

            var width = Math.Abs(InitPos.X - point.X);
            var height = Math.Abs(InitPos.Y - point.Y);
            rectangle.Width = width;
            rectangle.Height = height;

            if (point.X > canvasWidth - 1)
            {
                width = canvasWidth - InitPos.X;
                Canvas.SetLeft(rectangle, InitPos.X);
                rectangle.Width = width;
                position.left = (int)(InitPos.X);
            }
            else if (point.X < 0)
            {
                width = InitPos.X;
                Canvas.SetLeft(rectangle, 0);
                rectangle.Width = width;
                position.left = 0;
            }
            else if (InitPos.X < point.X)
            {
                Canvas.SetLeft(rectangle, InitPos.X);
                position.left = (int)(InitPos.X);
            }
            else
            {
                Canvas.SetLeft(rectangle, point.X);
                position.left = (int)(point.X);
            }

            if (point.Y > canvasHeight - 1)
            {
                height = canvasHeight - InitPos.Y;
                Canvas.SetTop(rectangle, InitPos.Y);
                rectangle.Height = height;
                position.top = (int)(InitPos.Y);
            }
            else if (point.Y < 0)
            {
                height = InitPos.Y;
                Canvas.SetTop(rectangle, 0);
                rectangle.Height = height;
                position.top = 0;
            }
            else if (InitPos.Y < point.Y)
            {
                Canvas.SetTop(rectangle, InitPos.Y);
                position.top = (int)(InitPos.Y);
            }
            else
            {
                Canvas.SetTop(rectangle, point.Y);
                position.top = (int)(point.Y);
            }

            position.width = (int)(width * (SystemParameters.PrimaryScreenWidth / canvasWidth));
            position.height = (int)(height * (SystemParameters.PrimaryScreenHeight / canvasHeight));
            position.top = (int)(position.top * (SystemParameters.PrimaryScreenHeight / canvasHeight));
            position.left = (int)(position.left * (SystemParameters.PrimaryScreenWidth / canvasWidth));

            return ret;
        }
    }

一部Rectangleの描画が入っておりますが、こちらの解説は
elsammit-beginnerblg.hatenablog.com
を参考にしてください。

比率の変更コードですが、

position.width = (int)(width * (SystemParameters.PrimaryScreenWidth / canvasWidth));
position.height = (int)(height * (SystemParameters.PrimaryScreenHeight / canvasHeight));
position.top = (int)(position.top * (SystemParameters.PrimaryScreenHeight / canvasHeight));
position.left = (int)(position.left * (SystemParameters.PrimaryScreenWidth / canvasWidth));

になります。
実施していることは、
実際のウィンドウとCanvasのサイズの比率を計算の上、比率でCanvas上に作成したRectangleのサイズを拡大・縮小しております。

■最後に

これで自分が録画したいエリアを指定することが出来るようになりました。
後でこちらのアプリはリリースしてみようかと思います。
まずは自分で使ってみて、使い勝手確認していきます。