diff --git a/src/Avalonia.Base/Media/DrawingImage.cs b/src/Avalonia.Base/Media/DrawingImage.cs index c83e8eb6ee..7949eaa351 100644 --- a/src/Avalonia.Base/Media/DrawingImage.cs +++ b/src/Avalonia.Base/Media/DrawingImage.cs @@ -22,6 +22,12 @@ namespace Avalonia.Media public static readonly StyledProperty DrawingProperty = AvaloniaProperty.Register(nameof(Drawing)); + /// + /// Defines the property. + /// + public static readonly StyledProperty ViewboxProperty = + AvaloniaProperty.Register(nameof(Viewbox)); + /// public event EventHandler? Invalidated; @@ -35,8 +41,25 @@ namespace Avalonia.Media set => SetValue(DrawingProperty, value); } + /// + /// Gets or sets a rectangular region of , in device independent pixels, to display + /// when rendering this image. + /// + /// + /// This value can be used to display only part of , or to surround it with empty + /// space. If null, will provide its own viewbox. + /// + /// + public Rect? Viewbox + { + get => GetValue(ViewboxProperty); + set => SetValue(ViewboxProperty, value); + } + /// - public Size Size => Drawing?.GetBounds().Size ?? default; + public Size Size => GetBounds().Size; + + private Rect GetBounds() => Viewbox ?? Drawing?.GetBounds() ?? default; /// void IImage.Draw( @@ -44,14 +67,18 @@ namespace Avalonia.Media Rect sourceRect, Rect destRect) { - var drawing = Drawing; + if (Drawing is not { } drawing || sourceRect.Size == default || destRect.Size == default) + { + return; + } + + var bounds = GetBounds(); - if (drawing == null) + if (bounds.Size == default) { return; } - var bounds = drawing.GetBounds(); var scale = Matrix.CreateScale( destRect.Width / sourceRect.Width, destRect.Height / sourceRect.Height); @@ -62,7 +89,7 @@ namespace Avalonia.Media using (context.PushClip(destRect)) using (context.PushTransform(translate * scale)) { - Drawing?.Draw(context); + drawing.Draw(context); } } @@ -71,7 +98,7 @@ namespace Avalonia.Media { base.OnPropertyChanged(change); - if (change.Property == DrawingProperty) + if (change.Property == DrawingProperty || change.Property == ViewboxProperty) { RaiseInvalidated(EventArgs.Empty); } diff --git a/tests/Avalonia.RenderTests/Media/ImageDrawingTests.cs b/tests/Avalonia.RenderTests/Media/ImageDrawingTests.cs index 4294e3874d..f1e9a12289 100644 --- a/tests/Avalonia.RenderTests/Media/ImageDrawingTests.cs +++ b/tests/Avalonia.RenderTests/Media/ImageDrawingTests.cs @@ -45,6 +45,31 @@ namespace Avalonia.Skia.RenderTests CompareImages(); } + [Fact] + public async Task ImageDrawing_Viewbox() + { + Decorator target = new Decorator + { + Width = 200, + Height = 200, + Child = new Image + { + Source = new DrawingImage + { + Viewbox = new Rect(48, 37, 100, 125), + Drawing = new ImageDrawing + { + ImageSource = new Bitmap(BitmapPath), + Rect = new Rect(0, 0, 200, 200), + } + } + } + }; + + await RenderToFile(target); + CompareImages(); + } + [Fact] public async Task ImageDrawing_BottomRight() { @@ -85,7 +110,7 @@ namespace Avalonia.Skia.RenderTests { var target = new Border { - Width = 400, + Width = 400, Height = 400, Child = new DrawingBrushTransformTest() }; diff --git a/tests/TestFiles/Skia/Media/ImageDrawing/ImageDrawing_Viewbox.expected.png b/tests/TestFiles/Skia/Media/ImageDrawing/ImageDrawing_Viewbox.expected.png new file mode 100644 index 0000000000..c2abd87bd2 Binary files /dev/null and b/tests/TestFiles/Skia/Media/ImageDrawing/ImageDrawing_Viewbox.expected.png differ