Browse Source

Applying optimisations

Former-commit-id: d202401f6555c117e25d4132d91a76d65aab0e7e
Former-commit-id: e9ee3a8064f0e9db131fd0b136ab327b0d7d29ff
pull/17/head
James South 12 years ago
parent
commit
4a41376fef
  1. 2
      src/ImageProcessor.Playground/Program.cs
  2. BIN
      src/ImageProcessor.Playground/images/input/000.png
  3. BIN
      src/ImageProcessor.Playground/images/input/effect_24bit.png
  4. 1
      src/ImageProcessor.Playground/images/input/h9ghTMB.png.REMOVED.git-id
  5. 1
      src/ImageProcessor.Playground/images/input/monster-whitebg.png2.REMOVED.git-id
  6. BIN
      src/ImageProcessor.Playground/images/input/pixel.png3
  7. 1
      src/ImageProcessor.Playground/images/input/tower - Copy.jpg2.REMOVED.git-id
  8. 4
      src/ImageProcessor/ImageProcessor.csproj
  9. 3
      src/ImageProcessor/Imaging/Formats/PngFormat.cs
  10. 2
      src/ImageProcessor/Imaging/Quantizers/Quantizer.cs
  11. 61
      src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ColorData.cs
  12. 31
      src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ColorMoment.cs
  13. 81
      src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ImageBuffer.cs
  14. 52
      src/ImageProcessor/Imaging/Quantizers/WuQuantizer/PaletteBuffer.cs
  15. 164
      src/ImageProcessor/Imaging/Quantizers/WuQuantizer/PaletteLookup.cs
  16. 2
      src/ImageProcessor/Imaging/Quantizers/WuQuantizer/Pixel.cs
  17. 17
      src/ImageProcessor/Imaging/Quantizers/WuQuantizer/QuantizedPalette.cs
  18. 89
      src/ImageProcessor/Imaging/Quantizers/WuQuantizer/WuQuantizer.cs
  19. 172
      src/ImageProcessor/Imaging/Quantizers/WuQuantizer/WuQuantizerBase.cs

2
src/ImageProcessor.Playground/Program.cs

@ -49,7 +49,7 @@ namespace ImageProcessor.PlayGround
di.Create(); di.Create();
} }
Image mask = Image.FromFile(Path.Combine(resolvedPath, "mask2.png")); // Image mask = Image.FromFile(Path.Combine(resolvedPath, "mask2.png"));
IEnumerable<FileInfo> files = GetFilesByExtensions(di, ".png"); IEnumerable<FileInfo> files = GetFilesByExtensions(di, ".png");
//IEnumerable<FileInfo> files = GetFilesByExtensions(di, ".gif", ".webp", ".bmp", ".jpg", ".png", ".tif"); //IEnumerable<FileInfo> files = GetFilesByExtensions(di, ".gif", ".webp", ".bmp", ".jpg", ".png", ".tif");

BIN
src/ImageProcessor.Playground/images/input/000.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 B

BIN
src/ImageProcessor.Playground/images/input/effect_24bit.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

1
src/ImageProcessor.Playground/images/input/h9ghTMB.png.REMOVED.git-id

@ -0,0 +1 @@
a442c2c7ce00c44e7de9f8221ad2c396be3431c5

1
src/ImageProcessor.Playground/images/input/monster-whitebg.png2.REMOVED.git-id

@ -1 +0,0 @@
71ccd68b75f913237cdd2047d5f5d0b39d9b16ae

BIN
src/ImageProcessor.Playground/images/input/pixel.png3

Binary file not shown.

Before

Width:  |  Height:  |  Size: 166 B

1
src/ImageProcessor.Playground/images/input/tower - Copy.jpg2.REMOVED.git-id

@ -1 +0,0 @@
98c3f8fce4fdd9eebafe12431c8e014fd39d243f

4
src/ImageProcessor/ImageProcessor.csproj

