diff --git a/src/Greenshot.Gfx/FastBitmap/FastBitmapBase.cs b/src/Greenshot.Gfx/FastBitmap/FastBitmapBase.cs
index 528619442..6e04b1846 100644
--- a/src/Greenshot.Gfx/FastBitmap/FastBitmapBase.cs
+++ b/src/Greenshot.Gfx/FastBitmap/FastBitmapBase.cs
@@ -333,31 +333,12 @@ namespace Greenshot.Gfx.FastBitmap
/// optional x ending coordinate of the hash calculation
/// uint with the hash
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);
+ }
///
/// Test if the bitmap containt the specified coordinates
diff --git a/src/Greenshot.Gfx/Murmur3.cs b/src/Greenshot.Gfx/Murmur3.cs
index 79371a58c..34dc9438b 100644
--- a/src/Greenshot.Gfx/Murmur3.cs
+++ b/src/Greenshot.Gfx/Murmur3.cs
@@ -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 MurmurHash
///
- 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;
-
- ///
- public override int HashSize => 32;
///
/// Constructor for the Murmur3 algorithm
///
///
- ///
- public Murmur3(uint seed, uint length = 0)
+ public Murmur3(uint seed)
{
_seed = seed;
- _initialLength = length;
- Initialize();
}
///
- /// Add the bytes to the hash
+ /// Wrapper for byte*
///
- /// first byte
- /// second byte
- /// third byte
- /// fourth byte
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void AddBytes(byte one, byte two, byte three, byte four)
+ /// uint
+ 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(pointer+offset, length);
+ return CalculateHash(values);
}
///
- /// Add the last bytes
+ /// Wrapper for string etc
///
- /// first byte
- /// second byte
- /// third byte
- public void AddTrailingBytes(byte one, byte two = 0, byte three = 0)
+ /// Type for the span
+ /// ReadOnlySpan of TSpan
+ /// uint
+ public uint CalculateHash(ReadOnlySpan valuesToHash) where TSpan : struct
{
- unchecked
- {
- var k = (uint) (one | two << 8 | three << 16);
- k *= C1;
- k = RotateLeft(k, R1);
- k *= C2;
- _hash ^= k;
- }
- }
-
- ///
- 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(valuesToHash));
}
///
- /// Returns the hash
+ /// Calculate a Murmur3 hash for the specified values
///
- public uint CalculatedHash
+ /// ReadOnlySpan of byte
+ /// uint with the hash
+ public uint CalculateHash(ReadOnlySpan valuesToHash)
{
- get
+ var uintSpan = MemoryMarshal.Cast(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;
}
- ///
- /// Generate a hash for the specified bytes
- ///
- /// byte array
- /// optional int with offset into the byte array
- /// optional int with the size
- /// uint with the hash
- public uint GenerateHash(byte[] bytes, int? offset = null, int? size = null)
- {
- Initialize();
- HashCore(bytes, offset ?? 0, size ?? bytes.Length);
- return CalculatedHash;
- }
-
- ///
- protected override byte[] HashFinal()
- {
- return BitConverter.GetBytes(CalculatedHash);
- }
-
- ///
- 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)
diff --git a/src/Greenshot.Gfx/Murmur3Span.cs b/src/Greenshot.Gfx/Murmur3Span.cs
deleted file mode 100644
index 0bfc4a48f..000000000
--- a/src/Greenshot.Gfx/Murmur3Span.cs
+++ /dev/null
@@ -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 .
-
-using System;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-
-namespace Greenshot.Gfx
-{
- ///
- /// This is an implementation of the Murmur3 hash algorithm
- /// See MurmurHash
- ///
- 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;
-
- ///
- /// Constructor for the Murmur3 algorithm
- ///
- ///
- public Murmur3Span(uint seed)
- {
- _seed = seed;
- }
-
- ///
- /// Wrapper for string etc
- ///
- /// Type for the span
- /// ReadOnlySpan of TSpan
- /// uint
- public uint CalculateHash(ReadOnlySpan valuesToHash) where TSpan : struct
- {
- return CalculateHash(MemoryMarshal.Cast(valuesToHash));
- }
-
- ///
- /// Calculate a Murmur3 hash for the specified values
- ///
- /// ReadOnlySpan of byte
- /// uint with the hash
- public uint CalculateHash(ReadOnlySpan valuesToHash)
- {
- var uintSpan = MemoryMarshal.Cast(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));
- }
- }
-}
diff --git a/src/Greenshot.PerformanceTests/Murmur3Performance.cs b/src/Greenshot.PerformanceTests/Murmur3Performance.cs
index d7b6b13e9..9da15ac63 100644
--- a/src/Greenshot.PerformanceTests/Murmur3Performance.cs
+++ b/src/Greenshot.PerformanceTests/Murmur3Performance.cs
@@ -18,7 +18,6 @@
// along with this program. If not, see .
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());
}
}
diff --git a/src/Greenshot.Tests/FileAssert.cs b/src/Greenshot.Tests/FileAssert.cs
new file mode 100644
index 000000000..6a622d0a7
--- /dev/null
+++ b/src/Greenshot.Tests/FileAssert.cs
@@ -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 .
+
+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);
+ }
+ }
+}
diff --git a/src/Greenshot.Tests/Greenshot.Tests.csproj b/src/Greenshot.Tests/Greenshot.Tests.csproj
index 8016ffb11..3e01b6452 100644
--- a/src/Greenshot.Tests/Greenshot.Tests.csproj
+++ b/src/Greenshot.Tests/Greenshot.Tests.csproj
@@ -49,5 +49,11 @@
+
+
+
+ PreserveNewest
+
+
\ No newline at end of file
diff --git a/src/Greenshot.Tests/Murmur3Tests.cs b/src/Greenshot.Tests/Murmur3Tests.cs
index 995eb7fbc..7e398f3b1 100644
--- a/src/Greenshot.Tests/Murmur3Tests.cs
+++ b/src/Greenshot.Tests/Murmur3Tests.cs
@@ -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();
- }
}
}
diff --git a/src/Greenshot.Tests/StitchTests.cs b/src/Greenshot.Tests/StitchTests.cs
index 5e1592c88..5c83dfaf1 100644
--- a/src/Greenshot.Tests/StitchTests.cs
+++ b/src/Greenshot.Tests/StitchTests.cs
@@ -18,6 +18,7 @@
// along with this program. If not, see .
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");
}
}
}
diff --git a/src/Greenshot.Tests/TestFiles/scroll-result.png b/src/Greenshot.Tests/TestFiles/scroll-result.png
new file mode 100644
index 000000000..54fddb6b3
Binary files /dev/null and b/src/Greenshot.Tests/TestFiles/scroll-result.png differ