muxread.c 19 KB

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