Skip to content

Resizetizer: Allow custom backends to opt into image processing via ResizetizerPlatformType #34220

@Redth

Description

@Redth

Description

The Resizetizer's MSBuild targets hard-code a closed set of five platforms that are allowed to use its image processing pipeline. Custom community backends (macOS AppKit, GTK/Linux, etc.) are completely excluded with no supported way to hook in.

Two small changes — totaling ~25 lines — would enable custom backends to reuse Resizetizer's core SVG→PNG conversion and multi-density image generation, eliminating ~80% of the resource processing work they currently must re-implement from scratch.

Part of #34099


Problem 1: The _ResizetizerIsCompatibleApp Gate

In Microsoft.Maui.Resizetizer.After.targets, the entire processing pipeline is gated behind:

<PropertyGroup Condition="'$(_ResizetizerIsAndroidApp)' == 'True'
    Or '$(_ResizetizerIsiOSApp)' == 'True'
    Or '$(_ResizetizerIsWPFApp)' == 'True'
    Or '$(_ResizetizerIsWindowsAppSdk)' == 'True'
    Or '$(_ResizetizerIsTizenApp)' == 'True'">
    <_ResizetizerIsCompatibleApp>True</_ResizetizerIsCompatibleApp>
</PropertyGroup>

Custom backends cannot trigger ResizetizeImages, ProcessMauiFonts, ProcessMauiAssets, or any resource processing target because they don't match any of these five conditions. There is no public opt-in mechanism.

Note: The targets file already detects macos and tvos platform identifiers (_ResizetizerPlatformIsmacOS, _ResizetizerPlatformIstvOS) but never uses them to set _ResizetizerIsCompatibleApp.

Problem 2: DpiPath.GetDpis() Returns Null on Unknown Platforms

Even if a custom backend could get past the compatibility gate, the ResizetizeImages task would fail because DpiPath.GetDpis(platform) returns null for any platform not in its switch statement:

public static DpiPath[] GetDpis(string platform)
{
    switch (platform.ToLowerInvariant())
    {
        case "ios": return Ios.Image;
        case "android": return Android.Image;
        case "uwp": return Windows.Image;
        case "wpf": return Wpf.Image;
        case "tizen": return Tizen.Image;
    }
    return null; // ← NullReferenceException downstream
}

The same issue exists in GetAppIconDpis() and GetOriginal().


Proposed Solution

Change 1: Allow External Platform Opt-In (~5 lines in After.targets)

Add a property-based opt-in so that any backend setting ResizetizerPlatformType can trigger processing:

<!-- After the existing platform-specific PropertyGroup -->

<!-- Allow external backends to opt in by setting ResizetizerPlatformType -->
<PropertyGroup Condition="'$(_ResizetizerIsCompatibleApp)' != 'True'
    And '$(ResizetizerPlatformType)' != ''">
    <_ResizetizerIsCompatibleApp>True</_ResizetizerIsCompatibleApp>
</PropertyGroup>

Custom backends would set this in their .targets file:

<PropertyGroup>
    <ResizetizerPlatformType>macos-appkit</ResizetizerPlatformType>
</PropertyGroup>

Change 2: Add Default DPI Fallback (~20 lines in DpiPath.cs)

Add a default case to the three methods in DpiPath.cs:

public static DpiPath[] GetDpis(string platform)
{
    switch (platform.ToLowerInvariant())
    {
        case "ios": return Ios.Image;
        case "android": return Android.Image;
        case "uwp": return Windows.Image;
        case "wpf": return Wpf.Image;
        case "tizen": return Tizen.Image;
        default:
            // Generic desktop fallback: 1x and 2x
            return new[]
            {
                new DpiPath("", 1.0m),
                new DpiPath("", 2.0m, null, "@2x"),
            };
    }
}

public static DpiPath GetOriginal(string platform)
{
    switch (platform.ToLowerInvariant())
    {
        // ... existing cases ...
        default: return new DpiPath("", 1.0m);
    }
}

public static DpiPath[] GetAppIconDpis(string platform, string appIconName)
{
    // ... existing cases ...
    default:
        return new[]
        {
            new DpiPath("", 1.0m, null, null, new SKSize(16, 16)),
            new DpiPath("", 1.0m, null, null, new SKSize(32, 32)),
            new DpiPath("", 1.0m, null, null, new SKSize(48, 48)),
            new DpiPath("", 1.0m, null, null, new SKSize(128, 128)),
            new DpiPath("", 1.0m, null, null, new SKSize(256, 256)),
            new DpiPath("", 1.0m, null, null, new SKSize(512, 512)),
            new DpiPath("", 1.0m, null, null, new SKSize(1024, 1024)),
        };
}

The 1x + @2x default covers macOS Retina, GTK HiDPI, and any other desktop platform. Custom backends then handle the platform-specific "last mile" — placing files into the correct item groups (BundleResource for macOS, Content for GTK, etc.) via their own .targets files.


What This Enables

With just these two changes, a custom backend's .targets file can:

  1. Set ResizetizerPlatformType to opt into image processing
  2. Let ResizetizeImages run — SVG→PNG conversion, tinting, multi-density generation all work
  3. Consume outputs from $(_MauiIntermediateImages) in their own AfterTargets hook
  4. Only implement the platform-specific output — which item groups to use, icon format generation, etc.

Example: macOS AppKit Backend Consuming Resizetizer Output

<!-- In Platform.Maui.MacOS.targets -->
<PropertyGroup>
    <ResizetizerPlatformType>macos-appkit</ResizetizerPlatformType>
</PropertyGroup>

<!-- Resizetizer processes images into obj/.../resizetizer/r/ -->
<!-- We just need to bundle them: -->
<Target Name="_MacOSBundleProcessedImages"
        AfterTargets="ResizetizeImages"
        Condition="'@(_ResizetizerCollectedImages)' != ''">
    <ItemGroup>
        <BundleResource Include="@(_ResizetizerCollectedImages)">
            <LogicalName>%(_ResizetizerCollectedImages.Filename)%(_ResizetizerCollectedImages.Extension)</LogicalName>
        </BundleResource>
    </ItemGroup>
</Target>

Example: GTK Backend

<PropertyGroup>
    <ResizetizerPlatformType>gtk</ResizetizerPlatformType>
</PropertyGroup>

<Target Name="_GtkBundleProcessedImages"
        AfterTargets="ResizetizeImages"
        Condition="'@(_ResizetizerCollectedImages)' != ''">
    <ItemGroup>
        <Content Include="@(_ResizetizerCollectedImages)">
            <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
            <Link>Resources\Images\%(_ResizetizerCollectedImages.Filename)%(_ResizetizerCollectedImages.Extension)</Link>
        </Content>
    </ItemGroup>
</Target>

Impact

  • Zero breaking changes — all existing platform behavior is unchanged
  • ~25 lines of code total across 2 files
  • Eliminates ~80% of custom backend resource processing work
  • Consistent behavior — custom backends get the same SVG conversion, tinting, and density generation as official platforms

Files Changed

  1. src/SingleProject/Resizetizer/src/nuget/buildTransitive/Microsoft.Maui.Resizetizer.After.targets — Add opt-in PropertyGroup (~5 lines)
  2. src/SingleProject/Resizetizer/src/DpiPath.cs — Add default cases to 3 switch statements (~20 lines)

Metadata

Metadata

Assignees

No one assigned

    Labels

    s/triagedIssue has been reviewed

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions