|
|
|
@ -27,6 +27,11 @@ namespace SixLabors.ImageSharp.Processing.Quantization.FrameQuantizers |
|
|
|
/// </summary>
|
|
|
|
private readonly bool singlePass; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The vector representation of the image palette.
|
|
|
|
/// </summary>
|
|
|
|
private Vector4[] paletteVector; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Initializes a new instance of the <see cref="FrameQuantizerBase{TPixel}"/> class.
|
|
|
|
/// </summary>
|
|
|
|
@ -35,10 +40,9 @@ namespace SixLabors.ImageSharp.Processing.Quantization.FrameQuantizers |
|
|
|
/// If true, the quantization process only needs to loop through the source pixels once
|
|
|
|
/// </param>
|
|
|
|
/// <remarks>
|
|
|
|
/// If you construct this class with a true value for singlePass, then the code will, when quantizing your image,
|
|
|
|
/// only call the <see cref="FirstPass(ImageFrame{TPixel}, int, int)"/> methods.
|
|
|
|
/// If two passes are required, the code will also call <see cref="SecondPass(ImageFrame{TPixel}, Span{byte}, int, int)"/>
|
|
|
|
/// and then 'QuantizeImage'.
|
|
|
|
/// If you construct this class with a <value>true</value> for <paramref name="singlePass"/>, then the code will
|
|
|
|
/// only call the <see cref="SecondPass(ImageFrame{TPixel}, Span{byte}, ReadOnlySpan{TPixel}, int, int)"/> method.
|
|
|
|
/// If two passes are required, the code will also call <see cref="FirstPass(ImageFrame{TPixel}, int, int)"/>.
|
|
|
|
/// </remarks>
|
|
|
|
protected FrameQuantizerBase(IQuantizer quantizer, bool singlePass) |
|
|
|
{ |
|
|
|
@ -73,28 +77,31 @@ namespace SixLabors.ImageSharp.Processing.Quantization.FrameQuantizers |
|
|
|
} |
|
|
|
|
|
|
|
// Collect the palette. Required before the second pass runs.
|
|
|
|
var quantizedFrame = new QuantizedFrame<TPixel>(image.MemoryAllocator, width, height, this.GetPalette()); |
|
|
|
TPixel[] palette = this.GetPalette(); |
|
|
|
this.paletteVector = new Vector4[palette.Length]; |
|
|
|
PixelOperations<TPixel>.Instance.ToScaledVector4(palette, this.paletteVector, palette.Length); |
|
|
|
var quantizedFrame = new QuantizedFrame<TPixel>(image.MemoryAllocator, width, height, palette); |
|
|
|
|
|
|
|
if (this.Dither) |
|
|
|
{ |
|
|
|
// We clone the image as we don't want to alter the original.
|
|
|
|
// We clone the image as we don't want to alter the original via dithering.
|
|
|
|
using (ImageFrame<TPixel> clone = image.Clone()) |
|
|
|
{ |
|
|
|
this.SecondPass(clone, quantizedFrame.GetPixelSpan(), width, height); |
|
|
|
this.SecondPass(clone, quantizedFrame.GetPixelSpan(), palette, width, height); |
|
|
|
} |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
this.SecondPass(image, quantizedFrame.GetPixelSpan(), width, height); |
|
|
|
this.SecondPass(image, quantizedFrame.GetPixelSpan(), palette, width, height); |
|
|
|
} |
|
|
|
|
|
|
|
return quantizedFrame; |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Execute the first pass through the pixels in the image
|
|
|
|
/// Execute the first pass through the pixels in the image to create the palette.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="source">The source data</param>
|
|
|
|
/// <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>
|
|
|
|
protected virtual void FirstPass(ImageFrame<TPixel> source, int width, int height) |
|
|
|
@ -102,17 +109,22 @@ namespace SixLabors.ImageSharp.Processing.Quantization.FrameQuantizers |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Execute a second pass through the image
|
|
|
|
/// Execute a second pass through the image to assign the pixels to a palette entry.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="source">The source image.</param>
|
|
|
|
/// <param name="output">The output pixel array</param>
|
|
|
|
/// <param name="width">The width in pixels of the image</param>
|
|
|
|
/// <param name="height">The height in pixels of the image</param>
|
|
|
|
protected abstract void SecondPass(ImageFrame<TPixel> source, Span<byte> output, int width, int height); |
|
|
|
/// <param name="output">The output pixel array.</param>
|
|
|
|
/// <param name="palette">The output color palette.</param>
|
|
|
|
/// <param name="width">The width in pixels of the image.</param>
|
|
|
|
/// <param name="height">The height in pixels of the image.</param>
|
|
|
|
protected abstract void SecondPass( |
|
|
|
ImageFrame<TPixel> source, |
|
|
|
Span<byte> output, |
|
|
|
ReadOnlySpan<TPixel> palette, |
|
|
|
int width, |
|
|
|
int height); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Retrieve the palette for the quantized image.
|
|
|
|
/// <remarks>Can be called more than once so make sure calls are cached.</remarks>
|
|
|
|
/// </summary>
|
|
|
|
/// <returns>
|
|
|
|
/// <see cref="T:TPixel[]"/>
|
|
|
|
@ -120,13 +132,34 @@ namespace SixLabors.ImageSharp.Processing.Quantization.FrameQuantizers |
|
|
|
protected abstract TPixel[] GetPalette(); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Returns the closest color from the palette to the given color by calculating the Euclidean distance.
|
|
|
|
/// Returns the index of the first instance of the transparent color in the palette.
|
|
|
|
/// </summary>
|
|
|
|
/// <returns>The <see cref="int"/>.</returns>
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
|
|
protected byte GetTransparentIndex() |
|
|
|
{ |
|
|
|
// Transparent pixels are much more likely to be found at the end of a palette.
|
|
|
|
int index = this.paletteVector.Length - 1; |
|
|
|
for (int i = this.paletteVector.Length - 1; i >= 0; i--) |
|
|
|
{ |
|
|
|
ref Vector4 candidate = ref this.paletteVector[i]; |
|
|
|
if (candidate.Equals(default)) |
|
|
|
{ |
|
|
|
index = i; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return (byte)index; |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Returns the closest color from the palette to the given color by calculating the
|
|
|
|
/// Euclidean distance in the Rgba colorspace.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="pixel">The color.</param>
|
|
|
|
/// <param name="colorPalette">The color palette.</param>
|
|
|
|
/// <returns>The <see cref="int"/></returns>
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
|
|
protected byte GetClosestPixel(TPixel pixel, TPixel[] colorPalette) |
|
|
|
protected byte GetClosestPixel(ref TPixel pixel) |
|
|
|
{ |
|
|
|
// Check if the color is in the lookup table
|
|
|
|
if (this.distanceCache.TryGetValue(pixel, out byte value)) |
|
|
|
@ -134,22 +167,22 @@ namespace SixLabors.ImageSharp.Processing.Quantization.FrameQuantizers |
|
|
|
return value; |
|
|
|
} |
|
|
|
|
|
|
|
return this.GetClosestPixelSlow(pixel, colorPalette); |
|
|
|
return this.GetClosestPixelSlow(ref pixel); |
|
|
|
} |
|
|
|
|
|
|
|
[MethodImpl(MethodImplOptions.NoInlining)] |
|
|
|
private byte GetClosestPixelSlow(TPixel pixel, TPixel[] colorPalette) |
|
|
|
private byte GetClosestPixelSlow(ref TPixel pixel) |
|
|
|
{ |
|
|
|
// Loop through the palette and find the nearest match.
|
|
|
|
int colorIndex = 0; |
|
|
|
float leastDistance = float.MaxValue; |
|
|
|
var vector = pixel.ToVector4(); |
|
|
|
Vector4 vector = pixel.ToScaledVector4(); |
|
|
|
float epsilon = Constants.EpsilonSquared; |
|
|
|
|
|
|
|
for (int index = 0; index < colorPalette.Length; index++) |
|
|
|
for (int index = 0; index < this.paletteVector.Length; index++) |
|
|
|
{ |
|
|
|
ref TPixel candidate = ref colorPalette[index]; |
|
|
|
float distance = Vector4.DistanceSquared(vector, candidate.ToVector4()); |
|
|
|
ref Vector4 candidate = ref this.paletteVector[index]; |
|
|
|
float distance = Vector4.DistanceSquared(vector, candidate); |
|
|
|
|
|
|
|
// Greater... Move on.
|
|
|
|
if (!(distance < leastDistance)) |
|
|
|
|