Enc: add a qmin / qmax range for quality factor

This is particularly useful for multi-pass search (but not only),
to prevent the search from going over or below a reasonable threshold.
E.g.: 'cwebp -qrange 50 80 ...' will prevent any unreasonable degradation.

new cwebp option: -qrange min max

Change-Id: I59f394533535fc20b6996bc0895f4301476d5eff
This commit is contained in:
Skal 2020-01-29 09:34:26 +01:00
parent 0fa56f307f
commit 6a0ff35872
6 changed files with 64 additions and 36 deletions

3
README
View File

@ -254,6 +254,8 @@ Options:
-partition_limit <int> . limit quality to fit the 512k limit on -partition_limit <int> . limit quality to fit the 512k limit on
the first partition (0=no degradation ... 100=full) the first partition (0=no degradation ... 100=full)
-pass <int> ............ analysis pass number (1..10) -pass <int> ............ analysis pass number (1..10)
-qrange <min> <max> .... specifies the permissible quality range
(default: 0 100)
-crop <x> <y> <w> <h> .. crop picture with the given rectangle -crop <x> <y> <w> <h> .. crop picture with the given rectangle
-resize <w> <h> ........ resize picture (after any cropping) -resize <w> <h> ........ resize picture (after any cropping)
-mt .................... use multi-threading if available -mt .................... use multi-threading if available
@ -294,6 +296,7 @@ Experimental Options:
-af .................... auto-adjust filter strength -af .................... auto-adjust filter strength
-pre <int> ............. pre-processing filter -pre <int> ............. pre-processing filter
The main options you might want to try in order to further tune the The main options you might want to try in order to further tune the
visual quality are: visual quality are:
-preset -preset

View File

