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