Browse Source

Begin Wu Quantizer

Former-commit-id: 3c6b59e30ffdbbc752829bd2f76675b4a18815ed
Former-commit-id: acfc7974999bfee275e867b5289724e697164e0c
af/merge-core
James South 11 years ago
parent
commit
a072a20ba9
  1. 12
      src/ImageProcessor.Playground/Program.cs
  2. 4
      src/ImageProcessor.Playground/images/input/circle.png
  3. 15
      src/ImageProcessor.UnitTests/Imaging/CropLayerUnitTests.cs
  4. 24
      src/ImageProcessor/Common/Extensions/ImageExtensions.cs
  5. 19
      src/ImageProcessor/ImageProcessor.csproj
  6. 38
      src/ImageProcessor/Imaging/Colors/Color32.cs
  7. 24
      src/ImageProcessor/Imaging/Formats/PngFormat.cs
  8. 49
      src/ImageProcessor/Imaging/Helpers/Adjustments.cs
  9. 55
      src/ImageProcessor/Imaging/ImageLayer.cs
  10. 6
      src/ImageProcessor/Imaging/Quantizers/OctreeQuantizer.cs
  11. 15
      src/ImageProcessor/Imaging/Quantizers/WuQuantizer/Box.cs
  12. 92
      src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ColorData.cs
  13. 73
      src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ColorMoment.cs
  14. 14
      src/ImageProcessor/Imaging/Quantizers/WuQuantizer/CubeCut.cs
  15. 9
      src/ImageProcessor/Imaging/Quantizers/WuQuantizer/IWuQuantizer.cs
  16. 10
      src/ImageProcessor/Imaging/Quantizers/WuQuantizer/Lookup.cs
  17. 50
      src/ImageProcessor/Imaging/Quantizers/WuQuantizer/Pixel.cs
  18. 15
      src/ImageProcessor/Imaging/Quantizers/WuQuantizer/QuantizationException.cs
  19. 17
      src/ImageProcessor/Imaging/Quantizers/WuQuantizer/QuantizedPalette.cs
  20. 91
      src/ImageProcessor/Imaging/Quantizers/WuQuantizer/WuQuantizer.cs
  21. 506
      src/ImageProcessor/Imaging/Quantizers/WuQuantizer/WuQuantizerBase.cs
  22. 34
      src/ImageProcessor/Processors/Alpha.cs
  23. 32
      src/ImageProcessor/Processors/Overlay.cs

12
src/ImageProcessor.Playground/Program.cs

@ -50,7 +50,7 @@ namespace ImageProcessor.PlayGround
}
Image mask = Image.FromFile(Path.Combine(resolvedPath, "mask2.png"));
IEnumerable<FileInfo> files = GetFilesByExtensions(di, ".gif");
IEnumerable<FileInfo> files = GetFilesByExtensions(di, ".png");
//IEnumerable<FileInfo> files = GetFilesByExtensions(di, ".gif", ".webp", ".bmp", ".jpg", ".png", ".tif");
foreach (FileInfo fileInfo in files)
@ -75,6 +75,7 @@ namespace ImageProcessor.PlayGround
//};
// Load, resize, set the format and quality and save an image.
imageFactory.Load(inStream)
//.Alpha(50)
//.BackgroundColor(Color.White)
//.Resize(new Size((int)(size.Width * 1.1), 0))
//.ContentAwareResize(layer)
@ -86,8 +87,8 @@ namespace ImageProcessor.PlayGround
//.ReplaceColor(Color.FromArgb(255, 1, 107, 165), Color.FromArgb(255, 1, 165, 13), 80)
//.Resize(size)
// .Resize(new ResizeLayer(size, ResizeMode.Max))
// .Resize(new ResizeLayer(size, ResizeMode.Stretch))
.DetectEdges(new SobelEdgeFilter(), true)
// .Resize(new ResizeLayer(size, ResizeMode.Stretch))
//.DetectEdges(new SobelEdgeFilter(), true)
//.DetectEdges(new LaplacianOfGaussianEdgeFilter())
//.EntropyCrop()
//.Filter(MatrixFilters.Invert)
@ -97,13 +98,16 @@ namespace ImageProcessor.PlayGround
//.Filter(MatrixFilters.HiSatch)
//.Pixelate(8)
//.GaussianSharpen(10)
.Format(new PngFormat() { IsIndexed = true })
.Save(Path.GetFullPath(Path.Combine(Path.GetDirectoryName(path), @"..\..\images\output", fileInfo.Name)));
stopwatch.Stop();
}
}
Console.WriteLine("Processed: " + fileInfo.Name + " in " + stopwatch.ElapsedMilliseconds + "ms");
Console.WriteLine(@"Completed {0} in {1:s\.fff} secs with peak memory usage of {2}.", fileInfo.Name, stopwatch.Elapsed, Process.GetCurrentProcess().PeakWorkingSet64.ToString("#,#"));
//Console.WriteLine("Processed: " + fileInfo.Name + " in " + stopwatch.ElapsedMilliseconds + "ms");
}
Console.ReadLine();

