148 lines
4.5 KiB
C#
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;
|
|
}
|
|
}
|