Home | History | Annotate | Download | only in enc
      1 // Copyright 2011 Google Inc. All Rights Reserved.
      2 //
      3 // Use of this source code is governed by a BSD-style license
      4 // that can be found in the COPYING file in the root of the source
      5 // tree. An additional intellectual property rights grant can be found
      6 // in the file PATENTS. All contributing project authors may
      7 // be found in the AUTHORS file in the root of the source tree.
      8 // -----------------------------------------------------------------------------
      9 //
     10 // Alpha-plane compression.
     11 //
     12 // Author: Skal (pascal.massimino (at) gmail.com)
     13 
     14 #include <assert.h>
     15 #include <stdlib.h>
     16 
     17 #include "./vp8enci.h"
     18 #include "../dsp/dsp.h"
     19 #include "../utils/filters.h"
     20 #include "../utils/quant_levels.h"
     21 #include "../utils/utils.h"
     22 #include "../webp/format_constants.h"
     23 
     24 // -----------------------------------------------------------------------------
     25 // Encodes the given alpha data via specified compression method 'method'.
     26 // The pre-processing (quantization) is performed if 'quality' is less than 100.
     27 // For such cases, the encoding is lossy. The valid range is [0, 100] for
     28 // 'quality' and [0, 1] for 'method':
     29 //   'method = 0' - No compression;
     30 //   'method = 1' - Use lossless coder on the alpha plane only
     31 // 'filter' values [0, 4] correspond to prediction modes none, horizontal,
     32 // vertical & gradient filters. The prediction mode 4 will try all the
     33 // prediction modes 0 to 3 and pick the best one.
     34 // 'effort_level': specifies how much effort must be spent to try and reduce
     35 //  the compressed output size. In range 0 (quick) to 6 (slow).
     36 //
     37 // 'output' corresponds to the buffer containing compressed alpha data.
     38 //          This buffer is allocated by this method and caller should call
     39 //          WebPSafeFree(*output) when done.
     40 // 'output_size' corresponds to size of this compressed alpha buffer.
     41 //
     42 // Returns 1 on successfully encoding the alpha and
     43 //         0 if either:
     44 //           invalid quality or method, or
     45 //           memory allocation for the compressed data fails.
     46 
     47 #include "../enc/vp8li.h"
     48 
     49 static int EncodeLossless(const uint8_t* const data, int width, int height,
     50                           int effort_level,  // in [0..6] range
     51                           VP8LBitWriter* const bw,
     52                           WebPAuxStats* const stats) {
     53   int ok = 0;
     54   WebPConfig config;
     55   WebPPicture picture;
     56 
     57   WebPPictureInit(&picture);
     58   picture.width = width;
     59   picture.height = height;
     60   picture.use_argb = 1;
     61   picture.stats = stats;
     62   if (!WebPPictureAlloc(&picture)) return 0;
     63 
     64   // Transfer the alpha values to the green channel.
     65   WebPDispatchAlphaToGreen(data, width, picture.width, picture.height,
     66                            picture.argb, picture.argb_stride);
     67 
     68   WebPConfigInit(&config);
     69   config.lossless = 1;
     70   // Enable exact, or it would alter RGB values of transparent alpha, which is
     71   // normally OK but not here since we are not encoding the input image but  an
     72   // internal encoding-related image containing necessary exact information in
     73   // RGB channels.
     74   config.exact = 1;
     75   config.method = effort_level;  // impact is very small
     76   // Set a low default quality for encoding alpha. Ensure that Alpha quality at
     77   // lower methods (3 and below) is less than the threshold for triggering
     78   // costly 'BackwardReferencesTraceBackwards'.
     79   config.quality = 8.f * effort_level;
     80   assert(config.quality >= 0 && config.quality <= 100.f);
     81 
     82   ok = (VP8LEncodeStream(&config, &picture, bw) == VP8_ENC_OK);
     83   WebPPictureFree(&picture);
     84   ok = ok && !bw->error_;
     85   if (!ok) {
     86     VP8LBitWriterWipeOut(bw);
     87     return 0;
     88   }
     89   return 1;
     90 }
     91 
     92 // -----------------------------------------------------------------------------
     93 
     94 // Small struct to hold the result of a filter mode compression attempt.
     95 typedef struct {
     96   size_t score;
     97   VP8BitWriter bw;
     98   WebPAuxStats stats;
     99 } FilterTrial;
    100 
    101 // This function always returns an initialized 'bw' object, even upon error.
    102 static int EncodeAlphaInternal(const uint8_t* const data, int width, int height,
    103                                int method, int filter, int reduce_levels,
    104                                int effort_level,  // in [0..6] range
    105                                uint8_t* const tmp_alpha,
    106                                FilterTrial* result) {
    107   int ok = 0;
    108   const uint8_t* alpha_src;
    109   WebPFilterFunc filter_func;
    110   uint8_t header;
    111   const size_t data_size = width * height;
    112   const uint8_t* output = NULL;
    113   size_t output_size = 0;
    114   VP8LBitWriter tmp_bw;
    115 
    116   assert((uint64_t)data_size == (uint64_t)width * height);  // as per spec
    117   assert(filter >= 0 && filter < WEBP_FILTER_LAST);
    118   assert(method >= ALPHA_NO_COMPRESSION);
    119   assert(method <= ALPHA_LOSSLESS_COMPRESSION);
    120   assert(sizeof(header) == ALPHA_HEADER_LEN);
    121   // TODO(skal): have a common function and #define's to validate alpha params.
    122 
    123   filter_func = WebPFilters[filter];
    124   if (filter_func != NULL) {
    125     filter_func(data, width, height, width, tmp_alpha);
    126     alpha_src = tmp_alpha;
    127   }  else {
    128     alpha_src = data;
    129   }
    130 
    131   if (method != ALPHA_NO_COMPRESSION) {
    132     ok = VP8LBitWriterInit(&tmp_bw, data_size >> 3);
    133     ok = ok && EncodeLossless(alpha_src, width, height, effort_level,
    134                               &tmp_bw, &result->stats);
    135     if (ok) {
    136       output = VP8LBitWriterFinish(&tmp_bw);
    137       output_size = VP8LBitWriterNumBytes(&tmp_bw);
    138       if (output_size > data_size) {
    139         // compressed size is larger than source! Revert to uncompressed mode.
    140         method = ALPHA_NO_COMPRESSION;
    141         VP8LBitWriterWipeOut(&tmp_bw);
    142       }
    143     } else {
    144       VP8LBitWriterWipeOut(&tmp_bw);
    145       return 0;
    146     }
    147   }
    148 
    149   if (method == ALPHA_NO_COMPRESSION) {
    150     output = alpha_src;
    151     output_size = data_size;
    152     ok = 1;
    153   }
    154 
    155   // Emit final result.
    156   header = method | (filter << 2);
    157   if (reduce_levels) header |= ALPHA_PREPROCESSED_LEVELS << 4;
    158 
    159   VP8BitWriterInit(&result->bw, ALPHA_HEADER_LEN + output_size);
    160   ok = ok && VP8BitWriterAppend(&result->bw, &header, ALPHA_HEADER_LEN);
    161   ok = ok && VP8BitWriterAppend(&result->bw, output, output_size);
    162 
    163   if (method != ALPHA_NO_COMPRESSION) {
    164     VP8LBitWriterWipeOut(&tmp_bw);
    165   }
    166   ok = ok && !result->bw.error_;
    167   result->score = VP8BitWriterSize(&result->bw);
    168   return ok;
    169 }
    170 
    171 // -----------------------------------------------------------------------------
    172 
    173 static int GetNumColors(const uint8_t* data, int width, int height,
    174                         int stride) {
    175   int j;
    176   int colors = 0;
    177   uint8_t color[256] = { 0 };
    178 
    179   for (j = 0; j < height; ++j) {
    180     int i;
    181     const uint8_t* const p = data + j * stride;
    182     for (i = 0; i < width; ++i) {
    183       color[p[i]] = 1;
    184     }
    185   }
    186   for (j = 0; j < 256; ++j) {
    187     if (color[j] > 0) ++colors;
    188   }
    189   return colors;
    190 }
    191 
    192 #define FILTER_TRY_NONE (1 << WEBP_FILTER_NONE)
    193 #define FILTER_TRY_ALL ((1 << WEBP_FILTER_LAST) - 1)
    194 
    195 // Given the input 'filter' option, return an OR'd bit-set of filters to try.
    196 static uint32_t GetFilterMap(const uint8_t* alpha, int width, int height,
    197                              int filter, int effort_level) {
    198   uint32_t bit_map = 0U;
    199   if (filter == WEBP_FILTER_FAST) {
    200     // Quick estimate of the best candidate.
    201     int try_filter_none = (effort_level > 3);
    202     const int kMinColorsForFilterNone = 16;
    203     const int kMaxColorsForFilterNone = 192;
    204     const int num_colors = GetNumColors(alpha, width, height, width);
    205     // For low number of colors, NONE yields better compression.
    206     filter = (num_colors <= kMinColorsForFilterNone)
    207         ? WEBP_FILTER_NONE
    208         : WebPEstimateBestFilter(alpha, width, height, width);
    209     bit_map |= 1 << filter;
    210     // For large number of colors, try FILTER_NONE in addition to the best
    211     // filter as well.
    212     if (try_filter_none || num_colors > kMaxColorsForFilterNone) {
    213       bit_map |= FILTER_TRY_NONE;
    214     }
    215   } else if (filter == WEBP_FILTER_NONE) {
    216     bit_map = FILTER_TRY_NONE;
    217   } else {  // WEBP_FILTER_BEST -> try all
    218     bit_map = FILTER_TRY_ALL;
    219   }
    220   return bit_map;
    221 }
    222 
    223 static void InitFilterTrial(FilterTrial* const score) {
    224   score->score = (size_t)~0U;
    225   VP8BitWriterInit(&score->bw, 0);
    226 }
    227 
    228 static int ApplyFiltersAndEncode(const uint8_t* alpha, int width, int height,
    229                                  size_t data_size, int method, int filter,
    230                                  int reduce_levels, int effort_level,
    231                                  uint8_t** const output,
    232                                  size_t* const output_size,
    233                                  WebPAuxStats* const stats) {
    234   int ok = 1;
    235   FilterTrial best;
    236   uint32_t try_map =
    237       GetFilterMap(alpha, width, height, filter, effort_level);
    238   InitFilterTrial(&best);
    239 
    240   if (try_map != FILTER_TRY_NONE) {
    241     uint8_t* filtered_alpha =  (uint8_t*)WebPSafeMalloc(1ULL, data_size);
    242     if (filtered_alpha == NULL) return 0;
    243 
    244     for (filter = WEBP_FILTER_NONE; ok && try_map; ++filter, try_map >>= 1) {
    245       if (try_map & 1) {
    246         FilterTrial trial;
    247         ok = EncodeAlphaInternal(alpha, width, height, method, filter,
    248                                  reduce_levels, effort_level, filtered_alpha,
    249                                  &trial);
    250         if (ok && trial.score < best.score) {
    251           VP8BitWriterWipeOut(&best.bw);
    252           best = trial;
    253         } else {
    254           VP8BitWriterWipeOut(&trial.bw);
    255         }
    256       }
    257     }
    258     WebPSafeFree(filtered_alpha);
    259   } else {
    260     ok = EncodeAlphaInternal(alpha, width, height, method, WEBP_FILTER_NONE,
    261                              reduce_levels, effort_level, NULL, &best);
    262   }
    263   if (ok) {
    264     if (stats != NULL) {
    265       stats->lossless_features = best.stats.lossless_features;
    266       stats->histogram_bits = best.stats.histogram_bits;
    267       stats->transform_bits = best.stats.transform_bits;
    268       stats->cache_bits = best.stats.cache_bits;
    269       stats->palette_size = best.stats.palette_size;
    270       stats->lossless_size = best.stats.lossless_size;
    271       stats->lossless_hdr_size = best.stats.lossless_hdr_size;
    272       stats->lossless_data_size = best.stats.lossless_data_size;
    273     }
    274     *output_size = VP8BitWriterSize(&best.bw);
    275     *output = VP8BitWriterBuf(&best.bw);
    276   } else {
    277     VP8BitWriterWipeOut(&best.bw);
    278   }
    279   return ok;
    280 }
    281 
    282 static int EncodeAlpha(VP8Encoder* const enc,
    283                        int quality, int method, int filter,
    284                        int effort_level,
    285                        uint8_t** const output, size_t* const output_size) {
    286   const WebPPicture* const pic = enc->pic_;
    287   const int width = pic->width;
    288   const int height = pic->height;
    289 
    290   uint8_t* quant_alpha = NULL;
    291   const size_t data_size = width * height;
    292   uint64_t sse = 0;
    293   int ok = 1;
    294   const int reduce_levels = (quality < 100);
    295 
    296   // quick sanity checks
    297   assert((uint64_t)data_size == (uint64_t)width * height);  // as per spec
    298   assert(enc != NULL && pic != NULL && pic->a != NULL);
    299   assert(output != NULL && output_size != NULL);
    300   assert(width > 0 && height > 0);
    301   assert(pic->a_stride >= width);
    302   assert(filter >= WEBP_FILTER_NONE && filter <= WEBP_FILTER_FAST);
    303 
    304   if (quality < 0 || quality > 100) {
    305     return 0;
    306   }
    307 
    308   if (method < ALPHA_NO_COMPRESSION || method > ALPHA_LOSSLESS_COMPRESSION) {
    309     return 0;
    310   }
    311 
    312   if (method == ALPHA_NO_COMPRESSION) {
    313     // Don't filter, as filtering will make no impact on compressed size.
    314     filter = WEBP_FILTER_NONE;
    315   }
    316 
    317   quant_alpha = (uint8_t*)WebPSafeMalloc(1ULL, data_size);
    318   if (quant_alpha == NULL) {
    319     return 0;
    320   }
    321 
    322   // Extract alpha data (width x height) from raw_data (stride x height).
    323   WebPCopyPlane(pic->a, pic->a_stride, quant_alpha, width, width, height);
    324 
    325   if (reduce_levels) {  // No Quantization required for 'quality = 100'.
    326     // 16 alpha levels gives quite a low MSE w.r.t original alpha plane hence
    327     // mapped to moderate quality 70. Hence Quality:[0, 70] -> Levels:[2, 16]
    328     // and Quality:]70, 100] -> Levels:]16, 256].
    329     const int alpha_levels = (quality <= 70) ? (2 + quality / 5)
    330                                              : (16 + (quality - 70) * 8);
    331     ok = QuantizeLevels(quant_alpha, width, height, alpha_levels, &sse);
    332   }
    333 
    334   if (ok) {
    335     VP8FiltersInit();
    336     ok = ApplyFiltersAndEncode(quant_alpha, width, height, data_size, method,
    337                                filter, reduce_levels, effort_level, output,
    338                                output_size, pic->stats);
    339     if (pic->stats != NULL) {  // need stats?
    340       pic->stats->coded_size += (int)(*output_size);
    341       enc->sse_[3] = sse;
    342     }
    343   }
    344 
    345   WebPSafeFree(quant_alpha);
    346   return ok;
    347 }
    348 
    349 //------------------------------------------------------------------------------
    350 // Main calls
    351 
    352 static int CompressAlphaJob(VP8Encoder* const enc, void* dummy) {
    353   const WebPConfig* config = enc->config_;
    354   uint8_t* alpha_data = NULL;
    355   size_t alpha_size = 0;
    356   const int effort_level = config->method;  // maps to [0..6]
    357   const WEBP_FILTER_TYPE filter =
    358       (config->alpha_filtering == 0) ? WEBP_FILTER_NONE :
    359       (config->alpha_filtering == 1) ? WEBP_FILTER_FAST :
    360                                        WEBP_FILTER_BEST;
    361   if (!EncodeAlpha(enc, config->alpha_quality, config->alpha_compression,
    362                    filter, effort_level, &alpha_data, &alpha_size)) {
    363     return 0;
    364   }
    365   if (alpha_size != (uint32_t)alpha_size) {  // Sanity check.
    366     WebPSafeFree(alpha_data);
    367     return 0;
    368   }
    369   enc->alpha_data_size_ = (uint32_t)alpha_size;
    370   enc->alpha_data_ = alpha_data;
    371   (void)dummy;
    372   return 1;
    373 }
    374 
    375 void VP8EncInitAlpha(VP8Encoder* const enc) {
    376   WebPInitAlphaProcessing();
    377   enc->has_alpha_ = WebPPictureHasTransparency(enc->pic_);
    378   enc->alpha_data_ = NULL;
    379   enc->alpha_data_size_ = 0;
    380   if (enc->thread_level_ > 0) {
    381     WebPWorker* const worker = &enc->alpha_worker_;
    382     WebPGetWorkerInterface()->Init(worker);
    383     worker->data1 = enc;
    384     worker->data2 = NULL;
    385     worker->hook = (WebPWorkerHook)CompressAlphaJob;
    386   }
    387 }
    388 
    389 int VP8EncStartAlpha(VP8Encoder* const enc) {
    390   if (enc->has_alpha_) {
    391     if (enc->thread_level_ > 0) {
    392       WebPWorker* const worker = &enc->alpha_worker_;
    393       // Makes sure worker is good to go.
    394       if (!WebPGetWorkerInterface()->Reset(worker)) {
    395         return 0;
    396       }
    397       WebPGetWorkerInterface()->Launch(worker);
    398       return 1;
    399     } else {
    400       return CompressAlphaJob(enc, NULL);   // just do the job right away
    401     }
    402   }
    403   return 1;
    404 }
    405 
    406 int VP8EncFinishAlpha(VP8Encoder* const enc) {
    407   if (enc->has_alpha_) {
    408     if (enc->thread_level_ > 0) {
    409       WebPWorker* const worker = &enc->alpha_worker_;
    410       if (!WebPGetWorkerInterface()->Sync(worker)) return 0;  // error
    411     }
    412   }
    413   return WebPReportProgress(enc->pic_, enc->percent_ + 20, &enc->percent_);
    414 }
    415 
    416 int VP8EncDeleteAlpha(VP8Encoder* const enc) {
    417   int ok = 1;
    418   if (enc->thread_level_ > 0) {
    419     WebPWorker* const worker = &enc->alpha_worker_;
    420     // finish anything left in flight
    421     ok = WebPGetWorkerInterface()->Sync(worker);
    422     // still need to end the worker, even if !ok
    423     WebPGetWorkerInterface()->End(worker);
    424   }
    425   WebPSafeFree(enc->alpha_data_);
    426   enc->alpha_data_ = NULL;
    427   enc->alpha_data_size_ = 0;
    428   enc->has_alpha_ = 0;
    429   return ok;
    430 }
    431