Add progress hook granularity in lossless

A WebPPicture instance is necessary to call WebPReportProgress() which
sets WebPPicture::error_code so as well use WebPEncodingSetError() to
record errors too, instead of functions returning a WebPEncodingError.
However there must be one WebPPicture instance per thread, with error
codes merged at sync time. A mutex could simplify that but it is not
the objective of this change.

https://groups.google.com/a/webmproject.org/g/webp-discuss/c/yOiP8APubgc/m/vCTvxl6ODgAJ

Change-Id: Ia1a8f9d1199202e1c88484ce719b0180a80447ce
This commit is contained in:
Yannis Guyon 2022-02-02 14:39:21 +01:00
parent 26139c7398
commit ec178f2c7f
9 changed files with 414 additions and 284 deletions

View File

@ -86,7 +86,7 @@ static int EncodeLossless(const uint8_t* const data, int width, int height,
// a decoder bug related to alpha with color cache. // a decoder bug related to alpha with color cache.
// See: https://code.google.com/p/webp/issues/detail?id=239 // See: https://code.google.com/p/webp/issues/detail?id=239
// Need to re-enable this later. // Need to re-enable this later.
ok = (VP8LEncodeStream(&config, &picture, bw, 0 /*use_cache*/) == VP8_ENC_OK); ok = VP8LEncodeStream(&config, &picture, bw, /*use_cache=*/0);
WebPPictureFree(&picture); WebPPictureFree(&picture);
ok = ok && !bw->error_; ok = ok && !bw->error_;
if (!ok) { if (!ok) {

View File

@ -10,6 +10,8 @@
// Author: Jyrki Alakuijala (jyrki@google.com) // Author: Jyrki Alakuijala (jyrki@google.com)
// //
#include "src/enc/backward_references_enc.h"
#include <assert.h> #include <assert.h>
#include <float.h> #include <float.h>
#include <math.h> #include <math.h>
@ -17,10 +19,11 @@
#include "src/dsp/dsp.h" #include "src/dsp/dsp.h"
#include "src/dsp/lossless.h" #include "src/dsp/lossless.h"
#include "src/dsp/lossless_common.h" #include "src/dsp/lossless_common.h"
#include "src/enc/backward_references_enc.h"
#include "src/enc/histogram_enc.h" #include "src/enc/histogram_enc.h"
#include "src/enc/vp8i_enc.h"
#include "src/utils/color_cache_utils.h" #include "src/utils/color_cache_utils.h"
#include "src/utils/utils.h" #include "src/utils/utils.h"
#include "src/webp/encode.h"
#define MIN_BLOCK_SIZE 256 // minimum block size for backward references #define MIN_BLOCK_SIZE 256 // minimum block size for backward references
@ -255,10 +258,13 @@ static WEBP_INLINE int MaxFindCopyLength(int len) {
int VP8LHashChainFill(VP8LHashChain* const p, int quality, int VP8LHashChainFill(VP8LHashChain* const p, int quality,
const uint32_t* const argb, int xsize, int ysize, const uint32_t* const argb, int xsize, int ysize,
int low_effort) { int low_effort, const WebPPicture* const pic,
int percent_range, int* const percent) {
const int size = xsize * ysize; const int size = xsize * ysize;
const int iter_max = GetMaxItersForQuality(quality); const int iter_max = GetMaxItersForQuality(quality);
const uint32_t window_size = GetWindowSizeForHashChain(quality, xsize); const uint32_t window_size = GetWindowSizeForHashChain(quality, xsize);
int remaining_percent = percent_range;
int percent_start = *percent;
int pos; int pos;
int argb_comp; int argb_comp;
uint32_t base_position; uint32_t base_position;
@ -276,7 +282,13 @@ int VP8LHashChainFill(VP8LHashChain* const p, int quality,
hash_to_first_index = hash_to_first_index =
(int32_t*)WebPSafeMalloc(HASH_SIZE, sizeof(*hash_to_first_index)); (int32_t*)WebPSafeMalloc(HASH_SIZE, sizeof(*hash_to_first_index));
if (hash_to_first_index == NULL) return 0; if (hash_to_first_index == NULL) {
WebPEncodingSetError(pic, VP8_ENC_ERROR_OUT_OF_MEMORY);
return 0;
}
percent_range = remaining_percent / 2;
remaining_percent -= percent_range;
// Set the int32_t array to -1. // Set the int32_t array to -1.
memset(hash_to_first_index, 0xff, HASH_SIZE * sizeof(*hash_to_first_index)); memset(hash_to_first_index, 0xff, HASH_SIZE * sizeof(*hash_to_first_index));
@ -323,12 +335,22 @@ int VP8LHashChainFill(VP8LHashChain* const p, int quality,
hash_to_first_index[hash_code] = pos++; hash_to_first_index[hash_code] = pos++;
argb_comp = argb_comp_next; argb_comp = argb_comp_next;
} }
if (!WebPReportProgress(
pic, percent_start + percent_range * pos / (size - 2), percent)) {
WebPSafeFree(hash_to_first_index);
return 0;
}
} }
// Process the penultimate pixel. // Process the penultimate pixel.
chain[pos] = hash_to_first_index[GetPixPairHash64(argb + pos)]; chain[pos] = hash_to_first_index[GetPixPairHash64(argb + pos)];
WebPSafeFree(hash_to_first_index); WebPSafeFree(hash_to_first_index);
percent_start += percent_range;
if (!WebPReportProgress(pic, percent_start, percent)) return 0;
percent_range = remaining_percent;
// Find the best match interval at each pixel, defined by an offset to the // Find the best match interval at each pixel, defined by an offset to the
// pixel and a length. The right-most pixel cannot match anything to the right // pixel and a length. The right-most pixel cannot match anything to the right
// (hence a best length of 0) and the left-most pixel nothing to the left // (hence a best length of 0) and the left-most pixel nothing to the left
@ -417,8 +439,17 @@ int VP8LHashChainFill(VP8LHashChain* const p, int quality,
max_base_position = base_position; max_base_position = base_position;
} }
} }
if (!WebPReportProgress(pic,
percent_start + percent_range *
(size - 2 - base_position) /
(size - 2),
percent)) {
return 0;
} }
return 1; }
return WebPReportProgress(pic, percent_start + percent_range, percent);
} }
static WEBP_INLINE void AddSingleLiteral(uint32_t pixel, int use_color_cache, static WEBP_INLINE void AddSingleLiteral(uint32_t pixel, int use_color_cache,
@ -1006,25 +1037,31 @@ Error:
return status; return status;
} }
WebPEncodingError VP8LGetBackwardReferences( int VP8LGetBackwardReferences(
int width, int height, const uint32_t* const argb, int quality, int width, int height, const uint32_t* const argb, int quality,
int low_effort, int lz77_types_to_try, int cache_bits_max, int do_no_cache, int low_effort, int lz77_types_to_try, int cache_bits_max, int do_no_cache,
const VP8LHashChain* const hash_chain, VP8LBackwardRefs* const refs, const VP8LHashChain* const hash_chain, VP8LBackwardRefs* const refs,
int* const cache_bits_best) { int* const cache_bits_best, const WebPPicture* const pic, int percent_range,
int* const percent) {
if (low_effort) { if (low_effort) {
VP8LBackwardRefs* refs_best; VP8LBackwardRefs* refs_best;
*cache_bits_best = cache_bits_max; *cache_bits_best = cache_bits_max;
refs_best = GetBackwardReferencesLowEffort( refs_best = GetBackwardReferencesLowEffort(
width, height, argb, cache_bits_best, hash_chain, refs); width, height, argb, cache_bits_best, hash_chain, refs);
if (refs_best == NULL) return VP8_ENC_ERROR_OUT_OF_MEMORY; if (refs_best == NULL) {
WebPEncodingSetError(pic, VP8_ENC_ERROR_OUT_OF_MEMORY);
return 0;
}
// Set it in first position. // Set it in first position.
BackwardRefsSwap(refs_best, &refs[0]); BackwardRefsSwap(refs_best, &refs[0]);
} else { } else {
if (!GetBackwardReferences(width, height, argb, quality, lz77_types_to_try, if (!GetBackwardReferences(width, height, argb, quality, lz77_types_to_try,
cache_bits_max, do_no_cache, hash_chain, refs, cache_bits_max, do_no_cache, hash_chain, refs,
cache_bits_best)) { cache_bits_best)) {
return VP8_ENC_ERROR_OUT_OF_MEMORY; WebPEncodingSetError(pic, VP8_ENC_ERROR_OUT_OF_MEMORY);
return 0;
} }
} }
return VP8_ENC_OK;
return WebPReportProgress(pic, *percent + percent_range, percent);
} }

