🎨

Microsoft.Maui.Graphics でクロスプラットフォームレンダリングしてみる

2021/12/03に公開

はじめに

この記事は Xamarin / MAUI Advent Calendar 2021 の 3 日目の記事です。

Visual Studio 2022 / .NET 6.0 がリリースされましたが、 .NET 6 の目玉の一つである MAUI は preview が継続しています。

MAUI の対応プラットフォームは現時点では Android / iOS / Mac / Windows (WinUI) と、 Xamarin.Forms の主要プラットフォームとあまり変わらないような感じです。

いろいろ調べてみるとグラフィックス関連を扱う Microsoft.Maui.Graphics というものが別パッケージに分離されており、こちらは現時点でも様々なプラットフォームで使えそうです。
試しに Windows Forms から始め、いろいろ使ってみました。

Microsoft.Maui.Graphics とは

https://github.com/dotnet/Microsoft.Maui.Graphics

Microsoft.Maui.Graphics はクロスプラットフォームの画像レンダリングライブラリです。
"System.Drawing" の後継 (代替) としての位置づけでもあるようです。

https://docs.microsoft.com/ja-jp/dotnet/core/compatibility/core-libraries/6.0/system-drawing-common-windows-only

クロスプラットフォームのレンダラーって Skia じゃないの・・・?って思うところですが、 Microsoft.Maui.Graphics の実装としては

の大きく二つに分かれていて、それぞれに Android / iOS / Mac / WinUI 向けの実装が用意されています。

色や座標といったグラフィックスにおける標準的な型も定義されており、そういった型の .NET 標準を定義している、という一面もありそうです。

MAUI 本体から使う場合は MAUI のコントロールである Microsoft.Maui.Controls.GraphicsView クラス を MAUI の UI レイアウト内に配置すると Platform 実装により Microsoft.Maui.Graphics の GraphicsView に置き換わって実際のレンダリングがそちらに行われます。

現在の preview では MAUI 本体から使うと、 Microsoft.Maui.Graphics の NativeGraphicsView が使用されて SkiaGraphicsView は使われていません。 MAUI のソースコードを見る限り Skia に関するコードはコメントアウトされているので将来的には有効化されるのではないかと思われます。

レンダリング処理を定義する

Microsoft.Maui.Graphics を使ってレンダリング処理を行う場合、そのコードは Microsoft.Maui.Graphics.IDrawing インターフェースを継承し、その中の Draw メソッドで実装します。この部分はプラットフォーム非依存です。

public class Drawable : IDrawable
{
    void IDrawable.Draw(ICanvas canvas, RectangleF dirtyRect)
    {
        //  Microsoft.Maui 内の Maui.Controls.Sample.Pages.GraphicsDrawable の
        //  実装をコピペする
    }
}

ここではサンプルとして Maui.Controls.Sample.Pages.GraphicsDrawable を流用します。

Windows Forms から Microsoft.Maui.Graphics を使う

Windows Forms は (少なくとも今のところは) MAUI の対応プラットフォームではないので Windows Forms 用の GraphicsView はありません。
SkiaSharp は Windows Forms 対応がされているので、これを用いて独自に Windows Forms 版の SkiaGraphicsView を実装します。

まず Windows Forms で新規プロジェクトを作成します。暗黙的 using のせいで Windows Forms のクラスと MAUI のクラスで名前衝突しまくるので ImplicitUsings は disable にする ことをお勧めします。

次に必要なパッケージを NuGet から追加します。

SkiaGraphicsView を実装していきます。 Windows Forms 向けなので SkiaSharp.Views.WindowsForms の SKControl をベースとし、コード自体は Microsoft.Maui.Graphics の中の Android や iOS の実装を参考に記述します。

public class SkiaGraphicsView : SKControl
{
    private IDrawable? _drawable;
    private SkiaCanvas _canvas;
    private ScalingCanvas _scalingCanvas;

    public SkiaGraphicsView(IDrawable? drawable = null)
    {
        _canvas = new SkiaCanvas();
        _scalingCanvas = new ScalingCanvas(_canvas);
        Drawable = drawable;
    }

    public IDrawable? Drawable
    {
        get => _drawable;
        set
        {
            _drawable = value;
            Invalidate();
        }
    }

