muxread.c 18 KB

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