Explorar o código

[Avalonia] Show side by side (work in progress) #89

Ruben hai 1 ano
pai
achega
7a40129cbf

+ 74 - 32
src/PicView.Avalonia/CustomControls/PicBox.cs

@@ -15,6 +15,7 @@ using PicView.Avalonia.AnimatedImage;
 using PicView.Avalonia.Navigation;
 using PicView.Avalonia.UI;
 using PicView.Avalonia.ViewModels;
+using PicView.Core.Config;
 using Vector = Avalonia.Vector;
 
 
@@ -61,6 +62,14 @@ public class PicBox : Control
         get => GetValue(SecondarySourceProperty);
         set => SetValue(SecondarySourceProperty, value);
     }
+    
+    public static readonly StyledProperty<double> SecondaryImageWidthProperty = AvaloniaProperty.Register<PicBox, double>(nameof(SecondaryImageWidth));
+
+    public double SecondaryImageWidth
+    {
+        get => GetValue(SecondaryImageWidthProperty);
+        set => SetValue(SecondaryImageWidthProperty, value);
+    }
 
     /// <summary>
     /// Defines the <see cref="ImageType"/> property.
@@ -216,15 +225,24 @@ public class PicBox : Control
 
     private void RenderBasedOnSettings(DrawingContext context, IImage source)
     {
+        var is1To1 = false; // TODO: replace with settings value
+        var isSideBySide = SettingsHelper.Settings.ImageScaling.ShowImageSideBySide;
+        var secondarySource = SecondarySource as IImage;
+        
         var viewPort = DetermineViewPort();
         Size sourceSize;
         if (source is null)
         {
             return;
         }
+        Size? secondarySourceSize = null;
         try
         {
             sourceSize = source.Size;
+            if (isSideBySide)
+            {
+                secondarySourceSize = secondarySource.Size;
+            }
         }
         catch (Exception e)
         {
@@ -267,29 +285,27 @@ public class PicBox : Control
                 }
                 else return;
             }
+            if (isSideBySide)
+            {
+                var nextPreloadValue = vm.ImageIterator?.GetNextPreLoadValue();
+                if (nextPreloadValue?.ImageModel != null)
+                {
+                    secondarySourceSize = new Size(nextPreloadValue.ImageModel.PixelWidth, nextPreloadValue.ImageModel.PixelHeight);
+                }
+                else
+                {
+                    return;
+                }
+            }
         }
     
-        var is1To1 = false; // TODO: replace with settings value
-        var isSideBySide = false; // TODO: replace with settings value
-    
         if (is1To1)
         {
             RenderImage1To1(context, source, viewPort, sourceSize);
         }
         else if (isSideBySide)
         {
-            if (SecondarySource is IImage secondarySource)
-            {
-                RenderImageSideBySide(context, source, secondarySource, viewPort);
-            }
-            else
-            {
-                // Handle invalid secondary source
-#if DEBUG
-                Console.WriteLine("Invalid secondary source type.");
-                TooltipHelper.ShowTooltipMessage("Invalid secondary source type.", true);
-#endif
-            }
+            RenderImageSideBySide(context, source, secondarySource, viewPort, sourceSize, secondarySourceSize);
         }
         else
         {
@@ -329,30 +345,56 @@ public class PicBox : Control
         }
     }
 
-    private void RenderImageSideBySide(DrawingContext context, IImage source, IImage secondarySource, Rect viewPort)
+    private void RenderImageSideBySide(DrawingContext context, IImage source, IImage secondarySource, Rect viewPort, Size sourceSize, Size? secondarySourceSize)
     {
-        // Get the aspect ratios of the images
-        var sourceAspectRatio = source.Size.Width / source.Size.Height;
-        var secondarySourceAspectRatio = secondarySource.Size.Width / secondarySource.Size.Height;
+        if (source == null || secondarySource == null || secondarySourceSize == null)
+        {
+            return;
+        }
 
-        // Calculate the width for each image
-        var halfViewportWidth = viewPort.Width / 2;
+        // Scale both images based on the height of the viewport
+        var scale = viewPort.Height / Math.Max(sourceSize.Height, secondarySourceSize.Value.Height);
 
-        // Calculate heights based on the aspect ratio
-        var sourceHeight = halfViewportWidth / sourceAspectRatio;
-        var secondarySourceHeight = halfViewportWidth / secondarySourceAspectRatio;
+        // Calculate the scaled size of the second image based on the specified width (SecondaryImageWidth)
+        var scaledSecondarySize = new Size(SecondaryImageWidth, secondarySourceSize.Value.Height * scale);
+    
+        // Calculate the remaining width for the first image
+        var firstImageWidth = viewPort.Width - scaledSecondarySize.Width;
 
-        // Calculate the rectangles for each image
-        var sourceRect = new Rect(0, 0, halfViewportWidth, sourceHeight);
-        var secondarySourceRect = new Rect(halfViewportWidth, 0, halfViewportWidth, secondarySourceHeight);
-        
+        if (firstImageWidth <= 0)
+        {
+            // If there's no space left for the first image, don't render anything
+            return;
+        }
+
+        // Scale the first image to fit within the remaining width while keeping the same height
+        var scaledSourceSize = new Size(firstImageWidth, sourceSize.Height * scale);
+
+        // Calculate the destination rectangles for both images
+        var sourceDestRect = new Rect(0, 0, firstImageWidth, viewPort.Height);
+        var secondaryDestRect = new Rect(firstImageWidth, 0, SecondaryImageWidth, viewPort.Height);
+
+        // Calculate the source rectangles (ensuring the aspect ratio is maintained)
+        var sourceRect = new Rect(sourceSize);
+        var secondarySourceRect = new Rect(secondarySourceSize.Value);
+
+        // Render the background before the images
         RenderBackground(context);
 
-        // Draw the first image
-        context.DrawImage(source, new Rect(source.Size), sourceRect);
+        try
+        {
+            // Render the first image (filling the remaining space)
+            context.DrawImage(source, sourceRect, sourceDestRect);
 
-        // Draw the second image
-        context.DrawImage(secondarySource, new Rect(secondarySource.Size), secondarySourceRect);
+            // Render the second image (with the fixed SecondaryImageWidth)
+            context.DrawImage(secondarySource, secondarySourceRect, secondaryDestRect);
+        }
+        catch (Exception e)
+        {
+#if DEBUG
+            Console.WriteLine(e);
+#endif
+        }
     }
 
     private void RenderBackground(DrawingContext context)

+ 606 - 509
src/PicView.Avalonia/Navigation/ImageIterator.cs

@@ -13,662 +13,759 @@ using PicView.Core.Gallery;
 using PicView.Core.Navigation;
 using Timer = System.Timers.Timer;
 
