From e895059a052df1bad7fa2e2dc7fd1a4256f97957 Mon Sep 17 00:00:00 2001 From: skal Date: Tue, 5 Feb 2013 19:40:18 +0100 Subject: [PATCH] add a -jpeg_like option This option remaps internal parameters to better match the expected compression curve of JPEG and produce output files of similar size, but with better quality. Change-Id: I96a1cbb480b1f6a0c6845a23c33dfd63f197b689 --- README | 1 + examples/cwebp.c | 3 +++ man/cwebp.1 | 8 +++++++- src/enc/analysis.c | 37 ++++++++++++++++++++++------------ src/enc/config.c | 3 +++ src/enc/quant.c | 50 +++++++++++++++++++++++++++++++++------------- src/enc/vp8enci.h | 1 + src/webp/encode.h | 6 +++++- 8 files changed, 80 insertions(+), 29 deletions(-) diff --git a/README b/README index 5dfe6404..990661cd 100644 --- a/README +++ b/README @@ -183,6 +183,7 @@ options: -progress .............. report encoding progress Experimental Options: + -jpeg_like ............. Roughly match expected JPEG size. -af .................... auto-adjust filter strength. -pre ............. pre-processing filter diff --git a/examples/cwebp.c b/examples/cwebp.c index 6bb4baed..213edffe 100644 --- a/examples/cwebp.c +++ b/examples/cwebp.c @@ -580,6 +580,7 @@ static void HelpLong(void) { printf(" -progress .............. report encoding progress\n"); printf("\n"); printf("Experimental Options:\n"); + printf(" -jpeg_like ............. Roughly match expected JPEG size.\n"); printf(" -af .................... auto-adjust filter strength.\n"); printf(" -pre ............. pre-processing filter\n"); printf("\n"); @@ -719,6 +720,8 @@ int main(int argc, const char *argv[]) { config.filter_strength = strtol(argv[++c], NULL, 0); } else if (!strcmp(argv[c], "-af")) { config.autofilter = 1; + } else if (!strcmp(argv[c], "-jpeg_like")) { + config.emulate_jpeg_size = 1; } else if (!strcmp(argv[c], "-strong")) { config.filter_type = 1; } else if (!strcmp(argv[c], "-sharpness") && c < argc - 1) { diff --git a/man/cwebp.1 b/man/cwebp.1 index 3c85843c..cce82b2c 100644 --- a/man/cwebp.1 +++ b/man/cwebp.1 @@ -1,5 +1,5 @@ .\" Hey, EMACS: -*- nroff -*- -.TH CWEBP 1 "February 01, 2013" +.TH CWEBP 1 "February 6, 2013" .SH NAME cwebp \- compress an image file to a WebP file .SH SYNOPSIS @@ -76,6 +76,12 @@ additional encoding possibilities and decide on the quality gain. Lower value can result is faster processing time at the expense of larger file size and lower compression quality. .TP +.B \-jpeg_like +Change the internal parameter mapping to better match the expected size +of JPEG compression. This flag will generally produce an output file of +similar size to its JPEG equivalent (for the same \fB\-q\fP setting), but +with less visual distortion. +.TP .B \-af Turns auto-filter on. This algorithm will spend additional time optimizing the filtering strength to reach a well-balanced quality. diff --git a/src/enc/analysis.c b/src/enc/analysis.c index 06142207..6ea87745 100644 --- a/src/enc/analysis.c +++ b/src/enc/analysis.c @@ -318,7 +318,8 @@ static int MBAnalyzeBestUVMode(VP8EncIterator* const it) { } static void MBAnalyze(VP8EncIterator* const it, - int alphas[MAX_ALPHA + 1], int* const uv_alpha) { + int alphas[MAX_ALPHA + 1], + int* const alpha, int* const uv_alpha) { const VP8Encoder* const enc = it->enc_; int best_alpha, best_uv_alpha; @@ -340,8 +341,11 @@ static void MBAnalyze(VP8EncIterator* const it, best_alpha = (3 * best_alpha + best_uv_alpha + 2) >> 2; best_alpha = FinalAlphaValue(best_alpha); alphas[best_alpha]++; - *uv_alpha += best_uv_alpha; it->mb_->alpha_ = best_alpha; // for later remapping. + + // Accumulate for later complexity analysis. + *alpha += best_alpha; // mixed susceptibility (not just luma) + *uv_alpha += best_uv_alpha; } static void DefaultMBInfo(VP8MBInfo* const mb) { @@ -362,35 +366,42 @@ static void DefaultMBInfo(VP8MBInfo* const mb) { // and decide intra4/intra16, but that's usually almost always a bad choice at // this stage. +static void ResetAllMBInfo(VP8Encoder* const enc) { + int n; + for (n = 0; n < enc->mb_w_ * enc->mb_h_; ++n) { + DefaultMBInfo(&enc->mb_info_[n]); + } + // Default susceptibilities. + enc->dqm_[0].alpha_ = 0; + enc->dqm_[0].beta_ = 0; + // Note: we can't compute this alpha_ / uv_alpha_. + WebPReportProgress(enc->pic_, enc->percent_ + 20, &enc->percent_); +} + int VP8EncAnalyze(VP8Encoder* const enc) { int ok = 1; const int do_segments = + enc->config_->emulate_jpeg_size || // We need the complexity evaluation. (enc->segment_hdr_.num_segments_ > 1) || (enc->method_ <= 2); // for methods 0,1,2, we need preds_[] to be filled. + enc->alpha_ = 0; + enc->uv_alpha_ = 0; if (do_segments) { int alphas[MAX_ALPHA + 1] = { 0 }; VP8EncIterator it; VP8IteratorInit(enc, &it); - enc->uv_alpha_ = 0; do { VP8IteratorImport(&it); - MBAnalyze(&it, alphas, &enc->uv_alpha_); + MBAnalyze(&it, alphas, &enc->alpha_, &enc->uv_alpha_); ok = VP8IteratorProgress(&it, 20); // Let's pretend we have perfect lossless reconstruction. } while (ok && VP8IteratorNext(&it, it.yuv_in_)); + enc->alpha_ /= enc->mb_w_ * enc->mb_h_; enc->uv_alpha_ /= enc->mb_w_ * enc->mb_h_; if (ok) AssignSegments(enc, alphas); } else { // Use only one default segment. - int n; - for (n = 0; n < enc->mb_w_ * enc->mb_h_; ++n) { - DefaultMBInfo(&enc->mb_info_[n]); - } - // Default susceptibilities. - enc->dqm_[0].alpha_ = 0; - enc->dqm_[0].beta_ = 0; - enc->uv_alpha_ = 0; // we can't compute this one. - WebPReportProgress(enc->pic_, enc->percent_ + 20, &enc->percent_); + ResetAllMBInfo(enc); } return ok; } diff --git a/src/enc/config.c b/src/enc/config.c index 1a261135..7053bee5 100644 --- a/src/enc/config.c +++ b/src/enc/config.c @@ -46,6 +46,7 @@ int WebPConfigInitInternal(WebPConfig* config, config->alpha_quality = 100; config->lossless = 0; config->image_hint = WEBP_HINT_DEFAULT; + config->emulate_jpeg_size = 0; // TODO(skal): tune. switch (preset) { @@ -122,6 +123,8 @@ int WebPValidateConfig(const WebPConfig* config) { return 0; if (config->image_hint >= WEBP_HINT_LAST) return 0; + if (config->emulate_jpeg_size < 0 || config->emulate_jpeg_size > 1) + return 0; return 1; } diff --git a/src/enc/quant.c b/src/enc/quant.c index b5d2d94c..4fbfef47 100644 --- a/src/enc/quant.c +++ b/src/enc/quant.c @@ -224,9 +224,35 @@ static void SetupFilterStrength(VP8Encoder* const enc) { // We want to emulate jpeg-like behaviour where the expected "good" quality // is around q=75. Internally, our "good" middle is around c=50. So we // map accordingly using linear piece-wise function -static double QualityToCompression(double q) { - const double c = q / 100.; - return (c < 0.75) ? c * (2. / 3.) : 2. * c - 1.; +static double QualityToCompression(double c) { + const double linear_c = (c < 0.75) ? c * (2. / 3.) : 2. * c - 1.; + // The file size roughly scales as pow(quantizer, 3.). Actually, the + // exponent is somewhere between 2.8 and 3.2, but we're mostly interested + // in the mid-quant range. So we scale the compressibility inversely to + // this power-law: quant ~= compression ^ 1/3. This law holds well for + // low quant. Finer modelling for high-quant would make use of kAcTable[] + // more explicitly. + const double v = pow(linear_c, 1 / 3.); + return v; +} + +static double QualityToJPEGCompression(double c, double alpha) { + // We map the complexity 'alpha' and quality setting 'c' to a compression + // exponent empirically matched to the compression curve of libjpeg6b. + // On average, the WebP output size will be roughly similar to that of a + // JPEG file compressed with same quality factor. + const double amin = 0.30; + const double amax = 0.85; + const double exp_min = 0.4; + const double exp_max = 0.9; + const double slope = (exp_min - exp_max) / (amax - amin); + // Linearly interpolate 'expn' from exp_min to exp_max + // in the [amin, amax] range. + const double expn = (alpha > amax) ? exp_min + : (alpha < amin) ? exp_max + : exp_max + slope * (alpha - amin); + const double v = pow(c, expn); + return v; } static int SegmentsAreEquivalent(const VP8SegmentInfo* const S1, @@ -274,18 +300,14 @@ void VP8SetSegmentParams(VP8Encoder* const enc, float quality) { int dq_uv_ac, dq_uv_dc; const int num_segments = enc->segment_hdr_.num_segments_; const double amp = SNS_TO_DQ * enc->config_->sns_strength / 100. / 128.; - const double c_base = QualityToCompression(quality); + const double Q = quality / 100.; + const double c_base = enc->config_->emulate_jpeg_size ? + QualityToJPEGCompression(Q, enc->alpha_ / 255.) : + QualityToCompression(Q); for (i = 0; i < num_segments; ++i) { - // The file size roughly scales as pow(quantizer, 3.). Actually, the - // exponent is somewhere between 2.8 and 3.2, but we're mostly interested - // in the mid-quant range. So we scale the compressibility inversely to - // this power-law: quant ~= compression ^ 1/3. This law holds well for - // low quant. Finer modelling for high-quant would make use of kAcTable[] - // more explicitely. - // Additionally, we modulate the base exponent 1/3 to accommodate for the - // quantization susceptibility and allow denser segments to be quantized - // more. - const double expn = (1. - amp * enc->dqm_[i].alpha_) / 3.; + // We modulate the base coefficient to accommodate for the quantization + // susceptibility and allow denser segments to be quantized more. + const double expn = 1. - amp * enc->dqm_[i].alpha_; const double c = pow(c_base, expn); const int q = (int)(127. * (1. - c)); assert(expn > 0.); diff --git a/src/enc/vp8enci.h b/src/enc/vp8enci.h index 671d02c8..a61d3a15 100644 --- a/src/enc/vp8enci.h +++ b/src/enc/vp8enci.h @@ -390,6 +390,7 @@ struct VP8Encoder { VP8SegmentInfo dqm_[NUM_MB_SEGMENTS]; int base_quant_; // nominal quantizer value. Only used // for relative coding of segments' quant. + int alpha_; // global susceptibility (<=> complexity) int uv_alpha_; // U/V quantization susceptibility // global offset of quantizers, shared by all segments int dq_y1_dc_; diff --git a/src/webp/encode.h b/src/webp/encode.h index 8b2fb25b..48ed19fe 100644 --- a/src/webp/encode.h +++ b/src/webp/encode.h @@ -121,8 +121,12 @@ struct WebPConfig { int partition_limit; // quality degradation allowed to fit the 512k limit // on prediction modes coding (0: no degradation, // 100: maximum possible degradation). + int emulate_jpeg_size; // If true, compression parameters will be remapped + // to better match the expected output size from + // JPEG compression. Generally, the output size will + // be similar but the degradation will be lower. - uint32_t pad[8]; // padding for later use + uint32_t pad[7]; // padding for later use }; // Enumerate some predefined settings for WebPConfig, depending on the type