Add no-color cache configuration to the cruncher

Fix another pessimization found by the pingo image compressor.

Refactoring is necessary to make LZ77 computation
common to cache or no-cache analysis.
Slower by 1.7x instead of 2x

Change-Id: I396701ea6e88543dbfe9471eb552877f6c8ce1e3
This commit is contained in:
Vincent Rabaud 2020-06-09 18:53:56 +02:00
parent 749a8b99f7
commit f0110baec0
5 changed files with 300 additions and 215 deletions

View File

@ -11,13 +11,14 @@
// //
#include <assert.h> #include <assert.h>
#include <float.h>
#include <math.h> #include <math.h>
#include "src/enc/backward_references_enc.h" #include "src/dsp/dsp.h"
#include "src/enc/histogram_enc.h"
#include "src/dsp/lossless.h" #include "src/dsp/lossless.h"
#include "src/dsp/lossless_common.h" #include "src/dsp/lossless_common.h"
#include "src/dsp/dsp.h" #include "src/enc/backward_references_enc.h"
#include "src/enc/histogram_enc.h"
#include "src/utils/color_cache_utils.h" #include "src/utils/color_cache_utils.h"
#include "src/utils/utils.h" #include "src/utils/utils.h"
@ -103,6 +104,20 @@ void VP8LBackwardRefsClear(VP8LBackwardRefs* const refs) {
} }
} }
// Swaps the content of two VP8LBackwardRefs.
static void BackwardRefsSwap(VP8LBackwardRefs* const refs1,
VP8LBackwardRefs* const refs2) {
const int point_to_refs1 =
(refs1->tail_ != NULL && refs1->tail_ == &refs1->refs_);
const int point_to_refs2 =
(refs2->tail_ != NULL && refs2->tail_ == &refs2->refs_);
const VP8LBackwardRefs tmp = *refs1;
*refs1 = *refs2;
*refs2 = tmp;
if (point_to_refs2) refs1->tail_ = &refs1->refs_;
if (point_to_refs1) refs2->tail_ = &refs2->refs_;
}
void VP8LBackwardRefsInit(VP8LBackwardRefs* const refs, int block_size) { void VP8LBackwardRefsInit(VP8LBackwardRefs* const refs, int block_size) {
assert(refs != NULL); assert(refs != NULL);
memset(refs, 0, sizeof(*refs)); memset(refs, 0, sizeof(*refs));
@ -154,6 +169,22 @@ static PixOrCopyBlock* BackwardRefsNewBlock(VP8LBackwardRefs* const refs) {
return b; return b;
} }
// Return 1 on success, 0 on error.
static int BackwardRefsClone(const VP8LBackwardRefs* const from,
VP8LBackwardRefs* const to) {
const PixOrCopyBlock* block_from = from->refs_;
VP8LClearBackwardRefs(to);
while (block_from != NULL) {
PixOrCopyBlock* const block_to = BackwardRefsNewBlock(to);
if (block_to == NULL) return 0;
memcpy(block_to->start_, block_from->start_,
block_from->size_ * sizeof(PixOrCopy));
block_to->size_ = block_from->size_;
block_from = block_from->next_;
}
return 1;
}
extern void VP8LBackwardRefsCursorAdd(VP8LBackwardRefs* const refs, extern void VP8LBackwardRefsCursorAdd(VP8LBackwardRefs* const refs,
const PixOrCopy v); const PixOrCopy v);
void VP8LBackwardRefsCursorAdd(VP8LBackwardRefs* const refs, void VP8LBackwardRefsCursorAdd(VP8LBackwardRefs* const refs,
@ -848,16 +879,21 @@ extern int VP8LBackwardReferencesTraceBackwards(
int xsize, int ysize, const uint32_t* const argb, int cache_bits, int xsize, int ysize, const uint32_t* const argb, int cache_bits,
const VP8LHashChain* const hash_chain, const VP8LHashChain* const hash_chain,
const VP8LBackwardRefs* const refs_src, VP8LBackwardRefs* const refs_dst); const VP8LBackwardRefs* const refs_src, VP8LBackwardRefs* const refs_dst);
static VP8LBackwardRefs* GetBackwardReferences( static int GetBackwardReferences(int width, int height,
int width, int height, const uint32_t* const argb, int quality, const uint32_t* const argb, int quality,
int lz77_types_to_try, int* const cache_bits, int lz77_types_to_try, int cache_bits_max,
const VP8LHashChain* const hash_chain, VP8LBackwardRefs* best, int do_no_cache,
VP8LBackwardRefs* worst) { const VP8LHashChain* const hash_chain,
const int cache_bits_initial = *cache_bits; VP8LBackwardRefs* const refs,
double bit_cost_best = -1; int* const cache_bits_best) {
VP8LHistogram* histo = NULL; VP8LHistogram* histo = NULL;
int lz77_type, lz77_type_best = 0; int i, lz77_type;
// Index 0 is for a color cache, index 1 for no cache (if needed).
int lz77_types_best[2] = {0, 0};
double bit_costs_best[2] = {DBL_MAX, DBL_MAX};
VP8LHashChain hash_chain_box; VP8LHashChain hash_chain_box;
VP8LBackwardRefs* const refs_tmp = &refs[do_no_cache ? 2 : 1];
int status = 0;
memset(&hash_chain_box, 0, sizeof(hash_chain_box)); memset(&hash_chain_box, 0, sizeof(hash_chain_box));
histo = VP8LAllocateHistogram(MAX_COLOR_CACHE_BITS); histo = VP8LAllocateHistogram(MAX_COLOR_CACHE_BITS);
@ -866,86 +902,129 @@ static VP8LBackwardRefs* GetBackwardReferences(
for (lz77_type = 1; lz77_types_to_try; for (lz77_type = 1; lz77_types_to_try;
lz77_types_to_try &= ~lz77_type, lz77_type <<= 1) { lz77_types_to_try &= ~lz77_type, lz77_type <<= 1) {
int res = 0; int res = 0;
double bit_cost; double bit_cost = 0.;
int cache_bits_tmp = cache_bits_initial;
if ((lz77_types_to_try & lz77_type) == 0) continue; if ((lz77_types_to_try & lz77_type) == 0) continue;
switch (lz77_type) { switch (lz77_type) {
case kLZ77RLE: case kLZ77RLE:
res = BackwardReferencesRle(width, height, argb, 0, worst); res = BackwardReferencesRle(width, height, argb, 0, refs_tmp);
break; break;
case kLZ77Standard: case kLZ77Standard:
// Compute LZ77 with no cache (0 bits), as the ideal LZ77 with a color // Compute LZ77 with no cache (0 bits), as the ideal LZ77 with a color
// 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,
refs_tmp);
break; break;
case kLZ77Box: case kLZ77Box:
if (!VP8LHashChainInit(&hash_chain_box, width * height)) goto Error; if (!VP8LHashChainInit(&hash_chain_box, width * height)) goto Error;
res = BackwardReferencesLz77Box(width, height, argb, 0, hash_chain, res = BackwardReferencesLz77Box(width, height, argb, 0, hash_chain,
&hash_chain_box, worst); &hash_chain_box, refs_tmp);
break; break;
default: default:
assert(0); assert(0);
} }
if (!res) goto Error; if (!res) goto Error;
// Next, try with a color cache and update the references. // Start with the no color cache case.
if (!CalculateBestCacheSize(argb, quality, worst, &cache_bits_tmp)) { for (i = 1; i >= 0; --i) {
goto Error; int cache_bits = (i == 1) ? 0 : cache_bits_max;
}
if (cache_bits_tmp > 0) { if (i == 1 && !do_no_cache) continue;
if (!BackwardRefsWithLocalCache(argb, cache_bits_tmp, worst)) {
goto Error; if (i == 0) {
// Try with a color cache.
if (!CalculateBestCacheSize(argb, quality, refs_tmp, &cache_bits)) {
goto Error;
}
if (cache_bits > 0) {
if (!BackwardRefsWithLocalCache(argb, cache_bits, refs_tmp)) {
goto Error;
}
}
}
if (i == 0 && do_no_cache && cache_bits == 0) {
// No need to re-compute bit_cost as it was computed at i == 1.
} else {
VP8LHistogramCreate(histo, refs_tmp, cache_bits);
bit_cost = VP8LHistogramEstimateBits(histo);
}
if (bit_cost < bit_costs_best[i]) {
if (i == 1) {
// Do not swap as the full cache analysis would have the wrong
// VP8LBackwardRefs to start with.
if (!BackwardRefsClone(refs_tmp, &refs[1])) goto Error;
} else {
BackwardRefsSwap(refs_tmp, &refs[0]);
}
bit_costs_best[i] = bit_cost;
lz77_types_best[i] = lz77_type;
if (i == 0) *cache_bits_best = cache_bits;
} }
} }
// Keep the best backward references.
VP8LHistogramCreate(histo, worst, cache_bits_tmp);
bit_cost = VP8LHistogramEstimateBits(histo);
if (lz77_type_best == 0 || bit_cost < bit_cost_best) {
VP8LBackwardRefs* const tmp = worst;
worst = best;
best = tmp;
bit_cost_best = bit_cost;
*cache_bits = cache_bits_tmp;
lz77_type_best = lz77_type;
}
} }
assert(lz77_type_best > 0); assert(lz77_types_best[0] > 0);
assert(!do_no_cache || lz77_types_best[1] > 0);
// 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 || lz77_type_best == kLZ77Box) && for (i = 1; i >= 0; --i) {
quality >= 25) { if (i == 1 && !do_no_cache) continue;
const VP8LHashChain* const hash_chain_tmp = if ((lz77_types_best[i] == kLZ77Standard ||
(lz77_type_best == kLZ77Standard) ? hash_chain : &hash_chain_box; lz77_types_best[i] == kLZ77Box) &&
if (VP8LBackwardReferencesTraceBackwards(width, height, argb, *cache_bits, quality >= 25) {
hash_chain_tmp, best, worst)) { const VP8LHashChain* const hash_chain_tmp =
double bit_cost_trace; (lz77_types_best[i] == kLZ77Standard) ? hash_chain : &hash_chain_box;
VP8LHistogramCreate(histo, worst, *cache_bits); const int cache_bits = (i == 1) ? 0 : *cache_bits_best;
bit_cost_trace = VP8LHistogramEstimateBits(histo); if (VP8LBackwardReferencesTraceBackwards(width, height, argb, cache_bits,
if (bit_cost_trace < bit_cost_best) best = worst; hash_chain_tmp, &refs[i],
refs_tmp)) {
double bit_cost_trace;
VP8LHistogramCreate(histo, refs_tmp, cache_bits);
bit_cost_trace = VP8LHistogramEstimateBits(histo);
if (bit_cost_trace < bit_costs_best[i]) {
BackwardRefsSwap(refs_tmp, &refs[i]);
}
}
}
BackwardReferences2DLocality(width, &refs[i]);
if (i == 1 && lz77_types_best[0] == lz77_types_best[1] &&
*cache_bits_best == 0) {
// If the best cache size is 0 and we have the same best LZ77, just copy
// the data over and stop here.
if (!BackwardRefsClone(&refs[1], &refs[0])) goto Error;
break;
} }
} }
status = 1;
BackwardReferences2DLocality(width, best);
Error: Error:
VP8LHashChainClear(&hash_chain_box); VP8LHashChainClear(&hash_chain_box);
VP8LFreeHistogram(histo); VP8LFreeHistogram(histo);
return best; return status;
} }
VP8LBackwardRefs* VP8LGetBackwardReferences( WebPEncodingError VP8LGetBackwardReferences(
int width, int height, const uint32_t* const argb, int quality, int width, int height, const uint32_t* const argb, int quality,
int low_effort, int lz77_types_to_try, int* const cache_bits, int low_effort, int lz77_types_to_try, int cache_bits_max, int do_no_cache,
const VP8LHashChain* const hash_chain, VP8LBackwardRefs* const refs_tmp1, const VP8LHashChain* const hash_chain, VP8LBackwardRefs* const refs,
VP8LBackwardRefs* const refs_tmp2) { int* const cache_bits_best) {
if (low_effort) { if (low_effort) {
return GetBackwardReferencesLowEffort(width, height, argb, cache_bits, VP8LBackwardRefs* refs_best;
hash_chain, refs_tmp1); *cache_bits_best = cache_bits_max;
refs_best = GetBackwardReferencesLowEffort(
width, height, argb, cache_bits_best, hash_chain, refs);
if (refs_best == NULL) return VP8_ENC_ERROR_OUT_OF_MEMORY;
// Set it in first position.
BackwardRefsSwap(refs_best, &refs[0]);
} else { } else {
return GetBackwardReferences(width, height, argb, quality, if (!GetBackwardReferences(width, height, argb, quality, lz77_types_to_try,
lz77_types_to_try, cache_bits, hash_chain, cache_bits_max, do_no_cache, hash_chain, refs,
refs_tmp1, refs_tmp2); cache_bits_best)) {
return VP8_ENC_ERROR_OUT_OF_MEMORY;
}
} }
return VP8_ENC_OK;
} }