4
src/ImageProcessor.Playground/images/input/circle.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:73f4d8e08292df487a457be0765cb7994f2d91600b1824170a82e8f20f0a6d0b
size 5089
oid sha256:33ceb5405f4f51976646815e3a90bcdbd7d5fcc8773db595001e4ca83a22fc24
size 1738

15
src/ImageProcessor.UnitTests/Imaging/CropLayerUnitTests.cs

@ -19,6 +19,21 @@ namespace ImageProcessor.UnitTests.Imaging
/// <summary>
/// Tests that the constructor saves the provided data
/// </summary>
/// <param name="left">
/// The left position.
/// </param>
/// <param name="top">
/// The top position.
/// </param>
/// <param name="right">
/// The right position.
/// </param>
/// <param name="bottom">
/// The bottom position.
/// </param>
/// <param name="mode">
/// The <see cref="CropMode"/>.
/// </param>
[Test]
[TestCase(10.5F, 11.2F, 15.6F, 108.9F, CropMode.Percentage)]
[TestCase(15.1F, 20.7F, 65.8F, 156.7F, CropMode.Pixels)]

24
src/ImageProcessor/Common/Extensions/ImageExtensions.cs

@ -0,0 +1,24 @@

namespace ImageProcessor.Common.Extensions
{
using System.Drawing;
using System.Drawing.Imaging;
public static class ImageExtensions
{
public static Image ChangePixelFormat(this Image image, PixelFormat format)
{
Bitmap clone = new Bitmap(image.Width, image.Height, format);
clone.SetResolution(image.HorizontalResolution, image.VerticalResolution);
using (Graphics graphics = Graphics.FromImage(clone))
{
graphics.DrawImage(image, new Rectangle(0, 0, clone.Width, clone.Height));
}
image = new Bitmap(clone);
return image;
}
}
}

19
src/ImageProcessor/ImageProcessor.csproj

@ -126,6 +126,7 @@
<ItemGroup>
<Compile Include="Common\Extensions\AssemblyExtensions.cs" />
<Compile Include="Common\Extensions\EnumerableExtensions.cs" />
<Compile Include="Common\Extensions\ImageExtensions.cs" />
<Compile Include="Configuration\ImageProcessorBootstrapper.cs" />
<Compile Include="Common\Exceptions\ImageProcessingException.cs" />
<Compile Include="Common\Extensions\DoubleExtensions.cs" />
@ -160,6 +161,7 @@
<Compile Include="ImageFactory.cs" />
<Compile Include="Imaging\AnchorPosition.cs" />
<Compile Include="Imaging\Helpers\ImageMaths.cs" />
<Compile Include="Imaging\ImageLayer.cs" />
<Compile Include="Imaging\MetaData\ExifPropertyTagType.cs" />
<Compile Include="Imaging\Convolution.cs" />
<Compile Include="Imaging\CropLayer.cs" />
@ -182,6 +184,19 @@
<Compile Include="Imaging\Helpers\Effects.cs" />
<Compile Include="Imaging\Quantizers\OctreeQuantizer.cs" />
<Compile Include="Imaging\Quantizers\Quantizer.cs" />
<Compile Include="Imaging\Quantizers\WuQuantizer\Box.cs" />
<Compile Include="Imaging\Quantizers\WuQuantizer\ColorData.cs" />
<Compile Include="Imaging\Quantizers\WuQuantizer\ColorMoment.cs" />
<Compile Include="Imaging\Quantizers\WuQuantizer\CubeCut.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>
<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\WuQuantizerBase.cs" />
<Compile Include="Imaging\ResizeLayer.cs" />
<Compile Include="Imaging\Filters\Photo\BlackWhiteMatrixFilter.cs" />
<Compile Include="Imaging\Filters\Photo\ColorMatrixes.cs" />
@ -230,7 +245,9 @@
<Compile Include="Processors\Watermark.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup />
<ItemGroup>
<WCFMetadata Include="Service References\" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
</Project>

