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
the first partition (0=no degradation ... 100=full)
-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
-resize <w> <h> ........ resize picture (after any cropping)
-mt .................... use multi-threading if available
@ -294,6 +296,7 @@ Experimental Options:
-af .................... auto-adjust filter strength
-pre <int> ............. pre-processing filter
The main options you might want to try in order to further tune the
visual quality are:
-preset

View File

@ -565,6 +565,8 @@ static void HelpLong(void) {
printf(" "
"the first partition (0=no degradation ... 100=full)\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(" -resize <w> <h> ........ resize picture (after any cropping)\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")) {
HelpLong();
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);
} 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);
config.show_compressed = 1;
} else if (!strcmp(argv[c], "-print_psnr")) {
@ -707,7 +709,7 @@ int main(int argc, const char* argv[]) {
print_distortion = 2;
} else if (!strcmp(argv[c], "-short")) {
++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.height = ExUtilGetInt(argv[++c], 0, &parse_error);
if (picture.width > WEBP_MAX_DIMENSION || picture.width < 0 ||
@ -717,30 +719,30 @@ int main(int argc, const char* argv[]) {
picture.width, picture.height);
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);
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);
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);
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);
} 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);
} else if (!strcmp(argv[c], "-alpha_cleanup")) {
// This flag is obsolete, does opposite of -exact.
config.exact = 0;
} else if (!strcmp(argv[c], "-exact")) {
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;
// background color is given in hex with an optional '0x' prefix
background_color = ExUtilGetInt(argv[++c], 16, &parse_error);
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;
if (!strcmp(argv[c], "none")) {
config.alpha_filtering = 0;
@ -756,10 +758,10 @@ int main(int argc, const char* argv[]) {
keep_alpha = 0;
} else if (!strcmp(argv[c], "-lossless")) {
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.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;
if (!strcmp(argv[c], "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]);
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);
} 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);
} 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);
} 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);
} else if (!strcmp(argv[c], "-af")) {
config.autofilter = 1;
@ -791,27 +793,32 @@ int main(int argc, const char* argv[]) {
config.filter_type = 1;
} else if (!strcmp(argv[c], "-nostrong")) {
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);
} else if (!strcmp(argv[c], "-sharp_yuv")) {
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);
} 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);
} 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);
} 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);
} 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);
} else if (!strcmp(argv[c], "-crop") && c < argc - 4) {
} else if (!strcmp(argv[c], "-crop") && c + 4 < argc) {
crop = 1;
crop_x = ExUtilGetInt(argv[++c], 0, &parse_error);
crop_y = ExUtilGetInt(argv[++c], 0, &parse_error);
crop_w = 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_h = ExUtilGetInt(argv[++c], 0, &parse_error);
#ifndef WEBP_DLL
@ -827,7 +834,7 @@ int main(int argc, const char* argv[]) {
show_progress = 1;
} else if (!strcmp(argv[c], "-quiet")) {
quiet = 1;
} else if (!strcmp(argv[c], "-preset") && c < argc - 1) {
} else if (!strcmp(argv[c], "-preset") && c + 1 < argc) {
WebPPreset preset;
++c;
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");
goto Error;
}
} else if (!strcmp(argv[c], "-metadata") && c < argc - 1) {
} else if (!strcmp(argv[c], "-metadata") && c + 1 < argc) {
static const struct {
const char* option;
int flag;
@ -898,7 +905,7 @@ int main(int argc, const char* argv[]) {
} else if (!strcmp(argv[c], "-v")) {
verbose = 1;
} 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;
} else if (argv[c][0] == '-') {
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
specified, a default value of '6' passes will be used.
.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
Turns auto\-filter on. This algorithm will spend additional time optimizing
the filtering strength to reach a well\-balanced quality.

View File

@ -39,6 +39,8 @@ int WebPConfigInitInternal(WebPConfig* config,
config->partitions = 0;
config->segments = 4;
config->pass = 1;
config->qmin = 0;
config->qmax = 100;
config->show_compressed = 0;
config->preprocessing = 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->autofilter < 0 || config->autofilter > 1) 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->preprocessing < 0 || config->preprocessing > 7) 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.
#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
int is_first;
float dq;
float q, last_q;
float qmin, qmax;
double value, last_value; // PSNR or size
double target;
int do_size_search;
@ -47,7 +52,9 @@ static int InitPassStats(const VP8Encoder* const enc, PassStats* const s) {
s->is_first = 1;
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
: (target_PSNR > 0.) ? target_PSNR
: 40.; // default, just in case
@ -56,10 +63,6 @@ static int InitPassStats(const VP8Encoder* const enc, PassStats* const s) {
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) {
float dq;
if (s->is_first) {
@ -75,7 +78,7 @@ static float ComputeNextQ(PassStats* const s) {
s->dq = Clamp(dq, -30.f, 30.f);
s->last_q = s->q;
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;
}
@ -848,9 +851,10 @@ int VP8EncTokenLoop(VP8Encoder* const enc) {
}
#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,
stats.last_q, stats.q, stats.dq);
stats.last_q, stats.q, stats.dq, stats.qmin, stats.qmax);
#endif
if (enc->max_i4_header_bits_ > 0 && size_p0 > PARTITION0_SIZE_LIMIT) {
++num_pass_left;

View File

@ -20,7 +20,7 @@
extern "C" {
#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++,
// the types are left here for reference.
@ -121,6 +121,8 @@ struct WebPConfig {
int alpha_quality; // Between 0 (smallest size) and 100 (lossless).
// Default is 100.
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.
// In-loop filtering is not applied.