Browse Source

Begin process to decouple shareable code from WPF; Add new core project with an image decoder

Ruben 2 years ago
parent
commit
f701d764d2

+ 170 - 0
src/PicView.Core/ImageDecoding/ImageDecoder.cs

@@ -0,0 +1,170 @@
+using System.Diagnostics;
+using ImageMagick;
+using ImageMagick.Formats;
+using SkiaSharp;
+
+namespace PicView.Core.ImageDecoding
+{
+    /// <summary>
+    /// Provides methods for decoding various image formats.
+    /// </summary>
+    public static class ImageDecoder
+    {
+        /// <summary>
+        /// Asynchronously reads and returns a MagickImage from the specified FileInfo.
+        /// </summary>
+        /// <param name="fileInfo">The FileInfo representing the image file.</param>
+        /// <returns>A Task containing the MagickImage if successful, otherwise null.</returns>
+        public static async Task<MagickImage?> GetMagickImageAsync(FileInfo fileInfo)
+        {
+            try
+            {
+                var magickImage = new MagickImage();
+                MagickFormat format;
+                var extension = fileInfo.Extension.ToLower();
+                switch (extension)
+                {
+                    case ".heic":
+                    case ".heif":
+                        magickImage.Settings.SetDefines(new HeicReadDefines
+                        {
+                            PreserveOrientation = true,
+                            DepthImage = true,
+                        });
+                        format = extension is ".heic" ? MagickFormat.Heic : MagickFormat.Heif;
+                        break;
+
+                    case ".jp2":
+                        magickImage.Settings.SetDefines(new Jp2ReadDefines
+                        {
+                            QualityLayers = 100,
+                        });
+                        format = MagickFormat.Jp2;
+                        break;
+
+                    case ".tif":
+                    case ".tiff":
+                        magickImage.Settings.SetDefines(new TiffReadDefines
+                        {
+                            IgnoreTags = new[]
+                            {
+                                "34022", // ColorTable
+                                "34025", // ImageColorValue
+                                "34026", // BackgroundColorValue
+                                "32928",
+                            },
+                        });
+                        format = MagickFormat.Tif;
+                        break;
+
+                    case ".psd":
+                        format = MagickFormat.Psd;
+                        break;
+
+                    default:
+                        format = MagickFormat.Unknown;
+                        break;
+                }
+
+                if (fileInfo.Length >= 2147483648)
+                {
+                    await using var fileStream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read,
+                        FileShare.ReadWrite, bufferSize: 4096, useAsync: fileInfo.Length > 1e+8);
+
+                    // Fixes "The file is too long. This operation is currently limited to supporting files less than 2 gigabytes in size."
+                    // ReSharper disable once MethodHasAsyncOverload
+                    magickImage.Read(fileStream, format);
+                }
+                else
+                {
+                    await magickImage.ReadAsync(fileInfo, format).ConfigureAwait(false);
+                }
+
+                magickImage?.AutoOrient();
+                return magickImage;
+            }
+            catch (Exception e)
+            {
+#if DEBUG
+                Trace.WriteLine($"{nameof(GetMagickImageAsync)} {fileInfo.Name} exception, \n {e.Message}");
+#endif
+                return null;
+            }
+        }
+
+        /// <summary>
+        /// Asynchronously reads and returns a MagickImage from the specified FileInfo for SVG format.
+        /// </summary>
+        /// <param name="fileInfo">The FileInfo representing the SVG file.</param>
+        /// <param name="magickFormat">The MagickFormat for the SVG image (SVG or SVGZ).</param>
+        /// <returns>A Task containing the MagickImage for SVG if successful, otherwise null.</returns>
+        public static async Task<MagickImage?> GetMagickSvgAsync(FileInfo fileInfo, MagickFormat magickFormat)
+        {
+            try
+            {
+                var magickImage = new MagickImage
+                {
+                    Quality = 100,
+                    ColorSpace = ColorSpace.Transparent,
+                    BackgroundColor = MagickColors.Transparent,
+                    Format = magickFormat,
+                };
+
+                // Streams with a length larger than 2GB are not supported, read from file instead
+                if (fileInfo.Length >= 2147483648)
+                {
+                    await Task.Run(() =>
+                    {
+                        magickImage = new MagickImage();
+                        magickImage.Read(fileInfo);
+                    }).ConfigureAwait(false);
+                }
+                else
+                {
+                    await using var fileStream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read,
+                        FileShare.ReadWrite, bufferSize: 4096, useAsync: fileInfo.Length > 1e+8);
+                    var data = new byte[fileStream.Length];
+                    // ReSharper disable once MustUseReturnValue
+                    await fileStream.ReadAsync(data.AsMemory(0, (int)fileStream.Length)).ConfigureAwait(false);
+                    magickImage.Read(data);
+                }
+
+                magickImage.Settings.BackgroundColor = MagickColors.Transparent;
+                magickImage.Settings.FillColor = MagickColors.Transparent;
+                magickImage.Settings.SetDefine("svg:xml-parse-huge", "true");
+
+                return magickImage;
+            }
+            catch (Exception e)
+            {
+#if DEBUG
+                Trace.WriteLine($"{nameof(GetMagickSvgAsync)} {fileInfo.Name} exception, \n {e.Message}");
+#endif
+                return null;
+            }
+        }
+
+        /// <summary>
+        /// Asynchronously reads and returns an SKBitmap from the specified FileInfo.
+        /// </summary>
+        /// <param name="fileInfo">The FileInfo representing the image file.</param>
+        /// <returns>A Task containing the SKBitmap if successful, otherwise null.</returns>
+        // ReSharper disable once InconsistentNaming
+        public static async Task<SKBitmap?> GetSKBitmapAsync(this FileInfo fileInfo)
+        {
+            try
+            {
+                await using var fileStream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read,
+                    FileShare.ReadWrite, bufferSize: 4096, useAsync: fileInfo.Length > 1e+8);
+                return SKBitmap.Decode(fileStream);
+            }
+            catch (Exception e)
+            {
+#if DEBUG
+                Trace.WriteLine($"{nameof(GetSKBitmapAsync)} {fileInfo.Name} exception:\n{e.Message}");
+#endif
+                return null;
+            }
+        }
+    }
+}

+ 14 - 0
src/PicView.Core/PicView.Core.csproj

@@ -0,0 +1,14 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net8.0</TargetFramework>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <Nullable>enable</Nullable>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Magick.NET-Q8-OpenMP-x64" Version="13.5.0" />
+    <PackageReference Include="SkiaSharp" Version="2.88.6" />
+  </ItemGroup>
+
+</Project>

+ 7 - 1
src/PicView.sln

@@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
 # Visual Studio Version 17
 VisualStudioVersion = 17.8.34112.27
 MinimumVisualStudioVersion = 10.0.40219.1
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PicView", "PicView\PicView.csproj", "{9B5425DC-4329-4A7A-A0C5-F93161F77245}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PicView.WPF", "PicView\PicView.WPF.csproj", "{9B5425DC-4329-4A7A-A0C5-F93161F77245}"
 	ProjectSection(ProjectDependencies) = postProject
 		{47DE1EC3-CD33-43E1-857F-4820C6AD16B6} = {47DE1EC3-CD33-43E1-857F-4820C6AD16B6}
 	EndProjectSection
@@ -12,6 +12,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PicView.Tools", "PicView.To
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "XamlAnimatedGif", "XamlAnimatedGif\XamlAnimatedGif.csproj", "{1D4E804D-33E8-46CC-B4FC-AC6A84F5085A}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PicView.Core", "PicView.Core\PicView.Core.csproj", "{17C701BC-1F25-4995-A7E5-1360EBC5904B}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -30,6 +32,10 @@ Global
 		{1D4E804D-33E8-46CC-B4FC-AC6A84F5085A}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{1D4E804D-33E8-46CC-B4FC-AC6A84F5085A}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{1D4E804D-33E8-46CC-B4FC-AC6A84F5085A}.Release|Any CPU.Build.0 = Release|Any CPU
