Skip to content

Commit

Permalink
Skyline: Improvements to ImageComparer for challenging differences (#…
Browse files Browse the repository at this point in the history
…3298)

- Added a binary diff pane for when pixels match but binary headers do not
- Added AlphaColorPickerButton to control the alpha and color used for image diffs
- Fixe Bitmap resolution variability with graph metafiles that was causing differences in the pHYs blocks of saved PNG files
  • Loading branch information
brendanx67 authored Dec 24, 2024
1 parent b21b512 commit 057eb9f
Show file tree
Hide file tree
Showing 10 changed files with 633 additions and 120 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
/*
* Original authors: Brendan MacLean <brendanx .at. uw.edu>
* MacCoss Lab, Department of Genome Sciences, UW
*
* Copyright 2024 University of Washington - Seattle, WA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Windows.Forms;
using ImageComparer.Properties;

namespace ImageComparer
{
public class AlphaColorPickerButton : ToolStripDropDownButton
{
private Color _selectedColor;
private int _alpha;
private Timer _colorChangeTimer;
private bool _pendingColorChange;

public event EventHandler ColorChanged;

public Color SelectedColor
{
get => Color.FromArgb(_alpha, _selectedColor);
set
{
_selectedColor = Color.FromArgb(value.R, value.G, value.B);
_alpha = value.A;
UpdateButtonAppearance();
TriggerColorChange();
}
}

public AlphaColorPickerButton()
{
_selectedColor = Settings.Default.ImageDiffColor;
_alpha = Settings.Default.ImageDiffAlpha;

ToolTipText = @"Pick Color";
InitializeDropDown();
InitializeTimer();
UpdateButtonAppearance();
}

private void InitializeTimer()
{
_colorChangeTimer = new Timer { Interval = 300 }; // 300ms delay
_colorChangeTimer.Tick += (s, e) =>
{
_colorChangeTimer.Stop();
if (_pendingColorChange)
{
_pendingColorChange = false;
OnColorChanged(EventArgs.Empty);
}
};
}

private void InitializeDropDown()
{
var predefinedColors = new[]
{
Color.Red, Color.Green, Color.Blue, Color.Yellow,
Color.Orange, Color.Purple, Color.Cyan, Color.Magenta
};

var panel = new Panel { Width = 8*30, Height = 120, BackColor = SystemColors.Control };

var colorPanel = new FlowLayoutPanel
{
Dock = DockStyle.Top,
Height = 24,
FlowDirection = FlowDirection.LeftToRight
};

foreach (var color in predefinedColors)
{
var colorButton = new Button
{
BackColor = color,
Width = 20,
Height = 20,
Margin = new Padding(5, 2, 5, 2),
FlatStyle = FlatStyle.Flat
};
colorButton.Click += (s, e) =>
{
_selectedColor = color;
UpdateButtonAppearance();
OnColorChanged(EventArgs.Empty);
};
colorPanel.Controls.Add(colorButton);
}

var alphaLabelText = new Label
{
Text = @"Alpha:",
Dock = DockStyle.Top,
TextAlign = ContentAlignment.MiddleRight,
Width = panel.Width/2,
};
var alphaLabelValue = new Label
{
Text = _alpha.ToString(),
Dock = DockStyle.Top,
TextAlign = ContentAlignment.MiddleRight,
Width = 25,
Margin = new Padding(0),
};
var alphaContainer = new FlowLayoutPanel
{
Dock = DockStyle.Bottom,
Height = 20,
FlowDirection = FlowDirection.LeftToRight,
Margin = new Padding(0)
};
alphaContainer.Controls.Add(alphaLabelText);
alphaContainer.Controls.Add(alphaLabelValue);

var alphaTrackBar = new TrackBar
{
Minimum = 0,
Maximum = 255,
Value = _alpha,
Dock = DockStyle.Bottom
};
alphaTrackBar.Scroll += (s, e) =>
{
_alpha = alphaTrackBar.Value;
alphaLabelValue.Text = _alpha.ToString();
UpdateButtonAppearance();
TriggerColorChange();
};

var moreColorsButton = new Button
{
Text = @"More Colors...",
Dock = DockStyle.Bottom
};
moreColorsButton.Click += (s, e) =>
{
using var dialog = new ColorDialog();
dialog.Color = _selectedColor;
if (dialog.ShowDialog() == DialogResult.OK)
{
_selectedColor = dialog.Color;
UpdateButtonAppearance();
OnColorChanged(EventArgs.Empty);
}
};

panel.Controls.Add(colorPanel);
panel.Controls.Add(alphaContainer);
panel.Controls.Add(alphaTrackBar);
panel.Controls.Add(moreColorsButton);

var host = new ToolStripControlHost(panel)
{
AutoSize = false,
Size = panel.Size
};

DropDownItems.Add(host);
}

private void UpdateButtonAppearance()
{
if (!DesignMode)
{
Image = CreateColorSwatch(SelectedColor, new Size(16, 16));
ToolTipText = $@"Selected Color: {SelectedColor}";
}
}

private Image CreateColorSwatch(Color color, Size size)
{
var bitmap = new Bitmap(size.Width, size.Height, PixelFormat.Format32bppArgb);
using var g = Graphics.FromImage(bitmap);
using var brush = new SolidBrush(color);
g.FillRectangle(brush, 0, 0, size.Width, size.Height);
g.DrawRectangle(Pens.Black, 0, 0, size.Width - 1, size.Height - 1);
return bitmap;
}

private void TriggerColorChange()
{
_pendingColorChange = true;
_colorChangeTimer.Stop();
_colorChangeTimer.Start();
}

protected virtual void OnColorChanged(EventArgs e)
{
ColorChanged?.Invoke(this, e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@
<setting name="LastOpenFolder" serializeAs="String">
<value />
</setting>
<setting name="ImageDiffColor" serializeAs="String">
<value>Red</value>
</setting>
<setting name="ImageDiffAlpha" serializeAs="String">
<value>127</value>
</setting>
</ImageComparer.Properties.Settings>
</userSettings>
</configuration>
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ public static IEnumerable<string> GetChangedFilePaths(string directoryPath)
using var reader = new StringReader(output);
while (reader.ReadLine() is { } line)
{
if (!line.StartsWith(" M "))
continue;
// 'git status --porcelain' format: XY path/to/file
var filePath = line.Substring(3).Replace('/', Path.DirectorySeparatorChar);
yield return Path.Combine(GetPathInfo(directoryPath).Root, filePath);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="AlphaColorPickerButton.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="FileSaver.cs" />
<Compile Include="GitFileHelper.cs" />
<Compile Include="ImageComparerWindow.cs">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,9 @@
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=EnumMember/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateConstants/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticReadonly/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=15b5b1f1_002D457c_002D4ca6_002Db278_002D5615aedc07d3/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static readonly fields (private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="READONLY_FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=236f7aa5_002D7b06_002D43ca_002Dbf2a_002D9b31bfcff09a/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Any" AccessRightKinds="Private" Description="Constant fields (private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="CONSTANT_FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=669e5282_002Dfb4b_002D4e90_002D91e7_002D07d269d04b60/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Any" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Constant fields (not private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="CONSTANT_FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=8b8504e3_002Df0be_002D4c14_002D9103_002Dc732f2bddc15/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Any" AccessRightKinds="Any" Description="Enum members"&gt;&lt;ElementKinds&gt;&lt;Kind Name="ENUM_MEMBER" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /&gt;&lt;/Policy&gt;</s:String>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EPredefinedNamingRulesToUserRulesUpgrade/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Screenshot/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 057eb9f

Please sign in to comment.