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.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")]