View File

@ -134,10 +134,11 @@ struct VP8LHashChain {
// Must be called first, to set size. // Must be called first, to set size.
int VP8LHashChainInit(VP8LHashChain* const p, int size); int VP8LHashChainInit(VP8LHashChain* const p, int size);
// Pre-compute the best matches for argb. // Pre-compute the best matches for argb. pic and percent are for progress.
int VP8LHashChainFill(VP8LHashChain* const p, int quality, int VP8LHashChainFill(VP8LHashChain* const p, int quality,
const uint32_t* const argb, int xsize, int ysize, const uint32_t* const argb, int xsize, int ysize,
int low_effort); int low_effort, const WebPPicture* const pic,
int percent_range, int* const percent);
void VP8LHashChainClear(VP8LHashChain* const p); // release memory void VP8LHashChainClear(VP8LHashChain* const p); // release memory
static WEBP_INLINE int VP8LHashChainFindOffset(const VP8LHashChain* const p, static WEBP_INLINE int VP8LHashChainFindOffset(const VP8LHashChain* const p,
@ -227,11 +228,14 @@ enum VP8LLZ77Type {
// VP8LBackwardRefs is put in the first element, the best value with no-cache in // VP8LBackwardRefs is put in the first element, the best value with no-cache in
// the second element. // the second element.
// In both cases, the last element is used as temporary internally. // In both cases, the last element is used as temporary internally.
WebPEncodingError VP8LGetBackwardReferences( // pic and percent are for progress.
// Returns false in case of error (stored in pic->error_code).
int VP8LGetBackwardReferences(
int width, int height, const uint32_t* const argb, int quality, int width, int height, const uint32_t* const argb, int quality,
int low_effort, int lz77_types_to_try, int cache_bits_max, int do_no_cache, int low_effort, int lz77_types_to_try, int cache_bits_max, int do_no_cache,
const VP8LHashChain* const hash_chain, VP8LBackwardRefs* const refs, const VP8LHashChain* const hash_chain, VP8LBackwardRefs* const refs,
int* const cache_bits_best); int* const cache_bits_best, const WebPPicture* const pic, int percent_range,
int* const percent);
#ifdef __cplusplus #ifdef __cplusplus
} }

View File

@ -15,10 +15,11 @@
#include <math.h> #include <math.h>
#include "src/enc/backward_references_enc.h"
#include "src/enc/histogram_enc.h"
#include "src/dsp/lossless.h" #include "src/dsp/lossless.h"
#include "src/dsp/lossless_common.h" #include "src/dsp/lossless_common.h"
#include "src/enc/backward_references_enc.h"
#include "src/enc/histogram_enc.h"
#include "src/enc/vp8i_enc.h"
#include "src/utils/utils.h" #include "src/utils/utils.h"
#define MAX_BIT_COST 1.e38 #define MAX_BIT_COST 1.e38
@ -1169,13 +1170,13 @@ static void RemoveEmptyHistograms(VP8LHistogramSet* const image_histo) {
} }
int VP8LGetHistoImageSymbols(int xsize, int ysize, int VP8LGetHistoImageSymbols(int xsize, int ysize,
const VP8LBackwardRefs* const refs, const VP8LBackwardRefs* const refs, int quality,
int quality, int low_effort, int low_effort, int histogram_bits, int cache_bits,
int histogram_bits, int cache_bits,
VP8LHistogramSet* const image_histo, VP8LHistogramSet* const image_histo,
VP8LHistogram* const tmp_histo, VP8LHistogram* const tmp_histo,
uint16_t* const histogram_symbols) { uint16_t* const histogram_symbols,
int ok = 0; const WebPPicture* const pic, int percent_range,
int* const percent) {
const int histo_xsize = const int histo_xsize =
histogram_bits ? VP8LSubSampleSize(xsize, histogram_bits) : 1; histogram_bits ? VP8LSubSampleSize(xsize, histogram_bits) : 1;
const int histo_ysize = const int histo_ysize =
@ -1192,7 +1193,10 @@ int VP8LGetHistoImageSymbols(int xsize, int ysize,
WebPSafeMalloc(2 * image_histo_raw_size, sizeof(map_tmp)); WebPSafeMalloc(2 * image_histo_raw_size, sizeof(map_tmp));
uint16_t* const cluster_mappings = map_tmp + image_histo_raw_size; uint16_t* const cluster_mappings = map_tmp + image_histo_raw_size;
int num_used = image_histo_raw_size; int num_used = image_histo_raw_size;
if (orig_histo == NULL || map_tmp == NULL) goto Error; if (orig_histo == NULL || map_tmp == NULL) {
WebPEncodingSetError(pic, VP8_ENC_ERROR_OUT_OF_MEMORY);
goto Error;
}
// Construct the histograms from backward references. // Construct the histograms from backward references.
HistogramBuild(xsize, histogram_bits, refs, orig_histo); HistogramBuild(xsize, histogram_bits, refs, orig_histo);
@ -1212,10 +1216,9 @@ int VP8LGetHistoImageSymbols(int xsize, int ysize,
HistogramAnalyzeEntropyBin(image_histo, bin_map, low_effort); HistogramAnalyzeEntropyBin(image_histo, bin_map, low_effort);
// Collapse histograms with similar entropy. // Collapse histograms with similar entropy.
HistogramCombineEntropyBin(image_histo, &num_used, histogram_symbols, HistogramCombineEntropyBin(
cluster_mappings, tmp_histo, bin_map, image_histo, &num_used, histogram_symbols, cluster_mappings, tmp_histo,
entropy_combine_num_bins, combine_cost_factor, bin_map, entropy_combine_num_bins, combine_cost_factor, low_effort);
low_effort);
OptimizeHistogramSymbols(image_histo, cluster_mappings, num_clusters, OptimizeHistogramSymbols(image_histo, cluster_mappings, num_clusters,
map_tmp, histogram_symbols); map_tmp, histogram_symbols);
} }
@ -1229,11 +1232,13 @@ int VP8LGetHistoImageSymbols(int xsize, int ysize,
int do_greedy; int do_greedy;
if (!HistogramCombineStochastic(image_histo, &num_used, threshold_size, if (!HistogramCombineStochastic(image_histo, &num_used, threshold_size,
&do_greedy)) { &do_greedy)) {
WebPEncodingSetError(pic, VP8_ENC_ERROR_OUT_OF_MEMORY);
goto Error; goto Error;
} }
if (do_greedy) { if (do_greedy) {
RemoveEmptyHistograms(image_histo); RemoveEmptyHistograms(image_histo);
if (!HistogramCombineGreedy(image_histo, &num_used)) { if (!HistogramCombineGreedy(image_histo, &num_used)) {
WebPEncodingSetError(pic, VP8_ENC_ERROR_OUT_OF_MEMORY);
goto Error; goto Error;
} }
} }
@ -1243,10 +1248,12 @@ int VP8LGetHistoImageSymbols(int xsize, int ysize,
RemoveEmptyHistograms(image_histo); RemoveEmptyHistograms(image_histo);
HistogramRemap(orig_histo, image_histo, histogram_symbols); HistogramRemap(orig_histo, image_histo, histogram_symbols);
ok = 1; if (!WebPReportProgress(pic, *percent + percent_range, percent)) {
goto Error;
}
Error: Error:
VP8LFreeHistogramSet(orig_histo); VP8LFreeHistogramSet(orig_histo);
WebPSafeFree(map_tmp); WebPSafeFree(map_tmp);
return ok; return (pic->error_code == VP8_ENC_OK);
} }

View File

@ -105,14 +105,16 @@ static WEBP_INLINE int VP8LHistogramNumCodes(int palette_code_bits) {
((palette_code_bits > 0) ? (1 << palette_code_bits) : 0); ((palette_code_bits > 0) ? (1 << palette_code_bits) : 0);
} }
// Builds the histogram image. // Builds the histogram image. pic and percent are for progress.
// Returns false in case of error (stored in pic->error_code).
int VP8LGetHistoImageSymbols(int xsize, int ysize, int VP8LGetHistoImageSymbols(int xsize, int ysize,
const VP8LBackwardRefs* const refs, const VP8LBackwardRefs* const refs, int quality,
int quality, int low_effort, int low_effort, int histogram_bits, int cache_bits,
int histogram_bits, int cache_bits,
VP8LHistogramSet* const image_histo, VP8LHistogramSet* const image_histo,
VP8LHistogram* const tmp_histo, VP8LHistogram* const tmp_histo,
uint16_t* const histogram_symbols); uint16_t* const histogram_symbols,
const WebPPicture* const pic, int percent_range,
int* const percent);
// Returns the entropy for the symbols in the input array. // Returns the entropy for the symbols in the input array.
double VP8LBitsEntropy(const uint32_t* const array, int n); double VP8LBitsEntropy(const uint32_t* const array, int n);

View File

@ -16,6 +16,7 @@
#include "src/dsp/lossless.h" #include "src/dsp/lossless.h"
#include "src/dsp/lossless_common.h" #include "src/dsp/lossless_common.h"
#include "src/enc/vp8i_enc.h"
#include "src/enc/vp8li_enc.h" #include "src/enc/vp8li_enc.h"
#define MAX_DIFF_COST (1e30f) #define MAX_DIFF_COST (1e30f)
@ -472,12 +473,15 @@ static void CopyImageWithPrediction(int width, int height,
// with respect to predictions. If near_lossless_quality < 100, applies // with respect to predictions. If near_lossless_quality < 100, applies
// near lossless processing, shaving off more bits of residuals for lower // near lossless processing, shaving off more bits of residuals for lower
// qualities. // qualities.
void VP8LResidualImage(int width, int height, int bits, int low_effort, int 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, int near_lossless_quality, uint32_t* const image, int near_lossless_quality,
int exact, int used_subtract_green) { int exact, int used_subtract_green,
const WebPPicture* const pic, int percent_range,
int* const percent) {
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);
int percent_start = *percent;
int tile_y; int tile_y;
int histo[4][256]; int histo[4][256];
const int max_quantization = 1 << VP8LNearLosslessBits(near_lossless_quality); const int max_quantization = 1 << VP8LNearLosslessBits(near_lossless_quality);
@ -491,17 +495,24 @@ void VP8LResidualImage(int width, int height, int bits, int low_effort,
for (tile_y = 0; tile_y < tiles_per_col; ++tile_y) { for (tile_y = 0; tile_y < tiles_per_col; ++tile_y) {
int tile_x; int tile_x;
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(
bits, histo, argb_scratch, argb, max_quantization, exact, width, height, tile_x, tile_y, bits, histo, argb_scratch, argb,
used_subtract_green, image); max_quantization, exact, used_subtract_green, image);
image[tile_y * tiles_per_row + tile_x] = ARGB_BLACK | (pred << 8); 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;
}
} }
} }
CopyImageWithPrediction(width, height, bits, image, argb_scratch, argb, CopyImageWithPrediction(width, height, bits, image, argb_scratch, argb,
low_effort, max_quantization, exact, low_effort, max_quantization, exact,
used_subtract_green); used_subtract_green);
return WebPReportProgress(pic, percent_start + percent_range, percent);
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -714,11 +725,14 @@ static void CopyTileWithColorTransform(int xsize, int ysize,
} }
} }
void VP8LColorSpaceTransform(int width, int height, int bits, int quality, int VP8LColorSpaceTransform(int width, int height, int bits, int quality,
uint32_t* const argb, uint32_t* image) { uint32_t* const argb, uint32_t* image,
const WebPPicture* const pic, int percent_range,
int* const percent) {
const int max_tile_size = 1 << bits; const int max_tile_size = 1 << bits;
const int tile_xsize = VP8LSubSampleSize(width, bits); const int tile_xsize = VP8LSubSampleSize(width, bits);
const int tile_ysize = VP8LSubSampleSize(height, bits); const int tile_ysize = VP8LSubSampleSize(height, bits);
int percent_start = *percent;
int accumulated_red_histo[256] = { 0 }; int accumulated_red_histo[256] = { 0 };
int accumulated_blue_histo[256] = { 0 }; int accumulated_blue_histo[256] = { 0 };
int tile_x, tile_y; int tile_x, tile_y;
@ -768,5 +782,11 @@ void VP8LColorSpaceTransform(int width, int height, int bits, int quality,
} }
} }
} }
if (!WebPReportProgress(
pic, percent_start + percent_range * tile_y / tile_ysize,
percent)) {
return 0;
} }
}
return 1;
} }

