diff --git a/src/ImageProcessorCore/Formats/Gif/GifEncoder.cs b/src/ImageProcessorCore/Formats/Gif/GifEncoder.cs
index 981dfb241..0ef36ebd2 100644
--- a/src/ImageProcessorCore/Formats/Gif/GifEncoder.cs
+++ b/src/ImageProcessorCore/Formats/Gif/GifEncoder.cs
@@ -10,6 +10,8 @@ namespace ImageProcessorCore.Formats
using System.Linq;
using System.Threading.Tasks;
+ using ImageProcessorCore.Quantizers;
+
///
/// Image encoder for writing image data to a stream in gif format.
///
@@ -21,6 +23,8 @@ namespace ImageProcessorCore.Formats
/// For gifs the value ranges from 1 to 256.
public int Quality { get; set; }
+ public IQuantizer Quantizer { get; set; }
+
///
public string Extension => "gif";
@@ -35,7 +39,7 @@ namespace ImageProcessorCore.Formats
extension = extension.StartsWith(".") ? extension.Substring(1) : extension;
return extension.Equals(this.Extension, StringComparison.OrdinalIgnoreCase);
}
-
+
///
public void Encode(ImageBase imageBase, Stream stream)
{
diff --git a/src/ImageProcessorCore/Formats/Quantizers/IQuantizer.cs b/src/ImageProcessorCore/Quantizers/IQuantizer.cs
similarity index 94%
rename from src/ImageProcessorCore/Formats/Quantizers/IQuantizer.cs
rename to src/ImageProcessorCore/Quantizers/IQuantizer.cs
index b54334a10..49ef056cd 100644
--- a/src/ImageProcessorCore/Formats/Quantizers/IQuantizer.cs
+++ b/src/ImageProcessorCore/Quantizers/IQuantizer.cs
@@ -3,7 +3,7 @@
// Licensed under the Apache License, Version 2.0.
//
-namespace ImageProcessorCore.Formats
+namespace ImageProcessorCore.Quantizers
{
///
/// Provides methods for allowing quantization of images pixels.
diff --git a/src/ImageProcessorCore/Formats/Quantizers/Octree/OctreeQuantizer.cs b/src/ImageProcessorCore/Quantizers/Octree/OctreeQuantizer.cs
similarity index 97%
rename from src/ImageProcessorCore/Formats/Quantizers/Octree/OctreeQuantizer.cs
rename to src/ImageProcessorCore/Quantizers/Octree/OctreeQuantizer.cs
index b064fc7e6..c681b4308 100644
--- a/src/ImageProcessorCore/Formats/Quantizers/Octree/OctreeQuantizer.cs
+++ b/src/ImageProcessorCore/Quantizers/Octree/OctreeQuantizer.cs
@@ -3,7 +3,7 @@
// Licensed under the Apache License, Version 2.0.
//
-namespace ImageProcessorCore.Formats
+namespace ImageProcessorCore.Quantizers
{
using System;
using System.Collections.Generic;
@@ -120,6 +120,19 @@ namespace ImageProcessorCore.Formats
return palette;
}
+ ///
+ /// Returns how many bits are required to store the specified number of colors.
+ /// Performs a Log2() on the value.
+ ///
+ /// The number of colors.
+ ///
+ /// The
+ ///
+ private int GetBitsNeededForColorDepth(int colors)
+ {
+ return (int)Math.Ceiling(Math.Log(colors, 2));
+ }
+
///
/// Class which does the actual quantization
///
diff --git a/src/ImageProcessorCore/Formats/Quantizers/Octree/Quantizer.cs b/src/ImageProcessorCore/Quantizers/Octree/Quantizer.cs
similarity index 96%
rename from src/ImageProcessorCore/Formats/Quantizers/Octree/Quantizer.cs
rename to src/ImageProcessorCore/Quantizers/Octree/Quantizer.cs
index 9b8371e04..74b0d7131 100644
--- a/src/ImageProcessorCore/Formats/Quantizers/Octree/Quantizer.cs
+++ b/src/ImageProcessorCore/Quantizers/Octree/Quantizer.cs
@@ -3,7 +3,7 @@
// Licensed under the Apache License, Version 2.0.
//
-namespace ImageProcessorCore.Formats
+namespace ImageProcessorCore.Quantizers
{
using System.Collections.Generic;
@@ -121,9 +121,7 @@ namespace ImageProcessorCore.Formats
///
/// Override this to process the pixel in the first pass of the algorithm
///
- ///
- /// The pixel to quantize
- ///
+ /// The pixel to quantize
///
/// This function need only be overridden if your quantize algorithm needs two passes,
/// such as an Octree quantizer.
@@ -135,9 +133,7 @@ namespace ImageProcessorCore.Formats
///
/// Override this to process the pixel in the second pass of the algorithm
///
- ///
- /// The pixel to quantize
- ///
+ /// The pixel to quantize
///
/// The quantized value
///
diff --git a/src/ImageProcessorCore/Formats/Quantizers/QuantizedImage.cs b/src/ImageProcessorCore/Quantizers/QuantizedImage.cs
similarity index 98%
rename from src/ImageProcessorCore/Formats/Quantizers/QuantizedImage.cs
rename to src/ImageProcessorCore/Quantizers/QuantizedImage.cs
index d46f5748f..d0f7928ee 100644
--- a/src/ImageProcessorCore/Formats/Quantizers/QuantizedImage.cs
+++ b/src/ImageProcessorCore/Quantizers/QuantizedImage.cs
@@ -3,7 +3,7 @@
// Licensed under the Apache License, Version 2.0.
//
-namespace ImageProcessorCore.Formats
+namespace ImageProcessorCore.Quantizers
{
using System;
using System.Threading.Tasks;
diff --git a/src/ImageProcessorCore/Formats/Quantizers/Wu/Box.cs b/src/ImageProcessorCore/Quantizers/Wu/Box.cs
similarity index 83%
rename from src/ImageProcessorCore/Formats/Quantizers/Wu/Box.cs
rename to src/ImageProcessorCore/Quantizers/Wu/Box.cs
index 618e1475c..b9300b087 100644
--- a/src/ImageProcessorCore/Formats/Quantizers/Wu/Box.cs
+++ b/src/ImageProcessorCore/Quantizers/Wu/Box.cs
@@ -1,7 +1,12 @@
-namespace ImageProcessorCore.Formats
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessorCore.Quantizers
{
///
- /// A box.
+ /// Represents a box color cube.
///
internal sealed class Box
{
diff --git a/src/ImageProcessorCore/Formats/Quantizers/Wu/WuQuantizer.cs b/src/ImageProcessorCore/Quantizers/Wu/WuQuantizer.cs
similarity index 81%
rename from src/ImageProcessorCore/Formats/Quantizers/Wu/WuQuantizer.cs
rename to src/ImageProcessorCore/Quantizers/Wu/WuQuantizer.cs
index 19b95bd00..578eddd04 100644
--- a/src/ImageProcessorCore/Formats/Quantizers/Wu/WuQuantizer.cs
+++ b/src/ImageProcessorCore/Quantizers/Wu/WuQuantizer.cs
@@ -1,16 +1,12 @@
-//
-// Copyright (c) 2014-2015 Jérémy Ansel
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
//
-//
-// Licensed under the MIT license. See LICENSE.txt
-//
-namespace ImageProcessorCore.Formats
+namespace ImageProcessorCore.Quantizers
{
using System;
using System.Collections.Generic;
- using System.Diagnostics.CodeAnalysis;
- using System.Threading.Tasks;
///
/// An implementation of Wu's color quantizer with alpha channel.
@@ -22,6 +18,10 @@ namespace ImageProcessorCore.Formats
/// ().
///
///
+ /// This adaptation is based on the excellent JeremyAnsel.ColorQuant by Jérémy Ansel
+ ///
+ ///
+ ///
/// Algorithm: Greedy orthogonal bipartition of RGB space for variance
/// minimization aided by inclusion-exclusion tricks.
/// For speed no nearest neighbor search is done. Slightly
@@ -29,9 +29,13 @@ namespace ImageProcessorCore.Formats
/// but more expensive versions.
///
///
- [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "Wu", Justification = "Reviewed")]
public sealed class WuQuantizer : IQuantizer
{
+ ///
+ /// The epsilon for comparing floating point numbers.
+ ///
+ private const float Epsilon = 0.001f;
+
///
/// The index bits.
///
@@ -65,37 +69,37 @@ namespace ImageProcessorCore.Formats
///
/// Moment of P(c).
///
- private long[] vwt;
+ private readonly long[] vwt;
///
/// Moment of r*P(c).
///
- private long[] vmr;
+ private readonly long[] vmr;
///
/// Moment of g*P(c).
///
- private long[] vmg;
+ private readonly long[] vmg;
///
/// Moment of b*P(c).
///
- private long[] vmb;
+ private readonly long[] vmb;
///
/// Moment of a*P(c).
///
- private long[] vma;
+ private readonly long[] vma;
///
/// Moment of c^2*P(c).
///
- private double[] m2;
+ private readonly double[] m2;
///
/// Color space tag.
///
- private byte[] tag;
+ private readonly byte[] tag;
///
/// Initializes a new instance of the class.
@@ -133,7 +137,7 @@ namespace ImageProcessorCore.Formats
this.Clear();
this.Build3DHistogram(image);
- this.M3d();
+ this.Get3DMoments();
Box[] cube;
this.BuildCube(out cube, ref colorCount);
@@ -329,9 +333,6 @@ namespace ImageProcessorCore.Formats
/// The image.
private void Build3DHistogram(ImageBase image)
{
-
-
- // TODO: Parallel
for (int y = 0; y < image.Height; y++)
{
for (int x = 0; x < image.Width; x++)
@@ -364,47 +365,47 @@ namespace ImageProcessorCore.Formats
/// Converts the histogram into moments so that we can rapidly calculate
/// the sums of the above quantities over any desired box.
///
- private void M3d()
+ private void Get3DMoments()
{
long[] volume = new long[IndexCount * IndexAlphaCount];
- long[] volume_r = new long[IndexCount * IndexAlphaCount];
- long[] volume_g = new long[IndexCount * IndexAlphaCount];
- long[] volume_b = new long[IndexCount * IndexAlphaCount];
- long[] volume_a = new long[IndexCount * IndexAlphaCount];
+ long[] volumeR = new long[IndexCount * IndexAlphaCount];
+ long[] volumeG = new long[IndexCount * IndexAlphaCount];
+ long[] volumeB = new long[IndexCount * IndexAlphaCount];
+ long[] volumeA = new long[IndexCount * IndexAlphaCount];
double[] volume2 = new double[IndexCount * IndexAlphaCount];
long[] area = new long[IndexAlphaCount];
- long[] area_r = new long[IndexAlphaCount];
- long[] area_g = new long[IndexAlphaCount];
- long[] area_b = new long[IndexAlphaCount];
- long[] area_a = new long[IndexAlphaCount];
+ long[] areaR = new long[IndexAlphaCount];
+ long[] areaG = new long[IndexAlphaCount];
+ long[] areaB = new long[IndexAlphaCount];
+ long[] areaA = new long[IndexAlphaCount];
double[] area2 = new double[IndexAlphaCount];
for (int r = 1; r < IndexCount; r++)
{
Array.Clear(volume, 0, IndexCount * IndexAlphaCount);
- Array.Clear(volume_r, 0, IndexCount * IndexAlphaCount);
- Array.Clear(volume_g, 0, IndexCount * IndexAlphaCount);
- Array.Clear(volume_b, 0, IndexCount * IndexAlphaCount);
- Array.Clear(volume_a, 0, IndexCount * IndexAlphaCount);
+ Array.Clear(volumeR, 0, IndexCount * IndexAlphaCount);
+ Array.Clear(volumeG, 0, IndexCount * IndexAlphaCount);
+ Array.Clear(volumeB, 0, IndexCount * IndexAlphaCount);
+ Array.Clear(volumeA, 0, IndexCount * IndexAlphaCount);
Array.Clear(volume2, 0, IndexCount * IndexAlphaCount);
for (int g = 1; g < IndexCount; g++)
{
Array.Clear(area, 0, IndexAlphaCount);
- Array.Clear(area_r, 0, IndexAlphaCount);
- Array.Clear(area_g, 0, IndexAlphaCount);
- Array.Clear(area_b, 0, IndexAlphaCount);
- Array.Clear(area_a, 0, IndexAlphaCount);
+ Array.Clear(areaR, 0, IndexAlphaCount);
+ Array.Clear(areaG, 0, IndexAlphaCount);
+ Array.Clear(areaB, 0, IndexAlphaCount);
+ Array.Clear(areaA, 0, IndexAlphaCount);
Array.Clear(area2, 0, IndexAlphaCount);
for (int b = 1; b < IndexCount; b++)
{
long line = 0;
- long line_r = 0;
- long line_g = 0;
- long line_b = 0;
- long line_a = 0;
+ long lineR = 0;
+ long lineG = 0;
+ long lineB = 0;
+ long lineA = 0;
double line2 = 0;
for (int a = 1; a < IndexAlphaCount; a++)
@@ -412,35 +413,35 @@ namespace ImageProcessorCore.Formats
int ind1 = Ind(r, g, b, a);
line += this.vwt[ind1];
- line_r += this.vmr[ind1];
- line_g += this.vmg[ind1];
- line_b += this.vmb[ind1];
- line_a += this.vma[ind1];
+ lineR += this.vmr[ind1];
+ lineG += this.vmg[ind1];
+ lineB += this.vmb[ind1];
+ lineA += this.vma[ind1];
line2 += this.m2[ind1];
area[a] += line;
- area_r[a] += line_r;
- area_g[a] += line_g;
- area_b[a] += line_b;
- area_a[a] += line_a;
+ areaR[a] += lineR;
+ areaG[a] += lineG;
+ areaB[a] += lineB;
+ areaA[a] += lineA;
area2[a] += line2;
int inv = (b * IndexAlphaCount) + a;
volume[inv] += area[a];
- volume_r[inv] += area_r[a];
- volume_g[inv] += area_g[a];
- volume_b[inv] += area_b[a];
- volume_a[inv] += area_a[a];
+ volumeR[inv] += areaR[a];
+ volumeG[inv] += areaG[a];
+ volumeB[inv] += areaB[a];
+ volumeA[inv] += areaA[a];
volume2[inv] += area2[a];
int ind2 = ind1 - Ind(1, 0, 0, 0);
this.vwt[ind1] = this.vwt[ind2] + volume[inv];
- this.vmr[ind1] = this.vmr[ind2] + volume_r[inv];
- this.vmg[ind1] = this.vmg[ind2] + volume_g[inv];
- this.vmb[ind1] = this.vmb[ind2] + volume_b[inv];
- this.vma[ind1] = this.vma[ind2] + volume_a[inv];
+ this.vmr[ind1] = this.vmr[ind2] + volumeR[inv];
+ this.vmg[ind1] = this.vmg[ind2] + volumeG[inv];
+ this.vmb[ind1] = this.vmb[ind2] + volumeB[inv];
+ this.vma[ind1] = this.vma[ind2] + volumeA[inv];
this.m2[ind1] = this.m2[ind2] + volume2[inv];
}
}
@@ -449,11 +450,11 @@ namespace ImageProcessorCore.Formats
}
///
- /// Computes the weighted variance of a box.
+ /// Computes the weighted variance of a box cube.
///
/// The cube.
- /// The result.
- private double Var(Box cube)
+ /// The .
+ private double Variance(Box cube)
{
double dr = Volume(cube, this.vmr);
double dg = Volume(cube, this.vmg);
@@ -493,58 +494,52 @@ namespace ImageProcessorCore.Formats
/// The first position.
/// The last position.
/// The cutting point.
- /// The whole red.
- /// The whole green.
- /// The whole blue.
- /// The whole alpha.
- /// The whole weight.
- /// The result.
- private double Maximize(Box cube, int direction, int first, int last, out int cut, double whole_r, double whole_g, double whole_b, double whole_a, double whole_w)
+ /// The whole red.
+ /// The whole green.
+ /// The whole blue.
+ /// The whole alpha.
+ /// The whole weight.
+ /// The .
+ private double Maximize(Box cube, int direction, int first, int last, out int cut, double wholeR, double wholeG, double wholeB, double wholeA, double wholeW)
{
- long base_r = Bottom(cube, direction, this.vmr);
- long base_g = Bottom(cube, direction, this.vmg);
- long base_b = Bottom(cube, direction, this.vmb);
- long base_a = Bottom(cube, direction, this.vma);
- long base_w = Bottom(cube, direction, this.vwt);
+ long baseR = Bottom(cube, direction, this.vmr);
+ long baseG = Bottom(cube, direction, this.vmg);
+ long baseB = Bottom(cube, direction, this.vmb);
+ long baseA = Bottom(cube, direction, this.vma);
+ long baseW = Bottom(cube, direction, this.vwt);
double max = 0.0;
cut = -1;
for (int i = first; i < last; i++)
{
- double half_r = base_r + Top(cube, direction, i, this.vmr);
- double half_g = base_g + Top(cube, direction, i, this.vmg);
- double half_b = base_b + Top(cube, direction, i, this.vmb);
- double half_a = base_a + Top(cube, direction, i, this.vma);
- double half_w = base_w + Top(cube, direction, i, this.vwt);
+ double halfR = baseR + Top(cube, direction, i, this.vmr);
+ double halfG = baseG + Top(cube, direction, i, this.vmg);
+ double halfB = baseB + Top(cube, direction, i, this.vmb);
+ double halfA = baseA + Top(cube, direction, i, this.vma);
+ double halfW = baseW + Top(cube, direction, i, this.vwt);
double temp;
- // TODO: Epsilon
- if (Math.Abs(half_w) < 0.001)
+ if (Math.Abs(halfW) < Epsilon)
{
continue;
}
- else
- {
- temp = ((half_r * half_r) + (half_g * half_g) + (half_b * half_b) + (half_a * half_a)) / half_w;
- }
- half_r = whole_r - half_r;
- half_g = whole_g - half_g;
- half_b = whole_b - half_b;
- half_a = whole_a - half_a;
- half_w = whole_w - half_w;
+ temp = ((halfR * halfR) + (halfG * halfG) + (halfB * halfB) + (halfA * halfA)) / halfW;
+
+ halfR = wholeR - halfR;
+ halfG = wholeG - halfG;
+ halfB = wholeB - halfB;
+ halfA = wholeA - halfA;
+ halfW = wholeW - halfW;
- // TODO: Epsilon
- if (Math.Abs(half_w) < 0.001)
+ if (Math.Abs(halfW) < Epsilon)
{
continue;
}
- else
- {
- temp += ((half_r * half_r) + (half_g * half_g) + (half_b * half_b) + (half_a * half_a)) / half_w;
- }
+
+ temp += ((halfR * halfR) + (halfG * halfG) + (halfB * halfB) + (halfA * halfA)) / halfW;
if (temp > max)
{
@@ -564,21 +559,21 @@ namespace ImageProcessorCore.Formats
/// Returns a value indicating whether the box has been split.
private bool Cut(Box set1, Box set2)
{
- double whole_r = Volume(set1, this.vmr);
- double whole_g = Volume(set1, this.vmg);
- double whole_b = Volume(set1, this.vmb);
- double whole_a = Volume(set1, this.vma);
- double whole_w = Volume(set1, this.vwt);
+ double wholeR = Volume(set1, this.vmr);
+ double wholeG = Volume(set1, this.vmg);
+ double wholeB = Volume(set1, this.vmb);
+ double wholeA = Volume(set1, this.vma);
+ double wholeW = Volume(set1, this.vwt);
int cutr;
int cutg;
int cutb;
int cuta;
- double maxr = this.Maximize(set1, 0, set1.R0 + 1, set1.R1, out cutr, whole_r, whole_g, whole_b, whole_a, whole_w);
- double maxg = this.Maximize(set1, 1, set1.G0 + 1, set1.G1, out cutg, whole_r, whole_g, whole_b, whole_a, whole_w);
- double maxb = this.Maximize(set1, 2, set1.B0 + 1, set1.B1, out cutb, whole_r, whole_g, whole_b, whole_a, whole_w);
- double maxa = this.Maximize(set1, 3, set1.A0 + 1, set1.A1, out cuta, whole_r, whole_g, whole_b, whole_a, whole_w);
+ double maxr = this.Maximize(set1, 0, set1.R0 + 1, set1.R1, out cutr, wholeR, wholeG, wholeB, wholeA, wholeW);
+ double maxg = this.Maximize(set1, 1, set1.G0 + 1, set1.G1, out cutg, wholeR, wholeG, wholeB, wholeA, wholeW);
+ double maxb = this.Maximize(set1, 2, set1.B0 + 1, set1.B1, out cutb, wholeR, wholeG, wholeB, wholeA, wholeW);
+ double maxa = this.Maximize(set1, 3, set1.A0 + 1, set1.A1, out cuta, wholeR, wholeG, wholeB, wholeA, wholeW);
int dir;
@@ -697,8 +692,8 @@ namespace ImageProcessorCore.Formats
{
if (this.Cut(cube[next], cube[i]))
{
- vv[next] = cube[next].Volume > 1 ? this.Var(cube[next]) : 0.0;
- vv[i] = cube[i].Volume > 1 ? this.Var(cube[i]) : 0.0;
+ vv[next] = cube[next].Volume > 1 ? this.Variance(cube[next]) : 0.0;
+ vv[i] = cube[i].Volume > 1 ? this.Variance(cube[i]) : 0.0;
}
else
{
@@ -745,8 +740,7 @@ namespace ImageProcessorCore.Formats
double weight = Volume(cube[k], this.vwt);
- // TODO: Epsilon
- if (Math.Abs(weight) > .0001)
+ if (Math.Abs(weight) > Epsilon)
{
byte r = (byte)(Volume(cube[k], this.vmr) / weight);
byte g = (byte)(Volume(cube[k], this.vmg) / weight);
@@ -778,18 +772,6 @@ namespace ImageProcessorCore.Formats
}
}
- //for (int i = 0; i < image.Length / 4; i++)
- //{
- // int a = image[(i * 4) + 3] >> (8 - WuAlphaColorQuantizer.IndexAlphaBits);
- // int r = image[(i * 4) + 2] >> (8 - WuAlphaColorQuantizer.IndexBits);
- // int g = image[(i * 4) + 1] >> (8 - WuAlphaColorQuantizer.IndexBits);
- // int b = image[i * 4] >> (8 - WuAlphaColorQuantizer.IndexBits);
-
- // int ind = WuAlphaColorQuantizer.Ind(r + 1, g + 1, b + 1, a + 1);
-
- // pixels[i] = this.tag[ind];
- //}
-
return new QuantizedImage(image.Width, image.Height, pallette.ToArray(), pixels);
}
}
diff --git a/tests/ImageProcessorCore.Tests/Processors/Formats/EncoderDecoderTests.cs b/tests/ImageProcessorCore.Tests/Processors/Formats/EncoderDecoderTests.cs
index 42e5022ce..9950fa5d9 100644
--- a/tests/ImageProcessorCore.Tests/Processors/Formats/EncoderDecoderTests.cs
+++ b/tests/ImageProcessorCore.Tests/Processors/Formats/EncoderDecoderTests.cs
@@ -7,6 +7,9 @@
using Xunit;
using System.Linq;
+
+ using ImageProcessorCore.Quantizers;
+
public class EncoderDecoderTests : ProcessorTestBase
{
[Fact]