diff --git a/src/ImageProcessorCore/Formats/Quantizers/Octree/OctreeQuantizer.cs b/src/ImageProcessorCore/Formats/Quantizers/Octree/OctreeQuantizer.cs
index 25c2171f91..f13f3aefed 100644
--- a/src/ImageProcessorCore/Formats/Quantizers/Octree/OctreeQuantizer.cs
+++ b/src/ImageProcessorCore/Formats/Quantizers/Octree/OctreeQuantizer.cs
@@ -51,8 +51,8 @@ namespace ImageProcessorCore.Formats
public OctreeQuantizer(int maxColors, int maxColorBits)
: base(false)
{
- Guard.MustBeLessThanOrEqualTo(maxColors, 255, "maxColors");
- Guard.MustBeBetweenOrEqualTo(maxColorBits, 1, 8, "maxColorBits");
+ Guard.MustBeBetweenOrEqualTo(maxColors, 1, 255, nameof(maxColors));
+ Guard.MustBeBetweenOrEqualTo(maxColorBits, 1, 8, nameof(maxColorBits));
// Construct the Octree
this.octree = new Octree(maxColorBits);
diff --git a/src/ImageProcessorCore/Formats/Quantizers/Wu/Box.cs b/src/ImageProcessorCore/Formats/Quantizers/Wu/Box.cs
index 54abb20e0f..618e1475ca 100644
--- a/src/ImageProcessorCore/Formats/Quantizers/Wu/Box.cs
+++ b/src/ImageProcessorCore/Formats/Quantizers/Wu/Box.cs
@@ -31,7 +31,7 @@
public int B0 { get; set; }
///
- /// Gets or sets the max green value, inclusive.
+ /// Gets or sets the max blue value, inclusive.
///
public int B1 { get; set; }
diff --git a/src/ImageProcessorCore/Formats/Quantizers/Wu/WuAlphaColorQuantizer.cs b/src/ImageProcessorCore/Formats/Quantizers/Wu/WuAlphaColorQuantizer.cs
deleted file mode 100644
index 2d63d76a72..0000000000
--- a/src/ImageProcessorCore/Formats/Quantizers/Wu/WuAlphaColorQuantizer.cs
+++ /dev/null
@@ -1,796 +0,0 @@
-//
-// Copyright (c) 2014-2015 Jérémy Ansel
-//
-//
-// Licensed under the MIT license. See LICENSE.txt
-//
-
-namespace ImageProcessorCore.Formats
-{
- using System;
- using System.Collections.Generic;
- using System.Diagnostics.CodeAnalysis;
-
- ///
- /// A Wu's color quantizer with alpha channel.
- ///
- ///
- ///
- /// Based on C Implementation of Xiaolin Wu's Color Quantizer (v. 2)
- /// (see Graphics Gems volume II, pages 126-133)
- /// ().
- ///
- ///
- /// Algorithm: Greedy orthogonal bipartition of RGB space for variance
- /// minimization aided by inclusion-exclusion tricks.
- /// For speed no nearest neighbor search is done. Slightly
- /// better performance can be expected by more sophisticated
- /// but more expensive versions.
- ///
- ///
- [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "Wu", Justification = "Reviewed")]
- public sealed class WuAlphaColorQuantizer : IQuantizer
- {
- ///
- /// The index bits.
- ///
- private const int IndexBits = 6;
-
- ///
- /// The index alpha bits.
- ///
- private const int IndexAlphaBits = 3;
-
- ///
- /// The index count.
- ///
- private const int IndexCount = (1 << WuAlphaColorQuantizer.IndexBits) + 1;
-
- ///
- /// The index alpha count.
- ///
- private const int IndexAlphaCount = (1 << WuAlphaColorQuantizer.IndexAlphaBits) + 1;
-
- ///
- /// The table length.
- ///
- private const int TableLength = WuAlphaColorQuantizer.IndexCount * WuAlphaColorQuantizer.IndexCount * WuAlphaColorQuantizer.IndexCount * WuAlphaColorQuantizer.IndexAlphaCount;
-
- ///
- /// Moment of P(c).
- ///
- private long[] vwt;
-
- ///
- /// Moment of r*P(c).
- ///
- private long[] vmr;
-
- ///
- /// Moment of g*P(c).
- ///
- private long[] vmg;
-
- ///
- /// Moment of b*P(c).
- ///
- private long[] vmb;
-
- ///
- /// Moment of a*P(c).
- ///
- private long[] vma;
-
- ///
- /// Moment of c^2*P(c).
- ///
- private double[] m2;
-
- ///
- /// Color space tag.
- ///
- private byte[] tag;
-
- ///
- /// Initializes a new instance of the class.
- ///
- public WuAlphaColorQuantizer()
- {
- this.vwt = new long[WuAlphaColorQuantizer.TableLength];
- this.vmr = new long[WuAlphaColorQuantizer.TableLength];
- this.vmg = new long[WuAlphaColorQuantizer.TableLength];
- this.vmb = new long[WuAlphaColorQuantizer.TableLength];
- this.vma = new long[WuAlphaColorQuantizer.TableLength];
- this.m2 = new double[WuAlphaColorQuantizer.TableLength];
-
- this.tag = new byte[WuAlphaColorQuantizer.TableLength];
- }
-
- ///
- /// Quantizes an image.
- ///
- /// The image (ARGB).
- /// The result.
- public QuantizedImage Quantize(ImageBase image)
- {
- return this.Quantize(image, 256);
- }
-
- ///
- /// Quantizes an image.
- ///
- /// The image (ARGB).
- /// The color count.
- /// The result.
- public QuantizedImage Quantize(ImageBase image, int colorCount)
- {
- if (image == null)
- {
- throw new ArgumentNullException("image");
- }
-
- if (colorCount < 1 || colorCount > 256)
- {
- throw new ArgumentOutOfRangeException("colorCount");
- }
-
- this.Clear();
-
- this.Hist3d(image);
- this.M3d();
-
- Box[] cube;
- this.BuildCube(out cube, ref colorCount);
-
- return this.GenerateResult(image, colorCount, cube);
- }
-
- ///
- /// Gets an index.
- ///
- /// The red value.
- /// The green value.
- /// The blue value.
- /// The alpha value.
- /// The index.
- private static int Ind(int r, int g, int b, int a)
- {
- return (r << ((WuAlphaColorQuantizer.IndexBits * 2) + WuAlphaColorQuantizer.IndexAlphaBits))
- + (r << (WuAlphaColorQuantizer.IndexBits + WuAlphaColorQuantizer.IndexAlphaBits + 1))
- + (g << (WuAlphaColorQuantizer.IndexBits + WuAlphaColorQuantizer.IndexAlphaBits))
- + (r << (WuAlphaColorQuantizer.IndexBits * 2))
- + (r << (WuAlphaColorQuantizer.IndexBits + 1))
- + (g << WuAlphaColorQuantizer.IndexBits)
- + ((r + g + b) << WuAlphaColorQuantizer.IndexAlphaBits)
- + r + g + b + a;
- }
-
- ///
- /// Computes sum over a box of any given statistic.
- ///
- /// The cube.
- /// The moment.
- /// The result.
- private static double Volume(Box cube, long[] moment)
- {
- return moment[WuAlphaColorQuantizer.Ind(cube.R1, cube.G1, cube.B1, cube.A1)]
- - moment[WuAlphaColorQuantizer.Ind(cube.R1, cube.G1, cube.B1, cube.A0)]
- - moment[WuAlphaColorQuantizer.Ind(cube.R1, cube.G1, cube.B0, cube.A1)]
- + moment[WuAlphaColorQuantizer.Ind(cube.R1, cube.G1, cube.B0, cube.A0)]
- - moment[WuAlphaColorQuantizer.Ind(cube.R1, cube.G0, cube.B1, cube.A1)]
- + moment[WuAlphaColorQuantizer.Ind(cube.R1, cube.G0, cube.B1, cube.A0)]
- + moment[WuAlphaColorQuantizer.Ind(cube.R1, cube.G0, cube.B0, cube.A1)]
- - moment[WuAlphaColorQuantizer.Ind(cube.R1, cube.G0, cube.B0, cube.A0)]
- - moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G1, cube.B1, cube.A1)]
- + moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G1, cube.B1, cube.A0)]
- + moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G1, cube.B0, cube.A1)]
- - moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G1, cube.B0, cube.A0)]
- + moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G0, cube.B1, cube.A1)]
- - moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G0, cube.B1, cube.A0)]
- - moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G0, cube.B0, cube.A1)]
- + moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G0, cube.B0, cube.A0)];
- }
-
- ///
- /// Computes part of Volume(cube, moment) that doesn't depend on r1, g1, or b1 (depending on direction).
- ///
- /// The cube.
- /// The direction.
- /// The moment.
- /// The result.
- private static long Bottom(Box cube, int direction, long[] moment)
- {
- switch (direction)
- {
- // Red
- case 3:
- return -moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G1, cube.B1, cube.A1)]
- + moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G1, cube.B1, cube.A0)]
- + moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G1, cube.B0, cube.A1)]
- - moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G1, cube.B0, cube.A0)]
- + moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G0, cube.B1, cube.A1)]
- - moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G0, cube.B1, cube.A0)]
- - moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G0, cube.B0, cube.A1)]
- + moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G0, cube.B0, cube.A0)];
-
- // Green
- case 2:
- return -moment[WuAlphaColorQuantizer.Ind(cube.R1, cube.G0, cube.B1, cube.A1)]
- + moment[WuAlphaColorQuantizer.Ind(cube.R1, cube.G0, cube.B1, cube.A0)]
- + moment[WuAlphaColorQuantizer.Ind(cube.R1, cube.G0, cube.B0, cube.A1)]
- - moment[WuAlphaColorQuantizer.Ind(cube.R1, cube.G0, cube.B0, cube.A0)]
- + moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G0, cube.B1, cube.A1)]
- - moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G0, cube.B1, cube.A0)]
- - moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G0, cube.B0, cube.A1)]
- + moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G0, cube.B0, cube.A0)];
-
- // Blue
- case 1:
- return -moment[WuAlphaColorQuantizer.Ind(cube.R1, cube.G1, cube.B0, cube.A1)]
- + moment[WuAlphaColorQuantizer.Ind(cube.R1, cube.G1, cube.B0, cube.A0)]
- + moment[WuAlphaColorQuantizer.Ind(cube.R1, cube.G0, cube.B0, cube.A1)]
- - moment[WuAlphaColorQuantizer.Ind(cube.R1, cube.G0, cube.B0, cube.A0)]
- + moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G1, cube.B0, cube.A1)]
- - moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G1, cube.B0, cube.A0)]
- - moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G0, cube.B0, cube.A1)]
- + moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G0, cube.B0, cube.A0)];
-
- // Alpha
- case 0:
- return -moment[WuAlphaColorQuantizer.Ind(cube.R1, cube.G1, cube.B1, cube.A0)]
- + moment[WuAlphaColorQuantizer.Ind(cube.R1, cube.G1, cube.B0, cube.A0)]
- + moment[WuAlphaColorQuantizer.Ind(cube.R1, cube.G0, cube.B1, cube.A0)]
- - moment[WuAlphaColorQuantizer.Ind(cube.R1, cube.G0, cube.B0, cube.A0)]
- + moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G1, cube.B1, cube.A0)]
- - moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G1, cube.B0, cube.A0)]
- - moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G0, cube.B1, cube.A0)]
- + moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G0, cube.B0, cube.A0)];
-
- default:
- throw new ArgumentOutOfRangeException("direction");
- }
- }
-
- ///
- /// Computes remainder of Volume(cube, moment), substituting position for r1, g1, or b1 (depending on direction).
- ///
- /// The cube.
- /// The direction.
- /// The position.
- /// The moment.
- /// The result.
- private static long Top(Box cube, int direction, int position, long[] moment)
- {
- switch (direction)
- {
- // Red
- case 3:
- return moment[WuAlphaColorQuantizer.Ind(position, cube.G1, cube.B1, cube.A1)]
- - moment[WuAlphaColorQuantizer.Ind(position, cube.G1, cube.B1, cube.A0)]
- - moment[WuAlphaColorQuantizer.Ind(position, cube.G1, cube.B0, cube.A1)]
- + moment[WuAlphaColorQuantizer.Ind(position, cube.G1, cube.B0, cube.A0)]
- - moment[WuAlphaColorQuantizer.Ind(position, cube.G0, cube.B1, cube.A1)]
- + moment[WuAlphaColorQuantizer.Ind(position, cube.G0, cube.B1, cube.A0)]
- + moment[WuAlphaColorQuantizer.Ind(position, cube.G0, cube.B0, cube.A1)]
- - moment[WuAlphaColorQuantizer.Ind(position, cube.G0, cube.B0, cube.A0)];
-
- // Green
- case 2:
- return moment[WuAlphaColorQuantizer.Ind(cube.R1, position, cube.B1, cube.A1)]
- - moment[WuAlphaColorQuantizer.Ind(cube.R1, position, cube.B1, cube.A0)]
- - moment[WuAlphaColorQuantizer.Ind(cube.R1, position, cube.B0, cube.A1)]
- + moment[WuAlphaColorQuantizer.Ind(cube.R1, position, cube.B0, cube.A0)]
- - moment[WuAlphaColorQuantizer.Ind(cube.R0, position, cube.B1, cube.A1)]
- + moment[WuAlphaColorQuantizer.Ind(cube.R0, position, cube.B1, cube.A0)]
- + moment[WuAlphaColorQuantizer.Ind(cube.R0, position, cube.B0, cube.A1)]
- - moment[WuAlphaColorQuantizer.Ind(cube.R0, position, cube.B0, cube.A0)];
-
- // Blue
- case 1:
- return moment[WuAlphaColorQuantizer.Ind(cube.R1, cube.G1, position, cube.A1)]
- - moment[WuAlphaColorQuantizer.Ind(cube.R1, cube.G1, position, cube.A0)]
- - moment[WuAlphaColorQuantizer.Ind(cube.R1, cube.G0, position, cube.A1)]
- + moment[WuAlphaColorQuantizer.Ind(cube.R1, cube.G0, position, cube.A0)]
- - moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G1, position, cube.A1)]
- + moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G1, position, cube.A0)]
- + moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G0, position, cube.A1)]
- - moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G0, position, cube.A0)];
-
- // Alpha
- case 0:
- return moment[WuAlphaColorQuantizer.Ind(cube.R1, cube.G1, cube.B1, position)]
- - moment[WuAlphaColorQuantizer.Ind(cube.R1, cube.G1, cube.B0, position)]
- - moment[WuAlphaColorQuantizer.Ind(cube.R1, cube.G0, cube.B1, position)]
- + moment[WuAlphaColorQuantizer.Ind(cube.R1, cube.G0, cube.B0, position)]
- - moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G1, cube.B1, position)]
- + moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G1, cube.B0, position)]
- + moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G0, cube.B1, position)]
- - moment[WuAlphaColorQuantizer.Ind(cube.R0, cube.G0, cube.B0, position)];
-
- default:
- throw new ArgumentOutOfRangeException("direction");
- }
- }
-
- ///
- /// Clears the tables.
- ///
- private void Clear()
- {
- Array.Clear(this.vwt, 0, WuAlphaColorQuantizer.TableLength);
- Array.Clear(this.vmr, 0, WuAlphaColorQuantizer.TableLength);
- Array.Clear(this.vmg, 0, WuAlphaColorQuantizer.TableLength);
- Array.Clear(this.vmb, 0, WuAlphaColorQuantizer.TableLength);
- Array.Clear(this.vma, 0, WuAlphaColorQuantizer.TableLength);
- Array.Clear(this.m2, 0, WuAlphaColorQuantizer.TableLength);
-
- Array.Clear(this.tag, 0, WuAlphaColorQuantizer.TableLength);
- }
-
- ///
- /// Builds a 3-D color histogram of counts, r/g/b, c^2.
- ///
- /// The image.
- private void Hist3d(ImageBase image)
- {
- // TODO: Parallel
- for (int y = 0; y < image.Height; y++)
- {
- for (int x = 0; x < image.Width; x++)
- {
- Bgra32 color = image[x, y];
-
- byte r = color.R;
- byte g = color.G;
- byte b = color.B;
- byte a = color.A;
-
- int inr = r >> (8 - WuAlphaColorQuantizer.IndexBits);
- int ing = g >> (8 - WuAlphaColorQuantizer.IndexBits);
- int inb = b >> (8 - WuAlphaColorQuantizer.IndexBits);
- int ina = a >> (8 - WuAlphaColorQuantizer.IndexAlphaBits);
-
- int ind = WuAlphaColorQuantizer.Ind(inr + 1, ing + 1, inb + 1, ina + 1);
-
- this.vwt[ind]++;
- this.vmr[ind] += r;
- this.vmg[ind] += g;
- this.vmb[ind] += b;
- this.vma[ind] += a;
- this.m2[ind] += (r * r) + (g * g) + (b * b) + (a * a);
- }
- }
- }
-
- ///
- /// Converts the histogram into moments so that we can rapidly calculate
- /// the sums of the above quantities over any desired box.
- ///
- private void M3d()
- {
- long[] volume = new long[WuAlphaColorQuantizer.IndexCount * WuAlphaColorQuantizer.IndexAlphaCount];
- long[] volume_r = new long[WuAlphaColorQuantizer.IndexCount * WuAlphaColorQuantizer.IndexAlphaCount];
- long[] volume_g = new long[WuAlphaColorQuantizer.IndexCount * WuAlphaColorQuantizer.IndexAlphaCount];
- long[] volume_b = new long[WuAlphaColorQuantizer.IndexCount * WuAlphaColorQuantizer.IndexAlphaCount];
- long[] volume_a = new long[WuAlphaColorQuantizer.IndexCount * WuAlphaColorQuantizer.IndexAlphaCount];
- double[] volume2 = new double[WuAlphaColorQuantizer.IndexCount * WuAlphaColorQuantizer.IndexAlphaCount];
-
- long[] area = new long[WuAlphaColorQuantizer.IndexAlphaCount];
- long[] area_r = new long[WuAlphaColorQuantizer.IndexAlphaCount];
- long[] area_g = new long[WuAlphaColorQuantizer.IndexAlphaCount];
- long[] area_b = new long[WuAlphaColorQuantizer.IndexAlphaCount];
- long[] area_a = new long[WuAlphaColorQuantizer.IndexAlphaCount];
- double[] area2 = new double[WuAlphaColorQuantizer.IndexAlphaCount];
-
- for (int r = 1; r < WuAlphaColorQuantizer.IndexCount; r++)
- {
- Array.Clear(volume, 0, WuAlphaColorQuantizer.IndexCount * WuAlphaColorQuantizer.IndexAlphaCount);
- Array.Clear(volume_r, 0, WuAlphaColorQuantizer.IndexCount * WuAlphaColorQuantizer.IndexAlphaCount);
- Array.Clear(volume_g, 0, WuAlphaColorQuantizer.IndexCount * WuAlphaColorQuantizer.IndexAlphaCount);
- Array.Clear(volume_b, 0, WuAlphaColorQuantizer.IndexCount * WuAlphaColorQuantizer.IndexAlphaCount);
- Array.Clear(volume_a, 0, WuAlphaColorQuantizer.IndexCount * WuAlphaColorQuantizer.IndexAlphaCount);
- Array.Clear(volume2, 0, WuAlphaColorQuantizer.IndexCount * WuAlphaColorQuantizer.IndexAlphaCount);
-
- for (int g = 1; g < WuAlphaColorQuantizer.IndexCount; g++)
- {
- Array.Clear(area, 0, WuAlphaColorQuantizer.IndexAlphaCount);
- Array.Clear(area_r, 0, WuAlphaColorQuantizer.IndexAlphaCount);
- Array.Clear(area_g, 0, WuAlphaColorQuantizer.IndexAlphaCount);
- Array.Clear(area_b, 0, WuAlphaColorQuantizer.IndexAlphaCount);
- Array.Clear(area_a, 0, WuAlphaColorQuantizer.IndexAlphaCount);
- Array.Clear(area2, 0, WuAlphaColorQuantizer.IndexAlphaCount);
-
- for (int b = 1; b < WuAlphaColorQuantizer.IndexCount; b++)
- {
- long line = 0;
- long line_r = 0;
- long line_g = 0;
- long line_b = 0;
- long line_a = 0;
- double line2 = 0;
-
- for (int a = 1; a < WuAlphaColorQuantizer.IndexAlphaCount; a++)
- {
- int ind1 = WuAlphaColorQuantizer.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];
- 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;
- area2[a] += line2;
-
- int inv = (b * WuAlphaColorQuantizer.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];
- volume2[inv] += area2[a];
-
- int ind2 = ind1 - WuAlphaColorQuantizer.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.m2[ind1] = this.m2[ind2] + volume2[inv];
- }
- }
- }
- }
- }
-
- ///
- /// Computes the weighted variance of a box.
- ///
- /// The cube.
- /// The result.
- private double Var(Box cube)
- {
- double dr = WuAlphaColorQuantizer.Volume(cube, this.vmr);
- double dg = WuAlphaColorQuantizer.Volume(cube, this.vmg);
- double db = WuAlphaColorQuantizer.Volume(cube, this.vmb);
- double da = WuAlphaColorQuantizer.Volume(cube, this.vma);
-
- double xx =
- this.m2[WuAlphaColorQuantizer.Ind(cube.R1, cube.G1, cube.B1, cube.A1)]
- - this.m2[WuAlphaColorQuantizer.Ind(cube.R1, cube.G1, cube.B1, cube.A0)]
- - this.m2[WuAlphaColorQuantizer.Ind(cube.R1, cube.G1, cube.B0, cube.A1)]
- + this.m2[WuAlphaColorQuantizer.Ind(cube.R1, cube.G1, cube.B0, cube.A0)]
- - this.m2[WuAlphaColorQuantizer.Ind(cube.R1, cube.G0, cube.B1, cube.A1)]
- + this.m2[WuAlphaColorQuantizer.Ind(cube.R1, cube.G0, cube.B1, cube.A0)]
- + this.m2[WuAlphaColorQuantizer.Ind(cube.R1, cube.G0, cube.B0, cube.A1)]
- - this.m2[WuAlphaColorQuantizer.Ind(cube.R1, cube.G0, cube.B0, cube.A0)]
- - this.m2[WuAlphaColorQuantizer.Ind(cube.R0, cube.G1, cube.B1, cube.A1)]
- + this.m2[WuAlphaColorQuantizer.Ind(cube.R0, cube.G1, cube.B1, cube.A0)]
- + this.m2[WuAlphaColorQuantizer.Ind(cube.R0, cube.G1, cube.B0, cube.A1)]
- - this.m2[WuAlphaColorQuantizer.Ind(cube.R0, cube.G1, cube.B0, cube.A0)]
- + this.m2[WuAlphaColorQuantizer.Ind(cube.R0, cube.G0, cube.B1, cube.A1)]
- - this.m2[WuAlphaColorQuantizer.Ind(cube.R0, cube.G0, cube.B1, cube.A0)]
- - this.m2[WuAlphaColorQuantizer.Ind(cube.R0, cube.G0, cube.B0, cube.A1)]
- + this.m2[WuAlphaColorQuantizer.Ind(cube.R0, cube.G0, cube.B0, cube.A0)];
-
- return xx - (((dr * dr) + (dg * dg) + (db * db) + (da * da)) / WuAlphaColorQuantizer.Volume(cube, this.vwt));
- }
-
- ///
- /// We want to minimize the sum of the variances of two sub-boxes.
- /// The sum(c^2) terms can be ignored since their sum over both sub-boxes
- /// is the same (the sum for the whole box) no matter where we split.
- /// The remaining terms have a minus sign in the variance formula,
- /// so we drop the minus sign and maximize the sum of the two terms.
- ///
- /// The cube.
- /// The direction.
- /// 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)
- {
- long base_r = WuAlphaColorQuantizer.Bottom(cube, direction, this.vmr);
- long base_g = WuAlphaColorQuantizer.Bottom(cube, direction, this.vmg);
- long base_b = WuAlphaColorQuantizer.Bottom(cube, direction, this.vmb);
- long base_a = WuAlphaColorQuantizer.Bottom(cube, direction, this.vma);
- long base_w = WuAlphaColorQuantizer.Bottom(cube, direction, this.vwt);
-
- double max = 0.0;
- cut = -1;
-
- for (int i = first; i < last; i++)
- {
- double half_r = base_r + WuAlphaColorQuantizer.Top(cube, direction, i, this.vmr);
- double half_g = base_g + WuAlphaColorQuantizer.Top(cube, direction, i, this.vmg);
- double half_b = base_b + WuAlphaColorQuantizer.Top(cube, direction, i, this.vmb);
- double half_a = base_a + WuAlphaColorQuantizer.Top(cube, direction, i, this.vma);
- double half_w = base_w + WuAlphaColorQuantizer.Top(cube, direction, i, this.vwt);
-
- double temp;
-
- if (half_w == 0)
- {
- 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;
-
- if (half_w == 0)
- {
- continue;
- }
- else
- {
- temp += ((half_r * half_r) + (half_g * half_g) + (half_b * half_b) + (half_a * half_a)) / half_w;
- }
-
- if (temp > max)
- {
- max = temp;
- cut = i;
- }
- }
-
- return max;
- }
-
- ///
- /// Cuts a box.
- ///
- /// The first set.
- /// The second set.
- /// Returns a value indicating whether the box has been split.
- private bool Cut(Box set1, Box set2)
- {
- double whole_r = WuAlphaColorQuantizer.Volume(set1, this.vmr);
- double whole_g = WuAlphaColorQuantizer.Volume(set1, this.vmg);
- double whole_b = WuAlphaColorQuantizer.Volume(set1, this.vmb);
- double whole_a = WuAlphaColorQuantizer.Volume(set1, this.vma);
- double whole_w = WuAlphaColorQuantizer.Volume(set1, this.vwt);
-
- int cutr;
- int cutg;
- int cutb;
- int cuta;
-
- double maxr = this.Maximize(set1, 3, set1.R0 + 1, set1.R1, out cutr, whole_r, whole_g, whole_b, whole_a, whole_w);
- double maxg = this.Maximize(set1, 2, set1.G0 + 1, set1.G1, out cutg, whole_r, whole_g, whole_b, whole_a, whole_w);
- double maxb = this.Maximize(set1, 1, set1.B0 + 1, set1.B1, out cutb, whole_r, whole_g, whole_b, whole_a, whole_w);
- double maxa = this.Maximize(set1, 0, set1.A0 + 1, set1.A1, out cuta, whole_r, whole_g, whole_b, whole_a, whole_w);
-
- int dir;
-
- if ((maxr >= maxg) && (maxr >= maxb) && (maxr >= maxa))
- {
- dir = 3;
-
- if (cutr < 0)
- {
- return false;
- }
- }
- else if ((maxg >= maxr) && (maxg >= maxb) && (maxg >= maxa))
- {
- dir = 2;
- }
- else if ((maxb >= maxr) && (maxb >= maxg) && (maxb >= maxa))
- {
- dir = 1;
- }
- else
- {
- dir = 0;
- }
-
- set2.R1 = set1.R1;
- set2.G1 = set1.G1;
- set2.B1 = set1.B1;
- set2.A1 = set1.A1;
-
- switch (dir)
- {
- // Red
- case 3:
- set2.R0 = set1.R1 = cutr;
- set2.G0 = set1.G0;
- set2.B0 = set1.B0;
- set2.A0 = set1.A0;
- break;
-
- // Green
- case 2:
- set2.G0 = set1.G1 = cutg;
- set2.R0 = set1.R0;
- set2.B0 = set1.B0;
- set2.A0 = set1.A0;
- break;
-
- // Blue
- case 1:
- set2.B0 = set1.B1 = cutb;
- set2.R0 = set1.R0;
- set2.G0 = set1.G0;
- set2.A0 = set1.A0;
- break;
-
- // Alpha
- case 0:
- set2.A0 = set1.A1 = cuta;
- set2.R0 = set1.R0;
- set2.G0 = set1.G0;
- set2.B0 = set1.B0;
- break;
- }
-
- set1.Volume = (set1.R1 - set1.R0) * (set1.G1 - set1.G0) * (set1.B1 - set1.B0) * (set1.A1 - set1.A0);
- set2.Volume = (set2.R1 - set2.R0) * (set2.G1 - set2.G0) * (set2.B1 - set2.B0) * (set2.A1 - set2.A0);
-
- return true;
- }
-
- ///
- /// Marks a color space tag.
- ///
- /// The cube.
- /// A label.
- private void Mark(Box cube, byte label)
- {
- for (int r = cube.R0 + 1; r <= cube.R1; r++)
- {
- for (int g = cube.G0 + 1; g <= cube.G1; g++)
- {
- for (int b = cube.B0 + 1; b <= cube.B1; b++)
- {
- for (int a = cube.A0 + 1; a <= cube.A1; a++)
- {
- this.tag[WuAlphaColorQuantizer.Ind(r, g, b, a)] = label;
- }
- }
- }
- }
- }
-
- ///
- /// Builds the cube.
- ///
- /// The cube.
- /// The color count.
- private void BuildCube(out Box[] cube, ref int colorCount)
- {
- cube = new Box[colorCount];
- double[] vv = new double[colorCount];
-
- for (int i = 0; i < colorCount; i++)
- {
- cube[i] = new Box();
- }
-
- cube[0].R0 = cube[0].G0 = cube[0].B0 = cube[0].A0 = 0;
- cube[0].R1 = cube[0].G1 = cube[0].B1 = WuAlphaColorQuantizer.IndexCount - 1;
- cube[0].A1 = WuAlphaColorQuantizer.IndexAlphaCount - 1;
-
- int next = 0;
-
- for (int i = 1; i < colorCount; i++)
- {
- 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;
- }
- else
- {
- vv[next] = 0.0;
- i--;
- }
-
- next = 0;
-
- double temp = vv[0];
- for (int k = 1; k <= i; k++)
- {
- if (vv[k] > temp)
- {
- temp = vv[k];
- next = k;
- }
- }
-
- if (temp <= 0.0)
- {
- colorCount = i + 1;
- break;
- }
- }
- }
-
- ///
- /// Generates the quantized result.
- ///
- /// The image.
- /// The color count.
- /// The cube.
- /// The result.
- private QuantizedImage GenerateResult(ImageBase image, int colorCount, Box[] cube)
- {
- //var quantizedImage = new QuantizedImage(image.Length / 4, colorCount);
-
- List pallette = new List();
- byte[] pixels = new byte[image.Width * image.Height];
-
- for (int k = 0; k < colorCount; k++)
- {
- this.Mark(cube[k], (byte)k);
-
- double weight = WuAlphaColorQuantizer.Volume(cube[k], this.vwt);
-
- // TODO: Epsilon
- if (Math.Abs(weight) > .0001)
- {
- byte r = (byte)(WuAlphaColorQuantizer.Volume(cube[k], this.vmr) / weight);
- byte g = (byte)(WuAlphaColorQuantizer.Volume(cube[k], this.vmg) / weight);
- byte b = (byte)(WuAlphaColorQuantizer.Volume(cube[k], this.vmb) / weight);
- byte a = (byte)(WuAlphaColorQuantizer.Volume(cube[k], this.vma) / weight);
-
- pallette.Add(new Bgra32(r, g, b, a));
- }
- else
- {
- pallette.Add(new Bgra32(0, 0, 0));
- }
- }
-
- int i = 0;
- for (int y = 0; y < image.Height; y++)
- {
- for (int x = 0; x < image.Width; x++)
- {
- Bgra32 color = image[x, y];
- int a = color.A >> (8 - WuAlphaColorQuantizer.IndexAlphaBits);
- int r = color.R >> (8 - WuAlphaColorQuantizer.IndexBits);
- int g = color.G >> (8 - WuAlphaColorQuantizer.IndexBits);
- int b = color.B >> (8 - WuAlphaColorQuantizer.IndexBits);
-
- int ind = WuAlphaColorQuantizer.Ind(r + 1, g + 1, b + 1, a + 1);
- pixels[i++] = this.tag[ind];
- }
- }
-
- //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);
- }
- }
-}
\ No newline at end of file
diff --git a/src/ImageProcessorCore/Formats/Quantizers/Wu/WuQuantizer.cs b/src/ImageProcessorCore/Formats/Quantizers/Wu/WuQuantizer.cs
new file mode 100644
index 0000000000..b2a5ac8488
--- /dev/null
+++ b/src/ImageProcessorCore/Formats/Quantizers/Wu/WuQuantizer.cs
@@ -0,0 +1,796 @@
+//
+// Copyright (c) 2014-2015 Jérémy Ansel
+//
+//
+// Licensed under the MIT license. See LICENSE.txt
+//
+
+namespace ImageProcessorCore.Formats
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Threading.Tasks;
+
+ ///
+ /// An implementation of Wu's color quantizer with alpha channel.
+ ///
+ ///
+ ///
+ /// Based on C Implementation of Xiaolin Wu's Color Quantizer (v. 2)
+ /// (see Graphics Gems volume II, pages 126-133)
+ /// ().
+ ///
+ ///
+ /// Algorithm: Greedy orthogonal bipartition of RGB space for variance
+ /// minimization aided by inclusion-exclusion tricks.
+ /// For speed no nearest neighbor search is done. Slightly
+ /// better performance can be expected by more sophisticated
+ /// but more expensive versions.
+ ///
+ ///
+ [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "Wu", Justification = "Reviewed")]
+ public sealed class WuQuantizer : IQuantizer
+ {
+ ///
+ /// The index bits.
+ ///
+ private const int IndexBits = 6;
+
+ ///
+ /// The index alpha bits.
+ ///
+ private const int IndexAlphaBits = 3;
+
+ ///
+ /// The index count.
+ ///
+ private const int IndexCount = (1 << WuQuantizer.IndexBits) + 1;
+
+ ///
+ /// The index alpha count.
+ ///
+ private const int IndexAlphaCount = (1 << WuQuantizer.IndexAlphaBits) + 1;
+
+ ///
+ /// The table length.
+ ///
+ private const int TableLength = WuQuantizer.IndexCount * WuQuantizer.IndexCount * WuQuantizer.IndexCount * WuQuantizer.IndexAlphaCount;
+
+ ///
+ /// Maximum allowed color depth
+ ///
+ private readonly int maxColors;
+
+ ///
+ /// Moment of P(c).
+ ///
+ private long[] vwt;
+
+ ///
+ /// Moment of r*P(c).
+ ///
+ private long[] vmr;
+
+ ///
+ /// Moment of g*P(c).
+ ///
+ private long[] vmg;
+
+ ///
+ /// Moment of b*P(c).
+ ///
+ private long[] vmb;
+
+ ///
+ /// Moment of a*P(c).
+ ///
+ private long[] vma;
+
+ ///
+ /// Moment of c^2*P(c).
+ ///
+ private double[] m2;
+
+ ///
+ /// Color space tag.
+ ///
+ private byte[] tag;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public WuQuantizer()
+ : this(256)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The maximum number of colors to return
+ public WuQuantizer(int maxColors)
+ {
+ Guard.MustBeBetweenOrEqualTo(maxColors, 1, 256, nameof(maxColors));
+
+ this.maxColors = maxColors;
+ this.vwt = new long[WuQuantizer.TableLength];
+ this.vmr = new long[WuQuantizer.TableLength];
+ this.vmg = new long[WuQuantizer.TableLength];
+ this.vmb = new long[WuQuantizer.TableLength];
+ this.vma = new long[WuQuantizer.TableLength];
+ this.m2 = new double[WuQuantizer.TableLength];
+ this.tag = new byte[WuQuantizer.TableLength];
+ }
+
+ ///
+ public QuantizedImage Quantize(ImageBase image)
+ {
+ Guard.NotNull(image, nameof(image));
+
+ int colorCount = this.maxColors;
+
+ this.Clear();
+
+ this.Hist3d(image);
+ this.M3d();
+
+ Box[] cube;
+ this.BuildCube(out cube, ref colorCount);
+
+ return this.GenerateResult(image, colorCount, cube);
+ }
+
+ ///
+ /// Gets an index.
+ ///
+ /// The red value.
+ /// The green value.
+ /// The blue value.
+ /// The alpha value.
+ /// The index.
+ private static int Ind(int r, int g, int b, int a)
+ {
+ return (r << ((WuQuantizer.IndexBits * 2) + WuQuantizer.IndexAlphaBits))
+ + (r << (WuQuantizer.IndexBits + WuQuantizer.IndexAlphaBits + 1))
+ + (g << (WuQuantizer.IndexBits + WuQuantizer.IndexAlphaBits))
+ + (r << (WuQuantizer.IndexBits * 2))
+ + (r << (WuQuantizer.IndexBits + 1))
+ + (g << WuQuantizer.IndexBits)
+ + ((r + g + b) << WuQuantizer.IndexAlphaBits)
+ + r + g + b + a;
+ }
+
+ ///
+ /// Computes sum over a box of any given statistic.
+ ///
+ /// The cube.
+ /// The moment.
+ /// The result.
+ private static double Volume(Box cube, long[] moment)
+ {
+ return moment[WuQuantizer.Ind(cube.R1, cube.G1, cube.B1, cube.A1)]
+ - moment[WuQuantizer.Ind(cube.R1, cube.G1, cube.B1, cube.A0)]
+ - moment[WuQuantizer.Ind(cube.R1, cube.G1, cube.B0, cube.A1)]
+ + moment[WuQuantizer.Ind(cube.R1, cube.G1, cube.B0, cube.A0)]
+ - moment[WuQuantizer.Ind(cube.R1, cube.G0, cube.B1, cube.A1)]
+ + moment[WuQuantizer.Ind(cube.R1, cube.G0, cube.B1, cube.A0)]
+ + moment[WuQuantizer.Ind(cube.R1, cube.G0, cube.B0, cube.A1)]
+ - moment[WuQuantizer.Ind(cube.R1, cube.G0, cube.B0, cube.A0)]
+ - moment[WuQuantizer.Ind(cube.R0, cube.G1, cube.B1, cube.A1)]
+ + moment[WuQuantizer.Ind(cube.R0, cube.G1, cube.B1, cube.A0)]
+ + moment[WuQuantizer.Ind(cube.R0, cube.G1, cube.B0, cube.A1)]
+ - moment[WuQuantizer.Ind(cube.R0, cube.G1, cube.B0, cube.A0)]
+ + moment[WuQuantizer.Ind(cube.R0, cube.G0, cube.B1, cube.A1)]
+ - moment[WuQuantizer.Ind(cube.R0, cube.G0, cube.B1, cube.A0)]
+ - moment[WuQuantizer.Ind(cube.R0, cube.G0, cube.B0, cube.A1)]
+ + moment[WuQuantizer.Ind(cube.R0, cube.G0, cube.B0, cube.A0)];
+ }
+
+ ///
+ /// Computes part of Volume(cube, moment) that doesn't depend on r1, g1, or b1 (depending on direction).
+ ///
+ /// The cube.
+ /// The direction.
+ /// The moment.
+ /// The result.
+ private static long Bottom(Box cube, int direction, long[] moment)
+ {
+ switch (direction)
+ {
+ // Red
+ case 0:
+ return -moment[WuQuantizer.Ind(cube.R0, cube.G1, cube.B1, cube.A1)]
+ + moment[WuQuantizer.Ind(cube.R0, cube.G1, cube.B1, cube.A0)]
+ + moment[WuQuantizer.Ind(cube.R0, cube.G1, cube.B0, cube.A1)]
+ - moment[WuQuantizer.Ind(cube.R0, cube.G1, cube.B0, cube.A0)]
+ + moment[WuQuantizer.Ind(cube.R0, cube.G0, cube.B1, cube.A1)]
+ - moment[WuQuantizer.Ind(cube.R0, cube.G0, cube.B1, cube.A0)]
+ - moment[WuQuantizer.Ind(cube.R0, cube.G0, cube.B0, cube.A1)]
+ + moment[WuQuantizer.Ind(cube.R0, cube.G0, cube.B0, cube.A0)];
+
+ // Green
+ case 1:
+ return -moment[WuQuantizer.Ind(cube.R1, cube.G0, cube.B1, cube.A1)]
+ + moment[WuQuantizer.Ind(cube.R1, cube.G0, cube.B1, cube.A0)]
+ + moment[WuQuantizer.Ind(cube.R1, cube.G0, cube.B0, cube.A1)]
+ - moment[WuQuantizer.Ind(cube.R1, cube.G0, cube.B0, cube.A0)]
+ + moment[WuQuantizer.Ind(cube.R0, cube.G0, cube.B1, cube.A1)]
+ - moment[WuQuantizer.Ind(cube.R0, cube.G0, cube.B1, cube.A0)]
+ - moment[WuQuantizer.Ind(cube.R0, cube.G0, cube.B0, cube.A1)]
+ + moment[WuQuantizer.Ind(cube.R0, cube.G0, cube.B0, cube.A0)];
+
+ // Blue
+ case 2:
+ return -moment[WuQuantizer.Ind(cube.R1, cube.G1, cube.B0, cube.A1)]
+ + moment[WuQuantizer.Ind(cube.R1, cube.G1, cube.B0, cube.A0)]
+ + moment[WuQuantizer.Ind(cube.R1, cube.G0, cube.B0, cube.A1)]
+ - moment[WuQuantizer.Ind(cube.R1, cube.G0, cube.B0, cube.A0)]
+ + moment[WuQuantizer.Ind(cube.R0, cube.G1, cube.B0, cube.A1)]
+ - moment[WuQuantizer.Ind(cube.R0, cube.G1, cube.B0, cube.A0)]
+ - moment[WuQuantizer.Ind(cube.R0, cube.G0, cube.B0, cube.A1)]
+ + moment[WuQuantizer.Ind(cube.R0, cube.G0, cube.B0, cube.A0)];
+
+ // Alpha
+ case 3:
+ return -moment[WuQuantizer.Ind(cube.R1, cube.G1, cube.B1, cube.A0)]
+ + moment[WuQuantizer.Ind(cube.R1, cube.G1, cube.B0, cube.A0)]
+ + moment[WuQuantizer.Ind(cube.R1, cube.G0, cube.B1, cube.A0)]
+ - moment[WuQuantizer.Ind(cube.R1, cube.G0, cube.B0, cube.A0)]
+ + moment[WuQuantizer.Ind(cube.R0, cube.G1, cube.B1, cube.A0)]
+ - moment[WuQuantizer.Ind(cube.R0, cube.G1, cube.B0, cube.A0)]
+ - moment[WuQuantizer.Ind(cube.R0, cube.G0, cube.B1, cube.A0)]
+ + moment[WuQuantizer.Ind(cube.R0, cube.G0, cube.B0, cube.A0)];
+
+ default:
+ throw new ArgumentOutOfRangeException(nameof(direction));
+ }
+ }
+
+ ///
+ /// Computes remainder of Volume(cube, moment), substituting position for r1, g1, or b1 (depending on direction).
+ ///
+ /// The cube.
+ /// The direction.
+ /// The position.
+ /// The moment.
+ /// The result.
+ private static long Top(Box cube, int direction, int position, long[] moment)
+ {
+ switch (direction)
+ {
+ // Red
+ case 0:
+ return moment[WuQuantizer.Ind(position, cube.G1, cube.B1, cube.A1)]
+ - moment[WuQuantizer.Ind(position, cube.G1, cube.B1, cube.A0)]
+ - moment[WuQuantizer.Ind(position, cube.G1, cube.B0, cube.A1)]
+ + moment[WuQuantizer.Ind(position, cube.G1, cube.B0, cube.A0)]
+ - moment[WuQuantizer.Ind(position, cube.G0, cube.B1, cube.A1)]
+ + moment[WuQuantizer.Ind(position, cube.G0, cube.B1, cube.A0)]
+ + moment[WuQuantizer.Ind(position, cube.G0, cube.B0, cube.A1)]
+ - moment[WuQuantizer.Ind(position, cube.G0, cube.B0, cube.A0)];
+
+ // Green
+ case 1:
+ return moment[WuQuantizer.Ind(cube.R1, position, cube.B1, cube.A1)]
+ - moment[WuQuantizer.Ind(cube.R1, position, cube.B1, cube.A0)]
+ - moment[WuQuantizer.Ind(cube.R1, position, cube.B0, cube.A1)]
+ + moment[WuQuantizer.Ind(cube.R1, position, cube.B0, cube.A0)]
+ - moment[WuQuantizer.Ind(cube.R0, position, cube.B1, cube.A1)]
+ + moment[WuQuantizer.Ind(cube.R0, position, cube.B1, cube.A0)]
+ + moment[WuQuantizer.Ind(cube.R0, position, cube.B0, cube.A1)]
+ - moment[WuQuantizer.Ind(cube.R0, position, cube.B0, cube.A0)];
+
+ // Blue
+ case 2:
+ return moment[WuQuantizer.Ind(cube.R1, cube.G1, position, cube.A1)]
+ - moment[WuQuantizer.Ind(cube.R1, cube.G1, position, cube.A0)]
+ - moment[WuQuantizer.Ind(cube.R1, cube.G0, position, cube.A1)]
+ + moment[WuQuantizer.Ind(cube.R1, cube.G0, position, cube.A0)]
+ - moment[WuQuantizer.Ind(cube.R0, cube.G1, position, cube.A1)]
+ + moment[WuQuantizer.Ind(cube.R0, cube.G1, position, cube.A0)]
+ + moment[WuQuantizer.Ind(cube.R0, cube.G0, position, cube.A1)]
+ - moment[WuQuantizer.Ind(cube.R0, cube.G0, position, cube.A0)];
+
+ // Alpha
+ case 3:
+ return moment[WuQuantizer.Ind(cube.R1, cube.G1, cube.B1, position)]
+ - moment[WuQuantizer.Ind(cube.R1, cube.G1, cube.B0, position)]
+ - moment[WuQuantizer.Ind(cube.R1, cube.G0, cube.B1, position)]
+ + moment[WuQuantizer.Ind(cube.R1, cube.G0, cube.B0, position)]
+ - moment[WuQuantizer.Ind(cube.R0, cube.G1, cube.B1, position)]
+ + moment[WuQuantizer.Ind(cube.R0, cube.G1, cube.B0, position)]
+ + moment[WuQuantizer.Ind(cube.R0, cube.G0, cube.B1, position)]
+ - moment[WuQuantizer.Ind(cube.R0, cube.G0, cube.B0, position)];
+
+ default:
+ throw new ArgumentOutOfRangeException("direction");
+ }
+ }
+
+ ///
+ /// Clears the tables.
+ ///
+ private void Clear()
+ {
+ Array.Clear(this.vwt, 0, WuQuantizer.TableLength);
+ Array.Clear(this.vmr, 0, WuQuantizer.TableLength);
+ Array.Clear(this.vmg, 0, WuQuantizer.TableLength);
+ Array.Clear(this.vmb, 0, WuQuantizer.TableLength);
+ Array.Clear(this.vma, 0, WuQuantizer.TableLength);
+ Array.Clear(this.m2, 0, WuQuantizer.TableLength);
+
+ Array.Clear(this.tag, 0, WuQuantizer.TableLength);
+ }
+
+ ///
+ /// Builds a 3-D color histogram of counts, r/g/b, c^2.
+ ///
+ /// The image.
+ private void Hist3d(ImageBase image)
+ {
+ // TODO: Parallel
+ for (int y = 0; y < image.Height; y++)
+ {
+ for (int x = 0; x < image.Width; x++)
+ {
+ Bgra32 color = image[x, y];
+
+ byte r = color.R;
+ byte g = color.G;
+ byte b = color.B;
+ byte a = color.A;
+
+ int inr = r >> (8 - WuQuantizer.IndexBits);
+ int ing = g >> (8 - WuQuantizer.IndexBits);
+ int inb = b >> (8 - WuQuantizer.IndexBits);
+ int ina = a >> (8 - WuQuantizer.IndexAlphaBits);
+
+ int ind = WuQuantizer.Ind(inr + 1, ing + 1, inb + 1, ina + 1);
+
+ this.vwt[ind]++;
+ this.vmr[ind] += r;
+ this.vmg[ind] += g;
+ this.vmb[ind] += b;
+ this.vma[ind] += a;
+ this.m2[ind] += (r * r) + (g * g) + (b * b) + (a * a);
+ }
+ }
+ }
+
+ ///
+ /// Converts the histogram into moments so that we can rapidly calculate
+ /// the sums of the above quantities over any desired box.
+ ///
+ private void M3d()
+ {
+ long[] volume = new long[WuQuantizer.IndexCount * WuQuantizer.IndexAlphaCount];
+ long[] volume_r = new long[WuQuantizer.IndexCount * WuQuantizer.IndexAlphaCount];
+ long[] volume_g = new long[WuQuantizer.IndexCount * WuQuantizer.IndexAlphaCount];
+ long[] volume_b = new long[WuQuantizer.IndexCount * WuQuantizer.IndexAlphaCount];
+ long[] volume_a = new long[WuQuantizer.IndexCount * WuQuantizer.IndexAlphaCount];
+ double[] volume2 = new double[WuQuantizer.IndexCount * WuQuantizer.IndexAlphaCount];
+
+ long[] area = new long[WuQuantizer.IndexAlphaCount];
+ long[] area_r = new long[WuQuantizer.IndexAlphaCount];
+ long[] area_g = new long[WuQuantizer.IndexAlphaCount];
+ long[] area_b = new long[WuQuantizer.IndexAlphaCount];
+ long[] area_a = new long[WuQuantizer.IndexAlphaCount];
+ double[] area2 = new double[WuQuantizer.IndexAlphaCount];
+
+ for (int r = 1; r < WuQuantizer.IndexCount; r++)
+ {
+ Array.Clear(volume, 0, WuQuantizer.IndexCount * WuQuantizer.IndexAlphaCount);
+ Array.Clear(volume_r, 0, WuQuantizer.IndexCount * WuQuantizer.IndexAlphaCount);
+ Array.Clear(volume_g, 0, WuQuantizer.IndexCount * WuQuantizer.IndexAlphaCount);
+ Array.Clear(volume_b, 0, WuQuantizer.IndexCount * WuQuantizer.IndexAlphaCount);
+ Array.Clear(volume_a, 0, WuQuantizer.IndexCount * WuQuantizer.IndexAlphaCount);
+ Array.Clear(volume2, 0, WuQuantizer.IndexCount * WuQuantizer.IndexAlphaCount);
+
+ for (int g = 1; g < WuQuantizer.IndexCount; g++)
+ {
+ Array.Clear(area, 0, WuQuantizer.IndexAlphaCount);
+ Array.Clear(area_r, 0, WuQuantizer.IndexAlphaCount);
+ Array.Clear(area_g, 0, WuQuantizer.IndexAlphaCount);
+ Array.Clear(area_b, 0, WuQuantizer.IndexAlphaCount);
+ Array.Clear(area_a, 0, WuQuantizer.IndexAlphaCount);
+ Array.Clear(area2, 0, WuQuantizer.IndexAlphaCount);
+
+ for (int b = 1; b < WuQuantizer.IndexCount; b++)
+ {
+ long line = 0;
+ long line_r = 0;
+ long line_g = 0;
+ long line_b = 0;
+ long line_a = 0;
+ double line2 = 0;
+
+ for (int a = 1; a < WuQuantizer.IndexAlphaCount; a++)
+ {
+ int ind1 = WuQuantizer.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];
+ 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;
+ area2[a] += line2;
+
+ int inv = (b * WuQuantizer.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];
+ volume2[inv] += area2[a];
+
+ int ind2 = ind1 - WuQuantizer.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.m2[ind1] = this.m2[ind2] + volume2[inv];
+ }
+ }
+ }
+ }
+ }
+
+ ///
+ /// Computes the weighted variance of a box.
+ ///
+ /// The cube.
+ /// The result.
+ private double Var(Box cube)
+ {
+ double dr = WuQuantizer.Volume(cube, this.vmr);
+ double dg = WuQuantizer.Volume(cube, this.vmg);
+ double db = WuQuantizer.Volume(cube, this.vmb);
+ double da = WuQuantizer.Volume(cube, this.vma);
+
+ double xx =
+ this.m2[WuQuantizer.Ind(cube.R1, cube.G1, cube.B1, cube.A1)]
+ - this.m2[WuQuantizer.Ind(cube.R1, cube.G1, cube.B1, cube.A0)]
+ - this.m2[WuQuantizer.Ind(cube.R1, cube.G1, cube.B0, cube.A1)]
+ + this.m2[WuQuantizer.Ind(cube.R1, cube.G1, cube.B0, cube.A0)]
+ - this.m2[WuQuantizer.Ind(cube.R1, cube.G0, cube.B1, cube.A1)]
+ + this.m2[WuQuantizer.Ind(cube.R1, cube.G0, cube.B1, cube.A0)]
+ + this.m2[WuQuantizer.Ind(cube.R1, cube.G0, cube.B0, cube.A1)]
+ - this.m2[WuQuantizer.Ind(cube.R1, cube.G0, cube.B0, cube.A0)]
+ - this.m2[WuQuantizer.Ind(cube.R0, cube.G1, cube.B1, cube.A1)]
+ + this.m2[WuQuantizer.Ind(cube.R0, cube.G1, cube.B1, cube.A0)]
+ + this.m2[WuQuantizer.Ind(cube.R0, cube.G1, cube.B0, cube.A1)]
+ - this.m2[WuQuantizer.Ind(cube.R0, cube.G1, cube.B0, cube.A0)]
+ + this.m2[WuQuantizer.Ind(cube.R0, cube.G0, cube.B1, cube.A1)]
+ - this.m2[WuQuantizer.Ind(cube.R0, cube.G0, cube.B1, cube.A0)]
+ - this.m2[WuQuantizer.Ind(cube.R0, cube.G0, cube.B0, cube.A1)]
+ + this.m2[WuQuantizer.Ind(cube.R0, cube.G0, cube.B0, cube.A0)];
+
+ return xx - (((dr * dr) + (dg * dg) + (db * db) + (da * da)) / WuQuantizer.Volume(cube, this.vwt));
+ }
+
+ ///
+ /// We want to minimize the sum of the variances of two sub-boxes.
+ /// The sum(c^2) terms can be ignored since their sum over both sub-boxes
+ /// is the same (the sum for the whole box) no matter where we split.
+ /// The remaining terms have a minus sign in the variance formula,
+ /// so we drop the minus sign and maximize the sum of the two terms.
+ ///
+ /// The cube.
+ /// The direction.
+ /// 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)
+ {
+ long base_r = WuQuantizer.Bottom(cube, direction, this.vmr);
+ long base_g = WuQuantizer.Bottom(cube, direction, this.vmg);
+ long base_b = WuQuantizer.Bottom(cube, direction, this.vmb);
+ long base_a = WuQuantizer.Bottom(cube, direction, this.vma);
+ long base_w = WuQuantizer.Bottom(cube, direction, this.vwt);
+
+ double max = 0.0;
+ cut = -1;
+
+ for (int i = first; i < last; i++)
+ {
+ double half_r = base_r + WuQuantizer.Top(cube, direction, i, this.vmr);
+ double half_g = base_g + WuQuantizer.Top(cube, direction, i, this.vmg);
+ double half_b = base_b + WuQuantizer.Top(cube, direction, i, this.vmb);
+ double half_a = base_a + WuQuantizer.Top(cube, direction, i, this.vma);
+ double half_w = base_w + WuQuantizer.Top(cube, direction, i, this.vwt);
+
+ double temp;
+
+ // TODO: Epsilon
+ if (Math.Abs(half_w) < 0.001)
+ {
+ 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;
+
+ // TODO: Epsilon
+ if (Math.Abs(half_w) < 0.001)
+ {
+ continue;
+ }
+ else
+ {
+ temp += ((half_r * half_r) + (half_g * half_g) + (half_b * half_b) + (half_a * half_a)) / half_w;
+ }
+
+ if (temp > max)
+ {
+ max = temp;
+ cut = i;
+ }
+ }
+
+ return max;
+ }
+
+ ///
+ /// Cuts a box.
+ ///
+ /// The first set.
+ /// The second set.
+ /// Returns a value indicating whether the box has been split.
+ private bool Cut(Box set1, Box set2)
+ {
+ double whole_r = WuQuantizer.Volume(set1, this.vmr);
+ double whole_g = WuQuantizer.Volume(set1, this.vmg);
+ double whole_b = WuQuantizer.Volume(set1, this.vmb);
+ double whole_a = WuQuantizer.Volume(set1, this.vma);
+ double whole_w = WuQuantizer.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);
+
+ int dir;
+
+ if ((maxr >= maxg) && (maxr >= maxb) && (maxr >= maxa))
+ {
+ dir = 0;
+
+ if (cutr < 0)
+ {
+ return false;
+ }
+ }
+ else if ((maxg >= maxr) && (maxg >= maxb) && (maxg >= maxa))
+ {
+ dir = 1;
+ }
+ else if ((maxb >= maxr) && (maxb >= maxg) && (maxb >= maxa))
+ {
+ dir = 2;
+ }
+ else
+ {
+ dir = 3;
+ }
+
+ set2.R1 = set1.R1;
+ set2.G1 = set1.G1;
+ set2.B1 = set1.B1;
+ set2.A1 = set1.A1;
+
+ switch (dir)
+ {
+ // Red
+ case 0:
+ set2.R0 = set1.R1 = cutr;
+ set2.G0 = set1.G0;
+ set2.B0 = set1.B0;
+ set2.A0 = set1.A0;
+ break;
+
+ // Green
+ case 1:
+ set2.G0 = set1.G1 = cutg;
+ set2.R0 = set1.R0;
+ set2.B0 = set1.B0;
+ set2.A0 = set1.A0;
+ break;
+
+ // Blue
+ case 2:
+ set2.B0 = set1.B1 = cutb;
+ set2.R0 = set1.R0;
+ set2.G0 = set1.G0;
+ set2.A0 = set1.A0;
+ break;
+
+ // Alpha
+ case 3:
+ set2.A0 = set1.A1 = cuta;
+ set2.R0 = set1.R0;
+ set2.G0 = set1.G0;
+ set2.B0 = set1.B0;
+ break;
+ }
+
+ set1.Volume = (set1.R1 - set1.R0) * (set1.G1 - set1.G0) * (set1.B1 - set1.B0) * (set1.A1 - set1.A0);
+ set2.Volume = (set2.R1 - set2.R0) * (set2.G1 - set2.G0) * (set2.B1 - set2.B0) * (set2.A1 - set2.A0);
+
+ return true;
+ }
+
+ ///
+ /// Marks a color space tag.
+ ///
+ /// The cube.
+ /// A label.
+ private void Mark(Box cube, byte label)
+ {
+ for (int r = cube.R0 + 1; r <= cube.R1; r++)
+ {
+ for (int g = cube.G0 + 1; g <= cube.G1; g++)
+ {
+ for (int b = cube.B0 + 1; b <= cube.B1; b++)
+ {
+ for (int a = cube.A0 + 1; a <= cube.A1; a++)
+ {
+ this.tag[WuQuantizer.Ind(r, g, b, a)] = label;
+ }
+ }
+ }
+ }
+ }
+
+ ///
+ /// Builds the cube.
+ ///
+ /// The cube.
+ /// The color count.
+ private void BuildCube(out Box[] cube, ref int colorCount)
+ {
+ cube = new Box[colorCount];
+ double[] vv = new double[colorCount];
+
+ for (int i = 0; i < colorCount; i++)
+ {
+ cube[i] = new Box();
+ }
+
+ cube[0].R0 = cube[0].G0 = cube[0].B0 = cube[0].A0 = 0;
+ cube[0].R1 = cube[0].G1 = cube[0].B1 = WuQuantizer.IndexCount - 1;
+ cube[0].A1 = WuQuantizer.IndexAlphaCount - 1;
+
+ int next = 0;
+
+ for (int i = 1; i < colorCount; i++)
+ {
+ 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;
+ }
+ else
+ {
+ vv[next] = 0.0;
+ i--;
+ }
+
+ next = 0;
+
+ double temp = vv[0];
+ for (int k = 1; k <= i; k++)
+ {
+ if (vv[k] > temp)
+ {
+ temp = vv[k];
+ next = k;
+ }
+ }
+
+ if (temp <= 0.0)
+ {
+ colorCount = i + 1;
+ break;
+ }
+ }
+ }
+
+ ///
+ /// Generates the quantized result.
+ ///
+ /// The image.
+ /// The color count.
+ /// The cube.
+ /// The result.
+ private QuantizedImage GenerateResult(ImageBase image, int colorCount, Box[] cube)
+ {
+ List pallette = new List();
+ byte[] pixels = new byte[image.Width * image.Height];
+
+ Parallel.For(
+ 0,
+ colorCount,
+ k =>
+ {
+ this.Mark(cube[k], (byte)k);
+
+ double weight = WuQuantizer.Volume(cube[k], this.vwt);
+
+ // TODO: Epsilon
+ if (Math.Abs(weight) > .0001)
+ {
+ byte r = (byte)(WuQuantizer.Volume(cube[k], this.vmr) / weight);
+ byte g = (byte)(WuQuantizer.Volume(cube[k], this.vmg) / weight);
+ byte b = (byte)(WuQuantizer.Volume(cube[k], this.vmb) / weight);
+ byte a = (byte)(WuQuantizer.Volume(cube[k], this.vma) / weight);
+
+ pallette.Add(new Bgra32(b, g, r, a));
+ }
+ else
+ {
+ pallette.Add(new Bgra32(0, 0, 0));
+ }
+ });
+
+ // TODO: Optimize here.
+ int i = 0;
+ for (int y = 0; y < image.Height; y++)
+ {
+ for (int x = 0; x < image.Width; x++)
+ {
+ Bgra32 color = image[x, y];
+ int a = color.A >> (8 - WuQuantizer.IndexAlphaBits);
+ int r = color.R >> (8 - WuQuantizer.IndexBits);
+ int g = color.G >> (8 - WuQuantizer.IndexBits);
+ int b = color.B >> (8 - WuQuantizer.IndexBits);
+
+ int ind = WuQuantizer.Ind(r + 1, g + 1, b + 1, a + 1);
+ pixels[i++] = this.tag[ind];
+ }
+ }
+
+ //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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/ImageProcessorCore.Tests/Processors/Formats/EncoderDecoderTests.cs b/tests/ImageProcessorCore.Tests/Processors/Formats/EncoderDecoderTests.cs
index bec08f27ce..42e5022ced 100644
--- a/tests/ImageProcessorCore.Tests/Processors/Formats/EncoderDecoderTests.cs
+++ b/tests/ImageProcessorCore.Tests/Processors/Formats/EncoderDecoderTests.cs
@@ -54,7 +54,7 @@
using (FileStream stream = File.OpenRead(file))
{
Image image = new Image(stream);
- IQuantizer quantizer = new WuAlphaColorQuantizer();
+ IQuantizer quantizer = new WuQuantizer();
QuantizedImage quantizedImage = quantizer.Quantize(image);
using (FileStream output = File.OpenWrite($"TestOutput/Quantize/{Path.GetFileName(file)}"))