/// <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(ColorBgra* pixel) { byte colorIndex = 0; uint colorHash = pixel->Bgra; // Check if the color is in the lookup table if (this._colorMap.ContainsKey(colorHash)) { colorIndex = this._colorMap[colorHash]; } else { // Not found - loop through the palette and find the nearest match. // Firstly check the alpha value - if 0, lookup the transparent color if (0 == pixel->A) { // Transparent. Lookup the first color with an alpha value of 0 for (int index = 0; index < this._colors.Length; index++) { if (0 == this._colors[index].A) { colorIndex = (byte) index; break; } } } else { // Not transparent... int leastDistance = int.MaxValue; int red = pixel->R; int green = pixel->G; int blue = pixel->B; // Loop through the entire palette, looking for the closest color match for (int index = 0; index < this._colors.Length; index++) { Color paletteColor = this._colors[index]; int redDistance = paletteColor.R - red; int greenDistance = paletteColor.G - green; int blueDistance = paletteColor.B - blue; int distance = (redDistance * redDistance) + (greenDistance * greenDistance) + (blueDistance * blueDistance); if (distance < leastDistance) { colorIndex = (byte) index; leastDistance = distance; // And if it's an exact match, exit the loop if (0 == distance) { break; } } } } // Now I have the color, pop it into the hashtable for next time this._colorMap.Add(colorHash, colorIndex); } return colorIndex; }
/// <summary> /// Constructs a new ColorBgra instance with the given 32-bit value. /// </summary> public static ColorBgra FromUInt32(UInt32 bgra) { ColorBgra color = new ColorBgra(); color.Bgra = bgra; return color; }
/// <summary> /// Creates a new ColorBgra instance with the given color and alpha values. /// </summary> public static ColorBgra FromRgba(byte r, byte g, byte b, byte a) { ColorBgra color = new ColorBgra(); color.R = r; color.G = g; color.B = b; color.A = a; return color; }
/// <summary> /// Creates a new ColorBgra instance with the given color and alpha values. /// </summary> public static ColorBgra FromBgra(byte b, byte g, byte r, byte a) { ColorBgra color = new ColorBgra(); color.Bgra = BgraToUInt32(b, g, r, a); return color; }
public static ColorBgra Clamped(double b, double g, double r, double a) { ColorBgra color = new ColorBgra(); if (r > 255) { color.R = 255; } else if (r < 0) { color.R = 0; } else { color.R = (byte) r; } if (g > 255) { color.G = 255; } else if (g < 0) { color.G = 0; } else { color.G = (byte) g; } if (b > 255) { color.B = 255; } else if (b < 0) { color.B = 0; } else { color.B = (byte) b; } if (a > 255) { color.A = 255; } else if (a < 0) { color.A = 0; } else { color.A = (byte) a; } return color; }
/// <summary> /// Increment the pixel count and add to the color information /// </summary> public void Increment(ColorBgra* 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(ColorBgra* 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> /// Return the palette index for the passed color /// </summary> public int GetPaletteIndex(ColorBgra* pixel, int level) { int paletteIndex = this._paletteIndex; if (!this._leaf) { int shift = 7 - level; int index = ((pixel->R & mask[level]) >> (shift - 2)) | ((pixel->G & mask[level]) >> (shift - 1)) | ((pixel->B & mask[level]) >> (shift)); if (null != this._children[index]) { paletteIndex = this._children[index].GetPaletteIndex(pixel, level + 1); } else { paletteIndex = -1; } } return paletteIndex; }
/// <summary> /// Add a given color value to the octree /// </summary> /// <param name="pixel"></param> public void AddColor(ColorBgra* pixel) { // Check if this request is for the same color as the last if (this._previousColor == pixel->Bgra) { // 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 == this._previousNode) { this._previousColor = pixel->Bgra; this._root.AddColor(pixel, this._maxColorBits, 0, this); } else { // Just update the previous node this._previousNode.Increment(pixel); } } else { this._previousColor = pixel->Bgra; this._root.AddColor(pixel, this._maxColorBits, 0, this); } }
/// <summary> /// Get the palette index for the passed color /// </summary> /// <param name="pixel"></param> /// <returns></returns> public int GetPaletteIndex(ColorBgra* pixel) { int ret = -1; ret = this._root.GetPaletteIndex(pixel, 0); if (ret < 0) { if (this.paletteTable == null) { this.paletteTable = new PaletteTable(this._palette); } ret = this.paletteTable.FindClosestPaletteIndex(pixel->ToColor()); } return ret; }
/// <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(ColorBgra* pixel) { byte paletteIndex = (byte) this._maxColors; // The color at [_maxColors] is set to transparent // Get the palette index if this non-transparent if (pixel->A > 0) { paletteIndex = (byte) this._octree.GetPaletteIndex(pixel); } return paletteIndex; }
/// <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(ColorBgra* pixel) { this._octree.AddColor(pixel); }
/// <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 = this.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 = ColorBgra.Clamped(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; target = ColorBgra.Clamped(srcPixel.B - ((errorThisRowB[col] * weight) / 8), srcPixel.G - ((errorThisRowG[col] * weight) / 8), srcPixel.R - ((errorThisRowR[col] * weight) / 8), srcPixel.A); byte pixelValue = this.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 { 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 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> /// 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) { }