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