From f2d6dc1eefdae4056de4ae0dfa513f2857d91693 Mon Sep 17 00:00:00 2001 From: Vincent Rabaud Date: Fri, 19 Jul 2024 15:27:49 +0200 Subject: [PATCH] Increase the transform bits if possible. This brings minor size improvements because repetitive values in the transform images are easily explainable through LZ77. Still, it makes an upcoming pull request a bit more stable. This is a rollforward of 7ec51c591608333fd5e6fdddbee16e1d65bef3db ee26766a89a149afe5f73fdcb8f2493ec808f2b7 Change-Id: I254ab3ccd5053344f89099280e8d994ecd55aee0 --- src/enc/predictor_enc.c | 76 +++++++++++++++++++++++++++++++++++++++-- src/enc/vp8l_enc.c | 45 ++++++++++++------------ src/enc/vp8li_enc.h | 4 +-- 3 files changed, 97 insertions(+), 28 deletions(-) diff --git a/src/enc/predictor_enc.c b/src/enc/predictor_enc.c index 9cfb70ec0..3a8da8fa7 100644 --- a/src/enc/predictor_enc.c +++ b/src/enc/predictor_enc.c @@ -14,6 +14,8 @@ // Urvang Joshi (urvang@google.com) // Vincent Rabaud (vrabaud@google.com) +#include + #include "src/dsp/lossless.h" #include "src/dsp/lossless_common.h" #include "src/enc/vp8i_enc.h" @@ -469,6 +471,71 @@ static void CopyImageWithPrediction(int width, int height, int bits, } } +// Checks whether 'image' can be subsampled by finding the biggest power of 2 +// squares (defined by 'best_bits') of uniform value it is made out of. +static void OptimizeSampling(uint32_t* const image, int full_width, + int full_height, int bits, int* best_bits_out) { + int width = VP8LSubSampleSize(full_width, bits); + int height = VP8LSubSampleSize(full_height, bits); + int old_width, x, y, square_size; + int best_bits = bits; + *best_bits_out = bits; + // Check rows first. + while (best_bits < MAX_TRANSFORM_BITS) { + const int new_square_size = 1 << (best_bits + 1 - bits); + int is_good = 1; + square_size = 1 << (best_bits - bits); + for (y = 0; y + square_size < height; y += new_square_size) { + // Check the first lines of consecutive line groups. + if (memcmp(&image[y * width], &image[(y + square_size) * width], + width * sizeof(*image)) != 0) { + is_good = 0; + break; + } + } + if (is_good) { + ++best_bits; + } else { + break; + } + } + if (best_bits == bits) return; + + // Check columns. + while (best_bits > bits) { + int is_good = 1; + square_size = 1 << (best_bits - bits); + for (y = 0; is_good && y < height; ++y) { + for (x = 0; is_good && x < width; x += square_size) { + int i; + for (i = x + 1; i < GetMin(x + square_size, width); ++i) { + if (image[y * width + i] != image[y * width + x]) { + is_good = 0; + break; + } + } + } + } + if (is_good) { + break; + } + --best_bits; + } + if (best_bits == bits) return; + + // Subsample the image. + old_width = width; + square_size = 1 << (best_bits - bits); + width = VP8LSubSampleSize(full_width, best_bits); + height = VP8LSubSampleSize(full_height, best_bits); + for (y = 0; y < height; ++y) { + for (x = 0; x < width; ++x) { + image[y * width + x] = image[square_size * (y * old_width + x)]; + } + } + *best_bits_out = best_bits; +} + // Finds the best predictor for each tile, and converts the image to residuals // with respect to predictions. If near_lossless_quality < 100, applies // near lossless processing, shaving off more bits of residuals for lower @@ -478,7 +545,7 @@ int VP8LResidualImage(int width, int height, int bits, int low_effort, uint32_t* const image, int near_lossless_quality, int exact, int used_subtract_green, const WebPPicture* const pic, int percent_range, - int* const percent) { + int* const percent, int* const best_bits) { const int tiles_per_row = VP8LSubSampleSize(width, bits); const int tiles_per_col = VP8LSubSampleSize(height, bits); int percent_start = *percent; @@ -488,6 +555,7 @@ int VP8LResidualImage(int width, int height, int bits, int low_effort, for (i = 0; i < tiles_per_row * tiles_per_col; ++i) { image[i] = ARGB_BLACK | (kPredLowEffort << 8); } + *best_bits = bits; } else { int tile_y; uint32_t histo[HISTO_SIZE] = { 0 }; @@ -506,9 +574,10 @@ int VP8LResidualImage(int width, int height, int bits, int low_effort, return 0; } } + OptimizeSampling(image, width, height, bits, best_bits); } - CopyImageWithPrediction(width, height, bits, image, argb_scratch, argb, + CopyImageWithPrediction(width, height, *best_bits, image, argb_scratch, argb, low_effort, max_quantization, exact, used_subtract_green); return WebPReportProgress(pic, percent_start + percent_range, percent); @@ -727,7 +796,7 @@ static void CopyTileWithColorTransform(int xsize, int ysize, int VP8LColorSpaceTransform(int width, int height, int bits, int quality, uint32_t* const argb, uint32_t* image, const WebPPicture* const pic, int percent_range, - int* const percent) { + int* const percent, int* const best_bits) { const int max_tile_size = 1 << bits; const int tile_xsize = VP8LSubSampleSize(width, bits); const int tile_ysize = VP8LSubSampleSize(height, bits); @@ -787,5 +856,6 @@ int VP8LColorSpaceTransform(int width, int height, int bits, int quality, return 0; } } + OptimizeSampling(image, width, height, bits, best_bits); return 1; } diff --git a/src/enc/vp8l_enc.c b/src/enc/vp8l_enc.c index 6e12d0573..ae28a837b 100644 --- a/src/enc/vp8l_enc.c +++ b/src/enc/vp8l_enc.c @@ -1071,26 +1071,27 @@ static int ApplyPredictFilter(VP8LEncoder* const enc, int width, int height, int quality, int low_effort, int used_subtract_green, VP8LBitWriter* const bw, int percent_range, int* const percent) { - const int pred_bits = enc->predictor_transform_bits_; - const int transform_width = VP8LSubSampleSize(width, pred_bits); - const int transform_height = VP8LSubSampleSize(height, pred_bits); + const int min_bits = enc->predictor_transform_bits_; + int best_bits; // we disable near-lossless quantization if palette is used. const int near_lossless_strength = enc->use_palette_ ? 100 : enc->config_->near_lossless; - if (!VP8LResidualImage( - width, height, pred_bits, low_effort, enc->argb_, enc->argb_scratch_, - enc->transform_data_, near_lossless_strength, enc->config_->exact, - used_subtract_green, enc->pic_, percent_range / 2, percent)) { + if (!VP8LResidualImage(width, height, min_bits, low_effort, enc->argb_, + enc->argb_scratch_, enc->transform_data_, + near_lossless_strength, enc->config_->exact, + used_subtract_green, enc->pic_, percent_range / 2, + percent, &best_bits)) { return 0; } VP8LPutBits(bw, TRANSFORM_PRESENT, 1); VP8LPutBits(bw, PREDICTOR_TRANSFORM, 2); - assert(pred_bits >= MIN_TRANSFORM_BITS && pred_bits <= MAX_TRANSFORM_BITS); - VP8LPutBits(bw, pred_bits - MIN_TRANSFORM_BITS, NUM_TRANSFORM_BITS); + assert(best_bits >= MIN_TRANSFORM_BITS && best_bits <= MAX_TRANSFORM_BITS); + VP8LPutBits(bw, best_bits - MIN_TRANSFORM_BITS, NUM_TRANSFORM_BITS); + enc->predictor_transform_bits_ = best_bits; return EncodeImageNoHuffman( - bw, enc->transform_data_, (VP8LHashChain*)&enc->hash_chain_, - (VP8LBackwardRefs*)&enc->refs_[0], transform_width, transform_height, + bw, enc->transform_data_, &enc->hash_chain_, &enc->refs_[0], + VP8LSubSampleSize(width, best_bits), VP8LSubSampleSize(height, best_bits), quality, low_effort, enc->pic_, percent_range - percent_range / 2, percent); } @@ -1099,24 +1100,22 @@ static int ApplyCrossColorFilter(VP8LEncoder* const enc, int width, int height, int quality, int low_effort, VP8LBitWriter* const bw, int percent_range, int* const percent) { - const int ccolor_transform_bits = enc->cross_color_transform_bits_; - const int transform_width = VP8LSubSampleSize(width, ccolor_transform_bits); - const int transform_height = VP8LSubSampleSize(height, ccolor_transform_bits); + const int min_bits = enc->cross_color_transform_bits_; + int best_bits; - if (!VP8LColorSpaceTransform(width, height, ccolor_transform_bits, quality, - enc->argb_, enc->transform_data_, enc->pic_, - percent_range / 2, percent)) { + if (!VP8LColorSpaceTransform(width, height, min_bits, quality, enc->argb_, + enc->transform_data_, enc->pic_, + percent_range / 2, percent, &best_bits)) { return 0; } VP8LPutBits(bw, TRANSFORM_PRESENT, 1); VP8LPutBits(bw, CROSS_COLOR_TRANSFORM, 2); - assert(ccolor_transform_bits >= MIN_TRANSFORM_BITS && - ccolor_transform_bits <= MAX_TRANSFORM_BITS); - VP8LPutBits(bw, ccolor_transform_bits - MIN_TRANSFORM_BITS, - NUM_TRANSFORM_BITS); + assert(best_bits >= MIN_TRANSFORM_BITS && best_bits <= MAX_TRANSFORM_BITS); + VP8LPutBits(bw, best_bits - MIN_TRANSFORM_BITS, NUM_TRANSFORM_BITS); + enc->cross_color_transform_bits_ = best_bits; return EncodeImageNoHuffman( - bw, enc->transform_data_, (VP8LHashChain*)&enc->hash_chain_, - (VP8LBackwardRefs*)&enc->refs_[0], transform_width, transform_height, + bw, enc->transform_data_, &enc->hash_chain_, &enc->refs_[0], + VP8LSubSampleSize(width, best_bits), VP8LSubSampleSize(height, best_bits), quality, low_effort, enc->pic_, percent_range - percent_range / 2, percent); } diff --git a/src/enc/vp8li_enc.h b/src/enc/vp8li_enc.h index f137dcf5f..75877e843 100644 --- a/src/enc/vp8li_enc.h +++ b/src/enc/vp8li_enc.h @@ -110,12 +110,12 @@ int VP8LResidualImage(int width, int height, int bits, int low_effort, uint32_t* const image, int near_lossless_quality, int exact, int used_subtract_green, const WebPPicture* const pic, int percent_range, - int* const percent); + int* const percent, int* const best_bits); int VP8LColorSpaceTransform(int width, int height, int bits, int quality, uint32_t* const argb, uint32_t* image, const WebPPicture* const pic, int percent_range, - int* const percent); + int* const percent, int* const best_bits); //------------------------------------------------------------------------------