@ -185,16 +185,18 @@
<Compile Include="Imaging\Quantizers\OctreeQuantizer.cs" /> <Compile Include="Imaging\Quantizers\OctreeQuantizer.cs" />
<Compile Include="Imaging\Quantizers\Quantizer.cs" /> <Compile Include="Imaging\Quantizers\Quantizer.cs" />
<Compile Include="Imaging\Quantizers\WuQuantizer\Box.cs" /> <Compile Include="Imaging\Quantizers\WuQuantizer\Box.cs" />
<Compile Include="Imaging\Quantizers\WuQuantizer\PaletteLookup.cs" />
<Compile Include="Imaging\Quantizers\WuQuantizer\PaletteBuffer.cs" />
<Compile Include="Imaging\Quantizers\WuQuantizer\ColorData.cs" /> <Compile Include="Imaging\Quantizers\WuQuantizer\ColorData.cs" />
<Compile Include="Imaging\Quantizers\WuQuantizer\ColorMoment.cs" /> <Compile Include="Imaging\Quantizers\WuQuantizer\ColorMoment.cs" />
<Compile Include="Imaging\Quantizers\WuQuantizer\CubeCut.cs" /> <Compile Include="Imaging\Quantizers\WuQuantizer\CubeCut.cs" />
<Compile Include="Imaging\Quantizers\WuQuantizer\ImageBuffer.cs" />
<Compile Include="Imaging\Quantizers\WuQuantizer\IWuQuantizer.cs" /> <Compile Include="Imaging\Quantizers\WuQuantizer\IWuQuantizer.cs" />
<Compile Include="Imaging\Quantizers\WuQuantizer\Lookup.cs" /> <Compile Include="Imaging\Quantizers\WuQuantizer\Lookup.cs" />
<Compile Include="Imaging\Quantizers\WuQuantizer\Pixel.cs"> <Compile Include="Imaging\Quantizers\WuQuantizer\Pixel.cs">
<SubType>Code</SubType> <SubType>Code</SubType>
</Compile> </Compile>
<Compile Include="Imaging\Quantizers\WuQuantizer\QuantizationException.cs" /> <Compile Include="Imaging\Quantizers\WuQuantizer\QuantizationException.cs" />
<Compile Include="Imaging\Quantizers\WuQuantizer\QuantizedPalette.cs" />
<Compile Include="Imaging\Quantizers\WuQuantizer\WuQuantizer.cs" /> <Compile Include="Imaging\Quantizers\WuQuantizer\WuQuantizer.cs" />
<Compile Include="Imaging\Quantizers\WuQuantizer\WuQuantizerBase.cs" /> <Compile Include="Imaging\Quantizers\WuQuantizer\WuQuantizerBase.cs" />
<Compile Include="Imaging\ResizeLayer.cs" /> <Compile Include="Imaging\ResizeLayer.cs" />

3
src/ImageProcessor/Imaging/Formats/PngFormat.cs

@ -104,6 +104,7 @@ namespace ImageProcessor.Imaging.Formats
// The Wu Quantizer expects a 32bbp image. // The Wu Quantizer expects a 32bbp image.
//if (Image.GetPixelFormatSize(image.PixelFormat) != 32) //if (Image.GetPixelFormatSize(image.PixelFormat) != 32)
//{ //{
Bitmap clone = new Bitmap(image.Width, image.Height, PixelFormat.Format32bppPArgb); Bitmap clone = new Bitmap(image.Width, image.Height, PixelFormat.Format32bppPArgb);
clone.SetResolution(image.HorizontalResolution, image.VerticalResolution); clone.SetResolution(image.HorizontalResolution, image.VerticalResolution);
@ -116,6 +117,8 @@ namespace ImageProcessor.Imaging.Formats
image.Dispose(); image.Dispose();
image = new WuQuantizer().QuantizeImage(clone); image = new WuQuantizer().QuantizeImage(clone);
// image = new OctreeQuantizer(255, 8).Quantize(image);
//} //}
//else //else
//{ //{

