Browse Source

Moar optimisations plus start tidy up

Former-commit-id: 98655c0fccf43335504a97036f9fc699c490af17
Former-commit-id: 002b3f569e94bd36357de09271033cf033ed3997
pull/17/head
James South 12 years ago
parent
commit
90340dd3ce
  1. 6
      src/ImageProcessor/ImageProcessor.csproj
  2. 3
      src/ImageProcessor/Imaging/Formats/PngFormat.cs
  3. 64
      src/ImageProcessor/Imaging/Quantizers/WuQuantizer/Box.cs
  4. 33
      src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ColorData.cs
  5. 33
      src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ColorMoment.cs
  6. 37
      src/ImageProcessor/Imaging/Quantizers/WuQuantizer/CubeCut.cs
  7. 35
      src/ImageProcessor/Imaging/Quantizers/WuQuantizer/IWuQuantizer.cs
  8. 93
      src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ImageBuffer.cs
  9. 10
      src/ImageProcessor/Imaging/Quantizers/WuQuantizer/Lookup.cs
  10. 52
      src/ImageProcessor/Imaging/Quantizers/WuQuantizer/PaletteBuffer.cs
  11. 73
      src/ImageProcessor/Imaging/Quantizers/WuQuantizer/PaletteColorHistory.cs
  12. 55
      src/ImageProcessor/Imaging/Quantizers/WuQuantizer/PaletteLookup.cs
  13. 33
      src/ImageProcessor/Imaging/Quantizers/WuQuantizer/Pixel.cs
  14. 3
      src/ImageProcessor/Imaging/Quantizers/WuQuantizer/QuantizationException.cs
  15. 63
      src/ImageProcessor/Imaging/Quantizers/WuQuantizer/WuQuantizer.cs
  16. 201
      src/ImageProcessor/Imaging/Quantizers/WuQuantizer/WuQuantizerBase.cs

6
src/ImageProcessor/ImageProcessor.csproj

@ -185,14 +185,12 @@
<Compile Include="Imaging\Quantizers\OctreeQuantizer.cs" /> <Compile Include="Imaging\Quantizers\OctreeQuantizer.cs" />
<Compile Include="Imaging\Quantizers\Quantizer.cs" /> <Compile Include="Imaging\Quantizers\Quantizer.cs" />
<Compile Include="Imaging\Quantizers\WuQuantizer\Box.cs" /> <Compile Include="Imaging\Quantizers\WuQuantizer\Box.cs" />
<Compile Include="Imaging\Quantizers\WuQuantizer\PaletteLookup.cs" />
<Compile Include="Imaging\Quantizers\WuQuantizer\PaletteBuffer.cs" />
<Compile Include="Imaging\Quantizers\WuQuantizer\ColorData.cs" />
<Compile Include="Imaging\Quantizers\WuQuantizer\ColorMoment.cs" /> <Compile Include="Imaging\Quantizers\WuQuantizer\ColorMoment.cs" />
<Compile Include="Imaging\Quantizers\WuQuantizer\PaletteColorHistory.cs" />
<Compile Include="Imaging\Quantizers\WuQuantizer\PaletteLookup.cs" />
<Compile Include="Imaging\Quantizers\WuQuantizer\CubeCut.cs" /> <Compile Include="Imaging\Quantizers\WuQuantizer\CubeCut.cs" />
<Compile Include="Imaging\Quantizers\WuQuantizer\ImageBuffer.cs" /> <Compile Include="Imaging\Quantizers\WuQuantizer\ImageBuffer.cs" />
<Compile Include="Imaging\Quantizers\WuQuantizer\IWuQuantizer.cs" /> <Compile Include="Imaging\Quantizers\WuQuantizer\IWuQuantizer.cs" />
<Compile Include="Imaging\Quantizers\WuQuantizer\Lookup.cs" />
<Compile Include="Imaging\Quantizers\WuQuantizer\Pixel.cs"> <Compile Include="Imaging\Quantizers\WuQuantizer\Pixel.cs">
<SubType>Code</SubType> <SubType>Code</SubType>
</Compile> </Compile>

3
src/ImageProcessor/Imaging/Formats/PngFormat.cs

@ -16,8 +16,7 @@ namespace ImageProcessor.Imaging.Formats
using ImageProcessor.Common.Extensions; using ImageProcessor.Common.Extensions;
using ImageProcessor.Imaging.Quantizers; using ImageProcessor.Imaging.Quantizers;
using ImageProcessor.Imaging.Quantizers.WuQuantizer;
using nQuant;
/// <summary> /// <summary>
/// Provides the necessary information to support png images. /// Provides the necessary information to support png images.

64
src/ImageProcessor/Imaging/Quantizers/WuQuantizer/Box.cs

@ -1,15 +1,65 @@
namespace nQuant // --------------------------------------------------------------------------------------------------------------------
// <copyright file="Box.cs" company="James South">
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// The box for storing color attributes.
// Adapted from <see href="https://github.com/drewnoakes" />
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
{ {
/// <summary>
/// The box for storing color attributes.
/// Adapted from <see href="https://github.com/drewnoakes"/>
/// </summary>
public struct Box public struct Box
{ {
public byte AlphaMinimum; /// <summary>
/// The alpha maximum.
/// </summary>
public byte AlphaMaximum; public byte AlphaMaximum;
public byte RedMinimum;
public byte RedMaximum; /// <summary>
public byte GreenMinimum; /// The alpha minimum.
public byte GreenMaximum; /// </summary>
public byte BlueMinimum; public byte AlphaMinimum;
/// <summary>
/// The blue maximum.
/// </summary>
public byte BlueMaximum; public byte BlueMaximum;
/// <summary>
/// The blue minimum.
/// </summary>
public byte BlueMinimum;
/// <summary>
/// The green maximum.
/// </summary>
public byte GreenMaximum;
/// <summary>
/// The green minimum.
/// </summary>
public byte GreenMinimum;
/// <summary>
/// The red maximum.
/// </summary>
public byte RedMaximum;
/// <summary>
/// The red minimum.
/// </summary>
public byte RedMinimum;
/// <summary>
/// The size.
/// </summary>
public int Size; public int Size;
} }
} }

