github.com/cellofellow/gopkg@v0.0.0-20140722061823-eec0544a62ad/image/webp/libwebp/src/enc/alpha.c (about)

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