diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs
index 70d6e2c10f..9ef29a733b 100644
--- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs
+++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs
@@ -244,9 +244,10 @@ namespace Avalonia.Skia
var drawableImage = (IDrawableBitmapImpl)source;
var s = sourceRect.ToSKRect();
var d = destRect.ToSKRect();
+ var isUpscaling = d.Width > s.Width || d.Height > s.Height;
var paint = SKPaintCache.Shared.Get();
- var samplingOptions = RenderOptions.BitmapInterpolationMode.ToSKSamplingOptions();
+ var samplingOptions = RenderOptions.BitmapInterpolationMode.ToSKSamplingOptions(isUpscaling);
paint.Color = new SKColor(255, 255, 255, (byte)(255 * opacity * _currentOpacity));
paint.BlendMode = RenderOptions.BitmapBlendingMode.ToSKBlendMode();
diff --git a/src/Skia/Avalonia.Skia/ImmutableBitmap.cs b/src/Skia/Avalonia.Skia/ImmutableBitmap.cs
index c8068fb6fa..802d29b4d0 100644
--- a/src/Skia/Avalonia.Skia/ImmutableBitmap.cs
+++ b/src/Skia/Avalonia.Skia/ImmutableBitmap.cs
@@ -48,9 +48,10 @@ namespace Avalonia.Skia
public ImmutableBitmap(ImmutableBitmap src, PixelSize destinationSize, BitmapInterpolationMode interpolationMode)
{
+ var isUpscaling = destinationSize.Width > src.PixelSize.Width || destinationSize.Height > src.PixelSize.Height;
SKImageInfo info = new SKImageInfo(destinationSize.Width, destinationSize.Height, SKColorType.Bgra8888);
_bitmap = new SKBitmap(info);
- src._image.ScalePixels(_bitmap.PeekPixels(), interpolationMode.ToSKSamplingOptions());
+ src._image.ScalePixels(_bitmap.PeekPixels(), interpolationMode.ToSKSamplingOptions(isUpscaling));
_bitmap.SetImmutable();
_image = SKImage.FromBitmap(_bitmap);
@@ -95,7 +96,8 @@ namespace Avalonia.Skia
if (_bitmap.Width != desired.Width || _bitmap.Height != desired.Height)
{
- var scaledBmp = _bitmap.Resize(desired, interpolationMode.ToSKSamplingOptions());
+ var isUpscaling = desired.Width > _bitmap.Width || desired.Height > _bitmap.Height;
+ var scaledBmp = _bitmap.Resize(desired, interpolationMode.ToSKSamplingOptions(isUpscaling));
_bitmap.Dispose();
_bitmap = scaledBmp;
}
diff --git a/src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs b/src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs
index 177dbf5cba..4a9502e49e 100644
--- a/src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs
+++ b/src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs
@@ -10,6 +10,9 @@ namespace Avalonia.Skia
public static class SkiaSharpExtensions
{
public static SKSamplingOptions ToSKSamplingOptions(this BitmapInterpolationMode interpolationMode)
+ => ToSKSamplingOptions(interpolationMode, true);
+
+ internal static SKSamplingOptions ToSKSamplingOptions(this BitmapInterpolationMode interpolationMode, bool isUpscaling)
{
return interpolationMode switch
{
@@ -20,7 +23,9 @@ namespace Avalonia.Skia
BitmapInterpolationMode.MediumQuality =>
new SKSamplingOptions(SKFilterMode.Linear, SKMipmapMode.Linear),
BitmapInterpolationMode.HighQuality =>
- new SKSamplingOptions(SKCubicResampler.Mitchell),
+ isUpscaling ?
+ new SKSamplingOptions(SKCubicResampler.Mitchell) :
+ new SKSamplingOptions(SKFilterMode.Linear, SKMipmapMode.Linear),
_ => throw new ArgumentOutOfRangeException(nameof(interpolationMode), interpolationMode, null)
};
}
diff --git a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs
index 387b84046e..7d8b247ad4 100644
--- a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs
+++ b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs
@@ -73,7 +73,8 @@ namespace Avalonia.Skia
if (bmp.Width != desired.Width || bmp.Height != desired.Height)
{
- var scaledBmp = bmp.Resize(desired, interpolationMode.ToSKSamplingOptions());
+ var isUpscaling = desired.Width > bmp.Width || desired.Height > bmp.Height;
+ var scaledBmp = bmp.Resize(desired, interpolationMode.ToSKSamplingOptions(isUpscaling));
bmp.Dispose();
bmp = scaledBmp;
}
diff --git a/tests/Avalonia.RenderTests.WpfCompare/Avalonia.RenderTests.WpfCompare.csproj b/tests/Avalonia.RenderTests.WpfCompare/Avalonia.RenderTests.WpfCompare.csproj
index d20f1b35d3..5925d73962 100644
--- a/tests/Avalonia.RenderTests.WpfCompare/Avalonia.RenderTests.WpfCompare.csproj
+++ b/tests/Avalonia.RenderTests.WpfCompare/Avalonia.RenderTests.WpfCompare.csproj
@@ -11,6 +11,9 @@
+
+
+
diff --git a/tests/Avalonia.RenderTests.WpfCompare/CrossUI.Wpf.cs b/tests/Avalonia.RenderTests.WpfCompare/CrossUI.Wpf.cs
index 633a0fff78..d6e6897d7e 100644
--- a/tests/Avalonia.RenderTests.WpfCompare/CrossUI.Wpf.cs
+++ b/tests/Avalonia.RenderTests.WpfCompare/CrossUI.Wpf.cs
@@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Windows.Media;
using System.Windows.Media.Imaging;
+using Avalonia.Media.Imaging;
using AlignmentX = System.Windows.Media.AlignmentX;
using AlignmentY = System.Windows.Media.AlignmentY;
using Brush = System.Windows.Media.Brush;
@@ -117,6 +118,16 @@ namespace Avalonia.RenderTests.WpfCompare
public static WRect ToWpf(this Rect rect) => new(rect.Left, rect.Top, rect.Width, rect.Height);
public static WColor ToWpf(this Color color) => WColor.FromArgb(color.A, color.R, color.G, color.B);
public static WMatrix ToWpf(this Matrix m) => new WMatrix(m.M11, m.M12, m.M21, m.M22, m.M31, m.M32);
+
+ public static BitmapScalingMode ToWpf(this BitmapInterpolationMode mode) => mode switch
+ {
+ BitmapInterpolationMode.Unspecified => BitmapScalingMode.Unspecified,
+ BitmapInterpolationMode.None => BitmapScalingMode.NearestNeighbor,
+ BitmapInterpolationMode.LowQuality => BitmapScalingMode.LowQuality,
+ BitmapInterpolationMode.MediumQuality => BitmapScalingMode.Linear,
+ BitmapInterpolationMode.HighQuality => BitmapScalingMode.HighQuality,
+ _ => throw new ArgumentOutOfRangeException(nameof(mode), mode, null)
+ };
}
internal class WpfCrossControl : Panel
@@ -131,6 +142,7 @@ namespace Avalonia.RenderTests.WpfCompare
Width = src.Bounds.Width;
Height = src.Bounds.Height;
RenderTransform = new MatrixTransform(src.RenderTransform.ToWpf());
+ RenderOptions.SetBitmapScalingMode(this, src.BitmapInterpolationMode.ToWpf());
foreach (var ch in src.Children)
{
var c = _children[ch];
diff --git a/tests/Avalonia.RenderTests/Assets/Star512.png b/tests/Avalonia.RenderTests/Assets/Star512.png
new file mode 100644
index 0000000000..cf863fdf9e
Binary files /dev/null and b/tests/Avalonia.RenderTests/Assets/Star512.png differ
diff --git a/tests/Avalonia.RenderTests/CrossTests/Media/ImageScalingTests.cs b/tests/Avalonia.RenderTests/CrossTests/Media/ImageScalingTests.cs
new file mode 100644
index 0000000000..478fddf1e7
--- /dev/null
+++ b/tests/Avalonia.RenderTests/CrossTests/Media/ImageScalingTests.cs
@@ -0,0 +1,39 @@
+using System.IO;
+using System.Runtime.CompilerServices;
+using Avalonia.Media.Imaging;
+using CrossUI;
+
+#if AVALONIA_SKIA
+namespace Avalonia.Skia.RenderTests.CrossTests;
+#elif AVALONIA_D2D
+namespace Avalonia.Direct2D1.RenderTests.CrossTests;
+#else
+namespace Avalonia.RenderTests.WpfCompare.CrossTests;
+#endif
+
+public class ImageScalingTests() : CrossTestBase("Media/ImageScaling")
+{
+ [CrossFact]
+ public void Upscaling_With_HighQuality_Should_Be_Antialiased()
+ => TestHighQualityScaling(1024);
+
+ [CrossFact]
+ public void Downscaling_With_HighQuality_Should_Be_Antialiased()
+ => TestHighQualityScaling(128);
+
+ private void TestHighQualityScaling(int size, [CallerMemberName] string? testName = null)
+ {
+ var directoryPath = Path.GetDirectoryName(typeof(ImageScalingTests).Assembly.Location);
+ var imagePath = Path.Join(directoryPath, "Assets", "Star512.png");
+
+ RenderAndCompare(
+ new CrossImageControl
+ {
+ Width = size,
+ Height = size,
+ Image = new CrossBitmapImage(imagePath),
+ BitmapInterpolationMode = BitmapInterpolationMode.HighQuality
+ },
+ testName);
+ }
+}
diff --git a/tests/Avalonia.RenderTests/CrossUI/CrossUI.Avalonia.cs b/tests/Avalonia.RenderTests/CrossUI/CrossUI.Avalonia.cs
index f3ec416ad1..15c6bbac3e 100644
--- a/tests/Avalonia.RenderTests/CrossUI/CrossUI.Avalonia.cs
+++ b/tests/Avalonia.RenderTests/CrossUI/CrossUI.Avalonia.cs
@@ -105,6 +105,7 @@ namespace Avalonia.Direct2D1.RenderTests.CrossUI
Height = src.Bounds.Height;
RenderTransform = new MatrixTransform(src.RenderTransform);
RenderTransformOrigin = new RelativePoint(default, RelativeUnit.Relative);
+ RenderOptions = RenderOptions with { BitmapInterpolationMode = src.BitmapInterpolationMode };
foreach (var ch in src.Children)
{
var c = _children[ch];
diff --git a/tests/Avalonia.RenderTests/CrossUI/CrossUI.cs b/tests/Avalonia.RenderTests/CrossUI/CrossUI.cs
index 7c690fc824..fbf55036ed 100644
--- a/tests/Avalonia.RenderTests/CrossUI/CrossUI.cs
+++ b/tests/Avalonia.RenderTests/CrossUI/CrossUI.cs
@@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using Avalonia.Media;
using Avalonia;
+using Avalonia.Media.Imaging;
namespace CrossUI;
@@ -229,6 +230,7 @@ public class CrossControl
public CrossPen? Outline;
public List Children = new();
public Matrix RenderTransform = Matrix.Identity;
+ public BitmapInterpolationMode BitmapInterpolationMode;
public virtual void Render(ICrossDrawingContext ctx)
{
diff --git a/tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj b/tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj
index 233160bd36..7fcc6975a9 100644
--- a/tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj
+++ b/tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj
@@ -18,6 +18,9 @@
+
+
+
diff --git a/tests/TestFiles/CrossTests/Media/ImageScaling/Downscaling_With_HighQuality_Should_Be_Antialiased.wpf.png b/tests/TestFiles/CrossTests/Media/ImageScaling/Downscaling_With_HighQuality_Should_Be_Antialiased.wpf.png
new file mode 100644
index 0000000000..17b2579f0d
Binary files /dev/null and b/tests/TestFiles/CrossTests/Media/ImageScaling/Downscaling_With_HighQuality_Should_Be_Antialiased.wpf.png differ
diff --git a/tests/TestFiles/CrossTests/Media/ImageScaling/Upscaling_With_HighQuality_Should_Be_Antialiased.wpf.png b/tests/TestFiles/CrossTests/Media/ImageScaling/Upscaling_With_HighQuality_Should_Be_Antialiased.wpf.png
new file mode 100644
index 0000000000..5d054f1b6e
Binary files /dev/null and b/tests/TestFiles/CrossTests/Media/ImageScaling/Upscaling_With_HighQuality_Should_Be_Antialiased.wpf.png differ