瀏覽代碼

Fix BitmapInterpolationMode.HighQuality when downscaling (#19513)

* Fix BitmapInterpolationMode.HighQuality when downscaling

* Fix SkiaSharpExtensions.ToSKSamplingOptions public API change
Julien Lebosquain 2 月之前
父節點
當前提交
9ee57c5616

+ 2 - 1
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();

+ 4 - 2
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;
                 }

+ 6 - 1
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)
             };
         }

+ 2 - 1
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;
                 }

+ 3 - 0
tests/Avalonia.RenderTests.WpfCompare/Avalonia.RenderTests.WpfCompare.csproj

@@ -11,6 +11,9 @@
     <Compile Include="..\Avalonia.RenderTests\CrossUI\CrossUI.cs"/>
     <Compile Include="..\Avalonia.RenderTests\CrossTests\**\*.cs"/>
   </ItemGroup>
+  <ItemGroup>
+    <Content Include="../Avalonia.RenderTests/**/*.png" CopyToOutputDirectory="Always" />
+  </ItemGroup>
   <ItemGroup>
     <PackageReference Include="Xunit.StaFact" Version="1.2.46-alpha" />
   </ItemGroup>

+ 12 - 0
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];

二進制
tests/Avalonia.RenderTests/Assets/Star512.png


+ 39 - 0
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);
+    }
+}

+ 1 - 0
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];

+ 2 - 0
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<CrossControl> Children = new();
     public Matrix RenderTransform = Matrix.Identity;
+    public BitmapInterpolationMode BitmapInterpolationMode;
     
     public virtual void Render(ICrossDrawingContext ctx)
     {

+ 3 - 0
tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj

@@ -18,6 +18,9 @@
   <ItemGroup>
     <EmbeddedResource Include="..\Avalonia.RenderTests\*\*.ttf" />
   </ItemGroup>
+  <ItemGroup>
+    <Content Include="../Avalonia.RenderTests/**/*.png" CopyToOutputDirectory="Always" />
+  </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\..\src\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj" />
     <ProjectReference Include="..\..\src\Markup\Avalonia.Markup\Avalonia.Markup.csproj" />

二進制
tests/TestFiles/CrossTests/Media/ImageScaling/Downscaling_With_HighQuality_Should_Be_Antialiased.wpf.png


二進制
tests/TestFiles/CrossTests/Media/ImageScaling/Upscaling_With_HighQuality_Should_Be_Antialiased.wpf.png