Browse Source

More tidy up and optimization

Former-commit-id: e828a954bf1ccf08ccb7671c9274fa4fda038e7e
Former-commit-id: f3e18600a7da66cc1a09f39d49e37a01f8ebbc09
pull/17/head
James South 11 years ago
parent
commit
8a5a7c028c
  1. 31
      src/ImageProcessor/Imaging/Quantizers/IQuantizer.cs
  2. 71
      src/ImageProcessor/Imaging/Quantizers/OctreeQuantizer.cs
  3. 64
      src/ImageProcessor/Imaging/Quantizers/Quantizer.cs
  4. 2
      src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ColorMoment.cs
  5. 10
      src/ImageProcessor/Imaging/Quantizers/WuQuantizer/IWuQuantizer.cs
  6. 14
      src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ImageBuffer.cs
  7. 4
      src/ImageProcessor/Imaging/Quantizers/WuQuantizer/PaletteColorHistory.cs
  8. 4
      src/ImageProcessor/Imaging/Quantizers/WuQuantizer/PaletteLookup.cs
  9. 59
      src/ImageProcessor/Imaging/Quantizers/WuQuantizer/Pixel.cs
  10. 61
      src/ImageProcessor/Imaging/Quantizers/WuQuantizer/WuQuantizer.cs
  11. 388
      src/ImageProcessor/Imaging/Quantizers/WuQuantizer/WuQuantizerBase.cs

31
src/ImageProcessor/Imaging/Quantizers/IQuantizer.cs

@ -0,0 +1,31 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="IQuantizer.cs" company="James South">
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// The Quantizer interface for allowing quantization of images.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Imaging.Quantizers
{
using System.Drawing;
/// <summary>
/// The Quantizer interface for allowing quantization of images.
/// </summary>
public interface IQuantizer
{
/// <summary>
/// Quantize an image and return the resulting output bitmap.
/// </summary>
/// <param name="source">
/// The image to quantize.
/// </param>
/// <returns>
/// A quantized version of the image.
/// </returns>
Bitmap Quantize(Image source);
}
}

71
src/ImageProcessor/Imaging/Quantizers/OctreeQuantizer.cs

