Browse Source

Moar optimisations plus start tidy up

Former-commit-id: 98655c0fccf43335504a97036f9fc699c490af17
Former-commit-id: 002b3f569e94bd36357de09271033cf033ed3997
pull/17/head
James South 11 years ago
parent
commit
90340dd3ce
  1. 6
      src/ImageProcessor/ImageProcessor.csproj
  2. 3
      src/ImageProcessor/Imaging/Formats/PngFormat.cs
  3. 64
      src/ImageProcessor/Imaging/Quantizers/WuQuantizer/Box.cs
  4. 33
      src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ColorData.cs
  5. 33
      src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ColorMoment.cs
  6. 37
      src/ImageProcessor/Imaging/Quantizers/WuQuantizer/CubeCut.cs
  7. 35
      src/ImageProcessor/Imaging/Quantizers/WuQuantizer/IWuQuantizer.cs
  8. 93
      src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ImageBuffer.cs
  9. 10
      src/ImageProcessor/Imaging/Quantizers/WuQuantizer/Lookup.cs
  10. 52
      src/ImageProcessor/Imaging/Quantizers/WuQuantizer/PaletteBuffer.cs
  11. 73
      src/ImageProcessor/Imaging/Quantizers/WuQuantizer/PaletteColorHistory.cs
  12. 55
      src/ImageProcessor/Imaging/Quantizers/WuQuantizer/PaletteLookup.cs
  13. 33
      src/ImageProcessor/Imaging/Quantizers/WuQuantizer/Pixel.cs
  14. 3
      src/ImageProcessor/Imaging/Quantizers/WuQuantizer/QuantizationException.cs
  15. 63
      src/ImageProcessor/Imaging/Quantizers/WuQuantizer/WuQuantizer.cs
  16. 201
      src/ImageProcessor/Imaging/Quantizers/WuQuantizer/WuQuantizerBase.cs

6
src/ImageProcessor/ImageProcessor.csproj

@ -185,14 +185,12 @@
<Compile Include="Imaging\Quantizers\OctreeQuantizer.cs" />
<Compile Include="Imaging\Quantizers\Quantizer.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\ColorMoment.cs" />
<Compile Include="Imaging\Quantizers\WuQuantizer\PaletteColorHistory.cs" />
<Compile Include="Imaging\Quantizers\WuQuantizer\PaletteLookup.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\Lookup.cs" />
<Compile Include="Imaging\Quantizers\WuQuantizer\Pixel.cs">
<SubType>Code</SubType>
</Compile>

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

@ -16,8 +16,7 @@ namespace ImageProcessor.Imaging.Formats
using ImageProcessor.Common.Extensions;
using ImageProcessor.Imaging.Quantizers;
using nQuant;
using ImageProcessor.Imaging.Quantizers.WuQuantizer;
/// <summary>
/// Provides the necessary information to support png images.

64
src/ImageProcessor/Imaging/Quantizers/WuQuantizer/Box.cs

@ -1,15 +1,65 @@
namespace nQuant
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Box.cs" company="James South">
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// The box for storing color attributes.
// Adapted from <see href="https://github.com/drewnoakes" />
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
{
/// <summary>
/// The box for storing color attributes.
/// Adapted from <see href="https://github.com/drewnoakes"/>
/// </summary>
public struct Box
{
public byte AlphaMinimum;
/// <summary>
/// The alpha maximum.
/// </summary>
public byte AlphaMaximum;
public byte RedMinimum;
public byte RedMaximum;
public byte GreenMinimum;
public byte GreenMaximum;
public byte BlueMinimum;
/// <summary>
/// The alpha minimum.
/// </summary>
public byte AlphaMinimum;
/// <summary>
/// The blue maximum.
/// </summary>
public byte BlueMaximum;
/// <summary>
/// The blue minimum.
/// </summary>
public byte BlueMinimum;
/// <summary>
/// The green maximum.
/// </summary>
public byte GreenMaximum;
/// <summary>
/// The green minimum.
/// </summary>
public byte GreenMinimum;
/// <summary>
/// The red maximum.
/// </summary>
public byte RedMaximum;
/// <summary>
/// The red minimum.
/// </summary>
public byte RedMinimum;
/// <summary>
/// The size.
/// </summary>
public int Size;
}
}

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

@ -1,33 +0,0 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="ColorData.cs" company="James South">
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace nQuant
{
/// <summary>
/// The color data.
/// </summary>
public class ColorData
{
/// <summary>
/// Initializes a new instance of the <see cref="ColorData"/> class.
/// </summary>
/// <param name="dataGranularity">
/// The data granularity.
/// </param>
public ColorData(int dataGranularity)
{
dataGranularity++;
this.Moments = new ColorMoment[dataGranularity, dataGranularity, dataGranularity, dataGranularity];
}
/// <summary>
/// Gets the moments.
/// </summary>
public ColorMoment[, , ,] Moments { get; private set; }
}
}

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

@ -1,7 +1,8 @@

namespace nQuant
//using System.Runtime.CompilerServices;
namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
{
public struct ColorMoment
struct ColorMoment
{
public long Alpha;
public long Red;
@ -43,16 +44,22 @@ namespace nQuant
return c1;
}
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Add(Pixel p)
{
Alpha += p.Alpha;
Red += p.Red;
Green += p.Green;
Blue += p.Blue;
byte pAlpha = p.Alpha;
byte pRed = p.Red;
byte pGreen = p.Green;
byte pBlue = p.Blue;
Alpha += pAlpha;
Red += pRed;
Green += pGreen;
Blue += pBlue;
Weight++;
Moment += p.Amplitude();
Moment += pAlpha * pAlpha + pRed * pRed + pGreen * pGreen + pBlue * pBlue;
}
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddFast(ref ColorMoment c2)
{
Alpha += c2.Alpha;
@ -62,21 +69,21 @@ namespace nQuant
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()
{
return this.Amplitude() / Weight;
return Amplitude() / Weight;
}
public float Variance()
{
var result = Moment - ((float)this.Amplitude() / this.Weight);
var result = Moment - (float)Amplitude() / Weight;
return float.IsNaN(result) ? 0.0f : result;
}
}
}
}

37
src/ImageProcessor/Imaging/Quantizers/WuQuantizer/CubeCut.cs

@ -1,14 +1,45 @@
namespace nQuant
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="CubeCut.cs" company="James South">
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// Represents a cube cut.
// Adapted from <see href="https://github.com/drewnoakes" />
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
{
/// <summary>
/// Represents a cube cut.
/// Adapted from <see href="https://github.com/drewnoakes"/>
/// </summary>
internal struct CubeCut
{
/// <summary>
/// The position.
/// </summary>
public readonly byte? Position;
/// <summary>
/// The value.
/// </summary>
public readonly float Value;
/// <summary>
/// Initializes a new instance of the <see cref="CubeCut"/> struct.
/// </summary>
/// <param name="cutPoint">
/// The cut point.
/// </param>
/// <param name="result">
/// The result.
/// </param>
public CubeCut(byte? cutPoint, float result)
{
Position = cutPoint;
Value = result;
this.Position = cutPoint;
this.Value = result;
}
}
}

35
src/ImageProcessor/Imaging/Quantizers/WuQuantizer/IWuQuantizer.cs

@ -1,9 +1,40 @@
using System.Drawing;
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="IWuQuantizer.cs" company="James South">
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// The WuQuantizer interface.
// Adapted from <see href="https://github.com/drewnoakes" />
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace nQuant
namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
{
using System.Drawing;
/// <summary>
/// The WuQuantizer interface.
/// Adapted from <see href="https://github.com/drewnoakes" />
/// </summary>
public interface IWuQuantizer
{
/// <summary>
/// Quantizes the given image.
/// </summary>
/// <param name="image">
/// The 32 bit per pixel <see cref="Image"/>.
/// </param>
/// <param name="alphaThreshold">
/// The alpha threshold. All colors with an alpha value less than this will be
/// considered fully transparent
/// </param>
/// <param name="alphaFader">
/// The alpha fader. Alpha values will be normalized to the nearest multiple of this value.
/// </param>
/// <returns>
/// The quantized <see cref="Image"/>.
/// </returns>
Image QuantizeImage(Bitmap image, int alphaThreshold, int alphaFader);
}
}

93
src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ImageBuffer.cs

