/// <summary> /// Adds a pixel to the color history. /// </summary> /// <param name="pixel"> /// The pixel to add. /// </param> public void AddPixel(Color32 pixel) { this.Alpha += pixel.A; this.Red += pixel.R; this.Green += pixel.G; this.Blue += pixel.B; this.Sum++; }
/// <summary> /// Quantizes the image contained within the <see cref="ImageBuffer"/> returning the result. /// </summary> /// <param name="imageBuffer"> /// The <see cref="ImageBuffer"/> for storing and manipulating pixel information.. /// </param> /// <param name="colorCount"> /// The maximum number of colors apply to the image. /// </param> /// <param name="lookups"> /// The array of <see cref="Color32"/> containing indexed versions of the images colors. /// </param> /// <param name="alphaThreshold"> /// All colors with an alpha value less than this will be considered fully transparent. /// </param> /// <returns> /// The quantized <see cref="Bitmap"/>. /// </returns> internal override Bitmap GetQuantizedImage(ImageBuffer imageBuffer, int colorCount, Color32[] lookups, int alphaThreshold) { Bitmap result = new Bitmap(imageBuffer.Image.Width, imageBuffer.Image.Height, PixelFormat.Format8bppIndexed); result.SetResolution(imageBuffer.Image.HorizontalResolution, imageBuffer.Image.VerticalResolution); ImageBuffer resultBuffer = new ImageBuffer(result); PaletteColorHistory[] paletteHistogram = new PaletteColorHistory[colorCount + 1]; resultBuffer.UpdatePixelIndexes(IndexedPixels(imageBuffer, lookups, alphaThreshold, paletteHistogram)); result.Palette = BuildPalette(result.Palette, paletteHistogram); return result; }
/// <summary> /// Initializes a new instance of the <see cref="PaletteLookup"/> class. /// </summary> /// <param name="palette"> /// The palette. /// </param> public PaletteLookup(Color32[] palette) { this.Palette = new LookupNode[palette.Length]; for (int paletteIndex = 0; paletteIndex < palette.Length; paletteIndex++) { this.Palette[paletteIndex] = new LookupNode { Color32 = palette[paletteIndex], PaletteIndex = (byte)paletteIndex }; } this.BuildLookup(palette); }
/// <summary> /// Gets palette index for the given pixel. /// </summary> /// <param name="pixel"> /// The pixel to return the index for. /// </param> /// <returns> /// The <see cref="byte"/> representing the index. /// </returns> public byte GetPaletteIndex(Color32 pixel) { int pixelKey = pixel.Argb & this.paletteMask; LookupNode[] bucket; if (!this.lookupNodes.TryGetValue(pixelKey, out bucket)) { bucket = this.Palette; } if (bucket.Length == 1) { return bucket[0].PaletteIndex; } int bestDistance = int.MaxValue; byte bestMatch = 0; foreach (LookupNode lookup in bucket) { Color32 lookupPixel = lookup.Color32; int deltaAlpha = pixel.A - lookupPixel.A; int distance = deltaAlpha * deltaAlpha; int deltaRed = pixel.R - lookupPixel.R; distance += deltaRed * deltaRed; int deltaGreen = pixel.G - lookupPixel.G; distance += deltaGreen * deltaGreen; int deltaBlue = pixel.B - lookupPixel.B; distance += deltaBlue * deltaBlue; if (distance >= bestDistance) { continue; } bestDistance = distance; bestMatch = lookup.PaletteIndex; } if ((bucket == this.Palette) && (pixelKey != 0)) { this.lookupNodes[pixelKey] = new[] { bucket[bestMatch] }; } return bestMatch; }
public static void then_should_add_each_property_value_together() { // Arrange var colorMoment = new ColorMoment { Alpha = 1, Blue = 2, Green = 3, Moment = 4, Red = 5, Weight = 6 }; var color32 = new Color32(6, 5, 4, 3); // Act colorMoment.Add(color32); // Assert Assert.That(colorMoment.Alpha, Is.EqualTo(7)); Assert.That(colorMoment.Red, Is.EqualTo(10)); Assert.That(colorMoment.Green, Is.EqualTo(7)); Assert.That(colorMoment.Blue, Is.EqualTo(5)); Assert.That(colorMoment.Moment, Is.EqualTo(90f)); Assert.That(colorMoment.Weight, Is.EqualTo(7)); }
/// <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(Color32* pixel) { // The color at [maxColors] is set to transparent byte paletteIndex = (byte)this.maxColors; // Get the palette index if this non-transparent if (pixel->A > 0) { paletteIndex = (byte)this.octree.GetPaletteIndex(pixel); } return paletteIndex; }
/// <summary> /// Adds a pixel to the current instance. /// </summary> /// <param name="pixel"> /// The pixel to add. /// </param> public void Add(Color32 pixel) { byte alpha = pixel.A; byte red = pixel.R; byte green = pixel.G; byte blue = pixel.B; this.Alpha += alpha; this.Red += red; this.Green += green; this.Blue += blue; this.Weight++; this.Moment += (alpha * alpha) + (red * red) + (green * green) + (blue * blue); }
/// <summary> /// Returns the hash code for the given instance. /// </summary> /// <param name="obj"> /// The instance of <see cref="Color32"/> to return the hash code for. /// </param> /// <returns> /// A 32-bit signed integer that is the hash code for this instance. /// </returns> private int GetHashCode(Color32 obj) { unchecked { int hashCode = obj.B.GetHashCode(); hashCode = (hashCode * 397) ^ obj.G.GetHashCode(); hashCode = (hashCode * 397) ^ obj.R.GetHashCode(); hashCode = (hashCode * 397) ^ obj.A.GetHashCode(); return hashCode; } }
/// <summary> /// The build lookup. /// </summary> /// <param name="palette"> /// The palette. /// </param> private void BuildLookup(Color32[] palette) { int mask = GetMask(palette); Dictionary<int, List<LookupNode>> tempLookup = new Dictionary<int, List<LookupNode>>(); foreach (LookupNode lookup in this.Palette) { int pixelKey = lookup.Color32.Argb & mask; List<LookupNode> bucket; if (!tempLookup.TryGetValue(pixelKey, out bucket)) { bucket = new List<LookupNode>(); tempLookup[pixelKey] = bucket; } bucket.Add(lookup); } this.lookupNodes = new Dictionary<int, LookupNode[]>(tempLookup.Count); foreach (int key in tempLookup.Keys) { this.lookupNodes[key] = tempLookup[key].ToArray(); } this.paletteMask = mask; }
/// <summary> /// Increment the pixel count and add to the color information /// </summary> /// <param name="pixel"> /// The pixel to add. /// </param> public void Increment(Color32* pixel) { this.pixelCount++; this.red += pixel->R; this.green += pixel->G; this.blue += pixel->B; }
/// <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(Color32* pixel, int colorBits, int level, Octree octree) { // Update the color information if this is a leaf if (this.leaf) { this.Increment(pixel); // Setup the previous node octree.TrackPrevious(this); } else { // Go to the next level down in the tree int shift = 7 - level; int index = ((pixel->R & Mask[level]) >> (shift - 2)) | ((pixel->G & Mask[level]) >> (shift - 1)) | ((pixel->B & Mask[level]) >> shift); OctreeNode child = this.children[index]; if (null == child) { // Create a new child node & store in the array child = new OctreeNode(level + 1, colorBits, octree); this.children[index] = child; } // Add the color to the child node child.AddColor(pixel, colorBits, level + 1, octree); } }
/// <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(Color32* pixel);
/// <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(Color32* pixel) { }
/// <summary> /// Add a given color value to the Octree /// </summary> /// <param name="pixel"> /// The <see cref="Color32"/>containing color information to add. /// </param> public void AddColor(Color32* pixel) { // Check if this request is for the same color as the last if (this.previousColor == pixel->Argb) { // If so, check if I have a previous node setup. This will only occur if the first color in the image // happens to be black, with an alpha component of zero. if (null == this.previousNode) { this.previousColor = pixel->Argb; this.root.AddColor(pixel, this.maxColorBits, 0, this); } else { // Just update the previous node this.previousNode.Increment(pixel); } } else { this.previousColor = pixel->Argb; this.root.AddColor(pixel, this.maxColorBits, 0, this); } }
/// <summary> /// Gets an enumerable array of bytes representing each row of the image. /// </summary> /// <param name="image"> /// The <see cref="ImageBuffer"/> for storing and manipulating pixel information. /// </param> /// <param name="lookups"> /// The array of <see cref="Color32"/> containing indexed versions of the images colors. /// </param> /// <param name="alphaThreshold"> /// The alpha threshold. /// </param> /// <param name="paletteHistogram"> /// The palette histogram. /// </param> /// <returns> /// The enumerable list of <see cref="byte"/> representing each pixel. /// </returns> private static IEnumerable<byte[]> IndexedPixels(ImageBuffer image, Color32[] lookups, int alphaThreshold, PaletteColorHistory[] paletteHistogram) { byte[] lineIndexes = new byte[image.Image.Width]; PaletteLookup lookup = new PaletteLookup(lookups); // Determine the correct fallback color. byte fallback = lookups.Length < AlphaMax ? AlphaMin : AlphaMax; foreach (Color32[] pixelLine in image.PixelLines) { int length = pixelLine.Length; for (int i = 0; i < length; i++) { Color32 pixel = pixelLine[i]; byte bestMatch = fallback; if (pixel.A > alphaThreshold) { bestMatch = lookup.GetPaletteIndex(pixel); paletteHistogram[bestMatch].AddPixel(pixel); } lineIndexes[i] = bestMatch; } yield return lineIndexes; } }
/// <summary> /// Get the palette index for the passed color /// </summary> /// <param name="pixel"> /// The <see cref="Color32"/> containing the pixel data. /// </param> /// <returns> /// The index of the given structure. /// </returns> public int GetPaletteIndex(Color32* pixel) { return this.root.GetPaletteIndex(pixel, 0); }
/// <summary> /// Quantizes the image contained within the <see cref="ImageBuffer"/> returning the result. /// </summary> /// <param name="imageBuffer"> /// The <see cref="ImageBuffer"/> for storing and manipulating pixel information.. /// </param> /// <param name="colorCount"> /// The maximum number of colors apply to the image. /// </param> /// <param name="lookups"> /// The array of <see cref="Color32"/> containing indexed versions of the images colors. /// </param> /// <param name="alphaThreshold"> /// All colors with an alpha value less than this will be considered fully transparent. /// </param> /// <returns> /// The quantized <see cref="Bitmap"/>. /// </returns> internal abstract Bitmap GetQuantizedImage(ImageBuffer imageBuffer, int colorCount, Color32[] lookups, int alphaThreshold);
/// <summary> /// Return the palette index for the passed color /// </summary> /// <param name="pixel"> /// The <see cref="Color32"/> representing the pixel. /// </param> /// <param name="level"> /// The level. /// </param> /// <returns> /// The <see cref="int"/> representing the index of the pixel in the palette. /// </returns> public int GetPaletteIndex(Color32* pixel, int level) { int index = this.paletteIndex; if (!this.leaf) { int shift = 7 - level; int pixelIndex = ((pixel->R & Mask[level]) >> (shift - 2)) | ((pixel->G & Mask[level]) >> (shift - 1)) | ((pixel->B & Mask[level]) >> shift); if (null != this.children[pixelIndex]) { index = this.children[pixelIndex].GetPaletteIndex(pixel, level + 1); } else { throw new Exception("Didn't expect this!"); } } return index; }
private static Color32[] BuildLookups(Box[] cubes, ColorMoment[, , ,] moments) { Color32[] lookups = new Color32[cubes.Length]; for (int cubeIndex = 0; cubeIndex < cubes.Length; cubeIndex++) { ColorMoment volume = Volume(moments, cubes[cubeIndex]); if (volume.Weight <= 0) { continue; } Color32 lookup = new Color32 { A = (byte)(volume.Alpha / volume.Weight), R = (byte)(volume.Red / volume.Weight), G = (byte)(volume.Green / volume.Weight), B = (byte)(volume.Blue / volume.Weight) }; lookups[cubeIndex] = lookup; } return lookups; }
/// <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(Color32* pixel) { // Add the color to the Octree this.octree.AddColor(pixel); }
/// <summary> /// Gets the mask value from the palette. /// </summary> /// <param name="palette"> /// The palette. /// </param> /// <returns> /// The <see cref="int"/> representing the component value of the mask. /// </returns> private static int GetMask(Color32[] palette) { IEnumerable<byte> alphas = palette.Select(p => p.A).ToArray(); byte maxAlpha = alphas.Max(); int uniqueAlphas = alphas.Distinct().Count(); IEnumerable<byte> reds = palette.Select(p => p.R).ToArray(); byte maxRed = reds.Max(); int uniqueReds = reds.Distinct().Count(); IEnumerable<byte> greens = palette.Select(p => p.G).ToArray(); byte maxGreen = greens.Max(); int uniqueGreens = greens.Distinct().Count(); IEnumerable<byte> blues = palette.Select(p => p.B).ToArray(); byte maxBlue = blues.Max(); int uniqueBlues = blues.Distinct().Count(); double totalUniques = uniqueAlphas + uniqueReds + uniqueGreens + uniqueBlues; double availableBits = 1.0 + Math.Log(uniqueAlphas * uniqueReds * uniqueGreens * uniqueBlues); byte alphaMask = ComputeBitMask(maxAlpha, Convert.ToInt32(Math.Round(uniqueAlphas / totalUniques * availableBits))); byte redMask = ComputeBitMask(maxRed, Convert.ToInt32(Math.Round(uniqueReds / totalUniques * availableBits))); byte greenMask = ComputeBitMask(maxGreen, Convert.ToInt32(Math.Round(uniqueGreens / totalUniques * availableBits))); byte blueMask = ComputeBitMask(maxBlue, Convert.ToInt32(Math.Round(uniqueBlues / totalUniques * availableBits))); Color32 maskedPixel = new Color32(alphaMask, redMask, greenMask, blueMask); return maskedPixel.Argb; }