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
This commit is contained in:
Vincent Rabaud 2025-04-21 20:56:33 +02:00
parent e53e213091
commit 52a430a7b6
2 changed files with 82 additions and 85 deletions

View File

@ -41,7 +41,7 @@ typedef enum {
RED, RED,
BLUE, BLUE,
ALPHA, ALPHA,
DISTANCE, DISTANCE
} HistogramIndex; } HistogramIndex;
// Return the size of the histogram for a given cache_bits. // 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) { if (init_arrays) {
HistogramClear(p); HistogramClear(p);
} else { } 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; p->bit_cost = 0;
memset(p->costs, 0, sizeof(p->costs)); memset(p->costs, 0, sizeof(p->costs));
memset(p->is_used, 0, sizeof(p->is_used)); 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'. // Get the symbol entropy for the distribution 'population'.
// Set 'trivial_sym', if there's only one symbol present in the distribution. // Set 'trivial_sym', if there's only one symbol present in the distribution.
static uint64_t PopulationCost(const uint32_t* const population, int length, 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) { uint8_t* const is_used) {
VP8LBitEntropy bit_entropy; VP8LBitEntropy bit_entropy;
VP8LStreaks stats; VP8LStreaks stats;
@ -332,13 +333,41 @@ static uint64_t PopulationCost(const uint32_t* const population, int length,
return BitsEntropyRefine(&bit_entropy) + FinalHuffmanCost(&stats); 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 // 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. // 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, // 'index' is the index of the symbol in the histogram (literal, red, blue,
// alpha, distance). // alpha, distance).
static WEBP_INLINE uint64_t GetCombinedEntropy( static WEBP_INLINE uint64_t
const VP8LHistogram* const histo_X, const VP8LHistogram* const histo_Y, GetCombinedEntropy(const VP8LHistogram* const histo_X,
HistogramIndex index, int trivial_at_end) { const VP8LHistogram* const histo_Y, HistogramIndex index) {
const uint32_t* X; const uint32_t* X;
const uint32_t* Y; const uint32_t* Y;
int length; int length;
@ -346,35 +375,18 @@ static WEBP_INLINE uint64_t GetCombinedEntropy(
VP8LBitEntropy bit_entropy; VP8LBitEntropy bit_entropy;
const int is_X_used = histo_X->is_used[index]; const int is_X_used = histo_X->is_used[index];
const int is_Y_used = histo_Y->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]; if (is_X_used) return histo_X->costs[index];
return histo_Y->costs[index]; return histo_Y->costs[index];
} }
assert(is_X_used && is_Y_used); assert(is_X_used && is_Y_used);
if (index == LITERAL) { GetPopulationInfo(histo_X, index, &X, &length);
X = histo_X->literal; GetPopulationInfo(histo_Y, index, &Y, &length);
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;
}
VP8LGetCombinedEntropyUnrefined(X, Y, length, &bit_entropy, &stats); VP8LGetCombinedEntropyUnrefined(X, Y, length, &bit_entropy, &stats);
return BitsEntropyRefine(&bit_entropy) + FinalHuffmanCost(&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( WEBP_NODISCARD static int GetCombinedHistogramEntropy(
const VP8LHistogram* const a, const VP8LHistogram* const b, const VP8LHistogram* const a, const VP8LHistogram* const b,
int64_t cost_threshold_in, uint64_t* cost, uint64_t costs[5]) { 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; const uint64_t cost_threshold = (uint64_t)cost_threshold_in;
assert(a->palette_code_bits == b->palette_code_bits); assert(a->palette_code_bits == b->palette_code_bits);
if (cost_threshold_in <= 0) return 0; if (cost_threshold_in <= 0) return 0;
*cost = costs[LITERAL] = *cost = 0;
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;
if (a->trivial_symbol != VP8L_NON_TRIVIAL_SYM && // No need to add the extra cost for length and distance as it is a constant
a->trivial_symbol == b->trivial_symbol) { // that does not influence the histograms.
// A, R and B are all 0 or 0xff. for (i = 0; i < 5; ++i) {
const uint32_t color_a = (a->trivial_symbol >> 24) & 0xff; costs[i] = GetCombinedEntropy(a, b, (HistogramIndex)i);
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);
*cost += costs[i]; *cost += costs[i];
if (*cost >= cost_threshold) return 0; 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; return 1;
} }
@ -450,10 +443,13 @@ WEBP_NODISCARD static int GetCombinedHistogramEntropy(
static WEBP_INLINE void HistogramAdd(const VP8LHistogram* const a, static WEBP_INLINE void HistogramAdd(const VP8LHistogram* const a,
const VP8LHistogram* const b, const VP8LHistogram* const b,
VP8LHistogram* const out) { VP8LHistogram* const out) {
int i;
VP8LHistogramAdd(a, b, out); VP8LHistogramAdd(a, b, out);
out->trivial_symbol = (a->trivial_symbol == b->trivial_symbol) for (i = 0; i < 5; ++i) {
? a->trivial_symbol out->trivial_symbol[i] = a->trivial_symbol[i] == b->trivial_symbol[i]
: VP8L_NON_TRIVIAL_SYM; ? a->trivial_symbol[i]
: VP8L_NON_TRIVIAL_SYM;
}
} }
// Performs out = a + b, computing the cost C(a+b) - C(a) - C(b) while comparing // 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) { static void UpdateHistogramCost(VP8LHistogram* const h) {
uint32_t alpha_sym, red_sym, blue_sym; int i;
const int num_codes = VP8LHistogramNumCodes(h->palette_code_bits); // No need to add the extra cost for length and distance as it is a constant
h->costs[ALPHA] = PopulationCost(h->alpha, NUM_LITERAL_CODES, &alpha_sym, // that does not influence the histograms.
&h->is_used[ALPHA]); for (i = 0; i < 5; ++i) {
// No need to add the extra cost as it is a constant that does not influence const uint32_t* population;
// the histograms. int length;
h->costs[DISTANCE] = PopulationCost(h->distance, NUM_DISTANCE_CODES, NULL, GetPopulationInfo(h, i, &population, &length);
&h->is_used[DISTANCE]); h->costs[i] = PopulationCost(population, length, &h->trivial_symbol[i],
h->costs[LITERAL] = &h->is_used[i]);
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]);
h->bit_cost = h->costs[LITERAL] + h->costs[RED] + h->costs[BLUE] + h->bit_cost = h->costs[LITERAL] + h->costs[RED] + h->costs[BLUE] +
h->costs[ALPHA] + h->costs[DISTANCE]; 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) { 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); -DivRound((int64_t)bit_cost * combine_cost_factor, 100);
if (HistogramAddEval(histograms[first], histograms[idx], cur_combo, if (HistogramAddEval(histograms[first], histograms[idx], cur_combo,
bit_cost_thresh)) { bit_cost_thresh)) {
const int max_combine_failures = 32;
// Try to merge two histograms only if the combo is a trivial one or // Try to merge two histograms only if the combo is a trivial one or
// the two candidate histograms are already non-trivial. // the two candidate histograms are already non-trivial.
// For some images, 'try_combine' turns out to be false for a lot of // For some images, 'try_combine' turns out to be false for a lot of
// histogram pairs. In that case, we fallback to combining // histogram pairs. In that case, we fallback to combining
// histograms as usual to avoid increasing the header size. // histograms as usual to avoid increasing the header size.
const int try_combine = int try_combine =
(cur_combo->trivial_symbol != VP8L_NON_TRIVIAL_SYM) || cur_combo->trivial_symbol[RED] != VP8L_NON_TRIVIAL_SYM &&
((histograms[idx]->trivial_symbol == VP8L_NON_TRIVIAL_SYM) && cur_combo->trivial_symbol[BLUE] != VP8L_NON_TRIVIAL_SYM &&
(histograms[first]->trivial_symbol == VP8L_NON_TRIVIAL_SYM)); cur_combo->trivial_symbol[ALPHA] != VP8L_NON_TRIVIAL_SYM;
const int max_combine_failures = 32; 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 || if (try_combine ||
bin_info[bin_id].num_combine_failures >= max_combine_failures) { bin_info[bin_id].num_combine_failures >= max_combine_failures) {
// move the (better) merged histogram to its final slot // move the (better) merged histogram to its final slot

View File

@ -24,7 +24,7 @@ extern "C" {
#endif #endif
// Not a trivial literal symbol. // 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. // A simple container for histograms of data.
typedef struct { typedef struct {
@ -37,8 +37,9 @@ typedef struct {
// Backward reference prefix-code histogram. // Backward reference prefix-code histogram.
uint32_t distance[NUM_DISTANCE_CODES]; uint32_t distance[NUM_DISTANCE_CODES];
int palette_code_bits; int palette_code_bits;
uint32_t trivial_symbol; // True, if histograms for Red, Blue & Alpha // Index of the unique value of a histogram if any, VP8L_NON_TRIVIAL_SYM
// literal symbols are single valued. // otherwise.
uint16_t trivial_symbol[5];
uint64_t bit_cost; // Cached value of total bit cost. uint64_t bit_cost; // Cached value of total bit cost.
// Cached values of entropy costs: literal, red, blue, alpha, distance // Cached values of entropy costs: literal, red, blue, alpha, distance
uint64_t costs[5]; uint64_t costs[5];