10. Working with resources, images, and fonts

Goal

Why this matters

Prerequisites

1. avares:// URIs and project structure

Assets live under your project (e.g., Assets/Images, Assets/Fonts). Include them as AvaloniaResource in the .csproj:

<ItemGroup>
  <AvaloniaResource Include="Assets/**" />
</ItemGroup>

URI structure: avares://<AssemblyName>/<RelativePath>.

Example: avares://InputPlayground/Assets/Images/logo.png.

avares:// references the compiled resource stream (not the file system). Use it consistently even within the same assembly to avoid issues with resource lookups.

2. Resource dictionaries and lookup order

ResourceDictionary derives from ResourceProvider and implements IResourceProvider. When you request {StaticResource} or call TryGetResource, Avalonia walks this chain:

  1. The requesting IResourceHost (control, style, or application).
  2. Parent styles (<Style.Resources>), control templates, and data templates.
  3. Theme dictionaries (ThemeVariantScope, Application.Styles, Application.Resources).
  4. Merged dictionaries (<ResourceDictionary.MergedDictionaries> or <ResourceInclude>).
  5. Global application resources and finally platform defaults (SystemResources).

ResourceDictionary.cs and ResourceNode.cs coordinate this traversal. Use TryGetResource when retrieving values from code:

if (control.TryGetResource("AccentBrush", ThemeVariant.Dark, out var value) && value is IBrush brush)
{
    control.Background = brush;
}

ThemeVariant lets you request a variant-specific value; pass ThemeVariant.Default to follow the same logic as {DynamicResource}.

Merge dictionaries to break assets into reusable packs:

<ResourceDictionary>
  <ResourceDictionary.MergedDictionaries>
    <ResourceInclude Source="avares://AssetsDemo/Assets/Colors.axaml"/>
    <ResourceInclude Source="avares://AssetsDemo/Assets/Icons.axaml"/>
  </ResourceDictionary.MergedDictionaries>
</ResourceDictionary>

Each merged dictionary is loaded lazily via IAssetLoader, so make sure the referenced file is marked as AvaloniaResource.

3. Loading assets in XAML and code

XAML

<Image Source="avares://AssetsDemo/Assets/Images/logo.png"
       Stretch="Uniform" Width="160"/>

Code using AssetLoader

using Avalonia;
using Avalonia.Media.Imaging;
using Avalonia.Platform;

var uri = new Uri("avares://AssetsDemo/Assets/Images/logo.png");
var assetLoader = AvaloniaLocator.Current.GetRequiredService<IAssetLoader>();

await using var stream = assetLoader.Open(uri);
LogoImage.Source = new Bitmap(stream);

AssetLoader is a static helper over the same IAssetLoader service. Prefer the interface when unit testing or when you need to mock resource access. Both live in Avalonia.Platform.

Need to probe for optional assets? Use assetLoader.TryOpen(uri) or AssetLoader.Exists(uri) to avoid exceptions.

Resource dictionaries

<ResourceDictionary xmlns="https://github.com/avaloniaui">
  <Bitmap x:Key="LogoBitmap">avares://AssetsDemo/Assets/Images/logo.png</Bitmap>
</ResourceDictionary>

You can then StaticResource expose LogoBitmap. Bitmaps created this way are cached.

4. Raster images, decoders, and caching

Image renders Avalonia.Media.Imaging.Bitmap. Decode streams once and keep the bitmap alive when the pixels are reused, instead of calling new Bitmap(stream) for every render. Performance tips:

<Image Source="avares://AssetsDemo/Assets/Images/photo.jpg"
       Width="240" Height="160"
       RenderOptions.BitmapInterpolationMode="HighQuality"/>

Interpolation modes defined in RenderOptions.cs.

Decode oversized images to a target width/height to save memory:

await using var stream = assetLoader.Open(uri);
using var decoded = Bitmap.DecodeToWidth(stream, 512);
PhotoImage.Source = decoded;

Bitmap and decoder helpers live in Bitmap.cs. Avalonia picks the right codec (PNG, JPEG, WebP, BMP, GIF) using Skia; for unsupported formats supply a custom IBitmapDecoder.

