diff --git a/src/ImageProcessor/ImageProcessor.csproj b/src/ImageProcessor/ImageProcessor.csproj
index 615d828f7..1bea02cb9 100644
--- a/src/ImageProcessor/ImageProcessor.csproj
+++ b/src/ImageProcessor/ImageProcessor.csproj
@@ -185,14 +185,12 @@
-
-
-
+
+
-
Code
diff --git a/src/ImageProcessor/Imaging/Formats/PngFormat.cs b/src/ImageProcessor/Imaging/Formats/PngFormat.cs
index 528740a4b..73d2e3146 100644
--- a/src/ImageProcessor/Imaging/Formats/PngFormat.cs
+++ b/src/ImageProcessor/Imaging/Formats/PngFormat.cs
@@ -16,8 +16,7 @@ namespace ImageProcessor.Imaging.Formats
using ImageProcessor.Common.Extensions;
using ImageProcessor.Imaging.Quantizers;
-
- using nQuant;
+ using ImageProcessor.Imaging.Quantizers.WuQuantizer;
///
/// Provides the necessary information to support png images.
diff --git a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/Box.cs b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/Box.cs
index 9ec158be5..23e159c22 100644
--- a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/Box.cs
+++ b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/Box.cs
@@ -1,15 +1,65 @@
-namespace nQuant
+// --------------------------------------------------------------------------------------------------------------------
+//
+// Copyright (c) James South.
+// Licensed under the Apache License, Version 2.0.
+//
+//
+// The box for storing color attributes.
+// Adapted from
+//
+// --------------------------------------------------------------------------------------------------------------------
+
+namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
{
+ ///
+ /// The box for storing color attributes.
+ /// Adapted from
+ ///
public struct Box
{
- public byte AlphaMinimum;
+ ///
+ /// The alpha maximum.
+ ///
public byte AlphaMaximum;
- public byte RedMinimum;
- public byte RedMaximum;
- public byte GreenMinimum;
- public byte GreenMaximum;
- public byte BlueMinimum;
+
+ ///
+ /// The alpha minimum.
+ ///
+ public byte AlphaMinimum;
+
+ ///
+ /// The blue maximum.
+ ///
public byte BlueMaximum;
+
+ ///
+ /// The blue minimum.
+ ///
+ public byte BlueMinimum;
+
+ ///
+ /// The green maximum.
+ ///
+ public byte GreenMaximum;
+
+ ///
+ /// The green minimum.
+ ///
+ public byte GreenMinimum;
+
+ ///
+ /// The red maximum.
+ ///
+ public byte RedMaximum;
+
+ ///
+ /// The red minimum.
+ ///
+ public byte RedMinimum;
+
+ ///
+ /// The size.
+ ///
public int Size;
}
}
\ No newline at end of file
diff --git a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ColorData.cs b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ColorData.cs
deleted file mode 100644
index 435c53669..000000000
--- a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ColorData.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-// --------------------------------------------------------------------------------------------------------------------
-//
-// Copyright (c) James South.
-// Licensed under the Apache License, Version 2.0.
-//
-// --------------------------------------------------------------------------------------------------------------------
-
-namespace nQuant
-{
- ///
- /// The color data.
- ///
- public class ColorData
- {
- ///
- /// Initializes a new instance of the class.
- ///
- ///
- /// The data granularity.
- ///
- public ColorData(int dataGranularity)
- {
- dataGranularity++;
-
- this.Moments = new ColorMoment[dataGranularity, dataGranularity, dataGranularity, dataGranularity];
- }
-
- ///
- /// Gets the moments.
- ///
- public ColorMoment[, , ,] Moments { get; private set; }
- }
-}
\ No newline at end of file
diff --git a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ColorMoment.cs b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ColorMoment.cs
index 1e751a73a..70573e988 100644
--- a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ColorMoment.cs
+++ b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ColorMoment.cs
@@ -1,7 +1,8 @@
-
-namespace nQuant
+//using System.Runtime.CompilerServices;
+
+namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
{
- public struct ColorMoment
+ struct ColorMoment
{
public long Alpha;
public long Red;
@@ -43,16 +44,22 @@ namespace nQuant
return c1;
}
+ //[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Add(Pixel p)
{
- Alpha += p.Alpha;
- Red += p.Red;
- Green += p.Green;
- Blue += p.Blue;
+ byte pAlpha = p.Alpha;
+ byte pRed = p.Red;
+ byte pGreen = p.Green;
+ byte pBlue = p.Blue;
+ Alpha += pAlpha;
+ Red += pRed;
+ Green += pGreen;
+ Blue += pBlue;
Weight++;
- Moment += p.Amplitude();
+ Moment += pAlpha * pAlpha + pRed * pRed + pGreen * pGreen + pBlue * pBlue;
}
+ //[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddFast(ref ColorMoment c2)
{
Alpha += c2.Alpha;
@@ -62,21 +69,21 @@ namespace nQuant
Weight += c2.Weight;
Moment += c2.Moment;
}
-
+
public long Amplitude()
{
- return (Alpha * Alpha) + (Red * Red) + (Green * Green) + (Blue * Blue);
+ return Alpha * Alpha + Red * Red + Green * Green + Blue * Blue;
}
public long WeightedDistance()
{
- return this.Amplitude() / Weight;
+ return Amplitude() / Weight;
}
public float Variance()
{
- var result = Moment - ((float)this.Amplitude() / this.Weight);
+ var result = Moment - (float)Amplitude() / Weight;
return float.IsNaN(result) ? 0.0f : result;
}
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/CubeCut.cs b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/CubeCut.cs
index 6500b2493..1d5412f6e 100644
--- a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/CubeCut.cs
+++ b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/CubeCut.cs
@@ -1,14 +1,45 @@
-namespace nQuant
+// --------------------------------------------------------------------------------------------------------------------
+//
+// Copyright (c) James South.
+// Licensed under the Apache License, Version 2.0.
+//
+//
+// Represents a cube cut.
+// Adapted from
+//
+// --------------------------------------------------------------------------------------------------------------------
+
+namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
{
+ ///
+ /// Represents a cube cut.
+ /// Adapted from
+ ///
internal struct CubeCut
{
+ ///
+ /// The position.
+ ///
public readonly byte? Position;
+
+ ///
+ /// The value.
+ ///
public readonly float Value;
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ ///
+ /// The cut point.
+ ///
+ ///
+ /// The result.
+ ///
public CubeCut(byte? cutPoint, float result)
{
- Position = cutPoint;
- Value = result;
+ this.Position = cutPoint;
+ this.Value = result;
}
}
}
\ No newline at end of file
diff --git a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/IWuQuantizer.cs b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/IWuQuantizer.cs
index 28f708cb8..1b06ba63e 100644
--- a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/IWuQuantizer.cs
+++ b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/IWuQuantizer.cs
@@ -1,9 +1,40 @@
-using System.Drawing;
+// --------------------------------------------------------------------------------------------------------------------
+//
+// Copyright (c) James South.
+// Licensed under the Apache License, Version 2.0.
+//
+//
+// The WuQuantizer interface.
+// Adapted from
+//
+// --------------------------------------------------------------------------------------------------------------------
-namespace nQuant
+namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
{
+ using System.Drawing;
+
+ ///
+ /// The WuQuantizer interface.
+ /// Adapted from
+ ///
public interface IWuQuantizer
{
+ ///
+ /// Quantizes the given image.
+ ///
+ ///
+ /// The 32 bit per pixel .
+ ///
+ ///
+ /// The alpha threshold. All colors with an alpha value less than this will be
+ /// considered fully transparent
+ ///
+ ///
+ /// The alpha fader. Alpha values will be normalized to the nearest multiple of this value.
+ ///
+ ///
+ /// The quantized .
+ ///
Image QuantizeImage(Bitmap image, int alphaThreshold, int alphaFader);
}
}
\ No newline at end of file
diff --git a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ImageBuffer.cs b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ImageBuffer.cs
index 70a48ab71..5341b1cbb 100644
--- a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ImageBuffer.cs
+++ b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ImageBuffer.cs
@@ -1,75 +1,106 @@
-using System;
-using System.Collections.Generic;
-using System.Drawing;
-using System.Drawing.Imaging;
-using System.Linq;
-using System.Runtime.InteropServices;
-using System.Text;
+// --------------------------------------------------------------------------------------------------------------------
+//
+// Copyright (c) James South.
+// Licensed under the Apache License, Version 2.0.
+//
+//
+// The image buffer for storing pixel information.
+// Adapted from
+//
+// --------------------------------------------------------------------------------------------------------------------
-namespace nQuant
+namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
{
- class ImageBuffer
+ using System.Collections.Generic;
+ using System.Drawing;
+ using System.Drawing.Imaging;
+ using System.Runtime.InteropServices;
+
+ ///
+ /// The image buffer for storing pixel information.
+ /// Adapted from
+ ///
+ internal class ImageBuffer
{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The image to store.
+ ///
public ImageBuffer(Bitmap image)
{
this.Image = image;
}
- public Bitmap Image { get; set; }
-
- protected const int Alpha = 3;
- protected const int Red = 2;
- protected const int Green = 1;
- protected const int Blue = 0;
+ ///
+ /// Gets the image.
+ ///
+ public Bitmap Image { get; private set; }
- public IEnumerable Pixels
+ ///
+ /// Gets the pixel lines.
+ ///
+ ///
+ /// Thrown if the given image is not a 32 bit per pixel image.
+ ///
+ public IEnumerable PixelLines
{
get
{
- var bitDepth = System.Drawing.Image.GetPixelFormatSize(Image.PixelFormat);
+ int bitDepth = System.Drawing.Image.GetPixelFormatSize(this.Image.PixelFormat);
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 height = this.Image.Height;
int[] buffer = new int[width];
+ Pixel[] pixels = new Pixel[width];
for (int rowIndex = 0; rowIndex < height; rowIndex++)
{
BitmapData data = this.Image.LockBits(Rectangle.FromLTRB(0, rowIndex, width, rowIndex + 1), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
try
{
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
{
this.Image.UnlockBits(data);
}
+
+ yield return pixels;
}
}
}
- public void UpdatePixelIndexes(IEnumerable indexes)
+ ///
+ /// Updates the pixel indexes.
+ ///
+ ///
+ /// The line indexes.
+ ///
+ public void UpdatePixelIndexes(IEnumerable lineIndexes)
{
int width = this.Image.Width;
int height = this.Image.Height;
- byte[] buffer = new byte[width];
- IEnumerator indexesIterator = indexes.GetEnumerator();
+ var indexesIterator = lineIndexes.GetEnumerator();
for (int rowIndex = 0; rowIndex < height; rowIndex++)
{
- for (int columnIndex = 0; columnIndex < buffer.Length; columnIndex++)
- {
- indexesIterator.MoveNext();
- buffer[columnIndex] = indexesIterator.Current;
- }
-
+ indexesIterator.MoveNext();
BitmapData data = this.Image.LockBits(Rectangle.FromLTRB(0, rowIndex, width, rowIndex + 1), ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);
try
{
- Marshal.Copy(buffer, 0, data.Scan0, width);
+ Marshal.Copy(indexesIterator.Current, 0, data.Scan0, width);
}
finally
{
@@ -78,4 +109,4 @@ namespace nQuant
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/Lookup.cs b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/Lookup.cs
deleted file mode 100644
index 88a702e9c..000000000
--- a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/Lookup.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-namespace nQuant
-{
- public class Lookup
- {
- public int Alpha;
- public int Red;
- public int Green;
- public int Blue;
- }
-}
\ No newline at end of file
diff --git a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/PaletteBuffer.cs b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/PaletteBuffer.cs
deleted file mode 100644
index ac7ba623c..000000000
--- a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/PaletteBuffer.cs
+++ /dev/null
@@ -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; }
- }
-}
\ No newline at end of file
diff --git a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/PaletteColorHistory.cs b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/PaletteColorHistory.cs
new file mode 100644
index 000000000..973f92884
--- /dev/null
+++ b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/PaletteColorHistory.cs
@@ -0,0 +1,73 @@
+// --------------------------------------------------------------------------------------------------------------------
+//
+// Copyright (c) James South.
+// Licensed under the Apache License, Version 2.0.
+//
+//
+// The palette color history.
+// Adapted from
+//
+// --------------------------------------------------------------------------------------------------------------------
+
+namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
+{
+ using System.Drawing;
+
+ ///
+ /// The palette color history.
+ /// Adapted from
+ ///
+ internal struct PaletteColorHistory
+ {
+ ///
+ /// The alpha component.
+ ///
+ public int Alpha;
+
+ ///
+ /// The red component.
+ ///
+ public int Red;
+
+ ///
+ /// The green component.
+ ///
+ public int Green;
+
+ ///
+ /// The blue component.
+ ///
+ public int Blue;
+
+ ///
+ /// The sum of the color components.
+ ///
+ public int Sum;
+
+ ///
+ /// Normalizes the color.
+ ///
+ ///
+ /// The normalized .
+ ///
+ 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;
+ }
+
+ ///
+ /// Adds a pixel to the color history.
+ ///
+ ///
+ /// The pixel.
+ ///
+ public void AddPixel(Pixel pixel)
+ {
+ this.Alpha += pixel.Alpha;
+ this.Red += pixel.Red;
+ this.Green += pixel.Green;
+ this.Blue += pixel.Blue;
+ this.Sum++;
+ }
+ }
+}
diff --git a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/PaletteLookup.cs b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/PaletteLookup.cs
index 8babb206c..43ba5d04b 100644
--- a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/PaletteLookup.cs
+++ b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/PaletteLookup.cs
@@ -1,46 +1,43 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-
-namespace nQuant
+namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+
class PaletteLookup
{
private int mMask;
- private Dictionary> mLookup = new Dictionary>(255);
- private List Palette { get; set; }
+ private Dictionary mLookup;
+ private LookupNode[] Palette { get; set; }
- public PaletteLookup(List palette)
+ public PaletteLookup(Pixel[] palette)
{
- Palette = new List(palette.Count);
- for (int paletteIndex = 0; paletteIndex < palette.Count; paletteIndex++)
+ Palette = new LookupNode[palette.Length];
+ 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);
}
public byte GetPaletteIndex(Pixel pixel)
{
-
int pixelKey = pixel.Argb & mMask;
- List bucket;
+ LookupNode[] bucket;
if (!mLookup.TryGetValue(pixelKey, out bucket))
{
bucket = Palette;
}
- if (bucket.Count == 1)
+ if (bucket.Length == 1)
{
return bucket[0].PaletteIndex;
}
int bestDistance = int.MaxValue;
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 deltaAlpha = pixel.Alpha - lookupPixel.Alpha;
@@ -61,29 +58,41 @@ namespace nQuant
bestDistance = distance;
bestMatch = lookup.PaletteIndex;
}
+
+ if ((bucket == Palette) && (pixelKey != 0))
+ {
+ mLookup[pixelKey] = new LookupNode[] { bucket[bestMatch] };
+ }
+
return bestMatch;
}
- private void BuildLookup(List palette)
+ private void BuildLookup(Pixel[] palette)
{
int mask = GetMask(palette);
+ Dictionary> tempLookup = new Dictionary>();
foreach (LookupNode lookup in Palette)
{
int pixelKey = lookup.Pixel.Argb & mask;
List bucket;
- if (!mLookup.TryGetValue(pixelKey, out bucket))
+ if (!tempLookup.TryGetValue(pixelKey, out bucket))
{
bucket = new List();
- mLookup[pixelKey] = bucket;
+ tempLookup[pixelKey] = bucket;
}
bucket.Add(lookup);
}
+ mLookup = new Dictionary(tempLookup.Count);
+ foreach (var key in tempLookup.Keys)
+ {
+ mLookup[key] = tempLookup[key].ToArray();
+ }
mMask = mask;
}
- private static int GetMask(List palette)
+ private static int GetMask(Pixel[] palette)
{
IEnumerable alphas = from pixel in palette
select pixel.Alpha;
@@ -107,12 +116,12 @@ namespace nQuant
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 redMask = ComputeBitMask(maxRed, Convert.ToInt32(Math.Round(uniqueReds / 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);
return maskedPixel.Argb;
diff --git a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/Pixel.cs b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/Pixel.cs
index 463059351..9fe10c252 100644
--- a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/Pixel.cs
+++ b/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)]
public struct Pixel
{
@@ -12,32 +12,27 @@ namespace nQuant
Red = red;
Green = green;
Blue = blue;
+
+ Debug.Assert(Argb == (alpha << 24 | red << 16 | green << 8 | blue));
}
- ///
- /// Initializes a new instance of the struct.
- ///
- ///
- /// The combined color components.
- ///
public Pixel(int argb)
: this()
{
- this.Argb = argb;
- }
-
- public long Amplitude()
- {
- return (Alpha * Alpha) + (Red * Red) + (Green * Green) + (Blue * Blue);
+ Argb = argb;
+ Debug.Assert(Alpha == ((uint)argb >> 24));
+ Debug.Assert(Red == ((uint)(argb >> 16) & 255));
+ Debug.Assert(Green == ((uint)(argb >> 8) & 255));
+ Debug.Assert(Blue == ((uint)argb & 255));
}
- [FieldOffsetAttribute(3)]
+ [FieldOffset(3)]
public byte Alpha;
- [FieldOffsetAttribute(2)]
+ [FieldOffset(2)]
public byte Red;
- [FieldOffsetAttribute(1)]
+ [FieldOffset(1)]
public byte Green;
- [FieldOffsetAttribute(0)]
+ [FieldOffset(0)]
public byte Blue;
[FieldOffset(0)]
public int Argb;
diff --git a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/QuantizationException.cs b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/QuantizationException.cs
index da644a5f5..e723f7dc1 100644
--- a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/QuantizationException.cs
+++ b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/QuantizationException.cs
@@ -3,8 +3,9 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
-namespace nQuant
+namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
{
+ [Serializable]
public class QuantizationException : ApplicationException
{
public QuantizationException(string message) : base(message)
diff --git a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/WuQuantizer.cs b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/WuQuantizer.cs
index 34a5b1b99..9046c9082 100644
--- a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/WuQuantizer.cs
+++ b/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.Imaging;
-namespace nQuant
+namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
{
- using System.Drawing.Imaging;
-
public class WuQuantizer : WuQuantizerBase, IWuQuantizer
{
- private IEnumerable indexedPixels(ImageBuffer image, List lookups, int alphaThreshold, PaletteBuffer paletteBuffer)
+ private static IEnumerable IndexedPixels(ImageBuffer image, Pixel[] lookups, int alphaThreshold, PaletteColorHistory[] paletteHistogram)
{
- var alphas = paletteBuffer.Alphas;
- var reds = paletteBuffer.Reds;
- var greens = paletteBuffer.Greens;
- var blues = paletteBuffer.Blues;
- var sums = paletteBuffer.Sums;
-
- PaletteLookup lookup = new PaletteLookup(lookups);
-
- foreach (Pixel pixel in image.Pixels)
+ var lineIndexes = new byte[image.Image.Width];
+ var lookup = new PaletteLookup(lookups);
+ foreach (var pixelLine in image.PixelLines)
{
- byte bestMatch = 255;
-
- if (pixel.Alpha >= alphaThreshold)
+ for (int pixelIndex = 0; pixelIndex < pixelLine.Length; pixelIndex++)
{
- bestMatch = lookup.GetPaletteIndex(pixel);
-
- alphas[bestMatch] += pixel.Alpha;
- reds[bestMatch] += pixel.Red;
- greens[bestMatch] += pixel.Green;
- blues[bestMatch] += pixel.Blue;
- sums[bestMatch]++;
+ Pixel pixel = pixelLine[pixelIndex];
+ byte bestMatch = AlphaColor;
+ if (pixel.Alpha >= alphaThreshold)
+ {
+ bestMatch = lookup.GetPaletteIndex(pixel);
+ paletteHistogram[bestMatch].AddPixel(pixel);
+ }
+ lineIndexes[pixelIndex] = bestMatch;
}
-
- yield return bestMatch;
+ yield return lineIndexes;
}
}
-
- internal override Image GetQuantizedImage(ImageBuffer image, int colorCount, List lookups, int alphaThreshold)
+ internal override Image GetQuantizedImage(ImageBuffer image, int colorCount, Pixel[] lookups, int alphaThreshold)
{
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);
- PaletteBuffer paletteBuffer = new PaletteBuffer(colorCount);
- resultBuffer.UpdatePixelIndexes(indexedPixels(image, lookups, alphaThreshold, paletteBuffer));
- result.Palette = paletteBuffer.BuildPalette(result.Palette);
+ var paletteHistogram = new PaletteColorHistory[colorCount + 1];
+ resultBuffer.UpdatePixelIndexes(IndexedPixels(image, lookups, alphaThreshold, paletteHistogram));
+ result.Palette = BuildPalette(result.Palette, paletteHistogram);
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;
+ }
}
}
diff --git a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/WuQuantizerBase.cs b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/WuQuantizerBase.cs
index b8aeb13df..1bdb2ea13 100644
--- a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/WuQuantizerBase.cs
+++ b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/WuQuantizerBase.cs
@@ -1,15 +1,28 @@
using System;
-using System.Collections.Generic;
using System.Drawing;
-using System.Drawing.Imaging;
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
{
- private const int MaxColor = 256;
protected const byte AlphaColor = 255;
protected const int Alpha = 3;
protected const int Red = 2;
@@ -25,66 +38,82 @@ namespace nQuant
public Image QuantizeImage(Bitmap image, int alphaThreshold, int alphaFader)
{
- var colorCount = MaxColor;
- ImageBuffer buffer = new ImageBuffer(image);
- var data = BuildHistogram(buffer, alphaThreshold, alphaFader);
-
- data = CalculateMoments(data);
- var cubes = SplitData(ref colorCount, data);
- var lookups = BuildLookups(cubes, data);
- return GetQuantizedImage(buffer, colorCount, lookups, alphaThreshold);
+ return QuantizeImage(image, alphaThreshold, alphaFader, null, 256);
+ }
+
+ public Image QuantizeImage(Bitmap image, int alphaThreshold, int alphaFader, Histogram histogram, int maxColors)
+ {
+ var buffer = new ImageBuffer(image);
+
+ 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);
- foreach (Pixel pixel in sourceImage.Pixels)
+ var moments = histogram.Moments;
+
+ foreach(var pixelLine in sourceImage.PixelLines)
{
- if (pixel.Alpha >= alphaThreshold)
+ for (int pixelIndex = 0; pixelIndex < pixelLine.Length; pixelIndex++)
{
- Pixel indexedPixel = pixel;
- if (indexedPixel.Alpha < 255)
+ Pixel pixel = pixelLine[pixelIndex];
+ byte pixelAlpha = pixel.Alpha;
+ if (pixelAlpha >= alphaThreshold)
{
- int alpha = pixel.Alpha + (pixel.Alpha % alphaFader);
- indexedPixel.Alpha = (byte)(alpha > 255 ? 255 : alpha);
- }
+ if (pixelAlpha < 255)
+ {
+ var alpha = pixel.Alpha + (pixel.Alpha % alphaFader);
+ pixelAlpha = (byte)(alpha > 255 ? 255 : alpha);
+ }
- indexedPixel.Alpha = (byte)((indexedPixel.Alpha >> 3) + 1);
- indexedPixel.Red = (byte)((indexedPixel.Red >> 3) + 1);
- indexedPixel.Green = (byte)((indexedPixel.Green >> 3) + 1);
- indexedPixel.Blue = (byte)((indexedPixel.Blue >> 3) + 1);
- colorData.Moments[indexedPixel.Alpha, indexedPixel.Red, indexedPixel.Green, indexedPixel.Blue].Add(pixel);
+ byte pixelRed = pixel.Red;
+ byte pixelGreen = pixel.Green;
+ byte pixelBlue = pixel.Blue;
+
+ 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 area = new ColorMoment[SideSize];
- var moments = data.Moments;
-
- for (var alphaIndex = 1; alphaIndex <= MaxSideIndex; ++alphaIndex)
+ for (var alphaIndex = 1; alphaIndex < SideSize; alphaIndex++)
{
- for (var redIndex = 1; redIndex <= MaxSideIndex; ++redIndex)
+ for (var redIndex = 1; redIndex < SideSize; redIndex++)
{
Array.Clear(area, 0, area.Length);
- for (var greenIndex = 1; greenIndex <= MaxSideIndex; ++greenIndex)
+ for (var greenIndex = 1; greenIndex < SideSize; greenIndex++)
{
- ColorMoment line = new ColorMoment();
- for (var blueIndex = 1; blueIndex <= MaxSideIndex; ++blueIndex)
+ var line = new ColorMoment();
+ for (var blueIndex = 1; blueIndex < SideSize; blueIndex++)
{
line.AddFast(ref moments[alphaIndex, redIndex, greenIndex, blueIndex]);
area[blueIndex].AddFast(ref line);
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)
@@ -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);
- float result = 0.0f;
+ var bottom = Bottom(cube, direction, moments);
+ var result = 0.0f;
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);
- if (half.Weight == 0)
- {
- continue;
- }
+ var half = bottom + Top(cube, direction, position, moments);
+ if (half.Weight == 0) continue;
+
+ var temp = half.WeightedDistance();
- long temp = half.WeightedDistance();
half = whole - half;
if (half.Weight != 0)
{
@@ -216,14 +243,14 @@ namespace nQuant
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;
- var whole = Volume(first, data.Moments);
- var maxAlpha = Maximize(data, first, Alpha, (byte)(first.AlphaMinimum + 1), first.AlphaMaximum, whole);
- var maxRed = Maximize(data, first, Red, (byte)(first.RedMinimum + 1), first.RedMaximum, whole);
- var maxGreen = Maximize(data, first, Green, (byte)(first.GreenMinimum + 1), first.GreenMaximum, whole);
- var maxBlue = Maximize(data, first, Blue, (byte)(first.BlueMinimum + 1), first.BlueMaximum, whole);
+ var whole = Volume(first, moments);
+ var maxAlpha = Maximize(moments, first, Alpha, (byte)(first.AlphaMinimum + 1), first.AlphaMaximum, whole);
+ var maxRed = Maximize(moments, first, Red, (byte)(first.RedMinimum + 1), first.RedMaximum, whole);
+ var maxGreen = Maximize(moments, first, Green, (byte)(first.GreenMinimum + 1), first.GreenMaximum, 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))
{
@@ -248,28 +275,28 @@ namespace nQuant
switch (direction)
{
case Alpha:
- second.AlphaMinimum = first.AlphaMaximum = (byte)maxAlpha.Position;
+ second.AlphaMinimum = first.AlphaMaximum = (byte) maxAlpha.Position;
second.RedMinimum = first.RedMinimum;
second.GreenMinimum = first.GreenMinimum;
second.BlueMinimum = first.BlueMinimum;
break;
case Red:
- second.RedMinimum = first.RedMaximum = (byte)maxRed.Position;
+ second.RedMinimum = first.RedMaximum = (byte) maxRed.Position;
second.AlphaMinimum = first.AlphaMinimum;
second.GreenMinimum = first.GreenMinimum;
second.BlueMinimum = first.BlueMinimum;
break;
case Green:
- second.GreenMinimum = first.GreenMaximum = (byte)maxGreen.Position;
+ second.GreenMinimum = first.GreenMaximum = (byte) maxGreen.Position;
second.AlphaMinimum = first.AlphaMinimum;
second.RedMinimum = first.RedMinimum;
second.BlueMinimum = first.BlueMinimum;
break;
case Blue:
- second.BlueMinimum = first.BlueMaximum = (byte)maxBlue.Position;
+ second.BlueMinimum = first.BlueMaximum = (byte) maxBlue.Position;
second.AlphaMinimum = first.AlphaMinimum;
second.RedMinimum = first.RedMinimum;
second.GreenMinimum = first.GreenMinimum;
@@ -282,9 +309,9 @@ namespace nQuant
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();
}
@@ -309,43 +336,22 @@ namespace nQuant
moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum]);
}
- private static float VolumeFloat(Box cube, float[, , ,] moment)
- {
- 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)
+ private static Box[] SplitData(ref int colorCount, ColorMoment[, , ,] moments)
{
--colorCount;
var next = 0;
- var volumeVariance = new float[MaxColor];
- var cubes = new Box[MaxColor];
+ var volumeVariance = new float[colorCount];
+ var cubes = new Box[colorCount];
cubes[0].AlphaMaximum = MaxSideIndex;
cubes[0].RedMaximum = MaxSideIndex;
cubes[0].GreenMaximum = MaxSideIndex;
cubes[0].BlueMaximum = MaxSideIndex;
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[cubeIndex] = cubes[cubeIndex].Size > 1 ? CalculateVariance(data, cubes[cubeIndex]) : 0.0f;
+ volumeVariance[next] = cubes[next].Size > 1 ? CalculateVariance(moments, cubes[next]) : 0.0f;
+ volumeVariance[cubeIndex] = cubes[cubeIndex].Size > 1 ? CalculateVariance(moments, cubes[cubeIndex]) : 0.0f;
}
else
{
@@ -370,18 +376,15 @@ namespace nQuant
return cubes.Take(colorCount).ToArray();
}
- private List BuildLookups(Box[] cubes, ColorData data)
+ private static Pixel[] BuildLookups(Box[] cubes, ColorMoment[, , ,] moments)
{
- List lookups = new List(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)
- {
- continue;
- }
+ if (volume.Weight <= 0) continue;
var lookup = new Pixel
{
@@ -390,13 +393,11 @@ namespace nQuant
Green = (byte)(volume.Green / volume.Weight),
Blue = (byte)(volume.Blue / volume.Weight)
};
-
- lookups.Add(lookup);
+ lookups[cubeIndex] = lookup;
}
-
return lookups;
}
- internal abstract Image GetQuantizedImage(ImageBuffer image, int colorCount, List lookups, int alphaThreshold);
+ internal abstract Image GetQuantizedImage(ImageBuffer image, int colorCount, Pixel[] lookups, int alphaThreshold);
}
}
\ No newline at end of file