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