Added support for image DPI scaling
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
@@ -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,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
var target = SKImage.Create(new SKImageInfo((int)size.Width, (int)size.Height));
|
||||
SkImage.ScalePixels(target.PeekPixels(), SKFilterQuality.High);
|
||||
|
||||
return target;
|
||||
|
||||
// bool ShouldApplyScaling()
|
||||
// {
|
||||
// const float tolerance = 1.1f;
|
||||
//
|
||||
// if (width > Width / tolerance || height > Height / tolerance)
|
||||
// return false;
|
||||
//
|
||||
// SkImage.sca
|
||||
// }
|
||||
ScaledImageCache ??= new List<(Size size, SKImage image)>();
|
||||
|
||||
static bool HasSimilarSize(Size a, Size b, float tolerance)
|
||||
foreach (var imageCache in ScaledImageCache)
|
||||
{
|
||||
return (a.Width / b.Width > 1 / tolerance && a.Width / b.Width < tolerance) ||
|
||||
(a.Height / b.Height > 1 / tolerance && a.Height / b.Height < tolerance);
|
||||
if (HasSimilarSize(imageCache.size, targetResolution))
|
||||
return imageCache.image;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
static bool HasSimilarSize(Size a, Size b)
|
||||
{
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user