private static IEnumerable<byte[]> indexedPixels(ImageBuffer image, Pixel[] lookups, int alphaThreshold, int maxColors, PaletteColorHistory[] paletteHistogram) { int pixelsCount = image.Image.Width * image.Image.Height; var lineIndexes = new byte[(int)System.Math.Ceiling(image.Image.Width / System.Math.Log(256.0, maxColors))]; PaletteLookup lookup = new PaletteLookup(lookups); --maxColors; foreach (var pixelLine in image.PixelLines) { for (int pixelIndex = 0; pixelIndex < pixelLine.Length; pixelIndex++) { Pixel pixel = pixelLine[pixelIndex]; byte bestMatch = (byte)maxColors; if (pixel.Alpha > alphaThreshold) { bestMatch = lookup.GetPaletteIndex(pixel); paletteHistogram[bestMatch].AddPixel(pixel); } switch (maxColors) { case 256 - 1: lineIndexes[pixelIndex] = bestMatch; break; case 16 - 1: if (pixelIndex % 2 == 0) { lineIndexes[pixelIndex / 2] = (byte)(bestMatch << 4); } else { lineIndexes[pixelIndex / 2] |= (byte)(bestMatch & 0x0F); } break; case 2 - 1: if (pixelIndex % 8 == 0) { lineIndexes[pixelIndex / 8] = (byte)(bestMatch << 7); } else { lineIndexes[pixelIndex / 8] |= (byte)((bestMatch & 0x01) << (7 - (pixelIndex % 8))); } break; } } yield return lineIndexes; } }
public Image QuantizeImage(Bitmap image, int alphaThreshold = 10, int alphaFader = 70, int maxColors = 256) { var colorCount = maxColors; ImageBuffer buffer = new ImageBuffer(image); var moments = BuildHistogram(buffer, alphaThreshold, alphaFader); CalculateMoments(moments); var cubes = SplitData(ref colorCount, maxColors, moments); var lookups = BuildLookups(cubes, moments); return GetQuantizedImage(buffer, colorCount, maxColors, lookups, alphaThreshold); }
internal override Image GetQuantizedImage(ImageBuffer image, int colorCount, int maxColors, Pixel[] lookups, int alphaThreshold) { PixelFormat pf; switch (maxColors) { case 256: pf = PixelFormat.Format8bppIndexed; break; case 16: pf = PixelFormat.Format4bppIndexed; break; case 2: pf = PixelFormat.Format1bppIndexed; break; default: throw new QuantizationException(string.Format("The target amount of colors is not supported. Requested {0} colors.", maxColors)); } var result = new Bitmap(image.Image.Width, image.Image.Height, pf); var resultBuffer = new ImageBuffer(result); var paletteHistogram = new PaletteColorHistory[colorCount + 1]; resultBuffer.UpdatePixelIndexes(indexedPixels(image, lookups, alphaThreshold, maxColors, paletteHistogram)); result.Palette = BuildPalette(result.Palette, paletteHistogram); return result; }
internal abstract Image GetQuantizedImage(ImageBuffer image, int colorCount, int maxColors, Pixel[] lookups, int alphaThreshold);
private static ColorMoment[, , ,] BuildHistogram(ImageBuffer sourceImage, int alphaThreshold, int alphaFader) { var moments = new ColorMoment[SideSize, SideSize, SideSize, SideSize]; foreach(var pixelLine in sourceImage.PixelLines) { for (int pixelIndex = 0; pixelIndex < pixelLine.Length; pixelIndex++) { Pixel pixel = pixelLine[pixelIndex]; byte pixelAlpha = pixel.Alpha; if (pixelAlpha > alphaThreshold) { if (pixelAlpha < 255) { var alpha = pixel.Alpha + (pixel.Alpha % alphaFader); pixelAlpha = (byte)(alpha > 255 ? 255 : alpha); } byte pixelRed = pixel.Red; byte pixelGreen = pixel.Green; byte pixelBlue = pixel.Blue; pixelAlpha = (byte)((pixelAlpha >> SidePixShift) + 1); pixelRed = (byte)((pixelRed >> SidePixShift) + 1); pixelGreen = (byte)((pixelGreen >> SidePixShift) + 1); pixelBlue = (byte)((pixelBlue >> SidePixShift) + 1); moments[pixelAlpha, pixelRed, pixelGreen, pixelBlue].Add(pixel); } } } return moments; }