Compile mux files (v0.6.0) with libwebp-encode target

This allows webp image encoders to embed ICC profiles.

Test: Dependency

Bug: 32984164
Change-Id: I982fd79e214809b65e49aca77954b50dcf6c1561
diff --git a/include/webp/mux.h b/include/webp/mux.h
new file mode 100644
index 0000000..daccc65
--- /dev/null
+++ b/include/webp/mux.h
@@ -0,0 +1,530 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the COPYING file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+// -----------------------------------------------------------------------------
+//
+//  RIFF container manipulation and encoding for WebP images.
+//
+// Authors: Urvang (urvang@google.com)
+//          Vikas (vikasa@google.com)
+
+#ifndef WEBP_WEBP_MUX_H_
+#define WEBP_WEBP_MUX_H_
+
+#include "./mux_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define WEBP_MUX_ABI_VERSION 0x0108        // MAJOR(8b) + MINOR(8b)
+
+//------------------------------------------------------------------------------
+// Mux API
+//
+// This API allows manipulation of WebP container images containing features
+// like color profile, metadata, animation.
+//
+// Code Example#1: Create a WebPMux object with image data, color profile and
+// XMP metadata.
+/*
+  int copy_data = 0;
+  WebPMux* mux = WebPMuxNew();
+  // ... (Prepare image data).
+  WebPMuxSetImage(mux, &image, copy_data);
+  // ... (Prepare ICCP color profile data).
+  WebPMuxSetChunk(mux, "ICCP", &icc_profile, copy_data);
+  // ... (Prepare XMP metadata).
+  WebPMuxSetChunk(mux, "XMP ", &xmp, copy_data);
+  // Get data from mux in WebP RIFF format.
+  WebPMuxAssemble(mux, &output_data);
+  WebPMuxDelete(mux);
+  // ... (Consume output_data; e.g. write output_data.bytes to file).
+  WebPDataClear(&output_data);
+*/
+
+// Code Example#2: Get image and color profile data from a WebP file.
+/*
+  int copy_data = 0;
+  // ... (Read data from file).
+  WebPMux* mux = WebPMuxCreate(&data, copy_data);
+  WebPMuxGetFrame(mux, 1, &image);
+  // ... (Consume image; e.g. call WebPDecode() to decode the data).
+  WebPMuxGetChunk(mux, "ICCP", &icc_profile);
+  // ... (Consume icc_data).
+  WebPMuxDelete(mux);
+  free(data);
+*/
+
+// Note: forward declaring enumerations is not allowed in (strict) C and C++,
+// the types are left here for reference.
+// typedef enum WebPMuxError WebPMuxError;
+// typedef enum WebPChunkId WebPChunkId;
+typedef struct WebPMux WebPMux;   // main opaque object.
+typedef struct WebPMuxFrameInfo WebPMuxFrameInfo;
+typedef struct WebPMuxAnimParams WebPMuxAnimParams;
+typedef struct WebPAnimEncoderOptions WebPAnimEncoderOptions;
+
+// Error codes
+typedef enum WebPMuxError {
+  WEBP_MUX_OK                 =  1,
+  WEBP_MUX_NOT_FOUND          =  0,
+  WEBP_MUX_INVALID_ARGUMENT   = -1,
+  WEBP_MUX_BAD_DATA           = -2,
+  WEBP_MUX_MEMORY_ERROR       = -3,
+  WEBP_MUX_NOT_ENOUGH_DATA    = -4
+} WebPMuxError;
+
+// IDs for different types of chunks.
+typedef enum WebPChunkId {
+  WEBP_CHUNK_VP8X,        // VP8X
+  WEBP_CHUNK_ICCP,        // ICCP
+  WEBP_CHUNK_ANIM,        // ANIM
+  WEBP_CHUNK_ANMF,        // ANMF
+  WEBP_CHUNK_DEPRECATED,  // (deprecated from FRGM)
+  WEBP_CHUNK_ALPHA,       // ALPH
+  WEBP_CHUNK_IMAGE,       // VP8/VP8L
+  WEBP_CHUNK_EXIF,        // EXIF
+  WEBP_CHUNK_XMP,         // XMP
+  WEBP_CHUNK_UNKNOWN,     // Other chunks.
+  WEBP_CHUNK_NIL
+} WebPChunkId;
+
+//------------------------------------------------------------------------------
+
+// Returns the version number of the mux library, packed in hexadecimal using
+// 8bits for each of major/minor/revision. E.g: v2.5.7 is 0x020507.
+WEBP_EXTERN(int) WebPGetMuxVersion(void);
+
+//------------------------------------------------------------------------------
+// Life of a Mux object
+
+// Internal, version-checked, entry point
+WEBP_EXTERN(WebPMux*) WebPNewInternal(int);
+
+// Creates an empty mux object.
+// Returns:
+//   A pointer to the newly created empty mux object.
+//   Or NULL in case of memory error.
+static WEBP_INLINE WebPMux* WebPMuxNew(void) {
+  return WebPNewInternal(WEBP_MUX_ABI_VERSION);
+}
+
+// Deletes the mux object.
+// Parameters:
+//   mux - (in/out) object to be deleted
+WEBP_EXTERN(void) WebPMuxDelete(WebPMux* mux);
+
+//------------------------------------------------------------------------------
+// Mux creation.
+
+// Internal, version-checked, entry point
+WEBP_EXTERN(WebPMux*) WebPMuxCreateInternal(const WebPData*, int, int);
+
+// Creates a mux object from raw data given in WebP RIFF format.
+// Parameters:
+//   bitstream - (in) the bitstream data in WebP RIFF format
+//   copy_data - (in) value 1 indicates given data WILL be copied to the mux
+//               object and value 0 indicates data will NOT be copied.
+// Returns:
+//   A pointer to the mux object created from given data - on success.
+//   NULL - In case of invalid data or memory error.
+static WEBP_INLINE WebPMux* WebPMuxCreate(const WebPData* bitstream,
+                                          int copy_data) {
+  return WebPMuxCreateInternal(bitstream, copy_data, WEBP_MUX_ABI_VERSION);
+}
+
+//------------------------------------------------------------------------------
+// Non-image chunks.
+
+// Note: Only non-image related chunks should be managed through chunk APIs.
+// (Image related chunks are: "ANMF", "VP8 ", "VP8L" and "ALPH").
+// To add, get and delete images, use WebPMuxSetImage(), WebPMuxPushFrame(),
+// WebPMuxGetFrame() and WebPMuxDeleteFrame().
+
+// Adds a chunk with id 'fourcc' and data 'chunk_data' in the mux object.
+// Any existing chunk(s) with the same id will be removed.
+// Parameters:
+//   mux - (in/out) object to which the chunk is to be added
+//   fourcc - (in) a character array containing the fourcc of the given chunk;
+//                 e.g., "ICCP", "XMP ", "EXIF" etc.
+//   chunk_data - (in) the chunk data to be added
+//   copy_data - (in) value 1 indicates given data WILL be copied to the mux
+//               object and value 0 indicates data will NOT be copied.
+// Returns:
+//   WEBP_MUX_INVALID_ARGUMENT - if mux, fourcc or chunk_data is NULL
+//                               or if fourcc corresponds to an image chunk.
+//   WEBP_MUX_MEMORY_ERROR - on memory allocation error.
+//   WEBP_MUX_OK - on success.
+WEBP_EXTERN(WebPMuxError) WebPMuxSetChunk(
+    WebPMux* mux, const char fourcc[4], const WebPData* chunk_data,
+    int copy_data);
+
+// Gets a reference to the data of the chunk with id 'fourcc' in the mux object.
+// The caller should NOT free the returned data.
+// Parameters:
+//   mux - (in) object from which the chunk data is to be fetched
+//   fourcc - (in) a character array containing the fourcc of the chunk;
+//                 e.g., "ICCP", "XMP ", "EXIF" etc.
+//   chunk_data - (out) returned chunk data
+// Returns:
+//   WEBP_MUX_INVALID_ARGUMENT - if mux, fourcc or chunk_data is NULL
+//                               or if fourcc corresponds to an image chunk.
+//   WEBP_MUX_NOT_FOUND - If mux does not contain a chunk with the given id.
+//   WEBP_MUX_OK - on success.
+WEBP_EXTERN(WebPMuxError) WebPMuxGetChunk(
+    const WebPMux* mux, const char fourcc[4], WebPData* chunk_data);
+
+// Deletes the chunk with the given 'fourcc' from the mux object.
+// Parameters:
+//   mux - (in/out) object from which the chunk is to be deleted
+//   fourcc - (in) a character array containing the fourcc of the chunk;
+//                 e.g., "ICCP", "XMP ", "EXIF" etc.
+// Returns:
+//   WEBP_MUX_INVALID_ARGUMENT - if mux or fourcc is NULL
+//                               or if fourcc corresponds to an image chunk.
+//   WEBP_MUX_NOT_FOUND - If mux does not contain a chunk with the given fourcc.
+//   WEBP_MUX_OK - on success.
+WEBP_EXTERN(WebPMuxError) WebPMuxDeleteChunk(
+    WebPMux* mux, const char fourcc[4]);
+
+//------------------------------------------------------------------------------
+// Images.
+
+// Encapsulates data about a single frame.
+struct WebPMuxFrameInfo {
+  WebPData    bitstream;  // image data: can be a raw VP8/VP8L bitstream
+                          // or a single-image WebP file.
+  int         x_offset;   // x-offset of the frame.
+  int         y_offset;   // y-offset of the frame.
+  int         duration;   // duration of the frame (in milliseconds).
+
+  WebPChunkId id;         // frame type: should be one of WEBP_CHUNK_ANMF
+                          // or WEBP_CHUNK_IMAGE
+  WebPMuxAnimDispose dispose_method;  // Disposal method for the frame.
+  WebPMuxAnimBlend   blend_method;    // Blend operation for the frame.
+  uint32_t    pad[1];     // padding for later use
+};
+
+// Sets the (non-animated) image in the mux object.
+// Note: Any existing images (including frames) will be removed.
+// Parameters:
+//   mux - (in/out) object in which the image is to be set
+//   bitstream - (in) can be a raw VP8/VP8L bitstream or a single-image
+//               WebP file (non-animated)
+//   copy_data - (in) value 1 indicates given data WILL be copied to the mux
+//               object and value 0 indicates data will NOT be copied.
+// Returns:
+//   WEBP_MUX_INVALID_ARGUMENT - if mux is NULL or bitstream is NULL.
+//   WEBP_MUX_MEMORY_ERROR - on memory allocation error.
+//   WEBP_MUX_OK - on success.
+WEBP_EXTERN(WebPMuxError) WebPMuxSetImage(
+    WebPMux* mux, const WebPData* bitstream, int copy_data);
+
+// Adds a frame at the end of the mux object.
+// Notes: (1) frame.id should be WEBP_CHUNK_ANMF
+//        (2) For setting a non-animated image, use WebPMuxSetImage() instead.
+//        (3) Type of frame being pushed must be same as the frames in mux.
+//        (4) As WebP only supports even offsets, any odd offset will be snapped
+//            to an even location using: offset &= ~1
+// Parameters:
+//   mux - (in/out) object to which the frame is to be added
+//   frame - (in) frame data.
+//   copy_data - (in) value 1 indicates given data WILL be copied to the mux
+//               object and value 0 indicates data will NOT be copied.
+// Returns:
+//   WEBP_MUX_INVALID_ARGUMENT - if mux or frame is NULL
+//                               or if content of 'frame' is invalid.
+//   WEBP_MUX_MEMORY_ERROR - on memory allocation error.
+//   WEBP_MUX_OK - on success.
+WEBP_EXTERN(WebPMuxError) WebPMuxPushFrame(
+    WebPMux* mux, const WebPMuxFrameInfo* frame, int copy_data);
+
+// Gets the nth frame from the mux object.
+// The content of 'frame->bitstream' is allocated using malloc(), and NOT
+// owned by the 'mux' object. It MUST be deallocated by the caller by calling
+// WebPDataClear().
+// nth=0 has a special meaning - last position.
+// Parameters:
+//   mux - (in) object from which the info is to be fetched
+//   nth - (in) index of the frame in the mux object
+//   frame - (out) data of the returned frame
+// Returns:
+//   WEBP_MUX_INVALID_ARGUMENT - if mux or frame is NULL.
+//   WEBP_MUX_NOT_FOUND - if there are less than nth frames in the mux object.
+//   WEBP_MUX_BAD_DATA - if nth frame chunk in mux is invalid.
+//   WEBP_MUX_MEMORY_ERROR - on memory allocation error.
+//   WEBP_MUX_OK - on success.
+WEBP_EXTERN(WebPMuxError) WebPMuxGetFrame(
+    const WebPMux* mux, uint32_t nth, WebPMuxFrameInfo* frame);
+
+// Deletes a frame from the mux object.
+// nth=0 has a special meaning - last position.
+// Parameters:
+//   mux - (in/out) object from which a frame is to be deleted
+//   nth - (in) The position from which the frame is to be deleted
+// Returns:
+//   WEBP_MUX_INVALID_ARGUMENT - if mux is NULL.
+//   WEBP_MUX_NOT_FOUND - If there are less than nth frames in the mux object
+//                        before deletion.
+//   WEBP_MUX_OK - on success.
+WEBP_EXTERN(WebPMuxError) WebPMuxDeleteFrame(WebPMux* mux, uint32_t nth);
+
+//------------------------------------------------------------------------------
+// Animation.
+
+// Animation parameters.
+struct WebPMuxAnimParams {
+  uint32_t bgcolor;  // Background color of the canvas stored (in MSB order) as:
+                     // Bits 00 to 07: Alpha.
+                     // Bits 08 to 15: Red.
+                     // Bits 16 to 23: Green.
+                     // Bits 24 to 31: Blue.
+  int loop_count;    // Number of times to repeat the animation [0 = infinite].
+};
+
+// Sets the animation parameters in the mux object. Any existing ANIM chunks
+// will be removed.
+// Parameters:
+//   mux - (in/out) object in which ANIM chunk is to be set/added
+//   params - (in) animation parameters.
+// Returns:
+//   WEBP_MUX_INVALID_ARGUMENT - if mux or params is NULL.
+//   WEBP_MUX_MEMORY_ERROR - on memory allocation error.
+//   WEBP_MUX_OK - on success.
+WEBP_EXTERN(WebPMuxError) WebPMuxSetAnimationParams(
+    WebPMux* mux, const WebPMuxAnimParams* params);
+
+// Gets the animation parameters from the mux object.
+// Parameters:
+//   mux - (in) object from which the animation parameters to be fetched
+//   params - (out) animation parameters extracted from the ANIM chunk
+// Returns:
+//   WEBP_MUX_INVALID_ARGUMENT - if mux or params is NULL.
+//   WEBP_MUX_NOT_FOUND - if ANIM chunk is not present in mux object.
+//   WEBP_MUX_OK - on success.
+WEBP_EXTERN(WebPMuxError) WebPMuxGetAnimationParams(
+    const WebPMux* mux, WebPMuxAnimParams* params);
+
+//------------------------------------------------------------------------------
+// Misc Utilities.
+
+// Sets the canvas size for the mux object. The width and height can be
+// specified explicitly or left as zero (0, 0).
+// * When width and height are specified explicitly, then this frame bound is
+//   enforced during subsequent calls to WebPMuxAssemble() and an error is
+//   reported if any animated frame does not completely fit within the canvas.
+// * When unspecified (0, 0), the constructed canvas will get the frame bounds
+//   from the bounding-box over all frames after calling WebPMuxAssemble().
+// Parameters:
+//   mux - (in) object to which the canvas size is to be set
+//   width - (in) canvas width
+//   height - (in) canvas height
+// Returns:
+//   WEBP_MUX_INVALID_ARGUMENT - if mux is NULL; or
+//                               width or height are invalid or out of bounds
+//   WEBP_MUX_OK - on success.
+WEBP_EXTERN(WebPMuxError) WebPMuxSetCanvasSize(WebPMux* mux,
+                                               int width, int height);
+
+// Gets the canvas size from the mux object.
+// Note: This method assumes that the VP8X chunk, if present, is up-to-date.
+// That is, the mux object hasn't been modified since the last call to
+// WebPMuxAssemble() or WebPMuxCreate().
+// Parameters:
+//   mux - (in) object from which the canvas size is to be fetched
+//   width - (out) canvas width
+//   height - (out) canvas height
+// Returns:
+//   WEBP_MUX_INVALID_ARGUMENT - if mux, width or height is NULL.
+//   WEBP_MUX_BAD_DATA - if VP8X/VP8/VP8L chunk or canvas size is invalid.
+//   WEBP_MUX_OK - on success.
+WEBP_EXTERN(WebPMuxError) WebPMuxGetCanvasSize(const WebPMux* mux,
+                                               int* width, int* height);
+
+// Gets the feature flags from the mux object.
+// Note: This method assumes that the VP8X chunk, if present, is up-to-date.
+// That is, the mux object hasn't been modified since the last call to
+// WebPMuxAssemble() or WebPMuxCreate().
+// Parameters:
+//   mux - (in) object from which the features are to be fetched
+//   flags - (out) the flags specifying which features are present in the
+//           mux object. This will be an OR of various flag values.
+//           Enum 'WebPFeatureFlags' can be used to test individual flag values.
+// Returns:
+//   WEBP_MUX_INVALID_ARGUMENT - if mux or flags is NULL.
+//   WEBP_MUX_BAD_DATA - if VP8X/VP8/VP8L chunk or canvas size is invalid.
+//   WEBP_MUX_OK - on success.
+WEBP_EXTERN(WebPMuxError) WebPMuxGetFeatures(const WebPMux* mux,
+                                             uint32_t* flags);
+
+// Gets number of chunks with the given 'id' in the mux object.
+// Parameters:
+//   mux - (in) object from which the info is to be fetched
+//   id - (in) chunk id specifying the type of chunk
+//   num_elements - (out) number of chunks with the given chunk id
+// Returns:
+//   WEBP_MUX_INVALID_ARGUMENT - if mux, or num_elements is NULL.
+//   WEBP_MUX_OK - on success.
+WEBP_EXTERN(WebPMuxError) WebPMuxNumChunks(const WebPMux* mux,
+                                           WebPChunkId id, int* num_elements);
+
+// Assembles all chunks in WebP RIFF format and returns in 'assembled_data'.
+// This function also validates the mux object.
+// Note: The content of 'assembled_data' will be ignored and overwritten.
+// Also, the content of 'assembled_data' is allocated using malloc(), and NOT
+// owned by the 'mux' object. It MUST be deallocated by the caller by calling
+// WebPDataClear(). It's always safe to call WebPDataClear() upon return,
+// even in case of error.
+// Parameters:
+//   mux - (in/out) object whose chunks are to be assembled
+//   assembled_data - (out) assembled WebP data
+// Returns:
+//   WEBP_MUX_BAD_DATA - if mux object is invalid.
+//   WEBP_MUX_INVALID_ARGUMENT - if mux or assembled_data is NULL.
+//   WEBP_MUX_MEMORY_ERROR - on memory allocation error.
+//   WEBP_MUX_OK - on success.
+WEBP_EXTERN(WebPMuxError) WebPMuxAssemble(WebPMux* mux,
+                                          WebPData* assembled_data);
+
+//------------------------------------------------------------------------------
+// WebPAnimEncoder API
+//
+// This API allows encoding (possibly) animated WebP images.
+//
+// Code Example:
+/*
+  WebPAnimEncoderOptions enc_options;
+  WebPAnimEncoderOptionsInit(&enc_options);
+  // Tune 'enc_options' as needed.
+  WebPAnimEncoder* enc = WebPAnimEncoderNew(width, height, &enc_options);
+  while(<there are more frames>) {
+    WebPConfig config;
+    WebPConfigInit(&config);
+    // Tune 'config' as needed.
+    WebPAnimEncoderAdd(enc, frame, timestamp_ms, &config);
+  }
+  WebPAnimEncoderAdd(enc, NULL, timestamp_ms, NULL);
+  WebPAnimEncoderAssemble(enc, webp_data);
+  WebPAnimEncoderDelete(enc);
+  // Write the 'webp_data' to a file, or re-mux it further.
+*/
+
+typedef struct WebPAnimEncoder WebPAnimEncoder;  // Main opaque object.
+
+// Forward declarations. Defined in encode.h.
+struct WebPPicture;
+struct WebPConfig;
+
+// Global options.
+struct WebPAnimEncoderOptions {
+  WebPMuxAnimParams anim_params;  // Animation parameters.
+  int minimize_size;    // If true, minimize the output size (slow). Implicitly
+                        // disables key-frame insertion.
+  int kmin;
+  int kmax;             // Minimum and maximum distance between consecutive key
+                        // frames in the output. The library may insert some key
+                        // frames as needed to satisfy this criteria.
+                        // Note that these conditions should hold: kmax > kmin
+                        // and kmin >= kmax / 2 + 1. Also, if kmax <= 0, then
+                        // key-frame insertion is disabled; and if kmax == 1,
+                        // then all frames will be key-frames (kmin value does
+                        // not matter for these special cases).
+  int allow_mixed;      // If true, use mixed compression mode; may choose
+                        // either lossy and lossless for each frame.
+  int verbose;          // If true, print info and warning messages to stderr.
+
+  uint32_t padding[4];  // Padding for later use.
+};
+
+// Internal, version-checked, entry point.
+WEBP_EXTERN(int) WebPAnimEncoderOptionsInitInternal(
+    WebPAnimEncoderOptions*, int);
+
+// Should always be called, to initialize a fresh WebPAnimEncoderOptions
+// structure before modification. Returns false in case of version mismatch.
+// WebPAnimEncoderOptionsInit() must have succeeded before using the
+// 'enc_options' object.
+static WEBP_INLINE int WebPAnimEncoderOptionsInit(
+    WebPAnimEncoderOptions* enc_options) {
+  return WebPAnimEncoderOptionsInitInternal(enc_options, WEBP_MUX_ABI_VERSION);
+}
+
+// Internal, version-checked, entry point.
+WEBP_EXTERN(WebPAnimEncoder*) WebPAnimEncoderNewInternal(
+    int, int, const WebPAnimEncoderOptions*, int);
+
+// Creates and initializes a WebPAnimEncoder object.
+// Parameters:
+//   width/height - (in) canvas width and height of the animation.
+//   enc_options - (in) encoding options; can be passed NULL to pick
+//                      reasonable defaults.
+// Returns:
+//   A pointer to the newly created WebPAnimEncoder object.
+//   Or NULL in case of memory error.
+static WEBP_INLINE WebPAnimEncoder* WebPAnimEncoderNew(
+    int width, int height, const WebPAnimEncoderOptions* enc_options) {
+  return WebPAnimEncoderNewInternal(width, height, enc_options,
+                                    WEBP_MUX_ABI_VERSION);
+}
+
+// Optimize the given frame for WebP, encode it and add it to the
+// WebPAnimEncoder object.
+// The last call to 'WebPAnimEncoderAdd' should be with frame = NULL, which
+// indicates that no more frames are to be added. This call is also used to
+// determine the duration of the last frame.
+// Parameters:
+//   enc - (in/out) object to which the frame is to be added.
+//   frame - (in/out) frame data in ARGB or YUV(A) format. If it is in YUV(A)
+//           format, it will be converted to ARGB, which incurs a small loss.
+//   timestamp_ms - (in) timestamp of this frame in milliseconds.
+//                       Duration of a frame would be calculated as
+//                       "timestamp of next frame - timestamp of this frame".
+//                       Hence, timestamps should be in non-decreasing order.
+//   config - (in) encoding options; can be passed NULL to pick
+//            reasonable defaults.
+// Returns:
+//   On error, returns false and frame->error_code is set appropriately.
+//   Otherwise, returns true.
+WEBP_EXTERN(int) WebPAnimEncoderAdd(
+    WebPAnimEncoder* enc, struct WebPPicture* frame, int timestamp_ms,
+    const struct WebPConfig* config);
+
+// Assemble all frames added so far into a WebP bitstream.
+// This call should be preceded by  a call to 'WebPAnimEncoderAdd' with
+// frame = NULL; if not, the duration of the last frame will be internally
+// estimated.
+// Parameters:
+//   enc - (in/out) object from which the frames are to be assembled.
+//   webp_data - (out) generated WebP bitstream.
+// Returns:
+//   True on success.
+WEBP_EXTERN(int) WebPAnimEncoderAssemble(WebPAnimEncoder* enc,
+                                         WebPData* webp_data);
+
+// Get error string corresponding to the most recent call using 'enc'. The
+// returned string is owned by 'enc' and is valid only until the next call to
+// WebPAnimEncoderAdd() or WebPAnimEncoderAssemble() or WebPAnimEncoderDelete().
+// Parameters:
+//   enc - (in/out) object from which the error string is to be fetched.
+// Returns:
+//   NULL if 'enc' is NULL. Otherwise, returns the error string if the last call
+//   to 'enc' had an error, or an empty string if the last call was a success.
+WEBP_EXTERN(const char*) WebPAnimEncoderGetError(WebPAnimEncoder* enc);
+
+// Deletes the WebPAnimEncoder object.
+// Parameters:
+//   enc - (in/out) object to be deleted
+WEBP_EXTERN(void) WebPAnimEncoderDelete(WebPAnimEncoder* enc);
+
+//------------------------------------------------------------------------------
+
+#ifdef __cplusplus
+}    // extern "C"
+#endif
+
+#endif  /* WEBP_WEBP_MUX_H_ */
diff --git a/src/Android.bp b/src/Android.bp
index df075ec..090b656 100644
--- a/src/Android.bp
+++ b/src/Android.bp
@@ -68,6 +68,10 @@
         "enc/tree_enc.c",
         "enc/vp8l_enc.c",
         "enc/webp_enc.c",
