Integrate a new LZ77 looking for matches in the neighborhood of a pixel only.

Change-Id: Ie2bbfee0a8d154b58f4a3068f3f634b7dad4c12d
This commit is contained in:
Vincent Rabaud 2017-06-07 19:06:28 +02:00
parent a1779a017b
commit 355c3d1bc7
4 changed files with 162 additions and 20 deletions

View File

@ -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) {

View File

@ -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;
}

View File

@ -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.

View File

@ -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;
}
}