View File

@ -16,6 +16,7 @@
#include <assert.h> #include <assert.h>
#include <stdlib.h> #include <stdlib.h>
#include "src/webp/types.h" #include "src/webp/types.h"
#include "src/webp/encode.h"
#include "src/webp/format_constants.h" #include "src/webp/format_constants.h"
#ifdef __cplusplus #ifdef __cplusplus
@ -218,14 +219,19 @@ enum VP8LLZ77Type {
// Evaluates best possible backward references for specified quality. // Evaluates best possible backward references for specified quality.
// The input cache_bits to 'VP8LGetBackwardReferences' sets the maximum cache // The input cache_bits to 'VP8LGetBackwardReferences' sets the maximum cache
// bits to use (passing 0 implies disabling the local color cache). // bits to use (passing 0 implies disabling the local color cache).
// The optimal cache bits is evaluated and set for the *cache_bits parameter. // The optimal cache bits is evaluated and set for the *cache_bits_best
// The return value is the pointer to the best of the two backward refs viz, // parameter with the matching refs_best.
// refs[0] or refs[1]. // If do_no_cache == 0, refs is an array of 2 values and the best
VP8LBackwardRefs* VP8LGetBackwardReferences( // VP8LBackwardRefs is put in the first element.
// If do_no_cache != 0, refs is an array of 3 values and the best
// VP8LBackwardRefs is put in the first element, the best value with no-cache in
// the second element.
// In both cases, the last element is used as temporary internally.
WebPEncodingError VP8LGetBackwardReferences(
int width, int height, const uint32_t* const argb, int quality, int width, int height, const uint32_t* const argb, int quality,
int low_effort, int lz77_types_to_try, int* const cache_bits, int low_effort, int lz77_types_to_try, int cache_bits_max, int do_no_cache,
const VP8LHashChain* const hash_chain, VP8LBackwardRefs* const refs_tmp1, const VP8LHashChain* const hash_chain, VP8LBackwardRefs* const refs,
VP8LBackwardRefs* const refs_tmp2); int* const cache_bits_best);
#ifdef __cplusplus #ifdef __cplusplus
} }

