Image.cs 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using System.IO;
  5. using QuestPDF.Drawing.Exceptions;
  6. using QuestPDF.Helpers;
  7. using SkiaSharp;
  8. namespace QuestPDF.Infrastructure
  9. {
  10. internal record GetImageVersionRequest
  11. {
  12. internal ImageSize Resolution { get; set; }
  13. internal ImageCompressionQuality CompressionQuality { get; set; }
  14. }
  15. /// <summary>
  16. /// <para>Caches the image in local memory for efficient reuse.</para>
  17. /// <para>Optimizes the generation process, especially:</para>
  18. /// <para>- For images repeated in a single document to enhance performance and reduce output file size (e.g., an image used as list bullet icon).</para>
  19. /// <para>- When an image appears on multiple document types for increased generation performance (e.g., a company logo).</para>
  20. /// </summary>
  21. /// <remarks>
  22. /// This class is thread safe.
  23. /// </remarks>
  24. public class Image
  25. {
  26. internal SKImage SkImage { get; }
  27. internal ImageSize Size { get; }
  28. internal LinkedList<(GetImageVersionRequest request, SKImage image)> ScaledImageCache { get; } = new();
  29. internal Image(SKImage image)
  30. {
  31. SkImage = image;
  32. Size = new ImageSize(image.Width, image.Height);
  33. }
  34. ~Image()
  35. {
  36. SkImage.Dispose();
  37. foreach (var cacheKey in ScaledImageCache)
  38. cacheKey.image.Dispose();
  39. }
  40. #region Scaling Image
  41. internal SKImage GetVersionOfSize(GetImageVersionRequest request)
  42. {
  43. foreach (var cacheKey in ScaledImageCache)
  44. {
  45. if (cacheKey.request == request)
  46. return cacheKey.image;
  47. }
  48. var result = SkImage.ResizeAndCompressImage(request.Resolution, request.CompressionQuality);
  49. ScaledImageCache.AddLast((request, result));
  50. return result;
  51. }
  52. #endregion
  53. #region public constructors
  54. private const string CannotDecodeExceptionMessage = "Cannot decode the provided image.";
  55. internal static Image FromSkImage(SKImage image)
  56. {
  57. return new Image(image);
  58. }
  59. /// <summary>
  60. /// Loads the image from binary data.
  61. /// <a href="https://www.questpdf.com/api-reference/image.html">Learn more</a>
  62. /// </summary>
  63. /// <include file='../Resources/Documentation.xml' path='documentation/doc[@for="image.remarks"]/*' />
  64. public static Image FromBinaryData(byte[] imageData)
  65. {
  66. var image = SKImage.FromEncodedData(imageData);
  67. if (image == null)
  68. throw new DocumentComposeException(CannotDecodeExceptionMessage);
  69. return new Image(image);
  70. }
  71. /// <summary>
  72. /// Loads the image from a file with specified path.
  73. /// <a href="https://www.questpdf.com/api-reference/image.html">Learn more</a>
  74. /// </summary>
  75. /// <include file='../Resources/Documentation.xml' path='documentation/doc[@for="image.remarks"]/*' />
  76. public static Image FromFile(string filePath)
  77. {
  78. var image = SKImage.FromEncodedData(filePath);
  79. if (image == null)
  80. {
  81. throw File.Exists(filePath)
  82. ? new DocumentComposeException(CannotDecodeExceptionMessage)
  83. : new DocumentComposeException($"Cannot load provided image, file not found: ${filePath}");
  84. }
  85. return new Image(image);
  86. }
  87. /// <summary>
  88. /// Loads the image from a stream.
  89. /// <a href="https://www.questpdf.com/api-reference/image.html">Learn more</a>
  90. /// </summary>
  91. /// <include file='../Resources/Documentation.xml' path='documentation/doc[@for="image.remarks"]/*' />
  92. public static Image FromStream(Stream fileStream)
  93. {
  94. var image = SKImage.FromEncodedData(fileStream);
  95. if (image == null)
  96. throw new DocumentComposeException(CannotDecodeExceptionMessage);
  97. return new Image(image);
  98. }
  99. #endregion
  100. }
  101. }