.
This commit is contained in:
parent
86aa0f856c
commit
0801ceee6a
310 changed files with 6712 additions and 418 deletions
148
test/Scry.Tests/PerceptualHashTests.cs
Normal file
148
test/Scry.Tests/PerceptualHashTests.cs
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue