|
|
|
@ -7,8 +7,9 @@ namespace ImageSharp.Quantizers |
|
|
|
{ |
|
|
|
using System; |
|
|
|
using System.Buffers; |
|
|
|
using System.Collections.Generic; |
|
|
|
using System.Numerics; |
|
|
|
using System.Threading.Tasks; |
|
|
|
using System.Runtime.CompilerServices; |
|
|
|
|
|
|
|
/// <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> : IQuantizer<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, 25); |
|
|
|
|
|
|
|
/// <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, 256); |
|
|
|
this.colors = maxColors.Clamp(1, 256); |
|
|
|
|
|
|
|
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) / 255F); |
|
|
|
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.colorCube[i] = new Box(); |
|
|
|
} |
|
|
|
|
|
|
|
cube[0].R0 = cube[0].G0 = cube[0].B0 = cube[0].A0 = 0; |
|
|
|
cube[0].R1 = cube[0].G1 = cube[0].B1 = IndexCount - 1; |
|
|
|
cube[0].A1 = IndexAlphaCount - 1; |
|
|
|
this.colorCube[0].R0 = this.colorCube[0].G0 = this.colorCube[0].B0 = this.colorCube[0].A0 = 0; |
|
|
|
this.colorCube[0].R1 = this.colorCube[0].G1 = this.colorCube[0].B1 = IndexCount - 1; |
|
|
|
this.colorCube[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.colorCube[next], this.colorCube[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.colorCube[next].Volume > 1 ? this.Variance(this.colorCube[next]) : 0F; |
|
|
|
vv[i] = this.colorCube[i].Volume > 1 ? this.Variance(this.colorCube[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) / 255F); |
|
|
|
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)]; |
|
|
|
} |
|
|
|
} |
|
|
|
} |