LoginSignup
0
1

More than 3 years have passed since last update.

Blazor(Mobile Blazor Bindings)でWebViewの描画範囲だけスクショする方法 (Windows WPFの場合)

Posted at

敗北から勝利までのメモ

何がしたかった

  • アプリケーションのUI部分を除いたキャプチャがしたかった (タイトルバーとかのUIを除いたWebViewだけキャプチャしたかった)

  • アプリケーション全体画像
    HoloViewer.png

宣伝

C#とBlazorでホロライブファン向けの動画ビューワー『ホロビューワー』を開発しました

結論

Windowsでは WebView2 CoreクラスのCapturePreviewメソッドを使用する。

以下PNGフォーマットで保存する場合のコード

<ContentView>
    <StackLayout>

        // 省略

        <StackLayout Orientation="StackOrientation.Horizontal">
            <Button Text="Capture" OnClick="CaptureSingle" />
        </StackLayout>
        <Grid HorizontalOptions="LayoutOptions.FillAndExpand" VerticalOptions="LayoutOptions.FillAndExpand">
            <StackLayout>
                <BlazorWebView @ref="BlazorWebViews" VerticalOptions="LayoutOptions.FillAndExpand">
                    <HoloViewer.WebUI.App />
                </BlazorWebView>
            </StackLayout>
        </Grid>

        // 省略

    </StackLayout>
</ContentView>


@code
{
    private List<BlazorWebView> blazorWebViews = new List<BlazorWebView>();

    public BlazorWebView BlazorWebViews { set { blazorWebViews.Add(value); } }

    void CaptureSingle ()
    {
        DependencyService.Get<IScreenCapture>().CaptureSingle(blazorWebViews.First());
    }
}
  • Sharedコード (DependencyService定義)
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.MobileBlazorBindings.Elements;

namespace HoloViewer
{
    public interface IScreenCapture
    {
        protected const string CaptureFileNameFormat = "yyyyMMdd_HHmmss";

        protected const string CaptureFileExtension = ".png";

        void CaptureSingle (BlazorWebView blazorWebView);
   }
}
  • Windows WPF側のコード
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.IO;
using Microsoft.MobileBlazorBindings.Elements;
using Xamarin.Forms.Platform.WPF;

[assembly: Xamarin.Forms.Dependency(typeof(HoloViewer.Windows.ScreenCapture))]
namespace HoloViewer.Windows
{
    class ScreenCapture : IScreenCapture
    {
        private async void Capture (BlazorWebView blazorWebView)
        {
            using (var fileStream = new FileStream(DateTime.Now.ToString(IScreenCapture.CaptureFileNameFormat) + IScreenCapture.CaptureFileExtension, FileMode.Create))
            {
                await WebView.CastWebView(blazorWebView).CoreWebView2.CapturePreviewAsync(Microsoft.Web.WebView2.Core.CoreWebView2CapturePreviewImageFormat.Png, fileStream);
            }
        }

        public void CaptureSingle (BlazorWebView blazorWebView)
        {
            Capture(blazorWebView);
        }
    }
}
  • BlazorWebViewからWebView2への変換処理のコード
    • WebView2本体はアクセス範囲がpublicじゃないのでリフレクションでアクセスする。
    • この辺はデバッガで変数の中身をしらみつぶしにして探した。
using System.Reflection;
using Microsoft.MobileBlazorBindings.Elements;
using Microsoft.MobileBlazorBindings.WebView.Elements;
using Microsoft.Web.WebView2.Wpf;

namespace HoloViewer.Windows
{
    class WebView
    {
        public static WebView2 CastWebView (BlazorWebView blazorWebView)
        {
            var content = ((Microsoft.MobileBlazorBindings.WebView.Elements.MobileBlazorBindingsBlazorWebView)blazorWebView.NativeControl).Content;
            var type = content.GetType();

            return (WebView2)type.GetProperty("RetainedNativeControl").GetValue(content);
        }
    }
}
  • 成功例 (画像だけだと加工できるし、うまくいってるかわからんな これ) Sucucess.png

勝利に至るまでの経過

  • 敗北その1

    • WPFだしMainWindowをキャプチャして必要なところだけ切り出せばいいと思って試したら、そもそもWebViewのUIキャプチャできていない。
    • 敗北その1の結果 Fail1.png
RenderTargetBitmapを使用するパターン
private void Capture ()
{
    var mainWindow = Application.Current.MainWindow;

    var renderTargetBitmap = new RenderTargetBitmap((int)mainWindow.Width, (int)mainWindow.Height, 96, 96, PixelFormats.Pbgra32);

    renderTargetBitmap.Render(mainWindow);

    var pngBitmapEncoder = new PngBitmapEncoder();

    pngBitmapEncoder.Frames.Add(BitmapFrame.Create(renderTargetBitmap));

    using (var fileStream = new FileStream(DateTime.Now.ToString(IScreenCapture.CaptureFileNameFormat) + IScreenCapture.CaptureFileExtension, FileMode.Create))
    {
        pngBitmapEncoder.Save(fileStream);
    }
}
  • 敗北その1の原因 (推測)
    • タスクマネージャーでプロセスを見てみると別のプロセスでWebView2が動作している。
    • よってMainWindow(画像だと無名になっているプロセス)だけをキャプチャしてもWebViewの箇所が表示されない。 (と推測しています)
    • WebView2のプロセスが複数ある理由は正直わかっていません。

WebView2Process.png

  • 敗北その2
    • CapturePreviewAsyncメソッドを await しないと0バイトのPNG画像が生成される。
    • そもそもawaitしていなかったり、Wait関数で待っても0バイトのPNG画像が生成されてダメなので注意
await忘れパターン
private void Capture (BlazorWebView blazorWebView)
{
    using (var fileStream = new FileStream(DateTime.Now.ToString(IScreenCapture.CaptureFileNameFormat) + IScreenCapture.CaptureFileExtension, FileMode.Create))
    {
        WebView.CastWebView(blazorWebView).CoreWebView2.CapturePreviewAsync(Microsoft.Web.WebView2.Core.CoreWebView2CapturePreviewImageFormat.Png, fileStream);
    }
}
  • 敗北その2の結果 Fail2.png

まとめ

  • Windows版はこの調子で機能を実装すれば勝てそう
  • WebView2がMacには対応していないためMac版は別の地獄がまっているはず
0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1