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 // Internal objects and utils for mux.
     11 //
     12 // Authors: Urvang (urvang (at) google.com)
     13 //          Vikas (vikasa (at) google.com)
     14 
     15 #include <assert.h>
     16 #include "src/mux/muxi.h"
     17 #include "src/utils/utils.h"
     18 
     19 #define UNDEFINED_CHUNK_SIZE ((uint32_t)(-1))
     20 
     21 const ChunkInfo kChunks[] = {
     22   { MKFOURCC('V', 'P', '8', 'X'),  WEBP_CHUNK_VP8X,    VP8X_CHUNK_SIZE },
     23   { MKFOURCC('I', 'C', 'C', 'P'),  WEBP_CHUNK_ICCP,    UNDEFINED_CHUNK_SIZE },
     24   { MKFOURCC('A', 'N', 'I', 'M'),  WEBP_CHUNK_ANIM,    ANIM_CHUNK_SIZE },
     25   { MKFOURCC('A', 'N', 'M', 'F'),  WEBP_CHUNK_ANMF,    ANMF_CHUNK_SIZE },
     26   { MKFOURCC('A', 'L', 'P', 'H'),  WEBP_CHUNK_ALPHA,   UNDEFINED_CHUNK_SIZE },
     27   { MKFOURCC('V', 'P', '8', ' '),  WEBP_CHUNK_IMAGE,   UNDEFINED_CHUNK_SIZE },
     28   { MKFOURCC('V', 'P', '8', 'L'),  WEBP_CHUNK_IMAGE,   UNDEFINED_CHUNK_SIZE },
     29   { MKFOURCC('E', 'X', 'I', 'F'),  WEBP_CHUNK_EXIF,    UNDEFINED_CHUNK_SIZE },
     30   { MKFOURCC('X', 'M', 'P', ' '),  WEBP_CHUNK_XMP,     UNDEFINED_CHUNK_SIZE },
     31   { NIL_TAG,                       WEBP_CHUNK_UNKNOWN, UNDEFINED_CHUNK_SIZE },
     32 
     33   { NIL_TAG,                       WEBP_CHUNK_NIL,     UNDEFINED_CHUNK_SIZE }
     34 };
     35 
     36 //------------------------------------------------------------------------------
     37 
     38 int WebPGetMuxVersion(void) {
     39   return (MUX_MAJ_VERSION << 16) | (MUX_MIN_VERSION << 8) | MUX_REV_VERSION;
     40 }
     41 
     42 //------------------------------------------------------------------------------
     43 // Life of a chunk object.
     44 
     45 void ChunkInit(WebPChunk* const chunk) {
     46   assert(chunk);
     47   memset(chunk, 0, sizeof(*chunk));
     48   chunk->tag_ = NIL_TAG;
     49 }
     50 
     51 WebPChunk* ChunkRelease(WebPChunk* const chunk) {
     52   WebPChunk* next;
     53   if (chunk == NULL) return NULL;
     54   if (chunk->owner_) {
     55     WebPDataClear(&chunk->data_);
     56   }
     57   next = chunk->next_;
     58   ChunkInit(chunk);
     59   return next;
     60 }
     61 
     62 //------------------------------------------------------------------------------
     63 // Chunk misc methods.
     64 
     65 CHUNK_INDEX ChunkGetIndexFromTag(uint32_t tag) {
     66   int i;
     67   for (i = 0; kChunks[i].tag != NIL_TAG; ++i) {
     68     if (tag == kChunks[i].tag) return (CHUNK_INDEX)i;
     69   }
     70   return IDX_UNKNOWN;
     71 }
     72 
     73 WebPChunkId ChunkGetIdFromTag(uint32_t tag) {
     74   int i;
     75   for (i = 0; kChunks[i].tag != NIL_TAG; ++i) {
     76     if (tag == kChunks[i].tag) return kChunks[i].id;
     77   }
     78   return WEBP_CHUNK_UNKNOWN;
     79 }
     80 
     81 uint32_t ChunkGetTagFromFourCC(const char fourcc[4]) {
     82   return MKFOURCC(fourcc[0], fourcc[1], fourcc[2], fourcc[3]);
     83 }
     84 
     85 CHUNK_INDEX ChunkGetIndexFromFourCC(const char fourcc[4]) {
     86   const uint32_t tag = ChunkGetTagFromFourCC(fourcc);
     87   return ChunkGetIndexFromTag(tag);
     88 }
     89 
     90 //------------------------------------------------------------------------------
     91 // Chunk search methods.
     92 
     93 // Returns next chunk in the chunk list with the given tag.
     94 static WebPChunk* ChunkSearchNextInList(WebPChunk* chunk, uint32_t tag) {
     95   while (chunk != NULL && chunk->tag_ != tag) {
     96     chunk = chunk->next_;
     97   }
     98   return chunk;
     99 }
    100 
    101 WebPChunk* ChunkSearchList(WebPChunk* first, uint32_t nth, uint32_t tag) {
    102   uint32_t iter = nth;
    103   first = ChunkSearchNextInList(first, tag);
    104   if (first == NULL) return NULL;
    105 
    106   while (--iter != 0) {
    107     WebPChunk* next_chunk = ChunkSearchNextInList(first->next_, tag);
    108     if (next_chunk == NULL) break;
    109     first = next_chunk;
    110   }
    111   return ((nth > 0) && (iter > 0)) ? NULL : first;
    112 }
    113 
    114 // Outputs a pointer to 'prev_chunk->next_',
    115 //   where 'prev_chunk' is the pointer to the chunk at position (nth - 1).
    116 // Returns true if nth chunk was found.
    117 static int ChunkSearchListToSet(WebPChunk** chunk_list, uint32_t nth,
    118                                 WebPChunk*** const location) {
    119   uint32_t count = 0;
    120   assert(chunk_list != NULL);
    121   *location = chunk_list;
    122 
    123   while (*chunk_list != NULL) {
    124     WebPChunk* const cur_chunk = *chunk_list;
    125     ++count;
    126     if (count == nth) return 1;  // Found.
    127     chunk_list = &cur_chunk->next_;
    128     *location = chunk_list;
    129   }
    130 
    131   // *chunk_list is ok to be NULL if adding at last location.
    132   return (nth == 0 || (count == nth - 1)) ? 1 : 0;
    133 }
    134 
    135 //------------------------------------------------------------------------------
    136 // Chunk writer methods.
    137 
    138 WebPMuxError ChunkAssignData(WebPChunk* chunk, const WebPData* const data,
    139                              int copy_data, uint32_t tag) {
    140   // For internally allocated chunks, always copy data & make it owner of data.
    141   if (tag == kChunks[IDX_VP8X].tag || tag == kChunks[IDX_ANIM].tag) {
    142     copy_data = 1;
    143   }
    144 
    145   ChunkRelease(chunk);
    146 
    147   if (data != NULL) {
    148     if (copy_data) {        // Copy data.
    149       if (!WebPDataCopy(data, &chunk->data_)) return WEBP_MUX_MEMORY_ERROR;
    150       chunk->owner_ = 1;    // Chunk is owner of data.
    151     } else {                // Don't copy data.
    152       chunk->data_ = *data;
    153     }
    154   }
    155   chunk->tag_ = tag;
    156   return WEBP_MUX_OK;
    157 }
    158 
    159 WebPMuxError ChunkSetNth(WebPChunk* chunk, WebPChunk** chunk_list,
    160                          uint32_t nth) {
    161   WebPChunk* new_chunk;
    162 
    163   if (!ChunkSearchListToSet(chunk_list, nth, &chunk_list)) {
    164     return WEBP_MUX_NOT_FOUND;
    165   }
    166 
    167   new_chunk = (WebPChunk*)WebPSafeMalloc(1ULL, sizeof(*new_chunk));
    168   if (new_chunk == NULL) return WEBP_MUX_MEMORY_ERROR;
    169   *new_chunk = *chunk;
    170   chunk->owner_ = 0;
    171   new_chunk->next_ = *chunk_list;
    172   *chunk_list = new_chunk;
    173   return WEBP_MUX_OK;
    174 }
    175 
    176 //------------------------------------------------------------------------------
    177 // Chunk deletion method(s).
    178 
    179 WebPChunk* ChunkDelete(WebPChunk* const chunk) {
    180   WebPChunk* const next = ChunkRelease(chunk);
    181   WebPSafeFree(chunk);
    182   return next;
    183 }
    184 
    185 void ChunkListDelete(WebPChunk** const chunk_list) {
    186   while (*chunk_list != NULL) {
    187     *chunk_list = ChunkDelete(*chunk_list);
    188   }
    189 }
    190 
    191 //------------------------------------------------------------------------------
    192 // Chunk serialization methods.
    193 
    194 static uint8_t* ChunkEmit(const WebPChunk* const chunk, uint8_t* dst) {
    195   const size_t chunk_size = chunk->data_.size;
    196   assert(chunk);
    197   assert(chunk->tag_ != NIL_TAG);
    198   PutLE32(dst + 0, chunk->tag_);
    199   PutLE32(dst + TAG_SIZE, (uint32_t)chunk_size);
    200   assert(chunk_size == (uint32_t)chunk_size);
    201   memcpy(dst + CHUNK_HEADER_SIZE, chunk->data_.bytes, chunk_size);
    202   if (chunk_size & 1)
    203     dst[CHUNK_HEADER_SIZE + chunk_size] = 0;  // Add padding.
    204   return dst + ChunkDiskSize(chunk);
    205 }
    206 
    207 uint8_t* ChunkListEmit(const WebPChunk* chunk_list, uint8_t* dst) {
    208   while (chunk_list != NULL) {
    209     dst = ChunkEmit(chunk_list, dst);
    210     chunk_list = chunk_list->next_;
    211   }
    212   return dst;
    213 }
    214 
    215 size_t ChunkListDiskSize(const WebPChunk* chunk_list) {
    216   size_t size = 0;
    217   while (chunk_list != NULL) {
    218     size += ChunkDiskSize(chunk_list);
    219     chunk_list = chunk_list->next_;
    220   }
    221   return size;
    222 }
    223 
    224 //------------------------------------------------------------------------------
    225 // Life of a MuxImage object.
    226 
    227 void MuxImageInit(WebPMuxImage* const wpi) {
    228   assert(wpi);
    229   memset(wpi, 0, sizeof(*wpi));
    230 }
    231 
    232 WebPMuxImage* MuxImageRelease(WebPMuxImage* const wpi) {
    233   WebPMuxImage* next;
    234   if (wpi == NULL) return NULL;
    235   ChunkDelete(wpi->header_);
    236   ChunkDelete(wpi->alpha_);
    237   ChunkDelete(wpi->img_);
    238   ChunkListDelete(&wpi->unknown_);
    239 
    240   next = wpi->next_;
    241   MuxImageInit(wpi);
    242   return next;
    243 }
    244 
    245 //------------------------------------------------------------------------------
    246 // MuxImage search methods.
    247 
    248 // Get a reference to appropriate chunk list within an image given chunk tag.
    249 static WebPChunk** GetChunkListFromId(const WebPMuxImage* const wpi,
    250                                       WebPChunkId id) {
    251   assert(wpi != NULL);
    252   switch (id) {
    253     case WEBP_CHUNK_ANMF:  return (WebPChunk**)&wpi->header_;
    254     case WEBP_CHUNK_ALPHA: return (WebPChunk**)&wpi->alpha_;
    255     case WEBP_CHUNK_IMAGE: return (WebPChunk**)&wpi->img_;
    256     default: return NULL;
    257   }
    258 }
    259 
    260 int MuxImageCount(const WebPMuxImage* wpi_list, WebPChunkId id) {
    261   int count = 0;
    262   const WebPMuxImage* current;
    263   for (current = wpi_list; current != NULL; current = current->next_) {
    264     if (id == WEBP_CHUNK_NIL) {
    265       ++count;  // Special case: count all images.
    266     } else {
    267       const WebPChunk* const wpi_chunk = *GetChunkListFromId(current, id);
    268       if (wpi_chunk != NULL) {
    269         const WebPChunkId wpi_chunk_id = ChunkGetIdFromTag(wpi_chunk->tag_);
    270         if (wpi_chunk_id == id) ++count;  // Count images with a matching 'id'.
    271       }
    272     }
    273   }
    274   return count;
    275 }
    276 
    277 // Outputs a pointer to 'prev_wpi->next_',
    278 //   where 'prev_wpi' is the pointer to the image at position (nth - 1).
    279 // Returns true if nth image was found.
    280 static int SearchImageToGetOrDelete(WebPMuxImage** wpi_list, uint32_t nth,
    281                                     WebPMuxImage*** const location) {
    282   uint32_t count = 0;
    283   assert(wpi_list);
    284   *location = wpi_list;
    285 
    286   if (nth == 0) {
    287     nth = MuxImageCount(*wpi_list, WEBP_CHUNK_NIL);
    288     if (nth == 0) return 0;  // Not found.
    289   }
    290 
    291   while (*wpi_list != NULL) {
    292     WebPMuxImage* const cur_wpi = *wpi_list;
    293     ++count;
    294     if (count == nth) return 1;  // Found.
    295     wpi_list = &cur_wpi->next_;
    296     *location = wpi_list;
    297   }
    298   return 0;  // Not found.
    299 }
    300 
    301 //------------------------------------------------------------------------------
    302 // MuxImage writer methods.
    303 
    304 WebPMuxError MuxImagePush(const WebPMuxImage* wpi, WebPMuxImage** wpi_list) {
    305   WebPMuxImage* new_wpi;
    306 
    307   while (*wpi_list != NULL) {
    308     WebPMuxImage* const cur_wpi = *wpi_list;
    309     if (cur_wpi->next_ == NULL) break;
    310     wpi_list = &cur_wpi->next_;
    311   }
    312 
    313   new_wpi = (WebPMuxImage*)WebPSafeMalloc(1ULL, sizeof(*new_wpi));
    314   if (new_wpi == NULL) return WEBP_MUX_MEMORY_ERROR;
    315   *new_wpi = *wpi;
    316   new_wpi->next_ = NULL;
    317 
    318   if (*wpi_list != NULL) {
    319     (*wpi_list)->next_ = new_wpi;
    320   } else {
    321     *wpi_list = new_wpi;
    322   }
    323   return WEBP_MUX_OK;
    324 }
    325 
    326 //------------------------------------------------------------------------------
    327 // MuxImage deletion methods.
    328 
    329 WebPMuxImage* MuxImageDelete(WebPMuxImage* const wpi) {
    330   // Delete the components of wpi. If wpi is NULL this is a noop.
    331   WebPMuxImage* const next = MuxImageRelease(wpi);
    332   WebPSafeFree(wpi);
    333   return next;
    334 }
    335 
    336 WebPMuxError MuxImageDeleteNth(WebPMuxImage** wpi_list, uint32_t nth) {
    337   assert(wpi_list);
    338   if (!SearchImageToGetOrDelete(wpi_list, nth, &wpi_list)) {
    339     return WEBP_MUX_NOT_FOUND;
    340   }
    341   *wpi_list = MuxImageDelete(*wpi_list);
    342   return WEBP_MUX_OK;
    343 }
    344 
    345 //------------------------------------------------------------------------------
    346 // MuxImage reader methods.
    347 
    348 WebPMuxError MuxImageGetNth(const WebPMuxImage** wpi_list, uint32_t nth,
    349                             WebPMuxImage** wpi) {
    350   assert(wpi_list);
    351   assert(wpi);
    352   if (!SearchImageToGetOrDelete((WebPMuxImage**)wpi_list, nth,
    353                                 (WebPMuxImage***)&wpi_list)) {
    354     return WEBP_MUX_NOT_FOUND;
    355   }
    356   *wpi = (WebPMuxImage*)*wpi_list;
    357   return WEBP_MUX_OK;
    358 }
    359 
    360 //------------------------------------------------------------------------------
    361 // MuxImage serialization methods.
    362 
    363 // Size of an image.
    364 size_t MuxImageDiskSize(const WebPMuxImage* const wpi) {
    365   size_t size = 0;
    366   if (wpi->header_ != NULL) size += ChunkDiskSize(wpi->header_);
    367   if (wpi->alpha_ != NULL) size += ChunkDiskSize(wpi->alpha_);
    368   if (wpi->img_ != NULL) size += ChunkDiskSize(wpi->img_);
    369   if (wpi->unknown_ != NULL) size += ChunkListDiskSize(wpi->unknown_);
    370   return size;
    371 }
    372 
    373 // Special case as ANMF chunk encapsulates other image chunks.
    374 static uint8_t* ChunkEmitSpecial(const WebPChunk* const header,
    375                                  size_t total_size, uint8_t* dst) {
    376   const size_t header_size = header->data_.size;
    377   const size_t offset_to_next = total_size - CHUNK_HEADER_SIZE;
    378   assert(header->tag_ == kChunks[IDX_ANMF].tag);
    379   PutLE32(dst + 0, header->tag_);
    380   PutLE32(dst + TAG_SIZE, (uint32_t)offset_to_next);
    381   assert(header_size == (uint32_t)header_size);
    382   memcpy(dst + CHUNK_HEADER_SIZE, header->data_.bytes, header_size);
    383   if (header_size & 1) {
    384     dst[CHUNK_HEADER_SIZE + header_size] = 0;  // Add padding.
    385   }
    386   return dst + ChunkDiskSize(header);
    387 }
    388 
    389 uint8_t* MuxImageEmit(const WebPMuxImage* const wpi, uint8_t* dst) {
    390   // Ordering of chunks to be emitted is strictly as follows:
    391   // 1. ANMF chunk (if present).
    392   // 2. ALPH chunk (if present).
    393   // 3. VP8/VP8L chunk.
    394   assert(wpi);
    395   if (wpi->header_ != NULL) {
    396     dst = ChunkEmitSpecial(wpi->header_, MuxImageDiskSize(wpi), dst);
    397   }
    398   if (wpi->alpha_ != NULL) dst = ChunkEmit(wpi->alpha_, dst);
    399   if (wpi->img_ != NULL) dst = ChunkEmit(wpi->img_, dst);
    400   if (wpi->unknown_ != NULL) dst = ChunkListEmit(wpi->unknown_, dst);
    401   return dst;
    402 }
    403 
    404 //------------------------------------------------------------------------------
    405 // Helper methods for mux.
    406 
    407 int MuxHasAlpha(const WebPMuxImage* images) {
    408   while (images != NULL) {
    409     if (images->has_alpha_) return 1;
    410     images = images->next_;
    411   }
    412   return 0;
    413 }
    414 
    415 uint8_t* MuxEmitRiffHeader(uint8_t* const data, size_t size) {
    416   PutLE32(data + 0, MKFOURCC('R', 'I', 'F', 'F'));
    417   PutLE32(data + TAG_SIZE, (uint32_t)size - CHUNK_HEADER_SIZE);
    418   assert(size == (uint32_t)size);
    419   PutLE32(data + TAG_SIZE + CHUNK_SIZE_BYTES, MKFOURCC('W', 'E', 'B', 'P'));
    420   return data + RIFF_HEADER_SIZE;
    421 }
    422 
    423 WebPChunk** MuxGetChunkListFromId(const WebPMux* mux, WebPChunkId id) {
    424   assert(mux != NULL);
    425   switch (id) {
    426     case WEBP_CHUNK_VP8X:    return (WebPChunk**)&mux->vp8x_;
    427     case WEBP_CHUNK_ICCP:    return (WebPChunk**)&mux->iccp_;
    428     case WEBP_CHUNK_ANIM:    return (WebPChunk**)&mux->anim_;
    429     case WEBP_CHUNK_EXIF:    return (WebPChunk**)&mux->exif_;
    430     case WEBP_CHUNK_XMP:     return (WebPChunk**)&mux->xmp_;
    431     default:                 return (WebPChunk**)&mux->unknown_;
    432   }
    433 }
    434 
    435 static int IsNotCompatible(int feature, int num_items) {
    436   return (feature != 0) != (num_items > 0);
    437 }
    438 
    439 #define NO_FLAG ((WebPFeatureFlags)0)
    440 
    441 // Test basic constraints:
    442 // retrieval, maximum number of chunks by index (use -1 to skip)
    443 // and feature incompatibility (use NO_FLAG to skip).
    444 // On success returns WEBP_MUX_OK and stores the chunk count in *num.
    445 static WebPMuxError ValidateChunk(const WebPMux* const mux, CHUNK_INDEX idx,
    446                                   WebPFeatureFlags feature,
    447                                   uint32_t vp8x_flags,
    448                                   int max, int* num) {
    449   const WebPMuxError err =
    450       WebPMuxNumChunks(mux, kChunks[idx].id, num);
    451   if (err != WEBP_MUX_OK) return err;
    452   if (max > -1 && *num > max) return WEBP_MUX_INVALID_ARGUMENT;
    453   if (feature != NO_FLAG && IsNotCompatible(vp8x_flags & feature, *num)) {
    454     return WEBP_MUX_INVALID_ARGUMENT;
    455   }
    456   return WEBP_MUX_OK;
    457 }
    458 
    459 WebPMuxError MuxValidate(const WebPMux* const mux) {
    460   int num_iccp;
    461   int num_exif;
    462   int num_xmp;
    463   int num_anim;
    464   int num_frames;
    465   int num_vp8x;
    466   int num_images;
    467   int num_alpha;
    468   uint32_t flags;
    469   WebPMuxError err;
    470 
    471   // Verify mux is not NULL.
    472   if (mux == NULL) return WEBP_MUX_INVALID_ARGUMENT;
    473 
    474   // Verify mux has at least one image.
    475   if (mux->images_ == NULL) return WEBP_MUX_INVALID_ARGUMENT;
    476 
    477   err = WebPMuxGetFeatures(mux, &flags);
    478   if (err != WEBP_MUX_OK) return err;
    479 
    480   // At most one color profile chunk.
    481   err = ValidateChunk(mux, IDX_ICCP, ICCP_FLAG, flags, 1, &num_iccp);
    482   if (err != WEBP_MUX_OK) return err;
    483 
    484   // At most one EXIF metadata.
    485   err = ValidateChunk(mux, IDX_EXIF, EXIF_FLAG, flags, 1, &num_exif);
    486   if (err != WEBP_MUX_OK) return err;
    487 
    488   // At most one XMP metadata.
    489   err = ValidateChunk(mux, IDX_XMP, XMP_FLAG, flags, 1, &num_xmp);
    490   if (err != WEBP_MUX_OK) return err;
    491 
    492   // Animation: ANIMATION_FLAG, ANIM chunk and ANMF chunk(s) are consistent.
    493   // At most one ANIM chunk.
    494   err = ValidateChunk(mux, IDX_ANIM, NO_FLAG, flags, 1, &num_anim);
    495   if (err != WEBP_MUX_OK) return err;
    496   err = ValidateChunk(mux, IDX_ANMF, NO_FLAG, flags, -1, &num_frames);
    497   if (err != WEBP_MUX_OK) return err;
    498 
    499   {
    500     const int has_animation = !!(flags & ANIMATION_FLAG);
    501     if (has_animation && (num_anim == 0 || num_frames == 0)) {
    502       return WEBP_MUX_INVALID_ARGUMENT;
    503     }
    504     if (!has_animation && (num_anim == 1 || num_frames > 0)) {
    505       return WEBP_MUX_INVALID_ARGUMENT;
    506     }
    507     if (!has_animation) {
    508       const WebPMuxImage* images = mux->images_;
    509       // There can be only one image.
    510       if (images == NULL || images->next_ != NULL) {
    511         return WEBP_MUX_INVALID_ARGUMENT;
    512       }
    513       // Size must match.
    514       if (mux->canvas_width_ > 0) {
    515         if (images->width_ != mux->canvas_width_ ||
    516             images->height_ != mux->canvas_height_) {
    517           return WEBP_MUX_INVALID_ARGUMENT;
    518         }
    519       }
    520     }
    521   }
    522 
    523   // Verify either VP8X chunk is present OR there is only one elem in
    524   // mux->images_.
    525   err = ValidateChunk(mux, IDX_VP8X, NO_FLAG, flags, 1, &num_vp8x);
    526   if (err != WEBP_MUX_OK) return err;
    527   err = ValidateChunk(mux, IDX_VP8, NO_FLAG, flags, -1, &num_images);
    528   if (err != WEBP_MUX_OK) return err;
    529   if (num_vp8x == 0 && num_images != 1) return WEBP_MUX_INVALID_ARGUMENT;
    530 
    531   // ALPHA_FLAG & alpha chunk(s) are consistent.
    532   // Note: ALPHA_FLAG can be set when there is actually no Alpha data present.
    533   if (MuxHasAlpha(mux->images_)) {
    534     if (num_vp8x > 0) {
    535       // VP8X chunk is present, so it should contain ALPHA_FLAG.
    536       if (!(flags & ALPHA_FLAG)) return WEBP_MUX_INVALID_ARGUMENT;
    537     } else {
    538       // VP8X chunk is not present, so ALPH chunks should NOT be present either.
    539       err = WebPMuxNumChunks(mux, WEBP_CHUNK_ALPHA, &num_alpha);
    540       if (err != WEBP_MUX_OK) return err;
    541       if (num_alpha > 0) return WEBP_MUX_INVALID_ARGUMENT;
    542     }
    543   }
    544 
    545   return WEBP_MUX_OK;
    546 }
    547 
    548 #undef NO_FLAG
    549 
    550 //------------------------------------------------------------------------------
    551 
    552