فهرست منبع

Refactor, rename image helper functions to smaller more focused classes.

Ruben 7 ماه پیش
والد
کامیت
ed047ea8aa

+ 2 - 2
src/PicView.Avalonia/ImageHandling/GetImageModel.cs

@@ -41,7 +41,7 @@ public static class GetImageModel
             {
                 case ".webp":
                     await AddImageAsync(fileInfo, imageModel).ConfigureAwait(false);
-                    if (ImageHelper.IsAnimated(fileInfo))
+                    if (ImageAnalyzer.IsAnimated(fileInfo))
                     {
                         imageModel.ImageType = ImageType.AnimatedWebp;
                     }
@@ -49,7 +49,7 @@ public static class GetImageModel
                     break;
                 case ".gif":
                     await AddImageAsync(fileInfo, imageModel).ConfigureAwait(false);
-                    if (ImageHelper.IsAnimated(fileInfo))
+                    if (ImageAnalyzer.IsAnimated(fileInfo))
                     {
                         imageModel.ImageType = ImageType.AnimatedGif;
                     }

+ 46 - 0
src/PicView.Avalonia/ImageHandling/ImageDownloader.cs

@@ -0,0 +1,46 @@
+using System.Diagnostics;
+
+namespace PicView.Avalonia.ImageHandling;
+
+/// <summary>
+/// Handles downloading images from URLs
+/// </summary>
+public static class ImageDownloader
+{
+    /// <summary>
+    /// Downloads an image from a URL to a local temporary file
+    /// </summary>
+    /// <param name="url">URL of the image to download</param>
+    /// <returns>Local path to the downloaded image or empty string if download failed</returns>
+    public static async Task<string> DownloadImageFromUrlAsync(string url)
+    {
+        if (string.IsNullOrWhiteSpace(url))
+        {
+            return string.Empty;
+        }
+        
+        try
+        {
+            var extension = Path.GetExtension(url);
+            var tempPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + extension);
+
+            using var client = new HttpClient();
+            var response = await client.GetAsync(url);
+            if (response.IsSuccessStatusCode)
+            {
+                await using var fs = new FileStream(tempPath, FileMode.Create);
+                await response.Content.CopyToAsync(fs);
+                return tempPath;
+            }
+        }
+        catch (Exception ex)
+        {
+#if DEBUG
+            Debug.WriteLine($"{nameof(DownloadImageFromUrlAsync)} Error downloading image: {ex.Message}");
+#endif
+            
+        }
+
+        return string.Empty;
+    }
+}

+ 105 - 0
src/PicView.Avalonia/ImageHandling/ImageFormatConverter.cs