38
src/ImageProcessor/Imaging/Colors/Color32.cs

@ -52,7 +52,43 @@ namespace ImageProcessor.Imaging.Colors
/// Permits the color32 to be treated as a 32 bit integer.
/// </summary>
[FieldOffset(0)]
public int ARGB;
public int Argb;
/// <summary>
/// Initializes a new instance of the <see cref="Color32"/> struct.
/// </summary>
/// <param name="alpha">
/// The alpha component.
/// </param>
/// <param name="red">
/// The red component.
/// </param>
/// <param name="green">
/// The green component.
/// </param>
/// <param name="blue">
/// The blue component.
/// </param>
public Color32(byte alpha, byte red, byte green, byte blue)
: this()
{
this.A = alpha;
this.R = red;
this.G = green;
this.B = blue;
}
/// <summary>
/// Initializes a new instance of the <see cref="Color32"/> struct.
/// </summary>
/// <param name="argb">
/// The combined color components.
/// </param>
public Color32(int argb)
: this()
{
this.Argb = argb;
}
/// <summary>
/// Gets the color for this Color32 object

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

@ -14,8 +14,11 @@ namespace ImageProcessor.Imaging.Formats
using System.Drawing.Imaging;
using System.IO;
using ImageProcessor.Common.Extensions;
using ImageProcessor.Imaging.Quantizers;
using nQuant;
/// <summary>
/// Provides the necessary information to support png images.
/// </summary>
@ -98,7 +101,26 @@ namespace ImageProcessor.Imaging.Formats
{
if (this.IsIndexed)
{
image = new OctreeQuantizer(255, 8).Quantize(image);
// 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);
using (Graphics graphics = Graphics.FromImage(clone))
{
graphics.Clear(Color.Transparent);
graphics.DrawImage(image, new Rectangle(0, 0, clone.Width, clone.Height));
}
image.Dispose();
image = new WuQuantizer().QuantizeImage(clone);
//}
//else
//{
// image = new WuQuantizer().QuantizeImage((Bitmap)image);
//}
}
return base.Save(path, image);

49
src/ImageProcessor/Imaging/Helpers/Adjustments.cs

@ -19,6 +19,53 @@ namespace ImageProcessor.Imaging.Helpers
/// </summary>
public static class Adjustments
{
/// <summary>
/// Adjusts the alpha component of the given image.
/// </summary>
/// <param name="source">
/// The <see cref="Image"/> source to adjust.
/// </param>
/// <param name="percentage">
/// The percentage value between 0 and 100 for adjusting the opacity.
/// </param>
/// <param name="rectangle">The rectangle to define the bounds of the area to adjust the opacity.
/// If null then the effect is applied to the entire image.</param>
/// <returns>
/// The <see cref="Bitmap"/> with the alpha component adjusted.
/// </returns>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown if the percentage value falls outside the acceptable range.
/// </exception>
public static Bitmap Alpha(Image source, int percentage, Rectangle? rectangle = null)
{
if (percentage > 100 || percentage < 0)
{
throw new ArgumentOutOfRangeException("percentage", "Percentage should be between 0 and 100.");
}
Rectangle bounds = rectangle.HasValue ? rectangle.Value : new Rectangle(0, 0, source.Width, source.Height);
ColorMatrix colorMatrix = new ColorMatrix();
colorMatrix.Matrix00 = colorMatrix.Matrix11 = colorMatrix.Matrix22 = colorMatrix.Matrix44 = 1;
colorMatrix.Matrix33 = (float)percentage / 100;
Bitmap alpha = new Bitmap(source.Width, source.Height);
alpha.SetResolution(source.HorizontalResolution, source.VerticalResolution);
using (Graphics graphics = Graphics.FromImage(alpha))
{
graphics.Clear(Color.Transparent);
using (ImageAttributes imageAttributes = new ImageAttributes())
{
imageAttributes.SetColorMatrix(colorMatrix);
graphics.DrawImage(source, bounds, 0, 0, source.Width, source.Height, GraphicsUnit.Pixel, imageAttributes);
}
}
source.Dispose();
return alpha;
}
/// <summary>
/// Adjusts the brightness component of the given image.
/// </summary>
@ -101,7 +148,7 @@ namespace ImageProcessor.Imaging.Helpers
contrastFactor++;
float factorTransform = 0.5f * (1.0f - contrastFactor);
ColorMatrix colorMatrix =
ColorMatrix colorMatrix =
new ColorMatrix(
new[]
{

55
src/ImageProcessor/Imaging/ImageLayer.cs

@ -1,4 +1,12 @@

// --------------------------------------------------------------------------------------------------------------------
// <copyright file="ImageLayer.cs" company="James South">
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// Encapsulates the properties required to add an image layer to an image.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Imaging
{
@ -46,5 +54,50 @@ namespace ImageProcessor.Imaging
get { return this.position; }
set { this.position = value; }
}
/// <summary>
/// Returns a value that indicates whether the specified object is an
/// <see cref="TextLayer"/> object that is equivalent to
/// this <see cref="TextLayer"/> object.
/// </summary>
/// <param name="obj">
/// The object to test.
/// </param>
/// <returns>
/// True if the given object is an <see cref="TextLayer"/> object that is equivalent to
/// this <see cref="TextLayer"/> object; otherwise, false.
/// </returns>
public override bool Equals(object obj)
{
ImageLayer imageLayer = obj as ImageLayer;
if (imageLayer == null)
{
return false;
}
return this.Image == imageLayer.Image
&& this.Size == imageLayer.Size
&& this.Opacity == imageLayer.Opacity
&& this.Position == imageLayer.Position;
}
/// <summary>
/// Returns the hash code for this instance.
/// </summary>
/// <returns>
/// A 32-bit signed integer that is the hash code for this instance.
/// </returns>
public override int GetHashCode()
{
unchecked
{
int hashCode = this.Image.GetHashCode();
hashCode = (hashCode * 397) ^ this.Size.GetHashCode();
hashCode = (hashCode * 397) ^ this.Opacity;
hashCode = (hashCode * 397) ^ this.Position.GetHashCode();
return hashCode;
}
}
}
}

6
src/ImageProcessor/Imaging/Quantizers/OctreeQuantizer.cs

@ -203,13 +203,13 @@ namespace ImageProcessor.Imaging.Quantizers
public void AddColor(Color32* pixel)
{
// Check if this request is for the same color as the last
if (this.previousColor == pixel->ARGB)
if (this.previousColor == pixel->Argb)
{
// If so, check if I have a previous node setup. This will only occur if the first color in the image
// happens to be black, with an alpha component of zero.
if (null == this.previousNode)
{
this.previousColor = pixel->ARGB;
this.previousColor = pixel->Argb;
this.root.AddColor(pixel, this.maxColorBits, 0, this);
}
else
@ -220,7 +220,7 @@ namespace ImageProcessor.Imaging.Quantizers
}
else
{
this.previousColor = pixel->ARGB;
this.previousColor = pixel->Argb;
this.root.AddColor(pixel, this.maxColorBits, 0, this);
}
}

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

@ -0,0 +1,15 @@
namespace nQuant
{
public struct Box
{
public byte AlphaMinimum;
public byte AlphaMaximum;
public byte RedMinimum;
public byte RedMaximum;
public byte GreenMinimum;
public byte GreenMaximum;
public byte BlueMinimum;
public byte BlueMaximum;
public int Size;
}
}

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

@ -0,0 +1,92 @@
// --------------------------------------------------------------------------------------------------------------------
// <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>
/// 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>
/// Initializes a new instance of the <see cref="ColorData"/> class.
/// </summary>
/// <param name="dataGranularity">
/// The data granularity.
/// </param>
/// <param name="bitmapWidth">
/// The bitmap width.
/// </param>
/// <param name="bitmapHeight">
/// The bitmap height.
/// </param>
public ColorData(int dataGranularity, int bitmapWidth, int bitmapHeight)
{
dataGranularity++;
this.Moments = new ColorMoment[dataGranularity, dataGranularity, dataGranularity, dataGranularity];
this.pixelsCount = bitmapWidth * bitmapHeight;
this.pixels = new Pixel[this.pixelsCount];
}
/// <summary>
/// Gets the moments.
/// </summary>
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;
}
}
}

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

