From 7d853d79dc23f50118db9c2832f94b9fb0ae15c4 Mon Sep 17 00:00:00 2001 From: Pascal Massimino <pascal.massimino@gmail.com> Date: Tue, 24 Jul 2012 16:15:36 -0700 Subject: [PATCH] add stats for lossless * Extend AuxStats with new fields it's slightly ABI-incompatible, but i guess it's ok for 0.1.99+ I expect to add more stats later, possibly (predictor stats, etc.) * Have cwebp report the features used by lossless compression (either for alpha or full lossless coding) * Print the PSNR for alpha (useful in case of -alpha_q) * clean-up alpha.c signatures + misc cleanup (added const '* const ptr', etc.) Change-Id: I157a21581f1793cb0c6cc0882e7b0a2dde68a970 --- examples/cwebp.c | 37 ++++++++++++--- src/enc/alpha.c | 117 +++++++++++++++++++++++++--------------------- src/enc/vp8enci.h | 8 ++-- src/enc/vp8l.c | 41 ++++++++++++---- src/enc/vp8li.h | 1 + src/enc/webpenc.c | 1 + src/webp/encode.h | 13 +++++- 7 files changed, 143 insertions(+), 75 deletions(-) diff --git a/examples/cwebp.c b/examples/cwebp.c index 86f5b7ae..9f933cff 100644 --- a/examples/cwebp.c +++ b/examples/cwebp.c @@ -614,6 +614,25 @@ static void PrintValues(const int values[4]) { fprintf(stderr, "|\n"); } +static void PrintFullLosslessInfo(const WebPAuxStats* const stats, + const char* const description) { + fprintf(stderr, "Lossless-%s compressed size: %d bytes\n", + description, stats->lossless_size); + if (stats->lossless_features) { + fprintf(stderr, " * Lossless features used:"); + if (stats->lossless_features & 1) fprintf(stderr, " PREDICTION"); + if (stats->lossless_features & 2) fprintf(stderr, " CROSS-COLOR-TRANSFORM"); + if (stats->lossless_features & 4) fprintf(stderr, " SUBTRACT-GREEN"); + if (stats->lossless_features & 8) fprintf(stderr, " PALETTE"); + fprintf(stderr, "\n"); + } + fprintf(stderr, " * Precision Bits: histogram=%d transform=%d cache=%d\n", + stats->histogram_bits, stats->transform_bits, stats->cache_bits); + if (stats->palette_size > 0) { + fprintf(stderr, " * Palette size: %d\n", stats->palette_size); + } +} + static void PrintExtraInfoLossless(const WebPPicture* const pic, int short_output, const char* const file_name) { @@ -624,6 +643,7 @@ static void PrintExtraInfoLossless(const WebPPicture* const pic, fprintf(stderr, "File: %s\n", file_name); fprintf(stderr, "Dimension: %d x %d\n", pic->width, pic->height); fprintf(stderr, "Output: %d bytes\n", stats->coded_size); + PrintFullLosslessInfo(stats, "ARGB"); } } @@ -658,9 +678,9 @@ static void PrintExtraInfoLossy(const WebPPicture* const pic, int short_output, 100.f * stats->header_bytes[0] / stats->coded_size, stats->header_bytes[1], 100.f * stats->header_bytes[1] / stats->coded_size); - if (stats->alpha_data_size) { - fprintf(stderr, " transparency: %6d\n", - stats->alpha_data_size); + if (stats->alpha_data_size > 0) { + fprintf(stderr, " transparency: %6d (%.1f dB)\n", + stats->alpha_data_size, stats->PSNR[4]); } if (stats->layer_data_size) { fprintf(stderr, " enhancement: %6d\n", @@ -686,8 +706,11 @@ static void PrintExtraInfoLossy(const WebPPicture* const pic, int short_output, fprintf(stderr, " segments total: "); PrintByteCount(totals, stats->coded_size, NULL); } + if (stats->lossless_size > 0) { + PrintFullLosslessInfo(stats, "alpha"); + } } - if (pic->extra_info) { + if (pic->extra_info != NULL) { const int mb_w = (pic->width + 15) / 16; const int mb_h = (pic->height + 15) / 16; const int type = pic->extra_info_type; @@ -1100,8 +1123,10 @@ int main(int argc, const char *argv[]) { fprintf(stderr, "be performed, but its results discarded.\n\n"); } } - picture.stats = &stats; - stats.user_data = (void*)in_file; + if (!quiet) { + picture.stats = &stats; + stats.user_data = (void*)in_file; + } // Compress if (verbose) { diff --git a/src/enc/alpha.c b/src/enc/alpha.c index b31cee8c..62d5c8fa 100644 --- a/src/enc/alpha.c +++ b/src/enc/alpha.c @@ -22,19 +22,15 @@ extern "C" { #endif // ----------------------------------------------------------------------------- -// int EncodeAlpha(const uint8_t* data, int width, int height, int stride, -// int quality, int method, int filter, int effort_level, -// uint8_t** output, size_t* output_size) -// -// Encodes the given alpha data 'data' of size 'stride'x'height' via specified -// compression method 'method'. The pre-processing (Quantization) is -// performed if 'quality' is less than 100. For such cases, the encoding is -// lossy. Valid ranges for 'quality' is [0, 100] and 'method' is [0, 1]: +// Encodes the given alpha data via specified compression method 'method'. +// The pre-processing (quantization) is performed if 'quality' is less than 100. +// For such cases, the encoding is lossy. The valid range is [0, 100] for +// 'quality' and [0, 1] for 'method': // 'method = 0' - No compression; // 'method = 1' - Use lossless coder on the alpha plane only // 'filter' values [0, 4] correspond to prediction modes none, horizontal, // vertical & gradient filters. The prediction mode 4 will try all the -// prediction modes (0 to 3) and pick the best prediction mode. +// prediction modes 0 to 3 and pick the best one. // 'effort_level': specifies how much effort must be spent to try and reduce // the compressed output size. In range 0 (quick) to 6 (slow). // @@ -50,10 +46,10 @@ extern "C" { #include "../enc/vp8li.h" -static int EncodeLossless(const uint8_t* data, int width, int height, +static int EncodeLossless(const uint8_t* const data, int width, int height, int effort_level, // in [0..6] range - VP8BitWriter* const bw) { - + VP8BitWriter* const bw, + WebPAuxStats* const stats) { int ok = 0; WebPConfig config; WebPPicture picture; @@ -63,6 +59,7 @@ static int EncodeLossless(const uint8_t* data, int width, int height, picture.width = width; picture.height = height; picture.use_argb = 1; + picture.stats = stats; if (!WebPPictureAlloc(&picture)) return 0; // Transfer the alpha values to the green channel. @@ -101,10 +98,12 @@ static int EncodeLossless(const uint8_t* data, int width, int height, // ----------------------------------------------------------------------------- -static int EncodeAlphaInternal(const uint8_t* data, int width, int height, +static int EncodeAlphaInternal(const uint8_t* const data, int width, int height, int method, int filter, int reduce_levels, int effort_level, // in [0..6] range - uint8_t* tmp_alpha, VP8BitWriter* const bw) { + uint8_t* const tmp_alpha, + VP8BitWriter* const bw, + WebPAuxStats* const stats) { int ok = 0; const uint8_t* alpha_src; WebPFilterFunc filter_func; @@ -139,7 +138,7 @@ static int EncodeAlphaInternal(const uint8_t* data, int width, int height, ok = VP8BitWriterAppend(bw, alpha_src, width * height); ok = ok && !bw->error_; } else { - ok = EncodeLossless(alpha_src, width, height, effort_level, bw); + ok = EncodeLossless(alpha_src, width, height, effort_level, bw, stats); VP8BitWriterFinish(bw); } return ok; @@ -157,19 +156,25 @@ static void CopyPlane(const uint8_t* src, int src_stride, } } -static int EncodeAlpha(const uint8_t* data, int width, int height, int stride, +static int EncodeAlpha(VP8Encoder* const enc, int quality, int method, int filter, int effort_level, - uint8_t** output, size_t* output_size) { + uint8_t** const output, size_t* const output_size) { + const WebPPicture* const pic = enc->pic_; + const int width = pic->width; + const int height = pic->height; + uint8_t* quant_alpha = NULL; const size_t data_size = width * height; + uint64_t sse = 0; int ok = 1; const int reduce_levels = (quality < 100); // quick sanity checks - assert(data != NULL && output != NULL && output_size != NULL); + assert(enc != NULL && pic != NULL && pic->a != NULL); + assert(output != NULL && output_size != NULL); assert(width > 0 && height > 0); - assert(stride >= width); + assert(pic->a_stride >= width); assert(filter >= WEBP_FILTER_NONE && filter <= WEBP_FILTER_FAST); if (quality < 0 || quality > 100) { @@ -186,7 +191,7 @@ static int EncodeAlpha(const uint8_t* data, int width, int height, int stride, } // Extract alpha data (width x height) from raw_data (stride x height). - CopyPlane(data, stride, quant_alpha, width, width, height); + CopyPlane(pic->a, pic->a_stride, quant_alpha, width, width, height); if (reduce_levels) { // No Quantization required for 'quality = 100'. // 16 alpha levels gives quite a low MSE w.r.t original alpha plane hence @@ -194,24 +199,22 @@ static int EncodeAlpha(const uint8_t* data, int width, int height, int stride, // and Quality:]70, 100] -> Levels:]16, 256]. const int alpha_levels = (quality <= 70) ? (2 + quality / 5) : (16 + (quality - 70) * 8); - ok = QuantizeLevels(quant_alpha, width, height, alpha_levels, NULL); + ok = QuantizeLevels(quant_alpha, width, height, alpha_levels, &sse); } if (ok) { VP8BitWriter bw; - size_t best_score; int test_filter; uint8_t* filtered_alpha = NULL; // We always test WEBP_FILTER_NONE first. ok = EncodeAlphaInternal(quant_alpha, width, height, method, WEBP_FILTER_NONE, reduce_levels, - effort_level, NULL, &bw); + effort_level, NULL, &bw, pic->stats); if (!ok) { VP8BitWriterWipeOut(&bw); goto End; } - best_score = VP8BitWriterSize(&bw); if (filter == WEBP_FILTER_FAST) { // Quick estimate of a second candidate? filter = EstimateBestFilter(quant_alpha, width, height, width); @@ -228,35 +231,46 @@ static int EncodeAlpha(const uint8_t* data, int width, int height, int stride, } // Try the other mode(s). - for (test_filter = WEBP_FILTER_HORIZONTAL; - ok && (test_filter <= WEBP_FILTER_GRADIENT); - ++test_filter) { - VP8BitWriter tmp_bw; - if (filter != WEBP_FILTER_BEST && test_filter != filter) { - continue; - } - - ok = EncodeAlphaInternal(quant_alpha, width, height, - method, test_filter, reduce_levels, - effort_level, filtered_alpha, &tmp_bw); - if (ok) { - const size_t score = VP8BitWriterSize(&tmp_bw); - if (score < best_score) { - // swap bitwriter objects. - VP8BitWriter tmp = tmp_bw; - tmp_bw = bw; - bw = tmp; - best_score = score; + { + WebPAuxStats best_stats; + size_t best_score = VP8BitWriterSize(&bw); + if (pic->stats != NULL) best_stats = *pic->stats; + for (test_filter = WEBP_FILTER_HORIZONTAL; + ok && (test_filter <= WEBP_FILTER_GRADIENT); + ++test_filter) { + VP8BitWriter tmp_bw; + if (filter != WEBP_FILTER_BEST && test_filter != filter) { + continue; } - } else { - VP8BitWriterWipeOut(&bw); + ok = EncodeAlphaInternal(quant_alpha, width, height, + method, test_filter, reduce_levels, + effort_level, filtered_alpha, &tmp_bw, + pic->stats); + if (ok) { + const size_t score = VP8BitWriterSize(&tmp_bw); + if (score < best_score) { + // swap bitwriter objects. + VP8BitWriter tmp = tmp_bw; + tmp_bw = bw; + bw = tmp; + best_score = score; + if (pic->stats != NULL) best_stats = *pic->stats; + } + } else { + VP8BitWriterWipeOut(&bw); + } + VP8BitWriterWipeOut(&tmp_bw); } - VP8BitWriterWipeOut(&tmp_bw); + if (pic->stats != NULL) *pic->stats = best_stats; } Ok: if (ok) { *output_size = VP8BitWriterSize(&bw); *output = VP8BitWriterBuf(&bw); + if (pic->stats != NULL) { // need stats? + pic->stats->coded_size += *output_size; + enc->sse_[3] = sse; + } } free(filtered_alpha); } @@ -269,16 +283,15 @@ static int EncodeAlpha(const uint8_t* data, int width, int height, int stride, //------------------------------------------------------------------------------ // Main calls -void VP8EncInitAlpha(VP8Encoder* enc) { +void VP8EncInitAlpha(VP8Encoder* const enc) { enc->has_alpha_ = WebPPictureHasTransparency(enc->pic_); enc->alpha_data_ = NULL; enc->alpha_data_size_ = 0; } -int VP8EncFinishAlpha(VP8Encoder* enc) { +int VP8EncFinishAlpha(VP8Encoder* const enc) { if (enc->has_alpha_) { const WebPConfig* config = enc->config_; - const WebPPicture* pic = enc->pic_; uint8_t* tmp_data = NULL; size_t tmp_size = 0; const int effort_level = config->method; // maps to [0..6] @@ -287,9 +300,7 @@ int VP8EncFinishAlpha(VP8Encoder* enc) { (config->alpha_filtering == 1) ? WEBP_FILTER_FAST : WEBP_FILTER_BEST; - assert(pic->a); - if (!EncodeAlpha(pic->a, pic->width, pic->height, pic->a_stride, - config->alpha_quality, config->alpha_compression, + if (!EncodeAlpha(enc, config->alpha_quality, config->alpha_compression, filter, effort_level, &tmp_data, &tmp_size)) { return 0; } @@ -303,7 +314,7 @@ int VP8EncFinishAlpha(VP8Encoder* enc) { return WebPReportProgress(enc->pic_, enc->percent_ + 20, &enc->percent_); } -void VP8EncDeleteAlpha(VP8Encoder* enc) { +void VP8EncDeleteAlpha(VP8Encoder* const enc) { free(enc->alpha_data_); enc->alpha_data_ = NULL; enc->alpha_data_size_ = 0; diff --git a/src/enc/vp8enci.h b/src/enc/vp8enci.h index 17ae2621..56f2f11b 100644 --- a/src/enc/vp8enci.h +++ b/src/enc/vp8enci.h @@ -402,7 +402,7 @@ struct VP8Encoder { // probabilities and statistics VP8Proba proba_; - uint64_t sse_[3]; // sum of Y/U/V squared errors for all macroblocks + uint64_t sse_[4]; // sum of Y/U/V/A squared errors for all macroblocks uint64_t sse_count_; // pixel count for the sse_[] stats int coded_size_; int residual_bytes_[3][4]; @@ -488,9 +488,9 @@ void VP8SetSegmentParams(VP8Encoder* const enc, float quality); int VP8Decimate(VP8EncIterator* const it, VP8ModeScore* const rd, int rd_opt); // in alpha.c -void VP8EncInitAlpha(VP8Encoder* enc); // initialize alpha compression -int VP8EncFinishAlpha(VP8Encoder* enc); // finalize compressed data -void VP8EncDeleteAlpha(VP8Encoder* enc); // delete compressed data +void VP8EncInitAlpha(VP8Encoder* const enc); // initialize alpha compression +int VP8EncFinishAlpha(VP8Encoder* const enc); // finalize compressed data +void VP8EncDeleteAlpha(VP8Encoder* const enc); // delete compressed data // in layer.c void VP8EncInitLayer(VP8Encoder* const enc); // init everything diff --git a/src/enc/vp8l.c b/src/enc/vp8l.c index 09e26ec3..afe810ed 100644 --- a/src/enc/vp8l.c +++ b/src/enc/vp8l.c @@ -610,7 +610,7 @@ static int EncodeImageInternal(VP8LBitWriter* const bw, // Check if it would be a good idea to subtract green from red and blue. We // only impact entropy in red/blue components, don't bother to look at others. -static int EvalAndApplySubtractGreen(const VP8LEncoder* const enc, +static int EvalAndApplySubtractGreen(VP8LEncoder* const enc, int width, int height, VP8LBitWriter* const bw) { if (!enc->use_palette_) { @@ -639,7 +639,8 @@ static int EvalAndApplySubtractGreen(const VP8LEncoder* const enc, free(histo); // Check if subtracting green yields low entropy. - if (bit_cost_after < bit_cost_before) { + enc->use_subtract_green_ = (bit_cost_after < bit_cost_before); + if (enc->use_subtract_green_) { VP8LWriteBits(bw, 1, TRANSFORM_PRESENT); VP8LWriteBits(bw, 2, SUBTRACT_GREEN); VP8LSubtractGreenFromBlueAndRed(enc->argb_, width * height); @@ -938,6 +939,7 @@ WebPEncodingError VP8LEncodeStream(const WebPConfig* const config, const int width = picture->width; const int height = picture->height; VP8LEncoder* const enc = VP8LEncoderNew(config, picture); + const size_t byte_position = VP8LBitWriterNumBytes(bw); if (enc == NULL) { err = VP8_ENC_ERROR_OUT_OF_MEMORY; @@ -1017,6 +1019,20 @@ WebPEncodingError VP8LEncodeStream(const WebPConfig* const config, goto Error; } + if (picture->stats != NULL) { + WebPAuxStats* const stats = picture->stats; + stats->lossless_features = 0; + if (enc->use_predict_) stats->lossless_features |= 1; + if (enc->use_cross_color_) stats->lossless_features |= 2; + if (enc->use_subtract_green_) stats->lossless_features |= 4; + if (enc->use_palette_) stats->lossless_features |= 8; + stats->histogram_bits = enc->histo_bits_; + stats->transform_bits = enc->transform_bits_; + stats->cache_bits = enc->cache_bits_; + stats->palette_size = enc->palette_size_; + stats->lossless_size = VP8LBitWriterNumBytes(bw) - byte_position; + } + Error: VP8LEncoderDelete(enc); return err; @@ -1045,6 +1061,16 @@ int VP8LEncodeImage(const WebPConfig* const config, err = VP8_ENC_ERROR_USER_ABORT; goto Error; } + // Reset stats (for pure lossless coding) + if (picture->stats != NULL) { + WebPAuxStats* const stats = picture->stats; + memset(stats, 0, sizeof(*stats)); + stats->PSNR[0] = 99.; + stats->PSNR[1] = 99.; + stats->PSNR[2] = 99.; + stats->PSNR[3] = 99.; + stats->PSNR[4] = 99.; + } // Write image size. VP8LBitWriterInit(&bw, (width * height) >> 1); @@ -1075,15 +1101,10 @@ int VP8LEncodeImage(const WebPConfig* const config, if (!WebPReportProgress(picture, 100, &percent)) goto UserAbort; - // Collect some stats if needed. + // Save size. if (picture->stats != NULL) { - WebPAuxStats* const stats = picture->stats; - memset(stats, 0, sizeof(*stats)); - stats->PSNR[0] = 99.; - stats->PSNR[1] = 99.; - stats->PSNR[2] = 99.; - stats->PSNR[3] = 99.; - stats->coded_size = (int)coded_size; + picture->stats->coded_size += (int)coded_size; + picture->stats->lossless_size = (int)coded_size; } if (picture->extra_info != NULL) { diff --git a/src/enc/vp8li.h b/src/enc/vp8li.h index 083ff595..eae90dd6 100644 --- a/src/enc/vp8li.h +++ b/src/enc/vp8li.h @@ -38,6 +38,7 @@ typedef struct { // Encoding parameters derived from image characteristics. int use_cross_color_; + int use_subtract_green_; int use_predict_; int use_palette_; int palette_size_; diff --git a/src/enc/webpenc.c b/src/enc/webpenc.c index 99ab170b..a00ac640 100644 --- a/src/enc/webpenc.c +++ b/src/enc/webpenc.c @@ -284,6 +284,7 @@ static void FinalizePSNR(const VP8Encoder* const enc) { stats->PSNR[1] = (float)GetPSNR(sse[1], size / 4); stats->PSNR[2] = (float)GetPSNR(sse[2], size / 4); stats->PSNR[3] = (float)GetPSNR(sse[0] + sse[1] + sse[2], size * 3 / 2); + stats->PSNR[4] = (float)GetPSNR(sse[3], size); } static void StoreStats(VP8Encoder* const enc) { diff --git a/src/webp/encode.h b/src/webp/encode.h index d2857659..1ee42819 100644 --- a/src/webp/encode.h +++ b/src/webp/encode.h @@ -157,7 +157,7 @@ typedef struct WebPPicture WebPPicture; // main structure for I/O typedef struct { int coded_size; // final size - float PSNR[4]; // peak-signal-to-noise ratio for Y/U/V/All + float PSNR[5]; // peak-signal-to-noise ratio for Y/U/V/All/Alpha int block_count[3]; // number of intra4/intra16/skipped macroblocks int header_bytes[2]; // approximate number of bytes spent for header // and mode-partition #0 @@ -173,7 +173,16 @@ typedef struct { void* user_data; // this field is free to be set to any value and // used during callbacks (like progress-report e.g.). - uint32_t pad[6]; // padding for later use + // lossless encoder statistics + uint32_t lossless_features; // bit0:predictor bit1:cross-color transform + // bit2:subtract-green bit3:color indexing + int histogram_bits; // number of precision bits of histogram + int transform_bits; // precision bits for transform + int cache_bits; // number of bits for color cache lookup + int palette_size; // number of color in palette, if used + int lossless_size; // final lossless size + + uint32_t pad[4]; // padding for later use } WebPAuxStats; // Signature for output function. Should return true if writing was successful.