diff --git a/src/ImageProcessor/Formats/Gif/GifEncoder.cs b/src/ImageProcessor/Formats/Gif/GifEncoder.cs
index 64d210872..8d630f808 100644
--- a/src/ImageProcessor/Formats/Gif/GifEncoder.cs
+++ b/src/ImageProcessor/Formats/Gif/GifEncoder.cs
@@ -85,7 +85,7 @@ namespace ImageProcessor.Formats
this.WriteShort(stream, descriptor.Width);
int size = descriptor.GlobalColorTableSize;
int bitdepth = this.GetBitsNeededForColorDepth(size) - 1;
- int packed = 0x80 | // 1 : global color table flag = 1 (GCT used)
+ int packed = 0x80 | // 1 : Global color table flag = 1 (GCT used)
0x70 | // 2-4 : color resolution
0x00 | // 5 : GCT sort flag = 0
bitdepth; // 6-8 : GCT size assume 1:1
diff --git a/src/ImageProcessor/Formats/Gif/Quantizer/IQuantizer.cs b/src/ImageProcessor/Formats/Gif/Quantizer/IQuantizer.cs
index f9867d674..bdc1ca27e 100644
--- a/src/ImageProcessor/Formats/Gif/Quantizer/IQuantizer.cs
+++ b/src/ImageProcessor/Formats/Gif/Quantizer/IQuantizer.cs
@@ -18,10 +18,10 @@ namespace ImageProcessor.Formats
///
/// Quantize an image and return the resulting output pixels.
///
- /// The image to quantize.
+ /// The image to quantize.
///
/// A representing a quantized version of the image pixels.
///
- byte[] Quantize(ImageBase image);
+ byte[] Quantize(ImageBase imageBase);
}
}
diff --git a/src/ImageProcessor/Formats/Gif/Quantizer/OctreeQuantizer.cs b/src/ImageProcessor/Formats/Gif/Quantizer/OctreeQuantizer.cs
index 838caf57c..368e5ace9 100644
--- a/src/ImageProcessor/Formats/Gif/Quantizer/OctreeQuantizer.cs
+++ b/src/ImageProcessor/Formats/Gif/Quantizer/OctreeQuantizer.cs
@@ -11,12 +11,20 @@
namespace ImageProcessor.Formats
{
+ using System;
+ using System.Collections.Generic;
+
///
- /// Encapsulates methods to calculate the color palette of an image using an Octree pattern.
+ /// Encapsulates methods to calculate the colour palette if an image using an Octree pattern.
///
///
public class OctreeQuantizer : Quantizer
{
+ ///
+ /// Stores the tree
+ ///
+ private readonly Octree octree;
+
///
/// Maximum allowed color depth
///
@@ -50,6 +58,489 @@ namespace ImageProcessor.Formats
: base(false)
{
Guard.LessEquals(maxColors, 255, "maxColors");
+ Guard.BetweenEquals(maxColorBits, 1, 8, "maxColorBits");
+
+ // Construct the Octree
+ this.octree = new Octree(maxColorBits);
+
+ this.maxColors = maxColors;
+ }
+
+ ///
+ /// Process the pixel in the first pass of the algorithm
+ ///
+ ///
+ /// The pixel to quantize
+ ///
+ ///
+ /// This function need only be overridden if your quantize algorithm needs two passes,
+ /// such as an Octree quantizer.
+ ///
+ protected override void InitialQuantizePixel(Bgra pixel)
+ {
+ // Add the color to the Octree
+ this.octree.AddColor(pixel);
+ }
+
+ ///
+ /// Override this to process the pixel in the second pass of the algorithm
+ ///
+ ///
+ /// The pixel to quantize
+ ///
+ ///
+ /// The quantized value
+ ///
+ protected override byte QuantizePixel(Bgra pixel)
+ {
+ // The color at [maxColors] is set to transparent
+ byte paletteIndex = (byte)this.maxColors;
+
+ // Get the palette index if this non-transparent
+ if (pixel.A > 0)
+ {
+ paletteIndex = (byte)this.octree.GetPaletteIndex(pixel);
+ }
+
+ return paletteIndex;
+ }
+
+ ///
+ /// Retrieve the palette for the quantized image.
+ ///
+ ///
+ /// The new color palette
+ ///
+ protected override List GetPalette()
+ {
+ // First off convert the Octree to maxColors colors
+ List palette = this.octree.Palletize(this.maxColors - 1);
+
+ // Add empty color for transparency
+ palette.Add(Bgra.Empty);
+
+ return palette;
+ }
+
+ ///
+ /// Class which does the actual quantization
+ ///
+ private class Octree
+ {
+ ///
+ /// Mask used when getting the appropriate pixels for a given node
+ ///
+ private static readonly int[] Mask = { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 };
+
+ ///
+ /// The root of the Octree
+ ///
+ private readonly OctreeNode root;
+
+ ///
+ /// Array of reducible nodes
+ ///
+ private readonly OctreeNode[] reducibleNodes;
+
+ ///
+ /// Maximum number of significant bits in the image
+ ///
+ private readonly int maxColorBits;
+
+ ///
+ /// Number of leaves in the tree
+ ///
+ private int leafCount;
+
+ ///
+ /// Store the last node quantized
+ ///
+ private OctreeNode previousNode;
+
+ ///
+ /// Cache the previous color quantized
+ ///
+ private int previousColor;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The maximum number of significant bits in the image
+ ///
+ public Octree(int maxColorBits)
+ {
+ this.maxColorBits = maxColorBits;
+ this.leafCount = 0;
+ this.reducibleNodes = new OctreeNode[9];
+ this.root = new OctreeNode(0, this.maxColorBits, this);
+ this.previousColor = 0;
+ this.previousNode = null;
+ }
+
+ ///
+ /// Gets or sets the number of leaves in the tree
+ ///
+ private int Leaves
+ {
+ get { return this.leafCount; }
+ set { this.leafCount = value; }
+ }
+
+ ///
+ /// Gets the array of reducible nodes
+ ///
+ private OctreeNode[] ReducibleNodes => this.reducibleNodes;
+
+ ///
+ /// Add a given color value to the Octree
+ ///
+ ///
+ /// The containing color information to add.
+ ///
+ public void AddColor(Bgra pixel)
+ {
+ // Check if this request is for the same color as the last
+ if (this.previousColor == pixel.BGRA)
+ {
+ // 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 (null == this.previousNode)
+ {
+ this.previousColor = pixel.BGRA;
+ this.root.AddColor(pixel, this.maxColorBits, 0, this);
+ }
+ else
+ {
+ // Just update the previous node
+ this.previousNode.Increment(pixel);
+ }
+ }
+ else
+ {
+ this.previousColor = pixel.BGRA;
+ this.root.AddColor(pixel, this.maxColorBits, 0, this);
+ }
+ }
+
+ ///
+ /// Convert the nodes in the Octree to a palette with a maximum of colorCount colors
+ ///
+ ///
+ /// The maximum number of colors
+ ///
+ ///
+ /// An with the palletized colors
+ ///
+ public List Palletize(int colorCount)
+ {
+ while (this.Leaves > colorCount)
+ {
+ this.Reduce();
+ }
+
+ // Now palletize the nodes
+ List palette = new List(this.Leaves);
+ int paletteIndex = 0;
+ this.root.ConstructPalette(palette, ref paletteIndex);
+
+ // And return the palette
+ return palette;
+ }
+
+ ///
+ /// Get the palette index for the passed color
+ ///
+ ///
+ /// The containing the pixel data.
+ ///
+ ///
+ /// The index of the given structure.
+ ///
+ public int GetPaletteIndex(Bgra pixel)
+ {
+ return this.root.GetPaletteIndex(pixel, 0);
+ }
+
+ ///
+ /// Keep track of the previous node that was quantized
+ ///
+ ///
+ /// The node last quantized
+ ///
+ protected void TrackPrevious(OctreeNode node)
+ {
+ this.previousNode = node;
+ }
+
+ ///
+ /// Reduce the depth of the tree
+ ///
+ private void Reduce()
+ {
+ // Find the deepest level containing at least one reducible node
+ int index = this.maxColorBits - 1;
+ while ((index > 0) && (null == this.reducibleNodes[index]))
+ {
+ 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.leafCount -= 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;
+ }
+
+ ///
+ /// Class which encapsulates each node in the tree
+ ///
+ protected class OctreeNode
+ {
+ ///
+ /// Pointers to any child nodes
+ ///
+ private readonly OctreeNode[] children;
+
+ ///
+ /// Flag indicating that this is a leaf node
+ ///
+ private bool leaf;
+
+ ///
+ /// Number of pixels in this node
+ ///
+ private int pixelCount;
+
+ ///
+ /// Red component
+ ///
+ private int red;
+
+ ///
+ /// Green Component
+ ///
+ private int green;
+
+ ///
+ /// Blue component
+ ///
+ private int blue;
+
+ ///
+ /// The index of this node in the palette
+ ///
+ private int paletteIndex;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The level in the tree = 0 - 7
+ ///
+ ///
+ /// The number of significant color bits in the image
+ ///
+ ///
+ /// The tree to which this node belongs
+ ///
+ 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];
+ }
+ }
+
+ ///
+ /// Gets the next reducible node
+ ///
+ public OctreeNode NextReducible { get; }
+
+ ///
+ /// Add a color into the tree
+ ///
+ ///
+ /// The color
+ ///
+ ///
+ /// The number of significant color bits
+ ///
+ ///
+ /// The level in the tree
+ ///
+ ///
+ /// The tree to which this node belongs
+ ///
+ public void AddColor(Bgra 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;
+ int index = ((pixel.R & Mask[level]) >> (shift - 2)) |
+ ((pixel.G & Mask[level]) >> (shift - 1)) |
+ ((pixel.B & Mask[level]) >> shift);
+
+ OctreeNode child = this.children[index];
+
+ if (null == child)
+ {
+ // Create a new child node & store 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);
+ }
+ }
+
+ ///
+ /// Reduce this node by removing all of its children
+ ///
+ /// The number of leaves removed
+ 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 (null != this.children[index])
+ {
+ 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;
+ }
+
+ ///
+ /// Traverse the tree, building up the color palette
+ ///
+ ///
+ /// The palette
+ ///
+ ///
+ /// The current palette index
+ ///
+ public void ConstructPalette(List palette, ref int index)
+ {
+ if (this.leaf)
+ {
+ // Consume the next palette index
+ this.paletteIndex = index++;
+
+ byte r = (byte)(this.red / this.pixelCount).Clamp(0, 255);
+ byte g = (byte)(this.green / this.pixelCount).Clamp(0, 255);
+ byte b = (byte)(this.blue / this.pixelCount).Clamp(0, 255);
+
+ // And set the color of the palette entry
+ palette.Add(new Bgra(b, g, r));
+ }
+ else
+ {
+ // Loop through children looking for leaves
+ for (int i = 0; i < 8; i++)
+ {
+ if (null != this.children[i])
+ {
+ this.children[i].ConstructPalette(palette, ref index);
+ }
+ }
+ }
+ }
+
+ ///
+ /// Return the palette index for the passed color
+ ///
+ ///
+ /// The representing the pixel.
+ ///
+ ///
+ /// The level.
+ ///
+ ///
+ /// The representing the index of the pixel in the palette.
+ ///
+ public int GetPaletteIndex(Bgra pixel, int level)
+ {
+ int index = this.paletteIndex;
+
+ if (!this.leaf)
+ {
+ int shift = 7 - level;
+ int pixelIndex = ((pixel.R & Mask[level]) >> (shift - 2)) |
+ ((pixel.G & Mask[level]) >> (shift - 1)) |
+ ((pixel.B & Mask[level]) >> shift);
+
+ if (null != this.children[pixelIndex])
+ {
+ index = this.children[pixelIndex].GetPaletteIndex(pixel, level + 1);
+ }
+ else
+ {
+ throw new Exception("Didn't expect this!");
+ }
+ }
+
+ return index;
+ }
+
+ ///
+ /// Increment the pixel count and add to the color information
+ ///
+ ///
+ /// The pixel to add.
+ ///
+ public void Increment(Bgra pixel)
+ {
+ this.pixelCount++;
+ this.red += pixel.R;
+ this.green += pixel.G;
+ this.blue += pixel.B;
+ }
+ }
}
}
}
diff --git a/src/ImageProcessor/Formats/Gif/Quantizer/Quantizer.cs b/src/ImageProcessor/Formats/Gif/Quantizer/Quantizer.cs
index 002715e52..38f16bb88 100644
--- a/src/ImageProcessor/Formats/Gif/Quantizer/Quantizer.cs
+++ b/src/ImageProcessor/Formats/Gif/Quantizer/Quantizer.cs
@@ -10,6 +10,8 @@
namespace ImageProcessor.Formats
{
+ using System.Collections.Generic;
+
///
/// Encapsulates methods to calculate the color palette of an image.
///
@@ -39,13 +41,102 @@ namespace ImageProcessor.Formats
///
/// Quantize an image and return the resulting output pixels.
///
- /// The image to quantize.
+ /// The image to quantize.
///
/// A representing a quantized version of the image pixels.
///
- public byte[] Quantize(ImageBase image)
+ public byte[] Quantize(ImageBase imageBase)
{
+ // Get the size of the source image
+ int height = imageBase.Height;
+ int width = imageBase.Width;
+ ImageBase copy = new ImageFrame((ImageFrame)imageBase);
+
+ // 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(copy, width, height);
+ }
+
throw new System.NotImplementedException();
}
+
+ ///
+ /// Execute the first pass through the pixels in the image
+ ///
+ /// The source data
+ /// The width in pixels of the image.
+ /// The height in pixels of the image.
+ protected virtual void FirstPass(ImageBase source, int width, int height)
+ {
+ // Loop through each row
+ for (int y = 0; y < height; y++)
+ {
+ // And loop through each xumn
+ for (int x = 0; x < width; x++)
+ {
+ // Now I have the pixel, call the FirstPassQuantize function...
+ this.InitialQuantizePixel(source[x, y]);
+ }
+ }
+ }
+
+ ///
+ /// Execute a second pass through the bitmap
+ ///
+ /// The source image.
+ /// The output pixel array
+ /// The width in pixels of the image
+ /// The height in pixels of the image
+ protected virtual void SecondPass(ImageBase source, byte[] output, int width, int height)
+ {
+ Bgra sourcePixel = source[0, 0];
+
+ // And convert the first pixel, so that I have values going into the loop
+ byte pixelValue = this.QuantizePixel(sourcePixel);
+
+ output[0] = pixelValue;
+
+ for (int y = 0; y < height; y++)
+ {
+ // TODO: Translate this from the old method.
+ }
+
+ }
+
+ ///
+ /// Override this to process the pixel in the first pass of the algorithm
+ ///
+ ///
+ /// The pixel to quantize
+ ///
+ ///
+ /// This function need only be overridden if your quantize algorithm needs two passes,
+ /// such as an Octree quantizer.
+ ///
+ protected virtual void InitialQuantizePixel(Bgra pixel)
+ {
+ }
+
+ ///
+ /// Override this to process the pixel in the second pass of the algorithm
+ ///
+ ///
+ /// The pixel to quantize
+ ///
+ ///
+ /// The quantized value
+ ///
+ protected abstract byte QuantizePixel(Bgra pixel);
+
+ ///
+ /// Retrieve the palette for the quantized image
+ ///
+ ///
+ /// The new color palette
+ ///
+ protected abstract List GetPalette();
}
}
diff --git a/src/ImageProcessor/Formats/Jpg/JpegEncoder.cs b/src/ImageProcessor/Formats/Jpg/JpegEncoder.cs
index c9c0d056d..03a395aad 100644
--- a/src/ImageProcessor/Formats/Jpg/JpegEncoder.cs
+++ b/src/ImageProcessor/Formats/Jpg/JpegEncoder.cs
@@ -87,8 +87,8 @@ namespace ImageProcessor.Formats
Guard.NotNull(image, "image");
Guard.NotNull(stream, "stream");
- int pixelWidth = image.PixelWidth;
- int pixelHeight = image.PixelHeight;
+ int pixelWidth = image.Width;
+ int pixelHeight = image.Height;
byte[] sourcePixels = image.Pixels;
@@ -101,7 +101,7 @@ namespace ImageProcessor.Formats
for (int x = 0; x < pixelWidth; x++)
{
int start = x * 3;
- int source = (y * pixelWidth + x) * 4;
+ int source = ((y * pixelWidth) + x) * 4;
samples[start] = sourcePixels[source + 2];
samples[start + 1] = sourcePixels[source + 1];
@@ -112,7 +112,7 @@ namespace ImageProcessor.Formats
}
JpegImage jpg = new JpegImage(rows, Colorspace.RGB);
- jpg.WriteJpeg(stream, new CompressionParameters { Quality = Quality });
+ jpg.WriteJpeg(stream, new CompressionParameters { Quality = this.Quality });
}
#endregion
diff --git a/src/ImageProcessor/Formats/Jpg/LibJpeg/BitStream.cs b/src/ImageProcessor/Formats/Jpg/LibJpeg/BitStream.cs
index 9885e4170..2bdaae68c 100644
--- a/src/ImageProcessor/Formats/Jpg/LibJpeg/BitStream.cs
+++ b/src/ImageProcessor/Formats/Jpg/LibJpeg/BitStream.cs
@@ -91,13 +91,7 @@ namespace ImageProcessor.Formats
///
/// Gets the underlying stream.
///
- public Stream UnderlyingStream
- {
- get
- {
- return this.stream;
- }
- }
+ public Stream UnderlyingStream => this.stream;
///
/// Disposes the object and frees resources for the Garbage Collector.