2
src/ImageProcessor/Imaging/Quantizers/Quantizer.cs

@ -59,9 +59,11 @@ namespace ImageProcessor.Imaging.Quantizers
// First off take a 32bpp copy of the image // First off take a 32bpp copy of the image
Bitmap copy = new Bitmap(width, height, PixelFormat.Format32bppArgb); Bitmap copy = new Bitmap(width, height, PixelFormat.Format32bppArgb);
copy.SetResolution(source.HorizontalResolution, source.VerticalResolution);
// And construct an 8bpp version // And construct an 8bpp version
Bitmap output = new Bitmap(width, height, PixelFormat.Format8bppIndexed); Bitmap output = new Bitmap(width, height, PixelFormat.Format8bppIndexed);
output.SetResolution(source.HorizontalResolution, source.VerticalResolution);
// Now lock the bitmap into memory // Now lock the bitmap into memory
using (Graphics g = Graphics.FromImage(copy)) using (Graphics g = Graphics.FromImage(copy))

61
src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ColorData.cs

@ -12,81 +12,22 @@ namespace nQuant
/// </summary> /// </summary>
public class ColorData public class ColorData
{ {
/// <summary>
/// The pixels.
/// </summary>
private readonly Pixel[] pixels;
/// <summary>
/// The pixels count.
/// </summary>
private readonly int pixelsCount;
/// <summary>
/// The pixel filling counter.
/// </summary>
private int pixelFillingCounter;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ColorData"/> class. /// Initializes a new instance of the <see cref="ColorData"/> class.
/// </summary> /// </summary>
/// <param name="dataGranularity"> /// <param name="dataGranularity">
/// The data granularity. /// The data granularity.
/// </param> /// </param>
/// <param name="bitmapWidth"> public ColorData(int dataGranularity)
/// The bitmap width.
/// </param>
/// <param name="bitmapHeight">
/// The bitmap height.
/// </param>
public ColorData(int dataGranularity, int bitmapWidth, int bitmapHeight)
{ {
dataGranularity++; dataGranularity++;
this.Moments = new ColorMoment[dataGranularity, dataGranularity, dataGranularity, dataGranularity]; this.Moments = new ColorMoment[dataGranularity, dataGranularity, dataGranularity, dataGranularity];
this.pixelsCount = bitmapWidth * bitmapHeight;
this.pixels = new Pixel[this.pixelsCount];
} }
/// <summary> /// <summary>
/// Gets the moments. /// Gets the moments.
/// </summary> /// </summary>
public ColorMoment[, , ,] Moments { get; private set; } public ColorMoment[, , ,] Moments { get; private set; }
/// <summary>
/// Gets the pixels.
/// </summary>
public Pixel[] Pixels
{
get
{
return this.pixels;
}
}
/// <summary>
/// Gets the pixels count.
/// </summary>
public int PixelsCount
{
get
{
return this.pixelsCount;
}
}
/// <summary>
/// The add pixel.
/// </summary>
/// <param name="pixel">
/// The pixel.
/// </param>
/// <param name="quantizedPixel">
/// The quantized pixel.
/// </param>
public void AddPixel(Pixel pixel, Pixel quantizedPixel)
{
this.pixels[this.pixelFillingCounter++] = pixel;
}
} }
} }

31
src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ColorMoment.cs