+		{17C701BC-1F25-4995-A7E5-1360EBC5904B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{17C701BC-1F25-4995-A7E5-1360EBC5904B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{17C701BC-1F25-4995-A7E5-1360EBC5904B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{17C701BC-1F25-4995-A7E5-1360EBC5904B}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE

+ 1 - 1
src/PicView/ChangeImage/FastPic.cs

@@ -89,7 +89,7 @@ namespace PicView.ChangeImage
                 if (preLoadValue is null)
                 {
                     var fileInfo = new FileInfo(Pics[FolderIndex]);
-                    var bitmapSource = await ImageDecoder.ReturnBitmapSourceAsync(fileInfo).ConfigureAwait(false);
+                    var bitmapSource = await Image2BitmapSource.ReturnBitmapSourceAsync(fileInfo).ConfigureAwait(false);
                     preLoadValue = new PreLoader.PreLoadValue(bitmapSource, fileInfo);
                 }
             }

+ 1 - 1
src/PicView/ChangeImage/LoadPic.cs

@@ -484,7 +484,7 @@ namespace PicView.ChangeImage
 
                 if (preLoadValue is null)
                 {
-                    var bitmapSource = await ImageDecoder.ReturnBitmapSourceAsync(fileInfo).ConfigureAwait(false);
+                    var bitmapSource = await Image2BitmapSource.ReturnBitmapSourceAsync(fileInfo).ConfigureAwait(false);
                     preLoadValue = new PreLoader.PreLoadValue(bitmapSource, fileInfo);
                 }
 

+ 1 - 1
src/PicView/ChangeImage/PreLoader.cs

@@ -86,7 +86,7 @@ namespace PicView.ChangeImage
                 if (add)
                 {
                     fileInfo ??= new FileInfo(Pics[index]);
-                    bitmapSource ??= await ImageDecoder.ReturnBitmapSourceAsync(fileInfo).ConfigureAwait(false);
+                    bitmapSource ??= await Image2BitmapSource.ReturnBitmapSourceAsync(fileInfo).ConfigureAwait(false);
                     preLoadValue.BitmapSource = bitmapSource;
                     preLoadValue.FileInfo = fileInfo;
 #if DEBUG

+ 1 - 1
src/PicView/ChangeImage/QuickLoad.cs

@@ -43,7 +43,7 @@ namespace PicView.ChangeImage
                 return;
             }
 
-            var bitmapSource = await ImageDecoder.ReturnBitmapSourceAsync(fileInfo).ConfigureAwait(false);
+            var bitmapSource = await Image2BitmapSource.ReturnBitmapSourceAsync(fileInfo).ConfigureAwait(false);
             await mainWindow.MainImage.Dispatcher.InvokeAsync(() =>
             {
                 mainWindow.MainImage.Source = bitmapSource;

+ 1 - 1
src/PicView/Editing/Crop/CropFunctions.cs

@@ -158,7 +158,7 @@ namespace PicView.Editing.Crop
 
             if (effectApplied)
             {
-                var frame = ImageDecoder.GetRenderedBitmapFrame();
+                var frame = Image2BitmapSource.GetRenderedBitmapFrame();
                 var croppedFrame = new CroppedBitmap(frame, crop);
                 Clipboard.SetImage(croppedFrame);
             }

+ 3 - 3
src/PicView/FileHandling/CopyPaste.cs

@@ -186,7 +186,7 @@ namespace PicView.FileHandling
                 {
                     if (ConfigureWindows.GetMainWindow.MainImage.Effect != null)
                     {
-                        pic = ImageDecoder.GetRenderedBitmapFrame();
+                        pic = Image2BitmapSource.GetRenderedBitmapFrame();
                     }
                     else
                     {
@@ -213,12 +213,12 @@ namespace PicView.FileHandling
                         BitmapSource bitmap;
                         if (preloadValue is null)
                         {
-                            bitmap = await ImageDecoder.ReturnBitmapSourceAsync(new FileInfo(Pics[id.Value]));
+                            bitmap = await Image2BitmapSource.ReturnBitmapSourceAsync(new FileInfo(Pics[id.Value]));
                         }
                         else
                         {
                             bitmap = preloadValue.BitmapSource ??
-                                     await ImageDecoder.ReturnBitmapSourceAsync(new FileInfo(Pics[id.Value]));
+                                     await Image2BitmapSource.ReturnBitmapSourceAsync(new FileInfo(Pics[id.Value]));
                         }
 
                         try

+ 1 - 1
src/PicView/FileHandling/HttpFunctions.cs

@@ -47,7 +47,7 @@ namespace PicView.FileHandling
             switch (check)
             {
                 default:
-                    var pic = await ImageDecoder.ReturnBitmapSourceAsync(new FileInfo(check)).ConfigureAwait(false);
+                    var pic = await Image2BitmapSource.ReturnBitmapSourceAsync(new FileInfo(check)).ConfigureAwait(false);
                     await UpdateImage.UpdateImageAsync(url, pic,
                             Path.GetExtension(url).Contains(".gif", StringComparison.OrdinalIgnoreCase), destination)
                         .ConfigureAwait(false);

+ 2 - 2
src/PicView/ImageHandling/Base64.cs

@@ -90,12 +90,12 @@ namespace PicView.ImageHandling
             BitmapFrame frame;
             if (path is null)
             {
-                frame = await ConfigureWindows.GetMainWindow.Dispatcher.InvokeAsync(ImageDecoder
+                frame = await ConfigureWindows.GetMainWindow.Dispatcher.InvokeAsync(Image2BitmapSource
                     .GetRenderedBitmapFrame);
             }
             else
             {
-                var bitmapSource = await ImageDecoder.ReturnBitmapSourceAsync(new FileInfo(path)).ConfigureAwait(false);
+                var bitmapSource = await Image2BitmapSource.ReturnBitmapSourceAsync(new FileInfo(path)).ConfigureAwait(false);
                 var sourceSize = new Size(bitmapSource.PixelWidth, bitmapSource.PixelHeight);
                 var rectangle = new Rectangle
                 {

+ 1 - 1
src/PicView/ImageHandling/GetImageData.cs

@@ -84,7 +84,7 @@ namespace PicView.ImageHandling
             else
             {
                 await ConfigureWindows.GetMainWindow.Dispatcher.BeginInvoke(DispatcherPriority.Normal,
-                    () => { bitmapSource = ImageDecoder.GetRenderedBitmapFrame(); });
+                    () => { bitmapSource = Image2BitmapSource.GetRenderedBitmapFrame(); });
             }
 
             if (fileInfo is not null && Navigation.Pics[Navigation.FolderIndex] != fileInfo.FullName)

+ 212 - 0
src/PicView/ImageHandling/Image2BitmapSource.cs

@@ -0,0 +1,212 @@
+using ImageMagick;
+using PicView.Core.ImageDecoding;
+using PicView.UILogic;
+using SkiaSharp.Views.WPF;
+using System.Diagnostics;
+using System.IO;
+using System.Windows;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Shapes;
+using Rotation = PicView.UILogic.TransformImage.Rotation;
+
+namespace PicView.ImageHandling
+{
+    /// <summary>
+    /// Provides methods for converting images to WPF's BitmapSource.
+    /// </summary>
+    internal static class Image2BitmapSource
+    {
+        /// <summary>
+        /// Asynchronously returns a BitmapSource based on the file extension.
+        /// </summary>
+        /// <param name="fileInfo">The FileInfo representing the image file.</param>
+        /// <returns>A Task containing the BitmapSource if successful, otherwise an error image.</returns>
+        internal static async Task<BitmapSource> ReturnBitmapSourceAsync(FileInfo fileInfo)
+        {
+            if (fileInfo is not { Length: > 0 })
+            {
+                return ImageFunctions.ImageErrorMessage();
+            }
+
+            var extension = fileInfo.Extension.ToLowerInvariant();
+
+            switch (extension)
+            {
+                case ".jpg":
+                case ".jpeg":
+                case ".jpe":
+                case ".png":
+                case ".bmp":
+                case ".gif":
+                case ".jfif":
+                case ".ico":
+                case ".webp":
+                case ".wbmp":
+                    return await GetWriteAbleBitmapAsync(fileInfo).ConfigureAwait(false);
+
+                case ".svg":
+                    return await GetMagickSvg(fileInfo, MagickFormat.Svg).ConfigureAwait(false);
+
+                case ".svgz":
+                    return await GetMagickSvg(fileInfo, MagickFormat.Svgz).ConfigureAwait(false);
+
+                case ".b64":
+                    return await Base64.Base64StringToBitmapAsync(fileInfo).ConfigureAwait(false) ??
+                           ImageFunctions.ImageErrorMessage();
+
+                default:
+                    return await GetDefaultBitmapSource(fileInfo).ConfigureAwait(false);
+            }
+        }
+
+        #region Render Image From Source
+
+        /// <summary>
+        /// Returns the currently viewed bitmap image to MagickImage.
+        /// </summary>
+        /// <returns>The rendered MagickImage if successful, otherwise null.</returns>
+        internal static MagickImage? GetRenderedMagickImage()
+        {
+            try
+            {
+                var frame = BitmapFrame.Create(GetRenderedBitmapFrame() ?? throw new InvalidOperationException());
+                var encoder = new PngBitmapEncoder();
+
+                encoder.Frames.Add(frame);
+
+                var magickImage = new MagickImage();
+                using (var stream = new MemoryStream())
+                {
+                    encoder.Save(stream);
+                    magickImage.Read(stream.ToArray());
+                }
+
+                magickImage.Quality = 100;
+
+                // Apply rotation and flip transformations
+                if (Rotation.IsFlipped)
+                {
+                    magickImage.Flop();
+                }
+
+                magickImage.Rotate(Rotation.RotationAngle);
+
+                return magickImage;
+            }
+            catch (Exception e)
+            {
+#if DEBUG
+                Trace.WriteLine($"{nameof(GetRenderedMagickImage)} exception, \n {e.Message}");
+#endif
+                return null;
+            }
+        }
+
+        /// <summary>
+        /// Returns the displayed image source to a BitmapFrame.
+        /// </summary>
+        /// <returns>The rendered BitmapFrame if successful, otherwise null.</returns>
+        internal static BitmapFrame? GetRenderedBitmapFrame()
+        {
+            try
+            {
+                if (ConfigureWindows.GetMainWindow.MainImage.Source is not BitmapSource sourceBitmap)
+                {
+                    return null;
+                }
+
+                var effect = ConfigureWindows.GetMainWindow.MainImage.Effect;
+
+                var rectangle = new Rectangle
+                {
+                    Fill = new ImageBrush(sourceBitmap),
+                    Effect = effect
+                };
+
+                var sourceSize = new Size(sourceBitmap.PixelWidth, sourceBitmap.PixelHeight);
+                rectangle.Measure(sourceSize);
+                rectangle.Arrange(new Rect(sourceSize));
+
+                var renderedBitmap = new RenderTargetBitmap(sourceBitmap.PixelWidth, sourceBitmap.PixelHeight,
+                    sourceBitmap.DpiX, sourceBitmap.DpiY, PixelFormats.Default);
+                renderedBitmap.Render(rectangle);
+
+                var bitmapFrame = BitmapFrame.Create(renderedBitmap);
+                bitmapFrame.Freeze();
+
+                return bitmapFrame;
+            }
+            catch (Exception exception)
+            {
+#if DEBUG
+                Trace.WriteLine($"{nameof(GetRenderedBitmapFrame)} exception, \n{exception.Message}");
+#endif
+                return null;
+            }
+        }
+
+        #endregion Render Image From Source
+
+        #region Private functions
+
+        /// <summary>
+        /// Asynchronously returns a BitmapSource for SVG images using MagickImage.
+        /// </summary>
+        /// <param name="fileInfo">The FileInfo representing the SVG file.</param>
+        /// <param name="magickFormat">The MagickFormat for the SVG image.</param>
+        /// <returns>A Task containing the BitmapSource for SVG if successful, otherwise an error image.</returns>
+        private static async Task<BitmapSource> GetMagickSvg(FileInfo fileInfo, MagickFormat magickFormat)
+        {
+            var magickImage = await ImageDecoder.GetMagickSvgAsync(fileInfo, magickFormat).ConfigureAwait(false);
+            if (magickImage is null)
+            {
+                return ImageFunctions.ImageErrorMessage();
+            }
+            var bitmap = magickImage.ToBitmapSource();
+            bitmap.Freeze();
+            magickImage.Dispose();
+            return bitmap;
+        }
+
+        /// <summary>
+        /// Asynchronously returns a BitmapSource using SKBitmap.
+        /// </summary>
+        /// <param name="fileInfo">The FileInfo representing the image file.</param>
+        /// <returns>A Task containing the BitmapSource if successful, otherwise an error image.</returns>
+        private static async Task<BitmapSource> GetWriteAbleBitmapAsync(FileInfo fileInfo)
+        {
+            using var sKBitmap = await fileInfo.GetSKBitmapAsync();
+            if (sKBitmap is null)
+            {
+                return ImageFunctions.ImageErrorMessage();
+            }
+
+            var skPic = sKBitmap.ToWriteableBitmap();
+            sKBitmap.Dispose();
+
+            skPic.Freeze();
+            return skPic;
+        }
+
+        /// <summary>
+        /// Asynchronously returns a default BitmapSource using MagickImage.
+        /// </summary>
+        /// <param name="fileInfo">The FileInfo representing the image file.</param>
+        /// <returns>A Task containing the BitmapSource if successful, otherwise an error image.</returns>
+        private static async Task<BitmapSource> GetDefaultBitmapSource(FileInfo fileInfo)
+        {
+            var magickImage = await ImageDecoder.GetMagickImageAsync(fileInfo).ConfigureAwait(false);
+            if (magickImage is null)
+            {
+                return ImageFunctions.ImageErrorMessage();
+            }
+
+            var bitmap = magickImage.ToBitmapSource();
+            bitmap.Freeze();
+            return bitmap;
+        }
+
+        #endregion Private functions
+    }
+}

+ 0 - 320
src/PicView/ImageHandling/ImageDecoder.cs

@@ -1,320 +0,0 @@
-using ImageMagick;
-using ImageMagick.Formats;
-using PicView.UILogic;
-using SkiaSharp;
-using SkiaSharp.Views.WPF;
-using System.Diagnostics;
-using System.IO;
-using System.Windows;
-using System.Windows.Media;
-using System.Windows.Media.Imaging;
-using System.Windows.Shapes;
-using Rotation = PicView.UILogic.TransformImage.Rotation;
-
-namespace PicView.ImageHandling
-{
-    internal static class ImageDecoder
-    {
-        /// <summary>
-        /// Decodes image from file to BitMapSource
-        /// </summary>
-        /// <param name="fileInfo">Cannot be null</param>
-        /// <returns></returns>
-        internal static async Task<BitmapSource> ReturnBitmapSourceAsync(FileInfo fileInfo)
-        {
-            if (fileInfo is not { Length: > 0 })
-            {
-                return ImageFunctions.ImageErrorMessage();
-            }
-
-            var extension = fileInfo.Extension.ToLowerInvariant();
-
-            switch (extension)
-            {
-                case ".jpg":
-                case ".jpeg":
-                case ".jpe":
-                case ".png":
-                case ".bmp":
-                case ".gif":
-                case ".jfif":
-                case ".ico":
-                case ".webp":
-                case ".wbmp":
-                    return await GetWriteAbleBitmapAsync(fileInfo).ConfigureAwait(false);
-
-                case ".svg":
-                    return await GetMagickSvg(fileInfo, MagickFormat.Svg).ConfigureAwait(false);
-
-                case ".svgz":
-                    return await GetMagickSvg(fileInfo, MagickFormat.Svgz).ConfigureAwait(false);
-
-                case ".b64":
-                    return await Base64.Base64StringToBitmapAsync(fileInfo).ConfigureAwait(false) ??
-                           ImageFunctions.ImageErrorMessage();
-
-                default:
-                    return await GetDefaultBitmapSource(fileInfo, extension).ConfigureAwait(false);
-            }
-        }
-
-        #region Render Image From Source
-
-        /// <summary>
-        /// Returns the currently viewed bitmap image to MagickImage
-        /// </summary>
-        /// <returns></returns>
-        internal static MagickImage? GetRenderedMagickImage()
-        {
-            try
-            {
-                var frame = BitmapFrame.Create(GetRenderedBitmapFrame() ?? throw new InvalidOperationException());
-                var encoder = new PngBitmapEncoder();
-
-                encoder.Frames.Add(frame);
-
-                var magickImage = new MagickImage();
-                using (var stream = new MemoryStream())
-                {
-                    encoder.Save(stream);
-                    magickImage.Read(stream.ToArray());
-                }
-
-                magickImage.Quality = 100;
-
-                // Apply rotation and flip transformations
-                if (Rotation.IsFlipped)
-                {
-                    magickImage.Flop();
-                }
-
-                magickImage.Rotate(Rotation.RotationAngle);
-
-                return magickImage;
-            }
-            catch (Exception e)
-            {
-#if DEBUG
-                Trace.WriteLine($"{nameof(GetRenderedMagickImage)} exception, \n {e.Message}");
-#endif
-                return null;
-            }
-        }
-
-        /// <summary>
-        /// Returns Displayed image source to a BitmapFrame
-        /// </summary>
-        /// <returns></returns>
-        internal static BitmapFrame? GetRenderedBitmapFrame()
-        {
-            try
-            {
-                if (ConfigureWindows.GetMainWindow.MainImage.Source is not BitmapSource sourceBitmap)
-                {
-                    return null;
-                }
-
-                var effect = ConfigureWindows.GetMainWindow.MainImage.Effect;
-
-                var rectangle = new Rectangle
-                {
-                    Fill = new ImageBrush(sourceBitmap),
-                    Effect = effect
-                };
-
-                var sourceSize = new Size(sourceBitmap.PixelWidth, sourceBitmap.PixelHeight);
-                rectangle.Measure(sourceSize);
-                rectangle.Arrange(new Rect(sourceSize));
-
-                var renderedBitmap = new RenderTargetBitmap(sourceBitmap.PixelWidth, sourceBitmap.PixelHeight,
-                    sourceBitmap.DpiX, sourceBitmap.DpiY, PixelFormats.Default);
-                renderedBitmap.Render(rectangle);
-
-                var bitmapFrame = BitmapFrame.Create(renderedBitmap);
-                bitmapFrame.Freeze();
-
-                return bitmapFrame;
-            }
-            catch (Exception exception)
-            {
-#if DEBUG
-                Trace.WriteLine($"{nameof(GetRenderedBitmapFrame)} exception, \n{exception.Message}");
-#endif
-                return null;
-            }
-        }
-
-        #endregion Render Image From Source
-
-        #region Private functions
-
-        /// <summary>
-        /// Create MagickImage and make sure its transparent, return it as BitmapSource
-        /// </summary>
-        /// <param name="fileInfo"></param>
-        /// <param name="magickFormat"></param>
-        /// <returns></returns>
-        private static async Task<BitmapSource> GetMagickSvg(FileInfo fileInfo, MagickFormat magickFormat)
-        {
-            try
-            {
-                var magickImage = new MagickImage
-                {
-                    Quality = 100,
-                    ColorSpace = ColorSpace.Transparent,
-                    BackgroundColor = MagickColors.Transparent,
-                    Format = magickFormat,
-                };
-
-                if (fileInfo.Length >=
-                    2147483648) // Streams with a length larger than 2GB are not supported, read from file instead
-                {
-                    await Task.Run(() =>
-                    {
-                        magickImage = new MagickImage();
-                        magickImage.Read(fileInfo);
-                    }).ConfigureAwait(false);
-                }
-                else
-                {
-                    await using var fileStream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read,
-                        FileShare.ReadWrite, 4096, fileInfo.Length > 1e+8);
-                    var data = new byte[fileStream.Length];
-                    // ReSharper disable once MustUseReturnValue
-                    await fileStream.ReadAsync(data.AsMemory(0, (int)fileStream.Length)).ConfigureAwait(false);
-                    magickImage.Read(data);
-                }
-
-                magickImage.Settings.BackgroundColor = MagickColors.Transparent;
-                magickImage.Settings.FillColor = MagickColors.Transparent;
-                magickImage.Settings.SetDefine("svg:xml-parse-huge", "true");
-
-                var bitmap = magickImage.ToBitmapSource();
-                bitmap.Freeze();
-                magickImage.Dispose();
-                return bitmap;
-            }
-            catch (Exception e)
-            {
-#if DEBUG
-                Trace.WriteLine($"{nameof(GetMagickSvg)} {fileInfo.Name} exception, \n {e.Message}");
-#endif
-                return ImageFunctions.ImageErrorMessage();
-            }
-        }
-
-        /// <summary>
-        /// Asynchronously gets a WriteableBitmap from the given file.
-        /// </summary>
-        /// <param name="fileInfo"></param>
-        /// <returns>A task that represents the asynchronous operation. The task result contains a <c>WriteableBitmap</c> object if successful; otherwise, it returns null.</returns>
-        private static async Task<BitmapSource> GetWriteAbleBitmapAsync(FileInfo fileInfo)
-        {
-            try
-            {
-                await using var fileStream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read,
-                    FileShare.ReadWrite, 4096, fileInfo.Length > 1e+8);
-                var sKBitmap = SKBitmap.Decode(fileStream);
-                if (sKBitmap is null)
-                {
-                    return ImageFunctions.ImageErrorMessage();
-                }
-
-                var skPic = sKBitmap.ToWriteableBitmap();
-                sKBitmap.Dispose();
-
-                skPic.Freeze();
-                return skPic;
-            }
-            catch (Exception e)
-            {
-#if DEBUG
-                Trace.WriteLine($"{nameof(GetWriteAbleBitmapAsync)} {fileInfo.Name} exception:\n{e.Message}");
-#endif
-                return ImageFunctions.ImageErrorMessage();
-            }
-        }
-
-        private static async Task<BitmapSource> GetDefaultBitmapSource(FileInfo fileInfo, string extension)
-        {
-            try
-            {
-                using var magickImage = new MagickImage();
-                MagickFormat format;
-
-                switch (extension)
-                {
-                    case ".heic":
-                    case ".heif":
-                        magickImage.Settings.SetDefines(new HeicReadDefines
-                        {
-                            PreserveOrientation = true,
-                            DepthImage = true,
-                        });
-                        format = extension is ".heic" ? MagickFormat.Heic : MagickFormat.Heif;
-                        break;
-
-                    case ".jp2":
-                        magickImage.Settings.SetDefines(new Jp2ReadDefines
-                        {
-                            QualityLayers = 100,
-                        });
-                        format = MagickFormat.Jp2;
-                        break;
-
-                    case ".tif":
-                    case ".tiff":
-                        magickImage.Settings.SetDefines(new TiffReadDefines
-                        {
-                            IgnoreTags = new[]
-                            {
-                                "34022", // ColorTable
-                                "34025", // ImageColorValue
-                                "34026", // BackgroundColorValue
-                                "32928",
-                            },
-                        });
-                        format = MagickFormat.Tif;
-                        break;
-
-                    case ".psd":
-                        format = MagickFormat.Psd;
-                        break;
-
-                    default:
-                        format = MagickFormat.Unknown;
-                        break;
-                }
-
-                if (fileInfo.Length >= 2147483648)
-                {
-                    await using var fileStream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read,
-                        FileShare.ReadWrite, 4096, true);
-
-                    // Fixes "The file is too long. This operation is currently limited to supporting files less than 2 gigabytes in size."
-                    // ReSharper disable once MethodHasAsyncOverload
-                    magickImage.Read(fileStream, format);
-                }
-                else
-                {
-                    await magickImage.ReadAsync(fileInfo, format).ConfigureAwait(false);
-                }
-
-                magickImage?.AutoOrient();
-                var bitmapSource = magickImage?.ToBitmapSource();
-                bitmapSource?.Freeze();
-                magickImage?.Dispose();
-                return bitmapSource ?? ImageFunctions.ImageErrorMessage();
-            }
-            catch (Exception exception)
-            {
-#if DEBUG
-                Trace.WriteLine($"{nameof(GetDefaultBitmapSource)} {fileInfo.Name} exception:\n{exception.Message}");
-#endif
-                return ImageFunctions.ImageErrorMessage();
-            }
-        }
-
-        #endregion Private functions
-    }
-}

+ 1 - 1
src/PicView/ImageHandling/SaveImages.cs

@@ -37,7 +37,7 @@ namespace PicView.ImageHandling
                 if (hlsl)
                 {
                     await ConfigureWindows.GetMainWindow.Dispatcher.BeginInvoke(DispatcherPriority.Normal,
-                        () => { magickImage = ImageDecoder.GetRenderedMagickImage(); });
+                        () => { magickImage = Image2BitmapSource.GetRenderedMagickImage(); });
                 }
                 else if (bitmapSource is not null)
                 {

+ 1 - 0
src/PicView/PicView.csproj → src/PicView/PicView.WPF.csproj

@@ -321,6 +321,7 @@
     <None Remove="Themes\Resources\img\icon__Q6k_icon.ico" />
   </ItemGroup>
   <ItemGroup>
+    <ProjectReference Include="..\PicView.Core\PicView.Core.csproj" />
     <ProjectReference Include="..\PicView.Tools\PicView.Tools.csproj" />
     <ProjectReference Include="..\XamlAnimatedGif\XamlAnimatedGif.csproj" />
   </ItemGroup>

+ 2 - 0
src/PicView/PicView.WPF.csproj.DotSettings

@@ -0,0 +1,2 @@
+<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
+	<s:String x:Key="/Default/CodeInspection/Daemon/ConfigureAwaitAnalysisMode/@EntryValue">UI</s:String></wpf:ResourceDictionary>

+ 1 - 1
src/PicView/UILogic/UIHelper.cs

@@ -927,7 +927,7 @@ namespace PicView.UILogic
                     if (preLoadValue is null)
                     {
                         var fileInfo = new FileInfo(Navigation.Pics[Navigation.FolderIndex]);
-                        var bitmapSource = await ImageDecoder.ReturnBitmapSourceAsync(fileInfo).ConfigureAwait(false);
+                        var bitmapSource = await Image2BitmapSource.ReturnBitmapSourceAsync(fileInfo).ConfigureAwait(false);
                         preLoadValue = new PreLoader.PreLoadValue(bitmapSource, fileInfo);
                     }
                     await ImageInfo.UpdateValuesAsync(preLoadValue.FileInfo).ConfigureAwait(false);