using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using System;
using System.IO;
namespace Roadie.Library.Imaging
{
///
/// Contains a variety of methods useful in generating image hashes for image comparison
/// and recognition.
///
/// Credit for the AverageHash implementation to David Oftedal of the University of Oslo.
///
public static class ImageHasher
{
#region Private constants and utility methods
///
/// Bitcounts array used for BitCount method (used in Similarity comparisons).
/// Don't try to read this or understand it, I certainly don't. Credit goes to
/// David Oftedal of the University of Oslo, Norway for this.
/// http://folk.uio.no/davidjo/computing.php
///
private static byte[] bitCounts = {
0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,1,2,2,3,2,3,3,4,
2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,
2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,
4,5,5,6,5,6,6,7,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,
2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,2,3,3,4,3,4,4,5,
3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,
4,5,5,6,5,6,6,7,5,6,6,7,6,7,7,8
};
///
/// Counts bits (duh). Utility function for similarity.
/// I wouldn't try to understand this. I just copy-pasta'd it
/// from Oftedal's implementation. It works.
///
/// The hash we are counting.
/// The total bit count.
private static uint BitCount(ulong num)
{
uint count = 0;
for (; num > 0; num >>= 8)
{
count += bitCounts[(num & 0xff)];
}
return count;
}
#endregion Private constants and utility methods
#region Public interface methods
///
/// Generate a hash for the image to be able to find like/matching images.
///
/// Image bytes
/// Hash of Image
public static ulong AverageHash(byte[] bytes)
{
using (Image image = Image.Load(bytes))
{
image.Mutate(ctx => ctx.Resize(8, 8).Grayscale());
using (var ms = new MemoryStream())
{
byte[] grayscale = new byte[64];
uint averageValue = 0;
for (int y = 0; y < 8; y++)
{
Span pixelRowSpan = image.GetPixelRowSpan(y);
for (int x = 0; x < 8; x++)
{
uint pixel = (uint)pixelRowSpan[x].PackedValue;
uint gray = (pixel & 0x00ff0000) >> 16;
gray += (pixel & 0x0000ff00) >> 8;
gray += (pixel & 0x000000ff);
gray /= 12;
grayscale[x + (y * 8)] = (byte)gray;
averageValue += gray;
}
}
averageValue /= 64;
ulong hash = 0;
for (int i = 0; i < 64; i++)
{
if (grayscale[i] >= averageValue)
{
hash |= (1UL << (63 - i));
}
}
return hash;
}
}
}
///
/// Computes the average hash of the image content in the given file.
///
/// Path to the input file.
/// The hash of the input file's image content.
public static ulong AverageHash(String path)
{
return AverageHash(File.ReadAllBytes(path));
}
public static bool ImagesAreSame(string path1, string path2)
{
return Similarity(path1, path2) == 100;
}
public static bool ImagesAreSame(byte[] image1, byte[] image2)
{
return Similarity(image1, image2) == 100;
}
///
/// Returns a percentage-based similarity value between the two given hashes. The higher
/// the percentage, the closer the hashes are to being identical.
///
/// The first hash.
/// The second hash.
/// The similarity percentage.
public static double Similarity(ulong hash1, ulong hash2)
{
return ((64 - BitCount(hash1 ^ hash2)) * 100) / 64.0;
}
///
/// Returns a percentage-based similarity value between the image content of the two given
/// files. The higher the percentage, the closer the image contents are to being identical.
///
/// The first image file.
/// The second image file.
/// The similarity percentage.
public static double Similarity(String path1, String path2)
{
ulong hash1 = AverageHash(path1);
ulong hash2 = AverageHash(path2);
return Similarity(hash1, hash2);
}
///
/// Returns a percentage-based similarity value between the image content of the two given
/// files. The higher the percentage, the closer the image contents are to being identical.
///
/// The first image bytes.
/// The second image bytes.
/// The similarity percentage.
public static double Similarity(byte[] image1, byte[] image2)
{
ulong hash1 = AverageHash(image1);
ulong hash2 = AverageHash(image2);
return Similarity(hash1, hash2);
}
#endregion Public interface methods
}
}