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