@ -43,30 +43,39 @@ namespace nQuant
return c1; return c1;
} }
public static ColorMoment operator +(ColorMoment m, Pixel p) public void Add(Pixel p)
{ {
m.Alpha += p.Alpha; Alpha += p.Alpha;
m.Red += p.Red; Red += p.Red;
m.Green += p.Green; Green += p.Green;
m.Blue += p.Blue; Blue += p.Blue;
m.Weight++; Weight++;
m.Moment += p.Distance(); Moment += p.Amplitude();
return m;
} }
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); return (Alpha * Alpha) + (Red * Red) + (Green * Green) + (Blue * Blue);
} }
public long WeightedDistance() public long WeightedDistance()
{ {
return Distance() / Weight; return this.Amplitude() / Weight;
} }
public float Variance() public float Variance()
{ {
var result = Moment - ((float)Distance() / Weight); var result = Moment - ((float)this.Amplitude() / this.Weight);
return float.IsNaN(result) ? 0.0f : result; return float.IsNaN(result) ? 0.0f : result;
} }
} }

81
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<Pixel> 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<byte> indexes)
{
int width = this.Image.Width;
int height = this.Image.Height;
byte[] buffer = new byte[width];
IEnumerator<byte> 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);
}
}
}
}
}

52
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; }
}
}

164
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<int, List<LookupNode>> mLookup = new Dictionary<int, List<LookupNode>>(255);
private List<LookupNode> Palette { get; set; }
public PaletteLookup(List<Pixel> palette)
{
Palette = new List<LookupNode>(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<LookupNode> 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<Pixel> palette)
{
int mask = GetMask(palette);
foreach (LookupNode lookup in Palette)
{
int pixelKey = lookup.Pixel.Argb & mask;
List<LookupNode> bucket;
if (!mLookup.TryGetValue(pixelKey, out bucket))
{
bucket = new List<LookupNode>();
mLookup[pixelKey] = bucket;
}
bucket.Add(lookup);
}
mMask = mask;
}
private static int GetMask(List<Pixel> palette)
{
IEnumerable<byte> alphas = from pixel in palette
select pixel.Alpha;
byte maxAlpha = alphas.Max();
int uniqueAlphas = alphas.Distinct().Count();
IEnumerable<byte> reds = from pixel in palette
select pixel.Red;
byte maxRed = reds.Max();
int uniqueReds = reds.Distinct().Count();
IEnumerable<byte> greens = from pixel in palette
select pixel.Green;
byte maxGreen = greens.Max();
int uniqueGreens = greens.Distinct().Count();
IEnumerable<byte> 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;
}
}
}

2
src/ImageProcessor/Imaging/Quantizers/WuQuantizer/Pixel.cs

@ -26,7 +26,7 @@ namespace nQuant
this.Argb = argb; this.Argb = argb;
} }
public long Distance() public long Amplitude()
{ {
return (Alpha * Alpha) + (Red * Red) + (Green * Green) + (Blue * Blue); return (Alpha * Alpha) + (Red * Red) + (Green * Green) + (Blue * Blue);
} }

17
src/ImageProcessor/Imaging/Quantizers/WuQuantizer/QuantizedPalette.cs

@ -1,17 +0,0 @@
using System.Collections.Generic;
using System.Drawing;
namespace nQuant
{
public class QuantizedPalette
{
public QuantizedPalette(int size)
{
Colors = new List<Color>();
PixelIndex = new byte[size];
}
public IList<Color> Colors { get; private set; }
public byte[] PixelIndex { get; private set; }
}
}

89
src/ImageProcessor/Imaging/Quantizers/WuQuantizer/WuQuantizer.cs

