From bdb61aa54530dd8d69bed144b2a6005eebc1a948 Mon Sep 17 00:00:00 2001 From: MarcinZiabek Date: Tue, 9 May 2023 00:47:28 +0200 Subject: [PATCH] Improved image handling --- .../QuestPDF.UnitTests/DynamicImageTests.cs | 6 +- Source/QuestPDF/Drawing/DocumentGenerator.cs | 19 ++-- Source/QuestPDF/Elements/AspectRatio.cs | 8 +- Source/QuestPDF/Elements/DynamicImage.cs | 20 +++- Source/QuestPDF/Elements/Image.cs | 63 ++++++++++- .../QuestPDF/Fluent/ImageElementExtensions.cs | 84 +++++++++----- .../QuestPDF/Fluent/ImageModelExtensions.cs | 79 ++++++++++---- Source/QuestPDF/Helpers/Helpers.cs | 12 -- Source/QuestPDF/Helpers/Placeholders.cs | 6 +- .../Infrastructure/DocumentSettings.cs | 4 +- Source/QuestPDF/Infrastructure/Image.cs | 103 +++++++----------- ...lingStrategy.cs => ImageResizeStrategy.cs} | 2 +- .../Infrastructure/ImageScalingQuality.cs | 15 --- Source/QuestPDF/Infrastructure/ImageSize.cs | 14 +++ 14 files changed, 273 insertions(+), 162 deletions(-) rename Source/QuestPDF/Infrastructure/{ImageScalingStrategy.cs => ImageResizeStrategy.cs} (84%) delete mode 100644 Source/QuestPDF/Infrastructure/ImageScalingQuality.cs create mode 100644 Source/QuestPDF/Infrastructure/ImageSize.cs diff --git a/Source/QuestPDF.UnitTests/DynamicImageTests.cs b/Source/QuestPDF.UnitTests/DynamicImageTests.cs index 91f379d..e1bfbe5 100644 --- a/Source/QuestPDF.UnitTests/DynamicImageTests.cs +++ b/Source/QuestPDF.UnitTests/DynamicImageTests.cs @@ -51,7 +51,7 @@ namespace QuestPDF.UnitTests [Test] public void Draw_PassesCorrectSizeToSource() { - Size passedSize = default; + ImageSize passedSize = default; TestPlan .For(x => new DynamicImage @@ -69,9 +69,9 @@ namespace QuestPDF.UnitTests passedSize.Should().BeEquivalentTo(new Size(400, 300)); } - byte[] GenerateImage(Size size) + byte[] GenerateImage(ImageSize size) { - var image = GenerateImage((int) size.Width, (int) size.Height); + var image = GenerateImage(size.Width, size.Height); return image.Encode(SKEncodedImageFormat.Png, 100).ToArray(); } diff --git a/Source/QuestPDF/Drawing/DocumentGenerator.cs b/Source/QuestPDF/Drawing/DocumentGenerator.cs index 696475e..336c90d 100644 --- a/Source/QuestPDF/Drawing/DocumentGenerator.cs +++ b/Source/QuestPDF/Drawing/DocumentGenerator.cs @@ -232,13 +232,18 @@ namespace QuestPDF.Drawing { content.VisitChildren(x => { - if (x is not QuestPDF.Elements.Image { DocumentImage: { } image }) - return; - - image.TargetDpi ??= settings.RasterDpi; - image.ScalingQuality ??= settings.ImageScalingQuality; - image.ScalingStrategy ??= settings.ImageScalingStrategy; - image.CompressionQuality ??= settings.ImageCompressionQuality; + if (x is QuestPDF.Elements.Image image) + { + image.TargetDpi ??= settings.RasterDpi; + image.ResizeStrategy ??= settings.ImageResizeStrategy; + image.CompressionQuality ??= settings.ImageCompressionQuality; + } + + if (x is QuestPDF.Elements.DynamicImage dynamicImage) + { + dynamicImage.TargetDpi ??= settings.RasterDpi; + dynamicImage.CompressionQuality ??= settings.ImageCompressionQuality; + } }); } diff --git a/Source/QuestPDF/Elements/AspectRatio.cs b/Source/QuestPDF/Elements/AspectRatio.cs index 100c4a6..627a4e4 100644 --- a/Source/QuestPDF/Elements/AspectRatio.cs +++ b/Source/QuestPDF/Elements/AspectRatio.cs @@ -8,11 +8,14 @@ namespace QuestPDF.Elements { public ContentDirection ContentDirection { get; set; } - public float Ratio { get; set; } = 1; + public float Ratio { get; set; } public AspectRatioOption Option { get; set; } = AspectRatioOption.FitWidth; internal override SpacePlan Measure(Size availableSpace) { + if (Ratio == 0) + return SpacePlan.FullRender(0, 0); + if(Child == null) return SpacePlan.FullRender(0, 0); @@ -56,6 +59,9 @@ namespace QuestPDF.Elements private Size GetTargetSize(Size availableSpace) { + if (Ratio == 0) + return availableSpace; + var spaceRatio = availableSpace.Width / availableSpace.Height; var fitHeight = new Size(availableSpace.Height * Ratio, availableSpace.Height) ; diff --git a/Source/QuestPDF/Elements/DynamicImage.cs b/Source/QuestPDF/Elements/DynamicImage.cs index e3ac262..6a7d29b 100644 --- a/Source/QuestPDF/Elements/DynamicImage.cs +++ b/Source/QuestPDF/Elements/DynamicImage.cs @@ -1,4 +1,5 @@ using System; +using System.Security; using QuestPDF.Drawing; using QuestPDF.Helpers; using QuestPDF.Infrastructure; @@ -6,9 +7,13 @@ using SkiaSharp; namespace QuestPDF.Elements { + public delegate byte[] GenerateDynamicImageDelegate(ImageSize size); + internal class DynamicImage : Element { - public Func? Source { get; set; } + internal int? TargetDpi { get; set; } + internal ImageCompressionQuality? CompressionQuality { get; set; } + public GenerateDynamicImageDelegate? Source { get; set; } internal override SpacePlan Measure(Size availableSpace) { @@ -19,7 +24,8 @@ namespace QuestPDF.Elements internal override void Draw(Size availableSpace) { - var imageData = Source?.Invoke(availableSpace); + var targetResolution = GetTargetResolution(availableSpace, TargetDpi.Value); + var imageData = Source?.Invoke(targetResolution); if (imageData == null) return; @@ -27,5 +33,15 @@ namespace QuestPDF.Elements using var image = SKImage.FromEncodedData(imageData); Canvas.DrawImage(image, Position.Zero, availableSpace); } + + private static ImageSize GetTargetResolution(Size availableSize, int targetDpi) + { + var scalingFactor = targetDpi / (float)DocumentSettings.DefaultRasterDpi; + + return new ImageSize( + (int)(availableSize.Width * scalingFactor), + (int)(availableSize.Height * scalingFactor) + ); + } } } \ No newline at end of file diff --git a/Source/QuestPDF/Elements/Image.cs b/Source/QuestPDF/Elements/Image.cs index 75e70c5..2f05b2e 100644 --- a/Source/QuestPDF/Elements/Image.cs +++ b/Source/QuestPDF/Elements/Image.cs @@ -9,6 +9,13 @@ namespace QuestPDF.Elements { public Infrastructure.Image? DocumentImage { get; set; } + internal bool UseOriginalImage { get; set; } + internal int? TargetDpi { get; set; } + internal ImageCompressionQuality? CompressionQuality { get; set; } + internal ImageResizeStrategy? ResizeStrategy { get; set; } + + private const float ImageSizeSimilarityToleranceMax = 0.75f; + ~Image() { if (DocumentImage is { IsDocumentScoped: true }) @@ -27,8 +34,62 @@ namespace QuestPDF.Elements if (DocumentImage == null) return; - var image = DocumentImage.GetVersionOfSize(availableSpace); + var image = GetImageToDraw(availableSpace); Canvas.DrawImage(image, Position.Zero, availableSpace); } + + private SKImage GetImageToDraw(Size availableSpace) + { + if (UseOriginalImage) + return DocumentImage.SkImage; + + var request = new GetImageVersionRequest + { + Resolution = GetTargetResolution(ResizeStrategy.Value, DocumentImage.Size, availableSpace, TargetDpi.Value), + CompressionQuality = CompressionQuality.Value, + ResizeStrategy = ResizeStrategy.Value + }; + + return DocumentImage.GetVersionOfSize(request); + } + + private static ImageSize GetTargetResolution(ImageResizeStrategy resizeStrategy, ImageSize imageResolution, Size availableAreaSize, int targetDpi) + { + if (resizeStrategy == ImageResizeStrategy.Never) + return imageResolution; + + var scalingFactor = targetDpi / (float)DocumentSettings.DefaultRasterDpi; + var targetResolution = new ImageSize( + (int)(availableAreaSize.Width * scalingFactor), + (int)(availableAreaSize.Height * scalingFactor)); + + if (resizeStrategy == ImageResizeStrategy.Always) + return targetResolution; + + var isSignificantlySmaller = IsImageSignificantlySmallerThanDrawingArea(imageResolution, targetResolution); + + if (resizeStrategy == ImageResizeStrategy.ScaleOnlyToSignificantlySmallerResolution && isSignificantlySmaller) + return targetResolution; + + var isSmaller = IsImageSmallerThanDrawingArea(imageResolution, targetResolution); + + if (resizeStrategy == ImageResizeStrategy.ScaleOnlyToSmallerResolution && isSmaller) + return targetResolution; + + return imageResolution; + } + + private static bool IsImageSmallerThanDrawingArea(ImageSize imageResolution, ImageSize targetResolution) + { + return imageResolution.Width < targetResolution.Width || imageResolution.Height < targetResolution.Height; + } + + private static bool IsImageSignificantlySmallerThanDrawingArea(ImageSize imageResolution, ImageSize targetResolution, float sizeSimilarityThreshold = ImageSizeSimilarityToleranceMax) + { + var widthRatio = targetResolution.Width / imageResolution.Width; + var heightRatio = targetResolution.Height / imageResolution.Height; + + return widthRatio < sizeSimilarityThreshold && heightRatio < sizeSimilarityThreshold; + } } } \ No newline at end of file diff --git a/Source/QuestPDF/Fluent/ImageElementExtensions.cs b/Source/QuestPDF/Fluent/ImageElementExtensions.cs index e739efe..1ce791f 100644 --- a/Source/QuestPDF/Fluent/ImageElementExtensions.cs +++ b/Source/QuestPDF/Fluent/ImageElementExtensions.cs @@ -9,25 +9,25 @@ namespace QuestPDF.Fluent { public static class ImageExtensions { - public static void Image(this IContainer parent, byte[] imageData, ImageScaling scaling = ImageScaling.FitWidth) + public static ImageDescriptor Image(this IContainer parent, byte[] imageData) { var image = Infrastructure.Image.FromBinaryData(imageData).DisposeAfterDocumentGeneration(); - parent.Image(image, scaling); + return parent.Image(image); } - public static void Image(this IContainer parent, string filePath, ImageScaling scaling = ImageScaling.FitWidth) + public static ImageDescriptor Image(this IContainer parent, string filePath) { var image = Infrastructure.Image.FromFile(filePath).DisposeAfterDocumentGeneration(); - parent.Image(image, scaling); + return parent.Image(image); } - public static void Image(this IContainer parent, Stream fileStream, ImageScaling scaling = ImageScaling.FitWidth) + public static ImageDescriptor Image(this IContainer parent, Stream fileStream) { var image = Infrastructure.Image.FromStream(fileStream).DisposeAfterDocumentGeneration(); - parent.Image(image, scaling); + return parent.Image(image); } - internal static void Image(this IContainer parent, Infrastructure.Image image, ImageScaling scaling = ImageScaling.FitWidth) + internal static ImageDescriptor Image(this IContainer parent, Infrastructure.Image image) { if (image == null) throw new DocumentComposeException("Cannot load or decode provided image."); @@ -37,32 +37,64 @@ namespace QuestPDF.Fluent DocumentImage = image }; - if (scaling != ImageScaling.Resize) + var aspectRationElement = new AspectRatio { - var aspectRatio = image.Width / (float)image.Height; - parent = parent.AspectRatio(aspectRatio, Map(scaling)); - } - - parent.Element(imageElement); - - static AspectRatioOption Map(ImageScaling scaling) - { - return scaling switch - { - ImageScaling.FitWidth => AspectRatioOption.FitWidth, - ImageScaling.FitHeight => AspectRatioOption.FitHeight, - ImageScaling.FitArea => AspectRatioOption.FitArea, - _ => throw new ArgumentOutOfRangeException() - }; - } + Child = imageElement + }; + + parent.Element(aspectRationElement); + return new ImageDescriptor(imageElement, aspectRationElement).FitWidth(); } - public static void Image(this IContainer element, Func imageSource) + public static void Image(this IContainer element, GenerateDynamicImageDelegate dynamicImageSource) { element.Element(new DynamicImage { - Source = imageSource + Source = dynamicImageSource }); } + + #region Obsolete + + [Obsolete("This element has been changed since version 2023.5. Please use the Image method overload that takes the GenerateDynamicImageDelegate as an argument.")] + public static void Image(this IContainer element, Func imageSource) + { + element.Image((ImageSize x) => imageSource(new Size(x.Width, x.Height))); + } + + [Obsolete("This element has been changed since version 2023.5. Please use the Image method overload that returns the ImageDescriptor object.")] + public static void Image(this IContainer parent, byte[] imageData, ImageScaling scaling) + { + parent.Image(imageData).ApplyScaling(scaling); + } + + [Obsolete("This element has been changed since version 2023.5. Please use the Image method overload that returns the ImageDescriptor object.")] + public static void Image(this IContainer parent, string filePath, ImageScaling scaling) + { + parent.Image(filePath).ApplyScaling(scaling); + } + + [Obsolete("This element has been changed since version 2023.5. Please use the Image method overload that returns the ImageDescriptor object.")] + public static void Image(this IContainer parent, Stream fileStream, ImageScaling scaling) + { + parent.Image(fileStream).ApplyScaling(scaling); + } + + internal static void ApplyScaling(this ImageDescriptor descriptor, ImageScaling scaling) + { + if (scaling == ImageScaling.Resize) + descriptor.FitUnproportionally(); + + else if (scaling == ImageScaling.FitWidth) + descriptor.FitWidth(); + + else if (scaling == ImageScaling.FitHeight) + descriptor.FitHeight(); + + else if (scaling == ImageScaling.FitArea) + descriptor.FitArea(); + } + + #endregion } } \ No newline at end of file diff --git a/Source/QuestPDF/Fluent/ImageModelExtensions.cs b/Source/QuestPDF/Fluent/ImageModelExtensions.cs index df0489c..5f91fb1 100644 --- a/Source/QuestPDF/Fluent/ImageModelExtensions.cs +++ b/Source/QuestPDF/Fluent/ImageModelExtensions.cs @@ -1,26 +1,31 @@ using QuestPDF.Drawing.Exceptions; +using QuestPDF.Elements; using QuestPDF.Infrastructure; namespace QuestPDF.Fluent { - public static class ImageModelExtensions + public class ImageDescriptor { - /// - /// When enabled, the image object is disposed automatically after document generation, and you don't need to call the Dispose method yourself. - /// - public static Image DisposeAfterDocumentGeneration(this Image image) - { - image.IsDocumentScoped = true; - return image; - } + private Elements.Image ImageElement { get; } + private AspectRatio AspectRatioElement { get; } + private float ImageAspectRatio { get; } + internal ImageDescriptor(Elements.Image imageElement, Elements.AspectRatio aspectRatioElement) + { + ImageElement = imageElement; + AspectRatioElement = aspectRatioElement; + + var imageSize = ImageElement.DocumentImage.Size; + ImageAspectRatio = imageSize.Width / (float)imageSize.Height; + } + /// /// When enabled, the library will not attempt to resize the image to fit the target DPI, nor save it with target image quality. /// - public static Image UseOriginalImage(this Image image) + public ImageDescriptor UseOriginalImage(bool value = true) { - image.UseOriginalImage = true; - return image; + ImageElement.UseOriginalImage = value; + return this; } /// @@ -29,10 +34,10 @@ namespace QuestPDF.Fluent /// When generating images, this parameter also controls the resolution of the generated content. /// Default value is 72. /// - public static Image WithRasterDpi(this Image image, int dpi) + public ImageDescriptor WithRasterDpi(int dpi) { - image.TargetDpi = dpi; - return image; + ImageElement.TargetDpi = dpi; + return this; } /// @@ -41,22 +46,48 @@ namespace QuestPDF.Fluent /// If this value is set to a value between 1 and 100, and the image is opaque, it will be encoded using the JPEG format with that quality setting. /// The default value is 90 (very high quality). /// - public static Image WithCompressionQuality(this Image image, ImageCompressionQuality quality) + public ImageDescriptor WithCompressionQuality(ImageCompressionQuality quality) { - image.CompressionQuality = quality; - return image; + ImageElement.CompressionQuality = quality; + return this; + } + + public ImageDescriptor WithResizeStrategy(ImageResizeStrategy strategy) + { + ImageElement.ResizeStrategy = strategy; + return this; } - public static Image WithScalingQuality(this Image image, ImageScalingQuality strategy) + #region Aspect Ratio + + public ImageDescriptor FitWidth() { - image.ScalingQuality = strategy; - return image; + return SetAspectRatio(AspectRatioOption.FitWidth); } - public static Image WithScalingStrategy(this Image image, ImageScalingStrategy strategy) + public ImageDescriptor FitHeight() { - image.ScalingStrategy = strategy; - return image; + return SetAspectRatio(AspectRatioOption.FitHeight); } + + public ImageDescriptor FitArea() + { + return SetAspectRatio(AspectRatioOption.FitArea); + } + + public ImageDescriptor FitUnproportionally() + { + AspectRatioElement.Ratio = 0; + return this; + } + + private ImageDescriptor SetAspectRatio(AspectRatioOption option) + { + AspectRatioElement.Ratio = ImageAspectRatio; + AspectRatioElement.Option = option; + return this; + } + + #endregion } } \ No newline at end of file diff --git a/Source/QuestPDF/Helpers/Helpers.cs b/Source/QuestPDF/Helpers/Helpers.cs index 2ba0a3b..89506c1 100644 --- a/Source/QuestPDF/Helpers/Helpers.cs +++ b/Source/QuestPDF/Helpers/Helpers.cs @@ -75,17 +75,5 @@ namespace QuestPDF.Helpers _ => throw new ArgumentOutOfRangeException(nameof(quality), quality, null) }; } - - internal static SKFilterQuality ToFilterQuality(this ImageScalingQuality quality) - { - return quality switch - { - ImageScalingQuality.Low => SKFilterQuality.None, - ImageScalingQuality.Medium => SKFilterQuality.Low, - ImageScalingQuality.High => SKFilterQuality.Medium, - ImageScalingQuality.Best => SKFilterQuality.High, - _ => throw new ArgumentOutOfRangeException(nameof(quality), quality, null) - }; - } } } \ No newline at end of file diff --git a/Source/QuestPDF/Helpers/Placeholders.cs b/Source/QuestPDF/Helpers/Placeholders.cs index bd11a6a..c9708dd 100644 --- a/Source/QuestPDF/Helpers/Placeholders.cs +++ b/Source/QuestPDF/Helpers/Placeholders.cs @@ -213,10 +213,10 @@ namespace QuestPDF.Helpers public static byte[] Image(int width, int height) { - return Image(new Size(width, height)); + return Image(new ImageSize(width, height)); } - public static byte[] Image(Size size) + public static byte[] Image(ImageSize size) { // shuffle corner positions var targetPositions = new[] @@ -239,7 +239,7 @@ namespace QuestPDF.Helpers .ToArray(); // create image with white background - var imageInfo = new SKImageInfo((int)size.Width, (int)size.Height); + var imageInfo = new SKImageInfo(size.Width, size.Height); using var surface = SKSurface.Create(imageInfo); using var backgroundPaint = new SKPaint diff --git a/Source/QuestPDF/Infrastructure/DocumentSettings.cs b/Source/QuestPDF/Infrastructure/DocumentSettings.cs index 6a4019b..5e0d294 100644 --- a/Source/QuestPDF/Infrastructure/DocumentSettings.cs +++ b/Source/QuestPDF/Infrastructure/DocumentSettings.cs @@ -20,9 +20,7 @@ public ImageCompressionQuality ImageCompressionQuality { get; set; } = ImageCompressionQuality.VeryHigh; // TODO: add comments - public ImageScalingStrategy ImageScalingStrategy { get; set; } = ImageScalingStrategy.ScaleOnlyToSignificantlySmallerResolution; - - public ImageScalingQuality ImageScalingQuality { get; set; } = ImageScalingQuality.High; + public ImageResizeStrategy ImageResizeStrategy { get; set; } = ImageResizeStrategy.ScaleOnlyToSignificantlySmallerResolution; /// /// The DPI (pixels-per-inch) at which images and features without native PDF support will be rasterized. diff --git a/Source/QuestPDF/Infrastructure/Image.cs b/Source/QuestPDF/Infrastructure/Image.cs index 32e4945..89f2611 100644 --- a/Source/QuestPDF/Infrastructure/Image.cs +++ b/Source/QuestPDF/Infrastructure/Image.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using QuestPDF.Drawing.Exceptions; using QuestPDF.Helpers; @@ -8,75 +7,55 @@ using SkiaSharp; namespace QuestPDF.Infrastructure { + internal record GetImageVersionRequest + { + internal ImageSize Resolution { get; set; } + internal ImageCompressionQuality CompressionQuality { get; set; } + internal ImageResizeStrategy ResizeStrategy { get; set; } + } + public class Image : IDisposable { - private SKImage SkImage { get; } - public int Width => SkImage.Width; - public int Height => SkImage.Height; - - internal List<(Size size, SKImage image)>? ScaledImageCache { get; set; } - + internal SKImage SkImage { get; } + internal ImageSize Size { get; } internal bool IsDocumentScoped { get; set; } - internal bool UseOriginalImage { get; set; } - internal int? TargetDpi { get; set; } - internal ImageCompressionQuality? CompressionQuality { get; set; } - internal ImageScalingQuality? ScalingQuality { get; set; } - internal ImageScalingStrategy? ScalingStrategy { get; set; } - - private const float ImageSizeSimilarityToleranceMax = 0.75f; + internal LinkedList<(GetImageVersionRequest request, SKImage image)> ScaledImageCache { get; } = new(); + private Image(SKImage image) { SkImage = image; + Size = new ImageSize(image.Width, image.Height); } public void Dispose() { SkImage.Dispose(); + + foreach (var cacheKey in ScaledImageCache) + cacheKey.image.Dispose(); } #region Scaling Image - internal SKImage GetVersionOfSize(Size availableAreaSize) + internal SKImage GetVersionOfSize(GetImageVersionRequest request) { - if (UseOriginalImage) - return SkImage; + foreach (var cacheKey in ScaledImageCache) + { + if (cacheKey.request == request) + return cacheKey.image; + } - var imageResolution = new Size(SkImage.Width, SkImage.Height); - var targetResolution = GetTargetResolution(ScalingStrategy.Value, imageResolution, availableAreaSize, TargetDpi ?? DocumentSettings.DefaultRasterDpi); - - return ScaleAndCompressImage(SkImage, targetResolution, ScalingQuality.Value, CompressionQuality.Value); + var result = ResizeAndCompressImage(SkImage, request.Resolution, request.CompressionQuality); + ScaledImageCache.AddLast((request, result)); + return result; } - - private static Size GetTargetResolution(ImageScalingStrategy scalingStrategy, Size imageResolution, Size availableAreaSize, int targetDpi) - { - if (scalingStrategy == ImageScalingStrategy.Never) - return imageResolution; - - if (scalingStrategy == ImageScalingStrategy.Always) - return availableAreaSize; - - var scalingFactor = targetDpi / DocumentSettings.DefaultRasterDpi; - var targetResolution = new Size(availableAreaSize.Width * scalingFactor, availableAreaSize.Height * scalingFactor); - var isSignificantlySmaller = IsImageSignificantlySmallerThanDrawingArea(imageResolution, targetResolution); - - if (scalingStrategy == ImageScalingStrategy.ScaleOnlyToSignificantlySmallerResolution && isSignificantlySmaller) - return targetResolution; - - var isSmaller = IsImageSmallerThanDrawingArea(imageResolution, targetResolution); - - if (scalingStrategy == ImageScalingStrategy.ScaleOnlyToSmallerResolution && isSmaller) - return targetResolution; - - return imageResolution; - } - private static SKImage CompressImage(SKImage image, ImageCompressionQuality compressionQuality) { var targetFormat = image.Info.IsOpaque - ? SKEncodedImageFormat.Png - : SKEncodedImageFormat.Jpeg; + ? SKEncodedImageFormat.Jpeg + : SKEncodedImageFormat.Png; if (targetFormat == SKEncodedImageFormat.Png) compressionQuality = ImageCompressionQuality.Best; @@ -85,29 +64,19 @@ namespace QuestPDF.Infrastructure return SKImage.FromEncodedData(data); } - private static SKImage ScaleAndCompressImage(SKImage image, Size targetResolution, ImageScalingQuality scalingQuality, ImageCompressionQuality compressionQuality) + private static SKImage ResizeAndCompressImage(SKImage image, ImageSize targetResolution, ImageCompressionQuality compressionQuality) { - var imageInfo = new SKImageInfo((int)targetResolution.Width, (int)targetResolution.Height); + if (image.Width == targetResolution.Width && image.Height == targetResolution.Height) + return CompressImage(image, compressionQuality); + + var imageInfo = new SKImageInfo(targetResolution.Width, targetResolution.Height); using var resultImage = SKImage.Create(imageInfo); - image.ScalePixels(resultImage.PeekPixels(), scalingQuality.ToFilterQuality()); + image.ScalePixels(resultImage.PeekPixels(), SKFilterQuality.Medium); return CompressImage(resultImage, compressionQuality); } - - private static bool IsImageSmallerThanDrawingArea(Size imageResolution, Size targetResolution) - { - return imageResolution.Width < targetResolution.Width || imageResolution.Height < targetResolution.Height; - } - - private static bool IsImageSignificantlySmallerThanDrawingArea(Size imageResolution, Size targetResolution, float sizeSimilarityThreshold = ImageSizeSimilarityToleranceMax) - { - var widthRatio = imageResolution.Width / targetResolution.Width; - var heightRatio = imageResolution.Height / targetResolution.Height; - - return widthRatio < sizeSimilarityThreshold && heightRatio < sizeSimilarityThreshold; - } - + #endregion #region public constructors @@ -141,5 +110,11 @@ namespace QuestPDF.Infrastructure } #endregion + + internal Image DisposeAfterDocumentGeneration() + { + IsDocumentScoped = true; + return this; + } } } \ No newline at end of file diff --git a/Source/QuestPDF/Infrastructure/ImageScalingStrategy.cs b/Source/QuestPDF/Infrastructure/ImageResizeStrategy.cs similarity index 84% rename from Source/QuestPDF/Infrastructure/ImageScalingStrategy.cs rename to Source/QuestPDF/Infrastructure/ImageResizeStrategy.cs index 2a873c4..290a24c 100644 --- a/Source/QuestPDF/Infrastructure/ImageScalingStrategy.cs +++ b/Source/QuestPDF/Infrastructure/ImageResizeStrategy.cs @@ -1,6 +1,6 @@ namespace QuestPDF.Infrastructure { - public enum ImageScalingStrategy + public enum ImageResizeStrategy { // TODO: add comments Always, diff --git a/Source/QuestPDF/Infrastructure/ImageScalingQuality.cs b/Source/QuestPDF/Infrastructure/ImageScalingQuality.cs deleted file mode 100644 index 7b1e420..0000000 --- a/Source/QuestPDF/Infrastructure/ImageScalingQuality.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace QuestPDF.Infrastructure -{ - // https://chromium.googlesource.com/skia/+/master/include/core/SkFilterQuality.h - public enum ImageScalingQuality - { - // TODO: add comments - Low, - - Medium, - - High, - - Best - } -} \ No newline at end of file diff --git a/Source/QuestPDF/Infrastructure/ImageSize.cs b/Source/QuestPDF/Infrastructure/ImageSize.cs new file mode 100644 index 0000000..c6df286 --- /dev/null +++ b/Source/QuestPDF/Infrastructure/ImageSize.cs @@ -0,0 +1,14 @@ +namespace QuestPDF.Infrastructure +{ + public struct ImageSize + { + public readonly int Width; + public readonly int Height; + + public ImageSize(int width, int height) + { + Width = width; + Height = height; + } + } +} \ No newline at end of file