@ -1,75 +1,106 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="ImageBuffer.cs" company="James South">
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// The image buffer for storing pixel information.
// Adapted from <see href="https://github.com/drewnoakes" />
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace nQuant
namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
{
class ImageBuffer
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
/// <summary>
/// The image buffer for storing pixel information.
/// Adapted from <see href="https://github.com/drewnoakes"/>
/// </summary>
internal class ImageBuffer
{
/// <summary>
/// Initializes a new instance of the <see cref="ImageBuffer"/> class.
/// </summary>
/// <param name="image">
/// The image to store.
/// </param>
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;
/// <summary>
/// Gets the image.
/// </summary>
public Bitmap Image { get; private set; }
public IEnumerable<Pixel> Pixels
/// <summary>
/// Gets the pixel lines.
/// </summary>
/// <exception cref="QuantizationException">
/// Thrown if the given image is not a 32 bit per pixel image.
/// </exception>
public IEnumerable<Pixel[]> PixelLines
{
get
{
var bitDepth = System.Drawing.Image.GetPixelFormatSize(Image.PixelFormat);
int bitDepth = System.Drawing.Image.GetPixelFormatSize(this.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));
{
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,
this.Image.Palette.Entries.Length));
}
int width = this.Image.Width;
int height = this.Image.Height;
int[] buffer = new int[width];
Pixel[] pixels = new Pixel[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)
for (int pixelIndex = 0; pixelIndex < buffer.Length; pixelIndex++)
{
yield return new Pixel(pixel);
pixels[pixelIndex] = new Pixel(buffer[pixelIndex]);
}
}
finally
{
this.Image.UnlockBits(data);
}
yield return pixels;
}
}
}
public void UpdatePixelIndexes(IEnumerable<byte> indexes)
/// <summary>
/// Updates the pixel indexes.
/// </summary>
/// <param name="lineIndexes">
/// The line indexes.
/// </param>
public void UpdatePixelIndexes(IEnumerable<byte[]> lineIndexes)
{
int width = this.Image.Width;
int height = this.Image.Height;
byte[] buffer = new byte[width];
IEnumerator<byte> indexesIterator = indexes.GetEnumerator();
var indexesIterator = lineIndexes.GetEnumerator();
for (int rowIndex = 0; rowIndex < height; rowIndex++)
{
for (int columnIndex = 0; columnIndex < buffer.Length; columnIndex++)
{
indexesIterator.MoveNext();
buffer[columnIndex] = indexesIterator.Current;
}
indexesIterator.MoveNext();
BitmapData data = this.Image.LockBits(Rectangle.FromLTRB(0, rowIndex, width, rowIndex + 1), ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);
try
{
Marshal.Copy(buffer, 0, data.Scan0, width);
Marshal.Copy(indexesIterator.Current, 0, data.Scan0, width);
}
finally
{
@ -78,4 +109,4 @@ namespace nQuant
}
}
}
}
}

10
src/ImageProcessor/Imaging/Quantizers/WuQuantizer/Lookup.cs

@ -1,10 +0,0 @@
namespace nQuant
{
public class Lookup
{
public int Alpha;
public int Red;
public int Green;
public int Blue;
}
}

52
src/ImageProcessor/Imaging/Quantizers/WuQuantizer/PaletteBuffer.cs

@ -1,52 +0,0 @@
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; }
}
}

73
src/ImageProcessor/Imaging/Quantizers/WuQuantizer/PaletteColorHistory.cs

@ -0,0 +1,73 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="PaletteColorHistory.cs" company="James South">
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// The palette color history.
// Adapted from <see href="https://github.com/drewnoakes" />
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
{
using System.Drawing;
/// <summary>
/// The palette color history.
/// Adapted from <see href="https://github.com/drewnoakes" />
/// </summary>
internal struct PaletteColorHistory
{
/// <summary>
/// The alpha component.
/// </summary>
public int Alpha;
/// <summary>
/// The red component.
/// </summary>
public int Red;
/// <summary>
/// The green component.
/// </summary>
public int Green;
/// <summary>
/// The blue component.
/// </summary>
public int Blue;
/// <summary>
/// The sum of the color components.
/// </summary>
public int Sum;
/// <summary>
/// Normalizes the color.
/// </summary>
/// <returns>
/// The normalized <see cref="Color"/>.
/// </returns>
public Color ToNormalizedColor()
{
return (this.Sum != 0) ? Color.FromArgb(this.Alpha /= this.Sum, this.Red /= this.Sum, this.Green /= this.Sum, this.Blue /= this.Sum) : Color.Empty;
}
/// <summary>
/// Adds a pixel to the color history.
/// </summary>
/// <param name="pixel">
/// The pixel.
/// </param>
public void AddPixel(Pixel pixel)
{
this.Alpha += pixel.Alpha;
this.Red += pixel.Red;
this.Green += pixel.Green;
this.Blue += pixel.Blue;
this.Sum++;
}
}
}