@ -4,88 +4,49 @@ using System.Drawing;
namespace nQuant namespace nQuant
{ {
using System.Drawing.Imaging;
public class WuQuantizer : WuQuantizerBase, IWuQuantizer public class WuQuantizer : WuQuantizerBase, IWuQuantizer
{ {
protected override QuantizedPalette GetQuantizedPalette(int colorCount, ColorData data, Box[] cubes, int alphaThreshold) private IEnumerable<byte> indexedPixels(ImageBuffer image, List<Pixel> lookups, int alphaThreshold, PaletteBuffer paletteBuffer)
{ {
int pixelsCount = data.Pixels.Length; var alphas = paletteBuffer.Alphas;
Lookup[] lookups = BuildLookups(cubes, data); var reds = paletteBuffer.Reds;
var greens = paletteBuffer.Greens;
var alphas = new int[colorCount + 1]; var blues = paletteBuffer.Blues;
var reds = new int[colorCount + 1]; var sums = paletteBuffer.Sums;
var greens = new int[colorCount + 1];
var blues = new int[colorCount + 1];
var sums = new int[colorCount + 1];
var palette = new QuantizedPalette(pixelsCount);
IList<Pixel> pixels = data.Pixels;
Dictionary<int, byte> cachedMaches = new Dictionary<int, byte>(); 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; bestMatch = lookup.GetPaletteIndex(pixel);
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;
}
alphas[bestMatch] += pixel.Alpha; alphas[bestMatch] += pixel.Alpha;
reds[bestMatch] += pixel.Red; reds[bestMatch] += pixel.Red;
greens[bestMatch] += pixel.Green; greens[bestMatch] += pixel.Green;
blues[bestMatch] += pixel.Blue; blues[bestMatch] += pixel.Blue;
sums[bestMatch]++; sums[bestMatch]++;
palette.PixelIndex[pixelIndex] = bestMatch;
}
else
{
palette.PixelIndex[pixelIndex] = AlphaColor;
} }
}
for (var paletteIndex = 0; paletteIndex < colorCount; paletteIndex++) yield return bestMatch;
{
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);
} }
}
palette.Colors.Add(Color.FromArgb(0, 0, 0, 0));
return palette; internal override Image GetQuantizedImage(ImageBuffer image, int colorCount, List<Pixel> 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;
} }
} }
} }

172
src/ImageProcessor/Imaging/Quantizers/WuQuantizer/WuQuantizerBase.cs

