Home | History | Annotate | Download | only in mux
      1 // Copyright 2014 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 //  AnimEncoder implementation.
     11 //
     12 
     13 #include <assert.h>
     14 #include <limits.h>
     15 #include <math.h>    // for pow()
     16 #include <stdio.h>
     17 #include <stdlib.h>  // for abs()
     18 
     19 #include "src/mux/animi.h"
     20 #include "src/utils/utils.h"
     21 #include "src/webp/decode.h"
     22 #include "src/webp/encode.h"
     23 #include "src/webp/format_constants.h"
     24 #include "src/webp/mux.h"
     25 
     26 #if defined(_MSC_VER) && _MSC_VER < 1900
     27 #define snprintf _snprintf
     28 #endif
     29 
     30 #define ERROR_STR_MAX_LENGTH 100
     31 
     32 //------------------------------------------------------------------------------
     33 // Internal structs.
     34 
     35 // Stores frame rectangle dimensions.
     36 typedef struct {
     37   int x_offset_, y_offset_, width_, height_;
     38 } FrameRectangle;
     39 
     40 // Used to store two candidates of encoded data for an animation frame. One of
     41 // the two will be chosen later.
     42 typedef struct {
     43   WebPMuxFrameInfo sub_frame_;  // Encoded frame rectangle.
     44   WebPMuxFrameInfo key_frame_;  // Encoded frame if it is a key-frame.
     45   int is_key_frame_;            // True if 'key_frame' has been chosen.
     46 } EncodedFrame;
     47 
     48 struct WebPAnimEncoder {
     49   const int canvas_width_;                  // Canvas width.
     50   const int canvas_height_;                 // Canvas height.
     51   const WebPAnimEncoderOptions options_;    // Global encoding options.
     52 
     53   FrameRectangle prev_rect_;          // Previous WebP frame rectangle.
     54   WebPConfig last_config_;            // Cached in case a re-encode is needed.
     55   WebPConfig last_config_reversed_;   // If 'last_config_' uses lossless, then
     56                                       // this config uses lossy and vice versa;
     57                                       // only valid if 'options_.allow_mixed'
     58                                       // is true.
     59 
     60   WebPPicture* curr_canvas_;          // Only pointer; we don't own memory.
     61 
     62   // Canvas buffers.
     63   WebPPicture curr_canvas_copy_;      // Possibly modified current canvas.
     64   int curr_canvas_copy_modified_;     // True if pixels in 'curr_canvas_copy_'
     65                                       // differ from those in 'curr_canvas_'.
     66 
     67   WebPPicture prev_canvas_;           // Previous canvas.
     68   WebPPicture prev_canvas_disposed_;  // Previous canvas disposed to background.
     69 
     70   // Encoded data.
     71   EncodedFrame* encoded_frames_;      // Array of encoded frames.
     72   size_t size_;             // Number of allocated frames.
     73   size_t start_;            // Frame start index.
     74   size_t count_;            // Number of valid frames.
     75   size_t flush_count_;      // If >0, 'flush_count' frames starting from
     76                             // 'start' are ready to be added to mux.
     77 
     78   // key-frame related.
     79   int64_t best_delta_;      // min(canvas size - frame size) over the frames.
     80                             // Can be negative in certain cases due to
     81                             // transparent pixels in a frame.
     82   int keyframe_;            // Index of selected key-frame relative to 'start_'.
     83   int count_since_key_frame_;     // Frames seen since the last key-frame.
     84 
     85   int first_timestamp_;           // Timestamp of the first frame.
     86   int prev_timestamp_;            // Timestamp of the last added frame.
     87   int prev_candidate_undecided_;  // True if it's not yet decided if previous
     88                                   // frame would be a sub-frame or a key-frame.
     89 
     90   // Misc.
     91   int is_first_frame_;  // True if first frame is yet to be added/being added.
     92   int got_null_frame_;  // True if WebPAnimEncoderAdd() has already been called
     93                         // with a NULL frame.
     94 
     95   size_t in_frame_count_;   // Number of input frames processed so far.
     96   size_t out_frame_count_;  // Number of frames added to mux so far. This may be
     97                             // different from 'in_frame_count_' due to merging.
     98 
     99   WebPMux* mux_;        // Muxer to assemble the WebP bitstream.
    100   char error_str_[ERROR_STR_MAX_LENGTH];  // Error string. Empty if no error.
    101 };
    102 
    103 // -----------------------------------------------------------------------------
    104 // Life of WebPAnimEncoder object.
    105 
    106 #define DELTA_INFINITY      (1ULL << 32)
    107 #define KEYFRAME_NONE       (-1)
    108 
    109 // Reset the counters in the WebPAnimEncoder.
    110 static void ResetCounters(WebPAnimEncoder* const enc) {
    111   enc->start_ = 0;
    112   enc->count_ = 0;
    113   enc->flush_count_ = 0;
    114   enc->best_delta_ = DELTA_INFINITY;
    115   enc->keyframe_ = KEYFRAME_NONE;
    116 }
    117 
    118 static void DisableKeyframes(WebPAnimEncoderOptions* const enc_options) {
    119   enc_options->kmax = INT_MAX;
    120   enc_options->kmin = enc_options->kmax - 1;
    121 }
    122 
    123 #define MAX_CACHED_FRAMES 30
    124 
    125 static void SanitizeEncoderOptions(WebPAnimEncoderOptions* const enc_options) {
    126   int print_warning = enc_options->verbose;
    127 
    128   if (enc_options->minimize_size) {
    129     DisableKeyframes(enc_options);
    130   }
    131 
    132   if (enc_options->kmax == 1) {  // All frames will be key-frames.
    133     enc_options->kmin = 0;
    134     enc_options->kmax = 0;
    135     return;
    136   } else if (enc_options->kmax <= 0) {
    137     DisableKeyframes(enc_options);
    138     print_warning = 0;
    139   }
    140 
    141   if (enc_options->kmin >= enc_options->kmax) {
    142     enc_options->kmin = enc_options->kmax - 1;
    143     if (print_warning) {
    144       fprintf(stderr, "WARNING: Setting kmin = %d, so that kmin < kmax.\n",
    145               enc_options->kmin);
    146     }
    147   } else {
    148     const int kmin_limit = enc_options->kmax / 2 + 1;
    149     if (enc_options->kmin < kmin_limit && kmin_limit < enc_options->kmax) {
    150       // This ensures that enc.keyframe + kmin >= kmax is always true. So, we
    151       // can flush all the frames in the 'count_since_key_frame == kmax' case.
    152       enc_options->kmin = kmin_limit;
    153       if (print_warning) {
    154         fprintf(stderr,
    155                 "WARNING: Setting kmin = %d, so that kmin >= kmax / 2 + 1.\n",
    156                 enc_options->kmin);
    157       }
    158     }
    159   }
    160   // Limit the max number of frames that are allocated.
    161   if (enc_options->kmax - enc_options->kmin > MAX_CACHED_FRAMES) {
    162     enc_options->kmin = enc_options->kmax - MAX_CACHED_FRAMES;
    163     if (print_warning) {
    164       fprintf(stderr,
    165               "WARNING: Setting kmin = %d, so that kmax - kmin <= %d.\n",
    166               enc_options->kmin, MAX_CACHED_FRAMES);
    167     }
    168   }
    169   assert(enc_options->kmin < enc_options->kmax);
    170 }
    171 
    172 #undef MAX_CACHED_FRAMES
    173 
    174 static void DefaultEncoderOptions(WebPAnimEncoderOptions* const enc_options) {
    175   enc_options->anim_params.loop_count = 0;
    176   enc_options->anim_params.bgcolor = 0xffffffff;  // White.
    177   enc_options->minimize_size = 0;
    178   DisableKeyframes(enc_options);
    179   enc_options->allow_mixed = 0;
    180   enc_options->verbose = 0;
    181 }
    182 
    183 int WebPAnimEncoderOptionsInitInternal(WebPAnimEncoderOptions* enc_options,
    184                                        int abi_version) {
    185   if (enc_options == NULL ||
    186       WEBP_ABI_IS_INCOMPATIBLE(abi_version, WEBP_MUX_ABI_VERSION)) {
    187     return 0;
    188   }
    189   DefaultEncoderOptions(enc_options);
    190   return 1;
    191 }
    192 
    193 // This starting value is more fit to WebPCleanupTransparentAreaLossless().
    194 #define TRANSPARENT_COLOR   0x00000000
    195 
    196 static void ClearRectangle(WebPPicture* const picture,
    197                            int left, int top, int width, int height) {
    198   int j;
    199   for (j = top; j < top + height; ++j) {
    200     uint32_t* const dst = picture->argb + j * picture->argb_stride;
    201     int i;
    202     for (i = left; i < left + width; ++i) {
    203       dst[i] = TRANSPARENT_COLOR;
    204     }
    205   }
    206 }
    207 
    208 static void WebPUtilClearPic(WebPPicture* const picture,
    209                              const FrameRectangle* const rect) {
    210   if (rect != NULL) {
    211     ClearRectangle(picture, rect->x_offset_, rect->y_offset_,
    212                    rect->width_, rect->height_);
    213   } else {
    214     ClearRectangle(picture, 0, 0, picture->width, picture->height);
    215   }
    216 }
    217 
    218 static void MarkNoError(WebPAnimEncoder* const enc) {
    219   enc->error_str_[0] = '\0';  // Empty string.
    220 }
    221 
    222 static void MarkError(WebPAnimEncoder* const enc, const char* str) {
    223   if (snprintf(enc->error_str_, ERROR_STR_MAX_LENGTH, "%s.", str) < 0) {
    224     assert(0);  // FIX ME!
    225   }
    226 }
    227 
    228 static void MarkError2(WebPAnimEncoder* const enc,
    229                        const char* str, int error_code) {
    230   if (snprintf(enc->error_str_, ERROR_STR_MAX_LENGTH, "%s: %d.", str,
    231                error_code) < 0) {
    232     assert(0);  // FIX ME!
    233   }
    234 }
    235 
    236 WebPAnimEncoder* WebPAnimEncoderNewInternal(
    237     int width, int height, const WebPAnimEncoderOptions* enc_options,
    238     int abi_version) {
    239   WebPAnimEncoder* enc;
    240 
    241   if (WEBP_ABI_IS_INCOMPATIBLE(abi_version, WEBP_MUX_ABI_VERSION)) {
    242     return NULL;
    243   }
    244   if (width <= 0 || height <= 0 ||
    245       (width * (uint64_t)height) >= MAX_IMAGE_AREA) {
    246     return NULL;
    247   }
    248 
    249   enc = (WebPAnimEncoder*)WebPSafeCalloc(1, sizeof(*enc));
    250   if (enc == NULL) return NULL;
    251   // sanity inits, so we can call WebPAnimEncoderDelete():
    252   enc->encoded_frames_ = NULL;
    253   enc->mux_ = NULL;
    254   MarkNoError(enc);
    255 
    256   // Dimensions and options.
    257   *(int*)&enc->canvas_width_ = width;
    258   *(int*)&enc->canvas_height_ = height;
    259   if (enc_options != NULL) {
    260     *(WebPAnimEncoderOptions*)&enc->options_ = *enc_options;
    261     SanitizeEncoderOptions((WebPAnimEncoderOptions*)&enc->options_);
    262   } else {
    263     DefaultEncoderOptions((WebPAnimEncoderOptions*)&enc->options_);
    264   }
    265 
    266   // Canvas buffers.
    267   if (!WebPPictureInit(&enc->curr_canvas_copy_) ||
    268       !WebPPictureInit(&enc->prev_canvas_) ||
    269       !WebPPictureInit(&enc->prev_canvas_disposed_)) {
    270     goto Err;
    271   }
    272   enc->curr_canvas_copy_.width = width;
    273   enc->curr_canvas_copy_.height = height;
    274   enc->curr_canvas_copy_.use_argb = 1;
    275   if (!WebPPictureAlloc(&enc->curr_canvas_copy_) ||
    276       !WebPPictureCopy(&enc->curr_canvas_copy_, &enc->prev_canvas_) ||
    277       !WebPPictureCopy(&enc->curr_canvas_copy_, &enc->prev_canvas_disposed_)) {
    278     goto Err;
    279   }
    280   WebPUtilClearPic(&enc->prev_canvas_, NULL);
    281   enc->curr_canvas_copy_modified_ = 1;
    282 
    283   // Encoded frames.
    284   ResetCounters(enc);
    285   // Note: one extra storage is for the previous frame.
    286   enc->size_ = enc->options_.kmax - enc->options_.kmin + 1;
    287   // We need space for at least 2 frames. But when kmin, kmax are both zero,
    288   // enc->size_ will be 1. So we handle that special case below.
    289   if (enc->size_ < 2) enc->size_ = 2;
    290   enc->encoded_frames_ =
    291       (EncodedFrame*)WebPSafeCalloc(enc->size_, sizeof(*enc->encoded_frames_));
    292   if (enc->encoded_frames_ == NULL) goto Err;
    293 
    294   enc->mux_ = WebPMuxNew();
    295   if (enc->mux_ == NULL) goto Err;
    296 
    297   enc->count_since_key_frame_ = 0;
    298   enc->first_timestamp_ = 0;
    299   enc->prev_timestamp_ = 0;
    300   enc->prev_candidate_undecided_ = 0;
    301   enc->is_first_frame_ = 1;
    302   enc->got_null_frame_ = 0;
    303 
    304   return enc;  // All OK.
    305 
    306  Err:
    307   WebPAnimEncoderDelete(enc);
    308   return NULL;
    309 }
    310 
    311 // Release the data contained by 'encoded_frame'.
    312 static void FrameRelease(EncodedFrame* const encoded_frame) {
    313   if (encoded_frame != NULL) {
    314     WebPDataClear(&encoded_frame->sub_frame_.bitstream);
    315     WebPDataClear(&encoded_frame->key_frame_.bitstream);
    316     memset(encoded_frame, 0, sizeof(*encoded_frame));
    317   }
    318 }
    319 
    320 void WebPAnimEncoderDelete(WebPAnimEncoder* enc) {
    321   if (enc != NULL) {
    322     WebPPictureFree(&enc->curr_canvas_copy_);
    323     WebPPictureFree(&enc->prev_canvas_);
    324     WebPPictureFree(&enc->prev_canvas_disposed_);
    325     if (enc->encoded_frames_ != NULL) {
    326       size_t i;
    327       for (i = 0; i < enc->size_; ++i) {
    328         FrameRelease(&enc->encoded_frames_[i]);
    329       }
    330       WebPSafeFree(enc->encoded_frames_);
    331     }
    332     WebPMuxDelete(enc->mux_);
    333     WebPSafeFree(enc);
    334   }
    335 }
    336 
    337 // -----------------------------------------------------------------------------
    338 // Frame addition.
    339 
    340 // Returns cached frame at the given 'position'.
    341 static EncodedFrame* GetFrame(const WebPAnimEncoder* const enc,
    342                               size_t position) {
    343   assert(enc->start_ + position < enc->size_);
    344   return &enc->encoded_frames_[enc->start_ + position];
    345 }
    346 
    347 typedef int (*ComparePixelsFunc)(const uint32_t*, int, const uint32_t*, int,
    348                                  int, int);
    349 
    350 // Returns true if 'length' number of pixels in 'src' and 'dst' are equal,
    351 // assuming the given step sizes between pixels.
    352 // 'max_allowed_diff' is unused and only there to allow function pointer use.
    353 static WEBP_INLINE int ComparePixelsLossless(const uint32_t* src, int src_step,
    354                                              const uint32_t* dst, int dst_step,
    355                                              int length, int max_allowed_diff) {
    356   (void)max_allowed_diff;
    357   assert(length > 0);
    358   while (length-- > 0) {
    359     if (*src != *dst) {
    360       return 0;
    361     }
    362     src += src_step;
    363     dst += dst_step;
    364   }
    365   return 1;
    366 }
    367 
    368 // Helper to check if each channel in 'src' and 'dst' is at most off by
    369 // 'max_allowed_diff'.
    370 static WEBP_INLINE int PixelsAreSimilar(uint32_t src, uint32_t dst,
    371                                         int max_allowed_diff) {
    372   const int src_a = (src >> 24) & 0xff;
    373   const int src_r = (src >> 16) & 0xff;
    374   const int src_g = (src >> 8) & 0xff;
    375   const int src_b = (src >> 0) & 0xff;
    376   const int dst_a = (dst >> 24) & 0xff;
    377   const int dst_r = (dst >> 16) & 0xff;
    378   const int dst_g = (dst >> 8) & 0xff;
    379   const int dst_b = (dst >> 0) & 0xff;
    380 
    381   return (src_a == dst_a) &&
    382          (abs(src_r - dst_r) * dst_a <= (max_allowed_diff * 255)) &&
    383          (abs(src_g - dst_g) * dst_a <= (max_allowed_diff * 255)) &&
    384          (abs(src_b - dst_b) * dst_a <= (max_allowed_diff * 255));
    385 }
    386 
    387 // Returns true if 'length' number of pixels in 'src' and 'dst' are within an
    388 // error bound, assuming the given step sizes between pixels.
    389 static WEBP_INLINE int ComparePixelsLossy(const uint32_t* src, int src_step,
    390                                           const uint32_t* dst, int dst_step,
    391                                           int length, int max_allowed_diff) {
    392   assert(length > 0);
    393   while (length-- > 0) {
    394     if (!PixelsAreSimilar(*src, *dst, max_allowed_diff)) {
    395       return 0;
    396     }
    397     src += src_step;
    398     dst += dst_step;
    399   }
    400   return 1;
    401 }
    402 
    403 static int IsEmptyRect(const FrameRectangle* const rect) {
    404   return (rect->width_ == 0) || (rect->height_ == 0);
    405 }
    406 
    407 static int QualityToMaxDiff(float quality) {
    408   const double val = pow(quality / 100., 0.5);
    409   const double max_diff = 31 * (1 - val) + 1 * val;
    410   return (int)(max_diff + 0.5);
    411 }
    412 
    413 // Assumes that an initial valid guess of change rectangle 'rect' is passed.
    414 static void MinimizeChangeRectangle(const WebPPicture* const src,
    415                                     const WebPPicture* const dst,
    416                                     FrameRectangle* const rect,
    417                                     int is_lossless, float quality) {
    418   int i, j;
    419   const ComparePixelsFunc compare_pixels =
    420       is_lossless ? ComparePixelsLossless : ComparePixelsLossy;
    421   const int max_allowed_diff_lossy = QualityToMaxDiff(quality);
    422   const int max_allowed_diff = is_lossless ? 0 : max_allowed_diff_lossy;
    423 
    424   // Sanity checks.
    425   assert(src->width == dst->width && src->height == dst->height);
    426   assert(rect->x_offset_ + rect->width_ <= dst->width);
    427   assert(rect->y_offset_ + rect->height_ <= dst->height);
    428 
    429   // Left boundary.
    430   for (i = rect->x_offset_; i < rect->x_offset_ + rect->width_; ++i) {
    431     const uint32_t* const src_argb =
    432         &src->argb[rect->y_offset_ * src->argb_stride + i];
    433     const uint32_t* const dst_argb =
    434         &dst->argb[rect->y_offset_ * dst->argb_stride + i];
    435     if (compare_pixels(src_argb, src->argb_stride, dst_argb, dst->argb_stride,
    436                        rect->height_, max_allowed_diff)) {
    437       --rect->width_;  // Redundant column.
    438       ++rect->x_offset_;
    439     } else {
    440       break;
    441     }
    442   }
    443   if (rect->width_ == 0) goto NoChange;
    444 
    445   // Right boundary.
    446   for (i = rect->x_offset_ + rect->width_ - 1; i >= rect->x_offset_; --i) {
    447     const uint32_t* const src_argb =
    448         &src->argb[rect->y_offset_ * src->argb_stride + i];
    449     const uint32_t* const dst_argb =
    450         &dst->argb[rect->y_offset_ * dst->argb_stride + i];
    451     if (compare_pixels(src_argb, src->argb_stride, dst_argb, dst->argb_stride,
    452                        rect->height_, max_allowed_diff)) {
    453       --rect->width_;  // Redundant column.
    454     } else {
    455       break;
    456     }
    457   }
    458   if (rect->width_ == 0) goto NoChange;
    459 
    460   // Top boundary.
    461   for (j = rect->y_offset_; j < rect->y_offset_ + rect->height_; ++j) {
    462     const uint32_t* const src_argb =
    463         &src->argb[j * src->argb_stride + rect->x_offset_];
    464     const uint32_t* const dst_argb =
    465         &dst->argb[j * dst->argb_stride + rect->x_offset_];
    466     if (compare_pixels(src_argb, 1, dst_argb, 1, rect->width_,
    467                        max_allowed_diff)) {
    468       --rect->height_;  // Redundant row.
    469       ++rect->y_offset_;
    470     } else {
    471       break;
    472     }
    473   }
    474   if (rect->height_ == 0) goto NoChange;
    475 
    476   // Bottom boundary.
    477   for (j = rect->y_offset_ + rect->height_ - 1; j >= rect->y_offset_; --j) {
    478     const uint32_t* const src_argb =
    479         &src->argb[j * src->argb_stride + rect->x_offset_];
    480     const uint32_t* const dst_argb =
    481         &dst->argb[j * dst->argb_stride + rect->x_offset_];
    482     if (compare_pixels(src_argb, 1, dst_argb, 1, rect->width_,
    483                        max_allowed_diff)) {
    484       --rect->height_;  // Redundant row.
    485     } else {
    486       break;
    487     }
    488   }
    489   if (rect->height_ == 0) goto NoChange;
    490 
    491   if (IsEmptyRect(rect)) {
    492  NoChange:
    493     rect->x_offset_ = 0;
    494     rect->y_offset_ = 0;
    495     rect->width_ = 0;
    496     rect->height_ = 0;
    497   }
    498 }
    499 
    500 // Snap rectangle to even offsets (and adjust dimensions if needed).
    501 static WEBP_INLINE void SnapToEvenOffsets(FrameRectangle* const rect) {
    502   rect->width_ += (rect->x_offset_ & 1);
    503   rect->height_ += (rect->y_offset_ & 1);
    504   rect->x_offset_ &= ~1;
    505   rect->y_offset_ &= ~1;
    506 }
    507 
    508 typedef struct {
    509   int should_try_;               // Should try this set of parameters.
    510   int empty_rect_allowed_;       // Frame with empty rectangle can be skipped.
    511   FrameRectangle rect_ll_;       // Frame rectangle for lossless compression.
    512   WebPPicture sub_frame_ll_;     // Sub-frame pic for lossless compression.
    513   FrameRectangle rect_lossy_;    // Frame rectangle for lossy compression.
    514                                  // Could be smaller than rect_ll_ as pixels
    515                                  // with small diffs can be ignored.
    516   WebPPicture sub_frame_lossy_;  // Sub-frame pic for lossless compression.
    517 } SubFrameParams;
    518 
    519 static int SubFrameParamsInit(SubFrameParams* const params,
    520                               int should_try, int empty_rect_allowed) {
    521   params->should_try_ = should_try;
    522   params->empty_rect_allowed_ = empty_rect_allowed;
    523   if (!WebPPictureInit(&params->sub_frame_ll_) ||
    524       !WebPPictureInit(&params->sub_frame_lossy_)) {
    525     return 0;
    526   }
    527   return 1;
    528 }
    529 
    530 static void SubFrameParamsFree(SubFrameParams* const params) {
    531   WebPPictureFree(&params->sub_frame_ll_);
    532   WebPPictureFree(&params->sub_frame_lossy_);
    533 }
    534 
    535 // Given previous and current canvas, picks the optimal rectangle for the
    536 // current frame based on 'is_lossless' and other parameters. Assumes that the
    537 // initial guess 'rect' is valid.
    538 static int GetSubRect(const WebPPicture* const prev_canvas,
    539                       const WebPPicture* const curr_canvas, int is_key_frame,
    540                       int is_first_frame, int empty_rect_allowed,
    541                       int is_lossless, float quality,
    542                       FrameRectangle* const rect,
    543                       WebPPicture* const sub_frame) {
    544   if (!is_key_frame || is_first_frame) {  // Optimize frame rectangle.
    545     // Note: This behaves as expected for first frame, as 'prev_canvas' is
    546     // initialized to a fully transparent canvas in the beginning.
    547     MinimizeChangeRectangle(prev_canvas, curr_canvas, rect,
    548                             is_lossless, quality);
    549   }
    550 
    551   if (IsEmptyRect(rect)) {
    552     if (empty_rect_allowed) {  // No need to get 'sub_frame'.
    553       return 1;
    554     } else {                   // Force a 1x1 rectangle.
    555       rect->width_ = 1;
    556       rect->height_ = 1;
    557       assert(rect->x_offset_ == 0);
    558       assert(rect->y_offset_ == 0);
    559     }
    560   }
    561 
    562   SnapToEvenOffsets(rect);
    563   return WebPPictureView(curr_canvas, rect->x_offset_, rect->y_offset_,
    564                          rect->width_, rect->height_, sub_frame);
    565 }
    566 
    567 // Picks optimal frame rectangle for both lossless and lossy compression. The
    568 // initial guess for frame rectangles will be the full canvas.
    569 static int GetSubRects(const WebPPicture* const prev_canvas,
    570                        const WebPPicture* const curr_canvas, int is_key_frame,
    571                        int is_first_frame, float quality,
    572                        SubFrameParams* const params) {
    573   // Lossless frame rectangle.
    574   params->rect_ll_.x_offset_ = 0;
    575   params->rect_ll_.y_offset_ = 0;
    576   params->rect_ll_.width_ = curr_canvas->width;
    577   params->rect_ll_.height_ = curr_canvas->height;
    578   if (!GetSubRect(prev_canvas, curr_canvas, is_key_frame, is_first_frame,
    579                   params->empty_rect_allowed_, 1, quality,
    580                   &params->rect_ll_, &params->sub_frame_ll_)) {
    581     return 0;
    582   }
    583   // Lossy frame rectangle.
    584   params->rect_lossy_ = params->rect_ll_;  // seed with lossless rect.
    585   return GetSubRect(prev_canvas, curr_canvas, is_key_frame, is_first_frame,
    586                     params->empty_rect_allowed_, 0, quality,
    587                     &params->rect_lossy_, &params->sub_frame_lossy_);
    588 }
    589 
    590 static WEBP_INLINE int clip(int v, int min_v, int max_v) {
    591   return (v < min_v) ? min_v : (v > max_v) ? max_v : v;
    592 }
    593 
    594 int WebPAnimEncoderRefineRect(
    595     const WebPPicture* const prev_canvas, const WebPPicture* const curr_canvas,
    596     int is_lossless, float quality, int* const x_offset, int* const y_offset,
    597     int* const width, int* const height) {
    598   FrameRectangle rect;
    599   const int right = clip(*x_offset + *width, 0, curr_canvas->width);
    600   const int left = clip(*x_offset, 0, curr_canvas->width - 1);
    601   const int bottom = clip(*y_offset + *height, 0, curr_canvas->height);
    602   const int top = clip(*y_offset, 0, curr_canvas->height - 1);
    603   if (prev_canvas == NULL || curr_canvas == NULL ||
    604       prev_canvas->width != curr_canvas->width ||
    605       prev_canvas->height != curr_canvas->height ||
    606       !prev_canvas->use_argb || !curr_canvas->use_argb) {
    607     return 0;
    608   }
    609   rect.x_offset_ = left;
    610   rect.y_offset_ = top;
    611   rect.width_ = clip(right - left, 0, curr_canvas->width - rect.x_offset_);
    612   rect.height_ = clip(bottom - top, 0, curr_canvas->height - rect.y_offset_);
    613   MinimizeChangeRectangle(prev_canvas, curr_canvas, &rect, is_lossless,
    614                           quality);
    615   SnapToEvenOffsets(&rect);
    616   *x_offset = rect.x_offset_;
    617   *y_offset = rect.y_offset_;
    618   *width = rect.width_;
    619   *height = rect.height_;
    620   return 1;
    621 }
    622 
    623 static void DisposeFrameRectangle(int dispose_method,
    624                                   const FrameRectangle* const rect,
    625                                   WebPPicture* const curr_canvas) {
    626   assert(rect != NULL);
    627   if (dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) {
    628     WebPUtilClearPic(curr_canvas, rect);
    629   }
    630 }
    631 
    632 static uint32_t RectArea(const FrameRectangle* const rect) {
    633   return (uint32_t)rect->width_ * rect->height_;
    634 }
    635 
    636 static int IsLosslessBlendingPossible(const WebPPicture* const src,
    637                                       const WebPPicture* const dst,
    638                                       const FrameRectangle* const rect) {
    639   int i, j;
    640   assert(src->width == dst->width && src->height == dst->height);
    641   assert(rect->x_offset_ + rect->width_ <= dst->width);
    642   assert(rect->y_offset_ + rect->height_ <= dst->height);
    643   for (j = rect->y_offset_; j < rect->y_offset_ + rect->height_; ++j) {
    644     for (i = rect->x_offset_; i < rect->x_offset_ + rect->width_; ++i) {
    645       const uint32_t src_pixel = src->argb[j * src->argb_stride + i];
    646       const uint32_t dst_pixel = dst->argb[j * dst->argb_stride + i];
    647       const uint32_t dst_alpha = dst_pixel >> 24;
    648       if (dst_alpha != 0xff && src_pixel != dst_pixel) {
    649         // In this case, if we use blending, we can't attain the desired
    650         // 'dst_pixel' value for this pixel. So, blending is not possible.
    651         return 0;
    652       }
    653     }
    654   }
    655   return 1;
    656 }
    657 
    658 static int IsLossyBlendingPossible(const WebPPicture* const src,
    659                                    const WebPPicture* const dst,
    660                                    const FrameRectangle* const rect,
    661                                    float quality) {
    662   const int max_allowed_diff_lossy = QualityToMaxDiff(quality);
    663   int i, j;
    664   assert(src->width == dst->width && src->height == dst->height);
    665   assert(rect->x_offset_ + rect->width_ <= dst->width);
    666   assert(rect->y_offset_ + rect->height_ <= dst->height);
    667   for (j = rect->y_offset_; j < rect->y_offset_ + rect->height_; ++j) {
    668     for (i = rect->x_offset_; i < rect->x_offset_ + rect->width_; ++i) {
    669       const uint32_t src_pixel = src->argb[j * src->argb_stride + i];
    670       const uint32_t dst_pixel = dst->argb[j * dst->argb_stride + i];
    671       const uint32_t dst_alpha = dst_pixel >> 24;
    672       if (dst_alpha != 0xff &&
    673           !PixelsAreSimilar(src_pixel, dst_pixel, max_allowed_diff_lossy)) {
    674         // In this case, if we use blending, we can't attain the desired
    675         // 'dst_pixel' value for this pixel. So, blending is not possible.
    676         return 0;
    677       }
    678     }
    679   }
    680   return 1;
    681 }
    682 
    683 // For pixels in 'rect', replace those pixels in 'dst' that are same as 'src' by
    684 // transparent pixels.
    685 // Returns true if at least one pixel gets modified.
    686 static int IncreaseTransparency(const WebPPicture* const src,
    687                                 const FrameRectangle* const rect,
    688                                 WebPPicture* const dst) {
    689   int i, j;
    690   int modified = 0;
    691   assert(src != NULL && dst != NULL && rect != NULL);
    692   assert(src->width == dst->width && src->height == dst->height);
    693   for (j = rect->y_offset_; j < rect->y_offset_ + rect->height_; ++j) {
    694     const uint32_t* const psrc = src->argb + j * src->argb_stride;
    695     uint32_t* const pdst = dst->argb + j * dst->argb_stride;
    696     for (i = rect->x_offset_; i < rect->x_offset_ + rect->width_; ++i) {
    697       if (psrc[i] == pdst[i] && pdst[i] != TRANSPARENT_COLOR) {
    698         pdst[i] = TRANSPARENT_COLOR;
    699         modified = 1;
    700       }
    701     }
    702   }
    703   return modified;
    704 }
    705 
    706 #undef TRANSPARENT_COLOR
    707 
    708 // Replace similar blocks of pixels by a 'see-through' transparent block
    709 // with uniform average color.
    710 // Assumes lossy compression is being used.
    711 // Returns true if at least one pixel gets modified.
    712 static int FlattenSimilarBlocks(const WebPPicture* const src,
    713                                 const FrameRectangle* const rect,
    714                                 WebPPicture* const dst, float quality) {
    715   const int max_allowed_diff_lossy = QualityToMaxDiff(quality);
    716   int i, j;
    717   int modified = 0;
    718   const int block_size = 8;
    719   const int y_start = (rect->y_offset_ + block_size) & ~(block_size - 1);
    720   const int y_end = (rect->y_offset_ + rect->height_) & ~(block_size - 1);
    721   const int x_start = (rect->x_offset_ + block_size) & ~(block_size - 1);
    722   const int x_end = (rect->x_offset_ + rect->width_) & ~(block_size - 1);
    723   assert(src != NULL && dst != NULL && rect != NULL);
    724   assert(src->width == dst->width && src->height == dst->height);
    725   assert((block_size & (block_size - 1)) == 0);  // must be a power of 2
    726   // Iterate over each block and count similar pixels.
    727   for (j = y_start; j < y_end; j += block_size) {
    728     for (i = x_start; i < x_end; i += block_size) {
    729       int cnt = 0;
    730       int avg_r = 0, avg_g = 0, avg_b = 0;
    731       int x, y;
    732       const uint32_t* const psrc = src->argb + j * src->argb_stride + i;
    733       uint32_t* const pdst = dst->argb + j * dst->argb_stride + i;
    734       for (y = 0; y < block_size; ++y) {
    735         for (x = 0; x < block_size; ++x) {
    736           const uint32_t src_pixel = psrc[x + y * src->argb_stride];
    737           const int alpha = src_pixel >> 24;
    738           if (alpha == 0xff &&
    739               PixelsAreSimilar(src_pixel, pdst[x + y * dst->argb_stride],
    740                                max_allowed_diff_lossy)) {
    741             ++cnt;
    742             avg_r += (src_pixel >> 16) & 0xff;
    743             avg_g += (src_pixel >> 8) & 0xff;
    744             avg_b += (src_pixel >> 0) & 0xff;
    745           }
    746         }
    747       }
    748       // If we have a fully similar block, we replace it with an
    749       // average transparent block. This compresses better in lossy mode.
    750       if (cnt == block_size * block_size) {
    751         const uint32_t color = (0x00          << 24) |
    752                                ((avg_r / cnt) << 16) |
    753                                ((avg_g / cnt) <<  8) |
    754                                ((avg_b / cnt) <<  0);
    755         for (y = 0; y < block_size; ++y) {
    756           for (x = 0; x < block_size; ++x) {
    757             pdst[x + y * dst->argb_stride] = color;
    758           }
    759         }
    760         modified = 1;
    761       }
    762     }
    763   }
    764   return modified;
    765 }
    766 
    767 static int EncodeFrame(const WebPConfig* const config, WebPPicture* const pic,
    768                        WebPMemoryWriter* const memory) {
    769   pic->use_argb = 1;
    770   pic->writer = WebPMemoryWrite;
    771   pic->custom_ptr = memory;
    772   if (!WebPEncode(config, pic)) {
    773     return 0;
    774   }
    775   return 1;
    776 }
    777 
    778 // Struct representing a candidate encoded frame including its metadata.
    779 typedef struct {
    780   WebPMemoryWriter  mem_;
    781   WebPMuxFrameInfo  info_;
    782   FrameRectangle    rect_;
    783   int               evaluate_;  // True if this candidate should be evaluated.
    784 } Candidate;
    785 
    786 // Generates a candidate encoded frame given a picture and metadata.
    787 static WebPEncodingError EncodeCandidate(WebPPicture* const sub_frame,
    788                                          const FrameRectangle* const rect,
    789                                          const WebPConfig* const encoder_config,
    790                                          int use_blending,
    791                                          Candidate* const candidate) {
    792   WebPConfig config = *encoder_config;
    793   WebPEncodingError error_code = VP8_ENC_OK;
    794   assert(candidate != NULL);
    795   memset(candidate, 0, sizeof(*candidate));
    796 
    797   // Set frame rect and info.
    798   candidate->rect_ = *rect;
    799   candidate->info_.id = WEBP_CHUNK_ANMF;
    800   candidate->info_.x_offset = rect->x_offset_;
    801   candidate->info_.y_offset = rect->y_offset_;
    802   candidate->info_.dispose_method = WEBP_MUX_DISPOSE_NONE;  // Set later.
    803   candidate->info_.blend_method =
    804       use_blending ? WEBP_MUX_BLEND : WEBP_MUX_NO_BLEND;
    805   candidate->info_.duration = 0;  // Set in next call to WebPAnimEncoderAdd().
    806 
    807   // Encode picture.
    808   WebPMemoryWriterInit(&candidate->mem_);
    809 
    810   if (!config.lossless && use_blending) {
    811     // Disable filtering to avoid blockiness in reconstructed frames at the
    812     // time of decoding.
    813     config.autofilter = 0;
    814     config.filter_strength = 0;
    815   }
    816   if (!EncodeFrame(&config, sub_frame, &candidate->mem_)) {
    817     error_code = sub_frame->error_code;
    818     goto Err;
    819   }
    820 
    821   candidate->evaluate_ = 1;
    822   return error_code;
    823 
    824  Err:
    825   WebPMemoryWriterClear(&candidate->mem_);
    826   return error_code;
    827 }
    828 
    829 static void CopyCurrentCanvas(WebPAnimEncoder* const enc) {
    830   if (enc->curr_canvas_copy_modified_) {
    831     WebPCopyPixels(enc->curr_canvas_, &enc->curr_canvas_copy_);
    832     enc->curr_canvas_copy_.progress_hook = enc->curr_canvas_->progress_hook;
    833     enc->curr_canvas_copy_.user_data = enc->curr_canvas_->user_data;
    834     enc->curr_canvas_copy_modified_ = 0;
    835   }
    836 }
    837 
    838 enum {
    839   LL_DISP_NONE = 0,
    840   LL_DISP_BG,
    841   LOSSY_DISP_NONE,
    842   LOSSY_DISP_BG,
    843   CANDIDATE_COUNT
    844 };
    845 
    846 #define MIN_COLORS_LOSSY     31  // Don't try lossy below this threshold.
    847 #define MAX_COLORS_LOSSLESS 194  // Don't try lossless above this threshold.
    848 
    849 // Generates candidates for a given dispose method given pre-filled sub-frame
    850 // 'params'.
    851 static WebPEncodingError GenerateCandidates(
    852     WebPAnimEncoder* const enc, Candidate candidates[CANDIDATE_COUNT],
    853     WebPMuxAnimDispose dispose_method, int is_lossless, int is_key_frame,
    854     SubFrameParams* const params,
    855     const WebPConfig* const config_ll, const WebPConfig* const config_lossy) {
    856   WebPEncodingError error_code = VP8_ENC_OK;
    857   const int is_dispose_none = (dispose_method == WEBP_MUX_DISPOSE_NONE);
    858   Candidate* const candidate_ll =
    859       is_dispose_none ? &candidates[LL_DISP_NONE] : &candidates[LL_DISP_BG];
    860   Candidate* const candidate_lossy = is_dispose_none
    861                                      ? &candidates[LOSSY_DISP_NONE]
    862                                      : &candidates[LOSSY_DISP_BG];
    863   WebPPicture* const curr_canvas = &enc->curr_canvas_copy_;
    864   const WebPPicture* const prev_canvas =
    865       is_dispose_none ? &enc->prev_canvas_ : &enc->prev_canvas_disposed_;
    866   int use_blending_ll, use_blending_lossy;
    867   int evaluate_ll, evaluate_lossy;
    868 
    869   CopyCurrentCanvas(enc);
    870   use_blending_ll =
    871       !is_key_frame &&
    872       IsLosslessBlendingPossible(prev_canvas, curr_canvas, &params->rect_ll_);
    873   use_blending_lossy =
    874       !is_key_frame &&
    875       IsLossyBlendingPossible(prev_canvas, curr_canvas, &params->rect_lossy_,
    876                               config_lossy->quality);
    877 
    878   // Pick candidates to be tried.
    879   if (!enc->options_.allow_mixed) {
    880     evaluate_ll = is_lossless;
    881     evaluate_lossy = !is_lossless;
    882   } else if (enc->options_.minimize_size) {
    883     evaluate_ll = 1;
    884     evaluate_lossy = 1;
    885   } else {  // Use a heuristic for trying lossless and/or lossy compression.
    886     const int num_colors = WebPGetColorPalette(&params->sub_frame_ll_, NULL);
    887     evaluate_ll = (num_colors < MAX_COLORS_LOSSLESS);
    888     evaluate_lossy = (num_colors >= MIN_COLORS_LOSSY);
    889   }
    890 
    891   // Generate candidates.
    892   if (evaluate_ll) {
    893     CopyCurrentCanvas(enc);
    894     if (use_blending_ll) {
    895       enc->curr_canvas_copy_modified_ =
    896           IncreaseTransparency(prev_canvas, &params->rect_ll_, curr_canvas);
    897     }
    898     error_code = EncodeCandidate(&params->sub_frame_ll_, &params->rect_ll_,
    899                                  config_ll, use_blending_ll, candidate_ll);
    900     if (error_code != VP8_ENC_OK) return error_code;
    901   }
    902   if (evaluate_lossy) {
    903     CopyCurrentCanvas(enc);
    904     if (use_blending_lossy) {
    905       enc->curr_canvas_copy_modified_ =
    906           FlattenSimilarBlocks(prev_canvas, &params->rect_lossy_, curr_canvas,
    907                                config_lossy->quality);
    908     }
    909     error_code =
    910         EncodeCandidate(&params->sub_frame_lossy_, &params->rect_lossy_,
    911                         config_lossy, use_blending_lossy, candidate_lossy);
    912     if (error_code != VP8_ENC_OK) return error_code;
    913     enc->curr_canvas_copy_modified_ = 1;
    914   }
    915   return error_code;
    916 }
    917 
    918 #undef MIN_COLORS_LOSSY
    919 #undef MAX_COLORS_LOSSLESS
    920 
    921 static void GetEncodedData(const WebPMemoryWriter* const memory,
    922                            WebPData* const encoded_data) {
    923   encoded_data->bytes = memory->mem;
    924   encoded_data->size  = memory->size;
    925 }
    926 
    927 // Sets dispose method of the previous frame to be 'dispose_method'.
    928 static void SetPreviousDisposeMethod(WebPAnimEncoder* const enc,
    929                                      WebPMuxAnimDispose dispose_method) {
    930   const size_t position = enc->count_ - 2;
    931   EncodedFrame* const prev_enc_frame = GetFrame(enc, position);
    932   assert(enc->count_ >= 2);  // As current and previous frames are in enc.
    933 
    934   if (enc->prev_candidate_undecided_) {
    935     assert(dispose_method == WEBP_MUX_DISPOSE_NONE);
    936     prev_enc_frame->sub_frame_.dispose_method = dispose_method;
    937     prev_enc_frame->key_frame_.dispose_method = dispose_method;
    938   } else {
    939     WebPMuxFrameInfo* const prev_info = prev_enc_frame->is_key_frame_
    940                                         ? &prev_enc_frame->key_frame_
    941                                         : &prev_enc_frame->sub_frame_;
    942     prev_info->dispose_method = dispose_method;
    943   }
    944 }
    945 
    946 static int IncreasePreviousDuration(WebPAnimEncoder* const enc, int duration) {
    947   const size_t position = enc->count_ - 1;
    948   EncodedFrame* const prev_enc_frame = GetFrame(enc, position);
    949   int new_duration;
    950 
    951   assert(enc->count_ >= 1);
    952   assert(prev_enc_frame->sub_frame_.duration ==
    953          prev_enc_frame->key_frame_.duration);
    954   assert(prev_enc_frame->sub_frame_.duration ==
    955          (prev_enc_frame->sub_frame_.duration & (MAX_DURATION - 1)));
    956   assert(duration == (duration & (MAX_DURATION - 1)));
    957 
    958   new_duration = prev_enc_frame->sub_frame_.duration + duration;
    959   if (new_duration >= MAX_DURATION) {  // Special case.
    960     // Separate out previous frame from earlier merged frames to avoid overflow.
    961     // We add a 1x1 transparent frame for the previous frame, with blending on.
    962     const FrameRectangle rect = { 0, 0, 1, 1 };
    963     const uint8_t lossless_1x1_bytes[] = {
    964       0x52, 0x49, 0x46, 0x46, 0x14, 0x00, 0x00, 0x00, 0x57, 0x45, 0x42, 0x50,
    965       0x56, 0x50, 0x38, 0x4c, 0x08, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00,
    966       0x10, 0x88, 0x88, 0x08
    967     };
    968     const WebPData lossless_1x1 = {
    969         lossless_1x1_bytes, sizeof(lossless_1x1_bytes)
    970     };
    971     const uint8_t lossy_1x1_bytes[] = {
    972       0x52, 0x49, 0x46, 0x46, 0x40, 0x00, 0x00, 0x00, 0x57, 0x45, 0x42, 0x50,
    973       0x56, 0x50, 0x38, 0x58, 0x0a, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
    974       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x4c, 0x50, 0x48, 0x02, 0x00,
    975       0x00, 0x00, 0x00, 0x00, 0x56, 0x50, 0x38, 0x20, 0x18, 0x00, 0x00, 0x00,
    976       0x30, 0x01, 0x00, 0x9d, 0x01, 0x2a, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00,
    977       0x34, 0x25, 0xa4, 0x00, 0x03, 0x70, 0x00, 0xfe, 0xfb, 0xfd, 0x50, 0x00
    978     };
    979     const WebPData lossy_1x1 = { lossy_1x1_bytes, sizeof(lossy_1x1_bytes) };
    980     const int can_use_lossless =
    981         (enc->last_config_.lossless || enc->options_.allow_mixed);
    982     EncodedFrame* const curr_enc_frame = GetFrame(enc, enc->count_);
    983     curr_enc_frame->is_key_frame_ = 0;
    984     curr_enc_frame->sub_frame_.id = WEBP_CHUNK_ANMF;
    985     curr_enc_frame->sub_frame_.x_offset = 0;
    986     curr_enc_frame->sub_frame_.y_offset = 0;
    987     curr_enc_frame->sub_frame_.dispose_method = WEBP_MUX_DISPOSE_NONE;
    988     curr_enc_frame->sub_frame_.blend_method = WEBP_MUX_BLEND;
    989     curr_enc_frame->sub_frame_.duration = duration;
    990     if (!WebPDataCopy(can_use_lossless ? &lossless_1x1 : &lossy_1x1,
    991                       &curr_enc_frame->sub_frame_.bitstream)) {
    992       return 0;
    993     }
    994     ++enc->count_;
    995     ++enc->count_since_key_frame_;
    996     enc->flush_count_ = enc->count_ - 1;
    997     enc->prev_candidate_undecided_ = 0;
    998     enc->prev_rect_ = rect;
    999   } else {                           // Regular case.
   1000     // Increase duration of the previous frame by 'duration'.
   1001     prev_enc_frame->sub_frame_.duration = new_duration;
   1002     prev_enc_frame->key_frame_.duration = new_duration;
   1003   }
   1004   return 1;
   1005 }
   1006 
   1007 // Pick the candidate encoded frame with smallest size and release other
   1008 // candidates.
   1009 // TODO(later): Perhaps a rough SSIM/PSNR produced by the encoder should
   1010 // also be a criteria, in addition to sizes.
   1011 static void PickBestCandidate(WebPAnimEncoder* const enc,
   1012                               Candidate* const candidates, int is_key_frame,
   1013                               EncodedFrame* const encoded_frame) {
   1014   int i;
   1015   int best_idx = -1;
   1016   size_t best_size = ~0;
   1017   for (i = 0; i < CANDIDATE_COUNT; ++i) {
   1018     if (candidates[i].evaluate_) {
   1019       const size_t candidate_size = candidates[i].mem_.size;
   1020       if (candidate_size < best_size) {
   1021         best_idx = i;
   1022         best_size = candidate_size;
   1023       }
   1024     }
   1025   }
   1026   assert(best_idx != -1);
   1027   for (i = 0; i < CANDIDATE_COUNT; ++i) {
   1028     if (candidates[i].evaluate_) {
   1029       if (i == best_idx) {
   1030         WebPMuxFrameInfo* const dst = is_key_frame
   1031                                       ? &encoded_frame->key_frame_
   1032                                       : &encoded_frame->sub_frame_;
   1033         *dst = candidates[i].info_;
   1034         GetEncodedData(&candidates[i].mem_, &dst->bitstream);
   1035         if (!is_key_frame) {
   1036           // Note: Previous dispose method only matters for non-keyframes.
   1037           // Also, we don't want to modify previous dispose method that was
   1038           // selected when a non key-frame was assumed.
   1039           const WebPMuxAnimDispose prev_dispose_method =
   1040               (best_idx == LL_DISP_NONE || best_idx == LOSSY_DISP_NONE)
   1041                   ? WEBP_MUX_DISPOSE_NONE
   1042                   : WEBP_MUX_DISPOSE_BACKGROUND;
   1043           SetPreviousDisposeMethod(enc, prev_dispose_method);
   1044         }
   1045         enc->prev_rect_ = candidates[i].rect_;  // save for next frame.
   1046       } else {
   1047         WebPMemoryWriterClear(&candidates[i].mem_);
   1048         candidates[i].evaluate_ = 0;
   1049       }
   1050     }
   1051   }
   1052 }
   1053 
   1054 // Depending on the configuration, tries different compressions
   1055 // (lossy/lossless), dispose methods, blending methods etc to encode the current
   1056 // frame and outputs the best one in 'encoded_frame'.
   1057 // 'frame_skipped' will be set to true if this frame should actually be skipped.
   1058 static WebPEncodingError SetFrame(WebPAnimEncoder* const enc,
   1059                                   const WebPConfig* const config,
   1060                                   int is_key_frame,
   1061                                   EncodedFrame* const encoded_frame,
   1062                                   int* const frame_skipped) {
   1063   int i;
   1064   WebPEncodingError error_code = VP8_ENC_OK;
   1065   const WebPPicture* const curr_canvas = &enc->curr_canvas_copy_;
   1066   const WebPPicture* const prev_canvas = &enc->prev_canvas_;
   1067   Candidate candidates[CANDIDATE_COUNT];
   1068   const int is_lossless = config->lossless;
   1069   const int consider_lossless = is_lossless || enc->options_.allow_mixed;
   1070   const int consider_lossy = !is_lossless || enc->options_.allow_mixed;
   1071   const int is_first_frame = enc->is_first_frame_;
   1072 
   1073   // First frame cannot be skipped as there is no 'previous frame' to merge it
   1074   // to. So, empty rectangle is not allowed for the first frame.
   1075   const int empty_rect_allowed_none = !is_first_frame;
   1076 
   1077   // Even if there is exact pixel match between 'disposed previous canvas' and
   1078   // 'current canvas', we can't skip current frame, as there may not be exact
   1079   // pixel match between 'previous canvas' and 'current canvas'. So, we don't
   1080   // allow empty rectangle in this case.
   1081   const int empty_rect_allowed_bg = 0;
   1082 
   1083   // If current frame is a key-frame, dispose method of previous frame doesn't
   1084   // matter, so we don't try dispose to background.
   1085   // Also, if key-frame insertion is on, and previous frame could be picked as
   1086   // either a sub-frame or a key-frame, then we can't be sure about what frame
   1087   // rectangle would be disposed. In that case too, we don't try dispose to
   1088   // background.
   1089   const int dispose_bg_possible =
   1090       !is_key_frame && !enc->prev_candidate_undecided_;
   1091 
   1092   SubFrameParams dispose_none_params;
   1093   SubFrameParams dispose_bg_params;
   1094 
   1095   WebPConfig config_ll = *config;
   1096   WebPConfig config_lossy = *config;
   1097   config_ll.lossless = 1;
   1098   config_lossy.lossless = 0;
   1099   enc->last_config_ = *config;
   1100   enc->last_config_reversed_ = config->lossless ? config_lossy : config_ll;
   1101   *frame_skipped = 0;
   1102 
   1103   if (!SubFrameParamsInit(&dispose_none_params, 1, empty_rect_allowed_none) ||
   1104       !SubFrameParamsInit(&dispose_bg_params, 0, empty_rect_allowed_bg)) {
   1105     return VP8_ENC_ERROR_INVALID_CONFIGURATION;
   1106   }
   1107 
   1108   memset(candidates, 0, sizeof(candidates));
   1109 
   1110   // Change-rectangle assuming previous frame was DISPOSE_NONE.
   1111   if (!GetSubRects(prev_canvas, curr_canvas, is_key_frame, is_first_frame,
   1112                    config_lossy.quality, &dispose_none_params)) {
   1113     error_code = VP8_ENC_ERROR_INVALID_CONFIGURATION;
   1114     goto Err;
   1115   }
   1116 
   1117   if ((consider_lossless && IsEmptyRect(&dispose_none_params.rect_ll_)) ||
   1118       (consider_lossy && IsEmptyRect(&dispose_none_params.rect_lossy_))) {
   1119     // Don't encode the frame at all. Instead, the duration of the previous
   1120     // frame will be increased later.
   1121     assert(empty_rect_allowed_none);
   1122     *frame_skipped = 1;
   1123     goto End;
   1124   }
   1125 
   1126   if (dispose_bg_possible) {
   1127     // Change-rectangle assuming previous frame was DISPOSE_BACKGROUND.
   1128     WebPPicture* const prev_canvas_disposed = &enc->prev_canvas_disposed_;
   1129     WebPCopyPixels(prev_canvas, prev_canvas_disposed);
   1130     DisposeFrameRectangle(WEBP_MUX_DISPOSE_BACKGROUND, &enc->prev_rect_,
   1131                           prev_canvas_disposed);
   1132 
   1133     if (!GetSubRects(prev_canvas_disposed, curr_canvas, is_key_frame,
   1134                      is_first_frame, config_lossy.quality,
   1135                      &dispose_bg_params)) {
   1136       error_code = VP8_ENC_ERROR_INVALID_CONFIGURATION;
   1137       goto Err;
   1138     }
   1139     assert(!IsEmptyRect(&dispose_bg_params.rect_ll_));
   1140     assert(!IsEmptyRect(&dispose_bg_params.rect_lossy_));
   1141 
   1142     if (enc->options_.minimize_size) {  // Try both dispose methods.
   1143       dispose_bg_params.should_try_ = 1;
   1144       dispose_none_params.should_try_ = 1;
   1145     } else if ((is_lossless &&
   1146                 RectArea(&dispose_bg_params.rect_ll_) <
   1147                     RectArea(&dispose_none_params.rect_ll_)) ||
   1148                (!is_lossless &&
   1149                 RectArea(&dispose_bg_params.rect_lossy_) <
   1150                     RectArea(&dispose_none_params.rect_lossy_))) {
   1151       dispose_bg_params.should_try_ = 1;  // Pick DISPOSE_BACKGROUND.
   1152       dispose_none_params.should_try_ = 0;
   1153     }
   1154   }
   1155 
   1156   if (dispose_none_params.should_try_) {
   1157     error_code = GenerateCandidates(
   1158         enc, candidates, WEBP_MUX_DISPOSE_NONE, is_lossless, is_key_frame,
   1159         &dispose_none_params, &config_ll, &config_lossy);
   1160     if (error_code != VP8_ENC_OK) goto Err;
   1161   }
   1162 
   1163   if (dispose_bg_params.should_try_) {
   1164     assert(!enc->is_first_frame_);
   1165     assert(dispose_bg_possible);
   1166     error_code = GenerateCandidates(
   1167         enc, candidates, WEBP_MUX_DISPOSE_BACKGROUND, is_lossless, is_key_frame,
   1168         &dispose_bg_params, &config_ll, &config_lossy);
   1169     if (error_code != VP8_ENC_OK) goto Err;
   1170   }
   1171 
   1172   PickBestCandidate(enc, candidates, is_key_frame, encoded_frame);
   1173 
   1174   goto End;
   1175 
   1176  Err:
   1177   for (i = 0; i < CANDIDATE_COUNT; ++i) {
   1178     if (candidates[i].evaluate_) {
   1179       WebPMemoryWriterClear(&candidates[i].mem_);
   1180     }
   1181   }
   1182 
   1183  End:
   1184   SubFrameParamsFree(&dispose_none_params);
   1185   SubFrameParamsFree(&dispose_bg_params);
   1186   return error_code;
   1187 }
   1188 
   1189 // Calculate the penalty incurred if we encode given frame as a key frame
   1190 // instead of a sub-frame.
   1191 static int64_t KeyFramePenalty(const EncodedFrame* const encoded_frame) {
   1192   return ((int64_t)encoded_frame->key_frame_.bitstream.size -
   1193           encoded_frame->sub_frame_.bitstream.size);
   1194 }
   1195 
   1196 static int CacheFrame(WebPAnimEncoder* const enc,
   1197                       const WebPConfig* const config) {
   1198   int ok = 0;
   1199   int frame_skipped = 0;
   1200   WebPEncodingError error_code = VP8_ENC_OK;
   1201   const size_t position = enc->count_;
   1202   EncodedFrame* const encoded_frame = GetFrame(enc, position);
   1203 
   1204   ++enc->count_;
   1205 
   1206   if (enc->is_first_frame_) {  // Add this as a key-frame.
   1207     error_code = SetFrame(enc, config, 1, encoded_frame, &frame_skipped);
   1208     if (error_code != VP8_ENC_OK) goto End;
   1209     assert(frame_skipped == 0);  // First frame can't be skipped, even if empty.
   1210     assert(position == 0 && enc->count_ == 1);
   1211     encoded_frame->is_key_frame_ = 1;
   1212     enc->flush_count_ = 0;
   1213     enc->count_since_key_frame_ = 0;
   1214     enc->prev_candidate_undecided_ = 0;
   1215   } else {
   1216     ++enc->count_since_key_frame_;
   1217     if (enc->count_since_key_frame_ <= enc->options_.kmin) {
   1218       // Add this as a frame rectangle.
   1219       error_code = SetFrame(enc, config, 0, encoded_frame, &frame_skipped);
   1220       if (error_code != VP8_ENC_OK) goto End;
   1221       if (frame_skipped) goto Skip;
   1222       encoded_frame->is_key_frame_ = 0;
   1223       enc->flush_count_ = enc->count_ - 1;
   1224       enc->prev_candidate_undecided_ = 0;
   1225     } else {
   1226       int64_t curr_delta;
   1227       FrameRectangle prev_rect_key, prev_rect_sub;
   1228 
   1229       // Add this as a frame rectangle to enc.
   1230       error_code = SetFrame(enc, config, 0, encoded_frame, &frame_skipped);
   1231       if (error_code != VP8_ENC_OK) goto End;
   1232       if (frame_skipped) goto Skip;
   1233       prev_rect_sub = enc->prev_rect_;
   1234 
   1235 
   1236       // Add this as a key-frame to enc, too.
   1237       error_code = SetFrame(enc, config, 1, encoded_frame, &frame_skipped);
   1238       if (error_code != VP8_ENC_OK) goto End;
   1239       assert(frame_skipped == 0);  // Key-frame cannot be an empty rectangle.
   1240       prev_rect_key = enc->prev_rect_;
   1241 
   1242       // Analyze size difference of the two variants.
   1243       curr_delta = KeyFramePenalty(encoded_frame);
   1244       if (curr_delta <= enc->best_delta_) {  // Pick this as the key-frame.
   1245         if (enc->keyframe_ != KEYFRAME_NONE) {
   1246           EncodedFrame* const old_keyframe = GetFrame(enc, enc->keyframe_);
   1247           assert(old_keyframe->is_key_frame_);
   1248           old_keyframe->is_key_frame_ = 0;
   1249         }
   1250         encoded_frame->is_key_frame_ = 1;
   1251         enc->prev_candidate_undecided_ = 1;
   1252         enc->keyframe_ = (int)position;
   1253         enc->best_delta_ = curr_delta;
   1254         enc->flush_count_ = enc->count_ - 1;  // We can flush previous frames.
   1255       } else {
   1256         encoded_frame->is_key_frame_ = 0;
   1257         enc->prev_candidate_undecided_ = 0;
   1258       }
   1259       // Note: We need '>=' below because when kmin and kmax are both zero,
   1260       // count_since_key_frame will always be > kmax.
   1261       if (enc->count_since_key_frame_ >= enc->options_.kmax) {
   1262         enc->flush_count_ = enc->count_ - 1;
   1263         enc->count_since_key_frame_ = 0;
   1264         enc->keyframe_ = KEYFRAME_NONE;
   1265         enc->best_delta_ = DELTA_INFINITY;
   1266       }
   1267       if (!enc->prev_candidate_undecided_) {
   1268         enc->prev_rect_ =
   1269             encoded_frame->is_key_frame_ ? prev_rect_key : prev_rect_sub;
   1270       }
   1271     }
   1272   }
   1273 
   1274   // Update previous to previous and previous canvases for next call.
   1275   WebPCopyPixels(enc->curr_canvas_, &enc->prev_canvas_);
   1276   enc->is_first_frame_ = 0;
   1277 
   1278  Skip:
   1279   ok = 1;
   1280   ++enc->in_frame_count_;
   1281 
   1282  End:
   1283   if (!ok || frame_skipped) {
   1284     FrameRelease(encoded_frame);
   1285     // We reset some counters, as the frame addition failed/was skipped.
   1286     --enc->count_;
   1287     if (!enc->is_first_frame_) --enc->count_since_key_frame_;
   1288     if (!ok) {
   1289       MarkError2(enc, "ERROR adding frame. WebPEncodingError", error_code);
   1290     }
   1291   }
   1292   enc->curr_canvas_->error_code = error_code;   // report error_code
   1293   assert(ok || error_code != VP8_ENC_OK);
   1294   return ok;
   1295 }
   1296 
   1297 static int FlushFrames(WebPAnimEncoder* const enc) {
   1298   while (enc->flush_count_ > 0) {
   1299     WebPMuxError err;
   1300     EncodedFrame* const curr = GetFrame(enc, 0);
   1301     const WebPMuxFrameInfo* const info =
   1302         curr->is_key_frame_ ? &curr->key_frame_ : &curr->sub_frame_;
   1303     assert(enc->mux_ != NULL);
   1304     err = WebPMuxPushFrame(enc->mux_, info, 1);
   1305     if (err != WEBP_MUX_OK) {
   1306       MarkError2(enc, "ERROR adding frame. WebPMuxError", err);
   1307       return 0;
   1308     }
   1309     if (enc->options_.verbose) {
   1310       fprintf(stderr, "INFO: Added frame. offset:%d,%d dispose:%d blend:%d\n",
   1311               info->x_offset, info->y_offset, info->dispose_method,
   1312               info->blend_method);
   1313     }
   1314     ++enc->out_frame_count_;
   1315     FrameRelease(curr);
   1316     ++enc->start_;
   1317     --enc->flush_count_;
   1318     --enc->count_;
   1319     if (enc->keyframe_ != KEYFRAME_NONE) --enc->keyframe_;
   1320   }
   1321 
   1322   if (enc->count_ == 1 && enc->start_ != 0) {
   1323     // Move enc->start to index 0.
   1324     const int enc_start_tmp = (int)enc->start_;
   1325     EncodedFrame temp = enc->encoded_frames_[0];
   1326     enc->encoded_frames_[0] = enc->encoded_frames_[enc_start_tmp];
   1327     enc->encoded_frames_[enc_start_tmp] = temp;
   1328     FrameRelease(&enc->encoded_frames_[enc_start_tmp]);
   1329     enc->start_ = 0;
   1330   }
   1331   return 1;
   1332 }
   1333 
   1334 #undef DELTA_INFINITY
   1335 #undef KEYFRAME_NONE
   1336 
   1337 int WebPAnimEncoderAdd(WebPAnimEncoder* enc, WebPPicture* frame, int timestamp,
   1338                        const WebPConfig* encoder_config) {
   1339   WebPConfig config;
   1340   int ok;
   1341 
   1342   if (enc == NULL) {
   1343     return 0;
   1344   }
   1345   MarkNoError(enc);
   1346 
   1347   if (!enc->is_first_frame_) {
   1348     // Make sure timestamps are non-decreasing (integer wrap-around is OK).
   1349     const uint32_t prev_frame_duration =
   1350         (uint32_t)timestamp - enc->prev_timestamp_;
   1351     if (prev_frame_duration >= MAX_DURATION) {
   1352       if (frame != NULL) {
   1353         frame->error_code = VP8_ENC_ERROR_INVALID_CONFIGURATION;
   1354       }
   1355       MarkError(enc, "ERROR adding frame: timestamps must be non-decreasing");
   1356       return 0;
   1357     }
   1358     if (!IncreasePreviousDuration(enc, (int)prev_frame_duration)) {
   1359       return 0;
   1360     }
   1361   } else {
   1362     enc->first_timestamp_ = timestamp;
   1363   }
   1364 
   1365   if (frame == NULL) {  // Special: last call.
   1366     enc->got_null_frame_ = 1;
   1367     enc->prev_timestamp_ = timestamp;
   1368     return 1;
   1369   }
   1370 
   1371   if (frame->width != enc->canvas_width_ ||
   1372       frame->height != enc->canvas_height_) {
   1373     frame->error_code = VP8_ENC_ERROR_INVALID_CONFIGURATION;
   1374     MarkError(enc, "ERROR adding frame: Invalid frame dimensions");
   1375     return 0;
   1376   }
   1377 
   1378   if (!frame->use_argb) {  // Convert frame from YUV(A) to ARGB.
   1379     if (enc->options_.verbose) {
   1380       fprintf(stderr, "WARNING: Converting frame from YUV(A) to ARGB format; "
   1381               "this incurs a small loss.\n");
   1382     }
   1383     if (!WebPPictureYUVAToARGB(frame)) {
   1384       MarkError(enc, "ERROR converting frame from YUV(A) to ARGB");
   1385       return 0;
   1386     }
   1387   }
   1388 
   1389   if (encoder_config != NULL) {
   1390     if (!WebPValidateConfig(encoder_config)) {
   1391       MarkError(enc, "ERROR adding frame: Invalid WebPConfig");
   1392       return 0;
   1393     }
   1394     config = *encoder_config;
   1395   } else {
   1396     WebPConfigInit(&config);
   1397     config.lossless = 1;
   1398   }
   1399   assert(enc->curr_canvas_ == NULL);
   1400   enc->curr_canvas_ = frame;  // Store reference.
   1401   assert(enc->curr_canvas_copy_modified_ == 1);
   1402   CopyCurrentCanvas(enc);
   1403 
   1404   ok = CacheFrame(enc, &config) && FlushFrames(enc);
   1405 
   1406   enc->curr_canvas_ = NULL;
   1407   enc->curr_canvas_copy_modified_ = 1;
   1408   if (ok) {
   1409     enc->prev_timestamp_ = timestamp;
   1410   }
   1411   return ok;
   1412 }
   1413 
   1414 // -----------------------------------------------------------------------------
   1415 // Bitstream assembly.
   1416 
   1417 static int DecodeFrameOntoCanvas(const WebPMuxFrameInfo* const frame,
   1418                                  WebPPicture* const canvas) {
   1419   const WebPData* const image = &frame->bitstream;
   1420   WebPPicture sub_image;
   1421   WebPDecoderConfig config;
   1422   WebPInitDecoderConfig(&config);
   1423   WebPUtilClearPic(canvas, NULL);
   1424   if (WebPGetFeatures(image->bytes, image->size, &config.input) !=
   1425       VP8_STATUS_OK) {
   1426     return 0;
   1427   }
   1428   if (!WebPPictureView(canvas, frame->x_offset, frame->y_offset,
   1429                        config.input.width, config.input.height, &sub_image)) {
   1430     return 0;
   1431   }
   1432   config.output.is_external_memory = 1;
   1433   config.output.colorspace = MODE_BGRA;
   1434   config.output.u.RGBA.rgba = (uint8_t*)sub_image.argb;
   1435   config.output.u.RGBA.stride = sub_image.argb_stride * 4;
   1436   config.output.u.RGBA.size = config.output.u.RGBA.stride * sub_image.height;
   1437 
   1438   if (WebPDecode(image->bytes, image->size, &config) != VP8_STATUS_OK) {
   1439     return 0;
   1440   }
   1441   return 1;
   1442 }
   1443 
   1444 static int FrameToFullCanvas(WebPAnimEncoder* const enc,
   1445                              const WebPMuxFrameInfo* const frame,
   1446                              WebPData* const full_image) {
   1447   WebPPicture* const canvas_buf = &enc->curr_canvas_copy_;
   1448   WebPMemoryWriter mem1, mem2;
   1449   WebPMemoryWriterInit(&mem1);
   1450   WebPMemoryWriterInit(&mem2);
   1451 
   1452   if (!DecodeFrameOntoCanvas(frame, canvas_buf)) goto Err;
   1453   if (!EncodeFrame(&enc->last_config_, canvas_buf, &mem1)) goto Err;
   1454   GetEncodedData(&mem1, full_image);
   1455 
   1456   if (enc->options_.allow_mixed) {
   1457     if (!EncodeFrame(&enc->last_config_reversed_, canvas_buf, &mem2)) goto Err;
   1458     if (mem2.size < mem1.size) {
   1459       GetEncodedData(&mem2, full_image);
   1460       WebPMemoryWriterClear(&mem1);
   1461     } else {
   1462       WebPMemoryWriterClear(&mem2);
   1463     }
   1464   }
   1465   return 1;
   1466 
   1467  Err:
   1468   WebPMemoryWriterClear(&mem1);
   1469   WebPMemoryWriterClear(&mem2);
   1470   return 0;
   1471 }
   1472 
   1473 // Convert a single-frame animation to a non-animated image if appropriate.
   1474 // TODO(urvang): Can we pick one of the two heuristically (based on frame
   1475 // rectangle and/or presence of alpha)?
   1476 static WebPMuxError OptimizeSingleFrame(WebPAnimEncoder* const enc,
   1477                                         WebPData* const webp_data) {
   1478   WebPMuxError err = WEBP_MUX_OK;
   1479   int canvas_width, canvas_height;
   1480   WebPMuxFrameInfo frame;
   1481   WebPData full_image;
   1482   WebPData webp_data2;
   1483   WebPMux* const mux = WebPMuxCreate(webp_data, 0);
   1484   if (mux == NULL) return WEBP_MUX_BAD_DATA;
   1485   assert(enc->out_frame_count_ == 1);
   1486   WebPDataInit(&frame.bitstream);
   1487   WebPDataInit(&full_image);
   1488   WebPDataInit(&webp_data2);
   1489 
   1490   err = WebPMuxGetFrame(mux, 1, &frame);
   1491   if (err != WEBP_MUX_OK) goto End;
   1492   if (frame.id != WEBP_CHUNK_ANMF) goto End;  // Non-animation: nothing to do.
   1493   err = WebPMuxGetCanvasSize(mux, &canvas_width, &canvas_height);
   1494   if (err != WEBP_MUX_OK) goto End;
   1495   if (!FrameToFullCanvas(enc, &frame, &full_image)) {
   1496     err = WEBP_MUX_BAD_DATA;
   1497     goto End;
   1498   }
   1499   err = WebPMuxSetImage(mux, &full_image, 1);
   1500   if (err != WEBP_MUX_OK) goto End;
   1501   err = WebPMuxAssemble(mux, &webp_data2);
   1502   if (err != WEBP_MUX_OK) goto End;
   1503 
   1504   if (webp_data2.size < webp_data->size) {  // Pick 'webp_data2' if smaller.
   1505     WebPDataClear(webp_data);
   1506     *webp_data = webp_data2;
   1507     WebPDataInit(&webp_data2);
   1508   }
   1509 
   1510  End:
   1511   WebPDataClear(&frame.bitstream);
   1512   WebPDataClear(&full_image);
   1513   WebPMuxDelete(mux);
   1514   WebPDataClear(&webp_data2);
   1515   return err;
   1516 }
   1517 
   1518 int WebPAnimEncoderAssemble(WebPAnimEncoder* enc, WebPData* webp_data) {
   1519   WebPMux* mux;
   1520   WebPMuxError err;
   1521 
   1522   if (enc == NULL) {
   1523     return 0;
   1524   }
   1525   MarkNoError(enc);
   1526 
   1527   if (webp_data == NULL) {
   1528     MarkError(enc, "ERROR assembling: NULL input");
   1529     return 0;
   1530   }
   1531 
   1532   if (enc->in_frame_count_ == 0) {
   1533     MarkError(enc, "ERROR: No frames to assemble");
   1534     return 0;
   1535   }
   1536 
   1537   if (!enc->got_null_frame_ && enc->in_frame_count_ > 1 && enc->count_ > 0) {
   1538     // set duration of the last frame to be avg of durations of previous frames.
   1539     const double delta_time =
   1540         (uint32_t)enc->prev_timestamp_ - enc->first_timestamp_;
   1541     const int average_duration = (int)(delta_time / (enc->in_frame_count_ - 1));
   1542     if (!IncreasePreviousDuration(enc, average_duration)) {
   1543       return 0;
   1544     }
   1545   }
   1546 
   1547   // Flush any remaining frames.
   1548   enc->flush_count_ = enc->count_;
   1549   if (!FlushFrames(enc)) {
   1550     return 0;
   1551   }
   1552 
   1553   // Set definitive canvas size.
   1554   mux = enc->mux_;
   1555   err = WebPMuxSetCanvasSize(mux, enc->canvas_width_, enc->canvas_height_);
   1556   if (err != WEBP_MUX_OK) goto Err;
   1557 
   1558   err = WebPMuxSetAnimationParams(mux, &enc->options_.anim_params);
   1559   if (err != WEBP_MUX_OK) goto Err;
   1560 
   1561   // Assemble into a WebP bitstream.
   1562   err = WebPMuxAssemble(mux, webp_data);
   1563   if (err != WEBP_MUX_OK) goto Err;
   1564 
   1565   if (enc->out_frame_count_ == 1) {
   1566     err = OptimizeSingleFrame(enc, webp_data);
   1567     if (err != WEBP_MUX_OK) goto Err;
   1568   }
   1569   return 1;
   1570 
   1571  Err:
   1572   MarkError2(enc, "ERROR assembling WebP", err);
   1573   return 0;
   1574 }
   1575 
   1576 const char* WebPAnimEncoderGetError(WebPAnimEncoder* enc) {
   1577   if (enc == NULL) return NULL;
   1578   return enc->error_str_;
   1579 }
   1580 
   1581 // -----------------------------------------------------------------------------
   1582