From 30971c9e950c2a83010dd3f203fd665fa7aedf21 Mon Sep 17 00:00:00 2001 From: Pascal Massimino Date: Thu, 1 Dec 2011 02:24:50 -0800 Subject: [PATCH] Implement progress report (and user abort) New cwebp flag is -progress Change-Id: Ied872cca13f512036860783bbee1bdbccad72768 --- README | 7 ++-- examples/cwebp.c | 19 ++++++++++- man/cwebp.1 | 3 ++ src/enc/alpha.c | 2 +- src/enc/analysis.c | 8 +++-- src/enc/frame.c | 73 ++++++++++++++++++++++++++---------------- src/enc/iterator.c | 9 ++++++ src/enc/layer.c | 6 ---- src/enc/syntax.c | 17 ++++++++-- src/enc/vp8enci.h | 9 ++++++ src/enc/webpenc.c | 20 +++++++++++- src/utils/bit_writer.c | 7 ++++ src/utils/bit_writer.h | 12 ++++++- src/webp/encode.h | 15 +++++++-- 14 files changed, 160 insertions(+), 47 deletions(-) diff --git a/README b/README index 02ad394b..c41d3961 100644 --- a/README +++ b/README @@ -127,6 +127,7 @@ options: -h / -help ............ short help -H / -longhelp ........ long help -q ............. quality factor (0:small..100:big) + -alpha_q ......... Transparency-compression quality (0..100) -preset ....... Preset setting, one of: default, photo, picture, drawing, icon, text @@ -141,20 +142,22 @@ options: -strong ................ use strong filter instead of simple. -partition_limit . limit quality to fit the 512k limit on the first partition (0=no degradation ... 100=full) - -alpha_comp ...... set the transparency-compression - -noalpha ............... discard any transparency information. -pass ............ analysis pass number (1..10) -partitions ...... number of partitions to use (0..3) -crop .. crop picture with the given rectangle -resize ........ resize picture (after any cropping) -map ............. print map of extra info. -d .......... dump the compressed output (PGM file). + -alpha_method .... 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 ............ Target size (in bytes) diff --git a/examples/cwebp.c b/examples/cwebp.c index 212a988f..572a3149 100644 --- a/examples/cwebp.c +++ b/examples/cwebp.c @@ -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) { diff --git a/man/cwebp.1 b/man/cwebp.1 index c4cece69..5c313319 100644 --- a/man/cwebp.1 +++ b/man/cwebp.1 @@ -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 diff --git a/src/enc/alpha.c b/src/enc/alpha.c index c4cb1c39..48cbe76a 100644 --- a/src/enc/alpha.c +++ b/src/enc/alpha.c @@ -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) { diff --git a/src/enc/analysis.c b/src/enc/analysis.c index cfa591c7..30eebb34 100644 --- a/src/enc/analysis.c +++ b/src/enc/analysis.c @@ -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) diff --git a/src/enc/frame.c b/src/enc/frame.c index cc07870b..cc7e50b5 100644 --- a/src/enc/frame.c +++ b/src/enc/frame.c @@ -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); } //------------------------------------------------------------------------------ diff --git a/src/enc/iterator.c b/src/enc/iterator.c index 5e05906a..20df95d6 100644 --- a/src/enc/iterator.c +++ b/src/enc/iterator.c @@ -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. diff --git a/src/enc/layer.c b/src/enc/layer.c index d5d76de2..22f25798 100644 --- a/src/enc/layer.c +++ b/src/enc/layer.c @@ -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) { diff --git a/src/enc/syntax.c b/src/enc/syntax.c index 435057f5..3678cc88 100644 --- a/src/enc/syntax.c +++ b/src/enc/syntax.c @@ -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; } diff --git a/src/enc/vp8enci.h b/src/enc/vp8enci.h index 282d05f2..46094a27 100644 --- a/src/enc/vp8enci.h +++ b/src/enc/vp8enci.h @@ -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. diff --git a/src/enc/webpenc.c b/src/enc/webpenc.c index a9153de4..d5b8ec4b 100644 --- a/src/enc/webpenc.c +++ b/src/enc/webpenc.c @@ -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; diff --git a/src/utils/bit_writer.c b/src/utils/bit_writer.c index 9ed8275a..86752380 100644 --- a/src/utils/bit_writer.c +++ b/src/utils/bit_writer.c @@ -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) diff --git a/src/utils/bit_writer.h b/src/utils/bit_writer.h index 9da27f05..9707bddb 100644 --- a/src/utils/bit_writer.h +++ b/src/utils/bit_writer.h @@ -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_; } diff --git a/src/webp/encode.h b/src/webp/encode.h index 6097bc63..442e70a8 100644 --- a/src/webp/encode.h +++ b/src/webp/encode.h @@ -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