@ -0,0 +1,73 @@

namespace nQuant
{
public struct ColorMoment
{
public long Alpha;
public long Red;
public long Green;
public long Blue;
public int Weight;
public float Moment;
public static ColorMoment operator +(ColorMoment c1, ColorMoment c2)
{
c1.Alpha += c2.Alpha;
c1.Red += c2.Red;
c1.Green += c2.Green;
c1.Blue += c2.Blue;
c1.Weight += c2.Weight;
c1.Moment += c2.Moment;
return c1;
}
public static ColorMoment operator -(ColorMoment c1, ColorMoment c2)
{
c1.Alpha -= c2.Alpha;
c1.Red -= c2.Red;
c1.Green -= c2.Green;
c1.Blue -= c2.Blue;
c1.Weight -= c2.Weight;
c1.Moment -= c2.Moment;
return c1;
}
public static ColorMoment operator -(ColorMoment c1)
{
c1.Alpha = -c1.Alpha;
c1.Red = -c1.Red;
c1.Green = -c1.Green;
c1.Blue = -c1.Blue;
c1.Weight = -c1.Weight;
c1.Moment = -c1.Moment;
return c1;
}
public static ColorMoment operator +(ColorMoment m, 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;
}
public long Distance()
{
return (Alpha * Alpha) + (Red * Red) + (Green * Green) + (Blue * Blue);
}
public long WeightedDistance()
{
return Distance() / Weight;
}
public float Variance()
{
var result = Moment - ((float)Distance() / Weight);
return float.IsNaN(result) ? 0.0f : result;
}
}
}

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

