/*
* 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 ;
}
}
}
}