From 16ab951abf652c96ca79f0ab1bd0ce6336e28f43 Mon Sep 17 00:00:00 2001 From: Jyrki Alakuijala Date: Mon, 8 Jun 2015 18:02:18 +0000 Subject: [PATCH 01/11] 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 --- src/enc/vp8l.c | 184 +++++++++++++++++++++++-------------------------- 1 file changed, 86 insertions(+), 98 deletions(-) diff --git a/src/enc/vp8l.c b/src/enc/vp8l.c index c619b674..3fb0cf78 100644 --- a/src/enc/vp8l.c +++ b/src/enc/vp8l.c @@ -189,12 +189,24 @@ static int AnalyzeAndCreatePalette(const WebPPicture* const pic, 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, int width, int height, int argb_stride, 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. - VP8LHistogramSet* const histo_set = VP8LAllocateHistogramSet(2, 0); + VP8LHistogramSet* const histo_set = VP8LAllocateHistogramSet(5, 0); assert(nonpredicted_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; VP8LHistogram* const histo_non_pred = histo_set->histograms[0]; 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) { uint32_t prev_pix = curr_row[0]; 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); VP8LHistogramAddSinglePixOrCopy(histo_non_pred, &pix_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; @@ -223,6 +245,11 @@ static int AnalyzeEntropy(const uint32_t* argb, } *nonpredicted_bits = VP8LHistogramEstimateBitsBulk(histo_non_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); return 1; } 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) { // Make tile size a function of encoding method (Range: 0 to 6). 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; } -static int EvalSubtractGreenForPalette(int palette_size, float quality) { - // 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) { +static int AnalyzeAndInit(VP8LEncoder* const enc) { const WebPPicture* const pic = enc->pic_; const int width = pic->width; 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 int method = config->method; 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 // at max MAX_REFS_BLOCK_PER_IMAGE blocks used: 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); + enc->use_cross_color_ = 0; + enc->use_predict_ = 0; + enc->use_subtract_green_ = 0; enc->use_palette_ = AnalyzeAndCreatePalette(pic, enc->palette_, &enc->palette_size_); - if (!enc->use_palette_ || - EvalSubtractGreenForPalette(enc->palette_size_, quality)) { - if (low_effort) { - // For low effort compression, avoid calling (costly) method - // AnalyzeSubtractGreen and enable the subtract-green transform - // for non-palette images. - subtract_green_score = subtract_green_threshold_non_palette * 0.99; - } else { - if (!AnalyzeSubtractGreen(pic->argb, width, height, - &subtract_green_score)) { - return 0; + if (low_effort) { + // AnalyzeEntropy is somewhat slow. + enc->use_predict_ = !enc->use_palette_; + enc->use_subtract_green_ = !enc->use_palette_; + enc->use_cross_color_ = 0; + } else { + if (!AnalyzeEntropy(pic->argb, width, height, pic->argb_stride, + &non_pred_entropy, &pred_entropy, + &pred_subtract_green_entropy, + &non_pred_subtract_green_entropy, + &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. enc->histo_bits_ = GetHistoBits(method, enc->use_palette_, pic->width, pic->height); 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; // palette-friendly input typically uses less literals @@ -1256,7 +1244,7 @@ WebPEncodingError VP8LEncodeStream(const WebPConfig* const config, // --------------------------------------------------------------------------- // Analyze image (entropy, num_palettes etc) - if (!AnalyzeAndInit(enc, config->image_hint)) { + if (!AnalyzeAndInit(enc)) { err = VP8_ENC_ERROR_OUT_OF_MEMORY; goto Error; } From 84326e4ab0dd0cf62ae8454802cfa837f804b06a Mon Sep 17 00:00:00 2001 From: Jyrki Alakuijala Date: Thu, 11 Jun 2015 11:08:50 +0000 Subject: [PATCH 02/11] lossless: Less code for the entropy selection Tested: 1000 png corpus gives same results Change-Id: Ief5ea7727290743b9bd893b08af7aa7951f556cb --- src/enc/vp8l.c | 105 +++++++++++++++++-------------------------------- 1 file changed, 36 insertions(+), 69 deletions(-) diff --git a/src/enc/vp8l.c b/src/enc/vp8l.c index 3fb0cf78..5d426a2d 100644 --- a/src/enc/vp8l.c +++ b/src/enc/vp8l.c @@ -198,27 +198,26 @@ static void AddSinglePixSubtractGreen(VP8LHistogram* const histo, ++histo->blue_[(PixOrCopyLiteral(v, 0) - green) & 0xff]; } +// These five modes are evaluated and their respective entropy is computed. +typedef enum { + kDirect = 0, + kSpatial = 1, + kSubGreen = 2, + kSpatialSubGreen = 3, + kPalette = 4, + kNumEntropyIx = 5, +} EntropyIx; + static int AnalyzeEntropy(const uint32_t* argb, int width, int height, int argb_stride, - double* const nonpredicted_bits, - double* const predicted_bits, - double* const predicted_sub_green_bits, - double* const nonpredicted_sub_green_bits, - double* const palette_bits) { + double entropy[kNumEntropyIx]) { // Allocate histogram set with cache_bits = 0. VP8LHistogramSet* const histo_set = VP8LAllocateHistogramSet(5, 0); - assert(nonpredicted_bits != NULL); - assert(predicted_bits != NULL); - if (histo_set != NULL) { - int x, y; + int i, x, y; const uint32_t* prev_row = argb; const uint32_t* curr_row = argb + argb_stride; - VP8LHistogram* const histo_non_pred = histo_set->histograms[0]; - 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]; + VP8LHistogram* const * const histo = &histo_set->histograms[0]; for (y = 1; y < height; ++y) { uint32_t prev_pix = curr_row[0]; for (x = 1; x < width; ++x) { @@ -229,27 +228,23 @@ static int AnalyzeEntropy(const uint32_t* argb, { const PixOrCopy pix_token = PixOrCopyCreateLiteral(pix); const PixOrCopy pix_diff_token = PixOrCopyCreateLiteral(pix_diff); - VP8LHistogramAddSinglePixOrCopy(histo_non_pred, &pix_token); - VP8LHistogramAddSinglePixOrCopy(histo_pred, &pix_diff_token); - AddSinglePixSubtractGreen(histo_subgreen, &pix_token); - AddSinglePixSubtractGreen(histo_pred_subgreen, &pix_diff_token); + VP8LHistogramAddSinglePixOrCopy(histo[kDirect], &pix_token); + VP8LHistogramAddSinglePixOrCopy(histo[kSpatial], &pix_diff_token); + AddSinglePixSubtractGreen(histo[kSubGreen], &pix_token); + AddSinglePixSubtractGreen(histo[kSpatialSubGreen], &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]; + ++histo[kPalette]->red_[hash & 0xff]; } } prev_row = curr_row; curr_row += argb_stride; } - *nonpredicted_bits = VP8LHistogramEstimateBitsBulk(histo_non_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); + for (i = 0; i < kNumEntropyIx; ++i) { + entropy[i] = VP8LHistogramEstimateBitsBulk(histo[i]); + } VP8LFreeHistogramSet(histo_set); return 1; } else { @@ -286,11 +281,6 @@ static int AnalyzeAndInit(VP8LEncoder* const enc) { // we round the block size up, so we're guaranteed to have // at max MAX_REFS_BLOCK_PER_IMAGE blocks used: 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); enc->use_cross_color_ = 0; @@ -305,49 +295,26 @@ static int AnalyzeAndInit(VP8LEncoder* const enc) { enc->use_subtract_green_ = !enc->use_palette_; enc->use_cross_color_ = 0; } else { + double entropy[kNumEntropyIx]; + EntropyIx min_entropy_ix = kDirect; + EntropyIx i = kDirect; + EntropyIx last_mode_to_analyze; if (!AnalyzeEntropy(pic->argb, width, height, pic->argb_stride, - &non_pred_entropy, &pred_entropy, - &pred_subtract_green_entropy, - &non_pred_subtract_green_entropy, - &palette_entropy)) { + &entropy[0])) { 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; + entropy[kPalette] -= 10.; // Small bias in favor of using the palette. + last_mode_to_analyze = enc->use_palette_ ? kPalette : kSpatialSubGreen; + for (i = 1; i <= last_mode_to_analyze; ++i) { + if (entropy[min_entropy_ix] > entropy[i]) { + min_entropy_ix = i; } } + enc->use_palette_ = (min_entropy_ix == kPalette); + enc->use_subtract_green_ = + (min_entropy_ix == kSubGreen) || (min_entropy_ix == kSpatialSubGreen); + enc->use_cross_color_ = enc->use_predict_ = + (min_entropy_ix == kSpatial) || (min_entropy_ix == kSpatialSubGreen); } // Evaluate histogram bits based on the original value of use_palette flag. enc->histo_bits_ = GetHistoBits(method, enc->use_palette_, pic->width, From 5e75642efd6363ba152ccf6a0d3b87840184c324 Mon Sep 17 00:00:00 2001 From: Jyrki Alakuijala Date: Thu, 11 Jun 2015 18:49:37 +0000 Subject: [PATCH 03/11] lossless: rle mode not to accept lengths smaller than 4. Gives a compression gain of 0.22 % Change-Id: I0f3b8dad6b4c1bfb16eab095a467f34466b9e3b7 --- src/enc/backward_references.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/enc/backward_references.c b/src/enc/backward_references.c index a6d64d1d..ca9f2675 100644 --- a/src/enc/backward_references.c +++ b/src/enc/backward_references.c @@ -368,7 +368,15 @@ static int BackwardReferencesRle(int xsize, int ysize, if (argb[i] == argb[i - 1]) { ++match_len; } else { - PushBackCopy(refs, match_len); + const int kMinLength = 4; + if (match_len >= kMinLength) { + PushBackCopy(refs, match_len); + } else { + int k; + for(k = match_len; k >= 1; --k) { + AddSingleLiteral(argb[i - k], use_color_cache, &hashers, refs); + } + } match_len = 0; AddSingleLiteral(argb[i], use_color_cache, &hashers, refs); } From 2cce031704eb28af3f02dcedb6d2583318ffe342 Mon Sep 17 00:00:00 2001 From: Jyrki Alakuijala Date: Tue, 16 Jun 2015 13:05:20 +0000 Subject: [PATCH 04/11] Faster alpha coding for webp No significant size degradation (+0.001 %) for 1000 image corpus Fixes the 8 ms vs 2 ms degradation from: "lossless: 0.37 % compression density improvement" Change-Id: Id540169a305d9d5c6213a82b46c879761b3ca608 --- src/enc/vp8l.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/enc/vp8l.c b/src/enc/vp8l.c index 5d426a2d..a64b7541 100644 --- a/src/enc/vp8l.c +++ b/src/enc/vp8l.c @@ -289,6 +289,12 @@ static int AnalyzeAndInit(VP8LEncoder* const enc) { enc->use_palette_ = AnalyzeAndCreatePalette(pic, enc->palette_, &enc->palette_size_); + // TODO(jyrki): replace the decision to be based on an actual estimate + // of entropy, or even spatial variance of entropy. + enc->histo_bits_ = GetHistoBits(method, enc->use_palette_, + pic->width, pic->height); + enc->transform_bits_ = GetTransformBits(method, enc->histo_bits_); + if (low_effort) { // AnalyzeEntropy is somewhat slow. enc->use_predict_ = !enc->use_palette_; @@ -316,10 +322,6 @@ static int AnalyzeAndInit(VP8LEncoder* const enc) { enc->use_cross_color_ = enc->use_predict_ = (min_entropy_ix == kSpatial) || (min_entropy_ix == kSpatialSubGreen); } - // Evaluate histogram bits based on the original value of use_palette flag. - enc->histo_bits_ = GetHistoBits(method, enc->use_palette_, pic->width, - pic->height); - enc->transform_bits_ = GetTransformBits(method, enc->histo_bits_); if (!VP8LHashChainInit(&enc->hash_chain_, pix_cnt)) return 0; From d92453f3815a93f01e3e2c9fd15c6c1b1eb4d191 Mon Sep 17 00:00:00 2001 From: Jyrki Alakuijala Date: Wed, 17 Jun 2015 17:28:34 +0000 Subject: [PATCH 05/11] lossless: Remove about 25 % of the speed degradation introduced in: "lossless: 0.37 % compression density improvement" Uses the statistics of red and blue histograms to decide if to run cross color correction at all. Improves compression density by 0.02 % or so. Change-Id: I47429557e9cdbd9fa90c584696f241b17427d73f --- src/enc/vp8l.c | 48 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/src/enc/vp8l.c b/src/enc/vp8l.c index a64b7541..d57f9e8c 100644 --- a/src/enc/vp8l.c +++ b/src/enc/vp8l.c @@ -210,7 +210,9 @@ typedef enum { static int AnalyzeEntropy(const uint32_t* argb, int width, int height, int argb_stride, - double entropy[kNumEntropyIx]) { + int use_palette, + EntropyIx* const min_entropy_ix, + int* const red_and_blue_always_zero) { // Allocate histogram set with cache_bits = 0. VP8LHistogramSet* const histo_set = VP8LAllocateHistogramSet(5, 0); if (histo_set != NULL) { @@ -242,8 +244,29 @@ static int AnalyzeEntropy(const uint32_t* argb, prev_row = curr_row; curr_row += argb_stride; } - for (i = 0; i < kNumEntropyIx; ++i) { - entropy[i] = VP8LHistogramEstimateBitsBulk(histo[i]); + { + double entropy[kNumEntropyIx]; + EntropyIx k; + EntropyIx last_mode_to_analyze = + use_palette ? kPalette : kSpatialSubGreen; + *min_entropy_ix = kDirect; + for (k = kDirect; k <= last_mode_to_analyze; ++k) { + entropy[k] = VP8LHistogramEstimateBitsBulk(histo[k]); + if (k == kDirect || entropy[*min_entropy_ix] >= entropy[k]) { + *min_entropy_ix = k; + } + } + *red_and_blue_always_zero = 1; + // Let's check if the histogram of the chosen entropy mode has + // non-zero red and blue values. If all are zero, we can later skip + // the cross color optimization. + for (i = 1; i < 256; ++i) { + if ((histo[*min_entropy_ix]->red_[i] | + histo[*min_entropy_ix]->blue_[i]) != 0) { + *red_and_blue_always_zero = 0; + break; + } + } } VP8LFreeHistogramSet(histo_set); return 1; @@ -301,26 +324,19 @@ static int AnalyzeAndInit(VP8LEncoder* const enc) { enc->use_subtract_green_ = !enc->use_palette_; enc->use_cross_color_ = 0; } else { - double entropy[kNumEntropyIx]; - EntropyIx min_entropy_ix = kDirect; - EntropyIx i = kDirect; - EntropyIx last_mode_to_analyze; + int red_and_blue_always_zero; + EntropyIx min_entropy_ix; if (!AnalyzeEntropy(pic->argb, width, height, pic->argb_stride, - &entropy[0])) { + enc->use_palette_, &min_entropy_ix, + &red_and_blue_always_zero)) { return 0; } - entropy[kPalette] -= 10.; // Small bias in favor of using the palette. - last_mode_to_analyze = enc->use_palette_ ? kPalette : kSpatialSubGreen; - for (i = 1; i <= last_mode_to_analyze; ++i) { - if (entropy[min_entropy_ix] > entropy[i]) { - min_entropy_ix = i; - } - } enc->use_palette_ = (min_entropy_ix == kPalette); enc->use_subtract_green_ = (min_entropy_ix == kSubGreen) || (min_entropy_ix == kSpatialSubGreen); - enc->use_cross_color_ = enc->use_predict_ = + enc->use_predict_ = (min_entropy_ix == kSpatial) || (min_entropy_ix == kSpatialSubGreen); + enc->use_cross_color_ = red_and_blue_always_zero ? 0 : enc->use_predict_; } if (!VP8LHashChainInit(&enc->hash_chain_, pix_cnt)) return 0; From 85b44d8a69e04f781ac5057b122b61cfaeeb2b25 Mon Sep 17 00:00:00 2001 From: Jyrki Alakuijala Date: Thu, 18 Jun 2015 14:54:33 +0000 Subject: [PATCH 06/11] lossless: encoding, don't compute unnecessary histo share the computation between different modes 3-5 % speedup for lossless alpha 1 % for lossy alpha no change in compression density Change-Id: I5e31413b3efcd4319121587da8320ac4f14550b2 --- src/dsp/lossless.h | 3 + src/dsp/lossless_enc.c | 16 +++--- src/enc/vp8l.c | 126 +++++++++++++++++++++++++++++++---------- 3 files changed, 106 insertions(+), 39 deletions(-) diff --git a/src/dsp/lossless.h b/src/dsp/lossless.h index 6da695ac..a7430c49 100644 --- a/src/dsp/lossless.h +++ b/src/dsp/lossless.h @@ -221,6 +221,9 @@ double VP8LPopulationCost(const uint32_t* const population, int length, double VP8LGetCombinedEntropy(const uint32_t* const X, const uint32_t* const Y, int length); +double VP8LBitsEntropy(const uint32_t* const array, int n, + uint32_t* const trivial_symbol); + // Estimate how many bits the combined entropy of literals and distance // approximately maps to. double VP8LHistogramEstimateBits(const VP8LHistogram* const p); diff --git a/src/dsp/lossless_enc.c b/src/dsp/lossless_enc.c index 5bad055e..bf20af30 100644 --- a/src/dsp/lossless_enc.c +++ b/src/dsp/lossless_enc.c @@ -473,8 +473,8 @@ static WEBP_INLINE double BitsEntropyRefine(int nonzeros, int sum, int max_val, // Returns the entropy for the symbols in the input array. // Also sets trivial_symbol to the code value, if the array has only one code // value. Otherwise, set it to VP8L_NON_TRIVIAL_SYM. -static double BitsEntropy(const uint32_t* const array, int n, - uint32_t* const trivial_symbol) { +double VP8LBitsEntropy(const uint32_t* const array, int n, + uint32_t* const trivial_symbol) { double retval = 0.; uint32_t sum = 0; uint32_t nonzero_code = VP8L_NON_TRIVIAL_SYM; @@ -555,7 +555,7 @@ static double HuffmanCostCombined(const uint32_t* const X, double VP8LPopulationCost(const uint32_t* const population, int length, uint32_t* const trivial_sym) { return - BitsEntropy(population, length, trivial_sym) + + VP8LBitsEntropy(population, length, trivial_sym) + HuffmanCost(population, length); } @@ -579,12 +579,12 @@ double VP8LHistogramEstimateBits(const VP8LHistogram* const p) { double VP8LHistogramEstimateBitsBulk(const VP8LHistogram* const p) { return - BitsEntropy(p->literal_, VP8LHistogramNumCodes(p->palette_code_bits_), + VP8LBitsEntropy(p->literal_, VP8LHistogramNumCodes(p->palette_code_bits_), NULL) - + BitsEntropy(p->red_, NUM_LITERAL_CODES, NULL) - + BitsEntropy(p->blue_, NUM_LITERAL_CODES, NULL) - + BitsEntropy(p->alpha_, NUM_LITERAL_CODES, NULL) - + BitsEntropy(p->distance_, NUM_DISTANCE_CODES, NULL) + + VP8LBitsEntropy(p->red_, NUM_LITERAL_CODES, NULL) + + VP8LBitsEntropy(p->blue_, NUM_LITERAL_CODES, NULL) + + VP8LBitsEntropy(p->alpha_, NUM_LITERAL_CODES, NULL) + + VP8LBitsEntropy(p->distance_, NUM_DISTANCE_CODES, NULL) + VP8LExtraCost(p->literal_ + NUM_LITERAL_CODES, NUM_LENGTH_CODES) + VP8LExtraCost(p->distance_, NUM_DISTANCE_CODES); } diff --git a/src/enc/vp8l.c b/src/enc/vp8l.c index d57f9e8c..b7befb54 100644 --- a/src/enc/vp8l.c +++ b/src/enc/vp8l.c @@ -189,15 +189,6 @@ static int AnalyzeAndCreatePalette(const WebPPicture* const pic, 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]; -} - // These five modes are evaluated and their respective entropy is computed. typedef enum { kDirect = 0, @@ -205,21 +196,52 @@ typedef enum { kSubGreen = 2, kSpatialSubGreen = 3, kPalette = 4, - kNumEntropyIx = 5, + kNumEntropyIx = 5 } EntropyIx; +typedef enum { + kHistoAlpha = 0, + kHistoAlphaPred, + kHistoGreen, + kHistoGreenPred, + kHistoRed, + kHistoRedPred, + kHistoBlue, + kHistoBluePred, + kHistoRedSubGreen, + kHistoRedPredSubGreen, + kHistoBlueSubGreen, + kHistoBluePredSubGreen, + kHistoPalette, + kHistoTotal // Must be last. +} HistoIx; + +static void AddSingleSubGreen(uint32_t p, uint32_t* r, uint32_t* b) { + const uint32_t green = (p >> 8) & 0xff; + ++r[((p >> 16) - green) & 0xff]; + ++b[(p - green) & 0xff]; +} + +static void AddSingle(uint32_t p, + uint32_t* a, uint32_t* r, uint32_t* g, uint32_t* b) { + ++a[p >> 24]; + ++r[(p >> 16) & 0xff]; + ++g[(p >> 8) & 0xff]; + ++b[(p & 0xff)]; +} + static int AnalyzeEntropy(const uint32_t* argb, int width, int height, int argb_stride, int use_palette, EntropyIx* const min_entropy_ix, int* const red_and_blue_always_zero) { // Allocate histogram set with cache_bits = 0. - VP8LHistogramSet* const histo_set = VP8LAllocateHistogramSet(5, 0); - if (histo_set != NULL) { + uint32_t* const histo = + (uint32_t*)WebPSafeCalloc(kHistoTotal, sizeof(*histo) * 256); + if (histo != NULL) { int i, x, y; const uint32_t* prev_row = argb; const uint32_t* curr_row = argb + argb_stride; - VP8LHistogram* const * const histo = &histo_set->histograms[0]; for (y = 1; y < height; ++y) { uint32_t prev_pix = curr_row[0]; for (x = 1; x < width; ++x) { @@ -227,32 +249,62 @@ static int AnalyzeEntropy(const uint32_t* argb, const uint32_t pix_diff = VP8LSubPixels(pix, prev_pix); if ((pix_diff == 0) || (pix == prev_row[x])) continue; prev_pix = pix; - { - const PixOrCopy pix_token = PixOrCopyCreateLiteral(pix); - const PixOrCopy pix_diff_token = PixOrCopyCreateLiteral(pix_diff); - VP8LHistogramAddSinglePixOrCopy(histo[kDirect], &pix_token); - VP8LHistogramAddSinglePixOrCopy(histo[kSpatial], &pix_diff_token); - AddSinglePixSubtractGreen(histo[kSubGreen], &pix_token); - AddSinglePixSubtractGreen(histo[kSpatialSubGreen], &pix_diff_token); - } + AddSingle(pix, + &histo[kHistoAlpha * 256], + &histo[kHistoRed * 256], + &histo[kHistoGreen * 256], + &histo[kHistoBlue * 256]); + AddSingle(pix_diff, + &histo[kHistoAlphaPred * 256], + &histo[kHistoRedPred * 256], + &histo[kHistoGreenPred * 256], + &histo[kHistoBluePred * 256]); + AddSingleSubGreen(pix, + &histo[kHistoRedSubGreen * 256], + &histo[kHistoBlueSubGreen * 256]); + AddSingleSubGreen(pix_diff, + &histo[kHistoRedPredSubGreen * 256], + &histo[kHistoBluePredSubGreen * 256]); { // Approximate the palette by the entropy of the multiplicative hash. const int hash = ((pix + (pix >> 19)) * 0x39c5fba7) >> 24; - ++histo[kPalette]->red_[hash & 0xff]; + ++histo[kHistoPalette * 256 + (hash & 0xff)]; } } prev_row = curr_row; curr_row += argb_stride; } { + double entropy_comp[kHistoTotal]; double entropy[kNumEntropyIx]; EntropyIx k; EntropyIx last_mode_to_analyze = use_palette ? kPalette : kSpatialSubGreen; + int j; + for (j = 0; j < kHistoTotal; ++j) { + entropy_comp[j] = VP8LBitsEntropy(&histo[j * 256], 256, NULL); + } + entropy[kDirect] = entropy_comp[kHistoAlpha] + + entropy_comp[kHistoRed] + + entropy_comp[kHistoGreen] + + entropy_comp[kHistoBlue]; + entropy[kSpatial] = entropy_comp[kHistoAlphaPred] + + entropy_comp[kHistoRedPred] + + entropy_comp[kHistoGreenPred] + + entropy_comp[kHistoBluePred]; + entropy[kSubGreen] = entropy_comp[kHistoAlpha] + + entropy_comp[kHistoRedSubGreen] + + entropy_comp[kHistoGreen] + + entropy_comp[kHistoBlueSubGreen]; + entropy[kSpatialSubGreen] = entropy_comp[kHistoAlphaPred] + + entropy_comp[kHistoRedPredSubGreen] + + entropy_comp[kHistoGreenPred] + + entropy_comp[kHistoBluePredSubGreen]; + entropy[kPalette] = entropy_comp[kHistoPalette]; + *min_entropy_ix = kDirect; - for (k = kDirect; k <= last_mode_to_analyze; ++k) { - entropy[k] = VP8LHistogramEstimateBitsBulk(histo[k]); - if (k == kDirect || entropy[*min_entropy_ix] >= entropy[k]) { + for (k = kDirect + 1; k <= last_mode_to_analyze; ++k) { + if (entropy[*min_entropy_ix] >= entropy[k]) { *min_entropy_ix = k; } } @@ -260,15 +312,27 @@ static int AnalyzeEntropy(const uint32_t* argb, // Let's check if the histogram of the chosen entropy mode has // non-zero red and blue values. If all are zero, we can later skip // the cross color optimization. - for (i = 1; i < 256; ++i) { - if ((histo[*min_entropy_ix]->red_[i] | - histo[*min_entropy_ix]->blue_[i]) != 0) { - *red_and_blue_always_zero = 0; - break; + { + static const uint8_t kHistoPairs[5][2] = { + { kHistoRed, kHistoBlue }, + { kHistoRedPred, kHistoBluePred }, + { kHistoRedSubGreen, kHistoBlueSubGreen }, + { kHistoRedPredSubGreen, kHistoBluePredSubGreen }, + { kHistoRed, kHistoBlue } + }; + const uint32_t* const red_histo = + &histo[256 * kHistoPairs[*min_entropy_ix][0]]; + const uint32_t* const blue_histo = + &histo[256 * kHistoPairs[*min_entropy_ix][1]]; + for (i = 1; i < 256; ++i) { + if ((red_histo[i] | blue_histo[i]) != 0) { + *red_and_blue_always_zero = 0; + break; + } } } } - VP8LFreeHistogramSet(histo_set); + free(histo); return 1; } else { return 0; From 7b23b19808f0f3d72cbca688e49b9f00b2493f40 Mon Sep 17 00:00:00 2001 From: Jyrki Alakuijala Date: Fri, 19 Jun 2015 10:17:46 +0000 Subject: [PATCH 07/11] lossless: Add zeroes into the predicted histograms. Increases compression density by 0.03 % for lossy. Speeds up at least one of the lossy alpha images by 20 %. Palette entropy 'kludge' seems to save 1-2 % on alpha images. Change-Id: I2116b8d81593ac8173bfba54a7c833997fca0804 --- src/enc/vp8l.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/enc/vp8l.c b/src/enc/vp8l.c index b7befb54..1e0b0b1b 100644 --- a/src/enc/vp8l.c +++ b/src/enc/vp8l.c @@ -217,7 +217,7 @@ typedef enum { } HistoIx; static void AddSingleSubGreen(uint32_t p, uint32_t* r, uint32_t* b) { - const uint32_t green = (p >> 8) & 0xff; + const uint32_t green = p >> 8; // The upper bits are masked away later. ++r[((p >> 16) - green) & 0xff]; ++b[(p - green) & 0xff]; } @@ -281,6 +281,16 @@ static int AnalyzeEntropy(const uint32_t* argb, EntropyIx last_mode_to_analyze = use_palette ? kPalette : kSpatialSubGreen; int j; + // Let's add one zero to the predicted histograms. The zeros are removed + // too efficiently by the pix_diff == 0 comparison, at least one of the + // zeros is likely to exist. + ++histo[kHistoRedPredSubGreen * 256]; + ++histo[kHistoBluePredSubGreen * 256]; + ++histo[kHistoRedPred * 256]; + ++histo[kHistoGreenPred * 256]; + ++histo[kHistoBluePred * 256]; + ++histo[kHistoAlphaPred * 256]; + for (j = 0; j < kHistoTotal; ++j) { entropy_comp[j] = VP8LBitsEntropy(&histo[j * 256], 256, NULL); } @@ -300,11 +310,12 @@ static int AnalyzeEntropy(const uint32_t* argb, entropy_comp[kHistoRedPredSubGreen] + entropy_comp[kHistoGreenPred] + entropy_comp[kHistoBluePredSubGreen]; - entropy[kPalette] = entropy_comp[kHistoPalette]; + // Palette mode seems more efficient in a breakeven case. Bias with 1.0. + entropy[kPalette] = entropy_comp[kHistoPalette] - 1.0; *min_entropy_ix = kDirect; for (k = kDirect + 1; k <= last_mode_to_analyze; ++k) { - if (entropy[*min_entropy_ix] >= entropy[k]) { + if (entropy[*min_entropy_ix] > entropy[k]) { *min_entropy_ix = k; } } From 888429f40979a9d01abf10f2b004c370c57e698a Mon Sep 17 00:00:00 2001 From: Jyrki Alakuijala Date: Tue, 23 Jun 2015 12:11:51 +0000 Subject: [PATCH 08/11] lossless: 0.5 % compression density improvement do not do length 2 matches far away speedup for non compressible data by inserting two literals at a time when no matches are found Change-Id: Ia8e033071f4186bb8148bb2bf13ca37586734aa3 --- src/enc/backward_references.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/enc/backward_references.c b/src/enc/backward_references.c index ca9f2675..a663b309 100644 --- a/src/enc/backward_references.c +++ b/src/enc/backward_references.c @@ -399,6 +399,7 @@ static int BackwardReferencesLz77(int xsize, int ysize, VP8LColorCache hashers; int iter_max, len_for_unit_dist; const int window_size = GetWindowSizeForHashChain(quality, xsize); + int min_matches = 32; GetParamsForHashChainFindCopy(quality, low_effort, &iter_max, &len_for_unit_dist); @@ -415,10 +416,11 @@ static int BackwardReferencesLz77(int xsize, int ysize, const int max_len = MaxFindCopyLength(pix_count - i); HashChainFindCopy(hash_chain, i, xsize, argb, max_len, window_size, iter_max, len_for_unit_dist, &offset, &len); - if (len >= MIN_LENGTH) { + if (len > MIN_LENGTH || (len == MIN_LENGTH && offset <= 512)) { int offset2 = 0; int len2 = 0; int k; + min_matches = 8; HashChainInsert(hash_chain, &argb[i], i); if ((len < (max_len >> 2)) && !low_effort) { // Evaluate Alternative#2: Insert the pixel at 'i' as literal, and code @@ -451,12 +453,17 @@ static int BackwardReferencesLz77(int xsize, int ysize, AddSingleLiteral(argb[i], use_color_cache, &hashers, refs); HashChainInsert(hash_chain, &argb[i], i); ++i; + --min_matches; + if (min_matches <= 0) { + AddSingleLiteral(argb[i], use_color_cache, &hashers, refs); + HashChainInsert(hash_chain, &argb[i], i); + ++i; + } } } while (i < pix_count) { - // Handle the last (two) pixel(s). + // Handle the last pixel(s). AddSingleLiteral(argb[i], use_color_cache, &hashers, refs); - if (i < (pix_count - 1)) HashChainInsert(hash_chain, &argb[i], i); ++i; } From 8e9c94dedbd7b7b3b818b4c864f2356ff03f4a5d Mon Sep 17 00:00:00 2001 From: Jyrki Alakuijala Date: Tue, 23 Jun 2015 14:25:06 +0000 Subject: [PATCH 09/11] lossless: simplify HashChainFindCopy heuristics for small speedup 0.0003 % worse compression Change-Id: Ic4b6b21e5279231c6321f2cec1c79f7e17e56afa --- src/enc/backward_references.c | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/enc/backward_references.c b/src/enc/backward_references.c index a663b309..b1affb1e 100644 --- a/src/enc/backward_references.c +++ b/src/enc/backward_references.c @@ -281,34 +281,32 @@ static int HashChainFindCopy(const VP8LHashChain* const p, int* const distance_ptr, int* const length_ptr) { const uint32_t* const argb_start = argb + base_position; - int iter = 0; + int iter = iter_max; int best_length = 1; - const int length_max = 256; int best_distance = 0; const int min_pos = (base_position > window_size) ? base_position - window_size : 0; int pos; + int length_max = 256; + if (max_len < length_max) { + length_max = max_len; + } assert(xsize > 0); for (pos = p->hash_to_first_index_[GetPixPairHash64(argb_start)]; pos >= min_pos; pos = p->chain_[pos]) { int curr_length; int distance; - if (iter > 8) { - if (iter > iter_max || best_length >= length_max) { - break; - } + if (--iter < 0) { + break; } - ++iter; curr_length = FindMatchLength(argb + pos, argb_start, best_length, max_len); - if (curr_length < best_length) continue; - - distance = base_position - pos; if (best_length < curr_length) { + distance = base_position - pos; best_length = curr_length; best_distance = distance; - if (curr_length >= max_len) { + if (curr_length >= length_max) { break; } if ((distance == 1 || distance == xsize) && From c4855ca2496db7f7f159ee087cecd86303c38773 Mon Sep 17 00:00:00 2001 From: Jyrki Alakuijala Date: Fri, 26 Jun 2015 14:24:59 +0000 Subject: [PATCH 10/11] lossless: Inlining add literal this is a simple speedup of about 1-2 % Change-Id: I0c7b01c0a69f4aeaf363ffda05a28871f1def696 --- src/enc/backward_references.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/enc/backward_references.c b/src/enc/backward_references.c index b1affb1e..4293d023 100644 --- a/src/enc/backward_references.c +++ b/src/enc/backward_references.c @@ -320,9 +320,9 @@ static int HashChainFindCopy(const VP8LHashChain* const p, return (best_length >= MIN_LENGTH); } -static void AddSingleLiteral(uint32_t pixel, int use_color_cache, - VP8LColorCache* const hashers, - VP8LBackwardRefs* const refs) { +static WEBP_INLINE void AddSingleLiteral(uint32_t pixel, int use_color_cache, + VP8LColorCache* const hashers, + VP8LBackwardRefs* const refs) { PixOrCopy v; if (use_color_cache && VP8LColorCacheContains(hashers, pixel)) { // push pixel as a PixOrCopyCreateCacheIdx pixel From 52931fd54824e6a467357340011768d78b62c5e4 Mon Sep 17 00:00:00 2001 From: Jyrki Alakuijala Date: Mon, 29 Jun 2015 13:25:42 +0000 Subject: [PATCH 11/11] lossless: combine the Huffman code with extra bits gives 2 % speedup 24.9 -> 25.5 MP/s for a photo with -q 0 -m 0 Change-Id: If9ae04683a86dd7b1fced2183cf79b9349a24a9e --- src/enc/vp8l.c | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/src/enc/vp8l.c b/src/enc/vp8l.c index 1e0b0b1b..e3e9f704 100644 --- a/src/enc/vp8l.c +++ b/src/enc/vp8l.c @@ -663,7 +663,7 @@ static void StoreHuffmanCode(VP8LBitWriter* const bw, } } -static void WriteHuffmanCode(VP8LBitWriter* const bw, +static WEBP_INLINE void WriteHuffmanCode(VP8LBitWriter* const bw, const HuffmanTreeCode* const code, int code_index) { const int depth = code->code_lengths[code_index]; @@ -671,6 +671,17 @@ static void WriteHuffmanCode(VP8LBitWriter* const bw, VP8LPutBits(bw, symbol, depth); } +static WEBP_INLINE void WriteHuffmanCodeWithExtraBits( + VP8LBitWriter* const bw, + const HuffmanTreeCode* const code, + int code_index, + int bits, + int n_bits) { + const int depth = code->code_lengths[code_index]; + const int symbol = code->codes[code_index]; + VP8LPutBits(bw, (bits << depth) | symbol, depth + n_bits); +} + static WebPEncodingError StoreImageToBitMask( VP8LBitWriter* const bw, int width, int histo_bits, VP8LBackwardRefs* const refs, @@ -695,26 +706,29 @@ static WebPEncodingError StoreImageToBitMask( (x >> histo_bits)]; codes = huffman_codes + 5 * histogram_ix; } - if (PixOrCopyIsCacheIdx(v)) { - const int code = PixOrCopyCacheIdx(v); - const int literal_ix = 256 + NUM_LENGTH_CODES + code; - WriteHuffmanCode(bw, codes, literal_ix); - } else if (PixOrCopyIsLiteral(v)) { + if (PixOrCopyIsLiteral(v)) { static const int order[] = { 1, 2, 0, 3 }; int k; for (k = 0; k < 4; ++k) { const int code = PixOrCopyLiteral(v, order[k]); WriteHuffmanCode(bw, codes + k, code); } + } else if (PixOrCopyIsCacheIdx(v)) { + const int code = PixOrCopyCacheIdx(v); + const int literal_ix = 256 + NUM_LENGTH_CODES + code; + WriteHuffmanCode(bw, codes, literal_ix); } else { int bits, n_bits; - int code, distance; + int code; + const int distance = PixOrCopyDistance(v); VP8LPrefixEncode(v->len, &code, &n_bits, &bits); - WriteHuffmanCode(bw, codes, 256 + code); - VP8LPutBits(bw, bits, n_bits); + WriteHuffmanCodeWithExtraBits(bw, codes, 256 + code, bits, n_bits); - distance = PixOrCopyDistance(v); + // Don't write the distance with the extra bits code since + // the distance can be up to 18 bits of extra bits, and the prefix + // 15 bits, totaling to 33, and our PutBits only supports up to 32 bits. + // TODO(jyrki): optimize this further. VP8LPrefixEncode(distance, &code, &n_bits, &bits); WriteHuffmanCode(bw, codes + 4, code); VP8LPutBits(bw, bits, n_bits);