mirror of
https://github.com/webmproject/libwebp.git
synced 2024-12-28 14:38:21 +01:00
Merge "Integrate a new LZ77 looking for matches in the neighborhood of a pixel only."
This commit is contained in:
commit
a88c6522f6
@ -648,7 +648,7 @@ static int BackwardReferencesHashChainDistanceOnly(
|
|||||||
// better than whatever we add.
|
// better than whatever we add.
|
||||||
int offset_j, len_j = 0;
|
int offset_j, len_j = 0;
|
||||||
int j;
|
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
|
// Figure out the last consecutive pixel within [i, reach + 1] with
|
||||||
// the same offset.
|
// the same offset.
|
||||||
for (j = i; j <= reach; ++j) {
|
for (j = i; j <= reach; ++j) {
|
||||||
|
@ -477,7 +477,8 @@ static int BackwardReferencesLz77(int xsize, int ysize,
|
|||||||
if (len >= MIN_LENGTH) {
|
if (len >= MIN_LENGTH) {
|
||||||
const int len_ini = len;
|
const int len_ini = len;
|
||||||
int max_reach = 0;
|
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.
|
// Only start from what we have not checked already.
|
||||||
i_last_check = (i > i_last_check) ? i : i_last_check;
|
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
|
// 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)
|
// [i,i+len) + [i+len, length of best match at i+len)
|
||||||
// while we check if we can use:
|
// while we check if we can use:
|
||||||
// [i,j) (where j<=i+len) + [j, length of best match at j)
|
// [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 len_j = VP8LHashChainFindLength(hash_chain, j);
|
||||||
const int reach =
|
const int reach =
|
||||||
j + (len_j >= MIN_LENGTH ? len_j : 1); // 1 for single literal.
|
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;
|
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,
|
static void BackwardReferences2DLocality(int xsize,
|
||||||
@ -695,6 +819,8 @@ static VP8LBackwardRefs* GetBackwardReferences(
|
|||||||
double bit_cost_best = -1;
|
double bit_cost_best = -1;
|
||||||
VP8LHistogram* histo = NULL;
|
VP8LHistogram* histo = NULL;
|
||||||
int lz77_type, lz77_type_best = 0;
|
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);
|
histo = VP8LAllocateHistogram(MAX_COLOR_CACHE_BITS);
|
||||||
if (histo == NULL) goto Error;
|
if (histo == NULL) goto Error;
|
||||||
@ -714,6 +840,11 @@ static VP8LBackwardRefs* GetBackwardReferences(
|
|||||||
// cache is not that different in practice.
|
// cache is not that different in practice.
|
||||||
res = BackwardReferencesLz77(width, height, argb, 0, hash_chain, worst);
|
res = BackwardReferencesLz77(width, height, argb, 0, hash_chain, worst);
|
||||||
break;
|
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:
|
default:
|
||||||
assert(0);
|
assert(0);
|
||||||
}
|
}
|
||||||
@ -745,9 +876,12 @@ static VP8LBackwardRefs* GetBackwardReferences(
|
|||||||
|
|
||||||
// Improve on simple LZ77 but only for high quality (TraceBackwards is
|
// Improve on simple LZ77 but only for high quality (TraceBackwards is
|
||||||
// costly).
|
// 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,
|
if (VP8LBackwardReferencesTraceBackwards(width, height, argb, *cache_bits,
|
||||||
hash_chain, best, worst)) {
|
hash_chain_tmp, best, worst)) {
|
||||||
double bit_cost_trace;
|
double bit_cost_trace;
|
||||||
VP8LHistogramCreate(histo, worst, *cache_bits);
|
VP8LHistogramCreate(histo, worst, *cache_bits);
|
||||||
bit_cost_trace = VP8LHistogramEstimateBits(histo);
|
bit_cost_trace = VP8LHistogramEstimateBits(histo);
|
||||||
@ -758,6 +892,7 @@ static VP8LBackwardRefs* GetBackwardReferences(
|
|||||||
BackwardReferences2DLocality(width, best);
|
BackwardReferences2DLocality(width, best);
|
||||||
|
|
||||||
Error:
|
Error:
|
||||||
|
VP8LHashChainClear(&hash_chain_box);
|
||||||
VP8LFreeHistogram(histo);
|
VP8LFreeHistogram(histo);
|
||||||
return best;
|
return best;
|
||||||
}
|
}
|
||||||
|
@ -216,6 +216,7 @@ static WEBP_INLINE void VP8LRefsCursorNext(VP8LRefsCursor* const c) {
|
|||||||
enum VP8LLZ77Type {
|
enum VP8LLZ77Type {
|
||||||
kLZ77Standard = 1,
|
kLZ77Standard = 1,
|
||||||
kLZ77RLE = 2,
|
kLZ77RLE = 2,
|
||||||
|
kLZ77Box = 4
|
||||||
};
|
};
|
||||||
|
|
||||||
// Evaluates best possible backward references for specified quality.
|
// Evaluates best possible backward references for specified quality.
|
||||||
|
@ -356,13 +356,13 @@ static int GetTransformBits(int method, int histo_bits) {
|
|||||||
return res;
|
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 {
|
typedef struct {
|
||||||
int entropy_idx_;
|
int entropy_idx_;
|
||||||
int lz77s_types_to_try_;
|
int lz77s_types_to_try_;
|
||||||
} CrunchConfig;
|
} CrunchConfig;
|
||||||
|
|
||||||
#define CRUNCH_CONFIGS_MAX kNumEntropyIx
|
#define CRUNCH_CONFIGS_MAX (kNumEntropyIx * 2)
|
||||||
|
|
||||||
static int AnalyzeAndInit(VP8LEncoder* const enc,
|
static int AnalyzeAndInit(VP8LEncoder* const enc,
|
||||||
CrunchConfig crunch_configs[CRUNCH_CONFIGS_MAX],
|
CrunchConfig crunch_configs[CRUNCH_CONFIGS_MAX],
|
||||||
@ -399,27 +399,33 @@ static int AnalyzeAndInit(VP8LEncoder* const enc,
|
|||||||
*crunch_configs_size = 1;
|
*crunch_configs_size = 1;
|
||||||
} else {
|
} else {
|
||||||
EntropyIx min_entropy_ix;
|
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,
|
if (!AnalyzeEntropy(pic->argb, width, height, pic->argb_stride, use_palette,
|
||||||
enc->palette_size_, enc->transform_bits_,
|
enc->palette_size_, enc->transform_bits_,
|
||||||
&min_entropy_ix, red_and_blue_always_zero)) {
|
&min_entropy_ix, red_and_blue_always_zero)) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
*crunch_configs_size = 0;
|
*crunch_configs_size = 0;
|
||||||
|
for (j = 0; j < n_lz77; ++j) {
|
||||||
if (method == 6 && config->quality == 100) {
|
if (method == 6 && config->quality == 100) {
|
||||||
// Go brute force on all transforms.
|
// Go brute force on all transforms.
|
||||||
for (i = 0; i < kNumEntropyIx; ++i) {
|
for (i = 0; i < kNumEntropyIx; ++i) {
|
||||||
if (i != kPalette || use_palette) {
|
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].entropy_idx_ = i;
|
||||||
crunch_configs[(*crunch_configs_size)++].lz77s_types_to_try_ =
|
crunch_configs[(*crunch_configs_size)++].lz77s_types_to_try_ =
|
||||||
kLZ77Standard | kLZ77RLE;
|
(j == 0) ? kLZ77Standard | kLZ77RLE : kLZ77Box;
|
||||||
assert(*crunch_configs_size <= CRUNCH_CONFIGS_MAX);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Only choose the guessed best transform.
|
// 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].entropy_idx_ = min_entropy_ix;
|
||||||
crunch_configs[(*crunch_configs_size)++].lz77s_types_to_try_ =
|
crunch_configs[(*crunch_configs_size)++].lz77s_types_to_try_ =
|
||||||
kLZ77Standard | kLZ77RLE;
|
(j == 0) ? kLZ77Standard | kLZ77RLE : kLZ77Box;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user