Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/Converters/DoubleConverters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ public static class DoubleConverters
new FuncValueConverter<double, double>(v => v - 1.0);

public static readonly FuncValueConverter<double, string> ToPercentage =
new FuncValueConverter<double, string>(v => (v * 100).ToString("F3") + "%");
new FuncValueConverter<double, string>(v => (v * 100).ToString("F0") + "%");

public static readonly FuncValueConverter<double, string> OneMinusToPercentage =
new FuncValueConverter<double, string>(v => ((1.0 - v) * 100).ToString("F3") + "%");
new FuncValueConverter<double, string>(v => ((1.0 - v) * 100).ToString("F0") + "%");

public static readonly FuncValueConverter<double, Thickness> ToLeftMargin =
new FuncValueConverter<double, Thickness>(v => new Thickness(v, 0, 0, 0));
Expand Down
1 change: 1 addition & 0 deletions src/Resources/Locales/en_US.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,7 @@
<x:String x:Key="Text.Diff.First" xml:space="preserve">First Difference</x:String>
<x:String x:Key="Text.Diff.IgnoreWhitespace" xml:space="preserve">Ignore All Whitespace Changes</x:String>
<x:String x:Key="Text.Diff.Image.Blend" xml:space="preserve">BLEND</x:String>
<x:String x:Key="Text.Diff.Image.Difference" xml:space="preserve">DIFFERENCE</x:String>
<x:String x:Key="Text.Diff.Image.SideBySide" xml:space="preserve">SIDE-BY-SIDE</x:String>
<x:String x:Key="Text.Diff.Image.Swipe" xml:space="preserve">SWIPE</x:String>
<x:String x:Key="Text.Diff.Last" xml:space="preserve">Last Difference</x:String>
Expand Down
7 changes: 7 additions & 0 deletions src/ViewModels/Preferences.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -736,6 +742,7 @@ private bool RemoveInvalidRepositoriesRecursive(List<RepositoryNode> 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;
Expand Down
115 changes: 115 additions & 0 deletions src/Views/ImageContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<double> AlphaProperty =
AvaloniaProperty.Register<ImageDifferenceControl, double>(nameof(Alpha), 1.0);

public double Alpha
{
get => GetValue(AlphaProperty);
set => SetValue(AlphaProperty, value);
}

public static readonly StyledProperty<Bitmap> OldImageProperty =
AvaloniaProperty.Register<ImageDifferenceControl, Bitmap>(nameof(OldImage));

public Bitmap OldImage
{
get => GetValue(OldImageProperty);
set => SetValue(OldImageProperty, value);
}

public static readonly StyledProperty<Bitmap> NewImageProperty =
AvaloniaProperty.Register<ImageDifferenceControl, Bitmap>(nameof(NewImage));

public Bitmap NewImage
{
get => GetValue(NewImageProperty);
set => SetValue(NewImageProperty, value);
}

static ImageDifferenceControl()
{
AffectsMeasure<ImageDifferenceControl>(OldImageProperty, NewImageProperty);
AffectsRender<ImageDifferenceControl>(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 };
}
}
74 changes: 70 additions & 4 deletions src/Views/ImageDiffView.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -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">
<TabControl Margin="0,0,0,8" TabStripPlacement="Bottom">
<TabControl SelectedIndex="{Binding Source={x:Static vm:Preferences.Instance}, Path=ImageDiffActiveIdx, Mode=TwoWay}" Margin="0,0,0,8" TabStripPlacement="Bottom">
<TabControl.Styles>
<Style Selector="TabControl /template/ ItemsPresenter#PART_ItemsPresenter > WrapPanel">
<Setter Property="HorizontalAlignment" Value="Center"/>
Expand Down Expand Up @@ -129,18 +130,19 @@

<Grid Grid.Row="2" ColumnDefinitions="100,200,100" Margin="0,12,0,0" HorizontalAlignment="Center">
<StackPanel Grid.Column="0" Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Top" Margin="0,0,8,0">
<TextBlock Classes="primary" Text="{DynamicResource Text.Diff.Old}"/>
<TextBlock Classes="primary"
Margin="8,0,0,0"
Margin="0,0,8,0"
Text="{Binding #ImageBlendSlider.Value, Converter={x:Static c:DoubleConverters.OneMinusToPercentage}}"
Foreground="{DynamicResource Brush.FG2}"/>
<TextBlock Classes="primary" Text="{DynamicResource Text.Diff.Old}"/>
</StackPanel>

