Skip to content

Commit f013d7f

Browse files
committed
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.)
1 parent 0fd2305 commit f013d7f

File tree

3 files changed

+180
-0
lines changed

3 files changed

+180
-0
lines changed

src/Resources/Locales/en_US.axaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,7 @@
319319
<x:String x:Key="Text.Diff.First" xml:space="preserve">First Difference</x:String>
320320
<x:String x:Key="Text.Diff.IgnoreWhitespace" xml:space="preserve">Ignore All Whitespace Changes</x:String>
321321
<x:String x:Key="Text.Diff.Image.Blend" xml:space="preserve">BLEND</x:String>
322+
<x:String x:Key="Text.Diff.Image.Difference" xml:space="preserve">DIFFERENCE</x:String>
322323
<x:String x:Key="Text.Diff.Image.SideBySide" xml:space="preserve">SIDE-BY-SIDE</x:String>
323324
<x:String x:Key="Text.Diff.Image.Swipe" xml:space="preserve">SWIPE</x:String>
324325
<x:String x:Key="Text.Diff.Last" xml:space="preserve">Last Difference</x:String>

src/Views/ImageContainer.cs

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,4 +361,119 @@ private void RenderSingleSide(DrawingContext context, Bitmap img, double w, doub
361361
private static readonly RenderOptions RO_SRC = new RenderOptions() { BitmapBlendingMode = BitmapBlendingMode.Source, BitmapInterpolationMode = BitmapInterpolationMode.HighQuality };
362362
private static readonly RenderOptions RO_DST = new RenderOptions() { BitmapBlendingMode = BitmapBlendingMode.Plus, BitmapInterpolationMode = BitmapInterpolationMode.HighQuality };
363363
}
364+
365+
public class ImageDifferenceControl : ImageContainer
366+
{
367+
public static readonly StyledProperty<double> AlphaProperty =
368+
AvaloniaProperty.Register<ImageDifferenceControl, double>(nameof(Alpha), 1.0);
369+
370+
public double Alpha
371+
{
372+
get => GetValue(AlphaProperty);
373+
set => SetValue(AlphaProperty, value);
374+
}
375+
376+
public static readonly StyledProperty<Bitmap> OldImageProperty =
377+
AvaloniaProperty.Register<ImageDifferenceControl, Bitmap>(nameof(OldImage));
378+
379+
public Bitmap OldImage
380+
{
381+
get => GetValue(OldImageProperty);
382+
set => SetValue(OldImageProperty, value);
383+
}
384+
385+
public static readonly StyledProperty<Bitmap> NewImageProperty =
386+
AvaloniaProperty.Register<ImageDifferenceControl, Bitmap>(nameof(NewImage));
387+
388+
public Bitmap NewImage
389+
{
390+
get => GetValue(NewImageProperty);
391+
set => SetValue(NewImageProperty, value);
392+
}
393+
394+
static ImageDifferenceControl()
395+
{
396+
AffectsMeasure<ImageDifferenceControl>(OldImageProperty, NewImageProperty);
397+
AffectsRender<ImageDifferenceControl>(AlphaProperty);
398+
}
399+
400+
public override void Render(DrawingContext context)
401+
{
402+
base.Render(context);
403+
404+
var alpha = Alpha;
405+
var left = OldImage;
406+
var right = NewImage;
407+
var drawLeft = left != null && alpha < 1.0;
408+
var drawRight = right != null && alpha > 0.0;
409+
410+
if (drawLeft && drawRight)
411+
{
412+
using (var rt = new RenderTargetBitmap(new PixelSize((int)Bounds.Width, (int)Bounds.Height), right.Dpi))
413+
{
414+
using (var dc = rt.CreateDrawingContext())
415+
{
416+
using (dc.PushRenderOptions(RO_SRC))
417+
RenderSingleSide(dc, left, rt.Size.Width, rt.Size.Height, Math.Min(1.0, 2.0 - 2.0 * alpha));
418+
419+
using (dc.PushRenderOptions(RO_DST))
420+
RenderSingleSide(dc, right, rt.Size.Width, rt.Size.Height, Math.Min(1.0, 2.0 * alpha));
421+
}
422+
423+
context.DrawImage(rt, new Rect(0, 0, Bounds.Width, Bounds.Height));
424+
}
425+
}
426+
else if (drawLeft)
427+
{
428+
RenderSingleSide(context, left, Bounds.Width, Bounds.Height, 1 - alpha);
429+
}
430+
else if (drawRight)
431+
{
432+
RenderSingleSide(context, right, Bounds.Width, Bounds.Height, alpha);
433+
}
434+
}
435+
436+
protected override Size MeasureOverride(Size availableSize)
437+
{
438+
var left = OldImage;
439+
var right = NewImage;
440+
441+
if (left == null)
442+
return right == null ? availableSize : GetDesiredSize(right.Size, availableSize);
443+
444+
if (right == null)
445+
return GetDesiredSize(left.Size, availableSize);
446+
447+
var ls = GetDesiredSize(left.Size, availableSize);
448+
var rs = GetDesiredSize(right.Size, availableSize);
449+
return ls.Width > rs.Width ? ls : rs;
450+
}
451+
452+
private Size GetDesiredSize(Size img, Size available)
453+
{
454+
var sw = available.Width / img.Width;
455+
var sh = available.Height / img.Height;
456+
var scale = Math.Min(1, Math.Min(sw, sh));
457+
return new Size(scale * img.Width, scale * img.Height);
458+
}
459+
460+
private void RenderSingleSide(DrawingContext context, Bitmap img, double w, double h, double alpha)
461+
{
462+
var imgW = img.Size.Width;
463+
var imgH = img.Size.Height;
464+
var scale = Math.Min(1, Math.Min(w / imgW, h / imgH));
465+
466+
var scaledW = img.Size.Width * scale;
467+
var scaledH = img.Size.Height * scale;
468+
469+
var src = new Rect(0, 0, imgW, imgH);
470+
var dst = new Rect((w - scaledW) * 0.5, (h - scaledH) * 0.5, scaledW, scaledH);
471+
472+
using (context.PushOpacity(alpha))
473+
context.DrawImage(img, src, dst);
474+
}
475+
476+
private static readonly RenderOptions RO_SRC = new RenderOptions() { BitmapBlendingMode = BitmapBlendingMode.Source, BitmapInterpolationMode = BitmapInterpolationMode.HighQuality };
477+
private static readonly RenderOptions RO_DST = new RenderOptions() { BitmapBlendingMode = BitmapBlendingMode.Difference, BitmapInterpolationMode = BitmapInterpolationMode.HighQuality };
478+
}
364479
}

