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