#region License Information (GPL v3) /* ShareX - A program that allows you to take screenshots and share any file type Copyright (c) 2007-2024 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; } } } }