File diff suppressed because it is too large Load Diff

View File

@ -89,9 +89,10 @@ int VP8LEncodeImage(const WebPConfig* const config,
// Encodes the main image stream using the supplied bit writer. // Encodes the main image stream using the supplied bit writer.
// If 'use_cache' is false, disables the use of color cache. // If 'use_cache' is false, disables the use of color cache.
WebPEncodingError VP8LEncodeStream(const WebPConfig* const config, // Returns false in case of error (stored in picture->error_code).
const WebPPicture* const picture, int VP8LEncodeStream(const WebPConfig* const config,
VP8LBitWriter* const bw, int use_cache); const WebPPicture* const picture, VP8LBitWriter* const bw,
int use_cache);
#if (WEBP_NEAR_LOSSLESS == 1) #if (WEBP_NEAR_LOSSLESS == 1)
// in near_lossless.c // in near_lossless.c
@ -103,13 +104,18 @@ int VP8ApplyNearLossless(const WebPPicture* const picture, int quality,
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Image transforms in predictor.c. // Image transforms in predictor.c.
void VP8LResidualImage(int width, int height, int bits, int low_effort, // pic and percent are for progress.
// Returns false in case of error (stored in pic->error_code).
int 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, int near_lossless, int exact, uint32_t* const image, int near_lossless, int exact,
int used_subtract_green); int used_subtract_green, const WebPPicture* const pic,
int percent_range, int* const percent);
void VP8LColorSpaceTransform(int width, int height, int bits, int quality, int VP8LColorSpaceTransform(int width, int height, int bits, int quality,
uint32_t* const argb, uint32_t* image); uint32_t* const argb, uint32_t* image,
const WebPPicture* const pic, int percent_range,
int* const percent);
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------

View File

@ -441,7 +441,7 @@ WEBP_EXTERN int WebPPictureCrop(WebPPicture* picture,
// the original dimension will be lost). Picture 'dst' need not be initialized // the original dimension will be lost). Picture 'dst' need not be initialized
// with WebPPictureInit() if it is different from 'src', since its content will // with WebPPictureInit() if it is different from 'src', since its content will
// be overwritten. // be overwritten.
// Returns false in case of memory allocation error or invalid parameters. // Returns false in case of invalid parameters.
WEBP_EXTERN int WebPPictureView(const WebPPicture* src, WEBP_EXTERN int WebPPictureView(const WebPPicture* src,
int left, int top, int width, int height, int left, int top, int width, int height,
WebPPicture* dst); WebPPicture* dst);