Implement a cruncher for lossless at method 6.

Go over the whole compression step for each of the
transforms and pick the best one.

Change-Id: I3a1b1458348c468558be0fcf491038a5724c9364
This commit is contained in:
Vincent Rabaud 2017-04-20 16:56:36 +02:00
parent 1b92b237ac
commit adab8ce020
4 changed files with 195 additions and 113 deletions

View File

@ -128,7 +128,10 @@ static int AnalyzeAndCreatePalette(const WebPPicture* const pic,
uint32_t palette[MAX_PALETTE_SIZE],
int* const palette_size) {
const int num_colors = WebPGetColorPalette(pic, palette);
if (num_colors > MAX_PALETTE_SIZE) return 0;
if (num_colors > MAX_PALETTE_SIZE) {
*palette_size = 0;
return 0;
}
*palette_size = num_colors;
qsort(palette, num_colors, sizeof(*palette), PaletteCompareColorsForQsort);
if (!low_effort && PaletteHasNonMonotonousDeltas(palette, num_colors)) {
@ -353,7 +356,10 @@ static int GetTransformBits(int method, int histo_bits) {
return res;
}
static int AnalyzeAndInit(VP8LEncoder* const enc) {
static int AnalyzeAndInit(VP8LEncoder* const enc,
int entropy_idx[kNumEntropyIx],
int* const num_entropy_idx,
int* const red_and_blue_always_zero) {
const WebPPicture* const pic = enc->pic_;
const int width = pic->width;
const int height = pic->height;
@ -365,48 +371,48 @@ static int AnalyzeAndInit(VP8LEncoder* const enc) {
// at max MAX_REFS_BLOCK_PER_IMAGE blocks used:
int refs_block_size = (pix_cnt - 1) / MAX_REFS_BLOCK_PER_IMAGE + 1;
int i;
int use_palette;
assert(pic != NULL && pic->argb != NULL);
enc->use_cross_color_ = 0;
enc->use_predict_ = 0;
enc->use_subtract_green_ = 0;
enc->use_palette_ =
use_palette =
AnalyzeAndCreatePalette(pic, low_effort,
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_,
enc->histo_bits_ = GetHistoBits(method, 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_;
enc->use_subtract_green_ = !enc->use_palette_;
enc->use_cross_color_ = 0;
entropy_idx[0] = use_palette ? kPalette : kSpatialSubGreen;
*num_entropy_idx = 1;
} else {
int red_and_blue_always_zero;
EntropyIx min_entropy_ix;
if (!AnalyzeEntropy(pic->argb, width, height, pic->argb_stride,
enc->use_palette_,
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)) {
&min_entropy_ix, red_and_blue_always_zero)) {
return 0;
}
enc->use_palette_ = (min_entropy_ix == kPalette);
enc->use_subtract_green_ =
(min_entropy_ix == kSubGreen) || (min_entropy_ix == kSpatialSubGreen);
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 (method == 6 && config->quality == 100) {
// Go brute force on all transforms.
*num_entropy_idx = 0;
for (i = 0; i < kNumEntropyIx; ++i) {
if (i != kPalette || use_palette) {
entropy_idx[(*num_entropy_idx)++] = i;
assert(*num_entropy_idx <= kNumEntropyIx);
}
}
} else {
// Only choose the guessed best transform.
entropy_idx[0] = min_entropy_ix;
*num_entropy_idx = 1;
}
}
if (!VP8LHashChainInit(&enc->hash_chain_, pix_cnt)) return 0;
// palette-friendly input typically uses less literals
// -> reduce block size a bit
if (enc->use_palette_) refs_block_size /= 2;
for (i = 0; i < 3; ++i) VP8LBackwardRefsInit(&enc->refs_[i], refs_block_size);
return 1;
@ -1181,6 +1187,7 @@ static WebPEncodingError AllocateTransformBuffer(VP8LEncoder* const enc,
}
enc->transform_mem_ = mem;
enc->transform_mem_size_ = (size_t)mem_size;
enc->argb_content_ = kEncoderNone;
}
enc->argb_ = mem;
mem = (uint32_t*)WEBP_ALIGN(mem + image_size);
@ -1201,11 +1208,13 @@ static WebPEncodingError MakeInputImageCopy(VP8LEncoder* const enc) {
int y;
err = AllocateTransformBuffer(enc, width, height);
if (err != VP8_ENC_OK) return err;
if (enc->argb_content_ == kEncoderARGB) return VP8_ENC_OK;
for (y = 0; y < height; ++y) {
memcpy(enc->argb_ + y * width,
picture->argb + y * picture->argb_stride,
width * sizeof(*enc->argb_));
}
enc->argb_content_ = kEncoderARGB;
assert(enc->current_width_ == width);
return VP8_ENC_OK;
}
@ -1383,6 +1392,7 @@ static WebPEncodingError MapImageFromPalette(VP8LEncoder* const enc,
err = ApplyPalette(src, src_stride,
enc->argb_, enc->current_width_,
palette, palette_size, width, height, xbits);
enc->argb_content_ = kEncoderPalette;
return err;
}
@ -1441,7 +1451,7 @@ static WebPEncodingError EncodeDeltaPalettePredictorImage(
err = EncodeImageNoHuffman(
bw, predictors, &enc->hash_chain_,
(VP8LBackwardRefs*)&enc->refs_[0], // cast const away
(VP8LBackwardRefs*)&enc->refs_[1], // cast const away
(VP8LBackwardRefs*)&enc->refs_[1],
transform_width, transform_height, quality, low_effort);
WebPSafeFree(predictors);
return err;
@ -1461,6 +1471,7 @@ static VP8LEncoder* VP8LEncoderNew(const WebPConfig* const config,
}
enc->config_ = config;
enc->pic_ = picture;
enc->argb_content_ = kEncoderNone;
VP8LEncDspInit();
@ -1494,8 +1505,14 @@ WebPEncodingError VP8LEncodeStream(const WebPConfig* const config,
int hdr_size = 0;
int data_size = 0;
int use_delta_palette = 0;
int entropy_idx[kNumEntropyIx];
int num_entropy_idx = 0;
int i;
int red_and_blue_always_zero = 0;
size_t best_size = 0;
VP8LBitWriter bw_init = *bw, bw_best;
if (enc == NULL) {
if (enc == NULL || !VP8LBitWriterInit(&bw_best, 0)) {
err = VP8_ENC_ERROR_OUT_OF_MEMORY;
goto Error;
}
@ -1503,109 +1520,147 @@ WebPEncodingError VP8LEncodeStream(const WebPConfig* const config,
// ---------------------------------------------------------------------------
// Analyze image (entropy, num_palettes etc)
if (!AnalyzeAndInit(enc)) {
if (!AnalyzeAndInit(enc, entropy_idx, &num_entropy_idx,
&red_and_blue_always_zero)) {
err = VP8_ENC_ERROR_OUT_OF_MEMORY;
goto Error;
}
// Apply near-lossless preprocessing.
use_near_lossless =
(config->near_lossless < 100) && !enc->use_palette_ && !enc->use_predict_;
if (use_near_lossless) {
err = AllocateTransformBuffer(enc, width, height);
if (err != VP8_ENC_OK) goto Error;
if (!VP8ApplyNearLossless(picture, config->near_lossless, enc->argb_)) {
err = VP8_ENC_ERROR_OUT_OF_MEMORY;
goto Error;
for (i = 0; i < num_entropy_idx; ++i) {
enc->use_palette_ = (entropy_idx[i] == kPalette);
enc->use_subtract_green_ = (entropy_idx[i] == kSubGreen) ||
(entropy_idx[i] == kSpatialSubGreen);
enc->use_predict_ = (entropy_idx[i] == kSpatial) ||
(entropy_idx[i] == kSpatialSubGreen);
if (low_effort) {
enc->use_cross_color_ = 0;
} else {
enc->use_cross_color_ = red_and_blue_always_zero ? 0 : enc->use_predict_;
}
}
// Reset any parameter in the encoder that is set in the previous iteration.
enc->cache_bits_ = 0;
VP8LBackwardRefsClear(&enc->refs_[0]);
VP8LBackwardRefsClear(&enc->refs_[1]);
#ifdef WEBP_EXPERIMENTAL_FEATURES
if (config->use_delta_palette) {
enc->use_predict_ = 1;
enc->use_cross_color_ = 0;
enc->use_subtract_green_ = 0;
enc->use_palette_ = 1;
err = MakeInputImageCopy(enc);
if (err != VP8_ENC_OK) goto Error;
err = WebPSearchOptimalDeltaPalette(enc);
if (err != VP8_ENC_OK) goto Error;
if (enc->use_palette_) {
// Apply near-lossless preprocessing.
use_near_lossless = (config->near_lossless < 100) && !enc->use_palette_ &&
!enc->use_predict_;
if (use_near_lossless) {
err = AllocateTransformBuffer(enc, width, height);
if (err != VP8_ENC_OK) goto Error;
err = EncodeDeltaPalettePredictorImage(bw, enc, quality, low_effort);
if (err != VP8_ENC_OK) goto Error;
use_delta_palette = 1;
if ((enc->argb_content_ != kEncoderNearLossless) &&
!VP8ApplyNearLossless(picture, config->near_lossless, enc->argb_)) {
err = VP8_ENC_ERROR_OUT_OF_MEMORY;
goto Error;
}
enc->argb_content_ = kEncoderNearLossless;
} else {
enc->argb_content_ = kEncoderNone;
}
#ifdef WEBP_EXPERIMENTAL_FEATURES
if (config->use_delta_palette) {
enc->use_predict_ = 1;
enc->use_cross_color_ = 0;
enc->use_subtract_green_ = 0;
enc->use_palette_ = 1;
if (enc->argb_content_ != kEncoderNearLossless &&
enc->argb_content_ != kEncoderPalette) {
err = MakeInputImageCopy(enc);
if (err != VP8_ENC_OK) goto Error;
}
err = WebPSearchOptimalDeltaPalette(enc);
if (err != VP8_ENC_OK) goto Error;
if (enc->use_palette_) {
err = AllocateTransformBuffer(enc, width, height);
if (err != VP8_ENC_OK) goto Error;
err = EncodeDeltaPalettePredictorImage(bw, enc, quality, low_effort);
if (err != VP8_ENC_OK) goto Error;
use_delta_palette = 1;
}
}
}
#endif // WEBP_EXPERIMENTAL_FEATURES
// Encode palette
if (enc->use_palette_) {
err = EncodePalette(bw, low_effort, enc);
if (err != VP8_ENC_OK) goto Error;
err = MapImageFromPalette(enc, use_delta_palette);
if (err != VP8_ENC_OK) goto Error;
// If using a color cache, do not have it bigger than the number of colors.
if (use_cache && enc->palette_size_ < (1 << MAX_COLOR_CACHE_BITS)) {
enc->cache_bits_ = BitsLog2Floor(enc->palette_size_) + 1;
}
}
if (!use_delta_palette) {
// In case image is not packed.
if (enc->argb_ == NULL) {
err = MakeInputImageCopy(enc);
// Encode palette
if (enc->use_palette_) {
err = EncodePalette(bw, low_effort, enc);
if (err != VP8_ENC_OK) goto Error;
err = MapImageFromPalette(enc, use_delta_palette);
if (err != VP8_ENC_OK) goto Error;
// If using a color cache, do not have it bigger than the number of
// colors.
if (use_cache && enc->palette_size_ < (1 << MAX_COLOR_CACHE_BITS)) {
enc->cache_bits_ = BitsLog2Floor(enc->palette_size_) + 1;
}
}
if (!use_delta_palette) {
// In case image is not packed.
if (enc->argb_content_ != kEncoderNearLossless &&
enc->argb_content_ != kEncoderPalette) {
err = MakeInputImageCopy(enc);
if (err != VP8_ENC_OK) goto Error;
}
// -----------------------------------------------------------------------
// Apply transforms and write transform data.
if (enc->use_subtract_green_) {
ApplySubtractGreen(enc, enc->current_width_, height, bw);
}
if (enc->use_predict_) {
err = ApplyPredictFilter(enc, enc->current_width_, height, quality,
low_effort, enc->use_subtract_green_, bw);
if (err != VP8_ENC_OK) goto Error;
}
if (enc->use_cross_color_) {
err = ApplyCrossColorFilter(enc, enc->current_width_, height, quality,
low_effort, bw);
if (err != VP8_ENC_OK) goto Error;
}
}
VP8LPutBits(bw, !TRANSFORM_PRESENT, 1); // No more transforms.
// -------------------------------------------------------------------------
// Apply transforms and write transform data.
// Encode and write the transformed image.
err = EncodeImageInternal(bw, enc->argb_, &enc->hash_chain_, enc->refs_,
enc->current_width_, height, quality, low_effort,
use_cache, &enc->cache_bits_, enc->histo_bits_,
byte_position, &hdr_size, &data_size);
if (err != VP8_ENC_OK) goto Error;
if (enc->use_subtract_green_) {
ApplySubtractGreen(enc, enc->current_width_, height, bw);
}
if (enc->use_predict_) {
err = ApplyPredictFilter(enc, enc->current_width_, height, quality,
low_effort, enc->use_subtract_green_, bw);
if (err != VP8_ENC_OK) goto Error;
}
if (enc->use_cross_color_) {
err = ApplyCrossColorFilter(enc, enc->current_width_,
height, quality, low_effort, bw);
if (err != VP8_ENC_OK) goto Error;
// If we are better than what we already have.
if (best_size == 0 || VP8LBitWriterNumBytes(bw) < best_size) {
best_size = VP8LBitWriterNumBytes(bw);
// Store the BitWriter.
VP8LBitWriterSwap(bw, &bw_best);
// Update the stats.
if (picture->stats != NULL) {
WebPAuxStats* const stats = picture->stats;
stats->lossless_features = 0;
if (enc->use_predict_) stats->lossless_features |= 1;
if (enc->use_cross_color_) stats->lossless_features |= 2;
if (enc->use_subtract_green_) stats->lossless_features |= 4;
if (enc->use_palette_) stats->lossless_features |= 8;
stats->histogram_bits = enc->histo_bits_;
stats->transform_bits = enc->transform_bits_;
stats->cache_bits = enc->cache_bits_;
stats->palette_size = enc->palette_size_;
stats->lossless_size = (int)(VP8LBitWriterNumBytes(bw) - byte_position);
stats->lossless_hdr_size = hdr_size;
stats->lossless_data_size = data_size;
}
}
// Reset the bit writer for the following iteration if any.
if (num_entropy_idx > 1) VP8LBitWriterReset(&bw_init, bw);
}
VP8LPutBits(bw, !TRANSFORM_PRESENT, 1); // No more transforms.
// ---------------------------------------------------------------------------
// Encode and write the transformed image.
err = EncodeImageInternal(bw, enc->argb_, &enc->hash_chain_, enc->refs_,
enc->current_width_, height, quality, low_effort,
use_cache, &enc->cache_bits_, enc->histo_bits_,
byte_position, &hdr_size, &data_size);
if (err != VP8_ENC_OK) goto Error;
if (picture->stats != NULL) {
WebPAuxStats* const stats = picture->stats;
stats->lossless_features = 0;
if (enc->use_predict_) stats->lossless_features |= 1;
if (enc->use_cross_color_) stats->lossless_features |= 2;
if (enc->use_subtract_green_) stats->lossless_features |= 4;
if (enc->use_palette_) stats->lossless_features |= 8;
stats->histogram_bits = enc->histo_bits_;
stats->transform_bits = enc->transform_bits_;
stats->cache_bits = enc->cache_bits_;
stats->palette_size = enc->palette_size_;
stats->lossless_size = (int)(VP8LBitWriterNumBytes(bw) - byte_position);
stats->lossless_hdr_size = hdr_size;
stats->lossless_data_size = data_size;
}
VP8LBitWriterSwap(&bw_best, bw);
Error:
VP8LEncoderDelete(enc);
VP8LBitWriterWipeOut(&bw_best);
return err;
}

View File

@ -27,16 +27,24 @@ extern "C" {
// maximum value of transform_bits_ in VP8LEncoder.
#define MAX_TRANSFORM_BITS 6
typedef enum {
kEncoderNone = 0,
kEncoderARGB,
kEncoderNearLossless,
kEncoderPalette
} VP8LEncoderARGBContent;
typedef struct {
const WebPConfig* config_; // user configuration and parameters
const WebPPicture* pic_; // input picture.
uint32_t* argb_; // Transformed argb image data.
uint32_t* argb_scratch_; // Scratch memory for argb rows
// (used for prediction).
uint32_t* transform_data_; // Scratch memory for transform data.
uint32_t* transform_mem_; // Currently allocated memory.
size_t transform_mem_size_; // Currently allocated memory size.
uint32_t* argb_; // Transformed argb image data.
VP8LEncoderARGBContent argb_content_; // Content type of the argb buffer.
uint32_t* argb_scratch_; // Scratch memory for argb rows
// (used for prediction).
uint32_t* transform_data_; // Scratch memory for transform data.
uint32_t* transform_mem_; // Currently allocated memory.
size_t transform_mem_size_; // Currently allocated memory size.
int current_width_; // Corresponds to packed image width.

View File

@ -246,6 +246,20 @@ void VP8LBitWriterWipeOut(VP8LBitWriter* const bw) {
}
}
void VP8LBitWriterReset(const VP8LBitWriter* const bw_init,
VP8LBitWriter* const bw) {
bw->bits_ = bw_init->bits_;
bw->used_ = bw_init->used_;
bw->cur_ = bw->buf_ + (bw_init->cur_ - bw_init->buf_);
bw->error_ = bw_init->error_;
}
void VP8LBitWriterSwap(VP8LBitWriter* const src, VP8LBitWriter* const dst) {
const VP8LBitWriter tmp = *src;
*src = *dst;
*dst = tmp;
}
void VP8LPutBitsFlushBits(VP8LBitWriter* const bw) {
// If needed, make some room by flushing some bits out.
if (bw->cur_ + VP8L_WRITER_BYTES > bw->end_) {

View File

@ -110,6 +110,11 @@ int VP8LBitWriterInit(VP8LBitWriter* const bw, size_t expected_size);
uint8_t* VP8LBitWriterFinish(VP8LBitWriter* const bw);
// Release any pending memory and zeroes the object.
void VP8LBitWriterWipeOut(VP8LBitWriter* const bw);
// Resets the cursor of the BitWriter bw to when it was like in bw_init.
void VP8LBitWriterReset(const VP8LBitWriter* const bw_init,
VP8LBitWriter* const bw);
// Swaps the memory held by two BitWriters.
void VP8LBitWriterSwap(VP8LBitWriter* const src, VP8LBitWriter* const dst);
// Internal function for VP8LPutBits flushing 32 bits from the written state.
void VP8LPutBitsFlushBits(VP8LBitWriter* const bw);