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