-namespace PicView.Avalonia.Navigation;
-public sealed class ImageIterator : IDisposable
+namespace PicView.Avalonia.Navigation
 {
-    #region Properties
+    public sealed class ImageIterator : IDisposable
+    {
+        #region Properties
 
-    private bool _disposed;
+        private bool _disposed;
 
-    public List<string> ImagePaths { get; private set; }
+        public List<string> ImagePaths { get; private set; }
 
-    public bool IsRenamingInProgress { get; set; }
+        public bool IsRenamingInProgress { get; set; }
 
-    public int CurrentIndex{ get; private set; }
-    
-    public FileInfo InitialFileInfo{ get; private set; } = null!;
-    public bool IsReversed { get; private set; }
-    private PreLoader PreLoader { get; } = new();
+        public int CurrentIndex { get; private set; }
 
-    private static FileSystemWatcher? _watcher;
-    private static bool _isRunning;
-    private readonly MainViewModel? _vm;
-    private readonly Lock _lock = new();
+        public FileInfo InitialFileInfo { get; private set; } = null!;
+        public bool IsReversed { get; private set; }
+        private PreLoader PreLoader { get; } = new();
 
-    #endregion
+        private static FileSystemWatcher? _watcher;
+        private static bool _isRunning;
+        private readonly MainViewModel? _vm;
+        private readonly Lock _lock = new();
 
-    #region Constructors
+        #endregion
 
-    public ImageIterator(FileInfo fileInfo, MainViewModel vm)
-    {
-        ArgumentNullException.ThrowIfNull(fileInfo);
-        _vm = vm;
-        ImagePaths = vm.PlatformService.GetFiles(fileInfo);
-        CurrentIndex = Directory.Exists(fileInfo.FullName) ? 0 : ImagePaths.IndexOf(fileInfo.FullName);
-        InitiateFileSystemWatcher(fileInfo);
-    }
-    
-    public ImageIterator(FileInfo fileInfo, List<string> imagePaths, int currentIndex, MainViewModel vm)
-    {
-        ArgumentNullException.ThrowIfNull(fileInfo);
-        _vm = vm;
-        ImagePaths = imagePaths;
-        CurrentIndex = currentIndex;
-        InitiateFileSystemWatcher(fileInfo);
-    }
-
-    #endregion
+        #region Constructors
 
-    #region File Watcher
-    
-    private void InitiateFileSystemWatcher(FileInfo fileInfo)
-    {
-        InitialFileInfo = fileInfo;
-        if (_watcher is not null)
+        public ImageIterator(FileInfo fileInfo, MainViewModel vm)
         {
-            _watcher.Dispose();
-            _watcher = null;
+            ArgumentNullException.ThrowIfNull(fileInfo);
+            _vm = vm;
+            ImagePaths = vm.PlatformService.GetFiles(fileInfo);
+            CurrentIndex = Directory.Exists(fileInfo.FullName) ? 0 : ImagePaths.IndexOf(fileInfo.FullName);
+            InitiateFileSystemWatcher(fileInfo);
         }
-        _watcher = new FileSystemWatcher();
-#if DEBUG
-        Debug.Assert(fileInfo.DirectoryName != null, "fileInfo.DirectoryName != null");
-#endif
-        _watcher.Path = fileInfo.DirectoryName;
-        _watcher.EnableRaisingEvents = true;
-        _watcher.Filter = "*.*";
-        _watcher.IncludeSubdirectories = SettingsHelper.Settings.Sorting.IncludeSubDirectories;
-        _watcher.Created += async (_, e) => await OnFileAdded(e);
-        _watcher.Deleted += async (_, e) => await OnFileDeleted(e);
-        _watcher.Renamed += async (_, e) => await OnFileRenamed(e);
-    }
-    
-    private async Task OnFileAdded(FileSystemEventArgs e)
-    {
-        if (IsRenamingInProgress)
-        {
-            return;
-        }
-        if (ImagePaths.Contains(e.FullPath))
+
+        public ImageIterator(FileInfo fileInfo, List<string> imagePaths, int currentIndex, MainViewModel vm)
         {
-            return;
+            ArgumentNullException.ThrowIfNull(fileInfo);
+            _vm = vm;
+            ImagePaths = imagePaths;
+            CurrentIndex = currentIndex;
+            InitiateFileSystemWatcher(fileInfo);
         }
-        if (e.FullPath.IsSupported() == false)
+
+        #endregion
+
+        #region File Watcher
+
+        private void InitiateFileSystemWatcher(FileInfo fileInfo)
         {
-            return;
+            InitialFileInfo = fileInfo;
+            if (_watcher is not null)
+            {
+                _watcher.Dispose();
+                _watcher = null;
+            }
+
+            _watcher = new FileSystemWatcher();
+#if DEBUG
+            Debug.Assert(fileInfo.DirectoryName != null, "fileInfo.DirectoryName != null");
+#endif
+            _watcher.Path = fileInfo.DirectoryName;
+            _watcher.EnableRaisingEvents = true;
+            _watcher.Filter = "*.*";
+            _watcher.IncludeSubdirectories = SettingsHelper.Settings.Sorting.IncludeSubDirectories;
+            _watcher.Created += async (_, e) => await OnFileAdded(e);
+            _watcher.Deleted += async (_, e) => await OnFileDeleted(e);
+            _watcher.Renamed += async (_, e) => await OnFileRenamed(e);
         }
-        var fileInfo = new FileInfo(e.FullPath);
-        if (fileInfo.Exists == false) { return; }
-        
-        var retries = 0;
-        while (_isRunning && retries < 10)
+
+        private async Task OnFileAdded(FileSystemEventArgs e)
         {
-            await Task.Delay(200);
-            retries++;
-        }
-        _isRunning = true;
+            if (IsRenamingInProgress)
+            {
+                return;
+            }
 
-        var newList = await Task.FromResult(_vm.PlatformService.GetFiles(fileInfo));
-        if (newList.Count == 0) { return; }
-        if (newList.Count == ImagePaths.Count) { return; }
+            if (ImagePaths.Contains(e.FullPath))
+            {
+                return;
+            }
 
-        if (fileInfo.Exists == false) { return; }
-        
-        ImagePaths = newList;
+            if (e.FullPath.IsSupported() == false)
+            {
+                return;
+            }
 
-        _isRunning = false;
+            var fileInfo = new FileInfo(e.FullPath);
+            if (fileInfo.Exists == false)
+            {
+                return;
+            }
 
-        var index = ImagePaths.IndexOf(e.FullPath);
-        if (index < 0) { return; }
+            var retries = 0;
+            while (_isRunning && retries < 10)
+            {
+                await Task.Delay(200);
+                retries++;
+            }
 
-        var nextIndex = index + 1;
-        if (index >= ImagePaths.Count)
-        {
-            nextIndex = 0;
-        }
-        var prevIndex = index - 1;
-        if (prevIndex < 0)
-        {
-            prevIndex = ImagePaths.Count - 1;
-        }
+            _isRunning = true;
 
-        var cleared = false;
-        if (PreLoader.Contains(index, ImagePaths) || PreLoader.Contains(nextIndex, ImagePaths) || PreLoader.Contains(prevIndex, ImagePaths))
-        {
-            PreLoader.Clear();
-            cleared = true;
-        }
+            var newList = await Task.FromResult(_vm.PlatformService.GetFiles(fileInfo));
+            if (newList.Count == 0)
+            {
+                return;
+            }
 
-        SetTitleHelper.SetTitle(_vm);
-        
-        var isGalleryItemAdded = await GalleryFunctions.AddGalleryItem(index, fileInfo, _vm);
-        if (isGalleryItemAdded)
-        {
-            if (SettingsHelper.Settings.Gallery.IsBottomGalleryShown && ImagePaths.Count > 1)
+            if (newList.Count == ImagePaths.Count)
+            {
+                return;
+            }
+
+            if (fileInfo.Exists == false)
+            {
+                return;
+            }
+
+            ImagePaths = newList;
+
+            _isRunning = false;
+
+            var index = ImagePaths.IndexOf(e.FullPath);
+            if (index < 0)
+            {
+                return;
+            }
+
+            var nextIndex = index + 1;
+            if (index >= ImagePaths.Count)
             {
-                if (_vm.GalleryMode is GalleryMode.BottomToClosed or GalleryMode.FullToClosed)
+                nextIndex = 0;
+            }
+
+            var prevIndex = index - 1;
+            if (prevIndex < 0)
+            {
+                prevIndex = ImagePaths.Count - 1;
+            }
+
+            var cleared = false;
+            if (PreLoader.Contains(index, ImagePaths) || PreLoader.Contains(nextIndex, ImagePaths) ||
+                PreLoader.Contains(prevIndex, ImagePaths))
+            {
+                PreLoader.Clear();
+                cleared = true;
+            }
+
+            SetTitleHelper.SetTitle(_vm);
+
+            var isGalleryItemAdded = await GalleryFunctions.AddGalleryItem(index, fileInfo, _vm);
+            if (isGalleryItemAdded)
+            {
+                if (SettingsHelper.Settings.Gallery.IsBottomGalleryShown && ImagePaths.Count > 1)
                 {
-                    _vm.GalleryMode = GalleryMode.ClosedToBottom;
+                    if (_vm.GalleryMode is GalleryMode.BottomToClosed or GalleryMode.FullToClosed)
+                    {
+                        _vm.GalleryMode = GalleryMode.ClosedToBottom;
+                    }
                 }
+
+                var indexOf = ImagePaths.IndexOf(_vm.FileInfo.FullName);
+                _vm.SelectedGalleryItemIndex = indexOf; // Fixes deselection bug
+                CurrentIndex = indexOf;
+                GalleryNavigation.CenterScrollToSelectedItem(_vm);
             }
-            var indexOf = ImagePaths.IndexOf(_vm.FileInfo.FullName);
-            _vm.SelectedGalleryItemIndex = indexOf; // Fixes deselection bug
-            CurrentIndex = indexOf;
-            GalleryNavigation.CenterScrollToSelectedItem(_vm);
-        }
 
 
-        if (cleared)
-        {
-            await Preload();
-        }
-    }
-    
-    private async Task OnFileDeleted(FileSystemEventArgs e)
-    {
-        if (IsRenamingInProgress)
-        {
-            return;
-        }
-        if (e.FullPath.IsSupported() == false)
-        {
-            return;
+            if (cleared)
+            {
+                await Preload();
+            }
         }
 
-        if (ImagePaths.Contains(e.FullPath) == false)
+        private async Task OnFileDeleted(FileSystemEventArgs e)
         {
-            return;
-        }
+            if (IsRenamingInProgress)
+            {
+                return;
+            }
 
-        if (_isRunning)
-        {
-            return;
-        }
-        _isRunning = true;
-        var index = ImagePaths.IndexOf(e.FullPath);
-        if (index < 0) { return; }
+            if (e.FullPath.IsSupported() == false)
+            {
+                return;
+            }
 
-        var nextIndex = index + 1;
-        if (index >= ImagePaths.Count)
-        {
-            nextIndex = 0;
-        }
-        var prevIndex = index - 1;
-        if (prevIndex < 0)
-        {
-            prevIndex = ImagePaths.Count - 1;
-        }
+            if (ImagePaths.Contains(e.FullPath) == false)
+            {
+                return;
+            }
 
-        var cleared = false;
-        if (PreLoader.Contains(index, ImagePaths) || PreLoader.Contains(nextIndex, ImagePaths) || PreLoader.Contains(prevIndex, ImagePaths))
-        {
-            PreLoader.Clear();
-            cleared = true;
-        }
-        else
-        {
-            PreLoader.Remove(index, ImagePaths);
-        }
-        
-        var sameFile = CurrentIndex == index;
-        if (!ImagePaths.Remove(e.FullPath))
-        {
-            return;
-        }
-        
-        if (sameFile)
-        {
-            if (ImagePaths.Count <= 0)
+            if (_isRunning)
             {
-                ErrorHandling.ShowStartUpMenu(_vm);
                 return;
             }
-            await NavigationHelper.Iterate(next:false, _vm);
-        }
-        else
-        {
+
+            _isRunning = true;
+            var index = ImagePaths.IndexOf(e.FullPath);
+            if (index < 0)
+            {
+                return;
+            }
+
+            var nextIndex = index + 1;
+            if (index >= ImagePaths.Count)
+            {
+                nextIndex = 0;
+            }
+
+            var prevIndex = index - 1;
+            if (prevIndex < 0)
+            {
+                prevIndex = ImagePaths.Count - 1;
+            }
+
+            var cleared = false;
+            if (PreLoader.Contains(index, ImagePaths) || PreLoader.Contains(nextIndex, ImagePaths) ||
+                PreLoader.Contains(prevIndex, ImagePaths))
+            {
+                PreLoader.Clear();
+                cleared = true;
+            }
+            else
+            {
+                PreLoader.Remove(index, ImagePaths);
+            }
+
+            var sameFile = CurrentIndex == index;
+            if (!ImagePaths.Remove(e.FullPath))
+            {
+                return;
+            }
+
+            if (sameFile)
+            {
+                if (ImagePaths.Count <= 0)
+                {
+                    ErrorHandling.ShowStartUpMenu(_vm);
+                    return;
+                }
+
+                await NavigationHelper.Iterate(false, _vm);
+            }
+            else
+            {
+                SetTitleHelper.SetTitle(_vm);
+            }
+
+            var removed = GalleryFunctions.RemoveGalleryItem(index, _vm);
+            if (removed)
+            {
+                if (SettingsHelper.Settings.Gallery.IsBottomGalleryShown)
+                {
+                    if (ImagePaths.Count == 1)
+                    {
+                        _vm.GalleryMode = GalleryMode.BottomToClosed;
+                    }
+                }
+
+                var indexOf = ImagePaths.IndexOf(_vm.FileInfo.FullName);
+                _vm.SelectedGalleryItemIndex = indexOf; // Fixes deselection bug
+                CurrentIndex = indexOf;
+                GalleryNavigation.CenterScrollToSelectedItem(_vm);
+            }
+
+
+            FileHistoryNavigation.Remove(e.FullPath);
+            _isRunning = false;
+
             SetTitleHelper.SetTitle(_vm);
+            if (cleared)
+            {
+                await Preload();
+            }
         }
 
-        var removed = GalleryFunctions.RemoveGalleryItem(index, _vm);
-        if (removed)
+        private async Task OnFileRenamed(RenamedEventArgs e)
         {
-            if (SettingsHelper.Settings.Gallery.IsBottomGalleryShown)
+            if (IsRenamingInProgress)
             {
-                if (ImagePaths.Count == 1)
+                return;
+            }
+
+            if (e.FullPath.IsSupported() == false)
+            {
+                if (ImagePaths.Contains(e.OldFullPath))
                 {
-                    _vm.GalleryMode = GalleryMode.BottomToClosed;
+                    ImagePaths.Remove(e.OldFullPath);
                 }
+
+                return;
             }
-            var indexOf = ImagePaths.IndexOf(_vm.FileInfo.FullName);
-            _vm.SelectedGalleryItemIndex = indexOf; // Fixes deselection bug
-            CurrentIndex = indexOf;
-            GalleryNavigation.CenterScrollToSelectedItem(_vm);
+
+            if (_isRunning)
+            {
+                return;
+            }
+
+            _isRunning = true;
+
+            var oldIndex = ImagePaths.IndexOf(e.OldFullPath);
+
+            var fileInfo = new FileInfo(e.FullPath);
+            if (fileInfo.Exists == false)
+            {
+                return;
+            }
+
+            var newList = FileListHelper.RetrieveFiles(fileInfo).ToList();
+            if (newList.Count == 0)
+            {
+                return;
+            }
+
+            if (fileInfo.Exists == false)
+            {
+                return;
+            }
+
+            ImagePaths = newList;
+
+            var index = ImagePaths.IndexOf(e.FullPath);
+            if (index < 0)
+            {
+                return;
+            }
+
+            if (fileInfo.Exists == false)
+            {
+                return;
+            }
+
+            SetTitleHelper.SetTitle(_vm);
+
+            await PreLoader.RefreshFileInfo(oldIndex, ImagePaths);
+
+            _isRunning = false;
+            FileHistoryNavigation.Rename(e.OldFullPath, e.FullPath);
+            GalleryFunctions.RemoveGalleryItem(oldIndex, _vm);
+            await GalleryFunctions.AddGalleryItem(index, fileInfo, _vm);
+            await GalleryFunctions.SortGalleryItems(ImagePaths, _vm);
         }
 
+        #endregion
 
-        FileHistoryNavigation.Remove(e.FullPath);
-        _isRunning = false;
+        #region Preloader
 
-        SetTitleHelper.SetTitle(_vm);
-        if (cleared)
+        public void Clear()
         {
-            await Preload();
+            PreLoader.Clear();
         }
-    }
-    
-    private async Task OnFileRenamed(RenamedEventArgs e)
-    {
-        if (IsRenamingInProgress)
+
+        public async Task Preload()
         {
-            return;
+            await PreLoader.PreLoadAsync(CurrentIndex, ImagePaths.Count, IsReversed, ImagePaths).ConfigureAwait(false);
         }
-        if (e.FullPath.IsSupported() == false)
+
+        public async Task AddAsync(int index, ImageModel imageModel)
         {
-            if (ImagePaths.Contains(e.OldFullPath))
-            {
-                ImagePaths.Remove(e.OldFullPath);
-            }
-            return;
+            await PreLoader.AddAsync(index, ImagePaths, imageModel).ConfigureAwait(false);
         }
-        if (_isRunning) { return; }
-        _isRunning = true;
-
-        var oldIndex = ImagePaths.IndexOf(e.OldFullPath);
-
-        var fileInfo = new FileInfo(e.FullPath);
-        if (fileInfo.Exists == false) { return; }
 
-        var newList = FileListHelper.RetrieveFiles(fileInfo).ToList();
-        if (newList.Count == 0) { return; }
-
-        if (fileInfo.Exists == false) { return; }
-        
-        ImagePaths = newList;
+        public PreLoader.PreLoadValue? GetPreLoadValue(int index)
+        {
+            return PreLoader.Get(index, ImagePaths);
+        }
 
-        var index = ImagePaths.IndexOf(e.FullPath);
-        if (index < 0) { return; }
+        public PreLoader.PreLoadValue? GetCurrentPreLoadValue()
+        {
+            return PreLoader.Get(CurrentIndex, ImagePaths);
+        }
 
-        if (fileInfo.Exists == false)
+        public async Task<PreLoader.PreLoadValue?> GetCurrentPreLoadValueAsync()
         {
-            return;
+            return await PreLoader.GetAsync(CurrentIndex, ImagePaths);
         }
-        
-        SetTitleHelper.SetTitle(_vm);
 
-        await PreLoader.RefreshFileInfo(oldIndex, ImagePaths);
+        public PreLoader.PreLoadValue? GetNextPreLoadValue()
+        {
+            var nextIndex = GetIteration(CurrentIndex, NavigateTo.Next);
+            return PreLoader.Get(nextIndex, ImagePaths);
+        }
 
-        _isRunning = false;
-        FileHistoryNavigation.Rename(e.OldFullPath, e.FullPath);
-        GalleryFunctions.RemoveGalleryItem(oldIndex, _vm);
-        await GalleryFunctions.AddGalleryItem(index,fileInfo, _vm);
-        await GalleryFunctions.SortGalleryItems(ImagePaths, _vm);
-    }
+        public async Task<PreLoader.PreLoadValue?>? GetNextPreLoadValueAsync()
+        {
+            var nextIndex = GetIteration(CurrentIndex, NavigateTo.Next);
+            return await PreLoader.GetAsync(nextIndex, ImagePaths);
+        }
 
-    #endregion
+        public void RemoveItemFromPreLoader(int index)
+        {
+            PreLoader.Remove(index, ImagePaths);
+        }
 
-    #region Preloader
+        public void RemoveCurrentItemFromPreLoader()
+        {
+            PreLoader.Remove(CurrentIndex, ImagePaths);
+        }
 
-    public void Clear()
-    {
-        PreLoader.Clear();
-    }
-    
-    public async Task Preload()
-    {
-        await PreLoader.PreLoadAsync(CurrentIndex, ImagePaths.Count, IsReversed, ImagePaths).ConfigureAwait(false);
-    }
+        #endregion
 
-    public async Task AddAsync(int index, ImageModel imageModel)
-    {
-        await PreLoader.AddAsync(index, ImagePaths, imageModel).ConfigureAwait(false);
-    }
+        #region Navigation
 
-    public PreLoader.PreLoadValue? GetPreLoadValue(int index)
-    {
-        return PreLoader.Get(index, ImagePaths);
-    }
-    
-    public PreLoader.PreLoadValue? GetCurrentPreLoadValue()
-    {
-        return PreLoader.Get(CurrentIndex, ImagePaths);
-    }
-    
-    public async Task<PreLoader.PreLoadValue?> GetCurrentPreLoadValueAsync()
-    {
-        return await PreLoader.GetAsync(CurrentIndex, ImagePaths);
-    }
-    
-    public void RemoveItemFromPreLoader(int index)
-    {
-        PreLoader.Remove(index, ImagePaths);
-    }
-    
-    public void RemoveCurrentItemFromPreLoader()
-    {
-        PreLoader.Remove(CurrentIndex, ImagePaths);
-    }
-    
-    #endregion
-    
-    #region Navigation
-    
-    public async Task ReloadFileList()
-    {
-        ImagePaths = await Task.FromResult(_vm.PlatformService.GetFiles(InitialFileInfo)).ConfigureAwait(false);
-        CurrentIndex = ImagePaths.IndexOf(InitialFileInfo.FullName);
-        InitiateFileSystemWatcher(InitialFileInfo);
-    }
+        public async Task ReloadFileList()
+        {
+            ImagePaths = await Task.FromResult(_vm.PlatformService.GetFiles(InitialFileInfo)).ConfigureAwait(false);
+            CurrentIndex = ImagePaths.IndexOf(InitialFileInfo.FullName);
+            InitiateFileSystemWatcher(InitialFileInfo);
+        }
 
-    public int GetIteration(int index, NavigateTo navigateTo)
-    {
-        int next;
-        switch (navigateTo)
+        public int GetIteration(int index, NavigateTo navigateTo, bool skip1 = false)
         {
-            case NavigateTo.Next:
-            case NavigateTo.Previous:
-                var indexChange = navigateTo == NavigateTo.Next ? 1 : -1;
-                IsReversed = navigateTo == NavigateTo.Previous;
-                if (SettingsHelper.Settings.UIProperties.Looping)
-                {
-                    next = (index + indexChange + ImagePaths.Count) % ImagePaths.Count;
-                }
-                else
-                {
-                    var newIndex = index + indexChange;
-                    if (newIndex < 0)
+            int next;
+            var skipAmount = skip1 ? 2 : 1;
+
+            switch (navigateTo)
+            {
+                case NavigateTo.Next:
+                case NavigateTo.Previous:
+                    var indexChange = navigateTo == NavigateTo.Next ? skipAmount : -skipAmount;
+                    IsReversed = navigateTo == NavigateTo.Previous;
+            
+                    if (SettingsHelper.Settings.UIProperties.Looping)
                     {
-                        return 0;
+                        next = (index + indexChange + ImagePaths.Count) % ImagePaths.Count;
                     }
-                    if (newIndex >= ImagePaths.Count)
+                    else
                     {
-                        return ImagePaths.Count - 1;
+                        var newIndex = index + indexChange;
+                
+                        // Ensure the new index doesn't go out of bounds
+                        if (newIndex < 0)
+                        {
+                            return 0;
+                        }
+
+                        if (newIndex >= ImagePaths.Count)
+                        {
+                            return ImagePaths.Count - 1;
+                        }
+
+                        next = newIndex;
                     }
-                    next = newIndex;
-                }
+                    break;
 
-                break;
+                case NavigateTo.First:
+                case NavigateTo.Last:
+                    if (ImagePaths.Count > PreLoader.MaxCount)
+                    {
+                        PreLoader.Clear();
+                    }
 
-            case NavigateTo.First:
-            case NavigateTo.Last:
-                if (ImagePaths.Count > PreLoader.MaxCount)
-                    PreLoader.Clear();
-                next = navigateTo == NavigateTo.First ? 0 : ImagePaths.Count - 1;
-                break;
+                    next = navigateTo == NavigateTo.First ? 0 : ImagePaths.Count - 1;
+                    break;
 
-            default: return -1;
-        }
-        return next;
-    }
+                default: 
+                    return -1;
+            }
 
-    public async Task NextIteration(NavigateTo navigateTo)
-    {
-        var index = GetIteration(CurrentIndex, navigateTo);
-        if (index < 0)
-        {
-            return;
+            return next;
         }
 
-        if (!MainKeyboardShortcuts.IsKeyHeldDown)
+        public async Task NextIteration(NavigateTo navigateTo)
         {
-            await IterateToIndex(index);
-        }
-        else
-        { 
-            await TimerIteration(index);
-        }
-    }
+            var index = GetIteration(CurrentIndex, navigateTo, SettingsHelper.Settings.ImageScaling.ShowImageSideBySide);
+            if (index < 0)
+            {
+                return;
+            }
 
-    public async Task IterateToIndex(int index)
-    {
-        if (index < 0 || index >= ImagePaths.Count)
-        {
-            ErrorHandling.ShowStartUpMenu(_vm);
-            return;
+            if (!MainKeyboardShortcuts.IsKeyHeldDown)
+            {
+                await IterateToIndex(index);
+            }
+            else
+            {
+                await TimerIteration(index);
+            }
         }
 
-        try
+        public async Task IterateToIndex(int index)
         {
-            lock (_lock)
+            if (index < 0 || index >= ImagePaths.Count)
             {
-                CurrentIndex = index;
+                ErrorHandling.ShowStartUpMenu(_vm);
+                return;
             }
 
-            // ReSharper disable once MethodHasAsyncOverload
-            var preloadValue = PreLoader.Get(index, ImagePaths);
-            if (preloadValue is not null)
+            try
             {
-                if (preloadValue.IsLoading)
+                lock (_lock)
+                {
+                    CurrentIndex = index;
+                }
+
+                // ReSharper disable once MethodHasAsyncOverload
+                var preloadValue = PreLoader.Get(index, ImagePaths);
+                if (preloadValue is not null)
+                {
+                    if (preloadValue.IsLoading)
+                    {
+                        if (index == CurrentIndex)
+                        {
+                            LoadingPreview(index);
+                        }
+                    }
+
+                    while (preloadValue.IsLoading)
+                    {
+                        await Task.Delay(20);
+                        lock (_lock)
+                        {
+                            if (CurrentIndex != index)
+                            {
+                                // Skip loading if user went to next value
+                                return;
+                            }
+                        }
+                    }
+                }
+                else
                 {
                     if (index == CurrentIndex)
                     {
                         LoadingPreview(index);
                     }
+
+                    preloadValue = await PreLoader.GetAsync(CurrentIndex, ImagePaths);
                 }
-                while (preloadValue.IsLoading)
+
+                lock (_lock)
                 {
-                    await Task.Delay(20);
-                    lock (_lock)
+                    if (CurrentIndex != index)
                     {
-                        if (CurrentIndex != index)
-                        {
-                            // Skip loading if user went to next value
-                            return;
-                        }
+                        // Skip loading if user went to next value
+                        return;
                     }
                 }
-            }
-            else
-            {
-                if (index == CurrentIndex)
+
+                if (SettingsHelper.Settings.ImageScaling.ShowImageSideBySide)
                 {
-                    LoadingPreview(index);
+                    var nextPreloadValue = await GetNextPreLoadValueAsync();
+                    _vm.SecondaryImageSource = nextPreloadValue.ImageModel.Image;
+                    await UpdateSource(index, preloadValue, nextPreloadValue);
+                }
+                else
+                {
+                    await UpdateSource(index, preloadValue);
                 }
-                preloadValue = await PreLoader.GetAsync(CurrentIndex, ImagePaths);
-            }
 
-            lock (_lock)
-            {
-                if (CurrentIndex != index)
+
+                if (ImagePaths.Count > 1)
                 {
-                    // Skip loading if user went to next value
-                    return;
+                    if (SettingsHelper.Settings.UIProperties.IsTaskbarProgressEnabled)
+                    {
+                        _vm.PlatformService.SetTaskbarProgress(index / (double)ImagePaths.Count);
+                    }
+
+                    await Preload();
                 }
-            }
-            
-            await UpdateSource(index, preloadValue);
-            if (ImagePaths.Count > 1)
-            {
-                if (SettingsHelper.Settings.UIProperties.IsTaskbarProgressEnabled)
+
+                // Add recent files, except when browsing archive
+                if (string.IsNullOrWhiteSpace(TempFileHelper.TempFilePath) && ImagePaths.Count > index)
                 {
-                    _vm.PlatformService.SetTaskbarProgress(index / (double)ImagePaths.Count);
+                    FileHistoryNavigation.Add(ImagePaths[index]);
                 }
 
-                await Preload();
+                await AddAsync(index, preloadValue.ImageModel);
             }
-
-            // Add recent files, except when browsing archive
-            if (string.IsNullOrWhiteSpace(TempFileHelper.TempFilePath) && ImagePaths.Count > index)
+            catch (Exception e)
             {
-                FileHistoryNavigation.Add(ImagePaths[index]);
+#if DEBUG
+                Console.WriteLine($"{nameof(IterateToIndex)} exception: \n{e.Message}");
+                await TooltipHelper.ShowTooltipMessageAsync(e.Message);
+#endif
+            }
+            finally
+            {
+                _vm.IsLoading = false;
             }
-
-            await AddAsync(index, preloadValue.ImageModel);
-        }
-        catch (Exception e)
-        {
-    #if DEBUG
-            Console.WriteLine($"{nameof(IterateToIndex)} exception: \n{e.Message}");
-            await TooltipHelper.ShowTooltipMessageAsync(e.Message);
-    #endif
-        }
-        finally
-        {
-            _vm.IsLoading = false;
         }
-    }
 
-    private static Timer? _timer;
+        private static Timer? _timer;
 
-    internal async Task TimerIteration(int index)
-    {
-        if (_timer is null)
+        internal async Task TimerIteration(int index)
         {
-            _timer = new Timer
+            if (_timer is null)
             {
-                AutoReset = false,
-                Enabled = true
-            };
-        }
-        else if (_timer.Enabled)
-        {
-            if (!MainKeyboardShortcuts.IsKeyHeldDown)
-            {
-                _timer = null;
+                _timer = new Timer
+                {
+                    AutoReset = false,
+                    Enabled = true
+                };
             }
-            return;
-        }
-
-        _timer.Interval = TimeSpan.FromSeconds(SettingsHelper.Settings.UIProperties.NavSpeed).TotalMilliseconds;
-        _timer.Start();
-        await IterateToIndex(index);
-    }
-    
-    public void UpdateFileListAndIndex(List<string> fileList, int index)
-    {
-        ImagePaths = fileList;
-        CurrentIndex = index;
-    }
-    
-    #endregion
+            else if (_timer.Enabled)
+            {
+                if (!MainKeyboardShortcuts.IsKeyHeldDown)
+                {
+                    _timer = null;
+                }
 
-    #region Update Source and Preview
+                return;
+            }
 
-    private async Task UpdateSource(int index, PreLoader.PreLoadValue? preLoadValue)
-    {
-        preLoadValue ??= await PreLoader.GetAsync(index, ImagePaths);
-        if (preLoadValue.ImageModel?.Image is null)
-        {
-            var fileInfo = preLoadValue.ImageModel?.FileInfo ?? new FileInfo(ImagePaths[index]);
-            preLoadValue.ImageModel = await ImageHelper.GetImageModelAsync(fileInfo).ConfigureAwait(false);
+            _timer.Interval = TimeSpan.FromSeconds(SettingsHelper.Settings.UIProperties.NavSpeed).TotalMilliseconds;
+            _timer.Start();
+            await IterateToIndex(index);
         }
-        _vm.IsLoading = false;
-        ExifHandling.SetImageModel(preLoadValue.ImageModel, vm: _vm);
-        _vm.ImageSource = preLoadValue.ImageModel.Image;
-        if (preLoadValue.ImageModel.ImageType is ImageType.AnimatedGif or ImageType.AnimatedWebp)
+
+        public void UpdateFileListAndIndex(List<string> fileList, int index)
         {
-            _vm.ImageViewer.MainImage.InitialAnimatedSource = preLoadValue.ImageModel.FileInfo.FullName;
+            ImagePaths = fileList;
+            CurrentIndex = index;
         }
-        _vm.ImageType = preLoadValue.ImageModel.ImageType;
-        await Dispatcher.UIThread.InvokeAsync(() =>
-        {
-            WindowHelper.SetSize(preLoadValue.ImageModel.PixelWidth, preLoadValue.ImageModel.PixelHeight, preLoadValue.ImageModel.Rotation, _vm);
-            SetTitleHelper.SetTitle(_vm, preLoadValue.ImageModel);
-        });
-        
-        if (_vm.RotationAngle != 0)
+
+        #endregion
+
+        #region Update Source and Preview
+
+        private async Task UpdateSource(int index, PreLoader.PreLoadValue? preLoadValue, PreLoader.PreLoadValue? nextPreloadValue = null)
         {
-            await Dispatcher.UIThread.InvokeAsync(() =>
+            preLoadValue ??= await PreLoader.GetAsync(index, ImagePaths);
+            if (preLoadValue.ImageModel?.Image is null)
             {
-                _vm.ImageViewer.Rotate(_vm.RotationAngle);
-            });
-        }
-        if (SettingsHelper.Settings.WindowProperties.KeepCentered)
-        {
+                var fileInfo = preLoadValue.ImageModel?.FileInfo ?? new FileInfo(ImagePaths[index]);
+                preLoadValue.ImageModel = await ImageHelper.GetImageModelAsync(fileInfo).ConfigureAwait(false);
+            }
+
+            _vm.IsLoading = false;
+            ExifHandling.SetImageModel(preLoadValue.ImageModel, _vm);
+            _vm.ImageSource = preLoadValue.ImageModel.Image;
+            if (preLoadValue.ImageModel.ImageType is ImageType.AnimatedGif or ImageType.AnimatedWebp)
+            {
+                _vm.ImageViewer.MainImage.InitialAnimatedSource = preLoadValue.ImageModel.FileInfo.FullName;
+            }
+
+            _vm.ImageType = preLoadValue.ImageModel.ImageType;
             await Dispatcher.UIThread.InvokeAsync(() =>
             {
-                WindowHelper.CenterWindowOnScreen(false);
+                WindowHelper.SetSize(preLoadValue.ImageModel.PixelWidth, preLoadValue.ImageModel.PixelHeight,
+                   nextPreloadValue?.ImageModel?.PixelWidth ?? 0, nextPreloadValue?.ImageModel?.PixelHeight ?? 0, preLoadValue.ImageModel.Rotation, _vm);
+                SetTitleHelper.SetTitle(_vm, preLoadValue.ImageModel);
             });
+
+            if (_vm.RotationAngle != 0)
+            {
+                await Dispatcher.UIThread.InvokeAsync(() => { _vm.ImageViewer.Rotate(_vm.RotationAngle); });
+            }
+
+            if (SettingsHelper.Settings.WindowProperties.KeepCentered)
+            {
+                await Dispatcher.UIThread.InvokeAsync(() => { WindowHelper.CenterWindowOnScreen(false); });
+            }
+
+            _vm.GetIndex = index + 1;
+            if (_vm.SelectedGalleryItemIndex != index)
+            {
+                _vm.SelectedGalleryItemIndex = index;
+                if (SettingsHelper.Settings.Gallery.IsBottomGalleryShown)
+                {
+                    GalleryNavigation.CenterScrollToSelectedItem(_vm);
+                }
+            }
+
+            await Dispatcher.UIThread.InvokeAsync(TooltipHelper.CloseToolTipMessage);
+
+            ExifHandling.UpdateExifValues(preLoadValue.ImageModel, _vm);
         }
-        
-        _vm.GetIndex = index + 1;
-        if (_vm.SelectedGalleryItemIndex != index)
+
+        public void LoadingPreview(int index)
         {
+            if (index != CurrentIndex)
+            {
+                return;
+            }
+
+            SetTitleHelper.SetLoadingTitle(_vm);
             _vm.SelectedGalleryItemIndex = index;
             if (SettingsHelper.Settings.Gallery.IsBottomGalleryShown)
             {
                 GalleryNavigation.CenterScrollToSelectedItem(_vm);
             }
-        }
-        await Dispatcher.UIThread.InvokeAsync(TooltipHelper.CloseToolTipMessage);
-        
-        ExifHandling.UpdateExifValues(preLoadValue.ImageModel, vm: _vm);
-    }
-    
-    public void LoadingPreview(int index)
-    {
-        if (index != CurrentIndex)
-        {
-            return;
-        }
-        SetTitleHelper.SetLoadingTitle(_vm);
-        _vm.SelectedGalleryItemIndex = index;
-        if (SettingsHelper.Settings.Gallery.IsBottomGalleryShown)
-        {
-            GalleryNavigation.CenterScrollToSelectedItem(_vm);
-        }
-        using var image = new MagickImage();
-        image.Ping(_vm.ImageIterator.ImagePaths[index]);
-        var thumb = image.GetExifProfile()?.CreateThumbnail();
-        if (thumb is null)
-        {
-            if (index == CurrentIndex)
+
+            using var image = new MagickImage();
+            image.Ping(_vm.ImageIterator.ImagePaths[index]);
+            var thumb = image.GetExifProfile()?.CreateThumbnail();
+            if (thumb is null)
             {
-                _vm.IsLoading = true;
-                _vm.ImageSource = null;
+                if (index == CurrentIndex)
+                {
+                    _vm.IsLoading = true;
+                    _vm.ImageSource = null;
+                }
+
+                return;
             }
-            return;
-        }
 
-        var byteArray = thumb.ToByteArray();
-        if (byteArray is null)
-        {
-            if (index == CurrentIndex)
+            var byteArray = thumb.ToByteArray();
+            if (byteArray is null)
             {
-                _vm.IsLoading = true;
-                _vm.ImageSource = null;
+                if (index == CurrentIndex)
+                {
+                    _vm.IsLoading = true;
+                    _vm.ImageSource = null;
+                }
+
+                return;
+            }
+
+            var stream = new MemoryStream(byteArray);
+            if (index != CurrentIndex)
+            {
+                return;
             }
-            return;
+
+            _vm.ImageSource = new Bitmap(stream);
+            _vm.ImageType = ImageType.Bitmap;
+            //WindowHelper.SetSize(image?.Width ?? 0, image?.Height ?? 0, 0,0, 0, _vm);
         }
-        var stream = new MemoryStream(byteArray);
-        if (index != CurrentIndex)
+
+        #endregion
+
+        #region IDisposable
+
+        public void Dispose()
         {
-            return;
+            Dispose(true);
+            GC.SuppressFinalize(this);
         }
 
-        _vm.ImageSource = new Bitmap(stream);
-        _vm.ImageType = ImageType.Bitmap;
-        WindowHelper.SetSize(image?.Width ?? 0, image?.Height ?? 0, 0, _vm);
-    }
-
-    #endregion
-    
-    #region IDisposable
+        private void Dispose(bool disposing)
+        {
+            if (_disposed)
+            {
+                return;
+            }
 
-    public void Dispose()
-    {
-        Dispose(true);
-        GC.SuppressFinalize(this);
-    }
+            if (disposing)
+            {
+                _watcher?.Dispose();
+                Clear();
+                _timer?.Dispose();
+            }
 
-    private void Dispose(bool disposing)
-    {
-        if (_disposed)
-            return;
+            _disposed = true;
+        }
 
-        if (disposing)
+        ~ImageIterator()
         {
-            _watcher?.Dispose();
-            Clear();
-            _timer?.Dispose();
+            Dispose(false);
         }
 
-        _disposed = true;
-    }
-
-    ~ImageIterator()
-    {
-        Dispose(false);
+        #endregion
     }
-    
-    #endregion
-}
+}