@@ -0,0 +1,105 @@
+using Avalonia.Media.Imaging;
+using PicView.Avalonia.Navigation;
+using PicView.Avalonia.ViewModels;
+using PicView.Core.FileHandling;
+using PicView.Core.ImageDecoding;
+
+namespace PicView.Avalonia.ImageHandling;
+
+/// <summary>
+///     Handles conversion of image files to common supported formats.
+/// </summary>
+public static class ImageFormatConverter
+{
+    /// <summary>
+    ///     Converts an image to a commonly supported format
+    /// </summary>
+    /// <param name="path">Path to the image file</param>
+    /// <param name="vm">MainViewModel instance</param>
+    /// <returns>Path to the converted image file, or empty string if conversion failed</returns>
+    /// <remarks>Saves the image to a temporary file and returns its path, if an effect is applied, or if the path is empty</remarks>
+    public static async Task<string> ConvertToCommonSupportedFormatAsync(string path, MainViewModel vm)
+    {
+        ArgumentNullException.ThrowIfNull(vm);
+
+        Bitmap? source = null;
+
+        // Primary case: Handle effect applied or empty path by saving current ImageSource
+        if (vm.EffectConfig is not null || string.IsNullOrWhiteSpace(path))
+        {
+            if (vm.ImageSource is Bitmap bmp)
+            {
+                source = bmp;
+            }
+        }
+        else if (NavigationManager.CanNavigate(vm) && !string.IsNullOrEmpty(path))
+        {
+            // Handle effects for the current file
+            if (vm.EffectConfig is not null && vm.FileInfo?.FullName == path)
+            {
+                if (vm.ImageSource is Bitmap bmp)
+                {
+                    source = bmp;
+                }
+            }
+            // Current path that's already in common format
+            else if (path == vm.FileInfo?.FullName)
+            {
+                if (path.IsCommon())
+                {
+                    // No need to convert
+                    return path;
+                }
+
+                if (vm.ImageSource is Bitmap bmp && vm.FileInfo.FullName.IsSupported())
+                {
+                    source = bmp;
+                }
+            }
+            // Different path - try to get from preload
+            else
+            {
+                var preloadValue = await NavigationManager.GetPreLoadValueAsync(path).ConfigureAwait(false);
+                if (preloadValue?.ImageModel.Image is Bitmap bitmap)
+                {
+                    source = bitmap;
+                }
+            }
+        }
+
+        // If we have a valid source bitmap, save it to a temp file
+        var tempPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".png");
+        if (source is not null)
+        {
+            await Task.Run(() => source.Save(tempPath));
+            return tempPath;
+        }
+
+        // Handle URL paths if no source bitmap was found yet
+        var url = path.GetURL();
+        if (!string.IsNullOrWhiteSpace(url))
+        {
+            path = await ImageDownloader.DownloadImageFromUrlAsync(url);
+            if (string.IsNullOrEmpty(path))
+            {
+                return string.Empty;
+            }
+        }
+
+        // Cleanup file:/// prefixes
+        if (path?.StartsWith("file:///") == true)
+        {
+            path = path.Replace("file:///", "");
+            path = path.Replace("%20", " ");
+        }
+
+        if (!File.Exists(path))
+        {
+            return string.Empty;
+        }
+
+        // Convert using SaveImageFileHelper as a fallback
+        var success = await SaveImageFileHelper.SaveImageAsync(null, path, tempPath, null, null, null, ".png");
+        return success ? tempPath : string.Empty;
+    }
+}

+ 0 - 152
src/PicView.Avalonia/ImageHandling/ImageHelper.cs