View File

@ -208,6 +208,7 @@ void VP8LHistogramAddSinglePixOrCopy(VP8LHistogram* const histo,
} else if (PixOrCopyIsCacheIdx(v)) { } else if (PixOrCopyIsCacheIdx(v)) {
const int literal_ix = const int literal_ix =
NUM_LITERAL_CODES + NUM_LENGTH_CODES + PixOrCopyCacheIdx(v); NUM_LITERAL_CODES + NUM_LENGTH_CODES + PixOrCopyCacheIdx(v);
assert(histo->palette_code_bits_ != 0);
++histo->literal_[literal_ix]; ++histo->literal_[literal_ix];
} else { } else {
int code, extra_bits; int code, extra_bits;

View File

@ -355,11 +355,15 @@ static int GetTransformBits(int method, int histo_bits) {
} }
// Set of parameters to be used in each iteration of the cruncher. // Set of parameters to be used in each iteration of the cruncher.
#define CRUNCH_CONFIGS_LZ77_MAX 2 #define CRUNCH_SUBCONFIGS_MAX 2
typedef struct {
int lz77_;
int do_no_cache_;
} CrunchSubConfig;
typedef struct { typedef struct {
int entropy_idx_; int entropy_idx_;
int lz77s_types_to_try_[CRUNCH_CONFIGS_LZ77_MAX]; CrunchSubConfig sub_configs_[CRUNCH_SUBCONFIGS_MAX];
int lz77s_types_to_try_size_; int sub_configs_size_;
} CrunchConfig; } CrunchConfig;
#define CRUNCH_CONFIGS_MAX kNumEntropyIx #define CRUNCH_CONFIGS_MAX kNumEntropyIx
@ -377,6 +381,9 @@ static int EncoderAnalyze(VP8LEncoder* const enc,
int i; int i;
int use_palette; int use_palette;
int n_lz77s; int n_lz77s;
// If set to 0, analyze the cache with the computed cache value. If 1, also
// analyze with no-cache.
int do_no_cache = 0;
assert(pic != NULL && pic->argb != NULL); assert(pic != NULL && pic->argb != NULL);
use_palette = use_palette =
@ -403,6 +410,7 @@ static int EncoderAnalyze(VP8LEncoder* const enc,
return 0; return 0;
} }
if (method == 6 && config->quality == 100) { if (method == 6 && config->quality == 100) {
do_no_cache = 1;
// Go brute force on all transforms. // Go brute force on all transforms.
*crunch_configs_size = 0; *crunch_configs_size = 0;
for (i = 0; i < kNumEntropyIx; ++i) { for (i = 0; i < kNumEntropyIx; ++i) {
@ -420,14 +428,16 @@ static int EncoderAnalyze(VP8LEncoder* const enc,
} }
} }
// Fill in the different LZ77s. // Fill in the different LZ77s.
assert(n_lz77s <= CRUNCH_CONFIGS_LZ77_MAX); assert(n_lz77s <= CRUNCH_SUBCONFIGS_MAX);
for (i = 0; i < *crunch_configs_size; ++i) { for (i = 0; i < *crunch_configs_size; ++i) {
int j; int j;
for (j = 0; j < n_lz77s; ++j) { for (j = 0; j < n_lz77s; ++j) {
crunch_configs[i].lz77s_types_to_try_[j] = assert(j < CRUNCH_SUBCONFIGS_MAX);
crunch_configs[i].sub_configs_[j].lz77_ =
(j == 0) ? kLZ77Standard | kLZ77RLE : kLZ77Box; (j == 0) ? kLZ77Standard | kLZ77RLE : kLZ77Box;
crunch_configs[i].sub_configs_[j].do_no_cache_ = do_no_cache;
} }
crunch_configs[i].lz77s_types_to_try_size_ = n_lz77s; crunch_configs[i].sub_configs_size_ = n_lz77s;
} }
return 1; return 1;
} }
@ -443,7 +453,7 @@ static int EncoderInit(VP8LEncoder* const enc) {
int i; int i;
if (!VP8LHashChainInit(&enc->hash_chain_, pix_cnt)) return 0; if (!VP8LHashChainInit(&enc->hash_chain_, pix_cnt)) return 0;
for (i = 0; i < 3; ++i) VP8LBackwardRefsInit(&enc->refs_[i], refs_block_size); for (i = 0; i < 4; ++i) VP8LBackwardRefsInit(&enc->refs_[i], refs_block_size);
return 1; return 1;
} }
@ -772,13 +782,10 @@ static WebPEncodingError StoreImageToBitMask(
} }
// Special case of EncodeImageInternal() for cache-bits=0, histo_bits=31 // Special case of EncodeImageInternal() for cache-bits=0, histo_bits=31
static WebPEncodingError EncodeImageNoHuffman(VP8LBitWriter* const bw, static WebPEncodingError EncodeImageNoHuffman(
const uint32_t* const argb, VP8LBitWriter* const bw, const uint32_t* const argb,
VP8LHashChain* const hash_chain, VP8LHashChain* const hash_chain, VP8LBackwardRefs* const refs_array,
VP8LBackwardRefs* const refs_tmp1, int width, int height, int quality, int low_effort) {
VP8LBackwardRefs* const refs_tmp2,
int width, int height,
int quality, int low_effort) {
int i; int i;
int max_tokens = 0; int max_tokens = 0;
WebPEncodingError err = VP8_ENC_OK; WebPEncodingError err = VP8_ENC_OK;
@ -801,13 +808,11 @@ static WebPEncodingError EncodeImageNoHuffman(VP8LBitWriter* const bw,
err = VP8_ENC_ERROR_OUT_OF_MEMORY; err = VP8_ENC_ERROR_OUT_OF_MEMORY;
goto Error; goto Error;
} }
refs = VP8LGetBackwardReferences(width, height, argb, quality, 0, err = VP8LGetBackwardReferences(
kLZ77Standard | kLZ77RLE, &cache_bits, width, height, argb, quality, /*low_effort=*/0, kLZ77Standard | kLZ77RLE,
hash_chain, refs_tmp1, refs_tmp2); cache_bits, /*do_no_cache=*/0, hash_chain, refs_array, &cache_bits);
if (refs == NULL) { if (err != VP8_ENC_OK) goto Error;
err = VP8_ENC_ERROR_OUT_OF_MEMORY; refs = &refs_array[0];
goto Error;
}
histogram_image = VP8LAllocateHistogramSet(1, cache_bits); histogram_image = VP8LAllocateHistogramSet(1, cache_bits);
if (histogram_image == NULL) { if (histogram_image == NULL) {
err = VP8_ENC_ERROR_OUT_OF_MEMORY; err = VP8_ENC_ERROR_OUT_OF_MEMORY;
@ -863,11 +868,11 @@ static WebPEncodingError EncodeImageNoHuffman(VP8LBitWriter* const bw,
static WebPEncodingError EncodeImageInternal( static WebPEncodingError EncodeImageInternal(
VP8LBitWriter* const bw, const uint32_t* const argb, VP8LBitWriter* const bw, const uint32_t* const argb,
VP8LHashChain* const hash_chain, VP8LBackwardRefs refs_array[3], int width, VP8LHashChain* const hash_chain, VP8LBackwardRefs refs_array[4], int width,
int height, int quality, int low_effort, int use_cache, int height, int quality, int low_effort, int use_cache,
const CrunchConfig* const config, int* cache_bits, int histogram_bits, const CrunchConfig* const config, int* cache_bits, int histogram_bits,
size_t init_byte_position, int* const hdr_size, int* const data_size) { size_t init_byte_position, int* const hdr_size, int* const data_size) {
WebPEncodingError err = VP8_ENC_OK; WebPEncodingError err = VP8_ENC_ERROR_OUT_OF_MEMORY;
const uint32_t histogram_image_xysize = const uint32_t histogram_image_xysize =
VP8LSubSampleSize(width, histogram_bits) * VP8LSubSampleSize(width, histogram_bits) *
VP8LSubSampleSize(height, histogram_bits); VP8LSubSampleSize(height, histogram_bits);
@ -879,103 +884,103 @@ static WebPEncodingError EncodeImageInternal(
3ULL * CODE_LENGTH_CODES, sizeof(*huff_tree)); 3ULL * CODE_LENGTH_CODES, sizeof(*huff_tree));
HuffmanTreeToken* tokens = NULL; HuffmanTreeToken* tokens = NULL;
HuffmanTreeCode* huffman_codes = NULL; HuffmanTreeCode* huffman_codes = NULL;
VP8LBackwardRefs* refs_best;
VP8LBackwardRefs* refs_tmp;
uint16_t* const histogram_symbols = uint16_t* const histogram_symbols =
(uint16_t*)WebPSafeMalloc(histogram_image_xysize, (uint16_t*)WebPSafeMalloc(histogram_image_xysize,
sizeof(*histogram_symbols)); sizeof(*histogram_symbols));
int lz77s_idx; int sub_configs_idx;
int cache_bits_init, write_histogram_image;
VP8LBitWriter bw_init = *bw, bw_best; VP8LBitWriter bw_init = *bw, bw_best;
int hdr_size_tmp; int hdr_size_tmp;
VP8LHashChain hash_chain_histogram; // histogram image hash chain
size_t bw_size_best = ~(size_t)0;
assert(histogram_bits >= MIN_HUFFMAN_BITS); assert(histogram_bits >= MIN_HUFFMAN_BITS);
assert(histogram_bits <= MAX_HUFFMAN_BITS); assert(histogram_bits <= MAX_HUFFMAN_BITS);
assert(hdr_size != NULL); assert(hdr_size != NULL);
assert(data_size != NULL); assert(data_size != NULL);
if (histogram_symbols == NULL) { // Make sure we can allocate the different objects.
err = VP8_ENC_ERROR_OUT_OF_MEMORY; memset(&hash_chain_histogram, 0, sizeof(hash_chain_histogram));
if (huff_tree == NULL || histogram_symbols == NULL ||
!VP8LHashChainInit(&hash_chain_histogram, histogram_image_xysize) ||
!VP8LHashChainFill(hash_chain, quality, argb, width, height,
low_effort)) {
goto Error; goto Error;
} }
if (use_cache) { if (use_cache) {
// If the value is different from zero, it has been set during the // If the value is different from zero, it has been set during the
// palette analysis. // palette analysis.
if (*cache_bits == 0) *cache_bits = MAX_COLOR_CACHE_BITS; cache_bits_init = (*cache_bits == 0) ? MAX_COLOR_CACHE_BITS : *cache_bits;
} else { } else {
*cache_bits = 0; cache_bits_init = 0;
} }
// 'best_refs' is the reference to the best backward refs and points to one // If several iterations will happen, clone into bw_best.
// of refs_array[0] or refs_array[1]. if (!VP8LBitWriterInit(&bw_best, 0) ||
// Calculate backward references from ARGB image. ((config->sub_configs_size_ > 1 ||
if (huff_tree == NULL || config->sub_configs_[0].do_no_cache_) &&
!VP8LHashChainFill(hash_chain, quality, argb, width, height,
low_effort) ||
!VP8LBitWriterInit(&bw_best, 0) ||
(config->lz77s_types_to_try_size_ > 1 &&
!VP8LBitWriterClone(bw, &bw_best))) { !VP8LBitWriterClone(bw, &bw_best))) {
err = VP8_ENC_ERROR_OUT_OF_MEMORY;
goto Error; goto Error;
} }
for (lz77s_idx = 0; lz77s_idx < config->lz77s_types_to_try_size_; for (sub_configs_idx = 0; sub_configs_idx < config->sub_configs_size_;
++lz77s_idx) { ++sub_configs_idx) {
refs_best = VP8LGetBackwardReferences( const CrunchSubConfig* const sub_config =
width, height, argb, quality, low_effort, &config->sub_configs_[sub_configs_idx];
config->lz77s_types_to_try_[lz77s_idx], cache_bits, hash_chain, int cache_bits_best, i_cache;
&refs_array[0], &refs_array[1]); err = VP8LGetBackwardReferences(width, height, argb, quality, low_effort,
if (refs_best == NULL) { sub_config->lz77_, cache_bits_init,
err = VP8_ENC_ERROR_OUT_OF_MEMORY; sub_config->do_no_cache_, hash_chain,
goto Error; &refs_array[0], &cache_bits_best);
} if (err != VP8_ENC_OK) goto Error;
// Keep the best references aside and use the other element from the first
// two as a temporary for later usage.
refs_tmp = &refs_array[refs_best == &refs_array[0] ? 1 : 0];
histogram_image = for (i_cache = 0; i_cache < (sub_config->do_no_cache_ ? 2 : 1); ++i_cache) {
VP8LAllocateHistogramSet(histogram_image_xysize, *cache_bits); const int cache_bits_tmp = (i_cache == 0) ? cache_bits_best : 0;
tmp_histo = VP8LAllocateHistogram(*cache_bits); // Speed-up: no need to study the no-cache case if it was already studied
if (histogram_image == NULL || tmp_histo == NULL) { // in i_cache == 0.
err = VP8_ENC_ERROR_OUT_OF_MEMORY; if (i_cache == 1 && cache_bits_best == 0) break;
goto Error;
}
// Build histogram image and symbols from backward references. // Reset the bit writer for this iteration.
if (!VP8LGetHistoImageSymbols(width, height, refs_best, quality, low_effort, VP8LBitWriterReset(&bw_init, bw);
histogram_bits, *cache_bits, histogram_image,
tmp_histo, histogram_symbols)) {
err = VP8_ENC_ERROR_OUT_OF_MEMORY;
goto Error;
}
// Create Huffman bit lengths and codes for each histogram image.
histogram_image_size = histogram_image->size;
bit_array_size = 5 * histogram_image_size;
huffman_codes = (HuffmanTreeCode*)WebPSafeCalloc(bit_array_size,
sizeof(*huffman_codes));
// Note: some histogram_image entries may point to tmp_histos[], so the
// latter need to outlive the following call to GetHuffBitLengthsAndCodes().
if (huffman_codes == NULL ||
!GetHuffBitLengthsAndCodes(histogram_image, huffman_codes)) {
err = VP8_ENC_ERROR_OUT_OF_MEMORY;
goto Error;
}
// Free combined histograms.
VP8LFreeHistogramSet(histogram_image);
histogram_image = NULL;
// Free scratch histograms. // Build histogram image and symbols from backward references.
VP8LFreeHistogram(tmp_histo); histogram_image =
tmp_histo = NULL; VP8LAllocateHistogramSet(histogram_image_xysize, cache_bits_tmp);
tmp_histo = VP8LAllocateHistogram(cache_bits_tmp);
if (histogram_image == NULL || tmp_histo == NULL ||
!VP8LGetHistoImageSymbols(width, height, &refs_array[i_cache],
quality, low_effort, histogram_bits,
cache_bits_tmp, histogram_image, tmp_histo,
histogram_symbols)) {
goto Error;
}
// Create Huffman bit lengths and codes for each histogram image.
histogram_image_size = histogram_image->size;
bit_array_size = 5 * histogram_image_size;
huffman_codes = (HuffmanTreeCode*)WebPSafeCalloc(bit_array_size,
sizeof(*huffman_codes));
// Note: some histogram_image entries may point to tmp_histos[], so the
// latter need to outlive the following call to
// GetHuffBitLengthsAndCodes().
if (huffman_codes == NULL ||
!GetHuffBitLengthsAndCodes(histogram_image, huffman_codes)) {
goto Error;
}
// Free combined histograms.
VP8LFreeHistogramSet(histogram_image);
histogram_image = NULL;
// Color Cache parameters. // Free scratch histograms.
if (*cache_bits > 0) { VP8LFreeHistogram(tmp_histo);
VP8LPutBits(bw, 1, 1); tmp_histo = NULL;
VP8LPutBits(bw, *cache_bits, 4);
} else {
VP8LPutBits(bw, 0, 1);
}
// Huffman image + meta huffman. // Color Cache parameters.
{ if (cache_bits_tmp > 0) {
const int write_histogram_image = (histogram_image_size > 1); VP8LPutBits(bw, 1, 1);
VP8LPutBits(bw, cache_bits_tmp, 4);
} else {
VP8LPutBits(bw, 0, 1);
}
// Huffman image + meta huffman.
write_histogram_image = (histogram_image_size > 1);
VP8LPutBits(bw, write_histogram_image, 1); VP8LPutBits(bw, write_histogram_image, 1);
if (write_histogram_image) { if (write_histogram_image) {
uint32_t* const histogram_argb = uint32_t* const histogram_argb =
@ -983,10 +988,7 @@ static WebPEncodingError EncodeImageInternal(
sizeof(*histogram_argb)); sizeof(*histogram_argb));
int max_index = 0; int max_index = 0;
uint32_t i; uint32_t i;
if (histogram_argb == NULL) { if (histogram_argb == NULL) goto Error;
err = VP8_ENC_ERROR_OUT_OF_MEMORY;
goto Error;
}
for (i = 0; i < histogram_image_xysize; ++i) { for (i = 0; i < histogram_image_xysize; ++i) {
const int symbol_index = histogram_symbols[i] & 0xffff; const int symbol_index = histogram_symbols[i] & 0xffff;
histogram_argb[i] = (symbol_index << 8); histogram_argb[i] = (symbol_index << 8);
@ -998,65 +1000,64 @@ static WebPEncodingError EncodeImageInternal(
VP8LPutBits(bw, histogram_bits - 2, 3); VP8LPutBits(bw, histogram_bits - 2, 3);
err = EncodeImageNoHuffman( err = EncodeImageNoHuffman(
bw, histogram_argb, hash_chain, refs_tmp, &refs_array[2], bw, histogram_argb, &hash_chain_histogram, &refs_array[2],
VP8LSubSampleSize(width, histogram_bits), VP8LSubSampleSize(width, histogram_bits),
VP8LSubSampleSize(height, histogram_bits), quality, low_effort); VP8LSubSampleSize(height, histogram_bits), quality, low_effort);
WebPSafeFree(histogram_argb); WebPSafeFree(histogram_argb);
if (err != VP8_ENC_OK) goto Error; if (err != VP8_ENC_OK) goto Error;
} }
}
// Store Huffman codes. // Store Huffman codes.
{ {
int i; int i;
int max_tokens = 0; int max_tokens = 0;
// Find maximum number of symbols for the huffman tree-set. // Find maximum number of symbols for the huffman tree-set.
for (i = 0; i < 5 * histogram_image_size; ++i) { for (i = 0; i < 5 * histogram_image_size; ++i) {
HuffmanTreeCode* const codes = &huffman_codes[i]; HuffmanTreeCode* const codes = &huffman_codes[i];
if (max_tokens < codes->num_symbols) { if (max_tokens < codes->num_symbols) {
max_tokens = codes->num_symbols; max_tokens = codes->num_symbols;
}
}
tokens = (HuffmanTreeToken*)WebPSafeMalloc(max_tokens, sizeof(*tokens));
if (tokens == NULL) goto Error;
for (i = 0; i < 5 * histogram_image_size; ++i) {
HuffmanTreeCode* const codes = &huffman_codes[i];
StoreHuffmanCode(bw, huff_tree, tokens, codes);
ClearHuffmanTreeIfOnlyOneSymbol(codes);
} }
} }
tokens = (HuffmanTreeToken*)WebPSafeMalloc(max_tokens, sizeof(*tokens)); // Store actual literals.
if (tokens == NULL) { hdr_size_tmp = (int)(VP8LBitWriterNumBytes(bw) - init_byte_position);
err = VP8_ENC_ERROR_OUT_OF_MEMORY; err = StoreImageToBitMask(bw, width, histogram_bits, &refs_array[i_cache],
goto Error; histogram_symbols, huffman_codes);
if (err != VP8_ENC_OK) goto Error;
// Keep track of the smallest image so far.
if (VP8LBitWriterNumBytes(bw) < bw_size_best) {
bw_size_best = VP8LBitWriterNumBytes(bw);
*cache_bits = cache_bits_tmp;
*hdr_size = hdr_size_tmp;
*data_size =
(int)(VP8LBitWriterNumBytes(bw) - init_byte_position - *hdr_size);
VP8LBitWriterSwap(bw, &bw_best);
} }
for (i = 0; i < 5 * histogram_image_size; ++i) { WebPSafeFree(tokens);
HuffmanTreeCode* const codes = &huffman_codes[i]; tokens = NULL;
StoreHuffmanCode(bw, huff_tree, tokens, codes); if (huffman_codes != NULL) {
ClearHuffmanTreeIfOnlyOneSymbol(codes); WebPSafeFree(huffman_codes->codes);
WebPSafeFree(huffman_codes);
huffman_codes = NULL;
} }
} }
// Store actual literals.
hdr_size_tmp = (int)(VP8LBitWriterNumBytes(bw) - init_byte_position);
err = StoreImageToBitMask(bw, width, histogram_bits, refs_best,
histogram_symbols, huffman_codes);
// Keep track of the smallest image so far.
if (lz77s_idx == 0 ||
VP8LBitWriterNumBytes(bw) < VP8LBitWriterNumBytes(&bw_best)) {
*hdr_size = hdr_size_tmp;
*data_size =
(int)(VP8LBitWriterNumBytes(bw) - init_byte_position - *hdr_size);
VP8LBitWriterSwap(bw, &bw_best);
}
// Reset the bit writer for the following iteration if any.
if (config->lz77s_types_to_try_size_ > 1) VP8LBitWriterReset(&bw_init, bw);
WebPSafeFree(tokens);
tokens = NULL;
if (huffman_codes != NULL) {
WebPSafeFree(huffman_codes->codes);
WebPSafeFree(huffman_codes);
huffman_codes = NULL;
}
} }
VP8LBitWriterSwap(bw, &bw_best); VP8LBitWriterSwap(bw, &bw_best);
err = VP8_ENC_OK;
Error: Error:
WebPSafeFree(tokens); WebPSafeFree(tokens);
WebPSafeFree(huff_tree); WebPSafeFree(huff_tree);
VP8LFreeHistogramSet(histogram_image); VP8LFreeHistogramSet(histogram_image);
VP8LFreeHistogram(tmp_histo); VP8LFreeHistogram(tmp_histo);
VP8LHashChainClear(&hash_chain_histogram);
if (huffman_codes != NULL) { if (huffman_codes != NULL) {
WebPSafeFree(huffman_codes->codes); WebPSafeFree(huffman_codes->codes);
WebPSafeFree(huffman_codes); WebPSafeFree(huffman_codes);
@ -1098,8 +1099,7 @@ static WebPEncodingError ApplyPredictFilter(const VP8LEncoder* const enc,
VP8LPutBits(bw, pred_bits - 2, 3); VP8LPutBits(bw, pred_bits - 2, 3);
return EncodeImageNoHuffman( return EncodeImageNoHuffman(
bw, enc->transform_data_, (VP8LHashChain*)&enc->hash_chain_, bw, enc->transform_data_, (VP8LHashChain*)&enc->hash_chain_,
(VP8LBackwardRefs*)&enc->refs_[0], // cast const away (VP8LBackwardRefs*)&enc->refs_[0], transform_width, transform_height,
(VP8LBackwardRefs*)&enc->refs_[1], transform_width, transform_height,
quality, low_effort); quality, low_effort);
} }
@ -1119,8 +1119,7 @@ static WebPEncodingError ApplyCrossColorFilter(const VP8LEncoder* const enc,
VP8LPutBits(bw, ccolor_transform_bits - 2, 3); VP8LPutBits(bw, ccolor_transform_bits - 2, 3);
return EncodeImageNoHuffman( return EncodeImageNoHuffman(
bw, enc->transform_data_, (VP8LHashChain*)&enc->hash_chain_, bw, enc->transform_data_, (VP8LHashChain*)&enc->hash_chain_,
(VP8LBackwardRefs*)&enc->refs_[0], // cast const away (VP8LBackwardRefs*)&enc->refs_[0], transform_width, transform_height,
(VP8LBackwardRefs*)&enc->refs_[1], transform_width, transform_height,
quality, low_effort); quality, low_effort);
} }
@ -1467,8 +1466,8 @@ static WebPEncodingError EncodePalette(VP8LBitWriter* const bw, int low_effort,
} }
tmp_palette[0] = palette[0]; tmp_palette[0] = palette[0];
return EncodeImageNoHuffman(bw, tmp_palette, &enc->hash_chain_, return EncodeImageNoHuffman(bw, tmp_palette, &enc->hash_chain_,
&enc->refs_[0], &enc->refs_[1], palette_size, 1, &enc->refs_[0], palette_size, 1, /*quality=*/20,
20 /* quality */, low_effort); low_effort);
} }
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -1494,7 +1493,7 @@ static void VP8LEncoderDelete(VP8LEncoder* enc) {
if (enc != NULL) { if (enc != NULL) {
int i; int i;
VP8LHashChainClear(&enc->hash_chain_); VP8LHashChainClear(&enc->hash_chain_);
for (i = 0; i < 3; ++i) VP8LBackwardRefsClear(&enc->refs_[i]); for (i = 0; i < 4; ++i) VP8LBackwardRefsClear(&enc->refs_[i]);
ClearTransformBuffer(enc); ClearTransformBuffer(enc);
WebPSafeFree(enc); WebPSafeFree(enc);
} }
@ -1544,7 +1543,7 @@ static int EncodeStreamHook(void* input, void* data2) {
int data_size = 0; int data_size = 0;
int use_delta_palette = 0; int use_delta_palette = 0;
int idx; int idx;
size_t best_size = 0; size_t best_size = ~(size_t)0;
VP8LBitWriter bw_init = *bw, bw_best; VP8LBitWriter bw_init = *bw, bw_best;
(void)data2; (void)data2;
@ -1645,7 +1644,7 @@ static int EncodeStreamHook(void* input, void* data2) {
if (err != VP8_ENC_OK) goto Error; if (err != VP8_ENC_OK) goto Error;
// If we are better than what we already have. // If we are better than what we already have.
if (idx == 0 || VP8LBitWriterNumBytes(bw) < best_size) { if (VP8LBitWriterNumBytes(bw) < best_size) {
best_size = VP8LBitWriterNumBytes(bw); best_size = VP8LBitWriterNumBytes(bw);
// Store the BitWriter. // Store the BitWriter.
VP8LBitWriterSwap(bw, &bw_best); VP8LBitWriterSwap(bw, &bw_best);
@ -1821,7 +1820,7 @@ Error:
} }
#undef CRUNCH_CONFIGS_MAX #undef CRUNCH_CONFIGS_MAX
#undef CRUNCH_CONFIGS_LZ77_MAX #undef CRUNCH_SUBCONFIGS_MAX
int VP8LEncodeImage(const WebPConfig* const config, int VP8LEncodeImage(const WebPConfig* const config,
const WebPPicture* const picture) { const WebPPicture* const picture) {

View File

@ -71,7 +71,7 @@ typedef struct {
uint32_t palette_[MAX_PALETTE_SIZE]; uint32_t palette_[MAX_PALETTE_SIZE];
// Some 'scratch' (potentially large) objects. // Some 'scratch' (potentially large) objects.
struct VP8LBackwardRefs refs_[3]; // Backward Refs array for temporaries. struct VP8LBackwardRefs refs_[4]; // Backward Refs array for temporaries.
VP8LHashChain hash_chain_; // HashChain data for constructing VP8LHashChain hash_chain_; // HashChain data for constructing
// backward references. // backward references.
} VP8LEncoder; } VP8LEncoder;