Skip to content

Commit df17f73

Browse files
committed
Fold in SIMD optimized PHash implementation
Remove separate `Optimized` file/tests
1 parent 36eadc6 commit df17f73

File tree

102 files changed

+72
-885
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

102 files changed

+72
-885
lines changed

src/ImageHash/HashAlgorithms/PerceptualHash.cs

Lines changed: 72 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44
using System.Collections.Generic;
55
using System.Diagnostics;
66
using System.Linq;
7+
using System.Numerics;
78
using System.Runtime.CompilerServices;
8-
9+
using System.Threading.Tasks;
910
using SixLabors.ImageSharp;
1011
using SixLabors.ImageSharp.PixelFormats;
1112
using SixLabors.ImageSharp.Processing;
@@ -18,16 +19,18 @@ public class PerceptualHash : IImageHash
1819
private const int Size = 64;
1920
private static readonly double Sqrt2DivSize = Math.Sqrt(2D / Size);
2021
private static readonly double Sqrt2 = 1 / Math.Sqrt(2);
22+
private static readonly double[,] _dctCoeffs = GenerateDctCoeffs();
23+
private static readonly List<Vector<double>>[] _dctCoeffsSimd = GenerateDctCoeffsSimd();
2124

2225
/// <inheritdoc />
2326
public ulong Hash(Image<Rgba32> image)
2427
{
2528
if (image == null)
2629
throw new ArgumentNullException(nameof(image));
2730

28-
var rows = new double[Size][];
31+
var rows = new double[Size, Size];
2932
var sequence = new double[Size];
30-
var matrix = new double[Size][];
33+
var matrix = new double[Size, Size];
3134

3235
image.Mutate(ctx => ctx
3336
.Resize(Size, Size)
@@ -40,38 +43,36 @@ public ulong Hash(Image<Rgba32> image)
4043
for (var x = 0; x < Size; x++)
4144
sequence[x] = image[x, y].R;
4245

43-
rows[y] = Dct1D(sequence);
46+
Dct1D_SIMD(sequence, rows, y);
4447
}
4548

4649
// Calculate the DCT for each column.
47-
for (var x = 0; x < Size; x++)
50+
for (var x = 0; x < 8; x++)
4851
{
4952
for (var y = 0; y < Size; y++)
50-
sequence[y] = rows[y][x];
53+
sequence[y] = rows[y, x];
5154

52-
matrix[x] = Dct1D(sequence);
55+
Dct1D_SIMD(sequence, matrix, x, limit: 8);
5356
}
5457

5558
// Only use the top 8x8 values.
56-
var top8X8 = new List<double>(Size);
59+
var top8X8 = new double[Size];
5760
for (var y = 0; y < 8; y++)
5861
{
5962
for (var x = 0; x < 8; x++)
60-
top8X8.Add(matrix[y][x]);
63+
top8X8[(y * 8) + x] = matrix[y, x];
6164
}
6265

63-
var topRight = top8X8.ToArray();
64-
6566
// Get Median.
66-
var median = CalculateMedian64Values(topRight);
67+
var median = CalculateMedian64Values(top8X8);
6768

6869
// Calculate hash.
6970
var mask = 1UL << (Size - 1);
7071
var hash = 0UL;
7172

7273
for (var i = 0; i < Size; i++)
7374
{
74-
if (topRight[i] > median)
75+
if (top8X8[i] > median)
7576
hash |= mask;
7677

7778
mask = mask >> 1;
@@ -87,28 +88,74 @@ private static double CalculateMedian64Values(IReadOnlyCollection<double> values
8788
return values.OrderBy(value => value).Skip(31).Take(2).Average();
8889
}
8990

91+
private static double[,] GenerateDctCoeffs()
92+
{
93+
double[,] c = new double[Size, Size];
94+
for (var coef = 0; coef < Size; coef++)
95+
{
96+
for (var i = 0; i < Size; i++)
97+
{
98+
c[i, coef] = Math.Cos(((2.0 * i) + 1.0) * coef * Math.PI / (2.0 * Size));
99+
}
100+
}
101+
102+
return c;
103+
}
104+
105+
private static List<Vector<double>>[] GenerateDctCoeffsSimd()
106+
{
107+
List<Vector<double>>[] results = new List<Vector<double>>[Size];
108+
for (var coef = 0; coef < Size; coef++)
109+
{
110+
var singleResultRaw = new double[Size];
111+
for (var i = 0; i < Size; i++)
112+
{
113+
singleResultRaw[i] = Math.Cos(((2.0 * i) + 1.0) * coef * Math.PI / (2.0 * Size));
114+
}
115+
116+
var singleResultList = new List<Vector<double>>();
117+
var stride = Vector<double>.Count;
118+
Debug.Assert(Size % stride == 0, "Size must be a multiple of SIMD stride");
119+
for (int i = 0; i < Size; i += stride)
120+
{
121+
var v = new Vector<double>(singleResultRaw, i);
122+
singleResultList.Add(v);
123+
}
124+
125+
results[coef] = singleResultList;
126+
}
127+
128+
return results;
129+
}
130+
90131
/// <summary>
91132
/// One dimensional Discrete Cosine Transformation.
92133
/// </summary>
93-
/// <param name="values">Should be an array of doubles of length 64.</param>
94-
/// <returns>array of doubles of length 64.</returns>
134+
/// <param name="valuesRaw">Should be an array of doubles of length 64.</param>
135+
/// <param name="coefficients">Coefficients.</param>
136+
/// <param name="ci">Coefficients index.</param>
137+
/// <param name="limit">Limit.</param>
95138
[MethodImpl(MethodImplOptions.AggressiveInlining)]
96-
private static double[] Dct1D(IReadOnlyList<double> values)
139+
private static void Dct1D_SIMD(double[] valuesRaw, double[,] coefficients, int ci, int limit = Size)
97140
{
98-
Debug.Assert(values.Count == 64, "This DCT method works with 64 doubles.");
99-
var coefficients = new double[Size];
141+
Debug.Assert(valuesRaw.Length == 64, "This DCT method works with 64 doubles.");
100142

101-
for (var coef = 0; coef < Size; coef++)
143+
var valuesList = new List<Vector<double>>();
144+
var stride = Vector<double>.Count;
145+
for (int i = 0; i < valuesRaw.Length; i += stride)
102146
{
103-
for (var i = 0; i < Size; i++)
104-
coefficients[coef] += values[i] * Math.Cos(((2.0 * i) + 1.0) * coef * Math.PI / (2.0 * Size));
147+
valuesList.Add(new Vector<double>(valuesRaw, i));
148+
}
105149

106-
coefficients[coef] *= Sqrt2DivSize;
150+
for (var coef = 0; coef < limit; coef++)
151+
{
152+
for (int i = 0; i < valuesList.Count; i++)
153+
coefficients[ci, coef] += Vector.Dot(valuesList[i], _dctCoeffsSimd[coef][i]);
154+
155+
coefficients[ci, coef] *= Sqrt2DivSize;
107156
if (coef == 0)
108-
coefficients[coef] *= Sqrt2;
157+
coefficients[ci, coef] *= Sqrt2;
109158
}
110-
111-
return coefficients;
112159
}
113160
}
114161
}

src/ImageHash/HashAlgorithms/PerceptualHashOptimized.cs

Lines changed: 0 additions & 161 deletions
This file was deleted.

0 commit comments

Comments
 (0)