Start Debugging

修正: <filter>または<text>を含むSVGで.NET MAUI ResizetizerがMAUIR0001 MissingMethodExceptionを出す

MAUI 10.0.101と10.0.110のResizetizerは整合しないSystem.Memory参照を同梱しているため、filterやtextを含むSVGが失敗します。Resizetizerを10.0.100に固定するか、filterとtextを取り除いてください。

.NET MAUI 10のビルドがerror MAUIR0001: There was an exception processing the imageと、SKImageFilter.CreateMatrixConvolution、SKTextBlobBuilder.AddPositionedRun、SKTypeface.Cloneのいずれかに対するSystem.MissingMethodExceptionで失敗し始めた場合、原因はSVG側ではなくResizetizerパッケージ自体にあります。Microsoft.Maui.Resizetizer 10.0.101と10.0.110は、System.Memory 4.0.5.0を要求するSkiaSharp 4.150.1ビルドを、4.0.2.0を要求するSvg.Skiaと一緒に同梱しており、MSBuildが2つの異なるReadOnlySpan<T>型を読み込んでしまいます。<filter>または<text>要素を使うSVGはすべてクラッシュします。最も速い修正は、MAUIの他の部分は10.0.110のままにしてMicrosoft.Maui.Resizetizerを10.0.100に固定することです。恒久的な修正は、MauiIcon、MauiSplashScreen、MauiImageのSVGからフィルターを削除し、テキストをパスに変換することです。

エラーが起きる状況

このエラーには2つのパターンが上流で報告されています。フィルターのパターンはdotnet/maui#38319からのものです。

error MAUIR0001: There was an exception processing the image '...\Resources\AppIcon\appicon.svg'.
System.MissingMethodException: Method not found:
'SkiaSharp.SKImageFilter SkiaSharp.SKImageFilter.CreateMatrixConvolution(
    SkiaSharp.SKSizeI, System.ReadOnlySpan`1<Single>, Single, Single,
    SkiaSharp.SKPointI, SkiaSharp.SKShaderTileMode, Boolean, SkiaSharp.SKImageFilter)'.
   at Svg.Skia.SkiaModel.ToSKImageFilter(SKImageFilter imageFilter)
   at Svg.Skia.SkiaModel.GetRenderImageFilter(SKImageFilter imageFilter)
   at Svg.Skia.SkiaModel.CreateRenderPaint(SKPaint paint)
   at Svg.Skia.SKSvg.Load(String path)
   at Microsoft.Maui.Resizetizer.SkiaSharpSvgTools..ctor(...)

10.0.101でのテキストのパターンはdotnet/maui#38507からのものです。

error MAUIR0001: There was an exception processing the image '.../Resources/Images/place_capsule.svg'.
System.MissingMethodException: Method not found: 'Void SkiaSharp.SKTextBlobBuilder.AddPositionedRun
(System.ReadOnlySpan`1<UInt16>, SkiaSharp.SKFont, System.ReadOnlySpan`1<SkiaSharp.SKPoint>)'.

10.0.110ではテキストのパターンが別のメソッドに移りました。10.0.110でSvg.Skiaが5.1.1から5.2.3に上がり、新しいバージョンはフォントの解決方法が異なるためです。単純な<text>要素を使った場合、10.0.110では次のようになります。

error MAUIR0001: There was an exception processing the image '.../text.svg'.
System.MissingMethodException: Method not found:
'SkiaSharp.SKTypeface SkiaSharp.SKTypeface.Clone(System.ReadOnlySpan`1<SkiaSharp.SKFontVariationPositionCoordinate>)'.
   at Svg.Skia.SkiaModel.ApplyVariableFontWeight(SKTypeface typeface, SKFontStyle style)
   at Svg.Skia.SkiaModel.ResolveSKTypeface(SKTypeface typeface)
   at Svg.Skia.SkiaModel.ToSKFont(SKPaint paint)

#38507のコメントには、iOSのGenerateSplashStoryboardステップで発生するHarfBuzzSharp.Font.SetVariations(ReadOnlySpan<Variation>)という同種の例外も報告されています。メソッド名が何であれ、シグネチャに注目してください。どれもReadOnlySpan<T>を引数に取っています。これがバグの本質です。

