Browse Source

Added support for image DPI scaling

MarcinZiabek 2 years ago
parent
commit
41ba1ac1ce

+ 0 - 22
Source/QuestPDF.Examples/Engine/Helpers.cs

@@ -1,22 +0,0 @@
-using System;
-using System.Diagnostics;
-using System.IO;
-using QuestPDF.Fluent;
-using QuestPDF.Infrastructure;
-
-namespace QuestPDF.Examples.Engine
-{
-    public static class Helpers
-    {
-        public static void GeneratePdfAndOpen(this Document document, string? fileName = null)
-        {
-            fileName ??= $"{Guid.NewGuid():D}.pdf";
-
-            var filePath = Path.GetTempPath() + fileName;
-            var documentData = document.GeneratePdf();
-            File.WriteAllBytes(filePath, documentData);
-            
-            Process.Start("explorer", filePath);
-        }
-    }
-}

+ 3 - 31
Source/QuestPDF.Examples/ImageExamples.cs

@@ -66,29 +66,7 @@ namespace QuestPDF.Examples
             });
         }
         
-        [Test]
-        public void ReusingTheSameImageFileShouldBePossible()
-        {
-            var image = Image.FromBinaryData(Placeholders.Image(300, 100)).DisposeAfterDocumentGeneration();
-                
-            RenderingTest
-                .Create()
-                .ProducePdf()
-                .PageSize(PageSizes.A4)
-                .ShowResults()
-                .Render(container =>
-                {
-                    container
-                        .Padding(20)
-                        .Column(column =>
-                        {
-                            column.Spacing(20);
-                                
-                            foreach (var i in Enumerable.Range(0, 1000))
-                                column.Item().Image(image);
-                        });
-                });
-        }
+        
         
         [Test]
         public void ImageResolutionScaling()
@@ -100,18 +78,12 @@ namespace QuestPDF.Examples
                 {
                     document.Page(page =>
                     {
-                        page.Size(11000, 11000);
+                        page.Size(210, 210);
                         page.Margin(50);
                         page.Content().Image(image);
                     });
                 })
-                .GeneratePdfAndOpen();
-
-           //Console.WriteLine(documentData.Length);
-            
-            // var filePath = Path.GetTempPath() + $"test.pdf";
-            // File.WriteAllBytes(filePath, documentData);
-            // Console.WriteLine(filePath);
+                .GeneratePdf($"test.pdf");
         }
     }
 }

+ 1 - 1
Source/QuestPDF.Examples/QuestPDF.Examples.csproj

@@ -1,7 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
     <PropertyGroup>
-        <TargetFramework>netcoreapp3.1</TargetFramework>
+        <TargetFramework>net6.0</TargetFramework>
         <IsPackable>false</IsPackable>
     </PropertyGroup>
 

+ 54 - 1
Source/QuestPDF.UnitTests/ImageTests.cs

@@ -1,7 +1,12 @@
-using NUnit.Framework;
+using System;
+using System.Linq;
+using System.Net.Mime;
+using FluentAssertions;
+using NUnit.Framework;
 using QuestPDF.Drawing;
 using QuestPDF.Elements;
 using QuestPDF.Fluent;
+using QuestPDF.Helpers;
 using QuestPDF.Infrastructure;
 using QuestPDF.UnitTests.TestEngine;
 using SkiaSharp;
@@ -59,5 +64,53 @@ namespace QuestPDF.UnitTests
             using var surface = SKSurface.Create(imageInfo);
             return surface.Snapshot();
         }
+
+        [Test]
+        public void UsingSharedImageShouldNotDrasticallyIncreaseDocumentSize()
+        {
+            var placeholderImage = Placeholders.Image(1000, 200);
+            
+            var documentWithSingleImageSize = GetDocumentSize(container =>
+            {
+                container.Image(placeholderImage);
+            });
+            
+            var documentWithMultipleImagesSize = GetDocumentSize(container =>
+            {
+                container.Column(column =>
+                {
+                    foreach (var i in Enumerable.Range(0, 100))
+                        column.Item().Image(placeholderImage);
+                });
+            });
+            
+            var documentWithSingleImageUsedMultipleTimesSize = GetDocumentSize(container =>
+            {
+                container.Column(column =>
+                {
+                    var sharedImage = Image.FromBinaryData(placeholderImage).DisposeAfterDocumentGeneration();
+                    
+                    foreach (var i in Enumerable.Range(0, 100))
+                        column.Item().Image(sharedImage);
+                });
+            });
+
+            (documentWithMultipleImagesSize / (float)documentWithSingleImageSize).Should().BeInRange(90, 100);
+            (documentWithSingleImageUsedMultipleTimesSize / (float)documentWithSingleImageSize).Should().BeInRange(1f, 1.5f);
+        }
+
+        private static int GetDocumentSize(Action<IContainer> container)
+        {
+            return Document
+                .Create(document =>
+                {
+                    document.Page(page =>
+                    {
+                        page.Content().Element(container);
+                    });
+                })
+                .GeneratePdf()
+                .Length;
+        }
     }
 }

