Start Debugging

Исправление: MAUIR0001 MissingMethodException в .NET MAUI Resizetizer на SVG с <filter> или <text>

MAUI 10.0.101 и 10.0.110 Resizetizer поставляются с несовпадающими ссылками на System.Memory, поэтому SVG с фильтрами или текстом падают с ошибкой. Зафиксируйте Resizetizer на версии 10.0.100 или уберите filter и text.

Если сборка вашего .NET MAUI 10 начала падать с error MAUIR0001: There was an exception processing the image и System.MissingMethodException для SKImageFilter.CreateMatrixConvolution, SKTextBlobBuilder.AddPositionedRun или SKTypeface.Clone, причина в самом пакете Resizetizer, а не в вашем SVG. Microsoft.Maui.Resizetizer версий 10.0.101 и 10.0.110 содержит сборку SkiaSharp 4.150.1, которая запрашивает System.Memory 4.0.5.0, рядом с Svg.Skia, которая запрашивает 4.0.2.0, и MSBuild загружает два разных типа ReadOnlySpan<T>. Любой SVG с элементом <filter> или <text> приводит к падению. Самое быстрое решение - зафиксировать Microsoft.Maui.Resizetizer на версии 10.0.100, оставив остальную часть MAUI на 10.0.110; надёжное решение - убрать фильтры и преобразовать текст в пути (paths) в SVG-файлах MauiIcon, MauiSplashScreen и MauiImage.

Ошибка в контексте

В апстриме сообщили о двух вариантах этой ошибки. Вариант с фильтром, из 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, и новая версия по-другому разрешает шрифты. Вот что я получаю на 10.0.110 с обычным элементом <text>:

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 также сообщается о варианте того же исключения с HarfBuzzSharp.Font.SetVariations(ReadOnlySpan<Variation>) на шаге GenerateSplashStoryboard для iOS. Каким бы ни было имя метода, посмотрите на сигнатуру: каждый из них принимает ReadOnlySpan<T>. В этом и есть вся суть бага.

Почему Resizetizer не может найти существующий метод

Первое, что делает каждый, - открывает SkiaSharp.dll в декомпиляторе и находит метод прямо на месте. Автор отчёта в #38507 сделал именно это и подтвердил через рефлексию, что AddPositionedRun(ReadOnlySpan<ushort>, SKFont, ReadOnlySpan<SKPoint>) присутствует. Я сделал то же самое с помощью System.Reflection.Metadata для папки buildTransitive каждой версии пакета, и всё сходится: методы существуют в 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 загружает зависимости задачи. MSBuild на .NET помещает каждую сборку задачи в собственный 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 общей платформы (shared framework). Этот фасад перенаправляет (type-forward) ReadOnlySpan<T> в System.Private.CoreLib.
  3. Svg.Skia компилирует вызов SKImageFilter.CreateMatrixConvolution(..., ReadOnlySpan<float> [из System.Memory.dll], ...). SkiaSharp предоставляет CreateMatrixConvolution(..., ReadOnlySpan<float> [из CoreLib], ...). Одинаковое имя, одинаковый текст, разная идентичность типа. Среда выполнения не может связать вызов и выбрасывает MissingMethodException при JIT-компиляции вызывающего метода.

Это также объясняет, почему трассировка в #38319 упоминает CreateMatrixConvolution, хотя SVG в репродукции использует только feGaussianBlur: исключение срабатывает при JIT-компиляции Svg.Skia.SkiaModel.ToSKImageFilter, а этот метод содержит вызов для каждого примитива фильтра. Любой SVG с любым <filter> доходит до него. SVG без фильтров или текста никогда не обращаются к API SkiaSharp, принимающему span, во время растеризации - поэтому иконка из шаблона по умолчанию по-прежнему собирается.

Чтобы доказать механизм, я скопировал папку buildTransitive из 10.0.110, удалил только System.Memory.dll и направил задачу на эту копию. Оба падавших SVG растеризовались нормально, потому что теперь каждый запрос System.Memory попадает на фасад платформы, и существует только один ReadOnlySpan<T>. Не используйте этот хак в продакшене, но он подтверждает диагноз.

Минимальный репродукт

Вам не нужен ворклоад MAUI, чтобы воспроизвести это, потому что Resizetizer - это обычная задача MSBuild. Извлеките 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>

С двумя тестовыми изображениями:

<!-- 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> для каждой версии пакета дал мне следующее на macOS с SDK 10.0.302:

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.

MAUI SDK добавляет Microsoft.Maui.Resizetizer как неявную PackageReference на $(MauiVersion), но таргеты удаляют неявный элемент, когда вы объявляете явный с тем же именем. 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.

Более грубая альтернатива, которую использовали оба автора отчётов об ошибках, - зафиксировать весь MAUI на более старой версии с помощью <MauiVersion>10.0.100</MauiVersion>. Это работает, но вы отказываетесь от всех исправлений в 10.0.110 ради обхода одной задачи сборки. Делайте так, только если у вас уже есть причина удерживать MAUI на старой версии.

Решение 2: убрать фильтры и текст из SVG, которые обрабатывает Resizetizer

Это решение я бы сохранил даже после того, как апстрим выпустит патч, потому что оно также делает так, что ваши иконки везде отображаются одинаково. Resizetizer растеризует SVG с помощью Svg.Skia, которая не является браузером. Текст зависит от шрифтов, установленных на машине сборки (ваш macOS CI-раннер и ваш ноутбук с Windows выберут разные запасные варианты), а фильтры SVG всегда были наименее достоверной частью любого рендерера, не являющегося браузером.

Преобразуйте текст в контуры (outlines). В 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 никогда его не видел.

Для фильтров у вас есть два варианта:

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

Чтобы найти все затронутые файлы до того, как это сделает CI, найдите по 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, если вы и так собирались

Resizetizer из .NET MAUI 11 RC 1 (11.0.0-rc.1.26451.6) по-прежнему включает SkiaSharp 3.116.1 и соответствующую Svg.Skia 2.0.0.4, и все три тестовых SVG растеризовались с ним нормально. Это не повод переходить на релиз-кандидат из-за одной ошибки сборки, но если обновление уже запланировано, эта проблема исчезнет вместе с ним. Учтите, что ветка обслуживания 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.

< Назад