Removed Murmur3 in the old variant in favor of the Span implementation, there is not much gain from keeping it.

This commit is contained in:
Robin 2020-01-28 14:00:36 +01:00
commit ea312eff65
9 changed files with 143 additions and 324 deletions

View file

@ -333,31 +333,12 @@ namespace Greenshot.Gfx.FastBitmap
/// <param name="left">optional x ending coordinate of the hash calculation</param> /// <param name="left">optional x ending coordinate of the hash calculation</param>
/// <returns>uint with the hash</returns> /// <returns>uint with the hash</returns>
public uint HorizontalHash(int y, int? right = null, int? left = null) public uint HorizontalHash(int y, int? right = null, int? left = null)
{ {
var offset = (left ?? Left) * BytesPerPixel + y * Stride; var offset = (left ?? Left) * BytesPerPixel + y * Stride;
var length = (right ?? Right) - (left ?? Left) * BytesPerPixel;
var length = (right ?? Right) - (left ?? Left) * BytesPerPixel; var hash = new Murmur3(Seed);
var hash = new Murmur3(Seed, (uint) length); return hash.CalculateHash(Pointer, offset, length);
}
while (length >= 4)
{
hash.AddBytes(Pointer[offset++], Pointer[offset++], Pointer[offset++], Pointer[offset++]);
length -= 4;
}
switch (length)
{
case 3:
hash.AddTrailingBytes(Pointer[offset++], Pointer[offset++], Pointer[offset]);
break;
case 2:
hash.AddTrailingBytes(Pointer[offset++], Pointer[offset]);
break;
case 1:
hash.AddTrailingBytes(Pointer[offset]);
break;
}
return hash.CalculatedHash;
}
/// <summary> /// <summary>
/// Test if the bitmap containt the specified coordinates /// Test if the bitmap containt the specified coordinates

View file

