github.com/cellofellow/gopkg@v0.0.0-20140722061823-eec0544a62ad/image/webp/libwebp/src/mux/muxread.c (about)

     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  // Read APIs for mux.
    11  //
    12  // Authors: Urvang (urvang@google.com)
    13  //          Vikas (vikasa@google.com)
    14  
    15  #include <assert.h>
    16  #include "./muxi.h"
    17  #include "../utils/utils.h"
    18  
    19  //------------------------------------------------------------------------------
    20  // Helper method(s).
    21  
    22  // Handy MACRO.
    23  #define SWITCH_ID_LIST(INDEX, LIST)                                           \
    24    if (idx == (INDEX)) {                                                       \
    25      const WebPChunk* const chunk = ChunkSearchList((LIST), nth,               \
    26                                                     kChunks[(INDEX)].tag);     \
    27      if (chunk) {                                                              \
    28        *data = chunk->data_;                                                   \
    29        return WEBP_MUX_OK;                                                     \
    30      } else {                                                                  \
    31        return WEBP_MUX_NOT_FOUND;                                              \
    32      }                                                                         \
    33    }
    34  
    35  static WebPMuxError MuxGet(const WebPMux* const mux, CHUNK_INDEX idx,
    36                             uint32_t nth, WebPData* const data) {
    37    assert(mux != NULL);
    38    assert(!IsWPI(kChunks[idx].id));
    39    WebPDataInit(data);
    40  
    41    SWITCH_ID_LIST(IDX_VP8X, mux->vp8x_);
    42    SWITCH_ID_LIST(IDX_ICCP, mux->iccp_);
    43    SWITCH_ID_LIST(IDX_ANIM, mux->anim_);
    44    SWITCH_ID_LIST(IDX_EXIF, mux->exif_);
    45    SWITCH_ID_LIST(IDX_XMP, mux->xmp_);
    46    SWITCH_ID_LIST(IDX_UNKNOWN, mux->unknown_);
    47    return WEBP_MUX_NOT_FOUND;
    48  }
    49  #undef SWITCH_ID_LIST
    50  
    51  // Fill the chunk with the given data (includes chunk header bytes), after some
    52  // verifications.
    53  static WebPMuxError ChunkVerifyAndAssign(WebPChunk* chunk,
    54                                           const uint8_t* data, size_t data_size,
    55                                           size_t riff_size, int copy_data) {
    56    uint32_t chunk_size;
    57    WebPData chunk_data;
    58  
    59    // Sanity checks.
    60    if (data_size < TAG_SIZE) return WEBP_MUX_NOT_ENOUGH_DATA;
    61    chunk_size = GetLE32(data + TAG_SIZE);
    62  
    63    {
    64      const size_t chunk_disk_size = SizeWithPadding(chunk_size);
    65      if (chunk_disk_size > riff_size) return WEBP_MUX_BAD_DATA;
    66      if (chunk_disk_size > data_size) return WEBP_MUX_NOT_ENOUGH_DATA;
    67    }
    68  
    69    // Data assignment.
    70    chunk_data.bytes = data + CHUNK_HEADER_SIZE;
    71    chunk_data.size = chunk_size;
    72    return ChunkAssignData(chunk, &chunk_data, copy_data, GetLE32(data + 0));
    73  }
    74  
    75  int MuxImageFinalize(WebPMuxImage* const wpi) {
    76    const WebPChunk* const img = wpi->img_;
    77    const WebPData* const image = &img->data_;
    78    const int is_lossless = (img->tag_ == kChunks[IDX_VP8L].tag);
    79    int w, h;
    80    int vp8l_has_alpha = 0;
    81    const int ok = is_lossless ?
    82        VP8LGetInfo(image->bytes, image->size, &w, &h, &vp8l_has_alpha) :
    83        VP8GetInfo(image->bytes, image->size, image->size, &w, &h);
    84    assert(img != NULL);
    85    if (ok) {
    86      // Ignore ALPH chunk accompanying VP8L.
    87      if (is_lossless && (wpi->alpha_ != NULL)) {
    88        ChunkDelete(wpi->alpha_);
    89        wpi->alpha_ = NULL;
    90      }
    91      wpi->width_ = w;
    92      wpi->height_ = h;
    93      wpi->has_alpha_ = vp8l_has_alpha || (wpi->alpha_ != NULL);
    94    }
    95    return ok;
    96  }
    97  
    98  static int MuxImageParse(const WebPChunk* const chunk, int copy_data,
    99                           WebPMuxImage* const wpi) {
   100    const uint8_t* bytes = chunk->data_.bytes;
   101    size_t size = chunk->data_.size;
   102    const uint8_t* const last = bytes + size;
   103    WebPChunk subchunk;
   104    size_t subchunk_size;
   105    ChunkInit(&subchunk);
   106  
   107    assert(chunk->tag_ == kChunks[IDX_ANMF].tag ||
   108           chunk->tag_ == kChunks[IDX_FRGM].tag);
   109    assert(!wpi->is_partial_);
   110  
   111    // ANMF/FRGM.
   112    {
   113      const size_t hdr_size = (chunk->tag_ == kChunks[IDX_ANMF].tag) ?
   114          ANMF_CHUNK_SIZE : FRGM_CHUNK_SIZE;
   115      const WebPData temp = { bytes, hdr_size };
   116      // Each of ANMF and FRGM chunk contain a header at the beginning. So, its
   117      // size should at least be 'hdr_size'.
   118      if (size < hdr_size) goto Fail;
   119      ChunkAssignData(&subchunk, &temp, copy_data, chunk->tag_);
   120    }
   121    ChunkSetNth(&subchunk, &wpi->header_, 1);
   122    wpi->is_partial_ = 1;  // Waiting for ALPH and/or VP8/VP8L chunks.
   123  
   124    // Rest of the chunks.
   125    subchunk_size = ChunkDiskSize(&subchunk) - CHUNK_HEADER_SIZE;
   126    bytes += subchunk_size;
   127    size -= subchunk_size;
   128  
   129    while (bytes != last) {
   130      ChunkInit(&subchunk);
   131      if (ChunkVerifyAndAssign(&subchunk, bytes, size, size,
   132                               copy_data) != WEBP_MUX_OK) {
   133        goto Fail;
   134      }
   135      switch (ChunkGetIdFromTag(subchunk.tag_)) {
   136        case WEBP_CHUNK_ALPHA:
   137          if (wpi->alpha_ != NULL) goto Fail;  // Consecutive ALPH chunks.
   138          if (ChunkSetNth(&subchunk, &wpi->alpha_, 1) != WEBP_MUX_OK) goto Fail;
   139          wpi->is_partial_ = 1;  // Waiting for a VP8 chunk.
   140          break;
   141        case WEBP_CHUNK_IMAGE:
   142          if (ChunkSetNth(&subchunk, &wpi->img_, 1) != WEBP_MUX_OK) goto Fail;
   143          if (!MuxImageFinalize(wpi)) goto Fail;
   144          wpi->is_partial_ = 0;  // wpi is completely filled.
   145          break;
   146        case WEBP_CHUNK_UNKNOWN:
   147          if (wpi->is_partial_) goto Fail;  // Encountered an unknown chunk
   148                                            // before some image chunks.
   149          if (ChunkSetNth(&subchunk, &wpi->unknown_, 0) != WEBP_MUX_OK) goto Fail;
   150          break;
   151        default:
   152          goto Fail;
   153          break;
   154      }
   155      subchunk_size = ChunkDiskSize(&subchunk);
   156      bytes += subchunk_size;
   157      size -= subchunk_size;
   158    }
   159    if (wpi->is_partial_) goto Fail;
   160    return 1;
   161  
   162   Fail:
   163    ChunkRelease(&subchunk);
   164    return 0;
   165  }
   166  
   167  //------------------------------------------------------------------------------
   168  // Create a mux object from WebP-RIFF data.
   169  
   170  WebPMux* WebPMuxCreateInternal(const WebPData* bitstream, int copy_data,
   171                                 int version) {
   172    size_t riff_size;
   173    uint32_t tag;
   174    const uint8_t* end;
   175    WebPMux* mux = NULL;
   176    WebPMuxImage* wpi = NULL;
   177    const uint8_t* data;
   178    size_t size;
   179    WebPChunk chunk;
   180    ChunkInit(&chunk);
   181  
   182    // Sanity checks.
   183    if (WEBP_ABI_IS_INCOMPATIBLE(version, WEBP_MUX_ABI_VERSION)) {
   184      return NULL;  // version mismatch
   185    }
   186    if (bitstream == NULL) return NULL;
   187  
   188    data = bitstream->bytes;
   189    size = bitstream->size;
   190  
   191    if (data == NULL) return NULL;
   192    if (size < RIFF_HEADER_SIZE) return NULL;
   193    if (GetLE32(data + 0) != MKFOURCC('R', 'I', 'F', 'F') ||
   194        GetLE32(data + CHUNK_HEADER_SIZE) != MKFOURCC('W', 'E', 'B', 'P')) {
   195      return NULL;
   196    }
   197  
   198    mux = WebPMuxNew();
   199    if (mux == NULL) return NULL;
   200  
   201    if (size < RIFF_HEADER_SIZE + TAG_SIZE) goto Err;
   202  
   203    tag = GetLE32(data + RIFF_HEADER_SIZE);
   204    if (tag != kChunks[IDX_VP8].tag &&
   205        tag != kChunks[IDX_VP8L].tag &&
   206        tag != kChunks[IDX_VP8X].tag) {
   207      goto Err;  // First chunk should be VP8, VP8L or VP8X.
   208    }
   209  
   210    riff_size = SizeWithPadding(GetLE32(data + TAG_SIZE));
   211    if (riff_size > MAX_CHUNK_PAYLOAD || riff_size > size) {
   212      goto Err;
   213    } else {
   214      if (riff_size < size) {  // Redundant data after last chunk.
   215        size = riff_size;  // To make sure we don't read any data beyond mux_size.
   216      }
   217    }
   218  
   219    end = data + size;
   220    data += RIFF_HEADER_SIZE;
   221    size -= RIFF_HEADER_SIZE;
   222  
   223    wpi = (WebPMuxImage*)malloc(sizeof(*wpi));
   224    if (wpi == NULL) goto Err;
   225    MuxImageInit(wpi);
   226  
   227    // Loop over chunks.
   228    while (data != end) {
   229      size_t data_size;
   230      WebPChunkId id;
   231      WebPChunk** chunk_list;
   232      if (ChunkVerifyAndAssign(&chunk, data, size, riff_size,
   233                               copy_data) != WEBP_MUX_OK) {
   234        goto Err;
   235      }
   236      data_size = ChunkDiskSize(&chunk);
   237      id = ChunkGetIdFromTag(chunk.tag_);
   238      switch (id) {
   239        case WEBP_CHUNK_ALPHA:
   240          if (wpi->alpha_ != NULL) goto Err;  // Consecutive ALPH chunks.
   241          if (ChunkSetNth(&chunk, &wpi->alpha_, 1) != WEBP_MUX_OK) goto Err;
   242          wpi->is_partial_ = 1;  // Waiting for a VP8 chunk.
   243          break;
   244        case WEBP_CHUNK_IMAGE:
   245          if (ChunkSetNth(&chunk, &wpi->img_, 1) != WEBP_MUX_OK) goto Err;
   246          if (!MuxImageFinalize(wpi)) goto Err;
   247          wpi->is_partial_ = 0;  // wpi is completely filled.
   248   PushImage:
   249          // Add this to mux->images_ list.
   250          if (MuxImagePush(wpi, &mux->images_) != WEBP_MUX_OK) goto Err;
   251          MuxImageInit(wpi);  // Reset for reading next image.
   252          break;
   253        case WEBP_CHUNK_ANMF:
   254  #ifdef WEBP_EXPERIMENTAL_FEATURES
   255        case WEBP_CHUNK_FRGM:
   256  #endif
   257          if (wpi->is_partial_) goto Err;  // Previous wpi is still incomplete.
   258          if (!MuxImageParse(&chunk, copy_data, wpi)) goto Err;
   259          ChunkRelease(&chunk);
   260          goto PushImage;
   261          break;
   262        default:  // A non-image chunk.
   263          if (wpi->is_partial_) goto Err;  // Encountered a non-image chunk before
   264                                           // getting all chunks of an image.
   265          chunk_list = MuxGetChunkListFromId(mux, id);  // List to add this chunk.
   266          if (ChunkSetNth(&chunk, chunk_list, 0) != WEBP_MUX_OK) goto Err;
   267          break;
   268      }
   269      data += data_size;
   270      size -= data_size;
   271      ChunkInit(&chunk);
   272    }
   273  
   274    // Validate mux if complete.
   275    if (MuxValidate(mux) != WEBP_MUX_OK) goto Err;
   276  
   277    MuxImageDelete(wpi);
   278    return mux;  // All OK;
   279  
   280   Err:  // Something bad happened.
   281    ChunkRelease(&chunk);
   282    MuxImageDelete(wpi);
   283    WebPMuxDelete(mux);
   284    return NULL;
   285  }
   286  
   287  //------------------------------------------------------------------------------
   288  // Get API(s).
   289  
   290  // Validates that the given mux has a single image.
   291  static WebPMuxError ValidateForSingleImage(const WebPMux* const mux) {
   292    const int num_images = MuxImageCount(mux->images_, WEBP_CHUNK_IMAGE);
   293    const int num_frames = MuxImageCount(mux->images_, WEBP_CHUNK_ANMF);
   294    const int num_fragments = MuxImageCount(mux->images_, WEBP_CHUNK_FRGM);
   295  
   296    if (num_images == 0) {
   297      // No images in mux.
   298      return WEBP_MUX_NOT_FOUND;
   299    } else if (num_images == 1 && num_frames == 0 && num_fragments == 0) {
   300      // Valid case (single image).
   301      return WEBP_MUX_OK;
   302    } else {
   303      // Frame/Fragment case OR an invalid mux.
   304      return WEBP_MUX_INVALID_ARGUMENT;
   305    }
   306  }
   307  
   308  // Get the canvas width, height and flags after validating that VP8X/VP8/VP8L
   309  // chunk and canvas size are valid.
   310  static WebPMuxError MuxGetCanvasInfo(const WebPMux* const mux,
   311                                       int* width, int* height, uint32_t* flags) {
   312    int w, h;
   313    uint32_t f = 0;
   314    WebPData data;
   315    assert(mux != NULL);
   316  
   317    // Check if VP8X chunk is present.
   318    if (MuxGet(mux, IDX_VP8X, 1, &data) == WEBP_MUX_OK) {
   319      if (data.size < VP8X_CHUNK_SIZE) return WEBP_MUX_BAD_DATA;
   320      f = GetLE32(data.bytes + 0);
   321      w = GetLE24(data.bytes + 4) + 1;
   322      h = GetLE24(data.bytes + 7) + 1;
   323    } else {  // Single image case.
   324      const WebPMuxImage* const wpi = mux->images_;
   325      WebPMuxError err = ValidateForSingleImage(mux);
   326      if (err != WEBP_MUX_OK) return err;
   327      assert(wpi != NULL);
   328      w = wpi->width_;
   329      h = wpi->height_;
   330      if (wpi->has_alpha_) f |= ALPHA_FLAG;
   331    }
   332    if (w * (uint64_t)h >= MAX_IMAGE_AREA) return WEBP_MUX_BAD_DATA;
   333  
   334    if (width != NULL) *width = w;
   335    if (height != NULL) *height = h;
   336    if (flags != NULL) *flags = f;
   337    return WEBP_MUX_OK;
   338  }
   339  
   340  WebPMuxError WebPMuxGetCanvasSize(const WebPMux* mux, int* width, int* height) {
   341    if (mux == NULL || width == NULL || height == NULL) {
   342      return WEBP_MUX_INVALID_ARGUMENT;
   343    }
   344    return MuxGetCanvasInfo(mux, width, height, NULL);
   345  }
   346  
   347  WebPMuxError WebPMuxGetFeatures(const WebPMux* mux, uint32_t* flags) {
   348    if (mux == NULL || flags == NULL) return WEBP_MUX_INVALID_ARGUMENT;
   349    return MuxGetCanvasInfo(mux, NULL, NULL, flags);
   350  }
   351  
   352  static uint8_t* EmitVP8XChunk(uint8_t* const dst, int width,
   353                                int height, uint32_t flags) {
   354    const size_t vp8x_size = CHUNK_HEADER_SIZE + VP8X_CHUNK_SIZE;
   355    assert(width >= 1 && height >= 1);
   356    assert(width <= MAX_CANVAS_SIZE && height <= MAX_CANVAS_SIZE);
   357    assert(width * (uint64_t)height < MAX_IMAGE_AREA);
   358    PutLE32(dst, MKFOURCC('V', 'P', '8', 'X'));
   359    PutLE32(dst + TAG_SIZE, VP8X_CHUNK_SIZE);
   360    PutLE32(dst + CHUNK_HEADER_SIZE, flags);
   361    PutLE24(dst + CHUNK_HEADER_SIZE + 4, width - 1);
   362    PutLE24(dst + CHUNK_HEADER_SIZE + 7, height - 1);
   363    return dst + vp8x_size;
   364  }
   365  
   366  // Assemble a single image WebP bitstream from 'wpi'.
   367  static WebPMuxError SynthesizeBitstream(const WebPMuxImage* const wpi,
   368                                          WebPData* const bitstream) {
   369    uint8_t* dst;
   370  
   371    // Allocate data.
   372    const int need_vp8x = (wpi->alpha_ != NULL);
   373    const size_t vp8x_size = need_vp8x ? CHUNK_HEADER_SIZE + VP8X_CHUNK_SIZE : 0;
   374    const size_t alpha_size = need_vp8x ? ChunkDiskSize(wpi->alpha_) : 0;
   375    // Note: No need to output ANMF/FRGM chunk for a single image.
   376    const size_t size = RIFF_HEADER_SIZE + vp8x_size + alpha_size +
   377                        ChunkDiskSize(wpi->img_);
   378    uint8_t* const data = (uint8_t*)malloc(size);
   379    if (data == NULL) return WEBP_MUX_MEMORY_ERROR;
   380  
   381    // Main RIFF header.
   382    dst = MuxEmitRiffHeader(data, size);
   383  
   384    if (need_vp8x) {
   385      dst = EmitVP8XChunk(dst, wpi->width_, wpi->height_, ALPHA_FLAG);  // VP8X.
   386      dst = ChunkListEmit(wpi->alpha_, dst);       // ALPH.
   387    }
   388  
   389    // Bitstream.
   390    dst = ChunkListEmit(wpi->img_, dst);
   391    assert(dst == data + size);
   392  
   393    // Output.
   394    bitstream->bytes = data;
   395    bitstream->size = size;
   396    return WEBP_MUX_OK;
   397  }
   398  
   399  WebPMuxError WebPMuxGetChunk(const WebPMux* mux, const char fourcc[4],
   400                               WebPData* chunk_data) {
   401    CHUNK_INDEX idx;
   402    if (mux == NULL || fourcc == NULL || chunk_data == NULL) {
   403      return WEBP_MUX_INVALID_ARGUMENT;
   404    }
   405    idx = ChunkGetIndexFromFourCC(fourcc);
   406    if (IsWPI(kChunks[idx].id)) {     // An image chunk.
   407      return WEBP_MUX_INVALID_ARGUMENT;
   408    } else if (idx != IDX_UNKNOWN) {  // A known chunk type.
   409      return MuxGet(mux, idx, 1, chunk_data);
   410    } else {                          // An unknown chunk type.
   411      const WebPChunk* const chunk =
   412          ChunkSearchList(mux->unknown_, 1, ChunkGetTagFromFourCC(fourcc));
   413      if (chunk == NULL) return WEBP_MUX_NOT_FOUND;
   414      *chunk_data = chunk->data_;
   415      return WEBP_MUX_OK;
   416    }
   417  }
   418  
   419  static WebPMuxError MuxGetImageInternal(const WebPMuxImage* const wpi,
   420                                          WebPMuxFrameInfo* const info) {
   421    // Set some defaults for unrelated fields.
   422    info->x_offset = 0;
   423    info->y_offset = 0;
   424    info->duration = 1;
   425    info->dispose_method = WEBP_MUX_DISPOSE_NONE;
   426    info->blend_method = WEBP_MUX_BLEND;
   427    // Extract data for related fields.
   428    info->id = ChunkGetIdFromTag(wpi->img_->tag_);
   429    return SynthesizeBitstream(wpi, &info->bitstream);
   430  }
   431  
   432  static WebPMuxError MuxGetFrameFragmentInternal(const WebPMuxImage* const wpi,
   433                                                  WebPMuxFrameInfo* const frame) {
   434    const int is_frame = (wpi->header_->tag_ == kChunks[IDX_ANMF].tag);
   435    const CHUNK_INDEX idx = is_frame ? IDX_ANMF : IDX_FRGM;
   436    const WebPData* frame_frgm_data;
   437  #ifndef WEBP_EXPERIMENTAL_FEATURES
   438    if (!is_frame) return WEBP_MUX_INVALID_ARGUMENT;
   439  #endif
   440    assert(wpi->header_ != NULL);  // Already checked by WebPMuxGetFrame().
   441    // Get frame/fragment chunk.
   442    frame_frgm_data = &wpi->header_->data_;
   443    if (frame_frgm_data->size < kChunks[idx].size) return WEBP_MUX_BAD_DATA;
   444    // Extract info.
   445    frame->x_offset = 2 * GetLE24(frame_frgm_data->bytes + 0);
   446    frame->y_offset = 2 * GetLE24(frame_frgm_data->bytes + 3);
   447    if (is_frame) {
   448      const uint8_t bits = frame_frgm_data->bytes[15];
   449      frame->duration = GetLE24(frame_frgm_data->bytes + 12);
   450      frame->dispose_method =
   451          (bits & 1) ? WEBP_MUX_DISPOSE_BACKGROUND : WEBP_MUX_DISPOSE_NONE;
   452      frame->blend_method = (bits & 2) ? WEBP_MUX_NO_BLEND : WEBP_MUX_BLEND;
   453    } else {  // Defaults for unused values.
   454      frame->duration = 1;
   455      frame->dispose_method = WEBP_MUX_DISPOSE_NONE;
   456      frame->blend_method = WEBP_MUX_BLEND;
   457    }
   458    frame->id = ChunkGetIdFromTag(wpi->header_->tag_);
   459    return SynthesizeBitstream(wpi, &frame->bitstream);
   460  }
   461  
   462  WebPMuxError WebPMuxGetFrame(
   463      const WebPMux* mux, uint32_t nth, WebPMuxFrameInfo* frame) {
   464    WebPMuxError err;
   465    WebPMuxImage* wpi;
   466  
   467    // Sanity checks.
   468    if (mux == NULL || frame == NULL) {
   469      return WEBP_MUX_INVALID_ARGUMENT;
   470    }
   471  
   472    // Get the nth WebPMuxImage.
   473    err = MuxImageGetNth((const WebPMuxImage**)&mux->images_, nth, &wpi);
   474    if (err != WEBP_MUX_OK) return err;
   475  
   476    // Get frame info.
   477    if (wpi->header_ == NULL) {
   478      return MuxGetImageInternal(wpi, frame);
   479    } else {
   480      return MuxGetFrameFragmentInternal(wpi, frame);
   481    }
   482  }
   483  
   484  WebPMuxError WebPMuxGetAnimationParams(const WebPMux* mux,
   485                                         WebPMuxAnimParams* params) {
   486    WebPData anim;
   487    WebPMuxError err;
   488  
   489    if (mux == NULL || params == NULL) return WEBP_MUX_INVALID_ARGUMENT;
   490  
   491    err = MuxGet(mux, IDX_ANIM, 1, &anim);
   492    if (err != WEBP_MUX_OK) return err;
   493    if (anim.size < kChunks[WEBP_CHUNK_ANIM].size) return WEBP_MUX_BAD_DATA;
   494    params->bgcolor = GetLE32(anim.bytes);
   495    params->loop_count = GetLE16(anim.bytes + 4);
   496  
   497    return WEBP_MUX_OK;
   498  }
   499  
   500  // Get chunk index from chunk id. Returns IDX_NIL if not found.
   501  static CHUNK_INDEX ChunkGetIndexFromId(WebPChunkId id) {
   502    int i;
   503    for (i = 0; kChunks[i].id != WEBP_CHUNK_NIL; ++i) {
   504      if (id == kChunks[i].id) return (CHUNK_INDEX)i;
   505    }
   506    return IDX_NIL;
   507  }
   508  
   509  // Count number of chunks matching 'tag' in the 'chunk_list'.
   510  // If tag == NIL_TAG, any tag will be matched.
   511  static int CountChunks(const WebPChunk* const chunk_list, uint32_t tag) {
   512    int count = 0;
   513    const WebPChunk* current;
   514    for (current = chunk_list; current != NULL; current = current->next_) {
   515      if (tag == NIL_TAG || current->tag_ == tag) {
   516        count++;  // Count chunks whose tags match.
   517      }
   518    }
   519    return count;
   520  }
   521  
   522  WebPMuxError WebPMuxNumChunks(const WebPMux* mux,
   523                                WebPChunkId id, int* num_elements) {
   524    if (mux == NULL || num_elements == NULL) {
   525      return WEBP_MUX_INVALID_ARGUMENT;
   526    }
   527  
   528    if (IsWPI(id)) {
   529      *num_elements = MuxImageCount(mux->images_, id);
   530    } else {
   531      WebPChunk* const* chunk_list = MuxGetChunkListFromId(mux, id);
   532      const CHUNK_INDEX idx = ChunkGetIndexFromId(id);
   533      *num_elements = CountChunks(*chunk_list, kChunks[idx].tag);
   534    }
   535  
   536    return WEBP_MUX_OK;
   537  }
   538  
   539  //------------------------------------------------------------------------------
   540