lossless: 0.37 % compression density improvement

counting the entropy expectation for five different configurations:
palette
non-predicted
non-predicted with subtract green
predicted
predicted with subtract green

and choose the strategy with the smallest expected entropy

Change-Id: Iaaf209c0d565660a54a4f9b3959067afb9951960
This commit is contained in:
Jyrki Alakuijala 2015-06-08 18:02:18 +00:00 committed by James Zern
parent 822f113ebb
commit 16ab951abf

View File

@ -189,12 +189,24 @@ static int AnalyzeAndCreatePalette(const WebPPicture* const pic,
return 1; return 1;
} }
static void AddSinglePixSubtractGreen(VP8LHistogram* const histo,
const PixOrCopy* const v) {
int green = PixOrCopyLiteral(v, 1);
++histo->alpha_[PixOrCopyLiteral(v, 3)];
++histo->red_[(PixOrCopyLiteral(v, 2) - green) & 0xff];
++histo->literal_[green];
++histo->blue_[(PixOrCopyLiteral(v, 0) - green) & 0xff];
}
static int AnalyzeEntropy(const uint32_t* argb, static int AnalyzeEntropy(const uint32_t* argb,
int width, int height, int argb_stride, int width, int height, int argb_stride,
double* const nonpredicted_bits, double* const nonpredicted_bits,
double* const predicted_bits) { double* const predicted_bits,
double* const predicted_sub_green_bits,
double* const nonpredicted_sub_green_bits,
double* const palette_bits) {
// Allocate histogram set with cache_bits = 0. // Allocate histogram set with cache_bits = 0.
VP8LHistogramSet* const histo_set = VP8LAllocateHistogramSet(2, 0); VP8LHistogramSet* const histo_set = VP8LAllocateHistogramSet(5, 0);
assert(nonpredicted_bits != NULL); assert(nonpredicted_bits != NULL);
assert(predicted_bits != NULL); assert(predicted_bits != NULL);
@ -204,6 +216,9 @@ static int AnalyzeEntropy(const uint32_t* argb,
const uint32_t* curr_row = argb + argb_stride; const uint32_t* curr_row = argb + argb_stride;
VP8LHistogram* const histo_non_pred = histo_set->histograms[0]; VP8LHistogram* const histo_non_pred = histo_set->histograms[0];
VP8LHistogram* const histo_pred = histo_set->histograms[1]; VP8LHistogram* const histo_pred = histo_set->histograms[1];
VP8LHistogram* const histo_pred_subgreen = histo_set->histograms[2];
VP8LHistogram* const histo_subgreen = histo_set->histograms[3];
VP8LHistogram* const histo_palette = histo_set->histograms[4];
for (y = 1; y < height; ++y) { for (y = 1; y < height; ++y) {
uint32_t prev_pix = curr_row[0]; uint32_t prev_pix = curr_row[0];
for (x = 1; x < width; ++x) { for (x = 1; x < width; ++x) {
@ -216,6 +231,13 @@ static int AnalyzeEntropy(const uint32_t* argb,
const PixOrCopy pix_diff_token = PixOrCopyCreateLiteral(pix_diff); const PixOrCopy pix_diff_token = PixOrCopyCreateLiteral(pix_diff);
VP8LHistogramAddSinglePixOrCopy(histo_non_pred, &pix_token); VP8LHistogramAddSinglePixOrCopy(histo_non_pred, &pix_token);
VP8LHistogramAddSinglePixOrCopy(histo_pred, &pix_diff_token); VP8LHistogramAddSinglePixOrCopy(histo_pred, &pix_diff_token);
AddSinglePixSubtractGreen(histo_subgreen, &pix_token);
AddSinglePixSubtractGreen(histo_pred_subgreen, &pix_diff_token);
}
{
// Approximate the palette by the entropy of the multiplicative hash.
const int hash = ((pix + (pix >> 19)) * 0x39c5fba7) >> 24;
++histo_palette->red_[hash & 0xff];
} }
} }
prev_row = curr_row; prev_row = curr_row;
@ -223,6 +245,11 @@ static int AnalyzeEntropy(const uint32_t* argb,
} }
*nonpredicted_bits = VP8LHistogramEstimateBitsBulk(histo_non_pred); *nonpredicted_bits = VP8LHistogramEstimateBitsBulk(histo_non_pred);
*predicted_bits = VP8LHistogramEstimateBitsBulk(histo_pred); *predicted_bits = VP8LHistogramEstimateBitsBulk(histo_pred);
*predicted_sub_green_bits =
VP8LHistogramEstimateBitsBulk(histo_pred_subgreen);
*nonpredicted_sub_green_bits =
VP8LHistogramEstimateBitsBulk(histo_subgreen);
*palette_bits = VP8LHistogramEstimateBitsBulk(histo_palette);
VP8LFreeHistogramSet(histo_set); VP8LFreeHistogramSet(histo_set);
return 1; return 1;
} else { } else {
@ -230,40 +257,6 @@ static int AnalyzeEntropy(const uint32_t* argb,
} }
} }
// Check if it would be a good idea to subtract green from red and blue. We
// only evaluate entropy in red/blue components, don't bother to look at others.
static int AnalyzeSubtractGreen(const uint32_t* const argb,
int width, int height,
double* const entropy_change_ratio) {
// Allocate histogram set with cache_bits = 1.
VP8LHistogramSet* const histo_set = VP8LAllocateHistogramSet(2, 1);
assert(entropy_change_ratio != NULL);
if (histo_set != NULL) {
int i;
double bit_cost, bit_cost_subgreen;
VP8LHistogram* const histo = histo_set->histograms[0];
VP8LHistogram* const histo_subgreen = histo_set->histograms[1];
for (i = 0; i < width * height; ++i) {
const uint32_t c = argb[i];
const int green = (c >> 8) & 0xff;
const int red = (c >> 16) & 0xff;
const int blue = (c >> 0) & 0xff;
++histo->red_[red];
++histo->blue_[blue];
++histo_subgreen->red_[(red - green) & 0xff];
++histo_subgreen->blue_[(blue - green) & 0xff];
}
bit_cost= VP8LHistogramEstimateBits(histo);
bit_cost_subgreen = VP8LHistogramEstimateBits(histo_subgreen);
VP8LFreeHistogramSet(histo_set);
*entropy_change_ratio = bit_cost_subgreen / (bit_cost + 1e-6);
return 1;
} else {
return 0;
}
}
static int GetHistoBits(int method, int use_palette, int width, int height) { static int GetHistoBits(int method, int use_palette, int width, int height) {
// Make tile size a function of encoding method (Range: 0 to 6). // Make tile size a function of encoding method (Range: 0 to 6).
int histo_bits = (use_palette ? 9 : 7) - method; int histo_bits = (use_palette ? 9 : 7) - method;
@ -282,21 +275,7 @@ static int GetTransformBits(int method, int histo_bits) {
return (histo_bits > max_transform_bits) ? max_transform_bits : histo_bits; return (histo_bits > max_transform_bits) ? max_transform_bits : histo_bits;
} }
static int EvalSubtractGreenForPalette(int palette_size, float quality) { static int AnalyzeAndInit(VP8LEncoder* const enc) {
// Evaluate non-palette encoding (subtract green, prediction transforms etc)
// for palette size in the mid-range (17-96) as for larger number of colors,
// the benefit from switching to non-palette is not much.
// Non-palette transforms are little CPU intensive, hence don't evaluate them
// for lower (<= 25) quality.
const int min_colors_non_palette = 17;
const int max_colors_non_palette = 96;
const float min_quality_non_palette = 26.f;
return (palette_size >= min_colors_non_palette) &&
(palette_size <= max_colors_non_palette) &&
(quality >= min_quality_non_palette);
}
static int AnalyzeAndInit(VP8LEncoder* const enc, WebPImageHint image_hint) {
const WebPPicture* const pic = enc->pic_; const WebPPicture* const pic = enc->pic_;
const int width = pic->width; const int width = pic->width;
const int height = pic->height; const int height = pic->height;
@ -304,68 +283,77 @@ static int AnalyzeAndInit(VP8LEncoder* const enc, WebPImageHint image_hint) {
const WebPConfig* const config = enc->config_; const WebPConfig* const config = enc->config_;
const int method = config->method; const int method = config->method;
const int low_effort = (config->method == 0); const int low_effort = (config->method == 0);
const float quality = config->quality;
double subtract_green_score = 10.0;
const double subtract_green_threshold_palette = 0.80;
const double subtract_green_threshold_non_palette = 1.0;
// we round the block size up, so we're guaranteed to have // we round the block size up, so we're guaranteed to have
// at max MAX_REFS_BLOCK_PER_IMAGE blocks used: // at max MAX_REFS_BLOCK_PER_IMAGE blocks used:
int refs_block_size = (pix_cnt - 1) / MAX_REFS_BLOCK_PER_IMAGE + 1; int refs_block_size = (pix_cnt - 1) / MAX_REFS_BLOCK_PER_IMAGE + 1;
double non_pred_entropy = 0.;
double non_pred_subtract_green_entropy = 0.;
double pred_entropy = 0.;
double pred_subtract_green_entropy = 0.;
double palette_entropy = 0.;
assert(pic != NULL && pic->argb != NULL); assert(pic != NULL && pic->argb != NULL);
enc->use_cross_color_ = 0;
enc->use_predict_ = 0;
enc->use_subtract_green_ = 0;
enc->use_palette_ = enc->use_palette_ =
AnalyzeAndCreatePalette(pic, enc->palette_, &enc->palette_size_); AnalyzeAndCreatePalette(pic, enc->palette_, &enc->palette_size_);
if (!enc->use_palette_ || if (low_effort) {
EvalSubtractGreenForPalette(enc->palette_size_, quality)) { // AnalyzeEntropy is somewhat slow.
if (low_effort) { enc->use_predict_ = !enc->use_palette_;
// For low effort compression, avoid calling (costly) method enc->use_subtract_green_ = !enc->use_palette_;
// AnalyzeSubtractGreen and enable the subtract-green transform enc->use_cross_color_ = 0;
// for non-palette images. } else {
subtract_green_score = subtract_green_threshold_non_palette * 0.99; if (!AnalyzeEntropy(pic->argb, width, height, pic->argb_stride,
} else { &non_pred_entropy, &pred_entropy,
if (!AnalyzeSubtractGreen(pic->argb, width, height, &pred_subtract_green_entropy,
&subtract_green_score)) { &non_pred_subtract_green_entropy,
return 0; &palette_entropy)) {
return 0;
}
palette_entropy -= 10.; // Small bias in favor of using the palette.
if (enc->use_palette_) {
// Check if avoiding the palette coding likely improves compression.
if (palette_entropy >= non_pred_entropy ||
palette_entropy >= non_pred_subtract_green_entropy ||
palette_entropy >= pred_entropy ||
palette_entropy >= pred_subtract_green_entropy) {
enc->use_palette_ = 0;
}
}
// TODO(jyrki): make this more clear.
if (!enc->use_palette_) {
// Choose the smallest of four options.
if (non_pred_entropy < non_pred_subtract_green_entropy &&
non_pred_entropy < pred_entropy &&
non_pred_entropy < pred_subtract_green_entropy) {
enc->use_subtract_green_ = 0;
enc->use_predict_ = 0;
enc->use_cross_color_ = 0;
} else if (pred_entropy < non_pred_subtract_green_entropy &&
pred_entropy < pred_subtract_green_entropy) {
enc->use_subtract_green_ = 0;
enc->use_predict_ = 1;
enc->use_cross_color_ = 1;
} else if (non_pred_subtract_green_entropy <
pred_subtract_green_entropy) {
enc->use_subtract_green_ = 1;
enc->use_predict_ = 0;
enc->use_cross_color_ = 0;
} else {
enc->use_subtract_green_ = 1;
enc->use_predict_ = 1;
enc->use_cross_color_ = 1;
} }
} }
} }
// Evaluate histogram bits based on the original value of use_palette flag. // Evaluate histogram bits based on the original value of use_palette flag.
enc->histo_bits_ = GetHistoBits(method, enc->use_palette_, pic->width, enc->histo_bits_ = GetHistoBits(method, enc->use_palette_, pic->width,
pic->height); pic->height);
enc->transform_bits_ = GetTransformBits(method, enc->histo_bits_); enc->transform_bits_ = GetTransformBits(method, enc->histo_bits_);
enc->use_subtract_green_ = 0;
if (enc->use_palette_) {
// Check if other transforms (subtract green etc) are potentially better.
if (subtract_green_score < subtract_green_threshold_palette) {
enc->use_subtract_green_ = 1;
enc->use_palette_ = 0;
}
} else {
// Non-palette case, check if subtract-green optimizes the entropy.
if (subtract_green_score < subtract_green_threshold_non_palette) {
enc->use_subtract_green_ = 1;
}
}
if (!enc->use_palette_) {
if (image_hint == WEBP_HINT_PHOTO) {
enc->use_predict_ = 1;
enc->use_cross_color_ = !low_effort;
} else {
double non_pred_entropy, pred_entropy;
if (!AnalyzeEntropy(pic->argb, width, height, pic->argb_stride,
&non_pred_entropy, &pred_entropy)) {
return 0;
}
if (pred_entropy < 0.95 * non_pred_entropy) {
enc->use_predict_ = 1;
enc->use_cross_color_ = !low_effort;
}
}
}
if (!VP8LHashChainInit(&enc->hash_chain_, pix_cnt)) return 0; if (!VP8LHashChainInit(&enc->hash_chain_, pix_cnt)) return 0;
// palette-friendly input typically uses less literals // palette-friendly input typically uses less literals
@ -1256,7 +1244,7 @@ WebPEncodingError VP8LEncodeStream(const WebPConfig* const config,
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Analyze image (entropy, num_palettes etc) // Analyze image (entropy, num_palettes etc)
if (!AnalyzeAndInit(enc, config->image_hint)) { if (!AnalyzeAndInit(enc)) {
err = VP8_ENC_ERROR_OUT_OF_MEMORY; err = VP8_ENC_ERROR_OUT_OF_MEMORY;
goto Error; goto Error;
} }