Coverage Report

Created: 2025-10-12 07:48

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