mirror of
https://github.com/webmproject/libwebp.git
synced 2024-12-26 05:38:22 +01:00
Design change in ANMF and FRGM chunks:
- Make ANMF and FRGM chunks hierarchical so that they encompass all chunks of that frame. - Use this in demuxer: stop parsing a frame if all image data for it isn't available yet. Thus, we have a frame-level incremental support; that is, all frames that are fully available can be parsed. - Note: We still keep incremental support for single images - so that they can be decoded with incremental decoding. Change-Id: Id1585b16b06caee1d84009c42a25d2de29fa6135
This commit is contained in:
parent
f903cbab9a
commit
81b8a741ed
@ -194,11 +194,11 @@ static int AddFrame(WebPDemuxer* const dmux, Frame* const frame) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Store image bearing chunks to 'frame'.
|
// Store image bearing chunks to 'frame'.
|
||||||
static ParseStatus StoreFrame(int frame_num, MemBuffer* const mem,
|
static ParseStatus StoreFrame(int frame_num, uint32_t min_size,
|
||||||
Frame* const frame) {
|
MemBuffer* const mem, Frame* const frame) {
|
||||||
int alpha_chunks = 0;
|
int alpha_chunks = 0;
|
||||||
int image_chunks = 0;
|
int image_chunks = 0;
|
||||||
int done = (MemDataSize(mem) < CHUNK_HEADER_SIZE);
|
int done = (MemDataSize(mem) < min_size);
|
||||||
ParseStatus status = PARSE_OK;
|
ParseStatus status = PARSE_OK;
|
||||||
|
|
||||||
if (done) return PARSE_NEED_MORE_DATA;
|
if (done) return PARSE_NEED_MORE_DATA;
|
||||||
@ -275,10 +275,10 @@ static ParseStatus StoreFrame(int frame_num, MemBuffer* const mem,
|
|||||||
// Returns PARSE_OK on success with *frame pointing to the new Frame.
|
// Returns PARSE_OK on success with *frame pointing to the new Frame.
|
||||||
// Returns PARSE_NEED_MORE_DATA with insufficient data, PARSE_ERROR otherwise.
|
// Returns PARSE_NEED_MORE_DATA with insufficient data, PARSE_ERROR otherwise.
|
||||||
static ParseStatus NewFrame(const MemBuffer* const mem,
|
static ParseStatus NewFrame(const MemBuffer* const mem,
|
||||||
uint32_t min_size, uint32_t expected_size,
|
uint32_t min_size, uint32_t actual_size,
|
||||||
uint32_t actual_size, Frame** frame) {
|
Frame** frame) {
|
||||||
if (SizeIsInvalid(mem, min_size)) return PARSE_ERROR;
|
if (SizeIsInvalid(mem, min_size)) return PARSE_ERROR;
|
||||||
if (actual_size < expected_size) return PARSE_ERROR;
|
if (actual_size < min_size) return PARSE_ERROR;
|
||||||
if (MemDataSize(mem) < min_size) return PARSE_NEED_MORE_DATA;
|
if (MemDataSize(mem) < min_size) return PARSE_NEED_MORE_DATA;
|
||||||
|
|
||||||
*frame = (Frame*)calloc(1, sizeof(**frame));
|
*frame = (Frame*)calloc(1, sizeof(**frame));
|
||||||
@ -290,12 +290,14 @@ static ParseStatus NewFrame(const MemBuffer* const mem,
|
|||||||
static ParseStatus ParseFrame(
|
static ParseStatus ParseFrame(
|
||||||
WebPDemuxer* const dmux, uint32_t frame_chunk_size) {
|
WebPDemuxer* const dmux, uint32_t frame_chunk_size) {
|
||||||
const int has_frames = !!(dmux->feature_flags_ & ANIMATION_FLAG);
|
const int has_frames = !!(dmux->feature_flags_ & ANIMATION_FLAG);
|
||||||
const uint32_t min_size = frame_chunk_size + CHUNK_HEADER_SIZE;
|
const uint32_t padding = (ANMF_CHUNK_SIZE & 1);
|
||||||
|
const uint32_t anmf_payload_size =
|
||||||
|
frame_chunk_size - (ANMF_CHUNK_SIZE + padding);
|
||||||
int added_frame = 0;
|
int added_frame = 0;
|
||||||
MemBuffer* const mem = &dmux->mem_;
|
MemBuffer* const mem = &dmux->mem_;
|
||||||
Frame* frame;
|
Frame* frame;
|
||||||
ParseStatus status =
|
ParseStatus status =
|
||||||
NewFrame(mem, min_size, ANMF_CHUNK_SIZE, frame_chunk_size, &frame);
|
NewFrame(mem, ANMF_CHUNK_SIZE, frame_chunk_size, &frame);
|
||||||
if (status != PARSE_OK) return status;
|
if (status != PARSE_OK) return status;
|
||||||
|
|
||||||
frame->x_offset_ = 2 * GetLE24s(mem);
|
frame->x_offset_ = 2 * GetLE24s(mem);
|
||||||
@ -303,14 +305,14 @@ static ParseStatus ParseFrame(
|
|||||||
frame->width_ = 1 + GetLE24s(mem);
|
frame->width_ = 1 + GetLE24s(mem);
|
||||||
frame->height_ = 1 + GetLE24s(mem);
|
frame->height_ = 1 + GetLE24s(mem);
|
||||||
frame->duration_ = 1 + GetLE24s(mem);
|
frame->duration_ = 1 + GetLE24s(mem);
|
||||||
Skip(mem, frame_chunk_size - ANMF_CHUNK_SIZE); // skip any trailing data.
|
Skip(mem, padding);
|
||||||
if (frame->width_ * (uint64_t)frame->height_ >= MAX_IMAGE_AREA) {
|
if (frame->width_ * (uint64_t)frame->height_ >= MAX_IMAGE_AREA) {
|
||||||
return PARSE_ERROR;
|
return PARSE_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store a (potentially partial) frame only if the animation flag is set
|
// Store a frame only if the animation flag is set and all data for this frame
|
||||||
// and there is some data in 'frame'.
|
// is available.
|
||||||
status = StoreFrame(dmux->num_frames_ + 1, mem, frame);
|
status = StoreFrame(dmux->num_frames_ + 1, anmf_payload_size, mem, frame);
|
||||||
if (status != PARSE_ERROR && has_frames && frame->frame_num_ > 0) {
|
if (status != PARSE_ERROR && has_frames && frame->frame_num_ > 0) {
|
||||||
added_frame = AddFrame(dmux, frame);
|
added_frame = AddFrame(dmux, frame);
|
||||||
if (added_frame) {
|
if (added_frame) {
|
||||||
@ -329,22 +331,24 @@ static ParseStatus ParseFrame(
|
|||||||
static ParseStatus ParseTile(WebPDemuxer* const dmux,
|
static ParseStatus ParseTile(WebPDemuxer* const dmux,
|
||||||
uint32_t tile_chunk_size) {
|
uint32_t tile_chunk_size) {
|
||||||
const int has_tiles = !!(dmux->feature_flags_ & TILE_FLAG);
|
const int has_tiles = !!(dmux->feature_flags_ & TILE_FLAG);
|
||||||
const uint32_t min_size = tile_chunk_size + CHUNK_HEADER_SIZE;
|
const uint32_t padding = (FRGM_CHUNK_SIZE & 1);
|
||||||
|
const uint32_t frgm_payload_size =
|
||||||
|
tile_chunk_size - (FRGM_CHUNK_SIZE + padding);
|
||||||
int added_tile = 0;
|
int added_tile = 0;
|
||||||
MemBuffer* const mem = &dmux->mem_;
|
MemBuffer* const mem = &dmux->mem_;
|
||||||
Frame* frame;
|
Frame* frame;
|
||||||
ParseStatus status =
|
ParseStatus status =
|
||||||
NewFrame(mem, min_size, FRGM_CHUNK_SIZE, tile_chunk_size, &frame);
|
NewFrame(mem, FRGM_CHUNK_SIZE, tile_chunk_size, &frame);
|
||||||
if (status != PARSE_OK) return status;
|
if (status != PARSE_OK) return status;
|
||||||
|
|
||||||
frame->is_tile_ = 1;
|
frame->is_tile_ = 1;
|
||||||
frame->x_offset_ = 2 * GetLE24s(mem);
|
frame->x_offset_ = 2 * GetLE24s(mem);
|
||||||
frame->y_offset_ = 2 * GetLE24s(mem);
|
frame->y_offset_ = 2 * GetLE24s(mem);
|
||||||
Skip(mem, tile_chunk_size - FRGM_CHUNK_SIZE); // skip any trailing data.
|
Skip(mem, padding);
|
||||||
|
|
||||||
// Store a (potentially partial) tile only if the tile flag is set
|
// Store a tile only if the tile flag is set and all data for this tile
|
||||||
// and the tile contains some data.
|
// is available.
|
||||||
status = StoreFrame(dmux->num_frames_, mem, frame);
|
status = StoreFrame(dmux->num_frames_, frgm_payload_size, mem, frame);
|
||||||
if (status != PARSE_ERROR && has_tiles && frame->frame_num_ > 0) {
|
if (status != PARSE_ERROR && has_tiles && frame->frame_num_ > 0) {
|
||||||
// Note num_frames_ is incremented only when all tiles have been consumed.
|
// Note num_frames_ is incremented only when all tiles have been consumed.
|
||||||
added_tile = AddFrame(dmux, frame);
|
added_tile = AddFrame(dmux, frame);
|
||||||
@ -411,7 +415,9 @@ static ParseStatus ParseSingleImage(WebPDemuxer* const dmux) {
|
|||||||
frame = (Frame*)calloc(1, sizeof(*frame));
|
frame = (Frame*)calloc(1, sizeof(*frame));
|
||||||
if (frame == NULL) return PARSE_ERROR;
|
if (frame == NULL) return PARSE_ERROR;
|
||||||
|
|
||||||
status = StoreFrame(1, &dmux->mem_, frame);
|
// For the single image case, we allow parsing of a partial frame. But we need
|
||||||
|
// at least CHUNK_HEADER_SIZE for parsing.
|
||||||
|
status = StoreFrame(1, CHUNK_HEADER_SIZE, &dmux->mem_, frame);
|
||||||
if (status != PARSE_ERROR) {
|
if (status != PARSE_ERROR) {
|
||||||
const int has_alpha = !!(dmux->feature_flags_ & ALPHA_FLAG);
|
const int has_alpha = !!(dmux->feature_flags_ & ALPHA_FLAG);
|
||||||
// Clear any alpha when the alpha flag is missing.
|
// Clear any alpha when the alpha flag is missing.
|
||||||
|
@ -399,13 +399,32 @@ size_t MuxImageListDiskSize(const WebPMuxImage* wpi_list) {
|
|||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Special case as ANMF/FRGM chunk encapsulates other image chunks.
|
||||||
|
static uint8_t* ChunkEmitSpecial(const WebPChunk* const header,
|
||||||
|
size_t total_size, uint8_t* dst) {
|
||||||
|
const size_t header_size = header->data_.size;
|
||||||
|
const size_t offset_to_next = total_size - CHUNK_HEADER_SIZE;
|
||||||
|
assert(header->tag_ == kChunks[IDX_ANMF].tag ||
|
||||||
|
header->tag_ == kChunks[IDX_FRGM].tag);
|
||||||
|
PutLE32(dst + 0, header->tag_);
|
||||||
|
PutLE32(dst + TAG_SIZE, (uint32_t)offset_to_next);
|
||||||
|
assert(header_size == (uint32_t)header_size);
|
||||||
|
memcpy(dst + CHUNK_HEADER_SIZE, header->data_.bytes, header_size);
|
||||||
|
if (header_size & 1) {
|
||||||
|
dst[CHUNK_HEADER_SIZE + header_size] = 0; // Add padding.
|
||||||
|
}
|
||||||
|
return dst + ChunkDiskSize(header);
|
||||||
|
}
|
||||||
|
|
||||||
uint8_t* MuxImageEmit(const WebPMuxImage* const wpi, uint8_t* dst) {
|
uint8_t* MuxImageEmit(const WebPMuxImage* const wpi, uint8_t* dst) {
|
||||||
// Ordering of chunks to be emitted is strictly as follows:
|
// Ordering of chunks to be emitted is strictly as follows:
|
||||||
// 1. ANMF/FRGM chunk (if present).
|
// 1. ANMF/FRGM chunk (if present).
|
||||||
// 2. ALPH chunk (if present).
|
// 2. ALPH chunk (if present).
|
||||||
// 3. VP8/VP8L chunk.
|
// 3. VP8/VP8L chunk.
|
||||||
assert(wpi);
|
assert(wpi);
|
||||||
if (wpi->header_ != NULL) dst = ChunkEmit(wpi->header_, dst);
|
if (wpi->header_ != NULL) {
|
||||||
|
dst = ChunkEmitSpecial(wpi->header_, MuxImageDiskSize(wpi), dst);
|
||||||
|
}
|
||||||
if (wpi->alpha_ != NULL) dst = ChunkEmit(wpi->alpha_, dst);
|
if (wpi->alpha_ != NULL) dst = ChunkEmit(wpi->alpha_, dst);
|
||||||
if (wpi->img_ != NULL) dst = ChunkEmit(wpi->img_, dst);
|
if (wpi->img_ != NULL) dst = ChunkEmit(wpi->img_, dst);
|
||||||
return dst;
|
return dst;
|
||||||
|
@ -51,10 +51,9 @@ static WebPMuxError MuxGet(const WebPMux* const mux, CHUNK_INDEX idx,
|
|||||||
|
|
||||||
// Fill the chunk with the given data (includes chunk header bytes), after some
|
// Fill the chunk with the given data (includes chunk header bytes), after some
|
||||||
// verifications.
|
// verifications.
|
||||||
static WebPMuxError ChunkVerifyAndAssignData(WebPChunk* chunk,
|
static WebPMuxError ChunkVerifyAndAssign(WebPChunk* chunk,
|
||||||
const uint8_t* data,
|
const uint8_t* data, size_t data_size,
|
||||||
size_t data_size, size_t riff_size,
|
size_t riff_size, int copy_data) {
|
||||||
int copy_data) {
|
|
||||||
uint32_t chunk_size;
|
uint32_t chunk_size;
|
||||||
WebPData chunk_data;
|
WebPData chunk_data;
|
||||||
|
|
||||||
@ -74,6 +73,66 @@ static WebPMuxError ChunkVerifyAndAssignData(WebPChunk* chunk,
|
|||||||
return ChunkAssignData(chunk, &chunk_data, copy_data, GetLE32(data + 0));
|
return ChunkAssignData(chunk, &chunk_data, copy_data, GetLE32(data + 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 };
|
||||||
|
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;
|
||||||
|
wpi->is_partial_ = 0; // wpi is completely filled.
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// Create a mux object from WebP-RIFF data.
|
// Create a mux object from WebP-RIFF data.
|
||||||
|
|
||||||
@ -136,42 +195,46 @@ WebPMux* WebPMuxCreateInternal(const WebPData* bitstream, int copy_data,
|
|||||||
|
|
||||||
// Loop over chunks.
|
// Loop over chunks.
|
||||||
while (data != end) {
|
while (data != end) {
|
||||||
|
size_t data_size;
|
||||||
WebPChunkId id;
|
WebPChunkId id;
|
||||||
WebPMuxError err;
|
WebPChunk** chunk_list;
|
||||||
|
if (ChunkVerifyAndAssign(&chunk, data, size, riff_size,
|
||||||
err = ChunkVerifyAndAssignData(&chunk, data, size, riff_size, copy_data);
|
copy_data) != WEBP_MUX_OK) {
|
||||||
if (err != WEBP_MUX_OK) goto Err;
|
goto Err;
|
||||||
|
}
|
||||||
|
data_size = ChunkDiskSize(&chunk);
|
||||||
id = ChunkGetIdFromTag(chunk.tag_);
|
id = ChunkGetIdFromTag(chunk.tag_);
|
||||||
|
switch (id) {
|
||||||
if (IsWPI(id)) { // An image chunk (frame/tile/alpha/vp8).
|
case WEBP_CHUNK_ALPHA:
|
||||||
WebPChunk** wpi_chunk_ptr =
|
if (wpi->alpha_ != NULL) goto Err; // Consecutive ALPH chunks.
|
||||||
MuxImageGetListFromId(wpi, id); // Image chunk to set.
|
if (ChunkSetNth(&chunk, &wpi->alpha_, 1) != WEBP_MUX_OK) goto Err;
|
||||||
assert(wpi_chunk_ptr != NULL);
|
wpi->is_partial_ = 1; // Waiting for a VP8 chunk.
|
||||||
if (*wpi_chunk_ptr != NULL) goto Err; // Consecutive alpha chunks or
|
break;
|
||||||
// consecutive frame/tile chunks.
|
case WEBP_CHUNK_IMAGE:
|
||||||
if (ChunkSetNth(&chunk, wpi_chunk_ptr, 1) != WEBP_MUX_OK) goto Err;
|
if (ChunkSetNth(&chunk, &wpi->img_, 1) != WEBP_MUX_OK) goto Err;
|
||||||
if (id == WEBP_CHUNK_IMAGE) {
|
|
||||||
wpi->is_partial_ = 0; // wpi is completely filled.
|
wpi->is_partial_ = 0; // wpi is completely filled.
|
||||||
|
PushImage:
|
||||||
// Add this to mux->images_ list.
|
// Add this to mux->images_ list.
|
||||||
if (MuxImagePush(wpi, &mux->images_) != WEBP_MUX_OK) goto Err;
|
if (MuxImagePush(wpi, &mux->images_) != WEBP_MUX_OK) goto Err;
|
||||||
MuxImageInit(wpi); // Reset for reading next image.
|
MuxImageInit(wpi); // Reset for reading next image.
|
||||||
} else {
|
break;
|
||||||
wpi->is_partial_ = 1; // wpi is only partially filled.
|
case WEBP_CHUNK_ANMF:
|
||||||
}
|
case WEBP_CHUNK_FRGM:
|
||||||
} else { // A non-image chunk.
|
if (wpi->is_partial_) goto Err; // Previous wpi is still incomplete.
|
||||||
WebPChunk** chunk_list;
|
if (!MuxImageParse(&chunk, copy_data, wpi)) goto Err;
|
||||||
if (wpi->is_partial_) goto Err; // Encountered a non-image chunk before
|
ChunkRelease(&chunk);
|
||||||
// getting all chunks of an image.
|
goto PushImage;
|
||||||
chunk_list = MuxGetChunkListFromId(mux, id); // List to add this chunk.
|
break;
|
||||||
if (chunk_list == NULL) chunk_list = &mux->unknown_;
|
default: // A non-image chunk.
|
||||||
if (ChunkSetNth(&chunk, chunk_list, 0) != WEBP_MUX_OK) goto Err;
|
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.
|
||||||
const size_t data_size = ChunkDiskSize(&chunk);
|
if (chunk_list == NULL) chunk_list = &mux->unknown_;
|
||||||
data += data_size;
|
if (ChunkSetNth(&chunk, chunk_list, 0) != WEBP_MUX_OK) goto Err;
|
||||||
size -= data_size;
|
break;
|
||||||
}
|
}
|
||||||
|
data += data_size;
|
||||||
|
size -= data_size;
|
||||||
ChunkInit(&chunk);
|
ChunkInit(&chunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user