diff --git a/src/enc/backward_references_cost_enc.c b/src/enc/backward_references_cost_enc.c index cbad3b41..c84e9ff4 100644 --- a/src/enc/backward_references_cost_enc.c +++ b/src/enc/backward_references_cost_enc.c @@ -648,7 +648,7 @@ static int BackwardReferencesHashChainDistanceOnly( // better than whatever we add. int offset_j, len_j = 0; int j; - assert(len == MAX_LENGTH); + assert(len == MAX_LENGTH || len == pix_count - i); // Figure out the last consecutive pixel within [i, reach + 1] with // the same offset. for (j = i; j <= reach; ++j) { diff --git a/src/enc/backward_references_enc.c b/src/enc/backward_references_enc.c index b649c090..ee59577a 100644 --- a/src/enc/backward_references_enc.c +++ b/src/enc/backward_references_enc.c @@ -477,7 +477,8 @@ static int BackwardReferencesLz77(int xsize, int ysize, if (len >= MIN_LENGTH) { const int len_ini = len; int max_reach = 0; - assert(i + len < pix_count); + const int j_max = + (i + len_ini >= pix_count) ? pix_count - 1 : i + len_ini; // Only start from what we have not checked already. i_last_check = (i > i_last_check) ? i : i_last_check; // We know the best match for the current pixel but we try to find the @@ -486,7 +487,7 @@ static int BackwardReferencesLz77(int xsize, int ysize, // [i,i+len) + [i+len, length of best match at i+len) // while we check if we can use: // [i,j) (where j<=i+len) + [j, length of best match at j) - for (j = i_last_check + 1; j <= i + len_ini; ++j) { + for (j = i_last_check + 1; j <= j_max; ++j) { const int len_j = VP8LHashChainFindLength(hash_chain, j); const int reach = j + (len_j >= MIN_LENGTH ? len_j : 1); // 1 for single literal. @@ -517,6 +518,129 @@ static int BackwardReferencesLz77(int xsize, int ysize, return ok; } +// Compute an LZ77 by forcing matches to happen within a given distance cost. +// We therefore limit the algorithm to the lowest 32 values in the PlaneCode +// definition. +#define WINDOW_OFFSETS_SIZE_MAX 32 +static int BackwardReferencesLz77Box(int xsize, int ysize, + const uint32_t* const argb, int cache_bits, + const VP8LHashChain* const hash_chain_best, + VP8LHashChain* hash_chain, + VP8LBackwardRefs* const refs) { + int i; + const int pix_count = xsize * ysize; + uint16_t* counts; + int window_offsets[WINDOW_OFFSETS_SIZE_MAX] = {0}; + int window_offsets_size = 0; + uint16_t* const counts_ini = + (uint16_t*)WebPSafeMalloc(xsize * ysize, sizeof(*counts_ini)); + if (counts_ini == NULL) return 0; + + // counts[i] counts how many times a pixel is repeated starting at position i. + i = pix_count - 2; + counts = counts_ini + i; + counts[1] = 1; + for (; i >= 0; --i, --counts) { + if (argb[i] == argb[i + 1]) { + // Max out the counts to MAX_LENGTH. + counts[0] = counts[1] + (counts[1] != MAX_LENGTH); + } else { + counts[0] = 1; + } + } + + // Figure out the window offsets around a pixel. They are stored in a + // spiraling order around the pixel as defined by VP8LDistanceToPlaneCode. + { + int x, y; + for (y = 0; y <= 6; ++y) { + for (x = -6; x <= 6; ++x) { + const int offset = y * xsize + x; + int plane_code; + // Ignore offsets that bring us after the pixel. + if (offset <= 0) continue; + plane_code = VP8LDistanceToPlaneCode(xsize, offset) - 1; + if (plane_code >= WINDOW_OFFSETS_SIZE_MAX) continue; + window_offsets[plane_code] = offset; + } + } + // For narrow images, not all plane codes are reached, so remove those. + for (i = 0; i < WINDOW_OFFSETS_SIZE_MAX; ++i) { + if (window_offsets[i] == 0) continue; + window_offsets[window_offsets_size++] = window_offsets[i]; + } + } + + for (i = pix_count - 1; i > 0; --i) { + int ind; + int best_length = VP8LHashChainFindLength(hash_chain_best, i); + int best_offset; + int do_compute = 1; + + if (best_length >= MAX_LENGTH) { + // Do not recompute the best match if we already have a maximal one in the + // window. + best_offset = VP8LHashChainFindOffset(hash_chain_best, i); + for (ind = 0; ind < window_offsets_size; ++ind) { + if (best_offset == window_offsets[ind]) { + do_compute = 0; + break; + } + } + } + if (do_compute) { + best_length = 0; + best_offset = 0; + // Find the longest match in a window around the pixel. + for (ind = 0; ind < window_offsets_size; ++ind) { + int curr_length = 0; + int j = i; + int j_offset = i - window_offsets[ind]; + if (j_offset < 0 || argb[j_offset] != argb[i]) continue; + // The longest match is the sum of how many times each pixel is + // repeated. + do { + const int counts_j_offset = counts_ini[j_offset]; + const int counts_j = counts_ini[j]; + if (counts_j_offset != counts_j) { + curr_length += + (counts_j_offset < counts_j) ? counts_j_offset : counts_j; + break; + } + // The same color is repeated counts_pos times at j_offset and j. + curr_length += counts_j_offset; + j_offset += counts_j_offset; + j += counts_j_offset; + } while (curr_length <= MAX_LENGTH && j < pix_count && + argb[j_offset] == argb[j]); + if (best_length < curr_length) { + best_offset = window_offsets[ind]; + if (curr_length > MAX_LENGTH) { + best_length = MAX_LENGTH; + break; + } else { + best_length = curr_length; + } + } + } + } + + assert(i + best_length <= pix_count); + assert(best_length <= MAX_LENGTH); + if (best_length <= MIN_LENGTH) { + hash_chain->offset_length_[i] = 0; + } else { + hash_chain->offset_length_[i] = + (best_offset << MAX_LENGTH_BITS) | (uint32_t)best_length; + } + } + hash_chain->offset_length_[0] = 0; + WebPSafeFree(counts_ini); + + return BackwardReferencesLz77(xsize, ysize, argb, cache_bits, hash_chain, + refs); +} + // ----------------------------------------------------------------------------- static void BackwardReferences2DLocality(int xsize, @@ -695,6 +819,8 @@ static VP8LBackwardRefs* GetBackwardReferences( double bit_cost_best = -1; VP8LHistogram* histo = NULL; int lz77_type, lz77_type_best = 0; + VP8LHashChain hash_chain_box; + memset(&hash_chain_box, 0, sizeof(hash_chain_box)); histo = VP8LAllocateHistogram(MAX_COLOR_CACHE_BITS); if (histo == NULL) goto Error; @@ -714,6 +840,11 @@ static VP8LBackwardRefs* GetBackwardReferences( // cache is not that different in practice. res = BackwardReferencesLz77(width, height, argb, 0, hash_chain, worst); break; + case kLZ77Box: + if (!VP8LHashChainInit(&hash_chain_box, width * height)) goto Error; + res = BackwardReferencesLz77Box(width, height, argb, 0, hash_chain, + &hash_chain_box, worst); + break; default: assert(0); } @@ -745,9 +876,12 @@ static VP8LBackwardRefs* GetBackwardReferences( // Improve on simple LZ77 but only for high quality (TraceBackwards is // costly). - if (lz77_type_best == kLZ77Standard && quality >= 25) { + if ((lz77_type_best == kLZ77Standard || lz77_type_best == kLZ77Box) && + quality >= 25) { + const VP8LHashChain* const hash_chain_tmp = + (lz77_type_best == kLZ77Standard) ? hash_chain : &hash_chain_box; if (VP8LBackwardReferencesTraceBackwards(width, height, argb, *cache_bits, - hash_chain, best, worst)) { + hash_chain_tmp, best, worst)) { double bit_cost_trace; VP8LHistogramCreate(histo, worst, *cache_bits); bit_cost_trace = VP8LHistogramEstimateBits(histo); @@ -758,6 +892,7 @@ static VP8LBackwardRefs* GetBackwardReferences( BackwardReferences2DLocality(width, best); Error: + VP8LHashChainClear(&hash_chain_box); VP8LFreeHistogram(histo); return best; } diff --git a/src/enc/backward_references_enc.h b/src/enc/backward_references_enc.h index ed27c372..c8ef6f03 100644 --- a/src/enc/backward_references_enc.h +++ b/src/enc/backward_references_enc.h @@ -216,6 +216,7 @@ static WEBP_INLINE void VP8LRefsCursorNext(VP8LRefsCursor* const c) { enum VP8LLZ77Type { kLZ77Standard = 1, kLZ77RLE = 2, + kLZ77Box = 4 }; // Evaluates best possible backward references for specified quality. diff --git a/src/enc/vp8l_enc.c b/src/enc/vp8l_enc.c index 4b66d2d2..2b5a15c9 100644 --- a/src/enc/vp8l_enc.c +++ b/src/enc/vp8l_enc.c @@ -356,13 +356,13 @@ static int GetTransformBits(int method, int histo_bits) { return res; } -// Se of parameters to be used in each iteration of the cruncher. +// Set of parameters to be used in each iteration of the cruncher. typedef struct { int entropy_idx_; int lz77s_types_to_try_; } CrunchConfig; -#define CRUNCH_CONFIGS_MAX kNumEntropyIx +#define CRUNCH_CONFIGS_MAX (kNumEntropyIx * 2) static int AnalyzeAndInit(VP8LEncoder* const enc, CrunchConfig crunch_configs[CRUNCH_CONFIGS_MAX], @@ -399,27 +399,33 @@ static int AnalyzeAndInit(VP8LEncoder* const enc, *crunch_configs_size = 1; } else { EntropyIx min_entropy_ix; + int j; + // Try out multiple LZ77 on images with few colors. + const int n_lz77 = 1 + (enc->palette_size_ > 0 && enc->palette_size_ <= 16); if (!AnalyzeEntropy(pic->argb, width, height, pic->argb_stride, use_palette, enc->palette_size_, enc->transform_bits_, &min_entropy_ix, red_and_blue_always_zero)) { return 0; } *crunch_configs_size = 0; - if (method == 6 && config->quality == 100) { - // Go brute force on all transforms. - for (i = 0; i < kNumEntropyIx; ++i) { - if (i != kPalette || use_palette) { - crunch_configs[*crunch_configs_size].entropy_idx_ = i; - crunch_configs[(*crunch_configs_size)++].lz77s_types_to_try_ = - kLZ77Standard | kLZ77RLE; - assert(*crunch_configs_size <= CRUNCH_CONFIGS_MAX); + for (j = 0; j < n_lz77; ++j) { + if (method == 6 && config->quality == 100) { + // Go brute force on all transforms. + for (i = 0; i < kNumEntropyIx; ++i) { + if (i != kPalette || use_palette) { + assert(*crunch_configs_size < CRUNCH_CONFIGS_MAX); + crunch_configs[*crunch_configs_size].entropy_idx_ = i; + crunch_configs[(*crunch_configs_size)++].lz77s_types_to_try_ = + (j == 0) ? kLZ77Standard | kLZ77RLE : kLZ77Box; + } } + } else { + // Only choose the guessed best transform. + assert(*crunch_configs_size < CRUNCH_CONFIGS_MAX); + crunch_configs[*crunch_configs_size].entropy_idx_ = min_entropy_ix; + crunch_configs[(*crunch_configs_size)++].lz77s_types_to_try_ = + (j == 0) ? kLZ77Standard | kLZ77RLE : kLZ77Box; } - } else { - // Only choose the guessed best transform. - crunch_configs[*crunch_configs_size].entropy_idx_ = min_entropy_ix; - crunch_configs[(*crunch_configs_size)++].lz77s_types_to_try_ = - kLZ77Standard | kLZ77RLE; } }