Browse Source

Merge pull request #6344 from wieslawsoltes/BrushTransformProperty

Add Transform property for Brush class
pull/7787/head
Max Katz 5 years ago
committed by GitHub
parent
commit
24be9eb325
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      samples/RenderDemo/MainWindow.xaml
  2. 71
      samples/RenderDemo/Pages/BrushesPage.axaml
  3. 18
      samples/RenderDemo/Pages/BrushesPage.axaml.cs
  4. 6
      src/Avalonia.Visuals/Animation/Animators/GradientBrushAnimator.cs
  5. 10
      src/Avalonia.Visuals/ApiCompatBaseline.txt
  6. 17
      src/Avalonia.Visuals/Media/Brush.cs
  7. 5
      src/Avalonia.Visuals/Media/IBrush.cs
  8. 4
      src/Avalonia.Visuals/Media/Immutable/ImmutableConicGradientBrush.cs
  9. 10
      src/Avalonia.Visuals/Media/Immutable/ImmutableGradientBrush.cs
  10. 3
      src/Avalonia.Visuals/Media/Immutable/ImmutableImageBrush.cs
  11. 4
      src/Avalonia.Visuals/Media/Immutable/ImmutableLinearGradientBrush.cs
  12. 4
      src/Avalonia.Visuals/Media/Immutable/ImmutableRadialGradientBrush.cs
  13. 15
      src/Avalonia.Visuals/Media/Immutable/ImmutableSolidColorBrush.cs
  14. 9
      src/Avalonia.Visuals/Media/Immutable/ImmutableTileBrush.cs
  15. 21
      src/Avalonia.Visuals/Media/Immutable/ImmutableTransform.cs
  16. 3
      src/Avalonia.Visuals/Media/Immutable/ImmutableVisualBrush.cs
  17. 10
      src/Avalonia.Visuals/Media/Transform.cs
  18. 26
      src/Avalonia.Visuals/Media/TransformExtensions.cs
  19. 67
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs

3
samples/RenderDemo/MainWindow.xaml

@ -66,5 +66,8 @@
<TabItem Header="Path Measurement">
<pages:PathMeasurementPage />
</TabItem>
<TabItem Header="Brushes">
<pages:BrushesPage />
</TabItem>
</controls:HamburgerMenu>
</Window>

71
samples/RenderDemo/Pages/BrushesPage.axaml

@ -0,0 +1,71 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="RenderDemo.Pages.BrushesPage">
<Canvas Background="White" Width="480" Height="360">
<Rectangle Canvas.Left="20" Canvas.Top="20" Width="440" Height="50">
<Rectangle.Fill>
<LinearGradientBrush StartPoint="0,0" EndPoint="410,0">
<LinearGradientBrush.Transform>
<TransformGroup>
<ScaleTransform ScaleX="0.5" />
<SkewTransform />
<RotateTransform />
<TranslateTransform X="5" Y="15" />
</TransformGroup>
</LinearGradientBrush.Transform>
<LinearGradientBrush.GradientStops>
<GradientStop Color="Blue" Offset="0" />
<GradientStop Color="Green" Offset="0.5" />
<GradientStop Color="Lime" Offset="1" />
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
<TextBlock Canvas.Left="20" Canvas.Top="70" FontSize="30" Text="scale(0.5) on gradient" />
<Rectangle Canvas.Left="20" Canvas.Top="110" Width="440" Height="50">
<Rectangle.Fill>
<RadialGradientBrush Center="0.0,0.0" GradientOrigin="0.0,0.0" Radius="0.13636364">
<RadialGradientBrush.Transform>
<TransformGroup>
<ScaleTransform />
<SkewTransform AngleX="45" />
<RotateTransform />
<TranslateTransform X="240" Y="45" />
</TransformGroup>
</RadialGradientBrush.Transform>
<RadialGradientBrush.GradientStops>
<GradientStop Color="Black" Offset="0" />
<GradientStop Color="#FFFFA500" Offset="1" />
</RadialGradientBrush.GradientStops>
</RadialGradientBrush>
</Rectangle.Fill>
</Rectangle>
<TextBlock Canvas.Left="20" Canvas.Top="160" FontSize="30" Text="skewX(45) on gradient" />
<Rectangle Canvas.Left="20" Canvas.Top="210" Width="440" Height="50">
<Rectangle.Fill>
<VisualBrush TileMode="Tile" SourceRect="0,0,20,20" DestinationRect="0,0,20,20" Stretch="None">
<VisualBrush.Transform>
<TransformGroup>
<ScaleTransform ScaleX="2" ScaleY="2" />
<SkewTransform AngleX="45" />
<RotateTransform />
<TranslateTransform X="5" Y="5" />
</TransformGroup>
</VisualBrush.Transform>
<VisualBrush.Visual>
<Canvas Width="20" Height="20">
<Rectangle Canvas.Left="0" Canvas.Top="0" Width="10" Height="10" Fill="Maroon" />
<Rectangle Canvas.Left="10" Canvas.Top="0" Width="10" Height="10" Fill="Green" />
<Rectangle Canvas.Left="0" Canvas.Top="10" Width="10" Height="10" Fill="Blue" />
<Rectangle Canvas.Left="10" Canvas.Top="10" Width="10" Height="10" Fill="Yellow" />
</Canvas>
</VisualBrush.Visual>
</VisualBrush>
</Rectangle.Fill>
</Rectangle>
<TextBlock Canvas.Left="20" Canvas.Top="260" FontSize="30" Text="scale(2), skewX(45) on pattern" />
</Canvas>
</UserControl>

