/src/libwebp/src/mux/muxinternal.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 | | // Internal objects and utils for mux. |
11 | | // |
12 | | // Authors: Urvang (urvang@google.com) |
13 | | // Vikas (vikasa@google.com) |
14 | | |
15 | | #include <assert.h> |
16 | | #include <stddef.h> |
17 | | #include <string.h> |
18 | | |
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 | | #define UNDEFINED_CHUNK_SIZE ((uint32_t)(-1)) |
27 | | |
28 | | const ChunkInfo kChunks[] = { |
29 | | {MKFOURCC('V', 'P', '8', 'X'), WEBP_CHUNK_VP8X, VP8X_CHUNK_SIZE}, |
30 | | {MKFOURCC('I', 'C', 'C', 'P'), WEBP_CHUNK_ICCP, UNDEFINED_CHUNK_SIZE}, |
31 | | {MKFOURCC('A', 'N', 'I', 'M'), WEBP_CHUNK_ANIM, ANIM_CHUNK_SIZE}, |
32 | | {MKFOURCC('A', 'N', 'M', 'F'), WEBP_CHUNK_ANMF, ANMF_CHUNK_SIZE}, |
33 | | {MKFOURCC('A', 'L', 'P', 'H'), WEBP_CHUNK_ALPHA, UNDEFINED_CHUNK_SIZE}, |
34 | | {MKFOURCC('V', 'P', '8', ' '), WEBP_CHUNK_IMAGE, UNDEFINED_CHUNK_SIZE}, |
35 | | {MKFOURCC('V', 'P', '8', 'L'), WEBP_CHUNK_IMAGE, UNDEFINED_CHUNK_SIZE}, |
36 | | {MKFOURCC('E', 'X', 'I', 'F'), WEBP_CHUNK_EXIF, UNDEFINED_CHUNK_SIZE}, |
37 | | {MKFOURCC('X', 'M', 'P', ' '), WEBP_CHUNK_XMP, UNDEFINED_CHUNK_SIZE}, |
38 | | {NIL_TAG, WEBP_CHUNK_UNKNOWN, UNDEFINED_CHUNK_SIZE}, |
39 | | |
40 | | {NIL_TAG, WEBP_CHUNK_NIL, UNDEFINED_CHUNK_SIZE}}; |
41 | | |
42 | | //------------------------------------------------------------------------------ |
43 | | |
44 | 0 | int WebPGetMuxVersion(void) { |
45 | 0 | return (MUX_MAJ_VERSION << 16) | (MUX_MIN_VERSION << 8) | MUX_REV_VERSION; |
46 | 0 | } |
47 | | |
48 | | //------------------------------------------------------------------------------ |
49 | | // Life of a chunk object. |
50 | | |
51 | 285k | void ChunkInit(WebPChunk* const chunk) { |
52 | 285k | assert(chunk); |
53 | 285k | memset(chunk, 0, sizeof(*chunk)); |
54 | 285k | chunk->tag = NIL_TAG; |
55 | 285k | } |
56 | | |
57 | 128k | WebPChunk* ChunkRelease(WebPChunk* const chunk) { |
58 | 128k | WebPChunk* next; |
59 | 128k | if (chunk == NULL) return NULL; |
60 | 128k | if (chunk->owner) { |
61 | 282 | WebPDataClear(&chunk->data); |
62 | 282 | } |
63 | 128k | next = chunk->next; |
64 | 128k | ChunkInit(chunk); |
65 | 128k | return next; |
66 | 128k | } |
67 | | |
68 | | //------------------------------------------------------------------------------ |
69 | | // Chunk misc methods. |
70 | | |
71 | 141 | CHUNK_INDEX ChunkGetIndexFromTag(uint32_t tag) { |
72 | 141 | int i; |
73 | 799 | for (i = 0; kChunks[i].tag != NIL_TAG; ++i) { |
74 | 794 | if (tag == kChunks[i].tag) return (CHUNK_INDEX)i; |
75 | 794 | } |
76 | 5 | return IDX_UNKNOWN; |
77 | 141 | } |
78 | | |
79 | 6.88k | WebPChunkId ChunkGetIdFromTag(uint32_t tag) { |
80 | 6.88k | int i; |
81 | 61.9k | for (i = 0; kChunks[i].tag != NIL_TAG; ++i) { |
82 | 56.6k | if (tag == kChunks[i].tag) return kChunks[i].id; |
83 | 56.6k | } |
84 | 5.31k | return WEBP_CHUNK_UNKNOWN; |
85 | 6.88k | } |
86 | | |
87 | 101 | uint32_t ChunkGetTagFromFourCC(const char fourcc[4]) { |
88 | 101 | return MKFOURCC(fourcc[0], fourcc[1], fourcc[2], fourcc[3]); |
89 | 101 | } |
90 | | |
91 | 51 | CHUNK_INDEX ChunkGetIndexFromFourCC(const char fourcc[4]) { |
92 | 51 | const uint32_t tag = ChunkGetTagFromFourCC(fourcc); |
93 | 51 | return ChunkGetIndexFromTag(tag); |
94 | 51 | } |
95 | | |
96 | | //------------------------------------------------------------------------------ |
97 | | // Chunk search methods. |
98 | | |
99 | | // Returns next chunk in the chunk list with the given tag. |
100 | 438 | static WebPChunk* ChunkSearchNextInList(WebPChunk* chunk, uint32_t tag) { |
101 | 445 | while (chunk != NULL && chunk->tag != tag) { |
102 | 7 | chunk = chunk->next; |
103 | 7 | } |
104 | 438 | return chunk; |
105 | 438 | } |
106 | | |
107 | 438 | WebPChunk* ChunkSearchList(WebPChunk* first, uint32_t nth, uint32_t tag) { |
108 | 438 | uint32_t iter = nth; |
109 | 438 | first = ChunkSearchNextInList(first, tag); |
110 | 438 | if (first == NULL) return NULL; |
111 | | |
112 | 266 | while (--iter != 0) { |
113 | 0 | WebPChunk* next_chunk = ChunkSearchNextInList(first->next, tag); |
114 | 0 | if (next_chunk == NULL) break; |
115 | 0 | first = next_chunk; |
116 | 0 | } |
117 | 266 | return ((nth > 0) && (iter > 0)) ? NULL : first; |
118 | 438 | } |
119 | | |
120 | | //------------------------------------------------------------------------------ |
121 | | // Chunk writer methods. |
122 | | |
123 | | WebPMuxError ChunkAssignData(WebPChunk* chunk, const WebPData* const data, |
124 | 6.60k | int copy_data, uint32_t tag) { |
125 | | // For internally allocated chunks, always copy data & make it owner of data. |
126 | 6.60k | if (tag == kChunks[IDX_VP8X].tag || tag == kChunks[IDX_ANIM].tag) { |
127 | 237 | copy_data = 1; |
128 | 237 | } |
129 | | |
130 | 6.60k | ChunkRelease(chunk); |
131 | | |
132 | 6.60k | if (data != NULL) { |
133 | 6.60k | if (copy_data) { // Copy data. |
134 | 282 | if (!WebPDataCopy(data, &chunk->data)) return WEBP_MUX_MEMORY_ERROR; |
135 | 282 | chunk->owner = 1; // Chunk is owner of data. |
136 | 6.32k | } else { // Don't copy data. |
137 | 6.32k | chunk->data = *data; |
138 | 6.32k | } |
139 | 6.60k | } |
140 | 6.60k | chunk->tag = tag; |
141 | 6.60k | return WEBP_MUX_OK; |
142 | 6.60k | } |
143 | | |
144 | | WebPMuxError ChunkSetHead(WebPChunk* const chunk, |
145 | 6.48k | WebPChunk** const chunk_list) { |
146 | 6.48k | WebPChunk* new_chunk; |
147 | | |
148 | 6.48k | assert(chunk_list != NULL); |
149 | 6.48k | if (*chunk_list != NULL) { |
150 | 0 | return WEBP_MUX_NOT_FOUND; |
151 | 0 | } |
152 | | |
153 | 6.48k | new_chunk = (WebPChunk*)WebPSafeMalloc(1ULL, sizeof(*new_chunk)); |
154 | 6.48k | if (new_chunk == NULL) return WEBP_MUX_MEMORY_ERROR; |
155 | 6.48k | *new_chunk = *chunk; |
156 | 6.48k | chunk->owner = 0; |
157 | 6.48k | new_chunk->next = NULL; |
158 | 6.48k | *chunk_list = new_chunk; |
159 | 6.48k | return WEBP_MUX_OK; |
160 | 6.48k | } |
161 | | |
162 | | WebPMuxError ChunkAppend(WebPChunk* const chunk, |
163 | 5.56k | WebPChunk*** const chunk_list) { |
164 | 5.56k | WebPMuxError err; |
165 | 5.56k | assert(chunk_list != NULL && *chunk_list != NULL); |
166 | | |
167 | 5.56k | if (**chunk_list == NULL) { |
168 | 444 | err = ChunkSetHead(chunk, *chunk_list); |
169 | 5.12k | } else { |
170 | 5.12k | WebPChunk* last_chunk = **chunk_list; |
171 | 5.12k | while (last_chunk->next != NULL) last_chunk = last_chunk->next; |
172 | 5.12k | err = ChunkSetHead(chunk, &last_chunk->next); |
173 | 5.12k | if (err == WEBP_MUX_OK) *chunk_list = &last_chunk->next; |
174 | 5.12k | } |
175 | 5.56k | return err; |
176 | 5.56k | } |
177 | | |
178 | | //------------------------------------------------------------------------------ |
179 | | // Chunk deletion method(s). |
180 | | |
181 | 6.48k | WebPChunk* ChunkDelete(WebPChunk* const chunk) { |
182 | 6.48k | WebPChunk* const next = ChunkRelease(chunk); |
183 | 6.48k | WebPSafeFree(chunk); |
184 | 6.48k | return next; |
185 | 6.48k | } |
186 | | |
187 | 695k | void ChunkListDelete(WebPChunk** const chunk_list) { |
188 | 702k | while (*chunk_list != NULL) { |
189 | 6.47k | *chunk_list = ChunkDelete(*chunk_list); |
190 | 6.47k | } |
191 | 695k | } |
192 | | |
193 | | //------------------------------------------------------------------------------ |
194 | | // Chunk serialization methods. |
195 | | |
196 | 135 | static uint8_t* ChunkEmit(const WebPChunk* const chunk, uint8_t* dst) { |
197 | 135 | const size_t chunk_size = chunk->data.size; |
198 | 135 | assert(chunk); |
199 | 135 | assert(chunk->tag != NIL_TAG); |
200 | 135 | PutLE32(dst + 0, chunk->tag); |
201 | 135 | PutLE32(dst + TAG_SIZE, (uint32_t)chunk_size); |
202 | 135 | assert(chunk_size == (uint32_t)chunk_size); |
203 | 135 | memcpy(dst + CHUNK_HEADER_SIZE, chunk->data.bytes, chunk_size); |
204 | 135 | if (chunk_size & 1) dst[CHUNK_HEADER_SIZE + chunk_size] = 0; // Add padding. |
205 | 135 | return dst + ChunkDiskSize(chunk); |
206 | 135 | } |
207 | | |
208 | 270 | uint8_t* ChunkListEmit(const WebPChunk* chunk_list, uint8_t* dst) { |
209 | 360 | while (chunk_list != NULL) { |
210 | 90 | dst = ChunkEmit(chunk_list, dst); |
211 | 90 | chunk_list = chunk_list->next; |
212 | 90 | } |
213 | 270 | return dst; |
214 | 270 | } |
215 | | |
216 | 270 | size_t ChunkListDiskSize(const WebPChunk* chunk_list) { |
217 | 270 | size_t size = 0; |
218 | 360 | while (chunk_list != NULL) { |
219 | 90 | size += ChunkDiskSize(chunk_list); |
220 | 90 | chunk_list = chunk_list->next; |
221 | 90 | } |
222 | 270 | return size; |
223 | 270 | } |
224 | | |
225 | | //------------------------------------------------------------------------------ |
226 | | // Life of a MuxImage object. |
227 | | |
228 | 2.32k | void MuxImageInit(WebPMuxImage* const wpi) { |
229 | 2.32k | assert(wpi); |
230 | 2.32k | memset(wpi, 0, sizeof(*wpi)); |
231 | 2.32k | } |
232 | | |
233 | 115k | WebPMuxImage* MuxImageRelease(WebPMuxImage* const wpi) { |
234 | 115k | WebPMuxImage* next; |
235 | 115k | if (wpi == NULL) return NULL; |
236 | | // There should be at most one chunk of 'header', 'alpha', 'img' but we call |
237 | | // ChunkListDelete to be safe |
238 | 1.16k | ChunkListDelete(&wpi->header); |
239 | 1.16k | ChunkListDelete(&wpi->alpha); |
240 | 1.16k | ChunkListDelete(&wpi->img); |
241 | 1.16k | ChunkListDelete(&wpi->unknown); |
242 | | |
243 | 1.16k | next = wpi->next; |
244 | 1.16k | MuxImageInit(wpi); |
245 | 1.16k | return next; |
246 | 115k | } |
247 | | |
248 | | //------------------------------------------------------------------------------ |
249 | | // MuxImage search methods. |
250 | | |
251 | | // Get a reference to appropriate chunk list within an image given chunk tag. |
252 | | static WebPChunk** GetChunkListFromId(const WebPMuxImage* const wpi, |
253 | 1.00k | WebPChunkId id) { |
254 | 1.00k | assert(wpi != NULL); |
255 | 1.00k | switch (id) { |
256 | 534 | case WEBP_CHUNK_ANMF: |
257 | 534 | return (WebPChunk**)&wpi->header; |
258 | 63 | case WEBP_CHUNK_ALPHA: |
259 | 63 | return (WebPChunk**)&wpi->alpha; |
260 | 407 | case WEBP_CHUNK_IMAGE: |
261 | 407 | return (WebPChunk**)&wpi->img; |
262 | 0 | default: |
263 | 0 | return NULL; |
264 | 1.00k | } |
265 | 1.00k | } |
266 | | |
267 | 890 | int MuxImageCount(const WebPMuxImage* wpi_list, WebPChunkId id) { |
268 | 890 | int count = 0; |
269 | 890 | const WebPMuxImage* current; |
270 | 1.89k | for (current = wpi_list; current != NULL; current = current->next) { |
271 | 1.00k | if (id == WEBP_CHUNK_NIL) { |
272 | 0 | ++count; // Special case: count all images. |
273 | 1.00k | } else { |
274 | 1.00k | const WebPChunk* const wpi_chunk = *GetChunkListFromId(current, id); |
275 | 1.00k | if (wpi_chunk != NULL) { |
276 | 428 | const WebPChunkId wpi_chunk_id = ChunkGetIdFromTag(wpi_chunk->tag); |
277 | 428 | if (wpi_chunk_id == id) ++count; // Count images with a matching 'id'. |
278 | 428 | } |
279 | 1.00k | } |
280 | 1.00k | } |
281 | 890 | return count; |
282 | 890 | } |
283 | | |
284 | | // Outputs a pointer to 'prev_wpi->next', |
285 | | // where 'prev_wpi' is the pointer to the image at position (nth - 1). |
286 | | // Returns true if nth image was found. |
287 | | static int SearchImageToGetOrDelete(WebPMuxImage** wpi_list, uint32_t nth, |
288 | 0 | WebPMuxImage*** const location) { |
289 | 0 | uint32_t count = 0; |
290 | 0 | assert(wpi_list); |
291 | 0 | *location = wpi_list; |
292 | |
|
293 | 0 | if (nth == 0) { |
294 | 0 | nth = MuxImageCount(*wpi_list, WEBP_CHUNK_NIL); |
295 | 0 | if (nth == 0) return 0; // Not found. |
296 | 0 | } |
297 | | |
298 | 0 | while (*wpi_list != NULL) { |
299 | 0 | WebPMuxImage* const cur_wpi = *wpi_list; |
300 | 0 | ++count; |
301 | 0 | if (count == nth) return 1; // Found. |
302 | 0 | wpi_list = &cur_wpi->next; |
303 | 0 | *location = wpi_list; |
304 | 0 | } |
305 | 0 | return 0; // Not found. |
306 | 0 | } |
307 | | |
308 | | //------------------------------------------------------------------------------ |
309 | | // MuxImage writer methods. |
310 | | |
311 | 661 | WebPMuxError MuxImagePush(const WebPMuxImage* wpi, WebPMuxImage** wpi_list) { |
312 | 661 | WebPMuxImage* new_wpi; |
313 | | |
314 | 837 | while (*wpi_list != NULL) { |
315 | 294 | WebPMuxImage* const cur_wpi = *wpi_list; |
316 | 294 | if (cur_wpi->next == NULL) break; |
317 | 176 | wpi_list = &cur_wpi->next; |
318 | 176 | } |
319 | | |
320 | 661 | new_wpi = (WebPMuxImage*)WebPSafeMalloc(1ULL, sizeof(*new_wpi)); |
321 | 661 | if (new_wpi == NULL) return WEBP_MUX_MEMORY_ERROR; |
322 | 661 | *new_wpi = *wpi; |
323 | 661 | new_wpi->next = NULL; |
324 | | |
325 | 661 | if (*wpi_list != NULL) { |
326 | 118 | (*wpi_list)->next = new_wpi; |
327 | 543 | } else { |
328 | 543 | *wpi_list = new_wpi; |
329 | 543 | } |
330 | 661 | return WEBP_MUX_OK; |
331 | 661 | } |
332 | | |
333 | | //------------------------------------------------------------------------------ |
334 | | // MuxImage deletion methods. |
335 | | |
336 | 115k | WebPMuxImage* MuxImageDelete(WebPMuxImage* const wpi) { |
337 | | // Delete the components of wpi. If wpi is NULL this is a noop. |
338 | 115k | WebPMuxImage* const next = MuxImageRelease(wpi); |
339 | 115k | WebPSafeFree(wpi); |
340 | 115k | return next; |
341 | 115k | } |
342 | | |
343 | 0 | WebPMuxError MuxImageDeleteNth(WebPMuxImage** wpi_list, uint32_t nth) { |
344 | 0 | assert(wpi_list); |
345 | 0 | if (!SearchImageToGetOrDelete(wpi_list, nth, &wpi_list)) { |
346 | 0 | return WEBP_MUX_NOT_FOUND; |
347 | 0 | } |
348 | 0 | *wpi_list = MuxImageDelete(*wpi_list); |
349 | 0 | return WEBP_MUX_OK; |
350 | 0 | } |
351 | | |
352 | | //------------------------------------------------------------------------------ |
353 | | // MuxImage reader methods. |
354 | | |
355 | | WebPMuxError MuxImageGetNth(const WebPMuxImage** wpi_list, uint32_t nth, |
356 | 0 | WebPMuxImage** wpi) { |
357 | 0 | assert(wpi_list); |
358 | 0 | assert(wpi); |
359 | 0 | if (!SearchImageToGetOrDelete((WebPMuxImage**)wpi_list, nth, |
360 | 0 | (WebPMuxImage***)&wpi_list)) { |
361 | 0 | return WEBP_MUX_NOT_FOUND; |
362 | 0 | } |
363 | 0 | *wpi = (WebPMuxImage*)*wpi_list; |
364 | 0 | return WEBP_MUX_OK; |
365 | 0 | } |
366 | | |
367 | | //------------------------------------------------------------------------------ |
368 | | // MuxImage serialization methods. |
369 | | |
370 | | // Size of an image. |
371 | 45 | size_t MuxImageDiskSize(const WebPMuxImage* const wpi) { |
372 | 45 | size_t size = 0; |
373 | 45 | if (wpi->header != NULL) size += ChunkDiskSize(wpi->header); |
374 | 45 | if (wpi->alpha != NULL) size += ChunkDiskSize(wpi->alpha); |
375 | 45 | if (wpi->img != NULL) size += ChunkDiskSize(wpi->img); |
376 | 45 | if (wpi->unknown != NULL) size += ChunkListDiskSize(wpi->unknown); |
377 | 45 | return size; |
378 | 45 | } |
379 | | |
380 | | // Special case as ANMF chunk encapsulates other image chunks. |
381 | | static uint8_t* ChunkEmitSpecial(const WebPChunk* const header, |
382 | 0 | size_t total_size, uint8_t* dst) { |
383 | 0 | const size_t header_size = header->data.size; |
384 | 0 | const size_t offset_to_next = total_size - CHUNK_HEADER_SIZE; |
385 | 0 | assert(header->tag == kChunks[IDX_ANMF].tag); |
386 | 0 | PutLE32(dst + 0, header->tag); |
387 | 0 | PutLE32(dst + TAG_SIZE, (uint32_t)offset_to_next); |
388 | 0 | assert(header_size == (uint32_t)header_size); |
389 | 0 | memcpy(dst + CHUNK_HEADER_SIZE, header->data.bytes, header_size); |
390 | 0 | if (header_size & 1) { |
391 | 0 | dst[CHUNK_HEADER_SIZE + header_size] = 0; // Add padding. |
392 | 0 | } |
393 | 0 | return dst + ChunkDiskSize(header); |
394 | 0 | } |
395 | | |
396 | 45 | uint8_t* MuxImageEmit(const WebPMuxImage* const wpi, uint8_t* dst) { |
397 | | // Ordering of chunks to be emitted is strictly as follows: |
398 | | // 1. ANMF chunk (if present). |
399 | | // 2. ALPH chunk (if present). |
400 | | // 3. VP8/VP8L chunk. |
401 | 45 | assert(wpi); |
402 | 45 | if (wpi->header != NULL) { |
403 | 0 | dst = ChunkEmitSpecial(wpi->header, MuxImageDiskSize(wpi), dst); |
404 | 0 | } |
405 | 45 | if (wpi->alpha != NULL) dst = ChunkEmit(wpi->alpha, dst); |
406 | 45 | if (wpi->img != NULL) dst = ChunkEmit(wpi->img, dst); |
407 | 45 | if (wpi->unknown != NULL) dst = ChunkListEmit(wpi->unknown, dst); |
408 | 45 | return dst; |
409 | 45 | } |
410 | | |
411 | | //------------------------------------------------------------------------------ |
412 | | // Helper methods for mux. |
413 | | |
414 | 248 | int MuxHasAlpha(const WebPMuxImage* images) { |
415 | 469 | while (images != NULL) { |
416 | 248 | if (images->has_alpha) return 1; |
417 | 221 | images = images->next; |
418 | 221 | } |
419 | 221 | return 0; |
420 | 248 | } |
421 | | |
422 | 45 | uint8_t* MuxEmitRiffHeader(uint8_t* const data, size_t size) { |
423 | 45 | PutLE32(data + 0, MKFOURCC('R', 'I', 'F', 'F')); |
424 | 45 | PutLE32(data + TAG_SIZE, (uint32_t)size - CHUNK_HEADER_SIZE); |
425 | 45 | assert(size == (uint32_t)size); |
426 | 45 | PutLE32(data + TAG_SIZE + CHUNK_SIZE_BYTES, MKFOURCC('W', 'E', 'B', 'P')); |
427 | 45 | return data + RIFF_HEADER_SIZE; |
428 | 45 | } |
429 | | |
430 | 1.78k | WebPChunk** MuxGetChunkListFromId(const WebPMux* mux, WebPChunkId id) { |
431 | 1.78k | assert(mux != NULL); |
432 | 1.78k | switch (id) { |
433 | 394 | case WEBP_CHUNK_VP8X: |
434 | 394 | return (WebPChunk**)&mux->vp8x; |
435 | 264 | case WEBP_CHUNK_ICCP: |
436 | 264 | return (WebPChunk**)&mux->iccp; |
437 | 308 | case WEBP_CHUNK_ANIM: |
438 | 308 | return (WebPChunk**)&mux->anim; |
439 | 348 | case WEBP_CHUNK_EXIF: |
440 | 348 | return (WebPChunk**)&mux->exif; |
441 | 259 | case WEBP_CHUNK_XMP: |
442 | 259 | return (WebPChunk**)&mux->xmp; |
443 | 207 | default: |
444 | 207 | return (WebPChunk**)&mux->unknown; |
445 | 1.78k | } |
446 | 1.78k | } |
447 | | |
448 | 762 | static int IsNotCompatible(int feature, int num_items) { |
449 | 762 | return (feature != 0) != (num_items > 0); |
450 | 762 | } |
451 | | |
452 | 4.21k | #define NO_FLAG ((WebPFeatureFlags)0) |
453 | | |
454 | | // Test basic constraints: |
455 | | // retrieval, maximum number of chunks by index (use -1 to skip) |
456 | | // and feature incompatibility (use NO_FLAG to skip). |
457 | | // On success returns WEBP_MUX_OK and stores the chunk count in *num. |
458 | | static WebPMuxError ValidateChunk(const WebPMux* const mux, CHUNK_INDEX idx, |
459 | | WebPFeatureFlags feature, uint32_t vp8x_flags, |
460 | 1.66k | int max, int* num) { |
461 | 1.66k | const WebPMuxError err = WebPMuxNumChunks(mux, kChunks[idx].id, num); |
462 | 1.66k | if (err != WEBP_MUX_OK) return err; |
463 | 1.66k | if (max > -1 && *num > max) return WEBP_MUX_INVALID_ARGUMENT; |
464 | 1.65k | if (feature != NO_FLAG && IsNotCompatible(vp8x_flags & feature, *num)) { |
465 | 5 | return WEBP_MUX_INVALID_ARGUMENT; |
466 | 5 | } |
467 | 1.65k | return WEBP_MUX_OK; |
468 | 1.65k | } |
469 | | |
470 | 276 | WebPMuxError MuxValidate(const WebPMux* const mux) { |
471 | 276 | int num_iccp; |
472 | 276 | int num_exif; |
473 | 276 | int num_xmp; |
474 | 276 | int num_anim; |
475 | 276 | int num_frames; |
476 | 276 | int num_vp8x; |
477 | 276 | int num_images; |
478 | 276 | int num_alpha; |
479 | 276 | uint32_t flags; |
480 | 276 | WebPMuxError err; |
481 | | |
482 | | // Verify mux is not NULL. |
483 | 276 | if (mux == NULL) return WEBP_MUX_INVALID_ARGUMENT; |
484 | | |
485 | | // Verify mux has at least one image. |
486 | 276 | if (mux->images == NULL) return WEBP_MUX_INVALID_ARGUMENT; |
487 | | |
488 | 276 | err = WebPMuxGetFeatures(mux, &flags); |
489 | 276 | if (err != WEBP_MUX_OK) return err; |
490 | | |
491 | | // At most one color profile chunk. |
492 | 259 | err = ValidateChunk(mux, IDX_ICCP, ICCP_FLAG, flags, 1, &num_iccp); |
493 | 259 | if (err != WEBP_MUX_OK) return err; |
494 | | |
495 | | // At most one EXIF metadata. |
496 | 256 | err = ValidateChunk(mux, IDX_EXIF, EXIF_FLAG, flags, 1, &num_exif); |
497 | 256 | if (err != WEBP_MUX_OK) return err; |
498 | | |
499 | | // At most one XMP metadata. |
500 | 252 | err = ValidateChunk(mux, IDX_XMP, XMP_FLAG, flags, 1, &num_xmp); |
501 | 252 | if (err != WEBP_MUX_OK) return err; |
502 | | |
503 | | // Animation: ANIMATION_FLAG, ANIM chunk and ANMF chunk(s) are consistent. |
504 | | // At most one ANIM chunk. |
505 | 249 | err = ValidateChunk(mux, IDX_ANIM, NO_FLAG, flags, 1, &num_anim); |
506 | 249 | if (err != WEBP_MUX_OK) return err; |
507 | 245 | err = ValidateChunk(mux, IDX_ANMF, NO_FLAG, flags, -1, &num_frames); |
508 | 245 | if (err != WEBP_MUX_OK) return err; |
509 | | |
510 | 245 | { |
511 | 245 | const int has_animation = !!(flags & ANIMATION_FLAG); |
512 | 245 | if (has_animation && (num_anim == 0 || num_frames == 0)) { |
513 | 2 | return WEBP_MUX_INVALID_ARGUMENT; |
514 | 2 | } |
515 | 243 | if (!has_animation && (num_anim == 1 || num_frames > 0)) { |
516 | 7 | return WEBP_MUX_INVALID_ARGUMENT; |
517 | 7 | } |
518 | 236 | if (!has_animation) { |
519 | 236 | const WebPMuxImage* images = mux->images; |
520 | | // There can be only one image. |
521 | 236 | if (images == NULL || images->next != NULL) { |
522 | 8 | return WEBP_MUX_INVALID_ARGUMENT; |
523 | 8 | } |
524 | | // Size must match. |
525 | 228 | if (mux->canvas_width > 0) { |
526 | 87 | if (images->width != mux->canvas_width || |
527 | 71 | images->height != mux->canvas_height) { |
528 | 24 | return WEBP_MUX_INVALID_ARGUMENT; |
529 | 24 | } |
530 | 87 | } |
531 | 228 | } |
532 | 236 | } |
533 | | |
534 | | // Verify either VP8X chunk is present OR there is only one elem in |
535 | | // mux->images. |
536 | 204 | err = ValidateChunk(mux, IDX_VP8X, NO_FLAG, flags, 1, &num_vp8x); |
537 | 204 | if (err != WEBP_MUX_OK) return err; |
538 | 203 | err = ValidateChunk(mux, IDX_VP8, NO_FLAG, flags, -1, &num_images); |
539 | 203 | if (err != WEBP_MUX_OK) return err; |
540 | 203 | if (num_vp8x == 0 && num_images != 1) return WEBP_MUX_INVALID_ARGUMENT; |
541 | | |
542 | | // ALPHA_FLAG & alpha chunk(s) are consistent. |
543 | | // Note: ALPHA_FLAG can be set when there is actually no Alpha data present. |
544 | 203 | if (MuxHasAlpha(mux->images)) { |
545 | 27 | if (num_vp8x > 0) { |
546 | | // VP8X chunk is present, so it should contain ALPHA_FLAG. |
547 | 9 | if (!(flags & ALPHA_FLAG)) return WEBP_MUX_INVALID_ARGUMENT; |
548 | 18 | } else { |
549 | | // VP8X chunk is not present, so ALPH chunks should NOT be present either. |
550 | 18 | err = WebPMuxNumChunks(mux, WEBP_CHUNK_ALPHA, &num_alpha); |
551 | 18 | if (err != WEBP_MUX_OK) return err; |
552 | 18 | if (num_alpha > 0) return WEBP_MUX_INVALID_ARGUMENT; |
553 | 18 | } |
554 | 27 | } |
555 | | |
556 | 201 | return WEBP_MUX_OK; |
557 | 203 | } |
558 | | |
559 | | #undef NO_FLAG |
560 | | |
561 | | //------------------------------------------------------------------------------ |