33
src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ColorData.cs

@ -1,33 +0,0 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="ColorData.cs" company="James South">
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace nQuant
{
/// <summary>
/// The color data.
/// </summary>
public class ColorData
{
/// <summary>
/// Initializes a new instance of the <see cref="ColorData"/> class.
/// </summary>
/// <param name="dataGranularity">
/// The data granularity.
/// </param>
public ColorData(int dataGranularity)
{
dataGranularity++;
this.Moments = new ColorMoment[dataGranularity, dataGranularity, dataGranularity, dataGranularity];
}
/// <summary>
/// Gets the moments.
/// </summary>
public ColorMoment[, , ,] Moments { get; private set; }
}
}

33
src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ColorMoment.cs

@ -1,7 +1,8 @@
 //using System.Runtime.CompilerServices;
namespace nQuant
namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
{ {
public struct ColorMoment struct ColorMoment
{ {
public long Alpha; public long Alpha;
public long Red; public long Red;
@ -43,16 +44,22 @@ namespace nQuant
return c1; return c1;
} }
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Add(Pixel p) public void Add(Pixel p)
{ {
Alpha += p.Alpha; byte pAlpha = p.Alpha;
Red += p.Red; byte pRed = p.Red;
Green += p.Green; byte pGreen = p.Green;
Blue += p.Blue; byte pBlue = p.Blue;
Alpha += pAlpha;
Red += pRed;
Green += pGreen;
Blue += pBlue;
Weight++; Weight++;
Moment += p.Amplitude(); Moment += pAlpha * pAlpha + pRed * pRed + pGreen * pGreen + pBlue * pBlue;
} }
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddFast(ref ColorMoment c2) public void AddFast(ref ColorMoment c2)
{ {
Alpha += c2.Alpha; Alpha += c2.Alpha;
@ -62,21 +69,21 @@ namespace nQuant
Weight += c2.Weight; Weight += c2.Weight;
Moment += c2.Moment; Moment += c2.Moment;
} }
public long Amplitude() public long Amplitude()
{ {
return (Alpha * Alpha) + (Red * Red) + (Green * Green) + (Blue * Blue); return Alpha * Alpha + Red * Red + Green * Green + Blue * Blue;
} }
public long WeightedDistance() public long WeightedDistance()
{ {
return this.Amplitude() / Weight; return Amplitude() / Weight;
} }
public float Variance() public float Variance()
{ {
var result = Moment - ((float)this.Amplitude() / this.Weight); var result = Moment - (float)Amplitude() / Weight;
return float.IsNaN(result) ? 0.0f : result; return float.IsNaN(result) ? 0.0f : result;
} }
} }
} }

37
src/ImageProcessor/Imaging/Quantizers/WuQuantizer/CubeCut.cs

@ -1,14 +1,45 @@
namespace nQuant // --------------------------------------------------------------------------------------------------------------------
// <copyright file="CubeCut.cs" company="James South">
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// Represents a cube cut.
// Adapted from <see href="https://github.com/drewnoakes" />
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
{ {
/// <summary>
/// Represents a cube cut.
/// Adapted from <see href="https://github.com/drewnoakes"/>
/// </summary>
internal struct CubeCut internal struct CubeCut
{ {
/// <summary>
/// The position.
/// </summary>
public readonly byte? Position; public readonly byte? Position;
/// <summary>
/// The value.
/// </summary>
public readonly float Value; public readonly float Value;
/// <summary>
/// Initializes a new instance of the <see cref="CubeCut"/> struct.
/// </summary>
/// <param name="cutPoint">
/// The cut point.
/// </param>
/// <param name="result">
/// The result.
/// </param>
public CubeCut(byte? cutPoint, float result) public CubeCut(byte? cutPoint, float result)
{ {
Position = cutPoint; this.Position = cutPoint;
Value = result; this.Value = result;
} }
} }
} }

35
src/ImageProcessor/Imaging/Quantizers/WuQuantizer/IWuQuantizer.cs

@ -1,9 +1,40 @@
using System.Drawing; // --------------------------------------------------------------------------------------------------------------------
// <copyright file="IWuQuantizer.cs" company="James South">
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// The WuQuantizer interface.
// Adapted from <see href="https://github.com/drewnoakes" />
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace nQuant namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
{ {
using System.Drawing;
/// <summary>
/// The WuQuantizer interface.
/// Adapted from <see href="https://github.com/drewnoakes" />
/// </summary>
public interface IWuQuantizer public interface IWuQuantizer
{ {
/// <summary>
/// Quantizes the given image.
/// </summary>
/// <param name="image">
/// The 32 bit per pixel <see cref="Image"/>.
/// </param>
/// <param name="alphaThreshold">
/// The alpha threshold. All colors with an alpha value less than this will be
/// considered fully transparent
/// </param>
/// <param name="alphaFader">
/// The alpha fader. Alpha values will be normalized to the nearest multiple of this value.
/// </param>
/// <returns>
/// The quantized <see cref="Image"/>.
/// </returns>
Image QuantizeImage(Bitmap image, int alphaThreshold, int alphaFader); Image QuantizeImage(Bitmap image, int alphaThreshold, int alphaFader);
} }
} }

93
src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ImageBuffer.cs

