Refactor predictor finding

This is useful for a forward change that will improve compression.
It splits the residual computation and the best predictor
selection.

The only downside is that more memory is allocated: we had 2
histograms before, we now have 14, but this is necessary for the
later change. Still, this is nothing compared to what is done
later in the pipeline in HistogramSetTotalSize where the number of
histograms created is the number of pixels in the subsampled image.

Change-Id: If03501a26f00462dd1809daa6e9314abd180945d
This commit is contained in:
Vincent Rabaud 2024-09-17 09:38:56 +02:00
parent a582b53b74
commit 367ca938f1

View File

@ -14,17 +14,24 @@
// Urvang Joshi (urvang@google.com)
// Vincent Rabaud (vrabaud@google.com)
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include "src/dsp/lossless.h"
#include "src/dsp/lossless_common.h"
#include "src/enc/vp8i_enc.h"
#include "src/enc/vp8li_enc.h"
#include "src/utils/utils.h"
#include "src/webp/encode.h"
#include "src/webp/format_constants.h"
#include "src/webp/types.h"
#define HISTO_SIZE (4 * 256)
static const int64_t kSpatialPredictorBias = 15ll << LOG_2_PRECISION_BITS;
static const int kPredLowEffort = 11;
static const uint32_t kMaskAlpha = 0xff000000;
static const int kNumPredModes = 14;
// Mostly used to reduce code size + readability
static WEBP_INLINE int GetMin(int a, int b) { return (a > b) ? b : a; }
@ -305,17 +312,61 @@ static WEBP_INLINE void GetResidual(
}
}
// Returns best predictor and updates the accumulated histogram.
// Accessors to residual histograms.
static WEBP_INLINE uint32_t* GetHistoArgb(uint32_t* const all_histos,
int mode) {
return &all_histos[mode * HISTO_SIZE];
}
static WEBP_INLINE const uint32_t* GetHistoArgbConst(
const uint32_t* const all_histos, int mode) {
return &all_histos[mode * HISTO_SIZE];
}
// Find and store the best predictor.
static void GetBestPredictorForTile(const uint32_t* const all_argb, int tile_x,
int tile_y, int tiles_per_row,
uint32_t* accumulated_argb,
uint32_t* const modes) {
// Prediction modes of the left and above neighbor tiles.
const int left_mode =
(tile_x > 0) ? (modes[tile_y * tiles_per_row + tile_x - 1] >> 8) & 0xff
: 0xff;
const int above_mode =
(tile_y > 0) ? (modes[(tile_y - 1) * tiles_per_row + tile_x] >> 8) & 0xff
: 0xff;
int mode;
int64_t best_diff = WEBP_INT64_MAX;
uint32_t best_mode = 0;
const uint32_t* best_histo = GetHistoArgbConst(all_argb, best_mode);
for (mode = 0; mode < kNumPredModes; ++mode) {
const uint32_t* const histo_argb = GetHistoArgbConst(all_argb, mode);
const int64_t cur_diff = PredictionCostSpatialHistogram(
accumulated_argb, histo_argb, mode, left_mode, above_mode);
if (cur_diff < best_diff) {
best_histo = histo_argb;
best_diff = cur_diff;
best_mode = mode;
}
}
// Update the accumulated histogram.
VP8LAddVectorEq(best_histo, accumulated_argb, HISTO_SIZE);
modes[tile_y * tiles_per_row + tile_x] = ARGB_BLACK | (best_mode << 8);
}
// Computes the residuals for the different predictors.
// If max_quantization > 1, assumes that near lossless processing will be
// applied, quantizing residuals to multiples of quantization levels up to
// max_quantization (the actual quantization level depends on smoothness near
// the given pixel).
static int GetBestPredictorForTile(
int width, int height, int tile_x, int tile_y, int bits,
uint32_t accumulated[HISTO_SIZE], uint32_t* const argb_scratch,
const uint32_t* const argb, int max_quantization, int exact,
int used_subtract_green, const uint32_t* const modes) {
const int kNumPredModes = 14;
static void ComputeResidualsForTile(int width, int height, int tile_x,
int tile_y, int bits,
uint32_t* const all_argb,
uint32_t* const argb_scratch,
const uint32_t* const argb,
int max_quantization, int exact,
int used_subtract_green) {
const int start_x = tile_x << bits;
const int start_y = tile_y << bits;
const int tile_size = 1 << bits;
@ -329,34 +380,19 @@ static int GetBestPredictorForTile(
#if (WEBP_NEAR_LOSSLESS == 1)
const int context_width = max_x + have_left + (max_x < width - start_x);
#endif
const int tiles_per_row = VP8LSubSampleSize(width, bits);
// Prediction modes of the left and above neighbor tiles.
const int left_mode = (tile_x > 0) ?
(modes[tile_y * tiles_per_row + tile_x - 1] >> 8) & 0xff : 0xff;
const int above_mode = (tile_y > 0) ?
(modes[(tile_y - 1) * tiles_per_row + tile_x] >> 8) & 0xff : 0xff;
// The width of upper_row and current_row is one pixel larger than image width
// to allow the top right pixel to point to the leftmost pixel of the next row
// when at the right edge.
uint32_t* upper_row = argb_scratch;
uint32_t* current_row = upper_row + width + 1;
uint8_t* const max_diffs = (uint8_t*)(current_row + width + 1);
int64_t best_diff = WEBP_INT64_MAX;
int best_mode = 0;
int mode;
uint32_t histo_stack_1[HISTO_SIZE];
uint32_t histo_stack_2[HISTO_SIZE];
// Need pointers to be able to swap arrays.
uint32_t* histo_argb = histo_stack_1;
uint32_t* best_histo = histo_stack_2;
uint32_t residuals[1 << MAX_TRANSFORM_BITS];
assert(bits <= MAX_TRANSFORM_BITS);
assert(max_x <= (1 << MAX_TRANSFORM_BITS));
for (mode = 0; mode < kNumPredModes; ++mode) {
int64_t cur_diff;
int relative_y;
memset(histo_argb, 0, sizeof(histo_stack_1));
uint32_t* const histo_argb = GetHistoArgb(all_argb, mode);
if (start_y > 0) {
// Read the row above the tile which will become the first upper_row.
// Include a pixel to the left if it exists; include a pixel to the right
@ -393,20 +429,7 @@ static int GetBestPredictorForTile(
UpdateHisto(histo_argb, residuals[relative_x]);
}
}
cur_diff = PredictionCostSpatialHistogram(accumulated, histo_argb, mode,
left_mode, above_mode);
if (cur_diff < best_diff) {
uint32_t* tmp = histo_argb;
histo_argb = best_histo;
best_histo = tmp;
best_diff = cur_diff;
best_mode = mode;
}
}
VP8LAddVectorEq(best_histo, accumulated, HISTO_SIZE);
return best_mode;
}
// Converts pixels of the image to residuals with respect to predictions.
@ -536,6 +559,59 @@ static void OptimizeSampling(uint32_t* const image, int full_width,
*best_bits_out = best_bits;
}
// Computes the best predictor image.
// Finds the best predictors per tile. Once done, finds the best predictor image
// sampling.
// best_bits is set to 0 in case of error.
static void GetBestPredictorsAndSampling(
int width, int height, const int bits, uint32_t* const argb_scratch,
const uint32_t* const argb, int max_quantization, int exact,
int used_subtract_green, const WebPPicture* const pic, int percent_range,
int* const percent, uint32_t* const all_modes, int* best_bits) {
const int tiles_per_row = VP8LSubSampleSize(width, bits);
const int tiles_per_col = VP8LSubSampleSize(height, bits);
// Compute the needed memory size for residual histograms and accumulated
// residual histograms.
const int num_argb = kNumPredModes * HISTO_SIZE;
const int num_accumulated_argb = HISTO_SIZE;
uint32_t* const raw_data = (uint32_t*)WebPSafeCalloc(
num_argb + num_accumulated_argb, sizeof(*raw_data));
uint32_t* const all_argb = raw_data;
uint32_t* const all_accumulated_argb = all_argb + num_argb;
const int percent_start = *percent;
int tile_x = 0, tile_y = 0;
*best_bits = 0;
if (raw_data == NULL) return;
while (tile_y < tiles_per_col) {
ComputeResidualsForTile(width, height, tile_x, tile_y, bits, all_argb,
argb_scratch, argb, max_quantization, exact,
used_subtract_green);
GetBestPredictorForTile(all_argb, tile_x, tile_y, tiles_per_row,
all_accumulated_argb, all_modes);
// Reset the residuals.
memset(all_argb, 0, HISTO_SIZE * kNumPredModes * sizeof(*all_argb));
if (tile_x == (tiles_per_row - 1)) {
tile_x = 0;
++tile_y;
} else {
++tile_x;
}
if (tile_x == 0 &&
!WebPReportProgress(
pic, percent_start + percent_range * tile_y / tiles_per_col,
percent)) {
WebPSafeFree(raw_data);
return;
}
}
WebPSafeFree(raw_data);
OptimizeSampling(all_modes, width, height, bits, 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
@ -557,24 +633,10 @@ int VP8LResidualImage(int width, int height, int bits, int low_effort,
}
*best_bits = bits;
} else {
int tile_y;
uint32_t histo[HISTO_SIZE] = { 0 };
for (tile_y = 0; tile_y < tiles_per_col; ++tile_y) {
int tile_x;
for (tile_x = 0; tile_x < tiles_per_row; ++tile_x) {
const int pred = GetBestPredictorForTile(
width, height, tile_x, tile_y, bits, histo, argb_scratch, argb,
max_quantization, exact, used_subtract_green, image);
image[tile_y * tiles_per_row + tile_x] = ARGB_BLACK | (pred << 8);
}
if (!WebPReportProgress(
pic, percent_start + percent_range * tile_y / tiles_per_col,
percent)) {
return 0;
}
}
OptimizeSampling(image, width, height, bits, best_bits);
GetBestPredictorsAndSampling(width, height, bits, argb_scratch, argb,
max_quantization, exact, used_subtract_green,
pic, percent_range, percent, image, best_bits);
if (*best_bits == 0) return 0;
}
CopyImageWithPrediction(width, height, *best_bits, image, argb_scratch, argb,