Browse Source

Fix BitmapInterpolationMode.HighQuality when downscaling (#19513)

* Fix BitmapInterpolationMode.HighQuality when downscaling

* Fix SkiaSharpExtensions.ToSKSamplingOptions public API change
pull/19525/head
Julien Lebosquain 6 months ago
committed by GitHub
parent
commit
9ee57c5616
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 3
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  2. 6
      src/Skia/Avalonia.Skia/ImmutableBitmap.cs
  3. 7
      src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs
  4. 3
      src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs
  5. 3
      tests/Avalonia.RenderTests.WpfCompare/Avalonia.RenderTests.WpfCompare.csproj
  6. 12
      tests/Avalonia.RenderTests.WpfCompare/CrossUI.Wpf.cs
  7. BIN
      tests/Avalonia.RenderTests/Assets/Star512.png
  8. 39
      tests/Avalonia.RenderTests/CrossTests/Media/ImageScalingTests.cs
  9. 1
      tests/Avalonia.RenderTests/CrossUI/CrossUI.Avalonia.cs
  10. 2
      tests/Avalonia.RenderTests/CrossUI/CrossUI.cs
  11. 3
      tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj
  12. BIN
      tests/TestFiles/CrossTests/Media/ImageScaling/Downscaling_With_HighQuality_Should_Be_Antialiased.wpf.png
  13. BIN
      tests/TestFiles/CrossTests/Media/ImageScaling/Upscaling_With_HighQuality_Should_Be_Antialiased.wpf.png

3
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@ -244,9 +244,10 @@ namespace Avalonia.Skia
var drawableImage = (IDrawableBitmapImpl)source; var drawableImage = (IDrawableBitmapImpl)source;
var s = sourceRect.ToSKRect(); var s = sourceRect.ToSKRect();
var d = destRect.ToSKRect(); var d = destRect.ToSKRect();
var isUpscaling = d.Width > s.Width || d.Height > s.Height;
var paint = SKPaintCache.Shared.Get(); 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.Color = new SKColor(255, 255, 255, (byte)(255 * opacity * _currentOpacity));
paint.BlendMode = RenderOptions.BitmapBlendingMode.ToSKBlendMode(); paint.BlendMode = RenderOptions.BitmapBlendingMode.ToSKBlendMode();

6
src/Skia/Avalonia.Skia/ImmutableBitmap.cs

@ -48,9 +48,10 @@ namespace Avalonia.Skia
public ImmutableBitmap(ImmutableBitmap src, PixelSize destinationSize, BitmapInterpolationMode interpolationMode) 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); SKImageInfo info = new SKImageInfo(destinationSize.Width, destinationSize.Height, SKColorType.Bgra8888);
_bitmap = new SKBitmap(info); _bitmap = new SKBitmap(info);
src._image.ScalePixels(_bitmap.PeekPixels(), interpolationMode.ToSKSamplingOptions()); src._image.ScalePixels(_bitmap.PeekPixels(), interpolationMode.ToSKSamplingOptions(isUpscaling));
_bitmap.SetImmutable(); _bitmap.SetImmutable();
_image = SKImage.FromBitmap(_bitmap); _image = SKImage.FromBitmap(_bitmap);
@ -95,7 +96,8 @@ namespace Avalonia.Skia
if (_bitmap.Width != desired.Width || _bitmap.Height != desired.Height) 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.Dispose();
_bitmap = scaledBmp; _bitmap = scaledBmp;
} }

7
src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs

