mirror of
https://github.com/greenshot/greenshot
synced 2025-08-22 22:34:27 -07:00
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:
parent
20d8229575
commit
ea312eff65
9 changed files with 143 additions and 324 deletions
|
@ -333,31 +333,12 @@ namespace Greenshot.Gfx.FastBitmap
|
|||
/// <param name="left">optional x ending coordinate of the hash calculation</param>
|
||||
/// <returns>uint with the hash</returns>
|
||||
public uint HorizontalHash(int y, int? right = null, int? left = null)
|
||||
{
|
||||
{
|
||||
var offset = (left ?? Left) * BytesPerPixel + y * Stride;
|
||||
|
||||
var length = (right ?? Right) - (left ?? Left) * BytesPerPixel;
|
||||
var hash = new Murmur3(Seed, (uint) 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;
|
||||
}
|
||||
var length = (right ?? Right) - (left ?? Left) * BytesPerPixel;
|
||||
var hash = new Murmur3(Seed);
|
||||
return hash.CalculateHash(Pointer, offset, length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test if the bitmap containt the specified coordinates
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Security.Cryptography;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Greenshot.Gfx
|
||||
{
|
||||
|
@ -27,7 +27,7 @@ namespace Greenshot.Gfx
|
|||
/// This is an implementation of the Murmur3 hash algorithm
|
||||
/// See <a href="https://en.wikipedia.org/wiki/MurmurHash">MurmurHash</a>
|
||||
/// </summary>
|
||||
public sealed class Murmur3 : HashAlgorithm
|
||||
public sealed class Murmur3
|
||||
{
|
||||
private const uint C1 = 0xcc9e2d51;
|
||||
private const uint C2 = 0x1b873593;
|
||||
|
@ -37,143 +37,95 @@ namespace Greenshot.Gfx
|
|||
private const uint N = 0xe6546b64;
|
||||
|
||||
private readonly uint _seed;
|
||||
private readonly uint _initialLength;
|
||||
private uint _hash;
|
||||
private uint _length;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int HashSize => 32;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for the Murmur3 algorithm
|
||||
/// </summary>
|
||||
/// <param name="seed"></param>
|
||||
/// <param name="length"></param>
|
||||
public Murmur3(uint seed, uint length = 0)
|
||||
public Murmur3(uint seed)
|
||||
{
|
||||
_seed = seed;
|
||||
_initialLength = length;
|
||||
Initialize();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add the bytes to the hash
|
||||
/// Wrapper for byte*
|
||||
/// </summary>
|
||||
/// <param name="one">first byte</param>
|
||||
/// <param name="two">second byte</param>
|
||||
/// <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)
|
||||
/// <returns>uint</returns>
|
||||
public unsafe uint CalculateHash(byte* pointer, int offset, int length)
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
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;
|
||||
}
|
||||
var values = new ReadOnlySpan<byte>(pointer+offset, length);
|
||||
return CalculateHash(values);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add the last bytes
|
||||
/// Wrapper for string etc
|
||||
/// </summary>
|
||||
/// <param name="one">first byte</param>
|
||||
/// <param name="two">second byte</param>
|
||||
/// <param name="three">third byte</param>
|
||||
public void AddTrailingBytes(byte one, byte two = 0, byte three = 0)
|
||||
/// <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
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
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;
|
||||
}
|
||||
return CalculateHash(MemoryMarshal.Cast<TSpan, byte>(valuesToHash));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the hash
|
||||
/// Calculate a Murmur3 hash for the specified values
|
||||
/// </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
|
||||
{
|
||||
hash ^= hash >> 16;
|
||||
hash *= 0x85ebca6b;
|
||||
hash ^= hash >> 13;
|
||||
hash *= 0xc2b2ae35;
|
||||
hash ^= hash >> 16;
|
||||
k *= C1;
|
||||
k = RotateLeft(k, R1);
|
||||
k *= C2;
|
||||
hash ^= k;
|
||||
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)]
|
||||
private static uint RotateLeft(uint x, byte r)
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,7 +18,6 @@
|
|||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
using System.Text;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Greenshot.Gfx;
|
||||
|
||||
|
@ -31,19 +30,11 @@ namespace Greenshot.PerformanceTests
|
|||
public class Murmur3Performance
|
||||
{
|
||||
private static readonly string TestString = "The quick brown fox jumps over the lazy dog";
|
||||
private static readonly byte[] TestBytes = Encoding.UTF8.GetBytes(TestString);
|
||||
|
||||
[Benchmark]
|
||||
public void MurmurPerformanceTestArray()
|
||||
public void MurmurPerformanceTest()
|
||||
{
|
||||
using var hashAlgorithm = new Murmur3(0x9747b28c);
|
||||
hashAlgorithm.GenerateHash(TestBytes);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void MurmurPerformanceTestSpan()
|
||||
{
|
||||
var hashAlgorithm = new Murmur3Span(0x9747b28c);
|
||||
var hashAlgorithm = new Murmur3(0x9747b28c);
|
||||
hashAlgorithm.CalculateHash(TestString.AsSpan());
|
||||
}
|
||||
}
|
||||
|
|
60
src/Greenshot.Tests/FileAssert.cs
Normal file
60
src/Greenshot.Tests/FileAssert.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -49,5 +49,11 @@
|
|||
<ProjectReference Include="..\Greenshot.Gfx\Greenshot.Gfx.csproj" />
|
||||
<ProjectReference Include="..\Greenshot\Greenshot.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="TestFiles\scroll-result.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -32,63 +32,14 @@ namespace Greenshot.Tests
|
|||
{
|
||||
private static readonly uint Seed = 0x9747b28c;
|
||||
private static readonly string TestString = "The quick brown fox jumps over the lazy dog";
|
||||
|
||||
|
||||
[Fact]
|
||||
public void Murmur3_basic1_Test()
|
||||
public void Murmur3_Test()
|
||||
{
|
||||
var hash = TestHash("Hello, world!", 1234);
|
||||
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 murmur3Span = new Murmur3(Seed);
|
||||
var testBytes = Encoding.UTF8.GetBytes(TestString);
|
||||
hash = murmur3Span.CalculateHash(testBytes.AsSpan());
|
||||
var hash = murmur3Span.CalculateHash(testBytes.AsSpan());
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
using System.Drawing.Imaging;
|
||||
using System.IO;
|
||||
using Greenshot.Gfx;
|
||||
using Greenshot.Gfx.Formats;
|
||||
using Greenshot.Gfx.Stitching;
|
||||
|
@ -45,6 +46,9 @@ namespace Greenshot.Tests
|
|||
|
||||
using var completedBitmap = bitmapStitcher.Result();
|
||||
completedBitmap.NativeBitmap.Save("scroll.png", ImageFormat.Png);
|
||||
FileAssert.AreEqual(@"TestFiles\scroll-result.png", "scroll.png");
|
||||
|
||||
File.Delete("scroll.png");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
BIN
src/Greenshot.Tests/TestFiles/scroll-result.png
Normal file
BIN
src/Greenshot.Tests/TestFiles/scroll-result.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 294 KiB |
Loading…
Add table
Add a link
Reference in a new issue