From 4a5e04ae2337efb24175ad855318437b8c9d8224 Mon Sep 17 00:00:00 2001 From: RKrom Date: Wed, 21 Mar 2012 11:44:13 +0000 Subject: [PATCH] Performance improvements for the Wu quantizer. git-svn-id: http://svn.code.sf.net/p/greenshot/code/trunk@1717 7dccd23d-a4a3-4e1f-8c07-b4c1b4018ab4 --- Greenshot/Helpers/ImageOutput.cs | 4 +- GreenshotPlugin/Core/ImageHelper.cs | 51 -- GreenshotPlugin/Core/QuantizerHelper.cs | 611 +++++++++++------------- 3 files changed, 276 insertions(+), 390 deletions(-) diff --git a/Greenshot/Helpers/ImageOutput.cs b/Greenshot/Helpers/ImageOutput.cs index f6005db98..4403b47a3 100644 --- a/Greenshot/Helpers/ImageOutput.cs +++ b/Greenshot/Helpers/ImageOutput.cs @@ -107,13 +107,13 @@ namespace Greenshot.Helpers { // check for color reduction, forced or automatically if (conf.OutputFileAutoReduceColors || reduceColors) { - IColorQuantizer quantizer = ImageHelper.PrepareQuantize((Bitmap)imageToSave); + WuQuantizer quantizer = new WuQuantizer((Bitmap)imageToSave); int colorCount = quantizer.GetColorCount(); LOG.InfoFormat("Image with format {0} has {1} colors", imageToSave.PixelFormat, colorCount); if (reduceColors || colorCount < 256) { try { LOG.Info("Reducing colors on bitmap to 255."); - imageToSave = ImageHelper.Quantize((Bitmap)imageToSave, quantizer, 255); + imageToSave = quantizer.GetQuantizedImage(255); // Make sure the "new" image is disposed disposeImage = true; } catch (Exception e) { diff --git a/GreenshotPlugin/Core/ImageHelper.cs b/GreenshotPlugin/Core/ImageHelper.cs index 3e3f11a8a..d57735e3b 100644 --- a/GreenshotPlugin/Core/ImageHelper.cs +++ b/GreenshotPlugin/Core/ImageHelper.cs @@ -1005,56 +1005,5 @@ namespace GreenshotPlugin.Core { returnBitmap.RotateFlip(rotateFlipType); return returnBitmap; } - - /// - /// Get a quantizer, so we can check if it pays off to quantize - /// - /// - /// IColorQuantizer - public static IColorQuantizer PrepareQuantize(Bitmap sourceBitmap) { - IColorQuantizer quantizer = new WuColorQuantizer(); - quantizer.Prepare(sourceBitmap); - using (BitmapBuffer bbbSrc = new BitmapBuffer(sourceBitmap, false)) { - bbbSrc.Lock(); - for (int y = 0; y < bbbSrc.Height; y++) { - for (int x = 0; x < bbbSrc.Width; x++) { - quantizer.AddColor(bbbSrc.GetColorAt(x, y)); - } - } - } - return quantizer; - } - - /// - /// Quantize the sourceBitmap with the Quantizer returned by PrepareQuantize - /// - /// - /// - /// Quantized bitmap - public static Bitmap Quantize(Bitmap sourceBitmap, IColorQuantizer quantizer, int paletteSize) { - Bitmap result = new Bitmap(sourceBitmap.Width, sourceBitmap.Height, PixelFormat.Format8bppIndexed); - List palette = quantizer.GetPalette(paletteSize); - ColorPalette imagePalette = result.Palette; - // copies all color entries - for (Int32 index = 0; index < palette.Count; index++) { - imagePalette.Entries[index] = palette[index]; - } - result.Palette = imagePalette; - - using (BitmapBuffer bbbDest = new BitmapBuffer(result, false)) { - bbbDest.Lock(); - using (BitmapBuffer bbbSrc = new BitmapBuffer(sourceBitmap, false)) { - bbbSrc.Lock(); - for (int y = 0; y < bbbSrc.Height; y++) { - for (int x = 0; x < bbbSrc.Width; x++) { - Color originalColor = bbbSrc.GetColorAt(x, y); - Int32 paletteIndex = quantizer.GetPaletteIndex(originalColor); - bbbDest.SetColorIndexAt(x,y, (byte)paletteIndex); - } - } - } - } - return result; - } } } diff --git a/GreenshotPlugin/Core/QuantizerHelper.cs b/GreenshotPlugin/Core/QuantizerHelper.cs index 4929d2d96..37490d4c6 100644 --- a/GreenshotPlugin/Core/QuantizerHelper.cs +++ b/GreenshotPlugin/Core/QuantizerHelper.cs @@ -25,43 +25,6 @@ using System.Drawing.Imaging; using System.Collections.Generic; namespace GreenshotPlugin.Core { - /// - /// This interface provides a color quantization capabilities. - /// - public interface IColorQuantizer { - /// - /// Prepares the quantizer for image processing. - /// - /// The image. - void Prepare(Image image); - - /// - /// Adds the color to quantizer. - /// - /// The color to be added. - void AddColor(Color color); - - /// - /// Gets the palette with specified count of the colors. - /// - /// The color count. - /// - List GetPalette(Int32 colorCount); - - /// - /// Gets the index of the palette for specific color. - /// - /// The color. - /// - Int32 GetPaletteIndex(Color color); - - /// - /// Gets the color count. - /// - /// - Int32 GetColorCount(); - } - internal class WuColorCube { /// /// Gets or sets the red minimum. @@ -106,26 +69,264 @@ namespace GreenshotPlugin.Core { public Int32 Volume { get; set; } } - public class QuantizationHelper { + public class WuQuantizer { + private static log4net.ILog LOG = log4net.LogManager.GetLogger(typeof(WuQuantizer)); private static readonly Color BackgroundColor; private static readonly Double[] Factors; - static QuantizationHelper() { + private const Int32 MAXCOLOR = 512; + private const Int32 RED = 2; + private const Int32 GREEN = 1; + private const Int32 BLUE = 0; + private const Int32 SIDESIZE = 33; + private const Int32 MAXSIDEINDEX = 32; + private const Int32 MAXVOLUME = SIDESIZE * SIDESIZE * SIDESIZE; + + // To count the colors + private int colorCount = 0; + + private Int32[] reds; + private Int32[] greens; + private Int32[] blues; + private Int32[] sums; + + private Int64[, ,] weights; + private Int64[, ,] momentsRed; + private Int64[, ,] momentsGreen; + private Int64[, ,] momentsBlue; + private Single[, ,] moments; + + private byte[] tag; + + private WuColorCube[] cubes; + private Bitmap sourceBitmap; + private Bitmap resultBitmap; + + static WuQuantizer() { BackgroundColor = Color.White; - Factors = PrecalculateFactors(); + Factors = new Double[256]; + for (Int32 value = 0; value < 256; value++) { + Factors[value] = value / 255.0; + } } /// - /// Precalculates the alpha-fix values for all the possible alpha values (0-255). + /// See for more details. /// - private static Double[] PrecalculateFactors() { - Double[] result = new Double[256]; + public WuQuantizer(Bitmap sourceBitmap) { + this.sourceBitmap = sourceBitmap; + // Make sure the color count variables are reset + BitArray bitArray = new BitArray((int)Math.Pow(2, 24)); + colorCount = 0; - for (Int32 value = 0; value < 256; value++) { - result[value] = value / 255.0; + // creates all the cubes + cubes = new WuColorCube[MAXCOLOR]; + + // initializes all the cubes + for (Int32 cubeIndex = 0; cubeIndex < MAXCOLOR; cubeIndex++) { + cubes[cubeIndex] = new WuColorCube(); } - return result; + // resets the reference minimums + cubes[0].RedMinimum = 0; + cubes[0].GreenMinimum = 0; + cubes[0].BlueMinimum = 0; + + // resets the reference maximums + cubes[0].RedMaximum = MAXSIDEINDEX; + cubes[0].GreenMaximum = MAXSIDEINDEX; + cubes[0].BlueMaximum = MAXSIDEINDEX; + + weights = new Int64[SIDESIZE, SIDESIZE, SIDESIZE]; + momentsRed = new Int64[SIDESIZE, SIDESIZE, SIDESIZE]; + momentsGreen = new Int64[SIDESIZE, SIDESIZE, SIDESIZE]; + momentsBlue = new Int64[SIDESIZE, SIDESIZE, SIDESIZE]; + moments = new Single[SIDESIZE, SIDESIZE, SIDESIZE]; + + Int32[] table = new Int32[256]; + + for (Int32 tableIndex = 0; tableIndex < 256; ++tableIndex) { + table[tableIndex] = tableIndex * tableIndex; + } + + // Use a bitmap to store the initial match, which is just as good as an array and saves us 2x the storage + resultBitmap = new Bitmap(sourceBitmap.Width, sourceBitmap.Height, PixelFormat.Format8bppIndexed); + using (BitmapBuffer bbbSrc = new BitmapBuffer(sourceBitmap, false)) { + bbbSrc.Lock(); + using (BitmapBuffer bbbDest = new BitmapBuffer(resultBitmap, false)) { + bbbDest.Lock(); + for (int y = 0; y < bbbSrc.Height; y++) { + for (int x = 0; x < bbbSrc.Width; x++) { + Color color = bbbSrc.GetColorAt(x, y); + color = ConvertAlpha(color); + // To count the colors + int index = color.ToArgb() & 0x00ffffff; + // Check if we already have this color + if (!bitArray.Get(index)) { + // If not, add 1 to the single colors + colorCount++; + bitArray.Set(index, true); + } + + Int32 indexRed = (color.R >> 3) + 1; + Int32 indexGreen = (color.G >> 3) + 1; + Int32 indexBlue = (color.B >> 3) + 1; + + weights[indexRed, indexGreen, indexBlue]++; + momentsRed[indexRed, indexGreen, indexBlue] += color.R; + momentsGreen[indexRed, indexGreen, indexBlue] += color.G; + momentsBlue[indexRed, indexGreen, indexBlue] += color.B; + moments[indexRed, indexGreen, indexBlue] += table[color.R] + table[color.G] + table[color.B]; + + // Store the initial "match" + Int32 paletteIndex = (indexRed << 10) + (indexRed << 6) + indexRed + (indexGreen << 5) + indexGreen + indexBlue; + bbbDest.SetColorIndexAt(x, y, (byte)(paletteIndex & 0xff)); + } + } + } + } + } + + /// + /// See for more details. + /// + public Int32 GetColorCount() { + return colorCount; + } + + /// + /// Get the image + /// + public Bitmap GetQuantizedImage(int allowedColorCount) { + // preprocess the colors + CalculateMoments(); + LOG.Info("Calculated the moments..."); + Int32 next = 0; + Single[] volumeVariance = new Single[MAXCOLOR]; + + // processes the cubes + for (Int32 cubeIndex = 1; cubeIndex < allowedColorCount; ++cubeIndex) { + // if cut is possible; make it + if (Cut(cubes[next], cubes[cubeIndex])) { + volumeVariance[next] = cubes[next].Volume > 1 ? CalculateVariance(cubes[next]) : 0.0f; + volumeVariance[cubeIndex] = cubes[cubeIndex].Volume > 1 ? CalculateVariance(cubes[cubeIndex]) : 0.0f; + } else { + // the cut was not possible, revert the index + volumeVariance[next] = 0.0f; + cubeIndex--; + } + + next = 0; + Single temp = volumeVariance[0]; + + for (Int32 index = 1; index <= cubeIndex; ++index) { + if (volumeVariance[index] > temp) { + temp = volumeVariance[index]; + next = index; + } + } + + if (temp <= 0.0) { + allowedColorCount = cubeIndex + 1; + break; + } + } + + Int32[] lookupRed = new Int32[MAXCOLOR]; + Int32[] lookupGreen = new Int32[MAXCOLOR]; + Int32[] lookupBlue = new Int32[MAXCOLOR]; + + tag = new byte[MAXVOLUME]; + + // precalculates lookup tables + for (byte k = 0; k < allowedColorCount; ++k) { + Mark(cubes[k], k, tag); + + long weight = Volume(cubes[k], weights); + + if (weight > 0) { + lookupRed[k] = (int)(Volume(cubes[k], momentsRed) / weight); + lookupGreen[k] = (int)(Volume(cubes[k], momentsGreen) / weight); + lookupBlue[k] = (int)(Volume(cubes[k], momentsBlue) / weight); + } else { + lookupRed[k] = 0; + lookupGreen[k] = 0; + lookupBlue[k] = 0; + } + } + + reds = new Int32[allowedColorCount + 1]; + greens = new Int32[allowedColorCount + 1]; + blues = new Int32[allowedColorCount + 1]; + sums = new Int32[allowedColorCount + 1]; + + LOG.Info("Starting bitmap reconstruction..."); + + using (BitmapBuffer bbbDest = new BitmapBuffer(resultBitmap, false)) { + bbbDest.Lock(); + using (BitmapBuffer bbbSrc = new BitmapBuffer(sourceBitmap, false)) { + bbbSrc.Lock(); + Dictionary lookup = new Dictionary(); + byte bestMatch; + for (int y = 0; y < bbbSrc.Height; y++) { + for (int x = 0; x < bbbSrc.Width; x++) { + Color color = bbbSrc.GetColorAt(x, y); + + // Check if we already matched the color + if (!lookup.ContainsKey(color)) { + // If not we need to find the best match + + // First get initial match + bestMatch = bbbDest.GetColorIndexAt(x, y); + bestMatch = tag[bestMatch]; + + Int32 bestDistance = 100000000; + for (byte lookupIndex = 0; lookupIndex < allowedColorCount; lookupIndex++) { + Int32 foundRed = lookupRed[lookupIndex]; + Int32 foundGreen = lookupGreen[lookupIndex]; + Int32 foundBlue = lookupBlue[lookupIndex]; + Int32 deltaRed = color.R - foundRed; + Int32 deltaGreen = color.G - foundGreen; + Int32 deltaBlue = color.B - foundBlue; + + Int32 distance = deltaRed * deltaRed + deltaGreen * deltaGreen + deltaBlue * deltaBlue; + + if (distance < bestDistance) { + bestDistance = distance; + bestMatch = lookupIndex; + } + } + lookup.Add(color, bestMatch); + } else { + // Already matched, so we just use the lookup + bestMatch = lookup[color]; + } + + reds[bestMatch] += color.R; + greens[bestMatch] += color.G; + blues[bestMatch] += color.B; + sums[bestMatch]++; + + bbbDest.SetColorIndexAt(x, y, bestMatch); + } + } + } + } + + ColorPalette imagePalette = resultBitmap.Palette; + + // generates palette + for (Int32 paletteIndex = 0; paletteIndex < allowedColorCount; paletteIndex++) { + if (sums[paletteIndex] > 0) { + reds[paletteIndex] /= sums[paletteIndex]; + greens[paletteIndex] /= sums[paletteIndex]; + blues[paletteIndex] /= sums[paletteIndex]; + } + + imagePalette.Entries[paletteIndex] = Color.FromArgb(255, reds[paletteIndex], greens[paletteIndex], blues[paletteIndex]); + } + resultBitmap.Palette = imagePalette; + return resultBitmap; } /// @@ -133,7 +334,7 @@ namespace GreenshotPlugin.Core { /// /// The alpha blended color (ARGB). /// The non-alpha blended color (RGB). - internal static Color ConvertAlpha(Color color) { + private static Color ConvertAlpha(Color color) { Color result = color; if (color.A < 255) { @@ -149,68 +350,19 @@ namespace GreenshotPlugin.Core { return result; } - } - /// - /// Author: Xiaolin Wu - /// Dept. of Computer Science - /// Univ. of Western Ontario - /// London, Ontario N6A 5B7 - /// wu@csd.uwo.ca - /// - public class WuColorQuantizer : IColorQuantizer { - #region | Constants | - - private const Int32 MaxColor = 512; - private const Int32 Red = 2; - private const Int32 Green = 1; - private const Int32 Blue = 0; - private const Int32 SideSize = 33; - private const Int32 MaxSideIndex = 32; - private const Int32 MaxVolume = SideSize * SideSize * SideSize; - - #endregion - - #region | Fields | - BitArray bitArray; - - private Int32[] reds; - private Int32[] greens; - private Int32[] blues; - private Int32[] sums; - private Int32[] indices; - - private Int64[, ,] weights; - private Int64[, ,] momentsRed; - private Int64[, ,] momentsGreen; - private Int64[, ,] momentsBlue; - private Single[, ,] moments; - - private Int32[] tag; - private Int32[] quantizedPixels; - private Int32[] table; - private Color[] pixels; - - private Int32 imageSize; - private Int32 pixelIndex; - - private WuColorCube[] cubes; - - #endregion - - #region | Helper methods | /// /// Converts the histogram to a series of moments. /// private void CalculateMoments() { - Int64[] area = new Int64[SideSize]; - Int64[] areaRed = new Int64[SideSize]; - Int64[] areaGreen = new Int64[SideSize]; - Int64[] areaBlue = new Int64[SideSize]; - Single[] area2 = new Single[SideSize]; + Int64[] area = new Int64[SIDESIZE]; + Int64[] areaRed = new Int64[SIDESIZE]; + Int64[] areaGreen = new Int64[SIDESIZE]; + Int64[] areaBlue = new Int64[SIDESIZE]; + Single[] area2 = new Single[SIDESIZE]; - for (Int32 redIndex = 1; redIndex <= MaxSideIndex; ++redIndex) { - for (Int32 index = 0; index <= MaxSideIndex; ++index) { + for (Int32 redIndex = 1; redIndex <= MAXSIDEINDEX; ++redIndex) { + for (Int32 index = 0; index <= MAXSIDEINDEX; ++index) { area[index] = 0; areaRed[index] = 0; areaGreen[index] = 0; @@ -218,14 +370,14 @@ namespace GreenshotPlugin.Core { area2[index] = 0; } - for (Int32 greenIndex = 1; greenIndex <= MaxSideIndex; ++greenIndex) { + for (Int32 greenIndex = 1; greenIndex <= MAXSIDEINDEX; ++greenIndex) { Int64 line = 0; Int64 lineRed = 0; Int64 lineGreen = 0; Int64 lineBlue = 0; Single line2 = 0.0f; - for (Int32 blueIndex = 1; blueIndex <= MaxSideIndex; ++blueIndex) { + for (Int32 blueIndex = 1; blueIndex <= MAXSIDEINDEX; ++blueIndex) { line += weights[redIndex, greenIndex, blueIndex]; lineRed += momentsRed[redIndex, greenIndex, blueIndex]; lineGreen += momentsGreen[redIndex, greenIndex, blueIndex]; @@ -281,19 +433,19 @@ namespace GreenshotPlugin.Core { /// private static Int64 Top(WuColorCube cube, Int32 direction, Int32 position, Int64[, ,] moment) { switch (direction) { - case Red: + case RED: return (moment[position, cube.GreenMaximum, cube.BlueMaximum] - moment[position, cube.GreenMaximum, cube.BlueMinimum] - moment[position, cube.GreenMinimum, cube.BlueMaximum] + moment[position, cube.GreenMinimum, cube.BlueMinimum]); - case Green: + case GREEN: return (moment[cube.RedMaximum, position, cube.BlueMaximum] - moment[cube.RedMaximum, position, cube.BlueMinimum] - moment[cube.RedMinimum, position, cube.BlueMaximum] + moment[cube.RedMinimum, position, cube.BlueMinimum]); - case Blue: + case BLUE: return (moment[cube.RedMaximum, cube.GreenMaximum, position] - moment[cube.RedMaximum, cube.GreenMinimum, position] - moment[cube.RedMinimum, cube.GreenMaximum, position] + @@ -309,19 +461,19 @@ namespace GreenshotPlugin.Core { /// private static Int64 Bottom(WuColorCube cube, Int32 direction, Int64[, ,] moment) { switch (direction) { - case Red: + case RED: return (-moment[cube.RedMinimum, cube.GreenMaximum, cube.BlueMaximum] + moment[cube.RedMinimum, cube.GreenMaximum, cube.BlueMinimum] + moment[cube.RedMinimum, cube.GreenMinimum, cube.BlueMaximum] - moment[cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum]); - case Green: + case GREEN: return (-moment[cube.RedMaximum, cube.GreenMinimum, cube.BlueMaximum] + moment[cube.RedMaximum, cube.GreenMinimum, cube.BlueMinimum] + moment[cube.RedMinimum, cube.GreenMinimum, cube.BlueMaximum] - moment[cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum]); - case Blue: + case BLUE: return (-moment[cube.RedMaximum, cube.GreenMaximum, cube.BlueMinimum] + moment[cube.RedMaximum, cube.GreenMinimum, cube.BlueMinimum] + moment[cube.RedMinimum, cube.GreenMaximum, cube.BlueMinimum] - @@ -405,20 +557,20 @@ namespace GreenshotPlugin.Core { Int64 wholeBlue = Volume(first, momentsBlue); Int64 wholeWeight = Volume(first, weights); - Single maxRed = Maximize(first, Red, first.RedMinimum + 1, first.RedMaximum, cutRed, wholeRed, wholeGreen, wholeBlue, wholeWeight); - Single maxGreen = Maximize(first, Green, first.GreenMinimum + 1, first.GreenMaximum, cutGreen, wholeRed, wholeGreen, wholeBlue, wholeWeight); - Single maxBlue = Maximize(first, Blue, first.BlueMinimum + 1, first.BlueMaximum, cutBlue, wholeRed, wholeGreen, wholeBlue, wholeWeight); + Single maxRed = Maximize(first, RED, first.RedMinimum + 1, first.RedMaximum, cutRed, wholeRed, wholeGreen, wholeBlue, wholeWeight); + Single maxGreen = Maximize(first, GREEN, first.GreenMinimum + 1, first.GreenMaximum, cutGreen, wholeRed, wholeGreen, wholeBlue, wholeWeight); + Single maxBlue = Maximize(first, BLUE, first.BlueMinimum + 1, first.BlueMaximum, cutBlue, wholeRed, wholeGreen, wholeBlue, wholeWeight); if ((maxRed >= maxGreen) && (maxRed >= maxBlue)) { - direction = Red; + direction = RED; // cannot split empty cube if (cutRed[0] < 0) return false; } else { if ((maxGreen >= maxRed) && (maxGreen >= maxBlue)) { - direction = Green; + direction = GREEN; } else { - direction = Blue; + direction = BLUE; } } @@ -428,19 +580,19 @@ namespace GreenshotPlugin.Core { // cuts in a certain direction switch (direction) { - case Red: + case RED: second.RedMinimum = first.RedMaximum = cutRed[0]; second.GreenMinimum = first.GreenMinimum; second.BlueMinimum = first.BlueMinimum; break; - case Green: + case GREEN: second.GreenMinimum = first.GreenMaximum = cutGreen[0]; second.RedMinimum = first.RedMinimum; second.BlueMinimum = first.BlueMinimum; break; - case Blue: + case BLUE: second.BlueMinimum = first.BlueMaximum = cutBlue[0]; second.RedMinimum = first.RedMinimum; second.GreenMinimum = first.GreenMinimum; @@ -458,229 +610,14 @@ namespace GreenshotPlugin.Core { /// /// Marks all the tags with a given label. /// - private void Mark(WuColorCube cube, Int32 label, Int32[] tag) { + private void Mark(WuColorCube cube, Int32 label, byte[] tag) { for (Int32 redIndex = cube.RedMinimum + 1; redIndex <= cube.RedMaximum; ++redIndex) { for (Int32 greenIndex = cube.GreenMinimum + 1; greenIndex <= cube.GreenMaximum; ++greenIndex) { for (Int32 blueIndex = cube.BlueMinimum + 1; blueIndex <= cube.BlueMaximum; ++blueIndex) { - tag[(redIndex << 10) + (redIndex << 6) + redIndex + (greenIndex << 5) + greenIndex + blueIndex] = label; + tag[(redIndex << 10) + (redIndex << 6) + redIndex + (greenIndex << 5) + greenIndex + blueIndex] = (byte)label; } } } } - - #endregion - - #region << IColorQuantizer >> - - /// - /// See for more details. - /// - public void Prepare(Image image) { - bitArray = new BitArray((int)Math.Pow(2, 24)); - // creates all the cubes - cubes = new WuColorCube[MaxColor]; - - // initializes all the cubes - for (Int32 cubeIndex = 0; cubeIndex < MaxColor; cubeIndex++) { - cubes[cubeIndex] = new WuColorCube(); - } - - // resets the reference minimums - cubes[0].RedMinimum = 0; - cubes[0].GreenMinimum = 0; - cubes[0].BlueMinimum = 0; - - // resets the reference maximums - cubes[0].RedMaximum = MaxSideIndex; - cubes[0].GreenMaximum = MaxSideIndex; - cubes[0].BlueMaximum = MaxSideIndex; - - weights = new Int64[SideSize, SideSize, SideSize]; - momentsRed = new Int64[SideSize, SideSize, SideSize]; - momentsGreen = new Int64[SideSize, SideSize, SideSize]; - momentsBlue = new Int64[SideSize, SideSize, SideSize]; - moments = new Single[SideSize, SideSize, SideSize]; - - table = new Int32[256]; - - for (Int32 tableIndex = 0; tableIndex < 256; ++tableIndex) { - table[tableIndex] = tableIndex * tableIndex; - } - - pixelIndex = 0; - imageSize = image.Width * image.Height; - - quantizedPixels = new Int32[imageSize]; - pixels = new Color[imageSize]; - } - - /// - /// See for more details. - /// - public void AddColor(Color color) { - color = QuantizationHelper.ConvertAlpha(color); - - // To count the colors - bitArray.Set(color.ToArgb() & 0x00ffffff, true); - - Int32 indexRed = (color.R >> 3) + 1; - Int32 indexGreen = (color.G >> 3) + 1; - Int32 indexBlue = (color.B >> 3) + 1; - - weights[indexRed, indexGreen, indexBlue]++; - momentsRed[indexRed, indexGreen, indexBlue] += color.R; - momentsGreen[indexRed, indexGreen, indexBlue] += color.G; - momentsBlue[indexRed, indexGreen, indexBlue] += color.B; - moments[indexRed, indexGreen, indexBlue] += table[color.R] + table[color.G] + table[color.B]; - - quantizedPixels[pixelIndex] = (indexRed << 10) + (indexRed << 6) + indexRed + (indexGreen << 5) + indexGreen + indexBlue; - pixels[pixelIndex] = color; - pixelIndex++; - } - - /// - /// See for more details. - /// - public Int32 GetColorCount() { - int result = 0; - - for (int i = 0; i < bitArray.Length; i++) { - if (bitArray.Get(i)) { - result++; - } - } - return result; - } - - /// - /// See for more details. - /// - public List GetPalette(int colorCount) { - // preprocess the colors - CalculateMoments(); - - Int32 next = 0; - Single[] volumeVariance = new Single[MaxColor]; - - // processes the cubes - for (Int32 cubeIndex = 1; cubeIndex < colorCount; ++cubeIndex) { - // if cut is possible; make it - if (Cut(cubes[next], cubes[cubeIndex])) { - volumeVariance[next] = cubes[next].Volume > 1 ? CalculateVariance(cubes[next]) : 0.0f; - volumeVariance[cubeIndex] = cubes[cubeIndex].Volume > 1 ? CalculateVariance(cubes[cubeIndex]) : 0.0f; - } else // the cut was not possible, revert the index - { - volumeVariance[next] = 0.0f; - cubeIndex--; - } - - next = 0; - Single temp = volumeVariance[0]; - - for (Int32 index = 1; index <= cubeIndex; ++index) { - if (volumeVariance[index] > temp) { - temp = volumeVariance[index]; - next = index; - } - } - - if (temp <= 0.0) { - colorCount = cubeIndex + 1; - break; - } - } - - Int32[] lookupRed = new Int32[MaxColor]; - Int32[] lookupGreen = new Int32[MaxColor]; - Int32[] lookupBlue = new Int32[MaxColor]; - - tag = new Int32[MaxVolume]; - - // precalculates lookup tables - for (int k = 0; k < colorCount; ++k) { - Mark(cubes[k], k, tag); - - long weight = Volume(cubes[k], weights); - - if (weight > 0) { - lookupRed[k] = (int)(Volume(cubes[k], momentsRed) / weight); - lookupGreen[k] = (int)(Volume(cubes[k], momentsGreen) / weight); - lookupBlue[k] = (int)(Volume(cubes[k], momentsBlue) / weight); - } else { - lookupRed[k] = 0; - lookupGreen[k] = 0; - lookupBlue[k] = 0; - } - } - - // copies the per pixel tags - for (Int32 index = 0; index < imageSize; ++index) { - quantizedPixels[index] = tag[quantizedPixels[index]]; - } - - reds = new Int32[colorCount + 1]; - greens = new Int32[colorCount + 1]; - blues = new Int32[colorCount + 1]; - sums = new Int32[colorCount + 1]; - indices = new Int32[imageSize]; - - // scans and adds colors - for (Int32 index = 0; index < imageSize; index++) { - Color color = pixels[index]; - - Int32 match = quantizedPixels[index]; - Int32 bestMatch = match; - Int32 bestDistance = 100000000; - - for (Int32 lookup = 0; lookup < colorCount; lookup++) { - Int32 foundRed = lookupRed[lookup]; - Int32 foundGreen = lookupGreen[lookup]; - Int32 foundBlue = lookupBlue[lookup]; - Int32 deltaRed = color.R - foundRed; - Int32 deltaGreen = color.G - foundGreen; - Int32 deltaBlue = color.B - foundBlue; - - Int32 distance = deltaRed * deltaRed + deltaGreen * deltaGreen + deltaBlue * deltaBlue; - - if (distance < bestDistance) { - bestDistance = distance; - bestMatch = lookup; - } - } - - reds[bestMatch] += color.R; - greens[bestMatch] += color.G; - blues[bestMatch] += color.B; - sums[bestMatch]++; - - indices[index] = bestMatch; - } - - List result = new List(); - - // generates palette - for (Int32 paletteIndex = 0; paletteIndex < colorCount; paletteIndex++) { - if (sums[paletteIndex] > 0) { - reds[paletteIndex] /= sums[paletteIndex]; - greens[paletteIndex] /= sums[paletteIndex]; - blues[paletteIndex] /= sums[paletteIndex]; - } - - Color color = Color.FromArgb(255, reds[paletteIndex], greens[paletteIndex], blues[paletteIndex]); - result.Add(color); - } - - pixelIndex = 0; - return result; - } - - /// - /// See for more details. - /// - public Int32 GetPaletteIndex(Color color) { - return indices[pixelIndex++]; - } - - #endregion } } \ No newline at end of file