18
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);
}
}

6
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:

10
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.Vector> 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<Avalonia.Vector>)' 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<T>(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<Avalonia.Media.Immutable.ImmutableGradientStop>, System.Double, Avalonia.Media.GradientSpreadMethod, System.Nullable<Avalonia.RelativePoint>, 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<Avalonia.Media.Immutable.ImmutableGradientStop>, 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<Avalonia.Media.Immutable.ImmutableGradientStop>, System.Double, Avalonia.Media.GradientSpreadMethod, System.Nullable<Avalonia.RelativePoint>, System.Nullable<Avalonia.RelativePoint>)' 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<Avalonia.Media.Immutable.ImmutableGradientStop>, System.Double, Avalonia.Media.GradientSpreadMethod, System.Nullable<Avalonia.RelativePoint>, System.Nullable<Avalonia.RelativePoint>, 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<System.Char>, 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<System.Int32>)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Utilities.ReadOnlySlice<T>..ctor(System.ReadOnlyMemory<T>, System.Int32, System.Int32)' does not exist in the implementation but it does exist in the contract.
Total Issues: 154
Total Issues: 162

17
src/Avalonia.Visuals/Media/Brush.cs

@ -18,13 +18,19 @@ namespace Avalonia.Media
public static readonly StyledProperty<double> OpacityProperty =
AvaloniaProperty.Register<Brush, double>(nameof(Opacity), 1.0);
/// <summary>
/// Defines the <see cref="Transform"/> property.
/// </summary>
public static readonly StyledProperty<ITransform?> TransformProperty =
AvaloniaProperty.Register<Brush, ITransform?>(nameof(Transform));
/// <inheritdoc/>
public event EventHandler? Invalidated;
static Brush()
{
Animation.Animation.RegisterAnimator<BaseBrushAnimator>(prop => typeof(IBrush).IsAssignableFrom(prop.PropertyType));
AffectsRender<Brush>(OpacityProperty);
AffectsRender<Brush>(OpacityProperty, TransformProperty);
}
/// <summary>
@ -36,6 +42,15 @@ namespace Avalonia.Media
set { SetValue(OpacityProperty, value); }
}
/// <summary>
/// Gets or sets the transform of the brush.
/// </summary>
public ITransform? Transform
{
get { return GetValue(TransformProperty); }
set { SetValue(TransformProperty, value); }
}
/// <summary>
/// Parses a brush string.
/// </summary>

5
src/Avalonia.Visuals/Media/IBrush.cs

