/* * Greenshot - a free and open source screenshot tool * Copyright (C) 2007-2012 Thomas Braun, Jens Klingen, Robin Krom * * For more information see: http://getgreenshot.org/ * The Greenshot project is hosted on Sourceforge: http://sourceforge.net/projects/greenshot/ * * 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 1 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, see . */ using System; using System.Collections; using System.Drawing; using System.Drawing.Imaging; using System.Runtime.InteropServices; namespace Greenshot.Helpers { /// /// Quantizer is a method to reduce the colors in a bitmap. /// Currently there is only 1 implementation: OctreeQuantizer /// public unsafe abstract class Quantizer { /// /// Construct the quantizer /// /// If true, the quantization only needs to loop through the source pixels once /// /// 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'. /// public Quantizer (bool singlePass) { _singlePass = singlePass ; } /// /// Quantize an image and return the resulting output bitmap /// /// The image to quantize /// A quantized version of the image public Bitmap Quantize(Image source) { // Get the size of the source image int height = source.Height ; int width = source.Width ; // And construct a rectangle from these dimensions Rectangle bounds = new Rectangle(0, 0, width, height) ; // First off take a 32bpp copy of the image Bitmap copy = new Bitmap ( width , height , PixelFormat.Format32bppArgb ) ; // And construct an 8bpp version Bitmap output = new Bitmap ( width , height , PixelFormat.Format8bppIndexed ) ; // Now lock the bitmap into memory using (Graphics g = Graphics.FromImage(copy)) { g.PageUnit = GraphicsUnit.Pixel ; // Draw the source image onto the copy bitmap, // which will effect a widening as appropriate. g.DrawImage(source, bounds ) ; } // Define a pointer to the bitmap data BitmapData sourceData = null ; try { // Get the source image bits and lock into memory sourceData = copy.LockBits ( bounds , ImageLockMode.ReadOnly , PixelFormat.Format32bppArgb ) ; // 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 ( !_singlePass ) FirstPass ( sourceData , width , height ) ; // Then set the color palette on the output bitmap. I'm passing in the current palette // as there's no way to construct a new, empty palette. output.Palette = this.GetPalette ( output.Palette ) ; // Then call the second pass which actually does the conversion SecondPass ( sourceData , output , width , height , bounds ) ; } finally { // Ensure that the bits are unlocked copy.UnlockBits ( sourceData ); copy.Dispose(); } // Last but not least, return the output bitmap return output; } /// /// 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 ( BitmapData sourceData , int width , int height ) { // Define the source data pointers. The source row is a byte to // keep addition of the stride value easier (as this is in bytes) byte* pSourceRow = (byte*)sourceData.Scan0.ToPointer ( ) ; Int32* pSourcePixel ; // Loop through each row for ( int row = 0 ; row < height ; row++ ) { // Set the source pixel to the first pixel in this row pSourcePixel = (Int32*) pSourceRow ; // And loop through each column for ( int col = 0 ; col < width ; col++ , pSourcePixel++ ) { // Now I have the pixel, call the FirstPassQuantize function... InitialQuantizePixel ( (Color32*)pSourcePixel ) ; } // Add the stride to the source row pSourceRow += sourceData.Stride ; } } /// /// Execute a second pass through the bitmap /// /// The source bitmap, locked into memory /// The output bitmap /// The width in pixels of the image /// The height in pixels of the image /// The bounding rectangle protected virtual void SecondPass ( BitmapData sourceData , Bitmap output , int width , int height , Rectangle bounds ) { BitmapData outputData = null ; try { // Lock the output bitmap into memory outputData = output.LockBits ( bounds , ImageLockMode.WriteOnly , PixelFormat.Format8bppIndexed ) ; // Define the source data pointers. The source row is a byte to // keep addition of the stride value easier (as this is in bytes) byte* pSourceRow = (byte*)sourceData.Scan0.ToPointer ( ) ; Int32* pSourcePixel = (Int32*)pSourceRow ; Int32* pPreviousPixel = pSourcePixel ; // Now define the destination data pointers byte* pDestinationRow = (byte*) outputData.Scan0.ToPointer(); byte* pDestinationPixel = pDestinationRow ; // And convert the first pixel, so that I have values going into the loop byte pixelValue = QuantizePixel ( (Color32*)pSourcePixel ) ; // Assign the value of the first pixel *pDestinationPixel = pixelValue ; // Loop through each row for ( int row = 0 ; row < height ; row++ ) { // Set the source pixel to the first pixel in this row pSourcePixel = (Int32*) pSourceRow ; // And set the destination pixel pointer to the first pixel in the row pDestinationPixel = pDestinationRow ; // Loop through each pixel on this scan line for ( int col = 0 ; col < width ; col++ , pSourcePixel++ , pDestinationPixel++ ) { // 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. if ( *pPreviousPixel != *pSourcePixel ) { // Quantize the pixel pixelValue = QuantizePixel ( (Color32*)pSourcePixel ) ; // And setup the previous pointer pPreviousPixel = pSourcePixel ; } // And set the pixel in the output *pDestinationPixel = pixelValue ; } // Add the stride to the source row pSourceRow += sourceData.Stride ; // And to the destination row pDestinationRow += outputData.Stride ; } } finally { // Ensure that I unlock the output bits output.UnlockBits ( outputData ) ; } } /// /// 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 ( Color32* 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 ( Color32* pixel ) ; /// /// Retrieve the palette for the quantized image /// /// Any old palette, this is overrwritten /// The new color palette protected abstract ColorPalette GetPalette ( ColorPalette original ) ; /// /// Flag used to indicate whether a single pass or two passes are needed for quantization. /// private bool _singlePass ; /// /// Struct that defines a 32 bpp colour /// /// /// This struct is used to read data from a 32 bits per pixel image /// in memory, and is ordered in this manner as this is the way that /// the data is layed out in memory /// [StructLayout(LayoutKind.Explicit)] public struct Color32 { /// /// Holds the blue component of the colour /// [FieldOffset(0)] public byte Blue ; /// /// Holds the green component of the colour /// [FieldOffset(1)] public byte Green ; /// /// Holds the red component of the colour /// [FieldOffset(2)] public byte Red ; /// /// Holds the alpha component of the colour /// [FieldOffset(3)] public byte Alpha ; /// /// Permits the color32 to be treated as an int32 /// [FieldOffset(0)] public int ARGB ; /// /// Return the color for this Color32 object /// public Color Color { get { return Color.FromArgb ( Alpha , Red , Green , Blue ) ; } } } } /// /// Quantize using an Octree /// public unsafe 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 ( null == _previousNode ) { _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 ; // Find the deepest level containing at least one reducible node for ( index = _maxColorBits - 1 ; ( index > 0 ) && ( null == _reducibleNodes[index] ) ; 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[8] { 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 ( null == child ) { // 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 { return _nextReducible ; } set { _nextReducible = value ; } } /// /// 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 ( null != _children[index] ) { _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 ( null != _children[index] ) { _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 ( null != _children[index] ) { 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 ; /// /// Pointer to next reducible node /// private OctreeNode _nextReducible ; /// /// The index of this node in the palette /// private int _paletteIndex ; } } } }