mirror of
https://github.com/webmproject/libwebp.git
synced 2025-07-12 22:14:29 +02:00
Implement progress report (and user abort)
New cwebp flag is -progress Change-Id: Ied872cca13f512036860783bbee1bdbccad72768
This commit is contained in:
committed by
James Zern
parent
eda520a92e
commit
30971c9e95
@ -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) {
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
@ -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.
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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_;
|
||||
}
|
||||
|
@ -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
|
||||
|
Reference in New Issue
Block a user