add a -partition_limit option to limit the number of bits used by intra4x4

Although it degrades quality, this option is useful to avoid the 512k
limit for partition #0.
If not enough to reach the lower bound of 4bits per macroblock header,
one should also limit the number of segments used (down to -segments 1)

See the man file for extra details.

Change-Id: Ia59ffac13176c85b809ddd6340d37b54ee9487ea
This commit is contained in:
Pascal Massimino 2011-08-23 15:58:22 -07:00
parent cd12b4b0ac
commit 900286e091
8 changed files with 56 additions and 8 deletions

2
README
View File

@ -140,6 +140,8 @@ options:
-f <int> ............... filter strength (0=off..100) -f <int> ............... filter strength (0=off..100)
-sharpness <int> ....... filter sharpness (0:most .. 7:least sharp) -sharpness <int> ....... filter sharpness (0:most .. 7:least sharp)
-strong ................ use strong filter instead of simple. -strong ................ use strong filter instead of simple.
-partition_limit <int> . limit quality to fit the 512k limit on
the first partition (0=no degradation ... 100=full)
-alpha_comp <int> ...... set the transparency-compression -alpha_comp <int> ...... set the transparency-compression
-noalpha ............... discard any transparency information. -noalpha ............... discard any transparency information.
-pass <int> ............ analysis pass number (1..10) -pass <int> ............ analysis pass number (1..10)

View File

