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.