5. ImageBrush and tiled backgrounds

ImageBrush paints surfaces:

<Ellipse Width="96" Height="96">
  <Ellipse.Fill>
    <ImageBrush Source="avares://AssetsDemo/Assets/Images/avatar.png"
                Stretch="UniformToFill" AlignmentX="Center" AlignmentY="Center"/>
  </Ellipse.Fill>
</Ellipse>

Tile backgrounds:

<Border Width="200" Height="120">
  <Border.Background>
    <ImageBrush Source="avares://AssetsDemo/Assets/Images/pattern.png"
                TileMode="Tile"
                Stretch="None"
                Transform="{ScaleTransform 0.5,0.5}"/>
  </Border.Background>
</Border>

ImageBrush documentation: ImageBrush.cs.

6. Vector graphics

Vector art scales with DPI, can adapt to theme colors, and stays crisp.

Inline geometry

<Path Data="M2 12 L9 19 L22 4"
      Stroke="{DynamicResource AccentBrush}"
      StrokeThickness="3"
      StrokeLineCap="Round" StrokeLineJoin="Round"/>

Store geometry in resources for reuse:

<ResourceDictionary xmlns="https://github.com/avaloniaui">
  <Geometry x:Key="IconCheck">M2 12 L9 19 L22 4</Geometry>
</ResourceDictionary>

Vector classes live under Avalonia.Media.

StreamGeometryContext for programmatic icons

Generate vector shapes in code when you need to compose icons dynamically or reuse geometry logic:

var geometry = new StreamGeometry();

using (var ctx = geometry.Open())
{
    ctx.BeginFigure(new Point(2, 12), isFilled: false);
    ctx.LineTo(new Point(9, 19));
    ctx.LineTo(new Point(22, 4));
    ctx.EndFigure(isClosed: false);
}

IconPath.Data = geometry;

StreamGeometry and StreamGeometryContext live in StreamGeometryContext.cs. Remember to freeze geometry instances or share them via resources to reduce allocations.

SVG support

Install the Avalonia.Svg.Skia package to render SVG assets natively:

<svg:SvgImage xmlns:svg="clr-namespace:Avalonia.Svg.Controls;assembly=Avalonia.Svg.Skia"
              Source="avares://AssetsDemo/Assets/Images/logo.svg"
              Stretch="Uniform" />

SVGs stay sharp at any DPI and can adapt colors if you parameterize them (e.g., replace fill attributes at build time). For simple icons, converting the path data into XAML keeps dependencies minimal.

7. Fonts and typography

Place fonts in Assets/Fonts. Register them in App.axaml via Global::Avalonia URI and specify the font face after #:

<Application.Resources>
  <FontFamily x:Key="HeadingFont">avares://AssetsDemo/Assets/Fonts/Inter.ttf#Inter</FontFamily>
</Application.Resources>

Use the font in styles:

<Application.Styles>
  <Style Selector="TextBlock.h1">
    <Setter Property="FontFamily" Value="{StaticResource HeadingFont}"/>
    <Setter Property="FontSize" Value="28"/>
    <Setter Property="FontWeight" Value="SemiBold"/>
  </Style>
</Application.Styles>

FontManager options

Configure global font settings in AppBuilder:

AppBuilder.Configure<App>()
    .UsePlatformDetect()
    .With(new FontManagerOptions
    {
        DefaultFamilyName = "avares://AssetsDemo/Assets/Fonts/Inter.ttf#Inter",
        FontFallbacks = new[] { new FontFallback { Family = "Segoe UI" }, new FontFallback { Family = "Roboto" } }
    })
    .StartWithClassicDesktopLifetime(args);

FontManagerOptions lives in FontManagerOptions.cs.

Multi-weight fonts

If fonts include multiple weights, specify them with FontWeight. If you ship multiple font files (Regular, Bold), ensure the #Family name is consistent.

Runtime font swaps and custom collections

You can inject fonts at runtime without restarting the app. Register an embedded collection and update resources:

using Avalonia.Media;
using Avalonia.Media.Fonts;