55
src/ImageProcessor/Imaging/Quantizers/WuQuantizer/PaletteLookup.cs

@ -1,46 +1,43 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace nQuant
namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
{
using System;
using System.Collections.Generic;
using System.Linq;
class PaletteLookup
{
private int mMask;
private Dictionary<int, List<LookupNode>> mLookup = new Dictionary<int, List<LookupNode>>(255);
private List<LookupNode> Palette { get; set; }
private Dictionary<int, LookupNode[]> mLookup;
private LookupNode[] Palette { get; set; }
public PaletteLookup(List<Pixel> palette)
public PaletteLookup(Pixel[] palette)
{
Palette = new List<LookupNode>(palette.Count);
for (int paletteIndex = 0; paletteIndex < palette.Count; paletteIndex++)
Palette = new LookupNode[palette.Length];
for (int paletteIndex = 0; paletteIndex < palette.Length; paletteIndex++)
{
Palette.Add(new LookupNode { Pixel = palette[paletteIndex], PaletteIndex = (byte)paletteIndex });
Palette[paletteIndex] = new LookupNode { Pixel = palette[paletteIndex], PaletteIndex = (byte)paletteIndex };
}
BuildLookup(palette);
}
public byte GetPaletteIndex(Pixel pixel)
{
int pixelKey = pixel.Argb & mMask;
List<LookupNode> bucket;
LookupNode[] bucket;
if (!mLookup.TryGetValue(pixelKey, out bucket))
{
bucket = Palette;
}
if (bucket.Count == 1)
if (bucket.Length == 1)
{
return bucket[0].PaletteIndex;
}
int bestDistance = int.MaxValue;
byte bestMatch = 0;
for (int lookupIndex = 0; lookupIndex < bucket.Count; lookupIndex++)
foreach (var lookup in bucket)
{
var lookup = bucket[lookupIndex];
var lookupPixel = lookup.Pixel;
var deltaAlpha = pixel.Alpha - lookupPixel.Alpha;
@ -61,29 +58,41 @@ namespace nQuant
bestDistance = distance;
bestMatch = lookup.PaletteIndex;
}
if ((bucket == Palette) && (pixelKey != 0))
{
mLookup[pixelKey] = new LookupNode[] { bucket[bestMatch] };
}
return bestMatch;
}
private void BuildLookup(List<Pixel> palette)
private void BuildLookup(Pixel[] palette)
{
int mask = GetMask(palette);
Dictionary<int, List<LookupNode>> tempLookup = new Dictionary<int, List<LookupNode>>();
foreach (LookupNode lookup in Palette)
{
int pixelKey = lookup.Pixel.Argb & mask;
List<LookupNode> bucket;
if (!mLookup.TryGetValue(pixelKey, out bucket))
if (!tempLookup.TryGetValue(pixelKey, out bucket))
{
bucket = new List<LookupNode>();
mLookup[pixelKey] = bucket;
tempLookup[pixelKey] = bucket;
}
bucket.Add(lookup);
}
mLookup = new Dictionary<int, LookupNode[]>(tempLookup.Count);
foreach (var key in tempLookup.Keys)
{
mLookup[key] = tempLookup[key].ToArray();
}
mMask = mask;
}
private static int GetMask(List<Pixel> palette)
private static int GetMask(Pixel[] palette)
{
IEnumerable<byte> alphas = from pixel in palette
select pixel.Alpha;
@ -107,12 +116,12 @@ namespace nQuant
double totalUniques = uniqueAlphas + uniqueReds + uniqueGreens + uniqueBlues;
const double AvailableBits = 8f;
double AvailableBits = 1.0 + Math.Log(uniqueAlphas * uniqueReds * uniqueGreens * uniqueBlues);
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)));
byte blueMask = ComputeBitMask(maxBlue, Convert.ToInt32(Math.Round(uniqueBlues / totalUniques * AvailableBits)));
Pixel maskedPixel = new Pixel(alphaMask, redMask, greenMask, blueMask);
return maskedPixel.Argb;

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