+ 1 - 1
Source/QuestPDF.UnitTests/QuestPDF.UnitTests.csproj

@@ -1,7 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
     <PropertyGroup>
-        <TargetFramework>netcoreapp3.1</TargetFramework>
+        <TargetFramework>net6.0</TargetFramework>
         <IsPackable>false</IsPackable>
     </PropertyGroup>
 

+ 45 - 19
Source/QuestPDF/Infrastructure/Image.cs

@@ -10,13 +10,17 @@ namespace QuestPDF.Infrastructure
     public class Image : IDisposable
     {
         private SKImage SkImage { get; }
-        internal List<(Size size, SKImage image)>? ScaledImageCache { get; }
+        internal List<(Size size, SKImage image)>? ScaledImageCache { get; set; }
         
-        public int? TargetDpi { get; set; }
+        internal int? TargetDpi { get; set; }
+        internal bool PerformScalingToTargetDpi { get; set; } = true;
         internal bool IsDocumentScoped { get; set; }
         
         public int Width => SkImage.Width;
         public int Height => SkImage.Height;
+
+        private const float ImageSizeSimilarityToleranceMax = 1.1f;
+        private const float ImageSizeSimilarityToleranceMin = 1 / ImageSizeSimilarityToleranceMax;
         
         private Image(SKImage image)
         {
@@ -31,28 +35,44 @@ namespace QuestPDF.Infrastructure
 
         internal SKImage GetVersionOfSize(Size size)
         {
-            if (size.Width > Width || size.Height > Height)
+            if (!PerformScalingToTargetDpi)
                 return SkImage;
+
+            var scalingFactor = TargetDpi.Value / (float)DocumentMetadata.DefaultPdfDpi;
+            var targetResolution = new Size(size.Width * scalingFactor, size.Height * scalingFactor);
+            
+            if (targetResolution.Width > Width || targetResolution.Height > Height)
+                return SkImage;
+            
+            ScaledImageCache ??= new List<(Size size, SKImage image)>();
             
-            var target = SKImage.Create(new SKImageInfo((int)size.Width, (int)size.Height));
-            SkImage.ScalePixels(target.PeekPixels(), SKFilterQuality.High);
+            foreach (var imageCache in ScaledImageCache)
+            {
+                if (HasSimilarSize(imageCache.size, targetResolution))
+                    return imageCache.image;
+            }
 
-            return target;
+            var scaledImage = ScaleImage(SkImage, targetResolution);
+            ScaledImageCache.Add((targetResolution, scaledImage));
+
+            return scaledImage;
+     
+            static SKImage ScaleImage(SKImage originalImage, Size targetSize)
+            {
+                var imageInfo = new SKImageInfo((int)targetSize.Width, (int)targetSize.Height);
+                var target = SKImage.Create(imageInfo);
+                originalImage.ScalePixels(target.PeekPixels(), SKFilterQuality.High);
 
-            // bool ShouldApplyScaling()
-            // {
-            //     const float tolerance = 1.1f;
-            //     
-            //     if (width > Width / tolerance || height > Height / tolerance)
-            //         return false;
-            //     
-            //     SkImage.sca
-            // }
+                return target;
+            }
             
-            static bool HasSimilarSize(Size a, Size b, float tolerance)
+            static bool HasSimilarSize(Size a, Size b)
             {
-                return (a.Width / b.Width > 1 / tolerance && a.Width / b.Width < tolerance) ||
-                       (a.Height / b.Height > 1 / tolerance && a.Height / b.Height < tolerance);
+                var widthRatio = a.Width / b.Width;
+                var heightRatio = a.Height / b.Height;
+
+                return widthRatio is > ImageSizeSimilarityToleranceMin and < ImageSizeSimilarityToleranceMax &&
+                       heightRatio is > ImageSizeSimilarityToleranceMin and < ImageSizeSimilarityToleranceMax;
             }
         }
 
@@ -101,7 +121,13 @@ namespace QuestPDF.Infrastructure
             TargetDpi = dpi;
             return this;
         }
-        
+
+        public Image ScaleToTargetDpi(bool value = true)
+        {
+            PerformScalingToTargetDpi = value;
+            return this;
+        }
+
         #endregion
     }
 }