/// <summary> /// Create a pair from indices "idx1" and "idx2" provided its cost is inferior to "threshold", a negative entropy. /// </summary> /// <returns>The cost of the pair, or 0 if it superior to threshold.</returns> private static double HistoPriorityListPush(List <HistogramPair> histoList, int maxSize, List <Vp8LHistogram> histograms, int idx1, int idx2, double threshold, Vp8LStreaks stats, Vp8LBitEntropy bitsEntropy) { var pair = new HistogramPair(); if (histoList.Count == maxSize) { return(0.0d); } if (idx1 > idx2) { int tmp = idx2; idx2 = idx1; idx1 = tmp; } pair.Idx1 = idx1; pair.Idx2 = idx2; Vp8LHistogram h1 = histograms[idx1]; Vp8LHistogram h2 = histograms[idx2]; HistoListUpdatePair(h1, h2, stats, bitsEntropy, threshold, pair); // Do not even consider the pair if it does not improve the entropy. if (pair.CostDiff >= threshold) { return(0.0d); } histoList.Add(pair); HistoListUpdateHead(histoList, pair); return(pair.CostDiff); }
/// <summary> /// Check whether a pair in the list should be updated as head or not. /// </summary> private static void HistoListUpdateHead(List <HistogramPair> histoList, HistogramPair pair) { if (pair.CostDiff < histoList[0].CostDiff) { // Replace the best pair. int oldIdx = histoList.IndexOf(pair); histoList[oldIdx] = histoList[0]; histoList[0] = pair; } }
/// <summary> /// Update the cost diff and combo of a pair of histograms. This needs to be called when the the histograms have been merged with a third one. /// </summary> private static void HistoListUpdatePair(Vp8LHistogram h1, Vp8LHistogram h2, Vp8LStreaks stats, Vp8LBitEntropy bitsEntropy, double threshold, HistogramPair pair) { double sumCost = h1.BitCost + h2.BitCost; pair.CostCombo = 0.0d; h1.GetCombinedHistogramEntropy(h2, stats, bitsEntropy, sumCost + threshold, costInitial: pair.CostCombo, out double cost); pair.CostCombo = cost; pair.CostDiff = pair.CostCombo - sumCost; }
private static void HistogramCombineGreedy(List <Vp8LHistogram> histograms) { int histoSize = histograms.Count(h => h != null); // Priority list of histogram pairs. var histoPriorityList = new List <HistogramPair>(); int maxSize = histoSize * histoSize; var stats = new Vp8LStreaks(); var bitsEntropy = new Vp8LBitEntropy(); for (int i = 0; i < histoSize; i++) { if (histograms[i] == null) { continue; } for (int j = i + 1; j < histoSize; j++) { if (histograms[j] == null) { continue; } HistoPriorityListPush(histoPriorityList, maxSize, histograms, i, j, 0.0d, stats, bitsEntropy); } } while (histoPriorityList.Count > 0) { int idx1 = histoPriorityList[0].Idx1; int idx2 = histoPriorityList[0].Idx2; HistogramAdd(histograms[idx2], histograms[idx1], histograms[idx1]); histograms[idx1].BitCost = histoPriorityList[0].CostCombo; // Remove merged histogram. histograms[idx2] = null; // Remove pairs intersecting the just combined best pair. for (int i = 0; i < histoPriorityList.Count;) { HistogramPair p = histoPriorityList.ElementAt(i); if (p.Idx1 == idx1 || p.Idx2 == idx1 || p.Idx1 == idx2 || p.Idx2 == idx2) { // Replace item at pos i with the last one and shrinking the list. histoPriorityList[i] = histoPriorityList[histoPriorityList.Count - 1]; histoPriorityList.RemoveAt(histoPriorityList.Count - 1); } else { HistoListUpdateHead(histoPriorityList, p); i++; } } // Push new pairs formed with combined histogram to the list. for (int i = 0; i < histoSize; i++) { if (i == idx1 || histograms[i] == null) { continue; } HistoPriorityListPush(histoPriorityList, maxSize, histograms, idx1, i, 0.0d, stats, bitsEntropy); } } }
/// <summary> /// Perform histogram aggregation using a stochastic approach. /// </summary> /// <returns>true if a greedy approach needs to be performed afterwards, false otherwise.</returns> private static bool HistogramCombineStochastic(List <Vp8LHistogram> histograms, int minClusterSize) { uint seed = 1; int triesWithNoSuccess = 0; int numUsed = histograms.Count(h => h != null); int outerIters = numUsed; int numTriesNoSuccess = outerIters / 2; var stats = new Vp8LStreaks(); var bitsEntropy = new Vp8LBitEntropy(); if (numUsed < minClusterSize) { return(true); } // Priority list of histogram pairs. Its size impacts the quality of the compression and the speed: // the smaller the faster but the worse for the compression. var histoPriorityList = new List <HistogramPair>(); int maxSize = 9; // Fill the initial mapping. int[] mappings = new int[histograms.Count]; for (int j = 0, iter = 0; iter < histograms.Count; iter++) { if (histograms[iter] == null) { continue; } mappings[j++] = iter; } // Collapse similar histograms. for (int iter = 0; iter < outerIters && numUsed >= minClusterSize && ++triesWithNoSuccess < numTriesNoSuccess; iter++) { double bestCost = histoPriorityList.Count == 0 ? 0.0d : histoPriorityList[0].CostDiff; int numTries = numUsed / 2; uint randRange = (uint)((numUsed - 1) * numUsed); // Pick random samples. for (int j = 0; numUsed >= 2 && j < numTries; j++) { // Choose two different histograms at random and try to combine them. uint tmp = MyRand(ref seed) % randRange; int idx1 = (int)(tmp / (numUsed - 1)); int idx2 = (int)(tmp % (numUsed - 1)); if (idx2 >= idx1) { idx2++; } idx1 = mappings[idx1]; idx2 = mappings[idx2]; // Calculate cost reduction on combination. double currCost = HistoPriorityListPush(histoPriorityList, maxSize, histograms, idx1, idx2, bestCost, stats, bitsEntropy); // Found a better pair? if (currCost < 0) { bestCost = currCost; if (histoPriorityList.Count == maxSize) { break; } } } if (histoPriorityList.Count == 0) { continue; } // Get the best histograms. int bestIdx1 = histoPriorityList[0].Idx1; int bestIdx2 = histoPriorityList[0].Idx2; int mappingIndex = Array.IndexOf(mappings, bestIdx2); Span <int> src = mappings.AsSpan(mappingIndex + 1, numUsed - mappingIndex - 1); Span <int> dst = mappings.AsSpan(mappingIndex); src.CopyTo(dst); // Merge the histograms and remove bestIdx2 from the list. HistogramAdd(histograms[bestIdx2], histograms[bestIdx1], histograms[bestIdx1]); histograms.ElementAt(bestIdx1).BitCost = histoPriorityList[0].CostCombo; histograms[bestIdx2] = null; numUsed--; for (int j = 0; j < histoPriorityList.Count;) { HistogramPair p = histoPriorityList[j]; bool isIdx1Best = p.Idx1 == bestIdx1 || p.Idx1 == bestIdx2; bool isIdx2Best = p.Idx2 == bestIdx1 || p.Idx2 == bestIdx2; bool doEval = false; // The front pair could have been duplicated by a random pick so // check for it all the time nevertheless. if (isIdx1Best && isIdx2Best) { histoPriorityList[j] = histoPriorityList[histoPriorityList.Count - 1]; histoPriorityList.RemoveAt(histoPriorityList.Count - 1); continue; } // Any pair containing one of the two best indices should only refer to // bestIdx1. Its cost should also be updated. if (isIdx1Best) { p.Idx1 = bestIdx1; doEval = true; } else if (isIdx2Best) { p.Idx2 = bestIdx1; doEval = true; } // Make sure the index order is respected. if (p.Idx1 > p.Idx2) { int tmp = p.Idx2; p.Idx2 = p.Idx1; p.Idx1 = tmp; } if (doEval) { // Re-evaluate the cost of an updated pair. HistoListUpdatePair(histograms[p.Idx1], histograms[p.Idx2], stats, bitsEntropy, 0.0d, p); if (p.CostDiff >= 0.0d) { histoPriorityList[j] = histoPriorityList[histoPriorityList.Count - 1]; histoPriorityList.RemoveAt(histoPriorityList.Count - 1); continue; } } HistoListUpdateHead(histoPriorityList, p); j++; } triesWithNoSuccess = 0; } bool doGreedy = numUsed <= minClusterSize; return(doGreedy); }