@ -1,7 +1,7 @@
namespace nQuant
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
{
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Explicit)]
public struct Pixel
{
@ -12,32 +12,27 @@ namespace nQuant
Red = red;
Green = green;
Blue = blue;
Debug.Assert(Argb == (alpha << 24 | red << 16 | green << 8 | blue));
}
/// <summary>
/// Initializes a new instance of the <see cref="Pixel"/> struct.
/// </summary>
/// <param name="argb">
/// The combined color components.
/// </param>
public Pixel(int argb)
: this()
{
this.Argb = argb;
}
public long Amplitude()
{
return (Alpha * Alpha) + (Red * Red) + (Green * Green) + (Blue * Blue);
Argb = argb;
Debug.Assert(Alpha == ((uint)argb >> 24));
Debug.Assert(Red == ((uint)(argb >> 16) & 255));
Debug.Assert(Green == ((uint)(argb >> 8) & 255));
Debug.Assert(Blue == ((uint)argb & 255));
}
[FieldOffsetAttribute(3)]
[FieldOffset(3)]
public byte Alpha;
[FieldOffsetAttribute(2)]
[FieldOffset(2)]
public byte Red;
[FieldOffsetAttribute(1)]
[FieldOffset(1)]
public byte Green;
[FieldOffsetAttribute(0)]
[FieldOffset(0)]
public byte Blue;
[FieldOffset(0)]
public int Argb;

3
src/ImageProcessor/Imaging/Quantizers/WuQuantizer/QuantizationException.cs