@ -10,6 +10,9 @@ namespace Avalonia.Skia
public static class SkiaSharpExtensions public static class SkiaSharpExtensions
{ {
public static SKSamplingOptions ToSKSamplingOptions(this BitmapInterpolationMode interpolationMode) public static SKSamplingOptions ToSKSamplingOptions(this BitmapInterpolationMode interpolationMode)
=> ToSKSamplingOptions(interpolationMode, true);
internal static SKSamplingOptions ToSKSamplingOptions(this BitmapInterpolationMode interpolationMode, bool isUpscaling)
{ {
return interpolationMode switch return interpolationMode switch
{ {
@ -20,7 +23,9 @@ namespace Avalonia.Skia
BitmapInterpolationMode.MediumQuality => BitmapInterpolationMode.MediumQuality =>
new SKSamplingOptions(SKFilterMode.Linear, SKMipmapMode.Linear), new SKSamplingOptions(SKFilterMode.Linear, SKMipmapMode.Linear),
BitmapInterpolationMode.HighQuality => BitmapInterpolationMode.HighQuality =>
new SKSamplingOptions(SKCubicResampler.Mitchell), isUpscaling ?
new SKSamplingOptions(SKCubicResampler.Mitchell) :
new SKSamplingOptions(SKFilterMode.Linear, SKMipmapMode.Linear),
_ => throw new ArgumentOutOfRangeException(nameof(interpolationMode), interpolationMode, null) _ => throw new ArgumentOutOfRangeException(nameof(interpolationMode), interpolationMode, null)
}; };
} }

3
src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs

@ -73,7 +73,8 @@ namespace Avalonia.Skia
if (bmp.Width != desired.Width || bmp.Height != desired.Height) 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.Dispose();
bmp = scaledBmp; bmp = scaledBmp;
} }

3
tests/Avalonia.RenderTests.WpfCompare/Avalonia.RenderTests.WpfCompare.csproj

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

12
tests/Avalonia.RenderTests.WpfCompare/CrossUI.Wpf.cs

@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Media.Imaging; using System.Windows.Media.Imaging;
using Avalonia.Media.Imaging;
using AlignmentX = System.Windows.Media.AlignmentX; using AlignmentX = System.Windows.Media.AlignmentX;
using AlignmentY = System.Windows.Media.AlignmentY; using AlignmentY = System.Windows.Media.AlignmentY;
using Brush = System.Windows.Media.Brush; 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 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 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 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 internal class WpfCrossControl : Panel
@ -131,6 +142,7 @@ namespace Avalonia.RenderTests.WpfCompare
Width = src.Bounds.Width; Width = src.Bounds.Width;
Height = src.Bounds.Height; Height = src.Bounds.Height;
RenderTransform = new MatrixTransform(src.RenderTransform.ToWpf()); RenderTransform = new MatrixTransform(src.RenderTransform.ToWpf());
RenderOptions.SetBitmapScalingMode(this, src.BitmapInterpolationMode.ToWpf());
foreach (var ch in src.Children) foreach (var ch in src.Children)
{ {
var c = _children[ch]; var c = _children[ch];

BIN
tests/Avalonia.RenderTests/Assets/Star512.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

39
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
tests/Avalonia.RenderTests/CrossUI/CrossUI.Avalonia.cs

@ -105,6 +105,7 @@ namespace Avalonia.Direct2D1.RenderTests.CrossUI
Height = src.Bounds.Height; Height = src.Bounds.Height;
RenderTransform = new MatrixTransform(src.RenderTransform); RenderTransform = new MatrixTransform(src.RenderTransform);
RenderTransformOrigin = new RelativePoint(default, RelativeUnit.Relative); RenderTransformOrigin = new RelativePoint(default, RelativeUnit.Relative);
RenderOptions = RenderOptions with { BitmapInterpolationMode = src.BitmapInterpolationMode };
foreach (var ch in src.Children) foreach (var ch in src.Children)
{ {
var c = _children[ch]; var c = _children[ch];

2
tests/Avalonia.RenderTests/CrossUI/CrossUI.cs

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia.Media; using Avalonia.Media;
using Avalonia; using Avalonia;
using Avalonia.Media.Imaging;
namespace CrossUI; namespace CrossUI;
@ -229,6 +230,7 @@ public class CrossControl
public CrossPen? Outline; public CrossPen? Outline;
public List<CrossControl> Children = new(); public List<CrossControl> Children = new();
public Matrix RenderTransform = Matrix.Identity; public Matrix RenderTransform = Matrix.Identity;
public BitmapInterpolationMode BitmapInterpolationMode;
public virtual void Render(ICrossDrawingContext ctx) public virtual void Render(ICrossDrawingContext ctx)
{ {

3
tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj

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

BIN
tests/TestFiles/CrossTests/Media/ImageScaling/Downscaling_With_HighQuality_Should_Be_Antialiased.wpf.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

BIN
tests/TestFiles/CrossTests/Media/ImageScaling/Upscaling_With_HighQuality_Should_Be_Antialiased.wpf.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Loading…
Cancel
Save