From 52a430a7b61ba87d054ee0c47d5a7dd9ed83b2f5 Mon Sep 17 00:00:00 2001 From: Vincent Rabaud Date: Mon, 21 Apr 2025 20:56:33 +0200 Subject: [PATCH] Generalize trivial histograms For now, this is used for histograms where A,R,B are trivial. This can be done on a per-symbol basis for speed-ups. Only the entropy bin merge criterion is kept with A,R,B to not create speed regressions (but compression improvements). Change-Id: Iaff6f6d5f157066e481bf43553ea5edd01ff1cde --- src/enc/histogram_enc.c | 160 ++++++++++++++++++++-------------------- src/enc/histogram_enc.h | 7 +- 2 files changed, 82 insertions(+), 85 deletions(-) diff --git a/src/enc/histogram_enc.c b/src/enc/histogram_enc.c index bc08a863..68b5ad00 100644 --- a/src/enc/histogram_enc.c +++ b/src/enc/histogram_enc.c @@ -41,7 +41,7 @@ typedef enum { RED, BLUE, ALPHA, - DISTANCE, + DISTANCE } HistogramIndex; // Return the size of the histogram for a given cache_bits. @@ -105,7 +105,8 @@ void VP8LHistogramInit(VP8LHistogram* const p, int palette_code_bits, if (init_arrays) { HistogramClear(p); } else { - p->trivial_symbol = 0; + int i; + for (i = 0; i < 5; ++i) p->trivial_symbol[i] = VP8L_NON_TRIVIAL_SYM; p->bit_cost = 0; memset(p->costs, 0, sizeof(p->costs)); memset(p->is_used, 0, sizeof(p->is_used)); @@ -317,7 +318,7 @@ static uint64_t FinalHuffmanCost(const VP8LStreaks* const stats) { // Get the symbol entropy for the distribution 'population'. // Set 'trivial_sym', if there's only one symbol present in the distribution. static uint64_t PopulationCost(const uint32_t* const population, int length, - uint32_t* const trivial_sym, + uint16_t* const trivial_sym, uint8_t* const is_used) { VP8LBitEntropy bit_entropy; VP8LStreaks stats; @@ -332,13 +333,41 @@ static uint64_t PopulationCost(const uint32_t* const population, int length, return BitsEntropyRefine(&bit_entropy) + FinalHuffmanCost(&stats); } +static WEBP_INLINE void GetPopulationInfo(const VP8LHistogram* const histo, + HistogramIndex index, + const uint32_t** population, + int* length) { + switch (index) { + case LITERAL: + *population = histo->literal; + *length = VP8LHistogramNumCodes(histo->palette_code_bits); + break; + case RED: + *population = histo->red; + *length = NUM_LITERAL_CODES; + break; + case BLUE: + *population = histo->blue; + *length = NUM_LITERAL_CODES; + break; + case ALPHA: + *population = histo->alpha; + *length = NUM_LITERAL_CODES; + break; + case DISTANCE: + *population = histo->distance; + *length = NUM_DISTANCE_CODES; + break; + } +} + // trivial_at_end is 1 if the two histograms only have one element that is // non-zero: both the zero-th one, or both the last one. // 'index' is the index of the symbol in the histogram (literal, red, blue, // alpha, distance). -static WEBP_INLINE uint64_t GetCombinedEntropy( - const VP8LHistogram* const histo_X, const VP8LHistogram* const histo_Y, - HistogramIndex index, int trivial_at_end) { +static WEBP_INLINE uint64_t +GetCombinedEntropy(const VP8LHistogram* const histo_X, + const VP8LHistogram* const histo_Y, HistogramIndex index) { const uint32_t* X; const uint32_t* Y; int length; @@ -346,35 +375,18 @@ static WEBP_INLINE uint64_t GetCombinedEntropy( VP8LBitEntropy bit_entropy; const int is_X_used = histo_X->is_used[index]; const int is_Y_used = histo_Y->is_used[index]; + const int is_trivial = + histo_X->trivial_symbol[index] != VP8L_NON_TRIVIAL_SYM && + histo_X->trivial_symbol[index] == histo_Y->trivial_symbol[index]; - if (trivial_at_end || !is_X_used || !is_Y_used) { + if (is_trivial || !is_X_used || !is_Y_used) { if (is_X_used) return histo_X->costs[index]; return histo_Y->costs[index]; } assert(is_X_used && is_Y_used); - if (index == LITERAL) { - X = histo_X->literal; - Y = histo_Y->literal; - length = VP8LHistogramNumCodes(histo_X->palette_code_bits); - } else if (index == RED) { - X = histo_X->red; - Y = histo_Y->red; - length = NUM_LITERAL_CODES; - } else if (index == BLUE) { - X = histo_X->blue; - Y = histo_Y->blue; - length = NUM_LITERAL_CODES; - } else if (index == ALPHA) { - X = histo_X->alpha; - Y = histo_Y->alpha; - length = NUM_LITERAL_CODES; - } else { - assert(index == DISTANCE); - X = histo_X->distance; - Y = histo_Y->distance; - length = NUM_DISTANCE_CODES; - } + GetPopulationInfo(histo_X, index, &X, &length); + GetPopulationInfo(histo_Y, index, &Y, &length); VP8LGetCombinedEntropyUnrefined(X, Y, length, &bit_entropy, &stats); return BitsEntropyRefine(&bit_entropy) + FinalHuffmanCost(&stats); } @@ -411,38 +423,19 @@ static WEBP_INLINE void SaturateAdd(uint64_t a, int64_t* b) { WEBP_NODISCARD static int GetCombinedHistogramEntropy( const VP8LHistogram* const a, const VP8LHistogram* const b, int64_t cost_threshold_in, uint64_t* cost, uint64_t costs[5]) { - int trivial_at_end = 0, i; + int i; const uint64_t cost_threshold = (uint64_t)cost_threshold_in; assert(a->palette_code_bits == b->palette_code_bits); if (cost_threshold_in <= 0) return 0; - *cost = costs[LITERAL] = - GetCombinedEntropy(a, b, LITERAL, /*trivial_at_end=*/0); - // No need to add the extra cost for lengths as it is a constant that does not - // influence the histograms. - if (*cost >= cost_threshold) return 0; + *cost = 0; - if (a->trivial_symbol != VP8L_NON_TRIVIAL_SYM && - a->trivial_symbol == b->trivial_symbol) { - // A, R and B are all 0 or 0xff. - const uint32_t color_a = (a->trivial_symbol >> 24) & 0xff; - const uint32_t color_r = (a->trivial_symbol >> 16) & 0xff; - const uint32_t color_b = (a->trivial_symbol >> 0) & 0xff; - if ((color_a == 0 || color_a == 0xff) && - (color_r == 0 || color_r == 0xff) && - (color_b == 0 || color_b == 0xff)) { - trivial_at_end = 1; - } - } - - for (i = 1; i <= 4; ++i) { - costs[i] = - GetCombinedEntropy(a, b, (HistogramIndex)i, - /*trivial_at_end=*/i <= 3 ? trivial_at_end : 0); + // No need to add the extra cost for length and distance as it is a constant + // that does not influence the histograms. + for (i = 0; i < 5; ++i) { + costs[i] = GetCombinedEntropy(a, b, (HistogramIndex)i); *cost += costs[i]; if (*cost >= cost_threshold) return 0; } - // No need to add the extra cost for distances as it is a constant that does - // not influence the histograms. return 1; } @@ -450,10 +443,13 @@ WEBP_NODISCARD static int GetCombinedHistogramEntropy( static WEBP_INLINE void HistogramAdd(const VP8LHistogram* const a, const VP8LHistogram* const b, VP8LHistogram* const out) { + int i; VP8LHistogramAdd(a, b, out); - out->trivial_symbol = (a->trivial_symbol == b->trivial_symbol) - ? a->trivial_symbol - : VP8L_NON_TRIVIAL_SYM; + for (i = 0; i < 5; ++i) { + out->trivial_symbol[i] = a->trivial_symbol[i] == b->trivial_symbol[i] + ? a->trivial_symbol[i] + : VP8L_NON_TRIVIAL_SYM; + } } // Performs out = a + b, computing the cost C(a+b) - C(a) - C(b) while comparing @@ -533,28 +529,18 @@ static void UpdateDominantCostRange( } static void UpdateHistogramCost(VP8LHistogram* const h) { - uint32_t alpha_sym, red_sym, blue_sym; - const int num_codes = VP8LHistogramNumCodes(h->palette_code_bits); - h->costs[ALPHA] = PopulationCost(h->alpha, NUM_LITERAL_CODES, &alpha_sym, - &h->is_used[ALPHA]); - // No need to add the extra cost as it is a constant that does not influence - // the histograms. - h->costs[DISTANCE] = PopulationCost(h->distance, NUM_DISTANCE_CODES, NULL, - &h->is_used[DISTANCE]); - h->costs[LITERAL] = - PopulationCost(h->literal, num_codes, NULL, &h->is_used[LITERAL]); - h->costs[RED] = - PopulationCost(h->red, NUM_LITERAL_CODES, &red_sym, &h->is_used[RED]); - h->costs[BLUE] = - PopulationCost(h->blue, NUM_LITERAL_CODES, &blue_sym, &h->is_used[BLUE]); + int i; + // No need to add the extra cost for length and distance as it is a constant + // that does not influence the histograms. + for (i = 0; i < 5; ++i) { + const uint32_t* population; + int length; + GetPopulationInfo(h, i, &population, &length); + h->costs[i] = PopulationCost(population, length, &h->trivial_symbol[i], + &h->is_used[i]); + } h->bit_cost = h->costs[LITERAL] + h->costs[RED] + h->costs[BLUE] + h->costs[ALPHA] + h->costs[DISTANCE]; - if ((alpha_sym | red_sym | blue_sym) == VP8L_NON_TRIVIAL_SYM) { - h->trivial_symbol = VP8L_NON_TRIVIAL_SYM; - } else { - h->trivial_symbol = - ((uint32_t)alpha_sym << 24) | (red_sym << 16) | (blue_sym << 0); - } } static int GetBinIdForEntropy(uint64_t min, uint64_t max, uint64_t val) { @@ -695,16 +681,26 @@ static void HistogramCombineEntropyBin(VP8LHistogramSet* const image_histo, -DivRound((int64_t)bit_cost * combine_cost_factor, 100); if (HistogramAddEval(histograms[first], histograms[idx], cur_combo, bit_cost_thresh)) { + const int max_combine_failures = 32; // Try to merge two histograms only if the combo is a trivial one or // the two candidate histograms are already non-trivial. // For some images, 'try_combine' turns out to be false for a lot of // histogram pairs. In that case, we fallback to combining // histograms as usual to avoid increasing the header size. - const int try_combine = - (cur_combo->trivial_symbol != VP8L_NON_TRIVIAL_SYM) || - ((histograms[idx]->trivial_symbol == VP8L_NON_TRIVIAL_SYM) && - (histograms[first]->trivial_symbol == VP8L_NON_TRIVIAL_SYM)); - const int max_combine_failures = 32; + int try_combine = + cur_combo->trivial_symbol[RED] != VP8L_NON_TRIVIAL_SYM && + cur_combo->trivial_symbol[BLUE] != VP8L_NON_TRIVIAL_SYM && + cur_combo->trivial_symbol[ALPHA] != VP8L_NON_TRIVIAL_SYM; + if (!try_combine) { + try_combine = + histograms[idx]->trivial_symbol[RED] == VP8L_NON_TRIVIAL_SYM || + histograms[idx]->trivial_symbol[BLUE] == VP8L_NON_TRIVIAL_SYM || + histograms[idx]->trivial_symbol[ALPHA] == VP8L_NON_TRIVIAL_SYM; + try_combine &= + histograms[first]->trivial_symbol[RED] == VP8L_NON_TRIVIAL_SYM || + histograms[first]->trivial_symbol[BLUE] == VP8L_NON_TRIVIAL_SYM || + histograms[first]->trivial_symbol[ALPHA] == VP8L_NON_TRIVIAL_SYM; + } if (try_combine || bin_info[bin_id].num_combine_failures >= max_combine_failures) { // move the (better) merged histogram to its final slot diff --git a/src/enc/histogram_enc.h b/src/enc/histogram_enc.h index 1472b61c..38528e77 100644 --- a/src/enc/histogram_enc.h +++ b/src/enc/histogram_enc.h @@ -24,7 +24,7 @@ extern "C" { #endif // Not a trivial literal symbol. -#define VP8L_NON_TRIVIAL_SYM (0xffffffff) +#define VP8L_NON_TRIVIAL_SYM ((uint16_t)(0xffff)) // A simple container for histograms of data. typedef struct { @@ -37,8 +37,9 @@ typedef struct { // Backward reference prefix-code histogram. uint32_t distance[NUM_DISTANCE_CODES]; int palette_code_bits; - uint32_t trivial_symbol; // True, if histograms for Red, Blue & Alpha - // literal symbols are single valued. + // Index of the unique value of a histogram if any, VP8L_NON_TRIVIAL_SYM + // otherwise. + uint16_t trivial_symbol[5]; uint64_t bit_cost; // Cached value of total bit cost. // Cached values of entropy costs: literal, red, blue, alpha, distance uint64_t costs[5];