Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f67b65e040 | |||
| c5f77e1fd1 | |||
| f60aea0f83 | |||
| b74e0327fa |
@@ -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()
|
||||
{
|
||||
|
||||
@@ -38,6 +38,9 @@
|
||||
<None Update="photo.jpg">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="checkbox.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 3.4 KiB |
@@ -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);
|
||||
|
||||
@@ -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>(TCanvas canvas, IDocument document, DocumentSettings settings)
|
||||
internal static void RenderDocument<TCanvas>(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>())
|
||||
{
|
||||
textBlockElement.Element.ApplyDefaultImageConfiguration(imageRasterDpi, imageCompressionQuality);
|
||||
textBlockElement.Element.ApplyDefaultImageConfiguration(imageRasterDpi, imageCompressionQuality, useOriginalImages);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -42,7 +42,7 @@ namespace QuestPDF.Drawing
|
||||
public override void EndPage()
|
||||
{
|
||||
Canvas.Save();
|
||||
var image = Surface.Snapshot().Encode(Settings.Format, Settings.Quality).ToArray();
|
||||
var image = Surface.Snapshot().Encode(Settings.ImageFormat.ToSkImageFormat(), Settings.ImageCompressionQuality.ToQualityValue()).ToArray();
|
||||
Images.Add(image);
|
||||
|
||||
Canvas.Dispose();
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -62,6 +62,17 @@ namespace QuestPDF.Helpers
|
||||
return size.Width < 0f || size.Height < 0f;
|
||||
}
|
||||
|
||||
internal static SKEncodedImageFormat ToSkImageFormat(this ImageFormat format)
|
||||
{
|
||||
return format switch
|
||||
{
|
||||
ImageFormat.Jpeg => SKEncodedImageFormat.Jpeg,
|
||||
ImageFormat.Png => SKEncodedImageFormat.Png,
|
||||
ImageFormat.Webp=> SKEncodedImageFormat.Webp,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(format), format, null)
|
||||
};
|
||||
}
|
||||
|
||||
internal static int ToQualityValue(this ImageCompressionQuality quality)
|
||||
{
|
||||
return quality switch
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace QuestPDF.Infrastructure
|
||||
{
|
||||
public enum ImageFormat
|
||||
{
|
||||
Jpeg,
|
||||
Png,
|
||||
Webp
|
||||
}
|
||||
}
|
||||
@@ -7,20 +7,19 @@ namespace QuestPDF.Infrastructure
|
||||
/// <summary>
|
||||
/// The file format used to encode the image(s).
|
||||
/// </summary>
|
||||
public SKEncodedImageFormat Format { get; set; } = SKEncodedImageFormat.Png;
|
||||
public ImageFormat ImageFormat { get; set; } = ImageFormat.Png;
|
||||
|
||||
/// <summary>
|
||||
/// The quality level to use for the image(s). This is in the range from 0-100.
|
||||
/// Encoding quality controls the trade-off between size and quality.
|
||||
/// The default value is "high quality".
|
||||
/// </summary>
|
||||
public int Quality { get; set; } = 100;
|
||||
public ImageCompressionQuality ImageCompressionQuality { get; set; } = ImageCompressionQuality.VeryHigh;
|
||||
|
||||
/// <summary>
|
||||
/// The DPI (pixels-per-inch) at which images and features without native PDF support will be rasterized.
|
||||
/// A larger DPI would create a PDF that reflects the original intent with better fidelity, but it can make for larger PDF files too, which would use more memory while rendering, and it would be slower to be processed or sent online or to printer.
|
||||
/// When generating images, this parameter also controls the resolution of the generated content.
|
||||
/// The DPI (pixels-per-inch) at which the document will be rasterized. This parameter controls the resolution of produced images.
|
||||
/// Default value is 144.
|
||||
/// </summary>
|
||||
public int RasterDpi { get; set; } = 144;
|
||||
public int RasterDpi { get; set; } = DocumentSettings.DefaultRasterDpi * 2;
|
||||
|
||||
|
||||
public static ImageGenerationSettings Default => new ImageGenerationSettings();
|
||||
|
||||
Reference in New Issue
Block a user