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