@@ -1,152 +0,0 @@
-using System.Diagnostics;
-using Avalonia.Media.Imaging;
-using ImageMagick;
-using PicView.Avalonia.Navigation;
-using PicView.Avalonia.UI;
-using PicView.Avalonia.ViewModels;
-using PicView.Core.FileHandling;
-using PicView.Core.ImageDecoding;
-
-namespace PicView.Avalonia.ImageHandling;
-
-public static class ImageHelper
-{
-    public static bool IsAnimated(FileInfo fileInfo)
-    {
-        var frames = ImageFunctionHelper.GetImageFrames(fileInfo.FullName);
-        return frames > 1;
-    }
-    
-    public static async Task<string> ConvertToCommonSupportedFormatAsync(string path, MainViewModel vm)
-    {
-        Bitmap? source = null;
-        
-        if (vm.EffectConfig is not null || string.IsNullOrWhiteSpace(path))
-        {
-            if (vm.ImageSource is Bitmap bmp)
-            {
-                source = bmp;
-            }
-        }
-        else if (NavigationManager.CanNavigate(vm) && !string.IsNullOrEmpty(path))
-        {
-            if (vm.EffectConfig is not null && vm.FileInfo.FullName == path)
-            {
-                if (vm.ImageSource is Bitmap bmp)
-                {
-                    source = bmp;
-                }
-            }
-            else if (path == vm.FileInfo.FullName)
-            {
-                if (path.IsCommon())
-                {
-                    return path;
-                }
-                if (vm.ImageSource is Bitmap bmp && vm.FileInfo.FullName.IsSupported())
-                {
-                    source = bmp;
-                }
-            }
-            else
-            {
-                var preloadValue = await NavigationManager.GetPreLoadValueAsync(path).ConfigureAwait(false);
-                if (preloadValue?.ImageModel.Image is Bitmap bitmap)
-                {
-                    source = bitmap;
-                }
-            }
-        }
-        var tempPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".png");
-        
-        if (source is not null)
-        {
-            await Task.Run(() => source.Save(tempPath));
-            return tempPath;
-        }
-        
-        var url = path.GetURL();
-        if (!string.IsNullOrWhiteSpace(url))
-        {
-            // Download the image from the URL
-            path = await DownloadImageFromUrlAsync(url);
-            if (string.IsNullOrEmpty(path))
-            {
-                return string.Empty; // If download fails, return empty
-            }
-        }
-        
-        if (path.StartsWith("file:///"))
-        {
-            path = path.Replace("file:///", "");
-            path = path.Replace("%20", " ");
-        }
-        if (!File.Exists(path))
-            return string.Empty;
-        
-        // Convert the image to a common supported format
-        // Save image to temp path
-        var success = await SaveImageFileHelper.SaveImageAsync(null, path, tempPath, null, null, null, ".png");
-        return !success ? string.Empty : tempPath;
-    }
-    
-    
-    private static async Task<string> DownloadImageFromUrlAsync(string url)
-    {
-        // TODO: Refactoring needed: Need to combine with the one in LoadPicFromUrlAsync and add unit tests
-        try
-        {
-            var tempPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + Path.GetExtension(url));
-
-            using var client = new HttpClient();
-            var response = await client.GetAsync(url);
-            if (response.IsSuccessStatusCode)
-            {
-                await using var fs = new FileStream(tempPath, FileMode.Create);
-                await response.Content.CopyToAsync(fs);
-                return tempPath;
-            }
-        }
-        catch (Exception ex)
-        {
-#if DEBUG
-            Trace.WriteLine($"Error downloading image: {ex.Message}");
-#endif
-            return string.Empty;
-        }
-
-        return string.Empty;
-    }
-    
-    public static async Task OptimizeImage(MainViewModel vm)
-    {
-        if (vm is null)
-        {
-            return;
-        }
-        if (!NavigationManager.CanNavigate(vm))
-        {
-            return;
-        }
-        if (vm.FileInfo is null)
-        {
-            return;
-        }
-        await Task.Run(() =>
-        {
-            try
-            {
-                ImageOptimizer imageOptimizer = new()
-                {
-                    OptimalCompression = true
-                };
-                imageOptimizer.LosslessCompress(vm.FileInfo.FullName);
-            }
-            catch (Exception e)
-            {
-                Trace.WriteLine(e);
-            }
-        });
-        SetTitleHelper.SetTitle(vm);
-    }
-}

+ 44 - 0
src/PicView.Avalonia/ImageHandling/ImageOptimizer.cs

@@ -0,0 +1,44 @@
+using System.Diagnostics;
+using PicView.Avalonia.Navigation;
+using PicView.Avalonia.UI;
+using PicView.Avalonia.ViewModels;
+
+namespace PicView.Avalonia.ImageHandling;
+
+/// <summary>
+/// Provides image optimization functionality
+/// </summary>
+public static class ImageOptimizer
+{
+    /// <summary>
+    /// Optimizes the current image in the view model
+    /// </summary>
+    /// <param name="vm">The main view model</param>
+    public static async Task OptimizeImageAsync(MainViewModel vm)
+    {
+        ArgumentNullException.ThrowIfNull(vm);
+
+        if (!NavigationManager.CanNavigate(vm) || vm.FileInfo == null)
+        {
+            return;
+        }
+        
+        await Task.Run(() =>
+        {
+            try
+            {
+                var optimizer = new ImageMagick.ImageOptimizer
+                {
+                    OptimalCompression = true
+                };
+                optimizer.LosslessCompress(vm.FileInfo.FullName);
+            }
+            catch (Exception ex)
+            {
+                Debug.WriteLine($"Error optimizing image: {ex.Message}");
+            }
+        });
+        
+        SetTitleHelper.SetTitle(vm);
+    }
+}

+ 1 - 1
src/PicView.Avalonia/UI/FunctionsHelper.cs

