/// <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 = Quantizer.ClampToByte(srcPixel.B - ((errorThisRowB[col] * weight) / 8)); target.G = Quantizer.ClampToByte(srcPixel.G - ((errorThisRowG[col] * weight) / 8)); target.R = Quantizer.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 { 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> /// 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> /// Linearly interpolates between two color values. /// </summary> /// <param name="from">The color value that represents 0 on the lerp number line.</param> /// <param name="to">The color value that represents 1 on the lerp number line.</param> /// <param name="frac">A value in the range [0, 1].</param> public static ColorBgra Lerp(ColorBgra from, ColorBgra to, double frac) { ColorBgra ret = new ColorBgra(); ret.B = (byte)Quantizer.ClampToByte(Quantizer.Lerp(from.B, to.B, frac)); ret.G = (byte)Quantizer.ClampToByte(Quantizer.Lerp(from.G, to.G, frac)); ret.R = (byte)Quantizer.ClampToByte(Quantizer.Lerp(from.R, to.R, frac)); ret.A = (byte)Quantizer.ClampToByte(Quantizer.Lerp(from.A, to.A, frac)); return ret; }
/// <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; }
/// <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> /// Return the palette index for the passed color /// </summary> public int GetPaletteIndex(ColorBgra* pixel, int level) { int paletteIndex = _paletteIndex; if (!_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 != _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(ColorBgra* pixel) { ++_pixelCount; _red += pixel->R; _green += pixel->G; _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 (_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 index = ((pixel->R & mask[level]) >> (shift - 2)) | ((pixel->G & mask[level]) >> (shift - 1)) | ((pixel->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 the palette index for the passed color /// </summary> /// <param name="pixel"></param> /// <returns></returns> public int GetPaletteIndex(ColorBgra* 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(pixel->ToColor()); } return ret; }
/// <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 (_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 == _previousNode) { _previousColor = pixel->Bgra; _root.AddColor(pixel, _maxColorBits, 0, this); } else { // Just update the previous node _previousNode.Increment(pixel); } } else { _previousColor = pixel->Bgra; _root.AddColor(pixel, _maxColorBits, 0, this); } }
/// <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)_maxColors; // The color at [_maxColors] is set to transparent // Get the palette index if this non-transparent if (pixel->A > 0) { paletteIndex = (byte)_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) { _octree.AddColor(pixel); }