@ -0,0 +1,14 @@
namespace nQuant
{
internal struct CubeCut
{
public readonly byte? Position;
public readonly float Value;
public CubeCut(byte? cutPoint, float result)
{
Position = cutPoint;
Value = result;
}
}
}

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

@ -0,0 +1,9 @@
using System.Drawing;
namespace nQuant
{
public interface IWuQuantizer
{
Image QuantizeImage(Bitmap image, int alphaThreshold, int alphaFader);
}
}

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

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

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

@ -0,0 +1,50 @@
namespace nQuant
{
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Explicit)]
public struct Pixel
{
public Pixel(byte alpha, byte red, byte green, byte blue)
: this()
{
Alpha = alpha;
Red = red;
Green = green;
Blue = 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 Distance()
{
return (Alpha * Alpha) + (Red * Red) + (Green * Green) + (Blue * Blue);
}
[FieldOffsetAttribute(3)]
public byte Alpha;
[FieldOffsetAttribute(2)]
public byte Red;
[FieldOffsetAttribute(1)]
public byte Green;
[FieldOffsetAttribute(0)]
public byte Blue;
[FieldOffset(0)]
public int Argb;
public override string ToString()
{
return string.Format("Alpha:{0} Red:{1} Green:{2} Blue:{3}", Alpha, Red, Green, Blue);
}
}
}

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

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace nQuant
{
public class QuantizationException : ApplicationException
{
public QuantizationException(string message) : base(message)
{
}
}
}

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

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

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

@ -0,0 +1,91 @@
using System;
using System.Collections.Generic;
using System.Drawing;
namespace nQuant
{
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<Pixel> pixels = data.Pixels;
Dictionary<int, byte> cachedMaches = new Dictionary<int, byte>();
for (int pixelIndex = 0; pixelIndex < pixelsCount; pixelIndex++)
{
Pixel pixel = pixels[pixelIndex];
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;
}
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);
}
palette.Colors.Add(Color.FromArgb(0, 0, 0, 0));
return palette;
}
}
}

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

