diff --git a/BizHawk.Client.EmuHawk/AVOut/GifWriter.cs b/BizHawk.Client.EmuHawk/AVOut/GifWriter.cs
index 7472fcf072..dab8999e95 100644
--- a/BizHawk.Client.EmuHawk/AVOut/GifWriter.cs
+++ b/BizHawk.Client.EmuHawk/AVOut/GifWriter.cs
@@ -141,31 +141,35 @@ namespace BizHawk.Client.EmuHawk
}
- Bitmap bmp = new Bitmap(source.BufferWidth, source.BufferHeight, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
+
+ using (var bmp = new Bitmap(source.BufferWidth, source.BufferHeight, System.Drawing.Imaging.PixelFormat.Format32bppArgb))
{
var data = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
System.Runtime.InteropServices.Marshal.Copy(source.GetVideoBuffer(), 0, data.Scan0, bmp.Width * bmp.Height);
bmp.UnlockBits(data);
- }
- MemoryStream ms = new MemoryStream();
- bmp.Save(ms, System.Drawing.Imaging.ImageFormat.Gif);
- byte[] b = ms.GetBuffer();
- if (!firstdone)
- {
- firstdone = true;
- b[10] = (byte)(b[10] & 0x78); // no global color table
- f.Write(b, 0, 13);
- f.Write(GifAnimation, 0, GifAnimation.Length);
- }
- b[785] = Delay[0];
- b[786] = Delay[1];
- b[798] = (byte)(b[798] | 0x87);
- f.Write(b, 781, 18);
- f.Write(b, 13, 768);
- f.Write(b, 799, (int)(ms.Length - 800));
+ using (var qBmp = new OctreeQuantizer(255, 8).Quantize(bmp))
+ {
+ MemoryStream ms = new MemoryStream();
+ qBmp.Save(ms, System.Drawing.Imaging.ImageFormat.Gif);
+ byte[] b = ms.GetBuffer();
+ if (!firstdone)
+ {
+ firstdone = true;
+ b[10] = (byte)(b[10] & 0x78); // no global color table
+ f.Write(b, 0, 13);
+ f.Write(GifAnimation, 0, GifAnimation.Length);
+ }
+ b[785] = Delay[0];
+ b[786] = Delay[1];
+ b[798] = (byte)(b[798] | 0x87);
+ f.Write(b, 781, 18);
+ f.Write(b, 13, 768);
+ f.Write(b, 799, (int)(ms.Length - 800));
- lastbyte = b[ms.Length - 1];
+ lastbyte = b[ms.Length - 1];
+ }
+ }
}
public void AddSamples(short[] samples)
diff --git a/BizHawk.Client.EmuHawk/AVOut/Quantize/OctreeQuantizer.cs b/BizHawk.Client.EmuHawk/AVOut/Quantize/OctreeQuantizer.cs
new file mode 100644
index 0000000000..49fb158c3f
--- /dev/null
+++ b/BizHawk.Client.EmuHawk/AVOut/Quantize/OctreeQuantizer.cs
@@ -0,0 +1,583 @@
+/////////////////////////////////////////////////////////////////////////////////
+// Paint.NET
+// Copyright (C) Rick Brewster, Chris Crosetto, Dennis Dietrich, Tom Jackson,
+// Michael Kelsey, Brandon Ortiz, Craig Taylor, Chris Trevino,
+// and Luke Walker
+// Portions Copyright (C) Microsoft Corporation. All Rights Reserved.
+// See src/setup/License.rtf for complete licensing and attribution information.
+/////////////////////////////////////////////////////////////////////////////////
+
+/////////////////////////////////////////////////////////////////////////////////
+// Copied for Paint.NET PCX Plugin
+// Copyright (C) Joshua Bell
+/////////////////////////////////////////////////////////////////////////////////
+
+// Based on: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnaspp/html/colorquant.asp
+
+//Bizhawk says: adapted from https://github.com/inexorabletash/PcxFileType/blob/master/Quantize
+
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Drawing.Imaging;
+
+namespace BizHawk.Client.EmuHawk
+{
+ ///
+ /// Quantize using an Octree
+ ///
+ unsafe class OctreeQuantizer : Quantizer
+ {
+ ///
+ /// Stores the tree
+ ///
+ private Octree _octree;
+
+ ///
+ /// Maximum allowed color depth
+ ///
+ private int _maxColors;
+
+ ///
+ /// 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");
+
+ _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(int pixel)
+ {
+ _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(int pixel)
+ {
+ byte paletteIndex = (byte)_maxColors; // The color at [_maxColors] is set to transparent
+
+ // Get the palette index if this non-transparent
+ int a = (pixel>>24)&0xFF;
+
+#if HANDLE_TRANSPARENCY
+ if (a > 0)
+#endif
+ {
+ 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
+ List palette = _octree.Palletize(_maxColors - 1);
+
+ // Then convert the palette based on those colors
+ for (int index = 0; index < palette.Count; index++)
+ {
+ original.Entries[index] = palette[index];
+ }
+
+#if ORIGINAL_CODE
+ for (int i = palette.Count; i < original.Entries.Length; ++i)
+ {
+ original.Entries[i] = Color.FromArgb(255, 0, 0, 0);
+ }
+
+ // Add the transparent color
+ original.Entries[_maxColors] = Color.FromArgb(0, 0, 0, 0);
+#else // PCX Plugin
+ // For PCX: Pad with transparency
+ for (int i = palette.Count; i < original.Entries.Length; ++i)
+ original.Entries[i] = Color.Transparent;
+#endif
+ return original;
+ }
+
+ ///
+ /// 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(int pixel)
+ {
+ // Check if this request is for the same color as the last
+ if (_previousColor == pixel)
+ {
+ // 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;
+ _root.AddColor(pixel, _maxColorBits, 0, this);
+ }
+ else
+ {
+ // Just update the previous node
+ _previousNode.Increment(pixel);
+ }
+ }
+ else
+ {
+ _previousColor = pixel;
+ _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--)
+ {
+ // intentionally blank
+ }
+
+ // 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;
+ }
+
+ private Color[] _palette;
+ private PaletteTable paletteTable;
+
+ ///
+ /// Convert the nodes in the octree to a palette with a maximum of colorCount colors
+ ///
+ /// The maximum number of colors
+ /// A list with the palettized colors
+ public List Palletize(int colorCount)
+ {
+ while (Leaves > colorCount)
+ {
+ Reduce();
+ }
+
+ // Now palettize the nodes
+ List palette = new List(Leaves);
+ int paletteIndex = 0;
+
+ _root.ConstructPalette(palette, ref paletteIndex);
+
+ // And return the palette
+ this._palette = palette.ToArray();
+ this.paletteTable = null;
+
+ return palette;
+ }
+
+ ///
+ /// Get the palette index for the passed color
+ ///
+ ///
+ ///
+ public int GetPaletteIndex(int pixel)
+ {
+ int ret = -1;
+
+ ret = _root.GetPaletteIndex(pixel, 0);
+
+ if (ret < 0)
+ {
+ if (this.paletteTable == null)
+ {
+ this.paletteTable = new PaletteTable(this._palette);
+ }
+
+ ret = this.paletteTable.FindClosestPaletteIndex(Color.FromArgb(pixel));
+ }
+
+ return ret;
+ }
+
+ ///
+ /// 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 = 0;
+ _green = 0;
+ _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(int 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 r = pixel & 0xFF;
+ int g = (pixel >> 8) & 0xFF;
+ int b = (pixel >> 16) & 0xFF;
+ int index = ((r & mask[level]) >> (shift - 2)) |
+ ((g & mask[level]) >> (shift - 1)) |
+ ((b & 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()
+ {
+ int children = 0;
+ _red = 0;
+ _green = 0;
+ _blue = 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(List palette, ref int paletteIndex)
+ {
+ if (_leaf)
+ {
+ // Consume the next palette index
+ _paletteIndex = paletteIndex++;
+
+ // And set the color of the palette entry
+ int r = _red / _pixelCount;
+ int g = _green / _pixelCount;
+ int b = _blue / _pixelCount;
+
+ palette.Add(Color.FromArgb(r, g, b));
+ }
+ 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(int pixel, int level)
+ {
+ int paletteIndex = _paletteIndex;
+
+ if (!_leaf)
+ {
+ int shift = 7 - level;
+ int r = pixel & 0xFF;
+ int g = (pixel >> 8) & 0xFF;
+ int b = (pixel >> 16) & 0xFF;
+ int index = ((r & mask[level]) >> (shift - 2)) |
+ ((g & mask[level]) >> (shift - 1)) |
+ ((b & mask[level]) >> (shift));
+
+ if (null != _children[index])
+ {
+ paletteIndex = _children[index].GetPaletteIndex(pixel, level + 1);
+ }
+ else
+ {
+ paletteIndex = -1;
+ }
+ }
+
+ return paletteIndex;
+ }
+
+ ///
+ /// Increment the pixel count and add to the color information
+ ///
+ public void Increment(int pixel)
+ {
+ ++_pixelCount;
+ int r = pixel&0xFF;
+ int g = (pixel>>8) & 0xFF;
+ int b = (pixel >> 16) & 0xFF;
+ _red += r;
+ _green += g;
+ _blue += b;
+ }
+
+ ///
+ /// 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;
+ }
+ }
+ }
+}
diff --git a/BizHawk.Client.EmuHawk/AVOut/Quantize/PaletteTable.cs b/BizHawk.Client.EmuHawk/AVOut/Quantize/PaletteTable.cs
new file mode 100644
index 0000000000..c85fd5cdcc
--- /dev/null
+++ b/BizHawk.Client.EmuHawk/AVOut/Quantize/PaletteTable.cs
@@ -0,0 +1,77 @@
+/////////////////////////////////////////////////////////////////////////////////
+// Paint.NET
+// Copyright (C) Rick Brewster, Chris Crosetto, Dennis Dietrich, Tom Jackson,
+// Michael Kelsey, Brandon Ortiz, Craig Taylor, Chris Trevino,
+// and Luke Walker
+// Portions Copyright (C) Microsoft Corporation. All Rights Reserved.
+// See src/setup/License.rtf for complete licensing and attribution information.
+/////////////////////////////////////////////////////////////////////////////////
+
+/////////////////////////////////////////////////////////////////////////////////
+// Copied for Paint.NET PCX Plugin
+// Copyright (C) Joshua Bell
+/////////////////////////////////////////////////////////////////////////////////
+
+//Bizhawk says: adapted from https://github.com/inexorabletash/PcxFileType/blob/master/Quantize
+
+using System.Drawing;
+
+namespace BizHawk.Client.EmuHawk
+{
+ public sealed class PaletteTable
+ {
+ private Color[] palette;
+
+ public Color this[int index]
+ {
+ get
+ {
+ return this.palette[index];
+ }
+
+ set
+ {
+ this.palette[index] = value;
+ }
+ }
+
+ private int GetDistanceSquared(Color a, Color b)
+ {
+ int dsq = 0; // delta squared
+ int v;
+
+ v = a.B - b.B;
+ dsq += v * v;
+ v = a.G - b.G;
+ dsq += v * v;
+ v = a.R - b.R;
+ dsq += v * v;
+
+ return dsq;
+ }
+
+ public int FindClosestPaletteIndex(Color pixel)
+ {
+ int dsqBest = int.MaxValue;
+ int ret = 0;
+
+ for (int i = 0; i < this.palette.Length; ++i)
+ {
+ int dsq = GetDistanceSquared(this.palette[i], pixel);
+
+ if (dsq < dsqBest)
+ {
+ dsqBest = dsq;
+ ret = i;
+ }
+ }
+
+ return ret;
+ }
+
+ public PaletteTable(Color[] palette)
+ {
+ this.palette = (Color[])palette.Clone();
+ }
+ }
+}
diff --git a/BizHawk.Client.EmuHawk/AVOut/Quantize/Quantizer.cs b/BizHawk.Client.EmuHawk/AVOut/Quantize/Quantizer.cs
new file mode 100644
index 0000000000..8effc61d66
--- /dev/null
+++ b/BizHawk.Client.EmuHawk/AVOut/Quantize/Quantizer.cs
@@ -0,0 +1,372 @@
+/////////////////////////////////////////////////////////////////////////////////
+// Paint.NET
+// Copyright (C) Rick Brewster, Chris Crosetto, Dennis Dietrich, Tom Jackson,
+// Michael Kelsey, Brandon Ortiz, Craig Taylor, Chris Trevino,
+// and Luke Walker
+// Portions Copyright (C) Microsoft Corporation. All Rights Reserved.
+// See src/setup/License.rtf for complete licensing and attribution information.
+/////////////////////////////////////////////////////////////////////////////////
+
+/////////////////////////////////////////////////////////////////////////////////
+// Copied for Paint.NET PCX Plugin
+// Copyright (C) Joshua Bell
+/////////////////////////////////////////////////////////////////////////////////
+
+// Based on: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnaspp/html/colorquant.asp
+
+//Bizhawk says: adapted from https://github.com/inexorabletash/PcxFileType/blob/master/Quantize
+
+using System;
+using System.Drawing;
+using System.Drawing.Imaging;
+
+namespace BizHawk.Client.EmuHawk
+{
+ ///
+ /// Summary description for Class1.
+ ///
+ internal unsafe abstract class Quantizer
+ {
+ ///
+ /// Flag used to indicate whether a single pass or two passes are needed for quantization.
+ ///
+ private bool _singlePass;
+
+ protected bool highquality;
+ public bool HighQuality
+ {
+ get
+ {
+ return highquality;
+ }
+
+ set
+ {
+ highquality = value;
+ }
+ }
+
+ protected int ditherLevel;
+ public int DitherLevel
+ {
+ get
+ {
+ return this.ditherLevel;
+ }
+
+ set
+ {
+ this.ditherLevel = value;
+ }
+ }
+
+ ///
+ /// 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;
+
+ if (source is Bitmap && source.PixelFormat == PixelFormat.Format32bppArgb)
+ {
+ copy = (Bitmap)source;
+ }
+ else
+ {
+ copy = new Bitmap(width, height, PixelFormat.Format32bppArgb);
+
+ // 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, 0, 0, bounds.Width, bounds.Height);
+ }
+ }
+
+ // And construct an 8bpp version
+ Bitmap output = new Bitmap(width, height, PixelFormat.Format8bppIndexed);
+
+ // 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);
+ }
+
+ if (copy != source)
+ {
+ 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();
+ int* 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++)
+ {
+ InitialQuantizePixel(*pSourcePixel);
+ }
+
+ // Add the stride to the source row
+ pSourceRow += sourceData.Stride;
+
+ }
+ }
+
+ int ClampToByte(int val)
+ {
+ if (val < 0) return 0;
+ else if (val > 255) return 255;
+ else return val;
+ }
+
+ ///
+ /// 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;
+ Color[] pallete = output.Palette.Entries;
+ int weight = ditherLevel;
+
+ try
+ {
+ // Lock the output bitmap into memory
+ outputData = output.LockBits(bounds, ImageLockMode.ReadWrite, 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;
+
+ // Now define the destination data pointers
+ byte* pDestinationRow = (byte *)outputData.Scan0.ToPointer();
+ byte* pDestinationPixel = pDestinationRow;
+
+ int[] errorThisRowR = new int[width + 1];
+ int[] errorThisRowG = new int[width + 1];
+ int[] errorThisRowB = new int[width + 1];
+
+ for (int row = 0; row < height; row++)
+ {
+ int[] errorNextRowR = new int[width + 1];
+ int[] errorNextRowG = new int[width + 1];
+ int[] errorNextRowB = new int[width + 1];
+
+ int ptrInc;
+
+ if ((row & 1) == 0)
+ {
+ pSourcePixel = (Int32*)pSourceRow;
+ pDestinationPixel = pDestinationRow;
+ ptrInc = +1;
+ }
+ else
+ {
+ pSourcePixel = (Int32*)pSourceRow + width - 1;
+ pDestinationPixel = pDestinationRow + width - 1;
+ ptrInc = -1;
+ }
+
+ // Loop through each pixel on this scan line
+ for (int col = 0; col < width; ++col)
+ {
+ // Quantize the pixel
+ int srcPixel = *pSourcePixel;
+
+ int srcR = srcPixel & 0xFF;
+ int srcG = (srcPixel>>8) & 0xFF;
+ int srcB = (srcPixel>>16) & 0xFF;
+ int srcA = (srcPixel >> 24) & 0xFF;
+
+ int targetB = ClampToByte(srcB - ((errorThisRowB[col] * weight) / 8));
+ int targetG = ClampToByte(srcG - ((errorThisRowG[col] * weight) / 8));
+ int targetR = ClampToByte(srcR - ((errorThisRowR[col] * weight) / 8));
+ int targetA = srcA;
+
+ int target = (targetA<<24)|(targetB<<16)|(targetG<<8)|targetR;
+
+ byte pixelValue = QuantizePixel(target);
+ *pDestinationPixel = pixelValue;
+
+ int actual = pallete[pixelValue].ToArgb();
+
+ int actualR = actual & 0xFF;
+ int actualG = (actual >> 8) & 0xFF;
+ int actualB = (actual >> 16) & 0xFF;
+ int errorR = actualR - targetR;
+ int errorG = actualG - targetG;
+ int errorB = actualB - targetB;
+
+ // Floyd-Steinberg Error Diffusion:
+ // a) 7/16 error goes to x+1
+ // b) 5/16 error goes to y+1
+ // c) 3/16 error goes to x-1,y+1
+ // d) 1/16 error goes to x+1,y+1
+
+ const int a = 7;
+ const int b = 5;
+ const int c = 3;
+
+ int errorRa = (errorR * a) / 16;
+ int errorRb = (errorR * b) / 16;
+ int errorRc = (errorR * c) / 16;
+ int errorRd = errorR - errorRa - errorRb - errorRc;
+
+ int errorGa = (errorG * a) / 16;
+ int errorGb = (errorG * b) / 16;
+ int errorGc = (errorG * c) / 16;
+ int errorGd = errorG - errorGa - errorGb - errorGc;
+
+ int errorBa = (errorB * a) / 16;
+ int errorBb = (errorB * b) / 16;
+ int errorBc = (errorB * c) / 16;
+ int errorBd = errorB - errorBa - errorBb - errorBc;
+
+ errorThisRowR[col + 1] += errorRa;
+ errorThisRowG[col + 1] += errorGa;
+ errorThisRowB[col + 1] += errorBa;
+
+ errorNextRowR[width - col] += errorRb;
+ errorNextRowG[width - col] += errorGb;
+ errorNextRowB[width - col] += errorBb;
+
+ if (col != 0)
+ {
+ errorNextRowR[width - (col - 1)] += errorRc;
+ errorNextRowG[width - (col - 1)] += errorGc;
+ errorNextRowB[width - (col - 1)] += errorBc;
+ }
+
+ errorNextRowR[width - (col + 1)] += errorRd;
+ errorNextRowG[width - (col + 1)] += errorGd;
+ errorNextRowB[width - (col + 1)] += errorBd;
+
+ unchecked
+ {
+ pSourcePixel += ptrInc;
+ pDestinationPixel += ptrInc;
+ }
+ }
+
+ // Add the stride to the source row
+ pSourceRow += sourceData.Stride;
+
+ // And to the destination row
+ pDestinationRow += outputData.Stride;
+
+ errorThisRowB = errorNextRowB;
+ errorThisRowG = errorNextRowG;
+ errorThisRowR = errorNextRowR;
+ }
+ }
+
+ 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(int 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(int pixel);
+
+ ///
+ /// Retrieve the palette for the quantized image
+ ///
+ /// Any old palette, this is overrwritten
+ /// The new color palette
+ protected abstract ColorPalette GetPalette(ColorPalette original);
+ }
+}