Browse Source

Merge remote-tracking branch 'origin/Core-Flava' into Core-Flava

Former-commit-id: 358477e4d69a9b31237386341fbb735a8296c96a
Former-commit-id: ef6bcf0b3b3bd81e57cf59221dbdcdfdb51e0a9c
Former-commit-id: 331c35405c129d9bb514c5ea5204fbc317d8a768
af/merge-core
James Jackson-South 10 years ago
parent
commit
51b3b75273
  1. 11
      src/ImageProcessorCore/Formats/Png/PngEncoderCore.cs
  2. 28
      src/ImageProcessorCore/Quantizers/IQuantizer.cs
  3. 538
      src/ImageProcessorCore/Quantizers/Octree/OctreeQuantizer.cs
  4. 149
      src/ImageProcessorCore/Quantizers/Octree/Quantizer.cs
  5. 25
      src/ImageProcessorCore/Quantizers/Wu/WuQuantizer.cs

11
src/ImageProcessorCore/Formats/Png/PngEncoderCore.cs

@ -9,7 +9,7 @@ namespace ImageProcessorCore.Formats
using System.IO;
using System.Threading.Tasks;
using ImageProcessorCore.Quantizers;
using Quantizers;
/// <summary>
/// Performs the png encoding operation.
@ -218,11 +218,12 @@ namespace ImageProcessorCore.Formats
if (this.Quantizer == null)
{
this.Quantizer = new WuQuantizer { Threshold = this.Threshold };
//this.Quantizer = new WuQuantizer<T, TP> { Threshold = this.Threshold };
this.Quantizer = new OctreeQuantizer<T, TP> { Threshold = this.Threshold };
}
// Quantize the image returning a palette.
QuantizedImage<T, TP> quantized = Quantizer.Quantize(image, this.Quality);
// Quantize the image returning a palette. This boxing is icky.
QuantizedImage<T, TP> quantized = ((IQuantizer<T, TP>)this.Quantizer).Quantize(image, this.Quality);
// Grab the palette and write it to the stream.
T[] palette = quantized.Palette;
@ -233,7 +234,7 @@ namespace ImageProcessorCore.Formats
byte[] colorTable = new byte[colorTableLength];
Parallel.For(
0,
0,
pixelCount,
Bootstrapper.Instance.ParallelOptions,
i =>

28
src/ImageProcessorCore/Quantizers/IQuantizer.cs

@ -8,25 +8,31 @@ namespace ImageProcessorCore.Quantizers
/// <summary>
/// Provides methods for allowing quantization of images pixels.
/// </summary>
public interface IQuantizer
/// <typeparam name="T">The pixel format.</typeparam>
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
public interface IQuantizer<T, TP> : IQuantizer
where T : IPackedVector<TP>
where TP : struct
{
/// <summary>
/// Gets or sets the transparency threshold.
/// </summary>
byte Threshold { get; set; }
/// <summary>
/// Quantize an image and return the resulting output pixels.
/// </summary>
/// <typeparam name="T">The pixel format.</typeparam>
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
/// <param name="image">The image to quantize.</param>
/// <param name="maxColors">The maximum number of colors to return.</param>
/// <returns>
/// A <see cref="T:QuantizedImage"/> representing a quantized version of the image pixels.
/// </returns>
QuantizedImage<T, TP> Quantize<T, TP>(ImageBase<T, TP> image, int maxColors)
where T : IPackedVector<TP>
where TP : struct;
QuantizedImage<T, TP> Quantize(ImageBase<T, TP> image, int maxColors);
}
/// <summary>
/// Provides methods for allowing quantization of images pixels.
/// </summary>
public interface IQuantizer
{
/// <summary>
/// Gets or sets the transparency threshold.
/// </summary>
byte Threshold { get; set; }
}
}

538
src/ImageProcessorCore/Quantizers/Octree/OctreeQuantizer.cs

