Browse Source

More tidy up and optimization

Former-commit-id: 834ddeb85ddf5af32efc20eb5692d7f3db10bd62
Former-commit-id: 188d948fb7af66508093e053eb5586789c8672bb
pull/17/head
James South 11 years ago
parent
commit
d9847df309
  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>
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>
/// Initializes a new instance of the <see cref="OctreeQuantizer"/> class.
/// </summary>
@ -69,7 +84,9 @@ namespace ImageProcessor.Imaging.Quantizers
/// <summary>
/// Process the pixel in the first pass of the algorithm
/// </summary>
/// <param name="pixel">The pixel to quantize</param>
/// <param name="pixel">
/// The pixel to quantize
/// </param>
/// <remarks>
/// 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
/// <summary>
/// Override this to process the pixel in the second pass of the algorithm
/// </summary>
/// <param name="pixel">The pixel to quantize</param>
/// <returns>The quantized value</returns>
/// <param name="pixel">
/// The pixel to quantize
/// </param>
/// <returns>
/// The quantized value
/// </returns>
protected override byte QuantizePixel(Color32* pixel)
{
// The color at [_maxColors] is set to transparent
@ -102,8 +123,12 @@ namespace ImageProcessor.Imaging.Quantizers
/// <summary>
/// Retrieve the palette for the quantized image
/// </summary>
/// <param name="original">Any old palette, this is overwritten</param>
/// <returns>The new color palette</returns>
/// <param name="original">
/// Any old palette, this is overwritten
/// </param>
/// <returns>
/// The new color palette
/// </returns>
protected override ColorPalette GetPalette(ColorPalette original)
{
// First off convert the Octree to maxColors colors
@ -228,8 +253,12 @@ namespace ImageProcessor.Imaging.Quantizers
/// <summary>
/// Convert the nodes in the Octree to a palette with a maximum of colorCount colors
/// </summary>
/// <param name="colorCount">The maximum number of colors</param>
/// <returns>An <see cref="ArrayList"/> with the palletized colors</returns>
/// <param name="colorCount">
/// The maximum number of colors
/// </param>
/// <returns>
/// An <see cref="ArrayList"/> with the palletized colors
/// </returns>
public ArrayList Palletize(int colorCount)
{
while (this.Leaves > colorCount)
@ -263,7 +292,9 @@ namespace ImageProcessor.Imaging.Quantizers
/// <summary>
/// Keep track of the previous node that was quantized
/// </summary>
/// <param name="node">The node last quantized</param>
/// <param name="node">
/// The node last quantized
/// </param>
protected void TrackPrevious(OctreeNode node)
{
this.previousNode = node;
@ -385,10 +416,18 @@ namespace ImageProcessor.Imaging.Quantizers
/// <summary>
/// Add a color into the tree
/// </summary>
/// <param name="pixel">The color</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>
/// <param name="pixel">
/// The color
/// </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)
{
// Update the color information if this is a leaf
@ -454,8 +493,12 @@ namespace ImageProcessor.Imaging.Quantizers
/// <summary>
/// Traverse the tree, building up the color palette
/// </summary>
/// <param name="palette">The palette</param>
/// <param name="index">The current palette index</param>
/// <param name="palette">
/// The palette
/// </param>
/// <param name="index">
/// The current palette index
/// </param>
public void ConstructPalette(ArrayList palette, ref int index)
{
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.
/// <see href="http://msdn.microsoft.com/en-us/library/aa479306.aspx"/>
/// </summary>
public unsafe abstract class Quantizer
public unsafe abstract class Quantizer : IQuantizer
{
/// <summary>
/// Flag used to indicate whether a single pass or two passes are needed for quantization.
@ -46,8 +46,12 @@ namespace ImageProcessor.Imaging.Quantizers
/// <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>
/// <param name="source">
/// The image to quantize.
/// </param>
/// <returns>
/// A quantized version of the image.
/// </returns>
public Bitmap Quantize(Image source)
{
// Get the size of the source image
@ -111,9 +115,15 @@ namespace ImageProcessor.Imaging.Quantizers
/// <summary>
/// Execute the first pass through the pixels in the image
/// </summary>
/// <param name="sourceData">The source data</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="sourceData">
/// The source data
/// </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)
{
// Define the source data pointers. The source row is a byte to
@ -141,11 +151,21 @@ namespace ImageProcessor.Imaging.Quantizers
/// <summary>
/// Execute a second pass through the bitmap
/// </summary>
/// <param name="sourceData">The source bitmap, locked into memory</param>
/// <param name="output">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>
/// <param name="sourceData">
/// The source bitmap, locked into memory
/// </param>
/// <param name="output">
/// 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)
{
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
/// <summary>
/// Override this to process the pixel in the first pass of the algorithm
/// </summary>
/// <param name="pixel">The pixel to quantize</param>
/// <param name="pixel">
/// The pixel to quantize
/// </param>
/// <remarks>
/// 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
/// <summary>
/// Override this to process the pixel in the second pass of the algorithm
/// </summary>
/// <param name="pixel">The pixel to quantize</param>
/// <returns>The quantized value</returns>
/// <param name="pixel">
/// The pixel to quantize
/// </param>
/// <returns>
/// The quantized value
/// </returns>
protected abstract byte QuantizePixel(Color32* pixel);
/// <summary>
/// Retrieve the palette for the quantized image
/// </summary>
/// <param name="original">Any old palette, this is overwritten</param>
/// <returns>The new color palette</returns>
/// <param name="original">
/// Any old palette, this is overwritten
/// </param>
/// <returns>
/// The new color palette
/// </returns>
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.
// </copyright>
// <summary>
// The color moment for holding pixel information.
// The cumulative color moment for holding pixel information.
// Adapted from <see href="https://github.com/drewnoakes" />
// </summary>
// --------------------------------------------------------------------------------------------------------------------

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

@ -4,7 +4,8 @@
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <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" />
// </summary>
// --------------------------------------------------------------------------------------------------------------------
@ -14,10 +15,11 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
using System.Drawing;
/// <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" />
/// </summary>
public interface IWuQuantizer
public interface IWuQuantizer : IQuantizer
{
/// <summary>
/// Quantizes the given image.
@ -35,6 +37,6 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
/// <returns>
/// The quantized <see cref="Image"/>.
/// </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.
// </copyright>
// <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" />
// </summary>
// --------------------------------------------------------------------------------------------------------------------
@ -20,7 +20,7 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
using ImageProcessor.Common.Exceptions;
/// <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"/>
/// </summary>
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];

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

@ -4,7 +4,7 @@
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// The palette color history.
// The palette color history containing the sum of all pixel data.
// Adapted from <see href="https://github.com/drewnoakes" />
// </summary>
// --------------------------------------------------------------------------------------------------------------------
@ -14,7 +14,7 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
using System.Drawing;
/// <summary>
/// The palette color history.
/// The palette color history containing the sum of all pixel data.
/// Adapted from <see href="https://github.com/drewnoakes" />
/// </summary>
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
{
Pixel = palette[paletteIndex],
Pixel = palette[paletteIndex],
PaletteIndex = (byte)paletteIndex
};
}
@ -240,7 +240,7 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
}
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();
}

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.Runtime.InteropServices;
namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
{
/// <summary>
/// The pixel.
/// </summary>
[StructLayout(LayoutKind.Explicit)]
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)
: this()
{
@ -16,6 +44,12 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
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)
: this()
{
@ -26,17 +60,42 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
Debug.Assert(Blue == ((uint)argb & 255));
}
/// <summary>
/// The alpha.
/// </summary>
[FieldOffset(3)]
public byte Alpha;
/// <summary>
/// The red.
/// </summary>
[FieldOffset(2)]
public byte Red;
/// <summary>
/// The green.
/// </summary>
[FieldOffset(1)]
public byte Green;
/// <summary>
/// The blue.
/// </summary>
[FieldOffset(0)]
public byte Blue;
/// <summary>
/// The argb.
/// </summary>
[FieldOffset(0)]
public int Argb;
/// <summary>
/// The to string.
/// </summary>
/// <returns>
/// The <see cref="string"/>.
/// </returns>
public override string ToString()
{
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.
// 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
@ -16,67 +21,68 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
/// a Wu color quantizer <see href="http://www.ece.mcmaster.ca/~xwu/cq.c"/>.
/// Adapted from <see href="https://github.com/drewnoakes"/>
/// </summary>
public class WuQuantizer : WuQuantizerBase, IWuQuantizer
public class WuQuantizer : WuQuantizerBase
{
/// <summary>
/// The get quantized image.
/// Quantizes the image contained within the <see cref="ImageBuffer"/> returning the result.
/// </summary>
/// <param name="image">
/// The image.
/// <param name="imageBuffer">
/// The <see cref="ImageBuffer"/> for storing and manipulating pixel information..
/// </param>
/// <param name="colorCount">
/// The color count.
/// The maximum number of colors apply to the image.
/// </param>
/// <param name="lookups">
/// The lookups.
/// The array of <see cref="Pixel"/> containing indexed versions of the images colors.
/// </param>
/// <param name="alphaThreshold">
/// The alpha threshold.
/// All colors with an alpha value less than this will be considered fully transparent.
/// </param>
/// <returns>
/// The <see cref="Image"/>.
/// The quantized <see cref="Bitmap"/>.
/// </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);
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;
}
/// <summary>
/// The build palette.
/// Builds a color palette from the given <see cref="PaletteColorHistory"/>.
/// </summary>
/// <param name="palette">
/// The palette.
/// The <see cref="ColorPalette"/> to fill.
/// </param>
/// <param name="paletteHistogram">
/// The palette histogram.
/// <param name="paletteHistory">
/// The <see cref="PaletteColorHistory"/> containing the sum of all pixel data.
/// </param>
/// <returns>
/// The <see cref="ColorPalette"/>.
/// </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;
}
/// <summary>
/// The indexed pixels.
/// Gets an enumerable array of bytes representing each row of the image.
/// </summary>
/// <param name="image">
/// The image.
/// The <see cref="ImageBuffer"/> for storing and manipulating pixel information.
/// </param>
/// <param name="lookups">
/// The lookups.
/// The array of <see cref="Pixel"/> containing indexed versions of the images colors.
/// </param>
/// <param name="alphaThreshold">
/// The alpha threshold.
@ -85,7 +91,7 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
/// The palette histogram.
/// </param>
/// <returns>
/// The <see cref="IEnumerable"/>.
/// The enumerable list of <see cref="byte"/> representing each pixel.
/// </returns>
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);
// 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;

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

@ -1,32 +1,29 @@
// --------------------------------------------------------------------------------------------------------------------
// <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
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;
/// <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>
public abstract class WuQuantizerBase
public abstract class WuQuantizerBase : IWuQuantizer
{
/// <summary>
/// The alpha color component.
/// The maximum value for an alpha color component.
/// </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>
/// The position of the alpha component within a byte array.
@ -64,10 +61,12 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
/// <param name="source">
/// The 32 bit per pixel image to quantize.
/// </param>
/// <returns>A quantized version of the image.</returns>
public Image QuantizeImage(Bitmap source)
/// <returns>
/// 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>
@ -76,12 +75,18 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
/// <param name="source">
/// The 32 bit per pixel image to quantize.
/// </param>
/// <param name="alphaThreshold">All colors with an alpha value less than this will be considered fully transparent.</param>
/// <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 Image QuantizeImage(Bitmap source, int alphaThreshold, int alphaFader)
/// <param name="alphaThreshold">
/// All colors with an alpha value less than this will be considered fully transparent.
/// </param>
/// <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>
@ -105,26 +110,73 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
/// <returns>
/// A quantized version of the image.
/// </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);
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);
}
/// <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>
/// Builds a histogram from the current image.
/// </summary>
@ -140,6 +192,7 @@ namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
/// <param name="alphaFader">
/// Alpha values will be normalized to the nearest multiple of this value.
/// </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)
{
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));
}
/// <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)
{
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
}
}
/// <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)
{
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)
{
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)
{
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);
}
/// <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)
{
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;
}
/// <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)
{
ColorMoment volume = Volume(cube, moments);
ColorMoment volume = Volume(moments, cube);
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] -
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]);
}
/// <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)
{
--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();
}
/// <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)
{
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);
}
}
Loading…
Cancel
Save