Home | History | Annotate | Download | only in mux
      1 // Copyright 2011 Google Inc. All Rights Reserved.
      2 //
      3 // Use of this source code is governed by a BSD-style license
      4 // that can be found in the COPYING file in the root of the source
      5 // tree. An additional intellectual property rights grant can be found
      6 // in the file PATENTS. All contributing project authors may
      7 // be found in the AUTHORS file in the root of the source tree.
      8 // -----------------------------------------------------------------------------
      9 //
     10 // Set and delete APIs for mux.
     11 //
     12 // Authors: Urvang (urvang (at) google.com)
     13 //          Vikas (vikasa (at) google.com)
     14 
     15 #include <assert.h>
     16 #include "./muxi.h"
     17 #include "../utils/utils.h"
     18 
     19 #if defined(__cplusplus) || defined(c_plusplus)
     20 extern "C" {
     21 #endif
     22 
     23 //------------------------------------------------------------------------------
     24 // Life of a mux object.
     25 
     26 static void MuxInit(WebPMux* const mux) {
     27   if (mux == NULL) return;
     28   memset(mux, 0, sizeof(*mux));
     29 }
     30 
     31 WebPMux* WebPNewInternal(int version) {
     32   if (WEBP_ABI_IS_INCOMPATIBLE(version, WEBP_MUX_ABI_VERSION)) {
     33     return NULL;
     34   } else {
     35     WebPMux* const mux = (WebPMux*)malloc(sizeof(WebPMux));
     36     // If mux is NULL MuxInit is a noop.
     37     MuxInit(mux);
     38     return mux;
     39   }
     40 }
     41 
     42 static void DeleteAllChunks(WebPChunk** const chunk_list) {
     43   while (*chunk_list) {
     44     *chunk_list = ChunkDelete(*chunk_list);
     45   }
     46 }
     47 
     48 static void MuxRelease(WebPMux* const mux) {
     49   if (mux == NULL) return;
     50   MuxImageDeleteAll(&mux->images_);
     51   DeleteAllChunks(&mux->vp8x_);
     52   DeleteAllChunks(&mux->iccp_);
     53   DeleteAllChunks(&mux->anim_);
     54   DeleteAllChunks(&mux->exif_);
     55   DeleteAllChunks(&mux->xmp_);
     56   DeleteAllChunks(&mux->unknown_);
     57 }
     58 
     59 void WebPMuxDelete(WebPMux* mux) {
     60   // If mux is NULL MuxRelease is a noop.
     61   MuxRelease(mux);
     62   free(mux);
     63 }
     64 
     65 //------------------------------------------------------------------------------
     66 // Helper method(s).
     67 
     68 // Handy MACRO, makes MuxSet() very symmetric to MuxGet().
     69 #define SWITCH_ID_LIST(INDEX, LIST)                                            \
     70   if (idx == (INDEX)) {                                                        \
     71     err = ChunkAssignData(&chunk, data, copy_data, kChunks[(INDEX)].tag);      \
     72     if (err == WEBP_MUX_OK) {                                                  \
     73       err = ChunkSetNth(&chunk, (LIST), nth);                                  \
     74     }                                                                          \
     75     return err;                                                                \
     76   }
     77 
     78 static WebPMuxError MuxSet(WebPMux* const mux, CHUNK_INDEX idx, uint32_t nth,
     79                            const WebPData* const data, int copy_data) {
     80   WebPChunk chunk;
     81   WebPMuxError err = WEBP_MUX_NOT_FOUND;
     82   assert(mux != NULL);
     83   assert(!IsWPI(kChunks[idx].id));
     84 
     85   ChunkInit(&chunk);
     86   SWITCH_ID_LIST(IDX_VP8X, &mux->vp8x_);
     87   SWITCH_ID_LIST(IDX_ICCP, &mux->iccp_);
     88   SWITCH_ID_LIST(IDX_ANIM, &mux->anim_);
     89   SWITCH_ID_LIST(IDX_EXIF, &mux->exif_);
     90   SWITCH_ID_LIST(IDX_XMP,  &mux->xmp_);
     91   if (idx == IDX_UNKNOWN && data->size > TAG_SIZE) {
     92     // For raw-data unknown chunk, the first four bytes should be the tag to be
     93     // used for the chunk.
     94     const WebPData tmp = { data->bytes + TAG_SIZE, data->size - TAG_SIZE };
     95     err = ChunkAssignData(&chunk, &tmp, copy_data, GetLE32(data->bytes + 0));
     96     if (err == WEBP_MUX_OK)
     97       err = ChunkSetNth(&chunk, &mux->unknown_, nth);
     98   }
     99   return err;
    100 }
    101 #undef SWITCH_ID_LIST
    102 
    103 static WebPMuxError MuxAddChunk(WebPMux* const mux, uint32_t nth, uint32_t tag,
    104                                 const uint8_t* data, size_t size,
    105                                 int copy_data) {
    106   const CHUNK_INDEX idx = ChunkGetIndexFromTag(tag);
    107   const WebPData chunk_data = { data, size };
    108   assert(mux != NULL);
    109   assert(size <= MAX_CHUNK_PAYLOAD);
    110   assert(idx != IDX_NIL);
    111   return MuxSet(mux, idx, nth, &chunk_data, copy_data);
    112 }
    113 
    114 // Create data for frame/fragment given image data, offsets and duration.
    115 static WebPMuxError CreateFrameFragmentData(
    116     const WebPData* const image, int x_offset, int y_offset, int duration,
    117     WebPMuxAnimDispose dispose_method, int is_lossless, int is_frame,
    118     WebPData* const frame_frgm) {
    119   int width;
    120   int height;
    121   uint8_t* frame_frgm_bytes;
    122   const size_t frame_frgm_size = kChunks[is_frame ? IDX_ANMF : IDX_FRGM].size;
    123 
    124   const int ok = is_lossless ?
    125       VP8LGetInfo(image->bytes, image->size, &width, &height, NULL) :
    126       VP8GetInfo(image->bytes, image->size, image->size, &width, &height);
    127   if (!ok) return WEBP_MUX_INVALID_ARGUMENT;
    128 
    129   assert(width > 0 && height > 0 && duration >= 0);
    130   assert(dispose_method == (dispose_method & 1));
    131   // Note: assertion on upper bounds is done in PutLE24().
    132 
    133   frame_frgm_bytes = (uint8_t*)malloc(frame_frgm_size);
    134   if (frame_frgm_bytes == NULL) return WEBP_MUX_MEMORY_ERROR;
    135 
    136   PutLE24(frame_frgm_bytes + 0, x_offset / 2);
    137   PutLE24(frame_frgm_bytes + 3, y_offset / 2);
    138 
    139   if (is_frame) {
    140     PutLE24(frame_frgm_bytes + 6, width - 1);
    141     PutLE24(frame_frgm_bytes + 9, height - 1);
    142     PutLE24(frame_frgm_bytes + 12, duration);
    143     frame_frgm_bytes[15] = (dispose_method & 1);
    144   }
    145 
    146   frame_frgm->bytes = frame_frgm_bytes;
    147   frame_frgm->size = frame_frgm_size;
    148   return WEBP_MUX_OK;
    149 }
    150 
    151 // Outputs image data given a bitstream. The bitstream can either be a
    152 // single-image WebP file or raw VP8/VP8L data.
    153 // Also outputs 'is_lossless' to be true if the given bitstream is lossless.
    154 static WebPMuxError GetImageData(const WebPData* const bitstream,
    155                                  WebPData* const image, WebPData* const alpha,
    156                                  int* const is_lossless) {
    157   WebPDataInit(alpha);  // Default: no alpha.
    158   if (bitstream->size < TAG_SIZE ||
    159       memcmp(bitstream->bytes, "RIFF", TAG_SIZE)) {
    160     // It is NOT webp file data. Return input data as is.
    161     *image = *bitstream;
    162   } else {
    163     // It is webp file data. Extract image data from it.
    164     const WebPMuxImage* wpi;
    165     WebPMux* const mux = WebPMuxCreate(bitstream, 0);
    166     if (mux == NULL) return WEBP_MUX_BAD_DATA;
    167     wpi = mux->images_;
    168     assert(wpi != NULL && wpi->img_ != NULL);
    169     *image = wpi->img_->data_;
    170     if (wpi->alpha_ != NULL) {
    171       *alpha = wpi->alpha_->data_;
    172     }
    173     WebPMuxDelete(mux);
    174   }
    175   *is_lossless = VP8LCheckSignature(image->bytes, image->size);
    176   return WEBP_MUX_OK;
    177 }
    178 
    179 static WebPMuxError DeleteChunks(WebPChunk** chunk_list, uint32_t tag) {
    180   WebPMuxError err = WEBP_MUX_NOT_FOUND;
    181   assert(chunk_list);
    182   while (*chunk_list) {
    183     WebPChunk* const chunk = *chunk_list;
    184     if (chunk->tag_ == tag) {
    185       *chunk_list = ChunkDelete(chunk);
    186       err = WEBP_MUX_OK;
    187     } else {
    188       chunk_list = &chunk->next_;
    189     }
    190   }
    191   return err;
    192 }
    193 
    194 static WebPMuxError MuxDeleteAllNamedData(WebPMux* const mux, uint32_t tag) {
    195   const WebPChunkId id = ChunkGetIdFromTag(tag);
    196   WebPChunk** chunk_list;
    197 
    198   assert(mux != NULL);
    199   if (IsWPI(id)) return WEBP_MUX_INVALID_ARGUMENT;
    200 
    201   chunk_list = MuxGetChunkListFromId(mux, id);
    202   if (chunk_list == NULL) return WEBP_MUX_INVALID_ARGUMENT;
    203 
    204   return DeleteChunks(chunk_list, tag);
    205 }
    206 
    207 //------------------------------------------------------------------------------
    208 // Set API(s).
    209 
    210 WebPMuxError WebPMuxSetChunk(WebPMux* mux, const char fourcc[4],
    211                              const WebPData* chunk_data, int copy_data) {
    212   CHUNK_INDEX idx;
    213   uint32_t tag;
    214   WebPMuxError err;
    215   if (mux == NULL || fourcc == NULL || chunk_data == NULL ||
    216       chunk_data->bytes == NULL || chunk_data->size > MAX_CHUNK_PAYLOAD) {
    217     return WEBP_MUX_INVALID_ARGUMENT;
    218   }
    219   idx = ChunkGetIndexFromFourCC(fourcc);
    220   tag = ChunkGetTagFromFourCC(fourcc);
    221 
    222   // Delete existing chunk(s) with the same 'fourcc'.
    223   err = MuxDeleteAllNamedData(mux, tag);
    224   if (err != WEBP_MUX_OK && err != WEBP_MUX_NOT_FOUND) return err;
    225 
    226   // Add the given chunk.
    227   return MuxSet(mux, idx, 1, chunk_data, copy_data);
    228 }
    229 
    230 // Creates a chunk from given 'data' and sets it as 1st chunk in 'chunk_list'.
    231 static WebPMuxError AddDataToChunkList(
    232     const WebPData* const data, int copy_data, uint32_t tag,
    233     WebPChunk** chunk_list) {
    234   WebPChunk chunk;
    235   WebPMuxError err;
    236   ChunkInit(&chunk);
    237   err = ChunkAssignData(&chunk, data, copy_data, tag);
    238   if (err != WEBP_MUX_OK) goto Err;
    239   err = ChunkSetNth(&chunk, chunk_list, 1);
    240   if (err != WEBP_MUX_OK) goto Err;
    241   return WEBP_MUX_OK;
    242  Err:
    243   ChunkRelease(&chunk);
    244   return err;
    245 }
    246 
    247 // Extracts image & alpha data from the given bitstream and then sets wpi.alpha_
    248 // and wpi.img_ appropriately.
    249 static WebPMuxError SetAlphaAndImageChunks(
    250     const WebPData* const bitstream, int copy_data, WebPMuxImage* const wpi) {
    251   int is_lossless = 0;
    252   WebPData image, alpha;
    253   WebPMuxError err = GetImageData(bitstream, &image, &alpha, &is_lossless);
    254   const int image_tag =
    255       is_lossless ? kChunks[IDX_VP8L].tag : kChunks[IDX_VP8].tag;
    256   if (err != WEBP_MUX_OK) return err;
    257   if (alpha.bytes != NULL) {
    258     err = AddDataToChunkList(&alpha, copy_data, kChunks[IDX_ALPHA].tag,
    259                              &wpi->alpha_);
    260     if (err != WEBP_MUX_OK) return err;
    261   }
    262   return AddDataToChunkList(&image, copy_data, image_tag, &wpi->img_);
    263 }
    264 
    265 WebPMuxError WebPMuxSetImage(WebPMux* mux, const WebPData* bitstream,
    266                              int copy_data) {
    267   WebPMuxImage wpi;
    268   WebPMuxError err;
    269 
    270   // Sanity checks.
    271   if (mux == NULL || bitstream == NULL || bitstream->bytes == NULL ||
    272       bitstream->size > MAX_CHUNK_PAYLOAD) {
    273     return WEBP_MUX_INVALID_ARGUMENT;
    274   }
    275 
    276   if (mux->images_ != NULL) {
    277     // Only one 'simple image' can be added in mux. So, remove present images.
    278     MuxImageDeleteAll(&mux->images_);
    279   }
    280 
    281   MuxImageInit(&wpi);
    282   err = SetAlphaAndImageChunks(bitstream, copy_data, &wpi);
    283   if (err != WEBP_MUX_OK) goto Err;
    284 
    285   // Add this WebPMuxImage to mux.
    286   err = MuxImagePush(&wpi, &mux->images_);
    287   if (err != WEBP_MUX_OK) goto Err;
    288 
    289   // All is well.
    290   return WEBP_MUX_OK;
    291 
    292  Err:  // Something bad happened.
    293   MuxImageRelease(&wpi);
    294   return err;
    295 }
    296 
    297 WebPMuxError WebPMuxPushFrame(WebPMux* mux, const WebPMuxFrameInfo* frame,
    298                               int copy_data) {
    299   WebPMuxImage wpi;
    300   WebPMuxError err;
    301   int is_frame;
    302   const WebPData* const bitstream = &frame->bitstream;
    303 
    304   // Sanity checks.
    305   if (mux == NULL || frame == NULL) return WEBP_MUX_INVALID_ARGUMENT;
    306 
    307   is_frame = (frame->id == WEBP_CHUNK_ANMF);
    308   if (!(is_frame || (frame->id == WEBP_CHUNK_FRGM))) {
    309     return WEBP_MUX_INVALID_ARGUMENT;
    310   }
    311 #ifndef WEBP_EXPERIMENTAL_FEATURES
    312   if (frame->id == WEBP_CHUNK_FRGM) {     // disabled for now.
    313     return WEBP_MUX_INVALID_ARGUMENT;
    314   }
    315 #endif
    316 
    317   if (bitstream->bytes == NULL || bitstream->size > MAX_CHUNK_PAYLOAD) {
    318     return WEBP_MUX_INVALID_ARGUMENT;
    319   }
    320 
    321   if (mux->images_ != NULL) {
    322     const WebPMuxImage* const image = mux->images_;
    323     const uint32_t image_id = (image->header_ != NULL) ?
    324         ChunkGetIdFromTag(image->header_->tag_) : WEBP_CHUNK_IMAGE;
    325     if (image_id != frame->id) {
    326       return WEBP_MUX_INVALID_ARGUMENT;  // Conflicting frame types.
    327     }
    328   }
    329 
    330   MuxImageInit(&wpi);
    331   err = SetAlphaAndImageChunks(bitstream, copy_data, &wpi);
    332   if (err != WEBP_MUX_OK) goto Err;
    333   assert(wpi.img_ != NULL);  // As SetAlphaAndImageChunks() was successful.
    334 
    335   {
    336     const int is_lossless = (wpi.img_->tag_ == kChunks[IDX_VP8L].tag);
    337     const int x_offset = frame->x_offset & ~1;  // Snap offsets to even.
    338     const int y_offset = frame->y_offset & ~1;
    339     const int duration = is_frame ? frame->duration : 1 /* unused */;
    340     const WebPMuxAnimDispose dispose_method =
    341         is_frame ? frame->dispose_method : 0 /* unused */;
    342     const uint32_t tag = kChunks[is_frame ? IDX_ANMF : IDX_FRGM].tag;
    343     WebPData frame_frgm;
    344     if (x_offset < 0 || x_offset >= MAX_POSITION_OFFSET ||
    345         y_offset < 0 || y_offset >= MAX_POSITION_OFFSET ||
    346         (duration < 0 || duration >= MAX_DURATION) ||
    347         dispose_method != (dispose_method & 1)) {
    348       err = WEBP_MUX_INVALID_ARGUMENT;
    349       goto Err;
    350     }
    351     err = CreateFrameFragmentData(&wpi.img_->data_, x_offset, y_offset,
    352                                   duration, dispose_method, is_lossless,
    353                                   is_frame, &frame_frgm);
    354     if (err != WEBP_MUX_OK) goto Err;
    355     // Add frame/fragment chunk (with copy_data = 1).
    356     err = AddDataToChunkList(&frame_frgm, 1, tag, &wpi.header_);
    357     WebPDataClear(&frame_frgm);  // frame_frgm owned by wpi.header_ now.
    358     if (err != WEBP_MUX_OK) goto Err;
    359   }
    360 
    361   // Add this WebPMuxImage to mux.
    362   err = MuxImagePush(&wpi, &mux->images_);
    363   if (err != WEBP_MUX_OK) goto Err;
    364 
    365   // All is well.
    366   return WEBP_MUX_OK;
    367 
    368  Err:  // Something bad happened.
    369   MuxImageRelease(&wpi);
    370   return err;
    371 }
    372 
    373 WebPMuxError WebPMuxSetAnimationParams(WebPMux* mux,
    374                                        const WebPMuxAnimParams* params) {
    375   WebPMuxError err;
    376   uint8_t data[ANIM_CHUNK_SIZE];
    377 
    378   if (mux == NULL || params == NULL) return WEBP_MUX_INVALID_ARGUMENT;
    379   if (params->loop_count < 0 || params->loop_count >= MAX_LOOP_COUNT) {
    380     return WEBP_MUX_INVALID_ARGUMENT;
    381   }
    382 
    383   // Delete any existing ANIM chunk(s).
    384   err = MuxDeleteAllNamedData(mux, kChunks[IDX_ANIM].tag);
    385   if (err != WEBP_MUX_OK && err != WEBP_MUX_NOT_FOUND) return err;
    386 
    387   // Set the animation parameters.
    388   PutLE32(data, params->bgcolor);
    389   PutLE16(data + 4, params->loop_count);
    390   return MuxAddChunk(mux, 1, kChunks[IDX_ANIM].tag, data, sizeof(data), 1);
    391 }
    392 
    393 //------------------------------------------------------------------------------
    394 // Delete API(s).
    395 
    396 WebPMuxError WebPMuxDeleteChunk(WebPMux* mux, const char fourcc[4]) {
    397   if (mux == NULL || fourcc == NULL) return WEBP_MUX_INVALID_ARGUMENT;
    398   return MuxDeleteAllNamedData(mux, ChunkGetTagFromFourCC(fourcc));
    399 }
    400 
    401 WebPMuxError WebPMuxDeleteFrame(WebPMux* mux, uint32_t nth) {
    402   if (mux == NULL) return WEBP_MUX_INVALID_ARGUMENT;
    403   return MuxImageDeleteNth(&mux->images_, nth);
    404 }
    405 
    406 //------------------------------------------------------------------------------
    407 // Assembly of the WebP RIFF file.
    408 
    409 static WebPMuxError GetFrameFragmentInfo(
    410     const WebPChunk* const frame_frgm_chunk,
    411     int* const x_offset, int* const y_offset, int* const duration) {
    412   const uint32_t tag = frame_frgm_chunk->tag_;
    413   const int is_frame = (tag == kChunks[IDX_ANMF].tag);
    414   const WebPData* const data = &frame_frgm_chunk->data_;
    415   const size_t expected_data_size =
    416       is_frame ? ANMF_CHUNK_SIZE : FRGM_CHUNK_SIZE;
    417   assert(frame_frgm_chunk != NULL);
    418   assert(tag == kChunks[IDX_ANMF].tag || tag ==  kChunks[IDX_FRGM].tag);
    419   if (data->size != expected_data_size) return WEBP_MUX_INVALID_ARGUMENT;
    420 
    421   *x_offset = 2 * GetLE24(data->bytes + 0);
    422   *y_offset = 2 * GetLE24(data->bytes + 3);
    423   if (is_frame) *duration = GetLE24(data->bytes + 12);
    424   return WEBP_MUX_OK;
    425 }
    426 
    427 WebPMuxError MuxGetImageWidthHeight(const WebPChunk* const image_chunk,
    428                                     int* const width, int* const height) {
    429   const uint32_t tag = image_chunk->tag_;
    430   const WebPData* const data = &image_chunk->data_;
    431   int w, h;
    432   int ok;
    433   assert(image_chunk != NULL);
    434   assert(tag == kChunks[IDX_VP8].tag || tag ==  kChunks[IDX_VP8L].tag);
    435   ok = (tag == kChunks[IDX_VP8].tag) ?
    436       VP8GetInfo(data->bytes, data->size, data->size, &w, &h) :
    437       VP8LGetInfo(data->bytes, data->size, &w, &h, NULL);
    438   if (ok) {
    439     *width = w;
    440     *height = h;
    441     return WEBP_MUX_OK;
    442   } else {
    443     return WEBP_MUX_BAD_DATA;
    444   }
    445 }
    446 
    447 static WebPMuxError GetImageInfo(const WebPMuxImage* const wpi,
    448                                  int* const x_offset, int* const y_offset,
    449                                  int* const duration,
    450                                  int* const width, int* const height) {
    451   const WebPChunk* const image_chunk = wpi->img_;
    452   const WebPChunk* const frame_frgm_chunk = wpi->header_;
    453 
    454   // Get offsets and duration from ANMF/FRGM chunk.
    455   const WebPMuxError err =
    456       GetFrameFragmentInfo(frame_frgm_chunk, x_offset, y_offset, duration);
    457   if (err != WEBP_MUX_OK) return err;
    458 
    459   // Get width and height from VP8/VP8L chunk.
    460   return MuxGetImageWidthHeight(image_chunk, width, height);
    461 }
    462 
    463 static WebPMuxError GetImageCanvasWidthHeight(
    464     const WebPMux* const mux, uint32_t flags,
    465     int* const width, int* const height) {
    466   WebPMuxImage* wpi = NULL;
    467   assert(mux != NULL);
    468   assert(width != NULL && height != NULL);
    469 
    470   wpi = mux->images_;
    471   assert(wpi != NULL);
    472   assert(wpi->img_ != NULL);
    473 
    474   if (wpi->next_) {
    475     int max_x = 0;
    476     int max_y = 0;
    477     int64_t image_area = 0;
    478     // Aggregate the bounding box for animation frames & fragmented images.
    479     for (; wpi != NULL; wpi = wpi->next_) {
    480       int x_offset = 0, y_offset = 0, duration = 0, w = 0, h = 0;
    481       const WebPMuxError err = GetImageInfo(wpi, &x_offset, &y_offset,
    482                                             &duration, &w, &h);
    483       const int max_x_pos = x_offset + w;
    484       const int max_y_pos = y_offset + h;
    485       if (err != WEBP_MUX_OK) return err;
    486       assert(x_offset < MAX_POSITION_OFFSET);
    487       assert(y_offset < MAX_POSITION_OFFSET);
    488 
    489       if (max_x_pos > max_x) max_x = max_x_pos;
    490       if (max_y_pos > max_y) max_y = max_y_pos;
    491       image_area += w * h;
    492     }
    493     *width = max_x;
    494     *height = max_y;
    495     // Crude check to validate that there are no image overlaps/holes for
    496     // fragmented images. Check that the aggregated image area for individual
    497     // fragments exactly matches the image area of the constructed canvas.
    498     // However, the area-match is necessary but not sufficient condition.
    499     if ((flags & FRAGMENTS_FLAG) && (image_area != (max_x * max_y))) {
    500       *width = 0;
    501       *height = 0;
    502       return WEBP_MUX_INVALID_ARGUMENT;
    503     }
    504   } else {
    505     // For a single image, extract the width & height from VP8/VP8L image-data.
    506     int w, h;
    507     const WebPChunk* const image_chunk = wpi->img_;
    508     const WebPMuxError err = MuxGetImageWidthHeight(image_chunk, &w, &h);
    509     if (err != WEBP_MUX_OK) return err;
    510     *width = w;
    511     *height = h;
    512   }
    513   return WEBP_MUX_OK;
    514 }
    515 
    516 // VP8X format:
    517 // Total Size : 10,
    518 // Flags  : 4 bytes,
    519 // Width  : 3 bytes,
    520 // Height : 3 bytes.
    521 static WebPMuxError CreateVP8XChunk(WebPMux* const mux) {
    522   WebPMuxError err = WEBP_MUX_OK;
    523   uint32_t flags = 0;
    524   int width = 0;
    525   int height = 0;
    526   uint8_t data[VP8X_CHUNK_SIZE];
    527   const size_t data_size = VP8X_CHUNK_SIZE;
    528   const WebPMuxImage* images = NULL;
    529 
    530   assert(mux != NULL);
    531   images = mux->images_;  // First image.
    532   if (images == NULL || images->img_ == NULL ||
    533       images->img_->data_.bytes == NULL) {
    534     return WEBP_MUX_INVALID_ARGUMENT;
    535   }
    536 
    537   // If VP8X chunk(s) is(are) already present, remove them (and later add new
    538   // VP8X chunk with updated flags).
    539   err = MuxDeleteAllNamedData(mux, kChunks[IDX_VP8X].tag);
    540   if (err != WEBP_MUX_OK && err != WEBP_MUX_NOT_FOUND) return err;
    541 
    542   // Set flags.
    543   if (mux->iccp_ != NULL && mux->iccp_->data_.bytes != NULL) {
    544     flags |= ICCP_FLAG;
    545   }
    546   if (mux->exif_ != NULL && mux->exif_->data_.bytes != NULL) {
    547     flags |= EXIF_FLAG;
    548   }
    549   if (mux->xmp_ != NULL && mux->xmp_->data_.bytes != NULL) {
    550     flags |= XMP_FLAG;
    551   }
    552   if (images->header_ != NULL) {
    553     if (images->header_->tag_ == kChunks[IDX_FRGM].tag) {
    554       // This is a fragmented image.
    555       flags |= FRAGMENTS_FLAG;
    556     } else if (images->header_->tag_ == kChunks[IDX_ANMF].tag) {
    557       // This is an image with animation.
    558       flags |= ANIMATION_FLAG;
    559     }
    560   }
    561   if (MuxImageCount(images, WEBP_CHUNK_ALPHA) > 0) {
    562     flags |= ALPHA_FLAG;  // Some images have an alpha channel.
    563   }
    564 
    565   if (flags == 0) {
    566     // For Simple Image, VP8X chunk should not be added.
    567     return WEBP_MUX_OK;
    568   }
    569 
    570   err = GetImageCanvasWidthHeight(mux, flags, &width, &height);
    571   if (err != WEBP_MUX_OK) return err;
    572 
    573   if (width <= 0 || height <= 0) {
    574     return WEBP_MUX_INVALID_ARGUMENT;
    575   }
    576   if (width > MAX_CANVAS_SIZE || height > MAX_CANVAS_SIZE) {
    577     return WEBP_MUX_INVALID_ARGUMENT;
    578   }
    579 
    580   if (MuxHasLosslessImages(images)) {
    581     // We have a file with a VP8X chunk having some lossless images.
    582     // As lossless images implicitly contain alpha, force ALPHA_FLAG to be true.
    583     // Note: This 'flags' update must NOT be done for a lossless image
    584     // without a VP8X chunk!
    585     flags |= ALPHA_FLAG;
    586   }
    587 
    588   PutLE32(data + 0, flags);   // VP8X chunk flags.
    589   PutLE24(data + 4, width - 1);   // canvas width.
    590   PutLE24(data + 7, height - 1);  // canvas height.
    591 
    592   err = MuxAddChunk(mux, 1, kChunks[IDX_VP8X].tag, data, data_size, 1);
    593   return err;
    594 }
    595 
    596 // Cleans up 'mux' by removing any unnecessary chunks.
    597 static WebPMuxError MuxCleanup(WebPMux* const mux) {
    598   int num_frames;
    599   int num_fragments;
    600   int num_anim_chunks;
    601 
    602   // If we have an image with single fragment or frame, convert it to a
    603   // non-animated non-fragmented image (to avoid writing FRGM/ANMF chunk
    604   // unnecessarily).
    605   WebPMuxError err = WebPMuxNumChunks(mux, kChunks[IDX_ANMF].id, &num_frames);
    606   if (err != WEBP_MUX_OK) return err;
    607   err = WebPMuxNumChunks(mux, kChunks[IDX_FRGM].id, &num_fragments);
    608   if (err != WEBP_MUX_OK) return err;
    609   if (num_frames == 1 || num_fragments == 1) {
    610     WebPMuxImage* frame_frag;
    611     err = MuxImageGetNth((const WebPMuxImage**)&mux->images_, 1, &frame_frag);
    612     assert(err == WEBP_MUX_OK);  // We know that one frame/fragment does exist.
    613     if (frame_frag->header_ != NULL) {
    614       assert(frame_frag->header_->tag_ == kChunks[IDX_ANMF].tag ||
    615              frame_frag->header_->tag_ == kChunks[IDX_FRGM].tag);
    616       ChunkDelete(frame_frag->header_);  // Removes ANMF/FRGM chunk.
    617       frame_frag->header_ = NULL;
    618     }
    619     num_frames = 0;
    620     num_fragments = 0;
    621   }
    622   // Remove ANIM chunk if this is a non-animated image.
    623   err = WebPMuxNumChunks(mux, kChunks[IDX_ANIM].id, &num_anim_chunks);
    624   if (err != WEBP_MUX_OK) return err;
    625   if (num_anim_chunks >= 1 && num_frames == 0) {
    626     err = MuxDeleteAllNamedData(mux, kChunks[IDX_ANIM].tag);
    627     if (err != WEBP_MUX_OK) return err;
    628   }
    629   return WEBP_MUX_OK;
    630 }
    631 
    632 WebPMuxError WebPMuxAssemble(WebPMux* mux, WebPData* assembled_data) {
    633   size_t size = 0;
    634   uint8_t* data = NULL;
    635   uint8_t* dst = NULL;
    636   WebPMuxError err;
    637 
    638   if (mux == NULL || assembled_data == NULL) {
    639     return WEBP_MUX_INVALID_ARGUMENT;
    640   }
    641 
    642   // Finalize mux.
    643   err = MuxCleanup(mux);
    644   if (err != WEBP_MUX_OK) return err;
    645   err = CreateVP8XChunk(mux);
    646   if (err != WEBP_MUX_OK) return err;
    647 
    648   // Allocate data.
    649   size = ChunksListDiskSize(mux->vp8x_) + ChunksListDiskSize(mux->iccp_)
    650        + ChunksListDiskSize(mux->anim_) + MuxImageListDiskSize(mux->images_)
    651        + ChunksListDiskSize(mux->exif_) + ChunksListDiskSize(mux->xmp_)
    652        + ChunksListDiskSize(mux->unknown_) + RIFF_HEADER_SIZE;
    653 
    654   data = (uint8_t*)malloc(size);
    655   if (data == NULL) return WEBP_MUX_MEMORY_ERROR;
    656 
    657   // Emit header & chunks.
    658   dst = MuxEmitRiffHeader(data, size);
    659   dst = ChunkListEmit(mux->vp8x_, dst);
    660   dst = ChunkListEmit(mux->iccp_, dst);
    661   dst = ChunkListEmit(mux->anim_, dst);
    662   dst = MuxImageListEmit(mux->images_, dst);
    663   dst = ChunkListEmit(mux->exif_, dst);
    664   dst = ChunkListEmit(mux->xmp_, dst);
    665   dst = ChunkListEmit(mux->unknown_, dst);
    666   assert(dst == data + size);
    667 
    668   // Validate mux.
    669   err = MuxValidate(mux);
    670   if (err != WEBP_MUX_OK) {
    671     free(data);
    672     data = NULL;
    673     size = 0;
    674   }
    675 
    676   // Finalize data.
    677   assembled_data->bytes = data;
    678   assembled_data->size = size;
    679 
    680   return err;
    681 }
    682 
    683 //------------------------------------------------------------------------------
    684 
    685 #if defined(__cplusplus) || defined(c_plusplus)
    686 }    // extern "C"
    687 #endif
    688