@ -0,0 +1,538 @@
// <copyright file="OctreeQuantizer.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore.Quantizers
{
using System;
using System.Collections.Generic;
using System.Linq;
/// <summary>
/// Encapsulates methods to calculate the colour palette if an image using an Octree pattern.
/// <see href="http://msdn.microsoft.com/en-us/library/aa479306.aspx"/>
/// </summary>
/// <typeparam name="T">The pixel format.</typeparam>
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
public sealed class OctreeQuantizer<T, TP> : Quantizer<T, TP>
where T : IPackedVector<TP>
where TP : struct
{
/// <summary>
/// Stores the tree
/// </summary>
private Octree octree;
/// <summary>
/// Maximum allowed color depth
/// </summary>
private int colors;
/// <summary>
/// Initializes a new instance of the <see cref="OctreeQuantizer{T,TP}"/> 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
/// </remarks>
public OctreeQuantizer()
: base(false)
{
}
/// <inheritdoc/>
public override QuantizedImage<T, TP> Quantize(ImageBase<T, TP> image, int maxColors)
{
this.colors = maxColors.Clamp(1, 255);
if (this.octree == null)
{
// Construct the Octree
this.octree = new Octree(this.GetBitsNeededForColorDepth(maxColors));
}
return base.Quantize(image, maxColors);
}
/// <summary>
/// Process the pixel in the first pass of the algorithm
/// </summary>
/// <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.
/// </remarks>
protected override void InitialQuantizePixel(T pixel)
{
// Add the color to the Octree
this.octree.AddColor(pixel);
}
/// <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>
protected override byte QuantizePixel(T pixel)
{
// The color at [maxColors] is set to transparent
byte paletteIndex = (byte)this.colors;
// Get the palette index if it's transparency meets criterea.
if (pixel.ToBytes()[3] > this.Threshold)
{
paletteIndex = (byte)this.octree.GetPaletteIndex(pixel);
}
return paletteIndex;
}
/// <summary>
/// Retrieve the palette for the quantized image.
/// </summary>
/// <returns>
/// The new color palette
/// </returns>
protected override List<T> GetPalette()
{
// First off convert the Octree to maxColors colors
List<T> palette = this.octree.Palletize(Math.Max(this.colors, 1));
int diff = this.colors - palette.Count;
palette.AddRange(Enumerable.Repeat(default(T), diff));
this.TransparentIndex = this.colors;
return palette;
}
/// <summary>
/// Returns how many bits are required to store the specified number of colors.
/// Performs a Log2() on the value.
/// </summary>
/// <param name="colorCount">The number of colors.</param>
/// <returns>
/// The <see cref="int"/>
/// </returns>
private int GetBitsNeededForColorDepth(int colorCount)
{
return (int)Math.Ceiling(Math.Log(colorCount, 2));
}
/// <summary>
/// Class which does the actual quantization
/// </summary>
private class Octree
{
/// <summary>
/// Mask used when getting the appropriate pixels for a given node
/// </summary>
private static readonly int[] Mask = { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 };
/// <summary>
/// The root of the Octree
/// </summary>
private readonly OctreeNode root;
/// <summary>
/// Array of reducible nodes
/// </summary>
private readonly OctreeNode[] reducibleNodes;
/// <summary>
/// Maximum number of significant bits in the image
/// </summary>
private readonly int maxColorBits;
/// <summary>
/// Store the last node quantized
/// </summary>
private OctreeNode previousNode;
/// <summary>
/// Cache the previous color quantized
/// </summary>
private TP previousColor;
/// <summary>
/// Initializes a new instance of the <see cref="Octree"/> class.
/// </summary>
/// <param name="maxColorBits">
/// The maximum number of significant bits in the image
/// </param>
public Octree(int maxColorBits)
{
this.maxColorBits = maxColorBits;
this.Leaves = 0;
this.reducibleNodes = new OctreeNode[9];
this.root = new OctreeNode(0, this.maxColorBits, this);
this.previousColor = default(TP);
this.previousNode = null;
}
/// <summary>
/// Gets or sets the number of leaves in the tree
/// </summary>
private int Leaves { get; set; }
/// <summary>
/// Gets the array of reducible nodes
/// </summary>
private OctreeNode[] ReducibleNodes => this.reducibleNodes;
/// <summary>
/// Add a given color value to the Octree
/// </summary>
/// <param name="pixel">
/// The <see cref="T"/>containing color information to add.
/// </param>
public void AddColor(T pixel)
{
TP packed = pixel.PackedValue();
// Check if this request is for the same color as the last
if (this.previousColor.Equals(packed))
{
// If so, check if I have a previous node setup. This will only occur if the first color in the image
// happens to be black, with an alpha component of zero.
if (this.previousNode == null)
{
this.previousColor = packed;
this.root.AddColor(pixel, this.maxColorBits, 0, this);
}
else
{
// Just update the previous node
this.previousNode.Increment(pixel);
}
}
else
{
this.previousColor = packed;
this.root.AddColor(pixel, this.maxColorBits, 0, this);
}
}
/// <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="List{T}"/> with the palletized colors
/// </returns>
public List<T> Palletize(int colorCount)
{
while (this.Leaves > colorCount)
{
this.Reduce();
}
// Now palletize the nodes
List<T> palette = new List<T>(this.Leaves);
int paletteIndex = 0;
this.root.ConstructPalette(palette, ref paletteIndex);
// And return the palette
return palette;
}
/// <summary>
/// Get the palette index for the passed color
/// </summary>
/// <param name="pixel">
/// The <see cref="T"/> containing the pixel data.
/// </param>
/// <returns>
/// The index of the given structure.
/// </returns>
public int GetPaletteIndex(T pixel)
{
return this.root.GetPaletteIndex(pixel, 0);
}
/// <summary>
/// Keep track of the previous node that was quantized
/// </summary>
/// <param name="node">
/// The node last quantized
/// </param>
protected void TrackPrevious(OctreeNode node)
{
this.previousNode = node;
}
/// <summary>
/// Reduce the depth of the tree
/// </summary>
private void Reduce()
{
// Find the deepest level containing at least one reducible node
int index = this.maxColorBits - 1;
while ((index > 0) && (this.reducibleNodes[index] == null))
{
index--;
}
// Reduce the node most recently added to the list at level 'index'
OctreeNode node = this.reducibleNodes[index];
this.reducibleNodes[index] = node.NextReducible;
// Decrement the leaf count after reducing the node
this.Leaves -= node.Reduce();
// And just in case I've reduced the last color to be added, and the next color to
// be added is the same, invalidate the previousNode...
this.previousNode = null;
}
/// <summary>
/// Class which encapsulates each node in the tree
/// </summary>
protected class OctreeNode
{
/// <summary>
/// Pointers to any child nodes
/// </summary>
private readonly OctreeNode[] children;
/// <summary>
/// Flag indicating that this is a leaf node
/// </summary>
private bool leaf;
/// <summary>
/// Number of pixels in this node
/// </summary>
private int pixelCount;
/// <summary>
/// Red component
/// </summary>
private int red;
/// <summary>
/// Green Component
/// </summary>
private int green;
/// <summary>
/// Blue component
/// </summary>
private int blue;
/// <summary>
/// The index of this node in the palette
/// </summary>
private int paletteIndex;
/// <summary>
/// Initializes a new instance of the <see cref="OctreeNode"/> class.
/// </summary>
/// <param name="level">
/// The level in the tree = 0 - 7
/// </param>
/// <param name="colorBits">
/// The number of significant color bits in the image
/// </param>
/// <param name="octree">
/// The tree to which this node belongs
/// </param>
public OctreeNode(int level, int colorBits, Octree octree)
{
// Construct the new node
this.leaf = level == colorBits;
this.red = this.green = this.blue = 0;
this.pixelCount = 0;
// If a leaf, increment the leaf count
if (this.leaf)
{
octree.Leaves++;
this.NextReducible = null;
this.children = null;
}
else
{
// Otherwise add this to the reducible nodes
this.NextReducible = octree.ReducibleNodes[level];
octree.ReducibleNodes[level] = this;
this.children = new OctreeNode[8];
}
}
/// <summary>
/// Gets the next reducible node
/// </summary>
public OctreeNode NextReducible { get; }
/// <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>
public void AddColor(T pixel, int colorBits, int level, Octree octree)
{
// Update the color information if this is a leaf
if (this.leaf)
{
this.Increment(pixel);
// Setup the previous node
octree.TrackPrevious(this);
}
else
{
// Go to the next level down in the tree
int shift = 7 - level;
byte[] components = pixel.ToBytes();
int index = ((components[2] & Mask[level]) >> (shift - 2)) |
((components[1] & Mask[level]) >> (shift - 1)) |
((components[0] & Mask[level]) >> shift);
OctreeNode child = this.children[index];
if (child == null)
{
// Create a new child node and store it in the array
child = new OctreeNode(level + 1, colorBits, octree);
this.children[index] = child;
}
// Add the color to the child node
child.AddColor(pixel, colorBits, level + 1, octree);
}
}
/// <summary>
/// Reduce this node by removing all of its children
/// </summary>
/// <returns>The number of leaves removed</returns>
public int Reduce()
{
this.red = this.green = this.blue = 0;
int childNodes = 0;
// Loop through all children and add their information to this node
for (int index = 0; index < 8; index++)
{
if (this.children[index] != null)
{
this.red += this.children[index].red;
this.green += this.children[index].green;
this.blue += this.children[index].blue;
this.pixelCount += this.children[index].pixelCount;
++childNodes;
this.children[index] = null;
}
}
// Now change this to a leaf node
this.leaf = true;
// Return the number of nodes to decrement the leaf count by
return childNodes - 1;
}
/// <summary>
/// Traverse the tree, building up the color palette
/// </summary>
/// <param name="palette">
/// The palette
/// </param>
/// <param name="index">
/// The current palette index
/// </param>
public void ConstructPalette(List<T> palette, ref int index)
{
if (this.leaf)
{
// Consume the next palette index
this.paletteIndex = index++;
byte r = (this.red / this.pixelCount).ToByte();
byte g = (this.green / this.pixelCount).ToByte();
byte b = (this.blue / this.pixelCount).ToByte();
// And set the color of the palette entry
T pixel = default(T);
pixel.PackBytes(r, g, b, 255);
palette.Add(pixel);
}
else
{
// Loop through children looking for leaves
for (int i = 0; i < 8; i++)
{
if (this.children[i] != null)
{
this.children[i].ConstructPalette(palette, ref index);
}
}
}
}
/// <summary>
/// Return the palette index for the passed color
/// </summary>
/// <param name="pixel">
/// The <see cref="T"/> representing the pixel.
/// </param>
/// <param name="level">
/// The level.
/// </param>
/// <returns>
/// The <see cref="int"/> representing the index of the pixel in the palette.
/// </returns>
public int GetPaletteIndex(T pixel, int level)
{
int index = this.paletteIndex;
if (!this.leaf)
{
int shift = 7 - level;
byte[] components = pixel.ToBytes();
int pixelIndex = ((components[2] & Mask[level]) >> (shift - 2)) |
((components[1] & Mask[level]) >> (shift - 1)) |
((components[0] & Mask[level]) >> shift);
if (this.children[pixelIndex] != null)
{
index = this.children[pixelIndex].GetPaletteIndex(pixel, level + 1);
}
else
{
throw new Exception($"Cannot retrive a pixel at the given index {pixelIndex}.");
}
}
return index;
}
/// <summary>
/// Increment the pixel count and add to the color information
/// </summary>
/// <param name="pixel">
/// The pixel to add.
/// </param>
public void Increment(T pixel)
{
this.pixelCount++;
byte[] components = pixel.ToBytes();
this.red += components[0];
this.green += components[1];
this.blue += components[2];
}
}
}
}
}