@@ -743,7 +743,7 @@ public static class FunctionsHelper
 
     public static async Task OptimizeImage()
     {
-        await ImageHelper.OptimizeImage(Vm);
+        await ImageOptimizer.OptimizeImageAsync(Vm).ConfigureAwait(false);
     }
 
     public static async Task Slideshow()

+ 2 - 2
src/PicView.Avalonia/ViewModels/MainViewModel.cs

@@ -1438,7 +1438,7 @@ public class MainViewModel : ViewModelBase
         IsLoading = true;
         try
         {
-            var file = await ImageHelper.ConvertToCommonSupportedFormatAsync(path, this).ConfigureAwait(false);
+            var file = await ImageFormatConverter.ConvertToCommonSupportedFormatAsync(path, this).ConfigureAwait(false);
 
             PlatformService?.SetAsWallpaper(file, WallpaperManager.GetWallpaperStyle(style));
         }
@@ -1470,7 +1470,7 @@ public class MainViewModel : ViewModelBase
 
         try
         {
-            var file = await ImageHelper.ConvertToCommonSupportedFormatAsync(path, this).ConfigureAwait(false);
+            var file = await ImageFormatConverter.ConvertToCommonSupportedFormatAsync(path, this).ConfigureAwait(false);
 
             var process = new Process
             {

+ 2 - 2
src/PicView.Avalonia/Views/SingleImageResizeView.axaml.cs

@@ -89,7 +89,7 @@ public partial class SingleImageResizeView : UserControl
                      vm.FileInfo.Extension.Equals(".png", StringComparison.OrdinalIgnoreCase))
             {
                 QualitySlider.IsEnabled = true;
-                var quality = ImageFunctionHelper.GetCompressionQuality(vm.FileInfo.FullName);
+                var quality = ImageAnalyzer.GetCompressionQuality(vm.FileInfo.FullName);
                 QualitySlider.Value = quality;
             }
             else
@@ -256,7 +256,7 @@ public partial class SingleImageResizeView : UserControl
             vm.FileInfo.Extension.Equals(".png", StringComparison.OrdinalIgnoreCase))
         {
             QualitySlider.IsEnabled = true;
-            var quality = ImageFunctionHelper.GetCompressionQuality(vm.FileInfo.FullName);
+            var quality = ImageAnalyzer.GetCompressionQuality(vm.FileInfo.FullName);
             QualitySlider.Value = quality;
         }
         else

+ 28 - 1
src/PicView.Core/ImageDecoding/ImageFunctionHelper.cs → src/PicView.Core/ImageDecoding/ImageAnalyzer.cs

@@ -3,7 +3,10 @@ using ImageMagick;
 
 namespace PicView.Core.ImageDecoding;
 
-public static class ImageFunctionHelper
+/// <summary>
+/// Provides methods to analyze image properties
+/// </summary>
+public static class ImageAnalyzer
 {
     /// <summary>
     ///     Gets the number of frames in an image.
@@ -30,7 +33,31 @@ public static class ImageFunctionHelper
             return 0;
         }
     }
+    
+    /// <summary>
+    /// Determines if the specified image file is animated
+    /// </summary>
+    /// <param name="fileInfo">File information for the image</param>
+    /// <returns>True if the image is animated; otherwise, false</returns>
+    public static bool IsAnimated(FileInfo fileInfo)
+    {
+        if (fileInfo is not { Exists: true })
+        {
+            return false;
+        }
+        
+        var frames = GetImageFrames(fileInfo.FullName);
+        return frames > 1;
+    }
         
+    /// <summary>
+    ///     Retrieves the compression quality of the specified image file.
+    /// </summary>
+    /// <param name="file">The path to the image file.</param>
+    /// <returns>The compression quality of the image, as a percentage (0-100).</returns>
+    /// <remarks>
+    ///     This method uses the Magick.NET library to load the image and retrieve the compression quality.
+    /// </remarks>
     public static uint GetCompressionQuality(string file)
     {
         using var magickImage = new MagickImage();