github.com/cellofellow/gopkg@v0.0.0-20140722061823-eec0544a62ad/image/webp/libwebp/src/mux/muxread.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 // Read APIs 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 //------------------------------------------------------------------------------ 20 // Helper method(s). 21 22 // Handy MACRO. 23 #define SWITCH_ID_LIST(INDEX, LIST) \ 24 if (idx == (INDEX)) { \ 25 const WebPChunk* const chunk = ChunkSearchList((LIST), nth, \ 26 kChunks[(INDEX)].tag); \ 27 if (chunk) { \ 28 *data = chunk->data_; \ 29 return WEBP_MUX_OK; \ 30 } else { \ 31 return WEBP_MUX_NOT_FOUND; \ 32 } \ 33 } 34 35 static WebPMuxError MuxGet(const WebPMux* const mux, CHUNK_INDEX idx, 36 uint32_t nth, WebPData* const data) { 37 assert(mux != NULL); 38 assert(!IsWPI(kChunks[idx].id)); 39 WebPDataInit(data); 40 41 SWITCH_ID_LIST(IDX_VP8X, mux->vp8x_); 42 SWITCH_ID_LIST(IDX_ICCP, mux->iccp_); 43 SWITCH_ID_LIST(IDX_ANIM, mux->anim_); 44 SWITCH_ID_LIST(IDX_EXIF, mux->exif_); 45 SWITCH_ID_LIST(IDX_XMP, mux->xmp_); 46 SWITCH_ID_LIST(IDX_UNKNOWN, mux->unknown_); 47 return WEBP_MUX_NOT_FOUND; 48 } 49 #undef SWITCH_ID_LIST 50 51 // Fill the chunk with the given data (includes chunk header bytes), after some 52 // verifications. 53 static WebPMuxError ChunkVerifyAndAssign(WebPChunk* chunk, 54 const uint8_t* data, size_t data_size, 55 size_t riff_size, int copy_data) { 56 uint32_t chunk_size; 57 WebPData chunk_data; 58 59 // Sanity checks. 60 if (data_size < TAG_SIZE) return WEBP_MUX_NOT_ENOUGH_DATA; 61 chunk_size = GetLE32(data + TAG_SIZE); 62 63 { 64 const size_t chunk_disk_size = SizeWithPadding(chunk_size); 65 if (chunk_disk_size > riff_size) return WEBP_MUX_BAD_DATA; 66 if (chunk_disk_size > data_size) return WEBP_MUX_NOT_ENOUGH_DATA; 67 } 68 69 // Data assignment. 70 chunk_data.bytes = data + CHUNK_HEADER_SIZE; 71 chunk_data.size = chunk_size; 72 return ChunkAssignData(chunk, &chunk_data, copy_data, GetLE32(data + 0)); 73 } 74 75 int MuxImageFinalize(WebPMuxImage* const wpi) { 76 const WebPChunk* const img = wpi->img_; 77 const WebPData* const image = &img->data_; 78 const int is_lossless = (img->tag_ == kChunks[IDX_VP8L].tag); 79 int w, h; 80 int vp8l_has_alpha = 0; 81 const int ok = is_lossless ? 82 VP8LGetInfo(image->bytes, image->size, &w, &h, &vp8l_has_alpha) : 83 VP8GetInfo(image->bytes, image->size, image->size, &w, &h); 84 assert(img != NULL); 85 if (ok) { 86 // Ignore ALPH chunk accompanying VP8L. 87 if (is_lossless && (wpi->alpha_ != NULL)) { 88 ChunkDelete(wpi->alpha_); 89 wpi->alpha_ = NULL; 90 } 91 wpi->width_ = w; 92 wpi->height_ = h; 93 wpi->has_alpha_ = vp8l_has_alpha || (wpi->alpha_ != NULL); 94 } 95 return ok; 96 } 97 98 static int MuxImageParse(const WebPChunk* const chunk, int copy_data, 99 WebPMuxImage* const wpi) { 100 const uint8_t* bytes = chunk->data_.bytes; 101 size_t size = chunk->data_.size; 102 const uint8_t* const last = bytes + size; 103 WebPChunk subchunk; 104 size_t subchunk_size; 105 ChunkInit(&subchunk); 106 107 assert(chunk->tag_ == kChunks[IDX_ANMF].tag || 108 chunk->tag_ == kChunks[IDX_FRGM].tag); 109 assert(!wpi->is_partial_); 110 111 // ANMF/FRGM. 112 { 113 const size_t hdr_size = (chunk->tag_ == kChunks[IDX_ANMF].tag) ? 114 ANMF_CHUNK_SIZE : FRGM_CHUNK_SIZE; 115 const WebPData temp = { bytes, hdr_size }; 116 // Each of ANMF and FRGM chunk contain a header at the beginning. So, its 117 // size should at least be 'hdr_size'. 118 if (size < hdr_size) goto Fail; 119 ChunkAssignData(&subchunk, &temp, copy_data, chunk->tag_); 120 } 121 ChunkSetNth(&subchunk, &wpi->header_, 1); 122 wpi->is_partial_ = 1; // Waiting for ALPH and/or VP8/VP8L chunks. 123 124 // Rest of the chunks. 125 subchunk_size = ChunkDiskSize(&subchunk) - CHUNK_HEADER_SIZE; 126 bytes += subchunk_size; 127 size -= subchunk_size; 128 129 while (bytes != last) { 130 ChunkInit(&subchunk); 131 if (ChunkVerifyAndAssign(&subchunk, bytes, size, size, 132 copy_data) != WEBP_MUX_OK) { 133 goto Fail; 134 } 135 switch (ChunkGetIdFromTag(subchunk.tag_)) { 136 case WEBP_CHUNK_ALPHA: 137 if (wpi->alpha_ != NULL) goto Fail; // Consecutive ALPH chunks. 138 if (ChunkSetNth(&subchunk, &wpi->alpha_, 1) != WEBP_MUX_OK) goto Fail; 139 wpi->is_partial_ = 1; // Waiting for a VP8 chunk. 140 break; 141 case WEBP_CHUNK_IMAGE: 142 if (ChunkSetNth(&subchunk, &wpi->img_, 1) != WEBP_MUX_OK) goto Fail; 143 if (!MuxImageFinalize(wpi)) goto Fail; 144 wpi->is_partial_ = 0; // wpi is completely filled. 145 break; 146 case WEBP_CHUNK_UNKNOWN: 147 if (wpi->is_partial_) goto Fail; // Encountered an unknown chunk 148 // before some image chunks. 149 if (ChunkSetNth(&subchunk, &wpi->unknown_, 0) != WEBP_MUX_OK) goto Fail; 150 break; 151 default: 152 goto Fail; 153 break; 154 } 155 subchunk_size = ChunkDiskSize(&subchunk); 156 bytes += subchunk_size; 157 size -= subchunk_size; 158 } 159 if (wpi->is_partial_) goto Fail; 160 return 1; 161 162 Fail: 163 ChunkRelease(&subchunk); 164 return 0; 165 } 166 167 //------------------------------------------------------------------------------ 168 // Create a mux object from WebP-RIFF data. 169 170 WebPMux* WebPMuxCreateInternal(const WebPData* bitstream, int copy_data, 171 int version) { 172 size_t riff_size; 173 uint32_t tag; 174 const uint8_t* end; 175 WebPMux* mux = NULL; 176 WebPMuxImage* wpi = NULL; 177 const uint8_t* data; 178 size_t size; 179 WebPChunk chunk; 180 ChunkInit(&chunk); 181 182 // Sanity checks. 183 if (WEBP_ABI_IS_INCOMPATIBLE(version, WEBP_MUX_ABI_VERSION)) { 184 return NULL; // version mismatch 185 } 186 if (bitstream == NULL) return NULL; 187 188 data = bitstream->bytes; 189 size = bitstream->size; 190 191 if (data == NULL) return NULL; 192 if (size < RIFF_HEADER_SIZE) return NULL; 193 if (GetLE32(data + 0) != MKFOURCC('R', 'I', 'F', 'F') || 194 GetLE32(data + CHUNK_HEADER_SIZE) != MKFOURCC('W', 'E', 'B', 'P')) { 195 return NULL; 196 } 197 198 mux = WebPMuxNew(); 199 if (mux == NULL) return NULL; 200 201 if (size < RIFF_HEADER_SIZE + TAG_SIZE) goto Err; 202 203 tag = GetLE32(data + RIFF_HEADER_SIZE); 204 if (tag != kChunks[IDX_VP8].tag && 205 tag != kChunks[IDX_VP8L].tag && 206 tag != kChunks[IDX_VP8X].tag) { 207 goto Err; // First chunk should be VP8, VP8L or VP8X. 208 } 209 210 riff_size = SizeWithPadding(GetLE32(data + TAG_SIZE)); 211 if (riff_size > MAX_CHUNK_PAYLOAD || riff_size > size) { 212 goto Err; 213 } else { 214 if (riff_size < size) { // Redundant data after last chunk. 215 size = riff_size; // To make sure we don't read any data beyond mux_size. 216 } 217 } 218 219 end = data + size; 220 data += RIFF_HEADER_SIZE; 221 size -= RIFF_HEADER_SIZE; 222 223 wpi = (WebPMuxImage*)malloc(sizeof(*wpi)); 224 if (wpi == NULL) goto Err; 225 MuxImageInit(wpi); 226 227 // Loop over chunks. 228 while (data != end) { 229 size_t data_size; 230 WebPChunkId id; 231 WebPChunk** chunk_list; 232 if (ChunkVerifyAndAssign(&chunk, data, size, riff_size, 233 copy_data) != WEBP_MUX_OK) { 234 goto Err; 235 } 236 data_size = ChunkDiskSize(&chunk); 237 id = ChunkGetIdFromTag(chunk.tag_); 238 switch (id) { 239 case WEBP_CHUNK_ALPHA: 240 if (wpi->alpha_ != NULL) goto Err; // Consecutive ALPH chunks. 241 if (ChunkSetNth(&chunk, &wpi->alpha_, 1) != WEBP_MUX_OK) goto Err; 242 wpi->is_partial_ = 1; // Waiting for a VP8 chunk. 243 break; 244 case WEBP_CHUNK_IMAGE: 245 if (ChunkSetNth(&chunk, &wpi->img_, 1) != WEBP_MUX_OK) goto Err; 246 if (!MuxImageFinalize(wpi)) goto Err; 247 wpi->is_partial_ = 0; // wpi is completely filled. 248 PushImage: 249 // Add this to mux->images_ list. 250 if (MuxImagePush(wpi, &mux->images_) != WEBP_MUX_OK) goto Err; 251 MuxImageInit(wpi); // Reset for reading next image. 252 break; 253 case WEBP_CHUNK_ANMF: 254 #ifdef WEBP_EXPERIMENTAL_FEATURES 255 case WEBP_CHUNK_FRGM: 256 #endif 257 if (wpi->is_partial_) goto Err; // Previous wpi is still incomplete. 258 if (!MuxImageParse(&chunk, copy_data, wpi)) goto Err; 259 ChunkRelease(&chunk); 260 goto PushImage; 261 break; 262 default: // A non-image chunk. 263 if (wpi->is_partial_) goto Err; // Encountered a non-image chunk before 264 // getting all chunks of an image. 265 chunk_list = MuxGetChunkListFromId(mux, id); // List to add this chunk. 266 if (ChunkSetNth(&chunk, chunk_list, 0) != WEBP_MUX_OK) goto Err; 267 break; 268 } 269 data += data_size; 270 size -= data_size; 271 ChunkInit(&chunk); 272 } 273 274 // Validate mux if complete. 275 if (MuxValidate(mux) != WEBP_MUX_OK) goto Err; 276 277 MuxImageDelete(wpi); 278 return mux; // All OK; 279 280 Err: // Something bad happened. 281 ChunkRelease(&chunk); 282 MuxImageDelete(wpi); 283 WebPMuxDelete(mux); 284 return NULL; 285 } 286 287 //------------------------------------------------------------------------------ 288 // Get API(s). 289 290 // Validates that the given mux has a single image. 291 static WebPMuxError ValidateForSingleImage(const WebPMux* const mux) { 292 const int num_images = MuxImageCount(mux->images_, WEBP_CHUNK_IMAGE); 293 const int num_frames = MuxImageCount(mux->images_, WEBP_CHUNK_ANMF); 294 const int num_fragments = MuxImageCount(mux->images_, WEBP_CHUNK_FRGM); 295 296 if (num_images == 0) { 297 // No images in mux. 298 return WEBP_MUX_NOT_FOUND; 299 } else if (num_images == 1 && num_frames == 0 && num_fragments == 0) { 300 // Valid case (single image). 301 return WEBP_MUX_OK; 302 } else { 303 // Frame/Fragment case OR an invalid mux. 304 return WEBP_MUX_INVALID_ARGUMENT; 305 } 306 } 307 308 // Get the canvas width, height and flags after validating that VP8X/VP8/VP8L 309 // chunk and canvas size are valid. 310 static WebPMuxError MuxGetCanvasInfo(const WebPMux* const mux, 311 int* width, int* height, uint32_t* flags) { 312 int w, h; 313 uint32_t f = 0; 314 WebPData data; 315 assert(mux != NULL); 316 317 // Check if VP8X chunk is present. 318 if (MuxGet(mux, IDX_VP8X, 1, &data) == WEBP_MUX_OK) { 319 if (data.size < VP8X_CHUNK_SIZE) return WEBP_MUX_BAD_DATA; 320 f = GetLE32(data.bytes + 0); 321 w = GetLE24(data.bytes + 4) + 1; 322 h = GetLE24(data.bytes + 7) + 1; 323 } else { // Single image case. 324 const WebPMuxImage* const wpi = mux->images_; 325 WebPMuxError err = ValidateForSingleImage(mux); 326 if (err != WEBP_MUX_OK) return err; 327 assert(wpi != NULL); 328 w = wpi->width_; 329 h = wpi->height_; 330 if (wpi->has_alpha_) f |= ALPHA_FLAG; 331 } 332 if (w * (uint64_t)h >= MAX_IMAGE_AREA) return WEBP_MUX_BAD_DATA; 333 334 if (width != NULL) *width = w; 335 if (height != NULL) *height = h; 336 if (flags != NULL) *flags = f; 337 return WEBP_MUX_OK; 338 } 339 340 WebPMuxError WebPMuxGetCanvasSize(const WebPMux* mux, int* width, int* height) { 341 if (mux == NULL || width == NULL || height == NULL) { 342 return WEBP_MUX_INVALID_ARGUMENT; 343 } 344 return MuxGetCanvasInfo(mux, width, height, NULL); 345 } 346 347 WebPMuxError WebPMuxGetFeatures(const WebPMux* mux, uint32_t* flags) { 348 if (mux == NULL || flags == NULL) return WEBP_MUX_INVALID_ARGUMENT; 349 return MuxGetCanvasInfo(mux, NULL, NULL, flags); 350 } 351 352 static uint8_t* EmitVP8XChunk(uint8_t* const dst, int width, 353 int height, uint32_t flags) { 354 const size_t vp8x_size = CHUNK_HEADER_SIZE + VP8X_CHUNK_SIZE; 355 assert(width >= 1 && height >= 1); 356 assert(width <= MAX_CANVAS_SIZE && height <= MAX_CANVAS_SIZE); 357 assert(width * (uint64_t)height < MAX_IMAGE_AREA); 358 PutLE32(dst, MKFOURCC('V', 'P', '8', 'X')); 359 PutLE32(dst + TAG_SIZE, VP8X_CHUNK_SIZE); 360 PutLE32(dst + CHUNK_HEADER_SIZE, flags); 361 PutLE24(dst + CHUNK_HEADER_SIZE + 4, width - 1); 362 PutLE24(dst + CHUNK_HEADER_SIZE + 7, height - 1); 363 return dst + vp8x_size; 364 } 365 366 // Assemble a single image WebP bitstream from 'wpi'. 367 static WebPMuxError SynthesizeBitstream(const WebPMuxImage* const wpi, 368 WebPData* const bitstream) { 369 uint8_t* dst; 370 371 // Allocate data. 372 const int need_vp8x = (wpi->alpha_ != NULL); 373 const size_t vp8x_size = need_vp8x ? CHUNK_HEADER_SIZE + VP8X_CHUNK_SIZE : 0; 374 const size_t alpha_size = need_vp8x ? ChunkDiskSize(wpi->alpha_) : 0; 375 // Note: No need to output ANMF/FRGM chunk for a single image. 376 const size_t size = RIFF_HEADER_SIZE + vp8x_size + alpha_size + 377 ChunkDiskSize(wpi->img_); 378 uint8_t* const data = (uint8_t*)malloc(size); 379 if (data == NULL) return WEBP_MUX_MEMORY_ERROR; 380 381 // Main RIFF header. 382 dst = MuxEmitRiffHeader(data, size); 383 384 if (need_vp8x) { 385 dst = EmitVP8XChunk(dst, wpi->width_, wpi->height_, ALPHA_FLAG); // VP8X. 386 dst = ChunkListEmit(wpi->alpha_, dst); // ALPH. 387 } 388 389 // Bitstream. 390 dst = ChunkListEmit(wpi->img_, dst); 391 assert(dst == data + size); 392 393 // Output. 394 bitstream->bytes = data; 395 bitstream->size = size; 396 return WEBP_MUX_OK; 397 } 398 399 WebPMuxError WebPMuxGetChunk(const WebPMux* mux, const char fourcc[4], 400 WebPData* chunk_data) { 401 CHUNK_INDEX idx; 402 if (mux == NULL || fourcc == NULL || chunk_data == NULL) { 403 return WEBP_MUX_INVALID_ARGUMENT; 404 } 405 idx = ChunkGetIndexFromFourCC(fourcc); 406 if (IsWPI(kChunks[idx].id)) { // An image chunk. 407 return WEBP_MUX_INVALID_ARGUMENT; 408 } else if (idx != IDX_UNKNOWN) { // A known chunk type. 409 return MuxGet(mux, idx, 1, chunk_data); 410 } else { // An unknown chunk type. 411 const WebPChunk* const chunk = 412 ChunkSearchList(mux->unknown_, 1, ChunkGetTagFromFourCC(fourcc)); 413 if (chunk == NULL) return WEBP_MUX_NOT_FOUND; 414 *chunk_data = chunk->data_; 415 return WEBP_MUX_OK; 416 } 417 } 418 419 static WebPMuxError MuxGetImageInternal(const WebPMuxImage* const wpi, 420 WebPMuxFrameInfo* const info) { 421 // Set some defaults for unrelated fields. 422 info->x_offset = 0; 423 info->y_offset = 0; 424 info->duration = 1; 425 info->dispose_method = WEBP_MUX_DISPOSE_NONE; 426 info->blend_method = WEBP_MUX_BLEND; 427 // Extract data for related fields. 428 info->id = ChunkGetIdFromTag(wpi->img_->tag_); 429 return SynthesizeBitstream(wpi, &info->bitstream); 430 } 431 432 static WebPMuxError MuxGetFrameFragmentInternal(const WebPMuxImage* const wpi, 433 WebPMuxFrameInfo* const frame) { 434 const int is_frame = (wpi->header_->tag_ == kChunks[IDX_ANMF].tag); 435 const CHUNK_INDEX idx = is_frame ? IDX_ANMF : IDX_FRGM; 436 const WebPData* frame_frgm_data; 437 #ifndef WEBP_EXPERIMENTAL_FEATURES 438 if (!is_frame) return WEBP_MUX_INVALID_ARGUMENT; 439 #endif 440 assert(wpi->header_ != NULL); // Already checked by WebPMuxGetFrame(). 441 // Get frame/fragment chunk. 442 frame_frgm_data = &wpi->header_->data_; 443 if (frame_frgm_data->size < kChunks[idx].size) return WEBP_MUX_BAD_DATA; 444 // Extract info. 445 frame->x_offset = 2 * GetLE24(frame_frgm_data->bytes + 0); 446 frame->y_offset = 2 * GetLE24(frame_frgm_data->bytes + 3); 447 if (is_frame) { 448 const uint8_t bits = frame_frgm_data->bytes[15]; 449 frame->duration = GetLE24(frame_frgm_data->bytes + 12); 450 frame->dispose_method = 451 (bits & 1) ? WEBP_MUX_DISPOSE_BACKGROUND : WEBP_MUX_DISPOSE_NONE; 452 frame->blend_method = (bits & 2) ? WEBP_MUX_NO_BLEND : WEBP_MUX_BLEND; 453 } else { // Defaults for unused values. 454 frame->duration = 1; 455 frame->dispose_method = WEBP_MUX_DISPOSE_NONE; 456 frame->blend_method = WEBP_MUX_BLEND; 457 } 458 frame->id = ChunkGetIdFromTag(wpi->header_->tag_); 459 return SynthesizeBitstream(wpi, &frame->bitstream); 460 } 461 462 WebPMuxError WebPMuxGetFrame( 463 const WebPMux* mux, uint32_t nth, WebPMuxFrameInfo* frame) { 464 WebPMuxError err; 465 WebPMuxImage* wpi; 466 467 // Sanity checks. 468 if (mux == NULL || frame == NULL) { 469 return WEBP_MUX_INVALID_ARGUMENT; 470 } 471 472 // Get the nth WebPMuxImage. 473 err = MuxImageGetNth((const WebPMuxImage**)&mux->images_, nth, &wpi); 474 if (err != WEBP_MUX_OK) return err; 475 476 // Get frame info. 477 if (wpi->header_ == NULL) { 478 return MuxGetImageInternal(wpi, frame); 479 } else { 480 return MuxGetFrameFragmentInternal(wpi, frame); 481 } 482 } 483 484 WebPMuxError WebPMuxGetAnimationParams(const WebPMux* mux, 485 WebPMuxAnimParams* params) { 486 WebPData anim; 487 WebPMuxError err; 488 489 if (mux == NULL || params == NULL) return WEBP_MUX_INVALID_ARGUMENT; 490 491 err = MuxGet(mux, IDX_ANIM, 1, &anim); 492 if (err != WEBP_MUX_OK) return err; 493 if (anim.size < kChunks[WEBP_CHUNK_ANIM].size) return WEBP_MUX_BAD_DATA; 494 params->bgcolor = GetLE32(anim.bytes); 495 params->loop_count = GetLE16(anim.bytes + 4); 496 497 return WEBP_MUX_OK; 498 } 499 500 // Get chunk index from chunk id. Returns IDX_NIL if not found. 501 static CHUNK_INDEX ChunkGetIndexFromId(WebPChunkId id) { 502 int i; 503 for (i = 0; kChunks[i].id != WEBP_CHUNK_NIL; ++i) { 504 if (id == kChunks[i].id) return (CHUNK_INDEX)i; 505 } 506 return IDX_NIL; 507 } 508 509 // Count number of chunks matching 'tag' in the 'chunk_list'. 510 // If tag == NIL_TAG, any tag will be matched. 511 static int CountChunks(const WebPChunk* const chunk_list, uint32_t tag) { 512 int count = 0; 513 const WebPChunk* current; 514 for (current = chunk_list; current != NULL; current = current->next_) { 515 if (tag == NIL_TAG || current->tag_ == tag) { 516 count++; // Count chunks whose tags match. 517 } 518 } 519 return count; 520 } 521 522 WebPMuxError WebPMuxNumChunks(const WebPMux* mux, 523 WebPChunkId id, int* num_elements) { 524 if (mux == NULL || num_elements == NULL) { 525 return WEBP_MUX_INVALID_ARGUMENT; 526 } 527 528 if (IsWPI(id)) { 529 *num_elements = MuxImageCount(mux->images_, id); 530 } else { 531 WebPChunk* const* chunk_list = MuxGetChunkListFromId(mux, id); 532 const CHUNK_INDEX idx = ChunkGetIndexFromId(id); 533 *num_elements = CountChunks(*chunk_list, kChunks[idx].tag); 534 } 535 536 return WEBP_MUX_OK; 537 } 538 539 //------------------------------------------------------------------------------ 540