var baseUri = new Uri("avares://AssetsDemo/Assets/BrandFonts/");
var collection = new EmbeddedFontCollection(new Uri("fonts:brand"), baseUri);

FontManager.Current.AddFontCollection(collection);

Application.Current!.Resources["BodyFont"] = new FontFamily("fonts:brand#Brand Sans");

EmbeddedFontCollection pulls all font files under the provided URI using IAssetLoader. Removing the collection via FontManager.Current.RemoveFontCollection(new Uri("fonts:brand")) detaches it again.

8. DPI scaling, caching, and performance

Avalonia measures layout in DIPs (1 DIP = 1/96 inch). High DPI monitors scale automatically.

RenderTargetBitmap and WriteableBitmap under Avalonia.Media.Imaging.

9. Dynamic resources, theme variants, and runtime updates

Bind brushes via DynamicResource so assets respond to theme changes. When a dictionary entry changes, ResourceDictionary.ResourcesChanged notifies every subscriber and controls update automatically:

<Application.Resources>
  <SolidColorBrush x:Key="AvatarFallbackBrush" Color="#1F2937"/>
</Application.Resources>

<Ellipse Fill="{DynamicResource AvatarFallbackBrush}"/>

At runtime you can swap assets:

Application.Current!.Resources["AvatarFallbackBrush"] = new SolidColorBrush(Color.Parse("#3B82F6"));

To scope variants, wrap content in a ThemeVariantScope and supply dictionaries per variant:

<ThemeVariantScope RequestedThemeVariant="Dark">
  <ThemeVariantScope.Resources>
    <SolidColorBrush x:Key="AvatarFallbackBrush" Color="#E5E7EB"/>
  </ThemeVariantScope.Resources>
  <ContentPresenter Content="{Binding}"/>
</ThemeVariantScope>

ThemeVariantScope relies on IResourceHost to merge dictionaries in order (scope → parent scope → application). To inspect all merged resources in DevTools, open Resources and observe how RequestedThemeVariant switches dictionaries.

10. Diagnostics

if (!AssetLoader.Exists(uri))
    throw new FileNotFoundException($"Asset {uri} not found");

11. Sample "asset gallery"

<Grid ColumnDefinitions="Auto,24,Auto" RowDefinitions="Auto,12,Auto">

  <Image Width="160" Height="80" Stretch="Uniform"
         Source="avares://AssetsDemo/Assets/Images/logo.png"/>

  <Rectangle Grid.Column="1" Grid.RowSpan="3" Width="24"/>


  <Ellipse Grid.Column="2" Width="96" Height="96">
    <Ellipse.Fill>
      <ImageBrush Source="avares://AssetsDemo/Assets/Images/avatar.png" Stretch="UniformToFill"/>
    </Ellipse.Fill>
  </Ellipse>

  <Rectangle Grid.Row="1" Grid.ColumnSpan="3" Height="12"/>


  <Canvas Grid.Row="2" Grid.Column="0" Width="28" Height="28">
    <Path Data="M2 14 L10 22 L26 6"
          Stroke="{DynamicResource AccentBrush}"
          StrokeThickness="3" StrokeLineCap="Round" StrokeLineJoin="Round"/>
  </Canvas>

  <TextBlock Grid.Row="2" Grid.Column="2" Classes="h1" Text="Asset gallery"/>
</Grid>

12. Practice exercises

  1. Move brand colors into Assets/Brand.axaml, include it with <ResourceInclude Source="avares://AssetsDemo/Assets/Brand.axaml"/>, and verify lookups succeed from a control in another assembly.
  2. Build an image component that prefers SVG (SvgImage) but falls back to a PNG Bitmap on platforms where the SVG package is missing.
  3. Decode a high-resolution photo with Bitmap.DecodeToWidth and compare memory usage against eagerly loading the original stream.
  4. Register an EmbeddedFontCollection at runtime and swap your typography resources by updating Application.Current.Resources["BodyFont"].
  5. Toggle ThemeVariantScope.RequestedThemeVariant at runtime and confirm DynamicResource-bound brushes and images update without recreating controls.

Look under the hood (source bookmarks)

Check yourself

What's next