149
src/ImageProcessorCore/Quantizers/Octree/Quantizer.cs

@ -0,0 +1,149 @@
// <copyright file="Quantizer.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore.Quantizers
{
using System.Collections.Generic;
using System.Threading.Tasks;
/// <summary>
/// Encapsulates methods to calculate the color palette of an image.
/// </summary>
public abstract class Quantizer<T, TP> : IQuantizer<T, TP>
where T : IPackedVector<TP>
where TP : struct
{
/// <summary>
/// Flag used to indicate whether a single pass or two passes are needed for quantization.
/// </summary>
private readonly bool singlePass;
/// <summary>
/// Initializes a new instance of the <see cref="Quantizer{T,TP}"/> class.
/// </summary>
/// <param name="singlePass">
/// If true, the quantization only needs to loop through the source pixels once
/// </param>
/// <remarks>
/// If you construct this class with a true value for singlePass, then the code will, when quantizing your image,
/// only call the 'QuantizeImage' function. If two passes are required, the code will call 'InitialQuantizeImage'
/// and then 'QuantizeImage'.
/// </remarks>
protected Quantizer(bool singlePass)
{
this.singlePass = singlePass;
}
/// <summary>
/// Gets or sets the transparency index.
/// </summary>
public int TransparentIndex { get; protected set; } = -1;
/// <inheritdoc/>
public byte Threshold { get; set; }
/// <inheritdoc/>
public virtual QuantizedImage<T, TP> Quantize(ImageBase<T, TP> image, int maxColors)
{
Guard.NotNull(image, nameof(image));
// Get the size of the source image
int height = image.Height;
int width = image.Width;
byte[] quantizedPixels = new byte[width * height];
List<T> palette;
using (IPixelAccessor<T, TP> pixels = image.Lock())
{
// Call the FirstPass function if not a single pass algorithm.
// For something like an Octree quantizer, this will run through
// all image pixels, build a data structure, and create a palette.
if (!this.singlePass)
{
this.FirstPass(pixels, width, height);
}
// Get the palette
palette = this.GetPalette();
this.SecondPass(pixels, quantizedPixels, width, height);
}
return new QuantizedImage<T, TP>(width, height, palette.ToArray(), quantizedPixels, this.TransparentIndex);
}
/// <summary>
/// Execute the first pass through the pixels in the image
/// </summary>
/// <param name="source">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(IPixelAccessor<T, TP> source, int width, int height)
{
// Loop through each row
for (int y = 0; y < height; y++)
{
// And loop through each column
for (int x = 0; x < width; x++)
{
// Now I have the pixel, call the FirstPassQuantize function...
this.InitialQuantizePixel(source[x, y]);
}
}
}
/// <summary>
/// Execute a second pass through the bitmap
/// </summary>
/// <param name="source">The source image.</param>
/// <param name="output">The output pixel array</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 SecondPass(IPixelAccessor<T, TP> source, byte[] output, int width, int height)
{
Parallel.For(
0,
source.Height,
Bootstrapper.Instance.ParallelOptions,
y =>
{
for (int x = 0; x < source.Width; x++)
{
T sourcePixel = source[x, y];
output[(y * source.Width) + x] = this.QuantizePixel(sourcePixel);
}
});
}
/// <summary>
/// Override this to process the pixel in the first pass of the algorithm
/// </summary>
/// <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.
/// </remarks>
protected virtual void InitialQuantizePixel(T pixel)
{
}
/// <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>
protected abstract byte QuantizePixel(T pixel);
/// <summary>
/// Retrieve the palette for the quantized image
/// </summary>
/// <returns>
/// The new color palette
/// </returns>
protected abstract List<T> GetPalette();
}
}

