diff --git a/samples/RenderDemo/MainWindow.xaml b/samples/RenderDemo/MainWindow.xaml
index 4a8fb819ca..923b51814f 100644
--- a/samples/RenderDemo/MainWindow.xaml
+++ b/samples/RenderDemo/MainWindow.xaml
@@ -66,5 +66,8 @@
+
+
+
diff --git a/samples/RenderDemo/Pages/BrushesPage.axaml b/samples/RenderDemo/Pages/BrushesPage.axaml
new file mode 100644
index 0000000000..9d5b6ee987
--- /dev/null
+++ b/samples/RenderDemo/Pages/BrushesPage.axaml
@@ -0,0 +1,71 @@
+
+
+
diff --git a/samples/RenderDemo/Pages/BrushesPage.axaml.cs b/samples/RenderDemo/Pages/BrushesPage.axaml.cs
new file mode 100644
index 0000000000..5852b72d9e
--- /dev/null
+++ b/samples/RenderDemo/Pages/BrushesPage.axaml.cs
@@ -0,0 +1,18 @@
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace RenderDemo.Pages;
+
+public class BrushesPage : UserControl
+{
+ public BrushesPage()
+ {
+ InitializeComponent();
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+}
+
diff --git a/src/Avalonia.Visuals/Animation/Animators/GradientBrushAnimator.cs b/src/Avalonia.Visuals/Animation/Animators/GradientBrushAnimator.cs
index 0979de16d0..5e97635c9a 100644
--- a/src/Avalonia.Visuals/Animation/Animators/GradientBrushAnimator.cs
+++ b/src/Avalonia.Visuals/Animation/Animators/GradientBrushAnimator.cs
@@ -30,6 +30,7 @@ namespace Avalonia.Animation.Animators
return new ImmutableRadialGradientBrush(
InterpolateStops(progress, oldValue.GradientStops, newValue.GradientStops),
s_doubleAnimator.Interpolate(progress, oldValue.Opacity, newValue.Opacity),
+ oldValue.Transform is { } ? new ImmutableTransform(oldValue.Transform.Value) : null,
oldValue.SpreadMethod,
s_relativePointAnimator.Interpolate(progress, oldRadial.Center, newRadial.Center),
s_relativePointAnimator.Interpolate(progress, oldRadial.GradientOrigin, newRadial.GradientOrigin),
@@ -39,6 +40,7 @@ namespace Avalonia.Animation.Animators
return new ImmutableConicGradientBrush(
InterpolateStops(progress, oldValue.GradientStops, newValue.GradientStops),
s_doubleAnimator.Interpolate(progress, oldValue.Opacity, newValue.Opacity),
+ oldValue.Transform is { } ? new ImmutableTransform(oldValue.Transform.Value) : null,
oldValue.SpreadMethod,
s_relativePointAnimator.Interpolate(progress, oldConic.Center, newConic.Center),
s_doubleAnimator.Interpolate(progress, oldConic.Angle, newConic.Angle));
@@ -47,6 +49,7 @@ namespace Avalonia.Animation.Animators
return new ImmutableLinearGradientBrush(
InterpolateStops(progress, oldValue.GradientStops, newValue.GradientStops),
s_doubleAnimator.Interpolate(progress, oldValue.Opacity, newValue.Opacity),
+ oldValue.Transform is { } ? new ImmutableTransform(oldValue.Transform.Value) : null,
oldValue.SpreadMethod,
s_relativePointAnimator.Interpolate(progress, oldLinear.StartPoint, newLinear.StartPoint),
s_relativePointAnimator.Interpolate(progress, oldLinear.EndPoint, newLinear.EndPoint));
@@ -98,16 +101,19 @@ namespace Avalonia.Animation.Animators
case IRadialGradientBrush oldRadial:
return new ImmutableRadialGradientBrush(
CreateStopsFromSolidColorBrush(solidColorBrush, oldRadial.GradientStops), solidColorBrush.Opacity,
+ oldRadial.Transform is { } ? new ImmutableTransform(oldRadial.Transform.Value) : null,
oldRadial.SpreadMethod, oldRadial.Center, oldRadial.GradientOrigin, oldRadial.Radius);
case IConicGradientBrush oldConic:
return new ImmutableConicGradientBrush(
CreateStopsFromSolidColorBrush(solidColorBrush, oldConic.GradientStops), solidColorBrush.Opacity,
+ oldConic.Transform is { } ? new ImmutableTransform(oldConic.Transform.Value) : null,
oldConic.SpreadMethod, oldConic.Center, oldConic.Angle);
case ILinearGradientBrush oldLinear:
return new ImmutableLinearGradientBrush(
CreateStopsFromSolidColorBrush(solidColorBrush, oldLinear.GradientStops), solidColorBrush.Opacity,
+ oldLinear.Transform is { } ? new ImmutableTransform(oldLinear.Transform.Value) : null,
oldLinear.SpreadMethod, oldLinear.StartPoint, oldLinear.EndPoint);
default:
diff --git a/src/Avalonia.Visuals/ApiCompatBaseline.txt b/src/Avalonia.Visuals/ApiCompatBaseline.txt
index e93a2b5632..c02b9d8639 100644
--- a/src/Avalonia.Visuals/ApiCompatBaseline.txt
+++ b/src/Avalonia.Visuals/ApiCompatBaseline.txt
@@ -41,6 +41,8 @@ MembersMustExist : Member 'public void Avalonia.Media.GlyphRun.GlyphIndices.set(
MembersMustExist : Member 'public Avalonia.Utilities.ReadOnlySlice Avalonia.Media.GlyphRun.GlyphOffsets.get()' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Media.GlyphRun.GlyphOffsets.set(Avalonia.Utilities.ReadOnlySlice)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Media.GlyphRun.GlyphTypeface.set(Avalonia.Media.GlyphTypeface)' does not exist in the implementation but it does exist in the contract.
+InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Media.ITransform Avalonia.Media.IBrush.Transform' is present in the implementation but not in the contract.
+InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Media.ITransform Avalonia.Media.IBrush.Transform.get()' is present in the implementation but not in the contract.
CannotSealType : Type 'Avalonia.Media.Pen' is actually (has the sealed modifier) sealed in the implementation but not sealed in the contract.
MembersMustExist : Member 'protected void Avalonia.Media.Pen.AffectsRender(Avalonia.AvaloniaProperty[])' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'protected void Avalonia.Media.Pen.RaiseInvalidated(System.EventArgs)' does not exist in the implementation but it does exist in the contract.
@@ -52,7 +54,13 @@ MembersMustExist : Member 'public void Avalonia.Media.TextHitTestResult.IsTraili
MembersMustExist : Member 'public void Avalonia.Media.TextHitTestResult.TextPosition.set(System.Int32)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Media.Typeface..ctor(Avalonia.Media.FontFamily, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Media.Typeface..ctor(System.String, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight)' does not exist in the implementation but it does exist in the contract.
+MembersMustExist : Member 'public void Avalonia.Media.Immutable.ImmutableConicGradientBrush..ctor(System.Collections.Generic.IReadOnlyList, System.Double, Avalonia.Media.GradientSpreadMethod, System.Nullable, System.Double)' does not exist in the implementation but it does exist in the contract.
+MembersMustExist : Member 'protected void Avalonia.Media.Immutable.ImmutableGradientBrush..ctor(System.Collections.Generic.IReadOnlyList, System.Double, Avalonia.Media.GradientSpreadMethod)' does not exist in the implementation but it does exist in the contract.
+MembersMustExist : Member 'public void Avalonia.Media.Immutable.ImmutableLinearGradientBrush..ctor(System.Collections.Generic.IReadOnlyList, System.Double, Avalonia.Media.GradientSpreadMethod, System.Nullable, System.Nullable)' does not exist in the implementation but it does exist in the contract.
+MembersMustExist : Member 'public void Avalonia.Media.Immutable.ImmutableRadialGradientBrush..ctor(System.Collections.Generic.IReadOnlyList, System.Double, Avalonia.Media.GradientSpreadMethod, System.Nullable, System.Nullable, System.Double)' does not exist in the implementation but it does exist in the contract.
TypeCannotChangeClassification : Type 'Avalonia.Media.Immutable.ImmutableSolidColorBrush' is a 'class' in the implementation but is a 'struct' in the contract.
+MembersMustExist : Member 'public void Avalonia.Media.Immutable.ImmutableSolidColorBrush..ctor(Avalonia.Media.Color, System.Double)' does not exist in the implementation but it does exist in the contract.
+MembersMustExist : Member 'protected void Avalonia.Media.Immutable.ImmutableTileBrush..ctor(Avalonia.Media.AlignmentX, Avalonia.Media.AlignmentY, Avalonia.RelativeRect, System.Double, Avalonia.RelativeRect, Avalonia.Media.Stretch, Avalonia.Media.TileMode, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.DrawableTextRun.Draw(Avalonia.Media.DrawingContext)' does not exist in the implementation but it does exist in the contract.
CannotAddAbstractMembers : Member 'public void Avalonia.Media.TextFormatting.DrawableTextRun.Draw(Avalonia.Media.DrawingContext, Avalonia.Point)' is abstract in the implementation but is missing in the contract.
CannotSealType : Type 'Avalonia.Media.TextFormatting.GenericTextParagraphProperties' is actually (has the sealed modifier) sealed in the implementation but not sealed in the contract.
@@ -153,4 +161,4 @@ InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Media.GlyphR
MembersMustExist : Member 'public Avalonia.Media.GlyphRun Avalonia.Platform.ITextShaperImpl.ShapeText(Avalonia.Utilities.ReadOnlySlice, Avalonia.Media.Typeface, System.Double, System.Globalization.CultureInfo)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'protected void Avalonia.Rendering.RendererBase.RenderFps(Avalonia.Platform.IDrawingContextImpl, Avalonia.Rect, System.Nullable)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Utilities.ReadOnlySlice..ctor(System.ReadOnlyMemory, System.Int32, System.Int32)' does not exist in the implementation but it does exist in the contract.
-Total Issues: 154
+Total Issues: 162
diff --git a/src/Avalonia.Visuals/Media/Brush.cs b/src/Avalonia.Visuals/Media/Brush.cs
index 11fbe9393a..9d989979a7 100644
--- a/src/Avalonia.Visuals/Media/Brush.cs
+++ b/src/Avalonia.Visuals/Media/Brush.cs
@@ -18,13 +18,19 @@ namespace Avalonia.Media
public static readonly StyledProperty OpacityProperty =
AvaloniaProperty.Register(nameof(Opacity), 1.0);
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty TransformProperty =
+ AvaloniaProperty.Register(nameof(Transform));
+
///
public event EventHandler? Invalidated;
static Brush()
{
Animation.Animation.RegisterAnimator(prop => typeof(IBrush).IsAssignableFrom(prop.PropertyType));
- AffectsRender(OpacityProperty);
+ AffectsRender(OpacityProperty, TransformProperty);
}
///
@@ -36,6 +42,15 @@ namespace Avalonia.Media
set { SetValue(OpacityProperty, value); }
}
+ ///
+ /// Gets or sets the transform of the brush.
+ ///
+ public ITransform? Transform
+ {
+ get { return GetValue(TransformProperty); }
+ set { SetValue(TransformProperty, value); }
+ }
+
///
/// Parses a brush string.
///
diff --git a/src/Avalonia.Visuals/Media/IBrush.cs b/src/Avalonia.Visuals/Media/IBrush.cs
index 15b7681be4..830c066182 100644
--- a/src/Avalonia.Visuals/Media/IBrush.cs
+++ b/src/Avalonia.Visuals/Media/IBrush.cs
@@ -12,5 +12,10 @@ namespace Avalonia.Media
/// Gets the opacity of the brush.
///
double Opacity { get; }
+
+ ///
+ /// Gets the transform of the brush.
+ ///
+ ITransform? Transform { get; }
}
}
diff --git a/src/Avalonia.Visuals/Media/Immutable/ImmutableConicGradientBrush.cs b/src/Avalonia.Visuals/Media/Immutable/ImmutableConicGradientBrush.cs
index d3c80dfcad..4b97615c4c 100644
--- a/src/Avalonia.Visuals/Media/Immutable/ImmutableConicGradientBrush.cs
+++ b/src/Avalonia.Visuals/Media/Immutable/ImmutableConicGradientBrush.cs
@@ -12,16 +12,18 @@ namespace Avalonia.Media.Immutable
///
/// The gradient stops.
/// The opacity of the brush.
+ /// The transform of the brush.
/// The spread method.
/// The center point for the gradient.
/// The starting angle for the gradient.
public ImmutableConicGradientBrush(
IReadOnlyList gradientStops,
double opacity = 1,
+ ImmutableTransform? transform = null,
GradientSpreadMethod spreadMethod = GradientSpreadMethod.Pad,
RelativePoint? center = null,
double angle = 0)
- : base(gradientStops, opacity, spreadMethod)
+ : base(gradientStops, opacity, transform, spreadMethod)
{
Center = center ?? RelativePoint.Center;
Angle = angle;
diff --git a/src/Avalonia.Visuals/Media/Immutable/ImmutableGradientBrush.cs b/src/Avalonia.Visuals/Media/Immutable/ImmutableGradientBrush.cs
index 1f6e3bbcfd..f1e51687d0 100644
--- a/src/Avalonia.Visuals/Media/Immutable/ImmutableGradientBrush.cs
+++ b/src/Avalonia.Visuals/Media/Immutable/ImmutableGradientBrush.cs
@@ -12,14 +12,17 @@ namespace Avalonia.Media.Immutable
///
/// The gradient stops.
/// The opacity of the brush.
+ /// The transform of the brush.
/// The spread method.
protected ImmutableGradientBrush(
IReadOnlyList gradientStops,
double opacity,
+ ImmutableTransform? transform,
GradientSpreadMethod spreadMethod)
{
GradientStops = gradientStops;
Opacity = opacity;
+ Transform = transform;
SpreadMethod = spreadMethod;
}
@@ -28,7 +31,7 @@ namespace Avalonia.Media.Immutable
///
/// The brush from which this brush's properties should be copied.
protected ImmutableGradientBrush(GradientBrush source)
- : this(source.GradientStops.ToImmutable(), source.Opacity, source.SpreadMethod)
+ : this(source.GradientStops.ToImmutable(), source.Opacity, source.Transform?.ToImmutable(), source.SpreadMethod)
{
}
@@ -39,6 +42,11 @@ namespace Avalonia.Media.Immutable
///
public double Opacity { get; }
+ ///
+ /// Gets the transform of the brush.
+ ///
+ public ITransform? Transform { get; }
+
///
public GradientSpreadMethod SpreadMethod { get; }
}
diff --git a/src/Avalonia.Visuals/Media/Immutable/ImmutableImageBrush.cs b/src/Avalonia.Visuals/Media/Immutable/ImmutableImageBrush.cs
index 15da8f8b43..2d4fe45c8e 100644
--- a/src/Avalonia.Visuals/Media/Immutable/ImmutableImageBrush.cs
+++ b/src/Avalonia.Visuals/Media/Immutable/ImmutableImageBrush.cs
@@ -16,6 +16,7 @@ namespace Avalonia.Media.Immutable
/// The vertical alignment of a tile in the destination.
/// The rectangle on the destination in which to paint a tile.
/// The opacity of the brush.
+ /// The transform of the brush.
/// The rectangle of the source image that will be displayed.
///
/// How the source rectangle will be stretched to fill the destination rect.
@@ -28,6 +29,7 @@ namespace Avalonia.Media.Immutable
AlignmentY alignmentY = AlignmentY.Center,
RelativeRect? destinationRect = null,
double opacity = 1,
+ ImmutableTransform? transform = null,
RelativeRect? sourceRect = null,
Stretch stretch = Stretch.Uniform,
TileMode tileMode = TileMode.None,
@@ -37,6 +39,7 @@ namespace Avalonia.Media.Immutable
alignmentY,
destinationRect ?? RelativeRect.Fill,
opacity,
+ transform,
sourceRect ?? RelativeRect.Fill,
stretch,
tileMode,
diff --git a/src/Avalonia.Visuals/Media/Immutable/ImmutableLinearGradientBrush.cs b/src/Avalonia.Visuals/Media/Immutable/ImmutableLinearGradientBrush.cs
index 912d77d763..64c0f9b44e 100644
--- a/src/Avalonia.Visuals/Media/Immutable/ImmutableLinearGradientBrush.cs
+++ b/src/Avalonia.Visuals/Media/Immutable/ImmutableLinearGradientBrush.cs
@@ -12,16 +12,18 @@ namespace Avalonia.Media.Immutable
///
/// The gradient stops.
/// The opacity of the brush.
+ /// The transform of the brush.
/// The spread method.
/// The start point for the gradient.
/// The end point for the gradient.
public ImmutableLinearGradientBrush(
IReadOnlyList gradientStops,
double opacity = 1,
+ ImmutableTransform? transform = null,
GradientSpreadMethod spreadMethod = GradientSpreadMethod.Pad,
RelativePoint? startPoint = null,
RelativePoint? endPoint = null)
- : base(gradientStops, opacity, spreadMethod)
+ : base(gradientStops, opacity, transform, spreadMethod)
{
StartPoint = startPoint ?? RelativePoint.TopLeft;
EndPoint = endPoint ?? RelativePoint.BottomRight;
diff --git a/src/Avalonia.Visuals/Media/Immutable/ImmutableRadialGradientBrush.cs b/src/Avalonia.Visuals/Media/Immutable/ImmutableRadialGradientBrush.cs
index e26fbab5f5..3da4bdd8e9 100644
--- a/src/Avalonia.Visuals/Media/Immutable/ImmutableRadialGradientBrush.cs
+++ b/src/Avalonia.Visuals/Media/Immutable/ImmutableRadialGradientBrush.cs
@@ -12,6 +12,7 @@ namespace Avalonia.Media.Immutable
///
/// The gradient stops.
/// The opacity of the brush.
+ /// The transform of the brush.
/// The spread method.
/// The start point for the gradient.
///
@@ -23,11 +24,12 @@ namespace Avalonia.Media.Immutable
public ImmutableRadialGradientBrush(
IReadOnlyList gradientStops,
double opacity = 1,
+ ImmutableTransform? transform = null,
GradientSpreadMethod spreadMethod = GradientSpreadMethod.Pad,
RelativePoint? center = null,
RelativePoint? gradientOrigin = null,
double radius = 0.5)
- : base(gradientStops, opacity, spreadMethod)
+ : base(gradientStops, opacity, transform, spreadMethod)
{
Center = center ?? RelativePoint.Center;
GradientOrigin = gradientOrigin ?? RelativePoint.Center;
diff --git a/src/Avalonia.Visuals/Media/Immutable/ImmutableSolidColorBrush.cs b/src/Avalonia.Visuals/Media/Immutable/ImmutableSolidColorBrush.cs
index 46e1df4054..9b1b2500ef 100644
--- a/src/Avalonia.Visuals/Media/Immutable/ImmutableSolidColorBrush.cs
+++ b/src/Avalonia.Visuals/Media/Immutable/ImmutableSolidColorBrush.cs
@@ -12,10 +12,12 @@ namespace Avalonia.Media.Immutable
///
/// The color to use.
/// The opacity of the brush.
- public ImmutableSolidColorBrush(Color color, double opacity = 1)
+ /// The transform of the brush.
+ public ImmutableSolidColorBrush(Color color, double opacity = 1, ImmutableTransform? transform = null)
{
Color = color;
Opacity = opacity;
+ Transform = null;
}
///
@@ -32,7 +34,7 @@ namespace Avalonia.Media.Immutable
///
/// The brush from which this brush's properties should be copied.
public ImmutableSolidColorBrush(ISolidColorBrush source)
- : this(source.Color, source.Opacity)
+ : this(source.Color, source.Opacity, source.Transform?.ToImmutable())
{
}
@@ -46,11 +48,16 @@ namespace Avalonia.Media.Immutable
///
public double Opacity { get; }
+ ///
+ /// Gets the transform of the brush.
+ ///
+ public ITransform? Transform { get; }
+
public bool Equals(ImmutableSolidColorBrush? other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
- return Color.Equals(other.Color) && Opacity.Equals(other.Opacity);
+ return Color.Equals(other.Color) && Opacity.Equals(other.Opacity) && (Transform == null && other.Transform == null ? true : (Transform != null && Transform.Equals(other.Transform)));
}
public override bool Equals(object? obj)
@@ -62,7 +69,7 @@ namespace Avalonia.Media.Immutable
{
unchecked
{
- return (Color.GetHashCode() * 397) ^ Opacity.GetHashCode();
+ return (Color.GetHashCode() * 397) ^ Opacity.GetHashCode() ^ (Transform is null ? 0 : Transform.GetHashCode());
}
}
diff --git a/src/Avalonia.Visuals/Media/Immutable/ImmutableTileBrush.cs b/src/Avalonia.Visuals/Media/Immutable/ImmutableTileBrush.cs
index fd4d921516..1019751733 100644
--- a/src/Avalonia.Visuals/Media/Immutable/ImmutableTileBrush.cs
+++ b/src/Avalonia.Visuals/Media/Immutable/ImmutableTileBrush.cs
@@ -14,6 +14,7 @@ namespace Avalonia.Media.Immutable
/// The vertical alignment of a tile in the destination.
/// The rectangle on the destination in which to paint a tile.
/// The opacity of the brush.
+ /// The transform of the brush.
/// The rectangle of the source image that will be displayed.
///
/// How the source rectangle will be stretched to fill the destination rect.
@@ -25,6 +26,7 @@ namespace Avalonia.Media.Immutable
AlignmentY alignmentY,
RelativeRect destinationRect,
double opacity,
+ ImmutableTransform? transform,
RelativeRect sourceRect,
Stretch stretch,
TileMode tileMode,
@@ -34,6 +36,7 @@ namespace Avalonia.Media.Immutable
AlignmentY = alignmentY;
DestinationRect = destinationRect;
Opacity = opacity;
+ Transform = transform;
SourceRect = sourceRect;
Stretch = stretch;
TileMode = tileMode;
@@ -50,6 +53,7 @@ namespace Avalonia.Media.Immutable
source.AlignmentY,
source.DestinationRect,
source.Opacity,
+ source.Transform?.ToImmutable(),
source.SourceRect,
source.Stretch,
source.TileMode,
@@ -69,6 +73,11 @@ namespace Avalonia.Media.Immutable
///
public double Opacity { get; }
+ ///
+ /// Gets the transform of the brush.
+ ///
+ public ITransform? Transform { get; }
+
///
public RelativeRect SourceRect { get; }
diff --git a/src/Avalonia.Visuals/Media/Immutable/ImmutableTransform.cs b/src/Avalonia.Visuals/Media/Immutable/ImmutableTransform.cs
new file mode 100644
index 0000000000..d5ff2b8317
--- /dev/null
+++ b/src/Avalonia.Visuals/Media/Immutable/ImmutableTransform.cs
@@ -0,0 +1,21 @@
+using Avalonia.VisualTree;
+
+namespace Avalonia.Media.Immutable
+{
+ ///
+ /// Represents a transform on an .
+ ///
+ public class ImmutableTransform : ITransform
+ {
+ public Matrix Value { get; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The transform matrix.
+ public ImmutableTransform(Matrix matrix)
+ {
+ Value = matrix;
+ }
+ }
+}
diff --git a/src/Avalonia.Visuals/Media/Immutable/ImmutableVisualBrush.cs b/src/Avalonia.Visuals/Media/Immutable/ImmutableVisualBrush.cs
index 4c6aae08ab..0fd2905660 100644
--- a/src/Avalonia.Visuals/Media/Immutable/ImmutableVisualBrush.cs
+++ b/src/Avalonia.Visuals/Media/Immutable/ImmutableVisualBrush.cs
@@ -16,6 +16,7 @@ namespace Avalonia.Media.Immutable
/// The vertical alignment of a tile in the destination.
/// The rectangle on the destination in which to paint a tile.
/// The opacity of the brush.
+ /// The transform of the brush.
/// The rectangle of the source image that will be displayed.
///
/// How the source rectangle will be stretched to fill the destination rect.
@@ -28,6 +29,7 @@ namespace Avalonia.Media.Immutable
AlignmentY alignmentY = AlignmentY.Center,
RelativeRect? destinationRect = null,
double opacity = 1,
+ ImmutableTransform? transform = null,
RelativeRect? sourceRect = null,
Stretch stretch = Stretch.Uniform,
TileMode tileMode = TileMode.None,
@@ -37,6 +39,7 @@ namespace Avalonia.Media.Immutable
alignmentY,
destinationRect ?? RelativeRect.Fill,
opacity,
+ transform,
sourceRect ?? RelativeRect.Fill,
stretch,
tileMode,
diff --git a/src/Avalonia.Visuals/Media/Transform.cs b/src/Avalonia.Visuals/Media/Transform.cs
index 60701ecfaf..023a8b9cdd 100644
--- a/src/Avalonia.Visuals/Media/Transform.cs
+++ b/src/Avalonia.Visuals/Media/Transform.cs
@@ -1,6 +1,7 @@
using System;
using Avalonia.Animation;
using Avalonia.Animation.Animators;
+using Avalonia.Media.Immutable;
using Avalonia.VisualTree;
namespace Avalonia.Media
@@ -44,6 +45,15 @@ namespace Avalonia.Media
Changed?.Invoke(this, EventArgs.Empty);
}
+ ///
+ /// Converts a transform to an immutable transform.
+ ///
+ /// The immutable transform
+ public ImmutableTransform ToImmutable()
+ {
+ return new ImmutableTransform(this.Value);
+ }
+
///
/// Returns a String representing this transform matrix instance.
///
diff --git a/src/Avalonia.Visuals/Media/TransformExtensions.cs b/src/Avalonia.Visuals/Media/TransformExtensions.cs
new file mode 100644
index 0000000000..ccf2231ce2
--- /dev/null
+++ b/src/Avalonia.Visuals/Media/TransformExtensions.cs
@@ -0,0 +1,26 @@
+using System;
+using Avalonia.Media.Immutable;
+
+namespace Avalonia.Media
+{
+ ///
+ /// Extension methods for transform classes.
+ ///
+ public static class TransformExtensions
+ {
+ ///
+ /// Converts a transform to an immutable transform.
+ ///
+ /// The transform.
+ ///
+ /// The result of calling if the transform is mutable,
+ /// otherwise .
+ ///
+ public static ImmutableTransform ToImmutable(this ITransform transform)
+ {
+ _ = transform ?? throw new ArgumentNullException(nameof(transform));
+
+ return (transform as Transform)?.ToImmutable() ?? new ImmutableTransform(transform.Value);
+ }
+ }
+}
diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs
index e695a9cb41..642808eacb 100644
--- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs
+++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs
@@ -614,10 +614,21 @@ namespace Avalonia.Skia
var end = linearGradient.EndPoint.ToPixels(targetSize).ToSKPoint();
// would be nice to cache these shaders possibly?
- using (var shader =
- SKShader.CreateLinearGradient(start, end, stopColors, stopOffsets, tileMode))
+ if (linearGradient.Transform is null)
{
- paintWrapper.Paint.Shader = shader;
+ using (var shader =
+ SKShader.CreateLinearGradient(start, end, stopColors, stopOffsets, tileMode))
+ {
+ paintWrapper.Paint.Shader = shader;
+ }
+ }
+ else
+ {
+ using (var shader =
+ SKShader.CreateLinearGradient(start, end, stopColors, stopOffsets, tileMode, linearGradient.Transform.Value.ToSKMatrix()))
+ {
+ paintWrapper.Paint.Shader = shader;
+ }
}
break;
@@ -632,10 +643,21 @@ namespace Avalonia.Skia
if (origin.Equals(center))
{
// when the origin is the same as the center the Skia RadialGradient acts the same as D2D
- using (var shader =
- SKShader.CreateRadialGradient(center, radius, stopColors, stopOffsets, tileMode))
+ if (radialGradient.Transform is null)
{
- paintWrapper.Paint.Shader = shader;
+ using (var shader =
+ SKShader.CreateRadialGradient(center, radius, stopColors, stopOffsets, tileMode))
+ {
+ paintWrapper.Paint.Shader = shader;
+ }
+ }
+ else
+ {
+ using (var shader =
+ SKShader.CreateRadialGradient(center, radius, stopColors, stopOffsets, tileMode, radialGradient.Transform.Value.ToSKMatrix()))
+ {
+ paintWrapper.Paint.Shader = shader;
+ }
}
}
else
@@ -659,12 +681,25 @@ namespace Avalonia.Skia
}
// compose with a background colour of the final stop to match D2D's behaviour of filling with the final color
- using (var shader = SKShader.CreateCompose(
- SKShader.CreateColor(reversedColors[0]),
- SKShader.CreateTwoPointConicalGradient(center, radius, origin, 0, reversedColors, reversedStops, tileMode)
- ))
+ if (radialGradient.Transform is null)
{
- paintWrapper.Paint.Shader = shader;
+ using (var shader = SKShader.CreateCompose(
+ SKShader.CreateColor(reversedColors[0]),
+ SKShader.CreateTwoPointConicalGradient(center, radius, origin, 0, reversedColors, reversedStops, tileMode)
+ ))
+ {
+ paintWrapper.Paint.Shader = shader;
+ }
+ }
+ else
+ {
+ using (var shader = SKShader.CreateCompose(
+ SKShader.CreateColor(reversedColors[0]),
+ SKShader.CreateTwoPointConicalGradient(center, radius, origin, 0, reversedColors, reversedStops, tileMode, radialGradient.Transform.Value.ToSKMatrix())
+ ))
+ {
+ paintWrapper.Paint.Shader = shader;
+ }
}
}
@@ -679,6 +714,11 @@ namespace Avalonia.Skia
var angle = (float)(conicGradient.Angle - 90);
var rotation = SKMatrix.CreateRotationDegrees(angle, center.X, center.Y);
+ if (conicGradient.Transform is { })
+ {
+ rotation = rotation.PreConcat(conicGradient.Transform.Value.ToSKMatrix());
+ }
+
using (var shader =
SKShader.CreateSweepGradient(center, stopColors, stopOffsets, rotation))
{
@@ -751,6 +791,11 @@ namespace Avalonia.Skia
tileTransform,
SKMatrix.CreateScale((float)(96.0 / _dpi.X), (float)(96.0 / _dpi.Y)));
+ if (tileBrush.Transform is { })
+ {
+ paintTransform = paintTransform.PreConcat(tileBrush.Transform.Value.ToSKMatrix());
+ }
+
using (var shader = image.ToShader(tileX, tileY, paintTransform))
{
paintWrapper.Paint.Shader = shader;