@ -20,151 +20,52 @@ namespace nQuant
public Image QuantizeImage(Bitmap image) public Image QuantizeImage(Bitmap image)
{ {
return QuantizeImage(image, 10, 70); return QuantizeImage(image, 0, 1);
} }
public Image QuantizeImage(Bitmap image, int alphaThreshold, int alphaFader) public Image QuantizeImage(Bitmap image, int alphaThreshold, int alphaFader)
{ {
var colorCount = MaxColor; var colorCount = MaxColor;
var data = BuildHistogram(image, alphaThreshold, alphaFader); ImageBuffer buffer = new ImageBuffer(image);
var data = BuildHistogram(buffer, alphaThreshold, alphaFader);
data = CalculateMoments(data); data = CalculateMoments(data);
var cubes = SplitData(ref colorCount, data); var cubes = SplitData(ref colorCount, data);
var palette = GetQuantizedPalette(colorCount, data, cubes, alphaThreshold); var lookups = BuildLookups(cubes, data);
return ProcessImagePixels(image, palette); 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); ColorData colorData = new ColorData(MaxSideIndex);
var newPalette = result.Palette; foreach (Pixel pixel in sourceImage.Pixels)
for (var index = 0; index < palette.Colors.Count; index++)
newPalette.Entries[index] = palette.Colors[index];
result.Palette = newPalette;
BitmapData targetData = null;
try
{ {
var resultHeight = result.Height; if (pixel.Alpha >= alphaThreshold)
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++)
{ {
var targetIndex = 0; Pixel indexedPixel = pixel;
for (var x = 0; x < resultWidth; x++) if (indexedPixel.Alpha < 255)
{ {
var targetIndexOffset = targetIndex >> 3; int alpha = pixel.Alpha + (pixel.Alpha % alphaFader);
targetValue[0] = indexedPixel.Alpha = (byte)(alpha > 255 ? 255 : alpha);
(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;
} }
Marshal.Copy(targetBuffer, 0, targetData.Scan0 + (targetByteLength * y), targetByteLength); indexedPixel.Alpha = (byte)((indexedPixel.Alpha >> 3) + 1);
} indexedPixel.Red = (byte)((indexedPixel.Red >> 3) + 1);
} indexedPixel.Green = (byte)((indexedPixel.Green >> 3) + 1);
finally indexedPixel.Blue = (byte)((indexedPixel.Blue >> 3) + 1);
{ colorData.Moments[indexedPixel.Alpha, indexedPixel.Red, indexedPixel.Green, indexedPixel.Blue].Add(pixel);
if (targetData != null)
{
result.UnlockBits(targetData);
} }
} }
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; return colorData;
} }
private static ColorData CalculateMoments(ColorData data) private static ColorData CalculateMoments(ColorData data)
{ {
var xarea = new ColorMoment[SideSize, SideSize]; var xarea = new ColorMoment[SideSize, SideSize];
var xPreviousArea = new ColorMoment[SideSize, SideSize];
var area = new ColorMoment[SideSize]; var area = new ColorMoment[SideSize];
var moments = data.Moments;
for (var alphaIndex = 1; alphaIndex <= MaxSideIndex; ++alphaIndex) for (var alphaIndex = 1; alphaIndex <= MaxSideIndex; ++alphaIndex)
{ {
for (var redIndex = 1; redIndex <= MaxSideIndex; ++redIndex) for (var redIndex = 1; redIndex <= MaxSideIndex; ++redIndex)
@ -175,17 +76,12 @@ namespace nQuant
ColorMoment line = new ColorMoment(); ColorMoment line = new ColorMoment();
for (var blueIndex = 1; blueIndex <= MaxSideIndex; ++blueIndex) for (var blueIndex = 1; blueIndex <= MaxSideIndex; ++blueIndex)
{ {
line += data.Moments[alphaIndex, redIndex, greenIndex, blueIndex]; line.AddFast(ref moments[alphaIndex, redIndex, greenIndex, blueIndex]);
area[blueIndex] += line; area[blueIndex].AddFast(ref line);
xarea[greenIndex, blueIndex].AddFast(ref area[blueIndex]);
xarea[greenIndex, blueIndex] = xPreviousArea[greenIndex, blueIndex] + area[blueIndex]; moments[alphaIndex, redIndex, greenIndex, blueIndex] = moments[alphaIndex - 1, redIndex, greenIndex, blueIndex] + xarea[greenIndex, blueIndex];
data.Moments[alphaIndex, redIndex, greenIndex, blueIndex] = data.Moments[alphaIndex - 1, redIndex, greenIndex, blueIndex] + xarea[greenIndex, blueIndex];
} }
} }
var temp = xarea;
xarea = xPreviousArea;
xPreviousArea = temp;
} }
} }
return data; return data;
@ -474,9 +370,9 @@ namespace nQuant
return cubes.Take(colorCount).ToArray(); return cubes.Take(colorCount).ToArray();
} }
protected Lookup[] BuildLookups(Box[] cubes, ColorData data) private List<Pixel> BuildLookups(Box[] cubes, ColorData data)
{ {
List<Lookup> lookups = new List<Lookup>(cubes.Length); List<Pixel> lookups = new List<Pixel>(cubes.Length);
foreach (var cube in cubes) foreach (var cube in cubes)
{ {
@ -487,20 +383,20 @@ namespace nQuant
continue; continue;
} }
var lookup = new Lookup var lookup = new Pixel
{ {
Alpha = (int)(volume.Alpha / volume.Weight), Alpha = (byte)(volume.Alpha / volume.Weight),
Red = (int)(volume.Red / volume.Weight), Red = (byte)(volume.Red / volume.Weight),
Green = (int)(volume.Green / volume.Weight), Green = (byte)(volume.Green / volume.Weight),
Blue = (int)(volume.Blue / volume.Weight) Blue = (byte)(volume.Blue / volume.Weight)
}; };
lookups.Add(lookup); 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<Pixel> lookups, int alphaThreshold);
} }
} }
Loading…
Cancel
Save