public void BlackAndWhiteReductionToOneColor() { Octree octree = new Octree(); octree.AddColor(Color.FromArgb(0, 0, 0)); octree.AddColor(Color.FromArgb(255, 255, 255)); octree.ReduceColorsCount(1); Color reductedColor = octree.GetColor(Color.FromArgb(0, 0, 0)); Assert.AreEqual(reductedColor, Color.FromArgb(127, 127, 127)); }
public void AddNewAndGetNewColor() { Octree octree = new Octree(); octree.AddColor(Color.FromArgb(0, 0, 0)); Assert.AreEqual(octree.GetColor(Color.FromArgb(0, 0, 0)), Color.FromArgb(0, 0, 0)); }
private static unsafe System.Drawing.Image ReduceColors(Bitmap bitmap, int maxColors, int numBits, Color transparentColor) { byte *numPtr; if ((numBits < 3) || (numBits > 8)) { throw new ArgumentOutOfRangeException("numBits"); } if (maxColors < 0x10) { throw new ArgumentOutOfRangeException("maxColors"); } int width = bitmap.Width; int height = bitmap.Height; Octree octree = new Octree(maxColors, numBits, transparentColor); for (int i = 0; i < width; i++) { for (int k = 0; k < height; k++) { octree.AddColor(bitmap.GetPixel(i, k)); } } ColorIndexTable colorIndexTable = octree.GetColorIndexTable(); Bitmap bitmap2 = new Bitmap(width, height, PixelFormat.Format8bppIndexed); ColorPalette palette = bitmap2.Palette; Rectangle rect = new Rectangle(0, 0, width, height); BitmapData bitmapdata = bitmap2.LockBits(rect, ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed); IntPtr ptr = bitmapdata.Scan0; if (bitmapdata.Stride > 0) { numPtr = (byte *)ptr.ToPointer(); } else { numPtr = (byte *)(ptr.ToPointer() + (bitmapdata.Stride * (height - 1))); } int num5 = Math.Abs(bitmapdata.Stride); for (int j = 0; j < height; j++) { for (int m = 0; m < width; m++) { byte *numPtr2 = (numPtr + (j * num5)) + m; Color pixel = bitmap.GetPixel(m, j); byte num8 = (byte)colorIndexTable[pixel]; numPtr2[0] = num8; } } colorIndexTable.CopyToColorPalette(palette); bitmap2.Palette = palette; bitmap2.UnlockBits(bitmapdata); return(bitmap2); }
private void NewOctree(Bitmap bitmap, int colorsLimit) { octree = new Octree(); for (int i = 0; i < bitmap.Width; i++) { for (int j = 0; j < bitmap.Height; j++) { octree.AddColor(bitmap.GetPixel(i, j)); if (ReduceColorsAlongCreatingOctree) { octree.ReduceColorsCount(colorsLimit); } } } }
/// <summary> /// Does the quantize. /// </summary> /// <param name="bitmapSource">The bitmap source.</param> /// <param name="pixelFormat">The pixel format.</param> /// <param name="useDither">if set to <c>true</c> [use dither].</param> /// <returns>The quantized image with the recalculated color palette.</returns> private static Bitmap DoQuantize(Bitmap bitmapSource, PixelFormat pixelFormat, bool useDither) { // We use these values a lot int width = bitmapSource.Width; int height = bitmapSource.Height; Rectangle sourceRect = Rectangle.FromLTRB(0, 0, width, height); Bitmap bitmapOptimized = null; try { // Create a bitmap with the same dimensions and the desired format bitmapOptimized = new Bitmap(width, height, pixelFormat); // Lock the bits of the source image for reading. // we will need to write if we do the dither. BitmapData bitmapDataSource = bitmapSource.LockBits( sourceRect, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb); try { // Perform the first pass, which generates the octree data // Create an Octree Octree octree = new Octree(pixelFormat); // Stride might be negative, indicating inverted row order. // Allocate a managed buffer for the pixel data, and copy it from the unmanaged pointer. int strideSource = Math.Abs(bitmapDataSource.Stride); byte[] sourceDataBuffer = new byte[strideSource * height]; Marshal.Copy(bitmapDataSource.Scan0, sourceDataBuffer, 0, sourceDataBuffer.Length); // We could skip every other row and/or every other column when sampling the colors // of the source image, rather than hitting every other pixel. It doesn't seem to // degrade the resulting image too much. But it doesn't really help the performance // too much because the majority of the time seems to be spent in other places. // For every row int rowStartSource = 0; for (int ndxRow = 0; ndxRow < height; ndxRow += 1) { // For each column for (int ndxCol = 0; ndxCol < width; ndxCol += 1) { // Add the color (4 bytes per pixel - ARGB) Pixel pixel = GetSourcePixel(sourceDataBuffer, rowStartSource, ndxCol); octree.AddColor(pixel); } rowStartSource += strideSource; } // Get the optimized colors Color[] colors = octree.GetPaletteColors(); // Set the palette from the octree ColorPalette palette = bitmapOptimized.Palette; for (var ndx = 0; ndx < palette.Entries.Length; ++ndx) { // Use the colors we calculated // for the rest, just set to transparent palette.Entries[ndx] = (ndx < colors.Length) ? colors[ndx] : Color.Transparent; } bitmapOptimized.Palette = palette; // Lock the bits of the optimized bitmap for writing. // we will also need to read if we are doing 1bpp or 4bpp BitmapData bitmapDataOutput = bitmapOptimized.LockBits(sourceRect, ImageLockMode.ReadWrite, pixelFormat); try { // Create a managed array for the destination bytes given the desired color depth // and marshal the unmanaged data to the managed array int strideOutput = Math.Abs(bitmapDataOutput.Stride); byte[] bitmapOutputBuffer = new byte[strideOutput * height]; // For each source pixel, compute the appropriate color index rowStartSource = 0; int rowStartOutput = 0; for (int ndxRow = 0; ndxRow < height; ++ndxRow) { // For each column for (int ndxCol = 0; ndxCol < width; ++ndxCol) { // Get the source color Pixel pixel = GetSourcePixel(sourceDataBuffer, rowStartSource, ndxCol); // Get the closest palette index int paletteIndex = octree.GetPaletteIndex(pixel); // If we want to dither and this isn't the transparent pixel if (useDither && pixel.Alpha != 0) { // Calculate the error Color paletteColor = colors[paletteIndex]; int deltaRed = pixel.Red - paletteColor.R; int deltaGreen = pixel.Green - paletteColor.G; int deltaBlue = pixel.Blue - paletteColor.B; // Propagate the dither error. // we'll use a standard Floyd-Steinberg matrix (1/16): // | 0 0 0 | // | 0 x 7 | // | 3 5 1 | // Make sure we're not on the right-hand edge if (ndxCol + 1 < width) { DitherSourcePixel(sourceDataBuffer, rowStartSource, ndxCol + 1, deltaRed, deltaGreen, deltaBlue, 7); } // Make sure we're not already on the bottom row if (ndxRow + 1 < height) { int nextRow = rowStartSource + strideSource; // Make sure we're not on the left-hand column if (ndxCol > 0) { // Down one row, but back one pixel DitherSourcePixel(sourceDataBuffer, nextRow, ndxCol - 1, deltaRed, deltaGreen, deltaBlue, 3); } // pixel directly below us DitherSourcePixel(sourceDataBuffer, nextRow, ndxCol, deltaRed, deltaGreen, deltaBlue, 5); // Make sure we're not on the right-hand column if (ndxCol + 1 < width) { // Down one row, but right one pixel DitherSourcePixel(sourceDataBuffer, nextRow, ndxCol + 1, deltaRed, deltaGreen, deltaBlue, 1); } } } // Set the bitmap index based on the format switch (pixelFormat) { case PixelFormat.Format8bppIndexed: // Each byte is a palette index bitmapOutputBuffer[rowStartOutput + ndxCol] = (byte)paletteIndex; break; case PixelFormat.Format4bppIndexed: // Each byte contains two pixels bitmapOutputBuffer[rowStartOutput + (ndxCol >> 1)] |= ((ndxCol & 1) == 1) ? (byte)(paletteIndex & 0x0f) // lower nibble : (byte)(paletteIndex << 4); // upper nibble break; case PixelFormat.Format1bppIndexed: // Each byte contains eight pixels if (paletteIndex != 0) { bitmapOutputBuffer[rowStartOutput + (ndxCol >> 3)] |= (byte)(0x80 >> (ndxCol & 0x07)); } break; } } rowStartSource += strideSource; rowStartOutput += strideOutput; } // Now copy the calculated pixel bytes from the managed array to the unmanaged bitmap Marshal.Copy(bitmapOutputBuffer, 0, bitmapDataOutput.Scan0, bitmapOutputBuffer.Length); } finally { bitmapOptimized.UnlockBits(bitmapDataOutput); bitmapDataOutput = null; } } finally { bitmapSource.UnlockBits(bitmapDataSource); bitmapDataSource = null; } } catch (Exception) { // If any exception is thrown, dispose of the bitmap object // we've been working on before we rethrow and bail if (bitmapOptimized != null) { bitmapOptimized.Dispose(); } throw; } // Caller is responsible for disposing of this bitmap! return bitmapOptimized; }
/// <summary> /// Does the quantize. /// </summary> /// <param name="bitmapSource">The bitmap source.</param> /// <param name="pixelFormat">The pixel format.</param> /// <param name="useDither">if set to <c>true</c> [use dither].</param> /// <returns>The quantized image with the recalculated color palette.</returns> private static Bitmap DoQuantize(Bitmap bitmapSource, PixelFormat pixelFormat, bool useDither) { // We use these values a lot int width = bitmapSource.Width; int height = bitmapSource.Height; Rectangle sourceRect = Rectangle.FromLTRB(0, 0, width, height); Bitmap bitmapOptimized = null; try { // Create a bitmap with the same dimensions and the desired format bitmapOptimized = new Bitmap(width, height, pixelFormat); // Lock the bits of the source image for reading. // we will need to write if we do the dither. BitmapData bitmapDataSource = bitmapSource.LockBits( sourceRect, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb); try { // Perform the first pass, which generates the octree data // Create an Octree Octree octree = new Octree(pixelFormat); // Stride might be negative, indicating inverted row order. // Allocate a managed buffer for the pixel data, and copy it from the unmanaged pointer. int strideSource = Math.Abs(bitmapDataSource.Stride); byte[] sourceDataBuffer = new byte[strideSource * height]; Marshal.Copy(bitmapDataSource.Scan0, sourceDataBuffer, 0, sourceDataBuffer.Length); // We could skip every other row and/or every other column when sampling the colors // of the source image, rather than hitting every other pixel. It doesn't seem to // degrade the resulting image too much. But it doesn't really help the performance // too much because the majority of the time seems to be spent in other places. // For every row int rowStartSource = 0; for (int ndxRow = 0; ndxRow < height; ndxRow += 1) { // For each column for (int ndxCol = 0; ndxCol < width; ndxCol += 1) { // Add the color (4 bytes per pixel - ARGB) Pixel pixel = GetSourcePixel(sourceDataBuffer, rowStartSource, ndxCol); octree.AddColor(pixel); } rowStartSource += strideSource; } // Get the optimized colors Color[] colors = octree.GetPaletteColors(); // Set the palette from the octree ColorPalette palette = bitmapOptimized.Palette; for (var ndx = 0; ndx < palette.Entries.Length; ++ndx) { // Use the colors we calculated // for the rest, just set to transparent palette.Entries[ndx] = (ndx < colors.Length) ? colors[ndx] : Color.Transparent; } bitmapOptimized.Palette = palette; // Lock the bits of the optimized bitmap for writing. // we will also need to read if we are doing 1bpp or 4bpp BitmapData bitmapDataOutput = bitmapOptimized.LockBits(sourceRect, ImageLockMode.ReadWrite, pixelFormat); try { // Create a managed array for the destination bytes given the desired color depth // and marshal the unmanaged data to the managed array int strideOutput = Math.Abs(bitmapDataOutput.Stride); byte[] bitmapOutputBuffer = new byte[strideOutput * height]; // For each source pixel, compute the appropriate color index rowStartSource = 0; int rowStartOutput = 0; for (int ndxRow = 0; ndxRow < height; ++ndxRow) { // For each column for (int ndxCol = 0; ndxCol < width; ++ndxCol) { // Get the source color Pixel pixel = GetSourcePixel(sourceDataBuffer, rowStartSource, ndxCol); // Get the closest palette index int paletteIndex = octree.GetPaletteIndex(pixel); // If we want to dither and this isn't the transparent pixel if (useDither && pixel.Alpha != 0) { // Calculate the error Color paletteColor = colors[paletteIndex]; int deltaRed = pixel.Red - paletteColor.R; int deltaGreen = pixel.Green - paletteColor.G; int deltaBlue = pixel.Blue - paletteColor.B; // Propagate the dither error. // we'll use a standard Floyd-Steinberg matrix (1/16): // | 0 0 0 | // | 0 x 7 | // | 3 5 1 | // Make sure we're not on the right-hand edge if (ndxCol + 1 < width) { DitherSourcePixel(sourceDataBuffer, rowStartSource, ndxCol + 1, deltaRed, deltaGreen, deltaBlue, 7); } // Make sure we're not already on the bottom row if (ndxRow + 1 < height) { int nextRow = rowStartSource + strideSource; // Make sure we're not on the left-hand column if (ndxCol > 0) { // Down one row, but back one pixel DitherSourcePixel(sourceDataBuffer, nextRow, ndxCol - 1, deltaRed, deltaGreen, deltaBlue, 3); } // pixel directly below us DitherSourcePixel(sourceDataBuffer, nextRow, ndxCol, deltaRed, deltaGreen, deltaBlue, 5); // Make sure we're not on the right-hand column if (ndxCol + 1 < width) { // Down one row, but right one pixel DitherSourcePixel(sourceDataBuffer, nextRow, ndxCol + 1, deltaRed, deltaGreen, deltaBlue, 1); } } } // Set the bitmap index based on the format switch (pixelFormat) { case PixelFormat.Format8bppIndexed: // Each byte is a palette index bitmapOutputBuffer[rowStartOutput + ndxCol] = (byte)paletteIndex; break; case PixelFormat.Format4bppIndexed: // Each byte contains two pixels bitmapOutputBuffer[rowStartOutput + (ndxCol >> 1)] |= ((ndxCol & 1) == 1) ? (byte)(paletteIndex & 0x0f) // lower nibble : (byte)(paletteIndex << 4); // upper nibble break; case PixelFormat.Format1bppIndexed: // Each byte contains eight pixels if (paletteIndex != 0) { bitmapOutputBuffer[rowStartOutput + (ndxCol >> 3)] |= (byte)(0x80 >> (ndxCol & 0x07)); } break; } } rowStartSource += strideSource; rowStartOutput += strideOutput; } // Now copy the calculated pixel bytes from the managed array to the unmanaged bitmap Marshal.Copy(bitmapOutputBuffer, 0, bitmapDataOutput.Scan0, bitmapOutputBuffer.Length); } finally { bitmapOptimized.UnlockBits(bitmapDataOutput); bitmapDataOutput = null; } } finally { bitmapSource.UnlockBits(bitmapDataSource); bitmapDataSource = null; } } catch (Exception) { // If any exception is thrown, dispose of the bitmap object // we've been working on before we rethrow and bail if (bitmapOptimized != null) { bitmapOptimized.Dispose(); } throw; } // Caller is responsible for disposing of this bitmap! return(bitmapOptimized); }
public void GetColorCounts() { Octree octree = new Octree(); octree.AddColor(Color.FromArgb(0, 0, 0)); octree.AddColor(Color.FromArgb(0, 0, 0)); octree.AddColor(Color.FromArgb(0, 0, 1)); octree.AddColor(Color.FromArgb(0, 1, 0)); octree.AddColor(Color.FromArgb(1, 0, 0)); octree.AddColor(Color.FromArgb(0, 0, 0)); octree.AddColor(Color.FromArgb(0, 0, 2)); octree.AddColor(Color.FromArgb(255, 255, 255)); octree.AddColor(Color.FromArgb(255, 255, 255)); octree.AddColor(Color.FromArgb(54, 23, 54)); octree.AddColor(Color.FromArgb(21, 213, 144)); octree.AddColor(Color.FromArgb(124, 43, 255)); Assert.AreEqual(octree.ColorsCount, 9); }
protected override void InitialQuantizePixel(Color32 pixel) { // Add the color to the octree _octree.AddColor(pixel); }
private static unsafe System.Drawing.Image ReduceColors(Bitmap bitmap, int maxColors, int numBits, Color transparentColor) { byte* numPtr; if ((numBits < 3) || (numBits > 8)) { throw new ArgumentOutOfRangeException("numBits"); } if (maxColors < 0x10) { throw new ArgumentOutOfRangeException("maxColors"); } int width = bitmap.Width; int height = bitmap.Height; Octree octree = new Octree(maxColors, numBits, transparentColor); for (int i = 0; i < width; i++) { for (int k = 0; k < height; k++) { octree.AddColor(bitmap.GetPixel(i, k)); } } ColorIndexTable colorIndexTable = octree.GetColorIndexTable(); Bitmap bitmap2 = new Bitmap(width, height, PixelFormat.Format8bppIndexed); ColorPalette palette = bitmap2.Palette; Rectangle rect = new Rectangle(0, 0, width, height); BitmapData bitmapdata = bitmap2.LockBits(rect, ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed); IntPtr ptr = bitmapdata.Scan0; if (bitmapdata.Stride > 0) { numPtr = (byte*) ptr.ToPointer(); } else { numPtr = (byte*) (ptr.ToPointer() + (bitmapdata.Stride * (height - 1))); } int num5 = Math.Abs(bitmapdata.Stride); for (int j = 0; j < height; j++) { for (int m = 0; m < width; m++) { byte* numPtr2 = (numPtr + (j * num5)) + m; Color pixel = bitmap.GetPixel(m, j); byte num8 = (byte) colorIndexTable[pixel]; numPtr2[0] = num8; } } colorIndexTable.CopyToColorPalette(palette); bitmap2.Palette = palette; bitmap2.UnlockBits(bitmapdata); return bitmap2; }