Browse Source

WuQuantizer now inherits Quantizer

af/merge-core
James Jackson-South 9 years ago
parent
commit
86fdf56867
  1. 3
      src/ImageSharp/Quantizers/Wu/Box.cs
  2. 361
      src/ImageSharp/Quantizers/Wu/WuQuantizer.cs

3
src/ImageSharp/Quantizers/Wu/Box.cs

@ -7,8 +7,9 @@ namespace ImageSharp.Quantizers
{
/// <summary>
/// Represents a box color cube.
/// TODO: This should be a struct for performance
/// </summary>
internal struct Box
internal sealed class Box
{
/// <summary>
/// Gets or sets the min red value, exclusive.

361
src/ImageSharp/Quantizers/Wu/WuQuantizer.cs

@ -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>
@ -58,82 +59,232 @@ namespace ImageSharp.Quantizers
/// </summary>
private const int TableLength = IndexCount * IndexCount * IndexCount * IndexAlphaCount;
/// <summary>
/// A buffer for storing pixels
/// </summary>
private readonly byte[] rgbaBuffer = new byte[4];
/// <summary>
/// A lookup table for colors
/// </summary>
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 float[] 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 = 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);
}
/// <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);
this.BuildCube(out Box[] cube, ref colorCount);
// And setup the previous pointer
previousPixel = sourcePixel;
return this.GenerateResult(imagePixels, colorCount, cube);
if (this.Dither)
{
transformedPixel = colorPalette[pixelValue];
}
}
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);
}
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>
@ -294,55 +445,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>
@ -665,30 +767,28 @@ 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];
float[] vv = new float[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] = default(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]) : 0F;
vv[i] = cube[i].Volume > 1 ? this.Variance(cube[i]) : 0F;
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
{
@ -710,79 +810,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);
float weight = Volume(cube[k], this.vwt);
if (MathF.Abs(weight) > Constants.Epsilon)
{
float r = Volume(cube[k], this.vmr) / weight;
float g = Volume(cube[k], this.vmg) / weight;
float b = Volume(cube[k], this.vmb) / weight;
float a = 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
WuArrayPool.LongPool.Return(this.vwt);
WuArrayPool.LongPool.Return(this.vmr);
WuArrayPool.LongPool.Return(this.vmg);
WuArrayPool.LongPool.Return(this.vmb);
WuArrayPool.LongPool.Return(this.vma);
WuArrayPool.FloatPool.Return(this.m2);
WuArrayPool.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 (byte)GetPaletteIndex(r + 1, g + 1, b + 1, a + 1);
}
}
}
}

Loading…
Cancel
Save