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 | // Set and delete 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 | // Life of a mux object. |
21 | |
22 | static void MuxInit(WebPMux* const mux) { |
23 | assert(mux != NULL); |
24 | memset(mux, 0, sizeof(*mux)); |
25 | mux->canvas_width_ = 0; // just to be explicit |
26 | mux->canvas_height_ = 0; |
27 | } |
28 | |
29 | WebPMux* WebPNewInternal(int version) { |
30 | if (WEBP_ABI_IS_INCOMPATIBLE(version, WEBP_MUX_ABI_VERSION)) { |
31 | return NULL; |
32 | } else { |
33 | WebPMux* const mux = (WebPMux*)WebPSafeMalloc(1ULL, sizeof(WebPMux)); |
34 | if (mux != NULL) MuxInit(mux); |
35 | return mux; |
36 | } |
37 | } |
38 | |
39 | // Delete all images in 'wpi_list'. |
40 | static void DeleteAllImages(WebPMuxImage** const wpi_list) { |
41 | while (*wpi_list != NULL) { |
42 | *wpi_list = MuxImageDelete(*wpi_list); |
43 | } |
44 | } |
45 | |
46 | static void MuxRelease(WebPMux* const mux) { |
47 | assert(mux != NULL); |
48 | DeleteAllImages(&mux->images_); |
49 | ChunkListDelete(&mux->vp8x_); |
50 | ChunkListDelete(&mux->iccp_); |
51 | ChunkListDelete(&mux->anim_); |
52 | ChunkListDelete(&mux->exif_); |
53 | ChunkListDelete(&mux->xmp_); |
54 | ChunkListDelete(&mux->unknown_); |
55 | } |
56 | |
57 | void WebPMuxDelete(WebPMux* mux) { |
58 | if (mux != NULL) { |
59 | MuxRelease(mux); |
60 | WebPSafeFree(mux); |
61 | } |
62 | } |
63 | |
64 | //------------------------------------------------------------------------------ |
65 | // Helper method(s). |
66 | |
67 | // Handy MACRO, makes MuxSet() very symmetric to MuxGet(). |
68 | #define SWITCH_ID_LIST(INDEX, LIST) \ |
69 | if (idx == (INDEX)) { \ |
70 | err = ChunkAssignData(&chunk, data, copy_data, tag); \ |
71 | if (err == WEBP_MUX_OK) { \ |
72 | err = ChunkSetHead(&chunk, (LIST)); \ |
73 | } \ |
74 | return err; \ |
75 | } |
76 | |
77 | static WebPMuxError MuxSet(WebPMux* const mux, uint32_t tag, |
78 | const WebPData* const data, int copy_data) { |
79 | WebPChunk chunk; |
80 | WebPMuxError err = WEBP_MUX_NOT_FOUND; |
81 | const CHUNK_INDEX idx = ChunkGetIndexFromTag(tag); |
82 | assert(mux != NULL); |
83 | assert(!IsWPI(kChunks[idx].id)); |
84 | |
85 | ChunkInit(&chunk); |
86 | SWITCH_ID_LIST(IDX_VP8X, &mux->vp8x_); |
87 | SWITCH_ID_LIST(IDX_ICCP, &mux->iccp_); |
88 | SWITCH_ID_LIST(IDX_ANIM, &mux->anim_); |
89 | SWITCH_ID_LIST(IDX_EXIF, &mux->exif_); |
90 | SWITCH_ID_LIST(IDX_XMP, &mux->xmp_); |
91 | SWITCH_ID_LIST(IDX_UNKNOWN, &mux->unknown_); |
92 | return err; |
93 | } |
94 | #undef SWITCH_ID_LIST |
95 | |
96 | // Create data for frame given image data, offsets and duration. |
97 | static WebPMuxError CreateFrameData( |
98 | int width, int height, const WebPMuxFrameInfo* const info, |
99 | WebPData* const frame) { |
100 | uint8_t* frame_bytes; |
101 | const size_t frame_size = kChunks[IDX_ANMF].size; |
102 | |
103 | assert(width > 0 && height > 0 && info->duration >= 0); |
104 | assert(info->dispose_method == (info->dispose_method & 1)); |
105 | // Note: assertion on upper bounds is done in PutLE24(). |
106 | |
107 | frame_bytes = (uint8_t*)WebPSafeMalloc(1ULL, frame_size); |
108 | if (frame_bytes == NULL) return WEBP_MUX_MEMORY_ERROR; |
109 | |
110 | PutLE24(frame_bytes + 0, info->x_offset / 2); |
111 | PutLE24(frame_bytes + 3, info->y_offset / 2); |
112 | |
113 | PutLE24(frame_bytes + 6, width - 1); |
114 | PutLE24(frame_bytes + 9, height - 1); |
115 | PutLE24(frame_bytes + 12, info->duration); |
116 | frame_bytes[15] = |
117 | (info->blend_method == WEBP_MUX_NO_BLEND ? 2 : 0) | |
118 | (info->dispose_method == WEBP_MUX_DISPOSE_BACKGROUND ? 1 : 0); |
119 | |
120 | frame->bytes = frame_bytes; |
121 | frame->size = frame_size; |
122 | return WEBP_MUX_OK; |
123 | } |
124 | |
125 | // Outputs image data given a bitstream. The bitstream can either be a |
126 | // single-image WebP file or raw VP8/VP8L data. |
127 | // Also outputs 'is_lossless' to be true if the given bitstream is lossless. |
128 | static WebPMuxError GetImageData(const WebPData* const bitstream, |
129 | WebPData* const image, WebPData* const alpha, |
130 | int* const is_lossless) { |
131 | WebPDataInit(alpha); // Default: no alpha. |
132 | if (bitstream->size < TAG_SIZE || |
133 | memcmp(bitstream->bytes, "RIFF" , TAG_SIZE)) { |
134 | // It is NOT webp file data. Return input data as is. |
135 | *image = *bitstream; |
136 | } else { |
137 | // It is webp file data. Extract image data from it. |
138 | const WebPMuxImage* wpi; |
139 | WebPMux* const mux = WebPMuxCreate(bitstream, 0); |
140 | if (mux == NULL) return WEBP_MUX_BAD_DATA; |
141 | wpi = mux->images_; |
142 | assert(wpi != NULL && wpi->img_ != NULL); |
143 | *image = wpi->img_->data_; |
144 | if (wpi->alpha_ != NULL) { |
145 | *alpha = wpi->alpha_->data_; |
146 | } |
147 | WebPMuxDelete(mux); |
148 | } |
149 | *is_lossless = VP8LCheckSignature(image->bytes, image->size); |
150 | return WEBP_MUX_OK; |
151 | } |
152 | |
153 | static WebPMuxError DeleteChunks(WebPChunk** chunk_list, uint32_t tag) { |
154 | WebPMuxError err = WEBP_MUX_NOT_FOUND; |
155 | assert(chunk_list); |
156 | while (*chunk_list) { |
157 | WebPChunk* const chunk = *chunk_list; |
158 | if (chunk->tag_ == tag) { |
159 | *chunk_list = ChunkDelete(chunk); |
160 | err = WEBP_MUX_OK; |
161 | } else { |
162 | chunk_list = &chunk->next_; |
163 | } |
164 | } |
165 | return err; |
166 | } |
167 | |
168 | static WebPMuxError MuxDeleteAllNamedData(WebPMux* const mux, uint32_t tag) { |
169 | const WebPChunkId id = ChunkGetIdFromTag(tag); |
170 | assert(mux != NULL); |
171 | if (IsWPI(id)) return WEBP_MUX_INVALID_ARGUMENT; |
172 | return DeleteChunks(MuxGetChunkListFromId(mux, id), tag); |
173 | } |
174 | |
175 | //------------------------------------------------------------------------------ |
176 | // Set API(s). |
177 | |
178 | WebPMuxError WebPMuxSetChunk(WebPMux* mux, const char fourcc[4], |
179 | const WebPData* chunk_data, int copy_data) { |
180 | uint32_t tag; |
181 | WebPMuxError err; |
182 | if (mux == NULL || fourcc == NULL || chunk_data == NULL || |
183 | chunk_data->bytes == NULL || chunk_data->size > MAX_CHUNK_PAYLOAD) { |
184 | return WEBP_MUX_INVALID_ARGUMENT; |
185 | } |
186 | tag = ChunkGetTagFromFourCC(fourcc); |
187 | |
188 | // Delete existing chunk(s) with the same 'fourcc'. |
189 | err = MuxDeleteAllNamedData(mux, tag); |
190 | if (err != WEBP_MUX_OK && err != WEBP_MUX_NOT_FOUND) return err; |
191 | |
192 | // Add the given chunk. |
193 | return MuxSet(mux, tag, chunk_data, copy_data); |
194 | } |
195 | |
196 | // Creates a chunk from given 'data' and sets it as 1st chunk in 'chunk_list'. |
197 | static WebPMuxError AddDataToChunkList( |
198 | const WebPData* const data, int copy_data, uint32_t tag, |
199 | WebPChunk** chunk_list) { |
200 | WebPChunk chunk; |
201 | WebPMuxError err; |
202 | ChunkInit(&chunk); |
203 | err = ChunkAssignData(&chunk, data, copy_data, tag); |
204 | if (err != WEBP_MUX_OK) goto Err; |
205 | err = ChunkSetHead(&chunk, chunk_list); |
206 | if (err != WEBP_MUX_OK) goto Err; |
207 | return WEBP_MUX_OK; |
208 | Err: |
209 | ChunkRelease(&chunk); |
210 | return err; |
211 | } |
212 | |
213 | // Extracts image & alpha data from the given bitstream and then sets wpi.alpha_ |
214 | // and wpi.img_ appropriately. |
215 | static WebPMuxError SetAlphaAndImageChunks( |
216 | const WebPData* const bitstream, int copy_data, WebPMuxImage* const wpi) { |
217 | int is_lossless = 0; |
218 | WebPData image, alpha; |
219 | WebPMuxError err = GetImageData(bitstream, &image, &alpha, &is_lossless); |
220 | const int image_tag = |
221 | is_lossless ? kChunks[IDX_VP8L].tag : kChunks[IDX_VP8].tag; |
222 | if (err != WEBP_MUX_OK) return err; |
223 | if (alpha.bytes != NULL) { |
224 | err = AddDataToChunkList(&alpha, copy_data, kChunks[IDX_ALPHA].tag, |
225 | &wpi->alpha_); |
226 | if (err != WEBP_MUX_OK) return err; |
227 | } |
228 | err = AddDataToChunkList(&image, copy_data, image_tag, &wpi->img_); |
229 | if (err != WEBP_MUX_OK) return err; |
230 | return MuxImageFinalize(wpi) ? WEBP_MUX_OK : WEBP_MUX_INVALID_ARGUMENT; |
231 | } |
232 | |
233 | WebPMuxError WebPMuxSetImage(WebPMux* mux, const WebPData* bitstream, |
234 | int copy_data) { |
235 | WebPMuxImage wpi; |
236 | WebPMuxError err; |
237 | |
238 | // Sanity checks. |
239 | if (mux == NULL || bitstream == NULL || bitstream->bytes == NULL || |
240 | bitstream->size > MAX_CHUNK_PAYLOAD) { |
241 | return WEBP_MUX_INVALID_ARGUMENT; |
242 | } |
243 | |
244 | if (mux->images_ != NULL) { |
245 | // Only one 'simple image' can be added in mux. So, remove present images. |
246 | DeleteAllImages(&mux->images_); |
247 | } |
248 | |
249 | MuxImageInit(&wpi); |
250 | err = SetAlphaAndImageChunks(bitstream, copy_data, &wpi); |
251 | if (err != WEBP_MUX_OK) goto Err; |
252 | |
253 | // Add this WebPMuxImage to mux. |
254 | err = MuxImagePush(&wpi, &mux->images_); |
255 | if (err != WEBP_MUX_OK) goto Err; |
256 | |
257 | // All is well. |
258 | return WEBP_MUX_OK; |
259 | |
260 | Err: // Something bad happened. |
261 | MuxImageRelease(&wpi); |
262 | return err; |
263 | } |
264 | |
265 | WebPMuxError WebPMuxPushFrame(WebPMux* mux, const WebPMuxFrameInfo* info, |
266 | int copy_data) { |
267 | WebPMuxImage wpi; |
268 | WebPMuxError err; |
269 | |
270 | // Sanity checks. |
271 | if (mux == NULL || info == NULL) return WEBP_MUX_INVALID_ARGUMENT; |
272 | |
273 | if (info->id != WEBP_CHUNK_ANMF) return WEBP_MUX_INVALID_ARGUMENT; |
274 | |
275 | if (info->bitstream.bytes == NULL || |
276 | info->bitstream.size > MAX_CHUNK_PAYLOAD) { |
277 | return WEBP_MUX_INVALID_ARGUMENT; |
278 | } |
279 | |
280 | if (mux->images_ != NULL) { |
281 | const WebPMuxImage* const image = mux->images_; |
282 | const uint32_t image_id = (image->header_ != NULL) ? |
283 | ChunkGetIdFromTag(image->header_->tag_) : WEBP_CHUNK_IMAGE; |
284 | if (image_id != info->id) { |
285 | return WEBP_MUX_INVALID_ARGUMENT; // Conflicting frame types. |
286 | } |
287 | } |
288 | |
289 | MuxImageInit(&wpi); |
290 | err = SetAlphaAndImageChunks(&info->bitstream, copy_data, &wpi); |
291 | if (err != WEBP_MUX_OK) goto Err; |
292 | assert(wpi.img_ != NULL); // As SetAlphaAndImageChunks() was successful. |
293 | |
294 | { |
295 | WebPData frame; |
296 | const uint32_t tag = kChunks[IDX_ANMF].tag; |
297 | WebPMuxFrameInfo tmp = *info; |
298 | tmp.x_offset &= ~1; // Snap offsets to even. |
299 | tmp.y_offset &= ~1; |
300 | if (tmp.x_offset < 0 || tmp.x_offset >= MAX_POSITION_OFFSET || |
301 | tmp.y_offset < 0 || tmp.y_offset >= MAX_POSITION_OFFSET || |
302 | (tmp.duration < 0 || tmp.duration >= MAX_DURATION) || |
303 | tmp.dispose_method != (tmp.dispose_method & 1)) { |
304 | err = WEBP_MUX_INVALID_ARGUMENT; |
305 | goto Err; |
306 | } |
307 | err = CreateFrameData(wpi.width_, wpi.height_, &tmp, &frame); |
308 | if (err != WEBP_MUX_OK) goto Err; |
309 | // Add frame chunk (with copy_data = 1). |
310 | err = AddDataToChunkList(&frame, 1, tag, &wpi.header_); |
311 | WebPDataClear(&frame); // frame owned by wpi.header_ now. |
312 | if (err != WEBP_MUX_OK) goto Err; |
313 | } |
314 | |
315 | // Add this WebPMuxImage to mux. |
316 | err = MuxImagePush(&wpi, &mux->images_); |
317 | if (err != WEBP_MUX_OK) goto Err; |
318 | |
319 | // All is well. |
320 | return WEBP_MUX_OK; |
321 | |
322 | Err: // Something bad happened. |
323 | MuxImageRelease(&wpi); |
324 | return err; |
325 | } |
326 | |
327 | WebPMuxError WebPMuxSetAnimationParams(WebPMux* mux, |
328 | const WebPMuxAnimParams* params) { |
329 | WebPMuxError err; |
330 | uint8_t data[ANIM_CHUNK_SIZE]; |
331 | const WebPData anim = { data, ANIM_CHUNK_SIZE }; |
332 | |
333 | if (mux == NULL || params == NULL) return WEBP_MUX_INVALID_ARGUMENT; |
334 | if (params->loop_count < 0 || params->loop_count >= MAX_LOOP_COUNT) { |
335 | return WEBP_MUX_INVALID_ARGUMENT; |
336 | } |
337 | |
338 | // Delete any existing ANIM chunk(s). |
339 | err = MuxDeleteAllNamedData(mux, kChunks[IDX_ANIM].tag); |
340 | if (err != WEBP_MUX_OK && err != WEBP_MUX_NOT_FOUND) return err; |
341 | |
342 | // Set the animation parameters. |
343 | PutLE32(data, params->bgcolor); |
344 | PutLE16(data + 4, params->loop_count); |
345 | return MuxSet(mux, kChunks[IDX_ANIM].tag, &anim, 1); |
346 | } |
347 | |
348 | WebPMuxError WebPMuxSetCanvasSize(WebPMux* mux, |
349 | int width, int height) { |
350 | WebPMuxError err; |
351 | if (mux == NULL) { |
352 | return WEBP_MUX_INVALID_ARGUMENT; |
353 | } |
354 | if (width < 0 || height < 0 || |
355 | width > MAX_CANVAS_SIZE || height > MAX_CANVAS_SIZE) { |
356 | return WEBP_MUX_INVALID_ARGUMENT; |
357 | } |
358 | if (width * (uint64_t)height >= MAX_IMAGE_AREA) { |
359 | return WEBP_MUX_INVALID_ARGUMENT; |
360 | } |
361 | if ((width * height) == 0 && (width | height) != 0) { |
362 | // one of width / height is zero, but not both -> invalid! |
363 | return WEBP_MUX_INVALID_ARGUMENT; |
364 | } |
365 | // If we already assembled a VP8X chunk, invalidate it. |
366 | err = MuxDeleteAllNamedData(mux, kChunks[IDX_VP8X].tag); |
367 | if (err != WEBP_MUX_OK && err != WEBP_MUX_NOT_FOUND) return err; |
368 | |
369 | mux->canvas_width_ = width; |
370 | mux->canvas_height_ = height; |
371 | return WEBP_MUX_OK; |
372 | } |
373 | |
374 | //------------------------------------------------------------------------------ |
375 | // Delete API(s). |
376 | |
377 | WebPMuxError WebPMuxDeleteChunk(WebPMux* mux, const char fourcc[4]) { |
378 | if (mux == NULL || fourcc == NULL) return WEBP_MUX_INVALID_ARGUMENT; |
379 | return MuxDeleteAllNamedData(mux, ChunkGetTagFromFourCC(fourcc)); |
380 | } |
381 | |
382 | WebPMuxError WebPMuxDeleteFrame(WebPMux* mux, uint32_t nth) { |
383 | if (mux == NULL) return WEBP_MUX_INVALID_ARGUMENT; |
384 | return MuxImageDeleteNth(&mux->images_, nth); |
385 | } |
386 | |
387 | //------------------------------------------------------------------------------ |
388 | // Assembly of the WebP RIFF file. |
389 | |
390 | static WebPMuxError GetFrameInfo( |
391 | const WebPChunk* const frame_chunk, |
392 | int* const x_offset, int* const y_offset, int* const duration) { |
393 | const WebPData* const data = &frame_chunk->data_; |
394 | const size_t expected_data_size = ANMF_CHUNK_SIZE; |
395 | assert(frame_chunk->tag_ == kChunks[IDX_ANMF].tag); |
396 | assert(frame_chunk != NULL); |
397 | if (data->size != expected_data_size) return WEBP_MUX_INVALID_ARGUMENT; |
398 | |
399 | *x_offset = 2 * GetLE24(data->bytes + 0); |
400 | *y_offset = 2 * GetLE24(data->bytes + 3); |
401 | *duration = GetLE24(data->bytes + 12); |
402 | return WEBP_MUX_OK; |
403 | } |
404 | |
405 | static WebPMuxError GetImageInfo(const WebPMuxImage* const wpi, |
406 | int* const x_offset, int* const y_offset, |
407 | int* const duration, |
408 | int* const width, int* const height) { |
409 | const WebPChunk* const frame_chunk = wpi->header_; |
410 | WebPMuxError err; |
411 | assert(wpi != NULL); |
412 | assert(frame_chunk != NULL); |
413 | |
414 | // Get offsets and duration from ANMF chunk. |
415 | err = GetFrameInfo(frame_chunk, x_offset, y_offset, duration); |
416 | if (err != WEBP_MUX_OK) return err; |
417 | |
418 | // Get width and height from VP8/VP8L chunk. |
419 | if (width != NULL) *width = wpi->width_; |
420 | if (height != NULL) *height = wpi->height_; |
421 | return WEBP_MUX_OK; |
422 | } |
423 | |
424 | // Returns the tightest dimension for the canvas considering the image list. |
425 | static WebPMuxError GetAdjustedCanvasSize(const WebPMux* const mux, |
426 | int* const width, int* const height) { |
427 | WebPMuxImage* wpi = NULL; |
428 | assert(mux != NULL); |
429 | assert(width != NULL && height != NULL); |
430 | |
431 | wpi = mux->images_; |
432 | assert(wpi != NULL); |
433 | assert(wpi->img_ != NULL); |
434 | |
435 | if (wpi->next_ != NULL) { |
436 | int max_x = 0, max_y = 0; |
437 | // if we have a chain of wpi's, header_ is necessarily set |
438 | assert(wpi->header_ != NULL); |
439 | // Aggregate the bounding box for animation frames. |
440 | for (; wpi != NULL; wpi = wpi->next_) { |
441 | int x_offset = 0, y_offset = 0, duration = 0, w = 0, h = 0; |
442 | const WebPMuxError err = GetImageInfo(wpi, &x_offset, &y_offset, |
443 | &duration, &w, &h); |
444 | const int max_x_pos = x_offset + w; |
445 | const int max_y_pos = y_offset + h; |
446 | if (err != WEBP_MUX_OK) return err; |
447 | assert(x_offset < MAX_POSITION_OFFSET); |
448 | assert(y_offset < MAX_POSITION_OFFSET); |
449 | |
450 | if (max_x_pos > max_x) max_x = max_x_pos; |
451 | if (max_y_pos > max_y) max_y = max_y_pos; |
452 | } |
453 | *width = max_x; |
454 | *height = max_y; |
455 | } else { |
456 | // For a single image, canvas dimensions are same as image dimensions. |
457 | *width = wpi->width_; |
458 | *height = wpi->height_; |
459 | } |
460 | return WEBP_MUX_OK; |
461 | } |
462 | |
463 | // VP8X format: |
464 | // Total Size : 10, |
465 | // Flags : 4 bytes, |
466 | // Width : 3 bytes, |
467 | // Height : 3 bytes. |
468 | static WebPMuxError CreateVP8XChunk(WebPMux* const mux) { |
469 | WebPMuxError err = WEBP_MUX_OK; |
470 | uint32_t flags = 0; |
471 | int width = 0; |
472 | int height = 0; |
473 | uint8_t data[VP8X_CHUNK_SIZE]; |
474 | const WebPData vp8x = { data, VP8X_CHUNK_SIZE }; |
475 | const WebPMuxImage* images = NULL; |
476 | |
477 | assert(mux != NULL); |
478 | images = mux->images_; // First image. |
479 | if (images == NULL || images->img_ == NULL || |
480 | images->img_->data_.bytes == NULL) { |
481 | return WEBP_MUX_INVALID_ARGUMENT; |
482 | } |
483 | |
484 | // If VP8X chunk(s) is(are) already present, remove them (and later add new |
485 | // VP8X chunk with updated flags). |
486 | err = MuxDeleteAllNamedData(mux, kChunks[IDX_VP8X].tag); |
487 | if (err != WEBP_MUX_OK && err != WEBP_MUX_NOT_FOUND) return err; |
488 | |
489 | // Set flags. |
490 | if (mux->iccp_ != NULL && mux->iccp_->data_.bytes != NULL) { |
491 | flags |= ICCP_FLAG; |
492 | } |
493 | if (mux->exif_ != NULL && mux->exif_->data_.bytes != NULL) { |
494 | flags |= EXIF_FLAG; |
495 | } |
496 | if (mux->xmp_ != NULL && mux->xmp_->data_.bytes != NULL) { |
497 | flags |= XMP_FLAG; |
498 | } |
499 | if (images->header_ != NULL) { |
500 | if (images->header_->tag_ == kChunks[IDX_ANMF].tag) { |
501 | // This is an image with animation. |
502 | flags |= ANIMATION_FLAG; |
503 | } |
504 | } |
505 | if (MuxImageCount(images, WEBP_CHUNK_ALPHA) > 0) { |
506 | flags |= ALPHA_FLAG; // Some images have an alpha channel. |
507 | } |
508 | |
509 | err = GetAdjustedCanvasSize(mux, &width, &height); |
510 | if (err != WEBP_MUX_OK) return err; |
511 | |
512 | if (width <= 0 || height <= 0) { |
513 | return WEBP_MUX_INVALID_ARGUMENT; |
514 | } |
515 | if (width > MAX_CANVAS_SIZE || height > MAX_CANVAS_SIZE) { |
516 | return WEBP_MUX_INVALID_ARGUMENT; |
517 | } |
518 | |
519 | if (mux->canvas_width_ != 0 || mux->canvas_height_ != 0) { |
520 | if (width > mux->canvas_width_ || height > mux->canvas_height_) { |
521 | return WEBP_MUX_INVALID_ARGUMENT; |
522 | } |
523 | width = mux->canvas_width_; |
524 | height = mux->canvas_height_; |
525 | } |
526 | |
527 | if (flags == 0 && mux->unknown_ == NULL) { |
528 | // For simple file format, VP8X chunk should not be added. |
529 | return WEBP_MUX_OK; |
530 | } |
531 | |
532 | if (MuxHasAlpha(images)) { |
533 | // This means some frames explicitly/implicitly contain alpha. |
534 | // Note: This 'flags' update must NOT be done for a lossless image |
535 | // without a VP8X chunk! |
536 | flags |= ALPHA_FLAG; |
537 | } |
538 | |
539 | PutLE32(data + 0, flags); // VP8X chunk flags. |
540 | PutLE24(data + 4, width - 1); // canvas width. |
541 | PutLE24(data + 7, height - 1); // canvas height. |
542 | |
543 | return MuxSet(mux, kChunks[IDX_VP8X].tag, &vp8x, 1); |
544 | } |
545 | |
546 | // Cleans up 'mux' by removing any unnecessary chunks. |
547 | static WebPMuxError MuxCleanup(WebPMux* const mux) { |
548 | int num_frames; |
549 | int num_anim_chunks; |
550 | |
551 | // If we have an image with a single frame, and its rectangle |
552 | // covers the whole canvas, convert it to a non-animated image |
553 | // (to avoid writing ANMF chunk unnecessarily). |
554 | WebPMuxError err = WebPMuxNumChunks(mux, kChunks[IDX_ANMF].id, &num_frames); |
555 | if (err != WEBP_MUX_OK) return err; |
556 | if (num_frames == 1) { |
557 | WebPMuxImage* frame = NULL; |
558 | err = MuxImageGetNth((const WebPMuxImage**)&mux->images_, 1, &frame); |
559 | assert(err == WEBP_MUX_OK); // We know that one frame does exist. |
560 | assert(frame != NULL); |
561 | if (frame->header_ != NULL && |
562 | ((mux->canvas_width_ == 0 && mux->canvas_height_ == 0) || |
563 | (frame->width_ == mux->canvas_width_ && |
564 | frame->height_ == mux->canvas_height_))) { |
565 | assert(frame->header_->tag_ == kChunks[IDX_ANMF].tag); |
566 | ChunkDelete(frame->header_); // Removes ANMF chunk. |
567 | frame->header_ = NULL; |
568 | num_frames = 0; |
569 | } |
570 | } |
571 | // Remove ANIM chunk if this is a non-animated image. |
572 | err = WebPMuxNumChunks(mux, kChunks[IDX_ANIM].id, &num_anim_chunks); |
573 | if (err != WEBP_MUX_OK) return err; |
574 | if (num_anim_chunks >= 1 && num_frames == 0) { |
575 | err = MuxDeleteAllNamedData(mux, kChunks[IDX_ANIM].tag); |
576 | if (err != WEBP_MUX_OK) return err; |
577 | } |
578 | return WEBP_MUX_OK; |
579 | } |
580 | |
581 | // Total size of a list of images. |
582 | static size_t ImageListDiskSize(const WebPMuxImage* wpi_list) { |
583 | size_t size = 0; |
584 | while (wpi_list != NULL) { |
585 | size += MuxImageDiskSize(wpi_list); |
586 | wpi_list = wpi_list->next_; |
587 | } |
588 | return size; |
589 | } |
590 | |
591 | // Write out the given list of images into 'dst'. |
592 | static uint8_t* ImageListEmit(const WebPMuxImage* wpi_list, uint8_t* dst) { |
593 | while (wpi_list != NULL) { |
594 | dst = MuxImageEmit(wpi_list, dst); |
595 | wpi_list = wpi_list->next_; |
596 | } |
597 | return dst; |
598 | } |
599 | |
600 | WebPMuxError WebPMuxAssemble(WebPMux* mux, WebPData* assembled_data) { |
601 | size_t size = 0; |
602 | uint8_t* data = NULL; |
603 | uint8_t* dst = NULL; |
604 | WebPMuxError err; |
605 | |
606 | if (assembled_data == NULL) { |
607 | return WEBP_MUX_INVALID_ARGUMENT; |
608 | } |
609 | // Clean up returned data, in case something goes wrong. |
610 | memset(assembled_data, 0, sizeof(*assembled_data)); |
611 | |
612 | if (mux == NULL) { |
613 | return WEBP_MUX_INVALID_ARGUMENT; |
614 | } |
615 | |
616 | // Finalize mux. |
617 | err = MuxCleanup(mux); |
618 | if (err != WEBP_MUX_OK) return err; |
619 | err = CreateVP8XChunk(mux); |
620 | if (err != WEBP_MUX_OK) return err; |
621 | |
622 | // Allocate data. |
623 | size = ChunkListDiskSize(mux->vp8x_) + ChunkListDiskSize(mux->iccp_) |
624 | + ChunkListDiskSize(mux->anim_) + ImageListDiskSize(mux->images_) |
625 | + ChunkListDiskSize(mux->exif_) + ChunkListDiskSize(mux->xmp_) |
626 | + ChunkListDiskSize(mux->unknown_) + RIFF_HEADER_SIZE; |
627 | |
628 | data = (uint8_t*)WebPSafeMalloc(1ULL, size); |
629 | if (data == NULL) return WEBP_MUX_MEMORY_ERROR; |
630 | |
631 | // Emit header & chunks. |
632 | dst = MuxEmitRiffHeader(data, size); |
633 | dst = ChunkListEmit(mux->vp8x_, dst); |
634 | dst = ChunkListEmit(mux->iccp_, dst); |
635 | dst = ChunkListEmit(mux->anim_, dst); |
636 | dst = ImageListEmit(mux->images_, dst); |
637 | dst = ChunkListEmit(mux->exif_, dst); |
638 | dst = ChunkListEmit(mux->xmp_, dst); |
639 | dst = ChunkListEmit(mux->unknown_, dst); |
640 | assert(dst == data + size); |
641 | |
642 | // Validate mux. |
643 | err = MuxValidate(mux); |
644 | if (err != WEBP_MUX_OK) { |
645 | WebPSafeFree(data); |
646 | data = NULL; |
647 | size = 0; |
648 | } |
649 | |
650 | // Finalize data. |
651 | assembled_data->bytes = data; |
652 | assembled_data->size = size; |
653 | |
654 | return err; |
655 | } |
656 | |
657 | //------------------------------------------------------------------------------ |
658 | |