22// Licensed under the Six Labors Split License.
33
44using System . Buffers ;
5+ using System . Numerics ;
56using System . Runtime . CompilerServices ;
67using System . Runtime . InteropServices ;
78using SixLabors . ImageSharp . Memory ;
@@ -14,13 +15,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization;
1415/// </summary>
1516/// <typeparam name="TPixel">The pixel format.</typeparam>
1617/// <para>
17- /// This class is not threadsafe and should not be accessed in parallel.
18+ /// This class is not thread safe and should not be accessed in parallel.
1819/// Doing so will result in non-idempotent results.
1920/// </para>
2021internal sealed class EuclideanPixelMap < TPixel > : IDisposable
2122 where TPixel : unmanaged, IPixel < TPixel >
2223{
2324 private Rgba32 [ ] rgbaPalette ;
25+ private int transparentIndex ;
2426
2527 /// <summary>
2628 /// Do not make this readonly! Struct value would be always copied on non-readonly method calls.
@@ -34,12 +36,24 @@ internal sealed class EuclideanPixelMap<TPixel> : IDisposable
3436 /// <param name="configuration">The configuration.</param>
3537 /// <param name="palette">The color palette to map from.</param>
3638 public EuclideanPixelMap ( Configuration configuration , ReadOnlyMemory < TPixel > palette )
39+ : this ( configuration , palette , - 1 )
40+ {
41+ }
42+
43+ /// <summary>
44+ /// Initializes a new instance of the <see cref="EuclideanPixelMap{TPixel}"/> class.
45+ /// </summary>
46+ /// <param name="configuration">The configuration.</param>
47+ /// <param name="palette">The color palette to map from.</param>
48+ /// <param name="transparentIndex">An explicit index at which to match transparent pixels.</param>
49+ public EuclideanPixelMap ( Configuration configuration , ReadOnlyMemory < TPixel > palette , int transparentIndex = - 1 )
3750 {
3851 this . configuration = configuration ;
3952 this . Palette = palette ;
4053 this . rgbaPalette = new Rgba32 [ palette . Length ] ;
4154 this . cache = new ColorDistanceCache ( configuration . MemoryAllocator ) ;
4255 PixelOperations < TPixel > . Instance . ToRgba32 ( configuration , this . Palette . Span , this . rgbaPalette ) ;
56+ this . transparentIndex = transparentIndex ;
4357 }
4458
4559 /// <summary>
@@ -91,16 +105,43 @@ public void Clear(ReadOnlyMemory<TPixel> palette)
91105 this . cache . Clear ( ) ;
92106 }
93107
108+ /// <summary>
109+ /// Allows setting the transparent index after construction.
110+ /// </summary>
111+ /// <param name="index">An explicit index at which to match transparent pixels.</param>
112+ public void SetTransparentIndex ( int index ) => this . transparentIndex = index ;
113+
94114 [ MethodImpl ( InliningOptions . ShortMethod ) ]
95115 private int GetClosestColorSlow ( Rgba32 rgba , ref TPixel paletteRef , out TPixel match )
96116 {
97117 // Loop through the palette and find the nearest match.
98118 int index = 0 ;
99119 float leastDistance = float . MaxValue ;
120+
121+ if ( this . transparentIndex >= 0 && rgba == default )
122+ {
123+ // We have explicit instructions. No need to search.
124+ index = this . transparentIndex ;
125+ this . cache . Add ( rgba , ( byte ) index ) ;
126+
127+ if ( index >= 0 && index < this . Palette . Length )
128+ {
129+ match = Unsafe . Add ( ref paletteRef , ( uint ) index ) ;
130+ }
131+ else
132+ {
133+ Unsafe . SkipInit ( out TPixel pixel ) ;
134+ pixel . FromScaledVector4 ( Vector4 . Zero ) ;
135+ match = pixel ;
136+ }
137+
138+ return index ;
139+ }
140+
100141 for ( int i = 0 ; i < this . rgbaPalette . Length ; i ++ )
101142 {
102143 Rgba32 candidate = this . rgbaPalette [ i ] ;
103- int distance = DistanceSquared ( rgba , candidate ) ;
144+ float distance = DistanceSquared ( rgba , candidate ) ;
104145
105146 // If it's an exact match, exit the loop
106147 if ( distance == 0 )
@@ -130,12 +171,12 @@ private int GetClosestColorSlow(Rgba32 rgba, ref TPixel paletteRef, out TPixel m
130171 /// <param name="b">The second point.</param>
131172 /// <returns>The distance squared.</returns>
132173 [ MethodImpl ( InliningOptions . ShortMethod ) ]
133- private static int DistanceSquared ( Rgba32 a , Rgba32 b )
174+ private static float DistanceSquared ( Rgba32 a , Rgba32 b )
134175 {
135- int deltaR = a . R - b . R ;
136- int deltaG = a . G - b . G ;
137- int deltaB = a . B - b . B ;
138- int deltaA = a . A - b . A ;
176+ float deltaR = a . R - b . R ;
177+ float deltaG = a . G - b . G ;
178+ float deltaB = a . B - b . B ;
179+ float deltaA = a . A - b . A ;
139180 return ( deltaR * deltaR ) + ( deltaG * deltaG ) + ( deltaB * deltaB ) + ( deltaA * deltaA ) ;
140181 }
141182
0 commit comments