@ -7,8 +7,9 @@ namespace ImageSharp.Quantizers
{
using System ;
using System.Buffers ;
using System.Collections.Generic ;
using System.Numerics ;
using System.Threading.Task s ;
using System.Runtime.CompilerService s ;
/// <summary>
/// An implementation of Wu's color quantizer with alpha channel.
@ -30,7 +31,7 @@ namespace ImageSharp.Quantizers
/// </para>
/// </remarks>
/// <typeparam name="TColor">The pixel format.</typeparam>
public sealed class WuQuantizer < TColor > : I Quantizer< TColor >
public class WuQuantizer < TColor > : Quantizer < TColor >
where TColor : struct , IPixel < TColor >
{
/// <summary>
@ -59,103 +60,238 @@ namespace ImageSharp.Quantizers
private const int TableLength = IndexCount * IndexCount * IndexCount * IndexAlphaCount ;
/// <summary>
/// The long array pool.
/// </summary>
private static readonly ArrayPool < long > LongPool = ArrayPool < long > . Create ( TableLength , 2 5 ) ;
/// <summary>
/// The double array pool.
/// A buffer for storing pixels
/// </summary>
private static readonly ArrayPool < double > DoublePool = ArrayPool < double > . Create ( TableLength , 5 ) ;
private readonly byte [ ] rgbaBuffer = new byte [ 4 ] ;
/// <summary>
/// The byte array pool.
/// A lookup table for colors
/// </summary>
private static readonly ArrayPool < byte > BytePool = ArrayPool < byte > . Create ( TableLength , 5 ) ;
private readonly Dictionary < TColor , byte > colorMap = new Dictionary < TColor , byte > ( ) ;
/// <summary>
/// Moment of <c>P(c)</c>.
/// </summary>
private readonly long [ ] vwt ;
private long [ ] vwt ;
/// <summary>
/// Moment of <c>r*P(c)</c>.
/// </summary>
private readonly long [ ] vmr ;
private long [ ] vmr ;
/// <summary>
/// Moment of <c>g*P(c)</c>.
/// </summary>
private readonly long [ ] vmg ;
private long [ ] vmg ;
/// <summary>
/// Moment of <c>b*P(c)</c>.
/// </summary>
private readonly long [ ] vmb ;
private long [ ] vmb ;
/// <summary>
/// Moment of <c>a*P(c)</c>.
/// </summary>
private readonly long [ ] vma ;
private long [ ] vma ;
/// <summary>
/// Moment of <c>c^2*P(c)</c>.
/// </summary>
private readonly double [ ] m2 ;
private float [ ] m2 ;
/// <summary>
/// Color space tag.
/// </summary>
private readonly byte [ ] tag ;
private byte [ ] tag ;
/// <summary>
/// A buffer for storing pixels
/// Maximum allowed color depth
/// </summary>
private readonly byte [ ] rgbaBuffer = new byte [ 4 ] ;
private int colors ;
/// <summary>
/// The reduced image palette
/// </summary>
private TColor [ ] palette ;
/// <summary>
/// The color cube representing the image palette
/// </summary>
private Box [ ] colorCube ;
/// <summary>
/// Initializes a new instance of the <see cref="WuQuantizer{TColor}"/> class.
/// </summary>
/// <remarks>
/// The Wu quantizer is a two pass algorithm. The initial pass sets up the 3-D color histogram,
/// the second pass quantizes a color based on the position in the histogram.
/// </remarks>
public WuQuantizer ( )
: base ( false )
{
this . vwt = LongPool . Rent ( TableLength ) ;
this . vmr = LongPool . Rent ( TableLength ) ;
this . vmg = LongPool . Rent ( TableLength ) ;
this . vmb = LongPool . Rent ( TableLength ) ;
this . vma = LongPool . Rent ( TableLength ) ;
this . m2 = DoublePool . Rent ( TableLength ) ;
this . tag = BytePool . Rent ( TableLength ) ;
}
/// <inheritdoc/>
public QuantizedImage < TColor > Quantize ( ImageBase < TColor > image , int maxColors )
public override QuantizedImage < TColor > Quantize ( ImageBase < TColor > image , int maxColors )
{
Guard . NotNull ( image , nameof ( image ) ) ;
int colorCount = maxColors . Clamp ( 1 , 2 5 6 ) ;
this . colors = maxColors . Clamp ( 1 , 2 5 6 ) ;
try
{
this . vwt = WuArrayPool . LongPool . Rent ( TableLength ) ;
this . vmr = WuArrayPool . LongPool . Rent ( TableLength ) ;
this . vmg = WuArrayPool . LongPool . Rent ( TableLength ) ;
this . vmb = WuArrayPool . LongPool . Rent ( TableLength ) ;
this . vma = WuArrayPool . LongPool . Rent ( TableLength ) ;
this . m2 = WuArrayPool . FloatPool . Rent ( TableLength ) ;
this . tag = WuArrayPool . BytePool . Rent ( TableLength ) ;
return base . Quantize ( image , this . colors ) ;
}
finally
{
WuArrayPool . LongPool . Return ( this . vwt , true ) ;
WuArrayPool . LongPool . Return ( this . vmr , true ) ;
WuArrayPool . LongPool . Return ( this . vmg , true ) ;
WuArrayPool . LongPool . Return ( this . vmb , true ) ;
WuArrayPool . LongPool . Return ( this . vma , true ) ;
WuArrayPool . FloatPool . Return ( this . m2 , true ) ;
WuArrayPool . BytePool . Return ( this . tag , true ) ;
}
}
/// <inheritdoc/>
protected override TColor [ ] GetPalette ( )
{
if ( this . palette = = null )
{
this . palette = new TColor [ this . colors ] ;
for ( int k = 0 ; k < this . colors ; k + + )
{
this . Mark ( this . colorCube [ k ] , ( byte ) k ) ;
float weight = Volume ( this . colorCube [ k ] , this . vwt ) ;
if ( MathF . Abs ( weight ) > Constants . Epsilon )
{
float r = Volume ( this . colorCube [ k ] , this . vmr ) / weight ;
float g = Volume ( this . colorCube [ k ] , this . vmg ) / weight ;
float b = Volume ( this . colorCube [ k ] , this . vmb ) / weight ;
float a = Volume ( this . colorCube [ k ] , this . vma ) / weight ;
TColor color = default ( TColor ) ;
color . PackFromVector4 ( new Vector4 ( r , g , b , a ) / 2 5 5F ) ;
this . palette [ k ] = color ;
}
}
}
return this . palette ;
}
/// <inheritdoc/>
protected override void InitialQuantizePixel ( TColor pixel )
{
// Add the color to a 3-D color histogram.
// Colors are expected in r->g->b->a format
pixel . ToXyzwBytes ( this . rgbaBuffer , 0 ) ;
byte r = this . rgbaBuffer [ 0 ] ;
byte g = this . rgbaBuffer [ 1 ] ;
byte b = this . rgbaBuffer [ 2 ] ;
byte a = this . rgbaBuffer [ 3 ] ;
int inr = r > > ( 8 - IndexBits ) ;
int ing = g > > ( 8 - IndexBits ) ;
int inb = b > > ( 8 - IndexBits ) ;
int ina = a > > ( 8 - IndexAlphaBits ) ;
int ind = GetPaletteIndex ( inr + 1 , ing + 1 , inb + 1 , ina + 1 ) ;
this . vwt [ ind ] + + ;
this . vmr [ ind ] + = r ;
this . vmg [ ind ] + = g ;
this . vmb [ ind ] + = b ;
this . vma [ ind ] + = a ;
this . m2 [ ind ] + = ( r * r ) + ( g * g ) + ( b * b ) + ( a * a ) ;
}
/// <inheritdoc/>
protected override void FirstPass ( PixelAccessor < TColor > source , int width , int height )
{
// Build up the 3-D color histogram
// Loop through each row
for ( int y = 0 ; y < height ; y + + )
{
// And loop through each column
for ( int x = 0 ; x < width ; x + + )
{
// Now I have the pixel, call the FirstPassQuantize function...
this . InitialQuantizePixel ( source [ x , y ] ) ;
}
}
this . Clear ( ) ;
this . Get3DMoments ( ) ;
this . BuildCube ( ) ;
}
using ( PixelAccessor < TColor > imagePixels = image . Lock ( ) )
/// <inheritdoc/>
protected override void SecondPass ( PixelAccessor < TColor > source , byte [ ] output , int width , int height )
{
// Load up the values for the first pixel. We can use these to speed up the second
// pass of the algorithm by avoiding transforming rows of identical color.
TColor sourcePixel = source [ 0 , 0 ] ;
TColor previousPixel = sourcePixel ;
byte pixelValue = this . QuantizePixel ( sourcePixel ) ;
TColor [ ] colorPalette = this . GetPalette ( ) ;
TColor transformedPixel = colorPalette [ pixelValue ] ;
for ( int y = 0 ; y < height ; y + + )
{
this . Build3DHistogram ( imagePixels ) ;
this . Get3DMoments ( ) ;
// And loop through each column
for ( int x = 0 ; x < width ; x + + )
{
// Get the pixel.
sourcePixel = source [ x , y ] ;
// Check if this is the same as the last pixel. If so use that value
// rather than calculating it again. This is an inexpensive optimization.
if ( ! previousPixel . Equals ( sourcePixel ) )
{
// Quantize the pixel
pixelValue = this . QuantizePixel ( sourcePixel ) ;
// And setup the previous pointer
previousPixel = sourcePixel ;
if ( this . Dither )
{
transformedPixel = colorPalette [ pixelValue ] ;
}
}
Box [ ] cube ;
this . BuildCube ( out cube , ref colorCount ) ;
if ( this . Dither )
{
// Apply the dithering matrix. We have to reapply the value now as the original has changed.
this . DitherType . Dither ( source , sourcePixel , transformedPixel , x , y , width , height , false ) ;
}
return this . GenerateResult ( imagePixels , colorCount , cube ) ;
output [ ( y * source . Width ) + x ] = pixelValue ;
}
}
}
/// <summary>
/// Gets an index.
/// Gets the index index of the given color in the palette .
/// </summary>
/// <param name="r">The red value.</param>
/// <param name="g">The green value.</param>
/// <param name="b">The blue value.</param>
/// <param name="a">The alpha value.</param>
/// <returns>The index.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int GetPaletteIndex ( int r , int g , int b , int a )
{
return ( r < < ( ( IndexBits * 2 ) + IndexAlphaBits ) ) + ( r < < ( IndexBits + IndexAlphaBits + 1 ) )
@ -169,7 +305,7 @@ namespace ImageSharp.Quantizers
/// <param name="cube">The cube.</param>
/// <param name="moment">The moment.</param>
/// <returns>The result.</returns>
private static double Volume ( Box cube , long [ ] moment )
private static float Volume ( Box cube , long [ ] moment )
{
return moment [ GetPaletteIndex ( cube . R1 , cube . G1 , cube . B1 , cube . A1 ) ]
- moment [ GetPaletteIndex ( cube . R1 , cube . G1 , cube . B1 , cube . A0 ) ]
@ -310,55 +446,6 @@ namespace ImageSharp.Quantizers
}
}
/// <summary>
/// Clears the tables.
/// </summary>
private void Clear ( )
{
Array . Clear ( this . vwt , 0 , TableLength ) ;
Array . Clear ( this . vmr , 0 , TableLength ) ;
Array . Clear ( this . vmg , 0 , TableLength ) ;
Array . Clear ( this . vmb , 0 , TableLength ) ;
Array . Clear ( this . vma , 0 , TableLength ) ;
Array . Clear ( this . m2 , 0 , TableLength ) ;
Array . Clear ( this . tag , 0 , TableLength ) ;
}
/// <summary>
/// Builds a 3-D color histogram of <c>counts, r/g/b, c^2</c>.
/// </summary>
/// <param name="pixels">The pixel accessor.</param>
private void Build3DHistogram ( PixelAccessor < TColor > pixels )
{
for ( int y = 0 ; y < pixels . Height ; y + + )
{
for ( int x = 0 ; x < pixels . Width ; x + + )
{
// Colors are expected in r->g->b->a format
pixels [ x , y ] . ToXyzwBytes ( this . rgbaBuffer , 0 ) ;
byte r = this . rgbaBuffer [ 0 ] ;
byte g = this . rgbaBuffer [ 1 ] ;
byte b = this . rgbaBuffer [ 2 ] ;
byte a = this . rgbaBuffer [ 3 ] ;
int inr = r > > ( 8 - IndexBits ) ;
int ing = g > > ( 8 - IndexBits ) ;
int inb = b > > ( 8 - IndexBits ) ;
int ina = a > > ( 8 - IndexAlphaBits ) ;
int ind = GetPaletteIndex ( inr + 1 , ing + 1 , inb + 1 , ina + 1 ) ;
this . vwt [ ind ] + + ;
this . vmr [ ind ] + = r ;
this . vmg [ ind ] + = g ;
this . vmb [ ind ] + = b ;
this . vma [ ind ] + = a ;
this . m2 [ ind ] + = ( r * r ) + ( g * g ) + ( b * b ) + ( a * a ) ;
}
}
}
/// <summary>
/// Converts the histogram into moments so that we can rapidly calculate the sums of the above quantities over any desired box.
/// </summary>
@ -369,14 +456,14 @@ namespace ImageSharp.Quantizers
long [ ] volumeG = ArrayPool < long > . Shared . Rent ( IndexCount * IndexAlphaCount ) ;
long [ ] volumeB = ArrayPool < long > . Shared . Rent ( IndexCount * IndexAlphaCount ) ;
long [ ] volumeA = ArrayPool < long > . Shared . Rent ( IndexCount * IndexAlphaCount ) ;
double [ ] volume2 = ArrayPool < double > . Shared . Rent ( IndexCount * IndexAlphaCount ) ;
float [ ] volume2 = ArrayPool < float > . Shared . Rent ( IndexCount * IndexAlphaCount ) ;
long [ ] area = ArrayPool < long > . Shared . Rent ( IndexAlphaCount ) ;
long [ ] areaR = ArrayPool < long > . Shared . Rent ( IndexAlphaCount ) ;
long [ ] areaG = ArrayPool < long > . Shared . Rent ( IndexAlphaCount ) ;
long [ ] areaB = ArrayPool < long > . Shared . Rent ( IndexAlphaCount ) ;
long [ ] areaA = ArrayPool < long > . Shared . Rent ( IndexAlphaCount ) ;
double [ ] area2 = ArrayPool < double > . Shared . Rent ( IndexAlphaCount ) ;
float [ ] area2 = ArrayPool < float > . Shared . Rent ( IndexAlphaCount ) ;
try
{
@ -405,7 +492,7 @@ namespace ImageSharp.Quantizers
long lineG = 0 ;
long lineB = 0 ;
long lineA = 0 ;
double line2 = 0 ;
float line2 = 0 ;
for ( int a = 1 ; a < IndexAlphaCount ; a + + )
{
@ -454,14 +541,14 @@ namespace ImageSharp.Quantizers
ArrayPool < long > . Shared . Return ( volumeG ) ;
ArrayPool < long > . Shared . Return ( volumeB ) ;
ArrayPool < long > . Shared . Return ( volumeA ) ;
ArrayPool < double > . Shared . Return ( volume2 ) ;
ArrayPool < float > . Shared . Return ( volume2 ) ;
ArrayPool < long > . Shared . Return ( area ) ;
ArrayPool < long > . Shared . Return ( areaR ) ;
ArrayPool < long > . Shared . Return ( areaG ) ;
ArrayPool < long > . Shared . Return ( areaB ) ;
ArrayPool < long > . Shared . Return ( areaA ) ;
ArrayPool < double > . Shared . Return ( area2 ) ;
ArrayPool < float > . Shared . Return ( area2 ) ;
}
}
@ -469,15 +556,15 @@ namespace ImageSharp.Quantizers
/// Computes the weighted variance of a box cube.
/// </summary>
/// <param name="cube">The cube.</param>
/// <returns>The <see cref="double "/>.</returns>
private double Variance ( Box cube )
/// <returns>The <see cref="float "/>.</returns>
private float Variance ( Box cube )
{
double dr = Volume ( cube , this . vmr ) ;
double dg = Volume ( cube , this . vmg ) ;
double db = Volume ( cube , this . vmb ) ;
double da = Volume ( cube , this . vma ) ;
float dr = Volume ( cube , this . vmr ) ;
float dg = Volume ( cube , this . vmg ) ;
float db = Volume ( cube , this . vmb ) ;
float da = Volume ( cube , this . vma ) ;
double xx =
float xx =
this . m2 [ GetPaletteIndex ( cube . R1 , cube . G1 , cube . B1 , cube . A1 ) ]
- this . m2 [ GetPaletteIndex ( cube . R1 , cube . G1 , cube . B1 , cube . A0 ) ]
- this . m2 [ GetPaletteIndex ( cube . R1 , cube . G1 , cube . B0 , cube . A1 ) ]
@ -515,8 +602,8 @@ namespace ImageSharp.Quantizers
/// <param name="wholeB">The whole blue.</param>
/// <param name="wholeA">The whole alpha.</param>
/// <param name="wholeW">The whole weight.</param>
/// <returns>The <see cref="double "/>.</returns>
private double Maximize ( Box cube , int direction , int first , int last , out int cut , double wholeR , double wholeG , double wholeB , double wholeA , double wholeW )
/// <returns>The <see cref="float "/>.</returns>
private float Maximize ( Box cube , int direction , int first , int last , out int cut , float wholeR , float wholeG , float wholeB , float wholeA , float wholeW )
{
long baseR = Bottom ( cube , direction , this . vmr ) ;
long baseG = Bottom ( cube , direction , this . vmg ) ;
@ -524,20 +611,20 @@ namespace ImageSharp.Quantizers
long baseA = Bottom ( cube , direction , this . vma ) ;
long baseW = Bottom ( cube , direction , this . vwt ) ;
double max = 0.0 ;
float max = 0F ;
cut = - 1 ;
for ( int i = first ; i < last ; i + + )
{
double halfR = baseR + Top ( cube , direction , i , this . vmr ) ;
double halfG = baseG + Top ( cube , direction , i , this . vmg ) ;
double halfB = baseB + Top ( cube , direction , i , this . vmb ) ;
double halfA = baseA + Top ( cube , direction , i , this . vma ) ;
double halfW = baseW + Top ( cube , direction , i , this . vwt ) ;
float halfR = baseR + Top ( cube , direction , i , this . vmr ) ;
float halfG = baseG + Top ( cube , direction , i , this . vmg ) ;
float halfB = baseB + Top ( cube , direction , i , this . vmb ) ;
float halfA = baseA + Top ( cube , direction , i , this . vma ) ;
float halfW = baseW + Top ( cube , direction , i , this . vwt ) ;
double temp ;
float temp ;
if ( Math . Abs ( halfW ) < Constants . Epsilon )
if ( MathF . Abs ( halfW ) < Constants . Epsilon )
{
continue ;
}
@ -550,7 +637,7 @@ namespace ImageSharp.Quantizers
halfA = wholeA - halfA ;
halfW = wholeW - halfW ;
if ( Math . Abs ( halfW ) < Constants . Epsilon )
if ( MathF . Abs ( halfW ) < Constants . Epsilon )
{
continue ;
}
@ -575,21 +662,16 @@ namespace ImageSharp.Quantizers
/// <returns>Returns a value indicating whether the box has been split.</returns>
private bool Cut ( Box set1 , Box set2 )
{
double wholeR = Volume ( set1 , this . vmr ) ;
double wholeG = Volume ( set1 , this . vmg ) ;
double wholeB = Volume ( set1 , this . vmb ) ;
double wholeA = Volume ( set1 , this . vma ) ;
double wholeW = Volume ( set1 , this . vwt ) ;
int cutr ;
int cutg ;
int cutb ;
int cuta ;
double maxr = this . Maximize ( set1 , 0 , set1 . R0 + 1 , set1 . R1 , out cutr , wholeR , wholeG , wholeB , wholeA , wholeW ) ;
double maxg = this . Maximize ( set1 , 1 , set1 . G0 + 1 , set1 . G1 , out cutg , wholeR , wholeG , wholeB , wholeA , wholeW ) ;
double maxb = this . Maximize ( set1 , 2 , set1 . B0 + 1 , set1 . B1 , out cutb , wholeR , wholeG , wholeB , wholeA , wholeW ) ;
double maxa = this . Maximize ( set1 , 3 , set1 . A0 + 1 , set1 . A1 , out cuta , wholeR , wholeG , wholeB , wholeA , wholeW ) ;
float wholeR = Volume ( set1 , this . vmr ) ;
float wholeG = Volume ( set1 , this . vmg ) ;
float wholeB = Volume ( set1 , this . vmb ) ;
float wholeA = Volume ( set1 , this . vma ) ;
float wholeW = Volume ( set1 , this . vwt ) ;
float maxr = this . Maximize ( set1 , 0 , set1 . R0 + 1 , set1 . R1 , out int cutr , wholeR , wholeG , wholeB , wholeA , wholeW ) ;
float maxg = this . Maximize ( set1 , 1 , set1 . G0 + 1 , set1 . G1 , out int cutg , wholeR , wholeG , wholeB , wholeA , wholeW ) ;
float maxb = this . Maximize ( set1 , 2 , set1 . B0 + 1 , set1 . B1 , out int cutb , wholeR , wholeG , wholeB , wholeA , wholeW ) ;
float maxa = this . Maximize ( set1 , 3 , set1 . A0 + 1 , set1 . A1 , out int cuta , wholeR , wholeG , wholeB , wholeA , wholeW ) ;
int dir ;
@ -686,40 +768,38 @@ namespace ImageSharp.Quantizers
/// <summary>
/// Builds the cube.
/// </summary>
/// <param name="cube">The cube.</param>
/// <param name="colorCount">The color count.</param>
private void BuildCube ( out Box [ ] cube , ref int colorCount )
private void BuildCube ( )
{
cube = new Box [ colorCount ] ;
double [ ] vv = new double [ colorCount ] ;
this . colorCube = new Box [ this . colors ] ;
float [ ] vv = new float [ this . colors ] ;
for ( int i = 0 ; i < colorCount ; i + + )
for ( int i = 0 ; i < this . colors ; i + + )
{
cube [ i ] = new Box ( ) ;
this . colorC ube [ i ] = new Box ( ) ;
}
cube [ 0 ] . R0 = cube [ 0 ] . G0 = cube [ 0 ] . B0 = cube [ 0 ] . A0 = 0 ;
cube [ 0 ] . R1 = c ube[ 0 ] . G1 = cube [ 0 ] . B1 = IndexCount - 1 ;
cube [ 0 ] . A1 = IndexAlphaCount - 1 ;
this . colorC ube [ 0 ] . R0 = this . colorC ube [ 0 ] . G0 = this . colorC ube [ 0 ] . B0 = this . colorC ube [ 0 ] . A0 = 0 ;
this . colorC ube [ 0 ] . R1 = this . colorC ube[ 0 ] . G1 = this . colorC ube [ 0 ] . B1 = IndexCount - 1 ;
this . colorC ube [ 0 ] . A1 = IndexAlphaCount - 1 ;
int next = 0 ;
for ( int i = 1 ; i < colorCount ; i + + )
for ( int i = 1 ; i < this . colors ; i + + )
{
if ( this . Cut ( cube [ next ] , cube [ i ] ) )
if ( this . Cut ( this . colorC ube [ next ] , this . colorC ube [ i ] ) )
{
vv [ next ] = cube [ next ] . Volume > 1 ? this . Variance ( cube [ next ] ) : 0.0 ;
vv [ i ] = cube [ i ] . Volume > 1 ? this . Variance ( cube [ i ] ) : 0.0 ;
vv [ next ] = this . colorC ube [ next ] . Volume > 1 ? this . Variance ( this . colorC ube [ next ] ) : 0F ;
vv [ i ] = this . colorC ube [ i ] . Volume > 1 ? this . Variance ( this . colorC ube [ i ] ) : 0F ;
}
else
{
vv [ next ] = 0.0 ;
vv [ next ] = 0F ;
i - - ;
}
next = 0 ;
double temp = vv [ 0 ] ;
float temp = vv [ 0 ] ;
for ( int k = 1 ; k < = i ; k + + )
{
if ( vv [ k ] > temp )
@ -731,79 +811,38 @@ namespace ImageSharp.Quantizers
if ( temp < = 0.0 )
{
colorCount = i + 1 ;
this . colors = i + 1 ;
break ;
}
}
}
/// <summary>
/// Generates the quantized result.
/// Process the pixel in the second pass of the algorithm
/// </summary>
/// <param name="imagePixels">The image pixels.</param>
/// <param name="colorCount">The color count.</param>
/// <param name="cube">The cube.</param>
/// <returns>The result.</returns>
private QuantizedImage < TColor > GenerateResult ( PixelAccessor < TColor > imagePixels , int colorCount , Box [ ] cube )
/// <param name="pixel">The pixel to quantize</param>
/// <returns>
/// The quantized value
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private byte QuantizePixel ( TColor pixel )
{
TColor [ ] pallette = new TColor [ colorCount ] ;
byte [ ] pixels = new byte [ imagePixels . Width * imagePixels . Height ] ;
int width = imagePixels . Width ;
int height = imagePixels . Height ;
for ( int k = 0 ; k < colorCount ; k + + )
if ( this . Dither )
{
this . Mark ( cube [ k ] , ( byte ) k ) ;
double weight = Volume ( cube [ k ] , this . vwt ) ;
if ( Math . Abs ( weight ) > Constants . Epsilon )
{
float r = ( float ) ( Volume ( cube [ k ] , this . vmr ) / weight ) ;
float g = ( float ) ( Volume ( cube [ k ] , this . vmg ) / weight ) ;
float b = ( float ) ( Volume ( cube [ k ] , this . vmb ) / weight ) ;
float a = ( float ) ( Volume ( cube [ k ] , this . vma ) / weight ) ;
TColor color = default ( TColor ) ;
color . PackFromVector4 ( new Vector4 ( r , g , b , a ) / 2 5 5F ) ;
pallette [ k ] = color ;
}
// The colors have changed so we need to use Euclidean distance caclulation to find the closest value.
// This palette can never be null here.
return this . GetClosestColor ( pixel , this . palette , this . colorMap ) ;
}
Parallel . For (
0 ,
height ,
imagePixels . ParallelOptions ,
y = >
{
byte [ ] rgba = ArrayPool < byte > . Shared . Rent ( 4 ) ;
for ( int x = 0 ; x < width ; x + + )
{
// Expected order r->g->b->a
imagePixels [ x , y ] . ToXyzwBytes ( rgba , 0 ) ;
int r = rgba [ 0 ] > > ( 8 - IndexBits ) ;
int g = rgba [ 1 ] > > ( 8 - IndexBits ) ;
int b = rgba [ 2 ] > > ( 8 - IndexBits ) ;
int a = rgba [ 3 ] > > ( 8 - IndexAlphaBits ) ;
int ind = GetPaletteIndex ( r + 1 , g + 1 , b + 1 , a + 1 ) ;
pixels [ ( y * width ) + x ] = this . tag [ ind ] ;
}
ArrayPool < byte > . Shared . Return ( rgba ) ;
} ) ;
// Expected order r->g->b->a
pixel . ToXyzwBytes ( this . rgbaBuffer , 0 ) ;
// Cleanup
LongPool . Return ( this . vwt ) ;
LongPool . Return ( this . vmr ) ;
LongPool . Return ( this . vmg ) ;
LongPool . Return ( this . vmb ) ;
LongPool . Return ( this . vma ) ;
DoublePool . Return ( this . m2 ) ;
BytePool . Return ( this . tag ) ;
int r = this . rgbaBuffer [ 0 ] > > ( 8 - IndexBits ) ;
int g = this . rgbaBuffer [ 1 ] > > ( 8 - IndexBits ) ;
int b = this . rgbaBuffer [ 2 ] > > ( 8 - IndexBits ) ;
int a = this . rgbaBuffer [ 3 ] > > ( 8 - IndexAlphaBits ) ;
return new QuantizedImage < TColor > ( width , height , pallette , pixels ) ;
return this . tag [ GetPaletteIndex ( r + 1 , g + 1 , b + 1 , a + 1 ) ] ;
}
}
}