@ -0,0 +1,506 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Runtime.InteropServices;
namespace nQuant
{
public abstract class WuQuantizerBase
{
private const int MaxColor = 256;
protected const byte AlphaColor = 255;
protected const int Alpha = 3;
protected const int Red = 2;
protected const int Green = 1;
protected const int Blue = 0;
private const int SideSize = 33;
private const int MaxSideIndex = 32;
public Image QuantizeImage(Bitmap image)
{
return QuantizeImage(image, 10, 70);
}
public Image QuantizeImage(Bitmap image, int alphaThreshold, int alphaFader)
{
var colorCount = MaxColor;
var data = BuildHistogram(image, alphaThreshold, alphaFader);
data = CalculateMoments(data);
var cubes = SplitData(ref colorCount, data);
var palette = GetQuantizedPalette(colorCount, data, cubes, alphaThreshold);
return ProcessImagePixels(image, palette);
}
private static Bitmap ProcessImagePixels(Image sourceImage, QuantizedPalette palette)
{
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
{
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++)
{
var targetIndex = 0;
for (var x = 0; x < resultWidth; x++)
{
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;
}
Marshal.Copy(targetBuffer, 0, targetData.Scan0 + (targetByteLength * y), targetByteLength);
}
}
finally
{
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;
}
private static ColorData CalculateMoments(ColorData data)
{
var xarea = new ColorMoment[SideSize, SideSize];
var xPreviousArea = new ColorMoment[SideSize, SideSize];
var area = new ColorMoment[SideSize];
for (var alphaIndex = 1; alphaIndex <= MaxSideIndex; ++alphaIndex)
{
for (var redIndex = 1; redIndex <= MaxSideIndex; ++redIndex)
{
Array.Clear(area, 0, area.Length);
for (var greenIndex = 1; greenIndex <= MaxSideIndex; ++greenIndex)
{
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];
}
}
var temp = xarea;
xarea = xPreviousArea;
xPreviousArea = temp;
}
}
return data;
}
private static ColorMoment Top(Box cube, int direction, int position, ColorMoment[, , ,] moment)
{
switch (direction)
{
case Alpha:
return (moment[position, cube.RedMaximum, cube.GreenMaximum, cube.BlueMaximum] -
moment[position, cube.RedMaximum, cube.GreenMinimum, cube.BlueMaximum] -
moment[position, cube.RedMinimum, cube.GreenMaximum, cube.BlueMaximum] +
moment[position, cube.RedMinimum, cube.GreenMinimum, cube.BlueMaximum]) -
(moment[position, cube.RedMaximum, cube.GreenMaximum, cube.BlueMinimum] -
moment[position, cube.RedMaximum, cube.GreenMinimum, cube.BlueMinimum] -
moment[position, cube.RedMinimum, cube.GreenMaximum, cube.BlueMinimum] +
moment[position, cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum]);
case Red:
return (moment[cube.AlphaMaximum, position, cube.GreenMaximum, cube.BlueMaximum] -
moment[cube.AlphaMaximum, position, cube.GreenMinimum, cube.BlueMaximum] -
moment[cube.AlphaMinimum, position, cube.GreenMaximum, cube.BlueMaximum] +
moment[cube.AlphaMinimum, position, cube.GreenMinimum, cube.BlueMaximum]) -
(moment[cube.AlphaMaximum, position, cube.GreenMaximum, cube.BlueMinimum] -
moment[cube.AlphaMaximum, position, cube.GreenMinimum, cube.BlueMinimum] -
moment[cube.AlphaMinimum, position, cube.GreenMaximum, cube.BlueMinimum] +
moment[cube.AlphaMinimum, position, cube.GreenMinimum, cube.BlueMinimum]);
case Green:
return (moment[cube.AlphaMaximum, cube.RedMaximum, position, cube.BlueMaximum] -
moment[cube.AlphaMaximum, cube.RedMinimum, position, cube.BlueMaximum] -
moment[cube.AlphaMinimum, cube.RedMaximum, position, cube.BlueMaximum] +
moment[cube.AlphaMinimum, cube.RedMinimum, position, cube.BlueMaximum]) -
(moment[cube.AlphaMaximum, cube.RedMaximum, position, cube.BlueMinimum] -
moment[cube.AlphaMaximum, cube.RedMinimum, position, cube.BlueMinimum] -
moment[cube.AlphaMinimum, cube.RedMaximum, position, cube.BlueMinimum] +
moment[cube.AlphaMinimum, cube.RedMinimum, position, cube.BlueMinimum]);
case Blue:
return (moment[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMaximum, position] -
moment[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMinimum, position] -
moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMaximum, position] +
moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMinimum, position]) -
(moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMaximum, position] -
moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMinimum, position] -
moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMaximum, position] +
moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMinimum, position]);
default:
return new ColorMoment();
}
}
private static ColorMoment Bottom(Box cube, int direction, ColorMoment[, , ,] moment)
{
switch (direction)
{
case Alpha:
return (-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.AlphaMinimum, cube.RedMaximum, cube.GreenMaximum, cube.BlueMinimum] +
moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMinimum] +
moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMinimum] -
moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum]);
case Red:
return (-moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMaximum] +
moment[cube.AlphaMaximum, cube.RedMinimum, 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.RedMinimum, cube.GreenMaximum, cube.BlueMinimum] +
moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum] +
moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMinimum] -
moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum]);
case Green:
return (-moment[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMaximum] +
moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMaximum] +
moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMaximum] -
moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMaximum]) -
(-moment[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMinimum] +
moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum] +
moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMinimum] -
moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum]);
case Blue:
return (-moment[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMaximum, cube.BlueMinimum] +
moment[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMinimum] +
moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMinimum] -
moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum]) -
(-moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMaximum, cube.BlueMinimum] +
moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMinimum] +
moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMinimum] -
moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum]);
default:
return new ColorMoment();
}
}
private static CubeCut Maximize(ColorData data, Box cube, int direction, byte first, byte last, ColorMoment whole)
{
var bottom = Bottom(cube, direction, data.Moments);
float result = 0.0f;
byte? cutPoint = null;
for (byte position = first; position < last; ++position)
{
var half = bottom + Top(cube, direction, position, data.Moments);
if (half.Weight == 0)
{
continue;
}
long temp = half.WeightedDistance();
half = whole - half;
if (half.Weight != 0)
{
temp += half.WeightedDistance();
if (temp > result)
{
result = temp;
cutPoint = position;
}
}
}
return new CubeCut(cutPoint, result);
}
private bool Cut(ColorData data, 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);
if ((maxAlpha.Value >= maxRed.Value) && (maxAlpha.Value >= maxGreen.Value) && (maxAlpha.Value >= maxBlue.Value))
{
direction = Alpha;
if (maxAlpha.Position == null) return false;
}
else if ((maxRed.Value >= maxAlpha.Value) && (maxRed.Value >= maxGreen.Value) && (maxRed.Value >= maxBlue.Value))
direction = Red;
else
{
if ((maxGreen.Value >= maxAlpha.Value) && (maxGreen.Value >= maxRed.Value) && (maxGreen.Value >= maxBlue.Value))
direction = Green;
else
direction = Blue;
}
second.AlphaMaximum = first.AlphaMaximum;
second.RedMaximum = first.RedMaximum;
second.GreenMaximum = first.GreenMaximum;
second.BlueMaximum = first.BlueMaximum;
switch (direction)
{
case Alpha:
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.AlphaMinimum = first.AlphaMinimum;
second.GreenMinimum = first.GreenMinimum;
second.BlueMinimum = first.BlueMinimum;
break;
case Green:
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.AlphaMinimum = first.AlphaMinimum;
second.RedMinimum = first.RedMinimum;
second.GreenMinimum = first.GreenMinimum;
break;
}
first.Size = (first.AlphaMaximum - first.AlphaMinimum) * (first.RedMaximum - first.RedMinimum) * (first.GreenMaximum - first.GreenMinimum) * (first.BlueMaximum - first.BlueMinimum);
second.Size = (second.AlphaMaximum - second.AlphaMinimum) * (second.RedMaximum - second.RedMinimum) * (second.GreenMaximum - second.GreenMinimum) * (second.BlueMaximum - second.BlueMinimum);
return true;
}
private static float CalculateVariance(ColorData data, Box cube)
{
ColorMoment volume = Volume(cube, data.Moments);
return volume.Variance();
}
private static ColorMoment Volume(Box cube, ColorMoment[, , ,] 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 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)
{
--colorCount;
var next = 0;
var volumeVariance = new float[MaxColor];
var cubes = new Box[MaxColor];
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]))
{
volumeVariance[next] = cubes[next].Size > 1 ? CalculateVariance(data, cubes[next]) : 0.0f;
volumeVariance[cubeIndex] = cubes[cubeIndex].Size > 1 ? CalculateVariance(data, cubes[cubeIndex]) : 0.0f;
}
else
{
volumeVariance[next] = 0.0f;
cubeIndex--;
}
next = 0;
var temp = volumeVariance[0];
for (var index = 1; index <= cubeIndex; ++index)
{
if (volumeVariance[index] <= temp) continue;
temp = volumeVariance[index];
next = index;
}
if (temp > 0.0) continue;
colorCount = cubeIndex + 1;
break;
}
return cubes.Take(colorCount).ToArray();
}
protected Lookup[] BuildLookups(Box[] cubes, ColorData data)
{
List<Lookup> lookups = new List<Lookup>(cubes.Length);
foreach (var cube in cubes)
{
var volume = Volume(cube, data.Moments);
if (volume.Weight <= 0)
{
continue;
}
var lookup = new Lookup
{
Alpha = (int)(volume.Alpha / volume.Weight),
Red = (int)(volume.Red / volume.Weight),
Green = (int)(volume.Green / volume.Weight),
Blue = (int)(volume.Blue / volume.Weight)
};
lookups.Add(lookup);
}
return lookups.ToArray();
}
protected abstract QuantizedPalette GetQuantizedPalette(int colorCount, ColorData data, Box[] cubes, int alphaThreshold);
}
}

