Improved alpha cleanup for the webp encoder when prediction transform is used.

Gives 0.9% smaller (2.4% compared to before alpha cleanup) size on the 1000 PNGs dataset:
Alpha cleanup before: 18856614
Alpha cleanup after: 18685802
For reference, with no alpha cleanup: 19159992

Note: WebPCleanupTransparentArea is still also called in WebPEncode. This cleanup still helps
preprocessing in the encoder, and the cases when the prediction transform is not used.

Change-Id: I63e69f48af6ddeb9804e2e603c59dde2718c6c28
This commit is contained in:
Lode Vandevenne 2015-12-04 14:37:49 +01:00 committed by Pascal Massimino
parent 2c08aac81a
commit 6938111357
4 changed files with 34 additions and 11 deletions

View File

@ -161,7 +161,7 @@ void VP8LCollectColorBlueTransforms_C(const uint32_t* argb, int stride,
void VP8LResidualImage(int width, int height, int bits, int low_effort, void VP8LResidualImage(int width, int height, int bits, int low_effort,
uint32_t* const argb, uint32_t* const argb_scratch, uint32_t* const argb, uint32_t* const argb_scratch,
uint32_t* const image); uint32_t* const image, int exact);
void VP8LColorSpaceTransform(int width, int height, int bits, int quality, void VP8LColorSpaceTransform(int width, int height, int bits, int quality,
uint32_t* const argb, uint32_t* image); uint32_t* const argb, uint32_t* image);

View File

@ -25,6 +25,7 @@
#define MAX_DIFF_COST (1e30f) #define MAX_DIFF_COST (1e30f)
static const int kPredLowEffort = 11; static const int kPredLowEffort = 11;
static const uint32_t kMaskAlpha = 0xff000000;
// lookup table for small values of log2(int) // lookup table for small values of log2(int)
const float kLog2Table[LOG_LOOKUP_IDX_MAX] = { const float kLog2Table[LOG_LOOKUP_IDX_MAX] = {
@ -659,7 +660,8 @@ static WEBP_INLINE uint32_t Predict(VP8LPredictorFunc pred_func,
static int GetBestPredictorForTile(int width, int height, static int GetBestPredictorForTile(int width, int height,
int tile_x, int tile_y, int bits, int tile_x, int tile_y, int bits,
int accumulated[4][256], int accumulated[4][256],
const uint32_t* const argb_scratch) { const uint32_t* const argb_scratch,
int exact) {
const int kNumPredModes = 14; const int kNumPredModes = 14;
const int col_start = tile_x << bits; const int col_start = tile_x << bits;
const int row_start = tile_y << bits; const int row_start = tile_y << bits;
@ -691,7 +693,11 @@ static int GetBestPredictorForTile(int width, int height,
const int col = col_start + x; const int col = col_start + x;
const uint32_t predict = const uint32_t predict =
Predict(pred_func, col, row, current_row, upper_row); Predict(pred_func, col, row, current_row, upper_row);
UpdateHisto(histo_argb, VP8LSubPixels(current_row[col], predict)); uint32_t residual = VP8LSubPixels(current_row[col], predict);
if (!exact && (current_row[col] & kMaskAlpha) == 0) {
residual &= kMaskAlpha; // See CopyTileWithPrediction.
}
UpdateHisto(histo_argb, residual);
} }
} }
cur_diff = PredictionCostSpatialHistogram( cur_diff = PredictionCostSpatialHistogram(
@ -717,7 +723,8 @@ static int GetBestPredictorForTile(int width, int height,
static void CopyImageWithPrediction(int width, int height, static void CopyImageWithPrediction(int width, int height,
int bits, uint32_t* const modes, int bits, uint32_t* const modes,
uint32_t* const argb_scratch, uint32_t* const argb_scratch,
uint32_t* const argb, int low_effort) { uint32_t* const argb,
int low_effort, int exact) {
const int tiles_per_row = VP8LSubSampleSize(width, bits); const int tiles_per_row = VP8LSubSampleSize(width, bits);
const int mask = (1 << bits) - 1; const int mask = (1 << bits) - 1;
// The row size is one pixel longer to allow the top right pixel to point to // The row size is one pixel longer to allow the top right pixel to point to
@ -744,14 +751,25 @@ static void CopyImageWithPrediction(int width, int height,
} }
} else { } else {
for (x = 0; x < width; ++x) { for (x = 0; x < width; ++x) {
uint32_t predict; uint32_t predict, residual;
if ((x & mask) == 0) { if ((x & mask) == 0) {
const int mode = const int mode =
(modes[(y >> bits) * tiles_per_row + (x >> bits)] >> 8) & 0xff; (modes[(y >> bits) * tiles_per_row + (x >> bits)] >> 8) & 0xff;
pred_func = VP8LPredictors[mode]; pred_func = VP8LPredictors[mode];
} }
predict = Predict(pred_func, x, y, current_row, upper_row); predict = Predict(pred_func, x, y, current_row, upper_row);
argb[y * width + x] = VP8LSubPixels(current_row[x], predict); residual = VP8LSubPixels(current_row[x], predict);
if (!exact && (current_row[x] & kMaskAlpha) == 0) {
// If alpha is 0, cleanup RGB. We can choose the RGB values of the
// residual for best compression. The prediction of alpha itself can
// be non-zero and must be kept though. We choose RGB of the residual
// to be 0.
residual &= kMaskAlpha;
// Update input image so that next predictions use correct RGB value.
current_row[x] = predict & ~kMaskAlpha;
if (x == 0 && y != 0) upper_row[width] = current_row[x];
}
argb[y * width + x] = residual;
} }
} }
} }
@ -759,7 +777,7 @@ static void CopyImageWithPrediction(int width, int height,
void VP8LResidualImage(int width, int height, int bits, int low_effort, void VP8LResidualImage(int width, int height, int bits, int low_effort,
uint32_t* const argb, uint32_t* const argb_scratch, uint32_t* const argb, uint32_t* const argb_scratch,
uint32_t* const image) { uint32_t* const image, int exact) {
const int max_tile_size = 1 << bits; const int max_tile_size = 1 << bits;
const int tiles_per_row = VP8LSubSampleSize(width, bits); const int tiles_per_row = VP8LSubSampleSize(width, bits);
const int tiles_per_col = VP8LSubSampleSize(height, bits); const int tiles_per_col = VP8LSubSampleSize(height, bits);
@ -787,15 +805,14 @@ void VP8LResidualImage(int width, int height, int bits, int low_effort,
this_tile_height * width * sizeof(*current_tile_rows)); this_tile_height * width * sizeof(*current_tile_rows));
for (tile_x = 0; tile_x < tiles_per_row; ++tile_x) { for (tile_x = 0; tile_x < tiles_per_row; ++tile_x) {
const int pred = GetBestPredictorForTile(width, height, tile_x, tile_y, const int pred = GetBestPredictorForTile(width, height, tile_x, tile_y,
bits, (int (*)[256])histo, bits, (int (*)[256])histo, argb_scratch, exact);
argb_scratch);
image[tile_y * tiles_per_row + tile_x] = ARGB_BLACK | (pred << 8); image[tile_y * tiles_per_row + tile_x] = ARGB_BLACK | (pred << 8);
} }
} }
} }
CopyImageWithPrediction(width, height, bits, CopyImageWithPrediction(width, height, bits,
image, argb_scratch, argb, low_effort); image, argb_scratch, argb, low_effort, exact);
} }
void VP8LSubtractGreenFromBlueAndRed_C(uint32_t* argb_data, int num_pixels) { void VP8LSubtractGreenFromBlueAndRed_C(uint32_t* argb_data, int num_pixels) {

View File

@ -67,6 +67,11 @@ static int EncodeLossless(const uint8_t* const data, int width, int height,
WebPConfigInit(&config); WebPConfigInit(&config);
config.lossless = 1; config.lossless = 1;
// Enable exact, or it would alter RGB values of transparent alpha, which is
// normally OK but not here since we are not encoding the input image but an
// internal encoding-related image containing necessary exact information in
// RGB channels.
config.exact = 1;
config.method = effort_level; // impact is very small config.method = effort_level; // impact is very small
// Set a low default quality for encoding alpha. Ensure that Alpha quality at // Set a low default quality for encoding alpha. Ensure that Alpha quality at
// lower methods (3 and below) is less than the threshold for triggering // lower methods (3 and below) is less than the threshold for triggering

View File

@ -1012,7 +1012,8 @@ static WebPEncodingError ApplyPredictFilter(const VP8LEncoder* const enc,
const int transform_height = VP8LSubSampleSize(height, pred_bits); const int transform_height = VP8LSubSampleSize(height, pred_bits);
VP8LResidualImage(width, height, pred_bits, low_effort, enc->argb_, VP8LResidualImage(width, height, pred_bits, low_effort, enc->argb_,
enc->argb_scratch_, enc->transform_data_); enc->argb_scratch_, enc->transform_data_,
enc->config_->exact);
VP8LPutBits(bw, TRANSFORM_PRESENT, 1); VP8LPutBits(bw, TRANSFORM_PRESENT, 1);
VP8LPutBits(bw, PREDICTOR_TRANSFORM, 2); VP8LPutBits(bw, PREDICTOR_TRANSFORM, 2);
assert(pred_bits >= 2); assert(pred_bits >= 2);