@ -3,8 +3,9 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace nQuant
namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
{
[Serializable]
public class QuantizationException : ApplicationException
{
public QuantizationException(string message) : base(message)

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

@ -1,52 +1,49 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
namespace nQuant
namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
{
using System.Drawing.Imaging;
public class WuQuantizer : WuQuantizerBase, IWuQuantizer
{
private IEnumerable<byte> indexedPixels(ImageBuffer image, List<Pixel> lookups, int alphaThreshold, PaletteBuffer paletteBuffer)
private static IEnumerable<byte[]> IndexedPixels(ImageBuffer image, Pixel[] lookups, int alphaThreshold, PaletteColorHistory[] paletteHistogram)
{
var alphas = paletteBuffer.Alphas;
var reds = paletteBuffer.Reds;
var greens = paletteBuffer.Greens;
var blues = paletteBuffer.Blues;
var sums = paletteBuffer.Sums;
PaletteLookup lookup = new PaletteLookup(lookups);
foreach (Pixel pixel in image.Pixels)
var lineIndexes = new byte[image.Image.Width];
var lookup = new PaletteLookup(lookups);
foreach (var pixelLine in image.PixelLines)
{
byte bestMatch = 255;
if (pixel.Alpha >= alphaThreshold)
for (int pixelIndex = 0; pixelIndex < pixelLine.Length; pixelIndex++)
{
bestMatch = lookup.GetPaletteIndex(pixel);
alphas[bestMatch] += pixel.Alpha;
reds[bestMatch] += pixel.Red;
greens[bestMatch] += pixel.Green;
blues[bestMatch] += pixel.Blue;
sums[bestMatch]++;
Pixel pixel = pixelLine[pixelIndex];
byte bestMatch = AlphaColor;
if (pixel.Alpha >= alphaThreshold)
{
bestMatch = lookup.GetPaletteIndex(pixel);
paletteHistogram[bestMatch].AddPixel(pixel);
}
lineIndexes[pixelIndex] = bestMatch;
}
yield return bestMatch;
yield return lineIndexes;
}
}
internal override Image GetQuantizedImage(ImageBuffer image, int colorCount, List<Pixel> lookups, int alphaThreshold)
internal override Image GetQuantizedImage(ImageBuffer image, int colorCount, 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);
var paletteHistogram = new PaletteColorHistory[colorCount + 1];
resultBuffer.UpdatePixelIndexes(IndexedPixels(image, lookups, alphaThreshold, paletteHistogram));
result.Palette = BuildPalette(result.Palette, paletteHistogram);
return result;
}
private static ColorPalette BuildPalette(ColorPalette palette, PaletteColorHistory[] paletteHistogram)
{
for (int paletteColorIndex = 0; paletteColorIndex < paletteHistogram.Length; paletteColorIndex++)
{
palette.Entries[paletteColorIndex] = paletteHistogram[paletteColorIndex].ToNormalizedColor();
}
return palette;
}
}
}

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

@ -1,15 +1,28 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Runtime.InteropServices;
namespace nQuant
namespace ImageProcessor.Imaging.Quantizers.WuQuantizer
{
public class Histogram
{
private const int SideSize = 33;
internal readonly ColorMoment[, , ,] Moments;
public Histogram()
{
// 47,436,840 bytes
Moments = new ColorMoment[SideSize, SideSize, SideSize, SideSize];
}
internal void Clear()
{
Array.Clear(Moments, 0, SideSize*SideSize*SideSize*SideSize);
}
}
public abstract class WuQuantizerBase
{
private const int MaxColor = 256;
protected const byte AlphaColor = 255;
protected const int Alpha = 3;
protected const int Red = 2;
@ -25,66 +38,82 @@ namespace nQuant
public Image QuantizeImage(Bitmap image, int alphaThreshold, int alphaFader)
{
var colorCount = MaxColor;
ImageBuffer buffer = new ImageBuffer(image);
var data = BuildHistogram(buffer, alphaThreshold, alphaFader);
data = CalculateMoments(data);
var cubes = SplitData(ref colorCount, data);
var lookups = BuildLookups(cubes, data);
return GetQuantizedImage(buffer, colorCount, lookups, alphaThreshold);
return QuantizeImage(image, alphaThreshold, alphaFader, null, 256);
}
public Image QuantizeImage(Bitmap image, int alphaThreshold, int alphaFader, Histogram histogram, int maxColors)
{
var buffer = new ImageBuffer(image);
if (histogram == null)
histogram = new Histogram();
else
histogram.Clear();
BuildHistogram(histogram, buffer, alphaThreshold, alphaFader);
CalculateMoments(histogram.Moments);
var cubes = SplitData(ref maxColors, histogram.Moments);
var lookups = BuildLookups(cubes, histogram.Moments);
return GetQuantizedImage(buffer, maxColors, lookups, alphaThreshold);
}
private static ColorData BuildHistogram(ImageBuffer sourceImage, int alphaThreshold, int alphaFader)
private static void BuildHistogram(Histogram histogram, ImageBuffer sourceImage, int alphaThreshold, int alphaFader)
{
ColorData colorData = new ColorData(MaxSideIndex);
foreach (Pixel pixel in sourceImage.Pixels)
var moments = histogram.Moments;
foreach(var pixelLine in sourceImage.PixelLines)
{
if (pixel.Alpha >= alphaThreshold)
for (int pixelIndex = 0; pixelIndex < pixelLine.Length; pixelIndex++)
{
Pixel indexedPixel = pixel;
if (indexedPixel.Alpha < 255)
Pixel pixel = pixelLine[pixelIndex];
byte pixelAlpha = pixel.Alpha;
if (pixelAlpha >= alphaThreshold)
{
int alpha = pixel.Alpha + (pixel.Alpha % alphaFader);
indexedPixel.Alpha = (byte)(alpha > 255 ? 255 : alpha);
}
if (pixelAlpha < 255)
{
var alpha = pixel.Alpha + (pixel.Alpha % alphaFader);
pixelAlpha = (byte)(alpha > 255 ? 255 : alpha);
}
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);
byte pixelRed = pixel.Red;
byte pixelGreen = pixel.Green;
byte pixelBlue = pixel.Blue;
pixelAlpha = (byte)((pixelAlpha >> 3) + 1);
pixelRed = (byte)((pixelRed >> 3) + 1);
pixelGreen = (byte)((pixelGreen >> 3) + 1);
pixelBlue = (byte)((pixelBlue >> 3) + 1);
moments[pixelAlpha, pixelRed, pixelGreen, pixelBlue].Add(pixel);
}
}
}
return colorData;
}
private static ColorData CalculateMoments(ColorData data)
private static void CalculateMoments(ColorMoment[, , ,] moments)
{
var xarea = new ColorMoment[SideSize, SideSize];
var area = new ColorMoment[SideSize];
var moments = data.Moments;
for (var alphaIndex = 1; alphaIndex <= MaxSideIndex; ++alphaIndex)
for (var alphaIndex = 1; alphaIndex < SideSize; alphaIndex++)
{
for (var redIndex = 1; redIndex <= MaxSideIndex; ++redIndex)
for (var redIndex = 1; redIndex < SideSize; redIndex++)
{
Array.Clear(area, 0, area.Length);
for (var greenIndex = 1; greenIndex <= MaxSideIndex; ++greenIndex)
for (var greenIndex = 1; greenIndex < SideSize; greenIndex++)
{
ColorMoment line = new ColorMoment();
for (var blueIndex = 1; blueIndex <= MaxSideIndex; ++blueIndex)
var line = new ColorMoment();
for (var blueIndex = 1; blueIndex < SideSize; 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];
ColorMoment moment = moments[alphaIndex - 1, redIndex, greenIndex, blueIndex];
moment.AddFast(ref xarea[greenIndex, blueIndex]);
moments[alphaIndex, redIndex, greenIndex, blueIndex] = moment;
}
}
}
}
return data;
}
private static ColorMoment Top(Box cube, int direction, int position, ColorMoment[, , ,] moment)
@ -185,21 +214,19 @@ namespace nQuant
}
}
private static CubeCut Maximize(ColorData data, Box cube, int direction, byte first, byte last, ColorMoment whole)
private static CubeCut Maximize(ColorMoment[, , ,] moments, Box cube, int direction, byte first, byte last, ColorMoment whole)
{
var bottom = Bottom(cube, direction, data.Moments);
float result = 0.0f;
var bottom = Bottom(cube, direction, moments);
var result = 0.0f;
byte? cutPoint = null;
for (byte position = first; position < last; ++position)
for (var position = first; position < last; ++position)
{
var half = bottom + Top(cube, direction, position, data.Moments);
if (half.Weight == 0)
{
continue;
}
var half = bottom + Top(cube, direction, position, moments);
if (half.Weight == 0) continue;
var temp = half.WeightedDistance();
long temp = half.WeightedDistance();
half = whole - half;
if (half.Weight != 0)
{
@ -216,14 +243,14 @@ namespace nQuant
return new CubeCut(cutPoint, result);
}
private bool Cut(ColorData data, ref Box first, ref Box second)
private static bool Cut(ColorMoment[, , ,] moments, ref Box first, ref Box second)
{
int direction;
var whole = Volume(first, data.Moments);
var maxAlpha = Maximize(data, first, Alpha, (byte)(first.AlphaMinimum + 1), first.AlphaMaximum, whole);
var maxRed = Maximize(data, first, Red, (byte)(first.RedMinimum + 1), first.RedMaximum, whole);
var maxGreen = Maximize(data, first, Green, (byte)(first.GreenMinimum + 1), first.GreenMaximum, whole);
var maxBlue = Maximize(data, first, Blue, (byte)(first.BlueMinimum + 1), first.BlueMaximum, whole);
var whole = Volume(first, moments);
var maxAlpha = Maximize(moments, first, Alpha, (byte)(first.AlphaMinimum + 1), first.AlphaMaximum, whole);
var maxRed = Maximize(moments, first, Red, (byte)(first.RedMinimum + 1), first.RedMaximum, whole);
var maxGreen = Maximize(moments, first, Green, (byte)(first.GreenMinimum + 1), first.GreenMaximum, whole);
var maxBlue = Maximize(moments, first, Blue, (byte)(first.BlueMinimum + 1), first.BlueMaximum, whole);
if ((maxAlpha.Value >= maxRed.Value) && (maxAlpha.Value >= maxGreen.Value) && (maxAlpha.Value >= maxBlue.Value))
{
@ -248,28 +275,28 @@ namespace nQuant
switch (direction)
{
case Alpha:
second.AlphaMinimum = first.AlphaMaximum = (byte)maxAlpha.Position;
second.AlphaMinimum = first.AlphaMaximum = (byte) maxAlpha.Position;
second.RedMinimum = first.RedMinimum;
second.GreenMinimum = first.GreenMinimum;
second.BlueMinimum = first.BlueMinimum;
break;
case Red:
second.RedMinimum = first.RedMaximum = (byte)maxRed.Position;
second.RedMinimum = first.RedMaximum = (byte) maxRed.Position;
second.AlphaMinimum = first.AlphaMinimum;
second.GreenMinimum = first.GreenMinimum;
second.BlueMinimum = first.BlueMinimum;
break;
case Green:
second.GreenMinimum = first.GreenMaximum = (byte)maxGreen.Position;
second.GreenMinimum = first.GreenMaximum = (byte) maxGreen.Position;
second.AlphaMinimum = first.AlphaMinimum;
second.RedMinimum = first.RedMinimum;
second.BlueMinimum = first.BlueMinimum;
break;
case Blue:
second.BlueMinimum = first.BlueMaximum = (byte)maxBlue.Position;
second.BlueMinimum = first.BlueMaximum = (byte) maxBlue.Position;
second.AlphaMinimum = first.AlphaMinimum;
second.RedMinimum = first.RedMinimum;
second.GreenMinimum = first.GreenMinimum;
@ -282,9 +309,9 @@ namespace nQuant
return true;
}
private static float CalculateVariance(ColorData data, Box cube)
private static float CalculateVariance(ColorMoment[, , ,] moments, Box cube)
{
ColorMoment volume = Volume(cube, data.Moments);
ColorMoment volume = Volume(cube, moments);
return volume.Variance();
}
@ -309,43 +336,22 @@ namespace nQuant
moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum]);
}
private static float VolumeFloat(Box cube, float[, , ,] moment)
{
return (moment[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMaximum, cube.BlueMaximum] -
moment[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMaximum] -
moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMaximum] +
moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMaximum] -
moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMaximum, cube.BlueMaximum] +
moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMaximum] +
moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMaximum] -
moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMaximum]) -
(moment[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMaximum, cube.BlueMinimum] -
moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMaximum, cube.BlueMinimum] -
moment[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMinimum] +
moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMinimum] -
moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMinimum] +
moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMinimum] +
moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum] -
moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum]);
}
private Box[] SplitData(ref int colorCount, ColorData data)
private static Box[] SplitData(ref int colorCount, ColorMoment[, , ,] moments)
{
--colorCount;
var next = 0;
var volumeVariance = new float[MaxColor];
var cubes = new Box[MaxColor];
var volumeVariance = new float[colorCount];
var cubes = new Box[colorCount];
cubes[0].AlphaMaximum = MaxSideIndex;
cubes[0].RedMaximum = MaxSideIndex;
cubes[0].GreenMaximum = MaxSideIndex;
cubes[0].BlueMaximum = MaxSideIndex;
for (var cubeIndex = 1; cubeIndex < colorCount; ++cubeIndex)
{
if (Cut(data, ref cubes[next], ref cubes[cubeIndex]))
if (Cut(moments, ref cubes[next], ref cubes[cubeIndex]))
{
volumeVariance[next] = cubes[next].Size > 1 ? CalculateVariance(data, cubes[next]) : 0.0f;
volumeVariance[cubeIndex] = cubes[cubeIndex].Size > 1 ? CalculateVariance(data, cubes[cubeIndex]) : 0.0f;
volumeVariance[next] = cubes[next].Size > 1 ? CalculateVariance(moments, cubes[next]) : 0.0f;
volumeVariance[cubeIndex] = cubes[cubeIndex].Size > 1 ? CalculateVariance(moments, cubes[cubeIndex]) : 0.0f;
}
else
{
@ -370,18 +376,15 @@ namespace nQuant
return cubes.Take(colorCount).ToArray();
}
private List<Pixel> BuildLookups(Box[] cubes, ColorData data)
private static Pixel[] BuildLookups(Box[] cubes, ColorMoment[, , ,] moments)
{
List<Pixel> lookups = new List<Pixel>(cubes.Length);
Pixel[] lookups = new Pixel[cubes.Length];
foreach (var cube in cubes)
for (int cubeIndex = 0; cubeIndex < cubes.Length; cubeIndex++)
{
var volume = Volume(cube, data.Moments);
var volume = Volume(cubes[cubeIndex], moments);
if (volume.Weight <= 0)
{
continue;
}
if (volume.Weight <= 0) continue;
var lookup = new Pixel
{
@ -390,13 +393,11 @@ namespace nQuant
Green = (byte)(volume.Green / volume.Weight),
Blue = (byte)(volume.Blue / volume.Weight)
};
lookups.Add(lookup);
lookups[cubeIndex] = lookup;
}
return lookups;
}
internal abstract Image GetQuantizedImage(ImageBuffer image, int colorCount, List<Pixel> lookups, int alphaThreshold);
internal abstract Image GetQuantizedImage(ImageBuffer image, int colorCount, Pixel[] lookups, int alphaThreshold);
}
}
Loading…
Cancel
Save