scry/test/Scry.Tests/PerceptualHashTests.cs
Chris Kruining 0801ceee6a
.
2026-02-05 09:41:07 +01:00

148 lines
4.5 KiB
C#

using Scry.Core.Imaging;
using SkiaSharp;
using Xunit;
namespace Scry.Tests;
public class PerceptualHashTests
{
[Fact]
public void ComputeHash_ReturnsConsistentHash()
{
using var bitmap = CreateTestBitmap(32, 32, SKColors.Red);
var hash1 = PerceptualHash.ComputeHash(bitmap);
var hash2 = PerceptualHash.ComputeHash(bitmap);
Assert.Equal(hash1, hash2);
}
[Fact]
public void ComputeColorHash_Returns24Bytes()
{
using var bitmap = CreateTestBitmap(32, 32, SKColors.Blue);
var hash = PerceptualHash.ComputeColorHash(bitmap);
Assert.Equal(24, hash.Length);
}
[Fact]
public void HammingDistance_IdenticalHashes_ReturnsZero()
{
var hash = new byte[] { 0xFF, 0x00, 0xAB, 0xCD };
var distance = PerceptualHash.HammingDistance(hash, hash);
Assert.Equal(0, distance);
}
[Fact]
public void HammingDistance_OppositeHashes_ReturnsMaxBits()
{
var hash1 = new byte[] { 0x00, 0x00 };
var hash2 = new byte[] { 0xFF, 0xFF };
var distance = PerceptualHash.HammingDistance(hash1, hash2);
Assert.Equal(16, distance);
}
[Fact]
public void HammingDistance_SingleBitDifference()
{
var hash1 = new byte[] { 0b00000000 };
var hash2 = new byte[] { 0b00000001 };
var distance = PerceptualHash.HammingDistance(hash1, hash2);
Assert.Equal(1, distance);
}
[Fact]
public void CalculateConfidence_ZeroDistance_ReturnsOne()
{
var confidence = PerceptualHash.CalculateConfidence(0, 192);
Assert.Equal(1.0f, confidence);
}
[Fact]
public void CalculateConfidence_HalfDistance_ReturnsHalf()
{
var confidence = PerceptualHash.CalculateConfidence(96, 192);
Assert.Equal(0.5f, confidence);
}
[Theory]
[InlineData("reference/brainstorm.png")]
[InlineData("reference/force_of_will.png")]
[InlineData("single_cards/llanowar_elves.jpg")]
public void ComputeColorHash_RealImages_ProducesValidHash(string imagePath)
{
var fullPath = Path.Combine("TestImages", imagePath);
if (!File.Exists(fullPath))
{
return;
}
using var bitmap = SKBitmap.Decode(fullPath);
Assert.NotNull(bitmap);
var hash = PerceptualHash.ComputeColorHash(bitmap);
Assert.Equal(24, hash.Length);
Assert.True(hash.Any(b => b != 0), "Hash should not be all zeros");
}
[Fact]
public void SimilarImages_HaveLowHammingDistance()
{
using var bitmap1 = CreateGradientBitmap(32, 32, SKColors.Red, SKColors.Blue);
using var bitmap2 = CreateGradientBitmap(32, 32, SKColors.Red, SKColors.Blue);
var hash1 = PerceptualHash.ComputeColorHash(bitmap1);
var hash2 = PerceptualHash.ComputeColorHash(bitmap2);
var distance = PerceptualHash.HammingDistance(hash1, hash2);
Assert.Equal(0, distance);
}
[Fact]
public void DifferentImages_HaveHighHammingDistance()
{
using var bitmap1 = CreateTestBitmap(32, 32, SKColors.Red);
using var bitmap2 = CreateTestBitmap(32, 32, SKColors.Blue);
var hash1 = PerceptualHash.ComputeColorHash(bitmap1);
var hash2 = PerceptualHash.ComputeColorHash(bitmap2);
var distance = PerceptualHash.HammingDistance(hash1, hash2);
Assert.True(distance > 10, $"Expected distance > 10, got {distance}");
}
private static SKBitmap CreateTestBitmap(int width, int height, SKColor color)
{
var bitmap = new SKBitmap(width, height, SKColorType.Rgba8888, SKAlphaType.Premul);
using var canvas = new SKCanvas(bitmap);
canvas.Clear(color);
return bitmap;
}
private static SKBitmap CreateGradientBitmap(int width, int height, SKColor start, SKColor end)
{
var bitmap = new SKBitmap(width, height, SKColorType.Rgba8888, SKAlphaType.Premul);
using var canvas = new SKCanvas(bitmap);
using var paint = new SKPaint();
paint.Shader = SKShader.CreateLinearGradient(
new SKPoint(0, 0),
new SKPoint(width, height),
new[] { start, end },
SKShaderTileMode.Clamp);
canvas.DrawRect(0, 0, width, height, paint);
return bitmap;
}
}