Implement progress report (and user abort)

New cwebp flag is -progress

Change-Id: Ied872cca13f512036860783bbee1bdbccad72768
This commit is contained in:
Pascal Massimino 2011-12-01 02:24:50 -08:00 committed by James Zern
parent eda520a92e
commit 30971c9e95
14 changed files with 160 additions and 47 deletions

7
README
View File

@ -127,6 +127,7 @@ options:
-h / -help ............ short help
-H / -longhelp ........ long help
-q <float> ............. quality factor (0:small..100:big)
-alpha_q <int> ......... Transparency-compression quality (0..100)
-preset <string> ....... Preset setting, one of:
default, photo, picture,
drawing, icon, text
@ -141,20 +142,22 @@ options:
-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
-noalpha ............... discard any transparency information.
-pass <int> ............ analysis pass number (1..10)
-partitions <int> ...... number of partitions to use (0..3)
-crop <x> <y> <w> <h> .. crop picture with the given rectangle
-resize <w> <h> ........ resize picture (after any cropping)
-map <int> ............. print map of extra info.
-d <file.pgm> .......... dump the compressed output (PGM file).
-alpha_method <int> .... Transparency-compression method (0..1)
-noalpha ............... discard any transparency information.
-short ................. condense printed message
-quiet ................. don't print anything.
-version ............... print version number and exit.
-noasm ................. disable all assembly optimizations.
-v ..................... verbose, e.g. print encoding/decoding times
-progress .............. report encoding progress
Experimental Options:
-size <int> ............ Target size (in bytes)

View File

