diff --git a/Source/QuestPDF.Examples/ImageExamples.cs b/Source/QuestPDF.Examples/ImageExamples.cs index bd81b65..48d4db1 100644 --- a/Source/QuestPDF.Examples/ImageExamples.cs +++ b/Source/QuestPDF.Examples/ImageExamples.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Linq; using NUnit.Framework; using QuestPDF.Drawing.Exceptions; using QuestPDF.Examples.Engine; @@ -79,11 +80,11 @@ namespace QuestPDF.Examples .ShowResults() .Render(page => { - page.Padding(10).Column(column => + page.Padding(15).Column(column => { - column.Spacing(10); + column.Spacing(15); - column.Item().Image("photo.jpg").WithRasterDpi(16); + column.Item().Image("photo.jpg").WithRasterDpi(16).FitUnproportionally(); column.Item().Image("photo.jpg").WithRasterDpi(72); }); }); @@ -99,9 +100,9 @@ namespace QuestPDF.Examples .ShowResults() .Render(page => { - page.Padding(10).Column(column => + page.Padding(15).Column(column => { - column.Spacing(10); + column.Spacing(15); column.Item().Image("photo.jpg").WithCompressionQuality(ImageCompressionQuality.VeryLow).WithRasterDpi(72); column.Item().Image("photo.jpg").WithCompressionQuality(ImageCompressionQuality.High).WithRasterDpi(72); @@ -109,6 +110,34 @@ namespace QuestPDF.Examples }); } + [Test] + public void ReusingImage_With() + { + RenderingTest + .Create() + .PageSize(400, 600) + .ProduceImages() + .ShowResults() + .Render(page => + { + page.Padding(15).Column(column => + { + column.Spacing(15); + + var image = Image.FromFile("checkbox.png"); + + foreach (var i in Enumerable.Range(0, 5)) + { + column.Item().Row(row => + { + row.AutoItem().Width(24).Image(image); + row.RelativeItem().PaddingLeft(8).AlignMiddle().Text(Placeholders.Label()).FontSize(16); + }); + } + }); + }); + } + [Test] public void Exception() { diff --git a/Source/QuestPDF.Examples/QuestPDF.Examples.csproj b/Source/QuestPDF.Examples/QuestPDF.Examples.csproj index 5f07fa7..4547520 100644 --- a/Source/QuestPDF.Examples/QuestPDF.Examples.csproj +++ b/Source/QuestPDF.Examples/QuestPDF.Examples.csproj @@ -38,6 +38,9 @@ PreserveNewest + + PreserveNewest + diff --git a/Source/QuestPDF.Examples/checkbox.png b/Source/QuestPDF.Examples/checkbox.png new file mode 100644 index 0000000..57422a7 Binary files /dev/null and b/Source/QuestPDF.Examples/checkbox.png differ diff --git a/Source/QuestPDF.UnitTests/ImageTests.cs b/Source/QuestPDF.UnitTests/ImageTests.cs index 3c70a33..5db35d9 100644 --- a/Source/QuestPDF.UnitTests/ImageTests.cs +++ b/Source/QuestPDF.UnitTests/ImageTests.cs @@ -85,7 +85,7 @@ namespace QuestPDF.UnitTests { container.Column(column => { - var sharedImage = DocumentImage.FromBinaryData(photo).DisposeAfterDocumentGeneration(); + var sharedImage = DocumentImage.FromBinaryData(photo); foreach (var i in Enumerable.Range(0, 10)) column.Item().Image(sharedImage); diff --git a/Source/QuestPDF/Drawing/DocumentGenerator.cs b/Source/QuestPDF/Drawing/DocumentGenerator.cs index fd64509..d1c07ee 100644 --- a/Source/QuestPDF/Drawing/DocumentGenerator.cs +++ b/Source/QuestPDF/Drawing/DocumentGenerator.cs @@ -52,7 +52,7 @@ namespace QuestPDF.Drawing var documentSettings = document.GetSettings(); documentSettings.ImageRasterDpi = settings.RasterDpi; var canvas = new ImageCanvas(settings); - RenderDocument(canvas, document, documentSettings); + RenderDocument(canvas, document, documentSettings, useOriginalImages: true); return canvas.Images; } @@ -93,7 +93,7 @@ namespace QuestPDF.Drawing return canvas.Pictures; } - internal static void RenderDocument(TCanvas canvas, IDocument document, DocumentSettings settings) + internal static void RenderDocument(TCanvas canvas, IDocument document, DocumentSettings settings, bool useOriginalImages = false) where TCanvas : ICanvas, IRenderingCanvas { var container = new DocumentContainer(); @@ -102,7 +102,7 @@ namespace QuestPDF.Drawing ApplyInheritedAndGlobalTexStyle(content, TextStyle.Default); ApplyContentDirection(content, settings.ContentDirection); - ApplyDefaultImageConfiguration(content, settings.ImageRasterDpi, settings.ImageCompressionQuality); + ApplyDefaultImageConfiguration(content, settings.ImageRasterDpi, settings.ImageCompressionQuality, useOriginalImages); var debuggingState = Settings.EnableDebugging ? ApplyDebugging(content) : null; @@ -229,7 +229,7 @@ namespace QuestPDF.Drawing ApplyContentDirection(child, direction); } - internal static void ApplyDefaultImageConfiguration(this Element? content, int imageRasterDpi, ImageCompressionQuality imageCompressionQuality) + internal static void ApplyDefaultImageConfiguration(this Element? content, int imageRasterDpi, ImageCompressionQuality imageCompressionQuality, bool useOriginalImages) { content.VisitChildren(x => { @@ -237,25 +237,28 @@ namespace QuestPDF.Drawing { image.TargetDpi ??= imageRasterDpi; image.CompressionQuality ??= imageCompressionQuality; + image.UseOriginalImage |= useOriginalImages; } if (x is QuestPDF.Elements.DynamicImage dynamicImage) { dynamicImage.TargetDpi ??= imageRasterDpi; dynamicImage.CompressionQuality ??= imageCompressionQuality; + dynamicImage.UseOriginalImage |= useOriginalImages; } if (x is DynamicHost dynamicHost) { dynamicHost.ImageTargetDpi ??= imageRasterDpi; dynamicHost.ImageCompressionQuality ??= imageCompressionQuality; + dynamicHost.UseOriginalImage |= useOriginalImages; } if (x is TextBlock textBlock) { foreach (var textBlockElement in textBlock.Items.OfType()) { - textBlockElement.Element.ApplyDefaultImageConfiguration(imageRasterDpi, imageCompressionQuality); + textBlockElement.Element.ApplyDefaultImageConfiguration(imageRasterDpi, imageCompressionQuality, useOriginalImages); } } }); diff --git a/Source/QuestPDF/Elements/Dynamic.cs b/Source/QuestPDF/Elements/Dynamic.cs index 3d1dbcc..f15a404 100644 --- a/Source/QuestPDF/Elements/Dynamic.cs +++ b/Source/QuestPDF/Elements/Dynamic.cs @@ -16,6 +16,7 @@ namespace QuestPDF.Elements internal int? ImageTargetDpi { get; set; } internal ImageCompressionQuality? ImageCompressionQuality { get; set; } + internal bool UseOriginalImage { get; set; } public DynamicHost(DynamicComponentProxy child) { @@ -63,6 +64,7 @@ namespace QuestPDF.Elements ImageTargetDpi = ImageTargetDpi.Value, ImageCompressionQuality = ImageCompressionQuality.Value, + UseOriginalImage = UseOriginalImage, PageNumber = PageContext.CurrentPage, TotalPages = PageContext.GetLocation(Infrastructure.PageContext.DocumentLocation).PageEnd, @@ -88,6 +90,7 @@ namespace QuestPDF.Elements internal int ImageTargetDpi { get; set; } internal ImageCompressionQuality ImageCompressionQuality { get; set; } + internal bool UseOriginalImage { get; set; } public int PageNumber { get; internal set; } public int TotalPages { get; internal set; } @@ -100,7 +103,7 @@ namespace QuestPDF.Elements container.ApplyInheritedAndGlobalTexStyle(TextStyle); container.ApplyContentDirection(ContentDirection); - container.ApplyDefaultImageConfiguration(ImageTargetDpi, ImageCompressionQuality); + container.ApplyDefaultImageConfiguration(ImageTargetDpi, ImageCompressionQuality, UseOriginalImage); container.InjectDependencies(PageContext, Canvas); container.VisitChildren(x => (x as IStateResettable)?.ResetState()); diff --git a/Source/QuestPDF/Elements/DynamicImage.cs b/Source/QuestPDF/Elements/DynamicImage.cs index 4441c63..d5d7028 100644 --- a/Source/QuestPDF/Elements/DynamicImage.cs +++ b/Source/QuestPDF/Elements/DynamicImage.cs @@ -13,6 +13,7 @@ namespace QuestPDF.Elements { internal int? TargetDpi { get; set; } internal ImageCompressionQuality? CompressionQuality { get; set; } + internal bool UseOriginalImage { get; set; } public GenerateDynamicImageDelegate? Source { get; set; } internal override SpacePlan Measure(Size availableSpace) @@ -31,6 +32,13 @@ namespace QuestPDF.Elements return; using var originalImage = SKImage.FromEncodedData(imageData); + + if (UseOriginalImage) + { + Canvas.DrawImage(originalImage, Position.Zero, availableSpace); + return; + } + using var compressedImage = originalImage.CompressImage(CompressionQuality.Value); var targetImage = Helpers.Helpers.GetImageWithSmallerSize(originalImage, compressedImage); diff --git a/Source/QuestPDF/Fluent/ImageExtensions.cs b/Source/QuestPDF/Fluent/ImageExtensions.cs index 2b64836..c8b4fa7 100644 --- a/Source/QuestPDF/Fluent/ImageExtensions.cs +++ b/Source/QuestPDF/Fluent/ImageExtensions.cs @@ -109,19 +109,19 @@ namespace QuestPDF.Fluent { public static ImageDescriptor Image(this IContainer parent, byte[] imageData) { - var image = Infrastructure.Image.FromBinaryData(imageData).DisposeAfterDocumentGeneration(); + var image = Infrastructure.Image.FromBinaryData(imageData); return parent.Image(image); } public static ImageDescriptor Image(this IContainer parent, string filePath) { - var image = Infrastructure.Image.FromFile(filePath).DisposeAfterDocumentGeneration(); + var image = Infrastructure.Image.FromFile(filePath); return parent.Image(image); } public static ImageDescriptor Image(this IContainer parent, Stream fileStream) { - var image = Infrastructure.Image.FromStream(fileStream).DisposeAfterDocumentGeneration(); + var image = Infrastructure.Image.FromStream(fileStream); return parent.Image(image); } diff --git a/Source/QuestPDF/Infrastructure/Image.cs b/Source/QuestPDF/Infrastructure/Image.cs index 5ed6579..eb1bc73 100644 --- a/Source/QuestPDF/Infrastructure/Image.cs +++ b/Source/QuestPDF/Infrastructure/Image.cs @@ -13,28 +13,29 @@ namespace QuestPDF.Infrastructure internal ImageCompressionQuality CompressionQuality { get; set; } } - public class Image : IDisposable + public class Image { internal SKImage SkImage { get; } internal ImageSize Size { get; } - internal bool IsDocumentScoped { get; set; } + internal bool IsDocumentScoped { get; } internal LinkedList<(GetImageVersionRequest request, SKImage image)> ScaledImageCache { get; } = new(); - private Image(SKImage image) + internal Image(SKImage image, bool isDocumentScoped) { SkImage = image; + IsDocumentScoped = isDocumentScoped; Size = new ImageSize(image.Width, image.Height); } - - public void Dispose() + + internal void Dispose() { SkImage.Dispose(); foreach (var cacheKey in ScaledImageCache) cacheKey.image.Dispose(); } - + #region Scaling Image internal SKImage GetVersionOfSize(GetImageVersionRequest request) @@ -79,15 +80,51 @@ namespace QuestPDF.Infrastructure if (image == null) throw new DocumentComposeException("Cannot load or decode provided image."); - return new Image(image); + return new Image(image, true); } #endregion + } - internal Image DisposeAfterDocumentGeneration() + public class SharedImage : Image, IDisposable + { + internal SharedImage(SKImage image) : base(image, false) { - IsDocumentScoped = true; - return this; + } + + public void Dispose() + { + base.Dispose(); + } + + #region public constructors + + public static new SharedImage FromBinaryData(byte[] imageData) + { + return CreateImage(SKImage.FromEncodedData(imageData)); + } + + public static new SharedImage FromFile(string filePath) + { + return CreateImage(SKImage.FromEncodedData(filePath)); + } + + public static new SharedImage FromStream(Stream fileStream) + { + return CreateImage(SKImage.FromEncodedData(fileStream)); + } + + private static SharedImage CreateImage(SKImage? image) + { + if (image == null) + throw new DocumentComposeException("Cannot load or decode provided image."); + + var result = new SharedImage(image); + + return result; + } + + #endregion } } \ No newline at end of file