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}
0 commit comments