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); }
static WebPMuxError MuxGetImageInternal(const WebPMuxImage* const wpi, WebPMuxFrameInfo* const info) { // Set some defaults for unrelated fields. info->x_offset = 0; info->y_offset = 0; info->duration = 1; info->dispose_method = WEBP_MUX_DISPOSE_NONE; info->blend_method = WEBP_MUX_BLEND; // Extract data for related fields. info->id = ChunkGetIdFromTag(wpi->img_->tag_); return SynthesizeBitstream(wpi, &info->bitstream); }
static WebPMuxError MuxDeleteAllNamedData(WebPMux* const mux, uint32_t tag) { const WebPChunkId id = ChunkGetIdFromTag(tag); WebPChunk** chunk_list; assert(mux != NULL); if (IsWPI(id)) return WEBP_MUX_INVALID_ARGUMENT; chunk_list = MuxGetChunkListFromId(mux, id); if (chunk_list == NULL) return WEBP_MUX_INVALID_ARGUMENT; return DeleteChunks(chunk_list, tag); }
int MuxImageCount(const WebPMuxImage* wpi_list, WebPChunkId id) { int count = 0; const WebPMuxImage* current; for (current = wpi_list; current != NULL; current = current->next_) { if (id == WEBP_CHUNK_NIL) { ++count; // Special case: count all images. } else { const WebPChunk* const wpi_chunk = *GetChunkListFromId(current, id); if (wpi_chunk != NULL) { const WebPChunkId wpi_chunk_id = ChunkGetIdFromTag(wpi_chunk->tag_); if (wpi_chunk_id == id) ++count; // Count images with a matching 'id'. } } } return count; }
WebPMuxError WebPMuxPushFrame(WebPMux* mux, const WebPMuxFrameInfo* frame, int copy_data) { WebPMuxImage wpi; WebPMuxError err; int is_frame; const WebPData* const bitstream = &frame->bitstream; // Sanity checks. if (mux == NULL || frame == NULL) return WEBP_MUX_INVALID_ARGUMENT; is_frame = (frame->id == WEBP_CHUNK_ANMF); if (!(is_frame || (frame->id == WEBP_CHUNK_FRGM))) { return WEBP_MUX_INVALID_ARGUMENT; } #ifndef WEBP_EXPERIMENTAL_FEATURES if (frame->id == WEBP_CHUNK_FRGM) { // disabled for now. return WEBP_MUX_INVALID_ARGUMENT; } #endif if (bitstream->bytes == NULL || bitstream->size > MAX_CHUNK_PAYLOAD) { return WEBP_MUX_INVALID_ARGUMENT; } if (mux->images_ != NULL) { const WebPMuxImage* const image = mux->images_; const uint32_t image_id = (image->header_ != NULL) ? ChunkGetIdFromTag(image->header_->tag_) : WEBP_CHUNK_IMAGE; if (image_id != frame->id) { return WEBP_MUX_INVALID_ARGUMENT; // Conflicting frame types. } } MuxImageInit(&wpi); err = SetAlphaAndImageChunks(bitstream, copy_data, &wpi); if (err != WEBP_MUX_OK) goto Err; assert(wpi.img_ != NULL); // As SetAlphaAndImageChunks() was successful. { WebPData frame_frgm; const uint32_t tag = kChunks[is_frame ? IDX_ANMF : IDX_FRGM].tag; WebPMuxFrameInfo tmp = *frame; tmp.x_offset &= ~1; // Snap offsets to even. tmp.y_offset &= ~1; if (!is_frame) { // Reset unused values. tmp.duration = 1; tmp.dispose_method = WEBP_MUX_DISPOSE_NONE; tmp.blend_method = WEBP_MUX_BLEND; } if (tmp.x_offset < 0 || tmp.x_offset >= MAX_POSITION_OFFSET || tmp.y_offset < 0 || tmp.y_offset >= MAX_POSITION_OFFSET || (tmp.duration < 0 || tmp.duration >= MAX_DURATION) || tmp.dispose_method != (tmp.dispose_method & 1)) { err = WEBP_MUX_INVALID_ARGUMENT; goto Err; } err = CreateFrameFragmentData(wpi.width_, wpi.height_, &tmp, is_frame, &frame_frgm); if (err != WEBP_MUX_OK) goto Err; // Add frame/fragment chunk (with copy_data = 1). err = AddDataToChunkList(&frame_frgm, 1, tag, &wpi.header_); WebPDataClear(&frame_frgm); // frame_frgm owned by wpi.header_ now. if (err != WEBP_MUX_OK) goto Err; } // Add this WebPMuxImage to mux. err = MuxImagePush(&wpi, &mux->images_); if (err != WEBP_MUX_OK) goto Err; // All is well. return WEBP_MUX_OK; Err: // Something bad happened. MuxImageRelease(&wpi); return err; }
static int MuxImageParse(const WebPChunk* const chunk, int copy_data, WebPMuxImage* const wpi) { const uint8_t* bytes = chunk->data_.bytes; size_t size = chunk->data_.size; const uint8_t* const last = bytes + size; WebPChunk subchunk; size_t subchunk_size; ChunkInit(&subchunk); assert(chunk->tag_ == kChunks[IDX_ANMF].tag || chunk->tag_ == kChunks[IDX_FRGM].tag); assert(!wpi->is_partial_); // ANMF/FRGM. { const size_t hdr_size = (chunk->tag_ == kChunks[IDX_ANMF].tag) ? ANMF_CHUNK_SIZE : FRGM_CHUNK_SIZE; const WebPData temp = { bytes, hdr_size }; // Each of ANMF and FRGM chunk contain a header at the beginning. So, its // size should at least be 'hdr_size'. if (size < hdr_size) goto Fail; ChunkAssignData(&subchunk, &temp, copy_data, chunk->tag_); } ChunkSetNth(&subchunk, &wpi->header_, 1); wpi->is_partial_ = 1; // Waiting for ALPH and/or VP8/VP8L chunks. // Rest of the chunks. subchunk_size = ChunkDiskSize(&subchunk) - CHUNK_HEADER_SIZE; bytes += subchunk_size; size -= subchunk_size; while (bytes != last) { ChunkInit(&subchunk); if (ChunkVerifyAndAssign(&subchunk, bytes, size, size, copy_data) != WEBP_MUX_OK) { goto Fail; } switch (ChunkGetIdFromTag(subchunk.tag_)) { case WEBP_CHUNK_ALPHA: if (wpi->alpha_ != NULL) goto Fail; // Consecutive ALPH chunks. if (ChunkSetNth(&subchunk, &wpi->alpha_, 1) != WEBP_MUX_OK) goto Fail; wpi->is_partial_ = 1; // Waiting for a VP8 chunk. break; case WEBP_CHUNK_IMAGE: if (ChunkSetNth(&subchunk, &wpi->img_, 1) != WEBP_MUX_OK) goto Fail; if (!MuxImageFinalize(wpi)) goto Fail; wpi->is_partial_ = 0; // wpi is completely filled. break; case WEBP_CHUNK_UNKNOWN: if (wpi->is_partial_) goto Fail; // Encountered an unknown chunk // before some image chunks. if (ChunkSetNth(&subchunk, &wpi->unknown_, 0) != WEBP_MUX_OK) goto Fail; break; default: goto Fail; break; } subchunk_size = ChunkDiskSize(&subchunk); bytes += subchunk_size; size -= subchunk_size; } if (wpi->is_partial_) goto Fail; return 1; Fail: ChunkRelease(&subchunk); return 0; }
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; }
WebPMuxError WebPMuxPushFrame(WebPMux* mux, const WebPMuxFrameInfo* info, int copy_data) { WebPMuxImage wpi; WebPMuxError err; // Sanity checks. if (mux == NULL || info == NULL) return WEBP_MUX_INVALID_ARGUMENT; if (info->id != WEBP_CHUNK_ANMF) return WEBP_MUX_INVALID_ARGUMENT; if (info->bitstream.bytes == NULL || info->bitstream.size > MAX_CHUNK_PAYLOAD) { return WEBP_MUX_INVALID_ARGUMENT; } if (mux->images_ != NULL) { const WebPMuxImage* const image = mux->images_; const uint32_t image_id = (image->header_ != NULL) ? ChunkGetIdFromTag(image->header_->tag_) : WEBP_CHUNK_IMAGE; if (image_id != info->id) { return WEBP_MUX_INVALID_ARGUMENT; // Conflicting frame types. } } MuxImageInit(&wpi); err = SetAlphaAndImageChunks(&info->bitstream, copy_data, &wpi); if (err != WEBP_MUX_OK) goto Err; assert(wpi.img_ != NULL); // As SetAlphaAndImageChunks() was successful. { WebPData frame; const uint32_t tag = kChunks[IDX_ANMF].tag; WebPMuxFrameInfo tmp = *info; tmp.x_offset &= ~1; // Snap offsets to even. tmp.y_offset &= ~1; if (tmp.x_offset < 0 || tmp.x_offset >= MAX_POSITION_OFFSET || tmp.y_offset < 0 || tmp.y_offset >= MAX_POSITION_OFFSET || (tmp.duration < 0 || tmp.duration >= MAX_DURATION) || tmp.dispose_method != (tmp.dispose_method & 1)) { err = WEBP_MUX_INVALID_ARGUMENT; goto Err; } err = CreateFrameData(wpi.width_, wpi.height_, &tmp, &frame); if (err != WEBP_MUX_OK) goto Err; // Add frame chunk (with copy_data = 1). err = AddDataToChunkList(&frame, 1, tag, &wpi.header_); WebPDataClear(&frame); // frame owned by wpi.header_ now. if (err != WEBP_MUX_OK) goto Err; } // Add this WebPMuxImage to mux. err = MuxImagePush(&wpi, &mux->images_); if (err != WEBP_MUX_OK) goto Err; // All is well. return WEBP_MUX_OK; Err: // Something bad happened. MuxImageRelease(&wpi); return err; }