diff --git a/src/ImageProcessor/Imaging/Quantizers/IQuantizer.cs b/src/ImageProcessor/Imaging/Quantizers/IQuantizer.cs new file mode 100644 index 000000000..697055af1 --- /dev/null +++ b/src/ImageProcessor/Imaging/Quantizers/IQuantizer.cs @@ -0,0 +1,31 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) James South. +// Licensed under the Apache License, Version 2.0. +// +// +// The Quantizer interface for allowing quantization of images. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace ImageProcessor.Imaging.Quantizers +{ + using System.Drawing; + + /// + /// The Quantizer interface for allowing quantization of images. + /// + public interface IQuantizer + { + /// + /// Quantize an image and return the resulting output bitmap. + /// + /// + /// The image to quantize. + /// + /// + /// A quantized version of the image. + /// + Bitmap Quantize(Image source); + } +} \ No newline at end of file diff --git a/src/ImageProcessor/Imaging/Quantizers/OctreeQuantizer.cs b/src/ImageProcessor/Imaging/Quantizers/OctreeQuantizer.cs index 3d3d1f595..eb5014f7b 100644 --- a/src/ImageProcessor/Imaging/Quantizers/OctreeQuantizer.cs +++ b/src/ImageProcessor/Imaging/Quantizers/OctreeQuantizer.cs @@ -34,6 +34,21 @@ namespace ImageProcessor.Imaging.Quantizers /// private readonly int maxColors; + /// + /// Initializes a new instance of the class. + /// + /// + /// The Octree quantizer is a two pass algorithm. The initial pass sets up the Octree, + /// the second pass quantizes a color based on the nodes in the tree. + /// + /// Defaults to return a maximum of 255 colors plus transparency with 8 significant bits. + /// + /// + public OctreeQuantizer() + : this(255, 8) + { + } + /// /// Initializes a new instance of the class. /// @@ -69,7 +84,9 @@ namespace ImageProcessor.Imaging.Quantizers /// /// Process the pixel in the first pass of the algorithm /// - /// The pixel to quantize + /// + /// The pixel to quantize + /// /// /// This function need only be overridden if your quantize algorithm needs two passes, /// such as an Octree quantizer. @@ -83,8 +100,12 @@ namespace ImageProcessor.Imaging.Quantizers /// /// Override this to process the pixel in the second pass of the algorithm /// - /// The pixel to quantize - /// The quantized value + /// + /// The pixel to quantize + /// + /// + /// The quantized value + /// protected override byte QuantizePixel(Color32* pixel) { // The color at [_maxColors] is set to transparent @@ -102,8 +123,12 @@ namespace ImageProcessor.Imaging.Quantizers /// /// Retrieve the palette for the quantized image /// - /// Any old palette, this is overwritten - /// The new color palette + /// + /// Any old palette, this is overwritten + /// + /// + /// The new color palette + /// protected override ColorPalette GetPalette(ColorPalette original) { // First off convert the Octree to maxColors colors @@ -228,8 +253,12 @@ namespace ImageProcessor.Imaging.Quantizers /// /// Convert the nodes in the Octree to a palette with a maximum of colorCount colors /// - /// The maximum number of colors - /// An with the palletized colors + /// + /// The maximum number of colors + /// + /// + /// An with the palletized colors + /// public ArrayList Palletize(int colorCount) { while (this.Leaves > colorCount) @@ -263,7 +292,9 @@ namespace ImageProcessor.Imaging.Quantizers /// /// Keep track of the previous node that was quantized /// - /// The node last quantized + /// + /// The node last quantized + /// protected void TrackPrevious(OctreeNode node) { this.previousNode = node; @@ -385,10 +416,18 @@ namespace ImageProcessor.Imaging.Quantizers /// /// Add a color into the tree /// - /// The color - /// The number of significant color bits - /// The level in the tree - /// The tree to which this node belongs + /// + /// The color + /// + /// + /// The number of significant color bits + /// + /// + /// The level in the tree + /// + /// + /// The tree to which this node belongs + /// public void AddColor(Color32* pixel, int colorBits, int level, Octree octree) { // Update the color information if this is a leaf @@ -454,8 +493,12 @@ namespace ImageProcessor.Imaging.Quantizers /// /// Traverse the tree, building up the color palette /// - /// The palette - /// The current palette index + /// + /// The palette + /// + /// + /// The current palette index + /// public void ConstructPalette(ArrayList palette, ref int index) { if (this.leaf) diff --git a/src/ImageProcessor/Imaging/Quantizers/Quantizer.cs b/src/ImageProcessor/Imaging/Quantizers/Quantizer.cs index 0c546d374..1a269da3f 100644 --- a/src/ImageProcessor/Imaging/Quantizers/Quantizer.cs +++ b/src/ImageProcessor/Imaging/Quantizers/Quantizer.cs @@ -20,7 +20,7 @@ namespace ImageProcessor.Imaging.Quantizers /// Encapsulates methods to calculate the color palette of an image. /// /// - public unsafe abstract class Quantizer + public unsafe abstract class Quantizer : IQuantizer { /// /// Flag used to indicate whether a single pass or two passes are needed for quantization. @@ -46,8 +46,12 @@ namespace ImageProcessor.Imaging.Quantizers /// /// Quantize an image and return the resulting output bitmap. /// - /// The image to quantize. - /// A quantized version of the image. + /// + /// The image to quantize. + /// + /// + /// A quantized version of the image. + /// public Bitmap Quantize(Image source) { // Get the size of the source image @@ -111,9 +115,15 @@ namespace ImageProcessor.Imaging.Quantizers /// /// Execute the first pass through the pixels in the image /// - /// The source data - /// The width in pixels of the image - /// The height in pixels of the image + /// + /// The source data + /// + /// + /// The width in pixels of the image + /// + /// + /// The height in pixels of the image + /// protected virtual void FirstPass(BitmapData sourceData, int width, int height) { // Define the source data pointers. The source row is a byte to @@ -141,11 +151,21 @@ namespace ImageProcessor.Imaging.Quantizers /// /// Execute a second pass through the bitmap /// - /// The source bitmap, locked into memory - /// The output bitmap - /// The width in pixels of the image - /// The height in pixels of the image - /// The bounding rectangle + /// + /// The source bitmap, locked into memory + /// + /// + /// The output bitmap + /// + /// + /// The width in pixels of the image + /// + /// + /// The height in pixels of the image + /// + /// + /// The bounding rectangle + /// protected virtual void SecondPass(BitmapData sourceData, Bitmap output, int width, int height, Rectangle bounds) { BitmapData outputData = null; @@ -184,7 +204,7 @@ namespace ImageProcessor.Imaging.Quantizers for (int col = 0; col < width; col++, sourcePixel++, destinationPixel++) { // Check if this is the same as the last pixel. If so use that value - // rather than calculating it again. This is an inexpensive optimisation. + // rather than calculating it again. This is an inexpensive optimization. if (*previousPixel != *sourcePixel) { // Quantize the pixel @@ -215,7 +235,9 @@ namespace ImageProcessor.Imaging.Quantizers /// /// Override this to process the pixel in the first pass of the algorithm /// - /// The pixel to quantize + /// + /// The pixel to quantize + /// /// /// This function need only be overridden if your quantize algorithm needs two passes, /// such as an Octree quantizer. @@ -227,15 +249,23 @@ namespace ImageProcessor.Imaging.Quantizers /// /// Override this to process the pixel in the second pass of the algorithm /// - /// The pixel to quantize - /// The quantized value + /// + /// The pixel to quantize + /// + /// + /// The quantized value + /// protected abstract byte QuantizePixel(Color32* pixel); /// /// Retrieve the palette for the quantized image /// - /// Any old palette, this is overwritten - /// The new color palette + /// + /// Any old palette, this is overwritten + /// + /// + /// The new color palette + /// protected abstract ColorPalette GetPalette(ColorPalette original); } } diff --git a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ColorMoment.cs b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ColorMoment.cs index 67040e58b..542aabbd4 100644 --- a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ColorMoment.cs +++ b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ColorMoment.cs @@ -4,7 +4,7 @@ // Licensed under the Apache License, Version 2.0. // // -// The color moment for holding pixel information. +// The cumulative color moment for holding pixel information. // Adapted from // // -------------------------------------------------------------------------------------------------------------------- diff --git a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/IWuQuantizer.cs b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/IWuQuantizer.cs index 1b06ba63e..90ffb15b4 100644 --- a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/IWuQuantizer.cs +++ b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/IWuQuantizer.cs @@ -4,7 +4,8 @@ // Licensed under the Apache License, Version 2.0. // // -// The WuQuantizer interface. +// Encapsulates methods to calculate the color palette of an image using +// a Wu color quantizer . // Adapted from // // -------------------------------------------------------------------------------------------------------------------- @@ -14,10 +15,11 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer using System.Drawing; /// - /// The WuQuantizer interface. + /// Encapsulates methods to calculate the color palette of an image using + /// a Wu color quantizer . /// Adapted from /// - public interface IWuQuantizer + public interface IWuQuantizer : IQuantizer { /// /// Quantizes the given image. @@ -35,6 +37,6 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer /// /// The quantized . /// - Image QuantizeImage(Bitmap image, int alphaThreshold, int alphaFader); + Bitmap Quantize(Image 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 a0ee180f4..1bdcc4749 100644 --- a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ImageBuffer.cs +++ b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ImageBuffer.cs @@ -4,7 +4,7 @@ // Licensed under the Apache License, Version 2.0. // // -// The image buffer for storing pixel information. +// The image buffer for storing and manipulating pixel information. // Adapted from // // -------------------------------------------------------------------------------------------------------------------- @@ -20,7 +20,7 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer using ImageProcessor.Common.Exceptions; /// - /// The image buffer for storing pixel information. + /// The image buffer for storing and manipulating pixel information. /// Adapted from /// internal class ImageBuffer @@ -51,16 +51,6 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer { get { - 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, - this.Image.Palette.Entries.Length)); - } - int width = this.Image.Width; int height = this.Image.Height; Pixel[] pixels = new Pixel[width]; diff --git a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/PaletteColorHistory.cs b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/PaletteColorHistory.cs index 190dd09e2..3f0cafb65 100644 --- a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/PaletteColorHistory.cs +++ b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/PaletteColorHistory.cs @@ -4,7 +4,7 @@ // Licensed under the Apache License, Version 2.0. // // -// The palette color history. +// The palette color history containing the sum of all pixel data. // Adapted from // // -------------------------------------------------------------------------------------------------------------------- @@ -14,7 +14,7 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer using System.Drawing; /// - /// The palette color history. + /// The palette color history containing the sum of all pixel data. /// Adapted from /// internal struct PaletteColorHistory diff --git a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/PaletteLookup.cs b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/PaletteLookup.cs index d808ecfa2..d69cfb240 100644 --- a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/PaletteLookup.cs +++ b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/PaletteLookup.cs @@ -44,7 +44,7 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer { this.Palette[paletteIndex] = new LookupNode { - Pixel = palette[paletteIndex], + Pixel = palette[paletteIndex], PaletteIndex = (byte)paletteIndex }; } @@ -240,7 +240,7 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer } this.lookupNodes = new Dictionary(tempLookup.Count); - foreach (var key in tempLookup.Keys) + foreach (int key in tempLookup.Keys) { this.lookupNodes[key] = tempLookup[key].ToArray(); } diff --git a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/Pixel.cs b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/Pixel.cs index 9fe10c252..be63cda10 100644 --- a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/Pixel.cs +++ b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/Pixel.cs @@ -1,10 +1,38 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) James South. +// Licensed under the Apache License, Version 2.0. +// +// -------------------------------------------------------------------------------------------------------------------- + + + using System.Diagnostics; using System.Runtime.InteropServices; + namespace ImageProcessor.Imaging.Quantizers.WuQuantizer { + /// + /// The pixel. + /// [StructLayout(LayoutKind.Explicit)] public struct Pixel { + /// + /// Initializes a new instance of the struct. + /// + /// + /// The alpha. + /// + /// + /// The red. + /// + /// + /// The green. + /// + /// + /// The blue. + /// public Pixel(byte alpha, byte red, byte green, byte blue) : this() { @@ -16,6 +44,12 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer Debug.Assert(Argb == (alpha << 24 | red << 16 | green << 8 | blue)); } + /// + /// Initializes a new instance of the struct. + /// + /// + /// The argb. + /// public Pixel(int argb) : this() { @@ -26,17 +60,42 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer Debug.Assert(Blue == ((uint)argb & 255)); } + /// + /// The alpha. + /// [FieldOffset(3)] public byte Alpha; + + /// + /// The red. + /// [FieldOffset(2)] public byte Red; + + /// + /// The green. + /// [FieldOffset(1)] public byte Green; + + /// + /// The blue. + /// [FieldOffset(0)] public byte Blue; + + /// + /// The argb. + /// [FieldOffset(0)] public int Argb; + /// + /// The to string. + /// + /// + /// The . + /// public override string ToString() { return string.Format("Alpha:{0} Red:{1} Green:{2} Blue:{3}", Alpha, Red, Green, Blue); diff --git a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/WuQuantizer.cs b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/WuQuantizer.cs index 7a103d054..dd4936d7e 100644 --- a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/WuQuantizer.cs +++ b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/WuQuantizer.cs @@ -3,6 +3,11 @@ // Copyright (c) James South. // Licensed under the Apache License, Version 2.0. // +// +// Encapsulates methods to calculate the color palette of an image using +// a Wu color quantizer . +// Adapted from +// // -------------------------------------------------------------------------------------------------------------------- namespace ImageProcessor.Imaging.Quantizers.WuQuantizer @@ -16,67 +21,68 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer /// a Wu color quantizer . /// Adapted from /// - public class WuQuantizer : WuQuantizerBase, IWuQuantizer + public class WuQuantizer : WuQuantizerBase { /// - /// The get quantized image. + /// Quantizes the image contained within the returning the result. /// - /// - /// The image. + /// + /// The for storing and manipulating pixel information.. /// /// - /// The color count. + /// The maximum number of colors apply to the image. /// /// - /// The lookups. + /// The array of containing indexed versions of the images colors. /// /// - /// The alpha threshold. + /// All colors with an alpha value less than this will be considered fully transparent. /// /// - /// The . + /// The quantized . /// - internal override Image GetQuantizedImage(ImageBuffer image, int colorCount, Pixel[] lookups, int alphaThreshold) + internal override Bitmap GetQuantizedImage(ImageBuffer imageBuffer, int colorCount, Pixel[] lookups, int alphaThreshold) { - Bitmap result = new Bitmap(image.Image.Width, image.Image.Height, PixelFormat.Format8bppIndexed); - result.SetResolution(image.Image.HorizontalResolution, image.Image.VerticalResolution); + Bitmap result = new Bitmap(imageBuffer.Image.Width, imageBuffer.Image.Height, PixelFormat.Format8bppIndexed); + result.SetResolution(imageBuffer.Image.HorizontalResolution, imageBuffer.Image.VerticalResolution); ImageBuffer resultBuffer = new ImageBuffer(result); PaletteColorHistory[] paletteHistogram = new PaletteColorHistory[colorCount + 1]; - resultBuffer.UpdatePixelIndexes(IndexedPixels(image, lookups, alphaThreshold, paletteHistogram)); + resultBuffer.UpdatePixelIndexes(IndexedPixels(imageBuffer, lookups, alphaThreshold, paletteHistogram)); result.Palette = BuildPalette(result.Palette, paletteHistogram); return result; } /// - /// The build palette. + /// Builds a color palette from the given . /// /// - /// The palette. + /// The to fill. /// - /// - /// The palette histogram. + /// + /// The containing the sum of all pixel data. /// /// /// The . /// - private static ColorPalette BuildPalette(ColorPalette palette, PaletteColorHistory[] paletteHistogram) + private static ColorPalette BuildPalette(ColorPalette palette, PaletteColorHistory[] paletteHistory) { - for (int paletteColorIndex = 0; paletteColorIndex < paletteHistogram.Length; paletteColorIndex++) + int length = paletteHistory.Length; + for (int i = 0; i < length; i++) { - palette.Entries[paletteColorIndex] = paletteHistogram[paletteColorIndex].ToNormalizedColor(); + palette.Entries[i] = paletteHistory[i].ToNormalizedColor(); } return palette; } /// - /// The indexed pixels. + /// Gets an enumerable array of bytes representing each row of the image. /// /// - /// The image. + /// The for storing and manipulating pixel information. /// /// - /// The lookups. + /// The array of containing indexed versions of the images colors. /// /// /// The alpha threshold. @@ -85,7 +91,7 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer /// The palette histogram. /// /// - /// The . + /// The enumerable list of representing each pixel. /// private static IEnumerable IndexedPixels(ImageBuffer image, Pixel[] lookups, int alphaThreshold, PaletteColorHistory[] paletteHistogram) { @@ -93,12 +99,13 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer PaletteLookup lookup = new PaletteLookup(lookups); // Determine the correct fallback color. - byte fallback = (byte)(lookups.Length < AlphaColor ? 0 : AlphaColor); + byte fallback = lookups.Length < AlphaMax ? AlphaMin : AlphaMax; foreach (Pixel[] pixelLine in image.PixelLines) { - for (int pixelIndex = 0; pixelIndex < pixelLine.Length; pixelIndex++) + int length = pixelLine.Length; + for (int i = 0; i < length; i++) { - Pixel pixel = pixelLine[pixelIndex]; + Pixel pixel = pixelLine[i]; byte bestMatch = fallback; if (pixel.Alpha > alphaThreshold) { @@ -106,7 +113,7 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer paletteHistogram[bestMatch].AddPixel(pixel); } - lineIndexes[pixelIndex] = bestMatch; + lineIndexes[i] = bestMatch; } yield return lineIndexes; diff --git a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/WuQuantizerBase.cs b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/WuQuantizerBase.cs index 00a7934fa..8e801bc9c 100644 --- a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/WuQuantizerBase.cs +++ b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/WuQuantizerBase.cs @@ -1,32 +1,29 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Copyright (c) James South. -// Licensed under the Apache License, Version 2.0. -// -// -// Encapsulates methods to calculate the color palette of an image using -// a Wu color quantizer . -// Adapted from -// -// -------------------------------------------------------------------------------------------------------------------- - -namespace ImageProcessor.Imaging.Quantizers.WuQuantizer +namespace ImageProcessor.Imaging.Quantizers.WuQuantizer { using System; + using System.Diagnostics.CodeAnalysis; using System.Drawing; + using System.Drawing.Imaging; using System.Linq; + using ImageProcessor.Common.Exceptions; + /// /// Encapsulates methods to calculate the color palette of an image using /// a Wu color quantizer . /// Adapted from /// - public abstract class WuQuantizerBase + public abstract class WuQuantizerBase : IWuQuantizer { /// - /// The alpha color component. + /// The maximum value for an alpha color component. /// - protected const byte AlphaColor = 255; + protected const byte AlphaMax = 255; + + /// + /// The minimum value for an alpha color component. + /// + protected const byte AlphaMin = 0; /// /// The position of the alpha component within a byte array. @@ -64,10 +61,12 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer /// /// The 32 bit per pixel image to quantize. /// - /// A quantized version of the image. - public Image QuantizeImage(Bitmap source) + /// + /// A quantized version of the image. + /// + public Bitmap Quantize(Image source) { - return this.QuantizeImage(source, 0, 1); + return this.Quantize(source, 0, 1); } /// @@ -76,12 +75,18 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer /// /// The 32 bit per pixel image to quantize. /// - /// All colors with an alpha value less than this will be considered fully transparent. - /// Alpha values will be normalized to the nearest multiple of this value. - /// A quantized version of the image. - public Image QuantizeImage(Bitmap source, int alphaThreshold, int alphaFader) + /// + /// All colors with an alpha value less than this will be considered fully transparent. + /// + /// + /// Alpha values will be normalized to the nearest multiple of this value. + /// + /// + /// A quantized version of the image. + /// + public Bitmap Quantize(Image source, int alphaThreshold, int alphaFader) { - return this.QuantizeImage(source, alphaThreshold, alphaFader, null, 256); + return this.Quantize(source, alphaThreshold, alphaFader, null, 256); } /// @@ -105,26 +110,73 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer /// /// A quantized version of the image. /// - public Image QuantizeImage(Bitmap source, int alphaThreshold, int alphaFader, Histogram histogram, int maxColors) + public Bitmap Quantize(Image source, int alphaThreshold, int alphaFader, Histogram histogram, int maxColors) { - ImageBuffer buffer = new ImageBuffer(source); - - if (histogram == null) + try { - histogram = new Histogram(); + ImageBuffer buffer; + + // The image has to be a 32 bit per pixel Argb image. + if (Image.GetPixelFormatSize(source.PixelFormat) != 32) + { + Bitmap clone = new Bitmap(source.Width, source.Height, PixelFormat.Format32bppPArgb); + clone.SetResolution(source.HorizontalResolution, source.VerticalResolution); + + using (Graphics graphics = Graphics.FromImage(clone)) + { + graphics.Clear(Color.Transparent); + graphics.DrawImage(source, new Rectangle(0, 0, clone.Width, clone.Height)); + } + + source.Dispose(); + buffer = new ImageBuffer(clone); + } + else + { + buffer = new ImageBuffer((Bitmap)source); + } + + if (histogram == null) + { + histogram = new Histogram(); + } + else + { + histogram.Clear(); + } + + BuildHistogram(histogram, buffer, alphaThreshold, alphaFader); + CalculateMoments(histogram.Moments); + Box[] cubes = SplitData(ref maxColors, histogram.Moments); + Pixel[] lookups = BuildLookups(cubes, histogram.Moments); + return this.GetQuantizedImage(buffer, maxColors, lookups, alphaThreshold); } - else + catch (Exception ex) { - histogram.Clear(); + throw new QuantizationException(ex.Message, ex); } - - BuildHistogram(histogram, buffer, alphaThreshold, alphaFader); - CalculateMoments(histogram.Moments); - Box[] cubes = SplitData(ref maxColors, histogram.Moments); - Pixel[] lookups = BuildLookups(cubes, histogram.Moments); - return this.GetQuantizedImage(buffer, maxColors, lookups, alphaThreshold); } + /// + /// Quantizes the image contained within the returning the result. + /// + /// + /// The for storing and manipulating pixel information.. + /// + /// + /// The maximum number of colors apply to the image. + /// + /// + /// The array of containing indexed versions of the images colors. + /// + /// + /// All colors with an alpha value less than this will be considered fully transparent. + /// + /// + /// The quantized . + /// + internal abstract Bitmap GetQuantizedImage(ImageBuffer imageBuffer, int colorCount, Pixel[] lookups, int alphaThreshold); + /// /// Builds a histogram from the current image. /// @@ -140,6 +192,7 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer /// /// Alpha values will be normalized to the nearest multiple of this value. /// + [SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1001:CommasMustBeSpacedCorrectly", Justification = "Reviewed. Suppression is OK here.")] private static void BuildHistogram(Histogram histogram, ImageBuffer imageBuffer, int alphaThreshold, int alphaFader) { ColorMoment[, , ,] moments = histogram.Moments; @@ -174,9 +227,16 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer moments[0, 0, 0, 0].Add(new Pixel(0, 0, 0, 0)); } + /// + /// Calculates the color moments from the histogram of moments. + /// + /// + /// The three dimensional array of to process. + /// + [SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1001:CommasMustBeSpacedCorrectly", Justification = "Reviewed. Suppression is OK here.")] private static void CalculateMoments(ColorMoment[, , ,] moments) { - ColorMoment[,] xarea = new ColorMoment[SideSize, SideSize]; + ColorMoment[,] areaSquared = new ColorMoment[SideSize, SideSize]; ColorMoment[] area = new ColorMoment[SideSize]; for (int alphaIndex = 1; alphaIndex < SideSize; alphaIndex++) { @@ -190,10 +250,10 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer { line.AddFast(ref moments[alphaIndex, redIndex, greenIndex, blueIndex]); area[blueIndex].AddFast(ref line); - xarea[greenIndex, blueIndex].AddFast(ref area[blueIndex]); + areaSquared[greenIndex, blueIndex].AddFast(ref area[blueIndex]); ColorMoment moment = moments[alphaIndex - 1, redIndex, greenIndex, blueIndex]; - moment.AddFast(ref xarea[greenIndex, blueIndex]); + moment.AddFast(ref areaSquared[greenIndex, blueIndex]); moments[alphaIndex, redIndex, greenIndex, blueIndex] = moment; } } @@ -201,6 +261,25 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer } } + /// + /// Calculates the volume of the top of the cube. + /// + /// + /// The cube to calculate the volume from. + /// + /// + /// The direction to calculate. + /// + /// + /// The position at which to begin. + /// + /// + /// The three dimensional moment. + /// + /// + /// The representing the top of the cube. + /// + [SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1001:CommasMustBeSpacedCorrectly", Justification = "Reviewed. Suppression is OK here.")] private static ColorMoment Top(Box cube, int direction, int position, ColorMoment[, , ,] moment) { switch (direction) @@ -250,6 +329,22 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer } } + /// + /// Calculates the volume of the bottom of the cube. + /// + /// + /// The cube to calculate the volume from. + /// + /// + /// The direction to calculate. + /// + /// + /// The three dimensional moment. + /// + /// + /// The representing the bottom of the cube. + /// + [SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1001:CommasMustBeSpacedCorrectly", Justification = "Reviewed. Suppression is OK here.")] private static ColorMoment Bottom(Box cube, int direction, ColorMoment[, , ,] moment) { switch (direction) @@ -299,10 +394,35 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer } } + /// + /// Maximizes the sum of the two boxes. + /// + /// + /// The . + /// + /// + /// The cube. + /// + /// + /// The direction. + /// + /// + /// The first byte. + /// + /// + /// The last byte. + /// + /// + /// The whole . + /// + /// + /// The representing the sum. + /// + [SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1001:CommasMustBeSpacedCorrectly", Justification = "Reviewed. Suppression is OK here.")] private static CubeCut Maximize(ColorMoment[, , ,] moments, Box cube, int direction, byte first, byte last, ColorMoment whole) { - var bottom = Bottom(cube, direction, moments); - var result = 0.0f; + ColorMoment bottom = Bottom(cube, direction, moments); + float result = 0.0f; byte? cutPoint = null; for (byte position = first; position < last; ++position) @@ -313,7 +433,7 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer continue; } - var temp = half.WeightedDistance(); + long temp = half.WeightedDistance(); half = whole - half; if (half.Weight != 0) @@ -331,28 +451,55 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer return new CubeCut(cutPoint, result); } + /// + /// Returns a value indicating whether a cube can be cut. + /// + /// + /// The three dimensional array of . + /// + /// + /// The first . + /// + /// + /// The second . + /// + /// + /// The indicating the result. + /// + [SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1001:CommasMustBeSpacedCorrectly", Justification = "Reviewed. Suppression is OK here.")] private static bool Cut(ColorMoment[, , ,] moments, ref Box first, ref Box second) { int direction; - 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); + ColorMoment whole = Volume(moments, first); + CubeCut maxAlpha = Maximize(moments, first, Alpha, (byte)(first.AlphaMinimum + 1), first.AlphaMaximum, whole); + CubeCut maxRed = Maximize(moments, first, Red, (byte)(first.RedMinimum + 1), first.RedMaximum, whole); + CubeCut maxGreen = Maximize(moments, first, Green, (byte)(first.GreenMinimum + 1), first.GreenMaximum, whole); + CubeCut 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)) { direction = Alpha; - if (maxAlpha.Position == null) return false; + if (maxAlpha.Position == null) + { + return false; + } } - else if ((maxRed.Value >= maxAlpha.Value) && (maxRed.Value >= maxGreen.Value) && (maxRed.Value >= maxBlue.Value)) + else if ((maxRed.Value >= maxAlpha.Value) && (maxRed.Value >= maxGreen.Value) + && (maxRed.Value >= maxBlue.Value)) + { direction = Red; + } else { - if ((maxGreen.Value >= maxAlpha.Value) && (maxGreen.Value >= maxRed.Value) && (maxGreen.Value >= maxBlue.Value)) + if ((maxGreen.Value >= maxAlpha.Value) && (maxGreen.Value >= maxRed.Value) + && (maxGreen.Value >= maxBlue.Value)) + { direction = Green; + } else + { direction = Blue; + } } second.AlphaMaximum = first.AlphaMaximum; @@ -363,6 +510,11 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer switch (direction) { case Alpha: + if (maxAlpha.Position == null) + { + return false; + } + second.AlphaMinimum = first.AlphaMaximum = (byte)maxAlpha.Position; second.RedMinimum = first.RedMinimum; second.GreenMinimum = first.GreenMinimum; @@ -370,6 +522,11 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer break; case Red: + if (maxRed.Position == null) + { + return false; + } + second.RedMinimum = first.RedMaximum = (byte)maxRed.Position; second.AlphaMinimum = first.AlphaMinimum; second.GreenMinimum = first.GreenMinimum; @@ -377,6 +534,11 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer break; case Green: + if (maxGreen.Position == null) + { + return false; + } + second.GreenMinimum = first.GreenMaximum = (byte)maxGreen.Position; second.AlphaMinimum = first.AlphaMinimum; second.RedMinimum = first.RedMinimum; @@ -384,6 +546,11 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer break; case Blue: + if (maxBlue.Position == null) + { + return false; + } + second.BlueMinimum = first.BlueMaximum = (byte)maxBlue.Position; second.AlphaMinimum = first.AlphaMinimum; second.RedMinimum = first.RedMinimum; @@ -397,44 +564,83 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer return true; } + /// + /// Calculates the variance of the volume of the cube. + /// + /// + /// The three dimensional array of . + /// + /// + /// The cube. + /// + /// + /// The representing the variance. + /// + [SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1001:CommasMustBeSpacedCorrectly", Justification = "Reviewed. Suppression is OK here.")] private static float CalculateVariance(ColorMoment[, , ,] moments, Box cube) { - ColorMoment volume = Volume(cube, moments); + ColorMoment volume = Volume(moments, cube); return volume.Variance(); } - private static ColorMoment Volume(Box cube, ColorMoment[, , ,] moment) + /// + /// Calculates the volume of the colors. + /// + /// + /// The three dimensional array of . + /// + /// + /// The cube. + /// + /// + /// The representing the volume. + /// + [SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1001:CommasMustBeSpacedCorrectly", Justification = "Reviewed. Suppression is OK here.")] + private static ColorMoment Volume(ColorMoment[, , ,] moments, Box cube) { - 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]); + return (moments[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMaximum, cube.BlueMaximum] - + moments[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMaximum] - + moments[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMaximum] + + moments[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMaximum] - + moments[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMaximum, cube.BlueMaximum] + + moments[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMaximum] + + moments[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMaximum] - + moments[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMaximum]) - + + (moments[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMaximum, cube.BlueMinimum] - + moments[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMaximum, cube.BlueMinimum] - + moments[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMinimum] + + moments[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMinimum] - + moments[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMinimum] + + moments[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMinimum] + + moments[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum] - + moments[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum]); } + /// + /// Splits the data. + /// + /// + /// The color count. + /// + /// + /// The three dimensional array of . + /// + /// + /// The array . + /// + [SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1001:CommasMustBeSpacedCorrectly", Justification = "Reviewed. Suppression is OK here.")] private static Box[] SplitData(ref int colorCount, ColorMoment[, , ,] moments) { --colorCount; - var next = 0; - var volumeVariance = new float[colorCount]; - var cubes = new Box[colorCount]; + int next = 0; + float[] volumeVariance = new float[colorCount]; + Box[] 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) + for (int cubeIndex = 1; cubeIndex < colorCount; ++cubeIndex) { if (Cut(moments, ref cubes[next], ref cubes[cubeIndex])) { @@ -448,29 +654,51 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer } next = 0; - var temp = volumeVariance[0]; + float temp = volumeVariance[0]; - for (var index = 1; index <= cubeIndex; ++index) + for (int index = 1; index <= cubeIndex; ++index) { - if (volumeVariance[index] <= temp) continue; + if (volumeVariance[index] <= temp) + { + continue; + } + temp = volumeVariance[index]; next = index; } - if (temp > 0.0) continue; + if (temp > 0.0) + { + continue; + } + colorCount = cubeIndex + 1; break; } + return cubes.Take(colorCount).ToArray(); } + /// + /// Builds an array of pixel data to look within. + /// + /// + /// The array of cubes. + /// + /// + /// The three dimensional array of . + /// + /// + /// The array of . + /// + [SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1001:CommasMustBeSpacedCorrectly", Justification = "Reviewed. Suppression is OK here.")] private static Pixel[] BuildLookups(Box[] cubes, ColorMoment[, , ,] moments) { Pixel[] lookups = new Pixel[cubes.Length]; for (int cubeIndex = 0; cubeIndex < cubes.Length; cubeIndex++) { - ColorMoment volume = Volume(cubes[cubeIndex], moments); + ColorMoment volume = Volume(moments, cubes[cubeIndex]); if (volume.Weight <= 0) { @@ -490,7 +718,5 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer return lookups; } - - internal abstract Image GetQuantizedImage(ImageBuffer image, int colorCount, Pixel[] lookups, int alphaThreshold); } } \ No newline at end of file