|
|
@ -23,11 +23,6 @@ namespace SixLabors.ImageSharp.Quantizers |
|
|
/// </summary>
|
|
|
/// </summary>
|
|
|
private readonly Dictionary<TPixel, byte> colorMap = new Dictionary<TPixel, byte>(); |
|
|
private readonly Dictionary<TPixel, byte> colorMap = new Dictionary<TPixel, byte>(); |
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// The pixel buffer, used to reduce allocations.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private readonly byte[] pixelBuffer = new byte[4]; |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
/// <summary>
|
|
|
/// Stores the tree
|
|
|
/// Stores the tree
|
|
|
/// </summary>
|
|
|
/// </summary>
|
|
|
@ -43,6 +38,11 @@ namespace SixLabors.ImageSharp.Quantizers |
|
|
/// </summary>
|
|
|
/// </summary>
|
|
|
private TPixel[] palette; |
|
|
private TPixel[] palette; |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// The transparent index
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private byte transparentIndex; |
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
/// <summary>
|
|
|
/// Initializes a new instance of the <see cref="OctreeQuantizer{TPixel}"/> class.
|
|
|
/// Initializes a new instance of the <see cref="OctreeQuantizer{TPixel}"/> class.
|
|
|
/// </summary>
|
|
|
/// </summary>
|
|
|
@ -73,7 +73,8 @@ namespace SixLabors.ImageSharp.Quantizers |
|
|
// pass of the algorithm by avoiding transforming rows of identical color.
|
|
|
// pass of the algorithm by avoiding transforming rows of identical color.
|
|
|
TPixel sourcePixel = source[0, 0]; |
|
|
TPixel sourcePixel = source[0, 0]; |
|
|
TPixel previousPixel = sourcePixel; |
|
|
TPixel previousPixel = sourcePixel; |
|
|
byte pixelValue = this.QuantizePixel(sourcePixel); |
|
|
var rgba = default(Rgba32); |
|
|
|
|
|
byte pixelValue = this.QuantizePixel(sourcePixel, ref rgba); |
|
|
TPixel[] colorPalette = this.GetPalette(); |
|
|
TPixel[] colorPalette = this.GetPalette(); |
|
|
TPixel transformedPixel = colorPalette[pixelValue]; |
|
|
TPixel transformedPixel = colorPalette[pixelValue]; |
|
|
|
|
|
|
|
|
@ -92,7 +93,7 @@ namespace SixLabors.ImageSharp.Quantizers |
|
|
if (!previousPixel.Equals(sourcePixel)) |
|
|
if (!previousPixel.Equals(sourcePixel)) |
|
|
{ |
|
|
{ |
|
|
// Quantize the pixel
|
|
|
// Quantize the pixel
|
|
|
pixelValue = this.QuantizePixel(sourcePixel); |
|
|
pixelValue = this.QuantizePixel(sourcePixel, ref rgba); |
|
|
|
|
|
|
|
|
// And setup the previous pointer
|
|
|
// And setup the previous pointer
|
|
|
previousPixel = sourcePixel; |
|
|
previousPixel = sourcePixel; |
|
|
@ -118,24 +119,57 @@ namespace SixLabors.ImageSharp.Quantizers |
|
|
protected override void InitialQuantizePixel(TPixel pixel) |
|
|
protected override void InitialQuantizePixel(TPixel pixel) |
|
|
{ |
|
|
{ |
|
|
// Add the color to the Octree
|
|
|
// Add the color to the Octree
|
|
|
this.octree.AddColor(pixel, this.pixelBuffer); |
|
|
var rgba = default(Rgba32); |
|
|
|
|
|
this.octree.AddColor(pixel, ref rgba); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/// <inheritdoc/>
|
|
|
/// <inheritdoc/>
|
|
|
protected override TPixel[] GetPalette() |
|
|
protected override TPixel[] GetPalette() |
|
|
{ |
|
|
{ |
|
|
return this.palette ?? (this.palette = this.octree.Palletize(Math.Max(this.colors, (byte)1))); |
|
|
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; |
|
|
|
|
|
var trans = default(Rgba32); |
|
|
|
|
|
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; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
/// <summary>
|
|
|
/// Process the pixel in the second pass of the algorithm
|
|
|
/// Process the pixel in the second pass of the algorithm
|
|
|
/// </summary>
|
|
|
/// </summary>
|
|
|
/// <param name="pixel">The pixel to quantize</param>
|
|
|
/// <param name="pixel">The pixel to quantize</param>
|
|
|
|
|
|
/// <param name="rgba">The color to compare against</param>
|
|
|
/// <returns>
|
|
|
/// <returns>
|
|
|
/// The quantized value
|
|
|
/// The quantized value
|
|
|
/// </returns>
|
|
|
/// </returns>
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
|
private byte QuantizePixel(TPixel pixel) |
|
|
private byte QuantizePixel(TPixel pixel, ref Rgba32 rgba) |
|
|
{ |
|
|
{ |
|
|
if (this.Dither) |
|
|
if (this.Dither) |
|
|
{ |
|
|
{ |
|
|
@ -144,13 +178,13 @@ namespace SixLabors.ImageSharp.Quantizers |
|
|
return this.GetClosestPixel(pixel, this.palette, this.colorMap); |
|
|
return this.GetClosestPixel(pixel, this.palette, this.colorMap); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
pixel.ToXyzwBytes(this.pixelBuffer, 0); |
|
|
pixel.ToRgba32(ref rgba); |
|
|
if (this.pixelBuffer[3] == 0) |
|
|
if (rgba.Equals(default(Rgba32))) |
|
|
{ |
|
|
{ |
|
|
return this.colors; |
|
|
return this.transparentIndex; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
return (byte)this.octree.GetPaletteIndex(pixel, this.pixelBuffer); |
|
|
return (byte)this.octree.GetPaletteIndex(pixel, ref rgba); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
/// <summary>
|
|
|
@ -233,8 +267,8 @@ namespace SixLabors.ImageSharp.Quantizers |
|
|
/// Add a given color value to the Octree
|
|
|
/// Add a given color value to the Octree
|
|
|
/// </summary>
|
|
|
/// </summary>
|
|
|
/// <param name="pixel">The pixel data.</param>
|
|
|
/// <param name="pixel">The pixel data.</param>
|
|
|
/// <param name="buffer">The buffer array.</param>
|
|
|
/// <param name="rgba">The color.</param>
|
|
|
public void AddColor(TPixel pixel, byte[] buffer) |
|
|
public void AddColor(TPixel pixel, ref Rgba32 rgba) |
|
|
{ |
|
|
{ |
|
|
// Check if this request is for the same color as the last
|
|
|
// Check if this request is for the same color as the last
|
|
|
if (this.previousColor.Equals(pixel)) |
|
|
if (this.previousColor.Equals(pixel)) |
|
|
@ -244,18 +278,18 @@ namespace SixLabors.ImageSharp.Quantizers |
|
|
if (this.previousNode == null) |
|
|
if (this.previousNode == null) |
|
|
{ |
|
|
{ |
|
|
this.previousColor = pixel; |
|
|
this.previousColor = pixel; |
|
|
this.root.AddColor(pixel, this.maxColorBits, 0, this, buffer); |
|
|
this.root.AddColor(pixel, this.maxColorBits, 0, this, ref rgba); |
|
|
} |
|
|
} |
|
|
else |
|
|
else |
|
|
{ |
|
|
{ |
|
|
// Just update the previous node
|
|
|
// Just update the previous node
|
|
|
this.previousNode.Increment(pixel, buffer); |
|
|
this.previousNode.Increment(pixel, ref rgba); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
else |
|
|
else |
|
|
{ |
|
|
{ |
|
|
this.previousColor = pixel; |
|
|
this.previousColor = pixel; |
|
|
this.root.AddColor(pixel, this.maxColorBits, 0, this, buffer); |
|
|
this.root.AddColor(pixel, this.maxColorBits, 0, this, ref rgba); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -287,13 +321,13 @@ namespace SixLabors.ImageSharp.Quantizers |
|
|
/// Get the palette index for the passed color
|
|
|
/// Get the palette index for the passed color
|
|
|
/// </summary>
|
|
|
/// </summary>
|
|
|
/// <param name="pixel">The pixel data.</param>
|
|
|
/// <param name="pixel">The pixel data.</param>
|
|
|
/// <param name="buffer">The buffer array.</param>
|
|
|
/// <param name="rgba">The color to map to.</param>
|
|
|
/// <returns>
|
|
|
/// <returns>
|
|
|
/// The <see cref="int"/>.
|
|
|
/// The <see cref="int"/>.
|
|
|
/// </returns>
|
|
|
/// </returns>
|
|
|
public int GetPaletteIndex(TPixel pixel, byte[] buffer) |
|
|
public int GetPaletteIndex(TPixel pixel, ref Rgba32 rgba) |
|
|
{ |
|
|
{ |
|
|
return this.root.GetPaletteIndex(pixel, 0, buffer); |
|
|
return this.root.GetPaletteIndex(pixel, 0, ref rgba); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
/// <summary>
|
|
|
@ -415,17 +449,17 @@ namespace SixLabors.ImageSharp.Quantizers |
|
|
/// <summary>
|
|
|
/// <summary>
|
|
|
/// Add a color into the tree
|
|
|
/// Add a color into the tree
|
|
|
/// </summary>
|
|
|
/// </summary>
|
|
|
/// <param name="pixel">The color</param>
|
|
|
/// <param name="pixel">The pixel color</param>
|
|
|
/// <param name="colorBits">The number of significant color bits</param>
|
|
|
/// <param name="colorBits">The number of significant color bits</param>
|
|
|
/// <param name="level">The level in the tree</param>
|
|
|
/// <param name="level">The level in the tree</param>
|
|
|
/// <param name="octree">The tree to which this node belongs</param>
|
|
|
/// <param name="octree">The tree to which this node belongs</param>
|
|
|
/// <param name="buffer">The buffer array.</param>
|
|
|
/// <param name="rgba">The color to map to.</param>
|
|
|
public void AddColor(TPixel pixel, int colorBits, int level, Octree octree, byte[] buffer) |
|
|
public void AddColor(TPixel pixel, int colorBits, int level, Octree octree, ref Rgba32 rgba) |
|
|
{ |
|
|
{ |
|
|
// Update the color information if this is a leaf
|
|
|
// Update the color information if this is a leaf
|
|
|
if (this.leaf) |
|
|
if (this.leaf) |
|
|
{ |
|
|
{ |
|
|
this.Increment(pixel, buffer); |
|
|
this.Increment(pixel, ref rgba); |
|
|
|
|
|
|
|
|
// Setup the previous node
|
|
|
// Setup the previous node
|
|
|
octree.TrackPrevious(this); |
|
|
octree.TrackPrevious(this); |
|
|
@ -434,11 +468,11 @@ namespace SixLabors.ImageSharp.Quantizers |
|
|
{ |
|
|
{ |
|
|
// Go to the next level down in the tree
|
|
|
// Go to the next level down in the tree
|
|
|
int shift = 7 - level; |
|
|
int shift = 7 - level; |
|
|
pixel.ToXyzwBytes(buffer, 0); |
|
|
pixel.ToRgba32(ref rgba); |
|
|
|
|
|
|
|
|
int index = ((buffer[2] & Mask[level]) >> (shift - 2)) | |
|
|
int index = ((rgba.B & Mask[level]) >> (shift - 2)) | |
|
|
((buffer[1] & Mask[level]) >> (shift - 1)) | |
|
|
((rgba.G & Mask[level]) >> (shift - 1)) | |
|
|
((buffer[0] & Mask[level]) >> shift); |
|
|
((rgba.R & Mask[level]) >> shift); |
|
|
|
|
|
|
|
|
OctreeNode child = this.children[index]; |
|
|
OctreeNode child = this.children[index]; |
|
|
|
|
|
|
|
|
@ -450,7 +484,7 @@ namespace SixLabors.ImageSharp.Quantizers |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// Add the color to the child node
|
|
|
// Add the color to the child node
|
|
|
child.AddColor(pixel, colorBits, level + 1, octree, buffer); |
|
|
child.AddColor(pixel, colorBits, level + 1, octree, ref rgba); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -524,26 +558,26 @@ namespace SixLabors.ImageSharp.Quantizers |
|
|
/// </summary>
|
|
|
/// </summary>
|
|
|
/// <param name="pixel">The pixel data.</param>
|
|
|
/// <param name="pixel">The pixel data.</param>
|
|
|
/// <param name="level">The level.</param>
|
|
|
/// <param name="level">The level.</param>
|
|
|
/// <param name="buffer">The buffer array.</param>
|
|
|
/// <param name="rgba">The color to map to.</param>
|
|
|
/// <returns>
|
|
|
/// <returns>
|
|
|
/// The <see cref="int"/> representing the index of the pixel in the palette.
|
|
|
/// The <see cref="int"/> representing the index of the pixel in the palette.
|
|
|
/// </returns>
|
|
|
/// </returns>
|
|
|
public int GetPaletteIndex(TPixel pixel, int level, byte[] buffer) |
|
|
public int GetPaletteIndex(TPixel pixel, int level, ref Rgba32 rgba) |
|
|
{ |
|
|
{ |
|
|
int index = this.paletteIndex; |
|
|
int index = this.paletteIndex; |
|
|
|
|
|
|
|
|
if (!this.leaf) |
|
|
if (!this.leaf) |
|
|
{ |
|
|
{ |
|
|
int shift = 7 - level; |
|
|
int shift = 7 - level; |
|
|
pixel.ToXyzwBytes(buffer, 0); |
|
|
pixel.ToRgba32(ref rgba); |
|
|
|
|
|
|
|
|
int pixelIndex = ((buffer[2] & Mask[level]) >> (shift - 2)) | |
|
|
int pixelIndex = ((rgba.B & Mask[level]) >> (shift - 2)) | |
|
|
((buffer[1] & Mask[level]) >> (shift - 1)) | |
|
|
((rgba.G & Mask[level]) >> (shift - 1)) | |
|
|
((buffer[0] & Mask[level]) >> shift); |
|
|
((rgba.R & Mask[level]) >> shift); |
|
|
|
|
|
|
|
|
if (this.children[pixelIndex] != null) |
|
|
if (this.children[pixelIndex] != null) |
|
|
{ |
|
|
{ |
|
|
index = this.children[pixelIndex].GetPaletteIndex(pixel, level + 1, buffer); |
|
|
index = this.children[pixelIndex].GetPaletteIndex(pixel, level + 1, ref rgba); |
|
|
} |
|
|
} |
|
|
else |
|
|
else |
|
|
{ |
|
|
{ |
|
|
@ -558,14 +592,14 @@ namespace SixLabors.ImageSharp.Quantizers |
|
|
/// Increment the pixel count and add to the color information
|
|
|
/// Increment the pixel count and add to the color information
|
|
|
/// </summary>
|
|
|
/// </summary>
|
|
|
/// <param name="pixel">The pixel to add.</param>
|
|
|
/// <param name="pixel">The pixel to add.</param>
|
|
|
/// <param name="buffer">The buffer array.</param>
|
|
|
/// <param name="rgba">The color to map to.</param>
|
|
|
public void Increment(TPixel pixel, byte[] buffer) |
|
|
public void Increment(TPixel pixel, ref Rgba32 rgba) |
|
|
{ |
|
|
{ |
|
|
pixel.ToXyzwBytes(buffer, 0); |
|
|
pixel.ToRgba32(ref rgba); |
|
|
this.pixelCount++; |
|
|
this.pixelCount++; |
|
|
this.red += buffer[0]; |
|
|
this.red += rgba.R; |
|
|
this.green += buffer[1]; |
|
|
this.green += rgba.G; |
|
|
this.blue += buffer[2]; |
|
|
this.blue += rgba.B; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|