From 512c9f6851c691dd801e9acb96f8a37916d1e8b3 Mon Sep 17 00:00:00 2001 From: David Sungaila Date: Sat, 21 Sep 2024 13:15:13 +0200 Subject: [PATCH] Workaround for missing PNG compression in SkiaSharp 3.x --- src/PDFtoImage/Conversion.Base64.cs | 5 ++-- src/PDFtoImage/Conversion.ByteArray.cs | 5 ++-- src/PDFtoImage/Conversion.Deprecated.cs | 6 ++-- src/PDFtoImage/Conversion.Stream.cs | 2 +- .../Internals/SKBitmapExtensions.cs | 28 +++++++++++++++++++ src/PDFtoImage/PDFtoImage.csproj | 10 +++++++ src/Tests/AspectRatioTests.cs | 11 ++++---- src/Tests/BatchingTests.cs | 17 +++++------ src/Tests/ComparisonTests.cs | 13 +++++---- src/WebConverter/Pages/Index.razor.cs | 3 +- 10 files changed, 72 insertions(+), 28 deletions(-) create mode 100644 src/PDFtoImage/Internals/SKBitmapExtensions.cs diff --git a/src/PDFtoImage/Conversion.Base64.cs b/src/PDFtoImage/Conversion.Base64.cs index cf01585..efdf1b3 100644 --- a/src/PDFtoImage/Conversion.Base64.cs +++ b/src/PDFtoImage/Conversion.Base64.cs @@ -1,4 +1,5 @@ -using SkiaSharp; +using PDFtoImage.Internals; +using SkiaSharp; using System; using System.Collections.Generic; using System.Drawing; @@ -294,7 +295,7 @@ internal static void SaveImpl(Stream imageStream, SKEncodedImageFormat format, s throw new ArgumentNullException(nameof(imageStream)); using var bitmap = ToImage(pdfAsBase64String, page, password, options); - bitmap.Encode(imageStream, format, 100); + bitmap.EncodeExt(imageStream, format, 100); } #endif } diff --git a/src/PDFtoImage/Conversion.ByteArray.cs b/src/PDFtoImage/Conversion.ByteArray.cs index 52c5820..41ab678 100644 --- a/src/PDFtoImage/Conversion.ByteArray.cs +++ b/src/PDFtoImage/Conversion.ByteArray.cs @@ -1,4 +1,5 @@ -using SkiaSharp; +using PDFtoImage.Internals; +using SkiaSharp; using System; using System.Collections.Generic; using System.Drawing; @@ -324,7 +325,7 @@ internal static void SaveImpl(Stream imageStream, SKEncodedImageFormat format, b throw new ArgumentNullException(nameof(imageStream)); using var bitmap = ToImage(pdfAsByteArray, page, password, options); - bitmap.Encode(imageStream, format, 100); + bitmap.EncodeExt(imageStream, format, 100); } #endif } diff --git a/src/PDFtoImage/Conversion.Deprecated.cs b/src/PDFtoImage/Conversion.Deprecated.cs index 3654aa7..487c9c4 100644 --- a/src/PDFtoImage/Conversion.Deprecated.cs +++ b/src/PDFtoImage/Conversion.Deprecated.cs @@ -456,7 +456,7 @@ internal static void SaveImpl(string filename, SKEncodedImageFormat format, Stre internal static void SaveImpl(Stream stream, SKEncodedImageFormat format, Stream pdfStream, bool leaveOpen = false, string? password = null, int page = 0, RenderOptions options = default) { using var bitmap = ToImage(pdfStream, leaveOpen, password, page, options); - bitmap.Encode(stream, format, 100); + bitmap.EncodeExt(stream, format, 100); } #if NET6_0_OR_GREATER @@ -480,7 +480,7 @@ internal static void SaveImpl(Stream imageStream, SKEncodedImageFormat format, b throw new ArgumentNullException(nameof(imageStream)); using var bitmap = ToImage(pdfAsByteArray, password, page, options); - bitmap.Encode(imageStream, format, 100); + bitmap.EncodeExt(imageStream, format, 100); } #if NET6_0_OR_GREATER @@ -504,7 +504,7 @@ internal static void SaveImpl(Stream imageStream, SKEncodedImageFormat format, s throw new ArgumentNullException(nameof(imageStream)); using var bitmap = ToImage(pdfAsBase64String, password, page, options); - bitmap.Encode(imageStream, format, 100); + bitmap.EncodeExt(imageStream, format, 100); } } } \ No newline at end of file diff --git a/src/PDFtoImage/Conversion.Stream.cs b/src/PDFtoImage/Conversion.Stream.cs index b829d48..ee162b4 100644 --- a/src/PDFtoImage/Conversion.Stream.cs +++ b/src/PDFtoImage/Conversion.Stream.cs @@ -355,7 +355,7 @@ internal static void SaveImpl(string filename, SKEncodedImageFormat format, Stre internal static void SaveImpl(Stream stream, SKEncodedImageFormat format, Stream pdfStream, Index page, bool leaveOpen = false, string? password = null, RenderOptions options = default) { using var bitmap = ToImage(pdfStream, page, leaveOpen, password, options); - bitmap.Encode(stream, format, 100); + bitmap.EncodeExt(stream, format, 100); } #endif } diff --git a/src/PDFtoImage/Internals/SKBitmapExtensions.cs b/src/PDFtoImage/Internals/SKBitmapExtensions.cs new file mode 100644 index 0000000..c05b4c3 --- /dev/null +++ b/src/PDFtoImage/Internals/SKBitmapExtensions.cs @@ -0,0 +1,28 @@ +using SkiaSharp; +using System; +using System.IO; + +namespace PDFtoImage.Internals +{ + internal static class SKBitmapExtensions + { + [Obsolete("This is a workaround for the missing PNG compression in SkiaSharp 3.x. Remove this once SkiaSharp is fixed.")] + public static bool EncodeExt(this SKBitmap bitmap, Stream dst, SKEncodedImageFormat format, int quality) + { + if (format == SKEncodedImageFormat.Png) + { + using var pixmap = new SKPixmap(bitmap.Info, bitmap.GetPixels()); + using var data = pixmap.Encode(SKPngEncoderOptions.Default); + + if (data == null) + return false; + + data.SaveTo(dst); + + return true; + } + + return bitmap.Encode(dst, format, quality); + } + } +} \ No newline at end of file diff --git a/src/PDFtoImage/PDFtoImage.csproj b/src/PDFtoImage/PDFtoImage.csproj index 19b9a0d..7138cb5 100644 --- a/src/PDFtoImage/PDFtoImage.csproj +++ b/src/PDFtoImage/PDFtoImage.csproj @@ -105,6 +105,16 @@ + + + + <_Parameter1>PDFtoImage.Tests + + + <_Parameter1>PDFtoImage.WebConverter + + + diff --git a/src/Tests/AspectRatioTests.cs b/src/Tests/AspectRatioTests.cs index 5dc6ce3..bfae046 100644 --- a/src/Tests/AspectRatioTests.cs +++ b/src/Tests/AspectRatioTests.cs @@ -1,4 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; +using PDFtoImage.Internals; using PDFtoImage.Tests; using System.IO; using static PDFtoImage.Conversion; @@ -352,7 +353,7 @@ public void IgnoreDpi(string fileName, int? width = null, int? height = null, bo { using var outputStream = CreateOutputStream(expectedPath); - ToImage(inputStream, true, options: new(Dpi: i, Width: width, Height: height, WithAnnotations: true, WithFormFill: true, WithAspectRatio: withAspectRatio)).Encode(outputStream, SkiaSharp.SKEncodedImageFormat.Jpeg, 100); + ToImage(inputStream, true, options: new(Dpi: i, Width: width, Height: height, WithAnnotations: true, WithFormFill: true, WithAspectRatio: withAspectRatio)).EncodeExt(outputStream, SkiaSharp.SKEncodedImageFormat.Jpeg, 100); CompareStreams(expectedPath, outputStream); } } @@ -375,8 +376,8 @@ public void IgnoreAspectRatio(string fileName, int width, int height) using var outputStream1 = CreateOutputStream(expectedPath); using var outputStream2 = CreateOutputStream(expectedPath); - ToImage(inputStream, true, options: new(Width: width, Height: height, WithAnnotations: true, WithFormFill: true, WithAspectRatio: false)).Encode(outputStream1, SkiaSharp.SKEncodedImageFormat.Jpeg, 100); - ToImage(inputStream, true, options: new(Width: width, Height: height, WithAnnotations: true, WithFormFill: true, WithAspectRatio: true)).Encode(outputStream2, SkiaSharp.SKEncodedImageFormat.Jpeg, 100); + ToImage(inputStream, true, options: new(Width: width, Height: height, WithAnnotations: true, WithFormFill: true, WithAspectRatio: false)).EncodeExt(outputStream1, SkiaSharp.SKEncodedImageFormat.Jpeg, 100); + ToImage(inputStream, true, options: new(Width: width, Height: height, WithAnnotations: true, WithFormFill: true, WithAspectRatio: true)).EncodeExt(outputStream2, SkiaSharp.SKEncodedImageFormat.Jpeg, 100); CompareStreams(expectedPath, outputStream1); CompareStreams(expectedPath, outputStream2); @@ -400,8 +401,8 @@ public void IgnoreAspectRatioWithDpi(string fileName, int dpi) using var outputStream1 = CreateOutputStream(expectedPath); using var outputStream2 = CreateOutputStream(expectedPath); - ToImage(inputStream, true, options: new(Dpi: dpi, WithAnnotations: true, WithFormFill: true, WithAspectRatio: false)).Encode(outputStream1, SkiaSharp.SKEncodedImageFormat.Jpeg, 100); - ToImage(inputStream, true, options: new(Dpi: dpi, WithAnnotations: true, WithFormFill: true, WithAspectRatio: true)).Encode(outputStream2, SkiaSharp.SKEncodedImageFormat.Jpeg, 100); + ToImage(inputStream, true, options: new(Dpi: dpi, WithAnnotations: true, WithFormFill: true, WithAspectRatio: false)).EncodeExt(outputStream1, SkiaSharp.SKEncodedImageFormat.Jpeg, 100); + ToImage(inputStream, true, options: new(Dpi: dpi, WithAnnotations: true, WithFormFill: true, WithAspectRatio: true)).EncodeExt(outputStream2, SkiaSharp.SKEncodedImageFormat.Jpeg, 100); CompareStreams(expectedPath, outputStream1); CompareStreams(expectedPath, outputStream2); diff --git a/src/Tests/BatchingTests.cs b/src/Tests/BatchingTests.cs index 4a72bdb..0602c67 100644 --- a/src/Tests/BatchingTests.cs +++ b/src/Tests/BatchingTests.cs @@ -1,4 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; +using PDFtoImage.Internals; using PDFtoImage.Tests; using SkiaSharp; using System; @@ -62,7 +63,7 @@ public void ToImagesWithSelectionOdd() { var expectedPath = Path.Combine("..", "Assets", "Expected", GetPlatformAsString(), $"Wikimedia_Commons_web_{selection[i]}.png"); using var outputStream = CreateOutputStream(expectedPath); - bitmap.Encode(outputStream, SKEncodedImageFormat.Png, 100); + bitmap.EncodeExt(outputStream, SKEncodedImageFormat.Png, 100); CompareStreams(expectedPath, outputStream); i++; @@ -150,7 +151,7 @@ public void ToImagesWithRangeAll() { var expectedPath = Path.Combine("..", "Assets", "Expected", GetPlatformAsString(), $"Wikimedia_Commons_web_{i}.png"); using var outputStream = CreateOutputStream(expectedPath); - bitmap.Encode(outputStream, SKEncodedImageFormat.Png, 100); + bitmap.EncodeExt(outputStream, SKEncodedImageFormat.Png, 100); CompareStreams(expectedPath, outputStream); i++; @@ -170,7 +171,7 @@ public void ToImagesWithRangeSecondHalf() { var expectedPath = Path.Combine("..", "Assets", "Expected", GetPlatformAsString(), $"Wikimedia_Commons_web_{i}.png"); using var outputStream = CreateOutputStream(expectedPath); - bitmap.Encode(outputStream, SKEncodedImageFormat.Png, 100); + bitmap.EncodeExt(outputStream, SKEncodedImageFormat.Png, 100); CompareStreams(expectedPath, outputStream); i++; @@ -190,7 +191,7 @@ public void ToImagesWithSelectionEven() { var expectedPath = Path.Combine("..", "Assets", "Expected", GetPlatformAsString(), $"Wikimedia_Commons_web_{selection[i]}.png"); using var outputStream = CreateOutputStream(expectedPath); - bitmap.Encode(outputStream, SKEncodedImageFormat.Png, 100); + bitmap.EncodeExt(outputStream, SKEncodedImageFormat.Png, 100); CompareStreams(expectedPath, outputStream); i++; @@ -210,7 +211,7 @@ public async Task ToImagesWithRangeAllAsync() { var expectedPath = Path.Combine("..", "Assets", "Expected", GetPlatformAsString(), $"Wikimedia_Commons_web_{i}.png"); using var outputStream = CreateOutputStream(expectedPath); - bitmap.Encode(outputStream, SKEncodedImageFormat.Png, 100); + bitmap.EncodeExt(outputStream, SKEncodedImageFormat.Png, 100); CompareStreams(expectedPath, outputStream); i++; @@ -230,7 +231,7 @@ public async Task ToImagesWithRangeSecondHalfAsync() { var expectedPath = Path.Combine("..", "Assets", "Expected", GetPlatformAsString(), $"Wikimedia_Commons_web_{i}.png"); using var outputStream = CreateOutputStream(expectedPath); - bitmap.Encode(outputStream, SKEncodedImageFormat.Png, 100); + bitmap.EncodeExt(outputStream, SKEncodedImageFormat.Png, 100); CompareStreams(expectedPath, outputStream); i++; @@ -250,7 +251,7 @@ public async Task ToImagesWithSelectionEvenAsync() { var expectedPath = Path.Combine("..", "Assets", "Expected", GetPlatformAsString(), $"Wikimedia_Commons_web_{selection[i]}.png"); using var outputStream = CreateOutputStream(expectedPath); - bitmap.Encode(outputStream, SKEncodedImageFormat.Png, 100); + bitmap.EncodeExt(outputStream, SKEncodedImageFormat.Png, 100); CompareStreams(expectedPath, outputStream); i++; @@ -270,7 +271,7 @@ public async Task ToImagesWithSelectionOddAsync() { var expectedPath = Path.Combine("..", "Assets", "Expected", GetPlatformAsString(), $"Wikimedia_Commons_web_{selection[i]}.png"); using var outputStream = CreateOutputStream(expectedPath); - bitmap.Encode(outputStream, SKEncodedImageFormat.Png, 100); + bitmap.EncodeExt(outputStream, SKEncodedImageFormat.Png, 100); CompareStreams(expectedPath, outputStream); i++; diff --git a/src/Tests/ComparisonTests.cs b/src/Tests/ComparisonTests.cs index 24a0c22..5daea80 100644 --- a/src/Tests/ComparisonTests.cs +++ b/src/Tests/ComparisonTests.cs @@ -1,4 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; +using PDFtoImage.Internals; using PDFtoImage.Tests; using SkiaSharp; using System; @@ -82,7 +83,7 @@ public void SaveWebpPages(bool withAnnotations = false) var expectedPath = Path.Combine("..", "Assets", "Expected", GetPlatformAsString(), $"Wikimedia_Commons_web_{page}{(withAnnotations ? "_ANNOT" : string.Empty)}.webp"); using var outputStream = CreateOutputStream(expectedPath); - image.Encode(outputStream, SKEncodedImageFormat.Webp, 100); + image.EncodeExt(outputStream, SKEncodedImageFormat.Webp, 100); CompareStreams(expectedPath, outputStream); @@ -105,7 +106,7 @@ public async Task SaveWebpPagesAsync(bool withAnnotations = false) var expectedPath = Path.Combine("..", "Assets", "Expected", GetPlatformAsString(), $"Wikimedia_Commons_web_{page}{(withAnnotations ? "_ANNOT" : string.Empty)}.webp"); using var outputStream = CreateOutputStream(expectedPath); - image.Encode(outputStream, SKEncodedImageFormat.Webp, 100); + image.EncodeExt(outputStream, SKEncodedImageFormat.Webp, 100); CompareStreams(expectedPath, outputStream); @@ -181,7 +182,7 @@ public void SavePngPages(bool withAnnotations = false) var expectedPath = Path.Combine("..", "Assets", "Expected", GetPlatformAsString(), $"Wikimedia_Commons_web_{page}{(withAnnotations ? "_ANNOT" : string.Empty)}.png"); using var outputStream = CreateOutputStream(expectedPath); - image.Encode(outputStream, SKEncodedImageFormat.Png, 100); + image.EncodeExt(outputStream, SKEncodedImageFormat.Png, 100); CompareStreams(expectedPath, outputStream); @@ -204,7 +205,7 @@ public async Task SavePngPagesAsync(bool withAnnotations = false) var expectedPath = Path.Combine("..", "Assets", "Expected", GetPlatformAsString(), $"Wikimedia_Commons_web_{page}{(withAnnotations ? "_ANNOT" : string.Empty)}.png"); using var outputStream = CreateOutputStream(expectedPath); - image.Encode(outputStream, SKEncodedImageFormat.Png, 100); + image.EncodeExt(outputStream, SKEncodedImageFormat.Png, 100); CompareStreams(expectedPath, outputStream); @@ -280,7 +281,7 @@ public void SaveJpegPages(bool withAnnotations = false) var expectedPath = Path.Combine("..", "Assets", "Expected", GetPlatformAsString(), $"Wikimedia_Commons_web_{page}{(withAnnotations ? "_ANNOT" : string.Empty)}.jpg"); using var outputStream = CreateOutputStream(expectedPath); - image.Encode(outputStream, SKEncodedImageFormat.Jpeg, 100); + image.EncodeExt(outputStream, SKEncodedImageFormat.Jpeg, 100); CompareStreams(expectedPath, outputStream); @@ -303,7 +304,7 @@ public async Task SaveJpegPagesAsync(bool withAnnotations = false) var expectedPath = Path.Combine("..", "Assets", "Expected", GetPlatformAsString(), $"Wikimedia_Commons_web_{page}{(withAnnotations ? "_ANNOT" : string.Empty)}.jpg"); using var outputStream = CreateOutputStream(expectedPath); - image.Encode(outputStream, SKEncodedImageFormat.Jpeg, 100); + image.EncodeExt(outputStream, SKEncodedImageFormat.Jpeg, 100); CompareStreams(expectedPath, outputStream); diff --git a/src/WebConverter/Pages/Index.razor.cs b/src/WebConverter/Pages/Index.razor.cs index 8ea649e..8235464 100644 --- a/src/WebConverter/Pages/Index.razor.cs +++ b/src/WebConverter/Pages/Index.razor.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Components.Forms; using Microsoft.Extensions.Logging; using Microsoft.JSInterop; +using PDFtoImage.Internals; using PDFtoImage.WebConverter.Models; using SkiaSharp; using System; @@ -182,7 +183,7 @@ await Task.Factory.StartNew(() => DpiRelativeToBounds: Model.DpiRelativeToBounds ) ); - encodeSuccess = bitmap!.Encode(Model.Output, Model.Format, Model.Quality); + encodeSuccess = bitmap!.EncodeExt(Model.Output, Model.Format, Model.Quality); }, TaskCreationOptions.LongRunning); if (!encodeSuccess)