📌

AvaloniaUIでのコードからのsvgアクセス方法

2024/02/23に公開

やりたいこと

Visual Studio 2022 + AvaloniaUI環境でAssetsにいれたsvgファイルにC#のコードからアクセスしたい

使用環境

Windows 11 : Home 22H2 + Visual Studio Community 2022(64bit)
Linuxテスト環境 : 上記Windows上のWSL2 Ubuntsu 22.04.2 LTS
Visual Studio2022, AvaloniaUI 11.0.2

Avalonia.Svg.Skia

Visual Studio 2022 ツール -> Nugetパッケージマネージャ -> ソリューションのNugetパッケージの管理から
Avalonia.Svg.Skiaをインストールする

アセットの追加

プロジェクトのAssetsフォルダの下にsvgファイルを追加する

ここで追加したファイルのビルドアクションがAvaloniaResourceになっていないといけないことに注意。

C#コードからのアクセス

以下のコードでアクセスできる

  • Avalonia.Svg.Skiaで読み込んだSvgファイルはSKBitmapになっていて、Bitmapに変換してもAvalonia.Media.Imaging.Bitmapになっていない。この変換のやり方が見つからなかった。そのためいったんpngファイルに書き出してから再読み込みしている。
  • Avalonia周りには拡張メソッドを使って実装されている機能が結構あって、それがコードの構造が見えなくなったり、コードがコピペで動かなくなっている原因になってることがままある気がする。特定のusingを外したりすると拡張メソッドが動かなくなって理由がわからずにコードが動かなくなったりするので、コード流用の際にはusingに何が使われてるかに注意が必要。
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using Avalonia.Media;

// SKPictureに対する拡張メソッドとしてSKPicture.ToImageが定義されている。これを使うために以下が必要。
using SkiaSharp;
using Svg.Skia;

namespace TestApp.Models
{
    public static class Icons
    {

        // 一度使ったimageは保持しておく
        public static Dictionary<string, IImage> iconImages = new Dictionary<string, IImage>();

        /// <summary>
        /// Assets/Icons/以下の(iconName).svgをIImage形式にして取り出す。
        /// 一度使ったIImageはキャッシュする。
        /// </summary>
        /// <param name="iconName">ファイル名(拡張子はのぞく)</param>
        /// <returns></returns>
        public static IImage IconBitmap(string iconName)
        {
            if (iconImages.ContainsKey(iconName)) return iconImages[iconName];

            lock (iconImages)
            {
                // Asset/Icons下のiconName".svg"をstringとして取得する
                string svgString;
                using (var stream = AssetLoader.Open(new Uri("avares://TestApp/Assets/Icons/" + iconName + ".svg")))
                {
                    byte[] buffer = new byte[stream.Length];
                    stream.Read(buffer, 0, (int)stream.Length);
                    var encoding = Encoding.GetEncoding("UTF-8");
                    svgString = encoding.GetString(buffer);
                }

                // SvgStringをiconName.pngにいったん書き出す

                Avalonia.Svg.Skia.Svg svg = new Avalonia.Svg.Skia.Svg(new Uri("avares://TestApp/Assets/Icons/" + iconName + ".svg"));
                    // ここのbaseUriは意味がよくわからない。わからないけどとりあえずsvgファイルのパスを食わせている。
                    // でもこれでsvgが読み込まれるわけではないよう
                svg.Source = svgString;
                    // SourceにsvgStringを入れるとsvg.Pictureが使えるようになる。
                if (svg.Picture == null) return null;

                // svg.Picture.ToBitmap(SKColors.Transparent, 1f, 1f, SKColorType.Bgra8888, SKAlphaType.Premul, SKColorSpace.CreateSrgb());
                // でSKBitmapを取得できるのだが、これをAvalonia.Media.Imaging.Bitmapに変換する方法がわからない。
                // しょうがないのでいったんpngファイルに書き出す。
                using (var stream = File.OpenWrite(iconName + ".png"))
                {
                    svg.Picture.ToImage(stream, SKColors.Transparent, SKEncodedImageFormat.Png, 100, 1f, 1f, SKColorType.Bgra8888, SKAlphaType.Premul, SKColorSpace.CreateSrgb());
                }

                // 書いたpngファイルをAvalonia.Media.Imaging.Bitmapとして読み込む。
                using (var stream = File.OpenRead(iconName + ".png"))
                {
                    Bitmap bmp = new Bitmap(stream);
                    iconImages.Add(iconName, bmp);
                }
            }
            return iconImages[iconName];
        }
    }
}

※追記
ファイルを経由せずにSKBitmapをBitmapに変換できる方法が見つかった

        // 下記に記載のあった方法を使うとファイルを経由せずにSKBitmapをBitmapに変換できた
        // https://github.com/AvaloniaUI/Avalonia/discussions/13610
        SKBitmap? skBitmap = svg.Picture.ToBitmap(SKColors.Transparent, 1f, 1f, SKColorType.Bgra8888, SKAlphaType.Premul, SKColorSpace.CreateSrgb());
        using (var enc = skBitmap.Encode(SKEncodedImageFormat.Png, 80))
        {
            using (var ms = new MemoryStream())
            {
                enc.SaveTo(ms);
                ms.Position = 0;
                Bitmap bmp =new Bitmap(ms);
                iconImages.Add(iconName, bmp);
            }
        }

Discussion