@ -1,75 +1,106 @@
using System; // --------------------------------------------------------------------------------------------------------------------
using System.Collections.Generic; // <copyright file="ImageBuffer.cs" company="James South">
using System.Drawing; // Copyright (c) James South.
using System.Drawing.Imaging; // Licensed under the Apache License, Version 2.0.
using System.Linq; // </copyright>
using System.Runtime.InteropServices; // <summary>
using System.Text; // The image buffer for storing pixel information.
// Adapted from <see href="https://github.com/drewnoakes" />
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace nQuant namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
{ {
class ImageBuffer using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
/// <summary>
/// The image buffer for storing pixel information.
/// Adapted from <see href="https://github.com/drewnoakes"/>
/// </summary>
internal class ImageBuffer
{ {
/// <summary>
/// Initializes a new instance of the <see cref="ImageBuffer"/> class.
/// </summary>
/// <param name="image">
/// The image to store.
/// </param>
public ImageBuffer(Bitmap image) public ImageBuffer(Bitmap image)
{ {
this.Image = image; this.Image = image;
} }
public Bitmap Image { get; set; } /// <summary>
/// Gets the image.
protected const int Alpha = 3; /// </summary>
protected const int Red = 2; public Bitmap Image { get; private set; }
protected const int Green = 1;
protected const int Blue = 0;
public IEnumerable<Pixel> Pixels /// <summary>
/// Gets the pixel lines.
/// </summary>
/// <exception cref="QuantizationException">
/// Thrown if the given image is not a 32 bit per pixel image.
/// </exception>
public IEnumerable<Pixel[]> PixelLines
{ {
get get
{ {
var bitDepth = System.Drawing.Image.GetPixelFormatSize(Image.PixelFormat); int bitDepth = System.Drawing.Image.GetPixelFormatSize(this.Image.PixelFormat);
if (bitDepth != 32) if (bitDepth != 32)
throw new QuantizationException(string.Format("The image you are attempting to quantize does not contain a 32 bit ARGB palette. This image has a bit depth of {0} with {1} colors.", bitDepth, Image.Palette.Entries.Length)); {
throw new QuantizationException(
string.Format(
"The image you are attempting to quantize does not contain a 32 bit ARGB palette. This image has a bit depth of {0} with {1} colors.",
bitDepth,
this.Image.Palette.Entries.Length));
}
int width = this.Image.Width; int width = this.Image.Width;
int height = this.Image.Height; int height = this.Image.Height;
int[] buffer = new int[width]; int[] buffer = new int[width];
Pixel[] pixels = new Pixel[width];
for (int rowIndex = 0; rowIndex < height; rowIndex++) for (int rowIndex = 0; rowIndex < height; rowIndex++)
{ {
BitmapData data = this.Image.LockBits(Rectangle.FromLTRB(0, rowIndex, width, rowIndex + 1), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); BitmapData data = this.Image.LockBits(Rectangle.FromLTRB(0, rowIndex, width, rowIndex + 1), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
try try
{ {
Marshal.Copy(data.Scan0, buffer, 0, width); Marshal.Copy(data.Scan0, buffer, 0, width);
foreach (int pixel in buffer) for (int pixelIndex = 0; pixelIndex < buffer.Length; pixelIndex++)
{ {
yield return new Pixel(pixel); pixels[pixelIndex] = new Pixel(buffer[pixelIndex]);
} }
} }
finally finally
{ {
this.Image.UnlockBits(data); this.Image.UnlockBits(data);
} }
yield return pixels;
} }
} }
} }
public void UpdatePixelIndexes(IEnumerable<byte> indexes) /// <summary>
/// Updates the pixel indexes.
/// </summary>
/// <param name="lineIndexes">
/// The line indexes.
/// </param>
public void UpdatePixelIndexes(IEnumerable<byte[]> lineIndexes)
{ {
int width = this.Image.Width; int width = this.Image.Width;
int height = this.Image.Height; int height = this.Image.Height;
byte[] buffer = new byte[width]; var indexesIterator = lineIndexes.GetEnumerator();
IEnumerator<byte> indexesIterator = indexes.GetEnumerator();
for (int rowIndex = 0; rowIndex < height; rowIndex++) for (int rowIndex = 0; rowIndex < height; rowIndex++)
{ {
for (int columnIndex = 0; columnIndex < buffer.Length; columnIndex++) indexesIterator.MoveNext();
{
indexesIterator.MoveNext();
buffer[columnIndex] = indexesIterator.Current;
}
BitmapData data = this.Image.LockBits(Rectangle.FromLTRB(0, rowIndex, width, rowIndex + 1), ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed); BitmapData data = this.Image.LockBits(Rectangle.FromLTRB(0, rowIndex, width, rowIndex + 1), ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);
try try
{ {
Marshal.Copy(buffer, 0, data.Scan0, width); Marshal.Copy(indexesIterator.Current, 0, data.Scan0, width);
} }
finally finally
{ {
@ -78,4 +109,4 @@ namespace nQuant
} }
} }
} }
} }

10
src/ImageProcessor/Imaging/Quantizers/WuQuantizer/Lookup.cs

@ -1,10 +0,0 @@
namespace nQuant
{
public class Lookup
{
public int Alpha;
public int Red;
public int Green;
public int Blue;
}
}

52
src/ImageProcessor/Imaging/Quantizers/WuQuantizer/PaletteBuffer.cs

@ -1,52 +0,0 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Text;
namespace nQuant
{
class PaletteBuffer
{
public PaletteBuffer(int colorCount)
{
Alphas = new int[colorCount + 1];
Reds = new int[colorCount + 1];
Greens = new int[colorCount + 1];
Blues = new int[colorCount + 1];
Sums = new int[colorCount + 1];
}
public ColorPalette BuildPalette(ColorPalette palette)
{
var alphas = this.Alphas;
var reds = this.Reds;
var greens = this.Greens;
var blues = this.Blues;
var sums = this.Sums;
for (var paletteIndex = 0; paletteIndex < Sums.Length; paletteIndex++)
{
if (sums[paletteIndex] > 0)
{
alphas[paletteIndex] /= sums[paletteIndex];
reds[paletteIndex] /= sums[paletteIndex];
greens[paletteIndex] /= sums[paletteIndex];
blues[paletteIndex] /= sums[paletteIndex];
}
var color = Color.FromArgb(alphas[paletteIndex], reds[paletteIndex], greens[paletteIndex], blues[paletteIndex]);
palette.Entries[paletteIndex] = color;
}
return palette;
}
public int[] Alphas { get; set; }
public int[] Reds { get; set; }
public int[] Greens { get; set; }
public int[] Blues { get; set; }
public int[] Sums { get; set; }
}
}

73
src/ImageProcessor/Imaging/Quantizers/WuQuantizer/PaletteColorHistory.cs

@ -0,0 +1,73 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="PaletteColorHistory.cs" company="James South">
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// The palette color history.
// Adapted from <see href="https://github.com/drewnoakes" />
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
{
using System.Drawing;
/// <summary>
/// The palette color history.
/// Adapted from <see href="https://github.com/drewnoakes" />
/// </summary>
internal struct PaletteColorHistory
{
/// <summary>
/// The alpha component.
/// </summary>
public int Alpha;
/// <summary>
/// The red component.
/// </summary>
public int Red;
/// <summary>
/// The green component.
/// </summary>
public int Green;
/// <summary>
/// The blue component.
/// </summary>
public int Blue;
/// <summary>
/// The sum of the color components.
/// </summary>
public int Sum;
/// <summary>
/// Normalizes the color.
/// </summary>
/// <returns>
/// The normalized <see cref="Color"/>.
/// </returns>
public Color ToNormalizedColor()
{
return (this.Sum != 0) ? Color.FromArgb(this.Alpha /= this.Sum, this.Red /= this.Sum, this.Green /= this.Sum, this.Blue /= this.Sum) : Color.Empty;
}
/// <summary>
/// Adds a pixel to the color history.
/// </summary>
/// <param name="pixel">
/// The pixel.
/// </param>
public void AddPixel(Pixel pixel)
{
this.Alpha += pixel.Alpha;
this.Red += pixel.Red;
this.Green += pixel.Green;
this.Blue += pixel.Blue;
this.Sum++;
}
}
}

55
src/ImageProcessor/Imaging/Quantizers/WuQuantizer/PaletteLookup.cs

@ -1,46 +1,43 @@
using System; namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace nQuant
{ {
using System;
using System.Collections.Generic;
using System.Linq;
class PaletteLookup class PaletteLookup
{ {
private int mMask; private int mMask;
private Dictionary<int, List<LookupNode>> mLookup = new Dictionary<int, List<LookupNode>>(255); private Dictionary<int, LookupNode[]> mLookup;
private List<LookupNode> Palette { get; set; } private LookupNode[] Palette { get; set; }
public PaletteLookup(List<Pixel> palette) public PaletteLookup(Pixel[] palette)
{ {
Palette = new List<LookupNode>(palette.Count); Palette = new LookupNode[palette.Length];
for (int paletteIndex = 0; paletteIndex < palette.Count; paletteIndex++) for (int paletteIndex = 0; paletteIndex < palette.Length; paletteIndex++)
{ {
Palette.Add(new LookupNode { Pixel = palette[paletteIndex], PaletteIndex = (byte)paletteIndex }); Palette[paletteIndex] = new LookupNode { Pixel = palette[paletteIndex], PaletteIndex = (byte)paletteIndex };
} }
BuildLookup(palette); BuildLookup(palette);
} }
public byte GetPaletteIndex(Pixel pixel) public byte GetPaletteIndex(Pixel pixel)
{ {
int pixelKey = pixel.Argb & mMask; int pixelKey = pixel.Argb & mMask;
List<LookupNode> bucket; LookupNode[] bucket;
if (!mLookup.TryGetValue(pixelKey, out bucket)) if (!mLookup.TryGetValue(pixelKey, out bucket))
{ {
bucket = Palette; bucket = Palette;
} }
if (bucket.Count == 1) if (bucket.Length == 1)
{ {
return bucket[0].PaletteIndex; return bucket[0].PaletteIndex;
} }
int bestDistance = int.MaxValue; int bestDistance = int.MaxValue;
byte bestMatch = 0; byte bestMatch = 0;
for (int lookupIndex = 0; lookupIndex < bucket.Count; lookupIndex++) foreach (var lookup in bucket)
{ {
var lookup = bucket[lookupIndex];
var lookupPixel = lookup.Pixel; var lookupPixel = lookup.Pixel;
var deltaAlpha = pixel.Alpha - lookupPixel.Alpha; var deltaAlpha = pixel.Alpha - lookupPixel.Alpha;
@ -61,29 +58,41 @@ namespace nQuant
bestDistance = distance; bestDistance = distance;
bestMatch = lookup.PaletteIndex; bestMatch = lookup.PaletteIndex;
} }
if ((bucket == Palette) && (pixelKey != 0))
{
mLookup[pixelKey] = new LookupNode[] { bucket[bestMatch] };
}
return bestMatch; return bestMatch;
} }
private void BuildLookup(List<Pixel> palette) private void BuildLookup(Pixel[] palette)
{ {
int mask = GetMask(palette); int mask = GetMask(palette);
Dictionary<int, List<LookupNode>> tempLookup = new Dictionary<int, List<LookupNode>>();
foreach (LookupNode lookup in Palette) foreach (LookupNode lookup in Palette)
{ {
int pixelKey = lookup.Pixel.Argb & mask; int pixelKey = lookup.Pixel.Argb & mask;
List<LookupNode> bucket; List<LookupNode> bucket;
if (!mLookup.TryGetValue(pixelKey, out bucket)) if (!tempLookup.TryGetValue(pixelKey, out bucket))
{ {
bucket = new List<LookupNode>(); bucket = new List<LookupNode>();
mLookup[pixelKey] = bucket; tempLookup[pixelKey] = bucket;
} }
bucket.Add(lookup); bucket.Add(lookup);
} }
mLookup = new Dictionary<int, LookupNode[]>(tempLookup.Count);
foreach (var key in tempLookup.Keys)
{
mLookup[key] = tempLookup[key].ToArray();
}
mMask = mask; mMask = mask;
} }
private static int GetMask(List<Pixel> palette) private static int GetMask(Pixel[] palette)
{ {
IEnumerable<byte> alphas = from pixel in palette IEnumerable<byte> alphas = from pixel in palette
select pixel.Alpha; select pixel.Alpha;
@ -107,12 +116,12 @@ namespace nQuant
double totalUniques = uniqueAlphas + uniqueReds + uniqueGreens + uniqueBlues; double totalUniques = uniqueAlphas + uniqueReds + uniqueGreens + uniqueBlues;
const double AvailableBits = 8f; double AvailableBits = 1.0 + Math.Log(uniqueAlphas * uniqueReds * uniqueGreens * uniqueBlues);
byte alphaMask = ComputeBitMask(maxAlpha, Convert.ToInt32(Math.Round(uniqueAlphas / totalUniques * AvailableBits))); byte alphaMask = ComputeBitMask(maxAlpha, Convert.ToInt32(Math.Round(uniqueAlphas / totalUniques * AvailableBits)));
byte redMask = ComputeBitMask(maxRed, Convert.ToInt32(Math.Round(uniqueReds / totalUniques * AvailableBits))); byte redMask = ComputeBitMask(maxRed, Convert.ToInt32(Math.Round(uniqueReds / totalUniques * AvailableBits)));
byte greenMask = ComputeBitMask(maxGreen, Convert.ToInt32(Math.Round(uniqueGreens / totalUniques * AvailableBits))); byte greenMask = ComputeBitMask(maxGreen, Convert.ToInt32(Math.Round(uniqueGreens / totalUniques * AvailableBits)));
byte blueMask = ComputeBitMask(maxAlpha, Convert.ToInt32(Math.Round(uniqueBlues / totalUniques * AvailableBits))); byte blueMask = ComputeBitMask(maxBlue, Convert.ToInt32(Math.Round(uniqueBlues / totalUniques * AvailableBits)));
Pixel maskedPixel = new Pixel(alphaMask, redMask, greenMask, blueMask); Pixel maskedPixel = new Pixel(alphaMask, redMask, greenMask, blueMask);
return maskedPixel.Argb; return maskedPixel.Argb;

33
src/ImageProcessor/Imaging/Quantizers/WuQuantizer/Pixel.cs

@ -1,7 +1,7 @@
namespace nQuant using System.Diagnostics;
using System.Runtime.InteropServices;
namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
{ {
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Explicit)] [StructLayout(LayoutKind.Explicit)]
public struct Pixel public struct Pixel
{ {
@ -12,32 +12,27 @@ namespace nQuant
Red = red; Red = red;
Green = green; Green = green;
Blue = blue; Blue = blue;
Debug.Assert(Argb == (alpha << 24 | red << 16 | green << 8 | blue));
} }
/// <summary>
/// Initializes a new instance of the <see cref="Pixel"/> struct.
/// </summary>
/// <param name="argb">
/// The combined color components.
/// </param>
public Pixel(int argb) public Pixel(int argb)
: this() : this()
{ {
this.Argb = argb; Argb = argb;
} Debug.Assert(Alpha == ((uint)argb >> 24));
Debug.Assert(Red == ((uint)(argb >> 16) & 255));
public long Amplitude() Debug.Assert(Green == ((uint)(argb >> 8) & 255));
{ Debug.Assert(Blue == ((uint)argb & 255));
return (Alpha * Alpha) + (Red * Red) + (Green * Green) + (Blue * Blue);
} }
[FieldOffsetAttribute(3)] [FieldOffset(3)]
public byte Alpha; public byte Alpha;
[FieldOffsetAttribute(2)] [FieldOffset(2)]
public byte Red; public byte Red;
[FieldOffsetAttribute(1)] [FieldOffset(1)]
public byte Green; public byte Green;
[FieldOffsetAttribute(0)] [FieldOffset(0)]
public byte Blue; public byte Blue;
[FieldOffset(0)] [FieldOffset(0)]
public int Argb; public int Argb;

3
src/ImageProcessor/Imaging/Quantizers/WuQuantizer/QuantizationException.cs

@ -3,8 +3,9 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
namespace nQuant namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
{ {
[Serializable]
public class QuantizationException : ApplicationException public class QuantizationException : ApplicationException
{ {
public QuantizationException(string message) : base(message) public QuantizationException(string message) : base(message)

63
src/ImageProcessor/Imaging/Quantizers/WuQuantizer/WuQuantizer.cs

@ -1,52 +1,49 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
using System.Drawing; using System.Drawing;
using System.Drawing.Imaging;
namespace nQuant namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
{ {
using System.Drawing.Imaging;
public class WuQuantizer : WuQuantizerBase, IWuQuantizer public class WuQuantizer : WuQuantizerBase, IWuQuantizer
{ {
private IEnumerable<byte> indexedPixels(ImageBuffer image, List<Pixel> lookups, int alphaThreshold, PaletteBuffer paletteBuffer) private static IEnumerable<byte[]> IndexedPixels(ImageBuffer image, Pixel[] lookups, int alphaThreshold, PaletteColorHistory[] paletteHistogram)
{ {
var alphas = paletteBuffer.Alphas; var lineIndexes = new byte[image.Image.Width];
var reds = paletteBuffer.Reds; var lookup = new PaletteLookup(lookups);
var greens = paletteBuffer.Greens; foreach (var pixelLine in image.PixelLines)
var blues = paletteBuffer.Blues;
var sums = paletteBuffer.Sums;
PaletteLookup lookup = new PaletteLookup(lookups);
foreach (Pixel pixel in image.Pixels)
{ {
byte bestMatch = 255; for (int pixelIndex = 0; pixelIndex < pixelLine.Length; pixelIndex++)
if (pixel.Alpha >= alphaThreshold)
{ {
bestMatch = lookup.GetPaletteIndex(pixel); Pixel pixel = pixelLine[pixelIndex];
byte bestMatch = AlphaColor;
alphas[bestMatch] += pixel.Alpha; if (pixel.Alpha >= alphaThreshold)
reds[bestMatch] += pixel.Red; {
greens[bestMatch] += pixel.Green; bestMatch = lookup.GetPaletteIndex(pixel);
blues[bestMatch] += pixel.Blue; paletteHistogram[bestMatch].AddPixel(pixel);
sums[bestMatch]++; }
lineIndexes[pixelIndex] = bestMatch;
} }
yield return lineIndexes;
yield return bestMatch;
} }
} }
internal override Image GetQuantizedImage(ImageBuffer image, int colorCount, Pixel[] lookups, int alphaThreshold)
internal override Image GetQuantizedImage(ImageBuffer image, int colorCount, List<Pixel> lookups, int alphaThreshold)
{ {
var result = new Bitmap(image.Image.Width, image.Image.Height, PixelFormat.Format8bppIndexed); var result = new Bitmap(image.Image.Width, image.Image.Height, PixelFormat.Format8bppIndexed);
result.SetResolution(image.Image.HorizontalResolution, image.Image.VerticalResolution);
var resultBuffer = new ImageBuffer(result); var resultBuffer = new ImageBuffer(result);
PaletteBuffer paletteBuffer = new PaletteBuffer(colorCount); var paletteHistogram = new PaletteColorHistory[colorCount + 1];
resultBuffer.UpdatePixelIndexes(indexedPixels(image, lookups, alphaThreshold, paletteBuffer)); resultBuffer.UpdatePixelIndexes(IndexedPixels(image, lookups, alphaThreshold, paletteHistogram));
result.Palette = paletteBuffer.BuildPalette(result.Palette); result.Palette = BuildPalette(result.Palette, paletteHistogram);
return result; return result;
} }
private static ColorPalette BuildPalette(ColorPalette palette, PaletteColorHistory[] paletteHistogram)
{
for (int paletteColorIndex = 0; paletteColorIndex < paletteHistogram.Length; paletteColorIndex++)
{
palette.Entries[paletteColorIndex] = paletteHistogram[paletteColorIndex].ToNormalizedColor();
}
return palette;
}
} }
} }

201
src/ImageProcessor/Imaging/Quantizers/WuQuantizer/WuQuantizerBase.cs

@ -1,15 +1,28 @@
using System; using System;
using System.Collections.Generic;
using System.Drawing; using System.Drawing;
using System.Drawing.Imaging;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices;
namespace nQuant namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
{ {
public class Histogram
{
private const int SideSize = 33;
internal readonly ColorMoment[, , ,] Moments;
public Histogram()
{
// 47,436,840 bytes
Moments = new ColorMoment[SideSize, SideSize, SideSize, SideSize];
}
internal void Clear()
{
Array.Clear(Moments, 0, SideSize*SideSize*SideSize*SideSize);
}
}
public abstract class WuQuantizerBase public abstract class WuQuantizerBase
{ {
private const int MaxColor = 256;
protected const byte AlphaColor = 255; protected const byte AlphaColor = 255;
protected const int Alpha = 3; protected const int Alpha = 3;
protected const int Red = 2; protected const int Red = 2;
@ -25,66 +38,82 @@ namespace nQuant
public Image QuantizeImage(Bitmap image, int alphaThreshold, int alphaFader) public Image QuantizeImage(Bitmap image, int alphaThreshold, int alphaFader)
{ {
var colorCount = MaxColor; return QuantizeImage(image, alphaThreshold, alphaFader, null, 256);
ImageBuffer buffer = new ImageBuffer(image); }
var data = BuildHistogram(buffer, alphaThreshold, alphaFader);
public Image QuantizeImage(Bitmap image, int alphaThreshold, int alphaFader, Histogram histogram, int maxColors)
data = CalculateMoments(data); {
var cubes = SplitData(ref colorCount, data); var buffer = new ImageBuffer(image);
var lookups = BuildLookups(cubes, data);
return GetQuantizedImage(buffer, colorCount, lookups, alphaThreshold); if (histogram == null)
histogram = new Histogram();
else
histogram.Clear();
BuildHistogram(histogram, buffer, alphaThreshold, alphaFader);
CalculateMoments(histogram.Moments);
var cubes = SplitData(ref maxColors, histogram.Moments);
var lookups = BuildLookups(cubes, histogram.Moments);
return GetQuantizedImage(buffer, maxColors, lookups, alphaThreshold);
} }
private static ColorData BuildHistogram(ImageBuffer sourceImage, int alphaThreshold, int alphaFader) private static void BuildHistogram(Histogram histogram, ImageBuffer sourceImage, int alphaThreshold, int alphaFader)
{ {
ColorData colorData = new ColorData(MaxSideIndex); var moments = histogram.Moments;
foreach (Pixel pixel in sourceImage.Pixels)
foreach(var pixelLine in sourceImage.PixelLines)
{ {
if (pixel.Alpha >= alphaThreshold) for (int pixelIndex = 0; pixelIndex < pixelLine.Length; pixelIndex++)
{ {
Pixel indexedPixel = pixel; Pixel pixel = pixelLine[pixelIndex];
if (indexedPixel.Alpha < 255) byte pixelAlpha = pixel.Alpha;
if (pixelAlpha >= alphaThreshold)
{ {
int alpha = pixel.Alpha + (pixel.Alpha % alphaFader); if (pixelAlpha < 255)
indexedPixel.Alpha = (byte)(alpha > 255 ? 255 : alpha); {
} var alpha = pixel.Alpha + (pixel.Alpha % alphaFader);
pixelAlpha = (byte)(alpha > 255 ? 255 : alpha);
}
indexedPixel.Alpha = (byte)((indexedPixel.Alpha >> 3) + 1); byte pixelRed = pixel.Red;
indexedPixel.Red = (byte)((indexedPixel.Red >> 3) + 1); byte pixelGreen = pixel.Green;
indexedPixel.Green = (byte)((indexedPixel.Green >> 3) + 1); byte pixelBlue = pixel.Blue;
indexedPixel.Blue = (byte)((indexedPixel.Blue >> 3) + 1);
colorData.Moments[indexedPixel.Alpha, indexedPixel.Red, indexedPixel.Green, indexedPixel.Blue].Add(pixel); pixelAlpha = (byte)((pixelAlpha >> 3) + 1);
pixelRed = (byte)((pixelRed >> 3) + 1);
pixelGreen = (byte)((pixelGreen >> 3) + 1);
pixelBlue = (byte)((pixelBlue >> 3) + 1);
moments[pixelAlpha, pixelRed, pixelGreen, pixelBlue].Add(pixel);
}
} }
} }
return colorData;
} }
private static ColorData CalculateMoments(ColorData data) private static void CalculateMoments(ColorMoment[, , ,] moments)
{ {
var xarea = new ColorMoment[SideSize, SideSize]; var xarea = new ColorMoment[SideSize, SideSize];
var area = new ColorMoment[SideSize]; var area = new ColorMoment[SideSize];
var moments = data.Moments; for (var alphaIndex = 1; alphaIndex < SideSize; alphaIndex++)
for (var alphaIndex = 1; alphaIndex <= MaxSideIndex; ++alphaIndex)
{ {
for (var redIndex = 1; redIndex <= MaxSideIndex; ++redIndex) for (var redIndex = 1; redIndex < SideSize; redIndex++)
{ {
Array.Clear(area, 0, area.Length); Array.Clear(area, 0, area.Length);
for (var greenIndex = 1; greenIndex <= MaxSideIndex; ++greenIndex) for (var greenIndex = 1; greenIndex < SideSize; greenIndex++)
{ {
ColorMoment line = new ColorMoment(); var line = new ColorMoment();
for (var blueIndex = 1; blueIndex <= MaxSideIndex; ++blueIndex) for (var blueIndex = 1; blueIndex < SideSize; blueIndex++)
{ {
line.AddFast(ref moments[alphaIndex, redIndex, greenIndex, blueIndex]); line.AddFast(ref moments[alphaIndex, redIndex, greenIndex, blueIndex]);
area[blueIndex].AddFast(ref line); area[blueIndex].AddFast(ref line);
xarea[greenIndex, blueIndex].AddFast(ref area[blueIndex]); xarea[greenIndex, blueIndex].AddFast(ref area[blueIndex]);
moments[alphaIndex, redIndex, greenIndex, blueIndex] = moments[alphaIndex - 1, redIndex, greenIndex, blueIndex] + xarea[greenIndex, blueIndex];
ColorMoment moment = moments[alphaIndex - 1, redIndex, greenIndex, blueIndex];
moment.AddFast(ref xarea[greenIndex, blueIndex]);
moments[alphaIndex, redIndex, greenIndex, blueIndex] = moment;
} }
} }
} }
} }
return data;
} }
private static ColorMoment Top(Box cube, int direction, int position, ColorMoment[, , ,] moment) private static ColorMoment Top(Box cube, int direction, int position, ColorMoment[, , ,] moment)
@ -185,21 +214,19 @@ namespace nQuant
} }
} }
private static CubeCut Maximize(ColorData data, Box cube, int direction, byte first, byte last, ColorMoment whole) private static CubeCut Maximize(ColorMoment[, , ,] moments, Box cube, int direction, byte first, byte last, ColorMoment whole)
{ {
var bottom = Bottom(cube, direction, data.Moments); var bottom = Bottom(cube, direction, moments);
float result = 0.0f; var result = 0.0f;
byte? cutPoint = null; byte? cutPoint = null;
for (byte position = first; position < last; ++position) for (var position = first; position < last; ++position)
{ {
var half = bottom + Top(cube, direction, position, data.Moments); var half = bottom + Top(cube, direction, position, moments);
if (half.Weight == 0) if (half.Weight == 0) continue;
{
continue; var temp = half.WeightedDistance();
}
long temp = half.WeightedDistance();
half = whole - half; half = whole - half;
if (half.Weight != 0) if (half.Weight != 0)
{ {
@ -216,14 +243,14 @@ namespace nQuant
return new CubeCut(cutPoint, result); return new CubeCut(cutPoint, result);
} }
private bool Cut(ColorData data, ref Box first, ref Box second) private static bool Cut(ColorMoment[, , ,] moments, ref Box first, ref Box second)
{ {
int direction; int direction;
var whole = Volume(first, data.Moments); var whole = Volume(first, moments);
var maxAlpha = Maximize(data, first, Alpha, (byte)(first.AlphaMinimum + 1), first.AlphaMaximum, whole); var maxAlpha = Maximize(moments, first, Alpha, (byte)(first.AlphaMinimum + 1), first.AlphaMaximum, whole);
var maxRed = Maximize(data, first, Red, (byte)(first.RedMinimum + 1), first.RedMaximum, whole); var maxRed = Maximize(moments, first, Red, (byte)(first.RedMinimum + 1), first.RedMaximum, whole);
var maxGreen = Maximize(data, first, Green, (byte)(first.GreenMinimum + 1), first.GreenMaximum, whole); var maxGreen = Maximize(moments, first, Green, (byte)(first.GreenMinimum + 1), first.GreenMaximum, whole);
var maxBlue = Maximize(data, first, Blue, (byte)(first.BlueMinimum + 1), first.BlueMaximum, whole); var maxBlue = Maximize(moments, first, Blue, (byte)(first.BlueMinimum + 1), first.BlueMaximum, whole);
if ((maxAlpha.Value >= maxRed.Value) && (maxAlpha.Value >= maxGreen.Value) && (maxAlpha.Value >= maxBlue.Value)) if ((maxAlpha.Value >= maxRed.Value) && (maxAlpha.Value >= maxGreen.Value) && (maxAlpha.Value >= maxBlue.Value))
{ {
@ -248,28 +275,28 @@ namespace nQuant
switch (direction) switch (direction)
{ {
case Alpha: case Alpha:
second.AlphaMinimum = first.AlphaMaximum = (byte)maxAlpha.Position; second.AlphaMinimum = first.AlphaMaximum = (byte) maxAlpha.Position;
second.RedMinimum = first.RedMinimum; second.RedMinimum = first.RedMinimum;
second.GreenMinimum = first.GreenMinimum; second.GreenMinimum = first.GreenMinimum;
second.BlueMinimum = first.BlueMinimum; second.BlueMinimum = first.BlueMinimum;
break; break;
case Red: case Red:
second.RedMinimum = first.RedMaximum = (byte)maxRed.Position; second.RedMinimum = first.RedMaximum = (byte) maxRed.Position;
second.AlphaMinimum = first.AlphaMinimum; second.AlphaMinimum = first.AlphaMinimum;
second.GreenMinimum = first.GreenMinimum; second.GreenMinimum = first.GreenMinimum;
second.BlueMinimum = first.BlueMinimum; second.BlueMinimum = first.BlueMinimum;
break; break;
case Green: case Green:
second.GreenMinimum = first.GreenMaximum = (byte)maxGreen.Position; second.GreenMinimum = first.GreenMaximum = (byte) maxGreen.Position;
second.AlphaMinimum = first.AlphaMinimum; second.AlphaMinimum = first.AlphaMinimum;
second.RedMinimum = first.RedMinimum; second.RedMinimum = first.RedMinimum;
second.BlueMinimum = first.BlueMinimum; second.BlueMinimum = first.BlueMinimum;
break; break;
case Blue: case Blue:
second.BlueMinimum = first.BlueMaximum = (byte)maxBlue.Position; second.BlueMinimum = first.BlueMaximum = (byte) maxBlue.Position;
second.AlphaMinimum = first.AlphaMinimum; second.AlphaMinimum = first.AlphaMinimum;
second.RedMinimum = first.RedMinimum; second.RedMinimum = first.RedMinimum;
second.GreenMinimum = first.GreenMinimum; second.GreenMinimum = first.GreenMinimum;
@ -282,9 +309,9 @@ namespace nQuant
return true; return true;
} }
private static float CalculateVariance(ColorData data, Box cube) private static float CalculateVariance(ColorMoment[, , ,] moments, Box cube)
{ {
ColorMoment volume = Volume(cube, data.Moments); ColorMoment volume = Volume(cube, moments);
return volume.Variance(); return volume.Variance();
} }
@ -309,43 +336,22 @@ namespace nQuant
moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum]); moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum]);
} }
private static float VolumeFloat(Box cube, float[, , ,] moment) private static Box[] SplitData(ref int colorCount, ColorMoment[, , ,] moments)
{
return (moment[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMaximum, cube.BlueMaximum] -
moment[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMaximum] -
moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMaximum] +
moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMaximum] -
moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMaximum, cube.BlueMaximum] +
moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMaximum] +
moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMaximum] -
moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMaximum]) -
(moment[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMaximum, cube.BlueMinimum] -
moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMaximum, cube.BlueMinimum] -
moment[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMinimum] +
moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMinimum] -
moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMinimum] +
moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMinimum] +
moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum] -
moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum]);
}
private Box[] SplitData(ref int colorCount, ColorData data)
{ {
--colorCount; --colorCount;
var next = 0; var next = 0;
var volumeVariance = new float[MaxColor]; var volumeVariance = new float[colorCount];
var cubes = new Box[MaxColor]; var cubes = new Box[colorCount];
cubes[0].AlphaMaximum = MaxSideIndex; cubes[0].AlphaMaximum = MaxSideIndex;
cubes[0].RedMaximum = MaxSideIndex; cubes[0].RedMaximum = MaxSideIndex;
cubes[0].GreenMaximum = MaxSideIndex; cubes[0].GreenMaximum = MaxSideIndex;
cubes[0].BlueMaximum = MaxSideIndex; cubes[0].BlueMaximum = MaxSideIndex;
for (var cubeIndex = 1; cubeIndex < colorCount; ++cubeIndex) for (var cubeIndex = 1; cubeIndex < colorCount; ++cubeIndex)
{ {
if (Cut(data, ref cubes[next], ref cubes[cubeIndex])) if (Cut(moments, ref cubes[next], ref cubes[cubeIndex]))
{ {
volumeVariance[next] = cubes[next].Size > 1 ? CalculateVariance(data, cubes[next]) : 0.0f; volumeVariance[next] = cubes[next].Size > 1 ? CalculateVariance(moments, cubes[next]) : 0.0f;
volumeVariance[cubeIndex] = cubes[cubeIndex].Size > 1 ? CalculateVariance(data, cubes[cubeIndex]) : 0.0f; volumeVariance[cubeIndex] = cubes[cubeIndex].Size > 1 ? CalculateVariance(moments, cubes[cubeIndex]) : 0.0f;
} }
else else
{ {
@ -370,18 +376,15 @@ namespace nQuant
return cubes.Take(colorCount).ToArray(); return cubes.Take(colorCount).ToArray();
} }
private List<Pixel> BuildLookups(Box[] cubes, ColorData data) private static Pixel[] BuildLookups(Box[] cubes, ColorMoment[, , ,] moments)
{ {
List<Pixel> lookups = new List<Pixel>(cubes.Length); Pixel[] lookups = new Pixel[cubes.Length];
foreach (var cube in cubes) for (int cubeIndex = 0; cubeIndex < cubes.Length; cubeIndex++)
{ {
var volume = Volume(cube, data.Moments); var volume = Volume(cubes[cubeIndex], moments);
if (volume.Weight <= 0) if (volume.Weight <= 0) continue;
{
continue;
}
var lookup = new Pixel var lookup = new Pixel
{ {
@ -390,13 +393,11 @@ namespace nQuant
Green = (byte)(volume.Green / volume.Weight), Green = (byte)(volume.Green / volume.Weight),
Blue = (byte)(volume.Blue / volume.Weight) Blue = (byte)(volume.Blue / volume.Weight)
}; };
lookups[cubeIndex] = lookup;
lookups.Add(lookup);
} }
return lookups; return lookups;
} }
internal abstract Image GetQuantizedImage(ImageBuffer image, int colorCount, List<Pixel> lookups, int alphaThreshold); internal abstract Image GetQuantizedImage(ImageBuffer image, int colorCount, Pixel[] lookups, int alphaThreshold);
} }
} }
Loading…
Cancel
Save