+        "mux/anim_encode.c",
+        "mux/muxedit.c",
+        "mux/muxinternal.c",
+        "mux/muxread.c",
         "utils/bit_reader_utils.c",
         "utils/bit_writer_utils.c",
         "utils/color_cache_utils.c",
diff --git a/src/mux/anim_encode.c b/src/mux/anim_encode.c
new file mode 100644
index 0000000..6066388
--- /dev/null
+++ b/src/mux/anim_encode.c
@@ -0,0 +1,1579 @@
+// Copyright 2014 Google Inc. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the COPYING file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+// -----------------------------------------------------------------------------
+//
+//  AnimEncoder implementation.
+//
+
+#include <assert.h>
+#include <limits.h>
+#include <math.h>    // for pow()
+#include <stdio.h>
+#include <stdlib.h>  // for abs()
+
+#include "../mux/animi.h"
+#include "../utils/utils.h"
+#include "../webp/decode.h"
+#include "../webp/encode.h"
+#include "../webp/format_constants.h"
+#include "../webp/mux.h"
+
+#if defined(_MSC_VER) && _MSC_VER < 1900
+#define snprintf _snprintf
+#endif
+
+#define ERROR_STR_MAX_LENGTH 100
+
+//------------------------------------------------------------------------------
+// Internal structs.
+
+// Stores frame rectangle dimensions.
+typedef struct {
+  int x_offset_, y_offset_, width_, height_;
+} FrameRect;
+
+// Used to store two candidates of encoded data for an animation frame. One of
+// the two will be chosen later.
+typedef struct {
+  WebPMuxFrameInfo sub_frame_;  // Encoded frame rectangle.
+  WebPMuxFrameInfo key_frame_;  // Encoded frame if it is a key-frame.
+  int is_key_frame_;            // True if 'key_frame' has been chosen.
+} EncodedFrame;
+
+struct WebPAnimEncoder {
+  const int canvas_width_;                  // Canvas width.
+  const int canvas_height_;                 // Canvas height.
+  const WebPAnimEncoderOptions options_;    // Global encoding options.
+
+  FrameRect prev_rect_;               // Previous WebP frame rectangle.
+  WebPConfig last_config_;            // Cached in case a re-encode is needed.
+  WebPConfig last_config_reversed_;   // If 'last_config_' uses lossless, then
+                                      // this config uses lossy and vice versa;
+                                      // only valid if 'options_.allow_mixed'
+                                      // is true.
+
+  WebPPicture* curr_canvas_;          // Only pointer; we don't own memory.
+
+  // Canvas buffers.
+  WebPPicture curr_canvas_copy_;      // Possibly modified current canvas.
+  int curr_canvas_copy_modified_;     // True if pixels in 'curr_canvas_copy_'
+                                      // differ from those in 'curr_canvas_'.
+
+  WebPPicture prev_canvas_;           // Previous canvas.
+  WebPPicture prev_canvas_disposed_;  // Previous canvas disposed to background.
+
+  // Encoded data.
+  EncodedFrame* encoded_frames_;      // Array of encoded frames.
+  size_t size_;             // Number of allocated frames.
+  size_t start_;            // Frame start index.
+  size_t count_;            // Number of valid frames.
+  size_t flush_count_;      // If >0, 'flush_count' frames starting from
+                            // 'start' are ready to be added to mux.
+
+  // key-frame related.
+  int64_t best_delta_;      // min(canvas size - frame size) over the frames.
+                            // Can be negative in certain cases due to
+                            // transparent pixels in a frame.
+  int keyframe_;            // Index of selected key-frame relative to 'start_'.
+  int count_since_key_frame_;     // Frames seen since the last key-frame.
+
+  int first_timestamp_;           // Timestamp of the first frame.
+  int prev_timestamp_;            // Timestamp of the last added frame.
+  int prev_candidate_undecided_;  // True if it's not yet decided if previous
+                                  // frame would be a sub-frame or a key-frame.
+
+  // Misc.
+  int is_first_frame_;  // True if first frame is yet to be added/being added.
+  int got_null_frame_;  // True if WebPAnimEncoderAdd() has already been called
+                        // with a NULL frame.
+
+  size_t in_frame_count_;   // Number of input frames processed so far.
+  size_t out_frame_count_;  // Number of frames added to mux so far. This may be
+                            // different from 'in_frame_count_' due to merging.
+
+  WebPMux* mux_;        // Muxer to assemble the WebP bitstream.
+  char error_str_[ERROR_STR_MAX_LENGTH];  // Error string. Empty if no error.
+};
+
+// -----------------------------------------------------------------------------
+// Life of WebPAnimEncoder object.
+
+#define DELTA_INFINITY      (1ULL << 32)
+#define KEYFRAME_NONE       (-1)
+
+// Reset the counters in the WebPAnimEncoder.
+static void ResetCounters(WebPAnimEncoder* const enc) {
+  enc->start_ = 0;
+  enc->count_ = 0;
+  enc->flush_count_ = 0;
+  enc->best_delta_ = DELTA_INFINITY;
+  enc->keyframe_ = KEYFRAME_NONE;
+}
+
+static void DisableKeyframes(WebPAnimEncoderOptions* const enc_options) {
+  enc_options->kmax = INT_MAX;
+  enc_options->kmin = enc_options->kmax - 1;
+}
+
+#define MAX_CACHED_FRAMES 30
+
+static void SanitizeEncoderOptions(WebPAnimEncoderOptions* const enc_options) {
+  int print_warning = enc_options->verbose;
+
+  if (enc_options->minimize_size) {
+    DisableKeyframes(enc_options);
+  }
+
+  if (enc_options->kmax == 1) {  // All frames will be key-frames.
+    enc_options->kmin = 0;
+    enc_options->kmax = 0;
+    return;
+  } else if (enc_options->kmax <= 0) {
+    DisableKeyframes(enc_options);
+    print_warning = 0;
+  }
+
+  if (enc_options->kmin >= enc_options->kmax) {
+    enc_options->kmin = enc_options->kmax - 1;
+    if (print_warning) {
+      fprintf(stderr, "WARNING: Setting kmin = %d, so that kmin < kmax.\n",
+              enc_options->kmin);
+    }
+  } else {
+    const int kmin_limit = enc_options->kmax / 2 + 1;
+    if (enc_options->kmin < kmin_limit && kmin_limit < enc_options->kmax) {
+      // This ensures that enc.keyframe + kmin >= kmax is always true. So, we
+      // can flush all the frames in the 'count_since_key_frame == kmax' case.
+      enc_options->kmin = kmin_limit;
+      if (print_warning) {
+        fprintf(stderr,
+                "WARNING: Setting kmin = %d, so that kmin >= kmax / 2 + 1.\n",
+                enc_options->kmin);
+      }
+    }
+  }
+  // Limit the max number of frames that are allocated.
+  if (enc_options->kmax - enc_options->kmin > MAX_CACHED_FRAMES) {
+    enc_options->kmin = enc_options->kmax - MAX_CACHED_FRAMES;
+    if (print_warning) {
+      fprintf(stderr,
+              "WARNING: Setting kmin = %d, so that kmax - kmin <= %d.\n",
+              enc_options->kmin, MAX_CACHED_FRAMES);
+    }
+  }
+  assert(enc_options->kmin < enc_options->kmax);
+}
+
+#undef MAX_CACHED_FRAMES
+
+static void DefaultEncoderOptions(WebPAnimEncoderOptions* const enc_options) {
+  enc_options->anim_params.loop_count = 0;
+  enc_options->anim_params.bgcolor = 0xffffffff;  // White.
+  enc_options->minimize_size = 0;
+  DisableKeyframes(enc_options);
+  enc_options->allow_mixed = 0;
+  enc_options->verbose = 0;
+}
+
+int WebPAnimEncoderOptionsInitInternal(WebPAnimEncoderOptions* enc_options,
+                                       int abi_version) {
+  if (enc_options == NULL ||
+      WEBP_ABI_IS_INCOMPATIBLE(abi_version, WEBP_MUX_ABI_VERSION)) {
+    return 0;
+  }
+  DefaultEncoderOptions(enc_options);
+  return 1;
+}
+
+// This starting value is more fit to WebPCleanupTransparentAreaLossless().
+#define TRANSPARENT_COLOR   0x00000000
+
+static void ClearRectangle(WebPPicture* const picture,
+                           int left, int top, int width, int height) {
+  int j;
+  for (j = top; j < top + height; ++j) {
+    uint32_t* const dst = picture->argb + j * picture->argb_stride;
+    int i;
+    for (i = left; i < left + width; ++i) {
+      dst[i] = TRANSPARENT_COLOR;
+    }
+  }
+}
+
+static void WebPUtilClearPic(WebPPicture* const picture,
+                             const FrameRect* const rect) {
+  if (rect != NULL) {
+    ClearRectangle(picture, rect->x_offset_, rect->y_offset_,
+                   rect->width_, rect->height_);
+  } else {
+    ClearRectangle(picture, 0, 0, picture->width, picture->height);
+  }
+}
+
+static void MarkNoError(WebPAnimEncoder* const enc) {
+  enc->error_str_[0] = '\0';  // Empty string.
+}
+
+static void MarkError(WebPAnimEncoder* const enc, const char* str) {
+  if (snprintf(enc->error_str_, ERROR_STR_MAX_LENGTH, "%s.", str) < 0) {
+    assert(0);  // FIX ME!
+  }
+}
+
+static void MarkError2(WebPAnimEncoder* const enc,
+                       const char* str, int error_code) {
+  if (snprintf(enc->error_str_, ERROR_STR_MAX_LENGTH, "%s: %d.", str,
+               error_code) < 0) {
+    assert(0);  // FIX ME!
+  }
+}
+
+WebPAnimEncoder* WebPAnimEncoderNewInternal(
+    int width, int height, const WebPAnimEncoderOptions* enc_options,
+    int abi_version) {
+  WebPAnimEncoder* enc;
+
+  if (WEBP_ABI_IS_INCOMPATIBLE(abi_version, WEBP_MUX_ABI_VERSION)) {
+    return NULL;
+  }
+  if (width <= 0 || height <= 0 ||
+      (width * (uint64_t)height) >= MAX_IMAGE_AREA) {
+    return NULL;
+  }
+
+  enc = (WebPAnimEncoder*)WebPSafeCalloc(1, sizeof(*enc));
+  if (enc == NULL) return NULL;
+  // sanity inits, so we can call WebPAnimEncoderDelete():
+  enc->encoded_frames_ = NULL;
+  enc->mux_ = NULL;
+  MarkNoError(enc);
+
+  // Dimensions and options.
+  *(int*)&enc->canvas_width_ = width;
+  *(int*)&enc->canvas_height_ = height;
+  if (enc_options != NULL) {
+    *(WebPAnimEncoderOptions*)&enc->options_ = *enc_options;
+    SanitizeEncoderOptions((WebPAnimEncoderOptions*)&enc->options_);
+  } else {
+    DefaultEncoderOptions((WebPAnimEncoderOptions*)&enc->options_);
+  }
+
+  // Canvas buffers.
+  if (!WebPPictureInit(&enc->curr_canvas_copy_) ||
+      !WebPPictureInit(&enc->prev_canvas_) ||
+      !WebPPictureInit(&enc->prev_canvas_disposed_)) {
+    goto Err;
+  }
+  enc->curr_canvas_copy_.width = width;
+  enc->curr_canvas_copy_.height = height;
+  enc->curr_canvas_copy_.use_argb = 1;
+  if (!WebPPictureAlloc(&enc->curr_canvas_copy_) ||
+      !WebPPictureCopy(&enc->curr_canvas_copy_, &enc->prev_canvas_) ||
+      !WebPPictureCopy(&enc->curr_canvas_copy_, &enc->prev_canvas_disposed_)) {
+    goto Err;
+  }
+  WebPUtilClearPic(&enc->prev_canvas_, NULL);
+  enc->curr_canvas_copy_modified_ = 1;
+
+  // Encoded frames.
+  ResetCounters(enc);
+  // Note: one extra storage is for the previous frame.
+  enc->size_ = enc->options_.kmax - enc->options_.kmin + 1;
+  // We need space for at least 2 frames. But when kmin, kmax are both zero,
+  // enc->size_ will be 1. So we handle that special case below.
+  if (enc->size_ < 2) enc->size_ = 2;
+  enc->encoded_frames_ =
+      (EncodedFrame*)WebPSafeCalloc(enc->size_, sizeof(*enc->encoded_frames_));
+  if (enc->encoded_frames_ == NULL) goto Err;
+
+  enc->mux_ = WebPMuxNew();
+  if (enc->mux_ == NULL) goto Err;
+
+  enc->count_since_key_frame_ = 0;
+  enc->first_timestamp_ = 0;
+  enc->prev_timestamp_ = 0;
+  enc->prev_candidate_undecided_ = 0;
+  enc->is_first_frame_ = 1;
+  enc->got_null_frame_ = 0;
+
+  return enc;  // All OK.
+
+ Err:
+  WebPAnimEncoderDelete(enc);
+  return NULL;
+}
+
+// Release the data contained by 'encoded_frame'.
+static void FrameRelease(EncodedFrame* const encoded_frame) {
+  if (encoded_frame != NULL) {
+    WebPDataClear(&encoded_frame->sub_frame_.bitstream);
+    WebPDataClear(&encoded_frame->key_frame_.bitstream);
+    memset(encoded_frame, 0, sizeof(*encoded_frame));
+  }
+}
+
+void WebPAnimEncoderDelete(WebPAnimEncoder* enc) {
+  if (enc != NULL) {
+    WebPPictureFree(&enc->curr_canvas_copy_);
+    WebPPictureFree(&enc->prev_canvas_);
+    WebPPictureFree(&enc->prev_canvas_disposed_);
+    if (enc->encoded_frames_ != NULL) {
+      size_t i;
+      for (i = 0; i < enc->size_; ++i) {
+        FrameRelease(&enc->encoded_frames_[i]);
+      }
+      WebPSafeFree(enc->encoded_frames_);
+    }
+    WebPMuxDelete(enc->mux_);
+    WebPSafeFree(enc);
+  }
+}
+
+// -----------------------------------------------------------------------------
+// Frame addition.
+
+// Returns cached frame at the given 'position'.
+static EncodedFrame* GetFrame(const WebPAnimEncoder* const enc,
+                              size_t position) {
+  assert(enc->start_ + position < enc->size_);
+  return &enc->encoded_frames_[enc->start_ + position];
+}
+
+typedef int (*ComparePixelsFunc)(const uint32_t*, int, const uint32_t*, int,
+                                 int, int);
+
+// Returns true if 'length' number of pixels in 'src' and 'dst' are equal,
+// assuming the given step sizes between pixels.
+// 'max_allowed_diff' is unused and only there to allow function pointer use.
+static WEBP_INLINE int ComparePixelsLossless(const uint32_t* src, int src_step,
+                                             const uint32_t* dst, int dst_step,
+                                             int length, int max_allowed_diff) {
+  (void)max_allowed_diff;
+  assert(length > 0);
+  while (length-- > 0) {
+    if (*src != *dst) {
+      return 0;
+    }
+    src += src_step;
+    dst += dst_step;
+  }
+  return 1;
+}
+
+// Helper to check if each channel in 'src' and 'dst' is at most off by
+// 'max_allowed_diff'.
+static WEBP_INLINE int PixelsAreSimilar(uint32_t src, uint32_t dst,
+                                        int max_allowed_diff) {
+  const int src_a = (src >> 24) & 0xff;
+  const int src_r = (src >> 16) & 0xff;
+  const int src_g = (src >> 8) & 0xff;
+  const int src_b = (src >> 0) & 0xff;
+  const int dst_a = (dst >> 24) & 0xff;
+  const int dst_r = (dst >> 16) & 0xff;
+  const int dst_g = (dst >> 8) & 0xff;
+  const int dst_b = (dst >> 0) & 0xff;
+
+  return (src_a == dst_a) &&
+         (abs(src_r - dst_r) * dst_a <= (max_allowed_diff * 255)) &&
+         (abs(src_g - dst_g) * dst_a <= (max_allowed_diff * 255)) &&
+         (abs(src_b - dst_b) * dst_a <= (max_allowed_diff * 255));
+}
+
+// Returns true if 'length' number of pixels in 'src' and 'dst' are within an
+// error bound, assuming the given step sizes between pixels.
+static WEBP_INLINE int ComparePixelsLossy(const uint32_t* src, int src_step,
+                                          const uint32_t* dst, int dst_step,
+                                          int length, int max_allowed_diff) {
+  assert(length > 0);
+  while (length-- > 0) {
+    if (!PixelsAreSimilar(*src, *dst, max_allowed_diff)) {
+      return 0;
+    }
+    src += src_step;
+    dst += dst_step;
+  }
+  return 1;
+}
+
+static int IsEmptyRect(const FrameRect* const rect) {
+  return (rect->width_ == 0) || (rect->height_ == 0);
+}
+
+static int QualityToMaxDiff(float quality) {
+  const double val = pow(quality / 100., 0.5);
+  const double max_diff = 31 * (1 - val) + 1 * val;
+  return (int)(max_diff + 0.5);
+}
+
+// Assumes that an initial valid guess of change rectangle 'rect' is passed.
+static void MinimizeChangeRectangle(const WebPPicture* const src,
+                                    const WebPPicture* const dst,
+                                    FrameRect* const rect,
+                                    int is_lossless, float quality) {
+  int i, j;
+  const ComparePixelsFunc compare_pixels =
+      is_lossless ? ComparePixelsLossless : ComparePixelsLossy;
+  const int max_allowed_diff_lossy = QualityToMaxDiff(quality);
+  const int max_allowed_diff = is_lossless ? 0 : max_allowed_diff_lossy;
+
+  // Sanity checks.
+  assert(src->width == dst->width && src->height == dst->height);
+  assert(rect->x_offset_ + rect->width_ <= dst->width);
+  assert(rect->y_offset_ + rect->height_ <= dst->height);
+
+  // Left boundary.
+  for (i = rect->x_offset_; i < rect->x_offset_ + rect->width_; ++i) {
+    const uint32_t* const src_argb =
+        &src->argb[rect->y_offset_ * src->argb_stride + i];
+    const uint32_t* const dst_argb =
+        &dst->argb[rect->y_offset_ * dst->argb_stride + i];
+    if (compare_pixels(src_argb, src->argb_stride, dst_argb, dst->argb_stride,
+                       rect->height_, max_allowed_diff)) {
+      --rect->width_;  // Redundant column.
+      ++rect->x_offset_;
+    } else {
+      break;
+    }
+  }
+  if (rect->width_ == 0) goto NoChange;
+
+  // Right boundary.
+  for (i = rect->x_offset_ + rect->width_ - 1; i >= rect->x_offset_; --i) {
+    const uint32_t* const src_argb =
+        &src->argb[rect->y_offset_ * src->argb_stride + i];
+    const uint32_t* const dst_argb =
+        &dst->argb[rect->y_offset_ * dst->argb_stride + i];
+    if (compare_pixels(src_argb, src->argb_stride, dst_argb, dst->argb_stride,
+                       rect->height_, max_allowed_diff)) {
+      --rect->width_;  // Redundant column.
+    } else {
+      break;
+    }
+  }
+  if (rect->width_ == 0) goto NoChange;
+
+  // Top boundary.
+  for (j = rect->y_offset_; j < rect->y_offset_ + rect->height_; ++j) {
+    const uint32_t* const src_argb =
+        &src->argb[j * src->argb_stride + rect->x_offset_];
+    const uint32_t* const dst_argb =
+        &dst->argb[j * dst->argb_stride + rect->x_offset_];
+    if (compare_pixels(src_argb, 1, dst_argb, 1, rect->width_,
+                       max_allowed_diff)) {
+      --rect->height_;  // Redundant row.
+      ++rect->y_offset_;
+    } else {
+      break;
+    }
+  }
+  if (rect->height_ == 0) goto NoChange;
+
+  // Bottom boundary.
+  for (j = rect->y_offset_ + rect->height_ - 1; j >= rect->y_offset_; --j) {
+    const uint32_t* const src_argb =
+        &src->argb[j * src->argb_stride + rect->x_offset_];
+    const uint32_t* const dst_argb =
+        &dst->argb[j * dst->argb_stride + rect->x_offset_];
+    if (compare_pixels(src_argb, 1, dst_argb, 1, rect->width_,
+                       max_allowed_diff)) {
+      --rect->height_;  // Redundant row.
+    } else {
+      break;
+    }
+  }
+  if (rect->height_ == 0) goto NoChange;
+
+  if (IsEmptyRect(rect)) {
+ NoChange:
+    rect->x_offset_ = 0;
+    rect->y_offset_ = 0;
+    rect->width_ = 0;
+    rect->height_ = 0;
+  }
+}
+
+// Snap rectangle to even offsets (and adjust dimensions if needed).
+static WEBP_INLINE void SnapToEvenOffsets(FrameRect* const rect) {
+  rect->width_ += (rect->x_offset_ & 1);
+  rect->height_ += (rect->y_offset_ & 1);
+  rect->x_offset_ &= ~1;
+  rect->y_offset_ &= ~1;
+}
+
+typedef struct {
+  int should_try_;               // Should try this set of parameters.
+  int empty_rect_allowed_;       // Frame with empty rectangle can be skipped.
+  FrameRect rect_ll_;            // Frame rectangle for lossless compression.
+  WebPPicture sub_frame_ll_;     // Sub-frame pic for lossless compression.
+  FrameRect rect_lossy_;         // Frame rectangle for lossy compression.
+                                 // Could be smaller than rect_ll_ as pixels
+                                 // with small diffs can be ignored.
+  WebPPicture sub_frame_lossy_;  // Sub-frame pic for lossless compression.
+} SubFrameParams;
+
+static int SubFrameParamsInit(SubFrameParams* const params,
+                              int should_try, int empty_rect_allowed) {
+  params->should_try_ = should_try;
+  params->empty_rect_allowed_ = empty_rect_allowed;
+  if (!WebPPictureInit(&params->sub_frame_ll_) ||
+      !WebPPictureInit(&params->sub_frame_lossy_)) {
+    return 0;
+  }
+  return 1;
+}
+
+static void SubFrameParamsFree(SubFrameParams* const params) {
+  WebPPictureFree(&params->sub_frame_ll_);
+  WebPPictureFree(&params->sub_frame_lossy_);
+}
+
+// Given previous and current canvas, picks the optimal rectangle for the
+// current frame based on 'is_lossless' and other parameters. Assumes that the
+// initial guess 'rect' is valid.
+static int GetSubRect(const WebPPicture* const prev_canvas,
+                      const WebPPicture* const curr_canvas, int is_key_frame,
+                      int is_first_frame, int empty_rect_allowed,
+                      int is_lossless, float quality, FrameRect* const rect,
+                      WebPPicture* const sub_frame) {
+  if (!is_key_frame || is_first_frame) {  // Optimize frame rectangle.
+    // Note: This behaves as expected for first frame, as 'prev_canvas' is
+    // initialized to a fully transparent canvas in the beginning.
+    MinimizeChangeRectangle(prev_canvas, curr_canvas, rect,
+                            is_lossless, quality);
+  }
+
+  if (IsEmptyRect(rect)) {
+    if (empty_rect_allowed) {  // No need to get 'sub_frame'.
+      return 1;
+    } else {                   // Force a 1x1 rectangle.
+      rect->width_ = 1;
+      rect->height_ = 1;
+      assert(rect->x_offset_ == 0);
+      assert(rect->y_offset_ == 0);
+    }
+  }
+
+  SnapToEvenOffsets(rect);
+  return WebPPictureView(curr_canvas, rect->x_offset_, rect->y_offset_,
+                         rect->width_, rect->height_, sub_frame);
+}
+
+// Picks optimal frame rectangle for both lossless and lossy compression. The
+// initial guess for frame rectangles will be the full canvas.
+static int GetSubRects(const WebPPicture* const prev_canvas,
+                       const WebPPicture* const curr_canvas, int is_key_frame,
+                       int is_first_frame, float quality,
+                       SubFrameParams* const params) {
+  // Lossless frame rectangle.
+  params->rect_ll_.x_offset_ = 0;
+  params->rect_ll_.y_offset_ = 0;
+  params->rect_ll_.width_ = curr_canvas->width;
+  params->rect_ll_.height_ = curr_canvas->height;
+  if (!GetSubRect(prev_canvas, curr_canvas, is_key_frame, is_first_frame,
+                  params->empty_rect_allowed_, 1, quality,
+                  &params->rect_ll_, &params->sub_frame_ll_)) {
+    return 0;
+  }
+  // Lossy frame rectangle.
+  params->rect_lossy_ = params->rect_ll_;  // seed with lossless rect.
+  return GetSubRect(prev_canvas, curr_canvas, is_key_frame, is_first_frame,
+                    params->empty_rect_allowed_, 0, quality,
+                    &params->rect_lossy_, &params->sub_frame_lossy_);
+}
+
+static WEBP_INLINE int clip(int v, int min_v, int max_v) {
+  return (v < min_v) ? min_v : (v > max_v) ? max_v : v;
+}
+
+int WebPAnimEncoderRefineRect(
+    const WebPPicture* const prev_canvas, const WebPPicture* const curr_canvas,
+    int is_lossless, float quality, int* const x_offset, int* const y_offset,
+    int* const width, int* const height) {
+  FrameRect rect;
+  const int right = clip(*x_offset + *width, 0, curr_canvas->width);
+  const int left = clip(*x_offset, 0, curr_canvas->width - 1);
+  const int bottom = clip(*y_offset + *height, 0, curr_canvas->height);
+  const int top = clip(*y_offset, 0, curr_canvas->height - 1);
+  if (prev_canvas == NULL || curr_canvas == NULL ||
+      prev_canvas->width != curr_canvas->width ||
+      prev_canvas->height != curr_canvas->height ||
+      !prev_canvas->use_argb || !curr_canvas->use_argb) {
+    return 0;
+  }
+  rect.x_offset_ = left;
+  rect.y_offset_ = top;
+  rect.width_ = clip(right - left, 0, curr_canvas->width - rect.x_offset_);
+  rect.height_ = clip(bottom - top, 0, curr_canvas->height - rect.y_offset_);
+  MinimizeChangeRectangle(prev_canvas, curr_canvas, &rect, is_lossless,
+                          quality);
+  SnapToEvenOffsets(&rect);
+  *x_offset = rect.x_offset_;
+  *y_offset = rect.y_offset_;
+  *width = rect.width_;
+  *height = rect.height_;
+  return 1;
+}
+
+static void DisposeFrameRectangle(int dispose_method,
+                                  const FrameRect* const rect,
+                                  WebPPicture* const curr_canvas) {
+  assert(rect != NULL);
+  if (dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) {
+    WebPUtilClearPic(curr_canvas, rect);
+  }
+}
+
+static uint32_t RectArea(const FrameRect* const rect) {
+  return (uint32_t)rect->width_ * rect->height_;
+}
+
+static int IsLosslessBlendingPossible(const WebPPicture* const src,
+                                      const WebPPicture* const dst,
+                                      const FrameRect* const rect) {
+  int i, j;
+  assert(src->width == dst->width && src->height == dst->height);
+  assert(rect->x_offset_ + rect->width_ <= dst->width);
+  assert(rect->y_offset_ + rect->height_ <= dst->height);
+  for (j = rect->y_offset_; j < rect->y_offset_ + rect->height_; ++j) {
+    for (i = rect->x_offset_; i < rect->x_offset_ + rect->width_; ++i) {
+      const uint32_t src_pixel = src->argb[j * src->argb_stride + i];
+      const uint32_t dst_pixel = dst->argb[j * dst->argb_stride + i];
+      const uint32_t dst_alpha = dst_pixel >> 24;
+      if (dst_alpha != 0xff && src_pixel != dst_pixel) {
+        // In this case, if we use blending, we can't attain the desired
+        // 'dst_pixel' value for this pixel. So, blending is not possible.
+        return 0;
+      }
+    }
+  }
+  return 1;
+}
+
+static int IsLossyBlendingPossible(const WebPPicture* const src,
+                                   const WebPPicture* const dst,
+                                   const FrameRect* const rect,
+                                   float quality) {
+  const int max_allowed_diff_lossy = QualityToMaxDiff(quality);
+  int i, j;
+  assert(src->width == dst->width && src->height == dst->height);
+  assert(rect->x_offset_ + rect->width_ <= dst->width);
+  assert(rect->y_offset_ + rect->height_ <= dst->height);
+  for (j = rect->y_offset_; j < rect->y_offset_ + rect->height_; ++j) {
+    for (i = rect->x_offset_; i < rect->x_offset_ + rect->width_; ++i) {
+      const uint32_t src_pixel = src->argb[j * src->argb_stride + i];
+      const uint32_t dst_pixel = dst->argb[j * dst->argb_stride + i];
+      const uint32_t dst_alpha = dst_pixel >> 24;
+      if (dst_alpha != 0xff &&
+          !PixelsAreSimilar(src_pixel, dst_pixel, max_allowed_diff_lossy)) {
+        // In this case, if we use blending, we can't attain the desired
+        // 'dst_pixel' value for this pixel. So, blending is not possible.
+        return 0;
+      }
+    }
+  }
+  return 1;
+}
+
+// For pixels in 'rect', replace those pixels in 'dst' that are same as 'src' by
+// transparent pixels.
+// Returns true if at least one pixel gets modified.
+static int IncreaseTransparency(const WebPPicture* const src,
+                                const FrameRect* const rect,
+                                WebPPicture* const dst) {
+  int i, j;
+  int modified = 0;
+  assert(src != NULL && dst != NULL && rect != NULL);
+  assert(src->width == dst->width && src->height == dst->height);
+  for (j = rect->y_offset_; j < rect->y_offset_ + rect->height_; ++j) {
+    const uint32_t* const psrc = src->argb + j * src->argb_stride;
+    uint32_t* const pdst = dst->argb + j * dst->argb_stride;
+    for (i = rect->x_offset_; i < rect->x_offset_ + rect->width_; ++i) {
+      if (psrc[i] == pdst[i] && pdst[i] != TRANSPARENT_COLOR) {
+        pdst[i] = TRANSPARENT_COLOR;
+        modified = 1;
+      }
+    }
+  }
+  return modified;
+}
+
+#undef TRANSPARENT_COLOR
+
+// Replace similar blocks of pixels by a 'see-through' transparent block
+// with uniform average color.
+// Assumes lossy compression is being used.
+// Returns true if at least one pixel gets modified.
+static int FlattenSimilarBlocks(const WebPPicture* const src,
+                                const FrameRect* const rect,
+                                WebPPicture* const dst, float quality) {
+  const int max_allowed_diff_lossy = QualityToMaxDiff(quality);
+  int i, j;
+  int modified = 0;
+  const int block_size = 8;
+  const int y_start = (rect->y_offset_ + block_size) & ~(block_size - 1);
+  const int y_end = (rect->y_offset_ + rect->height_) & ~(block_size - 1);
+  const int x_start = (rect->x_offset_ + block_size) & ~(block_size - 1);
+  const int x_end = (rect->x_offset_ + rect->width_) & ~(block_size - 1);
+  assert(src != NULL && dst != NULL && rect != NULL);
+  assert(src->width == dst->width && src->height == dst->height);
+  assert((block_size & (block_size - 1)) == 0);  // must be a power of 2
+  // Iterate over each block and count similar pixels.
+  for (j = y_start; j < y_end; j += block_size) {
+    for (i = x_start; i < x_end; i += block_size) {
+      int cnt = 0;
+      int avg_r = 0, avg_g = 0, avg_b = 0;
+      int x, y;
+      const uint32_t* const psrc = src->argb + j * src->argb_stride + i;
+      uint32_t* const pdst = dst->argb + j * dst->argb_stride + i;
+      for (y = 0; y < block_size; ++y) {
+        for (x = 0; x < block_size; ++x) {
+          const uint32_t src_pixel = psrc[x + y * src->argb_stride];
+          const int alpha = src_pixel >> 24;
+          if (alpha == 0xff &&
+              PixelsAreSimilar(src_pixel, pdst[x + y * dst->argb_stride],
+                               max_allowed_diff_lossy)) {
+            ++cnt;
+            avg_r += (src_pixel >> 16) & 0xff;
+            avg_g += (src_pixel >> 8) & 0xff;
+            avg_b += (src_pixel >> 0) & 0xff;
+          }
+        }
+      }
+      // If we have a fully similar block, we replace it with an
+      // average transparent block. This compresses better in lossy mode.
+      if (cnt == block_size * block_size) {
+        const uint32_t color = (0x00          << 24) |
+                               ((avg_r / cnt) << 16) |
+                               ((avg_g / cnt) <<  8) |
+                               ((avg_b / cnt) <<  0);
+        for (y = 0; y < block_size; ++y) {
+          for (x = 0; x < block_size; ++x) {
+            pdst[x + y * dst->argb_stride] = color;
+          }
+        }
+        modified = 1;
+      }
+    }
+  }
+  return modified;
+}
+
+static int EncodeFrame(const WebPConfig* const config, WebPPicture* const pic,
+                       WebPMemoryWriter* const memory) {
+  pic->use_argb = 1;
+  pic->writer = WebPMemoryWrite;
+  pic->custom_ptr = memory;
+  if (!WebPEncode(config, pic)) {
+    return 0;
+  }
+  return 1;
+}
+
+// Struct representing a candidate encoded frame including its metadata.
+typedef struct {
+  WebPMemoryWriter  mem_;
+  WebPMuxFrameInfo  info_;
+  FrameRect         rect_;
+  int               evaluate_;  // True if this candidate should be evaluated.
+} Candidate;
+
+// Generates a candidate encoded frame given a picture and metadata.
+static WebPEncodingError EncodeCandidate(WebPPicture* const sub_frame,
+                                         const FrameRect* const rect,
+                                         const WebPConfig* const encoder_config,
+                                         int use_blending,
+                                         Candidate* const candidate) {
+  WebPConfig config = *encoder_config;
+  WebPEncodingError error_code = VP8_ENC_OK;
+  assert(candidate != NULL);
+  memset(candidate, 0, sizeof(*candidate));
+
+  // Set frame rect and info.
+  candidate->rect_ = *rect;
+  candidate->info_.id = WEBP_CHUNK_ANMF;
+  candidate->info_.x_offset = rect->x_offset_;
+  candidate->info_.y_offset = rect->y_offset_;
+  candidate->info_.dispose_method = WEBP_MUX_DISPOSE_NONE;  // Set later.
+  candidate->info_.blend_method =
+      use_blending ? WEBP_MUX_BLEND : WEBP_MUX_NO_BLEND;
+  candidate->info_.duration = 0;  // Set in next call to WebPAnimEncoderAdd().
+
+  // Encode picture.
+  WebPMemoryWriterInit(&candidate->mem_);
+
+  if (!config.lossless && use_blending) {
+    // Disable filtering to avoid blockiness in reconstructed frames at the
+    // time of decoding.
+    config.autofilter = 0;
+    config.filter_strength = 0;
+  }
+  if (!EncodeFrame(&config, sub_frame, &candidate->mem_)) {
+    error_code = sub_frame->error_code;
+    goto Err;
+  }
+
+  candidate->evaluate_ = 1;
+  return error_code;
+
+ Err:
+  WebPMemoryWriterClear(&candidate->mem_);
+  return error_code;
+}
+
+static void CopyCurrentCanvas(WebPAnimEncoder* const enc) {
+  if (enc->curr_canvas_copy_modified_) {
+    WebPCopyPixels(enc->curr_canvas_, &enc->curr_canvas_copy_);
+    enc->curr_canvas_copy_.progress_hook = enc->curr_canvas_->progress_hook;
+    enc->curr_canvas_copy_.user_data = enc->curr_canvas_->user_data;
+    enc->curr_canvas_copy_modified_ = 0;
+  }
+}
+
+enum {
+  LL_DISP_NONE = 0,
+  LL_DISP_BG,
+  LOSSY_DISP_NONE,
+  LOSSY_DISP_BG,
+  CANDIDATE_COUNT
+};
+
+#define MIN_COLORS_LOSSY     31  // Don't try lossy below this threshold.
+#define MAX_COLORS_LOSSLESS 194  // Don't try lossless above this threshold.
+
+// Generates candidates for a given dispose method given pre-filled sub-frame
+// 'params'.
+static WebPEncodingError GenerateCandidates(
+    WebPAnimEncoder* const enc, Candidate candidates[CANDIDATE_COUNT],
+    WebPMuxAnimDispose dispose_method, int is_lossless, int is_key_frame,
+    SubFrameParams* const params,
+    const WebPConfig* const config_ll, const WebPConfig* const config_lossy) {
+  WebPEncodingError error_code = VP8_ENC_OK;
+  const int is_dispose_none = (dispose_method == WEBP_MUX_DISPOSE_NONE);
+  Candidate* const candidate_ll =
+      is_dispose_none ? &candidates[LL_DISP_NONE] : &candidates[LL_DISP_BG];
+  Candidate* const candidate_lossy = is_dispose_none
+                                     ? &candidates[LOSSY_DISP_NONE]
+                                     : &candidates[LOSSY_DISP_BG];
+  WebPPicture* const curr_canvas = &enc->curr_canvas_copy_;
+  const WebPPicture* const prev_canvas =
+      is_dispose_none ? &enc->prev_canvas_ : &enc->prev_canvas_disposed_;
+  int use_blending_ll, use_blending_lossy;
+  int evaluate_ll, evaluate_lossy;
+
+  CopyCurrentCanvas(enc);
+  use_blending_ll =
+      !is_key_frame &&
+      IsLosslessBlendingPossible(prev_canvas, curr_canvas, &params->rect_ll_);
+  use_blending_lossy =
+      !is_key_frame &&
+      IsLossyBlendingPossible(prev_canvas, curr_canvas, &params->rect_lossy_,
+                              config_lossy->quality);
+
+  // Pick candidates to be tried.
+  if (!enc->options_.allow_mixed) {
+    evaluate_ll = is_lossless;
+    evaluate_lossy = !is_lossless;
+  } else if (enc->options_.minimize_size) {
+    evaluate_ll = 1;
+    evaluate_lossy = 1;
+  } else {  // Use a heuristic for trying lossless and/or lossy compression.
+    const int num_colors = WebPGetColorPalette(&params->sub_frame_ll_, NULL);
+    evaluate_ll = (num_colors < MAX_COLORS_LOSSLESS);
+    evaluate_lossy = (num_colors >= MIN_COLORS_LOSSY);
+  }
+
+  // Generate candidates.
+  if (evaluate_ll) {
+    CopyCurrentCanvas(enc);
+    if (use_blending_ll) {
+      enc->curr_canvas_copy_modified_ =
+          IncreaseTransparency(prev_canvas, &params->rect_ll_, curr_canvas);
+    }
+    error_code = EncodeCandidate(&params->sub_frame_ll_, &params->rect_ll_,
+                                 config_ll, use_blending_ll, candidate_ll);
+    if (error_code != VP8_ENC_OK) return error_code;
+  }
+  if (evaluate_lossy) {
+    CopyCurrentCanvas(enc);
+    if (use_blending_lossy) {
+      enc->curr_canvas_copy_modified_ =
+          FlattenSimilarBlocks(prev_canvas, &params->rect_lossy_, curr_canvas,
+                               config_lossy->quality);
+    }
+    error_code =
+        EncodeCandidate(&params->sub_frame_lossy_, &params->rect_lossy_,
+                        config_lossy, use_blending_lossy, candidate_lossy);
+    if (error_code != VP8_ENC_OK) return error_code;
+    enc->curr_canvas_copy_modified_ = 1;
+  }
+  return error_code;
+}
+
+#undef MIN_COLORS_LOSSY
+#undef MAX_COLORS_LOSSLESS
+
+static void GetEncodedData(const WebPMemoryWriter* const memory,
+                           WebPData* const encoded_data) {
+  encoded_data->bytes = memory->mem;
+  encoded_data->size  = memory->size;
+}
+
+// Sets dispose method of the previous frame to be 'dispose_method'.
+static void SetPreviousDisposeMethod(WebPAnimEncoder* const enc,
+                                     WebPMuxAnimDispose dispose_method) {
+  const size_t position = enc->count_ - 2;
+  EncodedFrame* const prev_enc_frame = GetFrame(enc, position);
+  assert(enc->count_ >= 2);  // As current and previous frames are in enc.
+
+  if (enc->prev_candidate_undecided_) {
+    assert(dispose_method == WEBP_MUX_DISPOSE_NONE);
+    prev_enc_frame->sub_frame_.dispose_method = dispose_method;
+    prev_enc_frame->key_frame_.dispose_method = dispose_method;
+  } else {
+    WebPMuxFrameInfo* const prev_info = prev_enc_frame->is_key_frame_
+                                        ? &prev_enc_frame->key_frame_
+                                        : &prev_enc_frame->sub_frame_;
+    prev_info->dispose_method = dispose_method;
+  }
+}
+
+static int IncreasePreviousDuration(WebPAnimEncoder* const enc, int duration) {
+  const size_t position = enc->count_ - 1;
+  EncodedFrame* const prev_enc_frame = GetFrame(enc, position);
+  int new_duration;
+
+  assert(enc->count_ >= 1);
+  assert(prev_enc_frame->sub_frame_.duration ==
+         prev_enc_frame->key_frame_.duration);
+  assert(prev_enc_frame->sub_frame_.duration ==
+         (prev_enc_frame->sub_frame_.duration & (MAX_DURATION - 1)));
+  assert(duration == (duration & (MAX_DURATION - 1)));
+
+  new_duration = prev_enc_frame->sub_frame_.duration + duration;
+  if (new_duration >= MAX_DURATION) {  // Special case.
+    // Separate out previous frame from earlier merged frames to avoid overflow.
+    // We add a 1x1 transparent frame for the previous frame, with blending on.
+    const FrameRect rect = { 0, 0, 1, 1 };
+    const uint8_t lossless_1x1_bytes[] = {
+      0x52, 0x49, 0x46, 0x46, 0x14, 0x00, 0x00, 0x00, 0x57, 0x45, 0x42, 0x50,
+      0x56, 0x50, 0x38, 0x4c, 0x08, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00,
+      0x10, 0x88, 0x88, 0x08
+    };
+    const WebPData lossless_1x1 = {
+        lossless_1x1_bytes, sizeof(lossless_1x1_bytes)
+    };
+    const uint8_t lossy_1x1_bytes[] = {
+      0x52, 0x49, 0x46, 0x46, 0x40, 0x00, 0x00, 0x00, 0x57, 0x45, 0x42, 0x50,
+      0x56, 0x50, 0x38, 0x58, 0x0a, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x4c, 0x50, 0x48, 0x02, 0x00,
+      0x00, 0x00, 0x00, 0x00, 0x56, 0x50, 0x38, 0x20, 0x18, 0x00, 0x00, 0x00,
+      0x30, 0x01, 0x00, 0x9d, 0x01, 0x2a, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00,
+      0x34, 0x25, 0xa4, 0x00, 0x03, 0x70, 0x00, 0xfe, 0xfb, 0xfd, 0x50, 0x00
+    };
+    const WebPData lossy_1x1 = { lossy_1x1_bytes, sizeof(lossy_1x1_bytes) };
+    const int can_use_lossless =
+        (enc->last_config_.lossless || enc->options_.allow_mixed);
+    EncodedFrame* const curr_enc_frame = GetFrame(enc, enc->count_);
+    curr_enc_frame->is_key_frame_ = 0;
+    curr_enc_frame->sub_frame_.id = WEBP_CHUNK_ANMF;
+    curr_enc_frame->sub_frame_.x_offset = 0;
+    curr_enc_frame->sub_frame_.y_offset = 0;
+    curr_enc_frame->sub_frame_.dispose_method = WEBP_MUX_DISPOSE_NONE;
+    curr_enc_frame->sub_frame_.blend_method = WEBP_MUX_BLEND;
+    curr_enc_frame->sub_frame_.duration = duration;
+    if (!WebPDataCopy(can_use_lossless ? &lossless_1x1 : &lossy_1x1,
+                      &curr_enc_frame->sub_frame_.bitstream)) {
+      return 0;
+    }
+    ++enc->count_;
+    ++enc->count_since_key_frame_;
+    enc->flush_count_ = enc->count_ - 1;
+    enc->prev_candidate_undecided_ = 0;
+    enc->prev_rect_ = rect;
+  } else {                           // Regular case.
+    // Increase duration of the previous frame by 'duration'.
+    prev_enc_frame->sub_frame_.duration = new_duration;
+    prev_enc_frame->key_frame_.duration = new_duration;
+  }
+  return 1;
+}
+
+// Pick the candidate encoded frame with smallest size and release other
+// candidates.
+// TODO(later): Perhaps a rough SSIM/PSNR produced by the encoder should
+// also be a criteria, in addition to sizes.
+static void PickBestCandidate(WebPAnimEncoder* const enc,
+                              Candidate* const candidates, int is_key_frame,
+                              EncodedFrame* const encoded_frame) {
+  int i;
+  int best_idx = -1;
+  size_t best_size = ~0;
+  for (i = 0; i < CANDIDATE_COUNT; ++i) {
+    if (candidates[i].evaluate_) {
+      const size_t candidate_size = candidates[i].mem_.size;
+      if (candidate_size < best_size) {
+        best_idx = i;
+        best_size = candidate_size;
+      }
+    }
+  }
+  assert(best_idx != -1);
+  for (i = 0; i < CANDIDATE_COUNT; ++i) {
+    if (candidates[i].evaluate_) {
+      if (i == best_idx) {
+        WebPMuxFrameInfo* const dst = is_key_frame
+                                      ? &encoded_frame->key_frame_
+                                      : &encoded_frame->sub_frame_;
+        *dst = candidates[i].info_;
+        GetEncodedData(&candidates[i].mem_, &dst->bitstream);
+        if (!is_key_frame) {
+          // Note: Previous dispose method only matters for non-keyframes.
+          // Also, we don't want to modify previous dispose method that was
+          // selected when a non key-frame was assumed.
+          const WebPMuxAnimDispose prev_dispose_method =
+              (best_idx == LL_DISP_NONE || best_idx == LOSSY_DISP_NONE)
+                  ? WEBP_MUX_DISPOSE_NONE
+                  : WEBP_MUX_DISPOSE_BACKGROUND;
+          SetPreviousDisposeMethod(enc, prev_dispose_method);
+        }
+        enc->prev_rect_ = candidates[i].rect_;  // save for next frame.
+      } else {
+        WebPMemoryWriterClear(&candidates[i].mem_);
+        candidates[i].evaluate_ = 0;
+      }
+    }
+  }
+}
+
+// Depending on the configuration, tries different compressions
+// (lossy/lossless), dispose methods, blending methods etc to encode the current
+// frame and outputs the best one in 'encoded_frame'.
+// 'frame_skipped' will be set to true if this frame should actually be skipped.
+static WebPEncodingError SetFrame(WebPAnimEncoder* const enc,
+                                  const WebPConfig* const config,
+                                  int is_key_frame,
+                                  EncodedFrame* const encoded_frame,
+                                  int* const frame_skipped) {
+  int i;
+  WebPEncodingError error_code = VP8_ENC_OK;
+  const WebPPicture* const curr_canvas = &enc->curr_canvas_copy_;
+  const WebPPicture* const prev_canvas = &enc->prev_canvas_;
+  Candidate candidates[CANDIDATE_COUNT];
+  const int is_lossless = config->lossless;
+  const int consider_lossless = is_lossless || enc->options_.allow_mixed;
+  const int consider_lossy = !is_lossless || enc->options_.allow_mixed;
+  const int is_first_frame = enc->is_first_frame_;
+
+  // First frame cannot be skipped as there is no 'previous frame' to merge it
+  // to. So, empty rectangle is not allowed for the first frame.
+  const int empty_rect_allowed_none = !is_first_frame;
+
+  // Even if there is exact pixel match between 'disposed previous canvas' and
+  // 'current canvas', we can't skip current frame, as there may not be exact
+  // pixel match between 'previous canvas' and 'current canvas'. So, we don't
+  // allow empty rectangle in this case.
+  const int empty_rect_allowed_bg = 0;
+
+  // If current frame is a key-frame, dispose method of previous frame doesn't
+  // matter, so we don't try dispose to background.
+  // Also, if key-frame insertion is on, and previous frame could be picked as
+  // either a sub-frame or a key-frame, then we can't be sure about what frame
+  // rectangle would be disposed. In that case too, we don't try dispose to
+  // background.
+  const int dispose_bg_possible =
+      !is_key_frame && !enc->prev_candidate_undecided_;
+
+  SubFrameParams dispose_none_params;
+  SubFrameParams dispose_bg_params;
+
+  WebPConfig config_ll = *config;
+  WebPConfig config_lossy = *config;
+  config_ll.lossless = 1;
+  config_lossy.lossless = 0;
+  enc->last_config_ = *config;
+  enc->last_config_reversed_ = config->lossless ? config_lossy : config_ll;
+  *frame_skipped = 0;
+
+  if (!SubFrameParamsInit(&dispose_none_params, 1, empty_rect_allowed_none) ||
+      !SubFrameParamsInit(&dispose_bg_params, 0, empty_rect_allowed_bg)) {
+    return VP8_ENC_ERROR_INVALID_CONFIGURATION;
+  }
+
+  memset(candidates, 0, sizeof(candidates));
+
+  // Change-rectangle assuming previous frame was DISPOSE_NONE.
+  if (!GetSubRects(prev_canvas, curr_canvas, is_key_frame, is_first_frame,
+                   config_lossy.quality, &dispose_none_params)) {
+    error_code = VP8_ENC_ERROR_INVALID_CONFIGURATION;
+    goto Err;
+  }
+
+  if ((consider_lossless && IsEmptyRect(&dispose_none_params.rect_ll_)) ||
+      (consider_lossy && IsEmptyRect(&dispose_none_params.rect_lossy_))) {
+    // Don't encode the frame at all. Instead, the duration of the previous
+    // frame will be increased later.
+    assert(empty_rect_allowed_none);
+    *frame_skipped = 1;
+    goto End;
+  }
+
+  if (dispose_bg_possible) {
+    // Change-rectangle assuming previous frame was DISPOSE_BACKGROUND.
+    WebPPicture* const prev_canvas_disposed = &enc->prev_canvas_disposed_;
+    WebPCopyPixels(prev_canvas, prev_canvas_disposed);
+    DisposeFrameRectangle(WEBP_MUX_DISPOSE_BACKGROUND, &enc->prev_rect_,
+                          prev_canvas_disposed);
+
+    if (!GetSubRects(prev_canvas_disposed, curr_canvas, is_key_frame,
+                     is_first_frame, config_lossy.quality,
+                     &dispose_bg_params)) {
+      error_code = VP8_ENC_ERROR_INVALID_CONFIGURATION;
+      goto Err;
+    }
+    assert(!IsEmptyRect(&dispose_bg_params.rect_ll_));
+    assert(!IsEmptyRect(&dispose_bg_params.rect_lossy_));
+
+    if (enc->options_.minimize_size) {  // Try both dispose methods.
+      dispose_bg_params.should_try_ = 1;
+      dispose_none_params.should_try_ = 1;
+    } else if ((is_lossless &&
+                RectArea(&dispose_bg_params.rect_ll_) <
+                    RectArea(&dispose_none_params.rect_ll_)) ||
+               (!is_lossless &&
+                RectArea(&dispose_bg_params.rect_lossy_) <
+                    RectArea(&dispose_none_params.rect_lossy_))) {
+      dispose_bg_params.should_try_ = 1;  // Pick DISPOSE_BACKGROUND.
+      dispose_none_params.should_try_ = 0;
+    }
+  }
+
+  if (dispose_none_params.should_try_) {
+    error_code = GenerateCandidates(
+        enc, candidates, WEBP_MUX_DISPOSE_NONE, is_lossless, is_key_frame,
+        &dispose_none_params, &config_ll, &config_lossy);
+    if (error_code != VP8_ENC_OK) goto Err;
+  }
+
+  if (dispose_bg_params.should_try_) {
+    assert(!enc->is_first_frame_);
+    assert(dispose_bg_possible);
+    error_code = GenerateCandidates(
+        enc, candidates, WEBP_MUX_DISPOSE_BACKGROUND, is_lossless, is_key_frame,
+        &dispose_bg_params, &config_ll, &config_lossy);
+    if (error_code != VP8_ENC_OK) goto Err;
+  }
+
+  PickBestCandidate(enc, candidates, is_key_frame, encoded_frame);
+
+  goto End;
+
+ Err:
+  for (i = 0; i < CANDIDATE_COUNT; ++i) {
+    if (candidates[i].evaluate_) {
+      WebPMemoryWriterClear(&candidates[i].mem_);
+    }
+  }
+
+ End:
+  SubFrameParamsFree(&dispose_none_params);
+  SubFrameParamsFree(&dispose_bg_params);
+  return error_code;
+}
+
+// Calculate the penalty incurred if we encode given frame as a key frame
+// instead of a sub-frame.
+static int64_t KeyFramePenalty(const EncodedFrame* const encoded_frame) {
+  return ((int64_t)encoded_frame->key_frame_.bitstream.size -
+          encoded_frame->sub_frame_.bitstream.size);
+}
+
+static int CacheFrame(WebPAnimEncoder* const enc,
+                      const WebPConfig* const config) {
+  int ok = 0;
+  int frame_skipped = 0;
+  WebPEncodingError error_code = VP8_ENC_OK;
+  const size_t position = enc->count_;
+  EncodedFrame* const encoded_frame = GetFrame(enc, position);
+
+  ++enc->count_;
+
+  if (enc->is_first_frame_) {  // Add this as a key-frame.
+    error_code = SetFrame(enc, config, 1, encoded_frame, &frame_skipped);
+    if (error_code != VP8_ENC_OK) goto End;
+    assert(frame_skipped == 0);  // First frame can't be skipped, even if empty.
+    assert(position == 0 && enc->count_ == 1);
+    encoded_frame->is_key_frame_ = 1;
+    enc->flush_count_ = 0;
+    enc->count_since_key_frame_ = 0;
+    enc->prev_candidate_undecided_ = 0;
+  } else {
+    ++enc->count_since_key_frame_;
+    if (enc->count_since_key_frame_ <= enc->options_.kmin) {
+      // Add this as a frame rectangle.
+      error_code = SetFrame(enc, config, 0, encoded_frame, &frame_skipped);
+      if (error_code != VP8_ENC_OK) goto End;
+      if (frame_skipped) goto Skip;
+      encoded_frame->is_key_frame_ = 0;
+      enc->flush_count_ = enc->count_ - 1;
+      enc->prev_candidate_undecided_ = 0;
+    } else {
+      int64_t curr_delta;
+      FrameRect prev_rect_key, prev_rect_sub;
+
+      // Add this as a frame rectangle to enc.
+      error_code = SetFrame(enc, config, 0, encoded_frame, &frame_skipped);
+      if (error_code != VP8_ENC_OK) goto End;
+      if (frame_skipped) goto Skip;
+      prev_rect_sub = enc->prev_rect_;
+
+
+      // Add this as a key-frame to enc, too.
+      error_code = SetFrame(enc, config, 1, encoded_frame, &frame_skipped);
+      if (error_code != VP8_ENC_OK) goto End;
+      assert(frame_skipped == 0);  // Key-frame cannot be an empty rectangle.
+      prev_rect_key = enc->prev_rect_;
+
+      // Analyze size difference of the two variants.
+      curr_delta = KeyFramePenalty(encoded_frame);
+      if (curr_delta <= enc->best_delta_) {  // Pick this as the key-frame.
+        if (enc->keyframe_ != KEYFRAME_NONE) {
+          EncodedFrame* const old_keyframe = GetFrame(enc, enc->keyframe_);
+          assert(old_keyframe->is_key_frame_);
+          old_keyframe->is_key_frame_ = 0;
+        }
+        encoded_frame->is_key_frame_ = 1;
+        enc->prev_candidate_undecided_ = 1;
+        enc->keyframe_ = (int)position;
+        enc->best_delta_ = curr_delta;
+        enc->flush_count_ = enc->count_ - 1;  // We can flush previous frames.
+      } else {
+        encoded_frame->is_key_frame_ = 0;
+        enc->prev_candidate_undecided_ = 0;
+      }
+      // Note: We need '>=' below because when kmin and kmax are both zero,
+      // count_since_key_frame will always be > kmax.
+      if (enc->count_since_key_frame_ >= enc->options_.kmax) {
+        enc->flush_count_ = enc->count_ - 1;
+        enc->count_since_key_frame_ = 0;
+        enc->keyframe_ = KEYFRAME_NONE;
+        enc->best_delta_ = DELTA_INFINITY;
+      }
+      if (!enc->prev_candidate_undecided_) {
+        enc->prev_rect_ =
+            encoded_frame->is_key_frame_ ? prev_rect_key : prev_rect_sub;
+      }
+    }
+  }
+
+  // Update previous to previous and previous canvases for next call.
+  WebPCopyPixels(enc->curr_canvas_, &enc->prev_canvas_);
+  enc->is_first_frame_ = 0;
+
+ Skip:
+  ok = 1;
+  ++enc->in_frame_count_;
+
+ End:
+  if (!ok || frame_skipped) {
+    FrameRelease(encoded_frame);
+    // We reset some counters, as the frame addition failed/was skipped.
+    --enc->count_;
+    if (!enc->is_first_frame_) --enc->count_since_key_frame_;
+    if (!ok) {
+      MarkError2(enc, "ERROR adding frame. WebPEncodingError", error_code);
+    }
+  }
+  enc->curr_canvas_->error_code = error_code;   // report error_code
+  assert(ok || error_code != VP8_ENC_OK);
+  return ok;
+}
+
+static int FlushFrames(WebPAnimEncoder* const enc) {
+  while (enc->flush_count_ > 0) {
+    WebPMuxError err;
+    EncodedFrame* const curr = GetFrame(enc, 0);
+    const WebPMuxFrameInfo* const info =
+        curr->is_key_frame_ ? &curr->key_frame_ : &curr->sub_frame_;
+    assert(enc->mux_ != NULL);
+    err = WebPMuxPushFrame(enc->mux_, info, 1);
+    if (err != WEBP_MUX_OK) {
+      MarkError2(enc, "ERROR adding frame. WebPMuxError", err);
+      return 0;
+    }
+    if (enc->options_.verbose) {
+      fprintf(stderr, "INFO: Added frame. offset:%d,%d dispose:%d blend:%d\n",
+              info->x_offset, info->y_offset, info->dispose_method,
+              info->blend_method);
+    }
+    ++enc->out_frame_count_;
+    FrameRelease(curr);
+    ++enc->start_;
+    --enc->flush_count_;
+    --enc->count_;
+    if (enc->keyframe_ != KEYFRAME_NONE) --enc->keyframe_;
+  }
+
+  if (enc->count_ == 1 && enc->start_ != 0) {
+    // Move enc->start to index 0.
+    const int enc_start_tmp = (int)enc->start_;
+    EncodedFrame temp = enc->encoded_frames_[0];
+    enc->encoded_frames_[0] = enc->encoded_frames_[enc_start_tmp];
+    enc->encoded_frames_[enc_start_tmp] = temp;
+    FrameRelease(&enc->encoded_frames_[enc_start_tmp]);
+    enc->start_ = 0;
+  }
+  return 1;
+}
+
+#undef DELTA_INFINITY
+#undef KEYFRAME_NONE
+
+int WebPAnimEncoderAdd(WebPAnimEncoder* enc, WebPPicture* frame, int timestamp,
+                       const WebPConfig* encoder_config) {
+  WebPConfig config;
+  int ok;
+
+  if (enc == NULL) {
+    return 0;
+  }
+  MarkNoError(enc);
+
+  if (!enc->is_first_frame_) {
+    // Make sure timestamps are non-decreasing (integer wrap-around is OK).
+    const uint32_t prev_frame_duration =
+        (uint32_t)timestamp - enc->prev_timestamp_;
+    if (prev_frame_duration >= MAX_DURATION) {
+      if (frame != NULL) {
+        frame->error_code = VP8_ENC_ERROR_INVALID_CONFIGURATION;
+      }
+      MarkError(enc, "ERROR adding frame: timestamps must be non-decreasing");
+      return 0;
+    }
+    if (!IncreasePreviousDuration(enc, (int)prev_frame_duration)) {
+      return 0;
+    }
+  } else {
+    enc->first_timestamp_ = timestamp;
+  }
+
+  if (frame == NULL) {  // Special: last call.
+    enc->got_null_frame_ = 1;
+    enc->prev_timestamp_ = timestamp;
+    return 1;
+  }
+
+  if (frame->width != enc->canvas_width_ ||
+      frame->height != enc->canvas_height_) {
+    frame->error_code = VP8_ENC_ERROR_INVALID_CONFIGURATION;
+    MarkError(enc, "ERROR adding frame: Invalid frame dimensions");
+    return 0;
+  }
+
+  if (!frame->use_argb) {  // Convert frame from YUV(A) to ARGB.
+    if (enc->options_.verbose) {
+      fprintf(stderr, "WARNING: Converting frame from YUV(A) to ARGB format; "
+              "this incurs a small loss.\n");
+    }
+    if (!WebPPictureYUVAToARGB(frame)) {
+      MarkError(enc, "ERROR converting frame from YUV(A) to ARGB");
+      return 0;
+    }
+  }
+
+  if (encoder_config != NULL) {
+    if (!WebPValidateConfig(encoder_config)) {
+      MarkError(enc, "ERROR adding frame: Invalid WebPConfig");
+      return 0;
+    }
+    config = *encoder_config;
+  } else {
+    WebPConfigInit(&config);
+    config.lossless = 1;
+  }
+  assert(enc->curr_canvas_ == NULL);
+  enc->curr_canvas_ = frame;  // Store reference.
+  assert(enc->curr_canvas_copy_modified_ == 1);
+  CopyCurrentCanvas(enc);
+
+  ok = CacheFrame(enc, &config) && FlushFrames(enc);
+
+  enc->curr_canvas_ = NULL;
+  enc->curr_canvas_copy_modified_ = 1;
+  if (ok) {
+    enc->prev_timestamp_ = timestamp;
+  }
+  return ok;
+}
+
+// -----------------------------------------------------------------------------
+// Bitstream assembly.
+
+static int DecodeFrameOntoCanvas(const WebPMuxFrameInfo* const frame,
+                                 WebPPicture* const canvas) {
+  const WebPData* const image = &frame->bitstream;
+  WebPPicture sub_image;
+  WebPDecoderConfig config;
+  WebPInitDecoderConfig(&config);
+  WebPUtilClearPic(canvas, NULL);
+  if (WebPGetFeatures(image->bytes, image->size, &config.input) !=
+      VP8_STATUS_OK) {
+    return 0;
+  }
+  if (!WebPPictureView(canvas, frame->x_offset, frame->y_offset,
+                       config.input.width, config.input.height, &sub_image)) {
+    return 0;
+  }
+  config.output.is_external_memory = 1;
+  config.output.colorspace = MODE_BGRA;
+  config.output.u.RGBA.rgba = (uint8_t*)sub_image.argb;
+  config.output.u.RGBA.stride = sub_image.argb_stride * 4;
+  config.output.u.RGBA.size = config.output.u.RGBA.stride * sub_image.height;
+
+  if (WebPDecode(image->bytes, image->size, &config) != VP8_STATUS_OK) {
+    return 0;
+  }
+  return 1;
+}
+
+static int FrameToFullCanvas(WebPAnimEncoder* const enc,
+                             const WebPMuxFrameInfo* const frame,
+                             WebPData* const full_image) {
+  WebPPicture* const canvas_buf = &enc->curr_canvas_copy_;
+  WebPMemoryWriter mem1, mem2;
+  WebPMemoryWriterInit(&mem1);
+  WebPMemoryWriterInit(&mem2);
+
+  if (!DecodeFrameOntoCanvas(frame, canvas_buf)) goto Err;
+  if (!EncodeFrame(&enc->last_config_, canvas_buf, &mem1)) goto Err;
+  GetEncodedData(&mem1, full_image);
+
+  if (enc->options_.allow_mixed) {
+    if (!EncodeFrame(&enc->last_config_reversed_, canvas_buf, &mem2)) goto Err;
+    if (mem2.size < mem1.size) {
+      GetEncodedData(&mem2, full_image);
+      WebPMemoryWriterClear(&mem1);
+    } else {
+      WebPMemoryWriterClear(&mem2);
+    }
+  }
+  return 1;
+
+ Err:
+  WebPMemoryWriterClear(&mem1);
+  WebPMemoryWriterClear(&mem2);
+  return 0;
+}
+
+// Convert a single-frame animation to a non-animated image if appropriate.
+// TODO(urvang): Can we pick one of the two heuristically (based on frame
+// rectangle and/or presence of alpha)?
+static WebPMuxError OptimizeSingleFrame(WebPAnimEncoder* const enc,
+                                        WebPData* const webp_data) {
+  WebPMuxError err = WEBP_MUX_OK;
+  int canvas_width, canvas_height;
+  WebPMuxFrameInfo frame;
+  WebPData full_image;
+  WebPData webp_data2;
+  WebPMux* const mux = WebPMuxCreate(webp_data, 0);
+  if (mux == NULL) return WEBP_MUX_BAD_DATA;
+  assert(enc->out_frame_count_ == 1);
+  WebPDataInit(&frame.bitstream);
+  WebPDataInit(&full_image);
+  WebPDataInit(&webp_data2);
+
+  err = WebPMuxGetFrame(mux, 1, &frame);
+  if (err != WEBP_MUX_OK) goto End;
+  if (frame.id != WEBP_CHUNK_ANMF) goto End;  // Non-animation: nothing to do.
+  err = WebPMuxGetCanvasSize(mux, &canvas_width, &canvas_height);
+  if (err != WEBP_MUX_OK) goto End;
+  if (!FrameToFullCanvas(enc, &frame, &full_image)) {
+    err = WEBP_MUX_BAD_DATA;
+    goto End;
+  }
+  err = WebPMuxSetImage(mux, &full_image, 1);
+  if (err != WEBP_MUX_OK) goto End;
+  err = WebPMuxAssemble(mux, &webp_data2);
+  if (err != WEBP_MUX_OK) goto End;
+
+  if (webp_data2.size < webp_data->size) {  // Pick 'webp_data2' if smaller.
+    WebPDataClear(webp_data);
+    *webp_data = webp_data2;
+    WebPDataInit(&webp_data2);
+  }
+
+ End:
+  WebPDataClear(&frame.bitstream);
+  WebPDataClear(&full_image);
+  WebPMuxDelete(mux);
+  WebPDataClear(&webp_data2);
+  return err;
+}
+
+int WebPAnimEncoderAssemble(WebPAnimEncoder* enc, WebPData* webp_data) {
+  WebPMux* mux;
+  WebPMuxError err;
+
+  if (enc == NULL) {
+    return 0;
+  }
+  MarkNoError(enc);
+
+  if (webp_data == NULL) {
+    MarkError(enc, "ERROR assembling: NULL input");
+    return 0;
+  }
+
+  if (enc->in_frame_count_ == 0) {
+    MarkError(enc, "ERROR: No frames to assemble");
+    return 0;
+  }
+
+  if (!enc->got_null_frame_ && enc->in_frame_count_ > 1 && enc->count_ > 0) {
+    // set duration of the last frame to be avg of durations of previous frames.
+    const double delta_time = enc->prev_timestamp_ - enc->first_timestamp_;
+    const int average_duration = (int)(delta_time / (enc->in_frame_count_ - 1));
+    if (!IncreasePreviousDuration(enc, average_duration)) {
+      return 0;
+    }
+  }
+
+  // Flush any remaining frames.
+  enc->flush_count_ = enc->count_;
+  if (!FlushFrames(enc)) {
+    return 0;
+  }
+
+  // Set definitive canvas size.
+  mux = enc->mux_;
+  err = WebPMuxSetCanvasSize(mux, enc->canvas_width_, enc->canvas_height_);
+  if (err != WEBP_MUX_OK) goto Err;
+
+  err = WebPMuxSetAnimationParams(mux, &enc->options_.anim_params);
+  if (err != WEBP_MUX_OK) goto Err;
+
+  // Assemble into a WebP bitstream.
+  err = WebPMuxAssemble(mux, webp_data);
+  if (err != WEBP_MUX_OK) goto Err;
+
+  if (enc->out_frame_count_ == 1) {
+    err = OptimizeSingleFrame(enc, webp_data);
+    if (err != WEBP_MUX_OK) goto Err;
+  }
+  return 1;
+
+ Err:
+  MarkError2(enc, "ERROR assembling WebP", err);
+  return 0;
+}
+
+const char* WebPAnimEncoderGetError(WebPAnimEncoder* enc) {
+  if (enc == NULL) return NULL;
+  return enc->error_str_;
+}
+
+// -----------------------------------------------------------------------------
diff --git a/src/mux/animi.h b/src/mux/animi.h
new file mode 100644
index 0000000..cecaf1f
--- /dev/null
+++ b/src/mux/animi.h
@@ -0,0 +1,43 @@
+// Copyright 2016 Google Inc. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the COPYING file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+// -----------------------------------------------------------------------------
+//
+// Internal header for animation related functions.
+//
+// Author: Hui Su (huisu@google.com)
+
+#ifndef WEBP_MUX_ANIMI_H_
+#define WEBP_MUX_ANIMI_H_
+
+#include "../webp/mux.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Picks the optimal rectangle between two pictures, starting with initial
+// values of offsets and dimensions that are passed in. The initial
+// values will be clipped, if necessary, to make sure the rectangle is
+// within the canvas. "use_argb" must be true for both pictures.
+// Parameters:
+//   prev_canvas, curr_canvas - (in) two input pictures to compare.
+//   is_lossless, quality - (in) encoding settings.
+//   x_offset, y_offset, width, height - (in/out) rectangle between the two
+//                                                input pictures.
+// Returns true on success.
+int WebPAnimEncoderRefineRect(
+    const struct WebPPicture* const prev_canvas,
+    const struct WebPPicture* const curr_canvas,
+    int is_lossless, float quality, int* const x_offset, int* const y_offset,
+    int* const width, int* const height);
+
+#ifdef __cplusplus
+}    // extern "C"
+#endif
+
+#endif  /* WEBP_MUX_ANIMI_H_ */
diff --git a/src/mux/muxedit.c b/src/mux/muxedit.c
new file mode 100644
index 0000000..d2c5305
--- /dev/null
+++ b/src/mux/muxedit.c
@@ -0,0 +1,657 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the COPYING file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+// -----------------------------------------------------------------------------
+//
+// Set and delete APIs for mux.
+//
+// Authors: Urvang (urvang@google.com)
+//          Vikas (vikasa@google.com)
+
+#include <assert.h>
+#include "./muxi.h"
+#include "../utils/utils.h"
+
+//------------------------------------------------------------------------------
+// Life of a mux object.
+
+static void MuxInit(WebPMux* const mux) {
+  assert(mux != NULL);
+  memset(mux, 0, sizeof(*mux));
+  mux->canvas_width_ = 0;     // just to be explicit
+  mux->canvas_height_ = 0;
+}
+
+WebPMux* WebPNewInternal(int version) {
+  if (WEBP_ABI_IS_INCOMPATIBLE(version, WEBP_MUX_ABI_VERSION)) {
+    return NULL;
+  } else {
+    WebPMux* const mux = (WebPMux*)WebPSafeMalloc(1ULL, sizeof(WebPMux));
+    if (mux != NULL) MuxInit(mux);
+    return mux;
+  }
+}
+
+// Delete all images in 'wpi_list'.
+static void DeleteAllImages(WebPMuxImage** const wpi_list) {
+  while (*wpi_list != NULL) {
+    *wpi_list = MuxImageDelete(*wpi_list);
+  }
+}
+
+static void MuxRelease(WebPMux* const mux) {
+  assert(mux != NULL);
+  DeleteAllImages(&mux->images_);
+  ChunkListDelete(&mux->vp8x_);
+  ChunkListDelete(&mux->iccp_);
+  ChunkListDelete(&mux->anim_);
+  ChunkListDelete(&mux->exif_);
+  ChunkListDelete(&mux->xmp_);
+  ChunkListDelete(&mux->unknown_);
+}
+
+void WebPMuxDelete(WebPMux* mux) {
+  if (mux != NULL) {
+    MuxRelease(mux);
+    WebPSafeFree(mux);
+  }
+}
+
+//------------------------------------------------------------------------------
+// Helper method(s).
+
+// Handy MACRO, makes MuxSet() very symmetric to MuxGet().
+#define SWITCH_ID_LIST(INDEX, LIST)                                            \
+  if (idx == (INDEX)) {                                                        \
+    err = ChunkAssignData(&chunk, data, copy_data, tag);                       \
+    if (err == WEBP_MUX_OK) {                                                  \
+      err = ChunkSetNth(&chunk, (LIST), nth);                                  \
+    }                                                                          \
+    return err;                                                                \
+  }
+
+static WebPMuxError MuxSet(WebPMux* const mux, uint32_t tag, uint32_t nth,
+                           const WebPData* const data, int copy_data) {
+  WebPChunk chunk;
+  WebPMuxError err = WEBP_MUX_NOT_FOUND;
+  const CHUNK_INDEX idx = ChunkGetIndexFromTag(tag);
+  assert(mux != NULL);
+  assert(!IsWPI(kChunks[idx].id));
+
+  ChunkInit(&chunk);
+  SWITCH_ID_LIST(IDX_VP8X,    &mux->vp8x_);
+  SWITCH_ID_LIST(IDX_ICCP,    &mux->iccp_);
+  SWITCH_ID_LIST(IDX_ANIM,    &mux->anim_);
+  SWITCH_ID_LIST(IDX_EXIF,    &mux->exif_);
+  SWITCH_ID_LIST(IDX_XMP,     &mux->xmp_);
+  SWITCH_ID_LIST(IDX_UNKNOWN, &mux->unknown_);
+  return err;
+}
+#undef SWITCH_ID_LIST
+
+// Create data for frame given image data, offsets and duration.
+static WebPMuxError CreateFrameData(
+    int width, int height, const WebPMuxFrameInfo* const info,
+    WebPData* const frame) {
+  uint8_t* frame_bytes;
+  const size_t frame_size = kChunks[IDX_ANMF].size;
+
+  assert(width > 0 && height > 0 && info->duration >= 0);
+  assert(info->dispose_method == (info->dispose_method & 1));
+  // Note: assertion on upper bounds is done in PutLE24().
+
+  frame_bytes = (uint8_t*)WebPSafeMalloc(1ULL, frame_size);
+  if (frame_bytes == NULL) return WEBP_MUX_MEMORY_ERROR;
+
+  PutLE24(frame_bytes + 0, info->x_offset / 2);
+  PutLE24(frame_bytes + 3, info->y_offset / 2);
+
+  PutLE24(frame_bytes + 6, width - 1);
+  PutLE24(frame_bytes + 9, height - 1);
+  PutLE24(frame_bytes + 12, info->duration);
+  frame_bytes[15] =
+      (info->blend_method == WEBP_MUX_NO_BLEND ? 2 : 0) |
+      (info->dispose_method == WEBP_MUX_DISPOSE_BACKGROUND ? 1 : 0);
+
+  frame->bytes = frame_bytes;
+  frame->size = frame_size;
+  return WEBP_MUX_OK;
+}
+
+// Outputs image data given a bitstream. The bitstream can either be a
+// single-image WebP file or raw VP8/VP8L data.
+// Also outputs 'is_lossless' to be true if the given bitstream is lossless.
+static WebPMuxError GetImageData(const WebPData* const bitstream,
+                                 WebPData* const image, WebPData* const alpha,
+                                 int* const is_lossless) {
+  WebPDataInit(alpha);  // Default: no alpha.
+  if (bitstream->size < TAG_SIZE ||
+      memcmp(bitstream->bytes, "RIFF", TAG_SIZE)) {
+    // It is NOT webp file data. Return input data as is.
+    *image = *bitstream;
+  } else {
+    // It is webp file data. Extract image data from it.
+    const WebPMuxImage* wpi;
+    WebPMux* const mux = WebPMuxCreate(bitstream, 0);
+    if (mux == NULL) return WEBP_MUX_BAD_DATA;
+    wpi = mux->images_;
+    assert(wpi != NULL && wpi->img_ != NULL);
+    *image = wpi->img_->data_;
+    if (wpi->alpha_ != NULL) {
+      *alpha = wpi->alpha_->data_;
+    }
+    WebPMuxDelete(mux);
+  }
+  *is_lossless = VP8LCheckSignature(image->bytes, image->size);
+  return WEBP_MUX_OK;
+}
+
+static WebPMuxError DeleteChunks(WebPChunk** chunk_list, uint32_t tag) {
+  WebPMuxError err = WEBP_MUX_NOT_FOUND;
+  assert(chunk_list);
+  while (*chunk_list) {
+    WebPChunk* const chunk = *chunk_list;
+    if (chunk->tag_ == tag) {
+      *chunk_list = ChunkDelete(chunk);
+      err = WEBP_MUX_OK;
+    } else {
+      chunk_list = &chunk->next_;
+    }
+  }
+  return err;
+}
+
+static WebPMuxError MuxDeleteAllNamedData(WebPMux* const mux, uint32_t tag) {
+  const WebPChunkId id = ChunkGetIdFromTag(tag);
+  assert(mux != NULL);
+  if (IsWPI(id)) return WEBP_MUX_INVALID_ARGUMENT;
+  return DeleteChunks(MuxGetChunkListFromId(mux, id), tag);
+}
+
+//------------------------------------------------------------------------------
+// Set API(s).
+
+WebPMuxError WebPMuxSetChunk(WebPMux* mux, const char fourcc[4],
+                             const WebPData* chunk_data, int copy_data) {
+  uint32_t tag;
+  WebPMuxError err;
+  if (mux == NULL || fourcc == NULL || chunk_data == NULL ||
+      chunk_data->bytes == NULL || chunk_data->size > MAX_CHUNK_PAYLOAD) {
+    return WEBP_MUX_INVALID_ARGUMENT;
+  }
+  tag = ChunkGetTagFromFourCC(fourcc);
+
+  // Delete existing chunk(s) with the same 'fourcc'.
+  err = MuxDeleteAllNamedData(mux, tag);
+  if (err != WEBP_MUX_OK && err != WEBP_MUX_NOT_FOUND) return err;
+
+  // Add the given chunk.
+  return MuxSet(mux, tag, 1, chunk_data, copy_data);
+}
+
+// Creates a chunk from given 'data' and sets it as 1st chunk in 'chunk_list'.
+static WebPMuxError AddDataToChunkList(
+    const WebPData* const data, int copy_data, uint32_t tag,
+    WebPChunk** chunk_list) {
+  WebPChunk chunk;
+  WebPMuxError err;
+  ChunkInit(&chunk);
+  err = ChunkAssignData(&chunk, data, copy_data, tag);
+  if (err != WEBP_MUX_OK) goto Err;
+  err = ChunkSetNth(&chunk, chunk_list, 1);
+  if (err != WEBP_MUX_OK) goto Err;
+  return WEBP_MUX_OK;
+ Err:
+  ChunkRelease(&chunk);
+  return err;
+}
+
+// Extracts image & alpha data from the given bitstream and then sets wpi.alpha_
+// and wpi.img_ appropriately.
+static WebPMuxError SetAlphaAndImageChunks(
+    const WebPData* const bitstream, int copy_data, WebPMuxImage* const wpi) {
+  int is_lossless = 0;
+  WebPData image, alpha;
+  WebPMuxError err = GetImageData(bitstream, &image, &alpha, &is_lossless);
+  const int image_tag =
+      is_lossless ? kChunks[IDX_VP8L].tag : kChunks[IDX_VP8].tag;
+  if (err != WEBP_MUX_OK) return err;
+  if (alpha.bytes != NULL) {
+    err = AddDataToChunkList(&alpha, copy_data, kChunks[IDX_ALPHA].tag,
+                             &wpi->alpha_);
+    if (err != WEBP_MUX_OK) return err;
+  }
+  err = AddDataToChunkList(&image, copy_data, image_tag, &wpi->img_);
+  if (err != WEBP_MUX_OK) return err;
+  return MuxImageFinalize(wpi) ? WEBP_MUX_OK : WEBP_MUX_INVALID_ARGUMENT;
+}
+
+WebPMuxError WebPMuxSetImage(WebPMux* mux, const WebPData* bitstream,
+                             int copy_data) {
+  WebPMuxImage wpi;
+  WebPMuxError err;
+
+  // Sanity checks.
+  if (mux == NULL || bitstream == NULL || bitstream->bytes == NULL ||
+      bitstream->size > MAX_CHUNK_PAYLOAD) {
+    return WEBP_MUX_INVALID_ARGUMENT;
+  }
+
+  if (mux->images_ != NULL) {
+    // Only one 'simple image' can be added in mux. So, remove present images.
+    DeleteAllImages(&mux->images_);
+  }
+
+  MuxImageInit(&wpi);
+  err = SetAlphaAndImageChunks(bitstream, copy_data, &wpi);
+  if (err != WEBP_MUX_OK) goto Err;
+
+  // Add this WebPMuxImage to mux.
+  err = MuxImagePush(&wpi, &mux->images_);
+  if (err != WEBP_MUX_OK) goto Err;
+
+  // All is well.
+  return WEBP_MUX_OK;
+
+ Err:  // Something bad happened.
+  MuxImageRelease(&wpi);
+  return err;
+}
+
+WebPMuxError WebPMuxPushFrame(WebPMux* mux, const WebPMuxFrameInfo* info,
+                              int copy_data) {
+  WebPMuxImage wpi;
+  WebPMuxError err;
+  const WebPData* const bitstream = &info->bitstream;
+
+  // Sanity checks.
+  if (mux == NULL || info == NULL) return WEBP_MUX_INVALID_ARGUMENT;
+
+  if (info->id != WEBP_CHUNK_ANMF) return WEBP_MUX_INVALID_ARGUMENT;
+
+  if (bitstream->bytes == NULL || bitstream->size > MAX_CHUNK_PAYLOAD) {
+    return WEBP_MUX_INVALID_ARGUMENT;
+  }
+
+  if (mux->images_ != NULL) {
+    const WebPMuxImage* const image = mux->images_;
+    const uint32_t image_id = (image->header_ != NULL) ?
+        ChunkGetIdFromTag(image->header_->tag_) : WEBP_CHUNK_IMAGE;
+    if (image_id != info->id) {
+      return WEBP_MUX_INVALID_ARGUMENT;  // Conflicting frame types.
+    }
+  }
+
+  MuxImageInit(&wpi);
+  err = SetAlphaAndImageChunks(bitstream, copy_data, &wpi);
+  if (err != WEBP_MUX_OK) goto Err;
+  assert(wpi.img_ != NULL);  // As SetAlphaAndImageChunks() was successful.
+
+  {
+    WebPData frame;
+    const uint32_t tag = kChunks[IDX_ANMF].tag;
+    WebPMuxFrameInfo tmp = *info;
+    tmp.x_offset &= ~1;  // Snap offsets to even.
+    tmp.y_offset &= ~1;
+    if (tmp.x_offset < 0 || tmp.x_offset >= MAX_POSITION_OFFSET ||
+        tmp.y_offset < 0 || tmp.y_offset >= MAX_POSITION_OFFSET ||
+        (tmp.duration < 0 || tmp.duration >= MAX_DURATION) ||
+        tmp.dispose_method != (tmp.dispose_method & 1)) {
+      err = WEBP_MUX_INVALID_ARGUMENT;
+      goto Err;
+    }
+    err = CreateFrameData(wpi.width_, wpi.height_, &tmp, &frame);
+    if (err != WEBP_MUX_OK) goto Err;
+    // Add frame chunk (with copy_data = 1).
+    err = AddDataToChunkList(&frame, 1, tag, &wpi.header_);
+    WebPDataClear(&frame);  // frame owned by wpi.header_ now.
+    if (err != WEBP_MUX_OK) goto Err;
+  }
+
+  // Add this WebPMuxImage to mux.
+  err = MuxImagePush(&wpi, &mux->images_);
+  if (err != WEBP_MUX_OK) goto Err;
+
+  // All is well.
+  return WEBP_MUX_OK;
+
+ Err:  // Something bad happened.
+  MuxImageRelease(&wpi);
+  return err;
+}
+
+WebPMuxError WebPMuxSetAnimationParams(WebPMux* mux,
+                                       const WebPMuxAnimParams* params) {
+  WebPMuxError err;
+  uint8_t data[ANIM_CHUNK_SIZE];
+  const WebPData anim = { data, ANIM_CHUNK_SIZE };
+
+  if (mux == NULL || params == NULL) return WEBP_MUX_INVALID_ARGUMENT;
+  if (params->loop_count < 0 || params->loop_count >= MAX_LOOP_COUNT) {
+    return WEBP_MUX_INVALID_ARGUMENT;
+  }
+
+  // Delete any existing ANIM chunk(s).
+  err = MuxDeleteAllNamedData(mux, kChunks[IDX_ANIM].tag);
+  if (err != WEBP_MUX_OK && err != WEBP_MUX_NOT_FOUND) return err;
+
+  // Set the animation parameters.
+  PutLE32(data, params->bgcolor);
+  PutLE16(data + 4, params->loop_count);
+  return MuxSet(mux, kChunks[IDX_ANIM].tag, 1, &anim, 1);
+}
+
+WebPMuxError WebPMuxSetCanvasSize(WebPMux* mux,
+                                  int width, int height) {
+  WebPMuxError err;
+  if (mux == NULL) {
+    return WEBP_MUX_INVALID_ARGUMENT;
+  }
+  if (width < 0 || height < 0 ||
+      width > MAX_CANVAS_SIZE || height > MAX_CANVAS_SIZE) {
+    return WEBP_MUX_INVALID_ARGUMENT;
+  }
+  if (width * (uint64_t)height >= MAX_IMAGE_AREA) {
+    return WEBP_MUX_INVALID_ARGUMENT;
+  }
+  if ((width * height) == 0 && (width | height) != 0) {
+    // one of width / height is zero, but not both -> invalid!
+    return WEBP_MUX_INVALID_ARGUMENT;
+  }
+  // If we already assembled a VP8X chunk, invalidate it.
+  err = MuxDeleteAllNamedData(mux, kChunks[IDX_VP8X].tag);
+  if (err != WEBP_MUX_OK && err != WEBP_MUX_NOT_FOUND) return err;
+
+  mux->canvas_width_ = width;
+  mux->canvas_height_ = height;
+  return WEBP_MUX_OK;
+}
+
+//------------------------------------------------------------------------------
+// Delete API(s).
+
+WebPMuxError WebPMuxDeleteChunk(WebPMux* mux, const char fourcc[4]) {
+  if (mux == NULL || fourcc == NULL) return WEBP_MUX_INVALID_ARGUMENT;
+  return MuxDeleteAllNamedData(mux, ChunkGetTagFromFourCC(fourcc));
+}
+
+WebPMuxError WebPMuxDeleteFrame(WebPMux* mux, uint32_t nth) {
+  if (mux == NULL) return WEBP_MUX_INVALID_ARGUMENT;
+  return MuxImageDeleteNth(&mux->images_, nth);
+}
+
+//------------------------------------------------------------------------------
+// Assembly of the WebP RIFF file.
+
+static WebPMuxError GetFrameInfo(
+    const WebPChunk* const frame_chunk,
+    int* const x_offset, int* const y_offset, int* const duration) {
+  const WebPData* const data = &frame_chunk->data_;
+  const size_t expected_data_size = ANMF_CHUNK_SIZE;
+  assert(frame_chunk->tag_ == kChunks[IDX_ANMF].tag);
+  assert(frame_chunk != NULL);
+  if (data->size != expected_data_size) return WEBP_MUX_INVALID_ARGUMENT;
+
+  *x_offset = 2 * GetLE24(data->bytes + 0);
+  *y_offset = 2 * GetLE24(data->bytes + 3);
+  *duration = GetLE24(data->bytes + 12);
+  return WEBP_MUX_OK;
+}
+
+static WebPMuxError GetImageInfo(const WebPMuxImage* const wpi,
+                                 int* const x_offset, int* const y_offset,
+                                 int* const duration,
+                                 int* const width, int* const height) {
+  const WebPChunk* const frame_chunk = wpi->header_;
+  WebPMuxError err;
+  assert(wpi != NULL);
+  assert(frame_chunk != NULL);
+
+  // Get offsets and duration from ANMF chunk.
+  err = GetFrameInfo(frame_chunk, x_offset, y_offset, duration);
+  if (err != WEBP_MUX_OK) return err;
+
+  // Get width and height from VP8/VP8L chunk.
+  if (width != NULL) *width = wpi->width_;
+  if (height != NULL) *height = wpi->height_;
+  return WEBP_MUX_OK;
+}
+
+// Returns the tightest dimension for the canvas considering the image list.
+static WebPMuxError GetAdjustedCanvasSize(const WebPMux* const mux,
+                                          int* const width, int* const height) {
+  WebPMuxImage* wpi = NULL;
+  assert(mux != NULL);
+  assert(width != NULL && height != NULL);
+
+  wpi = mux->images_;
+  assert(wpi != NULL);
+  assert(wpi->img_ != NULL);
+
+  if (wpi->next_ != NULL) {
+    int max_x = 0, max_y = 0;
+    // if we have a chain of wpi's, header_ is necessarily set
+    assert(wpi->header_ != NULL);
+    // Aggregate the bounding box for animation frames.
+    for (; wpi != NULL; wpi = wpi->next_) {
+      int x_offset = 0, y_offset = 0, duration = 0, w = 0, h = 0;
+      const WebPMuxError err = GetImageInfo(wpi, &x_offset, &y_offset,
+                                            &duration, &w, &h);
+      const int max_x_pos = x_offset + w;
+      const int max_y_pos = y_offset + h;
+      if (err != WEBP_MUX_OK) return err;
+      assert(x_offset < MAX_POSITION_OFFSET);
+      assert(y_offset < MAX_POSITION_OFFSET);
+
+      if (max_x_pos > max_x) max_x = max_x_pos;
+      if (max_y_pos > max_y) max_y = max_y_pos;
+    }
+    *width = max_x;
+    *height = max_y;
+  } else {
+    // For a single image, canvas dimensions are same as image dimensions.
+    *width = wpi->width_;
+    *height = wpi->height_;
+  }
+  return WEBP_MUX_OK;
+}
+
+// VP8X format:
+// Total Size : 10,
+// Flags  : 4 bytes,
+// Width  : 3 bytes,
+// Height : 3 bytes.
+static WebPMuxError CreateVP8XChunk(WebPMux* const mux) {
+  WebPMuxError err = WEBP_MUX_OK;
+  uint32_t flags = 0;
+  int width = 0;
+  int height = 0;
+  uint8_t data[VP8X_CHUNK_SIZE];
+  const WebPData vp8x = { data, VP8X_CHUNK_SIZE };
+  const WebPMuxImage* images = NULL;
+
+  assert(mux != NULL);
+  images = mux->images_;  // First image.
+  if (images == NULL || images->img_ == NULL ||
+      images->img_->data_.bytes == NULL) {
+    return WEBP_MUX_INVALID_ARGUMENT;
+  }
+
+  // If VP8X chunk(s) is(are) already present, remove them (and later add new
+  // VP8X chunk with updated flags).
+  err = MuxDeleteAllNamedData(mux, kChunks[IDX_VP8X].tag);
+  if (err != WEBP_MUX_OK && err != WEBP_MUX_NOT_FOUND) return err;
+
+  // Set flags.
+  if (mux->iccp_ != NULL && mux->iccp_->data_.bytes != NULL) {
+    flags |= ICCP_FLAG;
+  }
+  if (mux->exif_ != NULL && mux->exif_->data_.bytes != NULL) {
+    flags |= EXIF_FLAG;
+  }
+  if (mux->xmp_ != NULL && mux->xmp_->data_.bytes != NULL) {
+    flags |= XMP_FLAG;
+  }
+  if (images->header_ != NULL) {
+    if (images->header_->tag_ == kChunks[IDX_ANMF].tag) {
+      // This is an image with animation.
+      flags |= ANIMATION_FLAG;
+    }
+  }
+  if (MuxImageCount(images, WEBP_CHUNK_ALPHA) > 0) {
+    flags |= ALPHA_FLAG;  // Some images have an alpha channel.
+  }
+
+  err = GetAdjustedCanvasSize(mux, &width, &height);
+  if (err != WEBP_MUX_OK) return err;
+
+  if (width <= 0 || height <= 0) {
+    return WEBP_MUX_INVALID_ARGUMENT;
+  }
+  if (width > MAX_CANVAS_SIZE || height > MAX_CANVAS_SIZE) {
+    return WEBP_MUX_INVALID_ARGUMENT;
+  }
+
+  if (mux->canvas_width_ != 0 || mux->canvas_height_ != 0) {
+    if (width > mux->canvas_width_ || height > mux->canvas_height_) {
+      return WEBP_MUX_INVALID_ARGUMENT;
+    }
+    width = mux->canvas_width_;
+    height = mux->canvas_height_;
+  }
+
+  if (flags == 0 && mux->unknown_ == NULL) {
+    // For simple file format, VP8X chunk should not be added.
+    return WEBP_MUX_OK;
+  }
+
+  if (MuxHasAlpha(images)) {
+    // This means some frames explicitly/implicitly contain alpha.
+    // Note: This 'flags' update must NOT be done for a lossless image
+    // without a VP8X chunk!
+    flags |= ALPHA_FLAG;
+  }
+
+  PutLE32(data + 0, flags);   // VP8X chunk flags.
+  PutLE24(data + 4, width - 1);   // canvas width.
+  PutLE24(data + 7, height - 1);  // canvas height.
+
+  return MuxSet(mux, kChunks[IDX_VP8X].tag, 1, &vp8x, 1);
+}
+
+// Cleans up 'mux' by removing any unnecessary chunks.
+static WebPMuxError MuxCleanup(WebPMux* const mux) {
+  int num_frames;
+  int num_anim_chunks;
+
+  // If we have an image with a single frame, and its rectangle
+  // covers the whole canvas, convert it to a non-animated image
+  // (to avoid writing ANMF chunk unnecessarily).
+  WebPMuxError err = WebPMuxNumChunks(mux, kChunks[IDX_ANMF].id, &num_frames);
+  if (err != WEBP_MUX_OK) return err;
+  if (num_frames == 1) {
+    WebPMuxImage* frame = NULL;
+    err = MuxImageGetNth((const WebPMuxImage**)&mux->images_, 1, &frame);
+    assert(err == WEBP_MUX_OK);  // We know that one frame does exist.
+    assert(frame != NULL);
+    if (frame->header_ != NULL &&
+        ((mux->canvas_width_ == 0 && mux->canvas_height_ == 0) ||
+         (frame->width_ == mux->canvas_width_ &&
+          frame->height_ == mux->canvas_height_))) {
+      assert(frame->header_->tag_ == kChunks[IDX_ANMF].tag);
+      ChunkDelete(frame->header_);  // Removes ANMF chunk.
+      frame->header_ = NULL;
+      num_frames = 0;
+    }
+  }
+  // Remove ANIM chunk if this is a non-animated image.
+  err = WebPMuxNumChunks(mux, kChunks[IDX_ANIM].id, &num_anim_chunks);
+  if (err != WEBP_MUX_OK) return err;
+  if (num_anim_chunks >= 1 && num_frames == 0) {
+    err = MuxDeleteAllNamedData(mux, kChunks[IDX_ANIM].tag);
+    if (err != WEBP_MUX_OK) return err;
+  }
+  return WEBP_MUX_OK;
+}
+
+// Total size of a list of images.
+static size_t ImageListDiskSize(const WebPMuxImage* wpi_list) {
+  size_t size = 0;
+  while (wpi_list != NULL) {
+    size += MuxImageDiskSize(wpi_list);
+    wpi_list = wpi_list->next_;
+  }
+  return size;
+}
+
+// Write out the given list of images into 'dst'.
+static uint8_t* ImageListEmit(const WebPMuxImage* wpi_list, uint8_t* dst) {
+  while (wpi_list != NULL) {
+    dst = MuxImageEmit(wpi_list, dst);
+    wpi_list = wpi_list->next_;
+  }
+  return dst;
+}
+
+WebPMuxError WebPMuxAssemble(WebPMux* mux, WebPData* assembled_data) {
+  size_t size = 0;
+  uint8_t* data = NULL;
+  uint8_t* dst = NULL;
+  WebPMuxError err;
+
+  if (assembled_data == NULL) {
+    return WEBP_MUX_INVALID_ARGUMENT;
+  }
+  // Clean up returned data, in case something goes wrong.
+  memset(assembled_data, 0, sizeof(*assembled_data));
+
+  if (mux == NULL) {
+    return WEBP_MUX_INVALID_ARGUMENT;
+  }
+
+  // Finalize mux.
+  err = MuxCleanup(mux);
+  if (err != WEBP_MUX_OK) return err;
+  err = CreateVP8XChunk(mux);
+  if (err != WEBP_MUX_OK) return err;
+
+  // Allocate data.
+  size = ChunkListDiskSize(mux->vp8x_) + ChunkListDiskSize(mux->iccp_)
+       + ChunkListDiskSize(mux->anim_) + ImageListDiskSize(mux->images_)
+       + ChunkListDiskSize(mux->exif_) + ChunkListDiskSize(mux->xmp_)
+       + ChunkListDiskSize(mux->unknown_) + RIFF_HEADER_SIZE;
+
+  data = (uint8_t*)WebPSafeMalloc(1ULL, size);
+  if (data == NULL) return WEBP_MUX_MEMORY_ERROR;
+
+  // Emit header & chunks.
+  dst = MuxEmitRiffHeader(data, size);
+  dst = ChunkListEmit(mux->vp8x_, dst);
+  dst = ChunkListEmit(mux->iccp_, dst);
+  dst = ChunkListEmit(mux->anim_, dst);
+  dst = ImageListEmit(mux->images_, dst);
+  dst = ChunkListEmit(mux->exif_, dst);
+  dst = ChunkListEmit(mux->xmp_, dst);
+  dst = ChunkListEmit(mux->unknown_, dst);
+  assert(dst == data + size);
+
+  // Validate mux.
+  err = MuxValidate(mux);
+  if (err != WEBP_MUX_OK) {
+    WebPSafeFree(data);
+    data = NULL;
+    size = 0;
+  }
+
+  // Finalize data.
+  assembled_data->bytes = data;
+  assembled_data->size = size;
+
+  return err;
+}
+
+//------------------------------------------------------------------------------
diff --git a/src/mux/muxi.h b/src/mux/muxi.h
new file mode 100644
index 0000000..e6606aa
--- /dev/null
+++ b/src/mux/muxi.h
@@ -0,0 +1,230 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the COPYING file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+// -----------------------------------------------------------------------------
+//
+// Internal header for mux library.
+//
+// Author: Urvang (urvang@google.com)
+
+#ifndef WEBP_MUX_MUXI_H_
+#define WEBP_MUX_MUXI_H_
+
+#include <stdlib.h>
+#include "../dec/vp8i_dec.h"
+#include "../dec/vp8li_dec.h"
+#include "../webp/mux.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+//------------------------------------------------------------------------------
+// Defines and constants.
+
+#define MUX_MAJ_VERSION 0
+#define MUX_MIN_VERSION 4
+#define MUX_REV_VERSION 0
+
+// Chunk object.
+typedef struct WebPChunk WebPChunk;
+struct WebPChunk {
+  uint32_t        tag_;
+  int             owner_;  // True if *data_ memory is owned internally.
+                           // VP8X, ANIM, and other internally created chunks
+                           // like ANMF are always owned.
+  WebPData        data_;
+  WebPChunk*      next_;
+};
+
+// MuxImage object. Store a full WebP image (including ANMF chunk, ALPH
+// chunk and VP8/VP8L chunk),
+typedef struct WebPMuxImage WebPMuxImage;
+struct WebPMuxImage {
+  WebPChunk*  header_;      // Corresponds to WEBP_CHUNK_ANMF.
+  WebPChunk*  alpha_;       // Corresponds to WEBP_CHUNK_ALPHA.
+  WebPChunk*  img_;         // Corresponds to WEBP_CHUNK_IMAGE.
+  WebPChunk*  unknown_;     // Corresponds to WEBP_CHUNK_UNKNOWN.
+  int         width_;
+  int         height_;
+  int         has_alpha_;   // Through ALPH chunk or as part of VP8L.
+  int         is_partial_;  // True if only some of the chunks are filled.
+  WebPMuxImage* next_;
+};
+
+// Main mux object. Stores data chunks.
+struct WebPMux {
+  WebPMuxImage*   images_;
+  WebPChunk*      iccp_;
+  WebPChunk*      exif_;
+  WebPChunk*      xmp_;
+  WebPChunk*      anim_;
+  WebPChunk*      vp8x_;
+
+  WebPChunk*      unknown_;
+  int             canvas_width_;
+  int             canvas_height_;
+};
+
+// CHUNK_INDEX enum: used for indexing within 'kChunks' (defined below) only.
+// Note: the reason for having two enums ('WebPChunkId' and 'CHUNK_INDEX') is to
+// allow two different chunks to have the same id (e.g. WebPChunkId
+// 'WEBP_CHUNK_IMAGE' can correspond to CHUNK_INDEX 'IDX_VP8' or 'IDX_VP8L').
+typedef enum {
+  IDX_VP8X = 0,
+  IDX_ICCP,
+  IDX_ANIM,
+  IDX_ANMF,
+  IDX_ALPHA,
+  IDX_VP8,
+  IDX_VP8L,
+  IDX_EXIF,
+  IDX_XMP,
+  IDX_UNKNOWN,
+
+  IDX_NIL,
+  IDX_LAST_CHUNK
+} CHUNK_INDEX;
+
+#define NIL_TAG 0x00000000u  // To signal void chunk.
+
+typedef struct {
+  uint32_t      tag;
+  WebPChunkId   id;
+  uint32_t      size;
+} ChunkInfo;
+
+extern const ChunkInfo kChunks[IDX_LAST_CHUNK];
+
+//------------------------------------------------------------------------------
+// Chunk object management.
+
+// Initialize.
+void ChunkInit(WebPChunk* const chunk);
+
+// Get chunk index from chunk tag. Returns IDX_UNKNOWN if not found.
+CHUNK_INDEX ChunkGetIndexFromTag(uint32_t tag);
+
+// Get chunk id from chunk tag. Returns WEBP_CHUNK_UNKNOWN if not found.
+WebPChunkId ChunkGetIdFromTag(uint32_t tag);
+
+// Convert a fourcc string to a tag.
+uint32_t ChunkGetTagFromFourCC(const char fourcc[4]);
+
+// Get chunk index from fourcc. Returns IDX_UNKNOWN if given fourcc is unknown.
+CHUNK_INDEX ChunkGetIndexFromFourCC(const char fourcc[4]);
+
+// Search for nth chunk with given 'tag' in the chunk list.
+// nth = 0 means "last of the list".
+WebPChunk* ChunkSearchList(WebPChunk* first, uint32_t nth, uint32_t tag);
+
+// Fill the chunk with the given data.
+WebPMuxError ChunkAssignData(WebPChunk* chunk, const WebPData* const data,
+                             int copy_data, uint32_t tag);
+
+// Sets 'chunk' at nth position in the 'chunk_list'.
+// nth = 0 has the special meaning "last of the list".
+// On success ownership is transferred from 'chunk' to the 'chunk_list'.
+WebPMuxError ChunkSetNth(WebPChunk* chunk, WebPChunk** chunk_list,
+                         uint32_t nth);
+
+// Releases chunk and returns chunk->next_.
+WebPChunk* ChunkRelease(WebPChunk* const chunk);
+
+// Deletes given chunk & returns chunk->next_.
+WebPChunk* ChunkDelete(WebPChunk* const chunk);
+
+// Deletes all chunks in the given chunk list.
+void ChunkListDelete(WebPChunk** const chunk_list);
+
+// Returns size of the chunk including chunk header and padding byte (if any).
+static WEBP_INLINE size_t SizeWithPadding(size_t chunk_size) {
+  return CHUNK_HEADER_SIZE + ((chunk_size + 1) & ~1U);
+}
+
+// Size of a chunk including header and padding.
+static WEBP_INLINE size_t ChunkDiskSize(const WebPChunk* chunk) {
+  const size_t data_size = chunk->data_.size;
+  assert(data_size < MAX_CHUNK_PAYLOAD);
+  return SizeWithPadding(data_size);
+}
+
+// Total size of a list of chunks.
+size_t ChunkListDiskSize(const WebPChunk* chunk_list);
+
+// Write out the given list of chunks into 'dst'.
+uint8_t* ChunkListEmit(const WebPChunk* chunk_list, uint8_t* dst);
+
+//------------------------------------------------------------------------------
+// MuxImage object management.
+
+// Initialize.
+void MuxImageInit(WebPMuxImage* const wpi);
+
+// Releases image 'wpi' and returns wpi->next.
+WebPMuxImage* MuxImageRelease(WebPMuxImage* const wpi);
+
+// Delete image 'wpi' and return the next image in the list or NULL.
+// 'wpi' can be NULL.
+WebPMuxImage* MuxImageDelete(WebPMuxImage* const wpi);
+
+// Count number of images matching the given tag id in the 'wpi_list'.
+// If id == WEBP_CHUNK_NIL, all images will be matched.
+int MuxImageCount(const WebPMuxImage* wpi_list, WebPChunkId id);
+
+// Update width/height/has_alpha info from chunks within wpi.
+// Also remove ALPH chunk if not needed.
+int MuxImageFinalize(WebPMuxImage* const wpi);
+
+// Check if given ID corresponds to an image related chunk.
+static WEBP_INLINE int IsWPI(WebPChunkId id) {
+  switch (id) {
+    case WEBP_CHUNK_ANMF:
+    case WEBP_CHUNK_ALPHA:
+    case WEBP_CHUNK_IMAGE:  return 1;
+    default:        return 0;
+  }
+}
+
+// Pushes 'wpi' at the end of 'wpi_list'.
+WebPMuxError MuxImagePush(const WebPMuxImage* wpi, WebPMuxImage** wpi_list);
+
+// Delete nth image in the image list.
+WebPMuxError MuxImageDeleteNth(WebPMuxImage** wpi_list, uint32_t nth);
+
+// Get nth image in the image list.
+WebPMuxError MuxImageGetNth(const WebPMuxImage** wpi_list, uint32_t nth,
+                            WebPMuxImage** wpi);
+
+// Total size of the given image.
+size_t MuxImageDiskSize(const WebPMuxImage* const wpi);
+
+// Write out the given image into 'dst'.
+uint8_t* MuxImageEmit(const WebPMuxImage* const wpi, uint8_t* dst);
+
+//------------------------------------------------------------------------------
+// Helper methods for mux.
+
+// Checks if the given image list contains at least one image with alpha.
+int MuxHasAlpha(const WebPMuxImage* images);
+
+// Write out RIFF header into 'data', given total data size 'size'.
+uint8_t* MuxEmitRiffHeader(uint8_t* const data, size_t size);
+
+// Returns the list where chunk with given ID is to be inserted in mux.
+WebPChunk** MuxGetChunkListFromId(const WebPMux* mux, WebPChunkId id);
+
+// Validates the given mux object.
+WebPMuxError MuxValidate(const WebPMux* const mux);
+
+//------------------------------------------------------------------------------
+
+#ifdef __cplusplus
+}    // extern "C"
+#endif
+
+#endif  /* WEBP_MUX_MUXI_H_ */
diff --git a/src/mux/muxinternal.c b/src/mux/muxinternal.c
new file mode 100644
index 0000000..387b57e
--- /dev/null
+++ b/src/mux/muxinternal.c
@@ -0,0 +1,538 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the COPYING file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+// -----------------------------------------------------------------------------
+//
+// Internal objects and utils for mux.
+//
+// Authors: Urvang (urvang@google.com)
+//          Vikas (vikasa@google.com)
+
+#include <assert.h>
+#include "./muxi.h"
+#include "../utils/utils.h"
+
+#define UNDEFINED_CHUNK_SIZE ((uint32_t)(-1))
+
+const ChunkInfo kChunks[] = {
+  { MKFOURCC('V', 'P', '8', 'X'),  WEBP_CHUNK_VP8X,    VP8X_CHUNK_SIZE },
+  { MKFOURCC('I', 'C', 'C', 'P'),  WEBP_CHUNK_ICCP,    UNDEFINED_CHUNK_SIZE },
+  { MKFOURCC('A', 'N', 'I', 'M'),  WEBP_CHUNK_ANIM,    ANIM_CHUNK_SIZE },
+  { MKFOURCC('A', 'N', 'M', 'F'),  WEBP_CHUNK_ANMF,    ANMF_CHUNK_SIZE },
+  { MKFOURCC('A', 'L', 'P', 'H'),  WEBP_CHUNK_ALPHA,   UNDEFINED_CHUNK_SIZE },
+  { MKFOURCC('V', 'P', '8', ' '),  WEBP_CHUNK_IMAGE,   UNDEFINED_CHUNK_SIZE },
+  { MKFOURCC('V', 'P', '8', 'L'),  WEBP_CHUNK_IMAGE,   UNDEFINED_CHUNK_SIZE },
+  { MKFOURCC('E', 'X', 'I', 'F'),  WEBP_CHUNK_EXIF,    UNDEFINED_CHUNK_SIZE },
+  { MKFOURCC('X', 'M', 'P', ' '),  WEBP_CHUNK_XMP,     UNDEFINED_CHUNK_SIZE },
+  { NIL_TAG,                       WEBP_CHUNK_UNKNOWN, UNDEFINED_CHUNK_SIZE },
+
+  { NIL_TAG,                       WEBP_CHUNK_NIL,     UNDEFINED_CHUNK_SIZE }
+};
+
+//------------------------------------------------------------------------------
+
+int WebPGetMuxVersion(void) {
+  return (MUX_MAJ_VERSION << 16) | (MUX_MIN_VERSION << 8) | MUX_REV_VERSION;
+}
+
+//------------------------------------------------------------------------------
+// Life of a chunk object.
+
+void ChunkInit(WebPChunk* const chunk) {
+  assert(chunk);
+  memset(chunk, 0, sizeof(*chunk));
+  chunk->tag_ = NIL_TAG;
+}
+
+WebPChunk* ChunkRelease(WebPChunk* const chunk) {
+  WebPChunk* next;
+  if (chunk == NULL) return NULL;
+  if (chunk->owner_) {
+    WebPDataClear(&chunk->data_);
+  }
+  next = chunk->next_;
+  ChunkInit(chunk);
+  return next;
+}
+
+//------------------------------------------------------------------------------
+// Chunk misc methods.
+
+CHUNK_INDEX ChunkGetIndexFromTag(uint32_t tag) {
+  int i;
+  for (i = 0; kChunks[i].tag != NIL_TAG; ++i) {
+    if (tag == kChunks[i].tag) return (CHUNK_INDEX)i;
+  }
+  return IDX_UNKNOWN;
+}
+
+WebPChunkId ChunkGetIdFromTag(uint32_t tag) {
+  int i;
+  for (i = 0; kChunks[i].tag != NIL_TAG; ++i) {
+    if (tag == kChunks[i].tag) return kChunks[i].id;
+  }
+  return WEBP_CHUNK_UNKNOWN;
+}
+
+uint32_t ChunkGetTagFromFourCC(const char fourcc[4]) {
+  return MKFOURCC(fourcc[0], fourcc[1], fourcc[2], fourcc[3]);
+}
+
+CHUNK_INDEX ChunkGetIndexFromFourCC(const char fourcc[4]) {
+  const uint32_t tag = ChunkGetTagFromFourCC(fourcc);
+  return ChunkGetIndexFromTag(tag);
+}
+
+//------------------------------------------------------------------------------
+// Chunk search methods.
+
+// Returns next chunk in the chunk list with the given tag.
+static WebPChunk* ChunkSearchNextInList(WebPChunk* chunk, uint32_t tag) {
+  while (chunk != NULL && chunk->tag_ != tag) {
+    chunk = chunk->next_;
+  }
+  return chunk;
+}
+
+WebPChunk* ChunkSearchList(WebPChunk* first, uint32_t nth, uint32_t tag) {
+  uint32_t iter = nth;
+  first = ChunkSearchNextInList(first, tag);
+  if (first == NULL) return NULL;
+
+  while (--iter != 0) {
+    WebPChunk* next_chunk = ChunkSearchNextInList(first->next_, tag);
+    if (next_chunk == NULL) break;
+    first = next_chunk;
+  }
+  return ((nth > 0) && (iter > 0)) ? NULL : first;
+}
+
+// Outputs a pointer to 'prev_chunk->next_',
+//   where 'prev_chunk' is the pointer to the chunk at position (nth - 1).
+// Returns true if nth chunk was found.
+static int ChunkSearchListToSet(WebPChunk** chunk_list, uint32_t nth,
+                                WebPChunk*** const location) {
+  uint32_t count = 0;
+  assert(chunk_list != NULL);
+  *location = chunk_list;
+
+  while (*chunk_list != NULL) {
+    WebPChunk* const cur_chunk = *chunk_list;
+    ++count;
+    if (count == nth) return 1;  // Found.
+    chunk_list = &cur_chunk->next_;
+    *location = chunk_list;
+  }
+
+  // *chunk_list is ok to be NULL if adding at last location.
+  return (nth == 0 || (count == nth - 1)) ? 1 : 0;
+}
+
+//------------------------------------------------------------------------------
+// Chunk writer methods.
+
+WebPMuxError ChunkAssignData(WebPChunk* chunk, const WebPData* const data,
+                             int copy_data, uint32_t tag) {
+  // For internally allocated chunks, always copy data & make it owner of data.
+  if (tag == kChunks[IDX_VP8X].tag || tag == kChunks[IDX_ANIM].tag) {
+    copy_data = 1;
+  }
+
+  ChunkRelease(chunk);
+
+  if (data != NULL) {
+    if (copy_data) {        // Copy data.
+      if (!WebPDataCopy(data, &chunk->data_)) return WEBP_MUX_MEMORY_ERROR;
+      chunk->owner_ = 1;    // Chunk is owner of data.
+    } else {                // Don't copy data.
+      chunk->data_ = *data;
+    }
+  }
+  chunk->tag_ = tag;
+  return WEBP_MUX_OK;
+}
+
+WebPMuxError ChunkSetNth(WebPChunk* chunk, WebPChunk** chunk_list,
+                         uint32_t nth) {
+  WebPChunk* new_chunk;
+
+  if (!ChunkSearchListToSet(chunk_list, nth, &chunk_list)) {
+    return WEBP_MUX_NOT_FOUND;
+  }
+
+  new_chunk = (WebPChunk*)WebPSafeMalloc(1ULL, sizeof(*new_chunk));
+  if (new_chunk == NULL) return WEBP_MUX_MEMORY_ERROR;
+  *new_chunk = *chunk;
+  chunk->owner_ = 0;
+  new_chunk->next_ = *chunk_list;
+  *chunk_list = new_chunk;
+  return WEBP_MUX_OK;
+}
+
+//------------------------------------------------------------------------------
+// Chunk deletion method(s).
+
+WebPChunk* ChunkDelete(WebPChunk* const chunk) {
+  WebPChunk* const next = ChunkRelease(chunk);
+  WebPSafeFree(chunk);
+  return next;
+}
+
+void ChunkListDelete(WebPChunk** const chunk_list) {
+  while (*chunk_list != NULL) {
+    *chunk_list = ChunkDelete(*chunk_list);
+  }
+}
+
+//------------------------------------------------------------------------------
+// Chunk serialization methods.
+
+static uint8_t* ChunkEmit(const WebPChunk* const chunk, uint8_t* dst) {
+  const size_t chunk_size = chunk->data_.size;
+  assert(chunk);
+  assert(chunk->tag_ != NIL_TAG);
+  PutLE32(dst + 0, chunk->tag_);
+  PutLE32(dst + TAG_SIZE, (uint32_t)chunk_size);
+  assert(chunk_size == (uint32_t)chunk_size);
+  memcpy(dst + CHUNK_HEADER_SIZE, chunk->data_.bytes, chunk_size);
+  if (chunk_size & 1)
+    dst[CHUNK_HEADER_SIZE + chunk_size] = 0;  // Add padding.
+  return dst + ChunkDiskSize(chunk);
+}
+
+uint8_t* ChunkListEmit(const WebPChunk* chunk_list, uint8_t* dst) {
+  while (chunk_list != NULL) {
+    dst = ChunkEmit(chunk_list, dst);
+    chunk_list = chunk_list->next_;
+  }
+  return dst;
+}
+
+size_t ChunkListDiskSize(const WebPChunk* chunk_list) {
+  size_t size = 0;
+  while (chunk_list != NULL) {
+    size += ChunkDiskSize(chunk_list);
+    chunk_list = chunk_list->next_;
+  }
+  return size;
+}
+
+//------------------------------------------------------------------------------
+// Life of a MuxImage object.
+
+void MuxImageInit(WebPMuxImage* const wpi) {
+  assert(wpi);
+  memset(wpi, 0, sizeof(*wpi));
+}
+
+WebPMuxImage* MuxImageRelease(WebPMuxImage* const wpi) {
+  WebPMuxImage* next;
+  if (wpi == NULL) return NULL;
+  ChunkDelete(wpi->header_);
+  ChunkDelete(wpi->alpha_);
+  ChunkDelete(wpi->img_);
+  ChunkListDelete(&wpi->unknown_);
+
+  next = wpi->next_;
+  MuxImageInit(wpi);
+  return next;
+}
+
+//------------------------------------------------------------------------------
+// MuxImage search methods.
+
+// Get a reference to appropriate chunk list within an image given chunk tag.
+static WebPChunk** GetChunkListFromId(const WebPMuxImage* const wpi,
+                                      WebPChunkId id) {
+  assert(wpi != NULL);
+  switch (id) {
+    case WEBP_CHUNK_ANMF:  return (WebPChunk**)&wpi->header_;
+    case WEBP_CHUNK_ALPHA: return (WebPChunk**)&wpi->alpha_;
+    case WEBP_CHUNK_IMAGE: return (WebPChunk**)&wpi->img_;
+    default: return NULL;
+  }
+}
+
+int MuxImageCount(const WebPMuxImage* wpi_list, WebPChunkId id) {
+  int count = 0;
+  const WebPMuxImage* current;
+  for (current = wpi_list; current != NULL; current = current->next_) {
+    if (id == WEBP_CHUNK_NIL) {
+      ++count;  // Special case: count all images.
+    } else {
+      const WebPChunk* const wpi_chunk = *GetChunkListFromId(current, id);
+      if (wpi_chunk != NULL) {
+        const WebPChunkId wpi_chunk_id = ChunkGetIdFromTag(wpi_chunk->tag_);
+        if (wpi_chunk_id == id) ++count;  // Count images with a matching 'id'.
+      }
+    }
+  }
+  return count;
+}
+
+// Outputs a pointer to 'prev_wpi->next_',
+//   where 'prev_wpi' is the pointer to the image at position (nth - 1).
+// Returns true if nth image was found.
+static int SearchImageToGetOrDelete(WebPMuxImage** wpi_list, uint32_t nth,
+                                    WebPMuxImage*** const location) {
+  uint32_t count = 0;
+  assert(wpi_list);
+  *location = wpi_list;
+
+  if (nth == 0) {
+    nth = MuxImageCount(*wpi_list, WEBP_CHUNK_NIL);
+    if (nth == 0) return 0;  // Not found.
+  }
+
+  while (*wpi_list != NULL) {
+    WebPMuxImage* const cur_wpi = *wpi_list;
+    ++count;
+    if (count == nth) return 1;  // Found.
+    wpi_list = &cur_wpi->next_;
+    *location = wpi_list;
+  }
+  return 0;  // Not found.
+}
+
+//------------------------------------------------------------------------------
+// MuxImage writer methods.
+
+WebPMuxError MuxImagePush(const WebPMuxImage* wpi, WebPMuxImage** wpi_list) {
+  WebPMuxImage* new_wpi;
+
+  while (*wpi_list != NULL) {
+    WebPMuxImage* const cur_wpi = *wpi_list;
+    if (cur_wpi->next_ == NULL) break;
+    wpi_list = &cur_wpi->next_;
+  }
+
+  new_wpi = (WebPMuxImage*)WebPSafeMalloc(1ULL, sizeof(*new_wpi));
+  if (new_wpi == NULL) return WEBP_MUX_MEMORY_ERROR;
+  *new_wpi = *wpi;
+  new_wpi->next_ = NULL;
+
+  if (*wpi_list != NULL) {
+    (*wpi_list)->next_ = new_wpi;
+  } else {
+    *wpi_list = new_wpi;
+  }
+  return WEBP_MUX_OK;
+}
+
+//------------------------------------------------------------------------------
+// MuxImage deletion methods.
+
+WebPMuxImage* MuxImageDelete(WebPMuxImage* const wpi) {
+  // Delete the components of wpi. If wpi is NULL this is a noop.
+  WebPMuxImage* const next = MuxImageRelease(wpi);
+  WebPSafeFree(wpi);
+  return next;
+}
+
+WebPMuxError MuxImageDeleteNth(WebPMuxImage** wpi_list, uint32_t nth) {
+  assert(wpi_list);
+  if (!SearchImageToGetOrDelete(wpi_list, nth, &wpi_list)) {
+    return WEBP_MUX_NOT_FOUND;
+  }
+  *wpi_list = MuxImageDelete(*wpi_list);
+  return WEBP_MUX_OK;
+}
+
+//------------------------------------------------------------------------------
+// MuxImage reader methods.
+
+WebPMuxError MuxImageGetNth(const WebPMuxImage** wpi_list, uint32_t nth,
+                            WebPMuxImage** wpi) {
+  assert(wpi_list);
+  assert(wpi);
+  if (!SearchImageToGetOrDelete((WebPMuxImage**)wpi_list, nth,
+                                (WebPMuxImage***)&wpi_list)) {
+    return WEBP_MUX_NOT_FOUND;
+  }
+  *wpi = (WebPMuxImage*)*wpi_list;
+  return WEBP_MUX_OK;
+}
+
+//------------------------------------------------------------------------------
+// MuxImage serialization methods.
+
+// Size of an image.
+size_t MuxImageDiskSize(const WebPMuxImage* const wpi) {
+  size_t size = 0;
+  if (wpi->header_ != NULL) size += ChunkDiskSize(wpi->header_);
+  if (wpi->alpha_ != NULL) size += ChunkDiskSize(wpi->alpha_);
+  if (wpi->img_ != NULL) size += ChunkDiskSize(wpi->img_);
+  if (wpi->unknown_ != NULL) size += ChunkListDiskSize(wpi->unknown_);
+  return size;
+}
+
+// Special case as ANMF chunk encapsulates other image chunks.
+static uint8_t* ChunkEmitSpecial(const WebPChunk* const header,
+                                 size_t total_size, uint8_t* dst) {
+  const size_t header_size = header->data_.size;
+  const size_t offset_to_next = total_size - CHUNK_HEADER_SIZE;
+  assert(header->tag_ == kChunks[IDX_ANMF].tag);
+  PutLE32(dst + 0, header->tag_);
+  PutLE32(dst + TAG_SIZE, (uint32_t)offset_to_next);
+  assert(header_size == (uint32_t)header_size);
+  memcpy(dst + CHUNK_HEADER_SIZE, header->data_.bytes, header_size);
+  if (header_size & 1) {
+    dst[CHUNK_HEADER_SIZE + header_size] = 0;  // Add padding.
+  }
+  return dst + ChunkDiskSize(header);
+}
+
+uint8_t* MuxImageEmit(const WebPMuxImage* const wpi, uint8_t* dst) {
+  // Ordering of chunks to be emitted is strictly as follows:
+  // 1. ANMF chunk (if present).
+  // 2. ALPH chunk (if present).
+  // 3. VP8/VP8L chunk.
+  assert(wpi);
+  if (wpi->header_ != NULL) {
+    dst = ChunkEmitSpecial(wpi->header_, MuxImageDiskSize(wpi), dst);
+  }
+  if (wpi->alpha_ != NULL) dst = ChunkEmit(wpi->alpha_, dst);
+  if (wpi->img_ != NULL) dst = ChunkEmit(wpi->img_, dst);
+  if (wpi->unknown_ != NULL) dst = ChunkListEmit(wpi->unknown_, dst);
+  return dst;
+}
+
+//------------------------------------------------------------------------------
+// Helper methods for mux.
+
+int MuxHasAlpha(const WebPMuxImage* images) {
+  while (images != NULL) {
+    if (images->has_alpha_) return 1;
+    images = images->next_;
+  }
+  return 0;
+}
+
+uint8_t* MuxEmitRiffHeader(uint8_t* const data, size_t size) {
+  PutLE32(data + 0, MKFOURCC('R', 'I', 'F', 'F'));
+  PutLE32(data + TAG_SIZE, (uint32_t)size - CHUNK_HEADER_SIZE);
+  assert(size == (uint32_t)size);
+  PutLE32(data + TAG_SIZE + CHUNK_SIZE_BYTES, MKFOURCC('W', 'E', 'B', 'P'));
+  return data + RIFF_HEADER_SIZE;
+}
+
+WebPChunk** MuxGetChunkListFromId(const WebPMux* mux, WebPChunkId id) {
+  assert(mux != NULL);
+  switch (id) {
+    case WEBP_CHUNK_VP8X:    return (WebPChunk**)&mux->vp8x_;
+    case WEBP_CHUNK_ICCP:    return (WebPChunk**)&mux->iccp_;
+    case WEBP_CHUNK_ANIM:    return (WebPChunk**)&mux->anim_;
+    case WEBP_CHUNK_EXIF:    return (WebPChunk**)&mux->exif_;
+    case WEBP_CHUNK_XMP:     return (WebPChunk**)&mux->xmp_;
+    default:                 return (WebPChunk**)&mux->unknown_;
+  }
+}
+
+static int IsNotCompatible(int feature, int num_items) {
+  return (feature != 0) != (num_items > 0);
+}
+
+#define NO_FLAG ((WebPFeatureFlags)0)
+
+// Test basic constraints:
+// retrieval, maximum number of chunks by index (use -1 to skip)
+// and feature incompatibility (use NO_FLAG to skip).
+// On success returns WEBP_MUX_OK and stores the chunk count in *num.
+static WebPMuxError ValidateChunk(const WebPMux* const mux, CHUNK_INDEX idx,
+                                  WebPFeatureFlags feature,
+                                  uint32_t vp8x_flags,
+                                  int max, int* num) {
+  const WebPMuxError err =
+      WebPMuxNumChunks(mux, kChunks[idx].id, num);
+  if (err != WEBP_MUX_OK) return err;
+  if (max > -1 && *num > max) return WEBP_MUX_INVALID_ARGUMENT;
+  if (feature != NO_FLAG && IsNotCompatible(vp8x_flags & feature, *num)) {
+    return WEBP_MUX_INVALID_ARGUMENT;
+  }
+  return WEBP_MUX_OK;
+}
+
+WebPMuxError MuxValidate(const WebPMux* const mux) {
+  int num_iccp;
+  int num_exif;
+  int num_xmp;
+  int num_anim;
+  int num_frames;
+  int num_vp8x;
+  int num_images;
+  int num_alpha;
+  uint32_t flags;
+  WebPMuxError err;
+
+  // Verify mux is not NULL.
+  if (mux == NULL) return WEBP_MUX_INVALID_ARGUMENT;
+
+  // Verify mux has at least one image.
+  if (mux->images_ == NULL) return WEBP_MUX_INVALID_ARGUMENT;
+
+  err = WebPMuxGetFeatures(mux, &flags);
+  if (err != WEBP_MUX_OK) return err;
+
+  // At most one color profile chunk.
+  err = ValidateChunk(mux, IDX_ICCP, ICCP_FLAG, flags, 1, &num_iccp);
+  if (err != WEBP_MUX_OK) return err;
+
+  // At most one EXIF metadata.
+  err = ValidateChunk(mux, IDX_EXIF, EXIF_FLAG, flags, 1, &num_exif);
+  if (err != WEBP_MUX_OK) return err;
+
+  // At most one XMP metadata.
+  err = ValidateChunk(mux, IDX_XMP, XMP_FLAG, flags, 1, &num_xmp);
+  if (err != WEBP_MUX_OK) return err;
+
+  // Animation: ANIMATION_FLAG, ANIM chunk and ANMF chunk(s) are consistent.
+  // At most one ANIM chunk.
+  err = ValidateChunk(mux, IDX_ANIM, NO_FLAG, flags, 1, &num_anim);
+  if (err != WEBP_MUX_OK) return err;
+  err = ValidateChunk(mux, IDX_ANMF, NO_FLAG, flags, -1, &num_frames);
+  if (err != WEBP_MUX_OK) return err;
+
+  {
+    const int has_animation = !!(flags & ANIMATION_FLAG);
+    if (has_animation && (num_anim == 0 || num_frames == 0)) {
+      return WEBP_MUX_INVALID_ARGUMENT;
+    }
+    if (!has_animation && (num_anim == 1 || num_frames > 0)) {
+      return WEBP_MUX_INVALID_ARGUMENT;
+    }
+  }
+
+  // Verify either VP8X chunk is present OR there is only one elem in
+  // mux->images_.
+  err = ValidateChunk(mux, IDX_VP8X, NO_FLAG, flags, 1, &num_vp8x);
+  if (err != WEBP_MUX_OK) return err;
+  err = ValidateChunk(mux, IDX_VP8, NO_FLAG, flags, -1, &num_images);
+  if (err != WEBP_MUX_OK) return err;
+  if (num_vp8x == 0 && num_images != 1) return WEBP_MUX_INVALID_ARGUMENT;
+
+  // ALPHA_FLAG & alpha chunk(s) are consistent.
+  if (MuxHasAlpha(mux->images_)) {
+    if (num_vp8x > 0) {
+      // VP8X chunk is present, so it should contain ALPHA_FLAG.
+      if (!(flags & ALPHA_FLAG)) return WEBP_MUX_INVALID_ARGUMENT;
+    } else {
+      // VP8X chunk is not present, so ALPH chunks should NOT be present either.
+      err = WebPMuxNumChunks(mux, WEBP_CHUNK_ALPHA, &num_alpha);
+      if (err != WEBP_MUX_OK) return err;
+      if (num_alpha > 0) return WEBP_MUX_INVALID_ARGUMENT;
+    }
+  } else {  // Mux doesn't need alpha. So, ALPHA_FLAG should NOT be present.
+    if (flags & ALPHA_FLAG) return WEBP_MUX_INVALID_ARGUMENT;
+  }
+
+  return WEBP_MUX_OK;
+}
+
+#undef NO_FLAG
+
+//------------------------------------------------------------------------------
+
diff --git a/src/mux/muxread.c b/src/mux/muxread.c
new file mode 100644
index 0000000..410acd9
--- /dev/null
+++ b/src/mux/muxread.c
@@ -0,0 +1,536 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the COPYING file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+// -----------------------------------------------------------------------------
+//
+// Read APIs for mux.
+//
+// Authors: Urvang (urvang@google.com)
+//          Vikas (vikasa@google.com)
+
+#include <assert.h>
+#include "./muxi.h"
+#include "../utils/utils.h"
+
+//------------------------------------------------------------------------------
+// Helper method(s).
+
+// Handy MACRO.
+#define SWITCH_ID_LIST(INDEX, LIST)                                           \
+  if (idx == (INDEX)) {                                                       \
+    const WebPChunk* const chunk = ChunkSearchList((LIST), nth,               \
+                                                   kChunks[(INDEX)].tag);     \
+    if (chunk) {                                                              \
+      *data = chunk->data_;                                                   \
+      return WEBP_MUX_OK;                                                     \
+    } else {                                                                  \
+      return WEBP_MUX_NOT_FOUND;                                              \
+    }                                                                         \
+  }
+
+static WebPMuxError MuxGet(const WebPMux* const mux, CHUNK_INDEX idx,
+                           uint32_t nth, WebPData* const data) {
+  assert(mux != NULL);
+  assert(!IsWPI(kChunks[idx].id));
+  WebPDataInit(data);
+
+  SWITCH_ID_LIST(IDX_VP8X, mux->vp8x_);
+  SWITCH_ID_LIST(IDX_ICCP, mux->iccp_);
+  SWITCH_ID_LIST(IDX_ANIM, mux->anim_);
+  SWITCH_ID_LIST(IDX_EXIF, mux->exif_);
+  SWITCH_ID_LIST(IDX_XMP, mux->xmp_);
+  SWITCH_ID_LIST(IDX_UNKNOWN, mux->unknown_);
+  return WEBP_MUX_NOT_FOUND;
+}
+#undef SWITCH_ID_LIST
+
+// Fill the chunk with the given data (includes chunk header bytes), after some
+// verifications.
+static WebPMuxError ChunkVerifyAndAssign(WebPChunk* chunk,
+                                         const uint8_t* data, size_t data_size,
+                                         size_t riff_size, int copy_data) {
+  uint32_t chunk_size;
+  WebPData chunk_data;
+
+  // Sanity checks.
+  if (data_size < CHUNK_HEADER_SIZE) return WEBP_MUX_NOT_ENOUGH_DATA;
+  chunk_size = GetLE32(data + TAG_SIZE);
+
+  {
+    const size_t chunk_disk_size = SizeWithPadding(chunk_size);
+    if (chunk_disk_size > riff_size) return WEBP_MUX_BAD_DATA;
+    if (chunk_disk_size > data_size) return WEBP_MUX_NOT_ENOUGH_DATA;
+  }
+
+  // Data assignment.
+  chunk_data.bytes = data + CHUNK_HEADER_SIZE;
+  chunk_data.size = chunk_size;
+  return ChunkAssignData(chunk, &chunk_data, copy_data, GetLE32(data + 0));
+}
+
+int MuxImageFinalize(WebPMuxImage* const wpi) {
+  const WebPChunk* const img = wpi->img_;
+  const WebPData* const image = &img->data_;
+  const int is_lossless = (img->tag_ == kChunks[IDX_VP8L].tag);
+  int w, h;
+  int vp8l_has_alpha = 0;
+  const int ok = is_lossless ?
+      VP8LGetInfo(image->bytes, image->size, &w, &h, &vp8l_has_alpha) :
+      VP8GetInfo(image->bytes, image->size, image->size, &w, &h);
+  assert(img != NULL);
+  if (ok) {
+    // Ignore ALPH chunk accompanying VP8L.
+    if (is_lossless && (wpi->alpha_ != NULL)) {
+      ChunkDelete(wpi->alpha_);
+      wpi->alpha_ = NULL;
+    }
+    wpi->width_ = w;
+    wpi->height_ = h;
+    wpi->has_alpha_ = vp8l_has_alpha || (wpi->alpha_ != NULL);
+  }
+  return ok;
+}
+
+static int MuxImageParse(const WebPChunk* const chunk, int copy_data,
+                         WebPMuxImage* const wpi) {
+  const uint8_t* bytes = chunk->data_.bytes;
+  size_t size = chunk->data_.size;
+  const uint8_t* const last = bytes + size;
+  WebPChunk subchunk;
+  size_t subchunk_size;
+  ChunkInit(&subchunk);
+
+  assert(chunk->tag_ == kChunks[IDX_ANMF].tag);
+  assert(!wpi->is_partial_);
+
+  // ANMF.
+  {
+    const size_t hdr_size = ANMF_CHUNK_SIZE;
+    const WebPData temp = { bytes, hdr_size };
+    // Each of ANMF chunk contain a header at the beginning. So, its size should
+    // be at least 'hdr_size'.
+    if (size < hdr_size) goto Fail;
+    ChunkAssignData(&subchunk, &temp, copy_data, chunk->tag_);
+  }
+  ChunkSetNth(&subchunk, &wpi->header_, 1);
+  wpi->is_partial_ = 1;  // Waiting for ALPH and/or VP8/VP8L chunks.
+
+  // Rest of the chunks.
+  subchunk_size = ChunkDiskSize(&subchunk) - CHUNK_HEADER_SIZE;
+  bytes += subchunk_size;
+  size -= subchunk_size;
+
+  while (bytes != last) {
+    ChunkInit(&subchunk);
+    if (ChunkVerifyAndAssign(&subchunk, bytes, size, size,
+                             copy_data) != WEBP_MUX_OK) {
+      goto Fail;
+    }
+    switch (ChunkGetIdFromTag(subchunk.tag_)) {
+      case WEBP_CHUNK_ALPHA:
+        if (wpi->alpha_ != NULL) goto Fail;  // Consecutive ALPH chunks.
+        if (ChunkSetNth(&subchunk, &wpi->alpha_, 1) != WEBP_MUX_OK) goto Fail;
+        wpi->is_partial_ = 1;  // Waiting for a VP8 chunk.
+        break;
+      case WEBP_CHUNK_IMAGE:
+        if (ChunkSetNth(&subchunk, &wpi->img_, 1) != WEBP_MUX_OK) goto Fail;
+        if (!MuxImageFinalize(wpi)) goto Fail;
+        wpi->is_partial_ = 0;  // wpi is completely filled.
+        break;
+      case WEBP_CHUNK_UNKNOWN:
+        if (wpi->is_partial_) goto Fail;  // Encountered an unknown chunk
+                                          // before some image chunks.
+        if (ChunkSetNth(&subchunk, &wpi->unknown_, 0) != WEBP_MUX_OK) goto Fail;
+        break;
+      default:
+        goto Fail;
+        break;
+    }
+    subchunk_size = ChunkDiskSize(&subchunk);
+    bytes += subchunk_size;
+    size -= subchunk_size;
+  }
+  if (wpi->is_partial_) goto Fail;
+  return 1;
+
+ Fail:
+  ChunkRelease(&subchunk);
+  return 0;
+}
+
+//------------------------------------------------------------------------------
+// Create a mux object from WebP-RIFF data.
+
+WebPMux* WebPMuxCreateInternal(const WebPData* bitstream, int copy_data,
+                               int version) {
+  size_t riff_size;
+  uint32_t tag;
+  const uint8_t* end;
+  WebPMux* mux = NULL;
+  WebPMuxImage* wpi = NULL;
+  const uint8_t* data;
+  size_t size;
+  WebPChunk chunk;
+  ChunkInit(&chunk);
+
+  // Sanity checks.
+  if (WEBP_ABI_IS_INCOMPATIBLE(version, WEBP_MUX_ABI_VERSION)) {
+    return NULL;  // version mismatch
+  }
+  if (bitstream == NULL) return NULL;
+
+  data = bitstream->bytes;
+  size = bitstream->size;
+
+  if (data == NULL) return NULL;
+  if (size < RIFF_HEADER_SIZE) return NULL;
+  if (GetLE32(data + 0) != MKFOURCC('R', 'I', 'F', 'F') ||
+      GetLE32(data + CHUNK_HEADER_SIZE) != MKFOURCC('W', 'E', 'B', 'P')) {
+    return NULL;
+  }
+
+  mux = WebPMuxNew();
+  if (mux == NULL) return NULL;
+
+  if (size < RIFF_HEADER_SIZE + TAG_SIZE) goto Err;
+
+  tag = GetLE32(data + RIFF_HEADER_SIZE);
+  if (tag != kChunks[IDX_VP8].tag &&
+      tag != kChunks[IDX_VP8L].tag &&
+      tag != kChunks[IDX_VP8X].tag) {
+    goto Err;  // First chunk should be VP8, VP8L or VP8X.
+  }
+
+  riff_size = SizeWithPadding(GetLE32(data + TAG_SIZE));
+  if (riff_size > MAX_CHUNK_PAYLOAD || riff_size > size) {
+    goto Err;
+  } else {
+    if (riff_size < size) {  // Redundant data after last chunk.
+      size = riff_size;  // To make sure we don't read any data beyond mux_size.
+    }
+  }
+
+  end = data + size;
+  data += RIFF_HEADER_SIZE;
+  size -= RIFF_HEADER_SIZE;
+
+  wpi = (WebPMuxImage*)WebPSafeMalloc(1ULL, sizeof(*wpi));
+  if (wpi == NULL) goto Err;
+  MuxImageInit(wpi);
+
+  // Loop over chunks.
+  while (data != end) {
+    size_t data_size;
+    WebPChunkId id;
+    WebPChunk** chunk_list;
+    if (ChunkVerifyAndAssign(&chunk, data, size, riff_size,
+                             copy_data) != WEBP_MUX_OK) {
+      goto Err;
+    }
+    data_size = ChunkDiskSize(&chunk);
+    id = ChunkGetIdFromTag(chunk.tag_);
+    switch (id) {
+      case WEBP_CHUNK_ALPHA:
+        if (wpi->alpha_ != NULL) goto Err;  // Consecutive ALPH chunks.
+        if (ChunkSetNth(&chunk, &wpi->alpha_, 1) != WEBP_MUX_OK) goto Err;
+        wpi->is_partial_ = 1;  // Waiting for a VP8 chunk.
+        break;
+      case WEBP_CHUNK_IMAGE:
+        if (ChunkSetNth(&chunk, &wpi->img_, 1) != WEBP_MUX_OK) goto Err;
+        if (!MuxImageFinalize(wpi)) goto Err;
+        wpi->is_partial_ = 0;  // wpi is completely filled.
+ PushImage:
+        // Add this to mux->images_ list.
+        if (MuxImagePush(wpi, &mux->images_) != WEBP_MUX_OK) goto Err;
+        MuxImageInit(wpi);  // Reset for reading next image.
+        break;
+      case WEBP_CHUNK_ANMF:
+        if (wpi->is_partial_) goto Err;  // Previous wpi is still incomplete.
+        if (!MuxImageParse(&chunk, copy_data, wpi)) goto Err;
+        ChunkRelease(&chunk);
+        goto PushImage;
+        break;
+      default:  // A non-image chunk.
+        if (wpi->is_partial_) goto Err;  // Encountered a non-image chunk before
+                                         // getting all chunks of an image.
+        chunk_list = MuxGetChunkListFromId(mux, id);  // List to add this chunk.
+        if (ChunkSetNth(&chunk, chunk_list, 0) != WEBP_MUX_OK) goto Err;
+        if (id == WEBP_CHUNK_VP8X) {  // grab global specs
+          mux->canvas_width_ = GetLE24(data + 12) + 1;
+          mux->canvas_height_ = GetLE24(data + 15) + 1;
+        }
+        break;
+    }
+    data += data_size;
+    size -= data_size;
+    ChunkInit(&chunk);
+  }
+
+  // Validate mux if complete.
+  if (MuxValidate(mux) != WEBP_MUX_OK) goto Err;
+
+  MuxImageDelete(wpi);
+  return mux;  // All OK;
+
+ Err:  // Something bad happened.
+  ChunkRelease(&chunk);
+  MuxImageDelete(wpi);
+  WebPMuxDelete(mux);
+  return NULL;
+}
+
+//------------------------------------------------------------------------------
+// Get API(s).
+
+// Validates that the given mux has a single image.
+static WebPMuxError ValidateForSingleImage(const WebPMux* const mux) {
+  const int num_images = MuxImageCount(mux->images_, WEBP_CHUNK_IMAGE);
+  const int num_frames = MuxImageCount(mux->images_, WEBP_CHUNK_ANMF);
+
+  if (num_images == 0) {
+    // No images in mux.
+    return WEBP_MUX_NOT_FOUND;
+  } else if (num_images == 1 && num_frames == 0) {
+    // Valid case (single image).
+    return WEBP_MUX_OK;
+  } else {
+    // Frame case OR an invalid mux.
+    return WEBP_MUX_INVALID_ARGUMENT;
+  }
+}
+
+// Get the canvas width, height and flags after validating that VP8X/VP8/VP8L
+// chunk and canvas size are valid.
+static WebPMuxError MuxGetCanvasInfo(const WebPMux* const mux,
+                                     int* width, int* height, uint32_t* flags) {
+  int w, h;
+  uint32_t f = 0;
+  WebPData data;
+  assert(mux != NULL);
+
+  // Check if VP8X chunk is present.
+  if (MuxGet(mux, IDX_VP8X, 1, &data) == WEBP_MUX_OK) {
+    if (data.size < VP8X_CHUNK_SIZE) return WEBP_MUX_BAD_DATA;
+    f = GetLE32(data.bytes + 0);
+    w = GetLE24(data.bytes + 4) + 1;
+    h = GetLE24(data.bytes + 7) + 1;
+  } else {
+    const WebPMuxImage* const wpi = mux->images_;
+    // Grab user-forced canvas size as default.
+    w = mux->canvas_width_;
+    h = mux->canvas_height_;
+    if (w == 0 && h == 0 && ValidateForSingleImage(mux) == WEBP_MUX_OK) {
+      // single image and not forced canvas size => use dimension of first frame
+      assert(wpi != NULL);
+      w = wpi->width_;
+      h = wpi->height_;
+    }
+    if (wpi != NULL) {
+      if (wpi->has_alpha_) f |= ALPHA_FLAG;
+    }
+  }
+  if (w * (uint64_t)h >= MAX_IMAGE_AREA) return WEBP_MUX_BAD_DATA;
+
+  if (width != NULL) *width = w;
+  if (height != NULL) *height = h;
+  if (flags != NULL) *flags = f;
+  return WEBP_MUX_OK;
+}
+
+WebPMuxError WebPMuxGetCanvasSize(const WebPMux* mux, int* width, int* height) {
+  if (mux == NULL || width == NULL || height == NULL) {
+    return WEBP_MUX_INVALID_ARGUMENT;
+  }
+  return MuxGetCanvasInfo(mux, width, height, NULL);
+}
+
+WebPMuxError WebPMuxGetFeatures(const WebPMux* mux, uint32_t* flags) {
+  if (mux == NULL || flags == NULL) return WEBP_MUX_INVALID_ARGUMENT;
+  return MuxGetCanvasInfo(mux, NULL, NULL, flags);
+}
+
+static uint8_t* EmitVP8XChunk(uint8_t* const dst, int width,
+                              int height, uint32_t flags) {
+  const size_t vp8x_size = CHUNK_HEADER_SIZE + VP8X_CHUNK_SIZE;
+  assert(width >= 1 && height >= 1);
+  assert(width <= MAX_CANVAS_SIZE && height <= MAX_CANVAS_SIZE);
+  assert(width * (uint64_t)height < MAX_IMAGE_AREA);
+  PutLE32(dst, MKFOURCC('V', 'P', '8', 'X'));
+  PutLE32(dst + TAG_SIZE, VP8X_CHUNK_SIZE);
+  PutLE32(dst + CHUNK_HEADER_SIZE, flags);
+  PutLE24(dst + CHUNK_HEADER_SIZE + 4, width - 1);
+  PutLE24(dst + CHUNK_HEADER_SIZE + 7, height - 1);
+  return dst + vp8x_size;
+}
+
+// Assemble a single image WebP bitstream from 'wpi'.
+static WebPMuxError SynthesizeBitstream(const WebPMuxImage* const wpi,
+                                        WebPData* const bitstream) {
+  uint8_t* dst;
+
+  // Allocate data.
+  const int need_vp8x = (wpi->alpha_ != NULL);
+  const size_t vp8x_size = need_vp8x ? CHUNK_HEADER_SIZE + VP8X_CHUNK_SIZE : 0;
+  const size_t alpha_size = need_vp8x ? ChunkDiskSize(wpi->alpha_) : 0;
+  // Note: No need to output ANMF chunk for a single image.
+  const size_t size = RIFF_HEADER_SIZE + vp8x_size + alpha_size +
+                      ChunkDiskSize(wpi->img_);
+  uint8_t* const data = (uint8_t*)WebPSafeMalloc(1ULL, size);
+  if (data == NULL) return WEBP_MUX_MEMORY_ERROR;
+
+  // Main RIFF header.
+  dst = MuxEmitRiffHeader(data, size);
+
+  if (need_vp8x) {
+    dst = EmitVP8XChunk(dst, wpi->width_, wpi->height_, ALPHA_FLAG);  // VP8X.
+    dst = ChunkListEmit(wpi->alpha_, dst);       // ALPH.
+  }
+
+  // Bitstream.
+  dst = ChunkListEmit(wpi->img_, dst);
+  assert(dst == data + size);
+
+  // Output.
+  bitstream->bytes = data;
+  bitstream->size = size;
+  return WEBP_MUX_OK;
+}
+
+WebPMuxError WebPMuxGetChunk(const WebPMux* mux, const char fourcc[4],
+                             WebPData* chunk_data) {
+  CHUNK_INDEX idx;
+  if (mux == NULL || fourcc == NULL || chunk_data == NULL) {
+    return WEBP_MUX_INVALID_ARGUMENT;
+  }
+  idx = ChunkGetIndexFromFourCC(fourcc);
+  if (IsWPI(kChunks[idx].id)) {     // An image chunk.
+    return WEBP_MUX_INVALID_ARGUMENT;
+  } else if (idx != IDX_UNKNOWN) {  // A known chunk type.
+    return MuxGet(mux, idx, 1, chunk_data);
+  } else {                          // An unknown chunk type.
+    const WebPChunk* const chunk =
+        ChunkSearchList(mux->unknown_, 1, ChunkGetTagFromFourCC(fourcc));
+    if (chunk == NULL) return WEBP_MUX_NOT_FOUND;
+    *chunk_data = chunk->data_;
+    return WEBP_MUX_OK;
+  }
+}
+
+static WebPMuxError MuxGetImageInternal(const WebPMuxImage* const wpi,
+                                        WebPMuxFrameInfo* const info) {
+  // Set some defaults for unrelated fields.
+  info->x_offset = 0;
+  info->y_offset = 0;
+  info->duration = 1;
+  info->dispose_method = WEBP_MUX_DISPOSE_NONE;
+  info->blend_method = WEBP_MUX_BLEND;
+  // Extract data for related fields.
+  info->id = ChunkGetIdFromTag(wpi->img_->tag_);
+  return SynthesizeBitstream(wpi, &info->bitstream);
+}
+
+static WebPMuxError MuxGetFrameInternal(const WebPMuxImage* const wpi,
+                                        WebPMuxFrameInfo* const frame) {
+  const int is_frame = (wpi->header_->tag_ == kChunks[IDX_ANMF].tag);
+  const WebPData* frame_data;
+  if (!is_frame) return WEBP_MUX_INVALID_ARGUMENT;
+  assert(wpi->header_ != NULL);  // Already checked by WebPMuxGetFrame().
+  // Get frame chunk.
+  frame_data = &wpi->header_->data_;
+  if (frame_data->size < kChunks[IDX_ANMF].size) return WEBP_MUX_BAD_DATA;
+  // Extract info.
+  frame->x_offset = 2 * GetLE24(frame_data->bytes + 0);
+  frame->y_offset = 2 * GetLE24(frame_data->bytes + 3);
+  {
+    const uint8_t bits = frame_data->bytes[15];
+    frame->duration = GetLE24(frame_data->bytes + 12);
+    frame->dispose_method =
+        (bits & 1) ? WEBP_MUX_DISPOSE_BACKGROUND : WEBP_MUX_DISPOSE_NONE;
+    frame->blend_method = (bits & 2) ? WEBP_MUX_NO_BLEND : WEBP_MUX_BLEND;
+  }
+  frame->id = ChunkGetIdFromTag(wpi->header_->tag_);
+  return SynthesizeBitstream(wpi, &frame->bitstream);
+}
+
+WebPMuxError WebPMuxGetFrame(
+    const WebPMux* mux, uint32_t nth, WebPMuxFrameInfo* frame) {
+  WebPMuxError err;
+  WebPMuxImage* wpi;
+
+  // Sanity checks.
+  if (mux == NULL || frame == NULL) {
+    return WEBP_MUX_INVALID_ARGUMENT;
+  }
+
+  // Get the nth WebPMuxImage.
+  err = MuxImageGetNth((const WebPMuxImage**)&mux->images_, nth, &wpi);
+  if (err != WEBP_MUX_OK) return err;
+
+  // Get frame info.
+  if (wpi->header_ == NULL) {
+    return MuxGetImageInternal(wpi, frame);
+  } else {
+    return MuxGetFrameInternal(wpi, frame);
+  }
+}
+
+WebPMuxError WebPMuxGetAnimationParams(const WebPMux* mux,
+                                       WebPMuxAnimParams* params) {
+  WebPData anim;
+  WebPMuxError err;
+
+  if (mux == NULL || params == NULL) return WEBP_MUX_INVALID_ARGUMENT;
+
+  err = MuxGet(mux, IDX_ANIM, 1, &anim);
+  if (err != WEBP_MUX_OK) return err;
+  if (anim.size < kChunks[WEBP_CHUNK_ANIM].size) return WEBP_MUX_BAD_DATA;
+  params->bgcolor = GetLE32(anim.bytes);
+  params->loop_count = GetLE16(anim.bytes + 4);
+
+  return WEBP_MUX_OK;
+}
+
+// Get chunk index from chunk id. Returns IDX_NIL if not found.
+static CHUNK_INDEX ChunkGetIndexFromId(WebPChunkId id) {
+  int i;
+  for (i = 0; kChunks[i].id != WEBP_CHUNK_NIL; ++i) {
+    if (id == kChunks[i].id) return (CHUNK_INDEX)i;
+  }
+  return IDX_NIL;
+}
+
+// Count number of chunks matching 'tag' in the 'chunk_list'.
+// If tag == NIL_TAG, any tag will be matched.
+static int CountChunks(const WebPChunk* const chunk_list, uint32_t tag) {
+  int count = 0;
+  const WebPChunk* current;
+  for (current = chunk_list; current != NULL; current = current->next_) {
+    if (tag == NIL_TAG || current->tag_ == tag) {
+      count++;  // Count chunks whose tags match.
+    }
+  }
+  return count;
+}
+
+WebPMuxError WebPMuxNumChunks(const WebPMux* mux,
+                              WebPChunkId id, int* num_elements) {
+  if (mux == NULL || num_elements == NULL) {
+    return WEBP_MUX_INVALID_ARGUMENT;
+  }
+
+  if (IsWPI(id)) {
+    *num_elements = MuxImageCount(mux->images_, id);
+  } else {
+    WebPChunk* const* chunk_list = MuxGetChunkListFromId(mux, id);
+    const CHUNK_INDEX idx = ChunkGetIndexFromId(id);
+    *num_elements = CountChunks(*chunk_list, kChunks[idx].tag);
+  }
+
+  return WEBP_MUX_OK;
+}
+
+//------------------------------------------------------------------------------
diff --git a/src/webp/mux.h b/src/webp/mux.h
new file mode 100644
index 0000000..daccc65
--- /dev/null
+++ b/src/webp/mux.h
@@ -0,0 +1,530 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the COPYING file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+// -----------------------------------------------------------------------------
+//
+//  RIFF container manipulation and encoding for WebP images.
+//
+// Authors: Urvang (urvang@google.com)
+//          Vikas (vikasa@google.com)
+
+#ifndef WEBP_WEBP_MUX_H_
+#define WEBP_WEBP_MUX_H_
+
+#include "./mux_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define WEBP_MUX_ABI_VERSION 0x0108        // MAJOR(8b) + MINOR(8b)
+
+//------------------------------------------------------------------------------
+// Mux API
+//
+// This API allows manipulation of WebP container images containing features
+// like color profile, metadata, animation.
+//
+// Code Example#1: Create a WebPMux object with image data, color profile and
+// XMP metadata.
+/*
+  int copy_data = 0;
+  WebPMux* mux = WebPMuxNew();
+  // ... (Prepare image data).
+  WebPMuxSetImage(mux, &image, copy_data);
+  // ... (Prepare ICCP color profile data).
+  WebPMuxSetChunk(mux, "ICCP", &icc_profile, copy_data);
+  // ... (Prepare XMP metadata).
+  WebPMuxSetChunk(mux, "XMP ", &xmp, copy_data);
+  // Get data from mux in WebP RIFF format.
+  WebPMuxAssemble(mux, &output_data);
+  WebPMuxDelete(mux);
+  // ... (Consume output_data; e.g. write output_data.bytes to file).
+  WebPDataClear(&output_data);
+*/
+
+// Code Example#2: Get image and color profile data from a WebP file.
+/*
+  int copy_data = 0;
+  // ... (Read data from file).
+  WebPMux* mux = WebPMuxCreate(&data, copy_data);
+  WebPMuxGetFrame(mux, 1, &image);
+  // ... (Consume image; e.g. call WebPDecode() to decode the data).
+  WebPMuxGetChunk(mux, "ICCP", &icc_profile);
+  // ... (Consume icc_data).
+  WebPMuxDelete(mux);
+  free(data);
+*/
+
+// Note: forward declaring enumerations is not allowed in (strict) C and C++,
+// the types are left here for reference.
+// typedef enum WebPMuxError WebPMuxError;
+// typedef enum WebPChunkId WebPChunkId;
+typedef struct WebPMux WebPMux;   // main opaque object.
+typedef struct WebPMuxFrameInfo WebPMuxFrameInfo;
+typedef struct WebPMuxAnimParams WebPMuxAnimParams;
+typedef struct WebPAnimEncoderOptions WebPAnimEncoderOptions;
+
+// Error codes
+typedef enum WebPMuxError {
+  WEBP_MUX_OK                 =  1,
+  WEBP_MUX_NOT_FOUND          =  0,
+  WEBP_MUX_INVALID_ARGUMENT   = -1,
+  WEBP_MUX_BAD_DATA           = -2,
+  WEBP_MUX_MEMORY_ERROR       = -3,
+  WEBP_MUX_NOT_ENOUGH_DATA    = -4
+} WebPMuxError;
+
+// IDs for different types of chunks.
+typedef enum WebPChunkId {
+  WEBP_CHUNK_VP8X,        // VP8X
+  WEBP_CHUNK_ICCP,        // ICCP
+  WEBP_CHUNK_ANIM,        // ANIM
+  WEBP_CHUNK_ANMF,        // ANMF
+  WEBP_CHUNK_DEPRECATED,  // (deprecated from FRGM)
+  WEBP_CHUNK_ALPHA,       // ALPH
+  WEBP_CHUNK_IMAGE,       // VP8/VP8L
+  WEBP_CHUNK_EXIF,        // EXIF
+  WEBP_CHUNK_XMP,         // XMP
+  WEBP_CHUNK_UNKNOWN,     // Other chunks.
+  WEBP_CHUNK_NIL
+} WebPChunkId;
+
+//------------------------------------------------------------------------------
+
+// Returns the version number of the mux library, packed in hexadecimal using
+// 8bits for each of major/minor/revision. E.g: v2.5.7 is 0x020507.
+WEBP_EXTERN(int) WebPGetMuxVersion(void);
+
+//------------------------------------------------------------------------------
+// Life of a Mux object
+
+// Internal, version-checked, entry point
+WEBP_EXTERN(WebPMux*) WebPNewInternal(int);
+
+// Creates an empty mux object.
+// Returns:
+//   A pointer to the newly created empty mux object.
+//   Or NULL in case of memory error.
+static WEBP_INLINE WebPMux* WebPMuxNew(void) {
+  return WebPNewInternal(WEBP_MUX_ABI_VERSION);
+}
+
+// Deletes the mux object.
+// Parameters:
+//   mux - (in/out) object to be deleted
+WEBP_EXTERN(void) WebPMuxDelete(WebPMux* mux);
+
+//------------------------------------------------------------------------------
+// Mux creation.
+
+// Internal, version-checked, entry point
+WEBP_EXTERN(WebPMux*) WebPMuxCreateInternal(const WebPData*, int, int);
+
+// Creates a mux object from raw data given in WebP RIFF format.
+// Parameters:
+//   bitstream - (in) the bitstream data in WebP RIFF format
+//   copy_data - (in) value 1 indicates given data WILL be copied to the mux
+//               object and value 0 indicates data will NOT be copied.
+// Returns:
+//   A pointer to the mux object created from given data - on success.
+//   NULL - In case of invalid data or memory error.
+static WEBP_INLINE WebPMux* WebPMuxCreate(const WebPData* bitstream,
+                                          int copy_data) {
+  return WebPMuxCreateInternal(bitstream, copy_data, WEBP_MUX_ABI_VERSION);
+}
+
+//------------------------------------------------------------------------------
+// Non-image chunks.
+
+// Note: Only non-image related chunks should be managed through chunk APIs.
+// (Image related chunks are: "ANMF", "VP8 ", "VP8L" and "ALPH").
+// To add, get and delete images, use WebPMuxSetImage(), WebPMuxPushFrame(),
+// WebPMuxGetFrame() and WebPMuxDeleteFrame().
+
+// Adds a chunk with id 'fourcc' and data 'chunk_data' in the mux object.
+// Any existing chunk(s) with the same id will be removed.
+// Parameters:
+//   mux - (in/out) object to which the chunk is to be added
+//   fourcc - (in) a character array containing the fourcc of the given chunk;
+//                 e.g., "ICCP", "XMP ", "EXIF" etc.
+//   chunk_data - (in) the chunk data to be added
+//   copy_data - (in) value 1 indicates given data WILL be copied to the mux
+//               object and value 0 indicates data will NOT be copied.
+// Returns:
+//   WEBP_MUX_INVALID_ARGUMENT - if mux, fourcc or chunk_data is NULL
+//                               or if fourcc corresponds to an image chunk.
+//   WEBP_MUX_MEMORY_ERROR - on memory allocation error.
+//   WEBP_MUX_OK - on success.
+WEBP_EXTERN(WebPMuxError) WebPMuxSetChunk(
+    WebPMux* mux, const char fourcc[4], const WebPData* chunk_data,
+    int copy_data);
+
+// Gets a reference to the data of the chunk with id 'fourcc' in the mux object.
+// The caller should NOT free the returned data.
+// Parameters:
+//   mux - (in) object from which the chunk data is to be fetched
+//   fourcc - (in) a character array containing the fourcc of the chunk;
+//                 e.g., "ICCP", "XMP ", "EXIF" etc.
+//   chunk_data - (out) returned chunk data
+// Returns:
+//   WEBP_MUX_INVALID_ARGUMENT - if mux, fourcc or chunk_data is NULL
+//                               or if fourcc corresponds to an image chunk.
+//   WEBP_MUX_NOT_FOUND - If mux does not contain a chunk with the given id.
+//   WEBP_MUX_OK - on success.
+WEBP_EXTERN(WebPMuxError) WebPMuxGetChunk(
+    const WebPMux* mux, const char fourcc[4], WebPData* chunk_data);
+
+// Deletes the chunk with the given 'fourcc' from the mux object.
+// Parameters:
+//   mux - (in/out) object from which the chunk is to be deleted
+//   fourcc - (in) a character array containing the fourcc of the chunk;
+//                 e.g., "ICCP", "XMP ", "EXIF" etc.
+// Returns:
+//   WEBP_MUX_INVALID_ARGUMENT - if mux or fourcc is NULL
+//                               or if fourcc corresponds to an image chunk.
+//   WEBP_MUX_NOT_FOUND - If mux does not contain a chunk with the given fourcc.
+//   WEBP_MUX_OK - on success.
+WEBP_EXTERN(WebPMuxError) WebPMuxDeleteChunk(
+    WebPMux* mux, const char fourcc[4]);
+
+//------------------------------------------------------------------------------
+// Images.
+
+// Encapsulates data about a single frame.
+struct WebPMuxFrameInfo {
+  WebPData    bitstream;  // image data: can be a raw VP8/VP8L bitstream
+                          // or a single-image WebP file.
+  int         x_offset;   // x-offset of the frame.
+  int         y_offset;   // y-offset of the frame.
+  int         duration;   // duration of the frame (in milliseconds).
+
+  WebPChunkId id;         // frame type: should be one of WEBP_CHUNK_ANMF
+                          // or WEBP_CHUNK_IMAGE
+  WebPMuxAnimDispose dispose_method;  // Disposal method for the frame.
+  WebPMuxAnimBlend   blend_method;    // Blend operation for the frame.
+  uint32_t    pad[1];     // padding for later use
+};
+
+// Sets the (non-animated) image in the mux object.
+// Note: Any existing images (including frames) will be removed.
+// Parameters:
+//   mux - (in/out) object in which the image is to be set
+//   bitstream - (in) can be a raw VP8/VP8L bitstream or a single-image
+//               WebP file (non-animated)
+//   copy_data - (in) value 1 indicates given data WILL be copied to the mux
+//               object and value 0 indicates data will NOT be copied.
+// Returns:
+//   WEBP_MUX_INVALID_ARGUMENT - if mux is NULL or bitstream is NULL.
+//   WEBP_MUX_MEMORY_ERROR - on memory allocation error.
+//   WEBP_MUX_OK - on success.
+WEBP_EXTERN(WebPMuxError) WebPMuxSetImage(
+    WebPMux* mux, const WebPData* bitstream, int copy_data);
+
+// Adds a frame at the end of the mux object.
+// Notes: (1) frame.id should be WEBP_CHUNK_ANMF
+//        (2) For setting a non-animated image, use WebPMuxSetImage() instead.
+//        (3) Type of frame being pushed must be same as the frames in mux.
+//        (4) As WebP only supports even offsets, any odd offset will be snapped
+//            to an even location using: offset &= ~1
+// Parameters:
+//   mux - (in/out) object to which the frame is to be added
+//   frame - (in) frame data.
+//   copy_data - (in) value 1 indicates given data WILL be copied to the mux
+//               object and value 0 indicates data will NOT be copied.
+// Returns:
+//   WEBP_MUX_INVALID_ARGUMENT - if mux or frame is NULL
+//                               or if content of 'frame' is invalid.
+//   WEBP_MUX_MEMORY_ERROR - on memory allocation error.
+//   WEBP_MUX_OK - on success.
+WEBP_EXTERN(WebPMuxError) WebPMuxPushFrame(
+    WebPMux* mux, const WebPMuxFrameInfo* frame, int copy_data);
+
+// Gets the nth frame from the mux object.
+// The content of 'frame->bitstream' is allocated using malloc(), and NOT
+// owned by the 'mux' object. It MUST be deallocated by the caller by calling
+// WebPDataClear().
+// nth=0 has a special meaning - last position.
+// Parameters:
+//   mux - (in) object from which the info is to be fetched
+//   nth - (in) index of the frame in the mux object
+//   frame - (out) data of the returned frame
+// Returns:
+//   WEBP_MUX_INVALID_ARGUMENT - if mux or frame is NULL.
+//   WEBP_MUX_NOT_FOUND - if there are less than nth frames in the mux object.
+//   WEBP_MUX_BAD_DATA - if nth frame chunk in mux is invalid.
+//   WEBP_MUX_MEMORY_ERROR - on memory allocation error.
+//   WEBP_MUX_OK - on success.
+WEBP_EXTERN(WebPMuxError) WebPMuxGetFrame(
+    const WebPMux* mux, uint32_t nth, WebPMuxFrameInfo* frame);
+
+// Deletes a frame from the mux object.
+// nth=0 has a special meaning - last position.
+// Parameters:
+//   mux - (in/out) object from which a frame is to be deleted
+//   nth - (in) The position from which the frame is to be deleted
+// Returns:
+//   WEBP_MUX_INVALID_ARGUMENT - if mux is NULL.
+//   WEBP_MUX_NOT_FOUND - If there are less than nth frames in the mux object
+//                        before deletion.
+//   WEBP_MUX_OK - on success.
+WEBP_EXTERN(WebPMuxError) WebPMuxDeleteFrame(WebPMux* mux, uint32_t nth);
+
+//------------------------------------------------------------------------------
+// Animation.
+
+// Animation parameters.
+struct WebPMuxAnimParams {
+  uint32_t bgcolor;  // Background color of the canvas stored (in MSB order) as:
+                     // Bits 00 to 07: Alpha.
+                     // Bits 08 to 15: Red.
+                     // Bits 16 to 23: Green.
+                     // Bits 24 to 31: Blue.
+  int loop_count;    // Number of times to repeat the animation [0 = infinite].
+};
+
+// Sets the animation parameters in the mux object. Any existing ANIM chunks
+// will be removed.
+// Parameters:
+//   mux - (in/out) object in which ANIM chunk is to be set/added
+//   params - (in) animation parameters.
+// Returns:
+//   WEBP_MUX_INVALID_ARGUMENT - if mux or params is NULL.
+//   WEBP_MUX_MEMORY_ERROR - on memory allocation error.
+//   WEBP_MUX_OK - on success.
+WEBP_EXTERN(WebPMuxError) WebPMuxSetAnimationParams(
+    WebPMux* mux, const WebPMuxAnimParams* params);
+
+// Gets the animation parameters from the mux object.
+// Parameters:
+//   mux - (in) object from which the animation parameters to be fetched
+//   params - (out) animation parameters extracted from the ANIM chunk
+// Returns:
+//   WEBP_MUX_INVALID_ARGUMENT - if mux or params is NULL.
+//   WEBP_MUX_NOT_FOUND - if ANIM chunk is not present in mux object.
+//   WEBP_MUX_OK - on success.
+WEBP_EXTERN(WebPMuxError) WebPMuxGetAnimationParams(
+    const WebPMux* mux, WebPMuxAnimParams* params);
+
+//------------------------------------------------------------------------------
+// Misc Utilities.
+
+// Sets the canvas size for the mux object. The width and height can be
+// specified explicitly or left as zero (0, 0).
+// * When width and height are specified explicitly, then this frame bound is
+//   enforced during subsequent calls to WebPMuxAssemble() and an error is
+//   reported if any animated frame does not completely fit within the canvas.
+// * When unspecified (0, 0), the constructed canvas will get the frame bounds
+//   from the bounding-box over all frames after calling WebPMuxAssemble().
+// Parameters:
+//   mux - (in) object to which the canvas size is to be set
+//   width - (in) canvas width
+//   height - (in) canvas height
+// Returns:
+//   WEBP_MUX_INVALID_ARGUMENT - if mux is NULL; or
+//                               width or height are invalid or out of bounds
+//   WEBP_MUX_OK - on success.
+WEBP_EXTERN(WebPMuxError) WebPMuxSetCanvasSize(WebPMux* mux,
+                                               int width, int height);
+
+// Gets the canvas size from the mux object.
+// Note: This method assumes that the VP8X chunk, if present, is up-to-date.
+// That is, the mux object hasn't been modified since the last call to
+// WebPMuxAssemble() or WebPMuxCreate().
+// Parameters:
+//   mux - (in) object from which the canvas size is to be fetched
+//   width - (out) canvas width
+//   height - (out) canvas height
+// Returns:
+//   WEBP_MUX_INVALID_ARGUMENT - if mux, width or height is NULL.
+//   WEBP_MUX_BAD_DATA - if VP8X/VP8/VP8L chunk or canvas size is invalid.
+//   WEBP_MUX_OK - on success.
+WEBP_EXTERN(WebPMuxError) WebPMuxGetCanvasSize(const WebPMux* mux,
+                                               int* width, int* height);
+
+// Gets the feature flags from the mux object.
+// Note: This method assumes that the VP8X chunk, if present, is up-to-date.
+// That is, the mux object hasn't been modified since the last call to
+// WebPMuxAssemble() or WebPMuxCreate().
+// Parameters:
+//   mux - (in) object from which the features are to be fetched
+//   flags - (out) the flags specifying which features are present in the
+//           mux object. This will be an OR of various flag values.
+//           Enum 'WebPFeatureFlags' can be used to test individual flag values.
+// Returns:
+//   WEBP_MUX_INVALID_ARGUMENT - if mux or flags is NULL.
+//   WEBP_MUX_BAD_DATA - if VP8X/VP8/VP8L chunk or canvas size is invalid.
+//   WEBP_MUX_OK - on success.
+WEBP_EXTERN(WebPMuxError) WebPMuxGetFeatures(const WebPMux* mux,
+                                             uint32_t* flags);
+
+// Gets number of chunks with the given 'id' in the mux object.
+// Parameters:
+//   mux - (in) object from which the info is to be fetched
+//   id - (in) chunk id specifying the type of chunk
+//   num_elements - (out) number of chunks with the given chunk id
+// Returns:
+//   WEBP_MUX_INVALID_ARGUMENT - if mux, or num_elements is NULL.
+//   WEBP_MUX_OK - on success.
+WEBP_EXTERN(WebPMuxError) WebPMuxNumChunks(const WebPMux* mux,
+                                           WebPChunkId id, int* num_elements);
+
+// Assembles all chunks in WebP RIFF format and returns in 'assembled_data'.
+// This function also validates the mux object.
+// Note: The content of 'assembled_data' will be ignored and overwritten.
+// Also, the content of 'assembled_data' is allocated using malloc(), and NOT
+// owned by the 'mux' object. It MUST be deallocated by the caller by calling
+// WebPDataClear(). It's always safe to call WebPDataClear() upon return,
+// even in case of error.
+// Parameters:
+//   mux - (in/out) object whose chunks are to be assembled
+//   assembled_data - (out) assembled WebP data
+// Returns:
+//   WEBP_MUX_BAD_DATA - if mux object is invalid.
+//   WEBP_MUX_INVALID_ARGUMENT - if mux or assembled_data is NULL.
+//   WEBP_MUX_MEMORY_ERROR - on memory allocation error.
+//   WEBP_MUX_OK - on success.
+WEBP_EXTERN(WebPMuxError) WebPMuxAssemble(WebPMux* mux,
+                                          WebPData* assembled_data);
+
+//------------------------------------------------------------------------------
+// WebPAnimEncoder API
+//
+// This API allows encoding (possibly) animated WebP images.
+//
+// Code Example:
+/*
+  WebPAnimEncoderOptions enc_options;
+  WebPAnimEncoderOptionsInit(&enc_options);
+  // Tune 'enc_options' as needed.
+  WebPAnimEncoder* enc = WebPAnimEncoderNew(width, height, &enc_options);
+  while(<there are more frames>) {
+    WebPConfig config;
+    WebPConfigInit(&config);
+    // Tune 'config' as needed.
+    WebPAnimEncoderAdd(enc, frame, timestamp_ms, &config);
+  }
+  WebPAnimEncoderAdd(enc, NULL, timestamp_ms, NULL);
+  WebPAnimEncoderAssemble(enc, webp_data);
+  WebPAnimEncoderDelete(enc);
+  // Write the 'webp_data' to a file, or re-mux it further.
+*/
+
+typedef struct WebPAnimEncoder WebPAnimEncoder;  // Main opaque object.
+
+// Forward declarations. Defined in encode.h.
+struct WebPPicture;
+struct WebPConfig;
+
+// Global options.
+struct WebPAnimEncoderOptions {
+  WebPMuxAnimParams anim_params;  // Animation parameters.
+  int minimize_size;    // If true, minimize the output size (slow). Implicitly
+                        // disables key-frame insertion.
+  int kmin;
+  int kmax;             // Minimum and maximum distance between consecutive key
+                        // frames in the output. The library may insert some key
+                        // frames as needed to satisfy this criteria.
+                        // Note that these conditions should hold: kmax > kmin
+                        // and kmin >= kmax / 2 + 1. Also, if kmax <= 0, then
+                        // key-frame insertion is disabled; and if kmax == 1,
+                        // then all frames will be key-frames (kmin value does
+                        // not matter for these special cases).
+  int allow_mixed;      // If true, use mixed compression mode; may choose
+                        // either lossy and lossless for each frame.
+  int verbose;          // If true, print info and warning messages to stderr.
+
+  uint32_t padding[4];  // Padding for later use.
+};
+
+// Internal, version-checked, entry point.
+WEBP_EXTERN(int) WebPAnimEncoderOptionsInitInternal(
+    WebPAnimEncoderOptions*, int);
+
+// Should always be called, to initialize a fresh WebPAnimEncoderOptions
+// structure before modification. Returns false in case of version mismatch.
+// WebPAnimEncoderOptionsInit() must have succeeded before using the
+// 'enc_options' object.
+static WEBP_INLINE int WebPAnimEncoderOptionsInit(
+    WebPAnimEncoderOptions* enc_options) {
+  return WebPAnimEncoderOptionsInitInternal(enc_options, WEBP_MUX_ABI_VERSION);
+}
+
+// Internal, version-checked, entry point.
+WEBP_EXTERN(WebPAnimEncoder*) WebPAnimEncoderNewInternal(
+    int, int, const WebPAnimEncoderOptions*, int);
+
+// Creates and initializes a WebPAnimEncoder object.
+// Parameters:
+//   width/height - (in) canvas width and height of the animation.
+//   enc_options - (in) encoding options; can be passed NULL to pick
+//                      reasonable defaults.
+// Returns:
+//   A pointer to the newly created WebPAnimEncoder object.
+//   Or NULL in case of memory error.
+static WEBP_INLINE WebPAnimEncoder* WebPAnimEncoderNew(
+    int width, int height, const WebPAnimEncoderOptions* enc_options) {
+  return WebPAnimEncoderNewInternal(width, height, enc_options,
+                                    WEBP_MUX_ABI_VERSION);
+}
+
+// Optimize the given frame for WebP, encode it and add it to the
+// WebPAnimEncoder object.
+// The last call to 'WebPAnimEncoderAdd' should be with frame = NULL, which
+// indicates that no more frames are to be added. This call is also used to
+// determine the duration of the last frame.
+// Parameters:
+//   enc - (in/out) object to which the frame is to be added.
+//   frame - (in/out) frame data in ARGB or YUV(A) format. If it is in YUV(A)
+//           format, it will be converted to ARGB, which incurs a small loss.
+//   timestamp_ms - (in) timestamp of this frame in milliseconds.
+//                       Duration of a frame would be calculated as
+//                       "timestamp of next frame - timestamp of this frame".
+//                       Hence, timestamps should be in non-decreasing order.
+//   config - (in) encoding options; can be passed NULL to pick
+//            reasonable defaults.
+// Returns:
+//   On error, returns false and frame->error_code is set appropriately.
+//   Otherwise, returns true.
+WEBP_EXTERN(int) WebPAnimEncoderAdd(
+    WebPAnimEncoder* enc, struct WebPPicture* frame, int timestamp_ms,
+    const struct WebPConfig* config);
+
+// Assemble all frames added so far into a WebP bitstream.
+// This call should be preceded by  a call to 'WebPAnimEncoderAdd' with
+// frame = NULL; if not, the duration of the last frame will be internally
+// estimated.
+// Parameters:
+//   enc - (in/out) object from which the frames are to be assembled.
+//   webp_data - (out) generated WebP bitstream.
+// Returns:
+//   True on success.
+WEBP_EXTERN(int) WebPAnimEncoderAssemble(WebPAnimEncoder* enc,
+                                         WebPData* webp_data);
+
+// Get error string corresponding to the most recent call using 'enc'. The
+// returned string is owned by 'enc' and is valid only until the next call to
+// WebPAnimEncoderAdd() or WebPAnimEncoderAssemble() or WebPAnimEncoderDelete().
+// Parameters:
+//   enc - (in/out) object from which the error string is to be fetched.
+// Returns:
+//   NULL if 'enc' is NULL. Otherwise, returns the error string if the last call
+//   to 'enc' had an error, or an empty string if the last call was a success.
+WEBP_EXTERN(const char*) WebPAnimEncoderGetError(WebPAnimEncoder* enc);
+
+// Deletes the WebPAnimEncoder object.
+// Parameters:
+//   enc - (in/out) object to be deleted
+WEBP_EXTERN(void) WebPAnimEncoderDelete(WebPAnimEncoder* enc);
+
+//------------------------------------------------------------------------------
+
+#ifdef __cplusplus
+}    // extern "C"
+#endif
+
+#endif  /* WEBP_WEBP_MUX_H_ */