Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,14 @@ Install-Package Svg.Controls.Skia.Avalonia
<Image Source="{SvgImage /Assets/__AJ_Digital_Camera.svg}"/>
```

#### Background

```XAML
<Border Width="100"
Height="100"
Background="{SvgImage /Assets/__AJ_Digital_Camera.svg}" />
```

### CSS styling

```XAML
Expand Down Expand Up @@ -265,6 +273,29 @@ Install-Package Svg.Controls.Skia.Avalonia
</Image>
```

#### SvgBrush Markup Extension

Use `SvgBrush` when you want to place an SVG-backed `VisualBrush` in a resource dictionary and reuse it for backgrounds or other brush targets:

```XAML
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:svg="clr-namespace:Avalonia.Svg.Skia;assembly=Svg.Controls.Skia.Avalonia">
<UserControl.Resources>
<svg:SvgBrush x:Key="TigerBrush"
Stretch="UniformToFill"
AlignmentX="Center"
AlignmentY="Center"
TileMode="Tile"
DestinationRect="0,0,1,1"
Opacity="0.85">/Assets/__tiger.svg</svg:SvgBrush>
</UserControl.Resources>

<Border Background="{DynamicResource TigerBrush}" />
</UserControl>
```

The optional properties mirror those on `VisualBrush`, so you can tweak layout, tiling, opacity, and transforms directly in XAML while the control takes care of loading and rendering the SVG content.

#### Avalonia Previewer

To make controls work with `Avalonia Previewer` please add the following lines to `BuildAvaloniaApp()` method:
Expand Down
7 changes: 7 additions & 0 deletions Svg.Skia.sln
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Svg.Custom", "src\Svg.Custom\Svg.Custom.csproj", "{CFA46E73-0050-4C57-85CE-6C5868A2483C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Svg.Controls.Skia.Avalonia.UnitTests", "tests\Svg.Controls.Skia.Avalonia.UnitTests\Svg.Controls.Skia.Avalonia.UnitTests.csproj", "{D4467DCA-494D-4C32-9525-4A9713221A53}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Svg.Controls.Avalonia.UnitTests", "tests\Svg.Controls.Avalonia.UnitTests\Svg.Controls.Avalonia.UnitTests.csproj", "{55A5979A-292D-4A51-A89E-14FCEF5E6792}"
EndProject
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Svg.Model", "src\Svg.Model\Svg.Model.csproj", "{4C970B2C-6C96-445B-B80B-4EFBF803FD5F}"
EndProject
Expand Down Expand Up @@ -157,6 +159,10 @@ Global
{D4467DCA-494D-4C32-9525-4A9713221A53}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D4467DCA-494D-4C32-9525-4A9713221A53}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D4467DCA-494D-4C32-9525-4A9713221A53}.Release|Any CPU.Build.0 = Release|Any CPU
{55A5979A-292D-4A51-A89E-14FCEF5E6792}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{55A5979A-292D-4A51-A89E-14FCEF5E6792}.Debug|Any CPU.Build.0 = Debug|Any CPU
{55A5979A-292D-4A51-A89E-14FCEF5E6792}.Release|Any CPU.ActiveCfg = Release|Any CPU
{55A5979A-292D-4A51-A89E-14FCEF5E6792}.Release|Any CPU.Build.0 = Release|Any CPU
{4C970B2C-6C96-445B-B80B-4EFBF803FD5F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4C970B2C-6C96-445B-B80B-4EFBF803FD5F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4C970B2C-6C96-445B-B80B-4EFBF803FD5F}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand Down Expand Up @@ -241,6 +247,7 @@ Global
{81724F00-B7C3-4E25-B473-C7433BABDC81} = {B65D5B3A-77BE-4AFF-B502-A136B9C932F8}
{CFA46E73-0050-4C57-85CE-6C5868A2483C} = {C5FFCF4B-86DC-453E-8006-44EE9EEFEE39}
{D4467DCA-494D-4C32-9525-4A9713221A53} = {7863AE7D-FF68-45BF-BA68-6FA0E5604CB7}
{55A5979A-292D-4A51-A89E-14FCEF5E6792} = {7863AE7D-FF68-45BF-BA68-6FA0E5604CB7}
{4C970B2C-6C96-445B-B80B-4EFBF803FD5F} = {4C42912C-9F8C-43D9-A4B5-4427F7EC8F18}
{29F59C87-EAE6-4DD3-8666-B79BFAF6B34D} = {4C42912C-9F8C-43D9-A4B5-4427F7EC8F18}
{223B7A5A-E263-4D40-9A6E-FE31EAE92F45} = {4C42912C-9F8C-43D9-A4B5-4427F7EC8F18}
Expand Down
17 changes: 17 additions & 0 deletions samples/AvaloniaSvgSample/MainWindow.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,23 @@
</Panel>
</TabItem>

<TabItem Header="Background">
<Panel Margin="16" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<Border CornerRadius="12"
Background="{SvgImage /Assets/__tiger.svg}" />
</Panel>
</TabItem>

<TabItem Header="Background (Resources)">
<TabItem.Resources>
<svg:SvgBrush x:Key="__tiger">/Assets/__tiger.svg</svg:SvgBrush>
</TabItem.Resources>
<Panel Margin="16" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<Border CornerRadius="12"
Background="{DynamicResource __tiger}" />
</Panel>
</TabItem>

<TabItem Header="String">
<DockPanel x:Name="svgStringDockPanel"
Background="Transparent"
Expand Down
17 changes: 17 additions & 0 deletions samples/AvaloniaSvgSkiaSample/MainWindow.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,23 @@
</Panel>
</TabItem>

<TabItem Header="Background">
<Panel Margin="16" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<Border CornerRadius="12"
Background="{SvgImage /Assets/__tiger.svg}" />
</Panel>
</TabItem>

<TabItem Header="Background (Resources)">
<TabItem.Resources>
<svg:SvgBrush x:Key="__tiger">/Assets/__tiger.svg</svg:SvgBrush>
</TabItem.Resources>
<Panel Margin="16" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<Border CornerRadius="12"
Background="{DynamicResource __tiger}" />
</Panel>
</TabItem>

<TabItem Header="String">
<DockPanel x:Name="svgStringDockPanel"
Background="Transparent"
Expand Down
181 changes: 181 additions & 0 deletions src/Svg.Controls.Avalonia/SvgBrush.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
// Copyright (c) Wiesław Šoltés. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.
using System;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Media;
using Avalonia.Metadata;

namespace Avalonia.Svg;

/// <summary>
/// Provides an SVG-backed brush that can be declared in XAML resources.
/// </summary>
public class SvgBrush : MarkupExtension
{
/// <summary>
/// Gets or sets the SVG resource or file path.
/// </summary>
[Content]
public string? Path { get; set; }

/// <summary>
/// Gets or sets the stretch applied to the resulting brush.
/// </summary>
public Stretch? Stretch { get; set; }

/// <summary>
/// Gets or sets the horizontal alignment applied to the resulting brush.
/// </summary>
public AlignmentX? AlignmentX { get; set; }

/// <summary>
/// Gets or sets the vertical alignment applied to the resulting brush.
/// </summary>
public AlignmentY? AlignmentY { get; set; }

/// <summary>
/// Gets or sets the tile mode applied to the resulting brush.
/// </summary>
public TileMode? TileMode { get; set; }

/// <summary>
/// Gets or sets the destination rectangle applied to the resulting brush.
/// </summary>
public RelativeRect? DestinationRect { get; set; }

/// <summary>
/// Gets or sets the source rectangle applied to the resulting brush.
/// </summary>
public RelativeRect? SourceRect { get; set; }

/// <summary>
/// Gets or sets the opacity applied to the resulting brush.
/// </summary>
public double? Opacity { get; set; }

/// <summary>
/// Gets or sets the transform applied to the resulting brush.
/// </summary>
public Transform? Transform { get; set; }

/// <summary>
/// Gets or sets the transform origin applied to the resulting brush.
/// </summary>
public RelativePoint? TransformOrigin { get; set; }

/// <summary>
/// Creates a <see cref="VisualBrush"/> configured with the supplied image and optional overrides.
/// </summary>
/// <param name="image">The SVG image instance rendered by the brush.</param>
/// <param name="stretch">Optional stretch applied to the brush.</param>
/// <param name="alignmentX">Optional horizontal alignment applied to the brush.</param>
/// <param name="alignmentY">Optional vertical alignment applied to the brush.</param>
/// <param name="tileMode">Optional tile mode applied to the brush.</param>
/// <param name="destinationRect">Optional destination rectangle for the brush content.</param>
/// <param name="sourceRect">Optional source rectangle cropping the brush content.</param>
/// <param name="opacity">Optional opacity multiplier applied to the brush.</param>
/// <param name="transform">Optional transform applied to the brush.</param>
/// <param name="transformOrigin">Optional transform origin applied when <paramref name="transform"/> is set.</param>
/// <returns>A <see cref="VisualBrush"/> that renders <paramref name="image"/>.</returns>
internal static IBrush CreateFromImage(
IImage image,
Stretch? stretch = null,
AlignmentX? alignmentX = null,
AlignmentY? alignmentY = null,
TileMode? tileMode = null,
RelativeRect? destinationRect = null,
RelativeRect? sourceRect = null,
double? opacity = null,
Transform? transform = null,
RelativePoint? transformOrigin = null)
{
var brush = new VisualBrush
{
Visual = new Image
{
Source = image
}
};

if (stretch.HasValue)
{
brush.Stretch = stretch.Value;
}

if (alignmentX.HasValue)
{
brush.AlignmentX = alignmentX.Value;
}

if (alignmentY.HasValue)
{
brush.AlignmentY = alignmentY.Value;
}

if (tileMode.HasValue)
{
brush.TileMode = tileMode.Value;
}

if (destinationRect.HasValue)
{
brush.DestinationRect = destinationRect.Value;
}

if (sourceRect.HasValue)
{
brush.SourceRect = sourceRect.Value;
}

if (opacity.HasValue)
{
brush.Opacity = opacity.Value;
}

if (transform is not null)
{
brush.Transform = transform;
}

if (transformOrigin.HasValue)
{
brush.TransformOrigin = transformOrigin.Value;
}

return brush;
}

/// <inheritdoc/>
public override object ProvideValue(IServiceProvider serviceProvider)
{
if (serviceProvider is null)
{
throw new ArgumentNullException(nameof(serviceProvider));
}

if (Path is null)
{
throw new InvalidOperationException("SvgBrush requires a non-null Path.");
}

var baseUri = serviceProvider.GetContextBaseUri();
var source = SvgSource.Load(Path, baseUri);
var image = new SvgImage
{
Source = source
};

return CreateFromImage(
image,
Stretch,
AlignmentX,
AlignmentY,
TileMode,
DestinationRect,
SourceRect,
Opacity,
Transform,
TransformOrigin);
}
}
23 changes: 16 additions & 7 deletions src/Svg.Controls.Avalonia/SvgImageExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,23 @@ public override object ProvideValue(IServiceProvider serviceProvider)
var baseUri = context.BaseUri;
var source = SvgSource.Load(path, baseUri);
var target = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget))!;
if (target.TargetProperty is AvaloniaProperty property)
var image = new SvgImage { Source = source };

if (target.TargetProperty is not AvaloniaProperty property)
{
return image;
}

if (typeof(IImage).IsAssignableFrom(property.PropertyType))
{
if (property.PropertyType == typeof(IImage))
{
return new SvgImage { Source = source };
}
return new Image { Source = new SvgImage { Source = source } };
return image;
}
return new SvgImage { Source = source };

if (typeof(IBrush).IsAssignableFrom(property.PropertyType))
{
return SvgBrush.CreateFromImage(image);
}

return new Image { Source = image };
}
}
Loading
Loading