@ -34,6 +34,21 @@ namespace ImageProcessor.Imaging.Quantizers
/// </summary> /// </summary>
private readonly int maxColors; private readonly int maxColors;
/// <summary>
/// Initializes a new instance of the <see cref="OctreeQuantizer"/> class.
/// </summary>
/// <remarks>
/// 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.
/// <para>
/// Defaults to return a maximum of 255 colors plus transparency with 8 significant bits.
/// </para>
/// </remarks>
public OctreeQuantizer()
: this(255, 8)
{
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="OctreeQuantizer"/> class. /// Initializes a new instance of the <see cref="OctreeQuantizer"/> class.
/// </summary> /// </summary>
@ -69,7 +84,9 @@ namespace ImageProcessor.Imaging.Quantizers
/// <summary> /// <summary>
/// Process the pixel in the first pass of the algorithm /// Process the pixel in the first pass of the algorithm
/// </summary> /// </summary>
/// <param name="pixel">The pixel to quantize</param> /// <param name="pixel">
/// The pixel to quantize
/// </param>
/// <remarks> /// <remarks>
/// This function need only be overridden if your quantize algorithm needs two passes, /// This function need only be overridden if your quantize algorithm needs two passes,
/// such as an Octree quantizer. /// such as an Octree quantizer.
@ -83,8 +100,12 @@ namespace ImageProcessor.Imaging.Quantizers
/// <summary> /// <summary>
/// Override this to process the pixel in the second pass of the algorithm /// Override this to process the pixel in the second pass of the algorithm
/// </summary> /// </summary>
/// <param name="pixel">The pixel to quantize</param> /// <param name="pixel">
/// <returns>The quantized value</returns> /// The pixel to quantize
/// </param>
/// <returns>
/// The quantized value
/// </returns>
protected override byte QuantizePixel(Color32* pixel) protected override byte QuantizePixel(Color32* pixel)
{ {
// The color at [_maxColors] is set to transparent // The color at [_maxColors] is set to transparent
@ -102,8 +123,12 @@ namespace ImageProcessor.Imaging.Quantizers
/// <summary> /// <summary>
/// Retrieve the palette for the quantized image /// Retrieve the palette for the quantized image
/// </summary> /// </summary>
/// <param name="original">Any old palette, this is overwritten</param> /// <param name="original">
/// <returns>The new color palette</returns> /// Any old palette, this is overwritten
/// </param>
/// <returns>
/// The new color palette
/// </returns>
protected override ColorPalette GetPalette(ColorPalette original) protected override ColorPalette GetPalette(ColorPalette original)
{ {
// First off convert the Octree to maxColors colors // First off convert the Octree to maxColors colors
@ -228,8 +253,12 @@ namespace ImageProcessor.Imaging.Quantizers
/// <summary> /// <summary>
/// Convert the nodes in the Octree to a palette with a maximum of colorCount colors /// Convert the nodes in the Octree to a palette with a maximum of colorCount colors
/// </summary> /// </summary>
/// <param name="colorCount">The maximum number of colors</param> /// <param name="colorCount">
/// <returns>An <see cref="ArrayList"/> with the palletized colors</returns> /// The maximum number of colors
/// </param>
/// <returns>
/// An <see cref="ArrayList"/> with the palletized colors
/// </returns>
public ArrayList Palletize(int colorCount) public ArrayList Palletize(int colorCount)
{ {
while (this.Leaves > colorCount) while (this.Leaves > colorCount)
@ -263,7 +292,9 @@ namespace ImageProcessor.Imaging.Quantizers
/// <summary> /// <summary>
/// Keep track of the previous node that was quantized /// Keep track of the previous node that was quantized
/// </summary> /// </summary>
/// <param name="node">The node last quantized</param> /// <param name="node">
/// The node last quantized
/// </param>
protected void TrackPrevious(OctreeNode node) protected void TrackPrevious(OctreeNode node)
{ {
this.previousNode = node; this.previousNode = node;
@ -385,10 +416,18 @@ namespace ImageProcessor.Imaging.Quantizers
/// <summary> /// <summary>
/// Add a color into the tree /// Add a color into the tree
/// </summary> /// </summary>
/// <param name="pixel">The color</param> /// <param name="pixel">
/// <param name="colorBits">The number of significant color bits</param> /// The color
/// <param name="level">The level in the tree</param> /// </param>
/// <param name="octree">The tree to which this node belongs</param> /// <param name="colorBits">
/// The number of significant color bits
/// </param>
/// <param name="level">
/// The level in the tree
/// </param>
/// <param name="octree">
/// The tree to which this node belongs
/// </param>
public void AddColor(Color32* pixel, int colorBits, int level, Octree octree) public void AddColor(Color32* pixel, int colorBits, int level, Octree octree)
{ {
// Update the color information if this is a leaf // Update the color information if this is a leaf
@ -454,8 +493,12 @@ namespace ImageProcessor.Imaging.Quantizers
/// <summary> /// <summary>
/// Traverse the tree, building up the color palette /// Traverse the tree, building up the color palette
/// </summary> /// </summary>
/// <param name="palette">The palette</param> /// <param name="palette">
/// <param name="index">The current palette index</param> /// The palette
/// </param>
/// <param name="index">
/// The current palette index
/// </param>
public void ConstructPalette(ArrayList palette, ref int index) public void ConstructPalette(ArrayList palette, ref int index)
{ {
if (this.leaf) if (this.leaf)

64
src/ImageProcessor/Imaging/Quantizers/Quantizer.cs

@ -20,7 +20,7 @@ namespace ImageProcessor.Imaging.Quantizers
/// Encapsulates methods to calculate the color palette of an image. /// Encapsulates methods to calculate the color palette of an image.
/// <see href="http://msdn.microsoft.com/en-us/library/aa479306.aspx"/> /// <see href="http://msdn.microsoft.com/en-us/library/aa479306.aspx"/>
/// </summary> /// </summary>
public unsafe abstract class Quantizer public unsafe abstract class Quantizer : IQuantizer
{ {
/// <summary> /// <summary>
/// Flag used to indicate whether a single pass or two passes are needed for quantization. /// Flag used to indicate whether a single pass or two passes are needed for quantization.
@ -46,8 +46,12 @@ namespace ImageProcessor.Imaging.Quantizers
/// <summary> /// <summary>
/// Quantize an image and return the resulting output bitmap. /// Quantize an image and return the resulting output bitmap.
/// </summary> /// </summary>
/// <param name="source">The image to quantize.</param> /// <param name="source">
/// <returns>A quantized version of the image.</returns> /// The image to quantize.
/// </param>
/// <returns>
/// A quantized version of the image.
/// </returns>
public Bitmap Quantize(Image source) public Bitmap Quantize(Image source)
{ {
// Get the size of the source image // Get the size of the source image
@ -111,9 +115,15 @@ namespace ImageProcessor.Imaging.Quantizers
/// <summary> /// <summary>
/// Execute the first pass through the pixels in the image /// Execute the first pass through the pixels in the image
/// </summary> /// </summary>
/// <param name="sourceData">The source data</param> /// <param name="sourceData">
/// <param name="width">The width in pixels of the image</param> /// The source data
/// <param name="height">The height in pixels of the image</param> /// </param>
/// <param name="width">
/// The width in pixels of the image
/// </param>
/// <param name="height">
/// The height in pixels of the image
/// </param>
protected virtual void FirstPass(BitmapData sourceData, int width, int height) protected virtual void FirstPass(BitmapData sourceData, int width, int height)
{ {
// Define the source data pointers. The source row is a byte to // Define the source data pointers. The source row is a byte to
@ -141,11 +151,21 @@ namespace ImageProcessor.Imaging.Quantizers
/// <summary> /// <summary>
/// Execute a second pass through the bitmap /// Execute a second pass through the bitmap
/// </summary> /// </summary>
/// <param name="sourceData">The source bitmap, locked into memory</param> /// <param name="sourceData">
/// <param name="output">The output bitmap</param> /// The source bitmap, locked into memory
/// <param name="width">The width in pixels of the image</param> /// </param>
/// <param name="height">The height in pixels of the image</param> /// <param name="output">
/// <param name="bounds">The bounding rectangle</param> /// The output bitmap
/// </param>
/// <param name="width">
/// The width in pixels of the image
/// </param>
/// <param name="height">
/// The height in pixels of the image
/// </param>
/// <param name="bounds">
/// The bounding rectangle
/// </param>
protected virtual void SecondPass(BitmapData sourceData, Bitmap output, int width, int height, Rectangle bounds) protected virtual void SecondPass(BitmapData sourceData, Bitmap output, int width, int height, Rectangle bounds)
{ {
BitmapData outputData = null; BitmapData outputData = null;
@ -184,7 +204,7 @@ namespace ImageProcessor.Imaging.Quantizers
for (int col = 0; col < width; col++, sourcePixel++, destinationPixel++) for (int col = 0; col < width; col++, sourcePixel++, destinationPixel++)
{ {
// Check if this is the same as the last pixel. If so use that value // 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) if (*previousPixel != *sourcePixel)
{ {
// Quantize the pixel // Quantize the pixel
@ -215,7 +235,9 @@ namespace ImageProcessor.Imaging.Quantizers
/// <summary> /// <summary>
/// Override this to process the pixel in the first pass of the algorithm /// Override this to process the pixel in the first pass of the algorithm
/// </summary> /// </summary>
/// <param name="pixel">The pixel to quantize</param> /// <param name="pixel">
/// The pixel to quantize
/// </param>
/// <remarks> /// <remarks>
/// This function need only be overridden if your quantize algorithm needs two passes, /// This function need only be overridden if your quantize algorithm needs two passes,
/// such as an Octree quantizer. /// such as an Octree quantizer.
@ -227,15 +249,23 @@ namespace ImageProcessor.Imaging.Quantizers
/// <summary> /// <summary>
/// Override this to process the pixel in the second pass of the algorithm /// Override this to process the pixel in the second pass of the algorithm
/// </summary> /// </summary>
/// <param name="pixel">The pixel to quantize</param> /// <param name="pixel">
/// <returns>The quantized value</returns> /// The pixel to quantize
/// </param>
/// <returns>
/// The quantized value
/// </returns>
protected abstract byte QuantizePixel(Color32* pixel); protected abstract byte QuantizePixel(Color32* pixel);
/// <summary> /// <summary>
/// Retrieve the palette for the quantized image /// Retrieve the palette for the quantized image
/// </summary> /// </summary>
/// <param name="original">Any old palette, this is overwritten</param> /// <param name="original">
/// <returns>The new color palette</returns> /// Any old palette, this is overwritten
/// </param>
/// <returns>
/// The new color palette
/// </returns>
protected abstract ColorPalette GetPalette(ColorPalette original); protected abstract ColorPalette GetPalette(ColorPalette original);
} }
} }

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

@ -4,7 +4,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
// </copyright> // </copyright>
// <summary> // <summary>
// The color moment for holding pixel information. // The cumulative color moment for holding pixel information.
// Adapted from <see href="https://github.com/drewnoakes" /> // Adapted from <see href="https://github.com/drewnoakes" />
// </summary> // </summary>
// -------------------------------------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------------------------------------

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

@ -4,7 +4,8 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
// </copyright> // </copyright>
// <summary> // <summary>
// The WuQuantizer interface. // Encapsulates methods to calculate the color palette of an image using
// a Wu color quantizer <see href="http://www.ece.mcmaster.ca/~xwu/cq.c" />.
// Adapted from <see href="https://github.com/drewnoakes" /> // Adapted from <see href="https://github.com/drewnoakes" />
// </summary> // </summary>
// -------------------------------------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------------------------------------
@ -14,10 +15,11 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
using System.Drawing; using System.Drawing;
/// <summary> /// <summary>
/// The WuQuantizer interface. /// Encapsulates methods to calculate the color palette of an image using
/// a Wu color quantizer <see href="http://www.ece.mcmaster.ca/~xwu/cq.c"/>.
/// Adapted from <see href="https://github.com/drewnoakes" /> /// Adapted from <see href="https://github.com/drewnoakes" />
/// </summary> /// </summary>
public interface IWuQuantizer public interface IWuQuantizer : IQuantizer
{ {
/// <summary> /// <summary>
/// Quantizes the given image. /// Quantizes the given image.
@ -35,6 +37,6 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
/// <returns> /// <returns>
/// The quantized <see cref="Image"/>. /// The quantized <see cref="Image"/>.
/// </returns> /// </returns>
Image QuantizeImage(Bitmap image, int alphaThreshold, int alphaFader); Bitmap Quantize(Image image, int alphaThreshold, int alphaFader);
} }
} }

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

@ -4,7 +4,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
// </copyright> // </copyright>
// <summary> // <summary>
// The image buffer for storing pixel information. // The image buffer for storing and manipulating pixel information.
// Adapted from <see href="https://github.com/drewnoakes" /> // Adapted from <see href="https://github.com/drewnoakes" />
// </summary> // </summary>
// -------------------------------------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------------------------------------
@ -20,7 +20,7 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
using ImageProcessor.Common.Exceptions; using ImageProcessor.Common.Exceptions;
/// <summary> /// <summary>
/// The image buffer for storing pixel information. /// The image buffer for storing and manipulating pixel information.
/// Adapted from <see href="https://github.com/drewnoakes"/> /// Adapted from <see href="https://github.com/drewnoakes"/>
/// </summary> /// </summary>
internal class ImageBuffer internal class ImageBuffer
@ -51,16 +51,6 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
{ {
get 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 width = this.Image.Width;
int height = this.Image.Height; int height = this.Image.Height;
Pixel[] pixels = new Pixel[width]; Pixel[] pixels = new Pixel[width];

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

@ -4,7 +4,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
// </copyright> // </copyright>
// <summary> // <summary>
// The palette color history. // The palette color history containing the sum of all pixel data.
// Adapted from <see href="https://github.com/drewnoakes" /> // Adapted from <see href="https://github.com/drewnoakes" />
// </summary> // </summary>
// -------------------------------------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------------------------------------
@ -14,7 +14,7 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
using System.Drawing; using System.Drawing;
/// <summary> /// <summary>
/// The palette color history. /// The palette color history containing the sum of all pixel data.
/// Adapted from <see href="https://github.com/drewnoakes" /> /// Adapted from <see href="https://github.com/drewnoakes" />
/// </summary> /// </summary>
internal struct PaletteColorHistory internal struct PaletteColorHistory

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

@ -44,7 +44,7 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
{ {
this.Palette[paletteIndex] = new LookupNode this.Palette[paletteIndex] = new LookupNode
{ {
Pixel = palette[paletteIndex], Pixel = palette[paletteIndex],
PaletteIndex = (byte)paletteIndex PaletteIndex = (byte)paletteIndex
}; };
} }
@ -240,7 +240,7 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
} }
this.lookupNodes = new Dictionary<int, LookupNode[]>(tempLookup.Count); this.lookupNodes = new Dictionary<int, LookupNode[]>(tempLookup.Count);
foreach (var key in tempLookup.Keys) foreach (int key in tempLookup.Keys)
{ {
this.lookupNodes[key] = tempLookup[key].ToArray(); this.lookupNodes[key] = tempLookup[key].ToArray();
} }

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

@ -1,10 +1,38 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Pixel.cs" company="James South">
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace ImageProcessor.Imaging.Quantizers.WuQuantizer namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
{ {
/// <summary>
/// The pixel.
/// </summary>
[StructLayout(LayoutKind.Explicit)] [StructLayout(LayoutKind.Explicit)]
public struct Pixel public struct Pixel
{ {
/// <summary>
/// Initializes a new instance of the <see cref="Pixel"/> struct.
/// </summary>
/// <param name="alpha">
/// The alpha.
/// </param>
/// <param name="red">
/// The red.
/// </param>
/// <param name="green">
/// The green.
/// </param>
/// <param name="blue">
/// The blue.
/// </param>
public Pixel(byte alpha, byte red, byte green, byte blue) public Pixel(byte alpha, byte red, byte green, byte blue)
: this() : this()
{ {
@ -16,6 +44,12 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
Debug.Assert(Argb == (alpha << 24 | red << 16 | green << 8 | blue)); Debug.Assert(Argb == (alpha << 24 | red << 16 | green << 8 | blue));
} }
/// <summary>
/// Initializes a new instance of the <see cref="Pixel"/> struct.
/// </summary>
/// <param name="argb">
/// The argb.
/// </param>
public Pixel(int argb) public Pixel(int argb)
: this() : this()
{ {
@ -26,17 +60,42 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
Debug.Assert(Blue == ((uint)argb & 255)); Debug.Assert(Blue == ((uint)argb & 255));
} }
/// <summary>
/// The alpha.
/// </summary>
[FieldOffset(3)] [FieldOffset(3)]
public byte Alpha; public byte Alpha;
/// <summary>
/// The red.
/// </summary>
[FieldOffset(2)] [FieldOffset(2)]
public byte Red; public byte Red;
/// <summary>
/// The green.
/// </summary>
[FieldOffset(1)] [FieldOffset(1)]
public byte Green; public byte Green;
/// <summary>
/// The blue.
/// </summary>
[FieldOffset(0)] [FieldOffset(0)]
public byte Blue; public byte Blue;
/// <summary>
/// The argb.
/// </summary>
[FieldOffset(0)] [FieldOffset(0)]
public int Argb; public int Argb;
/// <summary>
/// The to string.
/// </summary>
/// <returns>
/// The <see cref="string"/>.
/// </returns>
public override string ToString() public override string ToString()
{ {
return string.Format("Alpha:{0} Red:{1} Green:{2} Blue:{3}", Alpha, Red, Green, Blue); return string.Format("Alpha:{0} Red:{1} Green:{2} Blue:{3}", Alpha, Red, Green, Blue);

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

@ -3,6 +3,11 @@
// Copyright (c) James South. // Copyright (c) James South.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
// </copyright> // </copyright>
// <summary>
// Encapsulates methods to calculate the color palette of an image using
// a Wu color quantizer <see href="http://www.ece.mcmaster.ca/~xwu/cq.c" />.
// Adapted from <see href="https://github.com/drewnoakes" />
// </summary>
// -------------------------------------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Imaging.Quantizers.WuQuantizer namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
@ -16,67 +21,68 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
/// a Wu color quantizer <see href="http://www.ece.mcmaster.ca/~xwu/cq.c"/>. /// a Wu color quantizer <see href="http://www.ece.mcmaster.ca/~xwu/cq.c"/>.
/// Adapted from <see href="https://github.com/drewnoakes"/> /// Adapted from <see href="https://github.com/drewnoakes"/>
/// </summary> /// </summary>
public class WuQuantizer : WuQuantizerBase, IWuQuantizer public class WuQuantizer : WuQuantizerBase
{ {
/// <summary> /// <summary>
/// The get quantized image. /// Quantizes the image contained within the <see cref="ImageBuffer"/> returning the result.
/// </summary> /// </summary>
/// <param name="image"> /// <param name="imageBuffer">
/// The image. /// The <see cref="ImageBuffer"/> for storing and manipulating pixel information..
/// </param> /// </param>
/// <param name="colorCount"> /// <param name="colorCount">
/// The color count. /// The maximum number of colors apply to the image.
/// </param> /// </param>
/// <param name="lookups"> /// <param name="lookups">
/// The lookups. /// The array of <see cref="Pixel"/> containing indexed versions of the images colors.
/// </param> /// </param>
/// <param name="alphaThreshold"> /// <param name="alphaThreshold">
/// The alpha threshold. /// All colors with an alpha value less than this will be considered fully transparent.
/// </param> /// </param>
/// <returns> /// <returns>
/// The <see cref="Image"/>. /// The quantized <see cref="Bitmap"/>.
/// </returns> /// </returns>
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); Bitmap result = new Bitmap(imageBuffer.Image.Width, imageBuffer.Image.Height, PixelFormat.Format8bppIndexed);
result.SetResolution(image.Image.HorizontalResolution, image.Image.VerticalResolution); result.SetResolution(imageBuffer.Image.HorizontalResolution, imageBuffer.Image.VerticalResolution);
ImageBuffer resultBuffer = new ImageBuffer(result); ImageBuffer resultBuffer = new ImageBuffer(result);
PaletteColorHistory[] paletteHistogram = new PaletteColorHistory[colorCount + 1]; 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); result.Palette = BuildPalette(result.Palette, paletteHistogram);
return result; return result;
} }
/// <summary> /// <summary>
/// The build palette. /// Builds a color palette from the given <see cref="PaletteColorHistory"/>.
/// </summary> /// </summary>
/// <param name="palette"> /// <param name="palette">
/// The palette. /// The <see cref="ColorPalette"/> to fill.
/// </param> /// </param>
/// <param name="paletteHistogram"> /// <param name="paletteHistory">
/// The palette histogram. /// The <see cref="PaletteColorHistory"/> containing the sum of all pixel data.
/// </param> /// </param>
/// <returns> /// <returns>
/// The <see cref="ColorPalette"/>. /// The <see cref="ColorPalette"/>.
/// </returns> /// </returns>
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; return palette;
} }
/// <summary> /// <summary>
/// The indexed pixels. /// Gets an enumerable array of bytes representing each row of the image.
/// </summary> /// </summary>
/// <param name="image"> /// <param name="image">
/// The image. /// The <see cref="ImageBuffer"/> for storing and manipulating pixel information.
/// </param> /// </param>
/// <param name="lookups"> /// <param name="lookups">
/// The lookups. /// The array of <see cref="Pixel"/> containing indexed versions of the images colors.
/// </param> /// </param>
/// <param name="alphaThreshold"> /// <param name="alphaThreshold">
/// The alpha threshold. /// The alpha threshold.
@ -85,7 +91,7 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
/// The palette histogram. /// The palette histogram.
/// </param> /// </param>
/// <returns> /// <returns>
/// The <see cref="IEnumerable"/>. /// The enumerable list of <see cref="byte"/> representing each pixel.
/// </returns> /// </returns>
private static IEnumerable<byte[]> IndexedPixels(ImageBuffer image, Pixel[] lookups, int alphaThreshold, PaletteColorHistory[] paletteHistogram) private static IEnumerable<byte[]> IndexedPixels(ImageBuffer image, Pixel[] lookups, int alphaThreshold, PaletteColorHistory[] paletteHistogram)
{ {
@ -93,12 +99,13 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
PaletteLookup lookup = new PaletteLookup(lookups); PaletteLookup lookup = new PaletteLookup(lookups);
// Determine the correct fallback color. // 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) 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; byte bestMatch = fallback;
if (pixel.Alpha > alphaThreshold) if (pixel.Alpha > alphaThreshold)
{ {
@ -106,7 +113,7 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
paletteHistogram[bestMatch].AddPixel(pixel); paletteHistogram[bestMatch].AddPixel(pixel);
} }
lineIndexes[pixelIndex] = bestMatch; lineIndexes[i] = bestMatch;
} }
yield return lineIndexes; yield return lineIndexes;

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

@ -1,32 +1,29 @@
// -------------------------------------------------------------------------------------------------------------------- namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
// <copyright file="WuQuantizerBase.cs" company="James South">
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// Encapsulates methods to calculate the color palette of an image using
// a Wu color quantizer <see href="http://www.ece.mcmaster.ca/~xwu/cq.c" />.
// Adapted from <see href="https://github.com/drewnoakes" />
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
{ {
using System; using System;
using System.Diagnostics.CodeAnalysis;
using System.Drawing; using System.Drawing;
using System.Drawing.Imaging;
using System.Linq; using System.Linq;
using ImageProcessor.Common.Exceptions;
/// <summary> /// <summary>
/// Encapsulates methods to calculate the color palette of an image using /// Encapsulates methods to calculate the color palette of an image using
/// a Wu color quantizer <see href="http://www.ece.mcmaster.ca/~xwu/cq.c"/>. /// a Wu color quantizer <see href="http://www.ece.mcmaster.ca/~xwu/cq.c"/>.
/// Adapted from <see href="https://github.com/drewnoakes"/> /// Adapted from <see href="https://github.com/drewnoakes"/>
/// </summary> /// </summary>
public abstract class WuQuantizerBase public abstract class WuQuantizerBase : IWuQuantizer
{ {
/// <summary> /// <summary>
/// The alpha color component. /// The maximum value for an alpha color component.
/// </summary> /// </summary>
protected const byte AlphaColor = 255; protected const byte AlphaMax = 255;
/// <summary>
/// The minimum value for an alpha color component.
/// </summary>
protected const byte AlphaMin = 0;
/// <summary> /// <summary>
/// The position of the alpha component within a byte array. /// The position of the alpha component within a byte array.
@ -64,10 +61,12 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
/// <param name="source"> /// <param name="source">
/// The 32 bit per pixel image to quantize. /// The 32 bit per pixel image to quantize.
/// </param> /// </param>
/// <returns>A quantized version of the image.</returns> /// <returns>
public Image QuantizeImage(Bitmap source) /// A quantized version of the image.
/// </returns>
public Bitmap Quantize(Image source)
{ {
return this.QuantizeImage(source, 0, 1); return this.Quantize(source, 0, 1);
} }
/// <summary> /// <summary>
@ -76,12 +75,18 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
/// <param name="source"> /// <param name="source">
/// The 32 bit per pixel image to quantize. /// The 32 bit per pixel image to quantize.
/// </param> /// </param>
/// <param name="alphaThreshold">All colors with an alpha value less than this will be considered fully transparent.</param> /// <param name="alphaThreshold">
/// <param name="alphaFader">Alpha values will be normalized to the nearest multiple of this value.</param> /// All colors with an alpha value less than this will be considered fully transparent.
/// <returns>A quantized version of the image.</returns> /// </param>
public Image QuantizeImage(Bitmap source, int alphaThreshold, int alphaFader) /// <param name="alphaFader">
/// Alpha values will be normalized to the nearest multiple of this value.
/// </param>
/// <returns>
/// A quantized version of the image.
/// </returns>
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);
} }
/// <summary> /// <summary>
@ -105,26 +110,73 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
/// <returns> /// <returns>
/// A quantized version of the image. /// A quantized version of the image.
/// </returns> /// </returns>
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); try
if (histogram == null)
{ {
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);
} }
/// <summary>
/// Quantizes the image contained within the <see cref="ImageBuffer"/> returning the result.
/// </summary>
/// <param name="imageBuffer">
/// The <see cref="ImageBuffer"/> for storing and manipulating pixel information..
/// </param>
/// <param name="colorCount">
/// The maximum number of colors apply to the image.
/// </param>
/// <param name="lookups">
/// The array of <see cref="Pixel"/> containing indexed versions of the images colors.
/// </param>
/// <param name="alphaThreshold">
/// All colors with an alpha value less than this will be considered fully transparent.
/// </param>
/// <returns>
/// The quantized <see cref="Bitmap"/>.
/// </returns>
internal abstract Bitmap GetQuantizedImage(ImageBuffer imageBuffer, int colorCount, Pixel[] lookups, int alphaThreshold);
/// <summary> /// <summary>
/// Builds a histogram from the current image. /// Builds a histogram from the current image.
/// </summary> /// </summary>
@ -140,6 +192,7 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
/// <param name="alphaFader"> /// <param name="alphaFader">
/// Alpha values will be normalized to the nearest multiple of this value. /// Alpha values will be normalized to the nearest multiple of this value.
/// </param> /// </param>
[SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1001:CommasMustBeSpacedCorrectly", Justification = "Reviewed. Suppression is OK here.")]
private static void BuildHistogram(Histogram histogram, ImageBuffer imageBuffer, int alphaThreshold, int alphaFader) private static void BuildHistogram(Histogram histogram, ImageBuffer imageBuffer, int alphaThreshold, int alphaFader)
{ {
ColorMoment[, , ,] moments = histogram.Moments; 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)); moments[0, 0, 0, 0].Add(new Pixel(0, 0, 0, 0));
} }
/// <summary>
/// Calculates the color moments from the histogram of moments.
/// </summary>
/// <param name="moments">
/// The three dimensional array of <see cref="ColorMoment"/> to process.
/// </param>
[SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1001:CommasMustBeSpacedCorrectly", Justification = "Reviewed. Suppression is OK here.")]
private static void CalculateMoments(ColorMoment[, , ,] moments) private static void CalculateMoments(ColorMoment[, , ,] moments)
{ {
ColorMoment[,] xarea = new ColorMoment[SideSize, SideSize]; ColorMoment[,] areaSquared = new ColorMoment[SideSize, SideSize];
ColorMoment[] area = new ColorMoment[SideSize]; ColorMoment[] area = new ColorMoment[SideSize];
for (int alphaIndex = 1; alphaIndex < SideSize; alphaIndex++) for (int alphaIndex = 1; alphaIndex < SideSize; alphaIndex++)
{ {
@ -190,10 +250,10 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
{ {
line.AddFast(ref moments[alphaIndex, redIndex, greenIndex, blueIndex]); line.AddFast(ref moments[alphaIndex, redIndex, greenIndex, blueIndex]);
area[blueIndex].AddFast(ref line); area[blueIndex].AddFast(ref line);
xarea[greenIndex, blueIndex].AddFast(ref area[blueIndex]); areaSquared[greenIndex, blueIndex].AddFast(ref area[blueIndex]);
ColorMoment moment = moments[alphaIndex - 1, redIndex, greenIndex, 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; moments[alphaIndex, redIndex, greenIndex, blueIndex] = moment;
} }
} }
@ -201,6 +261,25 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
} }
} }
/// <summary>
/// Calculates the volume of the top of the cube.
/// </summary>
/// <param name="cube">
/// The cube to calculate the volume from.
/// </param>
/// <param name="direction">
/// The direction to calculate.
/// </param>
/// <param name="position">
/// The position at which to begin.
/// </param>
/// <param name="moment">
/// The three dimensional moment.
/// </param>
/// <returns>
/// The <see cref="ColorMoment"/> representing the top of the cube.
/// </returns>
[SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1001:CommasMustBeSpacedCorrectly", Justification = "Reviewed. Suppression is OK here.")]
private static ColorMoment Top(Box cube, int direction, int position, ColorMoment[, , ,] moment) private static ColorMoment Top(Box cube, int direction, int position, ColorMoment[, , ,] moment)
{ {
switch (direction) switch (direction)
@ -250,6 +329,22 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
} }
} }
/// <summary>
/// Calculates the volume of the bottom of the cube.
/// </summary>
/// <param name="cube">
/// The cube to calculate the volume from.
/// </param>
/// <param name="direction">
/// The direction to calculate.
/// </param>
/// <param name="moment">
/// The three dimensional moment.
/// </param>
/// <returns>
/// The <see cref="ColorMoment"/> representing the bottom of the cube.
/// </returns>
[SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1001:CommasMustBeSpacedCorrectly", Justification = "Reviewed. Suppression is OK here.")]
private static ColorMoment Bottom(Box cube, int direction, ColorMoment[, , ,] moment) private static ColorMoment Bottom(Box cube, int direction, ColorMoment[, , ,] moment)
{ {
switch (direction) switch (direction)
@ -299,10 +394,35 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
} }
} }
/// <summary>
/// Maximizes the sum of the two boxes.
/// </summary>
/// <param name="moments">
/// The <see cref="ColorMoment"/>.
/// </param>
/// <param name="cube">
/// The <see cref="Box"/> cube.
/// </param>
/// <param name="direction">
/// The direction.
/// </param>
/// <param name="first">
/// The first byte.
/// </param>
/// <param name="last">
/// The last byte.
/// </param>
/// <param name="whole">
/// The whole <see cref="ColorMoment"/>.
/// </param>
/// <returns>
/// The <see cref="CubeCut"/> representing the sum.
/// </returns>
[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) private static CubeCut Maximize(ColorMoment[, , ,] moments, Box cube, int direction, byte first, byte last, ColorMoment whole)
{ {
var bottom = Bottom(cube, direction, moments); ColorMoment bottom = Bottom(cube, direction, moments);
var result = 0.0f; float result = 0.0f;
byte? cutPoint = null; byte? cutPoint = null;
for (byte position = first; position < last; ++position) for (byte position = first; position < last; ++position)
@ -313,7 +433,7 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
continue; continue;
} }
var temp = half.WeightedDistance(); long temp = half.WeightedDistance();
half = whole - half; half = whole - half;
if (half.Weight != 0) if (half.Weight != 0)
@ -331,28 +451,55 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
return new CubeCut(cutPoint, result); return new CubeCut(cutPoint, result);
} }
/// <summary>
/// Returns a value indicating whether a cube can be cut.
/// </summary>
/// <param name="moments">
/// The three dimensional array of <see cref="ColorMoment"/>.
/// </param>
/// <param name="first">
/// The first <see cref="Box"/>.
/// </param>
/// <param name="second">
/// The second <see cref="Box"/>.
/// </param>
/// <returns>
/// The <see cref="bool"/> indicating the result.
/// </returns>
[SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1001:CommasMustBeSpacedCorrectly", Justification = "Reviewed. Suppression is OK here.")]
private static bool Cut(ColorMoment[, , ,] moments, ref Box first, ref Box second) private static bool Cut(ColorMoment[, , ,] moments, ref Box first, ref Box second)
{ {
int direction; int direction;
var whole = Volume(first, moments); ColorMoment whole = Volume(moments, first);
var maxAlpha = Maximize(moments, first, Alpha, (byte)(first.AlphaMinimum + 1), first.AlphaMaximum, whole); CubeCut 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); CubeCut 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); CubeCut 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); 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)) if ((maxAlpha.Value >= maxRed.Value) && (maxAlpha.Value >= maxGreen.Value) && (maxAlpha.Value >= maxBlue.Value))
{ {
direction = Alpha; 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; direction = Red;
}
else 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; direction = Green;
}
else else
{
direction = Blue; direction = Blue;
}
} }
second.AlphaMaximum = first.AlphaMaximum; second.AlphaMaximum = first.AlphaMaximum;
@ -363,6 +510,11 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
switch (direction) switch (direction)
{ {
case Alpha: case Alpha:
if (maxAlpha.Position == null)
{
return false;
}
second.AlphaMinimum = first.AlphaMaximum = (byte)maxAlpha.Position; second.AlphaMinimum = first.AlphaMaximum = (byte)maxAlpha.Position;
second.RedMinimum = first.RedMinimum; second.RedMinimum = first.RedMinimum;
second.GreenMinimum = first.GreenMinimum; second.GreenMinimum = first.GreenMinimum;
@ -370,6 +522,11 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
break; break;
case Red: case Red:
if (maxRed.Position == null)
{
return false;
}
second.RedMinimum = first.RedMaximum = (byte)maxRed.Position; second.RedMinimum = first.RedMaximum = (byte)maxRed.Position;
second.AlphaMinimum = first.AlphaMinimum; second.AlphaMinimum = first.AlphaMinimum;
second.GreenMinimum = first.GreenMinimum; second.GreenMinimum = first.GreenMinimum;
@ -377,6 +534,11 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
break; break;
case Green: case Green:
if (maxGreen.Position == null)
{
return false;
}
second.GreenMinimum = first.GreenMaximum = (byte)maxGreen.Position; second.GreenMinimum = first.GreenMaximum = (byte)maxGreen.Position;
second.AlphaMinimum = first.AlphaMinimum; second.AlphaMinimum = first.AlphaMinimum;
second.RedMinimum = first.RedMinimum; second.RedMinimum = first.RedMinimum;
@ -384,6 +546,11 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
break; break;
case Blue: case Blue:
if (maxBlue.Position == null)
{
return false;
}
second.BlueMinimum = first.BlueMaximum = (byte)maxBlue.Position; second.BlueMinimum = first.BlueMaximum = (byte)maxBlue.Position;
second.AlphaMinimum = first.AlphaMinimum; second.AlphaMinimum = first.AlphaMinimum;
second.RedMinimum = first.RedMinimum; second.RedMinimum = first.RedMinimum;
@ -397,44 +564,83 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
return true; return true;
} }
/// <summary>
/// Calculates the variance of the volume of the cube.
/// </summary>
/// <param name="moments">
/// The three dimensional array of <see cref="ColorMoment"/>.
/// </param>
/// <param name="cube">
/// The <see cref="Box"/> cube.
/// </param>
/// <returns>
/// The <see cref="float"/> representing the variance.
/// </returns>
[SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1001:CommasMustBeSpacedCorrectly", Justification = "Reviewed. Suppression is OK here.")]
private static float CalculateVariance(ColorMoment[, , ,] moments, Box cube) private static float CalculateVariance(ColorMoment[, , ,] moments, Box cube)
{ {
ColorMoment volume = Volume(cube, moments); ColorMoment volume = Volume(moments, cube);
return volume.Variance(); return volume.Variance();
} }
private static ColorMoment Volume(Box cube, ColorMoment[, , ,] moment) /// <summary>
/// Calculates the volume of the colors.
/// </summary>
/// <param name="moments">
/// The three dimensional array of <see cref="ColorMoment"/>.
/// </param>
/// <param name="cube">
/// The <see cref="Box"/> cube.
/// </param>
/// <returns>
/// The <see cref="float"/> representing the volume.
/// </returns>
[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] - return (moments[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMaximum, cube.BlueMaximum] -
moment[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMaximum] - moments[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMaximum] -
moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMaximum] + moments[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMaximum] +
moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMaximum] - moments[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMaximum] -
moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMaximum, cube.BlueMaximum] + moments[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMaximum, cube.BlueMaximum] +
moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMaximum] + moments[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMaximum] +
moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMaximum] - moments[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMaximum] -
moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMaximum]) - moments[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMaximum]) -
(moment[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMaximum, cube.BlueMinimum] - (moments[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMaximum, cube.BlueMinimum] -
moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMaximum, cube.BlueMinimum] - moments[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMaximum, cube.BlueMinimum] -
moment[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMinimum] + moments[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMinimum] +
moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMinimum] - moments[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMinimum] -
moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMinimum] + moments[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMinimum] +
moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMinimum] + moments[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMinimum] +
moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum] - moments[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum] -
moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum]); moments[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum]);
} }
/// <summary>
/// Splits the data.
/// </summary>
/// <param name="colorCount">
/// The color count.
/// </param>
/// <param name="moments">
/// The three dimensional array of <see cref="ColorMoment"/>.
/// </param>
/// <returns>
/// The array <see cref="Box"/>.
/// </returns>
[SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1001:CommasMustBeSpacedCorrectly", Justification = "Reviewed. Suppression is OK here.")]
private static Box[] SplitData(ref int colorCount, ColorMoment[, , ,] moments) private static Box[] SplitData(ref int colorCount, ColorMoment[, , ,] moments)
{ {
--colorCount; --colorCount;
var next = 0; int next = 0;
var volumeVariance = new float[colorCount]; float[] volumeVariance = new float[colorCount];
var cubes = new Box[colorCount]; Box[] cubes = new Box[colorCount];
cubes[0].AlphaMaximum = MaxSideIndex; cubes[0].AlphaMaximum = MaxSideIndex;
cubes[0].RedMaximum = MaxSideIndex; cubes[0].RedMaximum = MaxSideIndex;
cubes[0].GreenMaximum = MaxSideIndex; cubes[0].GreenMaximum = MaxSideIndex;
cubes[0].BlueMaximum = MaxSideIndex; cubes[0].BlueMaximum = MaxSideIndex;
for (var cubeIndex = 1; cubeIndex < colorCount; ++cubeIndex) for (int cubeIndex = 1; cubeIndex < colorCount; ++cubeIndex)
{ {
if (Cut(moments, ref cubes[next], ref cubes[cubeIndex])) if (Cut(moments, ref cubes[next], ref cubes[cubeIndex]))
{ {
@ -448,29 +654,51 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
} }
next = 0; 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]; temp = volumeVariance[index];
next = index; next = index;
} }
if (temp > 0.0) continue; if (temp > 0.0)
{
continue;
}
colorCount = cubeIndex + 1; colorCount = cubeIndex + 1;
break; break;
} }
return cubes.Take(colorCount).ToArray(); return cubes.Take(colorCount).ToArray();
} }
/// <summary>
/// Builds an array of pixel data to look within.
/// </summary>
/// <param name="cubes">
/// The array of <see cref="Box"/> cubes.
/// </param>
/// <param name="moments">
/// The three dimensional array of <see cref="ColorMoment"/>.
/// </param>
/// <returns>
/// The array of <see cref="Pixel"/>.
/// </returns>
[SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1001:CommasMustBeSpacedCorrectly", Justification = "Reviewed. Suppression is OK here.")]
private static Pixel[] BuildLookups(Box[] cubes, ColorMoment[, , ,] moments) private static Pixel[] BuildLookups(Box[] cubes, ColorMoment[, , ,] moments)
{ {
Pixel[] lookups = new Pixel[cubes.Length]; Pixel[] lookups = new Pixel[cubes.Length];
for (int cubeIndex = 0; cubeIndex < cubes.Length; cubeIndex++) for (int cubeIndex = 0; cubeIndex < cubes.Length; cubeIndex++)
{ {
ColorMoment volume = Volume(cubes[cubeIndex], moments); ColorMoment volume = Volume(moments, cubes[cubeIndex]);
if (volume.Weight <= 0) if (volume.Weight <= 0)
{ {
@ -490,7 +718,5 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
return lookups; return lookups;
} }
internal abstract Image GetQuantizedImage(ImageBuffer image, int colorCount, Pixel[] lookups, int alphaThreshold);
} }
} }
Loading…
Cancel
Save