    protected override void OnPaintSurface(SKPaintSurfaceEventArgs e)
    {
        if (_drawable == null) return;

        var skiaCanvas = e.Surface.Canvas;
        skiaCanvas.Clear(new SkiaSharp.SKColor(255, 255, 255, 255));

        _canvas.Canvas = skiaCanvas;

        _scalingCanvas.ResetState();
        _scalingCanvas.Scale(1, 1);

        _drawable.Draw(_scalingCanvas, new Microsoft.Maui.Graphics.RectangleF(0, 0, Width, Height));
    }
}

SKControl を SKGLControl に変更すると OpenGL レンダラーになります。

  • SkiaCanvas (ScalingCanvas) は Microsoft.Maui.Graphics.Skia による Microsoft.Maui.Graphics.AbstractCanvas の実装で、 MAUI からのレンダリングコマンドを Skia に置き換えて処理してくれます
  • SKControl の OnPaintSurface をオーバーライドし、ここで Skia にレンダリングする処理を実装します

最後に Windows Foms の Form に実装した SkiaGraphicsView を貼り付けます。

public partial class Form1 : Form
{
    private SkiaGraphicsView _view;

    public Form1()
    {
        InitializeComponent();
        _view = new SkiaGraphicsView(new Drawable());
        _view.Parent = this;
        //  _view のレイアウト調整
    }
}

実行すると次のようになります。

WinUI から Microsoft.Maui.Graphics を使う

Skia と比較するために Win2D 版も試してみます。

Win2D 自体は元々 Direct2D の Windows Runtime API 版なので UWP でも使えるはずなのですが、 MAUI は .NET 6 以降なので UWP テンプレートは使えないため WinUI を使う必要があります。

WinUI を使用する場合は Windows App SDK のインストールが必要です。

https://docs.microsoft.com/ja-jp/windows/apps/windows-app-sdk/

SDK をインストール後、 WinUI プロジェクトテンプレートでプロジェクトを作成します。

次に必要なパッケージを NuGet から追加します。

MainWindow.xaml を次のようにします (一部省略) 。 MainWindow.xaml.cs 内のボタンのイベントハンドラーは削除します。
Drawble のインスタンスは Resource 経由で入れています。

<Window
  xmlns:local="using:MauiGraphicsWinUI"
  xmlns:maui="using:Microsoft.Maui.Graphics.Win2D">
  <Grid>
    <Grid.Resources>
      <local:Drawable x:Key="Drawable" />
    </Grid.Resources>
    <maui:W2DGraphicsView HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Drawable="{StaticResource Drawable}"/>
  </Grid>
</Window>

ほぼ Skia と同じレンダリング結果になっているようです。

MAUI から Microsoft.Maui.Graphics を使う

最後に本命の MAUI のアプリから使ってみます。

まず MAUI アプリ開発ができるように Visual Studio 2022 (preview) の準備をして ".NET MAUI App" テンプレートでプロジェクトを作成します。

https://docs.microsoft.com/ja-jp/dotnet/maui/get-started/first-app

MAUI から使う場合は Microsoft.Maui.Controls.GraphicsView を配置し、 IDrawable 実装を設定します。下記の XAML は MAUI としての XAML です。

<ContentPage
  xmlns:local="clr-namespace:MauiApp1">
  <Grid>
    <Grid.Resources>
      <local:Drawable x:Key="Drawable" />
    </Grid.Resources>
    <GraphicsView HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand" Drawable="{StaticResource Drawable}"/>
  </Grid>
</ContentPage>

雰囲気的には WinUI の XAML とほぼ同じです。 Android で実行した場合は次のようになりました。

解像度の問題で Drawable の実装で先頭に

canvas.Scale(0.4f, 0.4f);

を入れましたが他は同一です。

現在の preview では API 31 な Android で実行しようとするとエラーで起動しないので API 30 以下の環境で試してください。

https://github.com/dotnet/maui/issues/3305

おわりに

実は図形を表す Shape はまた別にあるわけですが、 Shape はそれぞれが View なので比較的コストが高いと思いますので GraphicsView は複雑な描画をするのに向いているものになっているかなと思います。

MAUI はほとんど何もわかってなくてコード調べてたら Microsoft.Maui.Graphics を知って調べ始めたのが切っ掛けなのですが、 MAUI 本体から綺麗に分離されていて単体でも使いやすそうな感じです (単体で何に使うのかってのはありますけど) 。

MAUI はまだ preview で Microsoft.Maui.Graphics もまだまだ変わっていくと思いますので引き続き注目していきたいところです。

Discussion