ImageDecoder.cs 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. using System.Diagnostics;
  2. using ImageMagick;
  3. using ImageMagick.Formats;
  4. using SkiaSharp;
  5. namespace PicView.Core.ImageDecoding
  6. {
  7. /// <summary>
  8. /// Provides methods for decoding various image formats.
  9. /// </summary>
  10. public static class ImageDecoder
  11. {
  12. /// <summary>
  13. /// Asynchronously reads and returns a MagickImage from the specified FileInfo.
  14. /// </summary>
  15. /// <param name="fileInfo">The FileInfo representing the image file.</param>
  16. /// <returns>A Task containing the MagickImage if successful, otherwise null.</returns>
  17. public static async Task<MagickImage?> GetMagickImageAsync(FileInfo fileInfo)
  18. {
  19. try
  20. {
  21. var magickImage = new MagickImage();
  22. MagickFormat format;
  23. var extension = fileInfo.Extension.ToLower();
  24. switch (extension)
  25. {
  26. case ".heic":
  27. case ".heif":
  28. magickImage.Settings.SetDefines(new HeicReadDefines
  29. {
  30. PreserveOrientation = true,
  31. DepthImage = true,
  32. });
  33. format = extension is ".heic" ? MagickFormat.Heic : MagickFormat.Heif;
  34. break;
  35. case ".jp2":
  36. magickImage.Settings.SetDefines(new Jp2ReadDefines
  37. {
  38. QualityLayers = 100,
  39. });
  40. format = MagickFormat.Jp2;
  41. break;
  42. case ".tif":
  43. case ".tiff":
  44. magickImage.Settings.SetDefines(new TiffReadDefines
  45. {
  46. IgnoreTags = new[]
  47. {
  48. "34022", // ColorTable
  49. "34025", // ImageColorValue
  50. "34026", // BackgroundColorValue
  51. "32928",
  52. },
  53. });
  54. format = MagickFormat.Tif;
  55. break;
  56. case ".psd":
  57. format = MagickFormat.Psd;
  58. break;
  59. default:
  60. format = MagickFormat.Unknown;
  61. break;
  62. }
  63. if (fileInfo.Length >= 2147483648)
  64. {
  65. await using var fileStream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read,
  66. FileShare.ReadWrite, bufferSize: 4096, useAsync: fileInfo.Length > 1e+8);
  67. // Fixes "The file is too long. This operation is currently limited to supporting files less than 2 gigabytes in size."
  68. // ReSharper disable once MethodHasAsyncOverload
  69. magickImage.Read(fileStream, format);
  70. }
  71. else
  72. {
  73. await magickImage.ReadAsync(fileInfo, format).ConfigureAwait(false);
  74. }
  75. magickImage?.AutoOrient();
  76. return magickImage;
  77. }
  78. catch (Exception e)
  79. {
  80. #if DEBUG
  81. Trace.WriteLine($"{nameof(GetMagickImageAsync)} {fileInfo.Name} exception, \n {e.Message}");
  82. #endif
  83. return null;
  84. }
  85. }
  86. /// <summary>
  87. /// Asynchronously reads and returns a MagickImage from the specified FileInfo for SVG format.
  88. /// </summary>
  89. /// <param name="fileInfo">The FileInfo representing the SVG file.</param>
  90. /// <param name="magickFormat">The MagickFormat for the SVG image (SVG or SVGZ).</param>
  91. /// <returns>A Task containing the MagickImage for SVG if successful, otherwise null.</returns>
  92. public static async Task<MagickImage?> GetMagickSvgAsync(FileInfo fileInfo, MagickFormat magickFormat)
  93. {
  94. try
  95. {
  96. var magickImage = new MagickImage
  97. {
  98. Quality = 100,
  99. ColorSpace = ColorSpace.Transparent,
  100. BackgroundColor = MagickColors.Transparent,
  101. Format = magickFormat,
  102. };
  103. // Streams with a length larger than 2GB are not supported, read from file instead
  104. if (fileInfo.Length >= 2147483648)
  105. {
  106. await Task.Run(() =>
  107. {
  108. magickImage = new MagickImage();
  109. magickImage.Read(fileInfo);
  110. }).ConfigureAwait(false);
  111. }
  112. else
  113. {
  114. await using var fileStream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read,
  115. FileShare.ReadWrite, bufferSize: 4096, useAsync: fileInfo.Length > 1e+8);
  116. var data = new byte[fileStream.Length];
  117. // ReSharper disable once MustUseReturnValue
  118. await fileStream.ReadAsync(data.AsMemory(0, (int)fileStream.Length)).ConfigureAwait(false);
  119. magickImage.Read(data);
  120. }
  121. magickImage.Settings.BackgroundColor = MagickColors.Transparent;
  122. magickImage.Settings.FillColor = MagickColors.Transparent;
  123. magickImage.Settings.SetDefine("svg:xml-parse-huge", "true");
  124. return magickImage;
  125. }
  126. catch (Exception e)
  127. {
  128. #if DEBUG
  129. Trace.WriteLine($"{nameof(GetMagickSvgAsync)} {fileInfo.Name} exception, \n {e.Message}");
  130. #endif
  131. return null;
  132. }
  133. }
  134. /// <summary>
  135. /// Asynchronously reads and returns an SKBitmap from the specified FileInfo.
  136. /// </summary>
  137. /// <param name="fileInfo">The FileInfo representing the image file.</param>
  138. /// <returns>A Task containing the SKBitmap if successful, otherwise null.</returns>
  139. // ReSharper disable once InconsistentNaming
  140. public static async Task<SKBitmap?> GetSKBitmapAsync(this FileInfo fileInfo)
  141. {
  142. try
  143. {
  144. await using var fileStream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read,
  145. FileShare.ReadWrite, bufferSize: 4096, useAsync: fileInfo.Length > 1e+8);
  146. return SKBitmap.Decode(fileStream);
  147. }
  148. catch (Exception e)
  149. {
  150. #if DEBUG
  151. Trace.WriteLine($"{nameof(GetSKBitmapAsync)} {fileInfo.Name} exception:\n{e.Message}");
  152. #endif
  153. return null;
  154. }
  155. }
  156. }
  157. }