wabbajack/Wabbajack.ImageHashing/PHash.cs

139 lines
3.6 KiB
C#
Raw Normal View History

2021-06-16 05:16:25 +00:00
using System;
using System.Data;
using System.IO;
using System.Threading.Tasks;
2021-06-17 23:09:03 +00:00
using Newtonsoft.Json;
2021-06-16 05:16:25 +00:00
using Shipwreck.Phash;
using Wabbajack.Common;
namespace Wabbajack.ImageHashing
{
2021-06-17 23:09:03 +00:00
[JsonConverter(typeof(PHashJsonConverter))]
2021-06-16 05:16:25 +00:00
public struct PHash
{
private const int SIZE = 40;
2021-06-16 21:07:16 +00:00
private readonly int _hash;
2021-06-16 05:16:25 +00:00
2021-06-17 23:09:03 +00:00
public byte[] Data { get; }
2021-06-16 21:07:16 +00:00
private PHash(byte[] data)
2021-06-16 05:16:25 +00:00
{
2021-06-17 23:09:03 +00:00
Data = data;
if (Data.Length != SIZE)
2021-06-16 05:16:25 +00:00
throw new DataException();
2021-06-16 21:07:16 +00:00
long h = 0;
2021-06-17 23:09:03 +00:00
h |= Data[0];
2021-06-16 21:07:16 +00:00
h <<= 8;
2021-06-17 23:09:03 +00:00
h |= Data[1];
2021-06-16 21:07:16 +00:00
h <<= 8;
2021-06-17 23:09:03 +00:00
h |= Data[2];
2021-06-16 21:07:16 +00:00
h <<= 8;
2021-06-17 23:09:03 +00:00
h |= Data[3];
2021-06-16 21:07:16 +00:00
h <<= 8;
_hash = (int)h;
2021-06-16 05:16:25 +00:00
}
public static PHash FromBase64(string base64)
{
var data = base64.FromBase64();
if (data.Length != SIZE)
throw new DataException();
return new PHash(data);
}
public static PHash Read(BinaryReader br)
{
return new (br.ReadBytes(SIZE));
}
public void Write(BinaryWriter br)
{
2021-06-16 21:07:16 +00:00
if (_hash == 0)
br.Write(new byte[SIZE]);
else
2021-06-17 23:09:03 +00:00
br.Write(Data);
2021-06-16 05:16:25 +00:00
}
public static PHash FromDigest(Digest digest)
{
return new(digest.Coefficients);
}
public float Similarity(PHash other)
{
2021-06-17 23:09:03 +00:00
return ImagePhash.GetCrossCorrelation(this.Data, other.Data);
2021-06-16 05:16:25 +00:00
}
public override string ToString()
{
2021-06-17 23:09:03 +00:00
return Data.ToBase64();
2021-06-16 05:16:25 +00:00
}
public override int GetHashCode()
{
long h = 0;
2021-06-17 23:09:03 +00:00
h |= Data[0];
2021-06-16 05:16:25 +00:00
h <<= 8;
2021-06-17 23:09:03 +00:00
h |= Data[1];
2021-06-16 05:16:25 +00:00
h <<= 8;
2021-06-17 23:09:03 +00:00
h |= Data[2];
2021-06-16 05:16:25 +00:00
h <<= 8;
2021-06-17 23:09:03 +00:00
h |= Data[3];
2021-06-16 05:16:25 +00:00
h <<= 8;
return (int)h;
}
public static async Task<PHash> FromStream(Stream stream, Extension ext, bool takeStreamOwnership = true)
{
2021-06-18 23:33:58 +00:00
try
2021-06-16 05:16:25 +00:00
{
2021-06-18 23:33:58 +00:00
var ms = new MemoryStream();
await stream.CopyToAsync(ms);
if (takeStreamOwnership) await stream.DisposeAsync();
DDSImage img;
if (ext == new Extension(".dds"))
img = DDSImage.FromDDSMemory(ms.GetBuffer());
else if (ext == new Extension(".tga"))
{
img = DDSImage.FromTGAMemory(ms.GetBuffer());
}
else
{
throw new NotImplementedException("Only DDS and TGA files supported by PHash");
}
return img.PerceptionHash();
2021-06-16 05:16:25 +00:00
}
2021-06-18 23:33:58 +00:00
catch (Exception ex)
2021-06-16 05:16:25 +00:00
{
2021-06-18 23:33:58 +00:00
Utils.Log($"Error getting PHASH {ex}");
return default;
2021-06-16 05:16:25 +00:00
}
}
2021-06-17 23:09:03 +00:00
public static async Task<PHash> FromFile(AbsolutePath path)
{
await using var s = await path.OpenRead();
return await FromStream(s, path.Extension);
}
}
public class PHashJsonConverter : JsonConverter<PHash>
{
public override void WriteJson(JsonWriter writer, PHash value, JsonSerializer serializer)
{
writer.WriteValue(value.Data.ToBase64());
}
public override PHash ReadJson(JsonReader reader, Type objectType, PHash existingValue, bool hasExistingValue,
JsonSerializer serializer)
{
return PHash.FromBase64((string)reader.Value!);
}
2021-06-16 05:16:25 +00:00
}
}