@ -37,7 +37,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
where TPixel : struct , IPixel < TPixel >
{
// TODO: The WuFrameQuantizer<TPixel> code is rising several questions:
// - Do we really need to ALWAYS allocate the whole table of size TableLength? (~ 2471625 * sizeof(long) * 5 bytes )
// - Do we really need to ALWAYS allocate the whole table of size TableLength? (~ 2471625 * sizeof(long) * 5 bytes ) JS. I'm afraid so.
// - Isn't an AOS ("array of structures") layout more efficient & more readable than SOA ("structure of arrays") for this particular use case?
// (T, R, G, B, A, M2) could be grouped together!
// - It's a frequently used class, we need tests! (So we can optimize safely.) There are tests in the original!!! We should just adopt them!
@ -46,12 +46,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <summary>
/// The index bits.
/// </summary>
private const int IndexBits = 6 ;
private const int IndexBits = 5 ;
/// <summary>
/// The index alpha bits.
/// The index alpha bits. Keep separate for now to allow easy adjustment.
/// </summary>
private const int IndexAlphaBits = 3 ;
private const int IndexAlphaBits = 5 ;
/// <summary>
/// The index count.
@ -64,7 +64,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
private const int IndexAlphaCount = ( 1 < < IndexAlphaBits ) + 1 ;
/// <summary>
/// The table length.
/// The table length. Now 1185921.
/// </summary>
private const int TableLength = IndexCount * IndexCount * IndexCount * IndexAlphaCount ;
@ -179,18 +179,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
if ( this . palette is null )
{
this . palette = new TPixel [ this . colors ] ;
Span < long > vwtSpan = this . vwt . GetSpan ( ) ;
Span < long > vmrSpan = this . vmr . GetSpan ( ) ;
Span < long > vmgSpan = this . vmg . GetSpan ( ) ;
Span < long > vmbSpan = this . vmb . GetSpan ( ) ;
Span < long > vmaSpan = this . vma . GetSpan ( ) ;
for ( int k = 0 ; k < this . colors ; k + + )
{
this . Mark ( ref this . colorCube [ k ] , ( byte ) k ) ;
float weight = Volume ( ref this . colorCube [ k ] , this . vwt . GetSpan ( ) ) ;
float weight = Volume ( ref this . colorCube [ k ] , vwtSpan ) ;
if ( MathF . Abs ( weight ) > Constants . Epsilon )
{
float r = Volume ( ref this . colorCube [ k ] , this . vmr . Get Span( ) ) ;
float g = Volume ( ref this . colorCube [ k ] , this . vmg . Get Span( ) ) ;
float b = Volume ( ref this . colorCube [ k ] , this . vmb . Get Span( ) ) ;
float a = Volume ( ref this . colorCube [ k ] , this . vma . Get Span( ) ) ;
float r = Volume ( ref this . colorCube [ k ] , vmrSpan ) ;
float g = Volume ( ref this . colorCube [ k ] , vmgSpan ) ;
float b = Volume ( ref this . colorCube [ k ] , vmbSpan ) ;
float a = Volume ( ref this . colorCube [ k ] , vmaSpan ) ;
ref TPixel color = ref this . palette [ k ] ;
color . PackFromScaledVector4 ( new Vector4 ( r , g , b , a ) / weight / 2 5 5F ) ;
@ -201,57 +207,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
return this . palette ;
}
/// <summary>
/// Quantizes the pixel
/// </summary>
/// <param name="rgba">The rgba used to quantize the pixel input</param>
private void QuantizePixel ( ref Rgba32 rgba )
{
// Add the color to a 3-D color histogram.
int r = rgba . R > > ( 8 - IndexBits ) ;
int g = rgba . G > > ( 8 - IndexBits ) ;
int b = rgba . B > > ( 8 - IndexBits ) ;
int a = rgba . A > > ( 8 - IndexAlphaBits ) ;
int index = GetPaletteIndex ( r + 1 , g + 1 , b + 1 , a + 1 ) ;
Span < long > vwtSpan = this . vwt . GetSpan ( ) ;
Span < long > vmrSpan = this . vmr . GetSpan ( ) ;
Span < long > vmgSpan = this . vmg . GetSpan ( ) ;
Span < long > vmbSpan = this . vmb . GetSpan ( ) ;
Span < long > vmaSpan = this . vma . GetSpan ( ) ;
Span < float > m2Span = this . m2 . GetSpan ( ) ;
vwtSpan [ index ] + + ;
vmrSpan [ index ] + = rgba . R ;
vmgSpan [ index ] + = rgba . G ;
vmbSpan [ index ] + = rgba . B ;
vmaSpan [ index ] + = rgba . A ;
var vector = new Vector4 ( rgba . R , rgba . G , rgba . B , rgba . A ) ;
m2Span [ index ] + = Vector4 . Dot ( vector , vector ) ;
}
/// <inheritdoc/>
protected override void FirstPass ( ImageFrame < TPixel > source , int width , int height )
{
// Build up the 3-D color histogram
// Loop through each row
for ( int y = 0 ; y < height ; y + + )
{
Span < TPixel > row = source . GetPixelRowSpan ( y ) ;
ref TPixel scanBaseRef = ref MemoryMarshal . GetReference ( row ) ;
// And loop through each column
Rgba32 rgba = default ;
for ( int x = 0 ; x < width ; x + + )
{
ref TPixel pixel = ref Unsafe . Add ( ref scanBaseRef , x ) ;
pixel . ToRgba32 ( ref rgba ) ;
this . QuantizePixel ( ref rgba ) ;
}
}
this . Build3DHistogram ( source , width , height ) ;
this . Get3DMoments ( source . MemoryAllocator ) ;
this . BuildCube ( ) ;
}
@ -466,6 +425,54 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
}
}
/// <summary>
/// Builds a 3-D color histogram of <c>counts, r/g/b, c^2</c>.
/// </summary>
/// <param name="source">The source data.</param>
/// <param name="width">The width in pixels of the image.</param>
/// <param name="height">The height in pixels of the image.</param>
private void Build3DHistogram ( ImageFrame < TPixel > source , int width , int height )
{
Span < long > vwtSpan = this . vwt . GetSpan ( ) ;
Span < long > vmrSpan = this . vmr . GetSpan ( ) ;
Span < long > vmgSpan = this . vmg . GetSpan ( ) ;
Span < long > vmbSpan = this . vmb . GetSpan ( ) ;
Span < long > vmaSpan = this . vma . GetSpan ( ) ;
Span < float > m2Span = this . m2 . GetSpan ( ) ;
// Build up the 3-D color histogram
// Loop through each row
for ( int y = 0 ; y < height ; y + + )
{
Span < TPixel > row = source . GetPixelRowSpan ( y ) ;
ref TPixel scanBaseRef = ref MemoryMarshal . GetReference ( row ) ;
// And loop through each column
Rgba32 rgba = default ;
for ( int x = 0 ; x < width ; x + + )
{
ref TPixel pixel = ref Unsafe . Add ( ref scanBaseRef , x ) ;
pixel . ToRgba32 ( ref rgba ) ;
int r = rgba . R > > ( 8 - IndexBits ) ;
int g = rgba . G > > ( 8 - IndexBits ) ;
int b = rgba . B > > ( 8 - IndexBits ) ;
int a = rgba . A > > ( 8 - IndexAlphaBits ) ;
int index = GetPaletteIndex ( r + 1 , g + 1 , b + 1 , a + 1 ) ;
vwtSpan [ index ] + + ;
vmrSpan [ index ] + = rgba . R ;
vmgSpan [ index ] + = rgba . G ;
vmbSpan [ index ] + = rgba . B ;
vmaSpan [ index ] + = rgba . A ;
var vector = new Vector4 ( rgba . R , rgba . G , rgba . B , rgba . A ) ;
m2Span [ index ] + = Vector4 . Dot ( vector , vector ) ;
}
}
}
/// <summary>
/// Converts the histogram into moments so that we can rapidly calculate the sums of the above quantities over any desired box.
/// </summary>
@ -631,22 +638,28 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <returns>The <see cref="float"/>.</returns>
private float Maximize ( ref Box cube , int direction , int first , int last , out int cut , float wholeR , float wholeG , float wholeB , float wholeA , float wholeW )
{
long baseR = Bottom ( ref cube , direction , this . vmr . GetSpan ( ) ) ;
long baseG = Bottom ( ref cube , direction , this . vmg . GetSpan ( ) ) ;
long baseB = Bottom ( ref cube , direction , this . vmb . GetSpan ( ) ) ;
long baseA = Bottom ( ref cube , direction , this . vma . GetSpan ( ) ) ;
long baseW = Bottom ( ref cube , direction , this . vwt . GetSpan ( ) ) ;
Span < long > vwtSpan = this . vwt . GetSpan ( ) ;
Span < long > vmrSpan = this . vmr . GetSpan ( ) ;
Span < long > vmgSpan = this . vmg . GetSpan ( ) ;
Span < long > vmbSpan = this . vmb . GetSpan ( ) ;
Span < long > vmaSpan = this . vma . GetSpan ( ) ;
long baseR = Bottom ( ref cube , direction , vmrSpan ) ;
long baseG = Bottom ( ref cube , direction , vmgSpan ) ;
long baseB = Bottom ( ref cube , direction , vmbSpan ) ;
long baseA = Bottom ( ref cube , direction , vmaSpan ) ;
long baseW = Bottom ( ref cube , direction , vwtSpan ) ;
float max = 0F ;
cut = - 1 ;
for ( int i = first ; i < last ; i + + )
{
float halfR = baseR + Top ( ref cube , direction , i , this . vmr . Get Span( ) ) ;
float halfG = baseG + Top ( ref cube , direction , i , this . vmg . Get Span( ) ) ;
float halfB = baseB + Top ( ref cube , direction , i , this . vmb . Get Span( ) ) ;
float halfA = baseA + Top ( ref cube , direction , i , this . vma . Get Span( ) ) ;
float halfW = baseW + Top ( ref cube , direction , i , this . vwt . Get Span( ) ) ;
float halfR = baseR + Top ( ref cube , direction , i , vmrSpan ) ;
float halfG = baseG + Top ( ref cube , direction , i , vmgSpan ) ;
float halfB = baseB + Top ( ref cube , direction , i , vmbSpan ) ;
float halfA = baseA + Top ( ref cube , direction , i , vmaSpan ) ;
float halfW = baseW + Top ( ref cube , direction , i , vwtSpan ) ;
if ( MathF . Abs ( halfW ) < Constants . Epsilon )
{