add palette optimizer to gifs to improve image quality and compression (due to less dithering in low color images)
This commit is contained in:
parent
6e3fa4661e
commit
d39dc2296e
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Quantize using an Octree
|
||||
/// </summary>
|
||||
unsafe class OctreeQuantizer : Quantizer
|
||||
{
|
||||
/// <summary>
|
||||
/// Stores the tree
|
||||
/// </summary>
|
||||
private Octree _octree;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum allowed color depth
|
||||
/// </summary>
|
||||
private int _maxColors;
|
||||
|
||||
/// <summary>
|
||||
/// Construct the octree quantizer
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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
|
||||
/// </remarks>
|
||||
/// <param name="maxColors">The maximum number of colors to return</param>
|
||||
/// <param name="maxColorBits">The number of significant bits</param>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process the pixel in the first pass of the algorithm
|
||||
/// </summary>
|
||||
/// <param name="pixel">The pixel to quantize</param>
|
||||
/// <remarks>
|
||||
/// This function need only be overridden if your quantize algorithm needs two passes,
|
||||
/// such as an Octree quantizer.
|
||||
/// </remarks>
|
||||
protected override void InitialQuantizePixel(int pixel)
|
||||
{
|
||||
_octree.AddColor(pixel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override this to process the pixel in the second pass of the algorithm
|
||||
/// </summary>
|
||||
/// <param name="pixel">The pixel to quantize</param>
|
||||
/// <returns>The quantized value</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve the palette for the quantized image
|
||||
/// </summary>
|
||||
/// <param name="original">Any old palette, this is overrwritten</param>
|
||||
/// <returns>The new color palette</returns>
|
||||
protected override ColorPalette GetPalette(ColorPalette original)
|
||||
{
|
||||
// First off convert the octree to _maxColors colors
|
||||
List<Color> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Class which does the actual quantization
|
||||
/// </summary>
|
||||
private class Octree
|
||||
{
|
||||
/// <summary>
|
||||
/// Construct the octree
|
||||
/// </summary>
|
||||
/// <param name="maxColorBits">The maximum number of significant bits in the image</param>
|
||||
public Octree(int maxColorBits)
|
||||
{
|
||||
_maxColorBits = maxColorBits;
|
||||
_leafCount = 0;
|
||||
_reducibleNodes = new OctreeNode[9];
|
||||
_root = new OctreeNode(0, _maxColorBits, this);
|
||||
_previousColor = 0;
|
||||
_previousNode = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a given color value to the octree
|
||||
/// </summary>
|
||||
/// <param name="pixel"></param>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reduce the depth of the tree
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get/Set the number of leaves in the tree
|
||||
/// </summary>
|
||||
public int Leaves
|
||||
{
|
||||
get
|
||||
{
|
||||
return _leafCount;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_leafCount = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the array of reducible nodes
|
||||
/// </summary>
|
||||
protected OctreeNode[] ReducibleNodes
|
||||
{
|
||||
get
|
||||
{
|
||||
return _reducibleNodes;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Keep track of the previous node that was quantized
|
||||
/// </summary>
|
||||
/// <param name="node">The node last quantized</param>
|
||||
protected void TrackPrevious(OctreeNode node)
|
||||
{
|
||||
_previousNode = node;
|
||||
}
|
||||
|
||||
private Color[] _palette;
|
||||
private PaletteTable paletteTable;
|
||||
|
||||
/// <summary>
|
||||
/// Convert the nodes in the octree to a palette with a maximum of colorCount colors
|
||||
/// </summary>
|
||||
/// <param name="colorCount">The maximum number of colors</param>
|
||||
/// <returns>A list with the palettized colors</returns>
|
||||
public List<Color> Palletize(int colorCount)
|
||||
{
|
||||
while (Leaves > colorCount)
|
||||
{
|
||||
Reduce();
|
||||
}
|
||||
|
||||
// Now palettize the nodes
|
||||
List<Color> palette = new List<Color>(Leaves);
|
||||
int paletteIndex = 0;
|
||||
|
||||
_root.ConstructPalette(palette, ref paletteIndex);
|
||||
|
||||
// And return the palette
|
||||
this._palette = palette.ToArray();
|
||||
this.paletteTable = null;
|
||||
|
||||
return palette;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the palette index for the passed color
|
||||
/// </summary>
|
||||
/// <param name="pixel"></param>
|
||||
/// <returns></returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mask used when getting the appropriate pixels for a given node
|
||||
/// </summary>
|
||||
private static int[] mask = new int[8] { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 };
|
||||
|
||||
/// <summary>
|
||||
/// The root of the octree
|
||||
/// </summary>
|
||||
private OctreeNode _root;
|
||||
|
||||
/// <summary>
|
||||
/// Number of leaves in the tree
|
||||
/// </summary>
|
||||
private int _leafCount;
|
||||
|
||||
/// <summary>
|
||||
/// Array of reducible nodes
|
||||
/// </summary>
|
||||
private OctreeNode[] _reducibleNodes;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum number of significant bits in the image
|
||||
/// </summary>
|
||||
private int _maxColorBits;
|
||||
|
||||
/// <summary>
|
||||
/// Store the last node quantized
|
||||
/// </summary>
|
||||
private OctreeNode _previousNode;
|
||||
|
||||
/// <summary>
|
||||
/// Cache the previous color quantized
|
||||
/// </summary>
|
||||
private int _previousColor;
|
||||
|
||||
/// <summary>
|
||||
/// Class which encapsulates each node in the tree
|
||||
/// </summary>
|
||||
protected class OctreeNode
|
||||
{
|
||||
/// <summary>
|
||||
/// Construct the node
|
||||
/// </summary>
|
||||
/// <param name="level">The level in the tree = 0 - 7</param>
|
||||
/// <param name="colorBits">The number of significant color bits in the image</param>
|
||||
/// <param name="octree">The tree to which this node belongs</param>
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a color into the tree
|
||||
/// </summary>
|
||||
/// <param name="pixel">The color</param>
|
||||
/// <param name="colorBits">The number of significant color bits</param>
|
||||
/// <param name="level">The level in the tree</param>
|
||||
/// <param name="octree">The tree to which this node belongs</param>
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get/Set the next reducible node
|
||||
/// </summary>
|
||||
public OctreeNode NextReducible
|
||||
{
|
||||
get
|
||||
{
|
||||
return _nextReducible;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_nextReducible = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the child nodes
|
||||
/// </summary>
|
||||
public OctreeNode[] Children
|
||||
{
|
||||
get
|
||||
{
|
||||
return _children;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reduce this node by removing all of its children
|
||||
/// </summary>
|
||||
/// <returns>The number of leaves removed</returns>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Traverse the tree, building up the color palette
|
||||
/// </summary>
|
||||
/// <param name="palette">The palette</param>
|
||||
/// <param name="paletteIndex">The current palette index</param>
|
||||
public void ConstructPalette(List<Color> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the palette index for the passed color
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Increment the pixel count and add to the color information
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flag indicating that this is a leaf node
|
||||
/// </summary>
|
||||
private bool _leaf;
|
||||
|
||||
/// <summary>
|
||||
/// Number of pixels in this node
|
||||
/// </summary>
|
||||
private int _pixelCount;
|
||||
|
||||
/// <summary>
|
||||
/// Red component
|
||||
/// </summary>
|
||||
private int _red;
|
||||
|
||||
/// <summary>
|
||||
/// Green Component
|
||||
/// </summary>
|
||||
private int _green;
|
||||
|
||||
/// <summary>
|
||||
/// Blue component
|
||||
/// </summary>
|
||||
private int _blue;
|
||||
|
||||
/// <summary>
|
||||
/// Pointers to any child nodes
|
||||
/// </summary>
|
||||
private OctreeNode[] _children;
|
||||
|
||||
/// <summary>
|
||||
/// Pointer to next reducible node
|
||||
/// </summary>
|
||||
private OctreeNode _nextReducible;
|
||||
|
||||
/// <summary>
|
||||
/// The index of this node in the palette
|
||||
/// </summary>
|
||||
private int _paletteIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
/// Summary description for Class1.
|
||||
/// </summary>
|
||||
internal unsafe abstract class Quantizer
|
||||
{
|
||||
/// <summary>
|
||||
/// Flag used to indicate whether a single pass or two passes are needed for quantization.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct the quantizer
|
||||
/// </summary>
|
||||
/// <param name="singlePass">If true, the quantization only needs to loop through the source pixels once</param>
|
||||
/// <remarks>
|
||||
/// 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'.
|
||||
/// </remarks>
|
||||
public Quantizer(bool singlePass)
|
||||
{
|
||||
_singlePass = singlePass;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Quantize an image and return the resulting output bitmap
|
||||
/// </summary>
|
||||
/// <param name="source">The image to quantize</param>
|
||||
/// <returns>A quantized version of the image</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute the first pass through the pixels in the image
|
||||
/// </summary>
|
||||
/// <param name="sourceData">The source data</param>
|
||||
/// <param name="width">The width in pixels of the image</param>
|
||||
/// <param name="height">The height in pixels of the image</param>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute a second pass through the bitmap
|
||||
/// </summary>
|
||||
/// <param name="sourceData">The source bitmap, locked into memory</param>
|
||||
/// <param name="output">The output bitmap</param>
|
||||
/// <param name="width">The width in pixels of the image</param>
|
||||
/// <param name="height">The height in pixels of the image</param>
|
||||
/// <param name="bounds">The bounding rectangle</param>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override this to process the pixel in the first pass of the algorithm
|
||||
/// </summary>
|
||||
/// <param name="pixel">The pixel to quantize</param>
|
||||
/// <remarks>
|
||||
/// This function need only be overridden if your quantize algorithm needs two passes,
|
||||
/// such as an Octree quantizer.
|
||||
/// </remarks>
|
||||
protected virtual void InitialQuantizePixel(int pixel)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override this to process the pixel in the second pass of the algorithm
|
||||
/// </summary>
|
||||
/// <param name="pixel">The pixel to quantize</param>
|
||||
/// <returns>The quantized value</returns>
|
||||
protected abstract byte QuantizePixel(int pixel);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve the palette for the quantized image
|
||||
/// </summary>
|
||||
/// <param name="original">Any old palette, this is overrwritten</param>
|
||||
/// <returns>The new color palette</returns>
|
||||
protected abstract ColorPalette GetPalette(ColorPalette original);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue