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
This commit is contained in:
skal 2013-02-05 19:40:18 +01:00
parent 1f803f645d
commit e895059a05
8 changed files with 80 additions and 29 deletions

1
README
View File

@ -183,6 +183,7 @@ options:
-progress .............. report encoding progress
Experimental Options:
-jpeg_like ............. Roughly match expected JPEG size.
-af .................... auto-adjust filter strength.
-pre <int> ............. pre-processing filter

View File

@ -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 <int> ............. 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) {

View File

@ -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.

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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.);

View File

@ -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_;

View File

@ -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