diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index 6a9f72039a..b70e0bf77f 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -35,16 +35,16 @@ jobs:
vmImage: 'macOS-10.14'
steps:
- task: UseDotNet@2
- displayName: 'Use .NET Core SDK 3.1.x'
+ displayName: 'Use .NET Core SDK 3.1.101'
inputs:
packageType: sdk
- version: 3.1.x
+ version: 3.1.101
- task: UseDotNet@2
- displayName: 'Use .NET Core Runtime 3.1.x'
+ displayName: 'Use .NET Core Runtime 3.1.1'
inputs:
packageType: runtime
- version: 3.1.x
+ version: 3.1.1
- task: CmdLine@2
displayName: 'Install Mono 5.18'
diff --git a/samples/ControlCatalog/Pages/ImagePage.xaml b/samples/ControlCatalog/Pages/ImagePage.xaml
index 9b8f8af765..f61931ed72 100644
--- a/samples/ControlCatalog/Pages/ImagePage.xaml
+++ b/samples/ControlCatalog/Pages/ImagePage.xaml
@@ -7,7 +7,7 @@
Displays an image
-
+
Bitmap
@@ -22,6 +22,23 @@
+ Crop
+
+ None
+ Center
+ TopLeft
+ TopRight
+ BottomLeft
+ BottomRight
+
+
+
+
+
+
+
+
+
Drawing
None
diff --git a/samples/ControlCatalog/Pages/ImagePage.xaml.cs b/samples/ControlCatalog/Pages/ImagePage.xaml.cs
index bbe89d1dfd..d8f4d6d5a2 100644
--- a/samples/ControlCatalog/Pages/ImagePage.xaml.cs
+++ b/samples/ControlCatalog/Pages/ImagePage.xaml.cs
@@ -1,6 +1,10 @@
+using System;
+using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Media;
+using Avalonia.Media.Imaging;
+using Avalonia.Platform;
namespace ControlCatalog.Pages
{
@@ -8,12 +12,14 @@ namespace ControlCatalog.Pages
{
private readonly Image _bitmapImage;
private readonly Image _drawingImage;
+ private readonly Image _croppedImage;
public ImagePage()
{
InitializeComponent();
_bitmapImage = this.FindControl("bitmapImage");
_drawingImage = this.FindControl("drawingImage");
+ _croppedImage = this.FindControl("croppedImage");
}
private void InitializeComponent()
@@ -38,5 +44,32 @@ namespace ControlCatalog.Pages
_drawingImage.Stretch = (Stretch)comboxBox.SelectedIndex;
}
}
+
+ public void BitmapCropChanged(object sender, SelectionChangedEventArgs e)
+ {
+ if (_croppedImage != null)
+ {
+ var comboxBox = (ComboBox)sender;
+ var croppedBitmap = _croppedImage.Source as CroppedBitmap;
+ croppedBitmap.SourceRect = GetCropRect(comboxBox.SelectedIndex);
+ }
+ }
+
+ private PixelRect GetCropRect(int index)
+ {
+ var bitmapWidth = 640;
+ var bitmapHeight = 426;
+ var cropSize = new PixelSize(320, 240);
+ return index switch
+ {
+ 1 => new PixelRect(new PixelPoint((bitmapWidth - cropSize.Width) / 2, (bitmapHeight - cropSize.Width) / 2), cropSize),
+ 2 => new PixelRect(new PixelPoint(0, 0), cropSize),
+ 3 => new PixelRect(new PixelPoint(bitmapWidth - cropSize.Width, 0), cropSize),
+ 4 => new PixelRect(new PixelPoint(0, bitmapHeight - cropSize.Height), cropSize),
+ 5 => new PixelRect(new PixelPoint(bitmapWidth - cropSize.Width, bitmapHeight - cropSize.Height), cropSize),
+ _ => PixelRect.Empty
+ };
+
+ }
}
}
diff --git a/src/Avalonia.Styling/Styling/Activators/StyleClassActivator.cs b/src/Avalonia.Styling/Styling/Activators/StyleClassActivator.cs
index c884b6ac43..7906a29cb5 100644
--- a/src/Avalonia.Styling/Styling/Activators/StyleClassActivator.cs
+++ b/src/Avalonia.Styling/Styling/Activators/StyleClassActivator.cs
@@ -14,6 +14,7 @@ namespace Avalonia.Styling.Activators
{
private readonly IList _match;
private readonly IAvaloniaReadOnlyList _classes;
+ private NotifyCollectionChangedEventHandler? _classesChangedHandler;
public StyleClassActivator(IAvaloniaReadOnlyList classes, IList match)
{
@@ -21,6 +22,9 @@ namespace Avalonia.Styling.Activators
_match = match;
}
+ private NotifyCollectionChangedEventHandler ClassesChangedHandler =>
+ _classesChangedHandler ??= ClassesChanged;
+
public static bool AreClassesMatching(IReadOnlyList classes, IList toMatch)
{
int remainingMatches = toMatch.Count;
@@ -51,16 +55,15 @@ namespace Avalonia.Styling.Activators
return remainingMatches == 0;
}
-
protected override void Initialize()
{
PublishNext(IsMatching());
- _classes.CollectionChanged += ClassesChanged;
+ _classes.CollectionChanged += ClassesChangedHandler;
}
protected override void Deinitialize()
{
- _classes.CollectionChanged -= ClassesChanged;
+ _classes.CollectionChanged -= ClassesChangedHandler;
}
private void ClassesChanged(object sender, NotifyCollectionChangedEventArgs e)
diff --git a/src/Avalonia.Visuals/Media/Imaging/CroppedBitmap.cs b/src/Avalonia.Visuals/Media/Imaging/CroppedBitmap.cs
new file mode 100644
index 0000000000..70823e114c
--- /dev/null
+++ b/src/Avalonia.Visuals/Media/Imaging/CroppedBitmap.cs
@@ -0,0 +1,96 @@
+using System;
+using Avalonia.Visuals.Media.Imaging;
+
+namespace Avalonia.Media.Imaging
+{
+ ///
+ /// Crops a Bitmap.
+ ///
+ public class CroppedBitmap : AvaloniaObject, IImage, IAffectsRender, IDisposable
+ {
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty SourceProperty =
+ AvaloniaProperty.Register(nameof(Source));
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty SourceRectProperty =
+ AvaloniaProperty.Register(nameof(SourceRect));
+
+ public event EventHandler Invalidated;
+
+ static CroppedBitmap()
+ {
+ SourceRectProperty.Changed.AddClassHandler((x, e) => x.SourceRectChanged(e));
+ SourceProperty.Changed.AddClassHandler((x, e) => x.SourceChanged(e));
+ }
+
+ ///
+ /// Gets or sets the source for the bitmap.
+ ///
+ public IImage Source
+ {
+ get => GetValue(SourceProperty);
+ set => SetValue(SourceProperty, value);
+ }
+
+ ///
+ /// Gets or sets the rectangular area that the bitmap is cropped to.
+ ///
+ public PixelRect SourceRect
+ {
+ get => GetValue(SourceRectProperty);
+ set => SetValue(SourceRectProperty, value);
+ }
+
+ public CroppedBitmap()
+ {
+ Source = null;
+ SourceRect = default;
+ }
+
+ public CroppedBitmap(IImage source, PixelRect sourceRect)
+ {
+ Source = source;
+ SourceRect = sourceRect;
+ }
+
+ private void SourceChanged(AvaloniaPropertyChangedEventArgs e)
+ {
+ if (e.NewValue == null)
+ return;
+ if (!(e.NewValue is IBitmap))
+ throw new ArgumentException("Only IBitmap supported as source");
+ Invalidated?.Invoke(this, e);
+ }
+
+ private void SourceRectChanged(AvaloniaPropertyChangedEventArgs e) => Invalidated?.Invoke(this, e);
+
+ public virtual void Dispose()
+ {
+ (Source as IBitmap)?.Dispose();
+ }
+
+ public Size Size {
+ get
+ {
+ if (Source == null)
+ return Size.Empty;
+ if (SourceRect.IsEmpty)
+ return Source.Size;
+ return SourceRect.Size.ToSizeWithDpi((Source as IBitmap).Dpi);
+ }
+ }
+
+ public void Draw(DrawingContext context, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode)
+ {
+ if (Source == null)
+ return;
+ var topLeft = SourceRect.TopLeft.ToPointWithDpi((Source as IBitmap).Dpi);
+ Source.Draw(context, sourceRect.Translate(new Vector(topLeft.X, topLeft.Y)), destRect, bitmapInterpolationMode);
+ }
+ }
+}
diff --git a/src/Avalonia.Visuals/Properties/AssemblyInfo.cs b/src/Avalonia.Visuals/Properties/AssemblyInfo.cs
index 10fe74f9b9..29cc365251 100644
--- a/src/Avalonia.Visuals/Properties/AssemblyInfo.cs
+++ b/src/Avalonia.Visuals/Properties/AssemblyInfo.cs
@@ -8,6 +8,7 @@ using Avalonia.Metadata;
[assembly: InternalsVisibleTo("Avalonia.Visuals.UnitTests")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Animation")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Media")]
+[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Media.Imaging")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia")]
[assembly: InternalsVisibleTo("Avalonia.Direct2D1.RenderTests")]
diff --git a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
index b065079564..f11bd76a7b 100644
--- a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
+++ b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
@@ -355,15 +355,21 @@ namespace Avalonia.Rendering
node.BeginRender(context, isLayerRoot);
- foreach (var operation in node.DrawOperations)
+ var drawOperations = node.DrawOperations;
+ var drawOperationsCount = drawOperations.Count;
+ for (int i = 0; i < drawOperationsCount; i++)
{
+ var operation = drawOperations[i];
_currentDraw = operation;
operation.Item.Render(context);
_currentDraw = null;
}
- foreach (var child in node.Children)
+ var children = node.Children;
+ var childrenCount = children.Count;
+ for (int i = 0; i < childrenCount; i++)
{
+ var child = children[i];
Render(context, (VisualNode)child, layer, clipBounds);
}