• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "src/mux/muxi.h"
17 #include "src/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 
MuxGet(const WebPMux * const mux,CHUNK_INDEX idx,uint32_t nth,WebPData * const data)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   assert(idx != IDX_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.
ChunkVerifyAndAssign(WebPChunk * chunk,const uint8_t * data,size_t data_size,size_t riff_size,int copy_data)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 < CHUNK_HEADER_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 
MuxImageFinalize(WebPMuxImage * const wpi)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 
MuxImageParse(const WebPChunk * const chunk,int copy_data,WebPMuxImage * const wpi)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   assert(!wpi->is_partial_);
109 
110   // ANMF.
111   {
112     const size_t hdr_size = ANMF_CHUNK_SIZE;
113     const WebPData temp = { bytes, hdr_size };
114     // Each of ANMF chunk contain a header at the beginning. So, its size should
115     // be at least 'hdr_size'.
116     if (size < hdr_size) goto Fail;
117     ChunkAssignData(&subchunk, &temp, copy_data, chunk->tag_);
118   }
119   ChunkSetNth(&subchunk, &wpi->header_, 1);
120   wpi->is_partial_ = 1;  // Waiting for ALPH and/or VP8/VP8L chunks.
121 
122   // Rest of the chunks.
123   subchunk_size = ChunkDiskSize(&subchunk) - CHUNK_HEADER_SIZE;
124   bytes += subchunk_size;
125   size -= subchunk_size;
126 
127   while (bytes != last) {
128     ChunkInit(&subchunk);
129     if (ChunkVerifyAndAssign(&subchunk, bytes, size, size,
130                              copy_data) != WEBP_MUX_OK) {
131       goto Fail;
132     }
133     switch (ChunkGetIdFromTag(subchunk.tag_)) {
134       case WEBP_CHUNK_ALPHA:
135         if (wpi->alpha_ != NULL) goto Fail;  // Consecutive ALPH chunks.
136         if (ChunkSetNth(&subchunk, &wpi->alpha_, 1) != WEBP_MUX_OK) goto Fail;
137         wpi->is_partial_ = 1;  // Waiting for a VP8 chunk.
138         break;
139       case WEBP_CHUNK_IMAGE:
140         if (ChunkSetNth(&subchunk, &wpi->img_, 1) != WEBP_MUX_OK) goto Fail;
141         if (!MuxImageFinalize(wpi)) goto Fail;
142         wpi->is_partial_ = 0;  // wpi is completely filled.
143         break;
144       case WEBP_CHUNK_UNKNOWN:
145         if (wpi->is_partial_) goto Fail;  // Encountered an unknown chunk
146                                           // before some image chunks.
147         if (ChunkSetNth(&subchunk, &wpi->unknown_, 0) != WEBP_MUX_OK) goto Fail;
148         break;
149       default:
150         goto Fail;
151         break;
152     }
153     subchunk_size = ChunkDiskSize(&subchunk);
154     bytes += subchunk_size;
155     size -= subchunk_size;
156   }
157   if (wpi->is_partial_) goto Fail;
158   return 1;
159 
160  Fail:
161   ChunkRelease(&subchunk);
162   return 0;
163 }
164 
165 //------------------------------------------------------------------------------
166 // Create a mux object from WebP-RIFF data.
167 
WebPMuxCreateInternal(const WebPData * bitstream,int copy_data,int version)168 WebPMux* WebPMuxCreateInternal(const WebPData* bitstream, int copy_data,
169                                int version) {
170   size_t riff_size;
171   uint32_t tag;
172   const uint8_t* end;
173   WebPMux* mux = NULL;
174   WebPMuxImage* wpi = NULL;
175   const uint8_t* data;
176   size_t size;
177   WebPChunk chunk;
178   ChunkInit(&chunk);
179 
180   // Sanity checks.
181   if (WEBP_ABI_IS_INCOMPATIBLE(version, WEBP_MUX_ABI_VERSION)) {
182     return NULL;  // version mismatch
183   }
184   if (bitstream == NULL) return NULL;
185 
186   data = bitstream->bytes;
187   size = bitstream->size;
188 
189   if (data == NULL) return NULL;
190   if (size < RIFF_HEADER_SIZE) return NULL;
191   if (GetLE32(data + 0) != MKFOURCC('R', 'I', 'F', 'F') ||
192       GetLE32(data + CHUNK_HEADER_SIZE) != MKFOURCC('W', 'E', 'B', 'P')) {
193     return NULL;
194   }
195 
196   mux = WebPMuxNew();
197   if (mux == NULL) return NULL;
198 
199   if (size < RIFF_HEADER_SIZE + TAG_SIZE) goto Err;
200 
201   tag = GetLE32(data + RIFF_HEADER_SIZE);
202   if (tag != kChunks[IDX_VP8].tag &&
203       tag != kChunks[IDX_VP8L].tag &&
204       tag != kChunks[IDX_VP8X].tag) {
205     goto Err;  // First chunk should be VP8, VP8L or VP8X.
206   }
207 
208   riff_size = SizeWithPadding(GetLE32(data + TAG_SIZE));
209   if (riff_size > MAX_CHUNK_PAYLOAD || riff_size > size) {
210     goto Err;
211   } else {
212     if (riff_size < size) {  // Redundant data after last chunk.
213       size = riff_size;  // To make sure we don't read any data beyond mux_size.
214     }
215   }
216 
217   end = data + size;
218   data += RIFF_HEADER_SIZE;
219   size -= RIFF_HEADER_SIZE;
220 
221   wpi = (WebPMuxImage*)WebPSafeMalloc(1ULL, sizeof(*wpi));
222   if (wpi == NULL) goto Err;
223   MuxImageInit(wpi);
224 
225   // Loop over chunks.
226   while (data != end) {
227     size_t data_size;
228     WebPChunkId id;
229     WebPChunk** chunk_list;
230     if (ChunkVerifyAndAssign(&chunk, data, size, riff_size,
231                              copy_data) != WEBP_MUX_OK) {
232       goto Err;
233     }
234     data_size = ChunkDiskSize(&chunk);
235     id = ChunkGetIdFromTag(chunk.tag_);
236     switch (id) {
237       case WEBP_CHUNK_ALPHA:
238         if (wpi->alpha_ != NULL) goto Err;  // Consecutive ALPH chunks.
239         if (ChunkSetNth(&chunk, &wpi->alpha_, 1) != WEBP_MUX_OK) goto Err;
240         wpi->is_partial_ = 1;  // Waiting for a VP8 chunk.
241         break;
242       case WEBP_CHUNK_IMAGE:
243         if (ChunkSetNth(&chunk, &wpi->img_, 1) != WEBP_MUX_OK) goto Err;
244         if (!MuxImageFinalize(wpi)) goto Err;
245         wpi->is_partial_ = 0;  // wpi is completely filled.
246  PushImage:
247         // Add this to mux->images_ list.
248         if (MuxImagePush(wpi, &mux->images_) != WEBP_MUX_OK) goto Err;
249         MuxImageInit(wpi);  // Reset for reading next image.
250         break;
251       case WEBP_CHUNK_ANMF:
252         if (wpi->is_partial_) goto Err;  // Previous wpi is still incomplete.
253         if (!MuxImageParse(&chunk, copy_data, wpi)) goto Err;
254         ChunkRelease(&chunk);
255         goto PushImage;
256         break;
257       default:  // A non-image chunk.
258         if (wpi->is_partial_) goto Err;  // Encountered a non-image chunk before
259                                          // getting all chunks of an image.
260         chunk_list = MuxGetChunkListFromId(mux, id);  // List to add this chunk.
261         if (ChunkSetNth(&chunk, chunk_list, 0) != WEBP_MUX_OK) goto Err;
262         if (id == WEBP_CHUNK_VP8X) {  // grab global specs
263           mux->canvas_width_ = GetLE24(data + 12) + 1;
264           mux->canvas_height_ = GetLE24(data + 15) + 1;
265         }
266         break;
267     }
268     data += data_size;
269     size -= data_size;
270     ChunkInit(&chunk);
271   }
272 
273   // Incomplete image.
274   if (wpi->is_partial_) goto Err;
275 
276   // Validate mux if complete.
277   if (MuxValidate(mux) != WEBP_MUX_OK) goto Err;
278 
279   MuxImageDelete(wpi);
280   return mux;  // All OK;
281 
282  Err:  // Something bad happened.
283   ChunkRelease(&chunk);
284   MuxImageDelete(wpi);
285   WebPMuxDelete(mux);
286   return NULL;
287 }
288 
289 //------------------------------------------------------------------------------
290 // Get API(s).
291 
292 // Validates that the given mux has a single image.
ValidateForSingleImage(const WebPMux * const mux)293 static WebPMuxError ValidateForSingleImage(const WebPMux* const mux) {
294   const int num_images = MuxImageCount(mux->images_, WEBP_CHUNK_IMAGE);
295   const int num_frames = MuxImageCount(mux->images_, WEBP_CHUNK_ANMF);
296 
297   if (num_images == 0) {
298     // No images in mux.
299     return WEBP_MUX_NOT_FOUND;
300   } else if (num_images == 1 && num_frames == 0) {
301     // Valid case (single image).
302     return WEBP_MUX_OK;
303   } else {
304     // Frame case OR an invalid mux.
305     return WEBP_MUX_INVALID_ARGUMENT;
306   }
307 }
308 
309 // Get the canvas width, height and flags after validating that VP8X/VP8/VP8L
310 // chunk and canvas size are valid.
MuxGetCanvasInfo(const WebPMux * const mux,int * width,int * height,uint32_t * flags)311 static WebPMuxError MuxGetCanvasInfo(const WebPMux* const mux,
312                                      int* width, int* height, uint32_t* flags) {
313   int w, h;
314   uint32_t f = 0;
315   WebPData data;
316   assert(mux != NULL);
317 
318   // Check if VP8X chunk is present.
319   if (MuxGet(mux, IDX_VP8X, 1, &data) == WEBP_MUX_OK) {
320     if (data.size < VP8X_CHUNK_SIZE) return WEBP_MUX_BAD_DATA;
321     f = GetLE32(data.bytes + 0);
322     w = GetLE24(data.bytes + 4) + 1;
323     h = GetLE24(data.bytes + 7) + 1;
324   } else {
325     const WebPMuxImage* const wpi = mux->images_;
326     // Grab user-forced canvas size as default.
327     w = mux->canvas_width_;
328     h = mux->canvas_height_;
329     if (w == 0 && h == 0 && ValidateForSingleImage(mux) == WEBP_MUX_OK) {
330       // single image and not forced canvas size => use dimension of first frame
331       assert(wpi != NULL);
332       w = wpi->width_;
333       h = wpi->height_;
334     }
335     if (wpi != NULL) {
336       if (wpi->has_alpha_) f |= ALPHA_FLAG;
337     }
338   }
339   if (w * (uint64_t)h >= MAX_IMAGE_AREA) return WEBP_MUX_BAD_DATA;
340 
341   if (width != NULL) *width = w;
342   if (height != NULL) *height = h;
343   if (flags != NULL) *flags = f;
344   return WEBP_MUX_OK;
345 }
346 
WebPMuxGetCanvasSize(const WebPMux * mux,int * width,int * height)347 WebPMuxError WebPMuxGetCanvasSize(const WebPMux* mux, int* width, int* height) {
348   if (mux == NULL || width == NULL || height == NULL) {
349     return WEBP_MUX_INVALID_ARGUMENT;
350   }
351   return MuxGetCanvasInfo(mux, width, height, NULL);
352 }
353 
WebPMuxGetFeatures(const WebPMux * mux,uint32_t * flags)354 WebPMuxError WebPMuxGetFeatures(const WebPMux* mux, uint32_t* flags) {
355   if (mux == NULL || flags == NULL) return WEBP_MUX_INVALID_ARGUMENT;
356   return MuxGetCanvasInfo(mux, NULL, NULL, flags);
357 }
358 
EmitVP8XChunk(uint8_t * const dst,int width,int height,uint32_t flags)359 static uint8_t* EmitVP8XChunk(uint8_t* const dst, int width,
360                               int height, uint32_t flags) {
361   const size_t vp8x_size = CHUNK_HEADER_SIZE + VP8X_CHUNK_SIZE;
362   assert(width >= 1 && height >= 1);
363   assert(width <= MAX_CANVAS_SIZE && height <= MAX_CANVAS_SIZE);
364   assert(width * (uint64_t)height < MAX_IMAGE_AREA);
365   PutLE32(dst, MKFOURCC('V', 'P', '8', 'X'));
366   PutLE32(dst + TAG_SIZE, VP8X_CHUNK_SIZE);
367   PutLE32(dst + CHUNK_HEADER_SIZE, flags);
368   PutLE24(dst + CHUNK_HEADER_SIZE + 4, width - 1);
369   PutLE24(dst + CHUNK_HEADER_SIZE + 7, height - 1);
370   return dst + vp8x_size;
371 }
372 
373 // Assemble a single image WebP bitstream from 'wpi'.
SynthesizeBitstream(const WebPMuxImage * const wpi,WebPData * const bitstream)374 static WebPMuxError SynthesizeBitstream(const WebPMuxImage* const wpi,
375                                         WebPData* const bitstream) {
376   uint8_t* dst;
377 
378   // Allocate data.
379   const int need_vp8x = (wpi->alpha_ != NULL);
380   const size_t vp8x_size = need_vp8x ? CHUNK_HEADER_SIZE + VP8X_CHUNK_SIZE : 0;
381   const size_t alpha_size = need_vp8x ? ChunkDiskSize(wpi->alpha_) : 0;
382   // Note: No need to output ANMF chunk for a single image.
383   const size_t size = RIFF_HEADER_SIZE + vp8x_size + alpha_size +
384                       ChunkDiskSize(wpi->img_);
385   uint8_t* const data = (uint8_t*)WebPSafeMalloc(1ULL, size);
386   if (data == NULL) return WEBP_MUX_MEMORY_ERROR;
387 
388   // Main RIFF header.
389   dst = MuxEmitRiffHeader(data, size);
390 
391   if (need_vp8x) {
392     dst = EmitVP8XChunk(dst, wpi->width_, wpi->height_, ALPHA_FLAG);  // VP8X.
393     dst = ChunkListEmit(wpi->alpha_, dst);       // ALPH.
394   }
395 
396   // Bitstream.
397   dst = ChunkListEmit(wpi->img_, dst);
398   assert(dst == data + size);
399 
400   // Output.
401   bitstream->bytes = data;
402   bitstream->size = size;
403   return WEBP_MUX_OK;
404 }
405 
WebPMuxGetChunk(const WebPMux * mux,const char fourcc[4],WebPData * chunk_data)406 WebPMuxError WebPMuxGetChunk(const WebPMux* mux, const char fourcc[4],
407                              WebPData* chunk_data) {
408   CHUNK_INDEX idx;
409   if (mux == NULL || fourcc == NULL || chunk_data == NULL) {
410     return WEBP_MUX_INVALID_ARGUMENT;
411   }
412   idx = ChunkGetIndexFromFourCC(fourcc);
413   if (IsWPI(kChunks[idx].id)) {     // An image chunk.
414     return WEBP_MUX_INVALID_ARGUMENT;
415   } else if (idx != IDX_UNKNOWN) {  // A known chunk type.
416     return MuxGet(mux, idx, 1, chunk_data);
417   } else {                          // An unknown chunk type.
418     const WebPChunk* const chunk =
419         ChunkSearchList(mux->unknown_, 1, ChunkGetTagFromFourCC(fourcc));
420     if (chunk == NULL) return WEBP_MUX_NOT_FOUND;
421     *chunk_data = chunk->data_;
422     return WEBP_MUX_OK;
423   }
424 }
425 
MuxGetImageInternal(const WebPMuxImage * const wpi,WebPMuxFrameInfo * const info)426 static WebPMuxError MuxGetImageInternal(const WebPMuxImage* const wpi,
427                                         WebPMuxFrameInfo* const info) {
428   // Set some defaults for unrelated fields.
429   info->x_offset = 0;
430   info->y_offset = 0;
431   info->duration = 1;
432   info->dispose_method = WEBP_MUX_DISPOSE_NONE;
433   info->blend_method = WEBP_MUX_BLEND;
434   // Extract data for related fields.
435   info->id = ChunkGetIdFromTag(wpi->img_->tag_);
436   return SynthesizeBitstream(wpi, &info->bitstream);
437 }
438 
MuxGetFrameInternal(const WebPMuxImage * const wpi,WebPMuxFrameInfo * const frame)439 static WebPMuxError MuxGetFrameInternal(const WebPMuxImage* const wpi,
440                                         WebPMuxFrameInfo* const frame) {
441   const int is_frame = (wpi->header_->tag_ == kChunks[IDX_ANMF].tag);
442   const WebPData* frame_data;
443   if (!is_frame) return WEBP_MUX_INVALID_ARGUMENT;
444   assert(wpi->header_ != NULL);  // Already checked by WebPMuxGetFrame().
445   // Get frame chunk.
446   frame_data = &wpi->header_->data_;
447   if (frame_data->size < kChunks[IDX_ANMF].size) return WEBP_MUX_BAD_DATA;
448   // Extract info.
449   frame->x_offset = 2 * GetLE24(frame_data->bytes + 0);
450   frame->y_offset = 2 * GetLE24(frame_data->bytes + 3);
451   {
452     const uint8_t bits = frame_data->bytes[15];
453     frame->duration = GetLE24(frame_data->bytes + 12);
454     frame->dispose_method =
455         (bits & 1) ? WEBP_MUX_DISPOSE_BACKGROUND : WEBP_MUX_DISPOSE_NONE;
456     frame->blend_method = (bits & 2) ? WEBP_MUX_NO_BLEND : WEBP_MUX_BLEND;
457   }
458   frame->id = ChunkGetIdFromTag(wpi->header_->tag_);
459   return SynthesizeBitstream(wpi, &frame->bitstream);
460 }
461 
WebPMuxGetFrame(const WebPMux * mux,uint32_t nth,WebPMuxFrameInfo * frame)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 MuxGetFrameInternal(wpi, frame);
481   }
482 }
483 
WebPMuxGetAnimationParams(const WebPMux * mux,WebPMuxAnimParams * params)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.
ChunkGetIndexFromId(WebPChunkId id)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.
CountChunks(const WebPChunk * const chunk_list,uint32_t tag)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 
WebPMuxNumChunks(const WebPMux * mux,WebPChunkId id,int * num_elements)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