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