static WebPMuxError MuxGetFrameFragmentInternal(const WebPMuxImage* const wpi, WebPMuxFrameInfo* const frame) { const int is_frame = (wpi->header_->tag_ == kChunks[IDX_ANMF].tag); const CHUNK_INDEX idx = is_frame ? IDX_ANMF : IDX_FRGM; const WebPData* frame_frgm_data; if (!is_frame) return WEBP_MUX_INVALID_ARGUMENT; assert(wpi->header_ != NULL); // Already checked by WebPMuxGetFrame(). // Get frame/fragment chunk. frame_frgm_data = &wpi->header_->data_; if (frame_frgm_data->size < kChunks[idx].size) return WEBP_MUX_BAD_DATA; // Extract info. frame->x_offset = 2 * GetLE24(frame_frgm_data->bytes + 0); frame->y_offset = 2 * GetLE24(frame_frgm_data->bytes + 3); if (is_frame) { const uint8_t bits = frame_frgm_data->bytes[15]; frame->duration = GetLE24(frame_frgm_data->bytes + 12); frame->dispose_method = (bits & 1) ? WEBP_MUX_DISPOSE_BACKGROUND : WEBP_MUX_DISPOSE_NONE; frame->blend_method = (bits & 2) ? WEBP_MUX_NO_BLEND : WEBP_MUX_BLEND; } else { // Defaults for unused values. frame->duration = 1; frame->dispose_method = WEBP_MUX_DISPOSE_NONE; frame->blend_method = WEBP_MUX_BLEND; } frame->id = ChunkGetIdFromTag(wpi->header_->tag_); return SynthesizeBitstream(wpi, &frame->bitstream); }
// Get the canvas width, height and flags after validating that VP8X/VP8/VP8L // chunk and canvas size are valid. static WebPMuxError MuxGetCanvasInfo(const WebPMux* const mux, int* width, int* height, uint32_t* flags) { int w, h; uint32_t f = 0; WebPData data; assert(mux != NULL); // Check if VP8X chunk is present. if (MuxGet(mux, IDX_VP8X, 1, &data) == WEBP_MUX_OK) { if (data.size < VP8X_CHUNK_SIZE) return WEBP_MUX_BAD_DATA; f = GetLE32(data.bytes + 0); w = GetLE24(data.bytes + 4) + 1; h = GetLE24(data.bytes + 7) + 1; } else { const WebPMuxImage* const wpi = mux->images_; // Grab user-forced canvas size as default. w = mux->canvas_width_; h = mux->canvas_height_; if (w == 0 && h == 0 && ValidateForSingleImage(mux) == WEBP_MUX_OK) { // single image and not forced canvas size => use dimension of first frame assert(wpi != NULL); w = wpi->width_; h = wpi->height_; } if (wpi != NULL) { if (wpi->has_alpha_) f |= ALPHA_FLAG; } } if (w * (uint64_t)h >= MAX_IMAGE_AREA) return WEBP_MUX_BAD_DATA; if (width != NULL) *width = w; if (height != NULL) *height = h; if (flags != NULL) *flags = f; return WEBP_MUX_OK; }
// Get the canvas width, height and flags after validating that VP8X/VP8/VP8L // chunk and canvas size are valid. static WebPMuxError MuxGetCanvasInfo(const WebPMux* const mux, int* width, int* height, uint32_t* flags) { int w, h; uint32_t f = 0; WebPData data; assert(mux != NULL); // Check if VP8X chunk is present. if (MuxGet(mux, IDX_VP8X, 1, &data) == WEBP_MUX_OK) { if (data.size < VP8X_CHUNK_SIZE) return WEBP_MUX_BAD_DATA; f = GetLE32(data.bytes + 0); w = GetLE24(data.bytes + 4) + 1; h = GetLE24(data.bytes + 7) + 1; } else { // Single image case. const WebPMuxImage* const wpi = mux->images_; WebPMuxError err = ValidateForSingleImage(mux); if (err != WEBP_MUX_OK) return err; assert(wpi != NULL); w = wpi->width_; h = wpi->height_; if (wpi->has_alpha_) f |= ALPHA_FLAG; } if (w * (uint64_t)h >= MAX_IMAGE_AREA) return WEBP_MUX_BAD_DATA; if (width != NULL) *width = w; if (height != NULL) *height = h; if (flags != NULL) *flags = f; return WEBP_MUX_OK; }
static WebPMuxError GetFrameInfo( const WebPChunk* const frame_chunk, int* const x_offset, int* const y_offset, int* const duration) { const WebPData* const data = &frame_chunk->data_; const size_t expected_data_size = ANMF_CHUNK_SIZE; assert(frame_chunk->tag_ == kChunks[IDX_ANMF].tag); assert(frame_chunk != NULL); if (data->size != expected_data_size) return WEBP_MUX_INVALID_ARGUMENT; *x_offset = 2 * GetLE24(data->bytes + 0); *y_offset = 2 * GetLE24(data->bytes + 3); *duration = GetLE24(data->bytes + 12); return WEBP_MUX_OK; }
static WebPMuxError GetFrameFragmentInfo( const WebPChunk* const frame_frgm_chunk, int* const x_offset, int* const y_offset, int* const duration) { const uint32_t tag = frame_frgm_chunk->tag_; const int is_frame = (tag == kChunks[IDX_ANMF].tag); const WebPData* const data = &frame_frgm_chunk->data_; const size_t expected_data_size = is_frame ? ANMF_CHUNK_SIZE : FRGM_CHUNK_SIZE; assert(frame_frgm_chunk != NULL); assert(tag == kChunks[IDX_ANMF].tag || tag == kChunks[IDX_FRGM].tag); if (data->size != expected_data_size) return WEBP_MUX_INVALID_ARGUMENT; *x_offset = 2 * GetLE24(data->bytes + 0); *y_offset = 2 * GetLE24(data->bytes + 3); if (is_frame) *duration = GetLE24(data->bytes + 12); return WEBP_MUX_OK; }
static WebPMuxError GetFrameTileInfo(const WebPChunk* const frame_tile_chunk, int* const x_offset, int* const y_offset, int* const duration) { const uint32_t tag = frame_tile_chunk->tag_; const int is_frame = (tag == kChunks[IDX_FRAME].tag); const WebPData* const data = &frame_tile_chunk->data_; const size_t expected_data_size = is_frame ? FRAME_CHUNK_SIZE : TILE_CHUNK_SIZE; assert(frame_tile_chunk != NULL); assert(tag == kChunks[IDX_FRAME].tag || tag == kChunks[IDX_TILE].tag); if (data->size_ != expected_data_size) return WEBP_MUX_INVALID_ARGUMENT; *x_offset = 2 * GetLE24(data->bytes_ + 0); *y_offset = 2 * GetLE24(data->bytes_ + 3); if (is_frame) *duration = 1 + GetLE24(data->bytes_ + 12); return WEBP_MUX_OK; }
// Validates the VP8X header and skips over it. // Returns VP8_STATUS_BITSTREAM_ERROR for invalid VP8X header, // VP8_STATUS_NOT_ENOUGH_DATA in case of insufficient data, and // VP8_STATUS_OK otherwise. // If a VP8X chunk is found, found_vp8x is set to true and *width_ptr, // *height_ptr and *flags_ptr are set to the corresponding values extracted // from the VP8X chunk. static VP8StatusCode ParseVP8X(const uint8_t** const data, size_t* const data_size, int* const found_vp8x, int* const width_ptr, int* const height_ptr, uint32_t* const flags_ptr) { const uint32_t vp8x_size = CHUNK_HEADER_SIZE + VP8X_CHUNK_SIZE; assert(data != NULL); assert(data_size != NULL); assert(found_vp8x != NULL); *found_vp8x = 0; if (*data_size < CHUNK_HEADER_SIZE) { return VP8_STATUS_NOT_ENOUGH_DATA; // Insufficient data. } if (!memcmp(*data, "VP8X", TAG_SIZE)) { int width, height; uint32_t flags; const uint32_t chunk_size = GetLE32(*data + TAG_SIZE); if (chunk_size != VP8X_CHUNK_SIZE) { return VP8_STATUS_BITSTREAM_ERROR; // Wrong chunk size. } // Verify if enough data is available to validate the VP8X chunk. if (*data_size < vp8x_size) { return VP8_STATUS_NOT_ENOUGH_DATA; // Insufficient data. } flags = GetLE32(*data + 8); width = 1 + GetLE24(*data + 12); height = 1 + GetLE24(*data + 15); if (width * (uint64_t)height >= MAX_IMAGE_AREA) { return VP8_STATUS_BITSTREAM_ERROR; // image is too large } if (flags_ptr != NULL) *flags_ptr = flags; if (width_ptr != NULL) *width_ptr = width; if (height_ptr != NULL) *height_ptr = height; // Skip over VP8X header bytes. *data += vp8x_size; *data_size -= vp8x_size; *found_vp8x = 1; } return VP8_STATUS_OK; }
static WEBP_INLINE int ReadLE24s(MemBuffer* const mem) { const uint8_t* const data = mem->buf_ + mem->start_; const int val = GetLE24(data); Skip(mem, 3); return val; }
WebPMux* WebPMuxCreateInternal(const WebPData* bitstream, int copy_data, int version) { size_t riff_size; uint32_t tag; const uint8_t* end; WebPMux* mux = NULL; WebPMuxImage* wpi = NULL; const uint8_t* data; size_t size; WebPChunk chunk; ChunkInit(&chunk); // Sanity checks. if (WEBP_ABI_IS_INCOMPATIBLE(version, WEBP_MUX_ABI_VERSION)) { return NULL; // version mismatch } if (bitstream == NULL) return NULL; data = bitstream->bytes; size = bitstream->size; if (data == NULL) return NULL; if (size < RIFF_HEADER_SIZE) return NULL; if (GetLE32(data + 0) != MKFOURCC('R', 'I', 'F', 'F') || GetLE32(data + CHUNK_HEADER_SIZE) != MKFOURCC('W', 'E', 'B', 'P')) { return NULL; } mux = WebPMuxNew(); if (mux == NULL) return NULL; if (size < RIFF_HEADER_SIZE + TAG_SIZE) goto Err; tag = GetLE32(data + RIFF_HEADER_SIZE); if (tag != kChunks[IDX_VP8].tag && tag != kChunks[IDX_VP8L].tag && tag != kChunks[IDX_VP8X].tag) { goto Err; // First chunk should be VP8, VP8L or VP8X. } riff_size = SizeWithPadding(GetLE32(data + TAG_SIZE)); if (riff_size > MAX_CHUNK_PAYLOAD || riff_size > size) { goto Err; } else { if (riff_size < size) { // Redundant data after last chunk. size = riff_size; // To make sure we don't read any data beyond mux_size. } } end = data + size; data += RIFF_HEADER_SIZE; size -= RIFF_HEADER_SIZE; wpi = (WebPMuxImage*)WebPSafeMalloc(1ULL, sizeof(*wpi)); if (wpi == NULL) goto Err; MuxImageInit(wpi); // Loop over chunks. while (data != end) { size_t data_size; WebPChunkId id; WebPChunk** chunk_list; if (ChunkVerifyAndAssign(&chunk, data, size, riff_size, copy_data) != WEBP_MUX_OK) { goto Err; } data_size = ChunkDiskSize(&chunk); id = ChunkGetIdFromTag(chunk.tag_); switch (id) { case WEBP_CHUNK_ALPHA: if (wpi->alpha_ != NULL) goto Err; // Consecutive ALPH chunks. if (ChunkSetNth(&chunk, &wpi->alpha_, 1) != WEBP_MUX_OK) goto Err; wpi->is_partial_ = 1; // Waiting for a VP8 chunk. break; case WEBP_CHUNK_IMAGE: if (ChunkSetNth(&chunk, &wpi->img_, 1) != WEBP_MUX_OK) goto Err; if (!MuxImageFinalize(wpi)) goto Err; wpi->is_partial_ = 0; // wpi is completely filled. PushImage: // Add this to mux->images_ list. if (MuxImagePush(wpi, &mux->images_) != WEBP_MUX_OK) goto Err; MuxImageInit(wpi); // Reset for reading next image. break; case WEBP_CHUNK_ANMF: if (wpi->is_partial_) goto Err; // Previous wpi is still incomplete. if (!MuxImageParse(&chunk, copy_data, wpi)) goto Err; ChunkRelease(&chunk); goto PushImage; break; default: // A non-image chunk. if (wpi->is_partial_) goto Err; // Encountered a non-image chunk before // getting all chunks of an image. chunk_list = MuxGetChunkListFromId(mux, id); // List to add this chunk. if (ChunkSetNth(&chunk, chunk_list, 0) != WEBP_MUX_OK) goto Err; if (id == WEBP_CHUNK_VP8X) { // grab global specs mux->canvas_width_ = GetLE24(data + 12) + 1; mux->canvas_height_ = GetLE24(data + 15) + 1; } break; } data += data_size; size -= data_size; ChunkInit(&chunk); } // Validate mux if complete. if (MuxValidate(mux) != WEBP_MUX_OK) goto Err; MuxImageDelete(wpi); return mux; // All OK; Err: // Something bad happened. ChunkRelease(&chunk); MuxImageDelete(wpi); WebPMuxDelete(mux); return NULL; }