Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f67b65e040 | |||
| c5f77e1fd1 | |||
| f60aea0f83 | |||
| b74e0327fa | |||
| f20d7af0c5 | |||
| 3a73886d70 | |||
| 045b2a43b8 |
@@ -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);
|
||||
|
||||
@@ -45,13 +45,14 @@ namespace QuestPDF.Drawing
|
||||
throw new ArgumentException("The library requires a Stream object with the 'seek' capability available (the CanSeek flag). Please consider using the MemoryStream class.");
|
||||
}
|
||||
|
||||
internal static ICollection<byte[]> GenerateImages(IDocument document)
|
||||
internal static ICollection<byte[]> GenerateImages(IDocument document, ImageGenerationSettings settings)
|
||||
{
|
||||
ValidateLicense();
|
||||
|
||||
var settings = document.GetSettings();
|
||||
var documentSettings = document.GetSettings();
|
||||
documentSettings.ImageRasterDpi = settings.RasterDpi;
|
||||
var canvas = new ImageCanvas(settings);
|
||||
RenderDocument(canvas, document, settings);
|
||||
RenderDocument(canvas, document, documentSettings, useOriginalImages: true);
|
||||
|
||||
return canvas.Images;
|
||||
}
|
||||
@@ -92,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();
|
||||
@@ -101,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;
|
||||
|
||||
@@ -228,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 =>
|
||||
{
|
||||
@@ -236,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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -7,12 +7,12 @@ namespace QuestPDF.Drawing
|
||||
{
|
||||
internal class ImageCanvas : SkiaCanvasBase
|
||||
{
|
||||
private DocumentSettings Settings { get; }
|
||||
private ImageGenerationSettings Settings { get; }
|
||||
private SKSurface Surface { get; set; }
|
||||
|
||||
internal ICollection<byte[]> Images { get; } = new List<byte[]>();
|
||||
|
||||
public ImageCanvas(DocumentSettings settings)
|
||||
public ImageCanvas(ImageGenerationSettings settings)
|
||||
{
|
||||
Settings = settings;
|
||||
}
|
||||
@@ -30,7 +30,7 @@ namespace QuestPDF.Drawing
|
||||
|
||||
public override void BeginPage(Size size)
|
||||
{
|
||||
var scalingFactor = Settings.ImageRasterDpi / (float) PageSizes.PointsPerInch;
|
||||
var scalingFactor = Settings.RasterDpi / (float) PageSizes.PointsPerInch;
|
||||
var imageInfo = new SKImageInfo((int) (size.Width * scalingFactor), (int) (size.Height * scalingFactor));
|
||||
|
||||
Surface = SKSurface.Create(imageInfo);
|
||||
@@ -42,7 +42,7 @@ namespace QuestPDF.Drawing
|
||||
public override void EndPage()
|
||||
{
|
||||
Canvas.Save();
|
||||
var image = Surface.Snapshot().Encode(SKEncodedImageFormat.Png, 100).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);
|
||||
|
||||
@@ -75,15 +75,27 @@ namespace QuestPDF.Fluent
|
||||
|
||||
public static IEnumerable<byte[]> GenerateImages(this IDocument document)
|
||||
{
|
||||
return DocumentGenerator.GenerateImages(document);
|
||||
return document.GenerateImages(ImageGenerationSettings.Default);
|
||||
}
|
||||
|
||||
|
||||
public static IEnumerable<byte[]> GenerateImages(this IDocument document, ImageGenerationSettings settings)
|
||||
{
|
||||
return DocumentGenerator.GenerateImages(document, settings);
|
||||
}
|
||||
|
||||
/// <param name="filePath">Method should return fileName for given index</param>
|
||||
public static void GenerateImages(this IDocument document, Func<int, string> filePath)
|
||||
{
|
||||
document.GenerateImages(filePath, ImageGenerationSettings.Default);
|
||||
}
|
||||
|
||||
/// <param name="filePath">Method should return fileName for given index</param>
|
||||
public static void GenerateImages(this IDocument document, Func<int, string> filePath, ImageGenerationSettings settings)
|
||||
{
|
||||
var index = 0;
|
||||
|
||||
foreach (var imageData in document.GenerateImages())
|
||||
foreach (var imageData in document.GenerateImages(settings))
|
||||
{
|
||||
var path = filePath(index);
|
||||
|
||||
|
||||
@@ -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,21 +13,22 @@ 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();
|
||||
|
||||
@@ -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
|
||||
{
|
||||
IsDocumentScoped = true;
|
||||
return this;
|
||||
}
|
||||
internal SharedImage(SKImage image) : base(image, false)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using SkiaSharp;
|
||||
|
||||
namespace QuestPDF.Infrastructure
|
||||
{
|
||||
public class ImageGenerationSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// The file format used to encode the image(s).
|
||||
/// </summary>
|
||||
public ImageFormat ImageFormat { get; set; } = ImageFormat.Png;
|
||||
|
||||
/// <summary>
|
||||
/// Encoding quality controls the trade-off between size and quality.
|
||||
/// The default value is "high quality".
|
||||
/// </summary>
|
||||
public ImageCompressionQuality ImageCompressionQuality { get; set; } = ImageCompressionQuality.VeryHigh;
|
||||
|
||||
/// <summary>
|
||||
/// 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; } = DocumentSettings.DefaultRasterDpi * 2;
|
||||
|
||||
|
||||
public static ImageGenerationSettings Default => new ImageGenerationSettings();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user