/// <summary>
        /// Computes remainder of Volume(cube, moment), substituting position for r1, g1, or b1 (depending on direction).
        /// </summary>
        /// <param name="cube">The cube.</param>
        /// <param name="direction">The direction.</param>
        /// <param name="position">The position.</param>
        /// <param name="moment">The moment.</param>
        /// <returns>The result.</returns>
        private static long Top(Box cube, int direction, int position, long[] moment)
        {
            switch (direction)
            {
            // Red
            case 2:
                return(moment[WuColorQuantizer.GetIndex(position, cube.G1, cube.B1)]
                       - moment[WuColorQuantizer.GetIndex(position, cube.G1, cube.B0)]
                       - moment[WuColorQuantizer.GetIndex(position, cube.G0, cube.B1)]
                       + moment[WuColorQuantizer.GetIndex(position, cube.G0, cube.B0)]);

            // Green
            case 1:
                return(moment[WuColorQuantizer.GetIndex(cube.R1, position, cube.B1)]
                       - moment[WuColorQuantizer.GetIndex(cube.R1, position, cube.B0)]
                       - moment[WuColorQuantizer.GetIndex(cube.R0, position, cube.B1)]
                       + moment[WuColorQuantizer.GetIndex(cube.R0, position, cube.B0)]);

            // Blue
            case 0:
                return(moment[WuColorQuantizer.GetIndex(cube.R1, cube.G1, position)]
                       - moment[WuColorQuantizer.GetIndex(cube.R1, cube.G0, position)]
                       - moment[WuColorQuantizer.GetIndex(cube.R0, cube.G1, position)]
                       + moment[WuColorQuantizer.GetIndex(cube.R0, cube.G0, position)]);

            default:
                throw new ArgumentOutOfRangeException("direction");
            }
        }
 /// <summary>
 /// Computes sum over a box of any given statistic.
 /// </summary>
 /// <param name="cube">The cube.</param>
 /// <param name="moment">The moment.</param>
 /// <returns>The result.</returns>
 private static double Volume(Box cube, long[] moment)
 {
     return(moment[WuColorQuantizer.GetIndex(cube.R1, cube.G1, cube.B1)]
            - moment[WuColorQuantizer.GetIndex(cube.R1, cube.G1, cube.B0)]
            - moment[WuColorQuantizer.GetIndex(cube.R1, cube.G0, cube.B1)]
            + moment[WuColorQuantizer.GetIndex(cube.R1, cube.G0, cube.B0)]
            - moment[WuColorQuantizer.GetIndex(cube.R0, cube.G1, cube.B1)]
            + moment[WuColorQuantizer.GetIndex(cube.R0, cube.G1, cube.B0)]
            + moment[WuColorQuantizer.GetIndex(cube.R0, cube.G0, cube.B1)]
            - moment[WuColorQuantizer.GetIndex(cube.R0, cube.G0, cube.B0)]);
 }
 /// <summary>
 /// Marks a color space tag.
 /// </summary>
 /// <param name="cube">The cube.</param>
 /// <param name="label">A label.</param>
 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++)
             {
                 this.tag[WuColorQuantizer.GetIndex(r, g, b)] = label;
             }
         }
     }
 }
        /// <summary>
        /// Converts the histogram into moments so that we can rapidly calculate
        /// the sums of the above quantities over any desired box.
        /// </summary>
        private void Get3DMoments()
        {
            long[]   area  = new long[WuColorQuantizer.IndexCount];
            long[]   areaR = new long[WuColorQuantizer.IndexCount];
            long[]   areaG = new long[WuColorQuantizer.IndexCount];
            long[]   areaB = new long[WuColorQuantizer.IndexCount];
            double[] area2 = new double[WuColorQuantizer.IndexCount];

            for (int r = 1; r < WuColorQuantizer.IndexCount; r++)
            {
                Array.Clear(area, 0, WuColorQuantizer.IndexCount);
                Array.Clear(areaR, 0, WuColorQuantizer.IndexCount);
                Array.Clear(areaG, 0, WuColorQuantizer.IndexCount);
                Array.Clear(areaB, 0, WuColorQuantizer.IndexCount);
                Array.Clear(area2, 0, WuColorQuantizer.IndexCount);

                for (int g = 1; g < WuColorQuantizer.IndexCount; g++)
                {
                    long   line  = 0;
                    long   lineR = 0;
                    long   lineG = 0;
                    long   lineB = 0;
                    double line2 = 0;

                    for (int b = 1; b < WuColorQuantizer.IndexCount; b++)
                    {
                        int ind1 = WuColorQuantizer.GetIndex(r, g, b);

                        line  += this.vwt[ind1];
                        lineR += this.vmr[ind1];
                        lineG += this.vmg[ind1];
                        lineB += this.vmb[ind1];
                        line2 += this.m2[ind1];

                        area[b]  += line;
                        areaR[b] += lineR;
                        areaG[b] += lineG;
                        areaB[b] += lineB;
                        area2[b] += line2;

                        int ind2 = ind1 - WuColorQuantizer.GetIndex(1, 0, 0);

                        this.vwt[ind1] = this.vwt[ind2] + area[b];
                        this.vmr[ind1] = this.vmr[ind2] + areaR[b];
                        this.vmg[ind1] = this.vmg[ind2] + areaG[b];
                        this.vmb[ind1] = this.vmb[ind2] + areaB[b];
                        this.m2[ind1]  = this.m2[ind2] + area2[b];
                    }
                }
            }
        }
        /// <summary>
        /// Computes the weighted variance of a box.
        /// </summary>
        /// <param name="cube">The cube.</param>
        /// <returns>The result.</returns>
        private double Variance(Box cube)
        {
            double dr = WuColorQuantizer.Volume(cube, this.vmr);
            double dg = WuColorQuantizer.Volume(cube, this.vmg);
            double db = WuColorQuantizer.Volume(cube, this.vmb);

            double xx = this.m2[WuColorQuantizer.GetIndex(cube.R1, cube.G1, cube.B1)]
                        - this.m2[WuColorQuantizer.GetIndex(cube.R1, cube.G1, cube.B0)]
                        - this.m2[WuColorQuantizer.GetIndex(cube.R1, cube.G0, cube.B1)]
                        + this.m2[WuColorQuantizer.GetIndex(cube.R1, cube.G0, cube.B0)]
                        - this.m2[WuColorQuantizer.GetIndex(cube.R0, cube.G1, cube.B1)]
                        + this.m2[WuColorQuantizer.GetIndex(cube.R0, cube.G1, cube.B0)]
                        + this.m2[WuColorQuantizer.GetIndex(cube.R0, cube.G0, cube.B1)]
                        - this.m2[WuColorQuantizer.GetIndex(cube.R0, cube.G0, cube.B0)];

            return(xx - (((dr * dr) + (dg * dg) + (db * db)) / WuColorQuantizer.Volume(cube, this.vwt)));
        }
        /// <summary>
        /// Builds a 3-D color histogram of <c>counts, r/g/b, c^2</c>.
        /// </summary>
        /// <param name="image">The image.</param>
        private void Build3DHistogram(byte[] image)
        {
            for (int i = 0; i < image.Length; i += 4)
            {
                int r = image[i + 2];
                int g = image[i + 1];
                int b = image[i];

                int inr = r >> (8 - WuColorQuantizer.IndexBits);
                int ing = g >> (8 - WuColorQuantizer.IndexBits);
                int inb = b >> (8 - WuColorQuantizer.IndexBits);

                int ind = WuColorQuantizer.GetIndex(inr + 1, ing + 1, inb + 1);

                this.vwt[ind]++;
                this.vmr[ind] += r;
                this.vmg[ind] += g;
                this.vmb[ind] += b;
                this.m2[ind]  += (r * r) + (g * g) + (b * b);
            }
        }
        /// <summary>
        /// Generates the quantized result.
        /// </summary>
        /// <param name="image">The image.</param>
        /// <param name="colorCount">The color count.</param>
        /// <param name="cube">The cube.</param>
        /// <returns>The result.</returns>
        private ColorQuantizerResult GenerateResult(byte[] image, int colorCount, Box[] cube)
        {
            var quantizedImage = new ColorQuantizerResult(image.Length / 4, colorCount);

            for (int k = 0; k < colorCount; k++)
            {
                this.Mark(cube[k], (byte)k);

                double weight = WuColorQuantizer.Volume(cube[k], this.vwt);

                if (weight != 0)
                {
                    quantizedImage.Palette[(k * 4) + 3] = 0xff;
                    quantizedImage.Palette[(k * 4) + 2] = (byte)(WuColorQuantizer.Volume(cube[k], this.vmr) / weight);
                    quantizedImage.Palette[(k * 4) + 1] = (byte)(WuColorQuantizer.Volume(cube[k], this.vmg) / weight);
                    quantizedImage.Palette[k * 4]       = (byte)(WuColorQuantizer.Volume(cube[k], this.vmb) / weight);
                }
                else
                {
                    quantizedImage.Palette[(k * 4) + 3] = 0xff;
                    quantizedImage.Palette[(k * 4) + 2] = 0;
                    quantizedImage.Palette[(k * 4) + 1] = 0;
                    quantizedImage.Palette[k * 4]       = 0;
                }
            }

            for (int i = 0; i < image.Length / 4; i++)
            {
                int r = image[(i * 4) + 2] >> (8 - WuColorQuantizer.IndexBits);
                int g = image[(i * 4) + 1] >> (8 - WuColorQuantizer.IndexBits);
                int b = image[i * 4] >> (8 - WuColorQuantizer.IndexBits);

                int ind = WuColorQuantizer.GetIndex(r + 1, g + 1, b + 1);

                quantizedImage.Bytes[i] = this.tag[ind];
            }

            return(quantizedImage);
        }