go-libwebp

Experimental translation from libwebp to Go source.
Log | Files | Refs | README | LICENSE

anim_encode.c (59116B)


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