存在するはずのメソッドをResizetizerが見つけられない理由

誰もが最初にやるのは、SkiaSharp.dllをデコンパイラーで開いて、そこにメソッドが存在することを確認することです。#38507の報告者もまさにそれを行い、リフレクションによってAddPositionedRun(ReadOnlySpan<ushort>, SKFont, ReadOnlySpan<SKPoint>)が存在することを確認しています。私も同様に、各パッケージバージョンのbuildTransitiveフォルダーに対してSystem.Reflection.Metadataで確認したところ、10.0.100、10.0.101、10.0.110のいずれにもメソッドは存在していました。

違いはアセンブリ参照にあります。同梱されている各アセンブリが要求している内容は次のとおりです。

ResizetizerSkiaSharp.dll (TFM)SkiaSharpが要求するSystem.MemorySvg.Skiaが要求するSystem.Memory同梱されているSystem.Memory.dll
10.0.1003.116.1 (net462)4.0.1.24.0.1.24.0.1.2
10.0.1014.150.1 (net462)4.0.5.04.0.2.04.0.2.0
10.0.1104.150.1 (net462)4.0.5.04.0.2.04.0.2.0
11.0.0-rc.13.116.1 (net462)4.0.1.24.0.1.24.0.1.2

SkiaSharpの引き上げはdotnet/maui#37731(“Update SkiaSharp to 4.150.1”)で行われ、これが10.0.1xxのサービシングブランチにバックポートされて10.0.101でリリースされました。

次に、dotnet buildがタスクの依存関係をどう読み込むかを見てみましょう。.NET上のMSBuildは、タスクの各アセンブリを専用のMSBuildLoadContextに配置します。依存関係が要求されると、タスクのフォルダーを探索しますが、MSBuildLoadContext.Loadでは、ローカルのバージョンが要求されたバージョンより低い場合、そのローカルファイルをスキップします。

// dotnet/msbuild main, src/Framework/Loader/MSBuildLoadContext.cs (abridged)
AssemblyName candidateAssemblyName = AssemblyLoadContext.GetAssemblyName(candidatePath);
if (candidateAssemblyName.Version < assemblyName.Version)
{
    continue;
}
return LoadFromAssemblyPath(candidatePath);

したがって、10.0.101と10.0.110では次のようになります。

  1. Svg.SkiaはSystem.Memory 4.0.2.0を要求します。タスクのフォルダーには4.0.2.0があるため、MSBuildはそのファイルをプラグインコンテキストに読み込みます。このSystem.Memory.dllはアウトオブバンドのnetstandard2.0パッケージビルドであり、独自のSystem.ReadOnlySpan<T>型を定義しています。
  2. SkiaSharpはSystem.Memory 4.0.5.0を要求します。ローカルの4.0.2.0は古すぎるため、探索はデフォルトコンテキストにフォールバックし、共有フレームワークのSystem.Memoryファサードが解決されます。このファサードはReadOnlySpan<T>をSystem.Private.CoreLibにタイプフォワードします。
  3. Svg.SkiaはSKImageFilter.CreateMatrixConvolution(..., ReadOnlySpan<float> [from System.Memory.dll], ...)という呼び出しをコンパイルします。一方SkiaSharpが公開しているのはCreateMatrixConvolution(..., ReadOnlySpan<float> [from CoreLib], ...)です。名前もテキストも同じですが、型のアイデンティティが異なります。ランタイムはこれをバインドできず、呼び出し元メソッドをJITコンパイルする際にMissingMethodExceptionをスローします。

これは、再現用のSVGがfeGaussianBlurしか使っていないにもかかわらず、#38319のトレースにCreateMatrixConvolutionという名前が出てくる理由でもあります。この例外はSvg.Skia.SkiaModel.ToSKImageFilterがJITコンパイルされるときに発生し、このメソッドにはすべてのフィルタープリミティブに対する呼び出しが含まれているためです。<filter>を含むSVGはどれもこのコードに到達します。フィルターもテキストも使わないSVGは、ラスタライズ中にSpanを引数に取るSkiaSharp APIに一切触れないため、デフォルトのテンプレートアイコンは今でもビルドに成功します。

