diff options
Diffstat (limited to 'Software/Visual_Studio/Tango.RemoteDesktop/Quantization/Quantizer.cs')
| -rw-r--r-- | Software/Visual_Studio/Tango.RemoteDesktop/Quantization/Quantizer.cs | 335 |
1 files changed, 335 insertions, 0 deletions
diff --git a/Software/Visual_Studio/Tango.RemoteDesktop/Quantization/Quantizer.cs b/Software/Visual_Studio/Tango.RemoteDesktop/Quantization/Quantizer.cs new file mode 100644 index 000000000..e63dc1ad8 --- /dev/null +++ b/Software/Visual_Studio/Tango.RemoteDesktop/Quantization/Quantizer.cs @@ -0,0 +1,335 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +// Based on: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnaspp/html/colorquant.asp + +using Tango.RemoteDesktop.Quantization; +using System; +using System.Drawing; +using System.Drawing.Imaging; +using System.Runtime.InteropServices; + +namespace Tango.RemoteDesktop.Quantization +{ + 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 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) + { + this.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 version of the image + Bitmap img32bpp; + + if (source is Bitmap && source.PixelFormat == PixelFormat.Format32bppArgb) + { + img32bpp = (Bitmap)source; + } + else + { + img32bpp = new Bitmap(width, height, PixelFormat.Format32bppArgb); + + // Now lock the bitmap into memory + using (Graphics g = Graphics.FromImage(img32bpp)) + { + 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 = img32bpp.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 + img32bpp.UnlockBits(sourceData); + } + + if (img32bpp != source) + { + img32bpp.Dispose(); + img32bpp = null; + } + + // 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(); + 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++) + { + InitialQuantizePixel((ColorBgra *)pSourcePixel); + } + + // Add the stride to the source row + pSourceRow += sourceData.Stride; + } + } + + /// <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 + ColorBgra srcPixel = *(ColorBgra *)pSourcePixel; + ColorBgra target = new ColorBgra(); + + target.B = Utility.ClampToByte(srcPixel.B - ((errorThisRowB[col] * weight) / 8)); + target.G = Utility.ClampToByte(srcPixel.G - ((errorThisRowG[col] * weight) / 8)); + target.R = Utility.ClampToByte(srcPixel.R - ((errorThisRowR[col] * weight) / 8)); + target.A = srcPixel.A; + + byte pixelValue = QuantizePixel(&target); + *pDestinationPixel = pixelValue; + + ColorBgra actual = ColorBgra.FromColor(pallete[pixelValue]); + + int errorR = actual.R - target.R; + int errorG = actual.G - target.G; + int errorB = actual.B - target.B; + + // 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 is necessary because otherwise it throws a fit if ptrInc is negative. + 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(ColorBgra *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(ColorBgra *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); + } +} |