<Slider Grid.Column="1"
x:Name="ImageBlendSlider"
Minimum="0" Maximum="1"
VerticalAlignment="Top"
TickPlacement="None"
TickFrequency="0.5"
TickPlacement="BottomRight"
Margin="0"
MinHeight="0"
Foreground="{DynamicResource Brush.Border1}"
Expand All @@ -156,5 +158,69 @@
</Grid>
</Grid>
</TabItem>

<TabItem>
<TabItem.Header>
<TextBlock Text="{DynamicResource Text.Diff.Image.Difference}" FontSize="11"/>
</TabItem.Header>

<Grid RowDefinitions="Auto,*,Auto" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="8,16,8,0">
<Grid Grid.Row="0" ColumnDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto" HorizontalAlignment="Center">
<Border Grid.Column="0" Height="16" Background="{DynamicResource Brush.Badge}" CornerRadius="8" VerticalAlignment="Center">
<TextBlock Classes="primary" Text="{DynamicResource Text.Diff.Old}" Margin="8,0" FontSize="10" Foreground="{DynamicResource Brush.BadgeFG}"/>
</Border>

<TextBlock Grid.Column="1" Classes="primary" Text="{Binding OldImageSize}" Margin="8,0,0,0"/>
<TextBlock Grid.Column="2" Classes="primary" Text="{Binding OldFileSize, Converter={x:Static c:LongConverters.ToFileSize}}" Foreground="{DynamicResource Brush.FG2}" Margin="16,0,0,0"/>

<Border Grid.Column="3" Height="16" Background="Green" CornerRadius="8" VerticalAlignment="Center" Margin="32,0,0,0">
<TextBlock Classes="primary" Text="{DynamicResource Text.Diff.New}" Margin="8,0" FontSize="10" Foreground="White"/>
</Border>

<TextBlock Grid.Column="4" Classes="primary" Text="{Binding NewImageSize}" Margin="8,0,0,0"/>
<TextBlock Grid.Column="5" Classes="primary" Text="{Binding NewFileSize, Converter={x:Static c:LongConverters.ToFileSize}}" Foreground="{DynamicResource Brush.FG2}" Margin="16,0,0,0"/>
</Grid>

<Border Grid.Row="1" Margin="0,12,0,0" HorizontalAlignment="Center" Effect="drop-shadow(0 0 8 #A0000000)">
<Border Background="{DynamicResource Brush.Window}">
<Border BorderThickness="1" BorderBrush="{DynamicResource Brush.Border1}" Margin="8">
<v:ImageDifferenceControl Alpha="{Binding #ImageDifferenceSlider.Value}"
OldImage="{Binding Old}"
NewImage="{Binding New}"/>
</Border>
</Border>
</Border>

<Grid Grid.Row="2" ColumnDefinitions="100,200,100" Margin="0,12,0,0" HorizontalAlignment="Center">
<StackPanel Grid.Column="0" Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Top" Margin="0,0,8,0">
<TextBlock Classes="primary"
Margin="0,0,8,0"
Text="{Binding #ImageDifferenceSlider.Value, Converter={x:Static c:DoubleConverters.OneMinusToPercentage}}"
Foreground="{DynamicResource Brush.FG2}"/>
<TextBlock Classes="primary" Text="{DynamicResource Text.Diff.Old}"/>
</StackPanel>

<Slider Grid.Column="1"
x:Name="ImageDifferenceSlider"
Minimum="0" Maximum="1"
VerticalAlignment="Top"
TickFrequency="0.5"
TickPlacement="BottomRight"
Margin="0"
MinHeight="0"
Foreground="{DynamicResource Brush.Border1}"
Value="0.5"/>

<StackPanel Grid.Column="2" Orientation="Horizontal" VerticalAlignment="Top" Margin="8,0,0,0">
<TextBlock Classes="primary" Text="{DynamicResource Text.Diff.New}"/>
<TextBlock Classes="primary"
Margin="8,0,0,0"
Text="{Binding #ImageDifferenceSlider.Value, Converter={x:Static c:DoubleConverters.ToPercentage}}"
Foreground="{DynamicResource Brush.FG2}"/>
</StackPanel>
</Grid>
</Grid>
</TabItem>

</TabControl>
</UserControl>