@ -641,6 +641,15 @@ static int DumpPicture(const WebPPicture* const picture, const char* PGM_name) {
//------------------------------------------------------------------------------
static int ProgressReport(int percent, const WebPPicture* const picture) {
printf("[%s]: %3d %% \r",
(char*)picture->stats->user_data, percent);
fflush(stdout);
return 1; // all ok
}
//------------------------------------------------------------------------------
static void HelpShort(void) {
printf("Usage:\n\n");
printf(" cwebp [options] -q quality input.png -o output.webp\n\n");
@ -702,6 +711,7 @@ static void HelpLong(void) {
#endif
printf(" -v ..................... verbose, e.g. print encoding/decoding "
"times\n");
printf(" -progress .............. report encoding progress\n");
printf("\n");
printf("Experimental Options:\n");
printf(" -af .................... auto-adjust filter strength.\n");
@ -727,7 +737,8 @@ static const char* const kErrorMessages[] = {
"in the manual (`man cwebp`)",
"PARTITION_OVERFLOW: Partition is too big to fit 16M",
"BAD_WRITE: Picture writer returned an I/O error",
"FILE_TOO_BIG: File would be too big to fit in 4G"
"FILE_TOO_BIG: File would be too big to fit in 4G",
"USER_ABORT: encoding abort requested by user"
};
//------------------------------------------------------------------------------
@ -741,6 +752,7 @@ int main(int argc, const char *argv[]) {
int keep_alpha = 1;
int crop = 0, crop_x = 0, crop_y = 0, crop_w = 0, crop_h = 0;
int resize_w = 0, resize_h = 0;
int show_progress = 0;
WebPPicture picture;
WebPConfig config;
WebPAuxStats stats;
@ -833,6 +845,8 @@ int main(int argc, const char *argv[]) {
printf("%d.%d.%d\n",
(version >> 16) & 0xff, (version >> 8) & 0xff, version & 0xff);
return 0;
} else if (!strcmp(argv[c], "-progress")) {
show_progress = 1;
} else if (!strcmp(argv[c], "-quiet")) {
quiet = 1;
} else if (!strcmp(argv[c], "-preset") && c < argc - 1) {
@ -882,6 +896,8 @@ int main(int argc, const char *argv[]) {
fprintf(stderr, "Error! Cannot read input picture\n");
goto Error;
}
picture.progress_hook = (show_progress && !quiet) ? ProgressReport : NULL;
if (verbose) {
const double time = StopwatchReadAndReset(&stop_watch);
fprintf(stderr, "Time to read input: %.3fs\n", time);
@ -908,6 +924,7 @@ int main(int argc, const char *argv[]) {
}
}
picture.stats = &stats;
stats.user_data = (void*)in_file;
// Compress
if (verbose) {

View File

@ -156,6 +156,9 @@ Disable all assembly optimizations.
.B \-v
Print extra information (encoding time in particular).
.TP
.B \-progress
Report encoding progress in percent.
.TP
.B \-quiet
Do not print anything.
.TP

View File

@ -46,7 +46,7 @@ int VP8EncFinishAlpha(VP8Encoder* enc) {
enc->alpha_data_size_ = (uint32_t)tmp_size;
enc->alpha_data_ = tmp_data;
}
return 1;
return WebPReportProgress(enc, enc->percent_ + 20);
}
void VP8EncDeleteAlpha(VP8Encoder* enc) {

View File

@ -339,6 +339,7 @@ static void MBAnalyze(VP8EncIterator* const it,
// this stage.
int VP8EncAnalyze(VP8Encoder* const enc) {
int ok = 1;
int alphas[256] = { 0 };
VP8EncIterator it;
@ -347,12 +348,13 @@ int VP8EncAnalyze(VP8Encoder* const enc) {
do {
VP8IteratorImport(&it);
MBAnalyze(&it, alphas, &enc->uv_alpha_);
ok = VP8IteratorProgress(&it, 20);
// Let's pretend we have perfect lossless reconstruction.
} while (VP8IteratorNext(&it, it.yuv_in_));
} while (ok && VP8IteratorNext(&it, it.yuv_in_));
enc->uv_alpha_ /= enc->mb_w_ * enc->mb_h_;
AssignSegments(enc, alphas);
if (ok) AssignSegments(enc, alphas);
return 1;
return ok;
}
#if defined(__cplusplus) || defined(c_plusplus)

View File

@ -560,6 +560,7 @@ static void ResetAfterSkip(VP8EncIterator* const it) {
int VP8EncLoop(VP8Encoder* const enc) {
int i, s, p;
int ok = 1;
VP8EncIterator it;
VP8ModeScore info;
const int dont_use_skip = !enc->proba_.use_skip_proba_;
@ -595,12 +596,17 @@ int VP8EncLoop(VP8Encoder* const enc) {
StoreSideInfo(&it);
VP8StoreFilterStats(&it);
VP8IteratorExport(&it);
} while (VP8IteratorNext(&it, it.yuv_out_));
VP8AdjustFilterStrength(&it);
ok = VP8IteratorProgress(&it, 20);
} while (ok && VP8IteratorNext(&it, it.yuv_out_));
if (ok) {
VP8AdjustFilterStrength(&it);
}
// Finalize the partitions
for (p = 0; p < enc->num_parts_; ++p) {
VP8BitWriterFinish(enc->parts_ + p);
ok &= !enc->parts_[p].error_;
}
// and byte counters
if (enc->pic_->stats) {
@ -610,7 +616,10 @@ int VP8EncLoop(VP8Encoder* const enc) {
}
}
}
return 1;
if (!ok) { // need to do some memory cleanup
VP8EncFreeBitWriters(enc);
}
return ok;
}
//------------------------------------------------------------------------------
@ -622,7 +631,7 @@ int VP8EncLoop(VP8Encoder* const enc) {
#define kHeaderSizeEstimate (15 + 20 + 10) // TODO: fix better
static int OneStatPass(VP8Encoder* const enc, float q, int rd_opt, int nb_mbs,
float* const PSNR) {
float* const PSNR, int percent_delta) {
VP8EncIterator it;
uint64_t size = 0;
uint64_t distortion = 0;
@ -651,6 +660,8 @@ static int OneStatPass(VP8Encoder* const enc, float q, int rd_opt, int nb_mbs,
RecordResiduals(&it, &info);
size += info.R;
distortion += info.D;
if (percent_delta && !VP8IteratorProgress(&it, percent_delta))
return 0;
} while (VP8IteratorNext(&it, it.yuv_out_) && --nb_mbs > 0);
size += FinalizeSkipProba(enc);
size += FinalizeTokenProbas(enc);
@ -671,6 +682,10 @@ int VP8StatLoop(VP8Encoder* const enc) {
(enc->config_->target_size > 0 || enc->config_->target_PSNR > 0);
const int fast_probe = (enc->method_ < 2 && !do_search);
float q = enc->config_->quality;
const int max_passes = enc->config_->pass;
const int task_percent = 20;
const int percent_per_pass = (task_percent + max_passes / 2) / max_passes;
const int final_percent = enc->percent_ + task_percent;
int pass;
int nb_mbs;
@ -680,36 +695,38 @@ int VP8StatLoop(VP8Encoder* const enc) {
// No target size: just do several pass without changing 'q'
if (!do_search) {
for (pass = 0; pass < enc->config_->pass; ++pass) {
for (pass = 0; pass < max_passes; ++pass) {
const int rd_opt = (enc->method_ > 2);
OneStatPass(enc, q, rd_opt, nb_mbs, NULL);
if (!OneStatPass(enc, q, rd_opt, nb_mbs, NULL, percent_per_pass)) {
return 0;
}
}
return 1;
}
// binary search for a size close to target
for (pass = 0; pass < enc->config_->pass && (dqs[pass] > 0); ++pass) {
const int rd_opt = 1;
float PSNR;
int criterion;
const int size = OneStatPass(enc, q, rd_opt, nb_mbs, &PSNR);
} else {
// binary search for a size close to target
for (pass = 0; pass < max_passes && (dqs[pass] > 0); ++pass) {
const int rd_opt = 1;
float PSNR;
int criterion;
const int size = OneStatPass(enc, q, rd_opt, nb_mbs, &PSNR,
percent_per_pass);
#if DEBUG_SEARCH
printf("#%d size=%d PSNR=%.2f q=%.2f\n", pass, size, PSNR, q);
printf("#%d size=%d PSNR=%.2f q=%.2f\n", pass, size, PSNR, q);
#endif
if (enc->config_->target_PSNR > 0) {
criterion = (PSNR < enc->config_->target_PSNR);
} else {
criterion = (size < enc->config_->target_size);
}
// dichotomize
if (criterion) {
q += dqs[pass];
} else {
q -= dqs[pass];
if (!size) return 0;
if (enc->config_->target_PSNR > 0) {
criterion = (PSNR < enc->config_->target_PSNR);
} else {
criterion = (size < enc->config_->target_size);
}
// dichotomize
if (criterion) {
q += dqs[pass];
} else {
q -= dqs[pass];
}
}
}
return 1;
return WebPReportProgress(enc, final_percent);
}
//------------------------------------------------------------------------------

View File

@ -66,9 +66,18 @@ void VP8IteratorInit(VP8Encoder* const enc, VP8EncIterator* const it) {
it->yuv_out2_ = enc->yuv_out2_;
it->yuv_p_ = enc->yuv_p_;
it->lf_stats_ = enc->lf_stats_;
it->percent0_ = enc->percent_;
VP8IteratorReset(it);
}
int VP8IteratorProgress(const VP8EncIterator* const it, int delta) {
if (delta && it->enc_->pic_->progress_hook) {
const int percent = it->percent0_ + delta * it->y_ / (it->enc_->mb_h_ - 1);
return WebPReportProgress(it->enc_, percent);
}
return 1;
}
//------------------------------------------------------------------------------
// Import the source samples into the cache. Takes care of replicating
// boundary pixels if necessary.

View File

@ -18,10 +18,6 @@
extern "C" {
#endif
#ifdef WEBP_EXPERIMENTAL_FEATURES
#endif /* WEBP_EXPERIMENTAL_FEATURES */
//------------------------------------------------------------------------------
void VP8EncInitLayer(VP8Encoder* const enc) {
@ -35,8 +31,6 @@ void VP8EncInitLayer(VP8Encoder* const enc) {
void VP8EncCodeLayerBlock(VP8EncIterator* it) {
(void)it; // remove a warning
#ifdef WEBP_EXPERIMENTAL_FEATURES
#endif /* WEBP_EXPERIMENTAL_FEATURES */
}
int VP8EncFinishLayer(VP8Encoder* const enc) {

View File

@ -356,9 +356,20 @@ static size_t GeneratePartition0(VP8Encoder* const enc) {
return !bw->error_;
}
void VP8EncFreeBitWriters(VP8Encoder* const enc) {
int p;
VP8BitWriterWipeOut(&enc->bw_);
for (p = 0; p < enc->num_parts_; ++p) {
VP8BitWriterWipeOut(enc->parts_ + p);
}
}
int VP8EncWrite(VP8Encoder* const enc) {
WebPPicture* const pic = enc->pic_;
VP8BitWriter* const bw = &enc->bw_;
const int task_percent = 19;
const int percent_per_part = task_percent / enc->num_parts_;
const int final_percent = enc->percent_ + task_percent;
int ok = 0;
size_t vp8_size, pad, riff_size;
int p;
@ -399,7 +410,7 @@ int VP8EncWrite(VP8Encoder* const enc) {
ok = ok && PutWebPHeaders(enc, size0, vp8_size, riff_size)
&& pic->writer(part0, size0, pic)
&& EmitPartitionsSize(enc, pic);
free((void*)part0);
VP8BitWriterWipeOut(bw); // will free the internal buffer.
}
// Token partitions
@ -408,7 +419,8 @@ int VP8EncWrite(VP8Encoder* const enc) {
const size_t size = VP8BitWriterSize(enc->parts_ + p);
if (size)
ok = ok && pic->writer(buf, size, pic);
free((void*)buf);
VP8BitWriterWipeOut(enc->parts_ + p); // will free the internal buffer.
ok = ok && WebPReportProgress(enc, enc->percent_ + percent_per_part);
}
// Padding byte
@ -417,6 +429,7 @@ int VP8EncWrite(VP8Encoder* const enc) {
}
enc->coded_size_ = (int)(CHUNK_HEADER_SIZE + riff_size);
ok = ok && WebPReportProgress(enc, final_percent);
return ok;
}

View File

@ -272,6 +272,7 @@ typedef struct {
LFStats* lf_stats_; // filter stats (borrowed from enc_)
int do_trellis_; // if true, perform extra level optimisation
int done_; // true when scan is finished
int percent0_; // saved initial progress percent
} VP8EncIterator;
// in iterator.c
@ -288,6 +289,9 @@ void VP8IteratorExport(const VP8EncIterator* const it);
// it->yuv_out_ or it->yuv_in_.
int VP8IteratorNext(VP8EncIterator* const it,
const uint8_t* const block_to_save);
// Report progression based on macroblock rows. Return 0 for user-abort request.
int VP8IteratorProgress(const VP8EncIterator* const it,
int final_delta_percent);
// Intra4x4 iterations
void VP8IteratorStartI4(VP8EncIterator* const it);
// returns true if not done.
@ -330,6 +334,8 @@ struct VP8Encoder {
VP8BitWriter bw_; // part0
VP8BitWriter parts_[MAX_NUM_PARTITIONS]; // token partitions
int percent_; // for progress
// transparency blob
int has_alpha_;
uint8_t* alpha_data_; // non-NULL if transparency is present
@ -401,6 +407,8 @@ void VP8CodeIntraModes(VP8Encoder* const enc);
// and appending an assembly of all the pre-coded token partitions.
// Return true if everything is ok.
int VP8EncWrite(VP8Encoder* const enc);
// Release memory allocated for bit-writing in VP8EncLoop & seq.
void VP8EncFreeBitWriters(VP8Encoder* const enc);
// in frame.c
extern const uint8_t VP8EncBands[16 + 1];
@ -422,6 +430,7 @@ int VP8StatLoop(VP8Encoder* const enc);
// in webpenc.c
// Assign an error code to a picture. Return false for convenience.
int WebPEncodingSetError(WebPPicture* const pic, WebPEncodingError error);
int WebPReportProgress(VP8Encoder* const enc, int percent);
// in analysis.c
// Main analysis loop. Decides the segmentations and complexity.

View File

@ -242,6 +242,7 @@ static VP8Encoder* InitEncoder(const WebPConfig* const config,
enc->config_ = config;
enc->profile_ = use_filter ? ((config->filter_type == 1) ? 0 : 1) : 2;
enc->pic_ = picture;
enc->percent_ = 0;
MapConfigToTools(enc);
VP8EncDspInit();
@ -301,15 +302,28 @@ static void StoreStats(VP8Encoder* const enc) {
stats->block_count[i] = enc->block_count_[i];
}
}
WebPReportProgress(enc, 100); // done!
}
int WebPEncodingSetError(WebPPicture* const pic, WebPEncodingError error) {
assert((int)error <= VP8_ENC_ERROR_BAD_WRITE);
assert((int)error < VP8_ENC_ERROR_LAST);
assert((int)error >= VP8_ENC_OK);
pic->error_code = error;
return 0;
}
int WebPReportProgress(VP8Encoder* const enc, int percent) {
if (percent != enc->percent_) {
WebPPicture* const pic = enc->pic_;
enc->percent_ = percent;
if (pic->progress_hook && !pic->progress_hook(percent, pic)) {
// user abort requested
WebPEncodingSetError(pic, VP8_ENC_ERROR_USER_ABORT);
return 0;
}
}
return 1; // ok
}
//------------------------------------------------------------------------------
int WebPEncode(const WebPConfig* const config, WebPPicture* const pic) {
@ -332,6 +346,7 @@ int WebPEncode(const WebPConfig* const config, WebPPicture* const pic) {
enc = InitEncoder(config, pic);
if (enc == NULL) return 0; // pic->error is already set.
// Note: each of the tasks below account for 20% in the progress report.
ok = VP8EncAnalyze(enc)
&& VP8StatLoop(enc)
&& VP8EncLoop(enc)
@ -341,6 +356,9 @@ int WebPEncode(const WebPConfig* const config, WebPPicture* const pic) {
#endif
&& VP8EncWrite(enc);
StoreStats(enc);
if (!ok) {
VP8EncFreeBitWriters(enc);
}
DeleteEncoder(enc);
return ok;

View File

@ -179,6 +179,13 @@ int VP8BitWriterAppend(VP8BitWriter* const bw,
return 1;
}
void VP8BitWriterWipeOut(VP8BitWriter* const bw) {
if (bw) {
free(bw->buf_);
memset(bw, 0, sizeof(*bw));
}
}
//------------------------------------------------------------------------------
#if defined(__cplusplus) || defined(c_plusplus)

View File

@ -27,18 +27,26 @@ struct VP8BitWriter {
int32_t value_;
int run_; // number of outstanding bits
int nb_bits_; // number of pending bits
uint8_t* buf_;
uint8_t* buf_; // internal buffer. Re-allocated regularly. Not owned.
size_t pos_;
size_t max_pos_;
int error_; // true in case of error
};
// Initialize the object. Allocates some initial memory based on expected_size.
int VP8BitWriterInit(VP8BitWriter* const bw, size_t expected_size);
// Finalize the bitstream coding. Returns a pointer to the internal buffer.
uint8_t* VP8BitWriterFinish(VP8BitWriter* const bw);
// Release any pending memory and zeroes the object. Not a mandatory call.
// Only useful in case of error, when the internal buffer hasn't been grabbed!
void VP8BitWriterWipeOut(VP8BitWriter* const bw);
int VP8PutBit(VP8BitWriter* const bw, int bit, int prob);
int VP8PutBitUniform(VP8BitWriter* const bw, int bit);
void VP8PutValue(VP8BitWriter* const bw, int value, int nb_bits);
void VP8PutSignedValue(VP8BitWriter* const bw, int value, int nb_bits);
// Appends some bytes to the internal buffer. Data is copied.
int VP8BitWriterAppend(VP8BitWriter* const bw,
const uint8_t* data, size_t size);
@ -47,9 +55,11 @@ static WEBP_INLINE uint64_t VP8BitWriterPos(const VP8BitWriter* const bw) {
return (uint64_t)(bw->pos_ + bw->run_) * 8 + 8 + bw->nb_bits_;
}
// Returns a pointer to the internal buffer.
static WEBP_INLINE uint8_t* VP8BitWriterBuf(const VP8BitWriter* const bw) {
return bw->buf_;
}
// Returns the size of the internal buffer.
static WEBP_INLINE size_t VP8BitWriterSize(const VP8BitWriter* const bw) {
return bw->pos_;
}

View File

@ -20,7 +20,7 @@
extern "C" {
#endif
#define WEBP_ENCODER_ABI_VERSION 0x0002
#define WEBP_ENCODER_ABI_VERSION 0x0003
// Return the encoder's version number, packed in hexadecimal using 8bits for
// each of major/minor/revision. E.g: v2.5.7 is 0x020507.
@ -72,7 +72,7 @@ typedef struct {
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 encoding the alpha plane (0 = none,
// 1 = Backward reference counts encoded with
// 1 = backward reference counts encoded with
// arithmetic encoder). Default is 1.
int alpha_quality; // Between 0 (smallest size) and 100 (lossless).
// Default is 100.
@ -134,6 +134,9 @@ typedef struct {
int alpha_data_size; // size of the transparency data
int layer_data_size; // size of the enhancement layer data
void* user_data; // this field is free to be set to any value and
// used during callbacks (like progress-report e.g.).
} WebPAuxStats;
// Signature for output function. Should return 1 if writing was successful.
@ -142,6 +145,10 @@ typedef struct {
typedef int (*WebPWriterFunction)(const uint8_t* data, size_t data_size,
const WebPPicture* const picture);
// Progress hook, called from time to time to report progress. It can return 0
// to request an abort of the encoding process, or 1 otherwise if all is OK.
typedef int (*WebPProgressHook)(int percent, const WebPPicture* const picture);
typedef enum {
// chroma sampling
WEBP_YUV420 = 0, // 4:2:0
@ -169,6 +176,8 @@ typedef enum {
VP8_ENC_ERROR_PARTITION_OVERFLOW, // partition is bigger than 16M
VP8_ENC_ERROR_BAD_WRITE, // error while flushing bytes
VP8_ENC_ERROR_FILE_TOO_BIG, // file is bigger than 4G
VP8_ENC_ERROR_USER_ABORT, // abort request by user
VP8_ENC_ERROR_LAST // list terminator. always last.
} WebPEncodingError;
// maximum width/height allowed (inclusive), in pixels
@ -205,6 +214,8 @@ struct WebPPicture {
int uv0_stride;
WebPEncodingError error_code; // error code in case of problem.
WebPProgressHook progress_hook; // if not NULL, called while encoding.
};
// Internal, version-checked, entry point