@ -12,5 +12,10 @@ namespace Avalonia.Media
/// Gets the opacity of the brush.
/// </summary>
double Opacity { get; }
/// <summary>
/// Gets the transform of the brush.
/// </summary>
ITransform? Transform { get; }
}
}

4
src/Avalonia.Visuals/Media/Immutable/ImmutableConicGradientBrush.cs

@ -12,16 +12,18 @@ namespace Avalonia.Media.Immutable
/// </summary>
/// <param name="gradientStops">The gradient stops.</param>
/// <param name="opacity">The opacity of the brush.</param>
/// <param name="transform">The transform of the brush.</param>
/// <param name="spreadMethod">The spread method.</param>
/// <param name="center">The center point for the gradient.</param>
/// <param name="angle">The starting angle for the gradient.</param>
public ImmutableConicGradientBrush(
IReadOnlyList<ImmutableGradientStop> 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;

10
src/Avalonia.Visuals/Media/Immutable/ImmutableGradientBrush.cs

@ -12,14 +12,17 @@ namespace Avalonia.Media.Immutable
/// </summary>
/// <param name="gradientStops">The gradient stops.</param>
/// <param name="opacity">The opacity of the brush.</param>
/// <param name="transform">The transform of the brush.</param>
/// <param name="spreadMethod">The spread method.</param>
protected ImmutableGradientBrush(
IReadOnlyList<ImmutableGradientStop> gradientStops,
double opacity,
ImmutableTransform? transform,
GradientSpreadMethod spreadMethod)
{
GradientStops = gradientStops;
Opacity = opacity;
Transform = transform;
SpreadMethod = spreadMethod;
}
@ -28,7 +31,7 @@ namespace Avalonia.Media.Immutable
/// </summary>
/// <param name="source">The brush from which this brush's properties should be copied.</param>
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
/// <inheritdoc/>
public double Opacity { get; }
/// <summary>
/// Gets the transform of the brush.
/// </summary>
public ITransform? Transform { get; }
/// <inheritdoc/>
public GradientSpreadMethod SpreadMethod { get; }
}

3
src/Avalonia.Visuals/Media/Immutable/ImmutableImageBrush.cs

@ -16,6 +16,7 @@ namespace Avalonia.Media.Immutable
/// <param name="alignmentY">The vertical alignment of a tile in the destination.</param>
/// <param name="destinationRect">The rectangle on the destination in which to paint a tile.</param>
/// <param name="opacity">The opacity of the brush.</param>
/// <param name="transform">The transform of the brush.</param>
/// <param name="sourceRect">The rectangle of the source image that will be displayed.</param>
/// <param name="stretch">
/// 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,

4
src/Avalonia.Visuals/Media/Immutable/ImmutableLinearGradientBrush.cs

@ -12,16 +12,18 @@ namespace Avalonia.Media.Immutable
/// </summary>
/// <param name="gradientStops">The gradient stops.</param>
/// <param name="opacity">The opacity of the brush.</param>
/// <param name="transform">The transform of the brush.</param>
/// <param name="spreadMethod">The spread method.</param>
/// <param name="startPoint">The start point for the gradient.</param>
/// <param name="endPoint">The end point for the gradient.</param>
public ImmutableLinearGradientBrush(
IReadOnlyList<ImmutableGradientStop> 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;

4
src/Avalonia.Visuals/Media/Immutable/ImmutableRadialGradientBrush.cs

@ -12,6 +12,7 @@ namespace Avalonia.Media.Immutable
/// </summary>
/// <param name="gradientStops">The gradient stops.</param>
/// <param name="opacity">The opacity of the brush.</param>
/// <param name="transform">The transform of the brush.</param>
/// <param name="spreadMethod">The spread method.</param>
/// <param name="center">The start point for the gradient.</param>
/// <param name="gradientOrigin">
@ -23,11 +24,12 @@ namespace Avalonia.Media.Immutable
public ImmutableRadialGradientBrush(
IReadOnlyList<ImmutableGradientStop> 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;

15
src/Avalonia.Visuals/Media/Immutable/ImmutableSolidColorBrush.cs

@ -12,10 +12,12 @@ namespace Avalonia.Media.Immutable
/// </summary>
/// <param name="color">The color to use.</param>
/// <param name="opacity">The opacity of the brush.</param>
public ImmutableSolidColorBrush(Color color, double opacity = 1)
/// <param name="transform">The transform of the brush.</param>
public ImmutableSolidColorBrush(Color color, double opacity = 1, ImmutableTransform? transform = null)
{
Color = color;
Opacity = opacity;
Transform = null;
}
/// <summary>
@ -32,7 +34,7 @@ namespace Avalonia.Media.Immutable
/// </summary>
/// <param name="source">The brush from which this brush's properties should be copied.</param>
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
/// </summary>
public double Opacity { get; }
/// <summary>
/// Gets the transform of the brush.
/// </summary>
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());
}
}