+ 2 - 2
src/PicView.Avalonia/Navigation/NavigationHelper.cs

@@ -470,7 +470,7 @@ public static class NavigationHelper
             // Set to closed to ensure next gallery mode changing is fired
             vm.GalleryMode = GalleryMode.Closed;
         }
-        WindowHelper.SetSize(width, height, 0, vm);
+        WindowHelper.SetSize(width, height, 0,0, 0, vm);
         if (vm.RotationAngle != 0)
         {
             vm.ImageViewer.Rotate(vm.RotationAngle);
@@ -534,7 +534,7 @@ public static class NavigationHelper
         ExifHandling.SetImageModel(imageModel, vm);
         vm.ImageSource = imageModel.Image;
         vm.ImageType = imageModel.ImageType;
-        WindowHelper.SetSize(imageModel.PixelWidth, imageModel.PixelHeight, imageModel.Rotation, vm);
+        WindowHelper.SetSize(imageModel.PixelWidth, imageModel.PixelHeight, 0,0, imageModel.Rotation, vm);
         if (files is null)
         {
             vm.ImageIterator = new ImageIterator(fileInfo, vm);

+ 10 - 3
src/PicView.Avalonia/Navigation/QuickLoad.cs

@@ -34,7 +34,14 @@ public static class QuickLoad
         }
         vm.ImageSource = imageModel.Image;
         vm.ImageType = imageModel.ImageType;
-        WindowHelper.SetSize(imageModel.PixelWidth, imageModel.PixelHeight, imageModel.Rotation, vm);
+        PreLoader.PreLoadValue? secondaryPreloadValue = null;
+        if (SettingsHelper.Settings.ImageScaling.ShowImageSideBySide)
+        {
+            vm.ImageIterator = new ImageIterator(fileInfo, vm);
+            secondaryPreloadValue = await vm.ImageIterator.GetNextPreLoadValueAsync();
+            vm.SecondaryImageSource = secondaryPreloadValue?.ImageModel?.Image;
+        }
+        WindowHelper.SetSize(imageModel.PixelWidth, imageModel.PixelHeight, secondaryPreloadValue?.ImageModel?.PixelWidth ?? 0, secondaryPreloadValue?.ImageModel?.PixelHeight ?? 0, imageModel.Rotation, vm);
         vm.IsLoading = false;
         imageModel.EXIFOrientation = ImageHelper.GetExifOrientation(vm);
         ExifHandling.SetImageModel(imageModel, vm);
@@ -57,11 +64,11 @@ public static class QuickLoad
         }
         if (changed)
         {
-            WindowHelper.SetSize(imageModel.PixelWidth, imageModel.PixelHeight, imageModel.Rotation, vm);
+            WindowHelper.SetSize(imageModel.PixelWidth, imageModel.PixelHeight, secondaryPreloadValue?.ImageModel?.PixelWidth ?? 0, secondaryPreloadValue?.ImageModel?.PixelHeight ?? 0, imageModel.Rotation, vm);
         }
             
         ExifHandling.UpdateExifValues(imageModel, vm);
