diff --git a/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj b/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj index 65a439459..c71c76f08 100644 --- a/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj +++ b/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj @@ -20,9 +20,8 @@ - - - + + diff --git a/Jellyfin.Drawing.Skia/SkiaEncoder.cs b/Jellyfin.Drawing.Skia/SkiaEncoder.cs index 1f7c43de8..a1caa751b 100644 --- a/Jellyfin.Drawing.Skia/SkiaEncoder.cs +++ b/Jellyfin.Drawing.Skia/SkiaEncoder.cs @@ -395,6 +395,56 @@ namespace Jellyfin.Drawing.Skia return rotated; } + /// + /// Resizes an image on the CPU, by utilizing a surface and canvas. + /// + /// The convolutional matrix kernel used in this resize function gives a (light) sharpening effect. + /// This technique is similar to effect that can be created using for example the [Convolution matrix filter in GIMP](https://docs.gimp.org/2.10/en/gimp-filter-convolution-matrix.html). + /// + /// The source bitmap. + /// This specifies the target size and other information required to create the surface. + /// This enables anti-aliasing on the SKPaint instance. + /// This enables dithering on the SKPaint instance. + /// The resized image. + internal static SKImage ResizeImage(SKBitmap source, SKImageInfo targetInfo, bool isAntialias = false, bool isDither = false) + { + using var surface = SKSurface.Create(targetInfo); + using var canvas = surface.Canvas; + using var paint = new SKPaint + { + FilterQuality = SKFilterQuality.High, + IsAntialias = isAntialias, + IsDither = isDither + }; + + var kernel = new float[9] + { + 0, -.1f, 0, + -.1f, 1.4f, -.1f, + 0, -.1f, 0, + }; + + var kernelSize = new SKSizeI(3, 3); + var kernelOffset = new SKPointI(1, 1); + + paint.ImageFilter = SKImageFilter.CreateMatrixConvolution( + kernelSize, + kernel, + 1f, + 0f, + kernelOffset, + SKShaderTileMode.Clamp, + false); + + canvas.DrawBitmap( + source, + SKRect.Create(0, 0, source.Width, source.Height), + SKRect.Create(0, 0, targetInfo.Width, targetInfo.Height), + paint); + + return surface.Snapshot(); + } + /// public string EncodeImage(string inputPath, DateTime dateModified, string outputPath, bool autoOrient, ImageOrientation? orientation, int quality, ImageProcessingOptions options, ImageFormat selectedOutputFormat) { @@ -436,9 +486,9 @@ namespace Jellyfin.Drawing.Skia var width = newImageSize.Width; var height = newImageSize.Height; - using var resizedBitmap = new SKBitmap(width, height, bitmap.ColorType, bitmap.AlphaType); - // scale image - bitmap.ScalePixels(resizedBitmap, SKFilterQuality.High); + // scale image (the FromImage creates a copy) + var imageInfo = new SKImageInfo(width, height, bitmap.ColorType, bitmap.AlphaType, bitmap.ColorSpace); + using var resizedBitmap = SKBitmap.FromImage(ResizeImage(bitmap, imageInfo)); // If all we're doing is resizing then we can stop now if (!hasBackgroundColor && !hasForegroundColor && blur == 0 && !hasIndicator) @@ -446,7 +496,7 @@ namespace Jellyfin.Drawing.Skia Directory.CreateDirectory(Path.GetDirectoryName(outputPath)); using var outputStream = new SKFileWStream(outputPath); using var pixmap = new SKPixmap(new SKImageInfo(width, height), resizedBitmap.GetPixels()); - pixmap.Encode(outputStream, skiaOutputFormat, quality); + resizedBitmap.Encode(outputStream, skiaOutputFormat, quality); return outputPath; } diff --git a/Jellyfin.Drawing.Skia/StripCollageBuilder.cs b/Jellyfin.Drawing.Skia/StripCollageBuilder.cs index e0ee4a342..b08c3750d 100644 --- a/Jellyfin.Drawing.Skia/StripCollageBuilder.cs +++ b/Jellyfin.Drawing.Skia/StripCollageBuilder.cs @@ -115,15 +115,13 @@ namespace Jellyfin.Drawing.Skia // resize to the same aspect as the original int iWidth = Math.Abs(iHeight * currentBitmap.Width / currentBitmap.Height); - using var resizeBitmap = new SKBitmap(iWidth, iHeight, currentBitmap.ColorType, currentBitmap.AlphaType); - currentBitmap.ScalePixels(resizeBitmap, SKFilterQuality.High); + using var resizedImage = SkiaEncoder.ResizeImage(bitmap, new SKImageInfo(iWidth, iHeight, currentBitmap.ColorType, currentBitmap.AlphaType, currentBitmap.ColorSpace)); // crop image int ix = Math.Abs((iWidth - iSlice) / 2); - using var image = SKImage.FromBitmap(resizeBitmap); - using var subset = image.Subset(SKRectI.Create(ix, 0, iSlice, iHeight)); + using var subset = resizedImage.Subset(SKRectI.Create(ix, 0, iSlice, iHeight)); // draw image onto canvas - canvas.DrawImage(subset ?? image, iSlice * i, 0); + canvas.DrawImage(subset ?? resizedImage, iSlice * i, 0); } return bitmap; @@ -177,9 +175,9 @@ namespace Jellyfin.Drawing.Skia continue; } - using var resizedBitmap = new SKBitmap(cellWidth, cellHeight, currentBitmap.ColorType, currentBitmap.AlphaType); - // scale image - currentBitmap.ScalePixels(resizedBitmap, SKFilterQuality.High); + // Scale image. The FromBitmap creates a copy + var imageInfo = new SKImageInfo(cellWidth, cellHeight, currentBitmap.ColorType, currentBitmap.AlphaType, currentBitmap.ColorSpace); + using var resizedBitmap = SKBitmap.FromImage(SkiaEncoder.ResizeImage(bitmap, imageInfo)); // draw this image into the strip at the next position var xPos = x * cellWidth;