mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-10-26 16:45:09 +00:00
Backport pull request #17956 from jellyfin/release-12.z
Don't resample or sharpen images Skia isn't actually resizing
Original-merge: 7d7faf08b7
Merged-by: crobibero <cody@robibe.ro>
Backported-by: Cody Robibero <cody@robibe.ro>
This commit is contained in:
1 parent
6a317f45ce
commit
8e2b62331d
2 files changed
+112
-2
No files matched your search
@@ -556,6 +556,13 @@ public class SkiaEncoder : IImageEncoder
|
||||
/// <returns>The resized image.</returns>
|
||||
internal static SKImage ResizeImage(SKBitmap source, SKImageInfo targetInfo, bool isAntialias = false, bool isDither = false)
|
||||
{
|
||||
if (source.Width == targetInfo.Width && source.Height == targetInfo.Height)
|
||||
{
|
||||
return SKImage.FromBitmap(source);
|
||||
}
|
||||
|
||||
var isDownscale = source.Width > targetInfo.Width || source.Height > targetInfo.Height;
|
||||
|
||||
using var target = new SKBitmap(targetInfo);
|
||||
using var canvas = new SKCanvas(target);
|
||||
using var paint = new SKPaint();
|
||||
@@ -565,7 +572,7 @@ public class SkiaEncoder : IImageEncoder
|
||||
// Historically, kHigh implied cubic filtering, but only when upsampling.
|
||||
// If specified kHigh, and were down-sampling, Skia used to switch back to kMedium (bilinear filtering plus mipmaps).
|
||||
// With current skia API, passing Mitchell cubic when down-sampling will cause serious quality degradation.
|
||||
var samplingOptions = source.Width > targetInfo.Width || source.Height > targetInfo.Height
|
||||
var samplingOptions = isDownscale
|
||||
? DefaultSamplingOptions
|
||||
: UpscaleSamplingOptions;
|
||||
|
||||
@@ -576,7 +583,10 @@ public class SkiaEncoder : IImageEncoder
|
||||
samplingOptions,
|
||||
paint);
|
||||
|
||||
SharpenInPlace(target);
|
||||
if (isDownscale)
|
||||
{
|
||||
SharpenInPlace(target);
|
||||
}
|
||||
|
||||
return SKImage.FromBitmap(target);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
using SkiaSharp;
|
||||
using Xunit;
|
||||
|
||||
namespace Jellyfin.Drawing.Skia.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Covers what <see cref="SkiaEncoder.ResizeImage"/> does either side of a resize: at matching
|
||||
/// dimensions it must not touch the image at all, and sharpening belongs to downscales only.
|
||||
/// </summary>
|
||||
public class SkiaEncoderResizeTests
|
||||
{
|
||||
private static SKBitmap CreateEdgeBitmap(int width, int height)
|
||||
{
|
||||
var bitmap = new SKBitmap(new SKImageInfo(width, height, SKColorType.Rgba8888, SKAlphaType.Premul));
|
||||
using var canvas = new SKCanvas(bitmap);
|
||||
canvas.Clear(new SKColor(40, 60, 80));
|
||||
using var paint = new SKPaint { Color = new SKColor(220, 210, 200) };
|
||||
canvas.DrawRect(SKRect.Create(0, 0, width / 2f, height), paint);
|
||||
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
private static SKImageInfo InfoFor(SKBitmap source, int width, int height)
|
||||
=> new SKImageInfo(width, height, source.ColorType, source.AlphaType, source.ColorSpace);
|
||||
|
||||
/// <summary>
|
||||
/// Draws without sharpening, which is what the resize is expected to reduce to when it is not
|
||||
/// downscaling.
|
||||
/// </summary>
|
||||
private static SKBitmap DrawOnly(SKBitmap source, SKImageInfo targetInfo, SKSamplingOptions sampling)
|
||||
{
|
||||
var target = new SKBitmap(targetInfo);
|
||||
using var canvas = new SKCanvas(target);
|
||||
using var paint = new SKPaint();
|
||||
canvas.DrawBitmap(
|
||||
source,
|
||||
SKRect.Create(0, 0, source.Width, source.Height),
|
||||
SKRect.Create(0, 0, targetInfo.Width, targetInfo.Height),
|
||||
sampling,
|
||||
paint);
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
private static void AssertSamePixels(SKBitmap expected, SKBitmap actual)
|
||||
{
|
||||
Assert.Equal(expected.Width, actual.Width);
|
||||
Assert.Equal(expected.Height, actual.Height);
|
||||
|
||||
for (var y = 0; y < expected.Height; y++)
|
||||
{
|
||||
for (var x = 0; x < expected.Width; x++)
|
||||
{
|
||||
Assert.Equal(expected.GetPixel(x, y), actual.GetPixel(x, y));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResizeImage_MatchingDimensions_ReturnsTheImageUntouched()
|
||||
{
|
||||
using var source = CreateEdgeBitmap(16, 16);
|
||||
|
||||
using var result = SkiaEncoder.ResizeImage(source, InfoFor(source, 16, 16));
|
||||
using var resultBitmap = SKBitmap.FromImage(result);
|
||||
|
||||
// Unsharpened, so the edge is still exactly where it was.
|
||||
AssertSamePixels(source, resultBitmap);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResizeImage_Upscale_DoesNotSharpen()
|
||||
{
|
||||
using var source = CreateEdgeBitmap(8, 8);
|
||||
var targetInfo = InfoFor(source, 24, 24);
|
||||
|
||||
using var result = SkiaEncoder.ResizeImage(source, targetInfo);
|
||||
using var resultBitmap = SKBitmap.FromImage(result);
|
||||
using var expected = DrawOnly(source, targetInfo, SkiaEncoder.UpscaleSamplingOptions);
|
||||
|
||||
AssertSamePixels(expected, resultBitmap);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResizeImage_Downscale_StillSharpens()
|
||||
{
|
||||
using var source = CreateEdgeBitmap(32, 32);
|
||||
var targetInfo = InfoFor(source, 16, 16);
|
||||
|
||||
using var result = SkiaEncoder.ResizeImage(source, targetInfo);
|
||||
using var resultBitmap = SKBitmap.FromImage(result);
|
||||
using var unsharpened = DrawOnly(source, targetInfo, SkiaEncoder.DefaultSamplingOptions);
|
||||
using var sharpened = DrawOnly(source, targetInfo, SkiaEncoder.DefaultSamplingOptions);
|
||||
SkiaEncoder.SharpenInPlace(sharpened);
|
||||
|
||||
AssertSamePixels(sharpened, resultBitmap);
|
||||
// Guards the test itself: the edge has to be something sharpening actually changes.
|
||||
Assert.NotEqual(unsharpened.GetPixel(8, 8), sharpened.GetPixel(8, 8));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user