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