このメカニズムを証明するため、10.0.110のbuildTransitiveフォルダーをコピーし、System.Memory.dllだけを削除して、タスクにそのコピーを指し示させてみました。失敗していた2つのSVGはどちらも問題なくラスタライズされました。これは、System.Memoryへのすべての要求がフレームワークのファサードに行き着き、ReadOnlySpan<T>が1種類だけになるためです。このハックを本番に出荷してはいけませんが、診断の裏付けにはなります。

最小の再現手順

Resizetizerは通常のMSBuildタスクなので、これを再現するのにMAUIワークロードは不要です。microsoft.maui.resizetizer.10.0.110.nupkgを展開して、タスクを直接実行します。

<!-- .NET SDK 10.0.302, Microsoft.Maui.Resizetizer 10.0.110 (extracted nupkg), run.proj -->
<Project>
  <UsingTask AssemblyFile="$(RzDir)/Microsoft.Maui.Resizetizer.dll"
             TaskName="Microsoft.Maui.Resizetizer.ResizetizeImages" />
  <Target Name="Build">
    <ItemGroup><Img Include="$(Svg)" BaseSize="128,128" /></ItemGroup>
    <ResizetizeImages PlatformType="android"
                      IntermediateOutputPath="$(MSBuildThisFileDirectory)out/"
                      InputsFile="$(MSBuildThisFileDirectory)out/inputs.txt"
                      Images="@(Img)" />
  </Target>
</Project>

テスト用の画像は2つ用意します。

<!-- filter.svg: fails on Resizetizer 10.0.101 and 10.0.110 -->
<svg width="456" height="456" viewBox="0 0 456 456" xmlns="http://www.w3.org/2000/svg">
  <filter id="blur"><feGaussianBlur stdDeviation="8" /></filter>
  <rect width="456" height="456" fill="#512BD4" filter="url(#blur)" />
</svg>
<!-- text.svg: fails on Resizetizer 10.0.101 and 10.0.110 -->
<svg width="456" height="456" viewBox="0 0 456 456" xmlns="http://www.w3.org/2000/svg">
  <rect width="456" height="456" fill="#512BD4" />
  <text font-family="Arial" font-size="120" fill="#FFFFFF"><tspan x="60 150 240" y="280">SD!</tspan></text>
</svg>

各パッケージに対してdotnet build run.proj -nodeReuse:false -p:RzDir=<buildTransitive folder> -p:Svg=<file>を実行したところ、SDK 10.0.302のmacOSでは次の結果になりました。

Resizetizer通常のSVG<filter><text>
10.0.100OKOKOK
10.0.101OKCreateMatrixConvolutionAddPositionedRun
10.0.110OKCreateMatrixConvolutionSKTypeface.Clone
11.0.0-rc.1.26451.6OKOKOK

PlatformTypeをiosに切り替えても10.0.110では同様に失敗するため、私のテストでは10.0.110はどちらのプラットフォームでもどちらのパターンも修正していませんでした。本記事執筆時点で#38319と#38507はまだオープンであり、提案されている修正であるdotnet/maui#38883は、同梱されているSkiaSharpをnetstandard2.0ビルドに差し替えるドラフトです。このCI実行ではネイティブライブラリの不整合が表面化しており、次のサービスリリースに間に合うことは期待しない方がよいでしょう。

修正1: Microsoft.Maui.Resizetizerを10.0.100に固定する

Resizetizerはビルド時にのみ使われます。PNGとリソースファイルを生成するだけで、アプリに同梱されるものは何もありません。そのため、MAUIの他の部分を10.0.110のままにして、Resizetizerだけをバージョン1つ分戻しても安全です。

MAUI SDKはMicrosoft.Maui.Resizetizerを$(MauiVersion)の暗黙的なPackageReferenceとして追加しますが、同じ名前の明示的な参照を宣言すると、ターゲットはその暗黙的な項目を削除します。Microsoft.Maui.Controls 10.0.110もMicrosoft.Maui.Resizetizer >= 10.0.110に依存しているため、単純にダウングレードするとリストアが失敗します。