-        vm.ImageIterator = new ImageIterator(fileInfo, vm);
+        vm.ImageIterator ??= new ImageIterator(fileInfo, vm);
             
         SetTitleHelper.SetTitle(vm, imageModel);
         vm.GetIndex = vm.ImageIterator.CurrentIndex + 1;

+ 33 - 0
src/PicView.Avalonia/UI/FunctionsHelper.cs

@@ -115,6 +115,7 @@ public static class FunctionsHelper
 
             // Misc
             "ChangeBackground" => ChangeBackground,
+            "ShowSideBySide" => ShowSideBySide,
             "GalleryClick" => GalleryClick,
             "Slideshow" => Slideshow,
             "ColorPicker" => ColorPicker,
@@ -804,6 +805,38 @@ public static class FunctionsHelper
         await SettingsHelper.SaveSettingsAsync();
     }
     
+    public static async Task ShowSideBySide()
+    {
+        if (Vm is null)
+        {
+            return;
+        }
+        await Dispatcher.UIThread.InvokeAsync(() =>
+        {
+            Vm.ImageViewer.MainImage.InvalidateVisual();
+        });
+
+        if (SettingsHelper.Settings.ImageScaling.ShowImageSideBySide)
+        {
+            SettingsHelper.Settings.ImageScaling.ShowImageSideBySide = false;
+            Vm.IsShowingSideBySide = false;
+            Vm.SecondaryImageSource = null;
+            WindowHelper.SetSize(Vm.ImageWidth, Vm.ImageHeight, 0, 0, Vm.RotationAngle, Vm);
+        }
+        else
+        {
+            SettingsHelper.Settings.ImageScaling.ShowImageSideBySide = true;
+            Vm.IsShowingSideBySide = true;
+            if (NavigationHelper.CanNavigate(Vm))
+            {
+                var preloadValue = await Vm.ImageIterator?.GetNextPreLoadValueAsync();
+                Vm.SecondaryImageSource = preloadValue?.ImageModel.Image;
+                WindowHelper.SetSize(Vm.ImageWidth, Vm.ImageHeight, preloadValue.ImageModel.PixelWidth, preloadValue.ImageModel.PixelHeight, Vm.RotationAngle, Vm);
+            }
+        }
+        await SettingsHelper.SaveSettingsAsync();
+    }
+    
     public static async Task Reload()
     {
         if (Vm is null)

+ 56 - 20
src/PicView.Avalonia/UI/WindowHelper.cs

@@ -416,10 +416,21 @@ public static class WindowHelper
         }
 
         var preloadValue = vm.ImageIterator?.GetCurrentPreLoadValue();