@ -19,7 +19,7 @@
using System; using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Security.Cryptography; using System.Runtime.InteropServices;
namespace Greenshot.Gfx namespace Greenshot.Gfx
{ {
@ -27,7 +27,7 @@ namespace Greenshot.Gfx
/// This is an implementation of the Murmur3 hash algorithm /// This is an implementation of the Murmur3 hash algorithm
/// See <a href="https://en.wikipedia.org/wiki/MurmurHash">MurmurHash</a> /// See <a href="https://en.wikipedia.org/wiki/MurmurHash">MurmurHash</a>
/// </summary> /// </summary>
public sealed class Murmur3 : HashAlgorithm public sealed class Murmur3
{ {
private const uint C1 = 0xcc9e2d51; private const uint C1 = 0xcc9e2d51;
private const uint C2 = 0x1b873593; private const uint C2 = 0x1b873593;
@ -37,143 +37,95 @@ namespace Greenshot.Gfx
private const uint N = 0xe6546b64; private const uint N = 0xe6546b64;
private readonly uint _seed; private readonly uint _seed;
private readonly uint _initialLength;
private uint _hash;
private uint _length;
/// <inheritdoc />
public override int HashSize => 32;
/// <summary> /// <summary>
/// Constructor for the Murmur3 algorithm /// Constructor for the Murmur3 algorithm
/// </summary> /// </summary>
/// <param name="seed"></param> /// <param name="seed"></param>
/// <param name="length"></param> public Murmur3(uint seed)
public Murmur3(uint seed, uint length = 0)
{ {
_seed = seed; _seed = seed;
_initialLength = length;
Initialize();
} }
/// <summary> /// <summary>
/// Add the bytes to the hash /// Wrapper for byte*
/// </summary> /// </summary>
/// <param name="one">first byte</param> /// <returns>uint</returns>
/// <param name="two">second byte</param> public unsafe uint CalculateHash(byte* pointer, int offset, int length)
/// <param name="three">third byte</param>
/// <param name="four">fourth byte</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddBytes(byte one, byte two, byte three, byte four)
{ {
unchecked var values = new ReadOnlySpan<byte>(pointer+offset, length);
{ return CalculateHash(values);
var k = (uint)(one | two << 8 | three << 16 | four << 24);
k *= C1;
k = RotateLeft(k, R1);
k *= C2;
_hash ^= k;
_hash = RotateLeft(_hash, R2);
_hash = _hash * M + N;
}
} }
/// <summary> /// <summary>
/// Add the last bytes /// Wrapper for string etc
/// </summary> /// </summary>
/// <param name="one">first byte</param> /// <typeparam name="TSpan">Type for the span</typeparam>
/// <param name="two">second byte</param> /// <param name="valuesToHash">ReadOnlySpan of TSpan</param>
/// <param name="three">third byte</param> /// <returns>uint</returns>
public void AddTrailingBytes(byte one, byte two = 0, byte three = 0) public uint CalculateHash<TSpan>(ReadOnlySpan<TSpan> valuesToHash) where TSpan : struct
{ {
unchecked return CalculateHash(MemoryMarshal.Cast<TSpan, byte>(valuesToHash));
{
var k = (uint) (one | two << 8 | three << 16);
k *= C1;
k = RotateLeft(k, R1);
k *= C2;
_hash ^= k;
}
}
/// <inheritdoc />
protected override void HashCore(byte[] array, int ibStart, int cbSize)
{
_length = (uint)cbSize;
var curLength = cbSize;
var currentIndex = ibStart;
while (curLength >= 4)
{
AddBytes(array[currentIndex++], array[currentIndex++], array[currentIndex++], array[currentIndex++]);
curLength -= 4;
}
// Process the remaining bytes, if any
if (curLength <= 0)
{
return;
}
switch (curLength)
{
case 3:
AddTrailingBytes(array[currentIndex++], array[currentIndex++], array[currentIndex]);
break;
case 2:
AddTrailingBytes(array[currentIndex++], array[currentIndex]);
break;
case 1:
AddTrailingBytes(array[currentIndex]);
break;
}
} }
/// <summary> /// <summary>
/// Returns the hash /// Calculate a Murmur3 hash for the specified values
/// </summary> /// </summary>
public uint CalculatedHash /// <param name="valuesToHash">ReadOnlySpan of byte</param>
/// <returns>uint with the hash</returns>
public uint CalculateHash(ReadOnlySpan<byte> valuesToHash)
{ {
get var uintSpan = MemoryMarshal.Cast<byte, uint>(valuesToHash);
uint hash = _seed;
var uintSpanLength = uintSpan.Length;
// Hash with uints
for (int index = 0; index < uintSpanLength; index++)
{ {
var hash = _hash ^ _length; var k = uintSpan[index];
unchecked unchecked
{ {
hash ^= hash >> 16; k *= C1;
hash *= 0x85ebca6b; k = RotateLeft(k, R1);
hash ^= hash >> 13; k *= C2;
hash *= 0xc2b2ae35; hash ^= k;
hash ^= hash >> 16; hash = RotateLeft(hash, R2);
hash = hash * M + N;
} }
return hash;
} }
int valuesToProcess = valuesToHash.Length - (uintSpanLength * 4);
if (valuesToProcess > 0)
{
uint k = 0;
var startingOffset = uintSpanLength * 4;
// Hash the rest
for (int index = 0; index < valuesToProcess; index++)
{
k |= (uint)valuesToHash[startingOffset + index] << (8 * index);
}
unchecked
{
k *= C1;
k = RotateLeft(k, R1);
k *= C2;
hash ^= k;
}
}
// Calculate the final hash
hash ^= (uint)valuesToHash.Length;
unchecked
{
hash ^= hash >> 16;
hash *= 0x85ebca6b;
hash ^= hash >> 13;
hash *= 0xc2b2ae35;
hash ^= hash >> 16;
}
return hash;
} }
/// <summary>
/// Generate a hash for the specified bytes
/// </summary>
/// <param name="bytes">byte array</param>
/// <param name="offset">optional int with offset into the byte array</param>
/// <param name="size">optional int with the size</param>
/// <returns>uint with the hash</returns>
public uint GenerateHash(byte[] bytes, int? offset = null, int? size = null)
{
Initialize();
HashCore(bytes, offset ?? 0, size ?? bytes.Length);
return CalculatedHash;
}
/// <inheritdoc />
protected override byte[] HashFinal()
{
return BitConverter.GetBytes(CalculatedHash);
}
/// <inheritdoc />
public override void Initialize()
{
// re-initialize the Hash with the seed, to allow reuse
_hash = _seed;
_length = _initialLength;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static uint RotateLeft(uint x, byte r) private static uint RotateLeft(uint x, byte r)

View file

@ -1,126 +0,0 @@
// Greenshot - a free and open source screenshot tool
// Copyright (C) 2007-2020 Thomas Braun, Jens Klingen, Robin Krom
//
// For more information see: http://getgreenshot.org/
// The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 1 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Greenshot.Gfx
{
/// <summary>
/// This is an implementation of the Murmur3 hash algorithm
/// See <a href="https://en.wikipedia.org/wiki/MurmurHash">MurmurHash</a>
/// </summary>
public sealed class Murmur3Span
{
private const uint C1 = 0xcc9e2d51;
private const uint C2 = 0x1b873593;
private const int R1 = 15;
private const int R2 = 13;
private const uint M = 5;
private const uint N = 0xe6546b64;
private readonly uint _seed;
/// <summary>
/// Constructor for the Murmur3 algorithm
/// </summary>
/// <param name="seed"></param>
public Murmur3Span(uint seed)
{
_seed = seed;
}
/// <summary>
/// Wrapper for string etc
/// </summary>
/// <typeparam name="TSpan">Type for the span</typeparam>
/// <param name="valuesToHash">ReadOnlySpan of TSpan</param>
/// <returns>uint</returns>
public uint CalculateHash<TSpan>(ReadOnlySpan<TSpan> valuesToHash) where TSpan : struct
{
return CalculateHash(MemoryMarshal.Cast<TSpan, byte>(valuesToHash));
}
/// <summary>
/// Calculate a Murmur3 hash for the specified values
/// </summary>
/// <param name="valuesToHash">ReadOnlySpan of byte</param>
/// <returns>uint with the hash</returns>
public uint CalculateHash(ReadOnlySpan<byte> valuesToHash)
{
var uintSpan = MemoryMarshal.Cast<byte, uint>(valuesToHash);
uint hash = _seed;
var uintSpanLength = uintSpan.Length;
// Hash with uints
for (int index = 0; index < uintSpanLength; index++)
{
var k = uintSpan[index];
unchecked
{
k *= C1;
k = RotateLeft(k, R1);
k *= C2;
hash ^= k;
hash = RotateLeft(hash, R2);
hash = hash * M + N;
}
}
int valuesToProcess = valuesToHash.Length - (uintSpanLength * 4);
if (valuesToProcess > 0)
{
uint k = 0;
var startingOffset = uintSpanLength * 4;
// Hash the rest
for (int index = 0; index < valuesToProcess; index++)
{
k |= (uint)valuesToHash[startingOffset + index] << (8 * index);
}
unchecked
{
k *= C1;
k = RotateLeft(k, R1);
k *= C2;
hash ^= k;
}
}
// Calculate the final hash
hash ^= (uint)valuesToHash.Length;
unchecked
{
hash ^= hash >> 16;
hash *= 0x85ebca6b;
hash ^= hash >> 13;
hash *= 0xc2b2ae35;
hash ^= hash >> 16;
}
return hash;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static uint RotateLeft(uint x, byte r)
{
return (x << r) | (x >> (32 - r));
}
}
}

View file

@ -18,7 +18,6 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
using System; using System;
using System.Text;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using Greenshot.Gfx; using Greenshot.Gfx;
@ -31,19 +30,11 @@ namespace Greenshot.PerformanceTests
public class Murmur3Performance public class Murmur3Performance
{ {
private static readonly string TestString = "The quick brown fox jumps over the lazy dog"; private static readonly string TestString = "The quick brown fox jumps over the lazy dog";
private static readonly byte[] TestBytes = Encoding.UTF8.GetBytes(TestString);
[Benchmark] [Benchmark]
public void MurmurPerformanceTestArray() public void MurmurPerformanceTest()
{ {
using var hashAlgorithm = new Murmur3(0x9747b28c); var hashAlgorithm = new Murmur3(0x9747b28c);
hashAlgorithm.GenerateHash(TestBytes);
}
[Benchmark]
public void MurmurPerformanceTestSpan()
{
var hashAlgorithm = new Murmur3Span(0x9747b28c);
hashAlgorithm.CalculateHash(TestString.AsSpan()); hashAlgorithm.CalculateHash(TestString.AsSpan());
} }
} }

View file

@ -0,0 +1,60 @@
// Greenshot - a free and open source screenshot tool
// Copyright (C) 2007-2020 Thomas Braun, Jens Klingen, Robin Krom
//
// For more information see: http://getgreenshot.org/
// The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 1 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
using System.IO;
using System.Security.Cryptography;
using System.Text;
using Xunit;
namespace Greenshot.Tests
{
public static class FileAssert
{
static string GetFileHash(string filename)
{
Assert.True(File.Exists(filename));
using (var hash = new SHA1Managed())
{
var clearBytes = File.ReadAllBytes(filename);
var hashedBytes = hash.ComputeHash(clearBytes);
return ConvertBytesToHex(hashedBytes);
}
}
static string ConvertBytesToHex(byte[] bytes)
{
var sb = new StringBuilder();
foreach (var t in bytes)
{
sb.Append(t.ToString("x"));
}
return sb.ToString();
}
public static void AreEqual(string filename1, string filename2)
{
string hash1 = GetFileHash(filename1);
string hash2 = GetFileHash(filename2);
Assert.Equal(hash1, hash2);
}
}
}

View file

@ -49,5 +49,11 @@
<ProjectReference Include="..\Greenshot.Gfx\Greenshot.Gfx.csproj" /> <ProjectReference Include="..\Greenshot.Gfx\Greenshot.Gfx.csproj" />
<ProjectReference Include="..\Greenshot\Greenshot.csproj" /> <ProjectReference Include="..\Greenshot\Greenshot.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<None Update="TestFiles\scroll-result.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project> </Project>

View file

@ -32,63 +32,14 @@ namespace Greenshot.Tests
{ {
private static readonly uint Seed = 0x9747b28c; private static readonly uint Seed = 0x9747b28c;
private static readonly string TestString = "The quick brown fox jumps over the lazy dog"; private static readonly string TestString = "The quick brown fox jumps over the lazy dog";
[Fact] [Fact]
public void Murmur3_basic1_Test() public void Murmur3_Test()
{ {
var hash = TestHash("Hello, world!", 1234); var murmur3Span = new Murmur3(Seed);
Assert.Equal(0xfaf6cdb3u, hash);
}
[Fact]
public void Murmur3_basic2_Test()
{
var hash = TestHash(TestString, Seed);
Assert.Equal(0x2FA826CDu, hash);
hash = TestHash2(TestString, Seed);
Assert.Equal(0x2FA826CDu, hash);
}
[Fact]
public void Murmur3_Span_Test()
{
var hash = TestHash(TestString, Seed);
Assert.Equal(0x2FA826CDu, hash);
var murmur3Span = new Murmur3Span(Seed);
var testBytes = Encoding.UTF8.GetBytes(TestString); var testBytes = Encoding.UTF8.GetBytes(TestString);
hash = murmur3Span.CalculateHash(testBytes.AsSpan()); var hash = murmur3Span.CalculateHash(testBytes.AsSpan());
Assert.Equal(0x2FA826CDu, hash); Assert.Equal(0x2FA826CDu, hash);
} }
[Fact]
public void Murmur3_SpanChar_Test()
{
var hash = TestHashUnicode(TestString, Seed);
var murmur3Span = new Murmur3Span(Seed);
Assert.Equal(hash, murmur3Span.CalculateHash(TestString.AsSpan()));
}
private uint TestHash(string testString, uint seed)
{
using var hashAlgorithm = new Murmur3(seed);
var testBytes = Encoding.UTF8.GetBytes(testString);
var hash = hashAlgorithm.ComputeHash(testBytes);
return hash.ToUInt32();
}
private uint TestHash2(string testString, uint seed)
{
using var hashAlgorithm = new Murmur3(seed);
var testBytes = Encoding.UTF8.GetBytes(testString);
return hashAlgorithm.GenerateHash(testBytes);
}
private uint TestHashUnicode(string testString, uint seed)
{
using var hashAlgorithm = new Murmur3(seed);
var testBytes = Encoding.Unicode.GetBytes(testString);
var hash = hashAlgorithm.ComputeHash(testBytes);
return hash.ToUInt32();
}
} }
} }

View file

@ -18,6 +18,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
using System.Drawing.Imaging; using System.Drawing.Imaging;
using System.IO;
using Greenshot.Gfx; using Greenshot.Gfx;
using Greenshot.Gfx.Formats; using Greenshot.Gfx.Formats;
using Greenshot.Gfx.Stitching; using Greenshot.Gfx.Stitching;
@ -45,6 +46,9 @@ namespace Greenshot.Tests
using var completedBitmap = bitmapStitcher.Result(); using var completedBitmap = bitmapStitcher.Result();
completedBitmap.NativeBitmap.Save("scroll.png", ImageFormat.Png); completedBitmap.NativeBitmap.Save("scroll.png", ImageFormat.Png);
FileAssert.AreEqual(@"TestFiles\scroll-result.png", "scroll.png");
File.Delete("scroll.png");
} }
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 294 KiB