src/Views/ImageDiffView.axaml

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,5 +157,69 @@
157157
</Grid>
158158
</Grid>
159159
</TabItem>
160+
161+
<TabItem>
162+
<TabItem.Header>
163+
<TextBlock Text="{DynamicResource Text.Diff.Image.Difference}" FontSize="11"/>
164+
</TabItem.Header>
165+
166+
<Grid RowDefinitions="Auto,*,Auto" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="8,16,8,0">
167+
<Grid Grid.Row="0" ColumnDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto" HorizontalAlignment="Center">
168+
<Border Grid.Column="0" Height="16" Background="{DynamicResource Brush.Badge}" CornerRadius="8" VerticalAlignment="Center">
169+
<TextBlock Classes="primary" Text="{DynamicResource Text.Diff.Old}" Margin="8,0" FontSize="10" Foreground="{DynamicResource Brush.BadgeFG}"/>
170+
</Border>
171+
172+
<TextBlock Grid.Column="1" Classes="primary" Text="{Binding OldImageSize}" Margin="8,0,0,0"/>
173+
<TextBlock Grid.Column="2" Classes="primary" Text="{Binding OldFileSize, Converter={x:Static c:LongConverters.ToFileSize}}" Foreground="{DynamicResource Brush.FG2}" Margin="16,0,0,0"/>
174+
175+
<Border Grid.Column="3" Height="16" Background="Green" CornerRadius="8" VerticalAlignment="Center" Margin="32,0,0,0">
176+
<TextBlock Classes="primary" Text="{DynamicResource Text.Diff.New}" Margin="8,0" FontSize="10" Foreground="White"/>
177+
</Border>
178+
179+
<TextBlock Grid.Column="4" Classes="primary" Text="{Binding NewImageSize}" Margin="8,0,0,0"/>
180+
<TextBlock Grid.Column="5" Classes="primary" Text="{Binding NewFileSize, Converter={x:Static c:LongConverters.ToFileSize}}" Foreground="{DynamicResource Brush.FG2}" Margin="16,0,0,0"/>
181+
</Grid>
182+
183+
<Border Grid.Row="1" Margin="0,12,0,0" HorizontalAlignment="Center" Effect="drop-shadow(0 0 8 #A0000000)">
184+
<Border Background="{DynamicResource Brush.Window}">
185+
<Border BorderThickness="1" BorderBrush="{DynamicResource Brush.Border1}" Margin="8">
186+
<v:ImageDifferenceControl Alpha="{Binding #ImageDifferenceSlider.Value}"
187+
OldImage="{Binding Old}"
188+
NewImage="{Binding New}"/>
189+
</Border>
190+
</Border>
191+
</Border>
192+
193+
<Grid Grid.Row="2" ColumnDefinitions="100,200,100" Margin="0,12,0,0" HorizontalAlignment="Center">
194+
<StackPanel Grid.Column="0" Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Top" Margin="0,0,8,0">
195+
<TextBlock Classes="primary"
196+
Margin="0,0,8,0"
197+
Text="{Binding #ImageDifferenceSlider.Value, Converter={x:Static c:DoubleConverters.OneMinusToPercentage}}"
198+
Foreground="{DynamicResource Brush.FG2}"/>
199+
<TextBlock Classes="primary" Text="{DynamicResource Text.Diff.Old}"/>
200+
</StackPanel>
201+
202+
<Slider Grid.Column="1"
203+
x:Name="ImageDifferenceSlider"
204+
Minimum="0" Maximum="1"
205+
VerticalAlignment="Top"
206+
TickFrequency="0.5"
207+
TickPlacement="BottomRight"
208+
Margin="0"
209+
MinHeight="0"
210+
Foreground="{DynamicResource Brush.Border1}"
211+
Value="0.5"/>
212+
213+
<StackPanel Grid.Column="2" Orientation="Horizontal" VerticalAlignment="Top" Margin="8,0,0,0">
214+
<TextBlock Classes="primary" Text="{DynamicResource Text.Diff.New}"/>
215+
<TextBlock Classes="primary"
216+
Margin="8,0,0,0"
217+
Text="{Binding #ImageDifferenceSlider.Value, Converter={x:Static c:DoubleConverters.ToPercentage}}"
218+
Foreground="{DynamicResource Brush.FG2}"/>
219+
</StackPanel>
220+
</Grid>
221+
</Grid>
222+
</TabItem>
223+
160224
</TabControl>
161225
</UserControl>

0 commit comments

Comments
 (0)