-        SetSize(preloadValue?.ImageModel?.PixelWidth ?? vm.ImageWidth, preloadValue?.ImageModel?.PixelHeight ?? vm.ImageHeight, vm.RotationAngle, vm);
+        if (SettingsHelper.Settings.ImageScaling.ShowImageSideBySide)
+        {
+            var secondaryPreloadValue = vm.ImageIterator?.GetNextPreLoadValue();
+            if (secondaryPreloadValue != null)
+            {
+                SetSize(preloadValue?.ImageModel?.PixelWidth ?? vm.ImageWidth, preloadValue?.ImageModel?.PixelHeight ?? vm.ImageHeight, secondaryPreloadValue.ImageModel?.PixelWidth ?? vm.ImageWidth, secondaryPreloadValue.ImageModel?.PixelHeight ?? vm.ImageHeight, vm.RotationAngle, vm);
+            }
+        }
+        else
+        {
+            SetSize(preloadValue?.ImageModel?.PixelWidth ?? vm.ImageWidth, preloadValue?.ImageModel?.PixelHeight ?? vm.ImageHeight, 0, 0, vm.RotationAngle, vm);
+        }
     }
-
-    public static void SetSize(double width, double height, double rotation, MainViewModel vm)
+    
+    public static void SetSize(double width, double height, double secondWidth, double secondHeight, double rotation, MainViewModel vm)
     {
         width = width == 0 ? vm.ImageWidth : width;
         height = height == 0 ? vm.ImageHeight : height;
@@ -453,25 +464,50 @@ public static class WindowHelper
         {
             return;
         }
-        var size = ImageSizeCalculationHelper.GetImageSize(
-            width,
-            height,
-            screenSize.WorkingAreaWidth,
-            screenSize.WorkingAreaHeight,
-            desktopMinWidth,
-            desktopMinHeight,
-            ImageSizeCalculationHelper.GetInterfaceSize(),
-            rotation,
-            padding: SettingsHelper.Settings.ImageScaling.StretchImage ? 15 : 45,
-            screenSize.Scaling,
-            vm.TitlebarHeight,
-            vm.BottombarHeight,
-            vm.GalleryHeight,
-            containerWidth,
-            containerHeight);
-        
+        ImageSizeCalculationHelper.ImageSize size;
+        if (SettingsHelper.Settings.ImageScaling.ShowImageSideBySide && secondWidth > 0 && secondHeight > 0)
+        {
+            size = ImageSizeCalculationHelper.GetImageSize(
+                width,
+                height,
+                secondWidth,
+                secondHeight,
+                screenSize.WorkingAreaWidth,
+                screenSize.WorkingAreaHeight,
+                desktopMinWidth,
+                desktopMinHeight,
+                ImageSizeCalculationHelper.GetInterfaceSize(),
+                rotation,
+                padding: SettingsHelper.Settings.ImageScaling.StretchImage ? 15 : 45,
+                screenSize.Scaling,
+                vm.TitlebarHeight,
+                vm.BottombarHeight,
+                vm.GalleryHeight,
+                containerWidth,
+                containerHeight);
+        }
+        else
+        {
+            size = ImageSizeCalculationHelper.GetImageSize(
+                width,
+                height,
+                screenSize.WorkingAreaWidth,
+                screenSize.WorkingAreaHeight,
+                desktopMinWidth,
+                desktopMinHeight,
+                ImageSizeCalculationHelper.GetInterfaceSize(),
+                rotation,
+                padding: SettingsHelper.Settings.ImageScaling.StretchImage ? 15 : 45,
+                screenSize.Scaling,
+                vm.TitlebarHeight,
+                vm.BottombarHeight,
+                vm.GalleryHeight,
+                containerWidth,
+                containerHeight);
+        }
         vm.TitleMaxWidth = size.TitleMaxWidth;
         vm.ImageWidth = size.Width;
+        vm.SecondaryImageWidth = size.SecondaryWidth;
         vm.ImageHeight = size.Height;
         vm.GalleryMargin = new Thickness(0, 0, 0, size.Margin);
 

+ 20 - 0
src/PicView.Avalonia/ViewModels/MainViewModel.cs

@@ -66,6 +66,14 @@ public class MainViewModel : ViewModelBase
         get => _imageHeight;
         set => this.RaiseAndSetIfChanged(ref _imageHeight, value);
     }