9
src/Avalonia.Visuals/Media/Immutable/ImmutableTileBrush.cs

@ -14,6 +14,7 @@ namespace Avalonia.Media.Immutable
/// <param name="alignmentY">The vertical alignment of a tile in the destination.</param>
/// <param name="destinationRect">The rectangle on the destination in which to paint a tile.</param>
/// <param name="opacity">The opacity of the brush.</param>
/// <param name="transform">The transform of the brush.</param>
/// <param name="sourceRect">The rectangle of the source image that will be displayed.</param>
/// <param name="stretch">
/// 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
/// <inheritdoc/>
public double Opacity { get; }
/// <summary>
/// Gets the transform of the brush.
/// </summary>
public ITransform? Transform { get; }
/// <inheritdoc/>
public RelativeRect SourceRect { get; }

21
src/Avalonia.Visuals/Media/Immutable/ImmutableTransform.cs

@ -0,0 +1,21 @@
using Avalonia.VisualTree;
namespace Avalonia.Media.Immutable
{
/// <summary>
/// Represents a transform on an <see cref="IVisual"/>.
/// </summary>
public class ImmutableTransform : ITransform
{
public Matrix Value { get; }
/// <summary>
/// Initializes a new instance of the <see cref="ImmutableTransform"/> class.
/// </summary>
/// <param name="matrix">The transform matrix.</param>
public ImmutableTransform(Matrix matrix)
{
Value = matrix;
}
}
}

3
src/Avalonia.Visuals/Media/Immutable/ImmutableVisualBrush.cs

@ -16,6 +16,7 @@ namespace Avalonia.Media.Immutable
/// <param name="alignmentY">The vertical alignment of a tile in the destination.</param>
/// <param name="destinationRect">The rectangle on the destination in which to paint a tile.</param>
/// <param name="opacity">The opacity of the brush.</param>
/// <param name="transform">The transform of the brush.</param>
/// <param name="sourceRect">The rectangle of the source image that will be displayed.</param>
/// <param name="stretch">
/// 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,

10
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);
}
/// <summary>
/// Converts a transform to an immutable transform.
/// </summary>
/// <returns>The immutable transform</returns>
public ImmutableTransform ToImmutable()
{
return new ImmutableTransform(this.Value);
}
/// <summary>
/// Returns a String representing this transform matrix instance.
/// </summary>

26
src/Avalonia.Visuals/Media/TransformExtensions.cs

@ -0,0 +1,26 @@
using System;
using Avalonia.Media.Immutable;
namespace Avalonia.Media
{
/// <summary>
/// Extension methods for transform classes.
/// </summary>
public static class TransformExtensions
{
/// <summary>
/// Converts a transform to an immutable transform.
/// </summary>
/// <param name="transform">The transform.</param>
/// <returns>
/// The result of calling <see cref="Transform.ToImmutable"/> if the transform is mutable,
/// otherwise <paramref name="transform"/>.
/// </returns>
public static ImmutableTransform ToImmutable(this ITransform transform)
{
_ = transform ?? throw new ArgumentNullException(nameof(transform));
return (transform as Transform)?.ToImmutable() ?? new ImmutableTransform(transform.Value);
}
}
}

67
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;

Loading…
Cancel
Save