From 9e3e70d90ea4650d81d486a7c9f20463258a08b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=B6ran=20W?= Date: Sat, 6 Sep 2025 17:57:20 +0200 Subject: [PATCH 1/5] Removed decimals from % values shown for ImageBlendSlider --- src/Converters/DoubleConverters.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Converters/DoubleConverters.cs b/src/Converters/DoubleConverters.cs index b6676de9f..871a80b3e 100644 --- a/src/Converters/DoubleConverters.cs +++ b/src/Converters/DoubleConverters.cs @@ -12,10 +12,10 @@ public static class DoubleConverters new FuncValueConverter(v => v - 1.0); public static readonly FuncValueConverter ToPercentage = - new FuncValueConverter(v => (v * 100).ToString("F3") + "%"); + new FuncValueConverter(v => (v * 100).ToString("F0") + "%"); public static readonly FuncValueConverter OneMinusToPercentage = - new FuncValueConverter(v => ((1.0 - v) * 100).ToString("F3") + "%"); + new FuncValueConverter(v => ((1.0 - v) * 100).ToString("F0") + "%"); public static readonly FuncValueConverter ToLeftMargin = new FuncValueConverter(v => new Thickness(v, 0, 0, 0)); From 950beff69f8b3b5b9b0ca0c4d10df64b71ecff4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=B6ran=20W?= Date: Sun, 7 Sep 2025 00:38:56 +0200 Subject: [PATCH 2/5] Moved OLD text at ImageBlendSlider, for symmetry and non-jumpiness --- src/Views/ImageDiffView.axaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Views/ImageDiffView.axaml b/src/Views/ImageDiffView.axaml index 239991502..cec5eda47 100644 --- a/src/Views/ImageDiffView.axaml +++ b/src/Views/ImageDiffView.axaml @@ -129,11 +129,11 @@ - + Date: Sun, 7 Sep 2025 00:40:11 +0200 Subject: [PATCH 3/5] Added ticks on ImageBlendSlider, at 0%, 50% and 100% --- src/Views/ImageDiffView.axaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Views/ImageDiffView.axaml b/src/Views/ImageDiffView.axaml index cec5eda47..3a85fef90 100644 --- a/src/Views/ImageDiffView.axaml +++ b/src/Views/ImageDiffView.axaml @@ -140,7 +140,8 @@ x:Name="ImageBlendSlider" Minimum="0" Maximum="1" VerticalAlignment="Top" - TickPlacement="None" + TickFrequency="0.5" + TickPlacement="BottomRight" Margin="0" MinHeight="0" Foreground="{DynamicResource Brush.Border1}" From f013d7fc853a21214fef8e3ac42a2269e11677a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=B6ran=20W?= Date: Sun, 7 Sep 2025 00:46:06 +0200 Subject: [PATCH 4/5] Added new image-diff mode DIFFERENCE Similar to the BLEND image-diff mode, but instead blends towards total DIFFERENCE at 50%. (This way, we can look at the difference in the middle, and slide towards OLD or NEW to view their respective contributions to the diff.) --- src/Resources/Locales/en_US.axaml | 1 + src/Views/ImageContainer.cs | 115 ++++++++++++++++++++++++++++++ src/Views/ImageDiffView.axaml | 64 +++++++++++++++++ 3 files changed, 180 insertions(+) diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index 2fb39d495..f5a5f61a0 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -319,6 +319,7 @@ First Difference Ignore All Whitespace Changes BLEND + DIFFERENCE SIDE-BY-SIDE SWIPE Last Difference diff --git a/src/Views/ImageContainer.cs b/src/Views/ImageContainer.cs index 995f269bf..da0dafb8b 100644 --- a/src/Views/ImageContainer.cs +++ b/src/Views/ImageContainer.cs @@ -361,4 +361,119 @@ private void RenderSingleSide(DrawingContext context, Bitmap img, double w, doub private static readonly RenderOptions RO_SRC = new RenderOptions() { BitmapBlendingMode = BitmapBlendingMode.Source, BitmapInterpolationMode = BitmapInterpolationMode.HighQuality }; private static readonly RenderOptions RO_DST = new RenderOptions() { BitmapBlendingMode = BitmapBlendingMode.Plus, BitmapInterpolationMode = BitmapInterpolationMode.HighQuality }; } + + public class ImageDifferenceControl : ImageContainer + { + public static readonly StyledProperty AlphaProperty = + AvaloniaProperty.Register(nameof(Alpha), 1.0); + + public double Alpha + { + get => GetValue(AlphaProperty); + set => SetValue(AlphaProperty, value); + } + + public static readonly StyledProperty OldImageProperty = + AvaloniaProperty.Register(nameof(OldImage)); + + public Bitmap OldImage + { + get => GetValue(OldImageProperty); + set => SetValue(OldImageProperty, value); + } + + public static readonly StyledProperty NewImageProperty = + AvaloniaProperty.Register(nameof(NewImage)); + + public Bitmap NewImage + { + get => GetValue(NewImageProperty); + set => SetValue(NewImageProperty, value); + } + + static ImageDifferenceControl() + { + AffectsMeasure(OldImageProperty, NewImageProperty); + AffectsRender(AlphaProperty); + } + + public override void Render(DrawingContext context) + { + base.Render(context); + + var alpha = Alpha; + var left = OldImage; + var right = NewImage; + var drawLeft = left != null && alpha < 1.0; + var drawRight = right != null && alpha > 0.0; + + if (drawLeft && drawRight) + { + using (var rt = new RenderTargetBitmap(new PixelSize((int)Bounds.Width, (int)Bounds.Height), right.Dpi)) + { + using (var dc = rt.CreateDrawingContext()) + { + using (dc.PushRenderOptions(RO_SRC)) + RenderSingleSide(dc, left, rt.Size.Width, rt.Size.Height, Math.Min(1.0, 2.0 - 2.0 * alpha)); + + using (dc.PushRenderOptions(RO_DST)) + RenderSingleSide(dc, right, rt.Size.Width, rt.Size.Height, Math.Min(1.0, 2.0 * alpha)); + } + + context.DrawImage(rt, new Rect(0, 0, Bounds.Width, Bounds.Height)); + } + } + else if (drawLeft) + { + RenderSingleSide(context, left, Bounds.Width, Bounds.Height, 1 - alpha); + } + else if (drawRight) + { + RenderSingleSide(context, right, Bounds.Width, Bounds.Height, alpha); + } + } + + protected override Size MeasureOverride(Size availableSize) + { + var left = OldImage; + var right = NewImage; + + if (left == null) + return right == null ? availableSize : GetDesiredSize(right.Size, availableSize); + + if (right == null) + return GetDesiredSize(left.Size, availableSize); + + var ls = GetDesiredSize(left.Size, availableSize); + var rs = GetDesiredSize(right.Size, availableSize); + return ls.Width > rs.Width ? ls : rs; + } + + private Size GetDesiredSize(Size img, Size available) + { + var sw = available.Width / img.Width; + var sh = available.Height / img.Height; + var scale = Math.Min(1, Math.Min(sw, sh)); + return new Size(scale * img.Width, scale * img.Height); + } + + private void RenderSingleSide(DrawingContext context, Bitmap img, double w, double h, double alpha) + { + var imgW = img.Size.Width; + var imgH = img.Size.Height; + var scale = Math.Min(1, Math.Min(w / imgW, h / imgH)); + + var scaledW = img.Size.Width * scale; + var scaledH = img.Size.Height * scale; + + var src = new Rect(0, 0, imgW, imgH); + var dst = new Rect((w - scaledW) * 0.5, (h - scaledH) * 0.5, scaledW, scaledH); + + using (context.PushOpacity(alpha)) + context.DrawImage(img, src, dst); + } + + private static readonly RenderOptions RO_SRC = new RenderOptions() { BitmapBlendingMode = BitmapBlendingMode.Source, BitmapInterpolationMode = BitmapInterpolationMode.HighQuality }; + private static readonly RenderOptions RO_DST = new RenderOptions() { BitmapBlendingMode = BitmapBlendingMode.Difference, BitmapInterpolationMode = BitmapInterpolationMode.HighQuality }; + } } diff --git a/src/Views/ImageDiffView.axaml b/src/Views/ImageDiffView.axaml index 3a85fef90..8bb5b4c49 100644 --- a/src/Views/ImageDiffView.axaml +++ b/src/Views/ImageDiffView.axaml @@ -157,5 +157,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From e7d6f350d80f11834f1e753943cb79a9e3b90ba4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=B6ran=20W?= Date: Sun, 7 Sep 2025 00:57:15 +0200 Subject: [PATCH 5/5] Selected image-diff mode is now persistent --- src/ViewModels/Preferences.cs | 7 +++++++ src/Views/ImageDiffView.axaml | 3 ++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/ViewModels/Preferences.cs b/src/ViewModels/Preferences.cs index 414736519..18f24b27d 100644 --- a/src/ViewModels/Preferences.cs +++ b/src/ViewModels/Preferences.cs @@ -279,6 +279,12 @@ public int LFSImageActiveIdx set => SetProperty(ref _lfsImageActiveIdx, value); } + public int ImageDiffActiveIdx + { + get => _imageDiffActiveIdx; + set => SetProperty(ref _imageDiffActiveIdx, value); + } + public bool EnableCompactFoldersInChangesTree { get => _enableCompactFoldersInChangesTree; @@ -736,6 +742,7 @@ private bool RemoveInvalidRepositoriesRecursive(List collection) private bool _showHiddenSymbolsInDiffView = false; private bool _useFullTextDiff = false; private int _lfsImageActiveIdx = 0; + private int _imageDiffActiveIdx = 0; private bool _enableCompactFoldersInChangesTree = false; private Models.ChangeViewMode _unstagedChangeViewMode = Models.ChangeViewMode.List; diff --git a/src/Views/ImageDiffView.axaml b/src/Views/ImageDiffView.axaml index 8bb5b4c49..cf9af3877 100644 --- a/src/Views/ImageDiffView.axaml +++ b/src/Views/ImageDiffView.axaml @@ -3,12 +3,13 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:m="using:SourceGit.Models" + xmlns:vm="using:SourceGit.ViewModels" xmlns:v="using:SourceGit.Views" xmlns:c="using:SourceGit.Converters" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="SourceGit.Views.ImageDiffView" x:DataType="m:ImageDiff"> - +