+    
+    private double _imageSecondaryWidth;
+    
+    public double SecondaryImageWidth
+    {
+        get => _imageSecondaryWidth;
+        set => this.RaiseAndSetIfChanged(ref _imageSecondaryWidth, value);
+    }
 
     private Brush? _imageBackground;
     
@@ -74,6 +82,14 @@ public class MainViewModel : ViewModelBase
         get => _imageBackground;
         set => this.RaiseAndSetIfChanged(ref _imageBackground, value);
     }
+
+    private bool _isShowingSideBySide = SettingsHelper.Settings.ImageScaling.ShowImageSideBySide;
+    
+    public bool IsShowingSideBySide
+    {
+        get => _isShowingSideBySide;
+        set => this.RaiseAndSetIfChanged(ref _isShowingSideBySide, value);
+    }
     
     #endregion
 
@@ -482,6 +498,8 @@ public class MainViewModel : ViewModelBase
     public ReactiveCommand<string, Unit>? GalleryItemStretchCommand { get; }
     
     public ReactiveCommand<Unit, Unit>? ResetSettingsCommand { get; }
+    
+    public ReactiveCommand<Unit, Unit>? ShowSideBySideCommand { get; }
 
     #endregion Commands
 
@@ -1741,6 +1759,8 @@ public class MainViewModel : ViewModelBase
         OptimizeImageCommand = ReactiveCommand.CreateFromTask(FunctionsHelper.OptimizeImage);
         
         ChangeBackgroundCommand = ReactiveCommand.CreateFromTask(FunctionsHelper.ChangeBackground);
+        
+        ShowSideBySideCommand = ReactiveCommand.CreateFromTask(FunctionsHelper.ShowSideBySide);
 
         #endregion Image commands
 

+ 29 - 26
src/PicView.Avalonia/Views/ImageViewer.axaml

@@ -13,30 +13,33 @@
     <Design.DataContext>
         <vm:MainViewModel />
     </Design.DataContext>
-        <LayoutTransformControl x:Name="ImageLayoutTransformControl">
-            <customControls:AutoScrollViewer
-                Focusable="False"
-                ScrollChanged="ImageScrollViewer_OnScrollChanged"
-                VerticalScrollBarVisibility="{CompiledBinding ToggleScrollBarVisibility}"
-                x:Name="ImageScrollViewer">
-                    <customControls:PicBox
-                        Background="{CompiledBinding ImageBackground, Mode=OneWay}"
-                        Height="{CompiledBinding ImageHeight,
-                                                 Mode=OneWay}"
-                        ImageType="{CompiledBinding ImageType,
-                                                    Mode=OneWay}"
-                        RenderOptions.BitmapInterpolationMode="HighQuality"
-                        SecondarySource="{CompiledBinding SecondaryImageSource,
-                                                          Mode=OneWay}"
-                        Source="{CompiledBinding ImageSource,
-                                                 Mode=OneWay}"
-                        UseLayoutRounding="True"
-                        Width="{CompiledBinding ImageWidth,
-                                                Mode=OneWay}"
-                        x:Name="MainImage" 
-                        PointerMoved="MainImage_OnPointerMoved"
-                        PointerPressed="MainImage_OnPointerPressed"
-                        PointerReleased="MainImage_OnPointerReleased"/>
-            </customControls:AutoScrollViewer>
-        </LayoutTransformControl>
+    <LayoutTransformControl x:Name="ImageLayoutTransformControl">
+        <customControls:AutoScrollViewer
+            Focusable="False"
+            ScrollChanged="ImageScrollViewer_OnScrollChanged"
+            VerticalScrollBarVisibility="{CompiledBinding ToggleScrollBarVisibility}"
+            x:Name="ImageScrollViewer">
+            <customControls:PicBox
+                Background="{CompiledBinding ImageBackground,
+                                             Mode=OneWay}"
+                Height="{CompiledBinding ImageHeight,
+                                         Mode=OneWay}"
+                ImageType="{CompiledBinding ImageType,
+                                            Mode=OneWay}"
+                PointerMoved="MainImage_OnPointerMoved"
+                PointerPressed="MainImage_OnPointerPressed"
+                PointerReleased="MainImage_OnPointerReleased"
+                RenderOptions.BitmapInterpolationMode="HighQuality"
+                SecondaryImageWidth="{CompiledBinding SecondaryImageWidth,
+                                                      Mode=OneWay}"
+                SecondarySource="{CompiledBinding SecondaryImageSource,
+                                                  Mode=OneWay}"
+                Source="{CompiledBinding ImageSource,
+                                         Mode=OneWay}"
+                UseLayoutRounding="True"
+                Width="{CompiledBinding ImageWidth,
+                                        Mode=OneWay}"
+                x:Name="MainImage" />
+        </customControls:AutoScrollViewer>
+    </LayoutTransformControl>
 </UserControl>

