diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt
index 3d22a06313..cd46ef497d 100644
--- a/.github/actions/spell-check/expect.txt
+++ b/.github/actions/spell-check/expect.txt
@@ -218,6 +218,7 @@ coclass
CODENAME
codereview
Codespaces
+Coen
COINIT
colid
colorconv
diff --git a/Directory.Packages.props b/Directory.Packages.props
index f29669d9e7..23575616fe 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -9,6 +9,7 @@
+
diff --git a/NOTICE.md b/NOTICE.md
index b2232e4984..4dcc82579d 100644
--- a/NOTICE.md
+++ b/NOTICE.md
@@ -1496,6 +1496,7 @@ SOFTWARE.
- AdaptiveCards.Templating 2.0.5
- Appium.WebDriver 4.4.5
- Azure.AI.OpenAI 1.0.0-beta.17
+- CoenM.ImageSharp.ImageHash 1.3.6
- CommunityToolkit.Common 8.4.0
- CommunityToolkit.Labs.WinUI.Controls.MarkdownTextBlock 0.1.250703-build.2173
- CommunityToolkit.Mvvm 8.4.0
diff --git a/src/common/UITestAutomation/UITestAutomation.csproj b/src/common/UITestAutomation/UITestAutomation.csproj
index fc9da3b983..17841e0a60 100644
--- a/src/common/UITestAutomation/UITestAutomation.csproj
+++ b/src/common/UITestAutomation/UITestAutomation.csproj
@@ -20,6 +20,7 @@
+
diff --git a/src/common/UITestAutomation/VisualAssert.cs b/src/common/UITestAutomation/VisualAssert.cs
index 692090440a..844db5b027 100644
--- a/src/common/UITestAutomation/VisualAssert.cs
+++ b/src/common/UITestAutomation/VisualAssert.cs
@@ -6,7 +6,11 @@ using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
using System.IO;
+using CoenM.ImageHash;
+using CoenM.ImageHash.HashAlgorithms;
using Microsoft.VisualStudio.TestTools.UnitTesting;
+using SixLabors.ImageSharp;
+using SixLabors.ImageSharp.PixelFormats;
namespace Microsoft.PowerToys.UITest
{
@@ -127,34 +131,75 @@ namespace Microsoft.PowerToys.UITest
}
///
- /// Test if two images are equal bit-by-bit
+ /// Test if two images are equal using ImageHash comparison
///
/// baseline image
/// test image
/// true if are equal,otherwise false
private static bool AreEqual(Bitmap baselineImage, Bitmap testImage)
{
- if (baselineImage.Width != testImage.Width || baselineImage.Height != testImage.Height)
+ try
{
- return false;
+ // Define a threshold for similarity percentage
+ const int SimilarityThreshold = 95;
+
+ // Use CoenM.ImageHash for perceptual hash comparison
+ var hashAlgorithm = new AverageHash();
+
+ // Convert System.Drawing.Bitmap to SixLabors.ImageSharp.Image
+ using var baselineImageSharp = ConvertBitmapToImageSharp(baselineImage);
+ using var testImageSharp = ConvertBitmapToImageSharp(testImage);
+
+ // Calculate hashes for both images
+ var baselineHash = hashAlgorithm.Hash(baselineImageSharp);
+ var testHash = hashAlgorithm.Hash(testImageSharp);
+
+ // Compare hashes using CompareHash method
+ // Returns similarity percentage (0-100, where 100 is identical)
+ var similarity = CompareHash.Similarity(baselineHash, testHash);
+
+ // Consider images equal if similarity is very high
+ // Allow for minor rendering differences (threshold can be adjusted)
+ return similarity >= SimilarityThreshold; // 95% similarity threshold
}
-
- // WinAppDriver sometimes adds a border to the screenshot (around 2 pix width), and it is not always consistent.
- // So we exclude the border when comparing the images, and usually it is the edge of the windows, won't affect the comparison.
- int excludeBorderWidth = 5, excludeBorderHeight = 5;
-
- for (int x = excludeBorderWidth; x < baselineImage.Width - excludeBorderWidth; x++)
+ catch
{
- for (int y = excludeBorderHeight; y < baselineImage.Height - excludeBorderHeight; y++)
+ // Fallback to pixel-by-pixel comparison if hash comparison fails
+ if (baselineImage.Width != testImage.Width || baselineImage.Height != testImage.Height)
{
- if (!VisualHelper.PixIsSame(baselineImage.GetPixel(x, y), testImage.GetPixel(x, y)))
+ return false;
+ }
+
+ // WinAppDriver sometimes adds a border to the screenshot (around 2 pix width), and it is not always consistent.
+ // So we exclude the border when comparing the images, and usually it is the edge of the windows, won't affect the comparison.
+ int excludeBorderWidth = 5, excludeBorderHeight = 5;
+
+ for (int x = excludeBorderWidth; x < baselineImage.Width - excludeBorderWidth; x++)
+ {
+ for (int y = excludeBorderHeight; y < baselineImage.Height - excludeBorderHeight; y++)
{
- return false;
+ if (!VisualHelper.PixIsSame(baselineImage.GetPixel(x, y), testImage.GetPixel(x, y)))
+ {
+ return false;
+ }
}
}
- }
- return true;
+ return true;
+ }
+ }
+
+ ///
+ /// Convert System.Drawing.Bitmap to SixLabors.ImageSharp.Image
+ ///
+ /// The bitmap to convert
+ /// ImageSharp Image
+ private static Image ConvertBitmapToImageSharp(Bitmap bitmap)
+ {
+ using var memoryStream = new MemoryStream();
+ bitmap.Save(memoryStream, System.Drawing.Imaging.ImageFormat.Png);
+ memoryStream.Position = 0;
+ return SixLabors.ImageSharp.Image.Load(memoryStream);
}
}
}