@ -676,6 +676,9 @@ static void HelpLong(void) {
printf(" -sharpness <int> ....... " printf(" -sharpness <int> ....... "
"filter sharpness (0:most .. 7:least sharp)\n"); "filter sharpness (0:most .. 7:least sharp)\n");
printf(" -strong ................ use strong filter instead of simple.\n"); printf(" -strong ................ use strong filter instead of simple.\n");
printf(" -partition_limit <int> . limit quality to fit the 512k limit on\n");
printf(" "
"the first partition (0=no degradation ... 100=full)\n");
printf(" -alpha_comp <int> ...... set the transparency-compression\n"); printf(" -alpha_comp <int> ...... set the transparency-compression\n");
printf(" -noalpha ............... discard any transparency information.\n"); printf(" -noalpha ............... discard any transparency information.\n");
printf(" -pass <int> ............ analysis pass number (1..10)\n"); printf(" -pass <int> ............ analysis pass number (1..10)\n");
@ -712,10 +715,15 @@ static const char* const kErrorMessages[] = {
"BITSTREAM_OUT_OF_MEMORY: Out of memory re-allocating byte buffer", "BITSTREAM_OUT_OF_MEMORY: Out of memory re-allocating byte buffer",
"NULL_PARAMETER: NULL parameter passed to function", "NULL_PARAMETER: NULL parameter passed to function",
"INVALID_CONFIGURATION: configuration is invalid", "INVALID_CONFIGURATION: configuration is invalid",
"BAD_DIMENSION: Bad picture dimension", "BAD_DIMENSION: Bad picture dimension. Maximum width and height "
"PARTITION0_OVERFLOW: Partition #0 is too big to fit 512k", "allowed is 16383 pixels.",
"PARTITION_OVERFLOW: Partition is too big to fir 16M", "PARTITION0_OVERFLOW: Partition #0 is too big to fit 512k.\n"
"BAD_WRITE: Picture writer returned an error" "To reduce the size of this partition, try using less segments "
"with the -segments option, and eventually reduce the number of "
"header bits using -partition_limit. More details are available "
"in the manual (`man cwebp`)",
"PARTITION_OVERFLOW: Partition is too big to fit 16M",
"BAD_WRITE: Picture writer returned an I/O error"
}; };
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
@ -789,6 +797,8 @@ int main(int argc, const char *argv[]) {
config.preprocessing = strtol(argv[++c], NULL, 0); config.preprocessing = strtol(argv[++c], NULL, 0);
} else if (!strcmp(argv[c], "-segments") && c < argc - 1) { } else if (!strcmp(argv[c], "-segments") && c < argc - 1) {
config.segments = strtol(argv[++c], NULL, 0); config.segments = strtol(argv[++c], NULL, 0);
} else if (!strcmp(argv[c], "-partition_limit") && c < argc - 1) {
config.partition_limit = strtol(argv[++c], NULL, 0);
} else if (!strcmp(argv[c], "-alpha_comp") && c < argc - 1) { } else if (!strcmp(argv[c], "-alpha_comp") && c < argc - 1) {
config.alpha_compression = strtol(argv[++c], NULL, 0); config.alpha_compression = strtol(argv[++c], NULL, 0);
} else if (!strcmp(argv[c], "-noalpha")) { } else if (!strcmp(argv[c], "-noalpha")) {

View File

@ -1,5 +1,5 @@
.\" Hey, EMACS: -*- nroff -*- .\" Hey, EMACS: -*- nroff -*-
.TH CWEBP 1 "June 20, 2011" .TH CWEBP 1 "August 23, 2011"
.SH NAME .SH NAME
cwebp \- compress an image file to a WebP file cwebp \- compress an image file to a WebP file
.SH SYNOPSIS .SH SYNOPSIS
@ -86,6 +86,25 @@ used thanks to the \fB\-f\fP option). Strong filtering is off by default.
Change the number of partitions to use during the segmentation of the Change the number of partitions to use during the segmentation of the
sns algorithm. Segments should be in range 1 to 4. Default value is 4. sns algorithm. Segments should be in range 1 to 4. Default value is 4.
.TP .TP
.B \-partition_limit int
Degrade quality by limiting the number of bits used by some macroblocks.
Range is 0 (no degradation, the default) to 100 (full degradation).
Useful values are usually around 30-70 for moderately large images.
In the VP8 format, the so-called control partition has a limit of 512k and
is used to store the following information: whether the macroblock is skipped,
which segment it belongs to, whether it is coded as intra 4x4 or intra 16x16
mode, and finally the prediction modes to use for each of the sub-blocks.
For a very large image, 512k only leaves room to few bits per 16x16 macroblock.
The absolute minimum is 4 bits per macroblock. Skip, segment, and mode
information can use up almost all these 4 bits (although the case is unlikely),
which is problematic for very large images. The partition_limit factor controls
how frequently the most bit-costly mode (intra 4x4) will be used. This is
useful in case the 512k limit is reached and the following message is displayed:
\fIError code: 6 (PARTITION0_OVERFLOW: Partition #0 is too big to fit 512k)\fP.
If using \fB-partition_limit\fP is not enough to meet the 512k constraint, one
should use less segments in order to save more header bits per macroblock.
See the \fB-segments\fP option.
.TP
.B \-size int .B \-size int
Specify a target size (in bytes) to try and reach for the compressed output. Specify a target size (in bytes) to try and reach for the compressed output.
Compressor will make several pass of partial encoding in order to get as Compressor will make several pass of partial encoding in order to get as

View File

@ -42,6 +42,7 @@ int WebPConfigInitInternal(WebPConfig* const config,
config->preprocessing = 0; config->preprocessing = 0;
config->autofilter = 0; config->autofilter = 0;
config->alpha_compression = 0; config->alpha_compression = 0;
config->partition_limit = 0;
// TODO(skal): tune. // TODO(skal): tune.
switch (preset) { switch (preset) {
@ -106,6 +107,8 @@ int WebPValidateConfig(const WebPConfig* const config) {
return 0; return 0;
if (config->partitions < 0 || config->partitions > 3) if (config->partitions < 0 || config->partitions > 3)
return 0; return 0;
if (config->partition_limit < 0 || config->partition_limit > 100)
return 0;
if (config->alpha_compression < 0) if (config->alpha_compression < 0)
return 0; return 0;
return 1; return 1;

View File

@ -757,8 +757,13 @@ static int PickBestIntra4(VP8EncIterator* const it, VP8ModeScore* const rd) {
const int tlambda = dqm->tlambda_; const int tlambda = dqm->tlambda_;
const uint8_t* const src0 = it->yuv_in_ + Y_OFF; const uint8_t* const src0 = it->yuv_in_ + Y_OFF;
uint8_t* const best_blocks = it->yuv_out2_ + Y_OFF; uint8_t* const best_blocks = it->yuv_out2_ + Y_OFF;
int total_header_bits = 0;
VP8ModeScore rd_best; VP8ModeScore rd_best;
if (enc->max_i4_header_bits_ == 0) {
return 0;
}
InitScore(&rd_best); InitScore(&rd_best);
rd_best.score = 211; // '211' is the value of VP8BitCost(0, 145) rd_best.score = 211; // '211' is the value of VP8BitCost(0, 145)
VP8IteratorStartI4(it); VP8IteratorStartI4(it);
@ -799,7 +804,9 @@ static int PickBestIntra4(VP8EncIterator* const it, VP8ModeScore* const rd) {
} }
SetRDScore(dqm->lambda_mode_, &rd_i4); SetRDScore(dqm->lambda_mode_, &rd_i4);
AddScore(&rd_best, &rd_i4); AddScore(&rd_best, &rd_i4);
if (rd_best.score >= rd->score) { total_header_bits += mode_costs[best_mode];
if (rd_best.score >= rd->score ||
total_header_bits > enc->max_i4_header_bits_) {
return 0; return 0;
} }
// Copy selected samples if not in the right place already. // Copy selected samples if not in the right place already.

View File

@ -359,8 +359,9 @@ struct VP8Encoder {
int block_count_[3]; int block_count_[3];
// quality/speed settings // quality/speed settings
int method_; // 0=fastest, 6=best/slowest. int method_; // 0=fastest, 6=best/slowest.
int rd_opt_level_; // Deduced from method_. int rd_opt_level_; // Deduced from method_.
int max_i4_header_bits_; // partition #0 safeness factor
// Memory // Memory
VP8MBInfo* mb_info_; // contextual macroblock infos (mb_w_ + 1) VP8MBInfo* mb_info_; // contextual macroblock infos (mb_w_ + 1)

View File

@ -112,11 +112,15 @@ static void ResetBoundaryPredictions(VP8Encoder* const enc) {
static void MapConfigToTools(VP8Encoder* const enc) { static void MapConfigToTools(VP8Encoder* const enc) {
const int method = enc->config_->method; const int method = enc->config_->method;
const int limit = 100 - enc->config_->partition_limit;
enc->method_ = method; enc->method_ = method;
enc->rd_opt_level_ = (method >= 6) ? 3 enc->rd_opt_level_ = (method >= 6) ? 3
: (method >= 5) ? 2 : (method >= 5) ? 2
: (method >= 3) ? 1 : (method >= 3) ? 1
: 0; : 0;
enc->max_i4_header_bits_ =
256 * 16 * 16 * // upper bound: up to 16bit per 4x4 block
(limit * limit) / (100 * 100); // ... modulated with a quadratic curve.
} }
// Memory scaling with dimensions: // Memory scaling with dimensions:

View File

@ -69,6 +69,8 @@ typedef struct {
int preprocessing; // preprocessing filter (0=none, 1=segment-smooth) int preprocessing; // preprocessing filter (0=none, 1=segment-smooth)
int partitions; // log2(number of token partitions) in [0..3] int partitions; // log2(number of token partitions) in [0..3]
// Default is set to 0 for easier progressive decoding. // Default is set to 0 for easier progressive decoding.
int partition_limit; // quality degradation allowed to fit the 512k limit on
// prediction modes coding (0=no degradation, 100=full)
int alpha_compression; // Algorithm for optimizing the alpha plane (0 = none) int alpha_compression; // Algorithm for optimizing the alpha plane (0 = none)
} WebPConfig; } WebPConfig;