+ 9 - 15
src/PicView.Avalonia/Views/UC/Menus/ImageMenu.axaml

@@ -279,25 +279,19 @@
                     </StackPanel>
                 </Button>
 
-                <Button
+                <ToggleButton
                     Canvas.Left="160"
                     Canvas.Top="99"
                     Classes="ButtonBorder altHover"
-                    IsEnabled="False"
+                    Command="{CompiledBinding ShowSideBySideCommand}"
+                    IsChecked="{CompiledBinding IsShowingSideBySide}"
                     ToolTip.Tip="View image side by side">
-                    <StackPanel Orientation="Horizontal">
-                        <Image
-                            Height="20"
-                            Margin="9,0,8,0"
-                            Source="{StaticResource DisplayImage}"
-                            Width="20" />
-                        <TextBlock
-                            Classes="txt"
-                            Margin="1,0,6,0"
-                            MaxWidth="175"
-                            Text="Side by side" />
-                    </StackPanel>
-                </Button>
+                    <TextBlock
+                        Classes="txt"
+                        Margin="1,0,6,0"
+                        MaxWidth="175"
+                        Text="Side by side" />
+                </ToggleButton>
 
                 <ToggleButton
                     Canvas.Left="7"

+ 205 - 136
src/PicView.Core/Calculations/ImageSizeCalculationHelper.cs

@@ -1,179 +1,248 @@
-using PicView.Core.Config;
-using System.Runtime.InteropServices;
+using System.Runtime.InteropServices;
+using PicView.Core.Config;
 
