@ -3,6 +3,7 @@
using System ;
using System.Collections.Generic ;
using System.Numerics ;
using System.Runtime.CompilerServices ;
using System.Runtime.InteropServices ;
using SixLabors.ImageSharp.Advanced ;
@ -18,11 +19,6 @@ namespace SixLabors.ImageSharp.Processing.Quantization.FrameQuantizers
internal sealed class OctreeFrameQuantizer < TPixel > : FrameQuantizerBase < TPixel >
where TPixel : struct , IPixel < TPixel >
{
/// <summary>
/// A lookup table for colors
/// </summary>
private readonly Dictionary < TPixel , byte > colorMap = new Dictionary < TPixel , byte > ( ) ;
/// <summary>
/// Maximum allowed color depth
/// </summary>
@ -33,11 +29,6 @@ namespace SixLabors.ImageSharp.Processing.Quantization.FrameQuantizers
/// </summary>
private readonly Octree octree ;
/// <summary>
/// The reduced image palette
/// </summary>
private TPixel [ ] palette ;
/// <summary>
/// The transparent index
/// </summary>
@ -55,7 +46,7 @@ namespace SixLabors.ImageSharp.Processing.Quantization.FrameQuantizers
: base ( quantizer , false )
{
this . colors = ( byte ) quantizer . MaxColors ;
this . octree = new Octree ( thi s. GetBitsNeededForColorDepth ( this . colors ) ) ;
this . octree = new Octree ( ImageMath s. GetBitsNeededForColorDepth ( this . colors ) . Clamp ( 1 , 8 ) ) ;
}
/// <inheritdoc/>
@ -81,16 +72,21 @@ namespace SixLabors.ImageSharp.Processing.Quantization.FrameQuantizers
}
/// <inheritdoc/>
protected override void SecondPass ( ImageFrame < TPixel > source , byte [ ] output , int width , int height )
protected override void SecondPass (
ImageFrame < TPixel > source ,
Span < byte > output ,
ReadOnlySpan < TPixel > palette ,
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.
TPixel sourcePixel = source [ 0 , 0 ] ;
TPixel previousPixel = sourcePixel ;
Rgba32 rgba = default ;
byte pixelValue = this . QuantizePixel ( sourcePixel , ref rgba ) ;
TPixel [ ] colorPalette = this . GetPalette ( ) ;
TPixel transformedPixel = colorP alette[ pixelValue ] ;
this . transparentIndex = this . GetTransparentIndex ( ) ;
byte pixelValue = this . QuantizePixel ( ref sourcePixel , ref rgba ) ;
TPixel transformedPixel = p alette[ pixelValue ] ;
for ( int y = 0 ; y < height ; y + + )
{
@ -107,14 +103,14 @@ namespace SixLabors.ImageSharp.Processing.Quantization.FrameQuantizers
if ( ! previousPixel . Equals ( sourcePixel ) )
{
// Quantize the pixel
pixelValue = this . QuantizePixel ( sourcePixel , ref rgba ) ;
pixelValue = this . QuantizePixel ( ref sourcePixel , ref rgba ) ;
// And setup the previous pointer
previousPixel = sourcePixel ;
if ( this . Dither )
{
transformedPixel = colorP alette[ pixelValue ] ;
transformedPixel = p alette[ pixelValue ] ;
}
}
@ -130,62 +126,26 @@ namespace SixLabors.ImageSharp.Processing.Quantization.FrameQuantizers
}
/// <inheritdoc/>
protected override TPixel [ ] GetPalette ( )
{
if ( this . palette = = null )
{
this . palette = this . octree . Palletize ( Math . Max ( this . colors , ( byte ) 1 ) ) ;
this . transparentIndex = this . GetTransparentIndex ( ) ;
}
return this . palette ;
}
/// <summary>
/// Returns the index of the first instance of the transparent color in the palette.
/// </summary>
/// <returns>
/// The <see cref="int"/>.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private byte GetTransparentIndex ( )
{
// Transparent pixels are much more likely to be found at the end of a palette
int index = this . colors ;
Rgba32 trans = default ;
for ( int i = this . palette . Length - 1 ; i > = 0 ; i - - )
{
this . palette [ i ] . ToRgba32 ( ref trans ) ;
if ( trans . Equals ( default ( Rgba32 ) ) )
{
index = i ;
}
}
return ( byte ) index ;
}
protected override TPixel [ ] GetPalette ( ) = > this . octree . Palletize ( this . colors ) ;
/// <summary>
/// Process the pixel in the second pass of the algorithm
/// Process the pixel in the second pass of the algorithm.
/// </summary>
/// <param name="pixel">The pixel to quantize</param>
/// <param name="rgba">The color to compare against</param>
/// <returns>
/// The quantized value
/// </returns>
/// <param name="pixel">The pixel to quantize.</param>
/// <param name="rgba">The color to compare against.</param>
/// <returns>The <see cref="byte"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private byte QuantizePixel ( TPixel pixel , ref Rgba32 rgba )
private byte QuantizePixel ( ref TPixel pixel , ref Rgba32 rgba )
{
if ( this . Dither )
{
// The colors have changed so we need to use Euclidean distance calculation to find the closest value.
// This palette can never be null her e.
return this . GetClosestPixel ( pixel , this . palette , this . colorMap ) ;
// The colors have changed so we need to use Euclidean distance calculation to
// find the closest value.
return this . GetClosestPixel ( ref pixel ) ;
}
pixel . ToRgba32 ( ref rgba ) ;
if ( rgba . Equals ( default ( Rgba32 ) ) )
if ( rgba . Equals ( default ) )
{
return this . transparentIndex ;
}
@ -193,20 +153,6 @@ namespace SixLabors.ImageSharp.Processing.Quantization.FrameQuantizers
return ( byte ) this . octree . GetPaletteIndex ( ref pixel , ref rgba ) ;
}
/// <summary>
/// Returns how many bits are required to store the specified number of colors.
/// Performs a Log2() on the value.
/// </summary>
/// <param name="colorCount">The number of colors.</param>
/// <returns>
/// The <see cref="int"/>
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int GetBitsNeededForColorDepth ( int colorCount )
{
return ( int ) Math . Ceiling ( Math . Log ( colorCount , 2 ) ) ;
}
/// <summary>
/// Class which does the actual quantization
/// </summary>
@ -223,11 +169,6 @@ namespace SixLabors.ImageSharp.Processing.Quantization.FrameQuantizers
/// </summary>
private readonly OctreeNode root ;
/// <summary>
/// Array of reducible nodes
/// </summary>
private readonly OctreeNode [ ] reducibleNodes ;
/// <summary>
/// Maximum number of significant bits in the image
/// </summary>
@ -253,21 +194,32 @@ namespace SixLabors.ImageSharp.Processing.Quantization.FrameQuantizers
{
this . maxColorBits = maxColorBits ;
this . Leaves = 0 ;
this . r educibleNodes = new OctreeNode [ 9 ] ;
this . R educibleNodes = new OctreeNode [ 9 ] ;
this . root = new OctreeNode ( 0 , this . maxColorBits , this ) ;
this . previousColor = default ( TPixel ) ;
this . previousColor = default ;
this . previousNode = null ;
}
/// <summary>
/// Gets or sets the number of leaves in the tree
/// </summary>
private int Leaves { get ; set ; }
public int Leaves
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get ;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set ;
}
/// <summary>
/// Gets the array of reducible nodes
/// </summary>
private OctreeNode [ ] ReducibleNodes = > this . reducibleNodes ;
private OctreeNode [ ] ReducibleNodes
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get ;
}
/// <summary>
/// Add a given color value to the Octree
@ -306,6 +258,7 @@ namespace SixLabors.ImageSharp.Processing.Quantization.FrameQuantizers
/// <returns>
/// An <see cref="List{TPixel}"/> with the palletized colors
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public TPixel [ ] Palletize ( int colorCount )
{
while ( this . Leaves > colorCount )
@ -331,6 +284,7 @@ namespace SixLabors.ImageSharp.Processing.Quantization.FrameQuantizers
/// <returns>
/// The <see cref="int"/>.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int GetPaletteIndex ( ref TPixel pixel , ref Rgba32 rgba )
{
return this . root . GetPaletteIndex ( ref pixel , 0 , ref rgba ) ;
@ -342,6 +296,7 @@ namespace SixLabors.ImageSharp.Processing.Quantization.FrameQuantizers
/// <param name="node">
/// The node last quantized
/// </param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected void TrackPrevious ( OctreeNode node )
{
this . previousNode = node ;
@ -354,14 +309,14 @@ namespace SixLabors.ImageSharp.Processing.Quantization.FrameQuantizers
{
// Find the deepest level containing at least one reducible node
int index = this . maxColorBits - 1 ;
while ( ( index > 0 ) & & ( this . r educibleNodes[ index ] = = null ) )
while ( ( index > 0 ) & & ( this . R educibleNodes[ index ] = = null ) )
{
index - - ;
}
// Reduce the node most recently added to the list at level 'index'
OctreeNode node = this . r educibleNodes[ index ] ;
this . r educibleNodes[ index ] = node . NextReducible ;
OctreeNode node = this . R educibleNodes[ index ] ;
this . R educibleNodes[ index ] = node . NextReducible ;
// Decrement the leaf count after reducing the node
this . Leaves - = node . Reduce ( ) ;
@ -450,7 +405,11 @@ namespace SixLabors.ImageSharp.Processing.Quantization.FrameQuantizers
/// <summary>
/// Gets the next reducible node
/// </summary>
public OctreeNode NextReducible { get ; }
public OctreeNode NextReducible
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get ;
}
/// <summary>
/// Add a color into the tree
@ -476,12 +435,11 @@ namespace SixLabors.ImageSharp.Processing.Quantization.FrameQuantizers
int shift = 7 - level ;
pixel . ToRgba32 ( ref rgba ) ;
int index = ( ( rgba . B & Mask [ level ] ) > > ( shift - 2 ) ) |
( ( rgba . G & Mask [ level ] ) > > ( shift - 1 ) ) |
( ( rgba . R & Mask [ level ] ) > > shift ) ;
int index = ( ( rgba . B & Mask [ level ] ) > > ( shift - 2 ) )
| ( ( rgba . G & Mask [ level ] ) > > ( shift - 1 ) )
| ( ( rgba . R & Mask [ level ] ) > > shift ) ;
OctreeNode child = this . children [ index ] ;
if ( child = = null )
{
// Create a new child node and store it in the array
@ -506,12 +464,13 @@ namespace SixLabors.ImageSharp.Processing.Quantization.FrameQuantizers
// Loop through all children and add their information to this node
for ( int index = 0 ; index < 8 ; index + + )
{
if ( this . children [ index ] ! = null )
OctreeNode child = this . children [ index ] ;
if ( child ! = null )
{
this . red + = this . children [ index ] . red ;
this . green + = this . children [ index ] . green ;
this . blue + = this . children [ index ] . blue ;
this . pixelCount + = this . children [ index ] . pixelCount ;
this . red + = child . red ;
this . green + = child . green ;
this . blue + = child . blue ;
this . pixelCount + = child . pixelCount ;
+ + childNodes ;
this . children [ index ] = null ;
}
@ -529,18 +488,15 @@ namespace SixLabors.ImageSharp.Processing.Quantization.FrameQuantizers
/// </summary>
/// <param name="palette">The palette</param>
/// <param name="index">The current palette index</param>
[MethodImpl(MethodImplOptions.NoInlining)]
public void ConstructPalette ( TPixel [ ] palette , ref int index )
{
if ( this . leaf )
{
// This seems faster than using Vector4
byte r = ( this . red / this . pixelCount ) . ToByte ( ) ;
byte g = ( this . green / this . pixelCount ) . ToByte ( ) ;
byte b = ( this . blue / this . pixelCount ) . ToByte ( ) ;
// And set the color of the palette entry
// Set the color of the palette entry
var vector = Vector3 . Clamp ( new Vector3 ( this . red , this . green , this . blue ) / this . pixelCount , Vector3 . Zero , new Vector3 ( 2 5 5 ) ) ;
TPixel pixel = default ;
pixel . PackFromRgba32 ( new Rgba32 ( r , g , b , 2 5 5 ) ) ;
pixel . PackFromRgba32 ( new Rgba32 ( ( byte ) vector . X , ( byte ) vector . Y , ( byte ) vector . Z , byte . MaxValue ) ) ;
palette [ index ] = pixel ;
// Consume the next palette index
@ -551,10 +507,7 @@ namespace SixLabors.ImageSharp.Processing.Quantization.FrameQuantizers
// Loop through children looking for leaves
for ( int i = 0 ; i < 8 ; i + + )
{
if ( this . children [ i ] ! = null )
{
this . children [ i ] . ConstructPalette ( palette , ref index ) ;
}
this . children [ i ] ? . ConstructPalette ( palette , ref index ) ;
}
}
}
@ -568,6 +521,7 @@ namespace SixLabors.ImageSharp.Processing.Quantization.FrameQuantizers
/// <returns>
/// The <see cref="int"/> representing the index of the pixel in the palette.
/// </returns>
[MethodImpl(MethodImplOptions.NoInlining)]
public int GetPaletteIndex ( ref TPixel pixel , int level , ref Rgba32 rgba )
{
int index = this . paletteIndex ;
@ -577,17 +531,18 @@ namespace SixLabors.ImageSharp.Processing.Quantization.FrameQuantizers
int shift = 7 - level ;
pixel . ToRgba32 ( ref rgba ) ;
int pixelIndex = ( ( rgba . B & Mask [ level ] ) > > ( shift - 2 ) ) |
( ( rgba . G & Mask [ level ] ) > > ( shift - 1 ) ) |
( ( rgba . R & Mask [ level ] ) > > shift ) ;
int pixelIndex = ( ( rgba . B & Mask [ level ] ) > > ( shift - 2 ) )
| ( ( rgba . G & Mask [ level ] ) > > ( shift - 1 ) )
| ( ( rgba . R & Mask [ level ] ) > > shift ) ;
if ( this . children [ pixelIndex ] ! = null )
OctreeNode child = this . children [ pixelIndex ] ;
if ( child ! = null )
{
index = this . children [ pixelIndex ] . GetPaletteIndex ( ref pixel , level + 1 , ref rgba ) ;
index = child . GetPaletteIndex ( ref pixel , level + 1 , ref rgba ) ;
}
else
{
throw new Exception ( $"Cannot retrive a pixel at the given index {pixelIndex}." ) ;
throw new Exception ( $"Cannot retrie ve a pixel at the given index {pixelIndex}." ) ;
}
}
@ -599,6 +554,7 @@ namespace SixLabors.ImageSharp.Processing.Quantization.FrameQuantizers
/// </summary>
/// <param name="pixel">The pixel to add.</param>
/// <param name="rgba">The color to map to.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Increment ( ref TPixel pixel , ref Rgba32 rgba )
{
pixel . ToRgba32 ( ref rgba ) ;