25
src/ImageProcessorCore/Quantizers/Wu/WuQuantizer.cs

@ -30,7 +30,11 @@ namespace ImageProcessorCore.Quantizers
/// but more expensive versions.
/// </para>
/// </remarks>
public sealed class WuQuantizer : IQuantizer
/// <typeparam name="T">The pixel format.</typeparam>
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
public sealed class WuQuantizer<T, TP> : IQuantizer<T, TP>
where T : IPackedVector<TP>
where TP : struct
{
/// <summary>
/// The epsilon for comparing floating point numbers.
@ -98,7 +102,7 @@ namespace ImageProcessorCore.Quantizers
private readonly byte[] tag;
/// <summary>
/// Initializes a new instance of the <see cref="WuQuantizer"/> class.
/// Initializes a new instance of the <see cref="WuQuantizer{T,TP}"/> class.
/// </summary>
public WuQuantizer()
{
@ -115,9 +119,7 @@ namespace ImageProcessorCore.Quantizers
public byte Threshold { get; set; }
/// <inheritdoc/>
public QuantizedImage<T, TP> Quantize<T, TP>(ImageBase<T, TP> image, int maxColors)
where T : IPackedVector<TP>
where TP : struct
public QuantizedImage<T, TP> Quantize(ImageBase<T, TP> image, int maxColors)
{
Guard.NotNull(image, nameof(image));
@ -322,12 +324,8 @@ namespace ImageProcessorCore.Quantizers
/// <summary>
/// Builds a 3-D color histogram of <c>counts, r/g/b, c^2</c>.
/// </summary>
/// <typeparam name="T">The pixel format.</typeparam>
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
/// <param name="pixels">The pixel accessor.</param>
private void Build3DHistogram<T, TP>(IPixelAccessor<T, TP> pixels)
where T : IPackedVector<TP>
where TP : struct
private void Build3DHistogram(IPixelAccessor<T, TP> pixels)
{
for (int y = 0; y < pixels.Height; y++)
{
@ -721,15 +719,11 @@ namespace ImageProcessorCore.Quantizers
/// <summary>
/// Generates the quantized result.
/// </summary>
/// <typeparam name="T">The pixel format.</typeparam>
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
/// <param name="imagePixels">The image pixels.</param>
/// <param name="colorCount">The color count.</param>
/// <param name="cube">The cube.</param>
/// <returns>The result.</returns>
private QuantizedImage<T, TP> GenerateResult<T, TP>(IPixelAccessor<T, TP> imagePixels, int colorCount, Box[] cube)
where T : IPackedVector<TP>
where TP : struct
private QuantizedImage<T, TP> GenerateResult(IPixelAccessor<T, TP> imagePixels, int colorCount, Box[] cube)
{
List<T> pallette = new List<T>();
byte[] pixels = new byte[imagePixels.Width * imagePixels.Height];
@ -793,7 +787,6 @@ namespace ImageProcessorCore.Quantizers
}
});
return new QuantizedImage<T, TP>(width, height, pallette.ToArray(), pixels, transparentIndex);
}
}

Loading…
Cancel
Save