From c34bfc56f8d3baa8b7f3e6d116e27edaa9a46a3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B0=D0=B4=D0=B8=D0=BC=20=D0=9C=D0=B5=D0=BB=D1=8C?= =?UTF-8?q?=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Tue, 18 Feb 2020 16:25:28 +0300 Subject: [PATCH 1/6] Add class CroppedBitmap --- samples/ControlCatalog/Pages/ImagePage.xaml | 15 +++++- .../ControlCatalog/Pages/ImagePage.xaml.cs | 41 ++++++++++++++++ .../Media/Imaging/CroppedBitmap.cs | 47 +++++++++++++++++++ 3 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 src/Avalonia.Visuals/Media/Imaging/CroppedBitmap.cs diff --git a/samples/ControlCatalog/Pages/ImagePage.xaml b/samples/ControlCatalog/Pages/ImagePage.xaml index 9b8f8af765..c20f76cedd 100644 --- a/samples/ControlCatalog/Pages/ImagePage.xaml +++ b/samples/ControlCatalog/Pages/ImagePage.xaml @@ -7,7 +7,7 @@ Displays an image - + Bitmap @@ -22,6 +22,19 @@ + 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..d637c88102 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,17 @@ namespace ControlCatalog.Pages { private readonly Image _bitmapImage; private readonly Image _drawingImage; + private readonly Image _croppedImage; + private readonly IBitmap _croppedBitmapSource; public ImagePage() { InitializeComponent(); _bitmapImage = this.FindControl("bitmapImage"); _drawingImage = this.FindControl("drawingImage"); + _croppedImage = this.FindControl("croppedImage"); + _croppedBitmapSource = LoadBitmap("avares://ControlCatalog/Assets/delicate-arch-896885_640.jpg"); + _croppedImage.Source = new CroppedBitmap(_croppedBitmapSource, default); } private void InitializeComponent() @@ -38,5 +47,37 @@ namespace ControlCatalog.Pages _drawingImage.Stretch = (Stretch)comboxBox.SelectedIndex; } } + + public void BitmapCropChanged(object sender, SelectionChangedEventArgs e) + { + if (_croppedImage != null) + { + var comboxBox = (ComboBox)sender; + _croppedImage.Source = new CroppedBitmap( _croppedBitmapSource, GetCropRect(comboxBox.SelectedIndex)); + } + } + + private PixelRect GetCropRect(int index) + { + var bitmapWidth = _croppedBitmapSource.PixelSize.Width; + var bitmapHeight = _croppedBitmapSource.PixelSize.Height; + var cropSize = new PixelSize(bitmapWidth / 2, bitmapHeight / 2); + 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 + }; + + } + + private IBitmap LoadBitmap(string uri) + { + var assets = AvaloniaLocator.Current.GetService(); + return new Bitmap(assets.Open(new Uri(uri))); + } } } diff --git a/src/Avalonia.Visuals/Media/Imaging/CroppedBitmap.cs b/src/Avalonia.Visuals/Media/Imaging/CroppedBitmap.cs new file mode 100644 index 0000000000..6bdee24c03 --- /dev/null +++ b/src/Avalonia.Visuals/Media/Imaging/CroppedBitmap.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Avalonia.Visuals.Media.Imaging; + +namespace Avalonia.Media.Imaging +{ + public class CroppedBitmap : IImage, IDisposable + { + public CroppedBitmap() + { + Source = null; + SourceRect = default; + } + public CroppedBitmap(IBitmap source, PixelRect sourceRect) + { + Source = source; + SourceRect = sourceRect; + } + public virtual void Dispose() + { + Source?.Dispose(); + } + + public Size Size { + get + { + if (Source == null) + return Size.Empty; + if (SourceRect.IsEmpty) + return Source.Size; + return SourceRect.Size.ToSizeWithDpi(Source.Dpi); + } + } + + public void Draw(DrawingContext context, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode) + { + if (Source == null) + return; + var topLeft = SourceRect.TopLeft.ToPointWithDpi(Source.Dpi); + Source.Draw(context, sourceRect.Translate(new Vector(topLeft.X, topLeft.Y)), destRect, bitmapInterpolationMode); + } + + public IBitmap Source { get; } + public PixelRect SourceRect { get; } + } +} From 3aab2d5619a09835e48d3abe8106f8f543743cd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B0=D0=B4=D0=B8=D0=BC=20=D0=9C=D0=B5=D0=BB=D1=8C?= =?UTF-8?q?=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= Date: Mon, 2 Mar 2020 10:40:41 +0300 Subject: [PATCH 2/6] Added functionality to use CroppedBitmap directly in XAML --- samples/ControlCatalog/Pages/ImagePage.xaml | 8 ++- .../ControlCatalog/Pages/ImagePage.xaml.cs | 18 ++--- .../Media/Imaging/CroppedBitmap.cs | 69 ++++++++++++++++--- .../Properties/AssemblyInfo.cs | 3 +- 4 files changed, 72 insertions(+), 26 deletions(-) diff --git a/samples/ControlCatalog/Pages/ImagePage.xaml b/samples/ControlCatalog/Pages/ImagePage.xaml index c20f76cedd..f61931ed72 100644 --- a/samples/ControlCatalog/Pages/ImagePage.xaml +++ b/samples/ControlCatalog/Pages/ImagePage.xaml @@ -23,7 +23,7 @@ Crop - + None Center TopLeft @@ -31,7 +31,11 @@ BottomLeft BottomRight - + + + + + diff --git a/samples/ControlCatalog/Pages/ImagePage.xaml.cs b/samples/ControlCatalog/Pages/ImagePage.xaml.cs index d637c88102..d8f4d6d5a2 100644 --- a/samples/ControlCatalog/Pages/ImagePage.xaml.cs +++ b/samples/ControlCatalog/Pages/ImagePage.xaml.cs @@ -13,7 +13,6 @@ namespace ControlCatalog.Pages private readonly Image _bitmapImage; private readonly Image _drawingImage; private readonly Image _croppedImage; - private readonly IBitmap _croppedBitmapSource; public ImagePage() { @@ -21,8 +20,6 @@ namespace ControlCatalog.Pages _bitmapImage = this.FindControl("bitmapImage"); _drawingImage = this.FindControl("drawingImage"); _croppedImage = this.FindControl("croppedImage"); - _croppedBitmapSource = LoadBitmap("avares://ControlCatalog/Assets/delicate-arch-896885_640.jpg"); - _croppedImage.Source = new CroppedBitmap(_croppedBitmapSource, default); } private void InitializeComponent() @@ -53,15 +50,16 @@ namespace ControlCatalog.Pages if (_croppedImage != null) { var comboxBox = (ComboBox)sender; - _croppedImage.Source = new CroppedBitmap( _croppedBitmapSource, GetCropRect(comboxBox.SelectedIndex)); + var croppedBitmap = _croppedImage.Source as CroppedBitmap; + croppedBitmap.SourceRect = GetCropRect(comboxBox.SelectedIndex); } } private PixelRect GetCropRect(int index) { - var bitmapWidth = _croppedBitmapSource.PixelSize.Width; - var bitmapHeight = _croppedBitmapSource.PixelSize.Height; - var cropSize = new PixelSize(bitmapWidth / 2, bitmapHeight / 2); + 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), @@ -73,11 +71,5 @@ namespace ControlCatalog.Pages }; } - - private IBitmap LoadBitmap(string uri) - { - var assets = AvaloniaLocator.Current.GetService(); - return new Bitmap(assets.Open(new Uri(uri))); - } } } diff --git a/src/Avalonia.Visuals/Media/Imaging/CroppedBitmap.cs b/src/Avalonia.Visuals/Media/Imaging/CroppedBitmap.cs index 6bdee24c03..70823e114c 100644 --- a/src/Avalonia.Visuals/Media/Imaging/CroppedBitmap.cs +++ b/src/Avalonia.Visuals/Media/Imaging/CroppedBitmap.cs @@ -1,25 +1,77 @@ using System; -using System.Collections.Generic; -using System.Text; using Avalonia.Visuals.Media.Imaging; namespace Avalonia.Media.Imaging { - public class CroppedBitmap : IImage, IDisposable + /// + /// 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(IBitmap source, PixelRect sourceRect) + + 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?.Dispose(); + (Source as IBitmap)?.Dispose(); } public Size Size { @@ -29,7 +81,7 @@ namespace Avalonia.Media.Imaging return Size.Empty; if (SourceRect.IsEmpty) return Source.Size; - return SourceRect.Size.ToSizeWithDpi(Source.Dpi); + return SourceRect.Size.ToSizeWithDpi((Source as IBitmap).Dpi); } } @@ -37,11 +89,8 @@ namespace Avalonia.Media.Imaging { if (Source == null) return; - var topLeft = SourceRect.TopLeft.ToPointWithDpi(Source.Dpi); + var topLeft = SourceRect.TopLeft.ToPointWithDpi((Source as IBitmap).Dpi); Source.Draw(context, sourceRect.Translate(new Vector(topLeft.X, topLeft.Y)), destRect, bitmapInterpolationMode); } - - public IBitmap Source { get; } - public PixelRect SourceRect { get; } } } diff --git a/src/Avalonia.Visuals/Properties/AssemblyInfo.cs b/src/Avalonia.Visuals/Properties/AssemblyInfo.cs index 05c3d7e62a..8f08bc5f44 100644 --- a/src/Avalonia.Visuals/Properties/AssemblyInfo.cs +++ b/src/Avalonia.Visuals/Properties/AssemblyInfo.cs @@ -8,7 +8,8 @@ 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")] -[assembly: InternalsVisibleTo("Avalonia.Skia.RenderTests")] \ No newline at end of file +[assembly: InternalsVisibleTo("Avalonia.Skia.RenderTests")] From 44c5b4340245d27bcd81d8696c51726ffc275269 Mon Sep 17 00:00:00 2001 From: ahopper Date: Wed, 11 Mar 2020 11:34:44 +0000 Subject: [PATCH 3/6] Reduce deferred renderer enumerator allocations --- src/Avalonia.Visuals/Rendering/DeferredRenderer.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs index b065079564..b01dda4209 100644 --- a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs @@ -355,15 +355,19 @@ namespace Avalonia.Rendering node.BeginRender(context, isLayerRoot); - foreach (var operation in node.DrawOperations) + var drawOperations = node.DrawOperations; + for (int i = 0; i < drawOperations.Count; i++) { + var operation = drawOperations[i]; _currentDraw = operation; operation.Item.Render(context); _currentDraw = null; } - foreach (var child in node.Children) + var children = node.Children; + for (int i = 0; i < children.Count; i++) { + var child = children[i]; Render(context, (VisualNode)child, layer, clipBounds); } From 9207d439c4fc8f9ca7213ff5c36b9601c023db58 Mon Sep 17 00:00:00 2001 From: ahopper Date: Wed, 11 Mar 2020 14:45:24 +0000 Subject: [PATCH 4/6] local copies of .Count --- src/Avalonia.Visuals/Rendering/DeferredRenderer.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs index b01dda4209..f11bd76a7b 100644 --- a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs @@ -356,7 +356,8 @@ namespace Avalonia.Rendering node.BeginRender(context, isLayerRoot); var drawOperations = node.DrawOperations; - for (int i = 0; i < drawOperations.Count; i++) + var drawOperationsCount = drawOperations.Count; + for (int i = 0; i < drawOperationsCount; i++) { var operation = drawOperations[i]; _currentDraw = operation; @@ -365,7 +366,8 @@ namespace Avalonia.Rendering } var children = node.Children; - for (int i = 0; i < children.Count; i++) + var childrenCount = children.Count; + for (int i = 0; i < childrenCount; i++) { var child = children[i]; Render(context, (VisualNode)child, layer, clipBounds); From 3ea20ab7d095f08dc821bd31e1540fa50a94852a Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Sun, 15 Mar 2020 19:32:46 +0100 Subject: [PATCH 5/6] Avoid allocating extra delegates when initializing and deinitializing StyleClassActivator. --- .../Styling/Activators/StyleClassActivator.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) 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) From 233d6639e39f27aaeea7c933e0bb484aadfdf08c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 17 Mar 2020 14:39:46 +0100 Subject: [PATCH 6/6] Use more specific .NET core versions. To fix problem with CI failing on OSX. --- azure-pipelines.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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'