#region License Information (GPL v3)
/*
ShareX - A program that allows you to take screenshots and share any file type
Copyright (c) 2007-2020 ShareX Team
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
Optionally you can also view the license at .
*/
#endregion License Information (GPL v3)
using System;
using System.Collections;
using System.Drawing;
using System.Drawing.Imaging;
namespace ShareX.HelpersLib
{
///
/// Quantize using an Octree
///
public class OctreeQuantizer : Quantizer
{
///
/// Construct the octree quantizer
///
///
/// 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
///
/// The maximum number of colors to return
/// The number of significant bits
public OctreeQuantizer(int maxColors, int maxColorBits)
: base(false)
{
if (maxColors > 255)
throw new ArgumentOutOfRangeException("maxColors", maxColors, "The number of colors should be less than 256");
if ((maxColorBits < 1) | (maxColorBits > 8))
throw new ArgumentOutOfRangeException("maxColorBits", maxColorBits, "This should be between 1 and 8");
// Construct the octree
_octree = new Octree(maxColorBits);
_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(Color32 pixel)
{
// Add the color to the octree
_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(Color32 pixel)
{
byte paletteIndex = (byte)_maxColors; // The color at [_maxColors] is set to transparent
// Get the palette index if this non-transparent
if (pixel.Alpha > 0)
paletteIndex = (byte)_octree.GetPaletteIndex(pixel);
return paletteIndex;
}
///
/// Retrieve the palette for the quantized image
///
/// Any old palette, this is overrwritten
/// The new color palette
protected override ColorPalette GetPalette(ColorPalette original)
{
// First off convert the octree to _maxColors colors
ArrayList palette = _octree.Palletize(_maxColors - 1);
// Then convert the palette based on those colors
for (int index = 0; index < palette.Count; index++)
original.Entries[index] = (Color)palette[index];
// Add the transparent color
original.Entries[_maxColors] = Color.FromArgb(0, 0, 0, 0);
return original;
}
///
/// Stores the tree
///
private Octree _octree;
///
/// Maximum allowed color depth
///
private int _maxColors;
///
/// Class which does the actual quantization
///
private class Octree
{
///
/// Construct the octree
///
/// The maximum number of significant bits in the image
public Octree(int maxColorBits)
{
_maxColorBits = maxColorBits;
_leafCount = 0;
_reducibleNodes = new OctreeNode[9];
_root = new OctreeNode(0, _maxColorBits, this);
_previousColor = 0;
_previousNode = null;
}
///
/// Add a given color value to the octree
///
///
public void AddColor(Color32 pixel)
{
// Check if this request is for the same color as the last
if (_previousColor == pixel.ARGB)
{
// If so, check if I have a previous node setup. This will only ocurr if the first color in the image
// happens to be black, with an alpha component of zero.
if (_previousNode == null)
{
_previousColor = pixel.ARGB;
_root.AddColor(pixel, _maxColorBits, 0, this);
}
else
{
// Just update the previous node
_previousNode.Increment(pixel);
}
}
else
{
_previousColor = pixel.ARGB;
_root.AddColor(pixel, _maxColorBits, 0, this);
}
}
///
/// Reduce the depth of the tree
///
public void Reduce()
{
int index = _maxColorBits - 1;
// Find the deepest level containing at least one reducible node
while (index > 0 && _reducibleNodes[index] == null)
{
index--;
}
// Reduce the node most recently added to the list at level 'index'
OctreeNode node = _reducibleNodes[index];
_reducibleNodes[index] = node.NextReducible;
// Decrement the leaf count after reducing the node
_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...
_previousNode = null;
}
///
/// Get/Set the number of leaves in the tree
///
public int Leaves
{
get
{
return _leafCount;
}
set
{
_leafCount = value;
}
}
///
/// Return the array of reducible nodes
///
protected OctreeNode[] ReducibleNodes
{
get
{
return _reducibleNodes;
}
}
///
/// Keep track of the previous node that was quantized
///
/// The node last quantized
protected void TrackPrevious(OctreeNode node)
{
_previousNode = node;
}
///
/// Convert the nodes in the octree to a palette with a maximum of colorCount colors
///
/// The maximum number of colors
/// An arraylist with the palettized colors
public ArrayList Palletize(int colorCount)
{
while (Leaves > colorCount)
Reduce();
// Now palettize the nodes
ArrayList palette = new ArrayList(Leaves);
int paletteIndex = 0;
_root.ConstructPalette(palette, ref paletteIndex);
// And return the palette
return palette;
}
///
/// Get the palette index for the passed color
///
///
///
public int GetPaletteIndex(Color32 pixel)
{
return _root.GetPaletteIndex(pixel, 0);
}
///
/// Mask used when getting the appropriate pixels for a given node
///
private static int[] mask = new int[] { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 };
///
/// The root of the octree
///
private OctreeNode _root;
///
/// Number of leaves in the tree
///
private int _leafCount;
///
/// Array of reducible nodes
///
private OctreeNode[] _reducibleNodes;
///
/// Maximum number of significant bits in the image
///
private int _maxColorBits;
///
/// Store the last node quantized
///
private OctreeNode _previousNode;
///
/// Cache the previous color quantized
///
private int _previousColor;
///
/// Class which encapsulates each node in the tree
///
protected class OctreeNode
{
///
/// Construct the node
///
/// 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
_leaf = (level == colorBits);
_red = _green = _blue = 0;
_pixelCount = 0;
// If a leaf, increment the leaf count
if (_leaf)
{
octree.Leaves++;
NextReducible = null;
_children = null;
}
else
{
// Otherwise add this to the reducible nodes
NextReducible = octree.ReducibleNodes[level];
octree.ReducibleNodes[level] = this;
_children = new OctreeNode[8];
}
}
///
/// 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(Color32 pixel, int colorBits, int level, Octree octree)
{
// Update the color information if this is a leaf
if (_leaf)
{
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.Red & mask[level]) >> (shift - 2)) |
((pixel.Green & mask[level]) >> (shift - 1)) |
((pixel.Blue & mask[level]) >> (shift));
OctreeNode child = _children[index];
if (child == null)
{
// Create a new child node & store in the array
child = new OctreeNode(level + 1, colorBits, octree);
_children[index] = child;
}
// Add the color to the child node
child.AddColor(pixel, colorBits, level + 1, octree);
}
}
///
/// Get/Set the next reducible node
///
public OctreeNode NextReducible { get; set; }
///
/// Return the child nodes
///
public OctreeNode[] Children
{
get
{
return _children;
}
}
///
/// Reduce this node by removing all of its children
///
/// The number of leaves removed
public int Reduce()
{
_red = _green = _blue = 0;
int children = 0;
// Loop through all children and add their information to this node
for (int index = 0; index < 8; index++)
{
if (_children[index] != null)
{
_red += _children[index]._red;
_green += _children[index]._green;
_blue += _children[index]._blue;
_pixelCount += _children[index]._pixelCount;
++children;
_children[index] = null;
}
}
// Now change this to a leaf node
_leaf = true;
// Return the number of nodes to decrement the leaf count by
return children - 1;
}
///
/// Traverse the tree, building up the color palette
///
/// The palette
/// The current palette index
public void ConstructPalette(ArrayList palette, ref int paletteIndex)
{
if (_leaf)
{
// Consume the next palette index
_paletteIndex = paletteIndex++;
// And set the color of the palette entry
palette.Add(Color.FromArgb(_red / _pixelCount, _green / _pixelCount, _blue / _pixelCount));
}
else
{
// Loop through children looking for leaves
for (int index = 0; index < 8; index++)
{
if (_children[index] != null)
{
_children[index].ConstructPalette(palette, ref paletteIndex);
}
}
}
}
///
/// Return the palette index for the passed color
///
public int GetPaletteIndex(Color32 pixel, int level)
{
int paletteIndex = _paletteIndex;
if (!_leaf)
{
int shift = 7 - level;
int index = ((pixel.Red & mask[level]) >> (shift - 2)) |
((pixel.Green & mask[level]) >> (shift - 1)) |
((pixel.Blue & mask[level]) >> (shift));
if (_children[index] != null)
{
paletteIndex = _children[index].GetPaletteIndex(pixel, level + 1);
}
else
{
throw new Exception("Didn't expect this!");
}
}
return paletteIndex;
}
///
/// Increment the pixel count and add to the color information
///
public void Increment(Color32 pixel)
{
_pixelCount++;
_red += pixel.Red;
_green += pixel.Green;
_blue += pixel.Blue;
}
///
/// 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;
///
/// Pointers to any child nodes
///
private OctreeNode[] _children;
///
/// The index of this node in the palette
///
private int _paletteIndex;
}
}
}
}