/// <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 = 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.GetIndex(cube.R1, cube.G1, cube.B1, cube.A1)] - this.m2[WuAlphaColorQuantizer.GetIndex(cube.R1, cube.G1, cube.B1, cube.A0)] - this.m2[WuAlphaColorQuantizer.GetIndex(cube.R1, cube.G1, cube.B0, cube.A1)] + this.m2[WuAlphaColorQuantizer.GetIndex(cube.R1, cube.G1, cube.B0, cube.A0)] - this.m2[WuAlphaColorQuantizer.GetIndex(cube.R1, cube.G0, cube.B1, cube.A1)] + this.m2[WuAlphaColorQuantizer.GetIndex(cube.R1, cube.G0, cube.B1, cube.A0)] + this.m2[WuAlphaColorQuantizer.GetIndex(cube.R1, cube.G0, cube.B0, cube.A1)] - this.m2[WuAlphaColorQuantizer.GetIndex(cube.R1, cube.G0, cube.B0, cube.A0)] - this.m2[WuAlphaColorQuantizer.GetIndex(cube.R0, cube.G1, cube.B1, cube.A1)] + this.m2[WuAlphaColorQuantizer.GetIndex(cube.R0, cube.G1, cube.B1, cube.A0)] + this.m2[WuAlphaColorQuantizer.GetIndex(cube.R0, cube.G1, cube.B0, cube.A1)] - this.m2[WuAlphaColorQuantizer.GetIndex(cube.R0, cube.G1, cube.B0, cube.A0)] + this.m2[WuAlphaColorQuantizer.GetIndex(cube.R0, cube.G0, cube.B1, cube.A1)] - this.m2[WuAlphaColorQuantizer.GetIndex(cube.R0, cube.G0, cube.B1, cube.A0)] - this.m2[WuAlphaColorQuantizer.GetIndex(cube.R0, cube.G0, cube.B0, cube.A1)] + this.m2[WuAlphaColorQuantizer.GetIndex(cube.R0, cube.G0, cube.B0, cube.A0)]; return(xx - (((dr * dr) + (dg * dg) + (db * db) + (da * da)) / WuAlphaColorQuantizer.Volume(cube, this.vwt))); }
/// <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 3: return(moment[WuAlphaColorQuantizer.GetIndex(position, cube.G1, cube.B1, cube.A1)] - moment[WuAlphaColorQuantizer.GetIndex(position, cube.G1, cube.B1, cube.A0)] - moment[WuAlphaColorQuantizer.GetIndex(position, cube.G1, cube.B0, cube.A1)] + moment[WuAlphaColorQuantizer.GetIndex(position, cube.G1, cube.B0, cube.A0)] - moment[WuAlphaColorQuantizer.GetIndex(position, cube.G0, cube.B1, cube.A1)] + moment[WuAlphaColorQuantizer.GetIndex(position, cube.G0, cube.B1, cube.A0)] + moment[WuAlphaColorQuantizer.GetIndex(position, cube.G0, cube.B0, cube.A1)] - moment[WuAlphaColorQuantizer.GetIndex(position, cube.G0, cube.B0, cube.A0)]); // Green case 2: return(moment[WuAlphaColorQuantizer.GetIndex(cube.R1, position, cube.B1, cube.A1)] - moment[WuAlphaColorQuantizer.GetIndex(cube.R1, position, cube.B1, cube.A0)] - moment[WuAlphaColorQuantizer.GetIndex(cube.R1, position, cube.B0, cube.A1)] + moment[WuAlphaColorQuantizer.GetIndex(cube.R1, position, cube.B0, cube.A0)] - moment[WuAlphaColorQuantizer.GetIndex(cube.R0, position, cube.B1, cube.A1)] + moment[WuAlphaColorQuantizer.GetIndex(cube.R0, position, cube.B1, cube.A0)] + moment[WuAlphaColorQuantizer.GetIndex(cube.R0, position, cube.B0, cube.A1)] - moment[WuAlphaColorQuantizer.GetIndex(cube.R0, position, cube.B0, cube.A0)]); // Blue case 1: return(moment[WuAlphaColorQuantizer.GetIndex(cube.R1, cube.G1, position, cube.A1)] - moment[WuAlphaColorQuantizer.GetIndex(cube.R1, cube.G1, position, cube.A0)] - moment[WuAlphaColorQuantizer.GetIndex(cube.R1, cube.G0, position, cube.A1)] + moment[WuAlphaColorQuantizer.GetIndex(cube.R1, cube.G0, position, cube.A0)] - moment[WuAlphaColorQuantizer.GetIndex(cube.R0, cube.G1, position, cube.A1)] + moment[WuAlphaColorQuantizer.GetIndex(cube.R0, cube.G1, position, cube.A0)] + moment[WuAlphaColorQuantizer.GetIndex(cube.R0, cube.G0, position, cube.A1)] - moment[WuAlphaColorQuantizer.GetIndex(cube.R0, cube.G0, position, cube.A0)]); // Alpha case 0: return(moment[WuAlphaColorQuantizer.GetIndex(cube.R1, cube.G1, cube.B1, position)] - moment[WuAlphaColorQuantizer.GetIndex(cube.R1, cube.G1, cube.B0, position)] - moment[WuAlphaColorQuantizer.GetIndex(cube.R1, cube.G0, cube.B1, position)] + moment[WuAlphaColorQuantizer.GetIndex(cube.R1, cube.G0, cube.B0, position)] - moment[WuAlphaColorQuantizer.GetIndex(cube.R0, cube.G1, cube.B1, position)] + moment[WuAlphaColorQuantizer.GetIndex(cube.R0, cube.G1, cube.B0, position)] + moment[WuAlphaColorQuantizer.GetIndex(cube.R0, cube.G0, cube.B1, position)] - moment[WuAlphaColorQuantizer.GetIndex(cube.R0, cube.G0, cube.B0, position)]); default: throw new ArgumentOutOfRangeException(nameof(direction)); } }
/// <summary> /// 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. /// </summary> /// <param name="cube">The cube.</param> /// <param name="direction">The direction.</param> /// <param name="first">The first position.</param> /// <param name="last">The last position.</param> /// <param name="cut">The cutting point.</param> /// <param name="wholeR">The whole red.</param> /// <param name="wholeG">The whole green.</param> /// <param name="wholeB">The whole blue.</param> /// <param name="wholeA">The whole alpha.</param> /// <param name="wholeW">The whole weight.</param> /// <returns>The result.</returns> 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 baseR = WuAlphaColorQuantizer.Bottom(cube, direction, this.vmr); long baseG = WuAlphaColorQuantizer.Bottom(cube, direction, this.vmg); long baseB = WuAlphaColorQuantizer.Bottom(cube, direction, this.vmb); long baseA = WuAlphaColorQuantizer.Bottom(cube, direction, this.vma); long baseW = WuAlphaColorQuantizer.Bottom(cube, direction, this.vwt); double max = 0.0; cut = -1; for (int i = first; i < last; i++) { double halfR = baseR + WuAlphaColorQuantizer.Top(cube, direction, i, this.vmr); double halfG = baseG + WuAlphaColorQuantizer.Top(cube, direction, i, this.vmg); double halfB = baseB + WuAlphaColorQuantizer.Top(cube, direction, i, this.vmb); double halfA = baseA + WuAlphaColorQuantizer.Top(cube, direction, i, this.vma); double halfW = baseW + WuAlphaColorQuantizer.Top(cube, direction, i, this.vwt); if (halfW == 0) { continue; } double 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; if (halfW == 0) { continue; } temp += ((halfR * halfR) + (halfG * halfG) + (halfB * halfB) + (halfA * halfA)) / halfW; if (temp > max) { max = temp; cut = i; } } return(max); }
/// <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++) { for (int a = cube.A0 + 1; a <= cube.A1; a++) { this.tag[WuAlphaColorQuantizer.GetIndex(r, g, b, a)] = label; } } } } }
/// <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[WuAlphaColorQuantizer.GetIndex(cube.R1, cube.G1, cube.B1, cube.A1)] - moment[WuAlphaColorQuantizer.GetIndex(cube.R1, cube.G1, cube.B1, cube.A0)] - moment[WuAlphaColorQuantizer.GetIndex(cube.R1, cube.G1, cube.B0, cube.A1)] + moment[WuAlphaColorQuantizer.GetIndex(cube.R1, cube.G1, cube.B0, cube.A0)] - moment[WuAlphaColorQuantizer.GetIndex(cube.R1, cube.G0, cube.B1, cube.A1)] + moment[WuAlphaColorQuantizer.GetIndex(cube.R1, cube.G0, cube.B1, cube.A0)] + moment[WuAlphaColorQuantizer.GetIndex(cube.R1, cube.G0, cube.B0, cube.A1)] - moment[WuAlphaColorQuantizer.GetIndex(cube.R1, cube.G0, cube.B0, cube.A0)] - moment[WuAlphaColorQuantizer.GetIndex(cube.R0, cube.G1, cube.B1, cube.A1)] + moment[WuAlphaColorQuantizer.GetIndex(cube.R0, cube.G1, cube.B1, cube.A0)] + moment[WuAlphaColorQuantizer.GetIndex(cube.R0, cube.G1, cube.B0, cube.A1)] - moment[WuAlphaColorQuantizer.GetIndex(cube.R0, cube.G1, cube.B0, cube.A0)] + moment[WuAlphaColorQuantizer.GetIndex(cube.R0, cube.G0, cube.B1, cube.A1)] - moment[WuAlphaColorQuantizer.GetIndex(cube.R0, cube.G0, cube.B1, cube.A0)] - moment[WuAlphaColorQuantizer.GetIndex(cube.R0, cube.G0, cube.B0, cube.A1)] + moment[WuAlphaColorQuantizer.GetIndex(cube.R0, cube.G0, cube.B0, cube.A0)]); }
/// <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(Color[] image) { foreach (var color in image) { int a = color.A; int r = color.R; int g = color.G; int b = color.B; int inr = r >> (8 - IndexBits); int ing = g >> (8 - IndexBits); int inb = b >> (8 - IndexBits); int ina = a >> (8 - IndexAlphaBits); int ind = WuAlphaColorQuantizer.GetIndex(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); } }
/// <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 void GenerateResult(Color[] image, int colorCount, Box[] cube) { List <Color> palette = new List <Color>(); for (int k = 0; k < colorCount; k++) { this.Mark(cube[k], (byte)k); double weight = WuAlphaColorQuantizer.Volume(cube[k], this.vwt); byte A; byte R; byte G; byte B; if (weight != 0) { A = (byte)(Volume(cube[k], this.vma) / weight); R = (byte)(Volume(cube[k], this.vmr) / weight); G = (byte)(Volume(cube[k], this.vmg) / weight); B = (byte)(Volume(cube[k], this.vmb) / weight); } else { A = 0xff; R = 0; G = 0; B = 0; } palette.Add(Color.FromArgb(A, R, G, B)); } QuantPalette = palette.ToArray(); var length = colorCount == 256 ? image.Length : colorCount == 16 ? image.Length / 2 : throw new Exception("IndexedPixels: unknown pixelformat"); var returned = new byte[length]; for (int i = 0; i < image.Length; i++) { var color = image[i]; int a = color.A >> (8 - IndexAlphaBits); int r = color.R >> (8 - IndexBits); int g = color.G >> (8 - IndexBits); int b = color.B >> (8 - IndexBits); int ind = GetIndex(r + 1, g + 1, b + 1, a + 1); var value = tag[ind]; switch (colorCount) { case 256: returned[i] = value; break; case 16: if (i % 2 == 0) { returned[i / 2] = (byte)(value << 4); } else { returned[i / 2] |= (byte)(value & 0x0F); } break; case 2: if (i % 8 == 0) { returned[i / 8] = (byte)(value << 7); } else { returned[i / 8] |= (byte)((value & 0x01) << (7 - (i % 8))); } break; } } QuantData = returned; }
/// <summary> /// Cuts a box. /// </summary> /// <param name="set1">The first set.</param> /// <param name="set2">The second set.</param> /// <returns>Returns a value indicating whether the box has been split.</returns> private bool Cut(Box set1, Box set2) { double wholeR = WuAlphaColorQuantizer.Volume(set1, this.vmr); double wholeG = WuAlphaColorQuantizer.Volume(set1, this.vmg); double wholeB = WuAlphaColorQuantizer.Volume(set1, this.vmb); double wholeA = WuAlphaColorQuantizer.Volume(set1, this.vma); double wholeW = 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, wholeR, wholeG, wholeB, wholeA, wholeW); double maxg = this.Maximize(set1, 2, set1.G0 + 1, set1.G1, out cutg, wholeR, wholeG, wholeB, wholeA, wholeW); double maxb = this.Maximize(set1, 1, set1.B0 + 1, set1.B1, out cutb, wholeR, wholeG, wholeB, wholeA, wholeW); double maxa = this.Maximize(set1, 0, set1.A0 + 1, set1.A1, out cuta, wholeR, wholeG, wholeB, wholeA, wholeW); 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); }
/// <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[] volume = new long[WuAlphaColorQuantizer.IndexCount * WuAlphaColorQuantizer.IndexAlphaCount]; long[] volumeR = new long[WuAlphaColorQuantizer.IndexCount * WuAlphaColorQuantizer.IndexAlphaCount]; long[] volumeG = new long[WuAlphaColorQuantizer.IndexCount * WuAlphaColorQuantizer.IndexAlphaCount]; long[] volumeB = new long[WuAlphaColorQuantizer.IndexCount * WuAlphaColorQuantizer.IndexAlphaCount]; long[] volumeA = new long[WuAlphaColorQuantizer.IndexCount * WuAlphaColorQuantizer.IndexAlphaCount]; double[] volume2 = new double[WuAlphaColorQuantizer.IndexCount * WuAlphaColorQuantizer.IndexAlphaCount]; long[] area = new long[WuAlphaColorQuantizer.IndexAlphaCount]; long[] areaR = new long[WuAlphaColorQuantizer.IndexAlphaCount]; long[] areaG = new long[WuAlphaColorQuantizer.IndexAlphaCount]; long[] areaB = new long[WuAlphaColorQuantizer.IndexAlphaCount]; long[] areaA = 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(volumeR, 0, WuAlphaColorQuantizer.IndexCount * WuAlphaColorQuantizer.IndexAlphaCount); Array.Clear(volumeG, 0, WuAlphaColorQuantizer.IndexCount * WuAlphaColorQuantizer.IndexAlphaCount); Array.Clear(volumeB, 0, WuAlphaColorQuantizer.IndexCount * WuAlphaColorQuantizer.IndexAlphaCount); Array.Clear(volumeA, 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(areaR, 0, WuAlphaColorQuantizer.IndexAlphaCount); Array.Clear(areaG, 0, WuAlphaColorQuantizer.IndexAlphaCount); Array.Clear(areaB, 0, WuAlphaColorQuantizer.IndexAlphaCount); Array.Clear(areaA, 0, WuAlphaColorQuantizer.IndexAlphaCount); Array.Clear(area2, 0, WuAlphaColorQuantizer.IndexAlphaCount); for (int b = 1; b < WuAlphaColorQuantizer.IndexCount; b++) { long line = 0; long lineR = 0; long lineG = 0; long lineB = 0; long lineA = 0; double line2 = 0; for (int a = 1; a < WuAlphaColorQuantizer.IndexAlphaCount; a++) { int ind1 = WuAlphaColorQuantizer.GetIndex(r, g, b, a); line += this.vwt[ind1]; lineR += this.vmr[ind1]; lineG += this.vmg[ind1]; lineB += this.vmb[ind1]; lineA += this.vma[ind1]; line2 += this.m2[ind1]; area[a] += line; areaR[a] += lineR; areaG[a] += lineG; areaB[a] += lineB; areaA[a] += lineA; area2[a] += line2; int inv = (b * WuAlphaColorQuantizer.IndexAlphaCount) + a; volume[inv] += area[a]; volumeR[inv] += areaR[a]; volumeG[inv] += areaG[a]; volumeB[inv] += areaB[a]; volumeA[inv] += areaA[a]; volume2[inv] += area2[a]; int ind2 = ind1 - WuAlphaColorQuantizer.GetIndex(1, 0, 0, 0); this.vwt[ind1] = this.vwt[ind2] + volume[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]; } } } } }