@ -565,6 +565,8 @@ static void HelpLong(void) {
printf(" " printf(" "
"the first partition (0=no degradation ... 100=full)\n"); "the first partition (0=no degradation ... 100=full)\n");
printf(" -pass <int> ............ analysis pass number (1..10)\n"); printf(" -pass <int> ............ analysis pass number (1..10)\n");
printf(" -qrange <min> <max> .... specifies the permissible quality range\n"
" (default: 0 100)\n");
printf(" -crop <x> <y> <w> <h> .. crop picture with the given rectangle\n"); printf(" -crop <x> <y> <w> <h> .. crop picture with the given rectangle\n");
printf(" -resize <w> <h> ........ resize picture (after any cropping)\n"); printf(" -resize <w> <h> ........ resize picture (after any cropping)\n");
printf(" -mt .................... use multi-threading if available\n"); printf(" -mt .................... use multi-threading if available\n");
@ -691,9 +693,9 @@ int main(int argc, const char* argv[]) {
} else if (!strcmp(argv[c], "-H") || !strcmp(argv[c], "-longhelp")) { } else if (!strcmp(argv[c], "-H") || !strcmp(argv[c], "-longhelp")) {
HelpLong(); HelpLong();
FREE_WARGV_AND_RETURN(0); FREE_WARGV_AND_RETURN(0);
} else if (!strcmp(argv[c], "-o") && c < argc - 1) { } else if (!strcmp(argv[c], "-o") && c + 1 < argc) {
out_file = (const char*)GET_WARGV(argv, ++c); out_file = (const char*)GET_WARGV(argv, ++c);
} else if (!strcmp(argv[c], "-d") && c < argc - 1) { } else if (!strcmp(argv[c], "-d") && c + 1 < argc) {
dump_file = (const char*)GET_WARGV(argv, ++c); dump_file = (const char*)GET_WARGV(argv, ++c);
config.show_compressed = 1; config.show_compressed = 1;
} else if (!strcmp(argv[c], "-print_psnr")) { } else if (!strcmp(argv[c], "-print_psnr")) {
@ -707,7 +709,7 @@ int main(int argc, const char* argv[]) {
print_distortion = 2; print_distortion = 2;
} else if (!strcmp(argv[c], "-short")) { } else if (!strcmp(argv[c], "-short")) {
++short_output; ++short_output;
} else if (!strcmp(argv[c], "-s") && c < argc - 2) { } else if (!strcmp(argv[c], "-s") && c + 2 < argc) {
picture.width = ExUtilGetInt(argv[++c], 0, &parse_error); picture.width = ExUtilGetInt(argv[++c], 0, &parse_error);
picture.height = ExUtilGetInt(argv[++c], 0, &parse_error); picture.height = ExUtilGetInt(argv[++c], 0, &parse_error);
if (picture.width > WEBP_MAX_DIMENSION || picture.width < 0 || if (picture.width > WEBP_MAX_DIMENSION || picture.width < 0 ||
@ -717,30 +719,30 @@ int main(int argc, const char* argv[]) {
picture.width, picture.height); picture.width, picture.height);
goto Error; goto Error;
} }
} else if (!strcmp(argv[c], "-m") && c < argc - 1) { } else if (!strcmp(argv[c], "-m") && c + 1 < argc) {
config.method = ExUtilGetInt(argv[++c], 0, &parse_error); config.method = ExUtilGetInt(argv[++c], 0, &parse_error);
use_lossless_preset = 0; // disable -z option use_lossless_preset = 0; // disable -z option
} else if (!strcmp(argv[c], "-q") && c < argc - 1) { } else if (!strcmp(argv[c], "-q") && c + 1 < argc) {
config.quality = ExUtilGetFloat(argv[++c], &parse_error); config.quality = ExUtilGetFloat(argv[++c], &parse_error);
use_lossless_preset = 0; // disable -z option use_lossless_preset = 0; // disable -z option
} else if (!strcmp(argv[c], "-z") && c < argc - 1) { } else if (!strcmp(argv[c], "-z") && c + 1 < argc) {
lossless_preset = ExUtilGetInt(argv[++c], 0, &parse_error); lossless_preset = ExUtilGetInt(argv[++c], 0, &parse_error);
if (use_lossless_preset != 0) use_lossless_preset = 1; if (use_lossless_preset != 0) use_lossless_preset = 1;
} else if (!strcmp(argv[c], "-alpha_q") && c < argc - 1) { } else if (!strcmp(argv[c], "-alpha_q") && c + 1 < argc) {
config.alpha_quality = ExUtilGetInt(argv[++c], 0, &parse_error); config.alpha_quality = ExUtilGetInt(argv[++c], 0, &parse_error);
} else if (!strcmp(argv[c], "-alpha_method") && c < argc - 1) { } else if (!strcmp(argv[c], "-alpha_method") && c + 1 < argc) {
config.alpha_compression = ExUtilGetInt(argv[++c], 0, &parse_error); config.alpha_compression = ExUtilGetInt(argv[++c], 0, &parse_error);
} else if (!strcmp(argv[c], "-alpha_cleanup")) { } else if (!strcmp(argv[c], "-alpha_cleanup")) {
// This flag is obsolete, does opposite of -exact. // This flag is obsolete, does opposite of -exact.
config.exact = 0; config.exact = 0;
} else if (!strcmp(argv[c], "-exact")) { } else if (!strcmp(argv[c], "-exact")) {
config.exact = 1; config.exact = 1;
} else if (!strcmp(argv[c], "-blend_alpha") && c < argc - 1) { } else if (!strcmp(argv[c], "-blend_alpha") && c + 1 < argc) {
blend_alpha = 1; blend_alpha = 1;
// background color is given in hex with an optional '0x' prefix // background color is given in hex with an optional '0x' prefix
background_color = ExUtilGetInt(argv[++c], 16, &parse_error); background_color = ExUtilGetInt(argv[++c], 16, &parse_error);
background_color = background_color & 0x00ffffffu; background_color = background_color & 0x00ffffffu;
} else if (!strcmp(argv[c], "-alpha_filter") && c < argc - 1) { } else if (!strcmp(argv[c], "-alpha_filter") && c + 1 < argc) {
++c; ++c;
if (!strcmp(argv[c], "none")) { if (!strcmp(argv[c], "none")) {
config.alpha_filtering = 0; config.alpha_filtering = 0;
@ -756,10 +758,10 @@ int main(int argc, const char* argv[]) {
keep_alpha = 0; keep_alpha = 0;
} else if (!strcmp(argv[c], "-lossless")) { } else if (!strcmp(argv[c], "-lossless")) {
config.lossless = 1; config.lossless = 1;
} else if (!strcmp(argv[c], "-near_lossless") && c < argc - 1) { } else if (!strcmp(argv[c], "-near_lossless") && c + 1 < argc) {
config.near_lossless = ExUtilGetInt(argv[++c], 0, &parse_error); config.near_lossless = ExUtilGetInt(argv[++c], 0, &parse_error);
config.lossless = 1; // use near-lossless only with lossless config.lossless = 1; // use near-lossless only with lossless
} else if (!strcmp(argv[c], "-hint") && c < argc - 1) { } else if (!strcmp(argv[c], "-hint") && c + 1 < argc) {
++c; ++c;
if (!strcmp(argv[c], "photo")) { if (!strcmp(argv[c], "photo")) {
config.image_hint = WEBP_HINT_PHOTO; config.image_hint = WEBP_HINT_PHOTO;
@ -771,13 +773,13 @@ int main(int argc, const char* argv[]) {
fprintf(stderr, "Error! Unrecognized image hint: %s\n", argv[c]); fprintf(stderr, "Error! Unrecognized image hint: %s\n", argv[c]);
goto Error; goto Error;
} }
} else if (!strcmp(argv[c], "-size") && c < argc - 1) { } else if (!strcmp(argv[c], "-size") && c + 1 < argc) {
config.target_size = ExUtilGetInt(argv[++c], 0, &parse_error); config.target_size = ExUtilGetInt(argv[++c], 0, &parse_error);
} else if (!strcmp(argv[c], "-psnr") && c < argc - 1) { } else if (!strcmp(argv[c], "-psnr") && c + 1 < argc) {
config.target_PSNR = ExUtilGetFloat(argv[++c], &parse_error); config.target_PSNR = ExUtilGetFloat(argv[++c], &parse_error);
} else if (!strcmp(argv[c], "-sns") && c < argc - 1) { } else if (!strcmp(argv[c], "-sns") && c + 1 < argc) {
config.sns_strength = ExUtilGetInt(argv[++c], 0, &parse_error); config.sns_strength = ExUtilGetInt(argv[++c], 0, &parse_error);
} else if (!strcmp(argv[c], "-f") && c < argc - 1) { } else if (!strcmp(argv[c], "-f") && c + 1 < argc) {
config.filter_strength = ExUtilGetInt(argv[++c], 0, &parse_error); config.filter_strength = ExUtilGetInt(argv[++c], 0, &parse_error);
} else if (!strcmp(argv[c], "-af")) { } else if (!strcmp(argv[c], "-af")) {
config.autofilter = 1; config.autofilter = 1;
@ -791,27 +793,32 @@ int main(int argc, const char* argv[]) {
config.filter_type = 1; config.filter_type = 1;
} else if (!strcmp(argv[c], "-nostrong")) { } else if (!strcmp(argv[c], "-nostrong")) {
config.filter_type = 0; config.filter_type = 0;
} else if (!strcmp(argv[c], "-sharpness") && c < argc - 1) { } else if (!strcmp(argv[c], "-sharpness") && c + 1 < argc) {
config.filter_sharpness = ExUtilGetInt(argv[++c], 0, &parse_error); config.filter_sharpness = ExUtilGetInt(argv[++c], 0, &parse_error);
} else if (!strcmp(argv[c], "-sharp_yuv")) { } else if (!strcmp(argv[c], "-sharp_yuv")) {
config.use_sharp_yuv = 1; config.use_sharp_yuv = 1;
} else if (!strcmp(argv[c], "-pass") && c < argc - 1) { } else if (!strcmp(argv[c], "-pass") && c + 1 < argc) {
config.pass = ExUtilGetInt(argv[++c], 0, &parse_error); config.pass = ExUtilGetInt(argv[++c], 0, &parse_error);
} else if (!strcmp(argv[c], "-pre") && c < argc - 1) { } else if (!strcmp(argv[c], "-qrange") && c + 2 < argc) {
config.qmin = ExUtilGetInt(argv[++c], 0, &parse_error);
config.qmax = ExUtilGetInt(argv[++c], 0, &parse_error);
if (config.qmin < 0) config.qmin = 0;
if (config.qmax > 100) config.qmax = 100;
} else if (!strcmp(argv[c], "-pre") && c + 1 < argc) {
config.preprocessing = ExUtilGetInt(argv[++c], 0, &parse_error); config.preprocessing = ExUtilGetInt(argv[++c], 0, &parse_error);
} else if (!strcmp(argv[c], "-segments") && c < argc - 1) { } else if (!strcmp(argv[c], "-segments") && c + 1 < argc) {
config.segments = ExUtilGetInt(argv[++c], 0, &parse_error); config.segments = ExUtilGetInt(argv[++c], 0, &parse_error);
} else if (!strcmp(argv[c], "-partition_limit") && c < argc - 1) { } else if (!strcmp(argv[c], "-partition_limit") && c + 1 < argc) {
config.partition_limit = ExUtilGetInt(argv[++c], 0, &parse_error); config.partition_limit = ExUtilGetInt(argv[++c], 0, &parse_error);
} else if (!strcmp(argv[c], "-map") && c < argc - 1) { } else if (!strcmp(argv[c], "-map") && c + 1 < argc) {
picture.extra_info_type = ExUtilGetInt(argv[++c], 0, &parse_error); picture.extra_info_type = ExUtilGetInt(argv[++c], 0, &parse_error);
} else if (!strcmp(argv[c], "-crop") && c < argc - 4) { } else if (!strcmp(argv[c], "-crop") && c + 4 < argc) {
crop = 1; crop = 1;
crop_x = ExUtilGetInt(argv[++c], 0, &parse_error); crop_x = ExUtilGetInt(argv[++c], 0, &parse_error);
crop_y = ExUtilGetInt(argv[++c], 0, &parse_error); crop_y = ExUtilGetInt(argv[++c], 0, &parse_error);
crop_w = ExUtilGetInt(argv[++c], 0, &parse_error); crop_w = ExUtilGetInt(argv[++c], 0, &parse_error);
crop_h = ExUtilGetInt(argv[++c], 0, &parse_error); crop_h = ExUtilGetInt(argv[++c], 0, &parse_error);
} else if (!strcmp(argv[c], "-resize") && c < argc - 2) { } else if (!strcmp(argv[c], "-resize") && c + 2 < argc) {
resize_w = ExUtilGetInt(argv[++c], 0, &parse_error); resize_w = ExUtilGetInt(argv[++c], 0, &parse_error);
resize_h = ExUtilGetInt(argv[++c], 0, &parse_error); resize_h = ExUtilGetInt(argv[++c], 0, &parse_error);
#ifndef WEBP_DLL #ifndef WEBP_DLL
@ -827,7 +834,7 @@ int main(int argc, const char* argv[]) {
show_progress = 1; show_progress = 1;
} else if (!strcmp(argv[c], "-quiet")) { } else if (!strcmp(argv[c], "-quiet")) {
quiet = 1; quiet = 1;
} else if (!strcmp(argv[c], "-preset") && c < argc - 1) { } else if (!strcmp(argv[c], "-preset") && c + 1 < argc) {
WebPPreset preset; WebPPreset preset;
++c; ++c;
if (!strcmp(argv[c], "default")) { if (!strcmp(argv[c], "default")) {
@ -850,7 +857,7 @@ int main(int argc, const char* argv[]) {
fprintf(stderr, "Error! Could initialize configuration with preset.\n"); fprintf(stderr, "Error! Could initialize configuration with preset.\n");
goto Error; goto Error;
} }
} else if (!strcmp(argv[c], "-metadata") && c < argc - 1) { } else if (!strcmp(argv[c], "-metadata") && c + 1 < argc) {
static const struct { static const struct {
const char* option; const char* option;
int flag; int flag;
@ -898,7 +905,7 @@ int main(int argc, const char* argv[]) {
} else if (!strcmp(argv[c], "-v")) { } else if (!strcmp(argv[c], "-v")) {
verbose = 1; verbose = 1;
} else if (!strcmp(argv[c], "--")) { } else if (!strcmp(argv[c], "--")) {
if (c < argc - 1) in_file = (const char*)GET_WARGV(argv, ++c); if (c + 1 < argc) in_file = (const char*)GET_WARGV(argv, ++c);
break; break;
} else if (argv[c][0] == '-') { } else if (argv[c][0] == '-') {
fprintf(stderr, "Error! Unknown option '%s'\n", argv[c]); fprintf(stderr, "Error! Unknown option '%s'\n", argv[c]);

View File

@ -134,6 +134,13 @@ options \fB\-size\fP or \fB\-psnr\fP. Maximum value is 10, default is 1.
If options \fB\-size\fP or \fB\-psnr\fP were used, but \fB\-pass\fP wasn't If options \fB\-size\fP or \fB\-psnr\fP were used, but \fB\-pass\fP wasn't
specified, a default value of '6' passes will be used. specified, a default value of '6' passes will be used.
.TP .TP
.BI \-qrange " int int
Specifies the permissible interval for the quality factor. This is particularly
useful when using multi-pass (\fB\-size\fP or \fB\-psnr\fP options).
Default is 0 100.
If the quality factor is outside this range, it will be clamped.
If the minimum value must be less or equal to the maximum one.
.TP
.B \-af .B \-af
Turns auto\-filter on. This algorithm will spend additional time optimizing Turns auto\-filter on. This algorithm will spend additional time optimizing
the filtering strength to reach a well\-balanced quality. the filtering strength to reach a well\-balanced quality.

View File

@ -39,6 +39,8 @@ int WebPConfigInitInternal(WebPConfig* config,
config->partitions = 0; config->partitions = 0;
config->segments = 4; config->segments = 4;
config->pass = 1; config->pass = 1;
config->qmin = 0;
config->qmax = 100;
config->show_compressed = 0; config->show_compressed = 0;
config->preprocessing = 0; config->preprocessing = 0;
config->autofilter = 0; config->autofilter = 0;
@ -106,6 +108,9 @@ int WebPValidateConfig(const WebPConfig* config) {
if (config->filter_type < 0 || config->filter_type > 1) return 0; if (config->filter_type < 0 || config->filter_type > 1) return 0;
if (config->autofilter < 0 || config->autofilter > 1) return 0; if (config->autofilter < 0 || config->autofilter > 1) return 0;
if (config->pass < 1 || config->pass > 10) return 0; if (config->pass < 1 || config->pass > 10) return 0;
if (config->qmin < 0 || config->qmax > 100 || config->qmin > config->qmax) {
return 0;
}
if (config->show_compressed < 0 || config->show_compressed > 1) return 0; if (config->show_compressed < 0 || config->show_compressed > 1) return 0;
if (config->preprocessing < 0 || config->preprocessing > 7) return 0; if (config->preprocessing < 0 || config->preprocessing > 7) return 0;
if (config->partitions < 0 || config->partitions > 3) return 0; if (config->partitions < 0 || config->partitions > 3) return 0;

View File

@ -31,10 +31,15 @@
// we allow 2k of extra head-room in PARTITION0 limit. // we allow 2k of extra head-room in PARTITION0 limit.
#define PARTITION0_SIZE_LIMIT ((VP8_MAX_PARTITION0_SIZE - 2048ULL) << 11) #define PARTITION0_SIZE_LIMIT ((VP8_MAX_PARTITION0_SIZE - 2048ULL) << 11)
static float Clamp(float v, float min, float max) {
return (v < min) ? min : (v > max) ? max : v;
}
typedef struct { // struct for organizing convergence in either size or PSNR typedef struct { // struct for organizing convergence in either size or PSNR
int is_first; int is_first;
float dq; float dq;
float q, last_q; float q, last_q;
float qmin, qmax;
double value, last_value; // PSNR or size double value, last_value; // PSNR or size
double target; double target;
int do_size_search; int do_size_search;
@ -47,7 +52,9 @@ static int InitPassStats(const VP8Encoder* const enc, PassStats* const s) {
s->is_first = 1; s->is_first = 1;
s->dq = 10.f; s->dq = 10.f;
s->q = s->last_q = enc->config_->quality; s->qmin = enc->config_->qmin;
s->qmax = enc->config_->qmax;
s->q = s->last_q = Clamp(enc->config_->quality, s->qmin, s->qmax);
s->target = do_size_search ? (double)target_size s->target = do_size_search ? (double)target_size
: (target_PSNR > 0.) ? target_PSNR : (target_PSNR > 0.) ? target_PSNR
: 40.; // default, just in case : 40.; // default, just in case
@ -56,10 +63,6 @@ static int InitPassStats(const VP8Encoder* const enc, PassStats* const s) {
return do_size_search; return do_size_search;
} }
static float Clamp(float v, float min, float max) {
return (v < min) ? min : (v > max) ? max : v;
}
static float ComputeNextQ(PassStats* const s) { static float ComputeNextQ(PassStats* const s) {
float dq; float dq;
if (s->is_first) { if (s->is_first) {
@ -75,7 +78,7 @@ static float ComputeNextQ(PassStats* const s) {
s->dq = Clamp(dq, -30.f, 30.f); s->dq = Clamp(dq, -30.f, 30.f);
s->last_q = s->q; s->last_q = s->q;
s->last_value = s->value; s->last_value = s->value;
s->q = Clamp(s->q + s->dq, 0.f, 100.f); s->q = Clamp(s->q + s->dq, s->qmin, s->qmax);
return s->q; return s->q;
} }
@ -848,9 +851,10 @@ int VP8EncTokenLoop(VP8Encoder* const enc) {
} }
#if (DEBUG_SEARCH > 0) #if (DEBUG_SEARCH > 0)
printf("#%2d metric:%.1lf -> %.1lf last_q=%.2lf q=%.2lf dq=%.2lf\n", printf("#%2d metric:%.1lf -> %.1lf last_q=%.2lf q=%.2lf dq=%.2lf "
" range:[%.1f, %.1f]\n",
num_pass_left, stats.last_value, stats.value, num_pass_left, stats.last_value, stats.value,
stats.last_q, stats.q, stats.dq); stats.last_q, stats.q, stats.dq, stats.qmin, stats.qmax);
#endif #endif
if (enc->max_i4_header_bits_ > 0 && size_p0 > PARTITION0_SIZE_LIMIT) { if (enc->max_i4_header_bits_ > 0 && size_p0 > PARTITION0_SIZE_LIMIT) {
++num_pass_left; ++num_pass_left;

View File

@ -20,7 +20,7 @@
extern "C" { extern "C" {
#endif #endif
#define WEBP_ENCODER_ABI_VERSION 0x020f // MAJOR(8b) + MINOR(8b) #define WEBP_ENCODER_ABI_VERSION 0x0210 // MAJOR(8b) + MINOR(8b)
// Note: forward declaring enumerations is not allowed in (strict) C and C++, // Note: forward declaring enumerations is not allowed in (strict) C and C++,
// the types are left here for reference. // the types are left here for reference.
@ -121,6 +121,8 @@ struct WebPConfig {
int alpha_quality; // Between 0 (smallest size) and 100 (lossless). int alpha_quality; // Between 0 (smallest size) and 100 (lossless).
// Default is 100. // Default is 100.
int pass; // number of entropy-analysis passes (in [1..10]). int pass; // number of entropy-analysis passes (in [1..10]).
int qmin; // minimum permissible quality factor
int qmax; // maximum permissible quality factor
int show_compressed; // if true, export the compressed picture back. int show_compressed; // if true, export the compressed picture back.
// In-loop filtering is not applied. // In-loop filtering is not applied.