diff --git a/src/dsp/lossless.h b/src/dsp/lossless.h index ee677133..b59d0fba 100644 --- a/src/dsp/lossless.h +++ b/src/dsp/lossless.h @@ -161,7 +161,7 @@ void VP8LCollectColorBlueTransforms_C(const uint32_t* argb, int stride, void VP8LResidualImage(int width, int height, int bits, int low_effort, 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, uint32_t* const argb, uint32_t* image); diff --git a/src/dsp/lossless_enc.c b/src/dsp/lossless_enc.c index b81b1f44..d75b782c 100644 --- a/src/dsp/lossless_enc.c +++ b/src/dsp/lossless_enc.c @@ -25,6 +25,7 @@ #define MAX_DIFF_COST (1e30f) static const int kPredLowEffort = 11; +static const uint32_t kMaskAlpha = 0xff000000; // lookup table for small values of log2(int) 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, int tile_x, int tile_y, int bits, int accumulated[4][256], - const uint32_t* const argb_scratch) { + const uint32_t* const argb_scratch, + int exact) { const int kNumPredModes = 14; const int col_start = tile_x << 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 uint32_t predict = 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( @@ -717,7 +723,8 @@ static int GetBestPredictorForTile(int width, int height, static void CopyImageWithPrediction(int width, int height, int bits, uint32_t* const modes, 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 mask = (1 << bits) - 1; // 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 { for (x = 0; x < width; ++x) { - uint32_t predict; + uint32_t predict, residual; if ((x & mask) == 0) { const int mode = (modes[(y >> bits) * tiles_per_row + (x >> bits)] >> 8) & 0xff; pred_func = VP8LPredictors[mode]; } 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, 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 tiles_per_row = VP8LSubSampleSize(width, 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)); for (tile_x = 0; tile_x < tiles_per_row; ++tile_x) { const int pred = GetBestPredictorForTile(width, height, tile_x, tile_y, - bits, (int (*)[256])histo, - argb_scratch); + bits, (int (*)[256])histo, argb_scratch, exact); image[tile_y * tiles_per_row + tile_x] = ARGB_BLACK | (pred << 8); } } } 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) { diff --git a/src/enc/alpha.c b/src/enc/alpha.c index fad6346e..3c970b00 100644 --- a/src/enc/alpha.c +++ b/src/enc/alpha.c @@ -67,6 +67,11 @@ static int EncodeLossless(const uint8_t* const data, int width, int height, WebPConfigInit(&config); 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 // 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 diff --git a/src/enc/vp8l.c b/src/enc/vp8l.c index 7809af1a..84611a6f 100644 --- a/src/enc/vp8l.c +++ b/src/enc/vp8l.c @@ -1012,7 +1012,8 @@ static WebPEncodingError ApplyPredictFilter(const VP8LEncoder* const enc, const int transform_height = VP8LSubSampleSize(height, pred_bits); 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, PREDICTOR_TRANSFORM, 2); assert(pred_bits >= 2);