34
src/ImageProcessor/Processors/Alpha.cs

@ -13,9 +13,9 @@ namespace ImageProcessor.Processors
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using ImageProcessor.Common.Exceptions;
using ImageProcessor.Imaging.Helpers;
/// <summary>
/// Encapsulates methods to change the alpha component of the image to effect its transparency.
@ -65,35 +65,13 @@ namespace ImageProcessor.Processors
try
{
int alphaPercent = this.DynamicParameter;
int percentage = this.DynamicParameter;
newImage = new Bitmap(image.Width, image.Height);
newImage.SetResolution(image.HorizontalResolution, image.VerticalResolution);
newImage = new Bitmap(image);
newImage = Adjustments.Alpha(newImage, percentage);
ColorMatrix colorMatrix = new ColorMatrix();
colorMatrix.Matrix00 = colorMatrix.Matrix11 = colorMatrix.Matrix22 = colorMatrix.Matrix44 = 1;
colorMatrix.Matrix33 = (float)alphaPercent / 100;
using (Graphics graphics = Graphics.FromImage(newImage))
{
using (ImageAttributes imageAttributes = new ImageAttributes())
{
imageAttributes.SetColorMatrix(colorMatrix);
graphics.DrawImage(
image,
new Rectangle(0, 0, image.Width, image.Height),
0,
0,
image.Width,
image.Height,
GraphicsUnit.Pixel,
imageAttributes);
image.Dispose();
image = newImage;
}
}
image.Dispose();
image = newImage;
}
catch (Exception ex)
{

32
src/ImageProcessor/Processors/Overlay.cs

@ -14,6 +14,9 @@ namespace ImageProcessor.Processors
using System.Collections.Generic;
using System.Drawing;
using ImageProcessor.Common.Exceptions;
using ImageProcessor.Imaging;
/// <summary>
/// Adds an image overlay to the current image.
/// </summary>
@ -57,7 +60,34 @@ namespace ImageProcessor.Processors
/// </returns>
public Image ProcessImage(ImageFactory factory)
{
throw new NotImplementedException();
Bitmap newImage = null;
Image image = factory.Image;
try
{
newImage = new Bitmap(image);
ImageLayer imageLayer = this.DynamicParameter;
Image overlay = imageLayer.Image;
Size size = imageLayer.Size;
int opacity = Math.Min((int)Math.Ceiling((imageLayer.Opacity / 100f) * 255), 255);
image.Dispose();
image = newImage;
}
catch (Exception ex)
{
if (newImage != null)
{
newImage.Dispose();
}
throw new ImageProcessingException("Error processing image with " + this.GetType().Name, ex);
}
return image;
}
}
}

Loading…
Cancel
Save