error NU1605: Warning As Error: Detected package downgrade: Microsoft.Maui.Resizetizer from 10.0.110 to 10.0.100.
  App -> Microsoft.Maui.Controls 10.0.110 -> Microsoft.Maui.Resizetizer (>= 10.0.110)
  App -> Microsoft.Maui.Resizetizer (>= 10.0.100)

他の箇所での本物のダウングレードを隠してしまわないよう、NU1605はこの参照だけに限定して抑制します。

<!-- .NET 10, Microsoft.Maui.Controls 10.0.110, App.csproj -->
<ItemGroup>
  <PackageReference Include="Microsoft.Maui.Controls" Version="$(MauiVersion)" />

  <!-- Workaround for dotnet/maui#38319 and #38507. Remove when a fixed Resizetizer ships. -->
  <PackageReference Include="Microsoft.Maui.Resizetizer"
                    Version="10.0.100"
                    PrivateAssets="all"
                    NoWarn="NU1605" />
</ItemGroup>

これでクリーンにリストアでき、project.assets.jsonがMicrosoft.Maui.Resizetizer/10.0.100を解決することを確認しています。Central Package Managementを使っている場合は、VersionをPackageVersion項目に置き、NoWarn="NU1605"はPackageReference側に残してください。

最も大雑把な代替策は、両方のissueの報告者が使った方法で、<MauiVersion>10.0.100</MauiVersion>でMAUI全体を巻き戻すことです。これでも動作しますが、ビルドタスクの問題を回避するために10.0.110のすべての修正を手放すことになります。MAUIを戻す別の理由がすでにある場合にのみ行ってください。

修正2: Resizetizerが処理するSVGからフィルターとテキストを取り除く

これは、上流がパッチを出荷した後も維持しておきたい修正です。アイコンをどこでも同じように描画できるようになるからです。ResizetizerはSvg.SkiaでSVGをラスタライズしますが、これはブラウザではありません。テキストはビルドマシンに存在するフォントに依存するため(macOSのCIランナーとWindowsのノートPCでは異なるフォールバックが選ばれます)、SVGフィルターはこれまでもブラウザ以外のレンダラーの中で最も再現性の低い部分でした。

テキストはアウトラインに変換してください。Inkscape 1.xではコマンドラインから変換でき、アセットのフォルダー全体を処理するのに便利です。

# Inkscape 1.x, converts <text> to <path> and drops editor metadata
inkscape design/splash-source.svg --export-text-to-path --export-plain-svg --export-filename=Resources/Splash/splash.svg

Figmaでは、エクスポート前にテキストレイヤーに”Outline stroke”(アウトラインストローク)/“Flatten”(統合)を適用します。Illustratorでは”Create Outlines”(アウトラインを作成)です。編集可能なソースファイルはResources/の外のどこかに保管し、Resizetizerの目に触れないようにしてください。

フィルターについては、2つの選択肢があります。

<!-- .NET 10, MAUI 10.0.110, App.csproj -->
<ItemGroup>
  <MauiIcon Include="Resources\AppIcon\appicon.svg"
            ForegroundFile="Resources\AppIcon\appiconfg.png"
            Color="#512BD4" />
</ItemGroup>

CIより先に影響を受けるファイルをすべて見つけるには、2つの要素名をgrepします。

# any shell with grep; lists SVGs the 10.0.101/10.0.110 Resizetizer will choke on
grep -rlE "<(filter|text)[ >]" --include="*.svg" Resources/

修正3: どうせ予定していたならMAUI 11に移行する

.NET MAUI 11 RC 1のResizetizer(11.0.0-rc.1.26451.6)は依然としてSkiaSharp 3.116.1と、それに対応するSvg.Skia 2.0.0.4を同梱しており、3つのテストSVGはすべて問題なくラスタライズされました。これは1つのビルドエラーのためだけにリリース候補版に飛びつく理由にはなりませんが、アップグレードがすでに予定されているなら、この問題はそれと一緒に解消されます。10.0.1xxのサービシングブランチの方が先にSkiaSharp 4.150.1を取り込んだことは覚えておいてください。根本で修正されなければ、後のMAUI 11のビルドが同じ組み合わせを引き継ぐ可能性があります。

見落としやすい点とよく似た別の問題

関連記事

参考資料

Comments

Sign in with GitHub to comment. Reactions and replies thread back to the comments repo.

< 戻る