diff --git a/src/ImageProcessor.Playground/Program.cs b/src/ImageProcessor.Playground/Program.cs index abafa234a..a8362a2bd 100644 --- a/src/ImageProcessor.Playground/Program.cs +++ b/src/ImageProcessor.Playground/Program.cs @@ -49,7 +49,7 @@ namespace ImageProcessor.PlayGround di.Create(); } - Image mask = Image.FromFile(Path.Combine(resolvedPath, "mask2.png")); + // Image mask = Image.FromFile(Path.Combine(resolvedPath, "mask2.png")); IEnumerable files = GetFilesByExtensions(di, ".png"); //IEnumerable files = GetFilesByExtensions(di, ".gif", ".webp", ".bmp", ".jpg", ".png", ".tif"); diff --git a/src/ImageProcessor.Playground/images/input/000.png b/src/ImageProcessor.Playground/images/input/000.png new file mode 100644 index 000000000..892574db1 Binary files /dev/null and b/src/ImageProcessor.Playground/images/input/000.png differ diff --git a/src/ImageProcessor.Playground/images/input/effect_24bit.png b/src/ImageProcessor.Playground/images/input/effect_24bit.png new file mode 100644 index 000000000..bd594381d Binary files /dev/null and b/src/ImageProcessor.Playground/images/input/effect_24bit.png differ diff --git a/src/ImageProcessor.Playground/images/input/h9ghTMB.png.REMOVED.git-id b/src/ImageProcessor.Playground/images/input/h9ghTMB.png.REMOVED.git-id new file mode 100644 index 000000000..c25e3802f --- /dev/null +++ b/src/ImageProcessor.Playground/images/input/h9ghTMB.png.REMOVED.git-id @@ -0,0 +1 @@ +a442c2c7ce00c44e7de9f8221ad2c396be3431c5 \ No newline at end of file diff --git a/src/ImageProcessor.Playground/images/input/monster-whitebg.png2.REMOVED.git-id b/src/ImageProcessor.Playground/images/input/monster-whitebg.png2.REMOVED.git-id deleted file mode 100644 index 22d1aa140..000000000 --- a/src/ImageProcessor.Playground/images/input/monster-whitebg.png2.REMOVED.git-id +++ /dev/null @@ -1 +0,0 @@ -71ccd68b75f913237cdd2047d5f5d0b39d9b16ae \ No newline at end of file diff --git a/src/ImageProcessor.Playground/images/input/pixel.png3 b/src/ImageProcessor.Playground/images/input/pixel.png3 deleted file mode 100644 index 01ba8148e..000000000 Binary files a/src/ImageProcessor.Playground/images/input/pixel.png3 and /dev/null differ diff --git a/src/ImageProcessor.Playground/images/input/tower - Copy.jpg2.REMOVED.git-id b/src/ImageProcessor.Playground/images/input/tower - Copy.jpg2.REMOVED.git-id deleted file mode 100644 index d45f646a2..000000000 --- a/src/ImageProcessor.Playground/images/input/tower - Copy.jpg2.REMOVED.git-id +++ /dev/null @@ -1 +0,0 @@ -98c3f8fce4fdd9eebafe12431c8e014fd39d243f \ No newline at end of file diff --git a/src/ImageProcessor/ImageProcessor.csproj b/src/ImageProcessor/ImageProcessor.csproj index a728fc9be..615d828f7 100644 --- a/src/ImageProcessor/ImageProcessor.csproj +++ b/src/ImageProcessor/ImageProcessor.csproj @@ -185,16 +185,18 @@ + + + Code - diff --git a/src/ImageProcessor/Imaging/Formats/PngFormat.cs b/src/ImageProcessor/Imaging/Formats/PngFormat.cs index 81ef4cbdb..528740a4b 100644 --- a/src/ImageProcessor/Imaging/Formats/PngFormat.cs +++ b/src/ImageProcessor/Imaging/Formats/PngFormat.cs @@ -104,6 +104,7 @@ namespace ImageProcessor.Imaging.Formats // The Wu Quantizer expects a 32bbp image. //if (Image.GetPixelFormatSize(image.PixelFormat) != 32) //{ + Bitmap clone = new Bitmap(image.Width, image.Height, PixelFormat.Format32bppPArgb); clone.SetResolution(image.HorizontalResolution, image.VerticalResolution); @@ -116,6 +117,8 @@ namespace ImageProcessor.Imaging.Formats image.Dispose(); image = new WuQuantizer().QuantizeImage(clone); + + // image = new OctreeQuantizer(255, 8).Quantize(image); //} //else //{ diff --git a/src/ImageProcessor/Imaging/Quantizers/Quantizer.cs b/src/ImageProcessor/Imaging/Quantizers/Quantizer.cs index 04252e1fb..55bddb0a2 100644 --- a/src/ImageProcessor/Imaging/Quantizers/Quantizer.cs +++ b/src/ImageProcessor/Imaging/Quantizers/Quantizer.cs @@ -59,9 +59,11 @@ namespace ImageProcessor.Imaging.Quantizers // First off take a 32bpp copy of the image Bitmap copy = new Bitmap(width, height, PixelFormat.Format32bppArgb); + copy.SetResolution(source.HorizontalResolution, source.VerticalResolution); // And construct an 8bpp version Bitmap output = new Bitmap(width, height, PixelFormat.Format8bppIndexed); + output.SetResolution(source.HorizontalResolution, source.VerticalResolution); // Now lock the bitmap into memory using (Graphics g = Graphics.FromImage(copy)) diff --git a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ColorData.cs b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ColorData.cs index 13bf0230e..435c53669 100644 --- a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ColorData.cs +++ b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ColorData.cs @@ -12,81 +12,22 @@ namespace nQuant /// public class ColorData { - /// - /// The pixels. - /// - private readonly Pixel[] pixels; - - /// - /// The pixels count. - /// - private readonly int pixelsCount; - - /// - /// The pixel filling counter. - /// - private int pixelFillingCounter; - /// /// Initializes a new instance of the class. /// /// /// The data granularity. /// - /// - /// The bitmap width. - /// - /// - /// The bitmap height. - /// - public ColorData(int dataGranularity, int bitmapWidth, int bitmapHeight) + public ColorData(int dataGranularity) { dataGranularity++; this.Moments = new ColorMoment[dataGranularity, dataGranularity, dataGranularity, dataGranularity]; - this.pixelsCount = bitmapWidth * bitmapHeight; - this.pixels = new Pixel[this.pixelsCount]; } /// /// Gets the moments. /// public ColorMoment[, , ,] Moments { get; private set; } - - /// - /// Gets the pixels. - /// - public Pixel[] Pixels - { - get - { - return this.pixels; - } - } - - /// - /// Gets the pixels count. - /// - public int PixelsCount - { - get - { - return this.pixelsCount; - } - } - - /// - /// The add pixel. - /// - /// - /// The pixel. - /// - /// - /// The quantized pixel. - /// - public void AddPixel(Pixel pixel, Pixel quantizedPixel) - { - this.pixels[this.pixelFillingCounter++] = pixel; - } } } \ No newline at end of file diff --git a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ColorMoment.cs b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ColorMoment.cs index 7087a48a3..1e751a73a 100644 --- a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ColorMoment.cs +++ b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ColorMoment.cs @@ -43,30 +43,39 @@ namespace nQuant return c1; } - public static ColorMoment operator +(ColorMoment m, Pixel p) + public void Add(Pixel p) { - m.Alpha += p.Alpha; - m.Red += p.Red; - m.Green += p.Green; - m.Blue += p.Blue; - m.Weight++; - m.Moment += p.Distance(); - return m; + Alpha += p.Alpha; + Red += p.Red; + Green += p.Green; + Blue += p.Blue; + Weight++; + Moment += p.Amplitude(); } - public long Distance() + public void AddFast(ref ColorMoment c2) + { + Alpha += c2.Alpha; + Red += c2.Red; + Green += c2.Green; + Blue += c2.Blue; + Weight += c2.Weight; + Moment += c2.Moment; + } + + public long Amplitude() { return (Alpha * Alpha) + (Red * Red) + (Green * Green) + (Blue * Blue); } public long WeightedDistance() { - return Distance() / Weight; + return this.Amplitude() / Weight; } public float Variance() { - var result = Moment - ((float)Distance() / Weight); + var result = Moment - ((float)this.Amplitude() / this.Weight); return float.IsNaN(result) ? 0.0f : result; } } diff --git a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ImageBuffer.cs b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ImageBuffer.cs new file mode 100644 index 000000000..70a48ab71 --- /dev/null +++ b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ImageBuffer.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; + +namespace nQuant +{ + class ImageBuffer + { + public ImageBuffer(Bitmap image) + { + this.Image = image; + } + + public Bitmap Image { get; set; } + + protected const int Alpha = 3; + protected const int Red = 2; + protected const int Green = 1; + protected const int Blue = 0; + + public IEnumerable Pixels + { + get + { + var bitDepth = System.Drawing.Image.GetPixelFormatSize(Image.PixelFormat); + if (bitDepth != 32) + throw new QuantizationException(string.Format("The image you are attempting to quantize does not contain a 32 bit ARGB palette. This image has a bit depth of {0} with {1} colors.", bitDepth, Image.Palette.Entries.Length)); + + int width = this.Image.Width; + int height = this.Image.Height; + int[] buffer = new int[width]; + for (int rowIndex = 0; rowIndex < height; rowIndex++) + { + BitmapData data = this.Image.LockBits(Rectangle.FromLTRB(0, rowIndex, width, rowIndex + 1), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); + try + { + Marshal.Copy(data.Scan0, buffer, 0, width); + foreach (int pixel in buffer) + { + yield return new Pixel(pixel); + } + } + finally + { + this.Image.UnlockBits(data); + } + } + } + } + + public void UpdatePixelIndexes(IEnumerable indexes) + { + int width = this.Image.Width; + int height = this.Image.Height; + byte[] buffer = new byte[width]; + IEnumerator indexesIterator = indexes.GetEnumerator(); + for (int rowIndex = 0; rowIndex < height; rowIndex++) + { + for (int columnIndex = 0; columnIndex < buffer.Length; columnIndex++) + { + indexesIterator.MoveNext(); + buffer[columnIndex] = indexesIterator.Current; + } + + BitmapData data = this.Image.LockBits(Rectangle.FromLTRB(0, rowIndex, width, rowIndex + 1), ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed); + try + { + Marshal.Copy(buffer, 0, data.Scan0, width); + } + finally + { + this.Image.UnlockBits(data); + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/PaletteBuffer.cs b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/PaletteBuffer.cs new file mode 100644 index 000000000..ac7ba623c --- /dev/null +++ b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/PaletteBuffer.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; +using System.Linq; +using System.Text; + +namespace nQuant +{ + class PaletteBuffer + { + public PaletteBuffer(int colorCount) + { + Alphas = new int[colorCount + 1]; + Reds = new int[colorCount + 1]; + Greens = new int[colorCount + 1]; + Blues = new int[colorCount + 1]; + Sums = new int[colorCount + 1]; + } + + public ColorPalette BuildPalette(ColorPalette palette) + { + var alphas = this.Alphas; + var reds = this.Reds; + var greens = this.Greens; + var blues = this.Blues; + var sums = this.Sums; + + for (var paletteIndex = 0; paletteIndex < Sums.Length; paletteIndex++) + { + if (sums[paletteIndex] > 0) + { + alphas[paletteIndex] /= sums[paletteIndex]; + reds[paletteIndex] /= sums[paletteIndex]; + greens[paletteIndex] /= sums[paletteIndex]; + blues[paletteIndex] /= sums[paletteIndex]; + } + + var color = Color.FromArgb(alphas[paletteIndex], reds[paletteIndex], greens[paletteIndex], blues[paletteIndex]); + palette.Entries[paletteIndex] = color; + } + + return palette; + } + + public int[] Alphas { get; set; } + public int[] Reds { get; set; } + public int[] Greens { get; set; } + public int[] Blues { get; set; } + public int[] Sums { get; set; } + } +} \ No newline at end of file diff --git a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/PaletteLookup.cs b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/PaletteLookup.cs new file mode 100644 index 000000000..8babb206c --- /dev/null +++ b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/PaletteLookup.cs @@ -0,0 +1,164 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace nQuant +{ + class PaletteLookup + { + private int mMask; + private Dictionary> mLookup = new Dictionary>(255); + private List Palette { get; set; } + + public PaletteLookup(List palette) + { + Palette = new List(palette.Count); + for (int paletteIndex = 0; paletteIndex < palette.Count; paletteIndex++) + { + Palette.Add(new LookupNode { Pixel = palette[paletteIndex], PaletteIndex = (byte)paletteIndex }); + } + BuildLookup(palette); + } + + public byte GetPaletteIndex(Pixel pixel) + { + + int pixelKey = pixel.Argb & mMask; + List bucket; + if (!mLookup.TryGetValue(pixelKey, out bucket)) + { + bucket = Palette; + } + + if (bucket.Count == 1) + { + return bucket[0].PaletteIndex; + } + + int bestDistance = int.MaxValue; + byte bestMatch = 0; + for (int lookupIndex = 0; lookupIndex < bucket.Count; lookupIndex++) + { + var lookup = bucket[lookupIndex]; + var lookupPixel = lookup.Pixel; + + var deltaAlpha = pixel.Alpha - lookupPixel.Alpha; + int distance = deltaAlpha * deltaAlpha; + + var deltaRed = pixel.Red - lookupPixel.Red; + distance += deltaRed * deltaRed; + + var deltaGreen = pixel.Green - lookupPixel.Green; + distance += deltaGreen * deltaGreen; + + var deltaBlue = pixel.Blue - lookupPixel.Blue; + distance += deltaBlue * deltaBlue; + + if (distance >= bestDistance) + continue; + + bestDistance = distance; + bestMatch = lookup.PaletteIndex; + } + return bestMatch; + } + + private void BuildLookup(List palette) + { + int mask = GetMask(palette); + foreach (LookupNode lookup in Palette) + { + int pixelKey = lookup.Pixel.Argb & mask; + + List bucket; + if (!mLookup.TryGetValue(pixelKey, out bucket)) + { + bucket = new List(); + mLookup[pixelKey] = bucket; + } + bucket.Add(lookup); + } + + mMask = mask; + } + + private static int GetMask(List palette) + { + IEnumerable alphas = from pixel in palette + select pixel.Alpha; + byte maxAlpha = alphas.Max(); + int uniqueAlphas = alphas.Distinct().Count(); + + IEnumerable reds = from pixel in palette + select pixel.Red; + byte maxRed = reds.Max(); + int uniqueReds = reds.Distinct().Count(); + + IEnumerable greens = from pixel in palette + select pixel.Green; + byte maxGreen = greens.Max(); + int uniqueGreens = greens.Distinct().Count(); + + IEnumerable blues = from pixel in palette + select pixel.Blue; + byte maxBlue = blues.Max(); + int uniqueBlues = blues.Distinct().Count(); + + double totalUniques = uniqueAlphas + uniqueReds + uniqueGreens + uniqueBlues; + + const double AvailableBits = 8f; + + 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(maxAlpha, Convert.ToInt32(Math.Round(uniqueBlues / totalUniques * AvailableBits))); + + Pixel maskedPixel = new Pixel(alphaMask, redMask, greenMask, blueMask); + return maskedPixel.Argb; + } + + private static byte ComputeBitMask(byte max, int bits) + { + byte mask = 0; + + if (bits != 0) + { + byte highestSetBitIndex = HighestSetBitIndex(max); + + + for (int i = 0; i < bits; i++) + { + mask <<= 1; + mask++; + } + + for (int i = 0; i <= highestSetBitIndex - bits; i++) + { + mask <<= 1; + } + } + return mask; + } + + private static byte HighestSetBitIndex(byte value) + { + byte index = 0; + for (int i = 0; i < 8; i++) + { + if (0 != (value & 1)) + { + index = (byte)i; + } + value >>= 1; + } + return index; + } + + private struct LookupNode + { + public Pixel Pixel; + public byte PaletteIndex; + } + } +} diff --git a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/Pixel.cs b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/Pixel.cs index 0101b4af3..463059351 100644 --- a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/Pixel.cs +++ b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/Pixel.cs @@ -26,7 +26,7 @@ namespace nQuant this.Argb = argb; } - public long Distance() + public long Amplitude() { return (Alpha * Alpha) + (Red * Red) + (Green * Green) + (Blue * Blue); } diff --git a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/QuantizedPalette.cs b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/QuantizedPalette.cs deleted file mode 100644 index 13fd8a651..000000000 --- a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/QuantizedPalette.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Collections.Generic; -using System.Drawing; - -namespace nQuant -{ - public class QuantizedPalette - { - public QuantizedPalette(int size) - { - Colors = new List(); - PixelIndex = new byte[size]; - } - public IList Colors { get; private set; } - - public byte[] PixelIndex { get; private set; } - } -} \ No newline at end of file diff --git a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/WuQuantizer.cs b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/WuQuantizer.cs index 2354984fd..34a5b1b99 100644 --- a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/WuQuantizer.cs +++ b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/WuQuantizer.cs @@ -4,88 +4,49 @@ using System.Drawing; namespace nQuant { + using System.Drawing.Imaging; + public class WuQuantizer : WuQuantizerBase, IWuQuantizer { - protected override QuantizedPalette GetQuantizedPalette(int colorCount, ColorData data, Box[] cubes, int alphaThreshold) - { - int pixelsCount = data.Pixels.Length; - Lookup[] lookups = BuildLookups(cubes, data); - - var alphas = new int[colorCount + 1]; - var reds = new int[colorCount + 1]; - var greens = new int[colorCount + 1]; - var blues = new int[colorCount + 1]; - var sums = new int[colorCount + 1]; - var palette = new QuantizedPalette(pixelsCount); - - IList pixels = data.Pixels; + private IEnumerable indexedPixels(ImageBuffer image, List lookups, int alphaThreshold, PaletteBuffer paletteBuffer) + { + var alphas = paletteBuffer.Alphas; + var reds = paletteBuffer.Reds; + var greens = paletteBuffer.Greens; + var blues = paletteBuffer.Blues; + var sums = paletteBuffer.Sums; - Dictionary cachedMaches = new Dictionary(); + PaletteLookup lookup = new PaletteLookup(lookups); - for (int pixelIndex = 0; pixelIndex < pixelsCount; pixelIndex++) + foreach (Pixel pixel in image.Pixels) { - Pixel pixel = pixels[pixelIndex]; + byte bestMatch = 255; - if (pixel.Alpha > alphaThreshold) + if (pixel.Alpha >= alphaThreshold) { - byte bestMatch; - int argb = pixel.Argb; - - if (!cachedMaches.TryGetValue(argb, out bestMatch)) - { - int bestDistance = int.MaxValue; - - for (int lookupIndex = 0; lookupIndex < lookups.Length; lookupIndex++) - { - Lookup lookup = lookups[lookupIndex]; - var deltaAlpha = pixel.Alpha - lookup.Alpha; - var deltaRed = pixel.Red - lookup.Red; - var deltaGreen = pixel.Green - lookup.Green; - var deltaBlue = pixel.Blue - lookup.Blue; - - int distance = deltaAlpha * deltaAlpha + deltaRed * deltaRed + deltaGreen * deltaGreen + deltaBlue * deltaBlue; - - if (distance >= bestDistance) - continue; - - bestDistance = distance; - bestMatch = (byte)lookupIndex; - } - - cachedMaches[argb] = bestMatch; - } + bestMatch = lookup.GetPaletteIndex(pixel); alphas[bestMatch] += pixel.Alpha; reds[bestMatch] += pixel.Red; greens[bestMatch] += pixel.Green; blues[bestMatch] += pixel.Blue; sums[bestMatch]++; - - palette.PixelIndex[pixelIndex] = bestMatch; - } - else - { - palette.PixelIndex[pixelIndex] = AlphaColor; } - } - for (var paletteIndex = 0; paletteIndex < colorCount; paletteIndex++) - { - if (sums[paletteIndex] > 0) - { - alphas[paletteIndex] /= sums[paletteIndex]; - reds[paletteIndex] /= sums[paletteIndex]; - greens[paletteIndex] /= sums[paletteIndex]; - blues[paletteIndex] /= sums[paletteIndex]; - } - - var color = Color.FromArgb(alphas[paletteIndex], reds[paletteIndex], greens[paletteIndex], blues[paletteIndex]); - palette.Colors.Add(color); + yield return bestMatch; } + } - palette.Colors.Add(Color.FromArgb(0, 0, 0, 0)); - return palette; + internal override Image GetQuantizedImage(ImageBuffer image, int colorCount, List lookups, int alphaThreshold) + { + var result = new Bitmap(image.Image.Width, image.Image.Height, PixelFormat.Format8bppIndexed); + result.SetResolution(image.Image.HorizontalResolution, image.Image.VerticalResolution); + var resultBuffer = new ImageBuffer(result); + PaletteBuffer paletteBuffer = new PaletteBuffer(colorCount); + resultBuffer.UpdatePixelIndexes(indexedPixels(image, lookups, alphaThreshold, paletteBuffer)); + result.Palette = paletteBuffer.BuildPalette(result.Palette); + return result; } } } diff --git a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/WuQuantizerBase.cs b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/WuQuantizerBase.cs index 3baff786b..b8aeb13df 100644 --- a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/WuQuantizerBase.cs +++ b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/WuQuantizerBase.cs @@ -20,151 +20,52 @@ namespace nQuant public Image QuantizeImage(Bitmap image) { - return QuantizeImage(image, 10, 70); + return QuantizeImage(image, 0, 1); } public Image QuantizeImage(Bitmap image, int alphaThreshold, int alphaFader) { var colorCount = MaxColor; - var data = BuildHistogram(image, alphaThreshold, alphaFader); + ImageBuffer buffer = new ImageBuffer(image); + var data = BuildHistogram(buffer, alphaThreshold, alphaFader); + data = CalculateMoments(data); var cubes = SplitData(ref colorCount, data); - var palette = GetQuantizedPalette(colorCount, data, cubes, alphaThreshold); - return ProcessImagePixels(image, palette); + var lookups = BuildLookups(cubes, data); + return GetQuantizedImage(buffer, colorCount, lookups, alphaThreshold); } - private static Bitmap ProcessImagePixels(Image sourceImage, QuantizedPalette palette) + private static ColorData BuildHistogram(ImageBuffer sourceImage, int alphaThreshold, int alphaFader) { - var result = new Bitmap(sourceImage.Width, sourceImage.Height, PixelFormat.Format8bppIndexed); - var newPalette = result.Palette; - for (var index = 0; index < palette.Colors.Count; index++) - newPalette.Entries[index] = palette.Colors[index]; - result.Palette = newPalette; - - BitmapData targetData = null; - try + ColorData colorData = new ColorData(MaxSideIndex); + foreach (Pixel pixel in sourceImage.Pixels) { - var resultHeight = result.Height; - var resultWidth = result.Width; - targetData = result.LockBits(Rectangle.FromLTRB(0, 0, resultWidth, resultHeight), ImageLockMode.WriteOnly, result.PixelFormat); - const byte targetBitDepth = 8; - var targetByteLength = targetData.Stride < 0 ? -targetData.Stride : targetData.Stride; - var targetByteCount = Math.Max(1, targetBitDepth >> 3); - var targetBuffer = new byte[targetByteLength]; - var targetValue = new byte[targetByteCount]; - var pixelIndex = 0; - - - - for (var y = 0; y < resultHeight; y++) + if (pixel.Alpha >= alphaThreshold) { - var targetIndex = 0; - for (var x = 0; x < resultWidth; x++) + Pixel indexedPixel = pixel; + if (indexedPixel.Alpha < 255) { - var targetIndexOffset = targetIndex >> 3; - targetValue[0] = - (byte) - (palette.PixelIndex[pixelIndex] == AlphaColor - ? palette.Colors.Count - 1 - : palette.PixelIndex[pixelIndex]); - pixelIndex++; - - for (var valueIndex = 0; valueIndex < targetByteCount; valueIndex++) - { - targetBuffer[valueIndex + targetIndexOffset] = targetValue[valueIndex]; - } - - targetIndex += targetBitDepth; + int alpha = pixel.Alpha + (pixel.Alpha % alphaFader); + indexedPixel.Alpha = (byte)(alpha > 255 ? 255 : alpha); } - Marshal.Copy(targetBuffer, 0, targetData.Scan0 + (targetByteLength * y), targetByteLength); - } - } - finally - { - if (targetData != null) - { - result.UnlockBits(targetData); + indexedPixel.Alpha = (byte)((indexedPixel.Alpha >> 3) + 1); + indexedPixel.Red = (byte)((indexedPixel.Red >> 3) + 1); + indexedPixel.Green = (byte)((indexedPixel.Green >> 3) + 1); + indexedPixel.Blue = (byte)((indexedPixel.Blue >> 3) + 1); + colorData.Moments[indexedPixel.Alpha, indexedPixel.Red, indexedPixel.Green, indexedPixel.Blue].Add(pixel); } } - return result; - } - - private static ColorData BuildHistogram(Bitmap sourceImage, int alphaThreshold, int alphaFader) - { - int bitmapWidth = sourceImage.Width; - int bitmapHeight = sourceImage.Height; - - BitmapData data = sourceImage.LockBits( - Rectangle.FromLTRB(0, 0, bitmapWidth, bitmapHeight), - ImageLockMode.ReadOnly, - sourceImage.PixelFormat); - ColorData colorData = new ColorData(MaxSideIndex, bitmapWidth, bitmapHeight); - - try - { - var bitDepth = Image.GetPixelFormatSize(sourceImage.PixelFormat); - if (bitDepth != 32) - throw new QuantizationException(string.Format("Thie image you are attempting to quantize does not contain a 32 bit ARGB palette. This image has a bit depth of {0} with {1} colors.", bitDepth, sourceImage.Palette.Entries.Length)); - var byteLength = data.Stride < 0 ? -data.Stride : data.Stride; - var byteCount = Math.Max(1, bitDepth >> 3); - var buffer = new Byte[byteLength]; - - var value = new Byte[byteCount]; - - for (int y = 0; y < bitmapHeight; y++) - { - Marshal.Copy(data.Scan0 + (byteLength * y), buffer, 0, buffer.Length); - - var index = 0; - for (int x = 0; x < bitmapWidth; x++) - { - var indexOffset = index >> 3; - - for (var valueIndex = 0; valueIndex < byteCount; valueIndex++) - { - value[valueIndex] = buffer[valueIndex + indexOffset]; - } - - Pixel pixelValue = new Pixel(value[Alpha], value[Red], value[Green], value[Blue]); - - var indexAlpha = (byte)((value[Alpha] >> 3) + 1); - var indexRed = (byte)((value[Red] >> 3) + 1); - var indexGreen = (byte)((value[Green] >> 3) + 1); - var indexBlue = (byte)((value[Blue] >> 3) + 1); - - if (value[Alpha] > alphaThreshold) - { - if (value[Alpha] < 255) - { - var alpha = value[Alpha] + (value[Alpha] % alphaFader); - value[Alpha] = (byte)(alpha > 255 ? 255 : alpha); - indexAlpha = (byte)((value[Alpha] >> 3) + 1); - } - - colorData.Moments[indexAlpha, indexRed, indexGreen, indexBlue] += pixelValue; - } - - colorData.AddPixel( - pixelValue, - new Pixel(indexAlpha, indexRed, indexGreen, indexBlue)); - index += bitDepth; - } - } - } - finally - { - sourceImage.UnlockBits(data); - } return colorData; } private static ColorData CalculateMoments(ColorData data) { var xarea = new ColorMoment[SideSize, SideSize]; - var xPreviousArea = new ColorMoment[SideSize, SideSize]; var area = new ColorMoment[SideSize]; + var moments = data.Moments; + for (var alphaIndex = 1; alphaIndex <= MaxSideIndex; ++alphaIndex) { for (var redIndex = 1; redIndex <= MaxSideIndex; ++redIndex) @@ -175,17 +76,12 @@ namespace nQuant ColorMoment line = new ColorMoment(); for (var blueIndex = 1; blueIndex <= MaxSideIndex; ++blueIndex) { - line += data.Moments[alphaIndex, redIndex, greenIndex, blueIndex]; - area[blueIndex] += line; - - xarea[greenIndex, blueIndex] = xPreviousArea[greenIndex, blueIndex] + area[blueIndex]; - data.Moments[alphaIndex, redIndex, greenIndex, blueIndex] = data.Moments[alphaIndex - 1, redIndex, greenIndex, blueIndex] + xarea[greenIndex, blueIndex]; + line.AddFast(ref moments[alphaIndex, redIndex, greenIndex, blueIndex]); + area[blueIndex].AddFast(ref line); + xarea[greenIndex, blueIndex].AddFast(ref area[blueIndex]); + moments[alphaIndex, redIndex, greenIndex, blueIndex] = moments[alphaIndex - 1, redIndex, greenIndex, blueIndex] + xarea[greenIndex, blueIndex]; } } - - var temp = xarea; - xarea = xPreviousArea; - xPreviousArea = temp; } } return data; @@ -474,9 +370,9 @@ namespace nQuant return cubes.Take(colorCount).ToArray(); } - protected Lookup[] BuildLookups(Box[] cubes, ColorData data) + private List BuildLookups(Box[] cubes, ColorData data) { - List lookups = new List(cubes.Length); + List lookups = new List(cubes.Length); foreach (var cube in cubes) { @@ -487,20 +383,20 @@ namespace nQuant continue; } - var lookup = new Lookup + var lookup = new Pixel { - Alpha = (int)(volume.Alpha / volume.Weight), - Red = (int)(volume.Red / volume.Weight), - Green = (int)(volume.Green / volume.Weight), - Blue = (int)(volume.Blue / volume.Weight) + Alpha = (byte)(volume.Alpha / volume.Weight), + Red = (byte)(volume.Red / volume.Weight), + Green = (byte)(volume.Green / volume.Weight), + Blue = (byte)(volume.Blue / volume.Weight) }; lookups.Add(lookup); } - return lookups.ToArray(); + return lookups; } - protected abstract QuantizedPalette GetQuantizedPalette(int colorCount, ColorData data, Box[] cubes, int alphaThreshold); + internal abstract Image GetQuantizedImage(ImageBuffer image, int colorCount, List lookups, int alphaThreshold); } } \ No newline at end of file