-namespace PicView.Core.Calculations;
-
-public static class ImageSizeCalculationHelper
+namespace PicView.Core.Calculations
 {
-    
-    public struct ImageSize(double width, double height, double titleMaxWidth, double margin, double aspectRatio)
-    {
-        public double TitleMaxWidth { get; private set; } = titleMaxWidth;
-        public double Width { get; private set; } = width;
-        public double Height { get; private set; } = height;
-        public double Margin { get; private set; } = margin;
-        
-        public double AspectRatio { get; private set; } = aspectRatio;
-    }
-
-    public static double GetInterfaceSize()
-    {
-        return RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 165 : 228;
-    }
-    
-
-    public static ImageSize GetImageSize(double width,
-        double height,
-        double monitorWidth,
-        double monitorHeight,
-        double monitorMinWidth,
-        double monitorMinHeight,
-        double interfaceSize,
-        double rotationAngle,
-        double padding,
-        double dpiScaling,
-        double uiTopSize,
-        double uiBottomSize,
-        double galleryHeight,
-        double containerWidth,
-        double containerHeight)
+    public static class ImageSizeCalculationHelper
     {
-        if (width <= 0 || height <= 0 || rotationAngle > 360 || rotationAngle < 0)
+        public static double GetInterfaceSize()
         {
-            return new ImageSize(0, 0, 0, 0,0);
+            return RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 165 : 228;
         }
 
-        double aspectRatio;
-        double maxWidth, maxHeight;
-        var margin = 0d;
-        
-        var fullscreen = SettingsHelper.Settings.WindowProperties.Fullscreen || SettingsHelper.Settings.WindowProperties.Maximized;
-
-        var borderSpaceHeight = fullscreen ?
-            0 : uiTopSize + uiBottomSize + galleryHeight;
-        var borderSpaceWidth = fullscreen ?
-            0 : padding;
-
-        var workAreaWidth = monitorWidth * dpiScaling - borderSpaceWidth;
-        var workAreaHeight = monitorHeight * dpiScaling - borderSpaceHeight;
-        
-        if (SettingsHelper.Settings.Zoom.ScrollEnabled)
-        {
-            workAreaWidth -= SizeDefaults.ScrollbarSize * dpiScaling;
-            containerWidth -= SizeDefaults.ScrollbarSize * dpiScaling;
-        }
 
-        if (SettingsHelper.Settings.WindowProperties.AutoFit)
+        public static ImageSize GetImageSize(double width,
+            double height,
+            double monitorWidth,
+            double monitorHeight,
+            double monitorMinWidth,
+            double monitorMinHeight,
+            double interfaceSize,
+            double rotationAngle,
+            double padding,
+            double dpiScaling,
+            double uiTopSize,
+            double uiBottomSize,
+            double galleryHeight,
+            double containerWidth,
+            double containerHeight)
         {
-            if (SettingsHelper.Settings.Zoom.ScrollEnabled)
+            if (width <= 0 || height <= 0 || rotationAngle > 360 || rotationAngle < 0)
             {
-                maxWidth = workAreaWidth - padding;
-                maxHeight = workAreaHeight - padding;
+                return new ImageSize(0, 0, 0, 0, 0, 0);
             }
-            else
+
+            double aspectRatio;
+            double maxWidth, maxHeight;
+            var margin = 0d;
+
+            var fullscreen = SettingsHelper.Settings.WindowProperties.Fullscreen ||
+                             SettingsHelper.Settings.WindowProperties.Maximized;
+
+            var borderSpaceHeight = fullscreen ? 0 : uiTopSize + uiBottomSize + galleryHeight;
+            var borderSpaceWidth = fullscreen ? 0 : padding;
+
+            var workAreaWidth = monitorWidth * dpiScaling - borderSpaceWidth;
+            var workAreaHeight = monitorHeight * dpiScaling - borderSpaceHeight;
+
+            if (SettingsHelper.Settings.Zoom.ScrollEnabled)
             {
-                maxWidth = SettingsHelper.Settings.ImageScaling.StretchImage ? workAreaWidth - padding : Math.Min(workAreaWidth - padding, width);
-                maxHeight = SettingsHelper.Settings.ImageScaling.StretchImage ? workAreaHeight - padding : Math.Min(workAreaHeight - padding, height);
+                workAreaWidth -= SizeDefaults.ScrollbarSize * dpiScaling;
+                containerWidth -= SizeDefaults.ScrollbarSize * dpiScaling;
             }
-        }
-        else
-        {
-            maxWidth = SettingsHelper.Settings.ImageScaling.StretchImage ? containerWidth : Math.Min(containerWidth, width);
-            
-            if (SettingsHelper.Settings.Zoom.ScrollEnabled)
+
+            if (SettingsHelper.Settings.WindowProperties.AutoFit)
             {
-                maxHeight = SettingsHelper.Settings.ImageScaling.StretchImage
-                    ? Math.Max(containerHeight, height)
-                    : height;
+                if (SettingsHelper.Settings.Zoom.ScrollEnabled)
+                {
+                    maxWidth = workAreaWidth - padding;
+                    maxHeight = workAreaHeight - padding;
+                }
+                else
+                {
+                    maxWidth = SettingsHelper.Settings.ImageScaling.StretchImage
+                        ? workAreaWidth - padding
+                        : Math.Min(workAreaWidth - padding, width);
+                    maxHeight = SettingsHelper.Settings.ImageScaling.StretchImage
+                        ? workAreaHeight - padding
+                        : Math.Min(workAreaHeight - padding, height);
+                }
             }
             else
             {
-                maxHeight = SettingsHelper.Settings.ImageScaling.StretchImage
-                    ? containerHeight - galleryHeight
-                    : Math.Min(containerHeight - galleryHeight, height);
+                maxWidth = SettingsHelper.Settings.ImageScaling.StretchImage
+                    ? containerWidth
+                    : Math.Min(containerWidth, width);
+
+                if (SettingsHelper.Settings.Zoom.ScrollEnabled)
+                {
+                    maxHeight = SettingsHelper.Settings.ImageScaling.StretchImage
+                        ? Math.Max(containerHeight, height)
+                        : height;
+                }
+                else
+                {
+                    maxHeight = SettingsHelper.Settings.ImageScaling.StretchImage
+                        ? containerHeight - galleryHeight
+                        : Math.Min(containerHeight - galleryHeight, height);
+                }
             }
-        }
 
-        if (SettingsHelper.Settings.Gallery.IsBottomGalleryShown)
-        {
-            if (!SettingsHelper.Settings.UIProperties.ShowInterface)
+            if (SettingsHelper.Settings.Gallery.IsBottomGalleryShown)
             {
-                if (SettingsHelper.Settings.Gallery.ShowBottomGalleryInHiddenUI)
+                if (!SettingsHelper.Settings.UIProperties.ShowInterface)
                 {
-                    margin = galleryHeight > 0 ? galleryHeight : 0;
+                    if (SettingsHelper.Settings.Gallery.ShowBottomGalleryInHiddenUI)
+                    {
+                        margin = galleryHeight > 0 ? galleryHeight : 0;
+                    }
+                    else
+                    {
+                        margin = 0;
+                    }
                 }
                 else
                 {
-                    margin = 0;
+                    margin = galleryHeight > 0 ? galleryHeight : 0;
                 }
             }
-            else
+
+            // aspect ratio calculation
+            switch (rotationAngle)
             {
-                margin = galleryHeight > 0 ? galleryHeight : 0;
+                case 0:
+                case 180:
+                    aspectRatio = Math.Min(maxWidth / width, maxHeight / height);
+                    break;
+
+                case 90:
+                case 270:
+                    aspectRatio = Math.Min(maxWidth / height, maxHeight / width);
+                    break;
+
+                default:
+                    var rotationRadians = rotationAngle * Math.PI / 180;
+                    var newWidth = Math.Abs(width * Math.Cos(rotationRadians)) +
+                                   Math.Abs(height * Math.Sin(rotationRadians));
+                    var newHeight = Math.Abs(width * Math.Sin(rotationRadians)) +
+                                    Math.Abs(height * Math.Cos(rotationRadians));
+                    aspectRatio = Math.Min(maxWidth / newWidth, maxHeight / newHeight);
+                    break;
             }
+
+            // Fit image by aspect ratio calculation
+            // and update values
+            var xWidth = width * aspectRatio;
+            var xHeight = height * aspectRatio;
+
+            var titleMaxWidth = GetTitleMaxWidth(rotationAngle, xWidth, xHeight, monitorMinWidth, monitorMinHeight,
+                interfaceSize, containerWidth);
+
+            return new ImageSize(xWidth, xHeight, 0, titleMaxWidth, margin, aspectRatio);
         }
 
-        // aspect ratio calculation
-        switch (rotationAngle)
+        public static ImageSize GetImageSize(double width,
+            double height,
+            double secondaryWidth,
+            double secondaryHeight,
+            double monitorWidth,
+            double monitorHeight,
+            double monitorMinWidth,
+            double monitorMinHeight,
+            double interfaceSize,
+            double rotationAngle,
+            double padding,
+            double dpiScaling,
+            double uiTopSize,
+            double uiBottomSize,
+            double galleryHeight,
+            double containerWidth,
+            double containerHeight)
         {
-            case 0:
-            case 180:
-                aspectRatio = Math.Min(maxWidth / width, maxHeight / height);
-                break;
-
-            case 90:
-            case 270:
-                aspectRatio = Math.Min(maxWidth / height, maxHeight / width);
-                break;
-
-            default:
-                var rotationRadians = rotationAngle * Math.PI / 180;
-                var newWidth = Math.Abs(width * Math.Cos(rotationRadians)) +
-                               Math.Abs(height * Math.Sin(rotationRadians));
-                var newHeight = Math.Abs(width * Math.Sin(rotationRadians)) +
-                                Math.Abs(height * Math.Cos(rotationRadians));
-                aspectRatio = Math.Min(maxWidth / newWidth, maxHeight / newHeight);
-                break;
-        }
+            if (width <= 0 || height <= 0 || secondaryWidth <= 0 || secondaryHeight <= 0 || rotationAngle > 360 ||
+                rotationAngle < 0)
+            {
+                return new ImageSize(0, 0, 0, 0, 0,0);
+            }
 
-        // Fit image by aspect ratio calculation
-        // and update values
-        var xWidth = width * aspectRatio;
-        var xHeight = height * aspectRatio;
-        
-        var titleMaxWidth = GetTitleMaxWidth(rotationAngle, xWidth, xHeight, monitorMinWidth, monitorMinHeight, interfaceSize, containerWidth);
+            // Get sizes for both images
+            var firstSize = GetImageSize(width, height, monitorWidth, monitorHeight, monitorMinWidth, monitorMinHeight,
+                interfaceSize, rotationAngle, padding, dpiScaling, uiTopSize, uiBottomSize, galleryHeight, containerWidth,
+                containerHeight);
+            var secondSize = GetImageSize(secondaryWidth, secondaryHeight, monitorWidth, monitorHeight, monitorMinWidth,
+                monitorMinHeight, interfaceSize, rotationAngle, padding, dpiScaling, uiTopSize, uiBottomSize, galleryHeight,
+                containerWidth, containerHeight);
+    
+            // Determine maximum height for both images
+            var xHeight = Math.Max(firstSize.Height, secondSize.Height);
 
-        return new ImageSize(xWidth, xHeight, titleMaxWidth, margin, aspectRatio);
-    }
+            // Recalculate the widths to maintain the aspect ratio with the new maximum height
+            var xWidth1 = (firstSize.Width / firstSize.Height) * xHeight;
+            var xWidth2 = (secondSize.Width / secondSize.Height) * xHeight;
 
-    public static double GetTitleMaxWidth(double rotationAngle, double width, double height, double monitorMinWidth,
-        double monitorMinHeight, double interfaceSize, double containerWidth)
-    {
-        double titleMaxWidth;
-        var maximized = SettingsHelper.Settings.WindowProperties.Fullscreen ||
-            SettingsHelper.Settings.WindowProperties.Maximized;
-        
-        if (SettingsHelper.Settings.WindowProperties.AutoFit && !maximized)
-        {
-            titleMaxWidth = rotationAngle is 0 or 180
-                ? Math.Max(width, monitorMinWidth)
-                : Math.Max(height, monitorMinHeight);
+            // Combined width of both images
+            var combinedWidth = xWidth1 + xWidth2;
 
-            if (SettingsHelper.Settings.Zoom.ScrollEnabled)
+            // Available width (monitor width minus 2 for constraint)
+            var availableWidth = monitorWidth - 2;
+
+            // If combined width exceeds available width, scale both images down proportionally
+            if (combinedWidth > availableWidth)
             {
-                return titleMaxWidth;
+                var scaleFactor = availableWidth / combinedWidth;
+                xWidth2 *= scaleFactor;
+                xHeight *= scaleFactor;  // Adjust the height accordingly
             }
 
-            titleMaxWidth = titleMaxWidth - interfaceSize < interfaceSize
-                ? interfaceSize
-                : titleMaxWidth - interfaceSize;
+            // Calculate title max width for the first image (this can be adapted for the second image too)
+            var titleMaxWidth = GetTitleMaxWidth(rotationAngle, combinedWidth, xHeight, monitorMinWidth, monitorMinHeight, interfaceSize, containerWidth);
+
+            // Return the size of the first image (adjust if needed to return both images' sizes)
+            return new ImageSize(combinedWidth, xHeight, xWidth2, titleMaxWidth, 0, firstSize.AspectRatio);
         }
-        else
+
+
+        public static double GetTitleMaxWidth(double rotationAngle, double width, double height, double monitorMinWidth,
+            double monitorMinHeight, double interfaceSize, double containerWidth)
         {
-            // Fix title width to window size
-            titleMaxWidth = containerWidth - interfaceSize <= 0 ? 0 : containerWidth - interfaceSize;
+            double titleMaxWidth;
+            var maximized = SettingsHelper.Settings.WindowProperties.Fullscreen ||
+                            SettingsHelper.Settings.WindowProperties.Maximized;
+
+            if (SettingsHelper.Settings.WindowProperties.AutoFit && !maximized)
+            {
+                titleMaxWidth = rotationAngle is 0 or 180
+                    ? Math.Max(width, monitorMinWidth)
+                    : Math.Max(height, monitorMinHeight);
+
+                if (SettingsHelper.Settings.Zoom.ScrollEnabled)
+                {
+                    return titleMaxWidth;
+                }
+
+                titleMaxWidth = titleMaxWidth - interfaceSize < interfaceSize
+                    ? interfaceSize
+                    : titleMaxWidth - interfaceSize;
+            }
+            else
+            {
+                // Fix title width to window size
+                titleMaxWidth = containerWidth - interfaceSize <= 0 ? 0 : containerWidth - interfaceSize;
+            }
+
+            return titleMaxWidth;
         }
 
-        return titleMaxWidth;
+        public struct ImageSize(double width, double height, double secondaryWidth, double titleMaxWidth, double margin, double aspectRatio)
+        {
+            public double TitleMaxWidth { get; private set; } = titleMaxWidth;
+            public double Width { get; } = width;
+            public double Height { get; } = height;
+            
+            public double SecondaryWidth { get; } = secondaryWidth;
+            public double Margin { get; private set; } = margin;
+
+            public double AspectRatio { get; } = aspectRatio;
+        }
     }
 }

+ 2 - 0
src/PicView.Core/Config/AppSettings.cs

@@ -62,6 +62,8 @@ public class ImageScaling
 {
     public bool StretchImage { get; set; } = false;
     public bool IsScalingSetToNearestNeighbor { get; set; } = false;
+    
+    public bool ShowImageSideBySide { get; set; } = false;
 }
 
 public class Zoom

+ 26 - 25
src/PicView.Core/ImageDecoding/ImageFunctionHelper.cs

@@ -1,33 +1,34 @@
 using System.Diagnostics;
 using ImageMagick;
 
-namespace PicView.Core.ImageDecoding;
-
-public static class ImageFunctionHelper
+namespace PicView.Core.ImageDecoding
 {
-    /// <summary>
-    /// Gets the number of frames in an image.
-    /// </summary>
-    /// <param name="file">The path to the image file.</param>
-    /// <returns>The number of frames in the image. Returns 0 if an error occurs.</returns>
-    /// <remarks>
-    /// This method uses the Magick.NET library to load the image and retrieve the frame count.
-    /// </remarks>
-    public static int GetImageFrames(string file)
+    public static class ImageFunctionHelper
     {
-        try
-        {
-            using var magickImageCollection = new MagickImageCollection();
-            magickImageCollection.Ping(file);
-            return magickImageCollection.Count;
-        }
-        catch (MagickException ex)
+        /// <summary>
+        ///     Gets the number of frames in an image.
+        /// </summary>
+        /// <param name="file">The path to the image file.</param>
+        /// <returns>The number of frames in the image. Returns 0 if an error occurs.</returns>
+        /// <remarks>
+        ///     This method uses the Magick.NET library to load the image and retrieve the frame count.
+        /// </remarks>
+        public static int GetImageFrames(string file)
         {
-            #if DEBUG
-            Trace.WriteLine($"{nameof(GetImageFrames)} Exception \n{ex}");
-            #endif
-            
-            return 0;
+            try
+            {
+                using var magickImageCollection = new MagickImageCollection();
+                magickImageCollection.Ping(file);
+                return magickImageCollection.Count;
+            }
+            catch (MagickException ex)
+            {
+#if DEBUG
+                Trace.WriteLine($"{nameof(GetImageFrames)} Exception \n{ex}");
+#endif
+
+                return 0;
+            }
         }
     }
-}
+}